···11+---
22+'@hey-api/openapi-ts': patch
33+---
44+55+**plugin(@hey-api/sdk)**: add `examples` option
66+77+The SDK plugin can generate ready-to-use code examples for each operation, showing how to call the SDK methods with proper parameters and setup.
88+99+Learn how to generate examples on the [SDK plugin](https://heyapi.dev/openapi-ts/plugins/sdk#code-examples) page.
···11+---
22+'@hey-api/openapi-ts': patch
33+---
44+55+**output**: add `source` option
66+77+Source is a copy of the input specification used to generate your output. It can be used to power documentation tools or to persist a stable snapshot alongside your generated files.
88+99+Learn how to use the source on the [Output](https://heyapi.dev/openapi-ts/configuration/output#source) page.
···168168import foo from './foo.js';
169169```
170170171171+## Source
172172+173173+Source is a copy of the input specification used to generate your output. It can be used to power documentation tools or to persist a stable snapshot alongside your generated files.
174174+175175+Enabling the `source` option with `true` creates a `source.json` file in your output folder.
176176+177177+```js
178178+export default {
179179+ input: 'hey-api/backend', // sign up at app.heyapi.dev
180180+ output: {
181181+ path: 'src/client',
182182+ source: true, // [!code ++]
183183+ },
184184+};
185185+```
186186+187187+You can customize the file name and location using `fileName` and `path`. For example, this configuration will create an `openapi.json` file inside `src/client/source` directory.
188188+189189+```js
190190+export default {
191191+ input: 'hey-api/backend', // sign up at app.heyapi.dev
192192+ output: {
193193+ path: 'src/client',
194194+ source: {
195195+ fileName: 'openapi', // [!code ++]
196196+ path: './source', // [!code ++]
197197+ },
198198+ },
199199+};
200200+```
201201+202202+To use the source without writing it to disk, you can provide a `callback` function. This is useful for logging or integrating with external systems.
203203+204204+```js
205205+export default {
206206+ input: 'hey-api/backend', // sign up at app.heyapi.dev
207207+ output: {
208208+ path: 'src/client',
209209+ source: {
210210+ callback: (source) => console.log(source), // [!code ++]
211211+ path: null, // [!code ++]
212212+ },
213213+ },
214214+};
215215+```
216216+171217## Format
172218173219To format your output folder contents, set `format` to a valid formatter.
+236-3
docs/openapi-ts/plugins/sdk.md
···991010The SDK plugin generates a high-level, ergonomic API layer on top of the low-level HTTP client.
11111212-It exposes typed functions or classes for each operation, with built-in auth handling and optional request and response validation.
1212+It exposes typed functions or methods for each operation, with built-in auth handling, configurable request and response validation, and ready-to-use code examples.
13131414## Features
15151616- high-level SDK layer on top of the HTTP client
1717-- typed functions or classes per operation
1717+- typed functions or methods per operation
1818- built-in authentication handling
1919-- optional request and response validation
1919+- request and response validation
2020+- ready-to-use code examples
20212122## Installation
2223···259260 input: 'hey-api/backend', // sign up at app.heyapi.dev
260261 output: 'src/client',
261262 plugins: [
263263+ // ...other plugins
262264 {
263265 name: '@hey-api/sdk',
264266 validator: true, // or 'valibot' // [!code ++]
···282284 input: 'hey-api/backend', // sign up at app.heyapi.dev
283285 output: 'src/client',
284286 plugins: [
287287+ // ...other plugins
285288 {
286289 name: '@hey-api/sdk',
287290 validator: {
···297300 input: 'hey-api/backend', // sign up at app.heyapi.dev
298301 output: 'src/client',
299302 plugins: [
303303+ // ...other plugins
300304 {
301305 name: '@hey-api/sdk',
302306 validator: {
···310314:::
311315312316Learn more about available validators on the [Validators](/openapi-ts/validators) page.
317317+318318+## Code Examples
319319+320320+The SDK plugin can generate ready-to-use code examples for each operation, showing how to call the SDK methods with proper parameters and setup.
321321+322322+Examples are not generated by default, but you can enable and customize them through the `examples` option. With the default settings, an example might look like this.
323323+324324+::: code-group
325325+326326+```ts [example]
327327+import { PetStore } from 'your-package';
328328+329329+await new PetStore().addPet();
330330+```
331331+332332+```js [config]
333333+export default {
334334+ input: 'hey-api/backend', // sign up at app.heyapi.dev
335335+ output: 'src/client',
336336+ plugins: [
337337+ // ...other plugins
338338+ {
339339+ examples: true, // [!code ++]
340340+ name: '@hey-api/sdk',
341341+ operations: {
342342+ containerName: 'PetStore',
343343+ strategy: 'single',
344344+ },
345345+ },
346346+ ],
347347+};
348348+```
349349+350350+:::
351351+352352+### Module and Setup
353353+354354+To make examples more practical, configure `moduleName` to specify the package from which users import your SDK.
355355+356356+Next, set `setupName` to indicate how users should instantiate the SDK, typically only once per application.
357357+358358+::: code-group
359359+360360+```ts [example]
361361+import { PetStore } from '@petstore/client'; // [!code ++]
362362+363363+const client = new PetStore(); // [!code ++]
364364+365365+await client.addPet();
366366+```
367367+368368+```js [config]
369369+export default {
370370+ input: 'hey-api/backend', // sign up at app.heyapi.dev
371371+ output: 'src/client',
372372+ plugins: [
373373+ // ...other plugins
374374+ {
375375+ examples: {
376376+ moduleName: '@petstore/client', // [!code ++]
377377+ setupName: 'client', // [!code ++]
378378+ },
379379+ name: '@hey-api/sdk',
380380+ operations: {
381381+ containerName: 'PetStore',
382382+ strategy: 'single',
383383+ },
384384+ },
385385+ ],
386386+};
387387+```
388388+389389+:::
390390+391391+### Initialization
392392+393393+Often, your SDK needs to be instantiated with an API key or other configuration. In examples, `importSetup` lets you control how the SDK is initialized.
394394+395395+::: code-group
396396+397397+<!-- prettier-ignore-start -->
398398+```ts [example]
399399+import { PetStore } from '@petstore/client';
400400+401401+const client = new PetStore({ // [!code ++]
402402+ apiKey: 'YOUR_API_KEY', // [!code ++]
403403+}); // [!code ++]
404404+405405+await client.addPet();
406406+```
407407+<!-- prettier-ignore-end -->
408408+<!-- prettier-ignore-start -->
409409+```js [config]
410410+export default {
411411+ input: 'hey-api/backend', // sign up at app.heyapi.dev
412412+ output: 'src/client',
413413+ plugins: [
414414+ // ...other plugins
415415+ {
416416+ examples: {
417417+ importSetup: ({ $, node }) => // [!code ++]
418418+ $.new( // [!code ++]
419419+ node.name, // [!code ++]
420420+ $.object() // [!code ++]
421421+ .pretty() // [!code ++]
422422+ .prop('apiKey', $.literal('YOUR_API_KEY')), // [!code ++]
423423+ ), // [!code ++]
424424+ moduleName: '@petstore/client',
425425+ setupName: 'client',
426426+ },
427427+ name: '@hey-api/sdk',
428428+ operations: {
429429+ containerName: 'PetStore',
430430+ strategy: 'single',
431431+ },
432432+ },
433433+ ],
434434+};
435435+```
436436+<!-- prettier-ignore-end -->
437437+438438+:::
439439+440440+### Import Style
441441+442442+If you re-export the generated SDK from your own module, you can adjust `importName` and `importKind` to match your actual import style.
443443+444444+::: code-group
445445+446446+<!-- prettier-ignore-start -->
447447+```ts [example]
448448+import CatStore from '@petstore/client'; // [!code ++]
449449+450450+const client = new CatStore({ // [!code ++]
451451+ apiKey: 'YOUR_API_KEY',
452452+});
453453+454454+await client.addPet();
455455+```
456456+<!-- prettier-ignore-end -->
457457+458458+```js [config]
459459+export default {
460460+ input: 'hey-api/backend', // sign up at app.heyapi.dev
461461+ output: 'src/client',
462462+ plugins: [
463463+ // ...other plugins
464464+ {
465465+ examples: {
466466+ importKind: 'default', // [!code ++]
467467+ importName: 'CatStore', // [!code ++]
468468+ importSetup: ({ $, node }) =>
469469+ $(node.name).call(
470470+ $.object().pretty().prop('apiKey', $.literal('YOUR_API_KEY')),
471471+ ),
472472+ moduleName: '@petstore/client',
473473+ setupName: 'client',
474474+ },
475475+ name: '@hey-api/sdk',
476476+ operations: {
477477+ containerName: 'PetStore',
478478+ strategy: 'single',
479479+ },
480480+ },
481481+ ],
482482+};
483483+```
484484+485485+:::
486486+487487+### Payload
488488+489489+You can customize the example request using the `payload` option. Requests can also be customized selectively. For example, we can provide a default payload only for the `addPet()` method.
490490+491491+::: code-group
492492+493493+<!-- prettier-ignore-start -->
494494+```ts [example]
495495+import CatStore from '@petstore/client';
496496+497497+const client = new CatStore({
498498+ apiKey: 'YOUR_API_KEY',
499499+});
500500+501501+await client.addPet({ // [!code ++]
502502+ petId: 1234, // [!code ++]
503503+}); // [!code ++]
504504+```
505505+<!-- prettier-ignore-end -->
506506+<!-- prettier-ignore-start -->
507507+```js [config]
508508+export default {
509509+ input: 'hey-api/backend', // sign up at app.heyapi.dev
510510+ output: 'src/client',
511511+ plugins: [
512512+ // ...other plugins
513513+ {
514514+ examples: {
515515+ importKind: 'default',
516516+ importName: 'CatStore',
517517+ importSetup: ({ $, node }) =>
518518+ $(node.name).call(
519519+ $.object().pretty().prop('apiKey', $.literal('YOUR_API_KEY')),
520520+ ),
521521+ moduleName: '@petstore/client',
522522+ payload(operation, ctx) { // [!code ++]
523523+ const { $ } = ctx; // [!code ++]
524524+ if (operation.path === '/pet/{petId}' || operation.path === '/pet') { // [!code ++]
525525+ return $.object().pretty().prop('petId', $.literal(1234)); // [!code ++]
526526+ } // [!code ++]
527527+ }, // [!code ++]
528528+ setupName: 'client',
529529+ },
530530+ name: '@hey-api/sdk',
531531+ operations: {
532532+ containerName: 'PetStore',
533533+ strategy: 'single',
534534+ },
535535+ },
536536+ ],
537537+};
538538+```
539539+<!-- prettier-ignore-end -->
540540+541541+:::
542542+543543+### Display
544544+545545+Enabling examples does not produce visible output on its own. Examples are written into the source specification and can be consumed by documentation tools such as [Mintlify](https://kutt.it/6vrYy9) or [Scalar](https://kutt.it/skQUVd). To persist that specification, enable [Source](/openapi-ts/configuration/output#source) generation.
313546314547## API
315548
+9-1
packages/codegen-core/src/project/project.ts
···1515import type { IProject } from './types';
16161717export class Project implements IProject {
1818+ private _isPlanned = false;
1919+1820 readonly files: FileRegistry;
1921 readonly nodes = new NodeRegistry();
2022 readonly symbols = new SymbolRegistry();
···5759 this.root = path.resolve(args.root).replace(/[/\\]+$/, '');
5860 }
59616060- render(meta?: IProjectRenderMeta): ReadonlyArray<IOutput> {
6262+ plan(meta?: IProjectRenderMeta): void {
6363+ if (this._isPlanned) return;
6164 new Planner(this).plan(meta);
6565+ this._isPlanned = true;
6666+ }
6767+6868+ render(meta?: IProjectRenderMeta): ReadonlyArray<IOutput> {
6969+ if (!this._isPlanned) this.plan(meta);
6270 const files: Array<IOutput> = [];
6371 for (const file of this.files.registered()) {
6472 if (!file.external && file.finalPath && file.renderer) {
+7
packages/codegen-core/src/project/types.d.ts
···3737 /** Centralized node registry for the project. */
3838 readonly nodes: INodeRegistry;
3939 /**
4040+ * Finalizes the project structure, resolving nodes, symbols, and dependencies.
4141+ *
4242+ * @param meta Arbitrary metadata.
4343+ * @returns void
4444+ */
4545+ plan(meta?: IProjectRenderMeta): void;
4646+ /**
4047 * Produces output representations for all files in the project.
4148 *
4249 * @param meta Arbitrary metadata.
···11-import type { EnumExtensions } from '~/openApi/shared/types/openapi-spec-extensions';
11+import type { CodeSampleObject, EnumExtensions } from '~/openApi/shared/types';
2233import type { JsonSchemaDraft4 } from './json-schema-draft-4';
44import type { OpenApiV2_0_X_Nullable_Extensions } from './openapi-spec-extensions';
···584584 * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.
585585 */
586586 tags?: ReadonlyArray<string>;
587587+ /**
588588+ * A list of code samples associated with an operation.
589589+ */
590590+ 'x-codeSamples'?: ReadonlyArray<CodeSampleObject>;
587591}
588592589593/**
···11-import type { EnumExtensions } from '~/openApi/shared/types/openapi-spec-extensions';
11+import type { CodeSampleObject, EnumExtensions } from '~/openApi/shared/types';
2233/**
44 * OpenAPI Specification Extensions.
···509509 * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.
510510 */
511511 tags?: ReadonlyArray<string>;
512512+ /**
513513+ * A list of code samples associated with an operation.
514514+ */
515515+ 'x-codeSamples'?: ReadonlyArray<CodeSampleObject>;
512516}
513517514518/**
···11-import type { EnumExtensions } from '~/openApi/shared/types/openapi-spec-extensions';
11+import type { EnumExtensions } from '~/openApi/shared/types';
2233import type { MaybeArray } from '../../../types/utils';
44import type { SpecificationExtensions } from './spec';
···11+import type { CodeSampleObject } from '~/openApi/shared/types';
22+13import type { JsonSchemaDraft2020_12 } from './json-schema-draft-2020-12';
2435/**
···11031105 * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.
11041106 */
11051107 tags?: ReadonlyArray<string>;
11081108+ /**
11091109+ * A list of code samples associated with an operation.
11101110+ */
11111111+ 'x-codeSamples'?: ReadonlyArray<CodeSampleObject>;
11061112}
1107111311081114/**
···33import type { PluginClientNames, PluginValidatorNames } from '~/plugins/types';
44import type { NameTransformer } from '~/utils/naming';
5566+import type { ExamplesConfig, UserExamplesConfig } from './examples';
67import type { OperationsConfig, UserOperationsConfig } from './operations';
7889export type UserConfig = Plugin.Name<'@hey-api/sdk'> &
···2728 * @default true
2829 */
2930 client?: PluginClientNames | boolean;
3131+ /**
3232+ * Generate code examples for SDK operations and attach them to the
3333+ * input source (e.g. via `x-codeSamples`).
3434+ *
3535+ * Set to `false` to disable example generation entirely, or provide an
3636+ * object for fine-grained control over the output and post-processing.
3737+ *
3838+ * @default false
3939+ */
4040+ examples?: boolean | UserExamplesConfig;
3041 /**
3142 * Should the exports from the generated files be re-exported in the index
3243 * barrel file?
···209220 * @default true
210221 */
211222 client: PluginClientNames | false;
223223+ /**
224224+ * Configuration for generating SDK code examples.
225225+ */
226226+ examples: ExamplesConfig;
212227 /**
213228 * Should the exports from the generated files be re-exported in the index
214229 * barrel file?
···7171 }
7272 readonly '~brand' = nodeBrand;
73737474- /** Access patterns for this node. */
7575- toAccessNode?(
7676- node: this,
7777- options: AccessOptions,
7878- ctx: {
7979- /** The full chain. */
8080- chain: ReadonlyArray<TsDsl>;
8181- /** Position in the chain (0 = root). */
8282- index: number;
8383- /** Is this the leaf node? */
8484- isLeaf: boolean;
8585- /** Is this the root node? */
8686- isRoot: boolean;
8787- /** Total length of the chain. */
8888- length: number;
8989- },
9090- ): TsDsl | undefined;
9174 /** Branding property to identify the DSL class at runtime. */
9275 abstract readonly '~dsl': string & {};
9376···165148 }
166149 return this;
167150 }
151151+152152+ /** Access patterns for this node. */
153153+ toAccessNode?(
154154+ node: this,
155155+ options: AccessOptions,
156156+ ctx: {
157157+ /** The full chain. */
158158+ chain: ReadonlyArray<TsDsl>;
159159+ /** Position in the chain (0 = root). */
160160+ index: number;
161161+ /** Is this the leaf node? */
162162+ isLeaf: boolean;
163163+ /** Is this the root node? */
164164+ isRoot: boolean;
165165+ /** Total length of the chain. */
166166+ length: number;
167167+ },
168168+ ): TsDsl | undefined;
168169169170 protected $maybeId<T extends string | ts.Expression>(
170171 expr: T,
+3-1
packages/openapi-ts/src/ts-dsl/index.ts
···357357358358export type { MaybeTsDsl, TypeTsDsl } from './base';
359359export { TsDsl } from './base';
360360-export { TsDslContext } from './utils/context';
360360+export type { CallArgs } from './expr/call';
361361+export type { ExampleOptions } from './utils/context';
362362+export { ctx, TsDslContext } from './utils/context';
361363export { keywords } from './utils/keywords';
362364export { regexp } from './utils/regexp';
363365export { TypeScriptRenderer } from './utils/render';
+2-2
packages/openapi-ts/src/ts-dsl/layout/doc.ts
···44import type { MaybeArray } from '../base';
55import { TsDsl } from '../base';
66import { IdTsDsl } from '../expr/id';
77-import { TsDslContext } from '../utils/context';
77+import type { TsDslContext } from '../utils/context';
88+import { ctx } from '../utils/context';
89910type DocMaybeLazy<T> = ((ctx: TsDslContext) => T) | T;
1011export type DocFn = (d: DocTsDsl) => void;
···3132 }
32333334 apply<T extends ts.Node>(node: T): T {
3434- const ctx = new TsDslContext();
3535 const lines = this._lines.reduce((lines: Array<string>, line: DocLines) => {
3636 if (typeof line === 'function') line = line(ctx);
3737 for (const l of typeof line === 'string' ? [line] : line) {
+2-2
packages/openapi-ts/src/ts-dsl/layout/hint.ts
···44import type { MaybeArray } from '../base';
55import { TsDsl } from '../base';
66import { IdTsDsl } from '../expr/id';
77-import { TsDslContext } from '../utils/context';
77+import type { TsDslContext } from '../utils/context';
88+import { ctx } from '../utils/context';
89910type HintMaybeLazy<T> = ((ctx: TsDslContext) => T) | T;
1011export type HintFn = (d: HintTsDsl) => void;
···3132 }
32333334 apply<T extends ts.Node>(node: T): T {
3434- const ctx = new TsDslContext();
3535 const lines = this._lines.reduce(
3636 (lines: Array<string>, line: HintLines) => {
3737 if (typeof line === 'function') line = line(ctx);
+2-2
packages/openapi-ts/src/ts-dsl/layout/note.ts
···44import type { MaybeArray } from '../base';
55import { TsDsl } from '../base';
66import { IdTsDsl } from '../expr/id';
77-import { TsDslContext } from '../utils/context';
77+import type { TsDslContext } from '../utils/context';
88+import { ctx } from '../utils/context';
89910type NoteMaybeLazy<T> = ((ctx: TsDslContext) => T) | T;
1011export type NoteFn = (d: NoteTsDsl) => void;
···3132 }
32333334 apply<T extends ts.Node>(node: T): T {
3434- const ctx = new TsDslContext();
3535 const lines = this._lines.reduce(
3636 (lines: Array<string>, line: NoteLines) => {
3737 if (typeof line === 'function') line = line(ctx);
···22import type ts from 'typescript';
3344import { TsDsl } from '../base';
55-import { TsDslContext } from './context';
55+import type { TsDslContext } from './context';
66+import { ctx } from './context';
6778export type LazyThunk<T extends ts.Node> = (ctx: TsDslContext) => TsDsl<T>;
89···2223 }
23242425 toResult(): TsDsl<T> {
2525- return this._thunk(new TsDslContext());
2626+ return this._thunk(ctx);
2627 }
27282829 override toAst(): T {
+1-1
packages/openapi-ts/src/types/config.d.ts
···11+import type { Output, UserOutput } from '~/config/output';
12import type { Plugin } from '~/plugins';
23import type { PluginConfigMap } from '~/plugins/config';
34import type { PluginNames } from '~/plugins/types';
4556import type { Input, UserInput, Watch } from './input';
67import type { Logs } from './logs';
77-import type { Output, UserOutput } from './output';
88import type { Parser, UserParser } from './parser';
99import type { MaybeArray } from './utils';
1010
···44} from '@hey-api/codegen-core';
55import type ts from 'typescript';
6677+import type { MaybeArray, MaybeFunc } from '~/types/utils';
78import type { Casing, NameTransformer } from '~/utils/naming';
8999-import type { MaybeArray, MaybeFunc } from './utils';
1010+import type { SourceConfig, UserSourceConfig } from './source/types';
10111112export type Formatters = 'biome' | 'prettier';
1213···126127 */
127128 resolveModuleName?: (moduleName: string) => string | undefined;
128129 /**
130130+ * Configuration for generating a copy of the input source used to produce this output.
131131+ *
132132+ * Set to `false` to skip generating the source, or `true` to use defaults.
133133+ *
134134+ * You can also provide a configuration object to further customize behavior.
135135+ *
136136+ * @default false
137137+ */
138138+ source?: boolean | UserSourceConfig;
139139+ /**
129140 * Relative or absolute path to the tsconfig file we should use to
130141 * generate the output. If a path to tsconfig file is not provided, we
131142 * attempt to find one starting from the location of the
···219230 * Optional function to transform module specifiers.
220231 */
221232 resolveModuleName: ((moduleName: string) => string | undefined) | undefined;
233233+ /**
234234+ * Configuration for generating a copy of the input source used to produce this output.
235235+ */
236236+ source: SourceConfig;
222237 /**
223238 * The parsed TypeScript configuration used to generate the output.
224239 * If no `tsconfig` file path was provided or found, this will be `null`.
+6-1
packages/openapi-ts/src/types/utils.d.ts
···1919/**
2020 * Accepts a value, a function returning a value, or a function returning a promise of a value.
2121 */
2222-export type LazyOrAsync<T> = T | (() => T) | (() => Promise<T>);
2222+export type LazyOrAsync<T> = T | (() => MaybePromise<T>);
23232424/**
2525 * Accepts a value or a readonly array of values of type T.
···3232export type MaybeFunc<T extends (...args: Array<any>) => any> =
3333 | T
3434 | ReturnType<T>;
3535+3636+/**
3737+ * Accepts a value or a promise of a value.
3838+ */
3939+export type MaybePromise<T> = T | Promise<T>;
35403641/**
3742 * Converts all top-level Array properties to ReadonlyArray (shallow).