feat: openapi typescript client#2885
Conversation
…enAPI client Add `@redocly/openapi-typescript` and the `redocly generate-client` command: generate a typed TypeScript client from an OpenAPI description. The emitted client has zero runtime dependencies (web-standard fetch/AbortController/ URLSearchParams) and is produced via the TypeScript compiler AST, so output is correct by construction; `typescript` is the only peer dependency. Input: OpenAPI 3.0/3.1/3.2.0 + Swagger 2.0 (normalized to 3.x); file, URL, or a redocly.yaml `apis:` alias; operationId synthesized when absent. Output: single / split / tags / tags-split layouts; `functions` or `service-class` facade (per-instance config + credentials); flat or grouped argument styles. Types: inline types; enums as unions or runtime const objects; discriminated- union `is<Member>()` guards; `<Op>Result/Error/Params/Body/Headers/Variables` aliases with collision suppression; JSDoc from validation keywords; optional `Date` typing; typed multipart bodies (binary → Blob) auto-serialized to FormData. Runtime: setBaseUrl + typed ClientConfig; composable middleware (onRequest/ onResponse/onError); opt-in abort-aware retries (backoff, jitter, Retry-After, custom retryOn); per-call parseAs; OpenAPI query-serialization styles; `--error-mode result` discriminated returns; minification-safe OPERATIONS map; typed Server-Sent Events (async iterators, auto-reconnect, OAS 3.2 itemSchema). Auth: Basic / Bearer / apiKey (header, query, cookie) from securitySchemes, async token providers, and per-instance credentials via ClientConfig.auth. Generators (--generators): sdk (default), zod, tanstack-query (react/vue/svelte/ solid), swr, transformers, mock (MSW handlers + baked or faker data, seedable), plus an experimental custom-generator plugin API (@redocly/openapi-typescript/ plugin) with dual loading (inline + import specifier) and a validated compatibility contract. Each generator declares requires/facades/errorModes/ dateTypes, validated up front. Configuration via CLI flags, a redocly.yaml `x-openapi-typescript` block, or a defineConfig file; plus `--watch`. Hardened: document-derived names coerced to safe unique identifiers, comment text escaped, bounded SSE reader. Architecture, ADRs (0001-0012), and runnable examples included.
🦋 Changeset detectedLatest commit: 1adbf1e The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| ) { | ||
| await __sleep(__retryDelay(retry, attempt, response.headers.get('retry-after')), signal); | ||
| continue; | ||
| } |
There was a problem hiding this comment.
HTTP retries leave bodies unread
Medium Severity
In the generated __send loop, a retryable non-OK response triggers another fetch without consuming the prior response body. Unread bodies can keep connections busy and break retry behavior under load or with strict HTTP clients.
Reviewed by Cursor Bugbot for commit 6aeb52b. Configure here.
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
| if (buffer.length > 1048576) { | ||
| throw new Error('SSE frame exceeded 1048576 characters without a delimiter'); | ||
| } | ||
| } |
There was a problem hiding this comment.
SSE stream drops final frame
Medium Severity
When the SSE body reader finishes (done), any bytes still held in the parse buffer are never passed through __parseSseFrame. An event whose closing delimiter never arrives before the server closes the stream is discarded instead of yielded.
Reviewed by Cursor Bugbot for commit 2e5ae8d. Configure here.
| `The "${name}" generator requires --date-type ${descriptor.dateTypes.join(' or ')} (got "${dateType}") so the runtime values match the generated types.` | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Empty generators succeeds silently
Medium Severity
validateGenerators accepts an empty generators list and collectGeneratedFiles writes no files, yet handleGenerateClient still reports a successful generation. Misconfiguration such as generators: [] in config produces no client without an error.
Reviewed by Cursor Bugbot for commit 2e5ae8d. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 5 total unresolved issues (including 4 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ae5cebe. Configure here.
| }, | ||
| generators: selected, | ||
| registry, | ||
| }); |
There was a problem hiding this comment.
Empty generators list succeeds silently
Medium Severity
When generators is an empty array, generateClient runs zero generators and writes no client files, yet the CLI still reports success. The default sdk generator is only applied when generators is undefined, not when it is explicitly [] (e.g. from --generators , or an empty config list).
Reviewed by Cursor Bugbot for commit ae5cebe. Configure here.


What/Why/How?
Adds
@redocly/openapi-typescriptand theredocly generate-clientcommand: generate a typed TypeScript client from an OpenAPI description. The emitted client has zero runtime dependencies (web-standardfetch/AbortController/URLSearchParams) and is built via the TypeScript compiler AST, so output is correct by construction.typescriptis the only peer dep (the CLI provides it).Existing tools force a trade-off: types-only (you hand-write every fetch/auth/retry) or a full client that drags a runtime dependency into your bundle forever. This generates the full client into code you own — no runtime dep.
This PR includes approximately 54,000 lines of code. However, around 20,000 lines are examples, and another ~20,000 lines are tests and documentation, so the actual implementation is not that large.
single/split/tags/tags-splitlayouts;functionsorservice-classfacade (per-instance config + credentials);flat/groupedargs.is<Member>()guards,<Op>*aliases,Datetyping, typed multipart (binary →Blob).securitySchemes(+ async providers, per-instance), composable middleware, opt-in abort-aware retries,parseAs, query-serialization styles,resulterror mode, typed Server-Sent Events (auto-reconnect, OAS 3.2itemSchema).--generators):sdk(default),zod,tanstack-query(React/Vue/Svelte/Solid),swr,transformers,mock(MSW + baked/faker), plus a custom-generator plugin API (@redocly/openapi-typescript/plugin).redocly.yamlx-openapi-typescript, or adefineConfigfile.Reference
Testing
npm testgreen (compile + typecheck + unit + e2e); 100% per-file coverage on the package.tsc, and runs them against a mock server.packages/openapi-typescript/examples/.Screenshots (optional)
Check yourself
Security
Note
Medium Risk
Large new surface area (codegen + emitted runtime behavior for auth/network) is experimental but well-tested; primary risk is consumers depending on generated output before the API is declared stable.
Overview
Adds a new
@redocly/openapi-typescriptworkspace package and wires it into@redocly/clivia an experimentalgenerate-clientcommand (minor changeset for both packages).The generator turns OpenAPI 3.x or Swagger 2.0 into owned TypeScript using the TS compiler AST (not string templates), with a zero runtime dependency client (
fetch, auth, retries, middleware, SSE, multipart, optionalthrowvsresulterrors). Optional--generatorsadd sibling modules (Zod, TanStack Query, SWR, transformers, MSW mocks) and a custom generator plugin API. Configuration layersredocly.yamlx-openapi-typescript,defineConfig, and CLI flags.CLI: new
handleGenerateClientmerges config, resolvesapis:aliases, validates output/base URL, and callsgenerateClient.typescriptmoves to a CLI runtime dependency for generation.Docs & DX: large
generate-clientcommand reference, README section, package README/architecture/ADRs,CONTRIBUTINGmonorepo Vitest conventions, and typo fixes forrequest-mime-typedoc slugs.Tests & tooling: e2e under
tests/e2e/generate-client/(snapshots/gitignored consumers), root dev deps (MSW, faker, TanStack, etc.), formatter/linter ignores for generated examples and snapshots.Reviewed by Cursor Bugbot for commit 1adbf1e. Bugbot is set up for automated code reviews on this repo. Configure here.