Repository navigation
feat: accept JS Date for date-time request values - #299
Merged
Merged
Conversation
Request types generated from `format: date-time` string schemas are widened from `string` to `string | Date`, and any `Date` in a request is serialized to ISO 8601 before it is sent — in the request body (at any nesting depth), in query parameters, path parameters and headers. Closes #209. This is a widening only, so existing code passing ISO strings keeps compiling and working unchanged: * Response types are deliberately left as `string`. Changing them would break consumers that split, compare or re-parse those strings. * Component schemas used in both request and response position keep their original shape in `Components.Schemas`; a widened copy is emitted in the new, purely additive `Components.RequestSchemas` namespace and referenced from request position only. * `format: date` (`YYYY-MM-DD`) is *not* widened. Its wire format differs from a full ISO 8601 timestamp, and the runtime serializer has no access to the schema, so accepting a `Date` there would produce a value the API rejects. `HeaderValue` and `PathParameters` in `@mittwald/api-client-commons` gained `Date` as an accepted input type, which is required for the widened parameter types to satisfy the `RequestType` constraints. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mfal
marked this pull request as ready for review
September 7, 2026 08:12
mfal
commented
Sep 22, 2026
Replaces the inline `instanceof Date ? toISOString()` branch in OpenAPIPath with the shared serializeDates helper, so date serialization lives in exactly one place. Side effect: an invalid Date in a path parameter now throws RangeError like everywhere else, instead of producing "Invalid Date" in the URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Henning3110
approved these changes
Sep 22, 2026
mfal
added a commit
that referenced
this pull request
Sep 23, 2026
Master's date-time PR (#299) touched the same two files: - JSONSchema.ts: import-only conflict, both sides kept. Master also split the compile path in two, and component parameters now run through the new `compileAsRequestInput`, which did not apply the type alias workaround — a deprecated component parameter alias fell back to an empty `/** */` block. The deprecation handling of `compile` is mirrored there, with a regression test on the existing `LegacySort` fixture. - componentRefsToCustomTypes.ts: both sides added a declaration behind the same doc comment; both are kept, and a ref now carries this branch's sibling annotations while resolving through master's `resolveTSName`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mfal
added a commit
that referenced
this pull request
Sep 23, 2026
Master's date-time PR (#299) rewrote the same three spots in Request.ts: - The request body is now taken from the date-serialized request object, so a `Date` in a multipart body is appended as a bare ISO string part rather than a JSON-quoted one. Covered by a new test. - `makeAxiosHeaders` keeps this branch's optional-headers/content-type handling and applies master's `serializeDates` to the header values. `serializeDates` rebuilds plain objects, so a JSON body is no longer the caller's exact object instance. Three assertions in requestBody.test.ts that checked referential identity now compare structurally; the non-plain payloads (FormData, Blob, URLSearchParams) are still passed through by reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mfal
added a commit
that referenced
this pull request
Sep 23, 2026
Master's date-time PR (#299) added `RequestSchemas.ts`, modelled on `Schemas.ts` and therefore still calling the `populateNullableTypes` workaround this branch deletes — the merge left the generator uncompilable. The removal is applied there as well; the defensive `cloneDeep` stays. The committed client was regenerated from the merged state, so the generated types carry both the v16 `nullable` handling and master's widened date-time request parameters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #209
Lets callers pass a JS
Datewhere the spec declaresformat: date-time, instead of hand-formatting ISO 8601 strings:Not breaking — request side only. Verified below.
Scope decision
Request positions are widened to
(string | Date). Response types are deliberately left asstring: turning a response field into aDatewould break.split("T"),new Date(x), string comparison andJSON.stringifyat every existing call site. The issue's complaint is about sending dates, which this fully resolves.The obstacle, and how it is handled
formatis retained — the generator hands raw OpenAPI schemas tojson-schema-to-typescript, which just ignoresformatfor strings. jstt'stsTypeextension lets us override the emitted type per sub-schema, so no template work was needed.The real problem was component schemas shared between request and response position. Measured against the committed spec: 134 request-reachable schemas, 404 response-reachable, 72 overlapping — but only 4 of those transitively contain a
date-timeand are reachable from a request. So the generator emits widened copies of exactly those four into a new, purely additiveComponents.RequestSchemasnamespace and redirects request-position$refs there.Components.Schemasstays byte-identical, so responses are untouched.Parentheses in
(string | Date)matter: jstt appends[]to an item'stsTypeunwrapped, so a bare union would emitstring | Date[].Runtime
packages/commons/src/core/serializeDates.ts— a schema-agnostic recursive serializer applied inRequest.buildAxiosConfig. Schema-agnostic rather than per-field codegen because the runtime has no descriptor-level schema and the conversion is unambiguous. It recurses only into arrays and plain objects (prototypeObject.prototypeornull), soFormData/Blob/Uint8Array/class instances pass through by reference, never cloned. Input is never mutated.Three genuine bugs fixed along the way — bodies happened to work via
Date.prototype.toJSON, but:String(date)→ garbage in the URLDate.toString()→ non-ISOJSON.stringify(date)→ a quoted ISO stringHeaderValueandPathParametersgained| Date. This was required, not cosmetic:RequestWithQueryParameters<TQuery> = { queryParameters: TQuery & HttpHeaders }, so without it aDatequery param fails the index-signature side of the intersection. Both are input-shaped types, so it is a widening.Deliberately excluded
format: dateis not widened. Its wire format isYYYY-MM-DDand the runtime serializer cannot know that, so aDatewould be sent as a full ISO timestamp and rejected. The spec has a real instance (GET /v2/pageinsights?date=); a@ts-expect-errorcheck proves it still rejects aDate.Datenow throwsRangeError: Invalid time valueinstead of silently serializing tonullviatoJSON. Fail-fast on a programming error.Verification that this is not breaking
Regenerated in-PR (
build:client-prod), 210 insertions / 54 deletions across both clients. Every| Dateoccurrence, by enclosing namespace:Components.RequestSchemas(new, additive)Get.ParametersPatch.ParametersPost.ParametersPut.ParametersResponsesComponents.SchemasTests:
dateTimeInput.test.ts(widening transform —datevsdate-time, enums, existingtsType, nesting/arrays/compositions, non-mutation, refs untouched),dateTimeInput.integration.test.ts(compiles a spec end-to-end, asserts request widening, response non-widening, shared-schema preservation, ref redirection),serializeDates.test.ts,Request.test.ts(Dates in body/path/headers/query, arrays, non-plain payload passthrough, non-mutation).Also type-checked against the real generated client: the issue's exact example compiles; an ISO string still compiles;
response.data.terminationTargetDate.split("T")still compiles; a response-typedComponents.Schemas.Xis still assignable to the widenedComponents.RequestSchemas.X.yarn nx run-many -t test --skip-nx-cache(all 4 projects),yarn lint(4),yarn format, generatortest:unit(46/9 suites), commonstest(35/4 suites),api-client:test:compile— all pass.Residual risk
Inherent to any request-side widening: code that declares a variable of a generated request body type and then reads a date field from it now sees
string | Date. Constructing and passing request bodies — the overwhelmingly common case — is unaffected.🤖 Generated with Claude Code