@kubb/ast 5.0.0-alpha.4 → 5.0.0-alpha.40
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 +1682 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3323 -15
- package/dist/index.js +1624 -187
- package/dist/index.js.map +1 -1
- package/package.json +3 -13
- package/src/constants.ts +127 -5
- package/src/factory.ts +662 -22
- package/src/guards.ts +77 -9
- package/src/index.ts +44 -6
- package/src/infer.ts +130 -0
- package/src/mocks.ts +18 -11
- package/src/nodes/base.ts +44 -4
- package/src/nodes/code.ts +304 -0
- package/src/nodes/file.ts +230 -0
- package/src/nodes/function.ts +219 -0
- package/src/nodes/http.ts +17 -5
- package/src/nodes/index.ts +48 -7
- package/src/nodes/operation.ts +69 -6
- package/src/nodes/output.ts +26 -0
- package/src/nodes/parameter.ts +27 -1
- package/src/nodes/property.ts +23 -1
- package/src/nodes/response.ts +29 -3
- package/src/nodes/root.ts +46 -14
- package/src/nodes/schema.ts +440 -42
- package/src/printer.ts +172 -60
- package/src/refs.ts +39 -7
- package/src/resolvers.ts +45 -0
- package/src/transformers.ts +156 -0
- package/src/types.ts +30 -4
- package/src/utils.ts +620 -4
- package/src/visitor.ts +373 -90
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +0 -2
- package/dist/types.js +0 -1
- package/dist/visitor-oFfdU8QA.d.ts +0 -653
package/dist/index.cjs
CHANGED
|
@@ -1,19 +1,64 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
3
23
|
//#endregion
|
|
24
|
+
let node_crypto = require("node:crypto");
|
|
25
|
+
let node_path = require("node:path");
|
|
26
|
+
node_path = __toESM(node_path, 1);
|
|
4
27
|
//#region src/constants.ts
|
|
5
28
|
const visitorDepths = {
|
|
6
29
|
shallow: "shallow",
|
|
7
30
|
deep: "deep"
|
|
8
31
|
};
|
|
9
32
|
const nodeKinds = {
|
|
10
|
-
|
|
33
|
+
input: "Input",
|
|
34
|
+
output: "Output",
|
|
11
35
|
operation: "Operation",
|
|
12
36
|
schema: "Schema",
|
|
13
37
|
property: "Property",
|
|
14
38
|
parameter: "Parameter",
|
|
15
|
-
response: "Response"
|
|
39
|
+
response: "Response",
|
|
40
|
+
functionParameter: "FunctionParameter",
|
|
41
|
+
parameterGroup: "ParameterGroup",
|
|
42
|
+
functionParameters: "FunctionParameters",
|
|
43
|
+
type: "Type",
|
|
44
|
+
file: "File",
|
|
45
|
+
import: "Import",
|
|
46
|
+
export: "Export",
|
|
47
|
+
source: "Source",
|
|
48
|
+
text: "Text",
|
|
49
|
+
break: "Break"
|
|
16
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Canonical schema type strings used by AST schema nodes.
|
|
53
|
+
*
|
|
54
|
+
* These values are used across the AST as stable discriminators
|
|
55
|
+
* (for example `schema.type === schemaTypes.object`).
|
|
56
|
+
*
|
|
57
|
+
* The map is grouped by intent:
|
|
58
|
+
* - primitives (`string`, `number`, `boolean`, ...)
|
|
59
|
+
* - structural/composite (`object`, `array`, `union`, ...)
|
|
60
|
+
* - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
|
|
61
|
+
*/
|
|
17
62
|
const schemaTypes = {
|
|
18
63
|
string: "string",
|
|
19
64
|
number: "number",
|
|
@@ -37,8 +82,27 @@ const schemaTypes = {
|
|
|
37
82
|
uuid: "uuid",
|
|
38
83
|
email: "email",
|
|
39
84
|
url: "url",
|
|
40
|
-
|
|
85
|
+
ipv4: "ipv4",
|
|
86
|
+
ipv6: "ipv6",
|
|
87
|
+
blob: "blob",
|
|
88
|
+
never: "never"
|
|
41
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Primitive scalar schema types used when simplifying union members.
|
|
92
|
+
*/
|
|
93
|
+
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
94
|
+
"string",
|
|
95
|
+
"number",
|
|
96
|
+
"integer",
|
|
97
|
+
"bigint",
|
|
98
|
+
"boolean"
|
|
99
|
+
]);
|
|
100
|
+
/**
|
|
101
|
+
* Returns `true` when `type` is a scalar primitive schema type.
|
|
102
|
+
*/
|
|
103
|
+
function isScalarPrimitive(type) {
|
|
104
|
+
return SCALAR_PRIMITIVE_TYPES.has(type);
|
|
105
|
+
}
|
|
42
106
|
const httpMethods = {
|
|
43
107
|
get: "GET",
|
|
44
108
|
post: "POST",
|
|
@@ -71,204 +135,1353 @@ const mediaTypes = {
|
|
|
71
135
|
videoMp4: "video/mp4"
|
|
72
136
|
};
|
|
73
137
|
//#endregion
|
|
138
|
+
//#region ../../internals/utils/src/casing.ts
|
|
139
|
+
/**
|
|
140
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
141
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
142
|
+
* and capitalizes each word according to `pascal`.
|
|
143
|
+
*
|
|
144
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
145
|
+
*/
|
|
146
|
+
function toCamelOrPascal(text, pascal) {
|
|
147
|
+
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) => {
|
|
148
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
149
|
+
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
150
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
151
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
155
|
+
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
156
|
+
* Segments are joined with `/` to form a file path.
|
|
157
|
+
*
|
|
158
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
159
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
160
|
+
*/
|
|
161
|
+
function applyToFileParts(text, transformPart) {
|
|
162
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
163
|
+
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Converts `text` to camelCase.
|
|
167
|
+
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* camelCase('hello-world') // 'helloWorld'
|
|
171
|
+
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
172
|
+
*/
|
|
173
|
+
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
174
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
175
|
+
prefix,
|
|
176
|
+
suffix
|
|
177
|
+
} : {}));
|
|
178
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Converts `text` to PascalCase.
|
|
182
|
+
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* pascalCase('hello-world') // 'HelloWorld'
|
|
186
|
+
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
187
|
+
*/
|
|
188
|
+
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
189
|
+
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
190
|
+
prefix,
|
|
191
|
+
suffix
|
|
192
|
+
}) : camelCase(part));
|
|
193
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
197
|
+
/**
|
|
198
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* isValidVarName('status') // true
|
|
203
|
+
* isValidVarName('class') // false (reserved word)
|
|
204
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
function isValidVarName(name) {
|
|
208
|
+
try {
|
|
209
|
+
new Function(`var ${name}`);
|
|
210
|
+
} catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region ../../internals/utils/src/string.ts
|
|
217
|
+
/**
|
|
218
|
+
* Strips the file extension from a path or file name.
|
|
219
|
+
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* trimExtName('petStore.ts') // 'petStore'
|
|
223
|
+
* trimExtName('/src/models/pet.ts') // '/src/models/pet'
|
|
224
|
+
* trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
|
|
225
|
+
* trimExtName('noExtension') // 'noExtension'
|
|
226
|
+
*/
|
|
227
|
+
function trimExtName(text) {
|
|
228
|
+
const dotIndex = text.lastIndexOf(".");
|
|
229
|
+
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
230
|
+
return text;
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/guards.ts
|
|
234
|
+
/**
|
|
235
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* const schema = createSchema({ type: 'string' })
|
|
240
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
function narrowSchema(node, type) {
|
|
244
|
+
return node?.type === type ? node : void 0;
|
|
245
|
+
}
|
|
246
|
+
function isKind(kind) {
|
|
247
|
+
return (node) => node.kind === kind;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Returns `true` when the input is an `InputNode`.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* if (isInputNode(node)) {
|
|
255
|
+
* console.log(node.schemas.length)
|
|
256
|
+
* }
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
const isInputNode = isKind("Input");
|
|
260
|
+
/**
|
|
261
|
+
* Returns `true` when the input is an `OutputNode`.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* if (isOutputNode(node)) {
|
|
266
|
+
* console.log(node.files.length)
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
const isOutputNode = isKind("Output");
|
|
271
|
+
/**
|
|
272
|
+
* Returns `true` when the input is an `OperationNode`.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* if (isOperationNode(node)) {
|
|
277
|
+
* console.log(node.operationId)
|
|
278
|
+
* }
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
const isOperationNode = isKind("Operation");
|
|
282
|
+
/**
|
|
283
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* if (isSchemaNode(node)) {
|
|
288
|
+
* console.log(node.type)
|
|
289
|
+
* }
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
const isSchemaNode = isKind("Schema");
|
|
293
|
+
isKind("Property");
|
|
294
|
+
isKind("Parameter");
|
|
295
|
+
isKind("Response");
|
|
296
|
+
isKind("FunctionParameter");
|
|
297
|
+
isKind("ParameterGroup");
|
|
298
|
+
isKind("FunctionParameters");
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/utils.ts
|
|
301
|
+
const plainStringTypes = new Set([
|
|
302
|
+
"string",
|
|
303
|
+
"uuid",
|
|
304
|
+
"email",
|
|
305
|
+
"url",
|
|
306
|
+
"datetime"
|
|
307
|
+
]);
|
|
308
|
+
/**
|
|
309
|
+
* Returns a merged schema view for a ref node, combining the resolved `node.schema`
|
|
310
|
+
* (base from the referenced definition) with any usage-site sibling fields set directly
|
|
311
|
+
* on the ref node (description, readOnly, nullable, deprecated, etc.).
|
|
312
|
+
*
|
|
313
|
+
* Usage-site fields take precedence over the resolved schema's own fields when both are defined.
|
|
314
|
+
*
|
|
315
|
+
* For non-ref nodes the node itself is returned unchanged.
|
|
316
|
+
*/
|
|
317
|
+
function syncSchemaRef(node) {
|
|
318
|
+
const ref = narrowSchema(node, "ref");
|
|
319
|
+
if (!ref) return node;
|
|
320
|
+
if (!ref.schema) return node;
|
|
321
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
322
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
323
|
+
return createSchema({
|
|
324
|
+
...ref.schema,
|
|
325
|
+
...definedOverrides
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Returns `true` when a schema is emitted as a plain `string` type.
|
|
330
|
+
*
|
|
331
|
+
* - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
|
|
332
|
+
* - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* isStringType(createSchema({ type: 'uuid' })) // true
|
|
337
|
+
* isStringType(createSchema({ type: 'date', representation: 'date' })) // false
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
function isStringType(node) {
|
|
341
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
342
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
343
|
+
if (temporal) return temporal.representation !== "date";
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
348
|
+
*
|
|
349
|
+
* The input array is not mutated.
|
|
350
|
+
* If `casing` is not set, the original array is returned unchanged.
|
|
351
|
+
*
|
|
352
|
+
* Use this before passing parameters to schema builders so that property keys
|
|
353
|
+
* in generated output match the desired casing while preserving
|
|
354
|
+
* `OperationNode.parameters` for other consumers.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
|
|
359
|
+
* const cased = caseParams(params, 'camelcase')
|
|
360
|
+
* // cased[0].name === 'petId'
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
function caseParams(params, casing) {
|
|
364
|
+
if (!casing) return params;
|
|
365
|
+
return params.map((param) => {
|
|
366
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
367
|
+
return {
|
|
368
|
+
...param,
|
|
369
|
+
name: transformed
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
379
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
383
|
+
return createSchema({
|
|
384
|
+
type: "object",
|
|
385
|
+
primitive: "object",
|
|
386
|
+
properties: [createProperty({
|
|
387
|
+
name: propertyName,
|
|
388
|
+
schema: createSchema({
|
|
389
|
+
type: "enum",
|
|
390
|
+
primitive: "string",
|
|
391
|
+
enumValues: [value]
|
|
392
|
+
}),
|
|
393
|
+
required: true
|
|
394
|
+
})]
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function resolveParamsType({ node, param, resolver }) {
|
|
398
|
+
if (!resolver) return createParamsType({
|
|
399
|
+
variant: "reference",
|
|
400
|
+
name: param.schema.primitive ?? "unknown"
|
|
401
|
+
});
|
|
402
|
+
const individualName = resolver.resolveParamName(node, param);
|
|
403
|
+
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
404
|
+
const groupResolvers = {
|
|
405
|
+
path: resolver.resolvePathParamsName,
|
|
406
|
+
query: resolver.resolveQueryParamsName,
|
|
407
|
+
header: resolver.resolveHeaderParamsName
|
|
408
|
+
};
|
|
409
|
+
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
410
|
+
if (groupName && groupName !== individualName) return createParamsType({
|
|
411
|
+
variant: "member",
|
|
412
|
+
base: groupName,
|
|
413
|
+
key: param.name
|
|
414
|
+
});
|
|
415
|
+
return createParamsType({
|
|
416
|
+
variant: "reference",
|
|
417
|
+
name: individualName
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
|
|
422
|
+
*
|
|
423
|
+
* Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
|
|
424
|
+
* type resolution and `extraParams` for plugin-specific trailing parameters.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* const params = createOperationParams(node, {
|
|
429
|
+
* paramsType: 'inline',
|
|
430
|
+
* pathParamsType: 'inline',
|
|
431
|
+
* resolver: tsResolver,
|
|
432
|
+
* extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
|
|
433
|
+
* })
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
function createOperationParams(node, options) {
|
|
437
|
+
const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
|
|
438
|
+
const dataName = paramNames?.data ?? "data";
|
|
439
|
+
const paramsName = paramNames?.params ?? "params";
|
|
440
|
+
const headersName = paramNames?.headers ?? "headers";
|
|
441
|
+
const pathName = paramNames?.path ?? "pathParams";
|
|
442
|
+
const wrapType = (type) => createParamsType({
|
|
443
|
+
variant: "reference",
|
|
444
|
+
name: typeWrapper ? typeWrapper(type) : type
|
|
445
|
+
});
|
|
446
|
+
const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
|
|
447
|
+
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
448
|
+
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
449
|
+
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
450
|
+
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
451
|
+
const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
452
|
+
const bodyRequired = node.requestBody?.required ?? false;
|
|
453
|
+
const queryGroupType = resolver ? resolveGroupType({
|
|
454
|
+
node,
|
|
455
|
+
params: queryParams,
|
|
456
|
+
groupMethod: resolver.resolveQueryParamsName,
|
|
457
|
+
resolver
|
|
458
|
+
}) : void 0;
|
|
459
|
+
const headerGroupType = resolver ? resolveGroupType({
|
|
460
|
+
node,
|
|
461
|
+
params: headerParams,
|
|
462
|
+
groupMethod: resolver.resolveHeaderParamsName,
|
|
463
|
+
resolver
|
|
464
|
+
}) : void 0;
|
|
465
|
+
const params = [];
|
|
466
|
+
if (paramsType === "object") {
|
|
467
|
+
const children = [
|
|
468
|
+
...pathParams.map((p) => {
|
|
469
|
+
const type = resolveParamsType({
|
|
470
|
+
node,
|
|
471
|
+
param: p,
|
|
472
|
+
resolver
|
|
473
|
+
});
|
|
474
|
+
return createFunctionParameter({
|
|
475
|
+
name: p.name,
|
|
476
|
+
type: wrapTypeNode(type),
|
|
477
|
+
optional: !p.required
|
|
478
|
+
});
|
|
479
|
+
}),
|
|
480
|
+
...bodyType ? [createFunctionParameter({
|
|
481
|
+
name: dataName,
|
|
482
|
+
type: bodyType,
|
|
483
|
+
optional: !bodyRequired
|
|
484
|
+
})] : [],
|
|
485
|
+
...buildGroupParam({
|
|
486
|
+
name: paramsName,
|
|
487
|
+
node,
|
|
488
|
+
params: queryParams,
|
|
489
|
+
groupType: queryGroupType,
|
|
490
|
+
resolver,
|
|
491
|
+
wrapType
|
|
492
|
+
}),
|
|
493
|
+
...buildGroupParam({
|
|
494
|
+
name: headersName,
|
|
495
|
+
node,
|
|
496
|
+
params: headerParams,
|
|
497
|
+
groupType: headerGroupType,
|
|
498
|
+
resolver,
|
|
499
|
+
wrapType
|
|
500
|
+
})
|
|
501
|
+
];
|
|
502
|
+
if (children.length) params.push(createParameterGroup({
|
|
503
|
+
properties: children,
|
|
504
|
+
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
505
|
+
}));
|
|
506
|
+
} else {
|
|
507
|
+
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
508
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
|
|
509
|
+
params.push(createFunctionParameter({
|
|
510
|
+
name: pathName,
|
|
511
|
+
type: spreadType ? wrapType(spreadType) : void 0,
|
|
512
|
+
rest: true
|
|
513
|
+
}));
|
|
514
|
+
} else {
|
|
515
|
+
const pathChildren = pathParams.map((p) => {
|
|
516
|
+
const type = resolveParamsType({
|
|
517
|
+
node,
|
|
518
|
+
param: p,
|
|
519
|
+
resolver
|
|
520
|
+
});
|
|
521
|
+
return createFunctionParameter({
|
|
522
|
+
name: p.name,
|
|
523
|
+
type: wrapTypeNode(type),
|
|
524
|
+
optional: !p.required
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
params.push(createParameterGroup({
|
|
528
|
+
properties: pathChildren,
|
|
529
|
+
inline: pathParamsType === "inline",
|
|
530
|
+
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
if (bodyType) params.push(createFunctionParameter({
|
|
534
|
+
name: dataName,
|
|
535
|
+
type: bodyType,
|
|
536
|
+
optional: !bodyRequired
|
|
537
|
+
}));
|
|
538
|
+
params.push(...buildGroupParam({
|
|
539
|
+
name: paramsName,
|
|
540
|
+
node,
|
|
541
|
+
params: queryParams,
|
|
542
|
+
groupType: queryGroupType,
|
|
543
|
+
resolver,
|
|
544
|
+
wrapType
|
|
545
|
+
}));
|
|
546
|
+
params.push(...buildGroupParam({
|
|
547
|
+
name: headersName,
|
|
548
|
+
node,
|
|
549
|
+
params: headerParams,
|
|
550
|
+
groupType: headerGroupType,
|
|
551
|
+
resolver,
|
|
552
|
+
wrapType
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
params.push(...extraParams);
|
|
556
|
+
return createFunctionParameters({ params });
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
560
|
+
* Returns an empty array when there are no params to emit.
|
|
561
|
+
*
|
|
562
|
+
* If a pre-resolved `groupType` is provided it emits `name: GroupType`.
|
|
563
|
+
* Otherwise, it builds an inline struct from the individual params.
|
|
564
|
+
*/
|
|
565
|
+
function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
|
|
566
|
+
if (groupType) return [createFunctionParameter({
|
|
567
|
+
name,
|
|
568
|
+
type: groupType.type.kind === "ParamsType" && groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
569
|
+
optional: groupType.optional
|
|
570
|
+
})];
|
|
571
|
+
if (params.length) return [createFunctionParameter({
|
|
572
|
+
name,
|
|
573
|
+
type: toStructType({
|
|
574
|
+
node,
|
|
575
|
+
params,
|
|
576
|
+
resolver
|
|
577
|
+
}),
|
|
578
|
+
optional: params.every((p) => !p.required)
|
|
579
|
+
})];
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
584
|
+
* Returns `undefined` when the group name equals the individual param name (no real group).
|
|
585
|
+
*/
|
|
586
|
+
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
587
|
+
if (!params.length) return;
|
|
588
|
+
const firstParam = params[0];
|
|
589
|
+
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
590
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
591
|
+
const allOptional = params.every((p) => !p.required);
|
|
592
|
+
return {
|
|
593
|
+
type: createParamsType({
|
|
594
|
+
variant: "reference",
|
|
595
|
+
name: groupName
|
|
596
|
+
}),
|
|
597
|
+
optional: allOptional
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
|
|
602
|
+
*
|
|
603
|
+
* Used when query or header parameters have no dedicated group type name.
|
|
604
|
+
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
605
|
+
*/
|
|
606
|
+
function toStructType({ node, params, resolver }) {
|
|
607
|
+
return createParamsType({
|
|
608
|
+
variant: "struct",
|
|
609
|
+
properties: params.map((p) => ({
|
|
610
|
+
name: p.name,
|
|
611
|
+
optional: !p.required,
|
|
612
|
+
type: resolveParamsType({
|
|
613
|
+
node,
|
|
614
|
+
param: p,
|
|
615
|
+
resolver
|
|
616
|
+
})
|
|
617
|
+
}))
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function sourceKey(source) {
|
|
621
|
+
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
622
|
+
}
|
|
623
|
+
function pathTypeKey(path, isTypeOnly) {
|
|
624
|
+
return `${path}:${isTypeOnly ?? false}`;
|
|
625
|
+
}
|
|
626
|
+
function exportKey(path, name, isTypeOnly, asAlias) {
|
|
627
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
|
|
628
|
+
}
|
|
629
|
+
function importKey(path, name, isTypeOnly) {
|
|
630
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Computes a multi-level sort key for exports and imports:
|
|
634
|
+
* non-array names first (wildcards/namespace aliases); type-only before value; alphabetical path; unnamed before named.
|
|
635
|
+
*/
|
|
636
|
+
function sortKey(node) {
|
|
637
|
+
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
638
|
+
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
639
|
+
const hasName = node.name != null ? "1" : "0";
|
|
640
|
+
const name = Array.isArray(node.name) ? [...node.name].sort().join("\0") : node.name ?? "";
|
|
641
|
+
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Deduplicates an array of `SourceNode` objects.
|
|
645
|
+
* Named sources are deduplicated by `name + isExportable + isTypeOnly`.
|
|
646
|
+
* Unnamed sources are deduplicated by object reference.
|
|
647
|
+
*/
|
|
648
|
+
function combineSources(sources) {
|
|
649
|
+
const seen = /* @__PURE__ */ new Map();
|
|
650
|
+
for (const source of sources) {
|
|
651
|
+
const key = sourceKey(source);
|
|
652
|
+
if (!seen.has(key)) seen.set(key, source);
|
|
653
|
+
}
|
|
654
|
+
return [...seen.values()];
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Deduplicates and merges an array of `ExportNode` objects.
|
|
658
|
+
* Exports with the same path and `isTypeOnly` flag have their names merged.
|
|
659
|
+
*/
|
|
660
|
+
function combineExports(exports) {
|
|
661
|
+
const result = [];
|
|
662
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
663
|
+
const seen = /* @__PURE__ */ new Set();
|
|
664
|
+
for (const curr of [...exports].sort((a, b) => {
|
|
665
|
+
const ka = sortKey(a);
|
|
666
|
+
const kb = sortKey(b);
|
|
667
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
668
|
+
})) {
|
|
669
|
+
const { name, path, isTypeOnly, asAlias } = curr;
|
|
670
|
+
if (Array.isArray(name)) {
|
|
671
|
+
if (!name.length) continue;
|
|
672
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
673
|
+
const existing = namedByPath.get(key);
|
|
674
|
+
if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
|
|
675
|
+
else {
|
|
676
|
+
const newItem = {
|
|
677
|
+
...curr,
|
|
678
|
+
name: [...new Set(name)]
|
|
679
|
+
};
|
|
680
|
+
result.push(newItem);
|
|
681
|
+
namedByPath.set(key, newItem);
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
const key = exportKey(path, name, isTypeOnly, asAlias);
|
|
685
|
+
if (!seen.has(key)) {
|
|
686
|
+
result.push(curr);
|
|
687
|
+
seen.add(key);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Deduplicates and merges an array of `ImportNode` objects.
|
|
695
|
+
* Filters out unused imports (names not referenced in `source` or re-exported).
|
|
696
|
+
* Imports with the same path and `isTypeOnly` flag have their names merged.
|
|
697
|
+
*/
|
|
698
|
+
function combineImports(imports, exports, source) {
|
|
699
|
+
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
700
|
+
const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
|
|
701
|
+
const result = [];
|
|
702
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
704
|
+
for (const curr of [...imports].sort((a, b) => {
|
|
705
|
+
const ka = sortKey(a);
|
|
706
|
+
const kb = sortKey(b);
|
|
707
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
708
|
+
})) {
|
|
709
|
+
if (curr.path === curr.root) continue;
|
|
710
|
+
const { path, isTypeOnly } = curr;
|
|
711
|
+
let { name } = curr;
|
|
712
|
+
if (Array.isArray(name)) {
|
|
713
|
+
name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.propertyName));
|
|
714
|
+
if (!name.length) continue;
|
|
715
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
716
|
+
const existing = namedByPath.get(key);
|
|
717
|
+
if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
|
|
718
|
+
else {
|
|
719
|
+
const newItem = {
|
|
720
|
+
...curr,
|
|
721
|
+
name
|
|
722
|
+
};
|
|
723
|
+
result.push(newItem);
|
|
724
|
+
namedByPath.set(key, newItem);
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
if (name && !isUsed(name)) continue;
|
|
728
|
+
const key = importKey(path, name, isTypeOnly);
|
|
729
|
+
if (!seen.has(key)) {
|
|
730
|
+
result.push(curr);
|
|
731
|
+
seen.add(key);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Recursively extracts all string content embedded in a {@link CodeNode} tree.
|
|
739
|
+
*
|
|
740
|
+
* Includes text node values, and string attribute fields (`params`, `generics`,
|
|
741
|
+
* `returnType`, `type`) that may reference identifiers needing imports.
|
|
742
|
+
* Used by `createFile` to build the full source string for import filtering.
|
|
743
|
+
*/
|
|
744
|
+
function extractStringsFromNodes(nodes) {
|
|
745
|
+
if (!nodes?.length) return "";
|
|
746
|
+
return nodes.map((node) => {
|
|
747
|
+
if (typeof node === "string") return node;
|
|
748
|
+
if (node.kind === "Text") return node.value;
|
|
749
|
+
if (node.kind === "Break") return "";
|
|
750
|
+
if (node.kind === "Jsx") return node.value;
|
|
751
|
+
const parts = [];
|
|
752
|
+
if ("params" in node && node.params) parts.push(node.params);
|
|
753
|
+
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
754
|
+
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
755
|
+
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
756
|
+
const nested = extractStringsFromNodes(node.nodes);
|
|
757
|
+
if (nested) parts.push(nested);
|
|
758
|
+
return parts.join("\n");
|
|
759
|
+
}).filter(Boolean).join("\n");
|
|
760
|
+
}
|
|
761
|
+
//#endregion
|
|
74
762
|
//#region src/factory.ts
|
|
75
763
|
/**
|
|
76
|
-
*
|
|
764
|
+
* Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
|
|
765
|
+
*
|
|
766
|
+
* - `optional` is set for non-required, non-nullable schemas.
|
|
767
|
+
* - `nullish` is set for non-required, nullable schemas.
|
|
768
|
+
*/
|
|
769
|
+
function syncOptionality(schema, required) {
|
|
770
|
+
const nullable = schema.nullable ?? false;
|
|
771
|
+
return {
|
|
772
|
+
...schema,
|
|
773
|
+
optional: !required && !nullable ? true : void 0,
|
|
774
|
+
nullish: !required && nullable ? true : void 0
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* const input = createInput()
|
|
783
|
+
* // { kind: 'Input', schemas: [], operations: [] }
|
|
784
|
+
* ```
|
|
785
|
+
*
|
|
786
|
+
* @example
|
|
787
|
+
* ```ts
|
|
788
|
+
* const input = createInput({ schemas: [petSchema] })
|
|
789
|
+
* // keeps default operations: []
|
|
790
|
+
* ```
|
|
77
791
|
*/
|
|
78
|
-
function
|
|
792
|
+
function createInput(overrides = {}) {
|
|
79
793
|
return {
|
|
80
794
|
schemas: [],
|
|
81
795
|
operations: [],
|
|
82
796
|
...overrides,
|
|
83
|
-
kind: "
|
|
797
|
+
kind: "Input"
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Creates an `OutputNode` with a stable default for `files`.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* const output = createOutput()
|
|
806
|
+
* // { kind: 'Output', files: [] }
|
|
807
|
+
* ```
|
|
808
|
+
*
|
|
809
|
+
* @example
|
|
810
|
+
* ```ts
|
|
811
|
+
* const output = createOutput({ files: [petFile] })
|
|
812
|
+
* ```
|
|
813
|
+
*/
|
|
814
|
+
function createOutput(overrides = {}) {
|
|
815
|
+
return {
|
|
816
|
+
files: [],
|
|
817
|
+
...overrides,
|
|
818
|
+
kind: "Output"
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
|
|
823
|
+
*
|
|
824
|
+
* @example
|
|
825
|
+
* ```ts
|
|
826
|
+
* const operation = createOperation({
|
|
827
|
+
* operationId: 'getPetById',
|
|
828
|
+
* method: 'GET',
|
|
829
|
+
* path: '/pet/{petId}',
|
|
830
|
+
* })
|
|
831
|
+
* // tags, parameters, and responses are []
|
|
832
|
+
* ```
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```ts
|
|
836
|
+
* const operation = createOperation({
|
|
837
|
+
* operationId: 'findPets',
|
|
838
|
+
* method: 'GET',
|
|
839
|
+
* path: '/pet/findByStatus',
|
|
840
|
+
* tags: ['pet'],
|
|
841
|
+
* })
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
function createOperation(props) {
|
|
845
|
+
return {
|
|
846
|
+
tags: [],
|
|
847
|
+
parameters: [],
|
|
848
|
+
responses: [],
|
|
849
|
+
...props,
|
|
850
|
+
kind: "Operation"
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Maps schema `type` to its underlying `primitive`.
|
|
855
|
+
* Primitive types map to themselves; special string formats map to `'string'`.
|
|
856
|
+
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
857
|
+
*/
|
|
858
|
+
const TYPE_TO_PRIMITIVE = {
|
|
859
|
+
string: "string",
|
|
860
|
+
number: "number",
|
|
861
|
+
integer: "integer",
|
|
862
|
+
bigint: "bigint",
|
|
863
|
+
boolean: "boolean",
|
|
864
|
+
null: "null",
|
|
865
|
+
any: "any",
|
|
866
|
+
unknown: "unknown",
|
|
867
|
+
void: "void",
|
|
868
|
+
never: "never",
|
|
869
|
+
object: "object",
|
|
870
|
+
array: "array",
|
|
871
|
+
date: "date",
|
|
872
|
+
uuid: "string",
|
|
873
|
+
email: "string",
|
|
874
|
+
url: "string",
|
|
875
|
+
datetime: "string",
|
|
876
|
+
time: "string"
|
|
877
|
+
};
|
|
878
|
+
function createSchema(props) {
|
|
879
|
+
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
880
|
+
if (props["type"] === "object") return {
|
|
881
|
+
properties: [],
|
|
882
|
+
primitive: "object",
|
|
883
|
+
...props,
|
|
884
|
+
kind: "Schema"
|
|
885
|
+
};
|
|
886
|
+
return {
|
|
887
|
+
primitive: inferredPrimitive,
|
|
888
|
+
...props,
|
|
889
|
+
kind: "Schema"
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Creates a `PropertyNode`.
|
|
894
|
+
*
|
|
895
|
+
* `required` defaults to `false`.
|
|
896
|
+
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* ```ts
|
|
900
|
+
* const property = createProperty({
|
|
901
|
+
* name: 'status',
|
|
902
|
+
* schema: createSchema({ type: 'string' }),
|
|
903
|
+
* })
|
|
904
|
+
* // required=false, schema.optional=true
|
|
905
|
+
* ```
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```ts
|
|
909
|
+
* const property = createProperty({
|
|
910
|
+
* name: 'status',
|
|
911
|
+
* required: true,
|
|
912
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
913
|
+
* })
|
|
914
|
+
* // required=true, no optional/nullish
|
|
915
|
+
* ```
|
|
916
|
+
*/
|
|
917
|
+
function createProperty(props) {
|
|
918
|
+
const required = props.required ?? false;
|
|
919
|
+
return {
|
|
920
|
+
...props,
|
|
921
|
+
kind: "Property",
|
|
922
|
+
required,
|
|
923
|
+
schema: syncOptionality(props.schema, required)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Creates a `ParameterNode`.
|
|
928
|
+
*
|
|
929
|
+
* `required` defaults to `false`.
|
|
930
|
+
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```ts
|
|
934
|
+
* const param = createParameter({
|
|
935
|
+
* name: 'petId',
|
|
936
|
+
* in: 'path',
|
|
937
|
+
* required: true,
|
|
938
|
+
* schema: createSchema({ type: 'string' }),
|
|
939
|
+
* })
|
|
940
|
+
* ```
|
|
941
|
+
*
|
|
942
|
+
* @example
|
|
943
|
+
* ```ts
|
|
944
|
+
* const param = createParameter({
|
|
945
|
+
* name: 'status',
|
|
946
|
+
* in: 'query',
|
|
947
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
948
|
+
* })
|
|
949
|
+
* // required=false, schema.nullish=true
|
|
950
|
+
* ```
|
|
951
|
+
*/
|
|
952
|
+
function createParameter(props) {
|
|
953
|
+
const required = props.required ?? false;
|
|
954
|
+
return {
|
|
955
|
+
...props,
|
|
956
|
+
kind: "Parameter",
|
|
957
|
+
required,
|
|
958
|
+
schema: syncOptionality(props.schema, required)
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Creates a `ResponseNode`.
|
|
963
|
+
*
|
|
964
|
+
* @example
|
|
965
|
+
* ```ts
|
|
966
|
+
* const response = createResponse({
|
|
967
|
+
* statusCode: '200',
|
|
968
|
+
* description: 'Success',
|
|
969
|
+
* schema: createSchema({ type: 'object', properties: [] }),
|
|
970
|
+
* })
|
|
971
|
+
* ```
|
|
972
|
+
*/
|
|
973
|
+
function createResponse(props) {
|
|
974
|
+
return {
|
|
975
|
+
...props,
|
|
976
|
+
kind: "Response"
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Creates a `FunctionParameterNode`.
|
|
981
|
+
*
|
|
982
|
+
* `optional` defaults to `false`.
|
|
983
|
+
*
|
|
984
|
+
* @example Required typed param
|
|
985
|
+
* ```ts
|
|
986
|
+
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
|
|
987
|
+
* // → petId: string
|
|
988
|
+
* ```
|
|
989
|
+
*
|
|
990
|
+
* @example Optional param
|
|
991
|
+
* ```ts
|
|
992
|
+
* createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
993
|
+
* // → params?: QueryParams
|
|
994
|
+
* ```
|
|
995
|
+
*
|
|
996
|
+
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
997
|
+
* ```ts
|
|
998
|
+
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
999
|
+
* // → config: RequestConfig = {}
|
|
1000
|
+
* ```
|
|
1001
|
+
*/
|
|
1002
|
+
function createFunctionParameter(props) {
|
|
1003
|
+
return {
|
|
1004
|
+
optional: false,
|
|
1005
|
+
...props,
|
|
1006
|
+
kind: "FunctionParameter"
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Creates a {@link TypeNode} representing a language-agnostic structured type expression.
|
|
1011
|
+
*
|
|
1012
|
+
* Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
|
|
1013
|
+
* named field accessed from a group type. Each language's printer renders the variant
|
|
1014
|
+
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
1015
|
+
*
|
|
1016
|
+
* @example Reference type (TypeScript: `QueryParams`)
|
|
1017
|
+
* ```ts
|
|
1018
|
+
* createParamsType({ variant: 'reference', name: 'QueryParams' })
|
|
1019
|
+
* ```
|
|
1020
|
+
*
|
|
1021
|
+
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
|
|
1024
|
+
* ```
|
|
1025
|
+
*
|
|
1026
|
+
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
1027
|
+
* ```ts
|
|
1028
|
+
* createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
1029
|
+
* ```
|
|
1030
|
+
*/
|
|
1031
|
+
function createParamsType(props) {
|
|
1032
|
+
return {
|
|
1033
|
+
...props,
|
|
1034
|
+
kind: "ParamsType"
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
|
|
1039
|
+
*
|
|
1040
|
+
* @example Grouped param (TypeScript declaration)
|
|
1041
|
+
* ```ts
|
|
1042
|
+
* createParameterGroup({
|
|
1043
|
+
* properties: [
|
|
1044
|
+
* createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1045
|
+
* createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
|
|
1046
|
+
* ],
|
|
1047
|
+
* default: '{}',
|
|
1048
|
+
* })
|
|
1049
|
+
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
1050
|
+
* // call → { id, name }
|
|
1051
|
+
* ```
|
|
1052
|
+
*
|
|
1053
|
+
* @example Inline (spread) — children emitted as individual top-level parameters
|
|
1054
|
+
* ```ts
|
|
1055
|
+
* createParameterGroup({
|
|
1056
|
+
* properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
|
|
1057
|
+
* inline: true,
|
|
1058
|
+
* })
|
|
1059
|
+
* // declaration → petId: string
|
|
1060
|
+
* // call → petId
|
|
1061
|
+
* ```
|
|
1062
|
+
*/
|
|
1063
|
+
function createParameterGroup(props) {
|
|
1064
|
+
return {
|
|
1065
|
+
...props,
|
|
1066
|
+
kind: "ParameterGroup"
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
1071
|
+
*
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```ts
|
|
1074
|
+
* createFunctionParameters({
|
|
1075
|
+
* params: [
|
|
1076
|
+
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1077
|
+
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
|
|
1078
|
+
* ],
|
|
1079
|
+
* })
|
|
1080
|
+
* ```
|
|
1081
|
+
*
|
|
1082
|
+
* @example
|
|
1083
|
+
* ```ts
|
|
1084
|
+
* const empty = createFunctionParameters()
|
|
1085
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
1086
|
+
* ```
|
|
1087
|
+
*/
|
|
1088
|
+
function createFunctionParameters(props = {}) {
|
|
1089
|
+
return {
|
|
1090
|
+
params: [],
|
|
1091
|
+
...props,
|
|
1092
|
+
kind: "FunctionParameters"
|
|
84
1093
|
};
|
|
85
1094
|
}
|
|
86
1095
|
/**
|
|
87
|
-
* Creates an `
|
|
1096
|
+
* Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
|
|
1097
|
+
*
|
|
1098
|
+
* @example Named import
|
|
1099
|
+
* ```ts
|
|
1100
|
+
* createImport({ name: ['useState'], path: 'react' })
|
|
1101
|
+
* // import { useState } from 'react'
|
|
1102
|
+
* ```
|
|
1103
|
+
*
|
|
1104
|
+
* @example Type-only import
|
|
1105
|
+
* ```ts
|
|
1106
|
+
* createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
|
|
1107
|
+
* // import type { FC } from 'react'
|
|
1108
|
+
* ```
|
|
88
1109
|
*/
|
|
89
|
-
function
|
|
1110
|
+
function createImport(props) {
|
|
90
1111
|
return {
|
|
91
|
-
tags: [],
|
|
92
|
-
parameters: [],
|
|
93
|
-
responses: [],
|
|
94
1112
|
...props,
|
|
95
|
-
kind: "
|
|
1113
|
+
kind: "Import"
|
|
96
1114
|
};
|
|
97
1115
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1116
|
+
/**
|
|
1117
|
+
* Creates an `ExportNode` representing a language-agnostic export/public API declaration.
|
|
1118
|
+
*
|
|
1119
|
+
* @example Named export
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* createExport({ name: ['Pet'], path: './Pet' })
|
|
1122
|
+
* // export { Pet } from './Pet'
|
|
1123
|
+
* ```
|
|
1124
|
+
*
|
|
1125
|
+
* @example Wildcard export
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* createExport({ path: './utils' })
|
|
1128
|
+
* // export * from './utils'
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
function createExport(props) {
|
|
104
1132
|
return {
|
|
105
1133
|
...props,
|
|
106
|
-
kind: "
|
|
1134
|
+
kind: "Export"
|
|
107
1135
|
};
|
|
108
1136
|
}
|
|
109
1137
|
/**
|
|
110
|
-
* Creates a `
|
|
1138
|
+
* Creates a `SourceNode` representing a fragment of source code within a file.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```ts
|
|
1142
|
+
* createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
|
|
1143
|
+
* ```
|
|
111
1144
|
*/
|
|
112
|
-
function
|
|
1145
|
+
function createSource(props) {
|
|
113
1146
|
return {
|
|
114
|
-
required: false,
|
|
115
1147
|
...props,
|
|
116
|
-
kind: "
|
|
1148
|
+
kind: "Source"
|
|
117
1149
|
};
|
|
118
1150
|
}
|
|
119
1151
|
/**
|
|
120
|
-
* Creates a
|
|
1152
|
+
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
1153
|
+
*
|
|
1154
|
+
* Computes:
|
|
1155
|
+
* - `id` — SHA256 hash of the file path
|
|
1156
|
+
* - `name` — `baseName` without extension
|
|
1157
|
+
* - `extname` — extension extracted from `baseName`
|
|
1158
|
+
*
|
|
1159
|
+
* Deduplicates:
|
|
1160
|
+
* - `sources` via `combineSources`
|
|
1161
|
+
* - `exports` via `combineExports`
|
|
1162
|
+
* - `imports` via `combineImports` (also filters unused imports)
|
|
1163
|
+
*
|
|
1164
|
+
* @throws {Error} when `baseName` has no extension.
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* ```ts
|
|
1168
|
+
* const file = createFile({
|
|
1169
|
+
* baseName: 'petStore.ts',
|
|
1170
|
+
* path: 'src/models/petStore.ts',
|
|
1171
|
+
* sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
|
|
1172
|
+
* imports: [createImport({ name: ['z'], path: 'zod' })],
|
|
1173
|
+
* exports: [createExport({ name: ['Pet'], path: './petStore' })],
|
|
1174
|
+
* })
|
|
1175
|
+
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
1176
|
+
* // file.name = 'petStore'
|
|
1177
|
+
* // file.extname = '.ts'
|
|
1178
|
+
* ```
|
|
121
1179
|
*/
|
|
122
|
-
function
|
|
1180
|
+
function createFile(input) {
|
|
1181
|
+
const extname = node_path.default.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
|
|
1182
|
+
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
1183
|
+
const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
|
|
1184
|
+
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
1185
|
+
const resolvedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
|
|
1186
|
+
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
123
1187
|
return {
|
|
124
|
-
|
|
125
|
-
...
|
|
126
|
-
|
|
1188
|
+
kind: "File",
|
|
1189
|
+
...input,
|
|
1190
|
+
id: (0, node_crypto.createHash)("sha256").update(input.path).digest("hex"),
|
|
1191
|
+
name: trimExtName(input.baseName),
|
|
1192
|
+
extname,
|
|
1193
|
+
imports: resolvedImports,
|
|
1194
|
+
exports: resolvedExports,
|
|
1195
|
+
sources: resolvedSources,
|
|
1196
|
+
meta: input.meta ?? {}
|
|
127
1197
|
};
|
|
128
1198
|
}
|
|
129
1199
|
/**
|
|
130
|
-
* Creates a `
|
|
1200
|
+
* Creates a `ConstNode` representing a TypeScript `const` declaration.
|
|
1201
|
+
*
|
|
1202
|
+
* Mirrors the `Const` component from `@kubb/renderer-jsx`.
|
|
1203
|
+
* The component's `children` are represented as `nodes`.
|
|
1204
|
+
*
|
|
1205
|
+
* @example Simple constant
|
|
1206
|
+
* ```ts
|
|
1207
|
+
* createConst({ name: 'pet' })
|
|
1208
|
+
* // const pet = ...
|
|
1209
|
+
* ```
|
|
1210
|
+
*
|
|
1211
|
+
* @example Exported constant with type and `as const`
|
|
1212
|
+
* ```ts
|
|
1213
|
+
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
1214
|
+
* // export const pets: Pet[] = ... as const
|
|
1215
|
+
* ```
|
|
1216
|
+
*
|
|
1217
|
+
* @example With JSDoc and child nodes
|
|
1218
|
+
* ```ts
|
|
1219
|
+
* createConst({
|
|
1220
|
+
* name: 'config',
|
|
1221
|
+
* export: true,
|
|
1222
|
+
* JSDoc: { comments: ['@description App configuration'] },
|
|
1223
|
+
* nodes: [],
|
|
1224
|
+
* })
|
|
1225
|
+
* ```
|
|
131
1226
|
*/
|
|
132
|
-
function
|
|
1227
|
+
function createConst(props) {
|
|
133
1228
|
return {
|
|
134
1229
|
...props,
|
|
135
|
-
kind: "
|
|
1230
|
+
kind: "Const"
|
|
136
1231
|
};
|
|
137
1232
|
}
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/guards.ts
|
|
140
1233
|
/**
|
|
141
|
-
*
|
|
1234
|
+
* Creates a `TypeNode` representing a TypeScript `type` alias declaration.
|
|
1235
|
+
*
|
|
1236
|
+
* Mirrors the `Type` component from `@kubb/renderer-jsx`.
|
|
1237
|
+
* The component's `children` are represented as `nodes`.
|
|
1238
|
+
*
|
|
1239
|
+
* @example Simple type alias
|
|
1240
|
+
* ```ts
|
|
1241
|
+
* createType({ name: 'Pet' })
|
|
1242
|
+
* // type Pet = ...
|
|
1243
|
+
* ```
|
|
1244
|
+
*
|
|
1245
|
+
* @example Exported type with JSDoc
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* createType({
|
|
1248
|
+
* name: 'PetStatus',
|
|
1249
|
+
* export: true,
|
|
1250
|
+
* JSDoc: { comments: ['@description Status of a pet'] },
|
|
1251
|
+
* })
|
|
1252
|
+
* // export type PetStatus = ...
|
|
1253
|
+
* ```
|
|
142
1254
|
*/
|
|
143
|
-
function
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
1255
|
+
function createType(props) {
|
|
1256
|
+
return {
|
|
1257
|
+
...props,
|
|
1258
|
+
kind: "Type"
|
|
1259
|
+
};
|
|
148
1260
|
}
|
|
149
1261
|
/**
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
*
|
|
1262
|
+
* Creates a `FunctionNode` representing a TypeScript `function` declaration.
|
|
1263
|
+
*
|
|
1264
|
+
* Mirrors the `Function` component from `@kubb/renderer-jsx`.
|
|
1265
|
+
* The component's `children` are represented as `nodes`.
|
|
1266
|
+
*
|
|
1267
|
+
* @example Simple function
|
|
1268
|
+
* ```ts
|
|
1269
|
+
* createFunction({ name: 'getPet' })
|
|
1270
|
+
* // function getPet() { ... }
|
|
1271
|
+
* ```
|
|
1272
|
+
*
|
|
1273
|
+
* @example Exported async function with return type
|
|
1274
|
+
* ```ts
|
|
1275
|
+
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
1276
|
+
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
1277
|
+
* ```
|
|
1278
|
+
*
|
|
1279
|
+
* @example Function with generics and params
|
|
1280
|
+
* ```ts
|
|
1281
|
+
* createFunction({
|
|
1282
|
+
* name: 'identity',
|
|
1283
|
+
* export: true,
|
|
1284
|
+
* generics: ['T'],
|
|
1285
|
+
* params: 'value: T',
|
|
1286
|
+
* returnType: 'T',
|
|
1287
|
+
* })
|
|
1288
|
+
* // export function identity<T>(value: T): T { ... }
|
|
1289
|
+
* ```
|
|
155
1290
|
*/
|
|
156
|
-
|
|
1291
|
+
function createFunction(props) {
|
|
1292
|
+
return {
|
|
1293
|
+
...props,
|
|
1294
|
+
kind: "Function"
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
157
1297
|
/**
|
|
158
|
-
*
|
|
1298
|
+
* Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
|
|
1299
|
+
*
|
|
1300
|
+
* Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
|
|
1301
|
+
* The component's `children` are represented as `nodes`.
|
|
1302
|
+
*
|
|
1303
|
+
* @example Simple arrow function
|
|
1304
|
+
* ```ts
|
|
1305
|
+
* createArrowFunction({ name: 'getPet' })
|
|
1306
|
+
* // const getPet = () => { ... }
|
|
1307
|
+
* ```
|
|
1308
|
+
*
|
|
1309
|
+
* @example Single-line exported arrow function
|
|
1310
|
+
* ```ts
|
|
1311
|
+
* createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
|
|
1312
|
+
* // export const double = (n: number) => ...
|
|
1313
|
+
* ```
|
|
1314
|
+
*
|
|
1315
|
+
* @example Async arrow function with generics
|
|
1316
|
+
* ```ts
|
|
1317
|
+
* createArrowFunction({
|
|
1318
|
+
* name: 'fetchPet',
|
|
1319
|
+
* export: true,
|
|
1320
|
+
* async: true,
|
|
1321
|
+
* generics: ['T'],
|
|
1322
|
+
* params: 'id: string',
|
|
1323
|
+
* returnType: 'T',
|
|
1324
|
+
* })
|
|
1325
|
+
* // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
|
|
1326
|
+
* ```
|
|
159
1327
|
*/
|
|
160
|
-
|
|
1328
|
+
function createArrowFunction(props) {
|
|
1329
|
+
return {
|
|
1330
|
+
...props,
|
|
1331
|
+
kind: "ArrowFunction"
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
161
1334
|
/**
|
|
162
|
-
*
|
|
1335
|
+
* Creates a {@link TextNode} representing a raw string fragment in the source output.
|
|
1336
|
+
*
|
|
1337
|
+
* Use this instead of bare strings when building `nodes` arrays so that every
|
|
1338
|
+
* entry in the array is a typed {@link CodeNode}.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```ts
|
|
1342
|
+
* createText('return fetch(id)')
|
|
1343
|
+
* // { kind: 'Text', value: 'return fetch(id)' }
|
|
1344
|
+
* ```
|
|
163
1345
|
*/
|
|
164
|
-
|
|
1346
|
+
function createText(value) {
|
|
1347
|
+
return {
|
|
1348
|
+
value,
|
|
1349
|
+
kind: "Text"
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
165
1352
|
/**
|
|
166
|
-
*
|
|
1353
|
+
* Creates a {@link BreakNode} representing a line break in the source output.
|
|
1354
|
+
*
|
|
1355
|
+
* Corresponds to `<br/>` in JSX components. Prints as an empty string which,
|
|
1356
|
+
* when joined with `\n` by `printNodes`, produces a blank line.
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* ```ts
|
|
1360
|
+
* createBreak()
|
|
1361
|
+
* // { kind: 'Break' }
|
|
1362
|
+
* ```
|
|
167
1363
|
*/
|
|
168
|
-
|
|
1364
|
+
function createBreak() {
|
|
1365
|
+
return { kind: "Break" };
|
|
1366
|
+
}
|
|
169
1367
|
/**
|
|
170
|
-
*
|
|
1368
|
+
* Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
|
|
1369
|
+
*
|
|
1370
|
+
* Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
|
|
1371
|
+
*
|
|
1372
|
+
* @example
|
|
1373
|
+
* ```ts
|
|
1374
|
+
* createJsx('<>\n <a href={href}>Open</a>\n</>')
|
|
1375
|
+
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
1376
|
+
* ```
|
|
171
1377
|
*/
|
|
172
|
-
|
|
1378
|
+
function createJsx(value) {
|
|
1379
|
+
return {
|
|
1380
|
+
value,
|
|
1381
|
+
kind: "Jsx"
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
173
1384
|
//#endregion
|
|
174
1385
|
//#region src/printer.ts
|
|
175
1386
|
/**
|
|
176
|
-
* Creates a
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* object(node) {
|
|
194
|
-
* const props = node.properties
|
|
195
|
-
* ?.map(p => `${p.name}: ${this.print(p)}`)
|
|
196
|
-
* .join(', ') ?? ''
|
|
197
|
-
* return `z.object({ ${props} })`
|
|
198
|
-
* },
|
|
199
|
-
* },
|
|
200
|
-
* }
|
|
201
|
-
* })
|
|
1387
|
+
* Creates a schema printer factory.
|
|
1388
|
+
*
|
|
1389
|
+
* This function wraps a builder and makes options optional at call sites.
|
|
1390
|
+
*
|
|
1391
|
+
* The builder receives resolved options and returns:
|
|
1392
|
+
* - `name` — a unique identifier for the printer
|
|
1393
|
+
* - `options` — options stored on the returned printer instance
|
|
1394
|
+
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
1395
|
+
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
1396
|
+
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
1397
|
+
* - This keeps recursion safe and avoids self-calls
|
|
1398
|
+
*
|
|
1399
|
+
* When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
|
|
1400
|
+
*
|
|
1401
|
+
* @example Basic usage — Zod schema printer
|
|
1402
|
+
* ```ts
|
|
1403
|
+
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
202
1404
|
*
|
|
203
|
-
* const
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
1405
|
+
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
1406
|
+
* name: 'zod',
|
|
1407
|
+
* options: { strict: options.strict ?? true },
|
|
1408
|
+
* nodes: {
|
|
1409
|
+
* string: () => 'z.string()',
|
|
1410
|
+
* object(node) {
|
|
1411
|
+
* const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
|
|
1412
|
+
* return `z.object({ ${props} })`
|
|
1413
|
+
* },
|
|
1414
|
+
* },
|
|
1415
|
+
* }))
|
|
207
1416
|
* ```
|
|
208
1417
|
*/
|
|
209
1418
|
function definePrinter(build) {
|
|
210
|
-
return (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
1419
|
+
return createPrinterFactory((node) => node.type)(build);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
1423
|
+
**
|
|
1424
|
+
* @example
|
|
1425
|
+
* ```ts
|
|
1426
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
|
|
1427
|
+
* (node) => kindToHandlerKey[node.kind],
|
|
1428
|
+
* )
|
|
1429
|
+
* ```
|
|
1430
|
+
*/
|
|
1431
|
+
function createPrinterFactory(getKey) {
|
|
1432
|
+
return function(build) {
|
|
1433
|
+
return (options) => {
|
|
1434
|
+
const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
|
|
1435
|
+
const context = {
|
|
1436
|
+
options: resolvedOptions,
|
|
1437
|
+
transform: (node) => {
|
|
1438
|
+
const key = getKey(node);
|
|
1439
|
+
if (key === void 0) return null;
|
|
1440
|
+
const handler = nodes[key];
|
|
1441
|
+
if (!handler) return null;
|
|
1442
|
+
return handler.call(context, node);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
return {
|
|
1446
|
+
name,
|
|
1447
|
+
options: resolvedOptions,
|
|
1448
|
+
transform: context.transform,
|
|
1449
|
+
print: printOverride ? printOverride.bind(context) : context.transform
|
|
1450
|
+
};
|
|
224
1451
|
};
|
|
225
1452
|
};
|
|
226
1453
|
}
|
|
227
1454
|
//#endregion
|
|
228
1455
|
//#region src/refs.ts
|
|
229
1456
|
/**
|
|
230
|
-
*
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
* Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
|
|
239
|
-
*/
|
|
240
|
-
function resolveRef(refMap, ref) {
|
|
241
|
-
return refMap.get(ref);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Converts a `RefMap` to a plain object.
|
|
1457
|
+
* Returns the last path segment of a reference string.
|
|
1458
|
+
*
|
|
1459
|
+
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* ```ts
|
|
1463
|
+
* extractRefName('#/components/schemas/Pet') // 'Pet'
|
|
1464
|
+
* ```
|
|
245
1465
|
*/
|
|
246
|
-
function
|
|
247
|
-
return
|
|
1466
|
+
function extractRefName(ref) {
|
|
1467
|
+
return ref.split("/").at(-1) ?? ref;
|
|
248
1468
|
}
|
|
249
1469
|
//#endregion
|
|
250
|
-
//#region src/
|
|
251
|
-
const plainStringTypes = new Set([
|
|
252
|
-
"string",
|
|
253
|
-
"uuid",
|
|
254
|
-
"email",
|
|
255
|
-
"url",
|
|
256
|
-
"datetime"
|
|
257
|
-
]);
|
|
1470
|
+
//#region src/visitor.ts
|
|
258
1471
|
/**
|
|
259
|
-
*
|
|
1472
|
+
* Creates a small async concurrency limiter.
|
|
260
1473
|
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
1474
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
1475
|
+
*
|
|
1476
|
+
* @example
|
|
1477
|
+
* ```ts
|
|
1478
|
+
* const limit = createLimit(2)
|
|
1479
|
+
* for (const task of [taskA, taskB, taskC]) {
|
|
1480
|
+
* await limit(() => task())
|
|
1481
|
+
* }
|
|
1482
|
+
* // only 2 tasks run at the same time
|
|
1483
|
+
* ```
|
|
263
1484
|
*/
|
|
264
|
-
function isPlainStringType(node) {
|
|
265
|
-
if (plainStringTypes.has(node.type)) return true;
|
|
266
|
-
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
267
|
-
if (temporal) return temporal.representation !== "date";
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
//#endregion
|
|
271
|
-
//#region src/visitor.ts
|
|
272
1485
|
function createLimit(concurrency) {
|
|
273
1486
|
let active = 0;
|
|
274
1487
|
const queue = [];
|
|
@@ -291,14 +1504,25 @@ function createLimit(concurrency) {
|
|
|
291
1504
|
};
|
|
292
1505
|
}
|
|
293
1506
|
/**
|
|
294
|
-
*
|
|
1507
|
+
* Returns the immediate traversable children of `node`.
|
|
1508
|
+
*
|
|
1509
|
+
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
1510
|
+
* `additionalProperties`) are only included
|
|
1511
|
+
* when `recurse` is `true`; shallow mode skips them.
|
|
1512
|
+
*
|
|
1513
|
+
* @example
|
|
1514
|
+
* ```ts
|
|
1515
|
+
* const children = getChildren(operationNode, true)
|
|
1516
|
+
* // returns parameters, requestBody schema (if present), and responses
|
|
1517
|
+
* ```
|
|
295
1518
|
*/
|
|
296
1519
|
function getChildren(node, recurse) {
|
|
297
1520
|
switch (node.kind) {
|
|
298
|
-
case "
|
|
1521
|
+
case "Input": return [...node.schemas, ...node.operations];
|
|
1522
|
+
case "Output": return [];
|
|
299
1523
|
case "Operation": return [
|
|
300
1524
|
...node.parameters,
|
|
301
|
-
...node.requestBody ? [node.requestBody] : [],
|
|
1525
|
+
...node.requestBody?.schema ? [node.requestBody.schema] : [],
|
|
302
1526
|
...node.responses
|
|
303
1527
|
];
|
|
304
1528
|
case "Schema": {
|
|
@@ -307,163 +1531,425 @@ function getChildren(node, recurse) {
|
|
|
307
1531
|
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
308
1532
|
if ("items" in node && node.items) children.push(...node.items);
|
|
309
1533
|
if ("members" in node && node.members) children.push(...node.members);
|
|
1534
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
310
1535
|
return children;
|
|
311
1536
|
}
|
|
312
1537
|
case "Property": return [node.schema];
|
|
313
1538
|
case "Parameter": return [node.schema];
|
|
314
1539
|
case "Response": return node.schema ? [node.schema] : [];
|
|
1540
|
+
case "FunctionParameter":
|
|
1541
|
+
case "ParameterGroup":
|
|
1542
|
+
case "FunctionParameters":
|
|
1543
|
+
case "Type": return [];
|
|
1544
|
+
default: return [];
|
|
315
1545
|
}
|
|
316
1546
|
}
|
|
317
1547
|
/**
|
|
318
1548
|
* Depth-first traversal for side effects. Visitor return values are ignored.
|
|
319
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
1549
|
+
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
1550
|
+
* (default: `WALK_CONCURRENCY`).
|
|
1551
|
+
*
|
|
1552
|
+
* @example
|
|
1553
|
+
* ```ts
|
|
1554
|
+
* await walk(root, {
|
|
1555
|
+
* operation(node) {
|
|
1556
|
+
* console.log(node.operationId)
|
|
1557
|
+
* },
|
|
1558
|
+
* })
|
|
1559
|
+
* ```
|
|
1560
|
+
*
|
|
1561
|
+
* @example
|
|
1562
|
+
* ```ts
|
|
1563
|
+
* // Visit only the current node
|
|
1564
|
+
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
1565
|
+
* ```
|
|
320
1566
|
*/
|
|
321
|
-
async function walk(node,
|
|
322
|
-
return _walk(node,
|
|
1567
|
+
async function walk(node, options) {
|
|
1568
|
+
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
323
1569
|
}
|
|
324
|
-
async function _walk(node, visitor, recurse, limit) {
|
|
1570
|
+
async function _walk(node, visitor, recurse, limit, parent) {
|
|
325
1571
|
switch (node.kind) {
|
|
326
|
-
case "
|
|
327
|
-
await limit(() => visitor.
|
|
1572
|
+
case "Input":
|
|
1573
|
+
await limit(() => visitor.input?.(node, { parent }));
|
|
1574
|
+
break;
|
|
1575
|
+
case "Output":
|
|
1576
|
+
await limit(() => visitor.output?.(node, { parent }));
|
|
328
1577
|
break;
|
|
329
1578
|
case "Operation":
|
|
330
|
-
await limit(() => visitor.operation?.(node));
|
|
1579
|
+
await limit(() => visitor.operation?.(node, { parent }));
|
|
331
1580
|
break;
|
|
332
1581
|
case "Schema":
|
|
333
|
-
await limit(() => visitor.schema?.(node));
|
|
1582
|
+
await limit(() => visitor.schema?.(node, { parent }));
|
|
334
1583
|
break;
|
|
335
1584
|
case "Property":
|
|
336
|
-
await limit(() => visitor.property?.(node));
|
|
1585
|
+
await limit(() => visitor.property?.(node, { parent }));
|
|
337
1586
|
break;
|
|
338
1587
|
case "Parameter":
|
|
339
|
-
await limit(() => visitor.parameter?.(node));
|
|
1588
|
+
await limit(() => visitor.parameter?.(node, { parent }));
|
|
340
1589
|
break;
|
|
341
1590
|
case "Response":
|
|
342
|
-
await limit(() => visitor.response?.(node));
|
|
1591
|
+
await limit(() => visitor.response?.(node, { parent }));
|
|
343
1592
|
break;
|
|
1593
|
+
case "FunctionParameter":
|
|
1594
|
+
case "ParameterGroup":
|
|
1595
|
+
case "FunctionParameters": break;
|
|
344
1596
|
}
|
|
345
1597
|
const children = getChildren(node, recurse);
|
|
346
|
-
|
|
1598
|
+
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
347
1599
|
}
|
|
348
|
-
function transform(node,
|
|
349
|
-
const
|
|
1600
|
+
function transform(node, options) {
|
|
1601
|
+
const { depth, parent, ...visitor } = options;
|
|
1602
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
350
1603
|
switch (node.kind) {
|
|
351
|
-
case "
|
|
352
|
-
let
|
|
353
|
-
const replaced = visitor.
|
|
354
|
-
if (replaced)
|
|
1604
|
+
case "Input": {
|
|
1605
|
+
let input = node;
|
|
1606
|
+
const replaced = visitor.input?.(input, { parent });
|
|
1607
|
+
if (replaced) input = replaced;
|
|
355
1608
|
return {
|
|
356
|
-
...
|
|
357
|
-
schemas:
|
|
358
|
-
|
|
1609
|
+
...input,
|
|
1610
|
+
schemas: input.schemas.map((s) => transform(s, {
|
|
1611
|
+
...options,
|
|
1612
|
+
parent: input
|
|
1613
|
+
})),
|
|
1614
|
+
operations: input.operations.map((op) => transform(op, {
|
|
1615
|
+
...options,
|
|
1616
|
+
parent: input
|
|
1617
|
+
}))
|
|
359
1618
|
};
|
|
360
1619
|
}
|
|
1620
|
+
case "Output": {
|
|
1621
|
+
let output = node;
|
|
1622
|
+
const replaced = visitor.output?.(output, { parent });
|
|
1623
|
+
if (replaced) output = replaced;
|
|
1624
|
+
return output;
|
|
1625
|
+
}
|
|
361
1626
|
case "Operation": {
|
|
362
1627
|
let op = node;
|
|
363
|
-
const replaced = visitor.operation?.(op);
|
|
1628
|
+
const replaced = visitor.operation?.(op, { parent });
|
|
364
1629
|
if (replaced) op = replaced;
|
|
365
1630
|
return {
|
|
366
1631
|
...op,
|
|
367
|
-
parameters: op.parameters.map((p) => transform(p,
|
|
368
|
-
|
|
369
|
-
|
|
1632
|
+
parameters: op.parameters.map((p) => transform(p, {
|
|
1633
|
+
...options,
|
|
1634
|
+
parent: op
|
|
1635
|
+
})),
|
|
1636
|
+
requestBody: op.requestBody ? {
|
|
1637
|
+
...op.requestBody,
|
|
1638
|
+
schema: op.requestBody.schema ? transform(op.requestBody.schema, {
|
|
1639
|
+
...options,
|
|
1640
|
+
parent: op
|
|
1641
|
+
}) : void 0
|
|
1642
|
+
} : void 0,
|
|
1643
|
+
responses: op.responses.map((r) => transform(r, {
|
|
1644
|
+
...options,
|
|
1645
|
+
parent: op
|
|
1646
|
+
}))
|
|
370
1647
|
};
|
|
371
1648
|
}
|
|
372
1649
|
case "Schema": {
|
|
373
1650
|
let schema = node;
|
|
374
|
-
const replaced = visitor.schema?.(schema);
|
|
1651
|
+
const replaced = visitor.schema?.(schema, { parent });
|
|
375
1652
|
if (replaced) schema = replaced;
|
|
1653
|
+
const childOptions = {
|
|
1654
|
+
...options,
|
|
1655
|
+
parent: schema
|
|
1656
|
+
};
|
|
376
1657
|
return {
|
|
377
1658
|
...schema,
|
|
378
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p,
|
|
379
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i,
|
|
380
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m,
|
|
1659
|
+
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
1660
|
+
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
1661
|
+
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
1662
|
+
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
381
1663
|
};
|
|
382
1664
|
}
|
|
383
1665
|
case "Property": {
|
|
384
1666
|
let prop = node;
|
|
385
|
-
const replaced = visitor.property?.(prop);
|
|
1667
|
+
const replaced = visitor.property?.(prop, { parent });
|
|
386
1668
|
if (replaced) prop = replaced;
|
|
387
|
-
return {
|
|
1669
|
+
return createProperty({
|
|
388
1670
|
...prop,
|
|
389
|
-
schema: transform(prop.schema,
|
|
390
|
-
|
|
1671
|
+
schema: transform(prop.schema, {
|
|
1672
|
+
...options,
|
|
1673
|
+
parent: prop
|
|
1674
|
+
})
|
|
1675
|
+
});
|
|
391
1676
|
}
|
|
392
1677
|
case "Parameter": {
|
|
393
1678
|
let param = node;
|
|
394
|
-
const replaced = visitor.parameter?.(param);
|
|
1679
|
+
const replaced = visitor.parameter?.(param, { parent });
|
|
395
1680
|
if (replaced) param = replaced;
|
|
396
|
-
return {
|
|
1681
|
+
return createParameter({
|
|
397
1682
|
...param,
|
|
398
|
-
schema: transform(param.schema,
|
|
399
|
-
|
|
1683
|
+
schema: transform(param.schema, {
|
|
1684
|
+
...options,
|
|
1685
|
+
parent: param
|
|
1686
|
+
})
|
|
1687
|
+
});
|
|
400
1688
|
}
|
|
401
1689
|
case "Response": {
|
|
402
1690
|
let response = node;
|
|
403
|
-
const replaced = visitor.response?.(response);
|
|
1691
|
+
const replaced = visitor.response?.(response, { parent });
|
|
404
1692
|
if (replaced) response = replaced;
|
|
405
1693
|
return {
|
|
406
1694
|
...response,
|
|
407
|
-
schema:
|
|
1695
|
+
schema: transform(response.schema, {
|
|
1696
|
+
...options,
|
|
1697
|
+
parent: response
|
|
1698
|
+
})
|
|
408
1699
|
};
|
|
409
1700
|
}
|
|
1701
|
+
case "FunctionParameter":
|
|
1702
|
+
case "ParameterGroup":
|
|
1703
|
+
case "FunctionParameters":
|
|
1704
|
+
case "Type": return node;
|
|
1705
|
+
default: return node;
|
|
410
1706
|
}
|
|
411
1707
|
}
|
|
412
1708
|
/**
|
|
413
|
-
*
|
|
1709
|
+
* Runs a depth-first synchronous collection pass.
|
|
1710
|
+
*
|
|
1711
|
+
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
1712
|
+
*
|
|
1713
|
+
* @example
|
|
1714
|
+
* ```ts
|
|
1715
|
+
* const ids = collect(root, {
|
|
1716
|
+
* operation(node) {
|
|
1717
|
+
* return node.operationId
|
|
1718
|
+
* },
|
|
1719
|
+
* })
|
|
1720
|
+
* ```
|
|
1721
|
+
*
|
|
1722
|
+
* @example
|
|
1723
|
+
* ```ts
|
|
1724
|
+
* // Collect from only the current node
|
|
1725
|
+
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
1726
|
+
* ```
|
|
414
1727
|
*/
|
|
415
|
-
function collect(node,
|
|
416
|
-
const
|
|
1728
|
+
function collect(node, options) {
|
|
1729
|
+
const { depth, parent, ...visitor } = options;
|
|
1730
|
+
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
417
1731
|
const results = [];
|
|
418
1732
|
let v;
|
|
419
1733
|
switch (node.kind) {
|
|
420
|
-
case "
|
|
421
|
-
v = visitor.
|
|
1734
|
+
case "Input":
|
|
1735
|
+
v = visitor.input?.(node, { parent });
|
|
1736
|
+
break;
|
|
1737
|
+
case "Output":
|
|
1738
|
+
v = visitor.output?.(node, { parent });
|
|
422
1739
|
break;
|
|
423
1740
|
case "Operation":
|
|
424
|
-
v = visitor.operation?.(node);
|
|
1741
|
+
v = visitor.operation?.(node, { parent });
|
|
425
1742
|
break;
|
|
426
1743
|
case "Schema":
|
|
427
|
-
v = visitor.schema?.(node);
|
|
1744
|
+
v = visitor.schema?.(node, { parent });
|
|
428
1745
|
break;
|
|
429
1746
|
case "Property":
|
|
430
|
-
v = visitor.property?.(node);
|
|
1747
|
+
v = visitor.property?.(node, { parent });
|
|
431
1748
|
break;
|
|
432
1749
|
case "Parameter":
|
|
433
|
-
v = visitor.parameter?.(node);
|
|
1750
|
+
v = visitor.parameter?.(node, { parent });
|
|
434
1751
|
break;
|
|
435
1752
|
case "Response":
|
|
436
|
-
v = visitor.response?.(node);
|
|
1753
|
+
v = visitor.response?.(node, { parent });
|
|
437
1754
|
break;
|
|
1755
|
+
case "FunctionParameter":
|
|
1756
|
+
case "ParameterGroup":
|
|
1757
|
+
case "FunctionParameters": break;
|
|
438
1758
|
}
|
|
439
1759
|
if (v !== void 0) results.push(v);
|
|
440
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child,
|
|
1760
|
+
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
1761
|
+
...options,
|
|
1762
|
+
parent: node
|
|
1763
|
+
})) results.push(item);
|
|
441
1764
|
return results;
|
|
442
1765
|
}
|
|
443
1766
|
//#endregion
|
|
444
|
-
|
|
1767
|
+
//#region src/resolvers.ts
|
|
1768
|
+
function findDiscriminator(mapping, ref) {
|
|
1769
|
+
if (!mapping || !ref) return null;
|
|
1770
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
1771
|
+
}
|
|
1772
|
+
function childName(parentName, propName) {
|
|
1773
|
+
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
1774
|
+
}
|
|
1775
|
+
function enumPropName(parentName, propName, enumSuffix) {
|
|
1776
|
+
return pascalCase([
|
|
1777
|
+
parentName,
|
|
1778
|
+
propName,
|
|
1779
|
+
enumSuffix
|
|
1780
|
+
].filter(Boolean).join(" "));
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Collects import entries for all `ref` schema nodes in `node`.
|
|
1784
|
+
*/
|
|
1785
|
+
function collectImports({ node, nameMapping, resolve }) {
|
|
1786
|
+
return collect(node, { schema(schemaNode) {
|
|
1787
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
1788
|
+
if (!schemaRef?.ref) return;
|
|
1789
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
1790
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
1791
|
+
if (!result) return;
|
|
1792
|
+
return result;
|
|
1793
|
+
} });
|
|
1794
|
+
}
|
|
1795
|
+
//#endregion
|
|
1796
|
+
//#region src/transformers.ts
|
|
1797
|
+
/**
|
|
1798
|
+
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
1799
|
+
*
|
|
1800
|
+
* If `node` is not an object schema, or if the property does not exist, the input
|
|
1801
|
+
* node is returned as-is.
|
|
1802
|
+
*
|
|
1803
|
+
* @example
|
|
1804
|
+
* ```ts
|
|
1805
|
+
* const schema = createSchema({
|
|
1806
|
+
* type: 'object',
|
|
1807
|
+
* properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
|
|
1808
|
+
* })
|
|
1809
|
+
* const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
|
|
1810
|
+
* ```
|
|
1811
|
+
*/
|
|
1812
|
+
function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
1813
|
+
const objectNode = narrowSchema(node, "object");
|
|
1814
|
+
if (!objectNode?.properties?.length) return node;
|
|
1815
|
+
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
|
|
1816
|
+
return createSchema({
|
|
1817
|
+
...objectNode,
|
|
1818
|
+
properties: objectNode.properties.map((prop) => {
|
|
1819
|
+
if (prop.name !== propertyName) return prop;
|
|
1820
|
+
return createProperty({
|
|
1821
|
+
...prop,
|
|
1822
|
+
schema: createSchema({
|
|
1823
|
+
type: "enum",
|
|
1824
|
+
primitive: "string",
|
|
1825
|
+
enumValues: values,
|
|
1826
|
+
name: enumName,
|
|
1827
|
+
readOnly: prop.schema.readOnly,
|
|
1828
|
+
writeOnly: prop.schema.writeOnly
|
|
1829
|
+
})
|
|
1830
|
+
});
|
|
1831
|
+
})
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Merges adjacent anonymous object members into a single anonymous object member.
|
|
1836
|
+
*
|
|
1837
|
+
* @example
|
|
1838
|
+
* ```ts
|
|
1839
|
+
* const merged = mergeAdjacentObjects([
|
|
1840
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
|
|
1841
|
+
* createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
|
|
1842
|
+
* ])
|
|
1843
|
+
* ```
|
|
1844
|
+
*/
|
|
1845
|
+
function mergeAdjacentObjects(members) {
|
|
1846
|
+
return members.reduce((acc, member) => {
|
|
1847
|
+
const objectMember = narrowSchema(member, "object");
|
|
1848
|
+
if (objectMember && !objectMember.name) {
|
|
1849
|
+
const previous = acc.at(-1);
|
|
1850
|
+
const previousObject = previous ? narrowSchema(previous, "object") : void 0;
|
|
1851
|
+
if (previousObject && !previousObject.name) {
|
|
1852
|
+
acc[acc.length - 1] = createSchema({
|
|
1853
|
+
...previousObject,
|
|
1854
|
+
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1855
|
+
});
|
|
1856
|
+
return acc;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
acc.push(member);
|
|
1860
|
+
return acc;
|
|
1861
|
+
}, []);
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
1865
|
+
*
|
|
1866
|
+
* @example
|
|
1867
|
+
* ```ts
|
|
1868
|
+
* const simplified = simplifyUnion([
|
|
1869
|
+
* createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
|
|
1870
|
+
* createSchema({ type: 'string' }),
|
|
1871
|
+
* ])
|
|
1872
|
+
* // keeps only string member
|
|
1873
|
+
* ```
|
|
1874
|
+
*/
|
|
1875
|
+
function simplifyUnion(members) {
|
|
1876
|
+
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
1877
|
+
if (!scalarPrimitives.size) return members;
|
|
1878
|
+
return members.filter((member) => {
|
|
1879
|
+
const enumNode = narrowSchema(member, "enum");
|
|
1880
|
+
if (!enumNode) return true;
|
|
1881
|
+
const primitive = enumNode.primitive;
|
|
1882
|
+
if (!primitive) return true;
|
|
1883
|
+
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1884
|
+
if (scalarPrimitives.has(primitive)) return false;
|
|
1885
|
+
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1886
|
+
return true;
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
1890
|
+
const enumNode = narrowSchema(propNode, "enum");
|
|
1891
|
+
if (enumNode?.primitive === "boolean") return {
|
|
1892
|
+
...propNode,
|
|
1893
|
+
name: void 0
|
|
1894
|
+
};
|
|
1895
|
+
if (enumNode) return {
|
|
1896
|
+
...propNode,
|
|
1897
|
+
name: enumPropName(parentName, propName, enumSuffix)
|
|
1898
|
+
};
|
|
1899
|
+
return propNode;
|
|
1900
|
+
}
|
|
1901
|
+
//#endregion
|
|
1902
|
+
exports.caseParams = caseParams;
|
|
1903
|
+
exports.childName = childName;
|
|
445
1904
|
exports.collect = collect;
|
|
1905
|
+
exports.collectImports = collectImports;
|
|
1906
|
+
exports.createArrowFunction = createArrowFunction;
|
|
1907
|
+
exports.createBreak = createBreak;
|
|
1908
|
+
exports.createConst = createConst;
|
|
1909
|
+
exports.createDiscriminantNode = createDiscriminantNode;
|
|
1910
|
+
exports.createExport = createExport;
|
|
1911
|
+
exports.createFile = createFile;
|
|
1912
|
+
exports.createFunction = createFunction;
|
|
1913
|
+
exports.createFunctionParameter = createFunctionParameter;
|
|
1914
|
+
exports.createFunctionParameters = createFunctionParameters;
|
|
1915
|
+
exports.createImport = createImport;
|
|
1916
|
+
exports.createInput = createInput;
|
|
1917
|
+
exports.createJsx = createJsx;
|
|
446
1918
|
exports.createOperation = createOperation;
|
|
1919
|
+
exports.createOperationParams = createOperationParams;
|
|
1920
|
+
exports.createOutput = createOutput;
|
|
447
1921
|
exports.createParameter = createParameter;
|
|
1922
|
+
exports.createParameterGroup = createParameterGroup;
|
|
1923
|
+
exports.createParamsType = createParamsType;
|
|
1924
|
+
exports.createPrinterFactory = createPrinterFactory;
|
|
448
1925
|
exports.createProperty = createProperty;
|
|
449
1926
|
exports.createResponse = createResponse;
|
|
450
|
-
exports.createRoot = createRoot;
|
|
451
1927
|
exports.createSchema = createSchema;
|
|
1928
|
+
exports.createSource = createSource;
|
|
1929
|
+
exports.createText = createText;
|
|
1930
|
+
exports.createType = createType;
|
|
452
1931
|
exports.definePrinter = definePrinter;
|
|
1932
|
+
exports.enumPropName = enumPropName;
|
|
1933
|
+
exports.extractRefName = extractRefName;
|
|
1934
|
+
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
1935
|
+
exports.findDiscriminator = findDiscriminator;
|
|
453
1936
|
exports.httpMethods = httpMethods;
|
|
1937
|
+
exports.isInputNode = isInputNode;
|
|
454
1938
|
exports.isOperationNode = isOperationNode;
|
|
455
|
-
exports.
|
|
456
|
-
exports.
|
|
457
|
-
exports.isPropertyNode = isPropertyNode;
|
|
458
|
-
exports.isResponseNode = isResponseNode;
|
|
459
|
-
exports.isRootNode = isRootNode;
|
|
1939
|
+
exports.isOutputNode = isOutputNode;
|
|
1940
|
+
exports.isScalarPrimitive = isScalarPrimitive;
|
|
460
1941
|
exports.isSchemaNode = isSchemaNode;
|
|
1942
|
+
exports.isStringType = isStringType;
|
|
461
1943
|
exports.mediaTypes = mediaTypes;
|
|
1944
|
+
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
462
1945
|
exports.narrowSchema = narrowSchema;
|
|
463
1946
|
exports.nodeKinds = nodeKinds;
|
|
464
|
-
exports.refMapToObject = refMapToObject;
|
|
465
|
-
exports.resolveRef = resolveRef;
|
|
466
1947
|
exports.schemaTypes = schemaTypes;
|
|
1948
|
+
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
1949
|
+
exports.setEnumName = setEnumName;
|
|
1950
|
+
exports.simplifyUnion = simplifyUnion;
|
|
1951
|
+
exports.syncOptionality = syncOptionality;
|
|
1952
|
+
exports.syncSchemaRef = syncSchemaRef;
|
|
467
1953
|
exports.transform = transform;
|
|
468
1954
|
exports.walk = walk;
|
|
469
1955
|
|