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