@kubb/ast 5.0.0-alpha.22 → 5.0.0-alpha.23
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 +471 -441
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +159 -160
- package/dist/index.js +467 -426
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{visitor-COwfCgLK.d.ts → visitor-DsdLcLjR.d.ts} +206 -117
- package/package.json +1 -1
- package/src/constants.ts +15 -2
- package/src/factory.ts +69 -49
- package/src/guards.ts +3 -3
- package/src/index.ts +13 -20
- package/src/nodes/base.ts +2 -1
- package/src/nodes/function.ts +104 -34
- package/src/nodes/index.ts +1 -1
- package/src/nodes/operation.ts +7 -2
- package/src/nodes/schema.ts +3 -3
- package/src/{printers/printer.ts → printer.ts} +1 -1
- package/src/transformers.ts +2 -42
- package/src/types.ts +3 -2
- package/src/utils.ts +364 -13
- package/src/visitor.ts +6 -4
- package/src/printers/functionPrinter.ts +0 -196
- package/src/printers/index.ts +0 -3
package/dist/index.cjs
CHANGED
|
@@ -5,17 +5,6 @@ const visitorDepths = {
|
|
|
5
5
|
shallow: "shallow",
|
|
6
6
|
deep: "deep"
|
|
7
7
|
};
|
|
8
|
-
const nodeKinds = {
|
|
9
|
-
root: "Root",
|
|
10
|
-
operation: "Operation",
|
|
11
|
-
schema: "Schema",
|
|
12
|
-
property: "Property",
|
|
13
|
-
parameter: "Parameter",
|
|
14
|
-
response: "Response",
|
|
15
|
-
functionParameter: "FunctionParameter",
|
|
16
|
-
objectBindingParameter: "ObjectBindingParameter",
|
|
17
|
-
functionParameters: "FunctionParameters"
|
|
18
|
-
};
|
|
19
8
|
/**
|
|
20
9
|
* Canonical schema type strings used by AST schema nodes.
|
|
21
10
|
*
|
|
@@ -63,6 +52,12 @@ const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
|
63
52
|
"bigint",
|
|
64
53
|
"boolean"
|
|
65
54
|
]);
|
|
55
|
+
/**
|
|
56
|
+
* Returns `true` when `type` is a scalar primitive schema type.
|
|
57
|
+
*/
|
|
58
|
+
function isScalarPrimitive(type) {
|
|
59
|
+
return SCALAR_PRIMITIVE_TYPES.has(type);
|
|
60
|
+
}
|
|
66
61
|
const httpMethods = {
|
|
67
62
|
get: "GET",
|
|
68
63
|
post: "POST",
|
|
@@ -95,218 +90,14 @@ const mediaTypes = {
|
|
|
95
90
|
videoMp4: "video/mp4"
|
|
96
91
|
};
|
|
97
92
|
//#endregion
|
|
98
|
-
//#region
|
|
99
|
-
/**
|
|
100
|
-
* Shared implementation for camelCase and PascalCase conversion.
|
|
101
|
-
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
102
|
-
* and capitalizes each word according to `pascal`.
|
|
103
|
-
*
|
|
104
|
-
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
105
|
-
*/
|
|
106
|
-
function toCamelOrPascal(text, pascal) {
|
|
107
|
-
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) => {
|
|
108
|
-
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
109
|
-
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
110
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
111
|
-
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
115
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
116
|
-
* Segments are joined with `/` to form a file path.
|
|
117
|
-
*
|
|
118
|
-
* Only splits on dots followed by a letter so that version numbers
|
|
119
|
-
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
120
|
-
*/
|
|
121
|
-
function applyToFileParts(text, transformPart) {
|
|
122
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
123
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Converts `text` to camelCase.
|
|
127
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
131
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
132
|
-
*/
|
|
133
|
-
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
134
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
135
|
-
prefix,
|
|
136
|
-
suffix
|
|
137
|
-
} : {}));
|
|
138
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Converts `text` to PascalCase.
|
|
142
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* pascalCase('hello-world') // 'HelloWorld'
|
|
146
|
-
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
147
|
-
*/
|
|
148
|
-
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
149
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
150
|
-
prefix,
|
|
151
|
-
suffix
|
|
152
|
-
}) : camelCase(part));
|
|
153
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
154
|
-
}
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region ../../internals/utils/src/reserved.ts
|
|
157
|
-
/**
|
|
158
|
-
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* ```ts
|
|
162
|
-
* isValidVarName('status') // true
|
|
163
|
-
* isValidVarName('class') // false (reserved word)
|
|
164
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
function isValidVarName(name) {
|
|
168
|
-
try {
|
|
169
|
-
new Function(`var ${name}`);
|
|
170
|
-
} catch {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
//#endregion
|
|
176
|
-
//#region src/guards.ts
|
|
177
|
-
/**
|
|
178
|
-
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* ```ts
|
|
182
|
-
* const schema = createSchema({ type: 'string' })
|
|
183
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
184
|
-
* ```
|
|
185
|
-
*/
|
|
186
|
-
function narrowSchema(node, type) {
|
|
187
|
-
return node?.type === type ? node : void 0;
|
|
188
|
-
}
|
|
189
|
-
function isKind(kind) {
|
|
190
|
-
return (node) => node.kind === kind;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Returns `true` when the input is a `RootNode`.
|
|
194
|
-
*
|
|
195
|
-
* @example
|
|
196
|
-
* ```ts
|
|
197
|
-
* if (isRootNode(node)) {
|
|
198
|
-
* console.log(node.schemas.length)
|
|
199
|
-
* }
|
|
200
|
-
* ```
|
|
201
|
-
*/
|
|
202
|
-
const isRootNode = isKind("Root");
|
|
203
|
-
/**
|
|
204
|
-
* Returns `true` when the input is an `OperationNode`.
|
|
205
|
-
*
|
|
206
|
-
* @example
|
|
207
|
-
* ```ts
|
|
208
|
-
* if (isOperationNode(node)) {
|
|
209
|
-
* console.log(node.operationId)
|
|
210
|
-
* }
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
const isOperationNode = isKind("Operation");
|
|
214
|
-
/**
|
|
215
|
-
* Returns `true` when the input is a `SchemaNode`.
|
|
216
|
-
*
|
|
217
|
-
* @example
|
|
218
|
-
* ```ts
|
|
219
|
-
* if (isSchemaNode(node)) {
|
|
220
|
-
* console.log(node.type)
|
|
221
|
-
* }
|
|
222
|
-
* ```
|
|
223
|
-
*/
|
|
224
|
-
const isSchemaNode = isKind("Schema");
|
|
225
|
-
/**
|
|
226
|
-
* Returns `true` when the input is a `PropertyNode`.
|
|
227
|
-
*/
|
|
228
|
-
const isPropertyNode = isKind("Property");
|
|
229
|
-
/**
|
|
230
|
-
* Returns `true` when the input is a `ParameterNode`.
|
|
231
|
-
*/
|
|
232
|
-
const isParameterNode = isKind("Parameter");
|
|
233
|
-
/**
|
|
234
|
-
* Returns `true` when the input is a `ResponseNode`.
|
|
235
|
-
*/
|
|
236
|
-
const isResponseNode = isKind("Response");
|
|
237
|
-
/**
|
|
238
|
-
* Returns `true` when the input is a `FunctionParameterNode`.
|
|
239
|
-
*/
|
|
240
|
-
const isFunctionParameterNode = isKind("FunctionParameter");
|
|
241
|
-
/**
|
|
242
|
-
* Returns `true` when the input is an `ObjectBindingParameterNode`.
|
|
243
|
-
*/
|
|
244
|
-
const isObjectBindingParameterNode = isKind("ObjectBindingParameter");
|
|
245
|
-
/**
|
|
246
|
-
* Returns `true` when the input is a `FunctionParametersNode`.
|
|
247
|
-
*/
|
|
248
|
-
const isFunctionParametersNode = isKind("FunctionParameters");
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/utils.ts
|
|
251
|
-
const plainStringTypes = new Set([
|
|
252
|
-
"string",
|
|
253
|
-
"uuid",
|
|
254
|
-
"email",
|
|
255
|
-
"url",
|
|
256
|
-
"datetime"
|
|
257
|
-
]);
|
|
258
|
-
/**
|
|
259
|
-
* Returns `true` when a schema is emitted as a plain TypeScript `string`.
|
|
260
|
-
*
|
|
261
|
-
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
262
|
-
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* ```ts
|
|
266
|
-
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
267
|
-
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
function isStringType(node) {
|
|
271
|
-
if (plainStringTypes.has(node.type)) return true;
|
|
272
|
-
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
273
|
-
if (temporal) return temporal.representation !== "date";
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Applies casing rules to parameter names and returns a new parameter array.
|
|
278
|
-
*
|
|
279
|
-
* The input array is not mutated.
|
|
280
|
-
* If `casing` is not set, the original array is returned unchanged.
|
|
281
|
-
*
|
|
282
|
-
* Use this before passing parameters to schema builders so that property keys
|
|
283
|
-
* in generated output match the desired casing while preserving
|
|
284
|
-
* `OperationNode.parameters` for other consumers.
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* ```ts
|
|
288
|
-
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
289
|
-
* const cased = caseParams(params, 'camelcase')
|
|
290
|
-
* // cased[0].name === 'petId'
|
|
291
|
-
* ```
|
|
292
|
-
*/
|
|
293
|
-
function caseParams(params, casing) {
|
|
294
|
-
if (!casing) return params;
|
|
295
|
-
return params.map((param) => {
|
|
296
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
297
|
-
return {
|
|
298
|
-
...param,
|
|
299
|
-
name: transformed
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
93
|
+
//#region src/factory.ts
|
|
303
94
|
/**
|
|
304
95
|
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
305
96
|
*
|
|
306
97
|
* - `optional` is set for non-required, non-nullable schemas.
|
|
307
98
|
* - `nullish` is set for non-required, nullable schemas.
|
|
308
99
|
*/
|
|
309
|
-
function syncOptionality(
|
|
100
|
+
function syncOptionality(schema, required) {
|
|
310
101
|
const nullable = schema.nullable ?? false;
|
|
311
102
|
return {
|
|
312
103
|
...schema,
|
|
@@ -314,8 +105,6 @@ function syncOptionality(required, schema) {
|
|
|
314
105
|
nullish: !required && nullable ? true : void 0
|
|
315
106
|
};
|
|
316
107
|
}
|
|
317
|
-
//#endregion
|
|
318
|
-
//#region src/factory.ts
|
|
319
108
|
/**
|
|
320
109
|
* Creates a `RootNode` with stable defaults for `schemas` and `operations`.
|
|
321
110
|
*
|
|
@@ -413,7 +202,7 @@ function createProperty(props) {
|
|
|
413
202
|
...props,
|
|
414
203
|
kind: "Property",
|
|
415
204
|
required,
|
|
416
|
-
schema: syncOptionality(
|
|
205
|
+
schema: syncOptionality(props.schema, required)
|
|
417
206
|
};
|
|
418
207
|
}
|
|
419
208
|
/**
|
|
@@ -448,7 +237,7 @@ function createParameter(props) {
|
|
|
448
237
|
...props,
|
|
449
238
|
kind: "Parameter",
|
|
450
239
|
required,
|
|
451
|
-
schema: syncOptionality(
|
|
240
|
+
schema: syncOptionality(props.schema, required)
|
|
452
241
|
};
|
|
453
242
|
}
|
|
454
243
|
/**
|
|
@@ -470,49 +259,25 @@ function createResponse(props) {
|
|
|
470
259
|
};
|
|
471
260
|
}
|
|
472
261
|
/**
|
|
473
|
-
* Creates a single-property object schema used as a discriminator literal.
|
|
474
|
-
*
|
|
475
|
-
* @example
|
|
476
|
-
* ```ts
|
|
477
|
-
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
478
|
-
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
479
|
-
* ```
|
|
480
|
-
*/
|
|
481
|
-
function createDiscriminantNode({ propertyName, value }) {
|
|
482
|
-
return createSchema({
|
|
483
|
-
type: "object",
|
|
484
|
-
primitive: "object",
|
|
485
|
-
properties: [createProperty({
|
|
486
|
-
name: propertyName,
|
|
487
|
-
schema: createSchema({
|
|
488
|
-
type: "enum",
|
|
489
|
-
primitive: "string",
|
|
490
|
-
enumValues: [value]
|
|
491
|
-
}),
|
|
492
|
-
required: true
|
|
493
|
-
})]
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
262
|
* Creates a `FunctionParameterNode`.
|
|
498
263
|
*
|
|
499
264
|
* `optional` defaults to `false`.
|
|
500
265
|
*
|
|
501
266
|
* @example Required typed param
|
|
502
267
|
* ```ts
|
|
503
|
-
* createFunctionParameter({ name: 'petId', type: 'string' })
|
|
268
|
+
* createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }) })
|
|
504
269
|
* // → petId: string
|
|
505
270
|
* ```
|
|
506
271
|
*
|
|
507
272
|
* @example Optional param
|
|
508
273
|
* ```ts
|
|
509
|
-
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
274
|
+
* createFunctionParameter({ name: 'params', type: createTypeNode({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
510
275
|
* // → params?: QueryParams
|
|
511
276
|
* ```
|
|
512
277
|
*
|
|
513
278
|
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
514
279
|
* ```ts
|
|
515
|
-
* createFunctionParameter({ name: 'config', type: 'RequestConfig', default: '{}' })
|
|
280
|
+
* createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
516
281
|
* // → config: RequestConfig = {}
|
|
517
282
|
* ```
|
|
518
283
|
*/
|
|
@@ -524,14 +289,42 @@ function createFunctionParameter(props) {
|
|
|
524
289
|
};
|
|
525
290
|
}
|
|
526
291
|
/**
|
|
527
|
-
* Creates
|
|
292
|
+
* Creates a {@link TypeNode} representing a language-agnostic structured type expression.
|
|
293
|
+
*
|
|
294
|
+
* Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
|
|
295
|
+
* named field accessed from a group type. Each language's printer renders the variant
|
|
296
|
+
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
297
|
+
*
|
|
298
|
+
* @example Reference type (TypeScript: `QueryParams`)
|
|
299
|
+
* ```ts
|
|
300
|
+
* createTypeNode({ variant: 'reference', name: 'QueryParams' })
|
|
301
|
+
* ```
|
|
302
|
+
*
|
|
303
|
+
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
304
|
+
* ```ts
|
|
305
|
+
* createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
309
|
+
* ```ts
|
|
310
|
+
* createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
function createTypeNode(props) {
|
|
314
|
+
return {
|
|
315
|
+
...props,
|
|
316
|
+
kind: "Type"
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
|
|
528
321
|
*
|
|
529
|
-
* @example
|
|
322
|
+
* @example Grouped param (TypeScript declaration)
|
|
530
323
|
* ```ts
|
|
531
|
-
*
|
|
324
|
+
* createParameterGroup({
|
|
532
325
|
* properties: [
|
|
533
|
-
* createFunctionParameter({ name: 'id', type: 'string', optional: false }),
|
|
534
|
-
* createFunctionParameter({ name: 'name', type: 'string', optional: true }),
|
|
326
|
+
* createFunctionParameter({ name: 'id', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
|
|
327
|
+
* createFunctionParameter({ name: 'name', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: true }),
|
|
535
328
|
* ],
|
|
536
329
|
* default: '{}',
|
|
537
330
|
* })
|
|
@@ -539,20 +332,20 @@ function createFunctionParameter(props) {
|
|
|
539
332
|
* // call → { id, name }
|
|
540
333
|
* ```
|
|
541
334
|
*
|
|
542
|
-
* @example Inline
|
|
335
|
+
* @example Inline (spread) — children emitted as individual top-level parameters
|
|
543
336
|
* ```ts
|
|
544
|
-
*
|
|
545
|
-
* properties: [createFunctionParameter({ name: 'petId', type: 'string', optional: false })],
|
|
337
|
+
* createParameterGroup({
|
|
338
|
+
* properties: [createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false })],
|
|
546
339
|
* inline: true,
|
|
547
340
|
* })
|
|
548
341
|
* // declaration → petId: string
|
|
549
342
|
* // call → petId
|
|
550
343
|
* ```
|
|
551
344
|
*/
|
|
552
|
-
function
|
|
345
|
+
function createParameterGroup(props) {
|
|
553
346
|
return {
|
|
554
347
|
...props,
|
|
555
|
-
kind: "
|
|
348
|
+
kind: "ParameterGroup"
|
|
556
349
|
};
|
|
557
350
|
}
|
|
558
351
|
/**
|
|
@@ -562,8 +355,8 @@ function createObjectBindingParameter(props) {
|
|
|
562
355
|
* ```ts
|
|
563
356
|
* createFunctionParameters({
|
|
564
357
|
* params: [
|
|
565
|
-
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
566
|
-
* createFunctionParameter({ name: 'config', type: 'RequestConfig', optional: false, default: '{}' }),
|
|
358
|
+
* createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
|
|
359
|
+
* createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
|
|
567
360
|
* ],
|
|
568
361
|
* })
|
|
569
362
|
* ```
|
|
@@ -582,7 +375,53 @@ function createFunctionParameters(props = {}) {
|
|
|
582
375
|
};
|
|
583
376
|
}
|
|
584
377
|
//#endregion
|
|
585
|
-
//#region src/
|
|
378
|
+
//#region src/guards.ts
|
|
379
|
+
/**
|
|
380
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* const schema = createSchema({ type: 'string' })
|
|
385
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
function narrowSchema(node, type) {
|
|
389
|
+
return node?.type === type ? node : void 0;
|
|
390
|
+
}
|
|
391
|
+
function isKind(kind) {
|
|
392
|
+
return (node) => node.kind === kind;
|
|
393
|
+
}
|
|
394
|
+
isKind("Root");
|
|
395
|
+
/**
|
|
396
|
+
* Returns `true` when the input is an `OperationNode`.
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* ```ts
|
|
400
|
+
* if (isOperationNode(node)) {
|
|
401
|
+
* console.log(node.operationId)
|
|
402
|
+
* }
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
const isOperationNode = isKind("Operation");
|
|
406
|
+
/**
|
|
407
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```ts
|
|
411
|
+
* if (isSchemaNode(node)) {
|
|
412
|
+
* console.log(node.type)
|
|
413
|
+
* }
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
const isSchemaNode = isKind("Schema");
|
|
417
|
+
isKind("Property");
|
|
418
|
+
isKind("Parameter");
|
|
419
|
+
isKind("Response");
|
|
420
|
+
isKind("FunctionParameter");
|
|
421
|
+
isKind("ParameterGroup");
|
|
422
|
+
isKind("FunctionParameters");
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/printer.ts
|
|
586
425
|
/**
|
|
587
426
|
* Creates a schema printer factory.
|
|
588
427
|
*
|
|
@@ -652,123 +491,6 @@ function createPrinterFactory(getKey) {
|
|
|
652
491
|
};
|
|
653
492
|
}
|
|
654
493
|
//#endregion
|
|
655
|
-
//#region src/printers/functionPrinter.ts
|
|
656
|
-
const kindToHandlerKey = {
|
|
657
|
-
FunctionParameter: "functionParameter",
|
|
658
|
-
ObjectBindingParameter: "objectBindingParameter",
|
|
659
|
-
FunctionParameters: "functionParameters"
|
|
660
|
-
};
|
|
661
|
-
/**
|
|
662
|
-
* Creates a function-parameter printer factory.
|
|
663
|
-
*
|
|
664
|
-
* This wrapper uses `createPrinterFactory` and dispatches handlers by `node.kind`
|
|
665
|
-
* (for function nodes) rather than by `node.type` (for schema nodes).
|
|
666
|
-
*
|
|
667
|
-
* @example
|
|
668
|
-
* ```ts
|
|
669
|
-
* type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
|
|
670
|
-
*
|
|
671
|
-
* export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
|
|
672
|
-
* name: 'my',
|
|
673
|
-
* options,
|
|
674
|
-
* nodes: {
|
|
675
|
-
* functionParameter(node) {
|
|
676
|
-
* return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
|
|
677
|
-
* },
|
|
678
|
-
* objectBindingParameter(node) {
|
|
679
|
-
* const inner = node.properties.map(p => this.transform(p)).filter(Boolean).join(', ')
|
|
680
|
-
* return `{ ${inner} }`
|
|
681
|
-
* },
|
|
682
|
-
* functionParameters(node) {
|
|
683
|
-
* return node.params.map(p => this.transform(p)).filter(Boolean).join(', ')
|
|
684
|
-
* },
|
|
685
|
-
* },
|
|
686
|
-
* }))
|
|
687
|
-
* ```
|
|
688
|
-
*/
|
|
689
|
-
const defineFunctionPrinter = createPrinterFactory((node) => kindToHandlerKey[node.kind]);
|
|
690
|
-
function rank(param) {
|
|
691
|
-
if (param.kind === "ObjectBindingParameter") {
|
|
692
|
-
if (param.default) return 2;
|
|
693
|
-
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? 1 : 0;
|
|
694
|
-
}
|
|
695
|
-
if (param.rest) return 3;
|
|
696
|
-
if (param.default) return 2;
|
|
697
|
-
return param.optional ? 1 : 0;
|
|
698
|
-
}
|
|
699
|
-
function sortParams(params) {
|
|
700
|
-
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
701
|
-
}
|
|
702
|
-
function sortChildParams(params) {
|
|
703
|
-
return [...params].sort((a, b) => rank(a) - rank(b));
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Default function-signature printer.
|
|
707
|
-
* Covers the four standard output modes used across Kubb plugins.
|
|
708
|
-
*
|
|
709
|
-
* @example
|
|
710
|
-
* ```ts
|
|
711
|
-
* const printer = functionPrinter({ mode: 'declaration' })
|
|
712
|
-
*
|
|
713
|
-
* const sig = createFunctionParameters({
|
|
714
|
-
* params: [
|
|
715
|
-
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
716
|
-
* createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
|
|
717
|
-
* ],
|
|
718
|
-
* })
|
|
719
|
-
*
|
|
720
|
-
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
721
|
-
* ```
|
|
722
|
-
*/
|
|
723
|
-
const functionPrinter = defineFunctionPrinter((options) => ({
|
|
724
|
-
name: "functionParameters",
|
|
725
|
-
options,
|
|
726
|
-
nodes: {
|
|
727
|
-
functionParameter(node) {
|
|
728
|
-
const { mode, transformName, transformType } = this.options;
|
|
729
|
-
const name = transformName ? transformName(node.name) : node.name;
|
|
730
|
-
const type = node.type && transformType ? transformType(node.type) : node.type;
|
|
731
|
-
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
732
|
-
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
733
|
-
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
734
|
-
if (type) {
|
|
735
|
-
if (node.optional) return `${name}?: ${type}`;
|
|
736
|
-
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`;
|
|
737
|
-
}
|
|
738
|
-
return node.default ? `${name} = ${node.default}` : name;
|
|
739
|
-
},
|
|
740
|
-
objectBindingParameter(node) {
|
|
741
|
-
const { mode, transformName, transformType } = this.options;
|
|
742
|
-
const sorted = sortChildParams(node.properties);
|
|
743
|
-
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
744
|
-
if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
745
|
-
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
746
|
-
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
747
|
-
const names = sorted.map((p) => {
|
|
748
|
-
return transformName ? transformName(p.name) : p.name;
|
|
749
|
-
});
|
|
750
|
-
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
751
|
-
if (!nameStr) return null;
|
|
752
|
-
let typeAnnotation = node.type;
|
|
753
|
-
if (!typeAnnotation) {
|
|
754
|
-
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
755
|
-
const t = transformType && p.type ? transformType(p.type) : p.type;
|
|
756
|
-
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
757
|
-
});
|
|
758
|
-
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
759
|
-
}
|
|
760
|
-
if (typeAnnotation) {
|
|
761
|
-
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
762
|
-
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
763
|
-
}
|
|
764
|
-
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
765
|
-
},
|
|
766
|
-
functionParameters(node) {
|
|
767
|
-
return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}));
|
|
771
|
-
//#endregion
|
|
772
494
|
//#region src/refs.ts
|
|
773
495
|
/**
|
|
774
496
|
* Returns the last path segment of a reference string.
|
|
@@ -783,43 +505,83 @@ const functionPrinter = defineFunctionPrinter((options) => ({
|
|
|
783
505
|
function extractRefName(ref) {
|
|
784
506
|
return ref.split("/").at(-1) ?? ref;
|
|
785
507
|
}
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region ../../internals/utils/src/casing.ts
|
|
786
510
|
/**
|
|
787
|
-
*
|
|
511
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
512
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
513
|
+
* and capitalizes each word according to `pascal`.
|
|
788
514
|
*
|
|
789
|
-
*
|
|
515
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
516
|
+
*/
|
|
517
|
+
function toCamelOrPascal(text, pascal) {
|
|
518
|
+
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) => {
|
|
519
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
520
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
521
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
522
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
526
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
527
|
+
* Segments are joined with `/` to form a file path.
|
|
528
|
+
*
|
|
529
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
530
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
531
|
+
*/
|
|
532
|
+
function applyToFileParts(text, transformPart) {
|
|
533
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
534
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Converts `text` to camelCase.
|
|
538
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
790
539
|
*
|
|
791
540
|
* @example
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
* const pet = refMap.get('Pet')
|
|
795
|
-
* ```
|
|
541
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
542
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
796
543
|
*/
|
|
797
|
-
function
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
544
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
545
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
546
|
+
prefix,
|
|
547
|
+
suffix
|
|
548
|
+
} : {}));
|
|
549
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
801
550
|
}
|
|
802
551
|
/**
|
|
803
|
-
*
|
|
552
|
+
* Converts `text` to PascalCase.
|
|
553
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
804
554
|
*
|
|
805
555
|
* @example
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
* ```
|
|
556
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
557
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
809
558
|
*/
|
|
810
|
-
function
|
|
811
|
-
return
|
|
559
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
560
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
561
|
+
prefix,
|
|
562
|
+
suffix
|
|
563
|
+
}) : camelCase(part));
|
|
564
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
812
565
|
}
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
813
568
|
/**
|
|
814
|
-
*
|
|
569
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
815
570
|
*
|
|
816
571
|
* @example
|
|
817
572
|
* ```ts
|
|
818
|
-
*
|
|
573
|
+
* isValidVarName('status') // true
|
|
574
|
+
* isValidVarName('class') // false (reserved word)
|
|
575
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
819
576
|
* ```
|
|
820
577
|
*/
|
|
821
|
-
function
|
|
822
|
-
|
|
578
|
+
function isValidVarName(name) {
|
|
579
|
+
try {
|
|
580
|
+
new Function(`var ${name}`);
|
|
581
|
+
} catch {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
return true;
|
|
823
585
|
}
|
|
824
586
|
//#endregion
|
|
825
587
|
//#region src/visitor.ts
|
|
@@ -894,8 +656,9 @@ function getChildren(node, recurse) {
|
|
|
894
656
|
case "Parameter": return [node.schema];
|
|
895
657
|
case "Response": return node.schema ? [node.schema] : [];
|
|
896
658
|
case "FunctionParameter":
|
|
897
|
-
case "
|
|
898
|
-
case "FunctionParameters":
|
|
659
|
+
case "ParameterGroup":
|
|
660
|
+
case "FunctionParameters":
|
|
661
|
+
case "Type": return [];
|
|
899
662
|
}
|
|
900
663
|
}
|
|
901
664
|
/**
|
|
@@ -942,7 +705,7 @@ async function _walk(node, visitor, recurse, limit, parent) {
|
|
|
942
705
|
await limit(() => visitor.response?.(node, { parent }));
|
|
943
706
|
break;
|
|
944
707
|
case "FunctionParameter":
|
|
945
|
-
case "
|
|
708
|
+
case "ParameterGroup":
|
|
946
709
|
case "FunctionParameters": break;
|
|
947
710
|
}
|
|
948
711
|
const children = getChildren(node, recurse);
|
|
@@ -1044,8 +807,9 @@ function transform(node, options) {
|
|
|
1044
807
|
};
|
|
1045
808
|
}
|
|
1046
809
|
case "FunctionParameter":
|
|
1047
|
-
case "
|
|
1048
|
-
case "FunctionParameters":
|
|
810
|
+
case "ParameterGroup":
|
|
811
|
+
case "FunctionParameters":
|
|
812
|
+
case "Type": return node;
|
|
1049
813
|
}
|
|
1050
814
|
}
|
|
1051
815
|
/**
|
|
@@ -1129,7 +893,7 @@ function collect(node, options) {
|
|
|
1129
893
|
v = visitor.response?.(node, { parent });
|
|
1130
894
|
break;
|
|
1131
895
|
case "FunctionParameter":
|
|
1132
|
-
case "
|
|
896
|
+
case "ParameterGroup":
|
|
1133
897
|
case "FunctionParameters": break;
|
|
1134
898
|
}
|
|
1135
899
|
if (v !== void 0) results.push(v);
|
|
@@ -1249,7 +1013,7 @@ function mergeAdjacentObjects(members) {
|
|
|
1249
1013
|
* ```
|
|
1250
1014
|
*/
|
|
1251
1015
|
function simplifyUnion(members) {
|
|
1252
|
-
const scalarPrimitives = new Set(members.filter((member) =>
|
|
1016
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
1253
1017
|
if (!scalarPrimitives.size) return members;
|
|
1254
1018
|
return members.filter((member) => {
|
|
1255
1019
|
const enumNode = narrowSchema(member, "enum");
|
|
@@ -1274,33 +1038,308 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
1274
1038
|
};
|
|
1275
1039
|
return propNode;
|
|
1276
1040
|
}
|
|
1041
|
+
//#endregion
|
|
1042
|
+
//#region src/utils.ts
|
|
1043
|
+
const plainStringTypes = new Set([
|
|
1044
|
+
"string",
|
|
1045
|
+
"uuid",
|
|
1046
|
+
"email",
|
|
1047
|
+
"url",
|
|
1048
|
+
"datetime"
|
|
1049
|
+
]);
|
|
1277
1050
|
/**
|
|
1278
|
-
*
|
|
1051
|
+
* Returns `true` when a schema is emitted as a plain `string` type.
|
|
1052
|
+
*
|
|
1053
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
1054
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
1055
|
+
*
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```ts
|
|
1058
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
1059
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
1060
|
+
* ```
|
|
1279
1061
|
*/
|
|
1280
|
-
function
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1062
|
+
function isStringType(node) {
|
|
1063
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
1064
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
1065
|
+
if (temporal) return temporal.representation !== "date";
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
1070
|
+
*
|
|
1071
|
+
* The input array is not mutated.
|
|
1072
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
1073
|
+
*
|
|
1074
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
1075
|
+
* in generated output match the desired casing while preserving
|
|
1076
|
+
* `OperationNode.parameters` for other consumers.
|
|
1077
|
+
*
|
|
1078
|
+
* @example
|
|
1079
|
+
* ```ts
|
|
1080
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
1081
|
+
* const cased = caseParams(params, 'camelcase')
|
|
1082
|
+
* // cased[0].name === 'petId'
|
|
1083
|
+
* ```
|
|
1084
|
+
*/
|
|
1085
|
+
function caseParams(params, casing) {
|
|
1086
|
+
if (!casing) return params;
|
|
1087
|
+
return params.map((param) => {
|
|
1088
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
1089
|
+
return {
|
|
1090
|
+
...param,
|
|
1091
|
+
name: transformed
|
|
1092
|
+
};
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
1097
|
+
*
|
|
1098
|
+
* @example
|
|
1099
|
+
* ```ts
|
|
1100
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
1101
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
1102
|
+
* ```
|
|
1103
|
+
*/
|
|
1104
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
1105
|
+
return createSchema({
|
|
1106
|
+
type: "object",
|
|
1107
|
+
primitive: "object",
|
|
1108
|
+
properties: [createProperty({
|
|
1109
|
+
name: propertyName,
|
|
1110
|
+
schema: createSchema({
|
|
1111
|
+
type: "enum",
|
|
1112
|
+
primitive: "string",
|
|
1113
|
+
enumValues: [value]
|
|
1114
|
+
}),
|
|
1115
|
+
required: true
|
|
1116
|
+
})]
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
function resolveType({ node, param, resolver }) {
|
|
1120
|
+
if (!resolver) return createTypeNode({
|
|
1121
|
+
variant: "reference",
|
|
1122
|
+
name: param.schema.primitive ?? "unknown"
|
|
1123
|
+
});
|
|
1124
|
+
const individualName = resolver.resolveParamName(node, param);
|
|
1125
|
+
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
1126
|
+
const groupResolvers = {
|
|
1127
|
+
path: resolver.resolvePathParamsName,
|
|
1128
|
+
query: resolver.resolveQueryParamsName,
|
|
1129
|
+
header: resolver.resolveHeaderParamsName
|
|
1130
|
+
};
|
|
1131
|
+
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
1132
|
+
if (groupName && groupName !== individualName) return createTypeNode({
|
|
1133
|
+
variant: "member",
|
|
1134
|
+
base: groupName,
|
|
1135
|
+
key: param.name
|
|
1136
|
+
});
|
|
1137
|
+
return createTypeNode({
|
|
1138
|
+
variant: "reference",
|
|
1139
|
+
name: individualName
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
|
|
1144
|
+
*
|
|
1145
|
+
* Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
|
|
1146
|
+
* type resolution and `extraParams` for plugin-specific trailing parameters.
|
|
1147
|
+
*
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* const params = createOperationParams(node, {
|
|
1151
|
+
* paramsType: 'inline',
|
|
1152
|
+
* pathParamsType: 'inline',
|
|
1153
|
+
* resolver: tsResolver,
|
|
1154
|
+
* extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
|
|
1155
|
+
* })
|
|
1156
|
+
* ```
|
|
1157
|
+
*/
|
|
1158
|
+
function createOperationParams(node, options) {
|
|
1159
|
+
const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
|
|
1160
|
+
const dataName = paramNames?.data ?? "data";
|
|
1161
|
+
const paramsName = paramNames?.params ?? "params";
|
|
1162
|
+
const headersName = paramNames?.headers ?? "headers";
|
|
1163
|
+
const pathName = paramNames?.path ?? "pathParams";
|
|
1164
|
+
const wrapType = (type) => createTypeNode({
|
|
1165
|
+
variant: "reference",
|
|
1166
|
+
name: typeWrapper ? typeWrapper(type) : type
|
|
1167
|
+
});
|
|
1168
|
+
const wrapTypeNode = (type) => type.variant === "reference" ? wrapType(type.name) : type;
|
|
1169
|
+
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
1170
|
+
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
1171
|
+
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
1172
|
+
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
1173
|
+
const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
1174
|
+
const bodyRequired = node.requestBody?.required ?? false;
|
|
1175
|
+
const queryGroupType = resolver ? resolveGroupType({
|
|
1176
|
+
node,
|
|
1177
|
+
params: queryParams,
|
|
1178
|
+
groupMethod: resolver.resolveQueryParamsName,
|
|
1179
|
+
resolver
|
|
1180
|
+
}) : void 0;
|
|
1181
|
+
const headerGroupType = resolver ? resolveGroupType({
|
|
1182
|
+
node,
|
|
1183
|
+
params: headerParams,
|
|
1184
|
+
groupMethod: resolver.resolveHeaderParamsName,
|
|
1185
|
+
resolver
|
|
1186
|
+
}) : void 0;
|
|
1187
|
+
const params = [];
|
|
1188
|
+
if (paramsType === "object") {
|
|
1189
|
+
const children = [
|
|
1190
|
+
...pathParams.map((p) => {
|
|
1191
|
+
const type = resolveType({
|
|
1192
|
+
node,
|
|
1193
|
+
param: p,
|
|
1194
|
+
resolver
|
|
1195
|
+
});
|
|
1196
|
+
return createFunctionParameter({
|
|
1197
|
+
name: p.name,
|
|
1198
|
+
type: wrapTypeNode(type),
|
|
1199
|
+
optional: !p.required
|
|
1200
|
+
});
|
|
1201
|
+
}),
|
|
1202
|
+
...bodyType ? [createFunctionParameter({
|
|
1203
|
+
name: dataName,
|
|
1204
|
+
type: bodyType,
|
|
1205
|
+
optional: !bodyRequired
|
|
1206
|
+
})] : [],
|
|
1207
|
+
...buildGroupParam({
|
|
1208
|
+
name: paramsName,
|
|
1209
|
+
node,
|
|
1210
|
+
params: queryParams,
|
|
1211
|
+
groupType: queryGroupType,
|
|
1212
|
+
resolver,
|
|
1213
|
+
wrapType
|
|
1214
|
+
}),
|
|
1215
|
+
...buildGroupParam({
|
|
1216
|
+
name: headersName,
|
|
1217
|
+
node,
|
|
1218
|
+
params: headerParams,
|
|
1219
|
+
groupType: headerGroupType,
|
|
1220
|
+
resolver,
|
|
1221
|
+
wrapType
|
|
1222
|
+
})
|
|
1223
|
+
];
|
|
1224
|
+
if (children.length) params.push(createParameterGroup({
|
|
1225
|
+
properties: children,
|
|
1226
|
+
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
1227
|
+
}));
|
|
1228
|
+
} else {
|
|
1229
|
+
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
1230
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
|
|
1231
|
+
params.push(createFunctionParameter({
|
|
1232
|
+
name: pathName,
|
|
1233
|
+
type: spreadType ? wrapType(spreadType) : void 0,
|
|
1234
|
+
rest: true
|
|
1235
|
+
}));
|
|
1236
|
+
} else {
|
|
1237
|
+
const pathChildren = 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
|
+
params.push(createParameterGroup({
|
|
1250
|
+
properties: pathChildren,
|
|
1251
|
+
inline: pathParamsType === "inline",
|
|
1252
|
+
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
1253
|
+
}));
|
|
1298
1254
|
}
|
|
1299
|
-
|
|
1255
|
+
if (bodyType) params.push(createFunctionParameter({
|
|
1256
|
+
name: dataName,
|
|
1257
|
+
type: bodyType,
|
|
1258
|
+
optional: !bodyRequired
|
|
1259
|
+
}));
|
|
1260
|
+
params.push(...buildGroupParam({
|
|
1261
|
+
name: paramsName,
|
|
1262
|
+
node,
|
|
1263
|
+
params: queryParams,
|
|
1264
|
+
groupType: queryGroupType,
|
|
1265
|
+
resolver,
|
|
1266
|
+
wrapType
|
|
1267
|
+
}));
|
|
1268
|
+
params.push(...buildGroupParam({
|
|
1269
|
+
name: headersName,
|
|
1270
|
+
node,
|
|
1271
|
+
params: headerParams,
|
|
1272
|
+
groupType: headerGroupType,
|
|
1273
|
+
resolver,
|
|
1274
|
+
wrapType
|
|
1275
|
+
}));
|
|
1276
|
+
}
|
|
1277
|
+
params.push(...extraParams);
|
|
1278
|
+
return createFunctionParameters({ params });
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
1282
|
+
* Returns an empty array when there are no params to emit.
|
|
1283
|
+
*
|
|
1284
|
+
* If a pre-resolved `groupType` is provided it emits `name: GroupType`.
|
|
1285
|
+
* Otherwise, it builds an inline struct from the individual params.
|
|
1286
|
+
*/
|
|
1287
|
+
function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
|
|
1288
|
+
if (groupType) return [createFunctionParameter({
|
|
1289
|
+
name,
|
|
1290
|
+
type: groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
1291
|
+
optional: groupType.optional
|
|
1292
|
+
})];
|
|
1293
|
+
if (params.length) return [createFunctionParameter({
|
|
1294
|
+
name,
|
|
1295
|
+
type: toStructType({
|
|
1296
|
+
node,
|
|
1297
|
+
params,
|
|
1298
|
+
resolver
|
|
1299
|
+
}),
|
|
1300
|
+
optional: params.every((p) => !p.required)
|
|
1301
|
+
})];
|
|
1302
|
+
return [];
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1306
|
+
* Returns `undefined` when the group name equals the individual param name (no real group).
|
|
1307
|
+
*/
|
|
1308
|
+
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1309
|
+
if (!params.length) return;
|
|
1310
|
+
const firstParam = params[0];
|
|
1311
|
+
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1312
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1313
|
+
const allOptional = params.every((p) => !p.required);
|
|
1314
|
+
return {
|
|
1315
|
+
type: createTypeNode({
|
|
1316
|
+
variant: "reference",
|
|
1317
|
+
name: groupName
|
|
1318
|
+
}),
|
|
1319
|
+
optional: allOptional
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
|
|
1324
|
+
*
|
|
1325
|
+
* Used when query or header parameters have no dedicated group type name.
|
|
1326
|
+
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
1327
|
+
*/
|
|
1328
|
+
function toStructType({ node, params, resolver }) {
|
|
1329
|
+
return createTypeNode({
|
|
1330
|
+
variant: "struct",
|
|
1331
|
+
properties: params.map((p) => ({
|
|
1332
|
+
name: p.name,
|
|
1333
|
+
optional: !p.required,
|
|
1334
|
+
type: resolveType({
|
|
1335
|
+
node,
|
|
1336
|
+
param: p,
|
|
1337
|
+
resolver
|
|
1338
|
+
})
|
|
1339
|
+
}))
|
|
1340
|
+
});
|
|
1300
1341
|
}
|
|
1301
1342
|
//#endregion
|
|
1302
|
-
exports.SCALAR_PRIMITIVE_TYPES = SCALAR_PRIMITIVE_TYPES;
|
|
1303
|
-
exports.buildRefMap = buildRefMap;
|
|
1304
1343
|
exports.caseParams = caseParams;
|
|
1305
1344
|
exports.childName = childName;
|
|
1306
1345
|
exports.collect = collect;
|
|
@@ -1309,37 +1348,28 @@ exports.composeTransformers = composeTransformers;
|
|
|
1309
1348
|
exports.createDiscriminantNode = createDiscriminantNode;
|
|
1310
1349
|
exports.createFunctionParameter = createFunctionParameter;
|
|
1311
1350
|
exports.createFunctionParameters = createFunctionParameters;
|
|
1312
|
-
exports.createObjectBindingParameter = createObjectBindingParameter;
|
|
1313
1351
|
exports.createOperation = createOperation;
|
|
1352
|
+
exports.createOperationParams = createOperationParams;
|
|
1314
1353
|
exports.createParameter = createParameter;
|
|
1354
|
+
exports.createParameterGroup = createParameterGroup;
|
|
1355
|
+
exports.createPrinterFactory = createPrinterFactory;
|
|
1315
1356
|
exports.createProperty = createProperty;
|
|
1316
1357
|
exports.createResponse = createResponse;
|
|
1317
1358
|
exports.createRoot = createRoot;
|
|
1318
1359
|
exports.createSchema = createSchema;
|
|
1319
|
-
exports.
|
|
1360
|
+
exports.createTypeNode = createTypeNode;
|
|
1320
1361
|
exports.definePrinter = definePrinter;
|
|
1321
1362
|
exports.enumPropName = enumPropName;
|
|
1322
1363
|
exports.extractRefName = extractRefName;
|
|
1323
1364
|
exports.findDiscriminator = findDiscriminator;
|
|
1324
|
-
exports.functionPrinter = functionPrinter;
|
|
1325
1365
|
exports.httpMethods = httpMethods;
|
|
1326
|
-
exports.isFunctionParameterNode = isFunctionParameterNode;
|
|
1327
|
-
exports.isFunctionParametersNode = isFunctionParametersNode;
|
|
1328
|
-
exports.isObjectBindingParameterNode = isObjectBindingParameterNode;
|
|
1329
1366
|
exports.isOperationNode = isOperationNode;
|
|
1330
|
-
exports.
|
|
1331
|
-
exports.isPropertyNode = isPropertyNode;
|
|
1332
|
-
exports.isResponseNode = isResponseNode;
|
|
1333
|
-
exports.isRootNode = isRootNode;
|
|
1367
|
+
exports.isScalarPrimitive = isScalarPrimitive;
|
|
1334
1368
|
exports.isSchemaNode = isSchemaNode;
|
|
1335
1369
|
exports.isStringType = isStringType;
|
|
1336
1370
|
exports.mediaTypes = mediaTypes;
|
|
1337
1371
|
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
1338
1372
|
exports.narrowSchema = narrowSchema;
|
|
1339
|
-
exports.nodeKinds = nodeKinds;
|
|
1340
|
-
exports.refMapToObject = refMapToObject;
|
|
1341
|
-
exports.resolveNames = resolveNames;
|
|
1342
|
-
exports.resolveRef = resolveRef;
|
|
1343
1373
|
exports.schemaTypes = schemaTypes;
|
|
1344
1374
|
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
1345
1375
|
exports.setEnumName = setEnumName;
|