@kubb/ast 5.0.0-alpha.31 → 5.0.0-alpha.33
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/dist/index.cjs +1632 -1066
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +33 -3
- package/dist/index.js +1594 -1066
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-z-5U8NoF.d.ts → visitor-CJMIoAE3.d.ts} +893 -61
- package/package.json +1 -1
- package/src/constants.ts +9 -1
- package/src/factory.ts +350 -24
- package/src/guards.ts +17 -4
- package/src/index.ts +24 -5
- package/src/mocks.ts +6 -6
- package/src/nodes/base.ts +14 -2
- package/src/nodes/code.ts +304 -0
- package/src/nodes/file.ts +230 -0
- package/src/nodes/function.ts +35 -17
- package/src/nodes/index.ts +32 -7
- package/src/nodes/output.ts +26 -0
- package/src/nodes/root.ts +9 -8
- package/src/refs.ts +5 -5
- package/src/types.ts +17 -2
- package/src/utils.ts +208 -19
- package/src/visitor.ts +52 -25
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
2
23
|
//#endregion
|
|
24
|
+
let node_crypto = require("node:crypto");
|
|
25
|
+
let node_path = require("node:path");
|
|
26
|
+
node_path = __toESM(node_path);
|
|
3
27
|
//#region src/constants.ts
|
|
4
28
|
const visitorDepths = {
|
|
5
29
|
shallow: "shallow",
|
|
6
30
|
deep: "deep"
|
|
7
31
|
};
|
|
32
|
+
const nodeKinds = {
|
|
33
|
+
input: "Input",
|
|
34
|
+
output: "Output",
|
|
35
|
+
operation: "Operation",
|
|
36
|
+
schema: "Schema",
|
|
37
|
+
property: "Property",
|
|
38
|
+
parameter: "Parameter",
|
|
39
|
+
response: "Response",
|
|
40
|
+
functionParameter: "FunctionParameter",
|
|
41
|
+
parameterGroup: "ParameterGroup",
|
|
42
|
+
functionParameters: "FunctionParameters",
|
|
43
|
+
type: "Type",
|
|
44
|
+
file: "File",
|
|
45
|
+
import: "Import",
|
|
46
|
+
export: "Export",
|
|
47
|
+
source: "Source",
|
|
48
|
+
text: "Text",
|
|
49
|
+
break: "Break"
|
|
50
|
+
};
|
|
8
51
|
/**
|
|
9
52
|
* Canonical schema type strings used by AST schema nodes.
|
|
10
53
|
*
|
|
@@ -92,1332 +135,1855 @@ const mediaTypes = {
|
|
|
92
135
|
videoMp4: "video/mp4"
|
|
93
136
|
};
|
|
94
137
|
//#endregion
|
|
95
|
-
//#region src/
|
|
138
|
+
//#region ../../internals/utils/src/casing.ts
|
|
96
139
|
/**
|
|
97
|
-
*
|
|
140
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
141
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
142
|
+
* and capitalizes each word according to `pascal`.
|
|
98
143
|
*
|
|
99
|
-
*
|
|
100
|
-
* - `nullish` is set for non-required, nullable schemas.
|
|
144
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
101
145
|
*/
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
146
|
+
function toCamelOrPascal(text, pascal) {
|
|
147
|
+
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
148
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
149
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
150
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
151
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
109
152
|
}
|
|
110
153
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* ```ts
|
|
115
|
-
* const root = createRoot()
|
|
116
|
-
* // { kind: 'Root', schemas: [], operations: [] }
|
|
117
|
-
* ```
|
|
154
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
155
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
156
|
+
* Segments are joined with `/` to form a file path.
|
|
118
157
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* const root = createRoot({ schemas: [petSchema] })
|
|
122
|
-
* // keeps default operations: []
|
|
123
|
-
* ```
|
|
158
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
159
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
124
160
|
*/
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
operations: [],
|
|
129
|
-
...overrides,
|
|
130
|
-
kind: "Root"
|
|
131
|
-
};
|
|
161
|
+
function applyToFileParts(text, transformPart) {
|
|
162
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
163
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
132
164
|
}
|
|
133
165
|
/**
|
|
134
|
-
*
|
|
166
|
+
* Converts `text` to camelCase.
|
|
167
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
135
168
|
*
|
|
136
169
|
* @example
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
170
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
171
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
172
|
+
*/
|
|
173
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
174
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
175
|
+
prefix,
|
|
176
|
+
suffix
|
|
177
|
+
} : {}));
|
|
178
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Converts `text` to PascalCase.
|
|
182
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
145
183
|
*
|
|
146
184
|
* @example
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* operationId: 'findPets',
|
|
150
|
-
* method: 'GET',
|
|
151
|
-
* path: '/pet/findByStatus',
|
|
152
|
-
* tags: ['pet'],
|
|
153
|
-
* })
|
|
154
|
-
* ```
|
|
185
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
186
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
155
187
|
*/
|
|
156
|
-
function
|
|
157
|
-
return {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
kind: "Operation"
|
|
163
|
-
};
|
|
188
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
189
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
190
|
+
prefix,
|
|
191
|
+
suffix
|
|
192
|
+
}) : camelCase(part));
|
|
193
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
164
194
|
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region ../../internals/utils/src/string.ts
|
|
165
197
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
198
|
+
* Strips the file extension from a path or file name.
|
|
199
|
+
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* trimExtName('petStore.ts') // 'petStore'
|
|
203
|
+
* trimExtName('/src/models/pet.ts') // '/src/models/pet'
|
|
204
|
+
* trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
|
|
205
|
+
* trimExtName('noExtension') // 'noExtension'
|
|
169
206
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
bigint: "bigint",
|
|
175
|
-
boolean: "boolean",
|
|
176
|
-
null: "null",
|
|
177
|
-
any: "any",
|
|
178
|
-
unknown: "unknown",
|
|
179
|
-
void: "void",
|
|
180
|
-
never: "never",
|
|
181
|
-
object: "object",
|
|
182
|
-
array: "array",
|
|
183
|
-
date: "date",
|
|
184
|
-
uuid: "string",
|
|
185
|
-
email: "string",
|
|
186
|
-
url: "string",
|
|
187
|
-
datetime: "string",
|
|
188
|
-
time: "string"
|
|
189
|
-
};
|
|
190
|
-
function createSchema(props) {
|
|
191
|
-
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
192
|
-
if (props["type"] === "object") return {
|
|
193
|
-
properties: [],
|
|
194
|
-
primitive: "object",
|
|
195
|
-
...props,
|
|
196
|
-
kind: "Schema"
|
|
197
|
-
};
|
|
198
|
-
return {
|
|
199
|
-
primitive: inferredPrimitive,
|
|
200
|
-
...props,
|
|
201
|
-
kind: "Schema"
|
|
202
|
-
};
|
|
207
|
+
function trimExtName(text) {
|
|
208
|
+
const dotIndex = text.lastIndexOf(".");
|
|
209
|
+
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
210
|
+
return text;
|
|
203
211
|
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
204
214
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* `required` defaults to `false`.
|
|
208
|
-
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
215
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
209
216
|
*
|
|
210
217
|
* @example
|
|
211
218
|
* ```ts
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* })
|
|
216
|
-
* // required=false, schema.optional=true
|
|
219
|
+
* isValidVarName('status') // true
|
|
220
|
+
* isValidVarName('class') // false (reserved word)
|
|
221
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
217
222
|
* ```
|
|
223
|
+
*/
|
|
224
|
+
function isValidVarName(name) {
|
|
225
|
+
try {
|
|
226
|
+
new Function(`var ${name}`);
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/guards.ts
|
|
234
|
+
/**
|
|
235
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
218
236
|
*
|
|
219
237
|
* @example
|
|
220
238
|
* ```ts
|
|
221
|
-
* const
|
|
222
|
-
*
|
|
223
|
-
* required: true,
|
|
224
|
-
* schema: createSchema({ type: 'string', nullable: true }),
|
|
225
|
-
* })
|
|
226
|
-
* // required=true, no optional/nullish
|
|
239
|
+
* const schema = createSchema({ type: 'string' })
|
|
240
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
227
241
|
* ```
|
|
228
242
|
*/
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
required,
|
|
235
|
-
schema: syncOptionality(props.schema, required)
|
|
236
|
-
};
|
|
243
|
+
function narrowSchema(node, type) {
|
|
244
|
+
return node?.type === type ? node : void 0;
|
|
245
|
+
}
|
|
246
|
+
function isKind(kind) {
|
|
247
|
+
return (node) => node.kind === kind;
|
|
237
248
|
}
|
|
238
249
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* `required` defaults to `false`.
|
|
242
|
-
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
250
|
+
* Returns `true` when the input is an `InputNode`.
|
|
243
251
|
*
|
|
244
252
|
* @example
|
|
245
253
|
* ```ts
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* required: true,
|
|
250
|
-
* schema: createSchema({ type: 'string' }),
|
|
251
|
-
* })
|
|
254
|
+
* if (isInputNode(node)) {
|
|
255
|
+
* console.log(node.schemas.length)
|
|
256
|
+
* }
|
|
252
257
|
* ```
|
|
258
|
+
*/
|
|
259
|
+
const isInputNode = isKind("Input");
|
|
260
|
+
/**
|
|
261
|
+
* Returns `true` when the input is an `OutputNode`.
|
|
253
262
|
*
|
|
254
263
|
* @example
|
|
255
264
|
* ```ts
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* schema: createSchema({ type: 'string', nullable: true }),
|
|
260
|
-
* })
|
|
261
|
-
* // required=false, schema.nullish=true
|
|
265
|
+
* if (isOutputNode(node)) {
|
|
266
|
+
* console.log(node.files.length)
|
|
267
|
+
* }
|
|
262
268
|
* ```
|
|
263
269
|
*/
|
|
264
|
-
|
|
265
|
-
const required = props.required ?? false;
|
|
266
|
-
return {
|
|
267
|
-
...props,
|
|
268
|
-
kind: "Parameter",
|
|
269
|
-
required,
|
|
270
|
-
schema: syncOptionality(props.schema, required)
|
|
271
|
-
};
|
|
272
|
-
}
|
|
270
|
+
const isOutputNode = isKind("Output");
|
|
273
271
|
/**
|
|
274
|
-
*
|
|
272
|
+
* Returns `true` when the input is an `OperationNode`.
|
|
275
273
|
*
|
|
276
274
|
* @example
|
|
277
275
|
* ```ts
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
282
|
-
* })
|
|
276
|
+
* if (isOperationNode(node)) {
|
|
277
|
+
* console.log(node.operationId)
|
|
278
|
+
* }
|
|
283
279
|
* ```
|
|
284
280
|
*/
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
...props,
|
|
288
|
-
kind: "Response"
|
|
289
|
-
};
|
|
290
|
-
}
|
|
281
|
+
const isOperationNode = isKind("Operation");
|
|
291
282
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* `optional` defaults to `false`.
|
|
295
|
-
*
|
|
296
|
-
* @example Required typed param
|
|
297
|
-
* ```ts
|
|
298
|
-
* createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }) })
|
|
299
|
-
* // → petId: string
|
|
300
|
-
* ```
|
|
301
|
-
*
|
|
302
|
-
* @example Optional param
|
|
303
|
-
* ```ts
|
|
304
|
-
* createFunctionParameter({ name: 'params', type: createTypeNode({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
305
|
-
* // → params?: QueryParams
|
|
306
|
-
* ```
|
|
283
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
307
284
|
*
|
|
308
|
-
* @example
|
|
285
|
+
* @example
|
|
309
286
|
* ```ts
|
|
310
|
-
*
|
|
311
|
-
*
|
|
287
|
+
* if (isSchemaNode(node)) {
|
|
288
|
+
* console.log(node.type)
|
|
289
|
+
* }
|
|
312
290
|
* ```
|
|
313
291
|
*/
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
292
|
+
const isSchemaNode = isKind("Schema");
|
|
293
|
+
isKind("Property");
|
|
294
|
+
isKind("Parameter");
|
|
295
|
+
isKind("Response");
|
|
296
|
+
isKind("FunctionParameter");
|
|
297
|
+
isKind("ParameterGroup");
|
|
298
|
+
isKind("FunctionParameters");
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/utils.ts
|
|
301
|
+
const plainStringTypes = new Set([
|
|
302
|
+
"string",
|
|
303
|
+
"uuid",
|
|
304
|
+
"email",
|
|
305
|
+
"url",
|
|
306
|
+
"datetime"
|
|
307
|
+
]);
|
|
321
308
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* named field accessed from a group type. Each language's printer renders the variant
|
|
326
|
-
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
327
|
-
*
|
|
328
|
-
* @example Reference type (TypeScript: `QueryParams`)
|
|
329
|
-
* ```ts
|
|
330
|
-
* createTypeNode({ variant: 'reference', name: 'QueryParams' })
|
|
331
|
-
* ```
|
|
309
|
+
* Returns a merged schema view for a ref node, combining the resolved `node.schema`
|
|
310
|
+
* (base from the referenced definition) with any usage-site sibling fields set directly
|
|
311
|
+
* on the ref node (description, readOnly, nullable, deprecated, etc.).
|
|
332
312
|
*
|
|
333
|
-
*
|
|
334
|
-
* ```ts
|
|
335
|
-
* createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
|
|
336
|
-
* ```
|
|
313
|
+
* Usage-site fields take precedence over the resolved schema's own fields when both are defined.
|
|
337
314
|
*
|
|
338
|
-
*
|
|
339
|
-
* ```ts
|
|
340
|
-
* createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
341
|
-
* ```
|
|
315
|
+
* For non-ref nodes the node itself is returned unchanged.
|
|
342
316
|
*/
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
};
|
|
317
|
+
function syncSchemaRef(node) {
|
|
318
|
+
const ref = narrowSchema(node, "ref");
|
|
319
|
+
if (!ref) return node;
|
|
320
|
+
if (!ref.schema) return node;
|
|
321
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
322
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
323
|
+
return createSchema({
|
|
324
|
+
...ref.schema,
|
|
325
|
+
...definedOverrides
|
|
326
|
+
});
|
|
348
327
|
}
|
|
349
328
|
/**
|
|
350
|
-
*
|
|
329
|
+
* Returns `true` when a schema is emitted as a plain `string` type.
|
|
351
330
|
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* createParameterGroup({
|
|
355
|
-
* properties: [
|
|
356
|
-
* createFunctionParameter({ name: 'id', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
|
|
357
|
-
* createFunctionParameter({ name: 'name', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: true }),
|
|
358
|
-
* ],
|
|
359
|
-
* default: '{}',
|
|
360
|
-
* })
|
|
361
|
-
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
362
|
-
* // call → { id, name }
|
|
363
|
-
* ```
|
|
331
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
332
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
364
333
|
*
|
|
365
|
-
* @example
|
|
334
|
+
* @example
|
|
366
335
|
* ```ts
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* inline: true,
|
|
370
|
-
* })
|
|
371
|
-
* // declaration → petId: string
|
|
372
|
-
* // call → petId
|
|
336
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
337
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
373
338
|
* ```
|
|
374
339
|
*/
|
|
375
|
-
function
|
|
376
|
-
return
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
340
|
+
function isStringType(node) {
|
|
341
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
342
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
343
|
+
if (temporal) return temporal.representation !== "date";
|
|
344
|
+
return false;
|
|
380
345
|
}
|
|
381
346
|
/**
|
|
382
|
-
*
|
|
347
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
383
348
|
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* ],
|
|
391
|
-
* })
|
|
392
|
-
* ```
|
|
349
|
+
* The input array is not mutated.
|
|
350
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
351
|
+
*
|
|
352
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
353
|
+
* in generated output match the desired casing while preserving
|
|
354
|
+
* `OperationNode.parameters` for other consumers.
|
|
393
355
|
*
|
|
394
356
|
* @example
|
|
395
357
|
* ```ts
|
|
396
|
-
* const
|
|
397
|
-
*
|
|
358
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
359
|
+
* const cased = caseParams(params, 'camelcase')
|
|
360
|
+
* // cased[0].name === 'petId'
|
|
398
361
|
* ```
|
|
399
362
|
*/
|
|
400
|
-
function
|
|
401
|
-
return
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
363
|
+
function caseParams(params, casing) {
|
|
364
|
+
if (!casing) return params;
|
|
365
|
+
return params.map((param) => {
|
|
366
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
367
|
+
return {
|
|
368
|
+
...param,
|
|
369
|
+
name: transformed
|
|
370
|
+
};
|
|
371
|
+
});
|
|
406
372
|
}
|
|
407
|
-
//#endregion
|
|
408
|
-
//#region src/guards.ts
|
|
409
373
|
/**
|
|
410
|
-
*
|
|
374
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
411
375
|
*
|
|
412
376
|
* @example
|
|
413
377
|
* ```ts
|
|
414
|
-
*
|
|
415
|
-
*
|
|
378
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
379
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
416
380
|
* ```
|
|
417
381
|
*/
|
|
418
|
-
function
|
|
419
|
-
return
|
|
382
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
383
|
+
return createSchema({
|
|
384
|
+
type: "object",
|
|
385
|
+
primitive: "object",
|
|
386
|
+
properties: [createProperty({
|
|
387
|
+
name: propertyName,
|
|
388
|
+
schema: createSchema({
|
|
389
|
+
type: "enum",
|
|
390
|
+
primitive: "string",
|
|
391
|
+
enumValues: [value]
|
|
392
|
+
}),
|
|
393
|
+
required: true
|
|
394
|
+
})]
|
|
395
|
+
});
|
|
420
396
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
397
|
+
function resolveParamsType({ node, param, resolver }) {
|
|
398
|
+
if (!resolver) return createParamsType({
|
|
399
|
+
variant: "reference",
|
|
400
|
+
name: param.schema.primitive ?? "unknown"
|
|
401
|
+
});
|
|
402
|
+
const individualName = resolver.resolveParamName(node, param);
|
|
403
|
+
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
404
|
+
const groupResolvers = {
|
|
405
|
+
path: resolver.resolvePathParamsName,
|
|
406
|
+
query: resolver.resolveQueryParamsName,
|
|
407
|
+
header: resolver.resolveHeaderParamsName
|
|
408
|
+
};
|
|
409
|
+
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
410
|
+
if (groupName && groupName !== individualName) return createParamsType({
|
|
411
|
+
variant: "member",
|
|
412
|
+
base: groupName,
|
|
413
|
+
key: param.name
|
|
414
|
+
});
|
|
415
|
+
return createParamsType({
|
|
416
|
+
variant: "reference",
|
|
417
|
+
name: individualName
|
|
418
|
+
});
|
|
423
419
|
}
|
|
424
|
-
isKind("Root");
|
|
425
|
-
/**
|
|
426
|
-
* Returns `true` when the input is an `OperationNode`.
|
|
427
|
-
*
|
|
428
|
-
* @example
|
|
429
|
-
* ```ts
|
|
430
|
-
* if (isOperationNode(node)) {
|
|
431
|
-
* console.log(node.operationId)
|
|
432
|
-
* }
|
|
433
|
-
* ```
|
|
434
|
-
*/
|
|
435
|
-
const isOperationNode = isKind("Operation");
|
|
436
|
-
/**
|
|
437
|
-
* Returns `true` when the input is a `SchemaNode`.
|
|
438
|
-
*
|
|
439
|
-
* @example
|
|
440
|
-
* ```ts
|
|
441
|
-
* if (isSchemaNode(node)) {
|
|
442
|
-
* console.log(node.type)
|
|
443
|
-
* }
|
|
444
|
-
* ```
|
|
445
|
-
*/
|
|
446
|
-
const isSchemaNode = isKind("Schema");
|
|
447
|
-
isKind("Property");
|
|
448
|
-
isKind("Parameter");
|
|
449
|
-
isKind("Response");
|
|
450
|
-
isKind("FunctionParameter");
|
|
451
|
-
isKind("ParameterGroup");
|
|
452
|
-
isKind("FunctionParameters");
|
|
453
|
-
//#endregion
|
|
454
|
-
//#region src/printer.ts
|
|
455
420
|
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* This function wraps a builder and makes options optional at call sites.
|
|
459
|
-
*
|
|
460
|
-
* The builder receives resolved options and returns:
|
|
461
|
-
* - `name` — a unique identifier for the printer
|
|
462
|
-
* - `options` — options stored on the returned printer instance
|
|
463
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
464
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
465
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
466
|
-
* - This keeps recursion safe and avoids self-calls
|
|
467
|
-
*
|
|
468
|
-
* When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
|
|
421
|
+
* Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
|
|
469
422
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
423
|
+
* Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
|
|
424
|
+
* type resolution and `extraParams` for plugin-specific trailing parameters.
|
|
473
425
|
*
|
|
474
|
-
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
475
|
-
* name: 'zod',
|
|
476
|
-
* options: { strict: options.strict ?? true },
|
|
477
|
-
* nodes: {
|
|
478
|
-
* string: () => 'z.string()',
|
|
479
|
-
* object(node) {
|
|
480
|
-
* const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
|
|
481
|
-
* return `z.object({ ${props} })`
|
|
482
|
-
* },
|
|
483
|
-
* },
|
|
484
|
-
* }))
|
|
485
|
-
* ```
|
|
486
|
-
*/
|
|
487
|
-
function definePrinter(build) {
|
|
488
|
-
return createPrinterFactory((node) => node.type)(build);
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
492
|
-
**
|
|
493
426
|
* @example
|
|
494
427
|
* ```ts
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
428
|
+
* const params = createOperationParams(node, {
|
|
429
|
+
* paramsType: 'inline',
|
|
430
|
+
* pathParamsType: 'inline',
|
|
431
|
+
* resolver: tsResolver,
|
|
432
|
+
* extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
|
|
433
|
+
* })
|
|
498
434
|
* ```
|
|
499
435
|
*/
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
436
|
+
function createOperationParams(node, options) {
|
|
437
|
+
const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
|
|
438
|
+
const dataName = paramNames?.data ?? "data";
|
|
439
|
+
const paramsName = paramNames?.params ?? "params";
|
|
440
|
+
const headersName = paramNames?.headers ?? "headers";
|
|
441
|
+
const pathName = paramNames?.path ?? "pathParams";
|
|
442
|
+
const wrapType = (type) => createParamsType({
|
|
443
|
+
variant: "reference",
|
|
444
|
+
name: typeWrapper ? typeWrapper(type) : type
|
|
445
|
+
});
|
|
446
|
+
const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
|
|
447
|
+
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
448
|
+
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
449
|
+
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
450
|
+
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
451
|
+
const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
452
|
+
const bodyRequired = node.requestBody?.required ?? false;
|
|
453
|
+
const queryGroupType = resolver ? resolveGroupType({
|
|
454
|
+
node,
|
|
455
|
+
params: queryParams,
|
|
456
|
+
groupMethod: resolver.resolveQueryParamsName,
|
|
457
|
+
resolver
|
|
458
|
+
}) : void 0;
|
|
459
|
+
const headerGroupType = resolver ? resolveGroupType({
|
|
460
|
+
node,
|
|
461
|
+
params: headerParams,
|
|
462
|
+
groupMethod: resolver.resolveHeaderParamsName,
|
|
463
|
+
resolver
|
|
464
|
+
}) : void 0;
|
|
465
|
+
const params = [];
|
|
466
|
+
if (paramsType === "object") {
|
|
467
|
+
const children = [
|
|
468
|
+
...pathParams.map((p) => {
|
|
469
|
+
const type = resolveParamsType({
|
|
470
|
+
node,
|
|
471
|
+
param: p,
|
|
472
|
+
resolver
|
|
473
|
+
});
|
|
474
|
+
return createFunctionParameter({
|
|
475
|
+
name: p.name,
|
|
476
|
+
type: wrapTypeNode(type),
|
|
477
|
+
optional: !p.required
|
|
478
|
+
});
|
|
479
|
+
}),
|
|
480
|
+
...bodyType ? [createFunctionParameter({
|
|
481
|
+
name: dataName,
|
|
482
|
+
type: bodyType,
|
|
483
|
+
optional: !bodyRequired
|
|
484
|
+
})] : [],
|
|
485
|
+
...buildGroupParam({
|
|
486
|
+
name: paramsName,
|
|
487
|
+
node,
|
|
488
|
+
params: queryParams,
|
|
489
|
+
groupType: queryGroupType,
|
|
490
|
+
resolver,
|
|
491
|
+
wrapType
|
|
492
|
+
}),
|
|
493
|
+
...buildGroupParam({
|
|
494
|
+
name: headersName,
|
|
495
|
+
node,
|
|
496
|
+
params: headerParams,
|
|
497
|
+
groupType: headerGroupType,
|
|
498
|
+
resolver,
|
|
499
|
+
wrapType
|
|
500
|
+
})
|
|
501
|
+
];
|
|
502
|
+
if (children.length) params.push(createParameterGroup({
|
|
503
|
+
properties: children,
|
|
504
|
+
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
505
|
+
}));
|
|
506
|
+
} else {
|
|
507
|
+
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
508
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
|
|
509
|
+
params.push(createFunctionParameter({
|
|
510
|
+
name: pathName,
|
|
511
|
+
type: spreadType ? wrapType(spreadType) : void 0,
|
|
512
|
+
rest: true
|
|
513
|
+
}));
|
|
514
|
+
} else {
|
|
515
|
+
const pathChildren = pathParams.map((p) => {
|
|
516
|
+
const type = resolveParamsType({
|
|
517
|
+
node,
|
|
518
|
+
param: p,
|
|
519
|
+
resolver
|
|
520
|
+
});
|
|
521
|
+
return createFunctionParameter({
|
|
522
|
+
name: p.name,
|
|
523
|
+
type: wrapTypeNode(type),
|
|
524
|
+
optional: !p.required
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
params.push(createParameterGroup({
|
|
528
|
+
properties: pathChildren,
|
|
529
|
+
inline: pathParamsType === "inline",
|
|
530
|
+
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
if (bodyType) params.push(createFunctionParameter({
|
|
534
|
+
name: dataName,
|
|
535
|
+
type: bodyType,
|
|
536
|
+
optional: !bodyRequired
|
|
537
|
+
}));
|
|
538
|
+
params.push(...buildGroupParam({
|
|
539
|
+
name: paramsName,
|
|
540
|
+
node,
|
|
541
|
+
params: queryParams,
|
|
542
|
+
groupType: queryGroupType,
|
|
543
|
+
resolver,
|
|
544
|
+
wrapType
|
|
545
|
+
}));
|
|
546
|
+
params.push(...buildGroupParam({
|
|
547
|
+
name: headersName,
|
|
548
|
+
node,
|
|
549
|
+
params: headerParams,
|
|
550
|
+
groupType: headerGroupType,
|
|
551
|
+
resolver,
|
|
552
|
+
wrapType
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
params.push(...extraParams);
|
|
556
|
+
return createFunctionParameters({ params });
|
|
522
557
|
}
|
|
523
|
-
//#endregion
|
|
524
|
-
//#region src/refs.ts
|
|
525
558
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
559
|
+
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
560
|
+
* Returns an empty array when there are no params to emit.
|
|
529
561
|
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
533
|
-
* ```
|
|
562
|
+
* If a pre-resolved `groupType` is provided it emits `name: GroupType`.
|
|
563
|
+
* Otherwise, it builds an inline struct from the individual params.
|
|
534
564
|
*/
|
|
535
|
-
function
|
|
536
|
-
|
|
565
|
+
function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
|
|
566
|
+
if (groupType) return [createFunctionParameter({
|
|
567
|
+
name,
|
|
568
|
+
type: groupType.type.kind === "ParamsType" && groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
569
|
+
optional: groupType.optional
|
|
570
|
+
})];
|
|
571
|
+
if (params.length) return [createFunctionParameter({
|
|
572
|
+
name,
|
|
573
|
+
type: toStructType({
|
|
574
|
+
node,
|
|
575
|
+
params,
|
|
576
|
+
resolver
|
|
577
|
+
}),
|
|
578
|
+
optional: params.every((p) => !p.required)
|
|
579
|
+
})];
|
|
580
|
+
return [];
|
|
537
581
|
}
|
|
538
|
-
//#endregion
|
|
539
|
-
//#region ../../internals/utils/src/casing.ts
|
|
540
582
|
/**
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* and capitalizes each word according to `pascal`.
|
|
544
|
-
*
|
|
545
|
-
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
583
|
+
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
584
|
+
* Returns `undefined` when the group name equals the individual param name (no real group).
|
|
546
585
|
*/
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
586
|
+
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
587
|
+
if (!params.length) return;
|
|
588
|
+
const firstParam = params[0];
|
|
589
|
+
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
590
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
591
|
+
const allOptional = params.every((p) => !p.required);
|
|
592
|
+
return {
|
|
593
|
+
type: createParamsType({
|
|
594
|
+
variant: "reference",
|
|
595
|
+
name: groupName
|
|
596
|
+
}),
|
|
597
|
+
optional: allOptional
|
|
598
|
+
};
|
|
553
599
|
}
|
|
554
600
|
/**
|
|
555
|
-
*
|
|
556
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
557
|
-
* Segments are joined with `/` to form a file path.
|
|
601
|
+
* Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
|
|
558
602
|
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
603
|
+
* Used when query or header parameters have no dedicated group type name.
|
|
604
|
+
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
561
605
|
*/
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
|
|
606
|
+
function toStructType({ node, params, resolver }) {
|
|
607
|
+
return createParamsType({
|
|
608
|
+
variant: "struct",
|
|
609
|
+
properties: params.map((p) => ({
|
|
610
|
+
name: p.name,
|
|
611
|
+
optional: !p.required,
|
|
612
|
+
type: resolveParamsType({
|
|
613
|
+
node,
|
|
614
|
+
param: p,
|
|
615
|
+
resolver
|
|
616
|
+
})
|
|
617
|
+
}))
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function sourceKey(source) {
|
|
621
|
+
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
622
|
+
}
|
|
623
|
+
function pathTypeKey(path, isTypeOnly) {
|
|
624
|
+
return `${path}:${isTypeOnly ?? false}`;
|
|
625
|
+
}
|
|
626
|
+
function exportKey(path, name, isTypeOnly, asAlias) {
|
|
627
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
|
|
628
|
+
}
|
|
629
|
+
function importKey(path, name, isTypeOnly) {
|
|
630
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
|
|
565
631
|
}
|
|
566
632
|
/**
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
* @example
|
|
571
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
572
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
633
|
+
* Computes a multi-level sort key for exports and imports:
|
|
634
|
+
* non-array names first (wildcards/namespace aliases); type-only before value; alphabetical path; unnamed before named.
|
|
573
635
|
*/
|
|
574
|
-
function
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return
|
|
636
|
+
function sortKey(node) {
|
|
637
|
+
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
638
|
+
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
639
|
+
const hasName = node.name != null ? "1" : "0";
|
|
640
|
+
const name = Array.isArray(node.name) ? [...node.name].sort().join("\0") : node.name ?? "";
|
|
641
|
+
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
580
642
|
}
|
|
581
643
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
* @example
|
|
586
|
-
* pascalCase('hello-world') // 'HelloWorld'
|
|
587
|
-
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
644
|
+
* Deduplicates an array of `SourceNode` objects.
|
|
645
|
+
* Named sources are deduplicated by `name + isExportable + isTypeOnly`.
|
|
646
|
+
* Unnamed sources are deduplicated by object reference.
|
|
588
647
|
*/
|
|
589
|
-
function
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
648
|
+
function combineSources(sources) {
|
|
649
|
+
const seen = /* @__PURE__ */ new Map();
|
|
650
|
+
for (const source of sources) {
|
|
651
|
+
const key = sourceKey(source);
|
|
652
|
+
if (!seen.has(key)) seen.set(key, source);
|
|
653
|
+
}
|
|
654
|
+
return [...seen.values()];
|
|
595
655
|
}
|
|
596
|
-
//#endregion
|
|
597
|
-
//#region ../../internals/utils/src/reserved.ts
|
|
598
656
|
/**
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
* @example
|
|
602
|
-
* ```ts
|
|
603
|
-
* isValidVarName('status') // true
|
|
604
|
-
* isValidVarName('class') // false (reserved word)
|
|
605
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
606
|
-
* ```
|
|
657
|
+
* Deduplicates and merges an array of `ExportNode` objects.
|
|
658
|
+
* Exports with the same path and `isTypeOnly` flag have their names merged.
|
|
607
659
|
*/
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
660
|
+
function combineExports(exports) {
|
|
661
|
+
const result = [];
|
|
662
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
663
|
+
const seen = /* @__PURE__ */ new Set();
|
|
664
|
+
for (const curr of [...exports].sort((a, b) => {
|
|
665
|
+
const ka = sortKey(a);
|
|
666
|
+
const kb = sortKey(b);
|
|
667
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
668
|
+
})) {
|
|
669
|
+
const { name, path, isTypeOnly, asAlias } = curr;
|
|
670
|
+
if (Array.isArray(name)) {
|
|
671
|
+
if (!name.length) continue;
|
|
672
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
673
|
+
const existing = namedByPath.get(key);
|
|
674
|
+
if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
|
|
675
|
+
else {
|
|
676
|
+
const newItem = {
|
|
677
|
+
...curr,
|
|
678
|
+
name: [...new Set(name)]
|
|
679
|
+
};
|
|
680
|
+
result.push(newItem);
|
|
681
|
+
namedByPath.set(key, newItem);
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
const key = exportKey(path, name, isTypeOnly, asAlias);
|
|
685
|
+
if (!seen.has(key)) {
|
|
686
|
+
result.push(curr);
|
|
687
|
+
seen.add(key);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
613
690
|
}
|
|
614
|
-
return
|
|
691
|
+
return result;
|
|
615
692
|
}
|
|
616
|
-
//#endregion
|
|
617
|
-
//#region src/visitor.ts
|
|
618
693
|
/**
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
* @example
|
|
624
|
-
* ```ts
|
|
625
|
-
* const limit = createLimit(2)
|
|
626
|
-
* for (const task of [taskA, taskB, taskC]) {
|
|
627
|
-
* await limit(() => task())
|
|
628
|
-
* }
|
|
629
|
-
* // only 2 tasks run at the same time
|
|
630
|
-
* ```
|
|
694
|
+
* Deduplicates and merges an array of `ImportNode` objects.
|
|
695
|
+
* Filters out unused imports (names not referenced in `source` or re-exported).
|
|
696
|
+
* Imports with the same path and `isTypeOnly` flag have their names merged.
|
|
631
697
|
*/
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
698
|
+
function combineImports(imports, exports, source) {
|
|
699
|
+
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
700
|
+
const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
|
|
701
|
+
const result = [];
|
|
702
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
704
|
+
for (const curr of [...imports].sort((a, b) => {
|
|
705
|
+
const ka = sortKey(a);
|
|
706
|
+
const kb = sortKey(b);
|
|
707
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
708
|
+
})) {
|
|
709
|
+
if (curr.path === curr.root) continue;
|
|
710
|
+
const { path, isTypeOnly } = curr;
|
|
711
|
+
let { name } = curr;
|
|
712
|
+
if (Array.isArray(name)) {
|
|
713
|
+
name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.propertyName));
|
|
714
|
+
if (!name.length) continue;
|
|
715
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
716
|
+
const existing = namedByPath.get(key);
|
|
717
|
+
if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
|
|
718
|
+
else {
|
|
719
|
+
const newItem = {
|
|
720
|
+
...curr,
|
|
721
|
+
name
|
|
722
|
+
};
|
|
723
|
+
result.push(newItem);
|
|
724
|
+
namedByPath.set(key, newItem);
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
if (name && !isUsed(name)) continue;
|
|
728
|
+
const key = importKey(path, name, isTypeOnly);
|
|
729
|
+
if (!seen.has(key)) {
|
|
730
|
+
result.push(curr);
|
|
731
|
+
seen.add(key);
|
|
732
|
+
}
|
|
639
733
|
}
|
|
640
734
|
}
|
|
641
|
-
return
|
|
642
|
-
return new Promise((resolve, reject) => {
|
|
643
|
-
queue.push(() => {
|
|
644
|
-
Promise.resolve(fn()).then(resolve, reject).finally(() => {
|
|
645
|
-
active--;
|
|
646
|
-
next();
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
next();
|
|
650
|
-
});
|
|
651
|
-
};
|
|
735
|
+
return result;
|
|
652
736
|
}
|
|
653
737
|
/**
|
|
654
|
-
*
|
|
738
|
+
* Recursively extracts all string content embedded in a {@link CodeNode} tree.
|
|
655
739
|
*
|
|
656
|
-
*
|
|
657
|
-
* `
|
|
658
|
-
*
|
|
740
|
+
* Includes text node values, and string attribute fields (`params`, `generics`,
|
|
741
|
+
* `returnType`, `type`) that may reference identifiers needing imports.
|
|
742
|
+
* Used by `createFile` to build the full source string for import filtering.
|
|
743
|
+
*/
|
|
744
|
+
function extractStringsFromNodes(nodes) {
|
|
745
|
+
if (!nodes?.length) return "";
|
|
746
|
+
return nodes.map((node) => {
|
|
747
|
+
if (typeof node === "string") return node;
|
|
748
|
+
if (node.kind === "Text") return node.value;
|
|
749
|
+
if (node.kind === "Break") return "";
|
|
750
|
+
if (node.kind === "Jsx") return node.value;
|
|
751
|
+
const parts = [];
|
|
752
|
+
if ("params" in node && node.params) parts.push(node.params);
|
|
753
|
+
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
754
|
+
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
755
|
+
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
756
|
+
const nested = extractStringsFromNodes(node.nodes);
|
|
757
|
+
if (nested) parts.push(nested);
|
|
758
|
+
return parts.join("\n");
|
|
759
|
+
}).filter(Boolean).join("\n");
|
|
760
|
+
}
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/factory.ts
|
|
763
|
+
/**
|
|
764
|
+
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
659
765
|
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
* const children = getChildren(operationNode, true)
|
|
663
|
-
* // returns parameters, requestBody schema (if present), and responses
|
|
664
|
-
* ```
|
|
766
|
+
* - `optional` is set for non-required, non-nullable schemas.
|
|
767
|
+
* - `nullish` is set for non-required, nullable schemas.
|
|
665
768
|
*/
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
];
|
|
674
|
-
case "Schema": {
|
|
675
|
-
const children = [];
|
|
676
|
-
if (!recurse) return [];
|
|
677
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
678
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
679
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
680
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
681
|
-
return children;
|
|
682
|
-
}
|
|
683
|
-
case "Property": return [node.schema];
|
|
684
|
-
case "Parameter": return [node.schema];
|
|
685
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
686
|
-
case "FunctionParameter":
|
|
687
|
-
case "ParameterGroup":
|
|
688
|
-
case "FunctionParameters":
|
|
689
|
-
case "Type": return [];
|
|
690
|
-
}
|
|
769
|
+
function syncOptionality(schema, required) {
|
|
770
|
+
const nullable = schema.nullable ?? false;
|
|
771
|
+
return {
|
|
772
|
+
...schema,
|
|
773
|
+
optional: !required && !nullable ? true : void 0,
|
|
774
|
+
nullish: !required && nullable ? true : void 0
|
|
775
|
+
};
|
|
691
776
|
}
|
|
692
777
|
/**
|
|
693
|
-
*
|
|
694
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
695
|
-
* (default: `WALK_CONCURRENCY`).
|
|
778
|
+
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
696
779
|
*
|
|
697
780
|
* @example
|
|
698
781
|
* ```ts
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
* console.log(node.operationId)
|
|
702
|
-
* },
|
|
703
|
-
* })
|
|
782
|
+
* const input = createInput()
|
|
783
|
+
* // { kind: 'Input', schemas: [], operations: [] }
|
|
704
784
|
* ```
|
|
705
785
|
*
|
|
706
786
|
* @example
|
|
707
787
|
* ```ts
|
|
708
|
-
*
|
|
709
|
-
*
|
|
788
|
+
* const input = createInput({ schemas: [petSchema] })
|
|
789
|
+
* // keeps default operations: []
|
|
710
790
|
* ```
|
|
711
791
|
*/
|
|
712
|
-
|
|
713
|
-
return
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
break;
|
|
720
|
-
case "Operation":
|
|
721
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
722
|
-
break;
|
|
723
|
-
case "Schema":
|
|
724
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
725
|
-
break;
|
|
726
|
-
case "Property":
|
|
727
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
728
|
-
break;
|
|
729
|
-
case "Parameter":
|
|
730
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
731
|
-
break;
|
|
732
|
-
case "Response":
|
|
733
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
734
|
-
break;
|
|
735
|
-
case "FunctionParameter":
|
|
736
|
-
case "ParameterGroup":
|
|
737
|
-
case "FunctionParameters": break;
|
|
738
|
-
}
|
|
739
|
-
const children = getChildren(node, recurse);
|
|
740
|
-
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
741
|
-
}
|
|
742
|
-
function transform(node, options) {
|
|
743
|
-
const { depth, parent, ...visitor } = options;
|
|
744
|
-
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
745
|
-
switch (node.kind) {
|
|
746
|
-
case "Root": {
|
|
747
|
-
let root = node;
|
|
748
|
-
const replaced = visitor.root?.(root, { parent });
|
|
749
|
-
if (replaced) root = replaced;
|
|
750
|
-
return {
|
|
751
|
-
...root,
|
|
752
|
-
schemas: root.schemas.map((s) => transform(s, {
|
|
753
|
-
...options,
|
|
754
|
-
parent: root
|
|
755
|
-
})),
|
|
756
|
-
operations: root.operations.map((op) => transform(op, {
|
|
757
|
-
...options,
|
|
758
|
-
parent: root
|
|
759
|
-
}))
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
case "Operation": {
|
|
763
|
-
let op = node;
|
|
764
|
-
const replaced = visitor.operation?.(op, { parent });
|
|
765
|
-
if (replaced) op = replaced;
|
|
766
|
-
return {
|
|
767
|
-
...op,
|
|
768
|
-
parameters: op.parameters.map((p) => transform(p, {
|
|
769
|
-
...options,
|
|
770
|
-
parent: op
|
|
771
|
-
})),
|
|
772
|
-
requestBody: op.requestBody ? {
|
|
773
|
-
...op.requestBody,
|
|
774
|
-
schema: op.requestBody.schema ? transform(op.requestBody.schema, {
|
|
775
|
-
...options,
|
|
776
|
-
parent: op
|
|
777
|
-
}) : void 0
|
|
778
|
-
} : void 0,
|
|
779
|
-
responses: op.responses.map((r) => transform(r, {
|
|
780
|
-
...options,
|
|
781
|
-
parent: op
|
|
782
|
-
}))
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
case "Schema": {
|
|
786
|
-
let schema = node;
|
|
787
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
788
|
-
if (replaced) schema = replaced;
|
|
789
|
-
const childOptions = {
|
|
790
|
-
...options,
|
|
791
|
-
parent: schema
|
|
792
|
-
};
|
|
793
|
-
return {
|
|
794
|
-
...schema,
|
|
795
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
796
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
797
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
798
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
case "Property": {
|
|
802
|
-
let prop = node;
|
|
803
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
804
|
-
if (replaced) prop = replaced;
|
|
805
|
-
return createProperty({
|
|
806
|
-
...prop,
|
|
807
|
-
schema: transform(prop.schema, {
|
|
808
|
-
...options,
|
|
809
|
-
parent: prop
|
|
810
|
-
})
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
case "Parameter": {
|
|
814
|
-
let param = node;
|
|
815
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
816
|
-
if (replaced) param = replaced;
|
|
817
|
-
return createParameter({
|
|
818
|
-
...param,
|
|
819
|
-
schema: transform(param.schema, {
|
|
820
|
-
...options,
|
|
821
|
-
parent: param
|
|
822
|
-
})
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
case "Response": {
|
|
826
|
-
let response = node;
|
|
827
|
-
const replaced = visitor.response?.(response, { parent });
|
|
828
|
-
if (replaced) response = replaced;
|
|
829
|
-
return {
|
|
830
|
-
...response,
|
|
831
|
-
schema: transform(response.schema, {
|
|
832
|
-
...options,
|
|
833
|
-
parent: response
|
|
834
|
-
})
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
case "FunctionParameter":
|
|
838
|
-
case "ParameterGroup":
|
|
839
|
-
case "FunctionParameters":
|
|
840
|
-
case "Type": return node;
|
|
841
|
-
}
|
|
792
|
+
function createInput(overrides = {}) {
|
|
793
|
+
return {
|
|
794
|
+
schemas: [],
|
|
795
|
+
operations: [],
|
|
796
|
+
...overrides,
|
|
797
|
+
kind: "Input"
|
|
798
|
+
};
|
|
842
799
|
}
|
|
843
800
|
/**
|
|
844
|
-
*
|
|
801
|
+
* Creates an `OutputNode` with a stable default for `files`.
|
|
845
802
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* const output = createOutput()
|
|
806
|
+
* // { kind: 'Output', files: [] }
|
|
807
|
+
* ```
|
|
848
808
|
*
|
|
849
809
|
* @example
|
|
850
810
|
* ```ts
|
|
851
|
-
* const
|
|
852
|
-
* { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
|
|
853
|
-
* { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
|
|
854
|
-
* )
|
|
811
|
+
* const output = createOutput({ files: [petFile] })
|
|
855
812
|
* ```
|
|
856
813
|
*/
|
|
857
|
-
function
|
|
814
|
+
function createOutput(overrides = {}) {
|
|
858
815
|
return {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
operation(node, context) {
|
|
863
|
-
return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
|
|
864
|
-
},
|
|
865
|
-
schema(node, context) {
|
|
866
|
-
return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
|
|
867
|
-
},
|
|
868
|
-
property(node, context) {
|
|
869
|
-
return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
|
|
870
|
-
},
|
|
871
|
-
parameter(node, context) {
|
|
872
|
-
return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
|
|
873
|
-
},
|
|
874
|
-
response(node, context) {
|
|
875
|
-
return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
|
|
876
|
-
}
|
|
816
|
+
files: [],
|
|
817
|
+
...overrides,
|
|
818
|
+
kind: "Output"
|
|
877
819
|
};
|
|
878
820
|
}
|
|
879
821
|
/**
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
822
|
+
* Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
|
|
883
823
|
*
|
|
884
824
|
* @example
|
|
885
825
|
* ```ts
|
|
886
|
-
* const
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
* },
|
|
826
|
+
* const operation = createOperation({
|
|
827
|
+
* operationId: 'getPetById',
|
|
828
|
+
* method: 'GET',
|
|
829
|
+
* path: '/pet/{petId}',
|
|
890
830
|
* })
|
|
831
|
+
* // tags, parameters, and responses are []
|
|
891
832
|
* ```
|
|
892
833
|
*
|
|
893
834
|
* @example
|
|
894
835
|
* ```ts
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
case "Schema":
|
|
912
|
-
v = visitor.schema?.(node, { parent });
|
|
913
|
-
break;
|
|
914
|
-
case "Property":
|
|
915
|
-
v = visitor.property?.(node, { parent });
|
|
916
|
-
break;
|
|
917
|
-
case "Parameter":
|
|
918
|
-
v = visitor.parameter?.(node, { parent });
|
|
919
|
-
break;
|
|
920
|
-
case "Response":
|
|
921
|
-
v = visitor.response?.(node, { parent });
|
|
922
|
-
break;
|
|
923
|
-
case "FunctionParameter":
|
|
924
|
-
case "ParameterGroup":
|
|
925
|
-
case "FunctionParameters": break;
|
|
926
|
-
}
|
|
927
|
-
if (v !== void 0) results.push(v);
|
|
928
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
929
|
-
...options,
|
|
930
|
-
parent: node
|
|
931
|
-
})) results.push(item);
|
|
932
|
-
return results;
|
|
933
|
-
}
|
|
934
|
-
//#endregion
|
|
935
|
-
//#region src/resolvers.ts
|
|
936
|
-
function findDiscriminator(mapping, ref) {
|
|
937
|
-
if (!mapping || !ref) return null;
|
|
938
|
-
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
939
|
-
}
|
|
940
|
-
function childName(parentName, propName) {
|
|
941
|
-
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
942
|
-
}
|
|
943
|
-
function enumPropName(parentName, propName, enumSuffix) {
|
|
944
|
-
return pascalCase([
|
|
945
|
-
parentName,
|
|
946
|
-
propName,
|
|
947
|
-
enumSuffix
|
|
948
|
-
].filter(Boolean).join(" "));
|
|
836
|
+
* const operation = createOperation({
|
|
837
|
+
* operationId: 'findPets',
|
|
838
|
+
* method: 'GET',
|
|
839
|
+
* path: '/pet/findByStatus',
|
|
840
|
+
* tags: ['pet'],
|
|
841
|
+
* })
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
function createOperation(props) {
|
|
845
|
+
return {
|
|
846
|
+
tags: [],
|
|
847
|
+
parameters: [],
|
|
848
|
+
responses: [],
|
|
849
|
+
...props,
|
|
850
|
+
kind: "Operation"
|
|
851
|
+
};
|
|
949
852
|
}
|
|
950
853
|
/**
|
|
951
|
-
*
|
|
854
|
+
* Maps schema `type` to its underlying `primitive`.
|
|
855
|
+
* Primitive types map to themselves; special string formats map to `'string'`.
|
|
856
|
+
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
952
857
|
*/
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
858
|
+
const TYPE_TO_PRIMITIVE = {
|
|
859
|
+
string: "string",
|
|
860
|
+
number: "number",
|
|
861
|
+
integer: "integer",
|
|
862
|
+
bigint: "bigint",
|
|
863
|
+
boolean: "boolean",
|
|
864
|
+
null: "null",
|
|
865
|
+
any: "any",
|
|
866
|
+
unknown: "unknown",
|
|
867
|
+
void: "void",
|
|
868
|
+
never: "never",
|
|
869
|
+
object: "object",
|
|
870
|
+
array: "array",
|
|
871
|
+
date: "date",
|
|
872
|
+
uuid: "string",
|
|
873
|
+
email: "string",
|
|
874
|
+
url: "string",
|
|
875
|
+
datetime: "string",
|
|
876
|
+
time: "string"
|
|
877
|
+
};
|
|
878
|
+
function createSchema(props) {
|
|
879
|
+
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
880
|
+
if (props["type"] === "object") return {
|
|
881
|
+
properties: [],
|
|
882
|
+
primitive: "object",
|
|
883
|
+
...props,
|
|
884
|
+
kind: "Schema"
|
|
885
|
+
};
|
|
886
|
+
return {
|
|
887
|
+
primitive: inferredPrimitive,
|
|
888
|
+
...props,
|
|
889
|
+
kind: "Schema"
|
|
890
|
+
};
|
|
962
891
|
}
|
|
963
|
-
//#endregion
|
|
964
|
-
//#region src/transformers.ts
|
|
965
892
|
/**
|
|
966
|
-
*
|
|
893
|
+
* Creates a `PropertyNode`.
|
|
967
894
|
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
895
|
+
* `required` defaults to `false`.
|
|
896
|
+
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
970
897
|
*
|
|
971
898
|
* @example
|
|
972
899
|
* ```ts
|
|
973
|
-
* const
|
|
974
|
-
*
|
|
975
|
-
*
|
|
900
|
+
* const property = createProperty({
|
|
901
|
+
* name: 'status',
|
|
902
|
+
* schema: createSchema({ type: 'string' }),
|
|
976
903
|
* })
|
|
977
|
-
*
|
|
904
|
+
* // required=false, schema.optional=true
|
|
905
|
+
* ```
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```ts
|
|
909
|
+
* const property = createProperty({
|
|
910
|
+
* name: 'status',
|
|
911
|
+
* required: true,
|
|
912
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
913
|
+
* })
|
|
914
|
+
* // required=true, no optional/nullish
|
|
978
915
|
* ```
|
|
979
916
|
*/
|
|
980
|
-
function
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
return createProperty({
|
|
989
|
-
...prop,
|
|
990
|
-
schema: createSchema({
|
|
991
|
-
type: "enum",
|
|
992
|
-
primitive: "string",
|
|
993
|
-
enumValues: values,
|
|
994
|
-
name: enumName,
|
|
995
|
-
readOnly: prop.schema.readOnly,
|
|
996
|
-
writeOnly: prop.schema.writeOnly
|
|
997
|
-
})
|
|
998
|
-
});
|
|
999
|
-
})
|
|
1000
|
-
});
|
|
917
|
+
function createProperty(props) {
|
|
918
|
+
const required = props.required ?? false;
|
|
919
|
+
return {
|
|
920
|
+
...props,
|
|
921
|
+
kind: "Property",
|
|
922
|
+
required,
|
|
923
|
+
schema: syncOptionality(props.schema, required)
|
|
924
|
+
};
|
|
1001
925
|
}
|
|
1002
926
|
/**
|
|
1003
|
-
*
|
|
927
|
+
* Creates a `ParameterNode`.
|
|
928
|
+
*
|
|
929
|
+
* `required` defaults to `false`.
|
|
930
|
+
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
1004
931
|
*
|
|
1005
932
|
* @example
|
|
1006
933
|
* ```ts
|
|
1007
|
-
* const
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
934
|
+
* const param = createParameter({
|
|
935
|
+
* name: 'petId',
|
|
936
|
+
* in: 'path',
|
|
937
|
+
* required: true,
|
|
938
|
+
* schema: createSchema({ type: 'string' }),
|
|
939
|
+
* })
|
|
940
|
+
* ```
|
|
941
|
+
*
|
|
942
|
+
* @example
|
|
943
|
+
* ```ts
|
|
944
|
+
* const param = createParameter({
|
|
945
|
+
* name: 'status',
|
|
946
|
+
* in: 'query',
|
|
947
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
948
|
+
* })
|
|
949
|
+
* // required=false, schema.nullish=true
|
|
1011
950
|
* ```
|
|
1012
951
|
*/
|
|
1013
|
-
function
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
...previousObject,
|
|
1022
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1023
|
-
});
|
|
1024
|
-
return acc;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
acc.push(member);
|
|
1028
|
-
return acc;
|
|
1029
|
-
}, []);
|
|
952
|
+
function createParameter(props) {
|
|
953
|
+
const required = props.required ?? false;
|
|
954
|
+
return {
|
|
955
|
+
...props,
|
|
956
|
+
kind: "Parameter",
|
|
957
|
+
required,
|
|
958
|
+
schema: syncOptionality(props.schema, required)
|
|
959
|
+
};
|
|
1030
960
|
}
|
|
1031
961
|
/**
|
|
1032
|
-
*
|
|
962
|
+
* Creates a `ResponseNode`.
|
|
1033
963
|
*
|
|
1034
964
|
* @example
|
|
1035
965
|
* ```ts
|
|
1036
|
-
* const
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
* ])
|
|
1040
|
-
*
|
|
966
|
+
* const response = createResponse({
|
|
967
|
+
* statusCode: '200',
|
|
968
|
+
* description: 'Success',
|
|
969
|
+
* schema: createSchema({ type: 'object', properties: [] }),
|
|
970
|
+
* })
|
|
1041
971
|
* ```
|
|
1042
972
|
*/
|
|
1043
|
-
function
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const enumNode = narrowSchema(member, "enum");
|
|
1048
|
-
if (!enumNode) return true;
|
|
1049
|
-
const primitive = enumNode.primitive;
|
|
1050
|
-
if (!primitive) return true;
|
|
1051
|
-
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1052
|
-
if (scalarPrimitives.has(primitive)) return false;
|
|
1053
|
-
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1054
|
-
return true;
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
1058
|
-
const enumNode = narrowSchema(propNode, "enum");
|
|
1059
|
-
if (enumNode?.primitive === "boolean") return {
|
|
1060
|
-
...propNode,
|
|
1061
|
-
name: void 0
|
|
1062
|
-
};
|
|
1063
|
-
if (enumNode) return {
|
|
1064
|
-
...propNode,
|
|
1065
|
-
name: enumPropName(parentName, propName, enumSuffix)
|
|
973
|
+
function createResponse(props) {
|
|
974
|
+
return {
|
|
975
|
+
...props,
|
|
976
|
+
kind: "Response"
|
|
1066
977
|
};
|
|
1067
|
-
return propNode;
|
|
1068
978
|
}
|
|
1069
|
-
//#endregion
|
|
1070
|
-
//#region src/utils.ts
|
|
1071
|
-
const plainStringTypes = new Set([
|
|
1072
|
-
"string",
|
|
1073
|
-
"uuid",
|
|
1074
|
-
"email",
|
|
1075
|
-
"url",
|
|
1076
|
-
"datetime"
|
|
1077
|
-
]);
|
|
1078
979
|
/**
|
|
1079
|
-
*
|
|
1080
|
-
* (base from the referenced definition) with any usage-site sibling fields set directly
|
|
1081
|
-
* on the ref node (description, readOnly, nullable, deprecated, etc.).
|
|
980
|
+
* Creates a `FunctionParameterNode`.
|
|
1082
981
|
*
|
|
1083
|
-
*
|
|
982
|
+
* `optional` defaults to `false`.
|
|
1084
983
|
*
|
|
1085
|
-
*
|
|
984
|
+
* @example Required typed param
|
|
985
|
+
* ```ts
|
|
986
|
+
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
|
|
987
|
+
* // → petId: string
|
|
988
|
+
* ```
|
|
989
|
+
*
|
|
990
|
+
* @example Optional param
|
|
991
|
+
* ```ts
|
|
992
|
+
* createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
993
|
+
* // → params?: QueryParams
|
|
994
|
+
* ```
|
|
995
|
+
*
|
|
996
|
+
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
997
|
+
* ```ts
|
|
998
|
+
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
999
|
+
* // → config: RequestConfig = {}
|
|
1000
|
+
* ```
|
|
1086
1001
|
*/
|
|
1087
|
-
function
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
return createSchema({
|
|
1094
|
-
...ref.schema,
|
|
1095
|
-
...definedOverrides
|
|
1096
|
-
});
|
|
1002
|
+
function createFunctionParameter(props) {
|
|
1003
|
+
return {
|
|
1004
|
+
optional: false,
|
|
1005
|
+
...props,
|
|
1006
|
+
kind: "FunctionParameter"
|
|
1007
|
+
};
|
|
1097
1008
|
}
|
|
1098
1009
|
/**
|
|
1099
|
-
*
|
|
1010
|
+
* Creates a {@link TypeNode} representing a language-agnostic structured type expression.
|
|
1100
1011
|
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1012
|
+
* Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
|
|
1013
|
+
* named field accessed from a group type. Each language's printer renders the variant
|
|
1014
|
+
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
1015
|
+
*
|
|
1016
|
+
* @example Reference type (TypeScript: `QueryParams`)
|
|
1017
|
+
* ```ts
|
|
1018
|
+
* createParamsType({ variant: 'reference', name: 'QueryParams' })
|
|
1019
|
+
* ```
|
|
1020
|
+
*
|
|
1021
|
+
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
|
|
1024
|
+
* ```
|
|
1025
|
+
*
|
|
1026
|
+
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
1027
|
+
* ```ts
|
|
1028
|
+
* createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
1029
|
+
* ```
|
|
1030
|
+
*/
|
|
1031
|
+
function createParamsType(props) {
|
|
1032
|
+
return {
|
|
1033
|
+
...props,
|
|
1034
|
+
kind: "ParamsType"
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
|
|
1039
|
+
*
|
|
1040
|
+
* @example Grouped param (TypeScript declaration)
|
|
1041
|
+
* ```ts
|
|
1042
|
+
* createParameterGroup({
|
|
1043
|
+
* properties: [
|
|
1044
|
+
* createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1045
|
+
* createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
|
|
1046
|
+
* ],
|
|
1047
|
+
* default: '{}',
|
|
1048
|
+
* })
|
|
1049
|
+
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
1050
|
+
* // call → { id, name }
|
|
1051
|
+
* ```
|
|
1052
|
+
*
|
|
1053
|
+
* @example Inline (spread) — children emitted as individual top-level parameters
|
|
1054
|
+
* ```ts
|
|
1055
|
+
* createParameterGroup({
|
|
1056
|
+
* properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
|
|
1057
|
+
* inline: true,
|
|
1058
|
+
* })
|
|
1059
|
+
* // declaration → petId: string
|
|
1060
|
+
* // call → petId
|
|
1061
|
+
* ```
|
|
1062
|
+
*/
|
|
1063
|
+
function createParameterGroup(props) {
|
|
1064
|
+
return {
|
|
1065
|
+
...props,
|
|
1066
|
+
kind: "ParameterGroup"
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
1103
1071
|
*
|
|
1104
1072
|
* @example
|
|
1105
1073
|
* ```ts
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1074
|
+
* createFunctionParameters({
|
|
1075
|
+
* params: [
|
|
1076
|
+
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1077
|
+
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
|
|
1078
|
+
* ],
|
|
1079
|
+
* })
|
|
1080
|
+
* ```
|
|
1081
|
+
*
|
|
1082
|
+
* @example
|
|
1083
|
+
* ```ts
|
|
1084
|
+
* const empty = createFunctionParameters()
|
|
1085
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
1108
1086
|
* ```
|
|
1109
1087
|
*/
|
|
1110
|
-
function
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1088
|
+
function createFunctionParameters(props = {}) {
|
|
1089
|
+
return {
|
|
1090
|
+
params: [],
|
|
1091
|
+
...props,
|
|
1092
|
+
kind: "FunctionParameters"
|
|
1093
|
+
};
|
|
1115
1094
|
}
|
|
1116
1095
|
/**
|
|
1117
|
-
*
|
|
1096
|
+
* Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
|
|
1118
1097
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1098
|
+
* @example Named import
|
|
1099
|
+
* ```ts
|
|
1100
|
+
* createImport({ name: ['useState'], path: 'react' })
|
|
1101
|
+
* // import { useState } from 'react'
|
|
1102
|
+
* ```
|
|
1121
1103
|
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1104
|
+
* @example Type-only import
|
|
1105
|
+
* ```ts
|
|
1106
|
+
* createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
|
|
1107
|
+
* // import type { FC } from 'react'
|
|
1108
|
+
* ```
|
|
1109
|
+
*/
|
|
1110
|
+
function createImport(props) {
|
|
1111
|
+
return {
|
|
1112
|
+
...props,
|
|
1113
|
+
kind: "Import"
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Creates an `ExportNode` representing a language-agnostic export/public API declaration.
|
|
1118
|
+
*
|
|
1119
|
+
* @example Named export
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* createExport({ name: ['Pet'], path: './Pet' })
|
|
1122
|
+
* // export { Pet } from './Pet'
|
|
1123
|
+
* ```
|
|
1124
|
+
*
|
|
1125
|
+
* @example Wildcard export
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* createExport({ path: './utils' })
|
|
1128
|
+
* // export * from './utils'
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
function createExport(props) {
|
|
1132
|
+
return {
|
|
1133
|
+
...props,
|
|
1134
|
+
kind: "Export"
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Creates a `SourceNode` representing a fragment of source code within a file.
|
|
1125
1139
|
*
|
|
1126
1140
|
* @example
|
|
1127
1141
|
* ```ts
|
|
1128
|
-
*
|
|
1129
|
-
* const cased = caseParams(params, 'camelcase')
|
|
1130
|
-
* // cased[0].name === 'petId'
|
|
1142
|
+
* createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
|
|
1131
1143
|
* ```
|
|
1132
1144
|
*/
|
|
1133
|
-
function
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
...param,
|
|
1139
|
-
name: transformed
|
|
1140
|
-
};
|
|
1141
|
-
});
|
|
1145
|
+
function createSource(props) {
|
|
1146
|
+
return {
|
|
1147
|
+
...props,
|
|
1148
|
+
kind: "Source"
|
|
1149
|
+
};
|
|
1142
1150
|
}
|
|
1143
1151
|
/**
|
|
1144
|
-
* Creates a
|
|
1152
|
+
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
1153
|
+
*
|
|
1154
|
+
* Computes:
|
|
1155
|
+
* - `id` — SHA256 hash of the file path
|
|
1156
|
+
* - `name` — `baseName` without extension
|
|
1157
|
+
* - `extname` — extension extracted from `baseName`
|
|
1158
|
+
*
|
|
1159
|
+
* Deduplicates:
|
|
1160
|
+
* - `sources` via `combineSources`
|
|
1161
|
+
* - `exports` via `combineExports`
|
|
1162
|
+
* - `imports` via `combineImports` (also filters unused imports)
|
|
1163
|
+
*
|
|
1164
|
+
* @throws {Error} when `baseName` has no extension.
|
|
1145
1165
|
*
|
|
1146
1166
|
* @example
|
|
1147
1167
|
* ```ts
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1168
|
+
* const file = createFile({
|
|
1169
|
+
* baseName: 'petStore.ts',
|
|
1170
|
+
* path: 'src/models/petStore.ts',
|
|
1171
|
+
* sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
|
|
1172
|
+
* imports: [createImport({ name: ['z'], path: 'zod' })],
|
|
1173
|
+
* exports: [createExport({ name: ['Pet'], path: './petStore' })],
|
|
1174
|
+
* })
|
|
1175
|
+
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
1176
|
+
* // file.name = 'petStore'
|
|
1177
|
+
* // file.extname = '.ts'
|
|
1150
1178
|
* ```
|
|
1151
1179
|
*/
|
|
1152
|
-
function
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1180
|
+
function createFile(input) {
|
|
1181
|
+
const extname = node_path.default.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
|
|
1182
|
+
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
1183
|
+
const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
|
|
1184
|
+
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
1185
|
+
const resolvedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
|
|
1186
|
+
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
1187
|
+
return {
|
|
1188
|
+
kind: "File",
|
|
1189
|
+
...input,
|
|
1190
|
+
id: (0, node_crypto.createHash)("sha256").update(input.path).digest("hex"),
|
|
1191
|
+
name: trimExtName(input.baseName),
|
|
1192
|
+
extname,
|
|
1193
|
+
imports: resolvedImports,
|
|
1194
|
+
exports: resolvedExports,
|
|
1195
|
+
sources: resolvedSources,
|
|
1196
|
+
meta: input.meta ?? {}
|
|
1197
|
+
};
|
|
1166
1198
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1199
|
+
/**
|
|
1200
|
+
* Creates a `ConstNode` representing a TypeScript `const` declaration.
|
|
1201
|
+
*
|
|
1202
|
+
* Mirrors the `Const` component from `@kubb/renderer-jsx`.
|
|
1203
|
+
* The component's `children` are represented as `nodes`.
|
|
1204
|
+
*
|
|
1205
|
+
* @example Simple constant
|
|
1206
|
+
* ```ts
|
|
1207
|
+
* createConst({ name: 'pet' })
|
|
1208
|
+
* // const pet = ...
|
|
1209
|
+
* ```
|
|
1210
|
+
*
|
|
1211
|
+
* @example Exported constant with type and `as const`
|
|
1212
|
+
* ```ts
|
|
1213
|
+
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
1214
|
+
* // export const pets: Pet[] = ... as const
|
|
1215
|
+
* ```
|
|
1216
|
+
*
|
|
1217
|
+
* @example With JSDoc and child nodes
|
|
1218
|
+
* ```ts
|
|
1219
|
+
* createConst({
|
|
1220
|
+
* name: 'config',
|
|
1221
|
+
* export: true,
|
|
1222
|
+
* JSDoc: { comments: ['@description App configuration'] },
|
|
1223
|
+
* nodes: [],
|
|
1224
|
+
* })
|
|
1225
|
+
* ```
|
|
1226
|
+
*/
|
|
1227
|
+
function createConst(props) {
|
|
1228
|
+
return {
|
|
1229
|
+
...props,
|
|
1230
|
+
kind: "Const"
|
|
1178
1231
|
};
|
|
1179
|
-
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
1180
|
-
if (groupName && groupName !== individualName) return createTypeNode({
|
|
1181
|
-
variant: "member",
|
|
1182
|
-
base: groupName,
|
|
1183
|
-
key: param.name
|
|
1184
|
-
});
|
|
1185
|
-
return createTypeNode({
|
|
1186
|
-
variant: "reference",
|
|
1187
|
-
name: individualName
|
|
1188
|
-
});
|
|
1189
1232
|
}
|
|
1190
1233
|
/**
|
|
1191
|
-
*
|
|
1234
|
+
* Creates a `TypeNode` representing a TypeScript `type` alias declaration.
|
|
1192
1235
|
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1236
|
+
* Mirrors the `Type` component from `@kubb/renderer-jsx`.
|
|
1237
|
+
* The component's `children` are represented as `nodes`.
|
|
1195
1238
|
*
|
|
1196
|
-
* @example
|
|
1239
|
+
* @example Simple type alias
|
|
1197
1240
|
* ```ts
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1241
|
+
* createType({ name: 'Pet' })
|
|
1242
|
+
* // type Pet = ...
|
|
1243
|
+
* ```
|
|
1244
|
+
*
|
|
1245
|
+
* @example Exported type with JSDoc
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* createType({
|
|
1248
|
+
* name: 'PetStatus',
|
|
1249
|
+
* export: true,
|
|
1250
|
+
* JSDoc: { comments: ['@description Status of a pet'] },
|
|
1203
1251
|
* })
|
|
1252
|
+
* // export type PetStatus = ...
|
|
1204
1253
|
* ```
|
|
1205
1254
|
*/
|
|
1206
|
-
function
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1255
|
+
function createType(props) {
|
|
1256
|
+
return {
|
|
1257
|
+
...props,
|
|
1258
|
+
kind: "Type"
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Creates a `FunctionNode` representing a TypeScript `function` declaration.
|
|
1263
|
+
*
|
|
1264
|
+
* Mirrors the `Function` component from `@kubb/renderer-jsx`.
|
|
1265
|
+
* The component's `children` are represented as `nodes`.
|
|
1266
|
+
*
|
|
1267
|
+
* @example Simple function
|
|
1268
|
+
* ```ts
|
|
1269
|
+
* createFunction({ name: 'getPet' })
|
|
1270
|
+
* // function getPet() { ... }
|
|
1271
|
+
* ```
|
|
1272
|
+
*
|
|
1273
|
+
* @example Exported async function with return type
|
|
1274
|
+
* ```ts
|
|
1275
|
+
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
1276
|
+
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
1277
|
+
* ```
|
|
1278
|
+
*
|
|
1279
|
+
* @example Function with generics and params
|
|
1280
|
+
* ```ts
|
|
1281
|
+
* createFunction({
|
|
1282
|
+
* name: 'identity',
|
|
1283
|
+
* export: true,
|
|
1284
|
+
* generics: ['T'],
|
|
1285
|
+
* params: 'value: T',
|
|
1286
|
+
* returnType: 'T',
|
|
1287
|
+
* })
|
|
1288
|
+
* // export function identity<T>(value: T): T { ... }
|
|
1289
|
+
* ```
|
|
1290
|
+
*/
|
|
1291
|
+
function createFunction(props) {
|
|
1292
|
+
return {
|
|
1293
|
+
...props,
|
|
1294
|
+
kind: "Function"
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
|
|
1299
|
+
*
|
|
1300
|
+
* Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
|
|
1301
|
+
* The component's `children` are represented as `nodes`.
|
|
1302
|
+
*
|
|
1303
|
+
* @example Simple arrow function
|
|
1304
|
+
* ```ts
|
|
1305
|
+
* createArrowFunction({ name: 'getPet' })
|
|
1306
|
+
* // const getPet = () => { ... }
|
|
1307
|
+
* ```
|
|
1308
|
+
*
|
|
1309
|
+
* @example Single-line exported arrow function
|
|
1310
|
+
* ```ts
|
|
1311
|
+
* createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
|
|
1312
|
+
* // export const double = (n: number) => ...
|
|
1313
|
+
* ```
|
|
1314
|
+
*
|
|
1315
|
+
* @example Async arrow function with generics
|
|
1316
|
+
* ```ts
|
|
1317
|
+
* createArrowFunction({
|
|
1318
|
+
* name: 'fetchPet',
|
|
1319
|
+
* export: true,
|
|
1320
|
+
* async: true,
|
|
1321
|
+
* generics: ['T'],
|
|
1322
|
+
* params: 'id: string',
|
|
1323
|
+
* returnType: 'T',
|
|
1324
|
+
* })
|
|
1325
|
+
* // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
|
|
1326
|
+
* ```
|
|
1327
|
+
*/
|
|
1328
|
+
function createArrowFunction(props) {
|
|
1329
|
+
return {
|
|
1330
|
+
...props,
|
|
1331
|
+
kind: "ArrowFunction"
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Creates a {@link TextNode} representing a raw string fragment in the source output.
|
|
1336
|
+
*
|
|
1337
|
+
* Use this instead of bare strings when building `nodes` arrays so that every
|
|
1338
|
+
* entry in the array is a typed {@link CodeNode}.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```ts
|
|
1342
|
+
* createText('return fetch(id)')
|
|
1343
|
+
* // { kind: 'Text', value: 'return fetch(id)' }
|
|
1344
|
+
* ```
|
|
1345
|
+
*/
|
|
1346
|
+
function createText(value) {
|
|
1347
|
+
return {
|
|
1348
|
+
value,
|
|
1349
|
+
kind: "Text"
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Creates a {@link BreakNode} representing a line break in the source output.
|
|
1354
|
+
*
|
|
1355
|
+
* Corresponds to `<br/>` in JSX components. Prints as an empty string which,
|
|
1356
|
+
* when joined with `\n` by `printNodes`, produces a blank line.
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* ```ts
|
|
1360
|
+
* createBreak()
|
|
1361
|
+
* // { kind: 'Break' }
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
function createBreak() {
|
|
1365
|
+
return { kind: "Break" };
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
|
|
1369
|
+
*
|
|
1370
|
+
* Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
|
|
1371
|
+
*
|
|
1372
|
+
* @example
|
|
1373
|
+
* ```ts
|
|
1374
|
+
* createJsx('<>\n <a href={href}>Open</a>\n</>')
|
|
1375
|
+
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
1376
|
+
* ```
|
|
1377
|
+
*/
|
|
1378
|
+
function createJsx(value) {
|
|
1379
|
+
return {
|
|
1380
|
+
value,
|
|
1381
|
+
kind: "Jsx"
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
//#endregion
|
|
1385
|
+
//#region src/printer.ts
|
|
1386
|
+
/**
|
|
1387
|
+
* Creates a schema printer factory.
|
|
1388
|
+
*
|
|
1389
|
+
* This function wraps a builder and makes options optional at call sites.
|
|
1390
|
+
*
|
|
1391
|
+
* The builder receives resolved options and returns:
|
|
1392
|
+
* - `name` — a unique identifier for the printer
|
|
1393
|
+
* - `options` — options stored on the returned printer instance
|
|
1394
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
1395
|
+
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
1396
|
+
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
1397
|
+
* - This keeps recursion safe and avoids self-calls
|
|
1398
|
+
*
|
|
1399
|
+
* When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
|
|
1400
|
+
*
|
|
1401
|
+
* @example Basic usage — Zod schema printer
|
|
1402
|
+
* ```ts
|
|
1403
|
+
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
1404
|
+
*
|
|
1405
|
+
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
1406
|
+
* name: 'zod',
|
|
1407
|
+
* options: { strict: options.strict ?? true },
|
|
1408
|
+
* nodes: {
|
|
1409
|
+
* string: () => 'z.string()',
|
|
1410
|
+
* object(node) {
|
|
1411
|
+
* const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
|
|
1412
|
+
* return `z.object({ ${props} })`
|
|
1413
|
+
* },
|
|
1414
|
+
* },
|
|
1415
|
+
* }))
|
|
1416
|
+
* ```
|
|
1417
|
+
*/
|
|
1418
|
+
function definePrinter(build) {
|
|
1419
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
1423
|
+
**
|
|
1424
|
+
* @example
|
|
1425
|
+
* ```ts
|
|
1426
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
1427
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
1428
|
+
* )
|
|
1429
|
+
* ```
|
|
1430
|
+
*/
|
|
1431
|
+
function createPrinterFactory(getKey) {
|
|
1432
|
+
return function(build) {
|
|
1433
|
+
return (options) => {
|
|
1434
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
1435
|
+
const context = {
|
|
1436
|
+
options: resolvedOptions,
|
|
1437
|
+
transform: (node) => {
|
|
1438
|
+
const key = getKey(node);
|
|
1439
|
+
if (key === void 0) return null;
|
|
1440
|
+
const handler = nodes[key];
|
|
1441
|
+
if (!handler) return null;
|
|
1442
|
+
return handler.call(context, node);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
return {
|
|
1446
|
+
name,
|
|
1447
|
+
options: resolvedOptions,
|
|
1448
|
+
transform: context.transform,
|
|
1449
|
+
print: printOverride ? printOverride.bind(context) : context.transform
|
|
1450
|
+
};
|
|
1451
|
+
};
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/refs.ts
|
|
1456
|
+
/**
|
|
1457
|
+
* Returns the last path segment of a reference string.
|
|
1458
|
+
*
|
|
1459
|
+
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* ```ts
|
|
1463
|
+
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
function extractRefName(ref) {
|
|
1467
|
+
return ref.split("/").at(-1) ?? ref;
|
|
1468
|
+
}
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/visitor.ts
|
|
1471
|
+
/**
|
|
1472
|
+
* Creates a small async concurrency limiter.
|
|
1473
|
+
*
|
|
1474
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
1475
|
+
*
|
|
1476
|
+
* @example
|
|
1477
|
+
* ```ts
|
|
1478
|
+
* const limit = createLimit(2)
|
|
1479
|
+
* for (const task of [taskA, taskB, taskC]) {
|
|
1480
|
+
* await limit(() => task())
|
|
1481
|
+
* }
|
|
1482
|
+
* // only 2 tasks run at the same time
|
|
1483
|
+
* ```
|
|
1484
|
+
*/
|
|
1485
|
+
function createLimit(concurrency) {
|
|
1486
|
+
let active = 0;
|
|
1487
|
+
const queue = [];
|
|
1488
|
+
function next() {
|
|
1489
|
+
if (active < concurrency && queue.length > 0) {
|
|
1490
|
+
active++;
|
|
1491
|
+
queue.shift()();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return function limit(fn) {
|
|
1495
|
+
return new Promise((resolve, reject) => {
|
|
1496
|
+
queue.push(() => {
|
|
1497
|
+
Promise.resolve(fn()).then(resolve, reject).finally(() => {
|
|
1498
|
+
active--;
|
|
1499
|
+
next();
|
|
1248
1500
|
});
|
|
1249
|
-
})
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1501
|
+
});
|
|
1502
|
+
next();
|
|
1503
|
+
});
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Returns the immediate traversable children of `node`.
|
|
1508
|
+
*
|
|
1509
|
+
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
1510
|
+
* `additionalProperties`) are only included
|
|
1511
|
+
* when `recurse` is `true`; shallow mode skips them.
|
|
1512
|
+
*
|
|
1513
|
+
* @example
|
|
1514
|
+
* ```ts
|
|
1515
|
+
* const children = getChildren(operationNode, true)
|
|
1516
|
+
* // returns parameters, requestBody schema (if present), and responses
|
|
1517
|
+
* ```
|
|
1518
|
+
*/
|
|
1519
|
+
function getChildren(node, recurse) {
|
|
1520
|
+
switch (node.kind) {
|
|
1521
|
+
case "Input": return [...node.schemas, ...node.operations];
|
|
1522
|
+
case "Output": return [];
|
|
1523
|
+
case "Operation": return [
|
|
1524
|
+
...node.parameters,
|
|
1525
|
+
...node.requestBody?.schema ? [node.requestBody.schema] : [],
|
|
1526
|
+
...node.responses
|
|
1271
1527
|
];
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1528
|
+
case "Schema": {
|
|
1529
|
+
const children = [];
|
|
1530
|
+
if (!recurse) return [];
|
|
1531
|
+
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
1532
|
+
if ("items" in node && node.items) children.push(...node.items);
|
|
1533
|
+
if ("members" in node && node.members) children.push(...node.members);
|
|
1534
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
1535
|
+
return children;
|
|
1536
|
+
}
|
|
1537
|
+
case "Property": return [node.schema];
|
|
1538
|
+
case "Parameter": return [node.schema];
|
|
1539
|
+
case "Response": return node.schema ? [node.schema] : [];
|
|
1540
|
+
case "FunctionParameter":
|
|
1541
|
+
case "ParameterGroup":
|
|
1542
|
+
case "FunctionParameters":
|
|
1543
|
+
case "Type": return [];
|
|
1544
|
+
default: return [];
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Depth-first traversal for side effects. Visitor return values are ignored.
|
|
1549
|
+
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
1550
|
+
* (default: `WALK_CONCURRENCY`).
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```ts
|
|
1554
|
+
* await walk(root, {
|
|
1555
|
+
* operation(node) {
|
|
1556
|
+
* console.log(node.operationId)
|
|
1557
|
+
* },
|
|
1558
|
+
* })
|
|
1559
|
+
* ```
|
|
1560
|
+
*
|
|
1561
|
+
* @example
|
|
1562
|
+
* ```ts
|
|
1563
|
+
* // Visit only the current node
|
|
1564
|
+
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
1565
|
+
* ```
|
|
1566
|
+
*/
|
|
1567
|
+
async function walk(node, options) {
|
|
1568
|
+
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
1569
|
+
}
|
|
1570
|
+
async function _walk(node, visitor, recurse, limit, parent) {
|
|
1571
|
+
switch (node.kind) {
|
|
1572
|
+
case "Input":
|
|
1573
|
+
await limit(() => visitor.input?.(node, { parent }));
|
|
1574
|
+
break;
|
|
1575
|
+
case "Output":
|
|
1576
|
+
await limit(() => visitor.output?.(node, { parent }));
|
|
1577
|
+
break;
|
|
1578
|
+
case "Operation":
|
|
1579
|
+
await limit(() => visitor.operation?.(node, { parent }));
|
|
1580
|
+
break;
|
|
1581
|
+
case "Schema":
|
|
1582
|
+
await limit(() => visitor.schema?.(node, { parent }));
|
|
1583
|
+
break;
|
|
1584
|
+
case "Property":
|
|
1585
|
+
await limit(() => visitor.property?.(node, { parent }));
|
|
1586
|
+
break;
|
|
1587
|
+
case "Parameter":
|
|
1588
|
+
await limit(() => visitor.parameter?.(node, { parent }));
|
|
1589
|
+
break;
|
|
1590
|
+
case "Response":
|
|
1591
|
+
await limit(() => visitor.response?.(node, { parent }));
|
|
1592
|
+
break;
|
|
1593
|
+
case "FunctionParameter":
|
|
1594
|
+
case "ParameterGroup":
|
|
1595
|
+
case "FunctionParameters": break;
|
|
1596
|
+
}
|
|
1597
|
+
const children = getChildren(node, recurse);
|
|
1598
|
+
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
1599
|
+
}
|
|
1600
|
+
function transform(node, options) {
|
|
1601
|
+
const { depth, parent, ...visitor } = options;
|
|
1602
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
1603
|
+
switch (node.kind) {
|
|
1604
|
+
case "Input": {
|
|
1605
|
+
let input = node;
|
|
1606
|
+
const replaced = visitor.input?.(input, { parent });
|
|
1607
|
+
if (replaced) input = replaced;
|
|
1608
|
+
return {
|
|
1609
|
+
...input,
|
|
1610
|
+
schemas: input.schemas.map((s) => transform(s, {
|
|
1611
|
+
...options,
|
|
1612
|
+
parent: input
|
|
1613
|
+
})),
|
|
1614
|
+
operations: input.operations.map((op) => transform(op, {
|
|
1615
|
+
...options,
|
|
1616
|
+
parent: input
|
|
1617
|
+
}))
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
case "Output": {
|
|
1621
|
+
let output = node;
|
|
1622
|
+
const replaced = visitor.output?.(output, { parent });
|
|
1623
|
+
if (replaced) output = replaced;
|
|
1624
|
+
return output;
|
|
1625
|
+
}
|
|
1626
|
+
case "Operation": {
|
|
1627
|
+
let op = node;
|
|
1628
|
+
const replaced = visitor.operation?.(op, { parent });
|
|
1629
|
+
if (replaced) op = replaced;
|
|
1630
|
+
return {
|
|
1631
|
+
...op,
|
|
1632
|
+
parameters: op.parameters.map((p) => transform(p, {
|
|
1633
|
+
...options,
|
|
1634
|
+
parent: op
|
|
1635
|
+
})),
|
|
1636
|
+
requestBody: op.requestBody ? {
|
|
1637
|
+
...op.requestBody,
|
|
1638
|
+
schema: op.requestBody.schema ? transform(op.requestBody.schema, {
|
|
1639
|
+
...options,
|
|
1640
|
+
parent: op
|
|
1641
|
+
}) : void 0
|
|
1642
|
+
} : void 0,
|
|
1643
|
+
responses: op.responses.map((r) => transform(r, {
|
|
1644
|
+
...options,
|
|
1645
|
+
parent: op
|
|
1646
|
+
}))
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
case "Schema": {
|
|
1650
|
+
let schema = node;
|
|
1651
|
+
const replaced = visitor.schema?.(schema, { parent });
|
|
1652
|
+
if (replaced) schema = replaced;
|
|
1653
|
+
const childOptions = {
|
|
1654
|
+
...options,
|
|
1655
|
+
parent: schema
|
|
1656
|
+
};
|
|
1657
|
+
return {
|
|
1658
|
+
...schema,
|
|
1659
|
+
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
1660
|
+
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
1661
|
+
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
1662
|
+
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
case "Property": {
|
|
1666
|
+
let prop = node;
|
|
1667
|
+
const replaced = visitor.property?.(prop, { parent });
|
|
1668
|
+
if (replaced) prop = replaced;
|
|
1669
|
+
return createProperty({
|
|
1670
|
+
...prop,
|
|
1671
|
+
schema: transform(prop.schema, {
|
|
1672
|
+
...options,
|
|
1673
|
+
parent: prop
|
|
1674
|
+
})
|
|
1296
1675
|
});
|
|
1297
|
-
params.push(createParameterGroup({
|
|
1298
|
-
properties: pathChildren,
|
|
1299
|
-
inline: pathParamsType === "inline",
|
|
1300
|
-
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
1301
|
-
}));
|
|
1302
1676
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1677
|
+
case "Parameter": {
|
|
1678
|
+
let param = node;
|
|
1679
|
+
const replaced = visitor.parameter?.(param, { parent });
|
|
1680
|
+
if (replaced) param = replaced;
|
|
1681
|
+
return createParameter({
|
|
1682
|
+
...param,
|
|
1683
|
+
schema: transform(param.schema, {
|
|
1684
|
+
...options,
|
|
1685
|
+
parent: param
|
|
1686
|
+
})
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
case "Response": {
|
|
1690
|
+
let response = node;
|
|
1691
|
+
const replaced = visitor.response?.(response, { parent });
|
|
1692
|
+
if (replaced) response = replaced;
|
|
1693
|
+
return {
|
|
1694
|
+
...response,
|
|
1695
|
+
schema: transform(response.schema, {
|
|
1696
|
+
...options,
|
|
1697
|
+
parent: response
|
|
1698
|
+
})
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
case "FunctionParameter":
|
|
1702
|
+
case "ParameterGroup":
|
|
1703
|
+
case "FunctionParameters":
|
|
1704
|
+
case "Type": return node;
|
|
1705
|
+
default: return node;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Composes multiple visitors into one visitor, applied left to right.
|
|
1710
|
+
*
|
|
1711
|
+
* For each node kind, output from one visitor is input to the next.
|
|
1712
|
+
* If a visitor returns `undefined`, the previous node value is kept.
|
|
1713
|
+
*
|
|
1714
|
+
* @example
|
|
1715
|
+
* ```ts
|
|
1716
|
+
* const visitor = composeTransformers(
|
|
1717
|
+
* { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
|
|
1718
|
+
* { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
|
|
1719
|
+
* )
|
|
1720
|
+
* ```
|
|
1721
|
+
*/
|
|
1722
|
+
function composeTransformers(...visitors) {
|
|
1723
|
+
return {
|
|
1724
|
+
input(node, context) {
|
|
1725
|
+
return visitors.reduce((acc, v) => v.input?.(acc, context) ?? acc, node);
|
|
1726
|
+
},
|
|
1727
|
+
output(node, context) {
|
|
1728
|
+
return visitors.reduce((acc, v) => v.output?.(acc, context) ?? acc, node);
|
|
1729
|
+
},
|
|
1730
|
+
operation(node, context) {
|
|
1731
|
+
return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
|
|
1732
|
+
},
|
|
1733
|
+
schema(node, context) {
|
|
1734
|
+
return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
|
|
1735
|
+
},
|
|
1736
|
+
property(node, context) {
|
|
1737
|
+
return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
|
|
1738
|
+
},
|
|
1739
|
+
parameter(node, context) {
|
|
1740
|
+
return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
|
|
1741
|
+
},
|
|
1742
|
+
response(node, context) {
|
|
1743
|
+
return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Runs a depth-first synchronous collection pass.
|
|
1749
|
+
*
|
|
1750
|
+
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
1751
|
+
*
|
|
1752
|
+
* @example
|
|
1753
|
+
* ```ts
|
|
1754
|
+
* const ids = collect(root, {
|
|
1755
|
+
* operation(node) {
|
|
1756
|
+
* return node.operationId
|
|
1757
|
+
* },
|
|
1758
|
+
* })
|
|
1759
|
+
* ```
|
|
1760
|
+
*
|
|
1761
|
+
* @example
|
|
1762
|
+
* ```ts
|
|
1763
|
+
* // Collect from only the current node
|
|
1764
|
+
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
1765
|
+
* ```
|
|
1766
|
+
*/
|
|
1767
|
+
function collect(node, options) {
|
|
1768
|
+
const { depth, parent, ...visitor } = options;
|
|
1769
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
1770
|
+
const results = [];
|
|
1771
|
+
let v;
|
|
1772
|
+
switch (node.kind) {
|
|
1773
|
+
case "Input":
|
|
1774
|
+
v = visitor.input?.(node, { parent });
|
|
1775
|
+
break;
|
|
1776
|
+
case "Output":
|
|
1777
|
+
v = visitor.output?.(node, { parent });
|
|
1778
|
+
break;
|
|
1779
|
+
case "Operation":
|
|
1780
|
+
v = visitor.operation?.(node, { parent });
|
|
1781
|
+
break;
|
|
1782
|
+
case "Schema":
|
|
1783
|
+
v = visitor.schema?.(node, { parent });
|
|
1784
|
+
break;
|
|
1785
|
+
case "Property":
|
|
1786
|
+
v = visitor.property?.(node, { parent });
|
|
1787
|
+
break;
|
|
1788
|
+
case "Parameter":
|
|
1789
|
+
v = visitor.parameter?.(node, { parent });
|
|
1790
|
+
break;
|
|
1791
|
+
case "Response":
|
|
1792
|
+
v = visitor.response?.(node, { parent });
|
|
1793
|
+
break;
|
|
1794
|
+
case "FunctionParameter":
|
|
1795
|
+
case "ParameterGroup":
|
|
1796
|
+
case "FunctionParameters": break;
|
|
1324
1797
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1798
|
+
if (v !== void 0) results.push(v);
|
|
1799
|
+
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
1800
|
+
...options,
|
|
1801
|
+
parent: node
|
|
1802
|
+
})) results.push(item);
|
|
1803
|
+
return results;
|
|
1804
|
+
}
|
|
1805
|
+
//#endregion
|
|
1806
|
+
//#region src/resolvers.ts
|
|
1807
|
+
function findDiscriminator(mapping, ref) {
|
|
1808
|
+
if (!mapping || !ref) return null;
|
|
1809
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
1810
|
+
}
|
|
1811
|
+
function childName(parentName, propName) {
|
|
1812
|
+
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
1813
|
+
}
|
|
1814
|
+
function enumPropName(parentName, propName, enumSuffix) {
|
|
1815
|
+
return pascalCase([
|
|
1816
|
+
parentName,
|
|
1817
|
+
propName,
|
|
1818
|
+
enumSuffix
|
|
1819
|
+
].filter(Boolean).join(" "));
|
|
1327
1820
|
}
|
|
1328
1821
|
/**
|
|
1329
|
-
*
|
|
1330
|
-
|
|
1822
|
+
* Collects import entries for all `ref` schema nodes in `node`.
|
|
1823
|
+
*/
|
|
1824
|
+
function collectImports({ node, nameMapping, resolve }) {
|
|
1825
|
+
return collect(node, { schema(schemaNode) {
|
|
1826
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1827
|
+
if (!schemaRef?.ref) return;
|
|
1828
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
1829
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
1830
|
+
if (!result) return;
|
|
1831
|
+
return result;
|
|
1832
|
+
} });
|
|
1833
|
+
}
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/transformers.ts
|
|
1836
|
+
/**
|
|
1837
|
+
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
1331
1838
|
*
|
|
1332
|
-
* If
|
|
1333
|
-
*
|
|
1839
|
+
* If `node` is not an object schema, or if the property does not exist, the input
|
|
1840
|
+
* node is returned as-is.
|
|
1841
|
+
*
|
|
1842
|
+
* @example
|
|
1843
|
+
* ```ts
|
|
1844
|
+
* const schema = createSchema({
|
|
1845
|
+
* type: 'object',
|
|
1846
|
+
* properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
|
|
1847
|
+
* })
|
|
1848
|
+
* const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
|
|
1849
|
+
* ```
|
|
1334
1850
|
*/
|
|
1335
|
-
function
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1851
|
+
function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
1852
|
+
const objectNode = narrowSchema(node, "object");
|
|
1853
|
+
if (!objectNode?.properties?.length) return node;
|
|
1854
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
|
|
1855
|
+
return createSchema({
|
|
1856
|
+
...objectNode,
|
|
1857
|
+
properties: objectNode.properties.map((prop) => {
|
|
1858
|
+
if (prop.name !== propertyName) return prop;
|
|
1859
|
+
return createProperty({
|
|
1860
|
+
...prop,
|
|
1861
|
+
schema: createSchema({
|
|
1862
|
+
type: "enum",
|
|
1863
|
+
primitive: "string",
|
|
1864
|
+
enumValues: values,
|
|
1865
|
+
name: enumName,
|
|
1866
|
+
readOnly: prop.schema.readOnly,
|
|
1867
|
+
writeOnly: prop.schema.writeOnly
|
|
1868
|
+
})
|
|
1869
|
+
});
|
|
1870
|
+
})
|
|
1871
|
+
});
|
|
1351
1872
|
}
|
|
1352
1873
|
/**
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1874
|
+
* Merges adjacent anonymous object members into a single anonymous object member.
|
|
1875
|
+
*
|
|
1876
|
+
* @example
|
|
1877
|
+
* ```ts
|
|
1878
|
+
* const merged = mergeAdjacentObjects([
|
|
1879
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
|
|
1880
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
|
|
1881
|
+
* ])
|
|
1882
|
+
* ```
|
|
1355
1883
|
*/
|
|
1356
|
-
function
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1884
|
+
function mergeAdjacentObjects(members) {
|
|
1885
|
+
return members.reduce((acc, member) => {
|
|
1886
|
+
const objectMember = narrowSchema(member, "object");
|
|
1887
|
+
if (objectMember && !objectMember.name) {
|
|
1888
|
+
const previous = acc.at(-1);
|
|
1889
|
+
const previousObject = previous ? narrowSchema(previous, "object") : void 0;
|
|
1890
|
+
if (previousObject && !previousObject.name) {
|
|
1891
|
+
acc[acc.length - 1] = createSchema({
|
|
1892
|
+
...previousObject,
|
|
1893
|
+
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1894
|
+
});
|
|
1895
|
+
return acc;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
acc.push(member);
|
|
1899
|
+
return acc;
|
|
1900
|
+
}, []);
|
|
1369
1901
|
}
|
|
1370
1902
|
/**
|
|
1371
|
-
*
|
|
1903
|
+
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
1372
1904
|
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1905
|
+
* @example
|
|
1906
|
+
* ```ts
|
|
1907
|
+
* const simplified = simplifyUnion([
|
|
1908
|
+
* createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
|
|
1909
|
+
* createSchema({ type: 'string' }),
|
|
1910
|
+
* ])
|
|
1911
|
+
* // keeps only string member
|
|
1912
|
+
* ```
|
|
1375
1913
|
*/
|
|
1376
|
-
function
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1914
|
+
function simplifyUnion(members) {
|
|
1915
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
1916
|
+
if (!scalarPrimitives.size) return members;
|
|
1917
|
+
return members.filter((member) => {
|
|
1918
|
+
const enumNode = narrowSchema(member, "enum");
|
|
1919
|
+
if (!enumNode) return true;
|
|
1920
|
+
const primitive = enumNode.primitive;
|
|
1921
|
+
if (!primitive) return true;
|
|
1922
|
+
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1923
|
+
if (scalarPrimitives.has(primitive)) return false;
|
|
1924
|
+
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1925
|
+
return true;
|
|
1388
1926
|
});
|
|
1389
1927
|
}
|
|
1928
|
+
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
1929
|
+
const enumNode = narrowSchema(propNode, "enum");
|
|
1930
|
+
if (enumNode?.primitive === "boolean") return {
|
|
1931
|
+
...propNode,
|
|
1932
|
+
name: void 0
|
|
1933
|
+
};
|
|
1934
|
+
if (enumNode) return {
|
|
1935
|
+
...propNode,
|
|
1936
|
+
name: enumPropName(parentName, propName, enumSuffix)
|
|
1937
|
+
};
|
|
1938
|
+
return propNode;
|
|
1939
|
+
}
|
|
1390
1940
|
//#endregion
|
|
1391
1941
|
exports.caseParams = caseParams;
|
|
1392
1942
|
exports.childName = childName;
|
|
1393
1943
|
exports.collect = collect;
|
|
1394
1944
|
exports.collectImports = collectImports;
|
|
1395
1945
|
exports.composeTransformers = composeTransformers;
|
|
1946
|
+
exports.createArrowFunction = createArrowFunction;
|
|
1947
|
+
exports.createBreak = createBreak;
|
|
1948
|
+
exports.createConst = createConst;
|
|
1396
1949
|
exports.createDiscriminantNode = createDiscriminantNode;
|
|
1950
|
+
exports.createExport = createExport;
|
|
1951
|
+
exports.createFile = createFile;
|
|
1952
|
+
exports.createFunction = createFunction;
|
|
1397
1953
|
exports.createFunctionParameter = createFunctionParameter;
|
|
1398
1954
|
exports.createFunctionParameters = createFunctionParameters;
|
|
1955
|
+
exports.createImport = createImport;
|
|
1956
|
+
exports.createInput = createInput;
|
|
1957
|
+
exports.createJsx = createJsx;
|
|
1399
1958
|
exports.createOperation = createOperation;
|
|
1400
1959
|
exports.createOperationParams = createOperationParams;
|
|
1960
|
+
exports.createOutput = createOutput;
|
|
1401
1961
|
exports.createParameter = createParameter;
|
|
1402
1962
|
exports.createParameterGroup = createParameterGroup;
|
|
1963
|
+
exports.createParamsType = createParamsType;
|
|
1403
1964
|
exports.createPrinterFactory = createPrinterFactory;
|
|
1404
1965
|
exports.createProperty = createProperty;
|
|
1405
1966
|
exports.createResponse = createResponse;
|
|
1406
|
-
exports.createRoot = createRoot;
|
|
1407
1967
|
exports.createSchema = createSchema;
|
|
1408
|
-
exports.
|
|
1968
|
+
exports.createSource = createSource;
|
|
1969
|
+
exports.createText = createText;
|
|
1970
|
+
exports.createType = createType;
|
|
1409
1971
|
exports.definePrinter = definePrinter;
|
|
1410
1972
|
exports.enumPropName = enumPropName;
|
|
1411
1973
|
exports.extractRefName = extractRefName;
|
|
1974
|
+
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
1412
1975
|
exports.findDiscriminator = findDiscriminator;
|
|
1413
1976
|
exports.httpMethods = httpMethods;
|
|
1977
|
+
exports.isInputNode = isInputNode;
|
|
1414
1978
|
exports.isOperationNode = isOperationNode;
|
|
1979
|
+
exports.isOutputNode = isOutputNode;
|
|
1415
1980
|
exports.isScalarPrimitive = isScalarPrimitive;
|
|
1416
1981
|
exports.isSchemaNode = isSchemaNode;
|
|
1417
1982
|
exports.isStringType = isStringType;
|
|
1418
1983
|
exports.mediaTypes = mediaTypes;
|
|
1419
1984
|
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
1420
1985
|
exports.narrowSchema = narrowSchema;
|
|
1986
|
+
exports.nodeKinds = nodeKinds;
|
|
1421
1987
|
exports.schemaTypes = schemaTypes;
|
|
1422
1988
|
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
1423
1989
|
exports.setEnumName = setEnumName;
|