@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.
- package/lib/chunks/{TEQA5OM7.js → 2GJPOM5H.js} +1 -1
- package/lib/chunks/{W2NJCZOS.js → 3CR6IHZV.js} +1 -1
- package/lib/chunks/3JJEI7CJ.js +456 -0
- package/lib/chunks/{QRD3XQNU.js → 3M6ARSHY.js} +1 -1
- package/lib/chunks/3X52NOC5.js +7 -0
- package/lib/chunks/46OWEERC.js +19 -0
- package/lib/chunks/4IA3ZTGK.js +20 -0
- package/lib/chunks/{TO2KJ36N.js → 5E3L4HJN.js} +1 -1
- package/lib/chunks/5EG7SNNU.js +177 -0
- package/lib/chunks/5L3FH5WQ.js +16 -0
- package/lib/chunks/5L5WOOWA.js +7 -0
- package/lib/chunks/5ZEWVWOC.js +14 -0
- package/lib/chunks/6O3ZHZZ2.js +7 -0
- package/lib/chunks/{KEUH3PST.js → AJZAID3R.js} +1 -1
- package/lib/chunks/BD5JG7YZ.js +7 -0
- package/lib/chunks/{B6HS7CVY.js → BEOYUN4L.js} +48 -48
- package/lib/chunks/CF5YCHNA.js +7 -0
- package/lib/chunks/D27SC2KR.js +90 -0
- package/lib/chunks/{SN2EC2ZR.js → DYAVWTVD.js} +1 -1
- package/lib/chunks/E35Q6YJN.js +7 -0
- package/lib/chunks/EB2V4VMP.js +7 -0
- package/lib/chunks/{QVV2EKMT.js → FL3CA53Z.js} +1 -1
- package/lib/chunks/FVBSRIWC.js +18 -0
- package/lib/chunks/G2XR6TRU.js +67 -0
- package/lib/chunks/G3YJARF7.js +57 -0
- package/lib/chunks/GA6WYGKY.js +7 -0
- package/lib/chunks/GBRES7WG.js +5400 -0
- package/lib/chunks/GHPAE4DZ.js +20 -0
- package/lib/chunks/{SL6AH3N2.js → GJRJZYC4.js} +1 -1
- package/lib/chunks/{REBHK2JR.js → GOJNDGZT.js} +1 -1
- package/lib/chunks/HJYFWIVE.js +9 -0
- package/lib/chunks/HLZCPNF7.js +16 -0
- package/lib/chunks/KIQI3UBS.js +177 -0
- package/lib/chunks/KNPQJVYA.js +14 -0
- package/lib/chunks/L3TZNNJJ.js +13 -0
- package/lib/chunks/{34J2ZJDK.js → LUC2DWUK.js} +1 -1
- package/lib/chunks/MBHFDTYI.js +78 -0
- package/lib/chunks/NB54XRRU.js +10 -0
- package/lib/chunks/NSIQWNYL.js +7 -0
- package/lib/chunks/NWMTC4AN.js +7 -0
- package/lib/chunks/PFYPTG4G.js +7 -0
- package/lib/chunks/{5NR4DJW4.js → PVM7EVWV.js} +1 -1
- package/lib/chunks/Q5FNL2RM.js +22 -0
- package/lib/chunks/QW5MTOU2.js +22 -0
- package/lib/chunks/REFX2EKS.js +13 -0
- package/lib/chunks/S4GQKQ5G.js +7 -0
- package/lib/chunks/SGBB4RNM.js +11 -0
- package/lib/chunks/{YQW463Z7.js → SPPRUUK3.js} +1 -1
- package/lib/chunks/SQVG3CEM.js +8 -0
- package/lib/chunks/TSB6PODJ.js +13 -0
- package/lib/chunks/U2A6GEAN.js +42 -0
- package/lib/chunks/UDBINQ2S.js +42 -0
- package/lib/chunks/UKSQ7B5H.js +2268 -0
- package/lib/chunks/{QL4V2A4A.js → V3JMSQFP.js} +1 -1
- package/lib/chunks/VWSPU27A.js +30 -0
- package/lib/chunks/W62VKXGM.js +26 -0
- package/lib/chunks/WIZYCYZG.js +7 -0
- package/lib/chunks/XDK4JK3V.js +9 -0
- package/lib/chunks/{5PXRDPHV.js → YJGI7CZ2.js} +1 -1
- package/lib/chunks/YQS3OX36.js +11 -0
- package/lib/chunks/{S4MX7JPH.js → Z2AXUXMK.js} +1 -1
- package/lib/chunks/ZBL4IVEW.js +11 -0
- package/lib/chunks/ZGMTMVBB.js +19 -0
- package/lib/chunks/{RIFZSMUA.js → ZVUMPMHU.js} +1 -1
- package/lib/eject-assets/AGENTS.md +137 -0
- package/lib/eject-assets/generators/cli/docs.ts +224 -0
- package/lib/eject-assets/generators/cli/engine-source.ts +21 -0
- package/lib/eject-assets/generators/cli/index.ts +90 -0
- package/lib/eject-assets/generators/cli/render.ts +348 -0
- package/lib/eject-assets/generators/go/client.ts +57 -0
- package/lib/eject-assets/generators/go/descriptor.ts +41 -0
- package/lib/eject-assets/generators/go/index.ts +336 -0
- package/lib/eject-assets/generators/go/models.ts +179 -0
- package/lib/eject-assets/generators/go/naming.ts +49 -0
- package/lib/eject-assets/generators/go/operations.ts +296 -0
- package/lib/eject-assets/generators/go/pagination.ts +194 -0
- package/lib/eject-assets/generators/go/types.ts +60 -0
- package/lib/eject-assets/generators/mock/faker.ts +214 -0
- package/lib/eject-assets/generators/mock/index.ts +35 -0
- package/lib/eject-assets/generators/mock/render.ts +282 -0
- package/lib/eject-assets/generators/mock/sample.ts +316 -0
- package/lib/eject-assets/generators/mock/values.ts +63 -0
- package/lib/eject-assets/generators/php/client.ts +63 -0
- package/lib/eject-assets/generators/php/descriptor.ts +58 -0
- package/lib/eject-assets/generators/php/index.ts +237 -0
- package/lib/eject-assets/generators/php/models.ts +275 -0
- package/lib/eject-assets/generators/php/naming.ts +51 -0
- package/lib/eject-assets/generators/php/operations.ts +232 -0
- package/lib/eject-assets/generators/php/pagination.ts +133 -0
- package/lib/eject-assets/generators/php/types.ts +148 -0
- package/lib/eject-assets/generators/python/client.ts +123 -0
- package/lib/eject-assets/generators/python/descriptor.ts +52 -0
- package/lib/eject-assets/generators/python/index.ts +251 -0
- package/lib/eject-assets/generators/python/models.ts +242 -0
- package/lib/eject-assets/generators/python/naming.ts +46 -0
- package/lib/eject-assets/generators/python/operations.ts +147 -0
- package/lib/eject-assets/generators/python/pagination.ts +128 -0
- package/lib/eject-assets/generators/python/types.ts +54 -0
- package/lib/eject-assets/generators/swr/index.ts +37 -0
- package/lib/eject-assets/generators/swr/render.ts +78 -0
- package/lib/eject-assets/generators/tanstack-query/index.ts +48 -0
- package/lib/eject-assets/generators/tanstack-query/render.ts +346 -0
- package/lib/eject-assets/generators/transformers/index.ts +46 -0
- package/lib/eject-assets/generators/transformers/render.ts +506 -0
- package/lib/eject-assets/generators/typescript/banner.ts +35 -0
- package/lib/eject-assets/generators/typescript/client.ts +254 -0
- package/lib/eject-assets/generators/typescript/descriptor.ts +137 -0
- package/lib/eject-assets/generators/typescript/index.ts +101 -0
- package/lib/eject-assets/generators/typescript/inline-runtime.ts +135 -0
- package/lib/eject-assets/generators/typescript/operation-signature.ts +62 -0
- package/lib/eject-assets/generators/typescript/operation-types.ts +17 -0
- package/lib/eject-assets/generators/typescript/operations.ts +524 -0
- package/lib/eject-assets/generators/typescript/response-headers.ts +74 -0
- package/lib/eject-assets/generators/typescript/type-guards.ts +159 -0
- package/lib/eject-assets/generators/typescript/types.ts +172 -0
- package/lib/eject-assets/generators/zod/index.ts +32 -0
- package/lib/eject-assets/generators/zod/schemas.ts +456 -0
- package/lib/eject-assets/skills/cli-generator/SKILL.md +121 -0
- package/lib/eject-assets/skills/client-generators/SKILL.md +142 -0
- package/lib/eject-assets/skills/go-generator/SKILL.md +96 -0
- package/lib/eject-assets/skills/mock-generator/SKILL.md +50 -0
- package/lib/eject-assets/skills/php-generator/SKILL.md +112 -0
- package/lib/eject-assets/skills/python-generator/SKILL.md +110 -0
- package/lib/eject-assets/skills/swr-generator/SKILL.md +50 -0
- package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +55 -0
- package/lib/eject-assets/skills/transformers-generator/SKILL.md +47 -0
- package/lib/eject-assets/skills/typescript-generator/SKILL.md +95 -0
- package/lib/eject-assets/skills/zod-generator/SKILL.md +54 -0
- package/lib/index.js +272 -193
- package/package.json +1 -1
- package/lib/chunks/PIJLRHNN.js +0 -8
- 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
|
+
};
|