···72727373A single request schema is generated for each endpoint. It may contain a request body, parameters, and headers.
74747575-```ts
7575+::: code-group
7676+7777+```js [config]
7878+export default {
7979+ input: 'https://get.heyapi.dev/hey-api/backend',
8080+ output: 'src/client',
8181+ plugins: [
8282+ // ...other plugins
8383+ {
8484+ name: 'zod',
8585+ requests: true, // [!code ++]
8686+ },
8787+ ],
8888+};
8989+```
9090+9191+```ts [output]
7692const zData = z.object({
7793 body: z
7894 .object({
···87103});
88104```
89105106106+:::
107107+90108::: tip
91109If you need to access individual fields, you can do so using the [`.shape`](https://zod.dev/api?id=shape) API. For example, we can get the request body schema with `zData.shape.body`.
92110:::
···9711598116A single Zod schema is generated for all endpoint's responses. If the endpoint describes multiple responses, the generated schema is a union of all possible response shapes.
99117100100-```ts
118118+::: code-group
119119+120120+```js [config]
121121+export default {
122122+ input: 'https://get.heyapi.dev/hey-api/backend',
123123+ output: 'src/client',
124124+ plugins: [
125125+ // ...other plugins
126126+ {
127127+ name: 'zod',
128128+ responses: true, // [!code ++]
129129+ },
130130+ ],
131131+};
132132+```
133133+134134+```ts [output]
101135const zResponse = z.union([
102136 z.object({
103137 foo: z.string().optional(),
···107141 }),
108142]);
109143```
144144+145145+:::
110146111147You can customize the naming and casing pattern for `responses` schemas using the `.name` and `.case` options.
112148···114150115151A Zod schema is generated for every reusable definition from your input.
116152117117-```ts
153153+::: code-group
154154+155155+```js [config]
156156+export default {
157157+ input: 'https://get.heyapi.dev/hey-api/backend',
158158+ output: 'src/client',
159159+ plugins: [
160160+ // ...other plugins
161161+ {
162162+ name: 'zod',
163163+ definitions: true, // [!code ++]
164164+ },
165165+ ],
166166+};
167167+```
168168+169169+```ts [output]
118170const zFoo = z.number().int();
119171120172const zBar = z.object({
121173 bar: z.array(z.number().int()).optional(),
122174});
123175```
176176+177177+:::
124178125179You can customize the naming and casing pattern for `definitions` schemas using the `.name` and `.case` options.
126180···128182129183It's often useful to associate a schema with some additional [metadata](https://zod.dev/metadata) for documentation, code generation, AI structured outputs, form validation, and other purposes. If this is your use case, you can set `metadata` to `true` to generate additional metadata about schemas.
130184131131-```js
185185+::: code-group
186186+187187+```js [config]
132188export default {
133189 input: 'https://get.heyapi.dev/hey-api/backend',
134190 output: 'src/client',
135191 plugins: [
136192 // ...other plugins
137193 {
194194+ name: 'zod',
138195 metadata: true, // [!code ++]
196196+ },
197197+ ],
198198+};
199199+```
200200+201201+```ts [output]
202202+export const zFoo = z.string().describe('Additional metadata');
203203+```
204204+205205+:::
206206+207207+## Types
208208+209209+In addition to Zod schemas, you can generate schema-specific types. These can be generated for all schemas or for specific resources.
210210+211211+::: code-group
212212+213213+```js [config]
214214+export default {
215215+ input: 'https://get.heyapi.dev/hey-api/backend',
216216+ output: 'src/client',
217217+ plugins: [
218218+ // ...other plugins
219219+ {
139220 name: 'zod',
221221+ types: {
222222+ infer: false, // by default, no `z.infer` types [!code ++]
223223+ },
224224+ responses: {
225225+ types: {
226226+ infer: true, // `z.infer` types only for response schemas [!code ++]
227227+ },
228228+ },
140229 },
141230 ],
142231};
143232```
233233+234234+```ts [output]
235235+export type ResponseZodType = z.infer<typeof zResponse>;
236236+```
237237+238238+:::
239239+240240+You can customize the naming and casing pattern for schema-specific `types` using the `.name` and `.case` options.
144241145242## Config API
146243
···904904export const createTypeOfExpression = ({
905905 text,
906906}: {
907907- text: string | ts.TypeReferenceNode;
907907+ text: string | ts.Identifier;
908908}) => {
909909 const expression = ts.factory.createTypeOfExpression(
910910- // TODO: this crashes when passing reference, fix
911911- // TODO: https://github.com/hey-api/openapi-ts/issues/2289
912912- typeof text === 'string'
913913- ? createIdentifier({ text })
914914- : (text as unknown as ts.Identifier),
910910+ typeof text === 'string' ? createIdentifier({ text }) : text,
915911 );
916912 return expression;
917913};
+27-5
packages/openapi-ts/src/config/utils.ts
···33 ? Record<string, any>
44 : Extract<T, Record<string, any>>;
5566+type NotArray<T> = T extends any[] ? never : T;
77+type NotFunction<T> = T extends (...args: any[]) => any ? never : T;
88+type PlainObject<T> = T extends object
99+ ? NotFunction<T> extends never
1010+ ? never
1111+ : NotArray<T> extends never
1212+ ? never
1313+ : T
1414+ : never;
1515+616type MappersType<T> = {
717 boolean: T extends boolean
818 ? (value: boolean) => Partial<ObjectType<T>>
···1121 ? (value: (...args: any[]) => any) => Partial<ObjectType<T>>
1222 : never;
1323 number: T extends number ? (value: number) => Partial<ObjectType<T>> : never;
1414- object?: (value: Partial<ObjectType<T>>) => Partial<ObjectType<T>>;
2424+ object?: PlainObject<T> extends never
2525+ ? never
2626+ : (
2727+ value: Partial<PlainObject<T>>,
2828+ defaultValue: PlainObject<T>,
2929+ ) => Partial<ObjectType<T>>;
1530 string: T extends string ? (value: string) => Partial<ObjectType<T>> : never;
1631} extends infer U
1732 ? { [K in keyof U as U[K] extends never ? never : K]: U[K] }
···4560 : {
4661 mappers: MappersType<T>;
4762 }),
4848-) => ObjectType<T>;
6363+) => PlainObject<T>;
6464+6565+const isPlainObject = (value: unknown): value is Record<string, any> =>
6666+ typeof value === 'object' &&
6767+ value !== null &&
6868+ !Array.isArray(value) &&
6969+ typeof value !== 'function';
49705071const mergeResult = <T>(
5172 result: ObjectType<T>,
···96117 }
97118 break;
98119 case 'object':
9999- if (value !== null) {
120120+ if (isPlainObject(value)) {
100121 if (
101122 mappers &&
102123 'object' in mappers &&
···104125 ) {
105126 const mapper = mappers.object as (
106127 value: Record<string, any>,
128128+ defaultValue: ObjectType<any>,
107129 ) => Partial<ObjectType<any>>;
108108- result = mergeResult(result, mapper(value));
130130+ result = mergeResult(result, mapper(value, defaultValue));
109131 } else {
110132 result = mergeResult(result, value);
111133 }
···113135 break;
114136 }
115137116116- return result;
138138+ return result as any;
117139};
+52-1
packages/openapi-ts/src/generate/file/index.ts
···2020 Identifiers,
2121 Namespace,
2222 NodeInfo,
2323+ NodeReference,
2324} from './types';
2424-2525export class GeneratedFile {
2626 private _case: StringCase | undefined;
2727 /**
···5252 * ```
5353 */
5454 private names: Record<string, string> = {};
5555+ /**
5656+ * Another approach for named nodes, with proper support for renaming. Keys
5757+ * are node IDs and values are an array of references for given ID.
5858+ */
5959+ private nodeReferences: Record<string, Array<NodeReference>> = {};
5560 /**
5661 * Text value from node is kept in sync with `names`.
5762 *
6363+ * @deprecated
5864 * @example
5965 * ```js
6066 * {
···6773 * }
6874 * ```
6975 */
7676+ // TODO: nodes can be possibly replaced with `nodeReferences`, i.e. keep
7777+ // the name `nodes` and rewrite their functionality
7078 private nodes: Record<string, NodeInfo> = {};
71797280 /**
···117125 }
118126119127 /**
128128+ * Adds a reference node for a name. This can be used later to rename
129129+ * identifiers.
130130+ */
131131+ public addNodeReference<T>(
132132+ id: string,
133133+ node: Pick<NodeReference<T>, 'factory'>,
134134+ ): T {
135135+ if (!this.nodeReferences[id]) {
136136+ this.nodeReferences[id] = [];
137137+ }
138138+ const result = node.factory(this.names[id] ?? '');
139139+ this.nodeReferences[id].push({
140140+ factory: node.factory,
141141+ node: result as void,
142142+ });
143143+ return result;
144144+ }
145145+146146+ /**
120147 * Prevents a specific identifier from being created. This is useful for
121148 * transformers where we know a certain transformer won't be needed, and
122149 * we want to avoid attempting to create since we know it won't happen.
···165192 /**
166193 * Returns a node. If node doesn't exist, creates a blank reference.
167194 *
195195+ * @deprecated
168196 * @param id Node ID.
169197 * @returns Information about the node.
170198 */
···376404 /**
377405 * Inserts or updates a node.
378406 *
407407+ * @deprecated
379408 * @param id Node ID.
380409 * @param args Information about the node.
381410 * @returns Updated node.
···403432 this.nodes[id].exported = args.exported;
404433 }
405434 return this.nodes[id];
435435+ }
436436+437437+ /**
438438+ * Updates collected reference nodes for a name with the latest value.
439439+ *
440440+ * @param id Node ID.
441441+ * @param name Updated name for the nodes.
442442+ * @returns noop
443443+ */
444444+ public updateNodeReferences(id: string, name: string): void {
445445+ if (!this.nodeReferences[id]) {
446446+ return;
447447+ }
448448+ const finalName = getUniqueComponentName({
449449+ base: ensureValidIdentifier(name),
450450+ components: Object.values(this.names),
451451+ });
452452+ this.names[id] = finalName;
453453+ for (const node of this.nodeReferences[id]) {
454454+ const nextNode = node.factory(finalName);
455455+ Object.assign(node.node as unknown as object, nextNode);
456456+ }
406457 }
407458408459 public write(separator = '\n', tsConfig: ts.ParsedCommandLine | null = null) {
+14
packages/openapi-ts/src/generate/file/types.d.ts
···7575 */
7676 node: ts.TypeReferenceNode;
7777};
7878+7979+export type NodeReference<T = void> = {
8080+ /**
8181+ * Factory function that creates the node reference.
8282+ *
8383+ * @param name Identifier name.
8484+ * @returns Reference to the node object.
8585+ */
8686+ factory: (name: string) => T;
8787+ /**
8888+ * Reference to the node object.
8989+ */
9090+ node: T;
9191+};
···11-export { compiler } from '../../../compiler';
21export { defaultConfig, defineConfig } from './config';
32export type { HeyApiTransformersPlugin } from './types';