@kubb/ast 5.0.0-alpha.3 → 5.0.0-alpha.30
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 +1123 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +283 -17
- package/dist/index.js +1102 -154
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/visitor-z-5U8NoF.d.ts +2200 -0
- package/package.json +3 -2
- package/src/constants.ts +118 -4
- package/src/factory.ts +332 -18
- package/src/guards.ts +63 -8
- package/src/index.ts +25 -7
- package/src/infer.ts +130 -0
- package/src/mocks.ts +12 -5
- package/src/nodes/base.ts +32 -4
- package/src/nodes/function.ts +201 -0
- package/src/nodes/http.ts +17 -5
- package/src/nodes/index.ts +21 -5
- package/src/nodes/operation.ts +69 -6
- 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 +440 -42
- package/src/printer.ts +172 -60
- package/src/refs.ts +36 -4
- package/src/resolvers.ts +45 -0
- package/src/transformers.ts +156 -0
- package/src/types.ts +13 -2
- package/src/utils.ts +431 -4
- package/src/visitor.ts +378 -81
- package/dist/visitor-oFfdU8QA.d.ts +0 -653
package/dist/index.cjs
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
Object.defineProperty;
|
|
3
2
|
//#endregion
|
|
4
3
|
//#region src/constants.ts
|
|
5
4
|
const visitorDepths = {
|
|
6
5
|
shallow: "shallow",
|
|
7
6
|
deep: "deep"
|
|
8
7
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Canonical schema type strings used by AST schema nodes.
|
|
10
|
+
*
|
|
11
|
+
* These values are used across the AST as stable discriminators
|
|
12
|
+
* (for example `schema.type === schemaTypes.object`).
|
|
13
|
+
*
|
|
14
|
+
* The map is grouped by intent:
|
|
15
|
+
* - primitives (`string`, `number`, `boolean`, ...)
|
|
16
|
+
* - structural/composite (`object`, `array`, `union`, ...)
|
|
17
|
+
* - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
|
|
18
|
+
*/
|
|
17
19
|
const schemaTypes = {
|
|
18
20
|
string: "string",
|
|
19
21
|
number: "number",
|
|
@@ -37,8 +39,27 @@ const schemaTypes = {
|
|
|
37
39
|
uuid: "uuid",
|
|
38
40
|
email: "email",
|
|
39
41
|
url: "url",
|
|
40
|
-
|
|
42
|
+
ipv4: "ipv4",
|
|
43
|
+
ipv6: "ipv6",
|
|
44
|
+
blob: "blob",
|
|
45
|
+
never: "never"
|
|
41
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Primitive scalar schema types used when simplifying union members.
|
|
49
|
+
*/
|
|
50
|
+
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
51
|
+
"string",
|
|
52
|
+
"number",
|
|
53
|
+
"integer",
|
|
54
|
+
"bigint",
|
|
55
|
+
"boolean"
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* Returns `true` when `type` is a scalar primitive schema type.
|
|
59
|
+
*/
|
|
60
|
+
function isScalarPrimitive(type) {
|
|
61
|
+
return SCALAR_PRIMITIVE_TYPES.has(type);
|
|
62
|
+
}
|
|
42
63
|
const httpMethods = {
|
|
43
64
|
get: "GET",
|
|
44
65
|
post: "POST",
|
|
@@ -73,7 +94,33 @@ const mediaTypes = {
|
|
|
73
94
|
//#endregion
|
|
74
95
|
//#region src/factory.ts
|
|
75
96
|
/**
|
|
76
|
-
*
|
|
97
|
+
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
98
|
+
*
|
|
99
|
+
* - `optional` is set for non-required, non-nullable schemas.
|
|
100
|
+
* - `nullish` is set for non-required, nullable schemas.
|
|
101
|
+
*/
|
|
102
|
+
function syncOptionality(schema, required) {
|
|
103
|
+
const nullable = schema.nullable ?? false;
|
|
104
|
+
return {
|
|
105
|
+
...schema,
|
|
106
|
+
optional: !required && !nullable ? true : void 0,
|
|
107
|
+
nullish: !required && nullable ? true : void 0
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a `RootNode` with stable defaults for `schemas` and `operations`.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const root = createRoot()
|
|
116
|
+
* // { kind: 'Root', schemas: [], operations: [] }
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* const root = createRoot({ schemas: [petSchema] })
|
|
122
|
+
* // keeps default operations: []
|
|
123
|
+
* ```
|
|
77
124
|
*/
|
|
78
125
|
function createRoot(overrides = {}) {
|
|
79
126
|
return {
|
|
@@ -84,7 +131,27 @@ function createRoot(overrides = {}) {
|
|
|
84
131
|
};
|
|
85
132
|
}
|
|
86
133
|
/**
|
|
87
|
-
* Creates an `OperationNode`.
|
|
134
|
+
* Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const operation = createOperation({
|
|
139
|
+
* operationId: 'getPetById',
|
|
140
|
+
* method: 'GET',
|
|
141
|
+
* path: '/pet/{petId}',
|
|
142
|
+
* })
|
|
143
|
+
* // tags, parameters, and responses are []
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const operation = createOperation({
|
|
149
|
+
* operationId: 'findPets',
|
|
150
|
+
* method: 'GET',
|
|
151
|
+
* path: '/pet/findByStatus',
|
|
152
|
+
* tags: ['pet'],
|
|
153
|
+
* })
|
|
154
|
+
* ```
|
|
88
155
|
*/
|
|
89
156
|
function createOperation(props) {
|
|
90
157
|
return {
|
|
@@ -95,39 +162,125 @@ function createOperation(props) {
|
|
|
95
162
|
kind: "Operation"
|
|
96
163
|
};
|
|
97
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Maps schema `type` to its underlying `primitive`.
|
|
167
|
+
* Primitive types map to themselves; special string formats map to `'string'`.
|
|
168
|
+
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
169
|
+
*/
|
|
170
|
+
const TYPE_TO_PRIMITIVE = {
|
|
171
|
+
string: "string",
|
|
172
|
+
number: "number",
|
|
173
|
+
integer: "integer",
|
|
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
|
+
};
|
|
98
190
|
function createSchema(props) {
|
|
191
|
+
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
99
192
|
if (props["type"] === "object") return {
|
|
100
193
|
properties: [],
|
|
194
|
+
primitive: "object",
|
|
101
195
|
...props,
|
|
102
196
|
kind: "Schema"
|
|
103
197
|
};
|
|
104
198
|
return {
|
|
199
|
+
primitive: inferredPrimitive,
|
|
105
200
|
...props,
|
|
106
201
|
kind: "Schema"
|
|
107
202
|
};
|
|
108
203
|
}
|
|
109
204
|
/**
|
|
110
|
-
* Creates a `PropertyNode`.
|
|
205
|
+
* Creates a `PropertyNode`.
|
|
206
|
+
*
|
|
207
|
+
* `required` defaults to `false`.
|
|
208
|
+
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const property = createProperty({
|
|
213
|
+
* name: 'status',
|
|
214
|
+
* schema: createSchema({ type: 'string' }),
|
|
215
|
+
* })
|
|
216
|
+
* // required=false, schema.optional=true
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const property = createProperty({
|
|
222
|
+
* name: 'status',
|
|
223
|
+
* required: true,
|
|
224
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
225
|
+
* })
|
|
226
|
+
* // required=true, no optional/nullish
|
|
227
|
+
* ```
|
|
111
228
|
*/
|
|
112
229
|
function createProperty(props) {
|
|
230
|
+
const required = props.required ?? false;
|
|
113
231
|
return {
|
|
114
|
-
required: false,
|
|
115
232
|
...props,
|
|
116
|
-
kind: "Property"
|
|
233
|
+
kind: "Property",
|
|
234
|
+
required,
|
|
235
|
+
schema: syncOptionality(props.schema, required)
|
|
117
236
|
};
|
|
118
237
|
}
|
|
119
238
|
/**
|
|
120
|
-
* Creates a `ParameterNode`.
|
|
239
|
+
* Creates a `ParameterNode`.
|
|
240
|
+
*
|
|
241
|
+
* `required` defaults to `false`.
|
|
242
|
+
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* const param = createParameter({
|
|
247
|
+
* name: 'petId',
|
|
248
|
+
* in: 'path',
|
|
249
|
+
* required: true,
|
|
250
|
+
* schema: createSchema({ type: 'string' }),
|
|
251
|
+
* })
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* const param = createParameter({
|
|
257
|
+
* name: 'status',
|
|
258
|
+
* in: 'query',
|
|
259
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
260
|
+
* })
|
|
261
|
+
* // required=false, schema.nullish=true
|
|
262
|
+
* ```
|
|
121
263
|
*/
|
|
122
264
|
function createParameter(props) {
|
|
265
|
+
const required = props.required ?? false;
|
|
123
266
|
return {
|
|
124
|
-
required: false,
|
|
125
267
|
...props,
|
|
126
|
-
kind: "Parameter"
|
|
268
|
+
kind: "Parameter",
|
|
269
|
+
required,
|
|
270
|
+
schema: syncOptionality(props.schema, required)
|
|
127
271
|
};
|
|
128
272
|
}
|
|
129
273
|
/**
|
|
130
274
|
* Creates a `ResponseNode`.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const response = createResponse({
|
|
279
|
+
* statusCode: '200',
|
|
280
|
+
* description: 'Success',
|
|
281
|
+
* schema: createSchema({ type: 'object', properties: [] }),
|
|
282
|
+
* })
|
|
283
|
+
* ```
|
|
131
284
|
*/
|
|
132
285
|
function createResponse(props) {
|
|
133
286
|
return {
|
|
@@ -135,10 +288,132 @@ function createResponse(props) {
|
|
|
135
288
|
kind: "Response"
|
|
136
289
|
};
|
|
137
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Creates a `FunctionParameterNode`.
|
|
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
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
309
|
+
* ```ts
|
|
310
|
+
* createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
311
|
+
* // → config: RequestConfig = {}
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
function createFunctionParameter(props) {
|
|
315
|
+
return {
|
|
316
|
+
optional: false,
|
|
317
|
+
...props,
|
|
318
|
+
kind: "FunctionParameter"
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Creates a {@link TypeNode} representing a language-agnostic structured type expression.
|
|
323
|
+
*
|
|
324
|
+
* Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
|
|
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
|
+
* ```
|
|
332
|
+
*
|
|
333
|
+
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
334
|
+
* ```ts
|
|
335
|
+
* createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
|
|
336
|
+
* ```
|
|
337
|
+
*
|
|
338
|
+
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
339
|
+
* ```ts
|
|
340
|
+
* createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
function createTypeNode(props) {
|
|
344
|
+
return {
|
|
345
|
+
...props,
|
|
346
|
+
kind: "Type"
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
|
|
351
|
+
*
|
|
352
|
+
* @example Grouped param (TypeScript declaration)
|
|
353
|
+
* ```ts
|
|
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
|
+
* ```
|
|
364
|
+
*
|
|
365
|
+
* @example Inline (spread) — children emitted as individual top-level parameters
|
|
366
|
+
* ```ts
|
|
367
|
+
* createParameterGroup({
|
|
368
|
+
* properties: [createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false })],
|
|
369
|
+
* inline: true,
|
|
370
|
+
* })
|
|
371
|
+
* // declaration → petId: string
|
|
372
|
+
* // call → petId
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
function createParameterGroup(props) {
|
|
376
|
+
return {
|
|
377
|
+
...props,
|
|
378
|
+
kind: "ParameterGroup"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* createFunctionParameters({
|
|
387
|
+
* params: [
|
|
388
|
+
* createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
|
|
389
|
+
* createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
|
|
390
|
+
* ],
|
|
391
|
+
* })
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const empty = createFunctionParameters()
|
|
397
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
function createFunctionParameters(props = {}) {
|
|
401
|
+
return {
|
|
402
|
+
params: [],
|
|
403
|
+
...props,
|
|
404
|
+
kind: "FunctionParameters"
|
|
405
|
+
};
|
|
406
|
+
}
|
|
138
407
|
//#endregion
|
|
139
408
|
//#region src/guards.ts
|
|
140
409
|
/**
|
|
141
|
-
* Narrows a `SchemaNode` to the
|
|
410
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* const schema = createSchema({ type: 'string' })
|
|
415
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
416
|
+
* ```
|
|
142
417
|
*/
|
|
143
418
|
function narrowSchema(node, type) {
|
|
144
419
|
return node?.type === type ? node : void 0;
|
|
@@ -146,129 +421,214 @@ function narrowSchema(node, type) {
|
|
|
146
421
|
function isKind(kind) {
|
|
147
422
|
return (node) => node.kind === kind;
|
|
148
423
|
}
|
|
424
|
+
isKind("Root");
|
|
149
425
|
/**
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
*
|
|
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
|
+
* ```
|
|
155
434
|
*/
|
|
156
435
|
const isOperationNode = isKind("Operation");
|
|
157
436
|
/**
|
|
158
|
-
*
|
|
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
|
+
* ```
|
|
159
445
|
*/
|
|
160
446
|
const isSchemaNode = isKind("Schema");
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*/
|
|
168
|
-
const isParameterNode = isKind("Parameter");
|
|
169
|
-
/**
|
|
170
|
-
* Type guard for `ResponseNode`.
|
|
171
|
-
*/
|
|
172
|
-
const isResponseNode = isKind("Response");
|
|
447
|
+
isKind("Property");
|
|
448
|
+
isKind("Parameter");
|
|
449
|
+
isKind("Response");
|
|
450
|
+
isKind("FunctionParameter");
|
|
451
|
+
isKind("ParameterGroup");
|
|
452
|
+
isKind("FunctionParameters");
|
|
173
453
|
//#endregion
|
|
174
454
|
//#region src/printer.ts
|
|
175
455
|
/**
|
|
176
|
-
* Creates a
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```ts
|
|
182
|
-
* type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, { strict: boolean }, string>
|
|
183
|
-
*
|
|
184
|
-
* export const zodPrinter = definePrinter<ZodPrinter>((options) => {
|
|
185
|
-
* const { strict = true } = options
|
|
186
|
-
* return {
|
|
187
|
-
* name: 'zod',
|
|
188
|
-
* options: { strict },
|
|
189
|
-
* nodes: {
|
|
190
|
-
* string(node) {
|
|
191
|
-
* return `z.string()`
|
|
192
|
-
* },
|
|
193
|
-
* object(node) {
|
|
194
|
-
* const props = node.properties
|
|
195
|
-
* ?.map(p => `${p.name}: ${this.print(p)}`)
|
|
196
|
-
* .join(', ') ?? ''
|
|
197
|
-
* return `z.object({ ${props} })`
|
|
198
|
-
* },
|
|
199
|
-
* },
|
|
200
|
-
* }
|
|
201
|
-
* })
|
|
456
|
+
* Creates a schema printer factory.
|
|
457
|
+
*
|
|
458
|
+
* This function wraps a builder and makes options optional at call sites.
|
|
202
459
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
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).
|
|
469
|
+
*
|
|
470
|
+
* @example Basic usage — Zod schema printer
|
|
471
|
+
* ```ts
|
|
472
|
+
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
473
|
+
*
|
|
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
|
+
* }))
|
|
207
485
|
* ```
|
|
208
486
|
*/
|
|
209
487
|
function definePrinter(build) {
|
|
210
|
-
return (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
488
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
492
|
+
**
|
|
493
|
+
* @example
|
|
494
|
+
* ```ts
|
|
495
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
496
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
497
|
+
* )
|
|
498
|
+
* ```
|
|
499
|
+
*/
|
|
500
|
+
function createPrinterFactory(getKey) {
|
|
501
|
+
return function(build) {
|
|
502
|
+
return (options) => {
|
|
503
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
504
|
+
const context = {
|
|
505
|
+
options: resolvedOptions,
|
|
506
|
+
transform: (node) => {
|
|
507
|
+
const key = getKey(node);
|
|
508
|
+
if (key === void 0) return null;
|
|
509
|
+
const handler = nodes[key];
|
|
510
|
+
if (!handler) return null;
|
|
511
|
+
return handler.call(context, node);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
name,
|
|
516
|
+
options: resolvedOptions,
|
|
517
|
+
transform: context.transform,
|
|
518
|
+
print: printOverride ? printOverride.bind(context) : context.transform
|
|
519
|
+
};
|
|
224
520
|
};
|
|
225
521
|
};
|
|
226
522
|
}
|
|
227
523
|
//#endregion
|
|
228
524
|
//#region src/refs.ts
|
|
229
525
|
/**
|
|
230
|
-
*
|
|
526
|
+
* Returns the last path segment of a reference string.
|
|
527
|
+
*
|
|
528
|
+
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```ts
|
|
532
|
+
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
533
|
+
* ```
|
|
231
534
|
*/
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
|
|
235
|
-
return map;
|
|
535
|
+
function extractRefName(ref) {
|
|
536
|
+
return ref.split("/").at(-1) ?? ref;
|
|
236
537
|
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region ../../internals/utils/src/casing.ts
|
|
237
540
|
/**
|
|
238
|
-
*
|
|
541
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
542
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
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.
|
|
239
546
|
*/
|
|
240
|
-
function
|
|
241
|
-
return
|
|
547
|
+
function toCamelOrPascal(text, pascal) {
|
|
548
|
+
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) => {
|
|
549
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
550
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
551
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
552
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
242
553
|
}
|
|
243
554
|
/**
|
|
244
|
-
*
|
|
555
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
556
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
557
|
+
* Segments are joined with `/` to form a file path.
|
|
558
|
+
*
|
|
559
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
560
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
245
561
|
*/
|
|
246
|
-
function
|
|
247
|
-
|
|
562
|
+
function applyToFileParts(text, transformPart) {
|
|
563
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
564
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Converts `text` to camelCase.
|
|
568
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
572
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
573
|
+
*/
|
|
574
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
575
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
576
|
+
prefix,
|
|
577
|
+
suffix
|
|
578
|
+
} : {}));
|
|
579
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Converts `text` to PascalCase.
|
|
583
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
587
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
588
|
+
*/
|
|
589
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
590
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
591
|
+
prefix,
|
|
592
|
+
suffix
|
|
593
|
+
}) : camelCase(part));
|
|
594
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
248
595
|
}
|
|
249
596
|
//#endregion
|
|
250
|
-
//#region src/
|
|
251
|
-
const plainStringTypes = new Set([
|
|
252
|
-
"string",
|
|
253
|
-
"uuid",
|
|
254
|
-
"email",
|
|
255
|
-
"url",
|
|
256
|
-
"datetime"
|
|
257
|
-
]);
|
|
597
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
258
598
|
/**
|
|
259
|
-
* Returns `true` when
|
|
599
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
260
600
|
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* isValidVarName('status') // true
|
|
604
|
+
* isValidVarName('class') // false (reserved word)
|
|
605
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
606
|
+
* ```
|
|
263
607
|
*/
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
608
|
+
function isValidVarName(name) {
|
|
609
|
+
try {
|
|
610
|
+
new Function(`var ${name}`);
|
|
611
|
+
} catch {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
return true;
|
|
269
615
|
}
|
|
270
616
|
//#endregion
|
|
271
617
|
//#region src/visitor.ts
|
|
618
|
+
/**
|
|
619
|
+
* Creates a small async concurrency limiter.
|
|
620
|
+
*
|
|
621
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
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
|
+
* ```
|
|
631
|
+
*/
|
|
272
632
|
function createLimit(concurrency) {
|
|
273
633
|
let active = 0;
|
|
274
634
|
const queue = [];
|
|
@@ -291,14 +651,24 @@ function createLimit(concurrency) {
|
|
|
291
651
|
};
|
|
292
652
|
}
|
|
293
653
|
/**
|
|
294
|
-
*
|
|
654
|
+
* Returns the immediate traversable children of `node`.
|
|
655
|
+
*
|
|
656
|
+
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
657
|
+
* `additionalProperties`) are only included
|
|
658
|
+
* when `recurse` is `true`; shallow mode skips them.
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* const children = getChildren(operationNode, true)
|
|
663
|
+
* // returns parameters, requestBody schema (if present), and responses
|
|
664
|
+
* ```
|
|
295
665
|
*/
|
|
296
666
|
function getChildren(node, recurse) {
|
|
297
667
|
switch (node.kind) {
|
|
298
668
|
case "Root": return [...node.schemas, ...node.operations];
|
|
299
669
|
case "Operation": return [
|
|
300
670
|
...node.parameters,
|
|
301
|
-
...node.requestBody ? [node.requestBody] : [],
|
|
671
|
+
...node.requestBody?.schema ? [node.requestBody.schema] : [],
|
|
302
672
|
...node.responses
|
|
303
673
|
];
|
|
304
674
|
case "Schema": {
|
|
@@ -307,163 +677,753 @@ function getChildren(node, recurse) {
|
|
|
307
677
|
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
308
678
|
if ("items" in node && node.items) children.push(...node.items);
|
|
309
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);
|
|
310
681
|
return children;
|
|
311
682
|
}
|
|
312
683
|
case "Property": return [node.schema];
|
|
313
684
|
case "Parameter": return [node.schema];
|
|
314
685
|
case "Response": return node.schema ? [node.schema] : [];
|
|
686
|
+
case "FunctionParameter":
|
|
687
|
+
case "ParameterGroup":
|
|
688
|
+
case "FunctionParameters":
|
|
689
|
+
case "Type": return [];
|
|
315
690
|
}
|
|
316
691
|
}
|
|
317
692
|
/**
|
|
318
693
|
* Depth-first traversal for side effects. Visitor return values are ignored.
|
|
319
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
694
|
+
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
695
|
+
* (default: `WALK_CONCURRENCY`).
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```ts
|
|
699
|
+
* await walk(root, {
|
|
700
|
+
* operation(node) {
|
|
701
|
+
* console.log(node.operationId)
|
|
702
|
+
* },
|
|
703
|
+
* })
|
|
704
|
+
* ```
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```ts
|
|
708
|
+
* // Visit only the current node
|
|
709
|
+
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
710
|
+
* ```
|
|
320
711
|
*/
|
|
321
|
-
async function walk(node,
|
|
322
|
-
return _walk(node,
|
|
712
|
+
async function walk(node, options) {
|
|
713
|
+
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
323
714
|
}
|
|
324
|
-
async function _walk(node, visitor, recurse, limit) {
|
|
715
|
+
async function _walk(node, visitor, recurse, limit, parent) {
|
|
325
716
|
switch (node.kind) {
|
|
326
717
|
case "Root":
|
|
327
|
-
await limit(() => visitor.root?.(node));
|
|
718
|
+
await limit(() => visitor.root?.(node, { parent }));
|
|
328
719
|
break;
|
|
329
720
|
case "Operation":
|
|
330
|
-
await limit(() => visitor.operation?.(node));
|
|
721
|
+
await limit(() => visitor.operation?.(node, { parent }));
|
|
331
722
|
break;
|
|
332
723
|
case "Schema":
|
|
333
|
-
await limit(() => visitor.schema?.(node));
|
|
724
|
+
await limit(() => visitor.schema?.(node, { parent }));
|
|
334
725
|
break;
|
|
335
726
|
case "Property":
|
|
336
|
-
await limit(() => visitor.property?.(node));
|
|
727
|
+
await limit(() => visitor.property?.(node, { parent }));
|
|
337
728
|
break;
|
|
338
729
|
case "Parameter":
|
|
339
|
-
await limit(() => visitor.parameter?.(node));
|
|
730
|
+
await limit(() => visitor.parameter?.(node, { parent }));
|
|
340
731
|
break;
|
|
341
732
|
case "Response":
|
|
342
|
-
await limit(() => visitor.response?.(node));
|
|
733
|
+
await limit(() => visitor.response?.(node, { parent }));
|
|
343
734
|
break;
|
|
735
|
+
case "FunctionParameter":
|
|
736
|
+
case "ParameterGroup":
|
|
737
|
+
case "FunctionParameters": break;
|
|
344
738
|
}
|
|
345
739
|
const children = getChildren(node, recurse);
|
|
346
|
-
|
|
740
|
+
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
347
741
|
}
|
|
348
|
-
function transform(node,
|
|
349
|
-
const
|
|
742
|
+
function transform(node, options) {
|
|
743
|
+
const { depth, parent, ...visitor } = options;
|
|
744
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
350
745
|
switch (node.kind) {
|
|
351
746
|
case "Root": {
|
|
352
747
|
let root = node;
|
|
353
|
-
const replaced = visitor.root?.(root);
|
|
748
|
+
const replaced = visitor.root?.(root, { parent });
|
|
354
749
|
if (replaced) root = replaced;
|
|
355
750
|
return {
|
|
356
751
|
...root,
|
|
357
|
-
schemas: root.schemas.map((s) => transform(s,
|
|
358
|
-
|
|
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
|
+
}))
|
|
359
760
|
};
|
|
360
761
|
}
|
|
361
762
|
case "Operation": {
|
|
362
763
|
let op = node;
|
|
363
|
-
const replaced = visitor.operation?.(op);
|
|
764
|
+
const replaced = visitor.operation?.(op, { parent });
|
|
364
765
|
if (replaced) op = replaced;
|
|
365
766
|
return {
|
|
366
767
|
...op,
|
|
367
|
-
parameters: op.parameters.map((p) => transform(p,
|
|
368
|
-
|
|
369
|
-
|
|
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
|
+
}))
|
|
370
783
|
};
|
|
371
784
|
}
|
|
372
785
|
case "Schema": {
|
|
373
786
|
let schema = node;
|
|
374
|
-
const replaced = visitor.schema?.(schema);
|
|
787
|
+
const replaced = visitor.schema?.(schema, { parent });
|
|
375
788
|
if (replaced) schema = replaced;
|
|
789
|
+
const childOptions = {
|
|
790
|
+
...options,
|
|
791
|
+
parent: schema
|
|
792
|
+
};
|
|
376
793
|
return {
|
|
377
794
|
...schema,
|
|
378
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p,
|
|
379
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i,
|
|
380
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m,
|
|
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) } : {}
|
|
381
799
|
};
|
|
382
800
|
}
|
|
383
801
|
case "Property": {
|
|
384
802
|
let prop = node;
|
|
385
|
-
const replaced = visitor.property?.(prop);
|
|
803
|
+
const replaced = visitor.property?.(prop, { parent });
|
|
386
804
|
if (replaced) prop = replaced;
|
|
387
|
-
return {
|
|
805
|
+
return createProperty({
|
|
388
806
|
...prop,
|
|
389
|
-
schema: transform(prop.schema,
|
|
390
|
-
|
|
807
|
+
schema: transform(prop.schema, {
|
|
808
|
+
...options,
|
|
809
|
+
parent: prop
|
|
810
|
+
})
|
|
811
|
+
});
|
|
391
812
|
}
|
|
392
813
|
case "Parameter": {
|
|
393
814
|
let param = node;
|
|
394
|
-
const replaced = visitor.parameter?.(param);
|
|
815
|
+
const replaced = visitor.parameter?.(param, { parent });
|
|
395
816
|
if (replaced) param = replaced;
|
|
396
|
-
return {
|
|
817
|
+
return createParameter({
|
|
397
818
|
...param,
|
|
398
|
-
schema: transform(param.schema,
|
|
399
|
-
|
|
819
|
+
schema: transform(param.schema, {
|
|
820
|
+
...options,
|
|
821
|
+
parent: param
|
|
822
|
+
})
|
|
823
|
+
});
|
|
400
824
|
}
|
|
401
825
|
case "Response": {
|
|
402
826
|
let response = node;
|
|
403
|
-
const replaced = visitor.response?.(response);
|
|
827
|
+
const replaced = visitor.response?.(response, { parent });
|
|
404
828
|
if (replaced) response = replaced;
|
|
405
829
|
return {
|
|
406
830
|
...response,
|
|
407
|
-
schema:
|
|
831
|
+
schema: transform(response.schema, {
|
|
832
|
+
...options,
|
|
833
|
+
parent: response
|
|
834
|
+
})
|
|
408
835
|
};
|
|
409
836
|
}
|
|
837
|
+
case "FunctionParameter":
|
|
838
|
+
case "ParameterGroup":
|
|
839
|
+
case "FunctionParameters":
|
|
840
|
+
case "Type": return node;
|
|
410
841
|
}
|
|
411
842
|
}
|
|
412
843
|
/**
|
|
413
|
-
*
|
|
844
|
+
* Composes multiple visitors into one visitor, applied left to right.
|
|
845
|
+
*
|
|
846
|
+
* For each node kind, output from one visitor is input to the next.
|
|
847
|
+
* If a visitor returns `undefined`, the previous node value is kept.
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```ts
|
|
851
|
+
* const visitor = composeTransformers(
|
|
852
|
+
* { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
|
|
853
|
+
* { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
|
|
854
|
+
* )
|
|
855
|
+
* ```
|
|
414
856
|
*/
|
|
415
|
-
function
|
|
416
|
-
|
|
857
|
+
function composeTransformers(...visitors) {
|
|
858
|
+
return {
|
|
859
|
+
root(node, context) {
|
|
860
|
+
return visitors.reduce((acc, v) => v.root?.(acc, context) ?? acc, node);
|
|
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
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Runs a depth-first synchronous collection pass.
|
|
881
|
+
*
|
|
882
|
+
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```ts
|
|
886
|
+
* const ids = collect(root, {
|
|
887
|
+
* operation(node) {
|
|
888
|
+
* return node.operationId
|
|
889
|
+
* },
|
|
890
|
+
* })
|
|
891
|
+
* ```
|
|
892
|
+
*
|
|
893
|
+
* @example
|
|
894
|
+
* ```ts
|
|
895
|
+
* // Collect from only the current node
|
|
896
|
+
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
function collect(node, options) {
|
|
900
|
+
const { depth, parent, ...visitor } = options;
|
|
901
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
417
902
|
const results = [];
|
|
418
903
|
let v;
|
|
419
904
|
switch (node.kind) {
|
|
420
905
|
case "Root":
|
|
421
|
-
v = visitor.root?.(node);
|
|
906
|
+
v = visitor.root?.(node, { parent });
|
|
422
907
|
break;
|
|
423
908
|
case "Operation":
|
|
424
|
-
v = visitor.operation?.(node);
|
|
909
|
+
v = visitor.operation?.(node, { parent });
|
|
425
910
|
break;
|
|
426
911
|
case "Schema":
|
|
427
|
-
v = visitor.schema?.(node);
|
|
912
|
+
v = visitor.schema?.(node, { parent });
|
|
428
913
|
break;
|
|
429
914
|
case "Property":
|
|
430
|
-
v = visitor.property?.(node);
|
|
915
|
+
v = visitor.property?.(node, { parent });
|
|
431
916
|
break;
|
|
432
917
|
case "Parameter":
|
|
433
|
-
v = visitor.parameter?.(node);
|
|
918
|
+
v = visitor.parameter?.(node, { parent });
|
|
434
919
|
break;
|
|
435
920
|
case "Response":
|
|
436
|
-
v = visitor.response?.(node);
|
|
921
|
+
v = visitor.response?.(node, { parent });
|
|
437
922
|
break;
|
|
923
|
+
case "FunctionParameter":
|
|
924
|
+
case "ParameterGroup":
|
|
925
|
+
case "FunctionParameters": break;
|
|
438
926
|
}
|
|
439
927
|
if (v !== void 0) results.push(v);
|
|
440
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child,
|
|
928
|
+
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
929
|
+
...options,
|
|
930
|
+
parent: node
|
|
931
|
+
})) results.push(item);
|
|
441
932
|
return results;
|
|
442
933
|
}
|
|
443
934
|
//#endregion
|
|
444
|
-
|
|
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(" "));
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Collects import entries for all `ref` schema nodes in `node`.
|
|
952
|
+
*/
|
|
953
|
+
function collectImports({ node, nameMapping, resolve }) {
|
|
954
|
+
return collect(node, { schema(schemaNode) {
|
|
955
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
956
|
+
if (!schemaRef?.ref) return;
|
|
957
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
958
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
959
|
+
if (!result) return;
|
|
960
|
+
return result;
|
|
961
|
+
} });
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
964
|
+
//#region src/transformers.ts
|
|
965
|
+
/**
|
|
966
|
+
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
967
|
+
*
|
|
968
|
+
* If `node` is not an object schema, or if the property does not exist, the input
|
|
969
|
+
* node is returned as-is.
|
|
970
|
+
*
|
|
971
|
+
* @example
|
|
972
|
+
* ```ts
|
|
973
|
+
* const schema = createSchema({
|
|
974
|
+
* type: 'object',
|
|
975
|
+
* properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
|
|
976
|
+
* })
|
|
977
|
+
* const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
|
|
978
|
+
* ```
|
|
979
|
+
*/
|
|
980
|
+
function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
981
|
+
const objectNode = narrowSchema(node, "object");
|
|
982
|
+
if (!objectNode?.properties?.length) return node;
|
|
983
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
|
|
984
|
+
return createSchema({
|
|
985
|
+
...objectNode,
|
|
986
|
+
properties: objectNode.properties.map((prop) => {
|
|
987
|
+
if (prop.name !== propertyName) return prop;
|
|
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
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Merges adjacent anonymous object members into a single anonymous object member.
|
|
1004
|
+
*
|
|
1005
|
+
* @example
|
|
1006
|
+
* ```ts
|
|
1007
|
+
* const merged = mergeAdjacentObjects([
|
|
1008
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
|
|
1009
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
|
|
1010
|
+
* ])
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
function mergeAdjacentObjects(members) {
|
|
1014
|
+
return members.reduce((acc, member) => {
|
|
1015
|
+
const objectMember = narrowSchema(member, "object");
|
|
1016
|
+
if (objectMember && !objectMember.name) {
|
|
1017
|
+
const previous = acc.at(-1);
|
|
1018
|
+
const previousObject = previous ? narrowSchema(previous, "object") : void 0;
|
|
1019
|
+
if (previousObject && !previousObject.name) {
|
|
1020
|
+
acc[acc.length - 1] = createSchema({
|
|
1021
|
+
...previousObject,
|
|
1022
|
+
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1023
|
+
});
|
|
1024
|
+
return acc;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
acc.push(member);
|
|
1028
|
+
return acc;
|
|
1029
|
+
}, []);
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```ts
|
|
1036
|
+
* const simplified = simplifyUnion([
|
|
1037
|
+
* createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
|
|
1038
|
+
* createSchema({ type: 'string' }),
|
|
1039
|
+
* ])
|
|
1040
|
+
* // keeps only string member
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
1043
|
+
function simplifyUnion(members) {
|
|
1044
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
1045
|
+
if (!scalarPrimitives.size) return members;
|
|
1046
|
+
return members.filter((member) => {
|
|
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)
|
|
1066
|
+
};
|
|
1067
|
+
return propNode;
|
|
1068
|
+
}
|
|
1069
|
+
//#endregion
|
|
1070
|
+
//#region src/utils.ts
|
|
1071
|
+
const plainStringTypes = new Set([
|
|
1072
|
+
"string",
|
|
1073
|
+
"uuid",
|
|
1074
|
+
"email",
|
|
1075
|
+
"url",
|
|
1076
|
+
"datetime"
|
|
1077
|
+
]);
|
|
1078
|
+
/**
|
|
1079
|
+
* Returns a merged schema view for a ref node, combining the resolved `node.schema`
|
|
1080
|
+
* (base from the referenced definition) with any usage-site sibling fields set directly
|
|
1081
|
+
* on the ref node (description, readOnly, nullable, deprecated, etc.).
|
|
1082
|
+
*
|
|
1083
|
+
* Usage-site fields take precedence over the resolved schema's own fields when both are defined.
|
|
1084
|
+
*
|
|
1085
|
+
* For non-ref nodes the node itself is returned unchanged.
|
|
1086
|
+
*/
|
|
1087
|
+
function syncSchemaRef(node) {
|
|
1088
|
+
const ref = narrowSchema(node, "ref");
|
|
1089
|
+
if (!ref) return node;
|
|
1090
|
+
if (!ref.schema) return node;
|
|
1091
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
1092
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
1093
|
+
return createSchema({
|
|
1094
|
+
...ref.schema,
|
|
1095
|
+
...definedOverrides
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Returns `true` when a schema is emitted as a plain `string` type.
|
|
1100
|
+
*
|
|
1101
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
1102
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
1103
|
+
*
|
|
1104
|
+
* @example
|
|
1105
|
+
* ```ts
|
|
1106
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
1107
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
1108
|
+
* ```
|
|
1109
|
+
*/
|
|
1110
|
+
function isStringType(node) {
|
|
1111
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
1112
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
1113
|
+
if (temporal) return temporal.representation !== "date";
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
1118
|
+
*
|
|
1119
|
+
* The input array is not mutated.
|
|
1120
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
1121
|
+
*
|
|
1122
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
1123
|
+
* in generated output match the desired casing while preserving
|
|
1124
|
+
* `OperationNode.parameters` for other consumers.
|
|
1125
|
+
*
|
|
1126
|
+
* @example
|
|
1127
|
+
* ```ts
|
|
1128
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
1129
|
+
* const cased = caseParams(params, 'camelcase')
|
|
1130
|
+
* // cased[0].name === 'petId'
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
function caseParams(params, casing) {
|
|
1134
|
+
if (!casing) return params;
|
|
1135
|
+
return params.map((param) => {
|
|
1136
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
1137
|
+
return {
|
|
1138
|
+
...param,
|
|
1139
|
+
name: transformed
|
|
1140
|
+
};
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```ts
|
|
1148
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
1149
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
1150
|
+
* ```
|
|
1151
|
+
*/
|
|
1152
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
1153
|
+
return createSchema({
|
|
1154
|
+
type: "object",
|
|
1155
|
+
primitive: "object",
|
|
1156
|
+
properties: [createProperty({
|
|
1157
|
+
name: propertyName,
|
|
1158
|
+
schema: createSchema({
|
|
1159
|
+
type: "enum",
|
|
1160
|
+
primitive: "string",
|
|
1161
|
+
enumValues: [value]
|
|
1162
|
+
}),
|
|
1163
|
+
required: true
|
|
1164
|
+
})]
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function resolveType({ node, param, resolver }) {
|
|
1168
|
+
if (!resolver) return createTypeNode({
|
|
1169
|
+
variant: "reference",
|
|
1170
|
+
name: param.schema.primitive ?? "unknown"
|
|
1171
|
+
});
|
|
1172
|
+
const individualName = resolver.resolveParamName(node, param);
|
|
1173
|
+
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
1174
|
+
const groupResolvers = {
|
|
1175
|
+
path: resolver.resolvePathParamsName,
|
|
1176
|
+
query: resolver.resolveQueryParamsName,
|
|
1177
|
+
header: resolver.resolveHeaderParamsName
|
|
1178
|
+
};
|
|
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
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
|
|
1192
|
+
*
|
|
1193
|
+
* Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
|
|
1194
|
+
* type resolution and `extraParams` for plugin-specific trailing parameters.
|
|
1195
|
+
*
|
|
1196
|
+
* @example
|
|
1197
|
+
* ```ts
|
|
1198
|
+
* const params = createOperationParams(node, {
|
|
1199
|
+
* paramsType: 'inline',
|
|
1200
|
+
* pathParamsType: 'inline',
|
|
1201
|
+
* resolver: tsResolver,
|
|
1202
|
+
* extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
|
|
1203
|
+
* })
|
|
1204
|
+
* ```
|
|
1205
|
+
*/
|
|
1206
|
+
function createOperationParams(node, options) {
|
|
1207
|
+
const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
|
|
1208
|
+
const dataName = paramNames?.data ?? "data";
|
|
1209
|
+
const paramsName = paramNames?.params ?? "params";
|
|
1210
|
+
const headersName = paramNames?.headers ?? "headers";
|
|
1211
|
+
const pathName = paramNames?.path ?? "pathParams";
|
|
1212
|
+
const wrapType = (type) => createTypeNode({
|
|
1213
|
+
variant: "reference",
|
|
1214
|
+
name: typeWrapper ? typeWrapper(type) : type
|
|
1215
|
+
});
|
|
1216
|
+
const wrapTypeNode = (type) => type.variant === "reference" ? wrapType(type.name) : type;
|
|
1217
|
+
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
1218
|
+
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
1219
|
+
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
1220
|
+
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
1221
|
+
const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
1222
|
+
const bodyRequired = node.requestBody?.required ?? false;
|
|
1223
|
+
const queryGroupType = resolver ? resolveGroupType({
|
|
1224
|
+
node,
|
|
1225
|
+
params: queryParams,
|
|
1226
|
+
groupMethod: resolver.resolveQueryParamsName,
|
|
1227
|
+
resolver
|
|
1228
|
+
}) : void 0;
|
|
1229
|
+
const headerGroupType = resolver ? resolveGroupType({
|
|
1230
|
+
node,
|
|
1231
|
+
params: headerParams,
|
|
1232
|
+
groupMethod: resolver.resolveHeaderParamsName,
|
|
1233
|
+
resolver
|
|
1234
|
+
}) : void 0;
|
|
1235
|
+
const params = [];
|
|
1236
|
+
if (paramsType === "object") {
|
|
1237
|
+
const children = [
|
|
1238
|
+
...pathParams.map((p) => {
|
|
1239
|
+
const type = resolveType({
|
|
1240
|
+
node,
|
|
1241
|
+
param: p,
|
|
1242
|
+
resolver
|
|
1243
|
+
});
|
|
1244
|
+
return createFunctionParameter({
|
|
1245
|
+
name: p.name,
|
|
1246
|
+
type: wrapTypeNode(type),
|
|
1247
|
+
optional: !p.required
|
|
1248
|
+
});
|
|
1249
|
+
}),
|
|
1250
|
+
...bodyType ? [createFunctionParameter({
|
|
1251
|
+
name: dataName,
|
|
1252
|
+
type: bodyType,
|
|
1253
|
+
optional: !bodyRequired
|
|
1254
|
+
})] : [],
|
|
1255
|
+
...buildGroupParam({
|
|
1256
|
+
name: paramsName,
|
|
1257
|
+
node,
|
|
1258
|
+
params: queryParams,
|
|
1259
|
+
groupType: queryGroupType,
|
|
1260
|
+
resolver,
|
|
1261
|
+
wrapType
|
|
1262
|
+
}),
|
|
1263
|
+
...buildGroupParam({
|
|
1264
|
+
name: headersName,
|
|
1265
|
+
node,
|
|
1266
|
+
params: headerParams,
|
|
1267
|
+
groupType: headerGroupType,
|
|
1268
|
+
resolver,
|
|
1269
|
+
wrapType
|
|
1270
|
+
})
|
|
1271
|
+
];
|
|
1272
|
+
if (children.length) params.push(createParameterGroup({
|
|
1273
|
+
properties: children,
|
|
1274
|
+
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
1275
|
+
}));
|
|
1276
|
+
} else {
|
|
1277
|
+
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
1278
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
|
|
1279
|
+
params.push(createFunctionParameter({
|
|
1280
|
+
name: pathName,
|
|
1281
|
+
type: spreadType ? wrapType(spreadType) : void 0,
|
|
1282
|
+
rest: true
|
|
1283
|
+
}));
|
|
1284
|
+
} else {
|
|
1285
|
+
const pathChildren = pathParams.map((p) => {
|
|
1286
|
+
const type = resolveType({
|
|
1287
|
+
node,
|
|
1288
|
+
param: p,
|
|
1289
|
+
resolver
|
|
1290
|
+
});
|
|
1291
|
+
return createFunctionParameter({
|
|
1292
|
+
name: p.name,
|
|
1293
|
+
type: wrapTypeNode(type),
|
|
1294
|
+
optional: !p.required
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
params.push(createParameterGroup({
|
|
1298
|
+
properties: pathChildren,
|
|
1299
|
+
inline: pathParamsType === "inline",
|
|
1300
|
+
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
1301
|
+
}));
|
|
1302
|
+
}
|
|
1303
|
+
if (bodyType) params.push(createFunctionParameter({
|
|
1304
|
+
name: dataName,
|
|
1305
|
+
type: bodyType,
|
|
1306
|
+
optional: !bodyRequired
|
|
1307
|
+
}));
|
|
1308
|
+
params.push(...buildGroupParam({
|
|
1309
|
+
name: paramsName,
|
|
1310
|
+
node,
|
|
1311
|
+
params: queryParams,
|
|
1312
|
+
groupType: queryGroupType,
|
|
1313
|
+
resolver,
|
|
1314
|
+
wrapType
|
|
1315
|
+
}));
|
|
1316
|
+
params.push(...buildGroupParam({
|
|
1317
|
+
name: headersName,
|
|
1318
|
+
node,
|
|
1319
|
+
params: headerParams,
|
|
1320
|
+
groupType: headerGroupType,
|
|
1321
|
+
resolver,
|
|
1322
|
+
wrapType
|
|
1323
|
+
}));
|
|
1324
|
+
}
|
|
1325
|
+
params.push(...extraParams);
|
|
1326
|
+
return createFunctionParameters({ params });
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
1330
|
+
* Returns an empty array when there are no params to emit.
|
|
1331
|
+
*
|
|
1332
|
+
* If a pre-resolved `groupType` is provided it emits `name: GroupType`.
|
|
1333
|
+
* Otherwise, it builds an inline struct from the individual params.
|
|
1334
|
+
*/
|
|
1335
|
+
function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
|
|
1336
|
+
if (groupType) return [createFunctionParameter({
|
|
1337
|
+
name,
|
|
1338
|
+
type: groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
1339
|
+
optional: groupType.optional
|
|
1340
|
+
})];
|
|
1341
|
+
if (params.length) return [createFunctionParameter({
|
|
1342
|
+
name,
|
|
1343
|
+
type: toStructType({
|
|
1344
|
+
node,
|
|
1345
|
+
params,
|
|
1346
|
+
resolver
|
|
1347
|
+
}),
|
|
1348
|
+
optional: params.every((p) => !p.required)
|
|
1349
|
+
})];
|
|
1350
|
+
return [];
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1354
|
+
* Returns `undefined` when the group name equals the individual param name (no real group).
|
|
1355
|
+
*/
|
|
1356
|
+
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1357
|
+
if (!params.length) return;
|
|
1358
|
+
const firstParam = params[0];
|
|
1359
|
+
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1360
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1361
|
+
const allOptional = params.every((p) => !p.required);
|
|
1362
|
+
return {
|
|
1363
|
+
type: createTypeNode({
|
|
1364
|
+
variant: "reference",
|
|
1365
|
+
name: groupName
|
|
1366
|
+
}),
|
|
1367
|
+
optional: allOptional
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
|
|
1372
|
+
*
|
|
1373
|
+
* Used when query or header parameters have no dedicated group type name.
|
|
1374
|
+
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
1375
|
+
*/
|
|
1376
|
+
function toStructType({ node, params, resolver }) {
|
|
1377
|
+
return createTypeNode({
|
|
1378
|
+
variant: "struct",
|
|
1379
|
+
properties: params.map((p) => ({
|
|
1380
|
+
name: p.name,
|
|
1381
|
+
optional: !p.required,
|
|
1382
|
+
type: resolveType({
|
|
1383
|
+
node,
|
|
1384
|
+
param: p,
|
|
1385
|
+
resolver
|
|
1386
|
+
})
|
|
1387
|
+
}))
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
//#endregion
|
|
1391
|
+
exports.caseParams = caseParams;
|
|
1392
|
+
exports.childName = childName;
|
|
445
1393
|
exports.collect = collect;
|
|
1394
|
+
exports.collectImports = collectImports;
|
|
1395
|
+
exports.composeTransformers = composeTransformers;
|
|
1396
|
+
exports.createDiscriminantNode = createDiscriminantNode;
|
|
1397
|
+
exports.createFunctionParameter = createFunctionParameter;
|
|
1398
|
+
exports.createFunctionParameters = createFunctionParameters;
|
|
446
1399
|
exports.createOperation = createOperation;
|
|
1400
|
+
exports.createOperationParams = createOperationParams;
|
|
447
1401
|
exports.createParameter = createParameter;
|
|
1402
|
+
exports.createParameterGroup = createParameterGroup;
|
|
1403
|
+
exports.createPrinterFactory = createPrinterFactory;
|
|
448
1404
|
exports.createProperty = createProperty;
|
|
449
1405
|
exports.createResponse = createResponse;
|
|
450
1406
|
exports.createRoot = createRoot;
|
|
451
1407
|
exports.createSchema = createSchema;
|
|
1408
|
+
exports.createTypeNode = createTypeNode;
|
|
452
1409
|
exports.definePrinter = definePrinter;
|
|
1410
|
+
exports.enumPropName = enumPropName;
|
|
1411
|
+
exports.extractRefName = extractRefName;
|
|
1412
|
+
exports.findDiscriminator = findDiscriminator;
|
|
453
1413
|
exports.httpMethods = httpMethods;
|
|
454
1414
|
exports.isOperationNode = isOperationNode;
|
|
455
|
-
exports.
|
|
456
|
-
exports.isPlainStringType = isPlainStringType;
|
|
457
|
-
exports.isPropertyNode = isPropertyNode;
|
|
458
|
-
exports.isResponseNode = isResponseNode;
|
|
459
|
-
exports.isRootNode = isRootNode;
|
|
1415
|
+
exports.isScalarPrimitive = isScalarPrimitive;
|
|
460
1416
|
exports.isSchemaNode = isSchemaNode;
|
|
1417
|
+
exports.isStringType = isStringType;
|
|
461
1418
|
exports.mediaTypes = mediaTypes;
|
|
1419
|
+
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
462
1420
|
exports.narrowSchema = narrowSchema;
|
|
463
|
-
exports.nodeKinds = nodeKinds;
|
|
464
|
-
exports.refMapToObject = refMapToObject;
|
|
465
|
-
exports.resolveRef = resolveRef;
|
|
466
1421
|
exports.schemaTypes = schemaTypes;
|
|
1422
|
+
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
1423
|
+
exports.setEnumName = setEnumName;
|
|
1424
|
+
exports.simplifyUnion = simplifyUnion;
|
|
1425
|
+
exports.syncOptionality = syncOptionality;
|
|
1426
|
+
exports.syncSchemaRef = syncSchemaRef;
|
|
467
1427
|
exports.transform = transform;
|
|
468
1428
|
exports.walk = walk;
|
|
469
1429
|
|