@kubb/ast 5.0.0-alpha.2 → 5.0.0-alpha.21
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 +1032 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +275 -9
- package/dist/index.js +1009 -129
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/visitor-COwfCgLK.d.ts +1983 -0
- package/package.json +3 -2
- package/src/constants.ts +97 -4
- package/src/factory.ts +276 -18
- package/src/guards.ts +63 -8
- package/src/index.ts +32 -6
- package/src/infer.ts +130 -0
- package/src/mocks.ts +12 -5
- package/src/nodes/base.ts +31 -4
- package/src/nodes/function.ts +131 -0
- package/src/nodes/http.ts +17 -5
- package/src/nodes/index.ts +18 -5
- package/src/nodes/operation.ts +61 -4
- package/src/nodes/parameter.ts +27 -1
- package/src/nodes/property.ts +23 -1
- package/src/nodes/response.ts +29 -3
- package/src/nodes/root.ts +41 -10
- package/src/nodes/schema.ts +328 -38
- package/src/printers/functionPrinter.ts +196 -0
- package/src/printers/index.ts +3 -0
- package/src/printers/printer.ts +217 -0
- package/src/refs.ts +36 -4
- package/src/resolvers.ts +45 -0
- package/src/transformers.ts +196 -0
- package/src/types.ts +9 -2
- package/src/utils.ts +77 -0
- package/src/visitor.ts +376 -81
- package/dist/visitor-CmsfJzro.d.ts +0 -649
- package/src/printer.ts +0 -129
package/dist/index.js
CHANGED
|
@@ -10,8 +10,22 @@ const nodeKinds = {
|
|
|
10
10
|
schema: "Schema",
|
|
11
11
|
property: "Property",
|
|
12
12
|
parameter: "Parameter",
|
|
13
|
-
response: "Response"
|
|
13
|
+
response: "Response",
|
|
14
|
+
functionParameter: "FunctionParameter",
|
|
15
|
+
objectBindingParameter: "ObjectBindingParameter",
|
|
16
|
+
functionParameters: "FunctionParameters"
|
|
14
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Canonical schema type strings used by AST schema nodes.
|
|
20
|
+
*
|
|
21
|
+
* These values are used across the AST as stable discriminators
|
|
22
|
+
* (for example `schema.type === schemaTypes.object`).
|
|
23
|
+
*
|
|
24
|
+
* The map is grouped by intent:
|
|
25
|
+
* - primitives (`string`, `number`, `boolean`, ...)
|
|
26
|
+
* - structural/composite (`object`, `array`, `union`, ...)
|
|
27
|
+
* - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
|
|
28
|
+
*/
|
|
15
29
|
const schemaTypes = {
|
|
16
30
|
string: "string",
|
|
17
31
|
number: "number",
|
|
@@ -35,8 +49,19 @@ const schemaTypes = {
|
|
|
35
49
|
uuid: "uuid",
|
|
36
50
|
email: "email",
|
|
37
51
|
url: "url",
|
|
38
|
-
blob: "blob"
|
|
52
|
+
blob: "blob",
|
|
53
|
+
never: "never"
|
|
39
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Primitive scalar schema types used when simplifying union members.
|
|
57
|
+
*/
|
|
58
|
+
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
59
|
+
"string",
|
|
60
|
+
"number",
|
|
61
|
+
"integer",
|
|
62
|
+
"bigint",
|
|
63
|
+
"boolean"
|
|
64
|
+
]);
|
|
40
65
|
const httpMethods = {
|
|
41
66
|
get: "GET",
|
|
42
67
|
post: "POST",
|
|
@@ -69,9 +94,241 @@ const mediaTypes = {
|
|
|
69
94
|
videoMp4: "video/mp4"
|
|
70
95
|
};
|
|
71
96
|
//#endregion
|
|
97
|
+
//#region ../../internals/utils/src/casing.ts
|
|
98
|
+
/**
|
|
99
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
100
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
101
|
+
* and capitalizes each word according to `pascal`.
|
|
102
|
+
*
|
|
103
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
104
|
+
*/
|
|
105
|
+
function toCamelOrPascal(text, pascal) {
|
|
106
|
+
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) => {
|
|
107
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
108
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
109
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
110
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
114
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
115
|
+
* Segments are joined with `/` to form a file path.
|
|
116
|
+
*
|
|
117
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
118
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
119
|
+
*/
|
|
120
|
+
function applyToFileParts(text, transformPart) {
|
|
121
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
122
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Converts `text` to camelCase.
|
|
126
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
130
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
131
|
+
*/
|
|
132
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
133
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
134
|
+
prefix,
|
|
135
|
+
suffix
|
|
136
|
+
} : {}));
|
|
137
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Converts `text` to PascalCase.
|
|
141
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
145
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
146
|
+
*/
|
|
147
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
148
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
149
|
+
prefix,
|
|
150
|
+
suffix
|
|
151
|
+
}) : camelCase(part));
|
|
152
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
156
|
+
/**
|
|
157
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* isValidVarName('status') // true
|
|
162
|
+
* isValidVarName('class') // false (reserved word)
|
|
163
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
function isValidVarName(name) {
|
|
167
|
+
try {
|
|
168
|
+
new Function(`var ${name}`);
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/guards.ts
|
|
176
|
+
/**
|
|
177
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const schema = createSchema({ type: 'string' })
|
|
182
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
function narrowSchema(node, type) {
|
|
186
|
+
return node?.type === type ? node : void 0;
|
|
187
|
+
}
|
|
188
|
+
function isKind(kind) {
|
|
189
|
+
return (node) => node.kind === kind;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Returns `true` when the input is a `RootNode`.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* if (isRootNode(node)) {
|
|
197
|
+
* console.log(node.schemas.length)
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
const isRootNode = isKind("Root");
|
|
202
|
+
/**
|
|
203
|
+
* Returns `true` when the input is an `OperationNode`.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* if (isOperationNode(node)) {
|
|
208
|
+
* console.log(node.operationId)
|
|
209
|
+
* }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
const isOperationNode = isKind("Operation");
|
|
213
|
+
/**
|
|
214
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* if (isSchemaNode(node)) {
|
|
219
|
+
* console.log(node.type)
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
const isSchemaNode = isKind("Schema");
|
|
224
|
+
/**
|
|
225
|
+
* Returns `true` when the input is a `PropertyNode`.
|
|
226
|
+
*/
|
|
227
|
+
const isPropertyNode = isKind("Property");
|
|
228
|
+
/**
|
|
229
|
+
* Returns `true` when the input is a `ParameterNode`.
|
|
230
|
+
*/
|
|
231
|
+
const isParameterNode = isKind("Parameter");
|
|
232
|
+
/**
|
|
233
|
+
* Returns `true` when the input is a `ResponseNode`.
|
|
234
|
+
*/
|
|
235
|
+
const isResponseNode = isKind("Response");
|
|
236
|
+
/**
|
|
237
|
+
* Returns `true` when the input is a `FunctionParameterNode`.
|
|
238
|
+
*/
|
|
239
|
+
const isFunctionParameterNode = isKind("FunctionParameter");
|
|
240
|
+
/**
|
|
241
|
+
* Returns `true` when the input is an `ObjectBindingParameterNode`.
|
|
242
|
+
*/
|
|
243
|
+
const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
|
|
244
|
+
/**
|
|
245
|
+
* Returns `true` when the input is a `FunctionParametersNode`.
|
|
246
|
+
*/
|
|
247
|
+
const isFunctionParametersNode = isKind("FunctionParameters");
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/utils.ts
|
|
250
|
+
const plainStringTypes = new Set([
|
|
251
|
+
"string",
|
|
252
|
+
"uuid",
|
|
253
|
+
"email",
|
|
254
|
+
"url",
|
|
255
|
+
"datetime"
|
|
256
|
+
]);
|
|
257
|
+
/**
|
|
258
|
+
* Returns `true` when a schema is emitted as a plain TypeScript `string`.
|
|
259
|
+
*
|
|
260
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
261
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
266
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
function isStringType(node) {
|
|
270
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
271
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
272
|
+
if (temporal) return temporal.representation !== "date";
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
277
|
+
*
|
|
278
|
+
* The input array is not mutated.
|
|
279
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
280
|
+
*
|
|
281
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
282
|
+
* in generated output match the desired casing while preserving
|
|
283
|
+
* `OperationNode.parameters` for other consumers.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
288
|
+
* const cased = caseParams(params, 'camelcase')
|
|
289
|
+
* // cased[0].name === 'petId'
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
function caseParams(params, casing) {
|
|
293
|
+
if (!casing) return params;
|
|
294
|
+
return params.map((param) => {
|
|
295
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
296
|
+
return {
|
|
297
|
+
...param,
|
|
298
|
+
name: transformed
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
304
|
+
*
|
|
305
|
+
* - `optional` is set for non-required, non-nullable schemas.
|
|
306
|
+
* - `nullish` is set for non-required, nullable schemas.
|
|
307
|
+
*/
|
|
308
|
+
function syncOptionality(required, schema) {
|
|
309
|
+
const nullable = schema.nullable ?? false;
|
|
310
|
+
return {
|
|
311
|
+
...schema,
|
|
312
|
+
optional: !required && !nullable ? true : void 0,
|
|
313
|
+
nullish: !required && nullable ? true : void 0
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
72
317
|
//#region src/factory.ts
|
|
73
318
|
/**
|
|
74
|
-
* Creates a `RootNode`.
|
|
319
|
+
* Creates a `RootNode` with stable defaults for `schemas` and `operations`.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* const root = createRoot()
|
|
324
|
+
* // { kind: 'Root', schemas: [], operations: [] }
|
|
325
|
+
* ```
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* const root = createRoot({ schemas: [petSchema] })
|
|
330
|
+
* // keeps default operations: []
|
|
331
|
+
* ```
|
|
75
332
|
*/
|
|
76
333
|
function createRoot(overrides = {}) {
|
|
77
334
|
return {
|
|
@@ -82,7 +339,27 @@ function createRoot(overrides = {}) {
|
|
|
82
339
|
};
|
|
83
340
|
}
|
|
84
341
|
/**
|
|
85
|
-
* Creates an `OperationNode`.
|
|
342
|
+
* Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* const operation = createOperation({
|
|
347
|
+
* operationId: 'getPetById',
|
|
348
|
+
* method: 'GET',
|
|
349
|
+
* path: '/pet/{petId}',
|
|
350
|
+
* })
|
|
351
|
+
* // tags, parameters, and responses are []
|
|
352
|
+
* ```
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* const operation = createOperation({
|
|
357
|
+
* operationId: 'findPets',
|
|
358
|
+
* method: 'GET',
|
|
359
|
+
* path: '/pet/findByStatus',
|
|
360
|
+
* tags: ['pet'],
|
|
361
|
+
* })
|
|
362
|
+
* ```
|
|
86
363
|
*/
|
|
87
364
|
function createOperation(props) {
|
|
88
365
|
return {
|
|
@@ -105,27 +382,85 @@ function createSchema(props) {
|
|
|
105
382
|
};
|
|
106
383
|
}
|
|
107
384
|
/**
|
|
108
|
-
* Creates a `PropertyNode`.
|
|
385
|
+
* Creates a `PropertyNode`.
|
|
386
|
+
*
|
|
387
|
+
* `required` defaults to `false`.
|
|
388
|
+
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* const property = createProperty({
|
|
393
|
+
* name: 'status',
|
|
394
|
+
* schema: createSchema({ type: 'string' }),
|
|
395
|
+
* })
|
|
396
|
+
* // required=false, schema.optional=true
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* const property = createProperty({
|
|
402
|
+
* name: 'status',
|
|
403
|
+
* required: true,
|
|
404
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
405
|
+
* })
|
|
406
|
+
* // required=true, no optional/nullish
|
|
407
|
+
* ```
|
|
109
408
|
*/
|
|
110
409
|
function createProperty(props) {
|
|
410
|
+
const required = props.required ?? false;
|
|
111
411
|
return {
|
|
112
|
-
required: false,
|
|
113
412
|
...props,
|
|
114
|
-
kind: "Property"
|
|
413
|
+
kind: "Property",
|
|
414
|
+
required,
|
|
415
|
+
schema: syncOptionality(required, props.schema)
|
|
115
416
|
};
|
|
116
417
|
}
|
|
117
418
|
/**
|
|
118
|
-
* Creates a `ParameterNode`.
|
|
419
|
+
* Creates a `ParameterNode`.
|
|
420
|
+
*
|
|
421
|
+
* `required` defaults to `false`.
|
|
422
|
+
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```ts
|
|
426
|
+
* const param = createParameter({
|
|
427
|
+
* name: 'petId',
|
|
428
|
+
* in: 'path',
|
|
429
|
+
* required: true,
|
|
430
|
+
* schema: createSchema({ type: 'string' }),
|
|
431
|
+
* })
|
|
432
|
+
* ```
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* const param = createParameter({
|
|
437
|
+
* name: 'status',
|
|
438
|
+
* in: 'query',
|
|
439
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
440
|
+
* })
|
|
441
|
+
* // required=false, schema.nullish=true
|
|
442
|
+
* ```
|
|
119
443
|
*/
|
|
120
444
|
function createParameter(props) {
|
|
445
|
+
const required = props.required ?? false;
|
|
121
446
|
return {
|
|
122
|
-
required: false,
|
|
123
447
|
...props,
|
|
124
|
-
kind: "Parameter"
|
|
448
|
+
kind: "Parameter",
|
|
449
|
+
required,
|
|
450
|
+
schema: syncOptionality(required, props.schema)
|
|
125
451
|
};
|
|
126
452
|
}
|
|
127
453
|
/**
|
|
128
454
|
* Creates a `ResponseNode`.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```ts
|
|
458
|
+
* const response = createResponse({
|
|
459
|
+
* statusCode: '200',
|
|
460
|
+
* description: 'Success',
|
|
461
|
+
* schema: createSchema({ type: 'object', properties: [] }),
|
|
462
|
+
* })
|
|
463
|
+
* ```
|
|
129
464
|
*/
|
|
130
465
|
function createResponse(props) {
|
|
131
466
|
return {
|
|
@@ -133,99 +468,330 @@ function createResponse(props) {
|
|
|
133
468
|
kind: "Response"
|
|
134
469
|
};
|
|
135
470
|
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/guards.ts
|
|
138
471
|
/**
|
|
139
|
-
*
|
|
472
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
477
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
478
|
+
* ```
|
|
140
479
|
*/
|
|
141
|
-
function
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
480
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
481
|
+
return createSchema({
|
|
482
|
+
type: "object",
|
|
483
|
+
primitive: "object",
|
|
484
|
+
properties: [createProperty({
|
|
485
|
+
name: propertyName,
|
|
486
|
+
schema: createSchema({
|
|
487
|
+
type: "enum",
|
|
488
|
+
primitive: "string",
|
|
489
|
+
enumValues: [value]
|
|
490
|
+
}),
|
|
491
|
+
required: true
|
|
492
|
+
})]
|
|
493
|
+
});
|
|
146
494
|
}
|
|
147
495
|
/**
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
496
|
+
* Creates a `FunctionParameterNode`.
|
|
497
|
+
*
|
|
498
|
+
* `optional` defaults to `false`.
|
|
499
|
+
*
|
|
500
|
+
* @example Required typed param
|
|
501
|
+
* ```ts
|
|
502
|
+
* createFunctionParameter({ name: 'petId', type: 'string' })
|
|
503
|
+
* // → petId: string
|
|
504
|
+
* ```
|
|
505
|
+
*
|
|
506
|
+
* @example Optional param
|
|
507
|
+
* ```ts
|
|
508
|
+
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
509
|
+
* // → params?: QueryParams
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
513
|
+
* ```ts
|
|
514
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
|
|
515
|
+
* // → config: RequestConfig = {}
|
|
516
|
+
* ```
|
|
153
517
|
*/
|
|
154
|
-
|
|
518
|
+
function createFunctionParameter(props) {
|
|
519
|
+
return {
|
|
520
|
+
optional: false,
|
|
521
|
+
...props,
|
|
522
|
+
kind: "FunctionParameter"
|
|
523
|
+
};
|
|
524
|
+
}
|
|
155
525
|
/**
|
|
156
|
-
*
|
|
526
|
+
* Creates an `ObjectBindingParameterNode` for object-destructured parameter groups.
|
|
527
|
+
*
|
|
528
|
+
* @example Destructured object param
|
|
529
|
+
* ```ts
|
|
530
|
+
* createObjectBindingParameter({
|
|
531
|
+
* properties: [
|
|
532
|
+
* createFunctionParameter({ name: 'id', type: 'string', optional: false }),
|
|
533
|
+
* createFunctionParameter({ name: 'name', type: 'string', optional: true }),
|
|
534
|
+
* ],
|
|
535
|
+
* default: '{}',
|
|
536
|
+
* })
|
|
537
|
+
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
538
|
+
* // call → { id, name }
|
|
539
|
+
* ```
|
|
540
|
+
*
|
|
541
|
+
* @example Inline mode — children emitted as individual top-level parameters
|
|
542
|
+
* ```ts
|
|
543
|
+
* createObjectBindingParameter({
|
|
544
|
+
* properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
|
|
545
|
+
* inline: true,
|
|
546
|
+
* })
|
|
547
|
+
* // declaration → petId: string
|
|
548
|
+
* // call → petId
|
|
549
|
+
* ```
|
|
157
550
|
*/
|
|
158
|
-
|
|
551
|
+
function createObjectBindingParameter(props) {
|
|
552
|
+
return {
|
|
553
|
+
...props,
|
|
554
|
+
kind: "ObjectBindingParameter"
|
|
555
|
+
};
|
|
556
|
+
}
|
|
159
557
|
/**
|
|
160
|
-
*
|
|
558
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* createFunctionParameters({
|
|
563
|
+
* params: [
|
|
564
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
565
|
+
* createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
|
|
566
|
+
* ],
|
|
567
|
+
* })
|
|
568
|
+
* ```
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```ts
|
|
572
|
+
* const empty = createFunctionParameters()
|
|
573
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
574
|
+
* ```
|
|
161
575
|
*/
|
|
162
|
-
|
|
576
|
+
function createFunctionParameters(props = {}) {
|
|
577
|
+
return {
|
|
578
|
+
params: [],
|
|
579
|
+
...props,
|
|
580
|
+
kind: "FunctionParameters"
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/printers/printer.ts
|
|
163
585
|
/**
|
|
164
|
-
*
|
|
586
|
+
* Creates a schema printer factory.
|
|
587
|
+
*
|
|
588
|
+
* This function wraps a builder and makes options optional at call sites.
|
|
589
|
+
*
|
|
590
|
+
* The builder receives resolved options and returns:
|
|
591
|
+
* - `name` — a unique identifier for the printer
|
|
592
|
+
* - `options` — options stored on the returned printer instance
|
|
593
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
594
|
+
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
595
|
+
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
596
|
+
* - This keeps recursion safe and avoids self-calls
|
|
597
|
+
*
|
|
598
|
+
* When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
|
|
599
|
+
*
|
|
600
|
+
* @example Basic usage — Zod schema printer
|
|
601
|
+
* ```ts
|
|
602
|
+
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
603
|
+
*
|
|
604
|
+
* export const zodPrinter = definePrinter<ZodPrinter>((options) => ({
|
|
605
|
+
* name: 'zod',
|
|
606
|
+
* options: { strict: options.strict ?? true },
|
|
607
|
+
* nodes: {
|
|
608
|
+
* string: () => 'z.string()',
|
|
609
|
+
* object(node) {
|
|
610
|
+
* const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
|
|
611
|
+
* return `z.object({ ${props} })`
|
|
612
|
+
* },
|
|
613
|
+
* },
|
|
614
|
+
* }))
|
|
615
|
+
* ```
|
|
165
616
|
*/
|
|
166
|
-
|
|
617
|
+
function definePrinter(build) {
|
|
618
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
619
|
+
}
|
|
167
620
|
/**
|
|
168
|
-
*
|
|
621
|
+
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
622
|
+
**
|
|
623
|
+
* @example
|
|
624
|
+
* ```ts
|
|
625
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
626
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
627
|
+
* )
|
|
628
|
+
* ```
|
|
169
629
|
*/
|
|
170
|
-
|
|
630
|
+
function createPrinterFactory(getKey) {
|
|
631
|
+
return function(build) {
|
|
632
|
+
return (options) => {
|
|
633
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
634
|
+
const context = {
|
|
635
|
+
options: resolvedOptions,
|
|
636
|
+
transform: (node) => {
|
|
637
|
+
const key = getKey(node);
|
|
638
|
+
if (key === void 0) return null;
|
|
639
|
+
const handler = nodes[key];
|
|
640
|
+
if (!handler) return null;
|
|
641
|
+
return handler.call(context, node);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
return {
|
|
645
|
+
name,
|
|
646
|
+
options: resolvedOptions,
|
|
647
|
+
transform: context.transform,
|
|
648
|
+
print: printOverride ? printOverride.bind(context) : context.transform
|
|
649
|
+
};
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
}
|
|
171
653
|
//#endregion
|
|
172
|
-
//#region src/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
654
|
+
//#region src/printers/functionPrinter.ts
|
|
655
|
+
const kindToHandlerKey = {
|
|
656
|
+
FunctionParameter: "functionParameter",
|
|
657
|
+
ObjectBindingParameter: "objectBindingParameter",
|
|
658
|
+
FunctionParameters: "functionParameters"
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Creates a function-parameter printer factory.
|
|
662
|
+
*
|
|
663
|
+
* This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
|
|
664
|
+
* (for function nodes) rather than by `node.type` (for schema nodes).
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```ts
|
|
668
|
+
* type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
|
|
669
|
+
*
|
|
670
|
+
* export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
|
|
671
|
+
* name: 'my',
|
|
672
|
+
* options,
|
|
673
|
+
* nodes: {
|
|
674
|
+
* functionParameter(node) {
|
|
675
|
+
* return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
|
|
676
|
+
* },
|
|
677
|
+
* objectBindingParameter(node) {
|
|
678
|
+
* const inner = node.properties.map(p => this.transform(p)).filter(Boolean).join(', ')
|
|
679
|
+
* return `{ ${inner} }`
|
|
680
|
+
* },
|
|
681
|
+
* functionParameters(node) {
|
|
682
|
+
* return node.params.map(p => this.transform(p)).filter(Boolean).join(', ')
|
|
197
683
|
* },
|
|
198
|
-
* }
|
|
684
|
+
* },
|
|
685
|
+
* }))
|
|
686
|
+
* ```
|
|
687
|
+
*/
|
|
688
|
+
const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
|
|
689
|
+
function rank(param) {
|
|
690
|
+
if (param.kind === "ObjectBindingParameter") {
|
|
691
|
+
if (param.default) return 2;
|
|
692
|
+
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
|
|
693
|
+
}
|
|
694
|
+
if (param.rest) return 3;
|
|
695
|
+
if (param.default) return 2;
|
|
696
|
+
return param.optional ? 1 : 0;
|
|
697
|
+
}
|
|
698
|
+
function sortParams(params) {
|
|
699
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
700
|
+
}
|
|
701
|
+
function sortChildParams(params) {
|
|
702
|
+
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Default function-signature printer.
|
|
706
|
+
* Covers the four standard output modes used across Kubb plugins.
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* const printer = functionPrinter({ mode: 'declaration' })
|
|
711
|
+
*
|
|
712
|
+
* const sig = createFunctionParameters({
|
|
713
|
+
* params: [
|
|
714
|
+
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
715
|
+
* createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
|
|
716
|
+
* ],
|
|
199
717
|
* })
|
|
200
718
|
*
|
|
201
|
-
*
|
|
202
|
-
* printer.name // 'zod'
|
|
203
|
-
* printer.options // { strict: false }
|
|
204
|
-
* printer.print(node) // 'z.string()'
|
|
719
|
+
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
205
720
|
* ```
|
|
206
721
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
722
|
+
const functionPrinter = defineFunctionPrinter((options) => ({
|
|
723
|
+
name: "functionParameters",
|
|
724
|
+
options,
|
|
725
|
+
nodes: {
|
|
726
|
+
functionParameter(node) {
|
|
727
|
+
const { mode, transformName, transformType } = this.options;
|
|
728
|
+
const name = transformName ? transformName(node.name) : node.name;
|
|
729
|
+
const type = node.type && transformType ? transformType(node.type) : node.type;
|
|
730
|
+
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
731
|
+
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
732
|
+
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
733
|
+
if (type) {
|
|
734
|
+
if (node.optional) return `${name}?: ${type}`;
|
|
735
|
+
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
|
|
215
736
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
737
|
+
return node.default ? `${name} = ${node.default}` : name;
|
|
738
|
+
},
|
|
739
|
+
objectBindingParameter(node) {
|
|
740
|
+
const { mode, transformName, transformType } = this.options;
|
|
741
|
+
const sorted = sortChildParams(node.properties);
|
|
742
|
+
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
743
|
+
if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
744
|
+
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
745
|
+
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
746
|
+
const names = sorted.map((p) => {
|
|
747
|
+
return transformName ? transformName(p.name) : p.name;
|
|
748
|
+
});
|
|
749
|
+
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
750
|
+
if (!nameStr) return null;
|
|
751
|
+
let typeAnnotation = node.type;
|
|
752
|
+
if (!typeAnnotation) {
|
|
753
|
+
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
754
|
+
const t = transformType && p.type ? transformType(p.type) : p.type;
|
|
755
|
+
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
756
|
+
});
|
|
757
|
+
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
758
|
+
}
|
|
759
|
+
if (typeAnnotation) {
|
|
760
|
+
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
761
|
+
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
762
|
+
}
|
|
763
|
+
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
764
|
+
},
|
|
765
|
+
functionParameters(node) {
|
|
766
|
+
return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}));
|
|
225
770
|
//#endregion
|
|
226
771
|
//#region src/refs.ts
|
|
227
772
|
/**
|
|
228
|
-
*
|
|
773
|
+
* Returns the last path segment of a reference string.
|
|
774
|
+
*
|
|
775
|
+
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
776
|
+
*
|
|
777
|
+
* @example
|
|
778
|
+
* ```ts
|
|
779
|
+
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
780
|
+
* ```
|
|
781
|
+
*/
|
|
782
|
+
function extractRefName(ref) {
|
|
783
|
+
return ref.split("/").at(-1) ?? ref;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Builds a `RefMap` from `root.schemas` using each schema's `name`.
|
|
787
|
+
*
|
|
788
|
+
* Unnamed schemas are skipped.
|
|
789
|
+
*
|
|
790
|
+
* @example
|
|
791
|
+
* ```ts
|
|
792
|
+
* const refMap = buildRefMap(root)
|
|
793
|
+
* const pet = refMap.get('Pet')
|
|
794
|
+
* ```
|
|
229
795
|
*/
|
|
230
796
|
function buildRefMap(root) {
|
|
231
797
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -233,19 +799,45 @@ function buildRefMap(root) {
|
|
|
233
799
|
return map;
|
|
234
800
|
}
|
|
235
801
|
/**
|
|
236
|
-
*
|
|
802
|
+
* Resolves a schema by name from a `RefMap`.
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```ts
|
|
806
|
+
* const petSchema = resolveRef(refMap, 'Pet')
|
|
807
|
+
* ```
|
|
237
808
|
*/
|
|
238
809
|
function resolveRef(refMap, ref) {
|
|
239
810
|
return refMap.get(ref);
|
|
240
811
|
}
|
|
241
812
|
/**
|
|
242
|
-
* Converts a `RefMap`
|
|
813
|
+
* Converts a `RefMap` into a plain object.
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```ts
|
|
817
|
+
* const refsObject = refMapToObject(refMap)
|
|
818
|
+
* ```
|
|
243
819
|
*/
|
|
244
820
|
function refMapToObject(refMap) {
|
|
245
821
|
return Object.fromEntries(refMap);
|
|
246
822
|
}
|
|
247
823
|
//#endregion
|
|
248
824
|
//#region src/visitor.ts
|
|
825
|
+
/**
|
|
826
|
+
* Creates a small async concurrency limiter.
|
|
827
|
+
*
|
|
828
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* ```ts
|
|
832
|
+
* const limit = createLimit(2)
|
|
833
|
+
* await Promise.all([
|
|
834
|
+
* limit(() => taskA()),
|
|
835
|
+
* limit(() => taskB()),
|
|
836
|
+
* limit(() => taskC()),
|
|
837
|
+
* ])
|
|
838
|
+
* // only 2 tasks run at the same time
|
|
839
|
+
* ```
|
|
840
|
+
*/
|
|
249
841
|
function createLimit(concurrency) {
|
|
250
842
|
let active = 0;
|
|
251
843
|
const queue = [];
|
|
@@ -268,14 +860,24 @@ function createLimit(concurrency) {
|
|
|
268
860
|
};
|
|
269
861
|
}
|
|
270
862
|
/**
|
|
271
|
-
*
|
|
863
|
+
* Returns the immediate traversable children of `node`.
|
|
864
|
+
*
|
|
865
|
+
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
866
|
+
* `additionalProperties`) are only included
|
|
867
|
+
* when `recurse` is `true`; shallow mode skips them.
|
|
868
|
+
*
|
|
869
|
+
* @example
|
|
870
|
+
* ```ts
|
|
871
|
+
* const children = getChildren(operationNode, true)
|
|
872
|
+
* // returns parameters, requestBody schema (if present), and responses
|
|
873
|
+
* ```
|
|
272
874
|
*/
|
|
273
875
|
function getChildren(node, recurse) {
|
|
274
876
|
switch (node.kind) {
|
|
275
877
|
case "Root": return [...node.schemas, ...node.operations];
|
|
276
878
|
case "Operation": return [
|
|
277
879
|
...node.parameters,
|
|
278
|
-
...node.requestBody ? [node.requestBody] : [],
|
|
880
|
+
...node.requestBody?.schema ? [node.requestBody.schema] : [],
|
|
279
881
|
...node.responses
|
|
280
882
|
];
|
|
281
883
|
case "Schema": {
|
|
@@ -284,140 +886,418 @@ function getChildren(node, recurse) {
|
|
|
284
886
|
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
285
887
|
if ("items" in node && node.items) children.push(...node.items);
|
|
286
888
|
if ("members" in node && node.members) children.push(...node.members);
|
|
889
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
287
890
|
return children;
|
|
288
891
|
}
|
|
289
892
|
case "Property": return [node.schema];
|
|
290
893
|
case "Parameter": return [node.schema];
|
|
291
894
|
case "Response": return node.schema ? [node.schema] : [];
|
|
895
|
+
case "FunctionParameter":
|
|
896
|
+
case "ObjectBindingParameter":
|
|
897
|
+
case "FunctionParameters": return [];
|
|
292
898
|
}
|
|
293
899
|
}
|
|
294
900
|
/**
|
|
295
901
|
* Depth-first traversal for side effects. Visitor return values are ignored.
|
|
296
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
902
|
+
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
903
|
+
* (default: `WALK_CONCURRENCY`).
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* await walk(root, {
|
|
908
|
+
* operation(node) {
|
|
909
|
+
* console.log(node.operationId)
|
|
910
|
+
* },
|
|
911
|
+
* })
|
|
912
|
+
* ```
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```ts
|
|
916
|
+
* // Visit only the current node
|
|
917
|
+
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
918
|
+
* ```
|
|
297
919
|
*/
|
|
298
|
-
async function walk(node,
|
|
299
|
-
return _walk(node,
|
|
920
|
+
async function walk(node, options) {
|
|
921
|
+
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
300
922
|
}
|
|
301
|
-
async function _walk(node, visitor, recurse, limit) {
|
|
923
|
+
async function _walk(node, visitor, recurse, limit, parent) {
|
|
302
924
|
switch (node.kind) {
|
|
303
925
|
case "Root":
|
|
304
|
-
await limit(() => visitor.root?.(node));
|
|
926
|
+
await limit(() => visitor.root?.(node, { parent }));
|
|
305
927
|
break;
|
|
306
928
|
case "Operation":
|
|
307
|
-
await limit(() => visitor.operation?.(node));
|
|
929
|
+
await limit(() => visitor.operation?.(node, { parent }));
|
|
308
930
|
break;
|
|
309
931
|
case "Schema":
|
|
310
|
-
await limit(() => visitor.schema?.(node));
|
|
932
|
+
await limit(() => visitor.schema?.(node, { parent }));
|
|
311
933
|
break;
|
|
312
934
|
case "Property":
|
|
313
|
-
await limit(() => visitor.property?.(node));
|
|
935
|
+
await limit(() => visitor.property?.(node, { parent }));
|
|
314
936
|
break;
|
|
315
937
|
case "Parameter":
|
|
316
|
-
await limit(() => visitor.parameter?.(node));
|
|
938
|
+
await limit(() => visitor.parameter?.(node, { parent }));
|
|
317
939
|
break;
|
|
318
940
|
case "Response":
|
|
319
|
-
await limit(() => visitor.response?.(node));
|
|
941
|
+
await limit(() => visitor.response?.(node, { parent }));
|
|
320
942
|
break;
|
|
943
|
+
case "FunctionParameter":
|
|
944
|
+
case "ObjectBindingParameter":
|
|
945
|
+
case "FunctionParameters": break;
|
|
321
946
|
}
|
|
322
947
|
const children = getChildren(node, recurse);
|
|
323
|
-
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
|
|
948
|
+
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
|
|
324
949
|
}
|
|
325
|
-
function transform(node,
|
|
326
|
-
const
|
|
950
|
+
function transform(node, options) {
|
|
951
|
+
const { depth, parent, ...visitor } = options;
|
|
952
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
327
953
|
switch (node.kind) {
|
|
328
954
|
case "Root": {
|
|
329
955
|
let root = node;
|
|
330
|
-
const replaced = visitor.root?.(root);
|
|
956
|
+
const replaced = visitor.root?.(root, { parent });
|
|
331
957
|
if (replaced) root = replaced;
|
|
332
958
|
return {
|
|
333
959
|
...root,
|
|
334
|
-
schemas: root.schemas.map((s) => transform(s,
|
|
335
|
-
|
|
960
|
+
schemas: root.schemas.map((s) => transform(s, {
|
|
961
|
+
...options,
|
|
962
|
+
parent: root
|
|
963
|
+
})),
|
|
964
|
+
operations: root.operations.map((op) => transform(op, {
|
|
965
|
+
...options,
|
|
966
|
+
parent: root
|
|
967
|
+
}))
|
|
336
968
|
};
|
|
337
969
|
}
|
|
338
970
|
case "Operation": {
|
|
339
971
|
let op = node;
|
|
340
|
-
const replaced = visitor.operation?.(op);
|
|
972
|
+
const replaced = visitor.operation?.(op, { parent });
|
|
341
973
|
if (replaced) op = replaced;
|
|
342
974
|
return {
|
|
343
975
|
...op,
|
|
344
|
-
parameters: op.parameters.map((p) => transform(p,
|
|
345
|
-
|
|
346
|
-
|
|
976
|
+
parameters: op.parameters.map((p) => transform(p, {
|
|
977
|
+
...options,
|
|
978
|
+
parent: op
|
|
979
|
+
})),
|
|
980
|
+
requestBody: op.requestBody ? {
|
|
981
|
+
...op.requestBody,
|
|
982
|
+
schema: op.requestBody.schema ? transform(op.requestBody.schema, {
|
|
983
|
+
...options,
|
|
984
|
+
parent: op
|
|
985
|
+
}) : void 0
|
|
986
|
+
} : void 0,
|
|
987
|
+
responses: op.responses.map((r) => transform(r, {
|
|
988
|
+
...options,
|
|
989
|
+
parent: op
|
|
990
|
+
}))
|
|
347
991
|
};
|
|
348
992
|
}
|
|
349
993
|
case "Schema": {
|
|
350
994
|
let schema = node;
|
|
351
|
-
const replaced = visitor.schema?.(schema);
|
|
995
|
+
const replaced = visitor.schema?.(schema, { parent });
|
|
352
996
|
if (replaced) schema = replaced;
|
|
997
|
+
const childOptions = {
|
|
998
|
+
...options,
|
|
999
|
+
parent: schema
|
|
1000
|
+
};
|
|
353
1001
|
return {
|
|
354
1002
|
...schema,
|
|
355
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p,
|
|
356
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i,
|
|
357
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m,
|
|
1003
|
+
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
1004
|
+
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
1005
|
+
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
1006
|
+
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
358
1007
|
};
|
|
359
1008
|
}
|
|
360
1009
|
case "Property": {
|
|
361
1010
|
let prop = node;
|
|
362
|
-
const replaced = visitor.property?.(prop);
|
|
1011
|
+
const replaced = visitor.property?.(prop, { parent });
|
|
363
1012
|
if (replaced) prop = replaced;
|
|
364
|
-
return {
|
|
1013
|
+
return createProperty({
|
|
365
1014
|
...prop,
|
|
366
|
-
schema: transform(prop.schema,
|
|
367
|
-
|
|
1015
|
+
schema: transform(prop.schema, {
|
|
1016
|
+
...options,
|
|
1017
|
+
parent: prop
|
|
1018
|
+
})
|
|
1019
|
+
});
|
|
368
1020
|
}
|
|
369
1021
|
case "Parameter": {
|
|
370
1022
|
let param = node;
|
|
371
|
-
const replaced = visitor.parameter?.(param);
|
|
1023
|
+
const replaced = visitor.parameter?.(param, { parent });
|
|
372
1024
|
if (replaced) param = replaced;
|
|
373
|
-
return {
|
|
1025
|
+
return createParameter({
|
|
374
1026
|
...param,
|
|
375
|
-
schema: transform(param.schema,
|
|
376
|
-
|
|
1027
|
+
schema: transform(param.schema, {
|
|
1028
|
+
...options,
|
|
1029
|
+
parent: param
|
|
1030
|
+
})
|
|
1031
|
+
});
|
|
377
1032
|
}
|
|
378
1033
|
case "Response": {
|
|
379
1034
|
let response = node;
|
|
380
|
-
const replaced = visitor.response?.(response);
|
|
1035
|
+
const replaced = visitor.response?.(response, { parent });
|
|
381
1036
|
if (replaced) response = replaced;
|
|
382
1037
|
return {
|
|
383
1038
|
...response,
|
|
384
|
-
schema:
|
|
1039
|
+
schema: transform(response.schema, {
|
|
1040
|
+
...options,
|
|
1041
|
+
parent: response
|
|
1042
|
+
})
|
|
385
1043
|
};
|
|
386
1044
|
}
|
|
1045
|
+
case "FunctionParameter":
|
|
1046
|
+
case "ObjectBindingParameter":
|
|
1047
|
+
case "FunctionParameters": return node;
|
|
387
1048
|
}
|
|
388
1049
|
}
|
|
389
1050
|
/**
|
|
390
|
-
*
|
|
1051
|
+
* Composes multiple visitors into one visitor, applied left to right.
|
|
1052
|
+
*
|
|
1053
|
+
* For each node kind, output from one visitor is input to the next.
|
|
1054
|
+
* If a visitor returns `undefined`, the previous node value is kept.
|
|
1055
|
+
*
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```ts
|
|
1058
|
+
* const visitor = composeTransformers(
|
|
1059
|
+
* { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
|
|
1060
|
+
* { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
|
|
1061
|
+
* )
|
|
1062
|
+
* ```
|
|
391
1063
|
*/
|
|
392
|
-
function
|
|
393
|
-
|
|
1064
|
+
function composeTransformers(...visitors) {
|
|
1065
|
+
return {
|
|
1066
|
+
root(node, context) {
|
|
1067
|
+
return visitors.reduce((acc, v) => v.root?.(acc, context) ?? acc, node);
|
|
1068
|
+
},
|
|
1069
|
+
operation(node, context) {
|
|
1070
|
+
return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
|
|
1071
|
+
},
|
|
1072
|
+
schema(node, context) {
|
|
1073
|
+
return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
|
|
1074
|
+
},
|
|
1075
|
+
property(node, context) {
|
|
1076
|
+
return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
|
|
1077
|
+
},
|
|
1078
|
+
parameter(node, context) {
|
|
1079
|
+
return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
|
|
1080
|
+
},
|
|
1081
|
+
response(node, context) {
|
|
1082
|
+
return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Runs a depth-first synchronous collection pass.
|
|
1088
|
+
*
|
|
1089
|
+
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
1090
|
+
*
|
|
1091
|
+
* @example
|
|
1092
|
+
* ```ts
|
|
1093
|
+
* const ids = collect(root, {
|
|
1094
|
+
* operation(node) {
|
|
1095
|
+
* return node.operationId
|
|
1096
|
+
* },
|
|
1097
|
+
* })
|
|
1098
|
+
* ```
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```ts
|
|
1102
|
+
* // Collect from only the current node
|
|
1103
|
+
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
1104
|
+
* ```
|
|
1105
|
+
*/
|
|
1106
|
+
function collect(node, options) {
|
|
1107
|
+
const { depth, parent, ...visitor } = options;
|
|
1108
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
394
1109
|
const results = [];
|
|
395
1110
|
let v;
|
|
396
1111
|
switch (node.kind) {
|
|
397
1112
|
case "Root":
|
|
398
|
-
v = visitor.root?.(node);
|
|
1113
|
+
v = visitor.root?.(node, { parent });
|
|
399
1114
|
break;
|
|
400
1115
|
case "Operation":
|
|
401
|
-
v = visitor.operation?.(node);
|
|
1116
|
+
v = visitor.operation?.(node, { parent });
|
|
402
1117
|
break;
|
|
403
1118
|
case "Schema":
|
|
404
|
-
v = visitor.schema?.(node);
|
|
1119
|
+
v = visitor.schema?.(node, { parent });
|
|
405
1120
|
break;
|
|
406
1121
|
case "Property":
|
|
407
|
-
v = visitor.property?.(node);
|
|
1122
|
+
v = visitor.property?.(node, { parent });
|
|
408
1123
|
break;
|
|
409
1124
|
case "Parameter":
|
|
410
|
-
v = visitor.parameter?.(node);
|
|
1125
|
+
v = visitor.parameter?.(node, { parent });
|
|
411
1126
|
break;
|
|
412
1127
|
case "Response":
|
|
413
|
-
v = visitor.response?.(node);
|
|
1128
|
+
v = visitor.response?.(node, { parent });
|
|
414
1129
|
break;
|
|
1130
|
+
case "FunctionParameter":
|
|
1131
|
+
case "ObjectBindingParameter":
|
|
1132
|
+
case "FunctionParameters": break;
|
|
415
1133
|
}
|
|
416
1134
|
if (v !== void 0) results.push(v);
|
|
417
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child,
|
|
1135
|
+
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
1136
|
+
...options,
|
|
1137
|
+
parent: node
|
|
1138
|
+
})) results.push(item);
|
|
418
1139
|
return results;
|
|
419
1140
|
}
|
|
420
1141
|
//#endregion
|
|
421
|
-
|
|
1142
|
+
//#region src/resolvers.ts
|
|
1143
|
+
function findDiscriminator(mapping, ref) {
|
|
1144
|
+
if (!mapping || !ref) return null;
|
|
1145
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
1146
|
+
}
|
|
1147
|
+
function childName(parentName, propName) {
|
|
1148
|
+
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
1149
|
+
}
|
|
1150
|
+
function enumPropName(parentName, propName, enumSuffix) {
|
|
1151
|
+
return pascalCase([
|
|
1152
|
+
parentName,
|
|
1153
|
+
propName,
|
|
1154
|
+
enumSuffix
|
|
1155
|
+
].filter(Boolean).join(" "));
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Collects import entries for all `ref` schema nodes in `node`.
|
|
1159
|
+
*/
|
|
1160
|
+
function collectImports({ node, nameMapping, resolve }) {
|
|
1161
|
+
return collect(node, { schema(schemaNode) {
|
|
1162
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1163
|
+
if (!schemaRef?.ref) return;
|
|
1164
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
1165
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
1166
|
+
if (!result) return;
|
|
1167
|
+
return result;
|
|
1168
|
+
} });
|
|
1169
|
+
}
|
|
1170
|
+
//#endregion
|
|
1171
|
+
//#region src/transformers.ts
|
|
1172
|
+
/**
|
|
1173
|
+
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
1174
|
+
*
|
|
1175
|
+
* If `node` is not an object schema, or if the property does not exist, the input
|
|
1176
|
+
* node is returned as-is.
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```ts
|
|
1180
|
+
* const schema = createSchema({
|
|
1181
|
+
* type: 'object',
|
|
1182
|
+
* properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
|
|
1183
|
+
* })
|
|
1184
|
+
* const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
|
|
1185
|
+
* ```
|
|
1186
|
+
*/
|
|
1187
|
+
function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
1188
|
+
const objectNode = narrowSchema(node, "object");
|
|
1189
|
+
if (!objectNode?.properties?.length) return node;
|
|
1190
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
|
|
1191
|
+
return createSchema({
|
|
1192
|
+
...objectNode,
|
|
1193
|
+
properties: objectNode.properties.map((prop) => {
|
|
1194
|
+
if (prop.name !== propertyName) return prop;
|
|
1195
|
+
return createProperty({
|
|
1196
|
+
...prop,
|
|
1197
|
+
schema: createSchema({
|
|
1198
|
+
type: "enum",
|
|
1199
|
+
primitive: "string",
|
|
1200
|
+
enumValues: values,
|
|
1201
|
+
name: enumName,
|
|
1202
|
+
readOnly: prop.schema.readOnly,
|
|
1203
|
+
writeOnly: prop.schema.writeOnly
|
|
1204
|
+
})
|
|
1205
|
+
});
|
|
1206
|
+
})
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Merges adjacent anonymous object members into a single anonymous object member.
|
|
1211
|
+
*
|
|
1212
|
+
* @example
|
|
1213
|
+
* ```ts
|
|
1214
|
+
* const merged = mergeAdjacentObjects([
|
|
1215
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
|
|
1216
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
|
|
1217
|
+
* ])
|
|
1218
|
+
* ```
|
|
1219
|
+
*/
|
|
1220
|
+
function mergeAdjacentObjects(members) {
|
|
1221
|
+
return members.reduce((acc, member) => {
|
|
1222
|
+
const objectMember = narrowSchema(member, "object");
|
|
1223
|
+
if (objectMember && !objectMember.name) {
|
|
1224
|
+
const previous = acc.at(-1);
|
|
1225
|
+
const previousObject = previous ? narrowSchema(previous, "object") : void 0;
|
|
1226
|
+
if (previousObject && !previousObject.name) {
|
|
1227
|
+
acc[acc.length - 1] = createSchema({
|
|
1228
|
+
...previousObject,
|
|
1229
|
+
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1230
|
+
});
|
|
1231
|
+
return acc;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
acc.push(member);
|
|
1235
|
+
return acc;
|
|
1236
|
+
}, []);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
1240
|
+
*
|
|
1241
|
+
* @example
|
|
1242
|
+
* ```ts
|
|
1243
|
+
* const simplified = simplifyUnion([
|
|
1244
|
+
* createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
|
|
1245
|
+
* createSchema({ type: 'string' }),
|
|
1246
|
+
* ])
|
|
1247
|
+
* // keeps only string member
|
|
1248
|
+
* ```
|
|
1249
|
+
*/
|
|
1250
|
+
function simplifyUnion(members) {
|
|
1251
|
+
const scalarPrimitives = new Set(members.filter((member) => SCALAR_PRIMITIVE_TYPES.has(member.type)).map((m) => m.type));
|
|
1252
|
+
if (!scalarPrimitives.size) return members;
|
|
1253
|
+
return members.filter((member) => {
|
|
1254
|
+
const enumNode = narrowSchema(member, "enum");
|
|
1255
|
+
if (!enumNode) return true;
|
|
1256
|
+
const primitive = enumNode.primitive;
|
|
1257
|
+
if (!primitive) return true;
|
|
1258
|
+
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1259
|
+
if (scalarPrimitives.has(primitive)) return false;
|
|
1260
|
+
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1261
|
+
return true;
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
1265
|
+
const enumNode = narrowSchema(propNode, "enum");
|
|
1266
|
+
if (enumNode?.primitive === "boolean") return {
|
|
1267
|
+
...propNode,
|
|
1268
|
+
name: void 0
|
|
1269
|
+
};
|
|
1270
|
+
if (enumNode) return {
|
|
1271
|
+
...propNode,
|
|
1272
|
+
name: enumPropName(parentName, propName, enumSuffix)
|
|
1273
|
+
};
|
|
1274
|
+
return propNode;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Walks a schema tree and resolves `ref`/`enum` names through callbacks.
|
|
1278
|
+
*/
|
|
1279
|
+
function resolveNames({ node, nameMapping, resolveName, resolveEnumName }) {
|
|
1280
|
+
return transform(node, { schema(schemaNode) {
|
|
1281
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1282
|
+
if (schemaRef && (schemaRef.ref || schemaRef.name)) {
|
|
1283
|
+
const rawRef = schemaRef.ref ?? schemaRef.name;
|
|
1284
|
+
const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
|
|
1285
|
+
if (resolved) return {
|
|
1286
|
+
...schemaNode,
|
|
1287
|
+
name: resolved
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
const schemaEnum = narrowSchema(schemaNode, "enum");
|
|
1291
|
+
if (schemaEnum?.name) {
|
|
1292
|
+
const resolved = (resolveEnumName ?? resolveName)(schemaEnum.name);
|
|
1293
|
+
if (resolved) return {
|
|
1294
|
+
...schemaNode,
|
|
1295
|
+
name: resolved
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
} });
|
|
1299
|
+
}
|
|
1300
|
+
//#endregion
|
|
1301
|
+
export { SCALAR_PRIMITIVE_TYPES, buildRefMap, caseParams, childName, collect, collectImports, composeTransformers, createDiscriminantNode, createFunctionParameter, createFunctionParameters, createObjectBindingParameter, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, defineFunctionPrinter, definePrinter, enumPropName, extractRefName, findDiscriminator, functionPrinter, httpMethods, isFunctionParameterNode, isFunctionParametersNode, isObjectBindingParameterNode, isOperationNode, isParameterNode, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, refMapToObject, resolveNames, resolveRef, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, transform, walk };
|
|
422
1302
|
|
|
423
1303
|
//# sourceMappingURL=index.js.map
|