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