@redocly/cli 2.47.0 → 2.48.0

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.
Files changed (132) hide show
  1. package/lib/chunks/{TEQA5OM7.js → 2GJPOM5H.js} +1 -1
  2. package/lib/chunks/{W2NJCZOS.js → 3CR6IHZV.js} +1 -1
  3. package/lib/chunks/3JJEI7CJ.js +456 -0
  4. package/lib/chunks/{QRD3XQNU.js → 3M6ARSHY.js} +1 -1
  5. package/lib/chunks/3X52NOC5.js +7 -0
  6. package/lib/chunks/46OWEERC.js +19 -0
  7. package/lib/chunks/4IA3ZTGK.js +20 -0
  8. package/lib/chunks/{TO2KJ36N.js → 5E3L4HJN.js} +1 -1
  9. package/lib/chunks/5EG7SNNU.js +177 -0
  10. package/lib/chunks/5L3FH5WQ.js +16 -0
  11. package/lib/chunks/5L5WOOWA.js +7 -0
  12. package/lib/chunks/5ZEWVWOC.js +14 -0
  13. package/lib/chunks/6O3ZHZZ2.js +7 -0
  14. package/lib/chunks/{KEUH3PST.js → AJZAID3R.js} +1 -1
  15. package/lib/chunks/BD5JG7YZ.js +7 -0
  16. package/lib/chunks/{B6HS7CVY.js → BEOYUN4L.js} +48 -48
  17. package/lib/chunks/CF5YCHNA.js +7 -0
  18. package/lib/chunks/D27SC2KR.js +90 -0
  19. package/lib/chunks/{SN2EC2ZR.js → DYAVWTVD.js} +1 -1
  20. package/lib/chunks/E35Q6YJN.js +7 -0
  21. package/lib/chunks/EB2V4VMP.js +7 -0
  22. package/lib/chunks/{QVV2EKMT.js → FL3CA53Z.js} +1 -1
  23. package/lib/chunks/FVBSRIWC.js +18 -0
  24. package/lib/chunks/G2XR6TRU.js +67 -0
  25. package/lib/chunks/G3YJARF7.js +57 -0
  26. package/lib/chunks/GA6WYGKY.js +7 -0
  27. package/lib/chunks/GBRES7WG.js +5400 -0
  28. package/lib/chunks/GHPAE4DZ.js +20 -0
  29. package/lib/chunks/{SL6AH3N2.js → GJRJZYC4.js} +1 -1
  30. package/lib/chunks/{REBHK2JR.js → GOJNDGZT.js} +1 -1
  31. package/lib/chunks/HJYFWIVE.js +9 -0
  32. package/lib/chunks/HLZCPNF7.js +16 -0
  33. package/lib/chunks/KIQI3UBS.js +177 -0
  34. package/lib/chunks/KNPQJVYA.js +14 -0
  35. package/lib/chunks/L3TZNNJJ.js +13 -0
  36. package/lib/chunks/{34J2ZJDK.js → LUC2DWUK.js} +1 -1
  37. package/lib/chunks/MBHFDTYI.js +78 -0
  38. package/lib/chunks/NB54XRRU.js +10 -0
  39. package/lib/chunks/NSIQWNYL.js +7 -0
  40. package/lib/chunks/NWMTC4AN.js +7 -0
  41. package/lib/chunks/PFYPTG4G.js +7 -0
  42. package/lib/chunks/{5NR4DJW4.js → PVM7EVWV.js} +1 -1
  43. package/lib/chunks/Q5FNL2RM.js +22 -0
  44. package/lib/chunks/QW5MTOU2.js +22 -0
  45. package/lib/chunks/REFX2EKS.js +13 -0
  46. package/lib/chunks/S4GQKQ5G.js +7 -0
  47. package/lib/chunks/SGBB4RNM.js +11 -0
  48. package/lib/chunks/{YQW463Z7.js → SPPRUUK3.js} +1 -1
  49. package/lib/chunks/SQVG3CEM.js +8 -0
  50. package/lib/chunks/TSB6PODJ.js +13 -0
  51. package/lib/chunks/U2A6GEAN.js +42 -0
  52. package/lib/chunks/UDBINQ2S.js +42 -0
  53. package/lib/chunks/UKSQ7B5H.js +2268 -0
  54. package/lib/chunks/{QL4V2A4A.js → V3JMSQFP.js} +1 -1
  55. package/lib/chunks/VWSPU27A.js +30 -0
  56. package/lib/chunks/W62VKXGM.js +26 -0
  57. package/lib/chunks/WIZYCYZG.js +7 -0
  58. package/lib/chunks/XDK4JK3V.js +9 -0
  59. package/lib/chunks/{5PXRDPHV.js → YJGI7CZ2.js} +1 -1
  60. package/lib/chunks/YQS3OX36.js +11 -0
  61. package/lib/chunks/{S4MX7JPH.js → Z2AXUXMK.js} +1 -1
  62. package/lib/chunks/ZBL4IVEW.js +11 -0
  63. package/lib/chunks/ZGMTMVBB.js +19 -0
  64. package/lib/chunks/{RIFZSMUA.js → ZVUMPMHU.js} +1 -1
  65. package/lib/eject-assets/AGENTS.md +137 -0
  66. package/lib/eject-assets/generators/cli/docs.ts +224 -0
  67. package/lib/eject-assets/generators/cli/engine-source.ts +21 -0
  68. package/lib/eject-assets/generators/cli/index.ts +90 -0
  69. package/lib/eject-assets/generators/cli/render.ts +348 -0
  70. package/lib/eject-assets/generators/go/client.ts +57 -0
  71. package/lib/eject-assets/generators/go/descriptor.ts +41 -0
  72. package/lib/eject-assets/generators/go/index.ts +336 -0
  73. package/lib/eject-assets/generators/go/models.ts +179 -0
  74. package/lib/eject-assets/generators/go/naming.ts +49 -0
  75. package/lib/eject-assets/generators/go/operations.ts +296 -0
  76. package/lib/eject-assets/generators/go/pagination.ts +194 -0
  77. package/lib/eject-assets/generators/go/types.ts +60 -0
  78. package/lib/eject-assets/generators/mock/faker.ts +214 -0
  79. package/lib/eject-assets/generators/mock/index.ts +35 -0
  80. package/lib/eject-assets/generators/mock/render.ts +282 -0
  81. package/lib/eject-assets/generators/mock/sample.ts +316 -0
  82. package/lib/eject-assets/generators/mock/values.ts +63 -0
  83. package/lib/eject-assets/generators/php/client.ts +63 -0
  84. package/lib/eject-assets/generators/php/descriptor.ts +58 -0
  85. package/lib/eject-assets/generators/php/index.ts +237 -0
  86. package/lib/eject-assets/generators/php/models.ts +275 -0
  87. package/lib/eject-assets/generators/php/naming.ts +51 -0
  88. package/lib/eject-assets/generators/php/operations.ts +232 -0
  89. package/lib/eject-assets/generators/php/pagination.ts +133 -0
  90. package/lib/eject-assets/generators/php/types.ts +148 -0
  91. package/lib/eject-assets/generators/python/client.ts +123 -0
  92. package/lib/eject-assets/generators/python/descriptor.ts +52 -0
  93. package/lib/eject-assets/generators/python/index.ts +251 -0
  94. package/lib/eject-assets/generators/python/models.ts +242 -0
  95. package/lib/eject-assets/generators/python/naming.ts +46 -0
  96. package/lib/eject-assets/generators/python/operations.ts +147 -0
  97. package/lib/eject-assets/generators/python/pagination.ts +128 -0
  98. package/lib/eject-assets/generators/python/types.ts +54 -0
  99. package/lib/eject-assets/generators/swr/index.ts +37 -0
  100. package/lib/eject-assets/generators/swr/render.ts +78 -0
  101. package/lib/eject-assets/generators/tanstack-query/index.ts +48 -0
  102. package/lib/eject-assets/generators/tanstack-query/render.ts +346 -0
  103. package/lib/eject-assets/generators/transformers/index.ts +46 -0
  104. package/lib/eject-assets/generators/transformers/render.ts +506 -0
  105. package/lib/eject-assets/generators/typescript/banner.ts +35 -0
  106. package/lib/eject-assets/generators/typescript/client.ts +254 -0
  107. package/lib/eject-assets/generators/typescript/descriptor.ts +137 -0
  108. package/lib/eject-assets/generators/typescript/index.ts +101 -0
  109. package/lib/eject-assets/generators/typescript/inline-runtime.ts +135 -0
  110. package/lib/eject-assets/generators/typescript/operation-signature.ts +62 -0
  111. package/lib/eject-assets/generators/typescript/operation-types.ts +17 -0
  112. package/lib/eject-assets/generators/typescript/operations.ts +524 -0
  113. package/lib/eject-assets/generators/typescript/response-headers.ts +74 -0
  114. package/lib/eject-assets/generators/typescript/type-guards.ts +159 -0
  115. package/lib/eject-assets/generators/typescript/types.ts +172 -0
  116. package/lib/eject-assets/generators/zod/index.ts +32 -0
  117. package/lib/eject-assets/generators/zod/schemas.ts +456 -0
  118. package/lib/eject-assets/skills/cli-generator/SKILL.md +121 -0
  119. package/lib/eject-assets/skills/client-generators/SKILL.md +142 -0
  120. package/lib/eject-assets/skills/go-generator/SKILL.md +96 -0
  121. package/lib/eject-assets/skills/mock-generator/SKILL.md +50 -0
  122. package/lib/eject-assets/skills/php-generator/SKILL.md +112 -0
  123. package/lib/eject-assets/skills/python-generator/SKILL.md +110 -0
  124. package/lib/eject-assets/skills/swr-generator/SKILL.md +50 -0
  125. package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +55 -0
  126. package/lib/eject-assets/skills/transformers-generator/SKILL.md +47 -0
  127. package/lib/eject-assets/skills/typescript-generator/SKILL.md +95 -0
  128. package/lib/eject-assets/skills/zod-generator/SKILL.md +54 -0
  129. package/lib/index.js +272 -193
  130. package/package.json +1 -1
  131. package/lib/chunks/PIJLRHNN.js +0 -8
  132. package/lib/chunks/Y5HLCNL5.js +0 -2542
@@ -0,0 +1,159 @@
1
+ // Ejected from @redocly/client-generator@0.4.0 — the built-in "typescript" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator typescript --update`.
5
+ import type { DiscriminatorModel, NamedSchemaModel, SchemaModel } from '@redocly/client-generator';
6
+
7
+ /**
8
+ * A discriminated union we can emit guards for, found while walking the schema
9
+ * tree. `label` is the guard's `value` parameter type — the union's name for a
10
+ * top-level union (`MenuItem`), the inline member union (`SuccessItem | ErrorItem`)
11
+ * for one nested inside another schema.
12
+ */
13
+ type UnionSite = {
14
+ union: Extract<SchemaModel, { kind: 'union' }>;
15
+ label: string;
16
+ };
17
+
18
+ /** `is<Member>(value): value is <Member>` guards for every discriminated union (explicit or implicit). */
19
+ export function renderTypeGuards(schemas: NamedSchemaModel[]): string {
20
+ const byName = new Map(schemas.map((s) => [s.name, s.schema] as const));
21
+ const blocks: string[] = [];
22
+ const emitted = new Set<string>();
23
+ for (const named of schemas) {
24
+ for (const site of collectUnionSites(named)) {
25
+ const discriminator =
26
+ site.union.discriminator ?? detectImplicitDiscriminator(site.union, byName);
27
+ if (!discriminator) continue;
28
+ const valuesByTarget = new Map<string, string[]>();
29
+ for (const entry of discriminator.mapping) {
30
+ if (!byName.has(entry.schemaName)) continue;
31
+ const existing = valuesByTarget.get(entry.schemaName);
32
+ if (existing) existing.push(entry.value);
33
+ else valuesByTarget.set(entry.schemaName, [entry.value]);
34
+ }
35
+ for (const [schemaName, values] of valuesByTarget) {
36
+ const guardName = `is${schemaName}`;
37
+ if (emitted.has(guardName)) continue;
38
+ emitted.add(guardName);
39
+ const access = `(value as Record<string, unknown>)[${JSON.stringify(discriminator.propertyName)}]`;
40
+ const check =
41
+ values.length === 1
42
+ ? `${access} === ${JSON.stringify(values[0])}`
43
+ : `([${values.map((value) => JSON.stringify(value)).join(', ')}] as readonly unknown[]).includes(${access})`;
44
+ blocks.push(
45
+ [
46
+ '/**',
47
+ ` * Narrow a \`${site.label}\` to \`${schemaName}\` via its \`${discriminator.propertyName}\` discriminant.`,
48
+ ' */',
49
+ `export function ${guardName}(value: ${site.label}): value is ${schemaName} {`,
50
+ ` return ${check};`,
51
+ '}',
52
+ ].join('\n')
53
+ );
54
+ }
55
+ }
56
+ }
57
+ return blocks.join('\n\n');
58
+ }
59
+
60
+ /**
61
+ * The discriminated-union sites reachable from a named schema, in a stable order:
62
+ * the schema itself (when it is a union), then any nested unions found by walking
63
+ * its tree. A top-level union keeps its name as the guard parameter; nested unions
64
+ * use their inline member union.
65
+ */
66
+ function collectUnionSites(named: NamedSchemaModel): UnionSite[] {
67
+ const sites: UnionSite[] = [];
68
+ const root = named.schema;
69
+ if (root.kind === 'union') {
70
+ sites.push({ union: root, label: named.name });
71
+ for (const member of root.members) collectNestedSites(member, sites);
72
+ } else {
73
+ collectNestedSites(root, sites);
74
+ }
75
+ return sites;
76
+ }
77
+
78
+ /** Walk a schema subtree, recording each nested all-named-ref union as a site. */
79
+ function collectNestedSites(schema: SchemaModel, sites: UnionSite[]): void {
80
+ switch (schema.kind) {
81
+ case 'union': {
82
+ const names = schema.members.map((m) => (m.kind === 'ref' ? m.name : undefined));
83
+ if (names.every((n): n is string => n !== undefined)) {
84
+ sites.push({ union: schema, label: names.join(' | ') });
85
+ }
86
+ for (const member of schema.members) collectNestedSites(member, sites);
87
+ break;
88
+ }
89
+ case 'array':
90
+ collectNestedSites(schema.items, sites);
91
+ break;
92
+ case 'record':
93
+ collectNestedSites(schema.value, sites);
94
+ break;
95
+ case 'object':
96
+ for (const prop of schema.properties) collectNestedSites(prop.schema, sites);
97
+ break;
98
+ case 'intersection':
99
+ for (const member of schema.members) collectNestedSites(member, sites);
100
+ break;
101
+ // scalar / literal / enum / ref / null / unknown / omit have no nested unions.
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Detect an implicit discriminator: every member is a ref to a named schema,
107
+ * and they all pin one shared property to a distinct string literal. Returns
108
+ * `undefined` if no such property exists (so the union is left without guards).
109
+ */
110
+ function detectImplicitDiscriminator(
111
+ union: Extract<SchemaModel, { kind: 'union' }>,
112
+ byName: Map<string, SchemaModel>
113
+ ): DiscriminatorModel | undefined {
114
+ const memberNames: string[] = [];
115
+ for (const member of union.members) {
116
+ if (member.kind !== 'ref') return undefined;
117
+ const target = byName.get(member.name);
118
+ if (!target) return undefined;
119
+ memberNames.push(member.name);
120
+ }
121
+ if (memberNames.length < 2) return undefined;
122
+
123
+ const literalsPerMember = memberNames.map((name) => literalPropsOf(byName.get(name)!));
124
+
125
+ for (const propName of Object.keys(literalsPerMember[0])) {
126
+ if (!literalsPerMember.every((props) => propName in props)) continue;
127
+ const values = literalsPerMember.map((props) => props[propName]);
128
+ if (!values.every((v): v is string => typeof v === 'string')) continue;
129
+ if (new Set(values).size !== values.length) continue;
130
+ return {
131
+ propertyName: propName,
132
+ mapping: memberNames.map((name, i) => ({
133
+ value: values[i] as string,
134
+ schemaName: name,
135
+ })),
136
+ };
137
+ }
138
+ return undefined;
139
+ }
140
+
141
+ /**
142
+ * Collect a schema's literal-valued properties (name → const value), descending
143
+ * through `intersection` members (the shape `allOf` produces). Only inline
144
+ * object/intersection members are inspected; nested refs are not resolved.
145
+ */
146
+ function literalPropsOf(schema: SchemaModel): Record<string, string | number | boolean> {
147
+ const out: Record<string, string | number | boolean> = {};
148
+ const collect = (s: SchemaModel): void => {
149
+ if (s.kind === 'object') {
150
+ for (const prop of s.properties) {
151
+ if (prop.schema.kind === 'literal') out[prop.name] = prop.schema.value;
152
+ }
153
+ } else if (s.kind === 'intersection') {
154
+ for (const member of s.members) collect(member);
155
+ }
156
+ };
157
+ collect(schema);
158
+ return out;
159
+ }
@@ -0,0 +1,172 @@
1
+ // Ejected from @redocly/client-generator@0.4.0 — the built-in "typescript" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator typescript --update`.
5
+ // TypeScript TYPES as source text: pure string logic over the IR, no
6
+ // `typescript` import. Formatting contract: 4-space indent, double-quoted
7
+ // literals, compound members parenthesized inside unions/intersections/arrays.
8
+
9
+ import {
10
+ type DateType,
11
+ type NamedSchemaModel,
12
+ type PropertyModel,
13
+ type ScalarKind,
14
+ type SchemaMetadata,
15
+ type SchemaModel,
16
+ } from '@redocly/client-generator';
17
+ import {
18
+ escapeJsDoc,
19
+ isIdentifier,
20
+ jsdocText,
21
+ safeIdent,
22
+ } from '@redocly/client-generator/printers/typescript';
23
+
24
+ const INDENT = ' ';
25
+
26
+ /** A JSDoc block (description + metadata tags) as indented lines, or [] when empty. */
27
+ export function tsJsdoc(
28
+ text: string | undefined,
29
+ metadata: SchemaMetadata | undefined,
30
+ indent: string
31
+ ): string[] {
32
+ const body = jsdocText(text, metadata);
33
+ if (body === undefined) return [];
34
+ return [
35
+ `${indent}/**`,
36
+ ...escapeJsDoc(body)
37
+ .split('\n')
38
+ .map((line) => `${indent} * ${line}`.replace(/ +$/, '')),
39
+ `${indent} */`,
40
+ ];
41
+ }
42
+
43
+ function literalType(value: string | number | boolean): string {
44
+ return typeof value === 'string' ? JSON.stringify(value) : String(value);
45
+ }
46
+
47
+ function scalarType(
48
+ kind: ScalarKind,
49
+ metadata: SchemaMetadata | undefined,
50
+ dateType: DateType
51
+ ): string {
52
+ switch (kind) {
53
+ case 'string':
54
+ if (metadata?.format === 'binary') return 'Blob';
55
+ if (
56
+ dateType === 'Date' &&
57
+ (metadata?.format === 'date-time' || metadata?.format === 'date')
58
+ ) {
59
+ return 'Date';
60
+ }
61
+ return 'string';
62
+ case 'number':
63
+ case 'integer':
64
+ return 'number';
65
+ case 'boolean':
66
+ return 'boolean';
67
+ }
68
+ }
69
+
70
+ /** True when the rendered type needs parentheses as an array element / intersection member. */
71
+ function isCompound(schema: SchemaModel): boolean {
72
+ return (
73
+ schema.kind === 'union' ||
74
+ schema.kind === 'intersection' ||
75
+ (schema.kind === 'enum' && schema.values.length > 1)
76
+ );
77
+ }
78
+
79
+ /** The TypeScript type for an IR schema, rendered at `indent` (the containing line's indent). */
80
+ export function tsType(schema: SchemaModel, dateType: DateType = 'string', indent = ''): string {
81
+ switch (schema.kind) {
82
+ case 'scalar':
83
+ return scalarType(schema.scalar, schema.metadata, dateType);
84
+ case 'ref':
85
+ return schema.name;
86
+ case 'literal':
87
+ return literalType(schema.value);
88
+ case 'enum':
89
+ return schema.values.map(literalType).join(' | ');
90
+ case 'null':
91
+ return 'null';
92
+ case 'unknown':
93
+ return 'unknown';
94
+ case 'array': {
95
+ const element = tsType(schema.items, dateType, indent);
96
+ return isCompound(schema.items) ? `(${element})[]` : `${element}[]`;
97
+ }
98
+ case 'record':
99
+ return `Record<string, ${tsType(schema.value, dateType, indent)}>`;
100
+ case 'object': {
101
+ if (schema.properties.length === 0) return '{}';
102
+ const inner = indent + INDENT;
103
+ const lines = schema.properties.flatMap((property) =>
104
+ propertyLines(property, dateType, inner)
105
+ );
106
+ return `{\n${lines.join('\n')}\n${indent}}`;
107
+ }
108
+ case 'union':
109
+ return schema.members
110
+ .map((member) => {
111
+ const rendered = tsType(member, dateType, indent);
112
+ return isCompound(member) ? `(${rendered})` : rendered;
113
+ })
114
+ .join(' | ');
115
+ case 'intersection':
116
+ return schema.members
117
+ .map((member) => {
118
+ const rendered = tsType(member, dateType, indent);
119
+ return isCompound(member) ? `(${rendered})` : rendered;
120
+ })
121
+ .join(' & ');
122
+ case 'omit':
123
+ return `Omit<${schema.base}, ${schema.keys.map((key) => JSON.stringify(key)).join(' | ')}>`;
124
+ }
125
+ }
126
+
127
+ function propertyLines(property: PropertyModel, dateType: DateType, indent: string): string[] {
128
+ const name = safeIdent(property.name);
129
+ const readonly = property.readOnly ? 'readonly ' : '';
130
+ const optional = property.required ? '' : '?';
131
+ const type = tsType(property.schema, dateType, indent);
132
+ return [
133
+ ...tsJsdoc(property.description, property.schema.metadata, indent),
134
+ `${indent}${readonly}${name}${optional}: ${type};`,
135
+ ];
136
+ }
137
+
138
+ /**
139
+ * For a named **string** enum whose values are all valid identifiers, the runtime
140
+ * companion `export const X = { a: "a", … } as const;` (cohabiting with the type).
141
+ */
142
+ function enumConstLines(named: NamedSchemaModel): string[] {
143
+ const schema = named.schema;
144
+ if (schema.kind !== 'enum' || schema.scalar !== 'string') return [];
145
+ if (!schema.values.every((value) => typeof value === 'string' && isIdentifier(value))) return [];
146
+ return [
147
+ `export const ${named.name} = {`,
148
+ ...schema.values.map(
149
+ (value, index) =>
150
+ `${INDENT}${value}: ${JSON.stringify(value)}${index === schema.values.length - 1 ? '' : ','}`
151
+ ),
152
+ '} as const;',
153
+ ];
154
+ }
155
+
156
+ /** The model type aliases (with JSDoc and enum const companions), blank-line separated. */
157
+ export function renderTypeAliases(
158
+ schemas: NamedSchemaModel[],
159
+ dateType: DateType = 'string'
160
+ ): string {
161
+ const blocks: string[] = [];
162
+ for (const named of schemas) {
163
+ const lines = [
164
+ ...tsJsdoc(named.schema.description ?? named.description, named.schema.metadata, ''),
165
+ `export type ${named.name} = ${tsType(named.schema, dateType)};`,
166
+ ];
167
+ blocks.push(lines.join('\n'));
168
+ const constCompanion = enumConstLines(named);
169
+ if (constCompanion.length > 0) blocks.push(constCompanion.join('\n'));
170
+ }
171
+ return blocks.join('\n\n');
172
+ }
@@ -0,0 +1,32 @@
1
+ // Ejected from @redocly/client-generator@0.4.0 — the built-in "zod" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator zod --update`.
5
+ import type { Generator } from '@redocly/client-generator';
6
+ import { join } from 'node:path';
7
+
8
+ import { renderZodModule } from './schemas.ts';
9
+
10
+ /**
11
+ * The zod generator: a standalone `<stem>.zod.ts` module of Zod schemas (one
12
+ * `export const <Name>Schema` per IR named schema), plus runtime validation for
13
+ * the client — the `operationSchemas` request/response map and the `zodValidation`
14
+ * middleware (`use(zodValidation())`). The sdk client stays dependency-free —
15
+ * zod is the consumer's peer, and the module imports nothing from the client.
16
+ *
17
+ * Output-mode-agnostic: emits a single module beside the client regardless of
18
+ * how the sdk partitions its files. Emits nothing when the model has neither
19
+ * named schemas nor JSON operation bodies.
20
+ */
21
+ export const zodGenerator: Generator = ({ model, output, banner }) => {
22
+ const content = renderZodModule(model);
23
+ if (content === '') return [];
24
+ const header = banner.map((line) => `// ${line}`).join('\n');
25
+ return [{ path: join(output.dir, `${output.stem}.zod.ts`), content: `${header}\n\n${content}` }];
26
+ };
27
+
28
+ export default {
29
+ name: 'zod',
30
+ run: zodGenerator,
31
+ requiresGenerator: '^0.4.0',
32
+ };