Skip to content

feat: openapi typescript client#2885

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

feat: openapi typescript client#2885
Marshevskyy wants to merge 166 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/client-generator and the experimental redocly generate-client command: generate a fully-typed TypeScript client from an OpenAPI description — auth, retries, middleware, typed streaming, auto-pagination, and mocks out of the box. Output is built via the TypeScript compiler AST (not string templates), so it is correct by construction; typescript is the only peer dependency (the CLI provides it).

The architecture: one client, two distributions

Generated clients are typed operation descriptors (OPERATIONS … satisfies Record<string, OperationDescriptor>) plus an Ops type, wired into a createClient instance backed by a hand-written, directly-tested runtime (100% coverage). The --runtime option picks how that runtime ships:

  • inline (default) — one self-contained file, zero runtime dependencies (web-standard fetch/AbortController/URLSearchParams), embedding only the runtime modules the API actually uses (no SSE code for a spec without streams).
  • package — the generated file imports the engine from @redocly/client-generator, so runtime fixes reach every consumer via npm update with no regeneration; the emitted satisfies clause doubles as a build-time version-skew guard.

Application code is identical in both modes. Existing tools force a choice between types-only (hand-write every fetch/auth/retry) or a client with a permanent runtime dependency — this makes that a per-project knob instead.

Surface

  • Input: OpenAPI 3.0 / 3.1 / 3.2 + Swagger 2.0.
  • Output: --output-mode single (default) or split (entry + <name>.schemas.ts). Every module exports both call styles — the client instance (grouped args + configure/use/auth) and flat free functions (--args-style shapes them) — plus createClient for per-tenant/multi-instance use.
  • Types: inline schema types, discriminated-union is<Member>() guards, <Op>Result/Params/Body/Variables aliases, optional Date typing, typed multipart (binary → Blob).
  • Runtime: auth from securitySchemes (async token providers, per-instance credentials, generated setBearer/setApiKey* sugar); composable middleware where ctx.operation.{id,path,tags} are literal unions from your spec — a misspelled operationId fails compilation; opt-in abort-aware retries (backoff + jitter + Retry-After); per-call parseAs; OpenAPI query-serialization styles; --error-mode result for { data, error, response }; typed Server-Sent Events (auto-reconnect, Last-Event-ID, OAS 3.2 itemSchema).
  • Auto-pagination: declared, never guessed — a client.pagination convention block in redocly.yaml (or the x-pagination extension) gives paginated operations typed .pages() / .items() async iterators (cursor/offset/page styles), with item types resolved statically from the response schema. The convention applies only where it structurally fits (verified against params and the response schema); an explicit rule that doesn't fit fails generation with a per-operation error.
  • Publisher defaults: --setup bakes a defineClientSetup({ config, middleware }) module into the client, layered between spec defaults and app configure().
  • Add-on generators (--generators): sdk (default), zod, tanstack-query (React/Vue/Svelte/Solid), swr, transformers, mock (MSW, baked or faker) — each emits its own file and adds no dependency to the client — plus an experimental defineGenerator plugin API for custom emitters.
  • Config: CLI flags or a client block in redocly.yaml (shared defaults + per-API overrides under apis.<name>.client); programmatic generateClient(...) API.
  • Hardened: safe-identifier coercion, comment escaping, globalThis-safe embedded types, bounded SSE reader, restricted server-URL schemes, output-path and setup-path validation.

Size, honestly

~40.5k added lines across 390 files — down from ~91k after a restructure pass: generated output is no longer committed (example and consumer clients are gitignored, regenerated and type-checked in CI), nine byte-identical example specs became one shared file, and redundant tests and golden snapshots were cut with no coverage of distinct behavior lost.

Area Lines
Implementation (packages/client-generator/src, excl. tests) ~8,900
Unit tests ~13,100
E2E tests + live-server consumers ~10,400
16 runnable examples (hand-written consumers only; one canonical generated client stays committed as the drift baseline) ~5,200
User docs + ADRs (0001–0019) ~1,300
CLI/core wiring (command, config schema) ~500

Reference

Testing

  • npm test green (compile + typecheck + unit + e2e); coverage thresholds pass — remaining unit-coverage gaps are paths the e2e suites exercise.
  • e2e generates clients, type-checks them under strict tsc, and runs them against a mock server.
  • Runnable examples under tests/e2e/generate-client/examples/, regenerated and type-checked by the CI examples job.

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

High Risk
Large new experimental surface (codegen output, config schema, and runtime behavior) that consumer apps will depend on; mitigated by extensive unit/e2e coverage and CI example regeneration but API and output may still change in minors.

Overview
Introduces @redocly/client-generator and wires redocly generate-client into the CLI so OpenAPI 3.x (and normalized Swagger 2.0) specs become typed TypeScript clients with optional add-ons (zod, TanStack Query, SWR, MSW mocks, transformers) via AST-based codegen and a shared fetch runtime (inline zero-dep or package import).

Configuration & docs: redocly.yaml gains top-level and per-API client / clientOutput (merged in core config resolvers); command reference, usage guide, and ADRs document flags, pagination, and the experimental plugin API.

CI & release: E2E runs in two shards; a new examples job regenerates example clients and type-checks consumers; snapshot publishing installs before version bumps and publishes @redocly/client-generator; formatter/linter ignore generated e2e output paths.

Reviewed by Cursor Bugbot for commit 4adcdc8. 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: 00d2e49

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

This PR includes changesets to release 4 packages
Name Type
@redocly/client-generator Minor
@redocly/openapi-core Minor
@redocly/cli 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 Outdated
Comment thread packages/cli/src/commands/generate-client.ts Outdated
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/client-generator/src/index.ts Outdated
Comment thread packages/client-generator/src/emitters/runtime.ts Outdated
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread packages/client-generator/src/emitters/runtime.ts Outdated
Comment thread packages/client-generator/src/generators/index.ts
Comment thread packages/client-generator/src/index.ts Outdated
JLekawa and others added 3 commits June 18, 2026 12:17
…ing docs

Switch the runnable examples and the cafe e2e fixture/snapshot to the spec's
`servers[0].url` (api.cafe.redocly.com), regenerated with the current generator.
Demonstrate middleware in the fetch-functions example via `onResponse` so it
isn't blocked by the demo API's CORS preflight (it doesn't allow a custom
`X-Request-Id` request header). Add a "Testing the generated client" section to
the README (Node / browser-CORS / MSW mocks).
Comment thread packages/client-generator/src/generators/resolve.ts
Resolve conflicts in package.json, package-lock.json, packages/cli/package.json,
tsconfig.json, tsconfig.build.json, and vitest.config.ts.

- Adopt main's esbuild-bundled CLI build; add @redocly/openapi-typescript to
  packages/cli devDependencies so it bundles alongside openapi-core/respect-core.
- Bump openapi-typescript's @redocly/openapi-core dependency 2.31.4 -> 2.34.0 to
  match the workspace, so npm symlinks the workspace package instead of a nested
  copy (the mismatch produced divergent Config type identities).
- Extend the CLI bundle banner to shim __filename/__dirname (via var, to coexist
  with deps that self-declare them) so the bundled typescript compiler used by
  generate-client runs in ESM scope.
- Keep the per-glob 100% coverage threshold for openapi-typescript alongside
  main's repo-wide branches:73.
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.01x ± 0.01 ▓ 1.00x ± 0.01 ▓ 1.00x ± 0.01
cli-next ▓ 1.00x (Fastest) ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)

Comment thread packages/client-generator/src/emitters/runtime.ts Outdated
Comment thread packages/client-generator/src/generators/resolve.ts
…MD009

Three prose lines had a stray single trailing space (lines 644, 651, 939),
which markdownlint MD009 rejects (expects 0 or 2). Verified clean with
markdownlint-cli2 v0.22.0 against the repo's .markdownlint.yaml.
Comment thread packages/cli/src/commands/generate-client.ts Outdated
The vale job used reviewdog with filter_mode: file, which fetches the PR
diff to scope findings to changed files. GitHub's diff API caps at 20000
lines, so large PRs (this one adds ~54k lines) return 406 and reviewdog
fails on the post step — not on any actual vale finding.

filter_mode: nofilter skips the diff fetch and lints the full files
directly. Verified the committed docs are clean (0 errors/warnings/
suggestions across 320 files with vale 3.15.1), so nofilter adds no noise
and the error-level gate still holds.
The consumer harnesses (base/cafe/sse) have tracked index*.ts that import a
generated `./api.js`. The repo-wide `tsc --noEmit` includes tests/**/*.ts, so
it typechecks those imports — but `api.ts` was gitignored and only created when
the e2e suite ran, so a fresh checkout (CI) failed with TS2307.

The harnesses already expected api.ts present for typecheck (see the note in
sse.runtime.test.ts); gitignoring it was the gap. generate-client output is
byte-deterministic for these fixtures (fixed ports, fixed specs — verified by
regenerating and diffing), so commit the files instead of touching tsconfig.
The e2e suite still regenerates them each run, producing identical content
(verified: no drift after running base.test.ts).
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 74.98% (🎯 69%) 10007 / 13346
🔵 Statements 75.23% (🎯 69%) 10722 / 14251
🔵 Functions 79.38% (🎯 73%) 2068 / 2605
🔵 Branches 68.64% (🎯 61%) 7250 / 10562
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/generate-client.ts 0% 0% 0% 0% 38-177
packages/client-generator/src/config-file.ts 100% 88.88% 100% 100%
packages/client-generator/src/errors.ts 100% 100% 100% 100%
packages/client-generator/src/index.ts 94.11% 90% 100% 94.11% 136-137
packages/client-generator/src/loader.ts 100% 100% 100% 100%
packages/client-generator/src/plugin.ts 100% 100% 100% 100%
packages/client-generator/src/runtime-contract.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/auth.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/client-assembly.ts 99.1% 98.11% 100% 100% 193
packages/client-generator/src/emitters/descriptor.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/emit-options.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/faker.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/identifier.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/inline-runtime.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/jsdoc.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/mock.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operation-aliases.ts 93.18% 82.97% 72.72% 92.85% 207, 218, 229
packages/client-generator/src/emitters/operation-signature.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operation-types.ts 98.11% 94.44% 100% 97.91% 144
packages/client-generator/src/emitters/operations.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/pagination.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/runtime-sources.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/sample.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/setup-bake.ts 100% 96.15% 100% 100%
packages/client-generator/src/emitters/sse.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/support.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/swr.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/tanstack-query.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/transformers.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/ts.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/type-guards.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/types.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/wrapper-support.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/zod.ts 97.16% 94.28% 100% 98.31% 212, 214, 221-224
packages/client-generator/src/generators/anchor.ts 100% 100% 100% 100%
packages/client-generator/src/generators/index.ts 100% 100% 100% 100%
packages/client-generator/src/generators/mock.ts 100% 100% 100% 100%
packages/client-generator/src/generators/resolve.ts 100% 100% 100% 100%
packages/client-generator/src/generators/sdk.ts 100% 100% 100% 100%
packages/client-generator/src/generators/swr.ts 100% 100% 100% 100%
packages/client-generator/src/generators/tanstack-query.ts 100% 100% 100% 100%
packages/client-generator/src/generators/transformers.ts 100% 100% 100% 100%
packages/client-generator/src/generators/zod.ts 100% 100% 100% 100%
packages/client-generator/src/intermediate-representation/build.ts 100% 100% 100% 100%
packages/client-generator/src/intermediate-representation/model.ts 100% 100% 100% 100%
packages/client-generator/src/intermediate-representation/normalize-swagger2.ts 100% 100% 100% 100%
packages/client-generator/src/intermediate-representation/sanitize-identifiers.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/auth.ts 100% 96% 100% 100%
packages/client-generator/src/runtime/create-client.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/errors.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/index.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/multipart.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/paginate.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/parse.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/retry.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/send.ts 98.48% 100% 83.33% 100% 143
packages/client-generator/src/runtime/setup.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/sse.ts 100% 98.63% 100% 100%
packages/client-generator/src/runtime/url.ts 100% 100% 100% 100%
packages/core/src/config/config-resolvers.ts 71.59% 54.85% 88.88% 72.25% 67-88, 104, 132-135, 191, 230, 238, 294, 305, 314, 327, 337, 340-344, 349-358, 382-384, 394, 397, 400, 403, 406, 409, 422-424, 428, 434, 437, 440-443, 446-449, 452-455, 469-471, 478, 481, 484, 487, 490, 493, 518-520, 525-531
packages/core/src/types/redocly-yaml.ts 93.13% 84.9% 100% 92.92% 456, 488, 494, 538-545, 547, 706-711, 714-719
Generated in workflow #10747 for commit 38f807b by the Vitest Coverage Report Action

Comment thread packages/cli/src/commands/generate-client.ts Outdated
…apshots

The branch added `react`/`react-dom` `^18.2.0` to the root devDependencies
(main has neither — it resolves react 19.2.7 via packages/cli's
`^17 || ^18.2.0 || ^19.2.7` range). That root `^18.2.0` cap forced npm to
hoist react 18.3.1 for the whole workspace, so build-docs rendered React 18's
`useId` format (`tab:R9pq:0`) instead of the React 19 format (`tab_R_9pq_0`)
the committed redoc-static snapshots were generated with — failing
build-docs.test.ts on CI.

Bump root react/react-dom to `^19.2.0` so npm resolves 19.2.7, matching main.
Verified: build-docs.test.ts (7/7) and the react-19 consumers
(tanstack-query.runtime, swr) all pass.
filter_mode: nofilter alone wasn't enough — the github-pr-annotations reporter
still fetches the PR diff to position comments, and GitHub caps that diff at
20000 lines, so this 54k-line PR gets a 406 and reviewdog exits 1 (on the diff
fetch, not on any vale finding).

Switch reporter to `local`: reviewdog prints findings to the job log and exits
non-zero only on vale errors, with no GitHub API call and therefore no diff to
fetch. The gate still holds (committed docs verified: 0 vale errors across 320
files). Trade-off: findings show in the Actions log rather than as inline PR
annotations — which a 20k-line-capped diff can't render on a PR this size anyway.

@tatomyr tatomyr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked a couple of root files. Haven't checked any actual implementation yet.

Comment thread .github/workflows/docs-tests.yaml Outdated
Comment thread docs/@v2/commands/generate-client.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread vitest.config.ts Outdated
Comment thread tsconfig.json Outdated
Comment on lines +26 to +27
"exclude": ["node_modules"],
"include": [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated a bit, but we have to exclude examples since they include react code, which is not covered in this tsconfig...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's discuss offline whether we need React examples.

Comment thread packages/cli/scripts/build.mjs
Comment thread packages/openapi-typescript/README.md Outdated
@Marshevskyy Marshevskyy added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jul 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1784806880 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1784806880

⚠️ Note: This is a development build and may contain unstable features.

@vadyvas

vadyvas commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

name collision

openapi: 3.1.0
info: 
  title: qwe,
  version: 1.0.0
servers: 
  - url: https://api.example.com
paths:
  /items/{itemId}:
    get:
      operationId: getItem
      parameters:
        - 
          name: itemId
          in: path 
          required: true
          schema: 
            type: string
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: 
                $ref: '#/components/schemas/Item'
        '404':
          description: not found
          content:
            application/json:
              schema: 
                $ref: '#/components/schemas/ApiError'
components:
  schemas:
    Item:
      type: object
      required: [id, name]
      properties:
        id: 
          type: string
        name: 
          type: string
    ApiError:
      type: object
      required: [code, message]
      properties:
        code: 
          type: string
        message: 
          type: string
image

return {
// The spec's operationId, NOT the (possibly renamed) map key: `id` drives middleware
// targeting (`ctx.operation.id`) and must match inline mode's `operationMetaExpr`.
id: op.name,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Descriptor id drops original operationId

Medium Severity

After identifier sanitization, op.name is the coerced safe binding (for example list_orders), while the original OpenAPI operationId is kept on op.specName. The descriptor still sets id: op.name, so ctx.operation.id and the OperationId union no longer match the spec. Pagination already keys config off op.specName ?? op.name, so middleware targeting and config matching diverge for hyphenated or otherwise unsafe operationIds.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a94248. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

* collide with the slot keys — a spec-acknowledged runtime-contract limitation.
* A paginated operation's arrow is wrapped in `Object.assign(…, { pages, items })`
* so the flat sugar preserves the method-attached iterators.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path params collide with slot keys

Medium Severity

Path parameter names are uniqued only against other path params, not against the reserved slots params, body, headers, cookies, and init. A path param with one of those names produces duplicate flat-style parameters and overwrites the grouped args object key, so generation can emit invalid TypeScript and drop the path value at runtime.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a94248. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

const used = new Set<string>([...WIRING_NAMES, ...authSetterNames(model.securitySchemes)]);
const idents = new Map<string, string>();
for (const op of allOperations(model.services)) idents.set(op.name, uniqueIdent(op.name, used));
return idents;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Schema names collide with reserved exports

Medium Severity

WIRING_NAMES / auth-setter reservation renames colliding operations (configureconfigure_2), but schema names are only uniqued among themselves. A schema named Ops, Result, client, or OPERATIONS (or a string enum that emits export const &lt;Name&gt;) lands on the same type/value identifiers as the generated wiring and fails to compile. This matches the name-collision report in the PR discussion.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ab6b6c9. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

@vadyvas vadyvas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ran it against the Rebilly spec via an AI POC, found a few issues

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://github.com/Rebilly/api-definitions/blob/dd1d9cb7560aa70b30513c6b21ffc5a8cc08700c/openapi/components/schemas/Customer.yaml#L63-L66

createdTime:
  $ref: CreatedTime.yaml   # <-
updatedTime:
  $ref: UpdatedTime.yaml
client:
  dateType: Date
  generators: [sdk, transformers]
import { client, configure } from './out/client.ts';
import { transformCustomer } from './out/client.transformers.ts';

configure({
  fetch: async () =>
    new Response(
      JSON.stringify({
        id: 'cust_1',
        createdTime: '2024-01-01T00:00:00Z',    // $ref -> CreatedTime
        lastPaymentTime: '2024-06-01T00:00:00Z', // inline date-time
      }),
      { headers: { 'Content-Type': 'application/json' } }
    ),
});

const customer = transformCustomer(await client.GetCustomer({ id: 'cust_1' }));

console.log('createdTime:', customer.createdTime, '| Date?', customer.createdTime instanceof Date); 
// createdTime: 2024-01-01T00:00:00Z | Date? false
customer.createdTime.getFullYear();  
// TypeError: customer.createdTime.getFullYear is not a function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://github.com/Rebilly/api-definitions/blob/dd1d9cb7560aa70b30513c6b21ffc5a8cc08700c/openapi/components/schemas/Customer.yaml#L87-L98

lastPaymentTime:
  type: ['string', 'null']
  format: date-time
  readOnly: true
client:
  dateType: Date
  generators: [sdk, transformers]
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://github.com/Rebilly/api-definitions/blob/dd1d9cb7560aa70b30513c6b21ffc5a8cc08700c/openapi/components/schemas/ContactObject.yaml#L81-L87

dob:
  type: ['string', 'null']
  format: date
  example: 1980-04-01
client:
  dateType: Date
  generators: [sdk, mock]
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://github.com/Rebilly/api-definitions/blob/dd1d9cb7560aa70b30513c6b21ffc5a8cc08700c/openapi/components/schemas/AlternativePaymentInstrument.yaml#L12-L21

method:
  allOf:
    - $ref: ./PaymentMethod.yaml  
    - not:
        enum: [payment-card, ach, paypal, cash, check]
client:
  generators: [sdk, mock]
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can't configure pagination

https://github.com/Rebilly/api-definitions/blob/dd1d9cb7560aa70b30513c6b21ffc5a8cc08700c/openapi/paths/customers.yaml#L1-L40

tried

    client:
      pagination:
        style: offset
        limitParam: limit
        offsetParam: offset
        items: /
import { client, configure } from './out/client.ts';
import { transformCustomer } from './out/client.transformers.ts';

for await (const c of client.GetCustomerCollection.items({ params: { limit: 2 } })) {
  console.log(c.id);
}
Property 'items' does not exist on type '(args?: { params?: GetCustomerCollectionParams | undefined; } | undefined, init?: RequestOptions | undefined) => Promise<GetCustomerCollectionResult>'.ts(2339)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great job 👏🏼
I've pushed fixes

}
if (typeof items !== 'string' || !items.startsWith('/')) {
return '"items" must be a JSON pointer starting with "/"';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root array pagination blocked

High Severity

ruleShapeProblem requires items to start with /, so the RFC 6901 root pointer "" is rejected even though both resolveSchemaPointer and runtime resolvePointer treat "" as the whole document. items: / passes validation but resolves to a property named "", so APIs whose success body is a bare array cannot enable .pages()/.items().

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d4db8a. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

return schema.members.reduce<Record<string, unknown>>((acc, m) => {
const part = walk(m, byName, visiting, dateType);
return isPlainObject(part) ? Object.assign(acc, part) : acc;
}, {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mocks break on allOf not

Medium Severity

OpenAPI not schemas fall through to unknown, and intersection sampling keeps only object-shaped members. For the common allOf: [$ref: Enum, not: { enum: [...] }] pattern the mock factory therefore emits {} for a string/enum field, so generated mocks fail type-checking against the sdk types.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d4db8a. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

…k sampling for nullable dates and narrowing intersections, root-array pagination items
…ake mock factories honor overrides for non-object schemas
warnRename('schema', schema.name, safe);
schema.name = safe;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Schema names collide with wiring exports

High Severity

Operation names are renamed when they hit wiring exports (client, configure, use, OPERATIONS, auth setters, …) via packageIdents, but schema names are sanitized without that reserved set. A string-enum schema with one of those names still emits export const …, which then clashes with the generated client sugar and fails typecheck.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8375e45. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

(metadata?.format === 'date-time' || metadata?.format === 'date')
) {
return factory.createTypeReferenceNode('Date');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Date and Blob types get shadowed

Medium Severity

Emitted type nodes use bare Date, Blob, Record, and Omit identifiers. A same-named schema in the module shadows those builtins, so --date-type Date, binary fields, records, and Omit&lt;…&gt; request bodies resolve to the schema type instead of the platform type. Runtime Error was already hardened with globalThis.Error, but these positions were not.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8375e45. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

}

if (kept.length === 0) return argText;
return `(() => {\n${kept.join('\n')}\nreturn ${argText};\n})()`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Exported setup helpers break bake

Medium Severity

bakeSetup copies every non-import statement into an IIFE via getText, including export const / export function helpers. export is invalid inside a function body, so a normal setup module that exports helpers produces a syntax-invalid client.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 875f298. 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 40 total unresolved issues (including 39 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 9913cc5. Configure here.

if (p) ordered.push(p);
}
const used = new Set<string>();
const pathParams = ordered.map((param) => ({ param, ident: uniqueIdent(param.name, used) }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path params collide with flat slots

Medium Severity

operationSignature builds path-param identifiers without reserving the flat-mode slot names (params, body, headers, cookies, init). A path parameter named init (always present as the trailing arg) or params/body/etc. when that slot exists emits a duplicate parameter list, so the generated client fails to compile.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9913cc5. Configure here.

@Marshevskyy Marshevskyy added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1784895845 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1784895845

⚠️ Note: This is a development build and may contain unstable features.

@Marshevskyy Marshevskyy added snapshot Create experimental release PR and removed snapshot Create experimental release PR labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1784899983 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1784899983

⚠️ Note: This is a development build and may contain unstable features.

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

Labels

snapshot Create experimental release PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants