Skip to content

feat: openapi typescript client#2885

Open
Marshevskyy wants to merge 6 commits into
mainfrom
feat/ts-client-gen
Open

feat: openapi typescript client#2885
Marshevskyy wants to merge 6 commits into
mainfrom
feat/ts-client-gen

Conversation

@Marshevskyy

@Marshevskyy Marshevskyy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Adds @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 built via the TypeScript compiler AST, so output is correct by construction. typescript is 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.

  • Input: OpenAPI 3.0/3.1/3.2 + Swagger 2.0.
  • Output: single/split/tags/tags-split layouts; functions or service-class facade (per-instance config + credentials); flat/grouped args.
  • Types: inline types, discriminated-union is<Member>() guards, <Op>* aliases, Date typing, typed multipart (binary → Blob).
  • Runtime: auth from securitySchemes (+ async providers, per-instance), composable middleware, opt-in abort-aware retries, parseAs, query-serialization styles, result error mode, typed Server-Sent Events (auto-reconnect, OAS 3.2 itemSchema).
  • Generators (--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).
  • Config: CLI flags, redocly.yaml x-openapi-typescript, or a defineConfig file.
  • Hardened: safe-identifier coercion, comment escaping, bounded SSE reader.

Reference

Testing

  • npm test green (compile + typecheck + unit + e2e); 100% per-file coverage on the package.
  • e2e generates clients, type-checks them under strict tsc, and runs them against a mock server.
  • Runnable, drift-checked examples under packages/openapi-typescript/examples/.

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

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-typescript workspace package and wires it into @redocly/cli via an experimental generate-client command (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, optional throw vs result errors). Optional --generators add sibling modules (Zod, TanStack Query, SWR, transformers, MSW mocks) and a custom generator plugin API. Configuration layers redocly.yaml x-openapi-typescript, defineConfig, and CLI flags.

CLI: new handleGenerateClient merges config, resolves apis: aliases, validates output/base URL, and calls generateClient. typescript moves to a CLI runtime dependency for generation.

Docs & DX: large generate-client command reference, README section, package README/architecture/ADRs, CONTRIBUTING monorepo Vitest conventions, and typo fixes for request-mime-type doc 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.

…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.
@Marshevskyy Marshevskyy requested review from a team as code owners June 16, 2026 07:24
@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1adbf1e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@redocly/openapi-typescript Minor
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

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

Comment thread packages/cli/src/commands/generate-client.ts
Comment thread packages/cli/src/commands/generate-client.ts
Comment thread .changeset/openapi-typescript.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread README.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread packages/openapi-typescript/src/index.ts
) {
await __sleep(__retryDelay(retry, attempt, response.headers.get('retry-after')), signal);
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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.`
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2e5ae8d. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ae5cebe. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants