@redocly/client-generator 0.0.0-snapshot.1784554531
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +506 -0
- package/lib/config-file.d.ts +8 -0
- package/lib/config-file.d.ts.map +1 -0
- package/lib/config-file.js +32 -0
- package/lib/config-file.js.map +1 -0
- package/lib/config.d.ts +63 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +2 -0
- package/lib/config.js.map +1 -0
- package/lib/emitters/auth.d.ts +14 -0
- package/lib/emitters/auth.d.ts.map +1 -0
- package/lib/emitters/auth.js +30 -0
- package/lib/emitters/auth.js.map +1 -0
- package/lib/emitters/client-assembly.d.ts +15 -0
- package/lib/emitters/client-assembly.d.ts.map +1 -0
- package/lib/emitters/client-assembly.js +299 -0
- package/lib/emitters/client-assembly.js.map +1 -0
- package/lib/emitters/descriptor.d.ts +22 -0
- package/lib/emitters/descriptor.d.ts.map +1 -0
- package/lib/emitters/descriptor.js +207 -0
- package/lib/emitters/descriptor.js.map +1 -0
- package/lib/emitters/emit-options.d.ts +71 -0
- package/lib/emitters/emit-options.d.ts.map +1 -0
- package/lib/emitters/emit-options.js +28 -0
- package/lib/emitters/emit-options.js.map +1 -0
- package/lib/emitters/faker.d.ts +11 -0
- package/lib/emitters/faker.d.ts.map +1 -0
- package/lib/emitters/faker.js +204 -0
- package/lib/emitters/faker.js.map +1 -0
- package/lib/emitters/identifier.d.ts +28 -0
- package/lib/emitters/identifier.d.ts.map +1 -0
- package/lib/emitters/identifier.js +94 -0
- package/lib/emitters/identifier.js.map +1 -0
- package/lib/emitters/inline-runtime.d.ts +11 -0
- package/lib/emitters/inline-runtime.d.ts.map +1 -0
- package/lib/emitters/inline-runtime.js +97 -0
- package/lib/emitters/inline-runtime.js.map +1 -0
- package/lib/emitters/jsdoc.d.ts +9 -0
- package/lib/emitters/jsdoc.d.ts.map +1 -0
- package/lib/emitters/jsdoc.js +83 -0
- package/lib/emitters/jsdoc.js.map +1 -0
- package/lib/emitters/mock.d.ts +22 -0
- package/lib/emitters/mock.d.ts.map +1 -0
- package/lib/emitters/mock.js +243 -0
- package/lib/emitters/mock.js.map +1 -0
- package/lib/emitters/operation-aliases.d.ts +27 -0
- package/lib/emitters/operation-aliases.d.ts.map +1 -0
- package/lib/emitters/operation-aliases.js +138 -0
- package/lib/emitters/operation-aliases.js.map +1 -0
- package/lib/emitters/operation-signature.d.ts +23 -0
- package/lib/emitters/operation-signature.d.ts.map +1 -0
- package/lib/emitters/operation-signature.js +34 -0
- package/lib/emitters/operation-signature.js.map +1 -0
- package/lib/emitters/operation-types.d.ts +32 -0
- package/lib/emitters/operation-types.d.ts.map +1 -0
- package/lib/emitters/operation-types.js +117 -0
- package/lib/emitters/operation-types.js.map +1 -0
- package/lib/emitters/operations.d.ts +39 -0
- package/lib/emitters/operations.d.ts.map +1 -0
- package/lib/emitters/operations.js +33 -0
- package/lib/emitters/operations.js.map +1 -0
- package/lib/emitters/pagination.d.ts +69 -0
- package/lib/emitters/pagination.d.ts.map +1 -0
- package/lib/emitters/pagination.js +223 -0
- package/lib/emitters/pagination.js.map +1 -0
- package/lib/emitters/runtime-sources.d.ts +16 -0
- package/lib/emitters/runtime-sources.d.ts.map +1 -0
- package/lib/emitters/runtime-sources.js +16 -0
- package/lib/emitters/runtime-sources.js.map +1 -0
- package/lib/emitters/sample.d.ts +27 -0
- package/lib/emitters/sample.d.ts.map +1 -0
- package/lib/emitters/sample.js +161 -0
- package/lib/emitters/sample.js.map +1 -0
- package/lib/emitters/setup-bake.d.ts +15 -0
- package/lib/emitters/setup-bake.d.ts.map +1 -0
- package/lib/emitters/setup-bake.js +65 -0
- package/lib/emitters/setup-bake.js.map +1 -0
- package/lib/emitters/sse.d.ts +10 -0
- package/lib/emitters/sse.d.ts.map +1 -0
- package/lib/emitters/sse.js +46 -0
- package/lib/emitters/sse.js.map +1 -0
- package/lib/emitters/support.d.ts +13 -0
- package/lib/emitters/support.d.ts.map +1 -0
- package/lib/emitters/support.js +21 -0
- package/lib/emitters/support.js.map +1 -0
- package/lib/emitters/swr.d.ts +10 -0
- package/lib/emitters/swr.d.ts.map +1 -0
- package/lib/emitters/swr.js +88 -0
- package/lib/emitters/swr.js.map +1 -0
- package/lib/emitters/tanstack-query.d.ts +11 -0
- package/lib/emitters/tanstack-query.d.ts.map +1 -0
- package/lib/emitters/tanstack-query.js +68 -0
- package/lib/emitters/tanstack-query.js.map +1 -0
- package/lib/emitters/transformers.d.ts +11 -0
- package/lib/emitters/transformers.d.ts.map +1 -0
- package/lib/emitters/transformers.js +334 -0
- package/lib/emitters/transformers.js.map +1 -0
- package/lib/emitters/ts.d.ts +40 -0
- package/lib/emitters/ts.d.ts.map +1 -0
- package/lib/emitters/ts.js +104 -0
- package/lib/emitters/ts.js.map +1 -0
- package/lib/emitters/type-guards.d.ts +21 -0
- package/lib/emitters/type-guards.d.ts.map +1 -0
- package/lib/emitters/type-guards.js +183 -0
- package/lib/emitters/type-guards.js.map +1 -0
- package/lib/emitters/types.d.ts +23 -0
- package/lib/emitters/types.d.ts.map +1 -0
- package/lib/emitters/types.js +127 -0
- package/lib/emitters/types.js.map +1 -0
- package/lib/emitters/wrapper-support.d.ts +40 -0
- package/lib/emitters/wrapper-support.d.ts.map +1 -0
- package/lib/emitters/wrapper-support.js +112 -0
- package/lib/emitters/wrapper-support.js.map +1 -0
- package/lib/emitters/zod.d.ts +13 -0
- package/lib/emitters/zod.d.ts.map +1 -0
- package/lib/emitters/zod.js +344 -0
- package/lib/emitters/zod.js.map +1 -0
- package/lib/errors.d.ts +4 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +7 -0
- package/lib/errors.js.map +1 -0
- package/lib/generators/anchor.d.ts +9 -0
- package/lib/generators/anchor.d.ts.map +1 -0
- package/lib/generators/anchor.js +10 -0
- package/lib/generators/anchor.js.map +1 -0
- package/lib/generators/index.d.ts +15 -0
- package/lib/generators/index.d.ts.map +1 -0
- package/lib/generators/index.js +62 -0
- package/lib/generators/index.js.map +1 -0
- package/lib/generators/mock.d.ts +9 -0
- package/lib/generators/mock.d.ts.map +1 -0
- package/lib/generators/mock.js +23 -0
- package/lib/generators/mock.js.map +1 -0
- package/lib/generators/resolve.d.ts +19 -0
- package/lib/generators/resolve.d.ts.map +1 -0
- package/lib/generators/resolve.js +68 -0
- package/lib/generators/resolve.js.map +1 -0
- package/lib/generators/sdk.d.ts +12 -0
- package/lib/generators/sdk.d.ts.map +1 -0
- package/lib/generators/sdk.js +26 -0
- package/lib/generators/sdk.js.map +1 -0
- package/lib/generators/swr.d.ts +15 -0
- package/lib/generators/swr.d.ts.map +1 -0
- package/lib/generators/swr.js +27 -0
- package/lib/generators/swr.js.map +1 -0
- package/lib/generators/tanstack-query.d.ts +17 -0
- package/lib/generators/tanstack-query.d.ts.map +1 -0
- package/lib/generators/tanstack-query.js +30 -0
- package/lib/generators/tanstack-query.js.map +1 -0
- package/lib/generators/transformers.d.ts +19 -0
- package/lib/generators/transformers.d.ts.map +1 -0
- package/lib/generators/transformers.js +28 -0
- package/lib/generators/transformers.js.map +1 -0
- package/lib/generators/types.d.ts +66 -0
- package/lib/generators/types.d.ts.map +1 -0
- package/lib/generators/types.js +2 -0
- package/lib/generators/types.js.map +1 -0
- package/lib/generators/zod.d.ts +14 -0
- package/lib/generators/zod.d.ts.map +1 -0
- package/lib/generators/zod.js +23 -0
- package/lib/generators/zod.js.map +1 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +119 -0
- package/lib/index.js.map +1 -0
- package/lib/intermediate-representation/build.d.ts +4 -0
- package/lib/intermediate-representation/build.d.ts.map +1 -0
- package/lib/intermediate-representation/build.js +761 -0
- package/lib/intermediate-representation/build.js.map +1 -0
- package/lib/intermediate-representation/model.d.ts +275 -0
- package/lib/intermediate-representation/model.d.ts.map +1 -0
- package/lib/intermediate-representation/model.js +5 -0
- package/lib/intermediate-representation/model.js.map +1 -0
- package/lib/intermediate-representation/normalize-swagger2.d.ts +10 -0
- package/lib/intermediate-representation/normalize-swagger2.d.ts.map +1 -0
- package/lib/intermediate-representation/normalize-swagger2.js +210 -0
- package/lib/intermediate-representation/normalize-swagger2.js.map +1 -0
- package/lib/intermediate-representation/sanitize-identifiers.d.ts +30 -0
- package/lib/intermediate-representation/sanitize-identifiers.d.ts.map +1 -0
- package/lib/intermediate-representation/sanitize-identifiers.js +153 -0
- package/lib/intermediate-representation/sanitize-identifiers.js.map +1 -0
- package/lib/loader.d.ts +4 -0
- package/lib/loader.d.ts.map +1 -0
- package/lib/loader.js +14 -0
- package/lib/loader.js.map +1 -0
- package/lib/plugin.d.ts +19 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +41 -0
- package/lib/plugin.js.map +1 -0
- package/lib/runtime/auth.d.ts +15 -0
- package/lib/runtime/auth.d.ts.map +1 -0
- package/lib/runtime/auth.js +67 -0
- package/lib/runtime/auth.js.map +1 -0
- package/lib/runtime/create-client.d.ts +35 -0
- package/lib/runtime/create-client.d.ts.map +1 -0
- package/lib/runtime/create-client.js +215 -0
- package/lib/runtime/create-client.js.map +1 -0
- package/lib/runtime/errors.d.ts +11 -0
- package/lib/runtime/errors.d.ts.map +1 -0
- package/lib/runtime/errors.js +25 -0
- package/lib/runtime/errors.js.map +1 -0
- package/lib/runtime/index.d.ts +14 -0
- package/lib/runtime/index.d.ts.map +1 -0
- package/lib/runtime/index.js +24 -0
- package/lib/runtime/index.js.map +1 -0
- package/lib/runtime/multipart.d.ts +9 -0
- package/lib/runtime/multipart.d.ts.map +1 -0
- package/lib/runtime/multipart.js +31 -0
- package/lib/runtime/multipart.js.map +1 -0
- package/lib/runtime/paginate.d.ts +36 -0
- package/lib/runtime/paginate.d.ts.map +1 -0
- package/lib/runtime/paginate.js +99 -0
- package/lib/runtime/paginate.js.map +1 -0
- package/lib/runtime/parse.d.ts +9 -0
- package/lib/runtime/parse.d.ts.map +1 -0
- package/lib/runtime/parse.js +37 -0
- package/lib/runtime/parse.js.map +1 -0
- package/lib/runtime/retry.d.ts +15 -0
- package/lib/runtime/retry.d.ts.map +1 -0
- package/lib/runtime/retry.js +51 -0
- package/lib/runtime/retry.js.map +1 -0
- package/lib/runtime/send.d.ts +31 -0
- package/lib/runtime/send.d.ts.map +1 -0
- package/lib/runtime/send.js +138 -0
- package/lib/runtime/send.js.map +1 -0
- package/lib/runtime/setup.d.ts +11 -0
- package/lib/runtime/setup.d.ts.map +1 -0
- package/lib/runtime/setup.js +13 -0
- package/lib/runtime/setup.js.map +1 -0
- package/lib/runtime/sse.d.ts +21 -0
- package/lib/runtime/sse.d.ts.map +1 -0
- package/lib/runtime/sse.js +163 -0
- package/lib/runtime/sse.js.map +1 -0
- package/lib/runtime/types.d.ts +232 -0
- package/lib/runtime/types.d.ts.map +1 -0
- package/lib/runtime/types.js +9 -0
- package/lib/runtime/types.js.map +1 -0
- package/lib/runtime/url.d.ts +25 -0
- package/lib/runtime/url.d.ts.map +1 -0
- package/lib/runtime/url.js +97 -0
- package/lib/runtime/url.js.map +1 -0
- package/lib/runtime-contract.d.ts +27 -0
- package/lib/runtime-contract.d.ts.map +1 -0
- package/lib/runtime-contract.js +14 -0
- package/lib/runtime-contract.js.map +1 -0
- package/lib/types.d.ts +118 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
# @redocly/client-generator
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Experimental.** This package and the client generator are released as **experimental**: the generated output, options, and the plugin API may change in any minor release until it is declared stable (see [ADR-0013](./docs/adr/0013-experimental-status.md)).
|
|
4
|
+
> Pin your version if you depend on the output, and expect to regenerate when you upgrade.
|
|
5
|
+
> Feedback is very welcome while we stabilize it.
|
|
6
|
+
|
|
7
|
+
Generate a typed TypeScript client (inline types + `fetch` runtime) from an OpenAPI description.
|
|
8
|
+
The emitted client has **zero runtime dependencies** by default — it uses only web-standard APIs (`fetch`, `AbortController`, `URLSearchParams`, …), so it runs in browsers, Node ≥ 18, Bun, Deno, and edge runtimes.
|
|
9
|
+
Opt in to `runtime: 'package'` to import the runtime from this package instead, so engine fixes arrive via `npm update` without regenerating.
|
|
10
|
+
Code is produced through the TypeScript compiler AST (not string templates), so output is correct by construction; `typescript` is the only (peer) dependency.
|
|
11
|
+
|
|
12
|
+
This package is the engine behind the **`redocly generate-client`** command.
|
|
13
|
+
To run it from the command line, install [`@redocly/cli`](https://github.com/Redocly/redocly-cli) and see the [`generate-client` command reference](https://redocly.com/docs/cli/commands/generate-client).
|
|
14
|
+
The rest of this README covers using the package **programmatically**.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Broad input** — OpenAPI **3.0, 3.1, and 3.2.0**, plus **Swagger 2.0** (normalized to 3.x before generation)
|
|
19
|
+
`api` is a file path or URL.
|
|
20
|
+
- **Zero-dependency client** (the default `inline` runtime) built via the TS AST, with `typescript` as the only peer dep
|
|
21
|
+
- **Output modes** — `single` or `split` (schema types in a sibling `<stem>.schemas.ts`) (`outputMode`)
|
|
22
|
+
- **Runtime distribution** (`runtime`) — `inline` (default, self-contained) or `package`: the client imports its runtime from `@redocly/client-generator` (pure-data operation descriptors + a build-time version-skew guard), so engine fixes arrive via `npm update` — no regeneration
|
|
23
|
+
- **Both call styles, always** — a typed `client` instance (grouped-args methods, **per-instance configuration and credentials** via `createClient(OPERATIONS, { auth, serverUrl, … })`) plus free-function operations bound to it
|
|
24
|
+
- **Argument styles** — `flat` positional args or a `grouped` `vars` object (`argsStyle`)
|
|
25
|
+
- **Rich types** — inline types for every schema:
|
|
26
|
+
- string enums as unions or runtime `const` objects (`enumStyle`)
|
|
27
|
+
- **discriminated-union `is<Member>()` type guards** `<Op>Result` / `Error` / `Params` / `Body` / `Headers` / `Variables` aliases (collision-suppressed)
|
|
28
|
+
- validation keywords surfaced as JSDoc
|
|
29
|
+
- `dateType: 'Date'`
|
|
30
|
+
- **typed `multipart/form-data` bodies** (object fields, binary → `Blob`) auto-serialized to `FormData`
|
|
31
|
+
- **Runtime** — a typed `ClientConfig` (`serverUrl`, headers, `fetch` swap, hooks) applied via `configure()`:
|
|
32
|
+
- **composable middleware** (`onRequest`/`onResponse`/`onError`) whose `ctx.operation.id`/`path`/`tags` are the spec's **literal unions** — a misspelled operation id fails compilation
|
|
33
|
+
- **opt-in, abort-aware retries** with backoff, jitter, `Retry-After`, and a custom `retryOn`
|
|
34
|
+
- per-call response decoding (`parseAs`)
|
|
35
|
+
- OpenAPI **query-serialization styles**
|
|
36
|
+
- `errorMode: 'result'` for a discriminated `{ data, error, response }`
|
|
37
|
+
- a minification-safe `OPERATIONS` metadata map
|
|
38
|
+
- **Auth** — Basic / Bearer / apiKey (header, query, cookie) setters from `securitySchemes`, async token providers, and per-instance credentials via `ClientConfig.auth`
|
|
39
|
+
- **Server-Sent Events** — `text/event-stream` operations as typed async iterators with auto-reconnect; payloads typed from OpenAPI 3.2 `itemSchema`
|
|
40
|
+
- **Auto-pagination** — declared via the `pagination` option (a statically verified convention rule + per-operation overrides) or the spec's `x-pagination` extension (`cursor`/`offset`/`page` styles); paginated operations keep their one-shot call and gain `.pages()`/`.items()` async iterators, item types resolved from the response schema at generate time
|
|
41
|
+
- **Generators** (`generators`) — `sdk` (default), `zod`, `tanstack-query` (React/Vue/Svelte/Solid), `swr`, `transformers`, `mock` (MSW handlers + baked or `faker` data), and a [custom-generator plugin API](#custom-generators)
|
|
42
|
+
- **Hardened** — document-derived names coerced to safe unique identifiers, comment text escaped, and a bounded SSE reader
|
|
43
|
+
|
|
44
|
+
No add-on generator adds a dependency to the emitted client; its peer library is needed only in your app.
|
|
45
|
+
|
|
46
|
+
## Use programmatically
|
|
47
|
+
|
|
48
|
+
`generateClient(options)` is the API behind the CLI command — it loads the spec, builds the client, and writes the files:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { generateClient } from '@redocly/client-generator';
|
|
52
|
+
|
|
53
|
+
const result = await generateClient({
|
|
54
|
+
api: 'openapi.yaml', // file path or URL
|
|
55
|
+
output: 'src/client.ts', // entry file; the .schemas.ts sibling derives from it in split mode
|
|
56
|
+
outputMode: 'single', // 'single' | 'split'
|
|
57
|
+
runtime: 'inline', // 'inline' | 'package' (import the runtime from this package)
|
|
58
|
+
argsStyle: 'flat', // 'flat' | 'grouped'
|
|
59
|
+
errorMode: 'throw', // 'throw' | 'result'
|
|
60
|
+
dateType: 'string', // 'string' | 'Date' (pair 'Date' with the 'transformers' generator)
|
|
61
|
+
enumStyle: 'const-object', // 'const-object' | 'union'
|
|
62
|
+
generators: ['sdk'], // see "Generators" below
|
|
63
|
+
// serverUrl, queryFramework, mockData, mockSeed, pagination, customGenerators are also accepted
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(`Wrote ${result.files.length} file(s), ${result.bytes} bytes.`);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For type-safe authoring of a standalone options object, annotate it with `satisfies Config`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { generateClient, type Config } from '@redocly/client-generator';
|
|
73
|
+
|
|
74
|
+
const options = {
|
|
75
|
+
api: './openapi.yaml',
|
|
76
|
+
output: './src/api/client.ts',
|
|
77
|
+
generators: ['sdk', 'zod'],
|
|
78
|
+
} satisfies Config;
|
|
79
|
+
|
|
80
|
+
await generateClient(options);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
To inspect the output without writing to disk, the lower-level `collectGeneratedFiles(model, opts)` returns the files in memory (see `src/index.ts`).
|
|
84
|
+
|
|
85
|
+
## Using the generated client
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { configure, use, getOrderById, listMenuItems, setBearer } from './client.ts';
|
|
89
|
+
|
|
90
|
+
// Optional: global config (base URL, headers, fetch swap, hooks, retry).
|
|
91
|
+
configure({ retry: { retries: 3 } });
|
|
92
|
+
|
|
93
|
+
// Composable middleware for cross-cutting concerns (logging, tracing, auth refresh).
|
|
94
|
+
use({
|
|
95
|
+
onRequest: (ctx) => {
|
|
96
|
+
ctx.headers['X-Request-Id'] = crypto.randomUUID();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Auth helpers are generated from the spec's `securitySchemes`.
|
|
101
|
+
setBearer(token);
|
|
102
|
+
|
|
103
|
+
const menu = await listMenuItems({ limit: 10 });
|
|
104
|
+
const order = await getOrderById('ord_01khr487f7qm7p44xn427m43vb');
|
|
105
|
+
|
|
106
|
+
// Per-call options (AbortSignal, retry override) go in the trailing `init` arg.
|
|
107
|
+
const live = await listMenuItems({ limit: 10 }, { signal: controller.signal });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each operation's trailing `init` argument is `RequestOptions` (`RequestInit` plus an optional per-call `retry` override and a `parseAs` reader).
|
|
111
|
+
Retry is opt-in and abort-aware.
|
|
112
|
+
A custom `retryOn(ctx)` predicate can branch on `ctx.error` (transport failure) or `ctx.response` (HTTP status / body) and opt a `POST` in.
|
|
113
|
+
|
|
114
|
+
By default a response body is decoded by negotiating from its `Content-Type`.
|
|
115
|
+
Pass `parseAs` in `init` to force a reader — `'json'`, `'text'`, `'blob'`, `'arrayBuffer'`, `'formData'`, `'stream'` (the raw `ReadableStream`), or `'auto'` (the default):
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const stream = await getMenuItemPhoto('prd_…', { parseAs: 'stream' }); // ReadableStream
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`parseAs` is a **runtime override only** — it does not change the operation's static return type.
|
|
122
|
+
Forcing a reader that disagrees with the schema is the caller's responsibility.
|
|
123
|
+
|
|
124
|
+
Query parameters honor their OpenAPI serialization style.
|
|
125
|
+
The default (`form` + `explode: true`) repeats array values.
|
|
126
|
+
Declare `style` / `explode` / `allowReserved` on a parameter to get `form`+`explode:false` (`key=a,b`), `spaceDelimited` (`key=a%20b`), `pipeDelimited` (`key=a|b`), `deepObject`, or reserved-char passthrough.
|
|
127
|
+
|
|
128
|
+
A setter is generated for each injectable scheme — `setBearer` (HTTP `bearer` / OAuth2), `setBasicAuth(username, password)` (HTTP `basic`), and `setApiKey…` for `apiKey` schemes in header, query, or cookie — and each operation sends the credentials its `security` requires.
|
|
129
|
+
Each setter is instance-bound sugar over the exported client (`export const setBearer = client.auth.bearer;`); credentials are **per instance** (`ClientConfig.auth`), so independent instances built with `createClient(OPERATIONS, { auth })` carry independent credentials — the generated module exports `createClient` in both runtimes (see the [`multi-instance` example](./examples/multi-instance)).
|
|
130
|
+
Bearer/apiKey credentials accept a `TokenProvider` (a string or a possibly-async function resolved per request) for refresh-token flows:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { setBearer, setBasicAuth, setApiKey } from './client.ts';
|
|
134
|
+
|
|
135
|
+
setBearer(async () => await getFreshAccessToken()); // resolved before each authed call
|
|
136
|
+
setBasicAuth('alice', 's3cr3t'); // `Authorization: Basic <base64>`
|
|
137
|
+
setApiKey('my-api-key'); // header / query / cookie, per the scheme's `in`
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
With `argsStyle: 'grouped'`, inputs are bundled into a single `vars` object (typed as the operation's `<Op>Variables`) instead of positional arguments, while `init` stays the trailing argument:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const order = await getOrderById({ orderId: 'ord_01khr487f7qm7p44xn427m43vb' });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
With `errorMode: 'result'`, operations don't throw on non-2xx.
|
|
147
|
+
Each operation returns a discriminated `{ data, error, response }` whose `error` is typed from the spec's 4xx/5xx bodies (the `<Op>Error` union).
|
|
148
|
+
On success `error` is `undefined`; the HTTP status is always on `response.status`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const { data, error, response } = await getOrderById('ord_01khr487f7qm7p44xn427m43vb');
|
|
152
|
+
if (error)
|
|
153
|
+
console.error(response.status, error); // `error` is the typed body
|
|
154
|
+
else console.log(data.id); // `data` is the success body
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Transport/abort failures still throw in both modes.
|
|
158
|
+
|
|
159
|
+
The client also exports an `OPERATIONS` map (operationId → `{ method, path, tags }`) plus the `OperationId` / `OperationPath` / `OperationTag` / `OperationMetadata` types.
|
|
160
|
+
The string-literal keys, path templates, and tags survive minification, so they're the stable handle for cache/query keys, tracing span names, and request logging:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { OPERATIONS, getOrderById } from './client.ts';
|
|
164
|
+
|
|
165
|
+
const queryKey = [OPERATIONS.getOrderById.path, orderId]; // "/orders/{orderId}"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Customizing requests and responses
|
|
169
|
+
|
|
170
|
+
You shape requests and responses from your own code — never by editing the generated client, so changes survive regeneration.
|
|
171
|
+
Middleware (`use(...)` — sugar for `client.use(...)`, so it registers on that instance) hooks the request lifecycle, and each `RequestContext` carries the operation's identity so you can target by operationId or tag instead of brittle URL matching.
|
|
172
|
+
`onRequest` may mutate `ctx.url` / `ctx.method` / `ctx.headers` **and `ctx.body`**; `onResponse` may observe or replace the `Response`:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { configure, use, listMenuItems } from './client.ts';
|
|
176
|
+
|
|
177
|
+
// A custom transport — proxy, instrument, or (here) swap fetch entirely.
|
|
178
|
+
configure({ fetch: myFetch });
|
|
179
|
+
|
|
180
|
+
use({
|
|
181
|
+
onRequest: (ctx) => {
|
|
182
|
+
// Target specific operations by identity, not URL shape.
|
|
183
|
+
if (ctx.operation.id === 'createOrder' || ctx.operation.tags.includes('Orders')) {
|
|
184
|
+
ctx.headers['X-Idempotency-Key'] = crypto.randomUUID();
|
|
185
|
+
(ctx.body as { source?: string }).source = 'web'; // body edits are sent
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
onResponse: (response, ctx) => {
|
|
189
|
+
console.debug(ctx.operation.id, response.status);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// A header for one call only goes in the trailing RequestOptions.
|
|
194
|
+
await listMenuItems({}, { headers: { 'X-Request-Id': '42' } });
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`ctx.operation` is `{ id, path, tags }` — the operationId, the path template (`{param}` placeholders intact), and the operation's tags. All three are **typed literal unions** (`OperationId` / `OperationPath` / `OperationTag`, exported alongside the `OPERATIONS` map), so `ctx.operation.id === '…'` and `ctx.operation.tags.includes('…')` autocomplete and reject typos at compile time.
|
|
198
|
+
See the [`configure-and-middleware` example](../../tests/e2e/generate-client/examples/configure-and-middleware) for a runnable end-to-end version, and [ADR-0014](./docs/adr/0014-request-response-customization.md) for the rationale.
|
|
199
|
+
|
|
200
|
+
### Baking defaults into a published SDK
|
|
201
|
+
|
|
202
|
+
The customization above is composed by the **consumer**. If you instead **publish an SDK** and want those defaults already active for _your_ users, bake them in at generation time with `--setup <file>`. The setup module imports its contract from `@redocly/client-generator` (so it resolves and is unit-testable before the client is generated) and returns a `defineClientSetup({ config, middleware })`:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// client-setup.ts
|
|
206
|
+
import { defineClientSetup, type RequestContext } from '@redocly/client-generator';
|
|
207
|
+
|
|
208
|
+
export default defineClientSetup({
|
|
209
|
+
config: { serverUrl: 'https://api.acme.com', retry: { retries: 3 } },
|
|
210
|
+
middleware: [
|
|
211
|
+
{
|
|
212
|
+
onRequest: (ctx: RequestContext) => {
|
|
213
|
+
ctx.headers['X-Acme-SDK'] = '1.4.0';
|
|
214
|
+
if (ctx.operation.tags.includes('Orders'))
|
|
215
|
+
ctx.headers['X-Idempotency-Key'] = crypto.randomUUID();
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
redocly generate-client openapi.yaml --output src/api/client.ts --setup ./client-setup.ts
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The generator bakes the `config`/`middleware` into the generated client, so the published package applies them on import — your users call operations with no setup of their own, and can still override (their `configure`/`use` run after the baked ones; config layers spec defaults → baked setup → app `configure()`, middleware composes). Works across both output modes and both runtimes. A setup file may import **only** from `@redocly/client-generator`, so it never adds a dependency to the client.
|
|
227
|
+
|
|
228
|
+
See the [`baked-setup` example](./examples/baked-setup) and [ADR-0015](./docs/adr/0015-publisher-setup-bake-in.md).
|
|
229
|
+
|
|
230
|
+
## Testing the generated client
|
|
231
|
+
|
|
232
|
+
The client is plain `fetch` code, so you test it like any HTTP code — there is no special harness.
|
|
233
|
+
|
|
234
|
+
**In Node** (scripts, Vitest/Jest) there is **no CORS** — point it at any reachable API and call it:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { configure, listMenuItems } from './client.ts';
|
|
238
|
+
|
|
239
|
+
configure({ serverUrl: 'https://api.example.com' });
|
|
240
|
+
const items = await listMenuItems(); // resolves or throws ApiError
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**In the browser, CORS applies** — and it's the **target API's** policy, not the client's. Two things
|
|
244
|
+
to watch:
|
|
245
|
+
|
|
246
|
+
- The API must allow your origin (`Access-Control-Allow-Origin`).
|
|
247
|
+
- Any **custom request header** you add (e.g. `X-Request-Id` via `use({ onRequest })` middleware)
|
|
248
|
+
triggers a **CORS preflight**, so the API must also list that header in `Access-Control-Allow-Headers`
|
|
249
|
+
— otherwise the browser blocks the request and `fetch` throws `TypeError: Failed to fetch`. If the
|
|
250
|
+
API doesn't allow it, drop the header, use a dev proxy (e.g. Vite's `server.proxy`), or mock the API.
|
|
251
|
+
|
|
252
|
+
**Without a backend (recommended for tests and local dev), use MSW mocks.** Add `mock` to
|
|
253
|
+
`generators` to emit an MSW handler module; requests are intercepted in-process, so there's **no real
|
|
254
|
+
network and no CORS** — the same client runs unchanged against the mocks:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { setupServer } from 'msw/node'; // or 'msw/browser' for the browser
|
|
258
|
+
import { handlers } from './client.mocks.ts';
|
|
259
|
+
|
|
260
|
+
const server = setupServer(...handlers);
|
|
261
|
+
beforeAll(() => server.listen());
|
|
262
|
+
afterAll(() => server.close());
|
|
263
|
+
|
|
264
|
+
// now calls to the generated client resolve from the mocks
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The repo's [`examples/`](./examples) are runnable end-to-end: `npm install && npm run dev` in an
|
|
268
|
+
example directory serves it on `http://localhost:5173` against the live demo API (a CORS-enabled
|
|
269
|
+
`GET`), and `examples/mock` shows the MSW-backed flow.
|
|
270
|
+
|
|
271
|
+
## Generators
|
|
272
|
+
|
|
273
|
+
`generators` (default `['sdk']`) selects which files to emit.
|
|
274
|
+
The `sdk` client is dependency-free with the default `inline` runtime; each add-on lands in its own sibling file and needs its peer library only in your app.
|
|
275
|
+
Every generator works with both runtimes and both output modes.
|
|
276
|
+
|
|
277
|
+
### Runtime validation with Zod
|
|
278
|
+
|
|
279
|
+
`generators: ['sdk', 'zod']` emits a standalone `<output>.zod.ts` module of [Zod](https://zod.dev) schemas (one `export const <Name>Schema` per schema).
|
|
280
|
+
The generated client never imports Zod; only the `*.zod.ts` module does.
|
|
281
|
+
Install Zod in your app as a peer — any `zod` `^3.23 || ^4`:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { z } from 'zod';
|
|
285
|
+
import type { Pet } from './client.ts';
|
|
286
|
+
import { PetSchema } from './client.zod.ts';
|
|
287
|
+
|
|
288
|
+
const pet: Pet = PetSchema.parse(await res.json()); // z.infer<typeof PetSchema> === Pet
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Schemas carry the validation refinements stable across Zod 3.23 and 4 (`.min`/`.max`, `.gt`/`.lt`, `.int`, `.regex`).
|
|
292
|
+
Refs become `z.lazy(() => …)` so recursive schemas work.
|
|
293
|
+
Format helpers (`.email`/`.uuid`/`.url`) are not emitted, since they diverge between Zod 3 and 4.
|
|
294
|
+
|
|
295
|
+
The module also emits `operationSchemas` (request/response validators keyed by operationId) and the `zodValidation` middleware:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { use } from './client.ts';
|
|
299
|
+
import { zodValidation } from './client.zod.ts';
|
|
300
|
+
|
|
301
|
+
use(zodValidation()); // request bodies validated before the wire; JSON responses after it
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
A mismatch throws `ZodValidationError` (`operationId`, `direction`, zod `issues`); payloads are never mutated.
|
|
305
|
+
|
|
306
|
+
### TanStack Query
|
|
307
|
+
|
|
308
|
+
`generators: ['sdk', 'tanstack-query']` emits a standalone `<output>.tanstack.ts` module of [TanStack Query](https://tanstack.com/query) v5 factories wrapping the sdk operations.
|
|
309
|
+
Each query op (`GET`/`HEAD`) gets a `<op>QueryKey`/`<op>Options` factory (returning `queryOptions`).
|
|
310
|
+
Each mutation gets a `<op>Mutation` factory (returning `mutationKey`/`mutationFn`):
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
314
|
+
import { getPetOptions, createPetMutation } from './client.tanstack.ts';
|
|
315
|
+
|
|
316
|
+
const { data } = useQuery(getPetOptions({ id }));
|
|
317
|
+
const { mutate } = useMutation(createPetMutation());
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Only the `*.tanstack.ts` module imports TanStack Query; install it as a peer — any `@tanstack/react-query` `^5`.
|
|
321
|
+
The factories wrap the **throw-mode** sdk (the default), since TanStack's `queryFn` is expected to throw on error.
|
|
322
|
+
|
|
323
|
+
**Framework** — TanStack's `queryOptions`/`mutationOptions` API is identical across adapters, so the emitted module is byte-identical across frameworks; only the import specifier differs.
|
|
324
|
+
Set `queryFramework` to `react` (default), `vue`, `svelte`, or `solid` and install the matching adapter (`@tanstack/<framework>-query`, any `^5`).
|
|
325
|
+
|
|
326
|
+
### SWR
|
|
327
|
+
|
|
328
|
+
`generators: ['sdk', 'swr']` emits a standalone `<output>.swr.ts` module of [SWR](https://swr.vercel.app) hooks.
|
|
329
|
+
Each query op (`GET`/`HEAD`) gets a `<op>Key` tuple factory + a `use<Op>(vars, init?)` hook over `useSWR`; each mutation gets a `use<Op>()` hook over `useSWRMutation`:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { useGetPetById, useCreatePet } from './client.swr.ts';
|
|
333
|
+
|
|
334
|
+
const { data } = useGetPetById({ id });
|
|
335
|
+
const { trigger } = useCreatePet();
|
|
336
|
+
await trigger({ body: { name: 'Rex' } });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Only the `*.swr.ts` module imports SWR (`swr` for queries, `swr/mutation` for mutations); install it as a peer — any `swr` `^2`.
|
|
340
|
+
The hooks wrap the **throw-mode** sdk functions.
|
|
341
|
+
|
|
342
|
+
### Date transformers
|
|
343
|
+
|
|
344
|
+
By default `date-time`/`date` fields are typed `string`.
|
|
345
|
+
Set `dateType: 'Date'` to type them as `Date`, and add `'transformers'` to `generators` to emit a `<output>.transformers.ts` module of `transform<Name>` functions that convert wire ISO strings to `Date` at runtime:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { getPet } from './client.ts';
|
|
349
|
+
import { transformPet } from './client.transformers.ts';
|
|
350
|
+
|
|
351
|
+
const pet = transformPet(await getPet(id)); // pet.createdAt is now a Date
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The transformers import only the schema **types**, so they add no dependency to the client (`Date` is a web standard).
|
|
355
|
+
`int64` → `bigint` is deferred; without `dateType: 'Date'` the date fields stay `string`.
|
|
356
|
+
|
|
357
|
+
### MSW mocks
|
|
358
|
+
|
|
359
|
+
`generators: ['sdk', 'mock']` emits a standalone `<output>.mocks.ts` module of [MSW](https://mswjs.io) v2 request handlers and `create<Schema>(overrides?)` data factories.
|
|
360
|
+
Each handler intercepts its operation's method + path and responds with a fixture baked from the spec (prefers `example`/`default`; `format: binary` → `new Blob([])`.
|
|
361
|
+
Recursive schemas terminate at the cycle with an empty array/record).
|
|
362
|
+
Each `create<Schema>` factory builds the same default object and merges `overrides`, so factories double as test builders.
|
|
363
|
+
Install MSW as a dev dependency — `msw` `^2`:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
// test setup (Node)
|
|
367
|
+
import { setupServer } from 'msw/node';
|
|
368
|
+
import { handlers } from './client.mocks';
|
|
369
|
+
|
|
370
|
+
const server = setupServer(...handlers);
|
|
371
|
+
beforeAll(() => server.listen());
|
|
372
|
+
afterEach(() => server.resetHandlers());
|
|
373
|
+
afterAll(() => server.close());
|
|
374
|
+
|
|
375
|
+
// override a single factory for one case
|
|
376
|
+
import { createMenuItem } from './client.mocks';
|
|
377
|
+
const special = createMenuItem({ name: 'Cold Brew', price: 499 });
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Realistic data with faker** — mock data is **baked** by default (deterministic literals, no extra dependency).
|
|
381
|
+
Set `mockData: 'faker'` to emit [`@faker-js/faker`](https://fakerjs.dev) calls for realistic data, and `mockSeed: <n>` to pin faker's PRNG so the data is reproducible.
|
|
382
|
+
Factory signatures are identical in both modes; faker mode makes `@faker-js/faker` (`^9`) a dev dependency of your app (the client itself gains no dependency).
|
|
383
|
+
|
|
384
|
+
## Custom generators
|
|
385
|
+
|
|
386
|
+
Need an output the built-ins don't ship — validators in another library, a UI permissions map, mocks in your test runner's format, an SDK in your house style?
|
|
387
|
+
Write a **custom generator**: it reads the same OpenAPI-derived model the built-ins do, runs in the same pass, and its output never drifts from the spec.
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
// route-map-generator.ts
|
|
391
|
+
import { defineGenerator } from '@redocly/client-generator';
|
|
392
|
+
|
|
393
|
+
export default defineGenerator({
|
|
394
|
+
name: 'route-map',
|
|
395
|
+
requires: ['sdk'],
|
|
396
|
+
run({ model, outputPath }) {
|
|
397
|
+
const routes = model.services
|
|
398
|
+
.flatMap((service) => service.operations)
|
|
399
|
+
.map((op) => ` ${op.name}: '${op.method.toUpperCase()} ${op.path}',`)
|
|
400
|
+
.join('\n');
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
path: outputPath.replace(/\.ts$/, '.routes.ts'),
|
|
404
|
+
content: `export const routes = {\n${routes}\n} as const;\n`,
|
|
405
|
+
},
|
|
406
|
+
];
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Select it in `generators` by import specifier (a path or package), or register it inline and select it by `name`:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { generateClient } from '@redocly/client-generator';
|
|
415
|
+
import routeMap from './tools/route-map-generator.ts';
|
|
416
|
+
|
|
417
|
+
await generateClient({
|
|
418
|
+
api: './openapi.yaml',
|
|
419
|
+
output: './src/api/client.ts',
|
|
420
|
+
customGenerators: [routeMap], // register…
|
|
421
|
+
generators: ['sdk', 'route-map'], // …then select by name (or pass './tools/route-map-generator.ts')
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
`@redocly/client-generator` also exports the IR types and the codegen toolkit the built-ins use (`ts`, `printStatements`, `operationSignature`, `schemaToTypeNode`, `pascalCase`, …).
|
|
426
|
+
A generator declares the same `requires`/`errorModes`/`dateTypes`/`runtimes` contract, validated up front.
|
|
427
|
+
A custom generator never adds dependencies to the generated client.
|
|
428
|
+
See `examples/custom-generator` for a runnable example.
|
|
429
|
+
|
|
430
|
+
## Server-Sent Events (streaming)
|
|
431
|
+
|
|
432
|
+
An operation whose `2xx` response declares `text/event-stream` is generated — with no option — as a typed **async-generator** client method plus the matching free function.
|
|
433
|
+
Each event's `data` is typed from the OpenAPI 3.2 `itemSchema` (falling back to the media `schema`, then `string`):
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import { streamMessages } from './client.ts';
|
|
437
|
+
|
|
438
|
+
for await (const ev of streamMessages()) {
|
|
439
|
+
console.log(ev.id, ev.data.text); // ev: ServerSentEvent<Message>
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The stream **auto-reconnects** on a dropped connection, resuming via the `Last-Event-ID` header (backoff: server `retry:` → `reconnectDelay` → 1s, exponential with jitter, capped at 30s).
|
|
444
|
+
Tune or opt out per call (`{ reconnect: false }` / `{ reconnectDelay: 500 }`), and `break` or pass an `AbortSignal` to stop early — both end the iterator cleanly (no throw).
|
|
445
|
+
SSE operations always throw `ApiError` on an initial non-2xx and never return the `result`-mode shape.
|
|
446
|
+
|
|
447
|
+
## Auto-pagination
|
|
448
|
+
|
|
449
|
+
Pagination is **declared, never guessed** — in the `pagination` option (or `redocly.yaml` `client.pagination`; the spec's `x-pagination` operation extension takes the same fields).
|
|
450
|
+
A convention rule applies to every operation it **structurally fits** (the advance param is a declared query parameter of the right type; the JSON pointers resolve in the JSON success-response schema, `items` landing on an array); an explicit rule (an `operations` override or `x-pagination`) that doesn't fit fails generation.
|
|
451
|
+
Precedence per operation: `operations[id]` > `x-pagination` > convention; `exclude` wins over all.
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
await generateClient({
|
|
455
|
+
api: './openapi.yaml',
|
|
456
|
+
output: './src/api/client.ts',
|
|
457
|
+
pagination: {
|
|
458
|
+
style: 'cursor', // 'cursor' | 'offset' | 'page'
|
|
459
|
+
cursorParam: 'cursor', // the query param that receives the cursor
|
|
460
|
+
nextCursor: '/nextCursor', // JSON pointer to the next cursor in the response
|
|
461
|
+
items: '/orders', // JSON pointer to the page's item array
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Each paginated operation keeps its one-shot call and gains `.pages(args?, init?)` / `.items(args?, init?)` async iterators (the flat function too — note its iterators take the **grouped** args shape while the function itself stays positional).
|
|
467
|
+
Item types are resolved from the response schema at generate time — no runtime reflection:
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
import { client, listOrders } from './client.ts';
|
|
471
|
+
|
|
472
|
+
for await (const order of client.listOrders.items({ params: { limit: 20 } })) {
|
|
473
|
+
console.log(order.id); // `order` is typed `Order`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
await listOrders({ limit: 20 }); // flat one-shot: positional
|
|
477
|
+
for await (const page of listOrders.pages({ params: { cursor: 'c2' } })) {
|
|
478
|
+
// resume from a saved cursor; pass `{ signal }` as `init` to abort mid-iteration
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
`cursor` style stops when `nextCursor` is absent/`null`/empty (and throws if the cursor doesn't advance); `offset`/`page` styles stop on an empty page.
|
|
483
|
+
Iteration is error-mode-agnostic: a failed page throws `ApiError` even on `errorMode: 'result'` clients, where `.pages()` yields raw pages (not `{ data, error, response }` envelopes).
|
|
484
|
+
Inline output embeds the pagination module only when some operation paginates; `runtime: 'package'` clients receive pagination improvements via `npm update`.
|
|
485
|
+
|
|
486
|
+
## Examples
|
|
487
|
+
|
|
488
|
+
Runnable examples live in [`examples/`](../../tests/e2e/generate-client/examples): `zero-install-quickstart`, `fetch-functions`, `configure-and-middleware`, `baked-setup`, `sse-streaming`, `zod`, `tanstack-query`, `mock`, `custom-generator`, `programmatic`, `vendored-edge`, `package-runtime` (`runtime: 'package'` — engine fixes via `npm update`), `multi-instance` (per-tenant `createClient` instances over one generated module), `pagination` and `custom-pagination` (the declared convention vs. a hand-written helper), and `nested-facade` (a custom generator grouping operations by tag).
|
|
489
|
+
Each is a standalone Vite app with a checked-in, drift-checked generated client.
|
|
490
|
+
|
|
491
|
+
## Documentation
|
|
492
|
+
|
|
493
|
+
- [`generate-client` command reference](https://redocly.com/docs/cli/commands/generate-client) — CLI usage, every flag, and `redocly.yaml` configuration.
|
|
494
|
+
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) and the [ADRs](./docs/adr/) — how the package is built and why.
|
|
495
|
+
|
|
496
|
+
## Development
|
|
497
|
+
|
|
498
|
+
This package is part of the Redocly CLI monorepo. Run all commands from the repo root:
|
|
499
|
+
|
|
500
|
+
```sh
|
|
501
|
+
npm run compile # build this package
|
|
502
|
+
npm run unit # unit tests (this package is held at 100% coverage)
|
|
503
|
+
VITEST_SUITE=e2e npx vitest run tests/e2e/generate-client/ # behavioral e2e
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
The client runtime lives in `src/runtime/` (real, unit-testable modules; package mode imports them, inline mode embeds them via the generated `src/emitters/runtime-sources.ts` snapshot and the `src/emitters/inline-runtime.ts` assembler), the structural emitters in `src/emitters/`, the IR in `src/intermediate-representation/`, the generators in `src/generators/`, and the file-layout writers in `src/writers/`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Config } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge a base config (a `redocly.yaml` `client` block) with CLI overrides.
|
|
4
|
+
* Defined keys in `overrides` win; `undefined` override values are ignored
|
|
5
|
+
* so absent flags don't clobber the base values.
|
|
6
|
+
*/
|
|
7
|
+
export declare function mergeConfig(base: Partial<Config>, overrides: Partial<Config>): Partial<Config>;
|
|
8
|
+
//# sourceMappingURL=config-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-file.d.ts","sourceRoot":"","sources":["../src/config-file.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAwB9F"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge a base config (a `redocly.yaml` `client` block) with CLI overrides.
|
|
3
|
+
* Defined keys in `overrides` win; `undefined` override values are ignored
|
|
4
|
+
* so absent flags don't clobber the base values.
|
|
5
|
+
*/
|
|
6
|
+
export function mergeConfig(base, overrides) {
|
|
7
|
+
const merged = { ...base };
|
|
8
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
9
|
+
if (value !== undefined)
|
|
10
|
+
merged[key] = value;
|
|
11
|
+
}
|
|
12
|
+
// `pagination` is the one nested block: a partial override (e.g. a per-API `operations`
|
|
13
|
+
// map alone) must layer onto the shared convention fields, not replace the whole object.
|
|
14
|
+
// `operations` merge by id and `exclude` unions — both additive, so a per-API block
|
|
15
|
+
// extends the shared exclusions/overrides rather than dropping them.
|
|
16
|
+
if (base.pagination && overrides.pagination) {
|
|
17
|
+
merged.pagination = { ...base.pagination, ...overrides.pagination };
|
|
18
|
+
if (base.pagination.operations || overrides.pagination.operations) {
|
|
19
|
+
merged.pagination.operations = {
|
|
20
|
+
...base.pagination.operations,
|
|
21
|
+
...overrides.pagination.operations,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (base.pagination.exclude || overrides.pagination.exclude) {
|
|
25
|
+
merged.pagination.exclude = [
|
|
26
|
+
...new Set([...(base.pagination.exclude ?? []), ...(overrides.pagination.exclude ?? [])]),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return merged;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=config-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../src/config-file.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAqB,EAAE,SAA0B;IAC3E,MAAM,MAAM,GAAoB,EAAE,GAAG,IAAI,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS;YAAG,MAAkC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC5E,CAAC;IACD,wFAAwF;IACxF,yFAAyF;IACzF,oFAAoF;IACpF,qEAAqE;IACrE,IAAI,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;QAC5C,MAAM,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC;QACpE,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,IAAI,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;YAClE,MAAM,CAAC,UAAU,CAAC,UAAU,GAAG;gBAC7B,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU;gBAC7B,GAAG,SAAS,CAAC,UAAU,CAAC,UAAU;aACnC,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YAC5D,MAAM,CAAC,UAAU,CAAC,OAAO,GAAG;gBAC1B,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;aAC1F,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ArgsStyle } from './emitters/emit-options.js';
|
|
2
|
+
import type { PaginationConfig } from './emitters/pagination.js';
|
|
3
|
+
import type { CustomGenerator, OutputMode } from './generators/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The user-facing generation config: the options `generateClient()` accepts, plus the
|
|
6
|
+
* `generators` list. Annotate a standalone config object with `satisfies Config` for
|
|
7
|
+
* type-safe authoring. The CLI merges a `redocly.yaml` `client` block into this shape
|
|
8
|
+
* via `mergeConfig` (see config-file.ts).
|
|
9
|
+
*/
|
|
10
|
+
export type Config = {
|
|
11
|
+
/** Path or URL to the OpenAPI document (or an `apis:` alias from `redocly.yaml`). */
|
|
12
|
+
api: string;
|
|
13
|
+
/** Output anchor path (a `.ts` file; multi-file modes derive siblings from it). */
|
|
14
|
+
output: string;
|
|
15
|
+
/** File partitioning. Defaults to `single`. */
|
|
16
|
+
outputMode?: OutputMode;
|
|
17
|
+
/** How inputs are passed. Defaults to `flat`. */
|
|
18
|
+
argsStyle?: ArgsStyle;
|
|
19
|
+
/** Override the inlined base URL (else derived from `servers[0].url`). */
|
|
20
|
+
serverUrl?: string;
|
|
21
|
+
/** Named-enum emission. Defaults to `const-object`. */
|
|
22
|
+
enumStyle?: 'union' | 'const-object';
|
|
23
|
+
/** Error-handling shape of the generated client. `'throw'` (default) throws `ApiError`
|
|
24
|
+
* on non-2xx; `'result'` returns a discriminated `{ data, error, response }`. */
|
|
25
|
+
errorMode?: 'throw' | 'result';
|
|
26
|
+
/** How `date-time`/`date` string fields are typed. `'string'` (default) keeps the
|
|
27
|
+
* ISO wire shape; `'Date'` emits a `Date` reference (pair with the `transformers`
|
|
28
|
+
* generator so the runtime value matches). */
|
|
29
|
+
dateType?: 'string' | 'Date';
|
|
30
|
+
/** TanStack Query adapter the `tanstack-query` generator imports from
|
|
31
|
+
* (`@tanstack/${queryFramework}-query`). Defaults to `react`; only the import
|
|
32
|
+
* specifier changes. */
|
|
33
|
+
queryFramework?: 'react' | 'vue' | 'svelte' | 'solid';
|
|
34
|
+
/** How the `mock` generator produces data. `'baked'` (default) inlines deterministic
|
|
35
|
+
* literals (zero-dep); `'faker'` emits `@faker-js/faker` calls for realistic data
|
|
36
|
+
* (the consumer adds `@faker-js/faker` as a dev-dep). */
|
|
37
|
+
mockData?: 'baked' | 'faker';
|
|
38
|
+
/** Seed for faker-mode mocks: emits `faker.seed(<n>)` so runs reproduce. Baked mode ignores it. */
|
|
39
|
+
mockSeed?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Generators to run, in order. Defaults to `['sdk']`. Each entry is a built-in name, the `name`
|
|
42
|
+
* of an inline `customGenerators` entry, or an import specifier (path or package) for a plugin.
|
|
43
|
+
*/
|
|
44
|
+
generators?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Inline custom generators (the experimental plugin API), registered so they can be selected in
|
|
47
|
+
* `generators` by `name`. Authored with `defineGenerator` from `@redocly/client-generator`.
|
|
48
|
+
*/
|
|
49
|
+
customGenerators?: CustomGenerator[];
|
|
50
|
+
/**
|
|
51
|
+
* Path to a publisher setup module (`export default defineClientSetup({ config, middleware })`)
|
|
52
|
+
* baked into the generated client, so a published SDK ships its request/response defaults built
|
|
53
|
+
* in. Resolved against the config dir. Works across all output modes.
|
|
54
|
+
*/
|
|
55
|
+
setup?: string;
|
|
56
|
+
/** Runtime distribution: 'inline' (default, self-contained) | 'package' (imports @redocly/client-generator). */
|
|
57
|
+
runtime?: 'inline' | 'package';
|
|
58
|
+
/** Auto-pagination rules (convention + per-operation overrides + `exclude`), resolved
|
|
59
|
+
* together with each operation's `x-pagination` extension. Explicit rules that don't
|
|
60
|
+
* fit their operation fail generation. */
|
|
61
|
+
pagination?: PaginationConfig;
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iDAAiD;IACjD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACrC;qFACiF;IACjF,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/B;;kDAE8C;IAC9C,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B;;4BAEwB;IACxB,cAAc,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtD;;6DAEyD;IACzD,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7B,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gHAAgH;IAChH,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B;;8CAE0C;IAC1C,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SecuritySchemeModel } from '../intermediate-representation/model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Public setter name for an apiKey scheme: `setApiKey` when it's the only apiKey
|
|
4
|
+
* scheme (of any `in`), else `setApiKey<Key>` to disambiguate.
|
|
5
|
+
*/
|
|
6
|
+
export declare function apiKeySetterName(key: string, sole: boolean): string;
|
|
7
|
+
/**
|
|
8
|
+
* The public credential-setter names the client exports for a set of schemes,
|
|
9
|
+
* in emission order (`setBearer`, then `setBasicAuth`, then each apiKey setter).
|
|
10
|
+
* Also seeds the reserved-identifier set (`packageIdents`) so operation names
|
|
11
|
+
* can't collide with a setter.
|
|
12
|
+
*/
|
|
13
|
+
export declare function authSetterNames(schemes: SecuritySchemeModel[]): string[];
|
|
14
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/emitters/auth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAGnF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAWxE"}
|