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