@kubb/ast 5.0.0-beta.5 → 5.0.0-beta.50
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/LICENSE +17 -10
- package/README.md +28 -19
- package/dist/index.cjs +878 -788
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +33 -3326
- package/dist/index.js +863 -750
- package/dist/index.js.map +1 -1
- package/dist/types-BaaNZbSi.d.ts +3581 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/dist/utils-BIcKgbbc.js +626 -0
- package/dist/utils-BIcKgbbc.js.map +1 -0
- package/dist/utils-CMRZrT-w.cjs +794 -0
- package/dist/utils-CMRZrT-w.cjs.map +1 -0
- package/dist/utils.cjs +18 -0
- package/dist/utils.d.ts +205 -0
- package/dist/utils.js +2 -0
- package/package.json +13 -5
- package/src/constants.ts +10 -49
- package/src/dedupe.ts +200 -0
- package/src/dialect.ts +58 -0
- package/src/dispatch.ts +53 -0
- package/src/factory.ts +154 -21
- package/src/guards.ts +18 -48
- package/src/index.ts +11 -8
- package/src/infer.ts +16 -14
- package/src/nodes/base.ts +2 -0
- package/src/nodes/code.ts +22 -28
- package/src/nodes/content.ts +37 -0
- package/src/nodes/file.ts +17 -15
- package/src/nodes/function.ts +11 -11
- package/src/nodes/http.ts +1 -35
- package/src/nodes/index.ts +10 -12
- package/src/nodes/operation.ts +98 -62
- package/src/nodes/response.ts +21 -14
- package/src/nodes/root.ts +72 -10
- package/src/nodes/schema.ts +18 -15
- package/src/printer.ts +44 -38
- package/src/resolvers.ts +5 -19
- package/src/signature.ts +232 -0
- package/src/transformers.ts +21 -16
- package/src/types.ts +8 -18
- package/src/{utils.ts → utils/ast.ts} +125 -84
- package/src/utils/index.ts +295 -0
- package/src/visitor.ts +239 -281
- package/src/refs.ts +0 -67
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1,411 +1,46 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
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));
|
|
23
|
-
//#endregion
|
|
2
|
+
const require_utils = require("./utils-CMRZrT-w.cjs");
|
|
24
3
|
let node_crypto = require("node:crypto");
|
|
25
4
|
let node_path = require("node:path");
|
|
26
|
-
node_path = __toESM(node_path, 1);
|
|
27
|
-
//#region src/
|
|
28
|
-
const visitorDepths = {
|
|
29
|
-
shallow: "shallow",
|
|
30
|
-
deep: "deep"
|
|
31
|
-
};
|
|
32
|
-
const nodeKinds = {
|
|
33
|
-
input: "Input",
|
|
34
|
-
output: "Output",
|
|
35
|
-
operation: "Operation",
|
|
36
|
-
schema: "Schema",
|
|
37
|
-
property: "Property",
|
|
38
|
-
parameter: "Parameter",
|
|
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"
|
|
50
|
-
};
|
|
5
|
+
node_path = require_utils.__toESM(node_path, 1);
|
|
6
|
+
//#region ../../internals/utils/src/promise.ts
|
|
51
7
|
/**
|
|
52
|
-
*
|
|
8
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
53
9
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
const schemaTypes = {
|
|
59
|
-
/**
|
|
60
|
-
* Text value.
|
|
61
|
-
*/
|
|
62
|
-
string: "string",
|
|
63
|
-
/**
|
|
64
|
-
* Floating-point number (`float`, `double`).
|
|
65
|
-
*/
|
|
66
|
-
number: "number",
|
|
67
|
-
/**
|
|
68
|
-
* Whole number (`int32`). Use `bigint` for `int64`.
|
|
69
|
-
*/
|
|
70
|
-
integer: "integer",
|
|
71
|
-
/**
|
|
72
|
-
* 64-bit integer (`int64`). Only used when `integerType` is set to `'bigint'`.
|
|
73
|
-
*/
|
|
74
|
-
bigint: "bigint",
|
|
75
|
-
/**
|
|
76
|
-
* Boolean value
|
|
77
|
-
*/
|
|
78
|
-
boolean: "boolean",
|
|
79
|
-
/**
|
|
80
|
-
* Explicit null value.
|
|
81
|
-
*/
|
|
82
|
-
null: "null",
|
|
83
|
-
/**
|
|
84
|
-
* Any value (no type restriction).
|
|
85
|
-
*/
|
|
86
|
-
any: "any",
|
|
87
|
-
/**
|
|
88
|
-
* Unknown value (must be narrowed before usage).
|
|
89
|
-
*/
|
|
90
|
-
unknown: "unknown",
|
|
91
|
-
/**
|
|
92
|
-
* No return value (`void`).
|
|
93
|
-
*/
|
|
94
|
-
void: "void",
|
|
95
|
-
/**
|
|
96
|
-
* Object with named properties.
|
|
97
|
-
*/
|
|
98
|
-
object: "object",
|
|
99
|
-
/**
|
|
100
|
-
* Sequential list of items.
|
|
101
|
-
*/
|
|
102
|
-
array: "array",
|
|
103
|
-
/**
|
|
104
|
-
* Fixed-length list with position-specific items.
|
|
105
|
-
*/
|
|
106
|
-
tuple: "tuple",
|
|
107
|
-
/**
|
|
108
|
-
* "One of" multiple schema members.
|
|
109
|
-
*/
|
|
110
|
-
union: "union",
|
|
111
|
-
/**
|
|
112
|
-
* "All of" multiple schema members.
|
|
113
|
-
*/
|
|
114
|
-
intersection: "intersection",
|
|
115
|
-
/**
|
|
116
|
-
* Enum schema.
|
|
117
|
-
*/
|
|
118
|
-
enum: "enum",
|
|
119
|
-
/**
|
|
120
|
-
* Reference to another schema.
|
|
121
|
-
*/
|
|
122
|
-
ref: "ref",
|
|
123
|
-
/**
|
|
124
|
-
* Calendar date (for example `2026-03-24`).
|
|
125
|
-
*/
|
|
126
|
-
date: "date",
|
|
127
|
-
/**
|
|
128
|
-
* Date-time value (for example `2026-03-24T09:00:00Z`).
|
|
129
|
-
*/
|
|
130
|
-
datetime: "datetime",
|
|
131
|
-
/**
|
|
132
|
-
* Time-only value (for example `09:00:00`).
|
|
133
|
-
*/
|
|
134
|
-
time: "time",
|
|
135
|
-
/**
|
|
136
|
-
* UUID value.
|
|
137
|
-
*/
|
|
138
|
-
uuid: "uuid",
|
|
139
|
-
/**
|
|
140
|
-
* Email address value.
|
|
141
|
-
*/
|
|
142
|
-
email: "email",
|
|
143
|
-
/**
|
|
144
|
-
* URL value.
|
|
145
|
-
*/
|
|
146
|
-
url: "url",
|
|
147
|
-
/**
|
|
148
|
-
* IPv4 address value.
|
|
149
|
-
*/
|
|
150
|
-
ipv4: "ipv4",
|
|
151
|
-
/**
|
|
152
|
-
* IPv6 address value.
|
|
153
|
-
*/
|
|
154
|
-
ipv6: "ipv6",
|
|
155
|
-
/**
|
|
156
|
-
* Binary/blob value.
|
|
157
|
-
*/
|
|
158
|
-
blob: "blob",
|
|
159
|
-
/**
|
|
160
|
-
* Impossible value (`never`).
|
|
161
|
-
*/
|
|
162
|
-
never: "never"
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Scalar primitive schema types used for union simplification and type narrowing.
|
|
166
|
-
*
|
|
167
|
-
* Use `isScalarPrimitive()` to safely check whether a type is a scalar primitive.
|
|
168
|
-
*/
|
|
169
|
-
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
170
|
-
"string",
|
|
171
|
-
"number",
|
|
172
|
-
"integer",
|
|
173
|
-
"bigint",
|
|
174
|
-
"boolean"
|
|
175
|
-
]);
|
|
176
|
-
/**
|
|
177
|
-
* Type guard that returns `true` when `type` is a scalar primitive schema type.
|
|
178
|
-
*
|
|
179
|
-
* Use this to check if a schema type can be directly assigned without wrapping (e.g., `string | number | boolean`).
|
|
180
|
-
*/
|
|
181
|
-
function isScalarPrimitive(type) {
|
|
182
|
-
return SCALAR_PRIMITIVE_TYPES.has(type);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* HTTP method identifiers used by operation nodes.
|
|
186
|
-
*
|
|
187
|
-
* Includes all standard HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE).
|
|
188
|
-
*/
|
|
189
|
-
const httpMethods = {
|
|
190
|
-
get: "GET",
|
|
191
|
-
post: "POST",
|
|
192
|
-
put: "PUT",
|
|
193
|
-
patch: "PATCH",
|
|
194
|
-
delete: "DELETE",
|
|
195
|
-
head: "HEAD",
|
|
196
|
-
options: "OPTIONS",
|
|
197
|
-
trace: "TRACE"
|
|
198
|
-
};
|
|
199
|
-
/**
|
|
200
|
-
* Common MIME types used in request/response content negotiation.
|
|
201
|
-
*
|
|
202
|
-
* Covers JSON, XML, form data, PDFs, images, audio, and video formats.
|
|
203
|
-
* Use these as keys when serializing request/response bodies.
|
|
204
|
-
*/
|
|
205
|
-
const mediaTypes = {
|
|
206
|
-
applicationJson: "application/json",
|
|
207
|
-
applicationXml: "application/xml",
|
|
208
|
-
applicationFormUrlEncoded: "application/x-www-form-urlencoded",
|
|
209
|
-
applicationOctetStream: "application/octet-stream",
|
|
210
|
-
applicationPdf: "application/pdf",
|
|
211
|
-
applicationZip: "application/zip",
|
|
212
|
-
applicationGraphql: "application/graphql",
|
|
213
|
-
multipartFormData: "multipart/form-data",
|
|
214
|
-
textPlain: "text/plain",
|
|
215
|
-
textHtml: "text/html",
|
|
216
|
-
textCsv: "text/csv",
|
|
217
|
-
textXml: "text/xml",
|
|
218
|
-
imagePng: "image/png",
|
|
219
|
-
imageJpeg: "image/jpeg",
|
|
220
|
-
imageGif: "image/gif",
|
|
221
|
-
imageWebp: "image/webp",
|
|
222
|
-
imageSvgXml: "image/svg+xml",
|
|
223
|
-
audioMpeg: "audio/mpeg",
|
|
224
|
-
videoMp4: "video/mp4"
|
|
225
|
-
};
|
|
226
|
-
//#endregion
|
|
227
|
-
//#region ../../internals/utils/src/casing.ts
|
|
228
|
-
/**
|
|
229
|
-
* Shared implementation for camelCase and PascalCase conversion.
|
|
230
|
-
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
231
|
-
* and capitalizes each word according to `pascal`.
|
|
232
|
-
*
|
|
233
|
-
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
234
|
-
*/
|
|
235
|
-
function toCamelOrPascal(text, pascal) {
|
|
236
|
-
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) => {
|
|
237
|
-
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
238
|
-
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
|
|
239
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
240
|
-
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
244
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
245
|
-
* Segments are joined with `/` to form a file path.
|
|
10
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
11
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
12
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
13
|
+
* inner (created once per outer miss) keyed by the second.
|
|
246
14
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
15
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
16
|
+
* cleared independently of the memoized function.
|
|
249
17
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*/
|
|
256
|
-
function applyToFileParts(text, transformPart) {
|
|
257
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
258
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Converts `text` to camelCase.
|
|
262
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
263
|
-
*
|
|
264
|
-
* @example
|
|
265
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
266
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
267
|
-
*/
|
|
268
|
-
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
269
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
270
|
-
prefix,
|
|
271
|
-
suffix
|
|
272
|
-
} : {}));
|
|
273
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Converts `text` to PascalCase.
|
|
277
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
278
|
-
*
|
|
279
|
-
* @example
|
|
280
|
-
* pascalCase('hello-world') // 'HelloWorld'
|
|
281
|
-
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
|
|
282
|
-
*/
|
|
283
|
-
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
284
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
285
|
-
prefix,
|
|
286
|
-
suffix
|
|
287
|
-
}) : camelCase(part));
|
|
288
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
289
|
-
}
|
|
290
|
-
//#endregion
|
|
291
|
-
//#region ../../internals/utils/src/reserved.ts
|
|
292
|
-
/**
|
|
293
|
-
* JavaScript and Java reserved words.
|
|
294
|
-
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
295
|
-
*/
|
|
296
|
-
const reservedWords = new Set([
|
|
297
|
-
"abstract",
|
|
298
|
-
"arguments",
|
|
299
|
-
"boolean",
|
|
300
|
-
"break",
|
|
301
|
-
"byte",
|
|
302
|
-
"case",
|
|
303
|
-
"catch",
|
|
304
|
-
"char",
|
|
305
|
-
"class",
|
|
306
|
-
"const",
|
|
307
|
-
"continue",
|
|
308
|
-
"debugger",
|
|
309
|
-
"default",
|
|
310
|
-
"delete",
|
|
311
|
-
"do",
|
|
312
|
-
"double",
|
|
313
|
-
"else",
|
|
314
|
-
"enum",
|
|
315
|
-
"eval",
|
|
316
|
-
"export",
|
|
317
|
-
"extends",
|
|
318
|
-
"false",
|
|
319
|
-
"final",
|
|
320
|
-
"finally",
|
|
321
|
-
"float",
|
|
322
|
-
"for",
|
|
323
|
-
"function",
|
|
324
|
-
"goto",
|
|
325
|
-
"if",
|
|
326
|
-
"implements",
|
|
327
|
-
"import",
|
|
328
|
-
"in",
|
|
329
|
-
"instanceof",
|
|
330
|
-
"int",
|
|
331
|
-
"interface",
|
|
332
|
-
"let",
|
|
333
|
-
"long",
|
|
334
|
-
"native",
|
|
335
|
-
"new",
|
|
336
|
-
"null",
|
|
337
|
-
"package",
|
|
338
|
-
"private",
|
|
339
|
-
"protected",
|
|
340
|
-
"public",
|
|
341
|
-
"return",
|
|
342
|
-
"short",
|
|
343
|
-
"static",
|
|
344
|
-
"super",
|
|
345
|
-
"switch",
|
|
346
|
-
"synchronized",
|
|
347
|
-
"this",
|
|
348
|
-
"throw",
|
|
349
|
-
"throws",
|
|
350
|
-
"transient",
|
|
351
|
-
"true",
|
|
352
|
-
"try",
|
|
353
|
-
"typeof",
|
|
354
|
-
"var",
|
|
355
|
-
"void",
|
|
356
|
-
"volatile",
|
|
357
|
-
"while",
|
|
358
|
-
"with",
|
|
359
|
-
"yield",
|
|
360
|
-
"Array",
|
|
361
|
-
"Date",
|
|
362
|
-
"hasOwnProperty",
|
|
363
|
-
"Infinity",
|
|
364
|
-
"isFinite",
|
|
365
|
-
"isNaN",
|
|
366
|
-
"isPrototypeOf",
|
|
367
|
-
"length",
|
|
368
|
-
"Math",
|
|
369
|
-
"name",
|
|
370
|
-
"NaN",
|
|
371
|
-
"Number",
|
|
372
|
-
"Object",
|
|
373
|
-
"prototype",
|
|
374
|
-
"String",
|
|
375
|
-
"toString",
|
|
376
|
-
"undefined",
|
|
377
|
-
"valueOf"
|
|
378
|
-
]);
|
|
379
|
-
/**
|
|
380
|
-
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
18
|
+
* @example Single WeakMap key
|
|
19
|
+
* ```ts
|
|
20
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
21
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
22
|
+
* ```
|
|
381
23
|
*
|
|
382
|
-
* @example
|
|
24
|
+
* @example Single Map key (primitive)
|
|
383
25
|
* ```ts
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
26
|
+
* const cache = new Map<string, Resolver>()
|
|
27
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
387
28
|
* ```
|
|
388
|
-
*/
|
|
389
|
-
function isValidVarName(name) {
|
|
390
|
-
if (!name || reservedWords.has(name)) return false;
|
|
391
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
392
|
-
}
|
|
393
|
-
//#endregion
|
|
394
|
-
//#region ../../internals/utils/src/string.ts
|
|
395
|
-
/**
|
|
396
|
-
* Strips the file extension from a path or file name.
|
|
397
|
-
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
398
29
|
*
|
|
399
|
-
* @example
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
30
|
+
* @example Two-level (object + primitive)
|
|
31
|
+
* ```ts
|
|
32
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
33
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
34
|
+
* fn(params)('camelcase')
|
|
35
|
+
* ```
|
|
404
36
|
*/
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
37
|
+
function memoize(store, factory) {
|
|
38
|
+
return (key) => {
|
|
39
|
+
if (store.has(key)) return store.get(key);
|
|
40
|
+
const value = factory(key);
|
|
41
|
+
store.set(key, value);
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
409
44
|
}
|
|
410
45
|
//#endregion
|
|
411
46
|
//#region src/guards.ts
|
|
@@ -415,11 +50,11 @@ function trimExtName(text) {
|
|
|
415
50
|
* @example
|
|
416
51
|
* ```ts
|
|
417
52
|
* const schema = createSchema({ type: 'string' })
|
|
418
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode |
|
|
53
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
419
54
|
* ```
|
|
420
55
|
*/
|
|
421
56
|
function narrowSchema(node, type) {
|
|
422
|
-
return node?.type === type ? node :
|
|
57
|
+
return node?.type === type ? node : null;
|
|
423
58
|
}
|
|
424
59
|
function isKind(kind) {
|
|
425
60
|
return (node) => node.kind === kind;
|
|
@@ -458,37 +93,29 @@ const isOutputNode = isKind("Output");
|
|
|
458
93
|
*/
|
|
459
94
|
const isOperationNode = isKind("Operation");
|
|
460
95
|
/**
|
|
461
|
-
*
|
|
96
|
+
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
462
97
|
*
|
|
463
98
|
* @example
|
|
464
99
|
* ```ts
|
|
465
|
-
* if (
|
|
466
|
-
* console.log(node.
|
|
100
|
+
* if (isHttpOperationNode(node)) {
|
|
101
|
+
* console.log(node.method, node.path)
|
|
467
102
|
* }
|
|
468
103
|
* ```
|
|
469
104
|
*/
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
isKind("Response");
|
|
474
|
-
isKind("FunctionParameter");
|
|
475
|
-
isKind("ParameterGroup");
|
|
476
|
-
isKind("FunctionParameters");
|
|
477
|
-
//#endregion
|
|
478
|
-
//#region src/refs.ts
|
|
105
|
+
function isHttpOperationNode(node) {
|
|
106
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
107
|
+
}
|
|
479
108
|
/**
|
|
480
|
-
* Returns
|
|
481
|
-
*
|
|
482
|
-
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
109
|
+
* Returns `true` when the input is a `SchemaNode`.
|
|
483
110
|
*
|
|
484
111
|
* @example
|
|
485
112
|
* ```ts
|
|
486
|
-
*
|
|
113
|
+
* if (isSchemaNode(node)) {
|
|
114
|
+
* console.log(node.type)
|
|
115
|
+
* }
|
|
487
116
|
* ```
|
|
488
117
|
*/
|
|
489
|
-
|
|
490
|
-
return ref.split("/").at(-1) ?? ref;
|
|
491
|
-
}
|
|
118
|
+
const isSchemaNode = isKind("Schema");
|
|
492
119
|
//#endregion
|
|
493
120
|
//#region src/visitor.ts
|
|
494
121
|
/**
|
|
@@ -526,53 +153,92 @@ function createLimit(concurrency) {
|
|
|
526
153
|
});
|
|
527
154
|
};
|
|
528
155
|
}
|
|
156
|
+
const visitorKeysByKind = {
|
|
157
|
+
Input: ["schemas", "operations"],
|
|
158
|
+
Operation: [
|
|
159
|
+
"parameters",
|
|
160
|
+
"requestBody",
|
|
161
|
+
"responses"
|
|
162
|
+
],
|
|
163
|
+
RequestBody: ["content"],
|
|
164
|
+
Content: ["schema"],
|
|
165
|
+
Response: ["content"],
|
|
166
|
+
Schema: [
|
|
167
|
+
"properties",
|
|
168
|
+
"items",
|
|
169
|
+
"members",
|
|
170
|
+
"additionalProperties"
|
|
171
|
+
],
|
|
172
|
+
Property: ["schema"],
|
|
173
|
+
Parameter: ["schema"]
|
|
174
|
+
};
|
|
529
175
|
/**
|
|
530
|
-
* Returns
|
|
176
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
177
|
+
*/
|
|
178
|
+
function isNode(value) {
|
|
179
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
531
183
|
*
|
|
532
|
-
*
|
|
533
|
-
* `additionalProperties`) are only included
|
|
534
|
-
* when `recurse` is `true`; shallow mode skips them.
|
|
184
|
+
* `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
|
|
535
185
|
*
|
|
536
186
|
* @example
|
|
537
187
|
* ```ts
|
|
538
188
|
* const children = getChildren(operationNode, true)
|
|
539
|
-
* // returns parameters,
|
|
540
|
-
* ```
|
|
541
|
-
*/
|
|
542
|
-
function getChildren(node, recurse) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const children = [];
|
|
553
|
-
if (!recurse) return [];
|
|
554
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
555
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
556
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
557
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
558
|
-
return children;
|
|
559
|
-
}
|
|
560
|
-
case "Property": return [node.schema];
|
|
561
|
-
case "Parameter": return [node.schema];
|
|
562
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
563
|
-
case "FunctionParameter":
|
|
564
|
-
case "ParameterGroup":
|
|
565
|
-
case "FunctionParameters":
|
|
566
|
-
case "Type": return [];
|
|
567
|
-
default: return [];
|
|
189
|
+
* // returns parameters, the request body, and responses
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
function* getChildren(node, recurse) {
|
|
193
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
194
|
+
const keys = visitorKeysByKind[node.kind];
|
|
195
|
+
if (!keys) return;
|
|
196
|
+
const record = node;
|
|
197
|
+
for (const key of keys) {
|
|
198
|
+
const value = record[key];
|
|
199
|
+
if (Array.isArray(value)) {
|
|
200
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
201
|
+
} else if (isNode(value)) yield value;
|
|
568
202
|
}
|
|
569
203
|
}
|
|
570
204
|
/**
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
205
|
+
* Maps a node `kind` to the matching visitor callback name. Only the seven
|
|
206
|
+
* traversable node kinds have an entry. Every other kind resolves to
|
|
207
|
+
* `undefined` and is skipped.
|
|
208
|
+
*/
|
|
209
|
+
const VISITOR_KEY_BY_KIND = {
|
|
210
|
+
Input: "input",
|
|
211
|
+
Output: "output",
|
|
212
|
+
Operation: "operation",
|
|
213
|
+
Schema: "schema",
|
|
214
|
+
Property: "property",
|
|
215
|
+
Parameter: "parameter",
|
|
216
|
+
Response: "response"
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
220
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
221
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
222
|
+
*
|
|
223
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
224
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
225
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
226
|
+
*/
|
|
227
|
+
function applyVisitor(node, visitor, parent) {
|
|
228
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
229
|
+
if (!key) return void 0;
|
|
230
|
+
const fn = visitor[key];
|
|
231
|
+
return fn?.(node, { parent });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
235
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
574
236
|
*
|
|
575
|
-
*
|
|
237
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
238
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
239
|
+
* work. Lower values reduce memory pressure.
|
|
240
|
+
*
|
|
241
|
+
* @example Log every operation
|
|
576
242
|
* ```ts
|
|
577
243
|
* await walk(root, {
|
|
578
244
|
* operation(node) {
|
|
@@ -581,216 +247,118 @@ function getChildren(node, recurse) {
|
|
|
581
247
|
* })
|
|
582
248
|
* ```
|
|
583
249
|
*
|
|
584
|
-
* @example
|
|
250
|
+
* @example Only visit the root node
|
|
585
251
|
* ```ts
|
|
586
|
-
*
|
|
587
|
-
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
252
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
588
253
|
* ```
|
|
589
254
|
*/
|
|
590
255
|
async function walk(node, options) {
|
|
591
|
-
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
256
|
+
return _walk(node, options, (options.depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
592
257
|
}
|
|
593
258
|
async function _walk(node, visitor, recurse, limit, parent) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
case "Output":
|
|
599
|
-
await limit(() => visitor.output?.(node, { parent }));
|
|
600
|
-
break;
|
|
601
|
-
case "Operation":
|
|
602
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
603
|
-
break;
|
|
604
|
-
case "Schema":
|
|
605
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
606
|
-
break;
|
|
607
|
-
case "Property":
|
|
608
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
609
|
-
break;
|
|
610
|
-
case "Parameter":
|
|
611
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
612
|
-
break;
|
|
613
|
-
case "Response":
|
|
614
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
615
|
-
break;
|
|
616
|
-
case "FunctionParameter":
|
|
617
|
-
case "ParameterGroup":
|
|
618
|
-
case "FunctionParameters": break;
|
|
619
|
-
}
|
|
620
|
-
const children = getChildren(node, recurse);
|
|
621
|
-
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
259
|
+
await limit(() => applyVisitor(node, visitor, parent));
|
|
260
|
+
const children = Array.from(getChildren(node, recurse));
|
|
261
|
+
if (children.length === 0) return;
|
|
262
|
+
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
|
|
622
263
|
}
|
|
623
264
|
function transform(node, options) {
|
|
624
265
|
const { depth, parent, ...visitor } = options;
|
|
625
|
-
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
parent: op
|
|
666
|
-
}) : void 0
|
|
667
|
-
}))
|
|
668
|
-
} : void 0,
|
|
669
|
-
responses: op.responses.map((r) => transform(r, {
|
|
670
|
-
...options,
|
|
671
|
-
parent: op
|
|
672
|
-
}))
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
case "Schema": {
|
|
676
|
-
let schema = node;
|
|
677
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
678
|
-
if (replaced) schema = replaced;
|
|
679
|
-
const childOptions = {
|
|
680
|
-
...options,
|
|
681
|
-
parent: schema
|
|
682
|
-
};
|
|
683
|
-
return {
|
|
684
|
-
...schema,
|
|
685
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
686
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
687
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
688
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
case "Property": {
|
|
692
|
-
let prop = node;
|
|
693
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
694
|
-
if (replaced) prop = replaced;
|
|
695
|
-
return createProperty({
|
|
696
|
-
...prop,
|
|
697
|
-
schema: transform(prop.schema, {
|
|
698
|
-
...options,
|
|
699
|
-
parent: prop
|
|
700
|
-
})
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
case "Parameter": {
|
|
704
|
-
let param = node;
|
|
705
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
706
|
-
if (replaced) param = replaced;
|
|
707
|
-
return createParameter({
|
|
708
|
-
...param,
|
|
709
|
-
schema: transform(param.schema, {
|
|
710
|
-
...options,
|
|
711
|
-
parent: param
|
|
712
|
-
})
|
|
266
|
+
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
267
|
+
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
268
|
+
if (rebuilt === node) return node;
|
|
269
|
+
const finalize = nodeFinalizers[rebuilt.kind];
|
|
270
|
+
return finalize ? finalize(rebuilt) : rebuilt;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
|
|
274
|
+
* resync schema optionality against their `required` flag once the schema may
|
|
275
|
+
* have changed.
|
|
276
|
+
*/
|
|
277
|
+
const nodeFinalizers = {
|
|
278
|
+
Property: (node) => createProperty(node),
|
|
279
|
+
Parameter: (node) => createParameter(node)
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
283
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
284
|
+
* `Schema` children are skipped in shallow mode.
|
|
285
|
+
*/
|
|
286
|
+
function transformChildren(node, options, recurse) {
|
|
287
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
288
|
+
const keys = visitorKeysByKind[node.kind];
|
|
289
|
+
if (!keys) return node;
|
|
290
|
+
const record = node;
|
|
291
|
+
const childOptions = {
|
|
292
|
+
...options,
|
|
293
|
+
parent: node
|
|
294
|
+
};
|
|
295
|
+
let updates;
|
|
296
|
+
for (const key of keys) {
|
|
297
|
+
if (!(key in record)) continue;
|
|
298
|
+
const value = record[key];
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
let changed = false;
|
|
301
|
+
const mapped = value.map((item) => {
|
|
302
|
+
if (!isNode(item)) return item;
|
|
303
|
+
const next = transform(item, childOptions);
|
|
304
|
+
if (next !== item) changed = true;
|
|
305
|
+
return next;
|
|
713
306
|
});
|
|
307
|
+
if (changed) (updates ??= {})[key] = mapped;
|
|
308
|
+
} else if (isNode(value)) {
|
|
309
|
+
const next = transform(value, childOptions);
|
|
310
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
714
311
|
}
|
|
715
|
-
case "Response": {
|
|
716
|
-
let response = node;
|
|
717
|
-
const replaced = visitor.response?.(response, { parent });
|
|
718
|
-
if (replaced) response = replaced;
|
|
719
|
-
return {
|
|
720
|
-
...response,
|
|
721
|
-
schema: transform(response.schema, {
|
|
722
|
-
...options,
|
|
723
|
-
parent: response
|
|
724
|
-
})
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
case "FunctionParameter":
|
|
728
|
-
case "ParameterGroup":
|
|
729
|
-
case "FunctionParameters":
|
|
730
|
-
case "Type": return node;
|
|
731
|
-
default: return node;
|
|
732
312
|
}
|
|
313
|
+
return updates ? {
|
|
314
|
+
...node,
|
|
315
|
+
...updates
|
|
316
|
+
} : node;
|
|
733
317
|
}
|
|
734
318
|
/**
|
|
735
|
-
*
|
|
319
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
320
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
736
321
|
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
* @example
|
|
322
|
+
* @example Collect every operationId
|
|
740
323
|
* ```ts
|
|
741
|
-
* const ids =
|
|
324
|
+
* const ids: string[] = []
|
|
325
|
+
* for (const id of collectLazy<string>(root, {
|
|
742
326
|
* operation(node) {
|
|
743
327
|
* return node.operationId
|
|
744
328
|
* },
|
|
745
|
-
* })
|
|
329
|
+
* })) {
|
|
330
|
+
* ids.push(id)
|
|
331
|
+
* }
|
|
746
332
|
* ```
|
|
333
|
+
*/
|
|
334
|
+
function* collectLazy(node, options) {
|
|
335
|
+
const { depth, parent, ...visitor } = options;
|
|
336
|
+
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
337
|
+
const v = applyVisitor(node, visitor, parent);
|
|
338
|
+
if (v != null) yield v;
|
|
339
|
+
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
340
|
+
...options,
|
|
341
|
+
parent: node
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
346
|
+
* the visitor callbacks return.
|
|
747
347
|
*
|
|
748
|
-
* @example
|
|
348
|
+
* @example Collect every operationId
|
|
749
349
|
* ```ts
|
|
750
|
-
*
|
|
751
|
-
*
|
|
350
|
+
* const ids = collect<string>(root, {
|
|
351
|
+
* operation(node) {
|
|
352
|
+
* return node.operationId
|
|
353
|
+
* },
|
|
354
|
+
* })
|
|
752
355
|
* ```
|
|
753
356
|
*/
|
|
754
357
|
function collect(node, options) {
|
|
755
|
-
|
|
756
|
-
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
757
|
-
const results = [];
|
|
758
|
-
let v;
|
|
759
|
-
switch (node.kind) {
|
|
760
|
-
case "Input":
|
|
761
|
-
v = visitor.input?.(node, { parent });
|
|
762
|
-
break;
|
|
763
|
-
case "Output":
|
|
764
|
-
v = visitor.output?.(node, { parent });
|
|
765
|
-
break;
|
|
766
|
-
case "Operation":
|
|
767
|
-
v = visitor.operation?.(node, { parent });
|
|
768
|
-
break;
|
|
769
|
-
case "Schema":
|
|
770
|
-
v = visitor.schema?.(node, { parent });
|
|
771
|
-
break;
|
|
772
|
-
case "Property":
|
|
773
|
-
v = visitor.property?.(node, { parent });
|
|
774
|
-
break;
|
|
775
|
-
case "Parameter":
|
|
776
|
-
v = visitor.parameter?.(node, { parent });
|
|
777
|
-
break;
|
|
778
|
-
case "Response":
|
|
779
|
-
v = visitor.response?.(node, { parent });
|
|
780
|
-
break;
|
|
781
|
-
case "FunctionParameter":
|
|
782
|
-
case "ParameterGroup":
|
|
783
|
-
case "FunctionParameters": break;
|
|
784
|
-
}
|
|
785
|
-
if (v !== void 0) results.push(v);
|
|
786
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
787
|
-
...options,
|
|
788
|
-
parent: node
|
|
789
|
-
})) results.push(item);
|
|
790
|
-
return results;
|
|
358
|
+
return Array.from(collectLazy(node, options));
|
|
791
359
|
}
|
|
792
360
|
//#endregion
|
|
793
|
-
//#region src/utils.ts
|
|
361
|
+
//#region src/utils/ast.ts
|
|
794
362
|
const plainStringTypes = new Set([
|
|
795
363
|
"string",
|
|
796
364
|
"uuid",
|
|
@@ -841,15 +409,16 @@ function isStringType(node) {
|
|
|
841
409
|
* the desired casing while preserving `OperationNode.parameters` for other consumers.
|
|
842
410
|
* The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
|
|
843
411
|
*/
|
|
412
|
+
const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
|
|
413
|
+
const transformed = casing === "camelcase" || !require_utils.isValidVarName(param.name) ? require_utils.camelCase(param.name) : param.name;
|
|
414
|
+
return {
|
|
415
|
+
...param,
|
|
416
|
+
name: transformed
|
|
417
|
+
};
|
|
418
|
+
})));
|
|
844
419
|
function caseParams(params, casing) {
|
|
845
420
|
if (!casing) return params;
|
|
846
|
-
return params
|
|
847
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
848
|
-
return {
|
|
849
|
-
...param,
|
|
850
|
-
name: transformed
|
|
851
|
-
};
|
|
852
|
-
});
|
|
421
|
+
return caseParamsMemo(params)(casing);
|
|
853
422
|
}
|
|
854
423
|
/**
|
|
855
424
|
* Creates a single-property object schema used as a discriminator literal.
|
|
@@ -978,7 +547,7 @@ function createOperationParams(node, options) {
|
|
|
978
547
|
}));
|
|
979
548
|
} else {
|
|
980
549
|
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
981
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0])
|
|
550
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
982
551
|
params.push(createFunctionParameter({
|
|
983
552
|
name: pathName,
|
|
984
553
|
type: spreadType ? wrapType(spreadType) : void 0,
|
|
@@ -1054,13 +623,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
|
|
|
1054
623
|
}
|
|
1055
624
|
/**
|
|
1056
625
|
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1057
|
-
* Returns `
|
|
626
|
+
* Returns `null` when the group name equals the individual param name (no real group).
|
|
1058
627
|
*/
|
|
1059
628
|
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1060
|
-
if (!params.length) return;
|
|
629
|
+
if (!params.length) return null;
|
|
1061
630
|
const firstParam = params[0];
|
|
1062
631
|
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1063
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
632
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
1064
633
|
const allOptional = params.every((p) => !p.required);
|
|
1065
634
|
return {
|
|
1066
635
|
type: createParamsType({
|
|
@@ -1104,13 +673,13 @@ function importKey(path, name, isTypeOnly) {
|
|
|
1104
673
|
}
|
|
1105
674
|
/**
|
|
1106
675
|
* Computes a multi-level sort key for exports and imports:
|
|
1107
|
-
* non-array names first (wildcards/namespace aliases)
|
|
676
|
+
* non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
|
|
1108
677
|
*/
|
|
1109
678
|
function sortKey(node) {
|
|
1110
679
|
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
1111
680
|
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
1112
681
|
const hasName = node.name != null ? "1" : "0";
|
|
1113
|
-
const name = Array.isArray(node.name) ?
|
|
682
|
+
const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
|
|
1114
683
|
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
1115
684
|
}
|
|
1116
685
|
/**
|
|
@@ -1127,6 +696,16 @@ function combineSources(sources) {
|
|
|
1127
696
|
return [...seen.values()];
|
|
1128
697
|
}
|
|
1129
698
|
/**
|
|
699
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
700
|
+
*
|
|
701
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
702
|
+
*/
|
|
703
|
+
function mergeNameArrays(existing, incoming) {
|
|
704
|
+
const merged = new Set(existing);
|
|
705
|
+
for (const name of incoming) merged.add(name);
|
|
706
|
+
return [...merged];
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
1130
709
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1131
710
|
*
|
|
1132
711
|
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
@@ -1147,11 +726,8 @@ function combineExports(exports) {
|
|
|
1147
726
|
if (!name.length) continue;
|
|
1148
727
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1149
728
|
const existing = namedByPath.get(key);
|
|
1150
|
-
if (existing && Array.isArray(existing.name))
|
|
1151
|
-
|
|
1152
|
-
for (const n of name) merged.add(n);
|
|
1153
|
-
existing.name = [...merged];
|
|
1154
|
-
} else {
|
|
729
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
730
|
+
else {
|
|
1155
731
|
const newItem = {
|
|
1156
732
|
...curr,
|
|
1157
733
|
name: [...new Set(name)]
|
|
@@ -1187,6 +763,11 @@ function combineImports(imports, exports, source) {
|
|
|
1187
763
|
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
1188
764
|
return importNameMemo.get(key);
|
|
1189
765
|
};
|
|
766
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
767
|
+
for (const node of imports) {
|
|
768
|
+
if (!Array.isArray(node.name)) continue;
|
|
769
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
770
|
+
}
|
|
1190
771
|
const result = [];
|
|
1191
772
|
const namedByPath = /* @__PURE__ */ new Map();
|
|
1192
773
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1204,11 +785,8 @@ function combineImports(imports, exports, source) {
|
|
|
1204
785
|
if (!name.length) continue;
|
|
1205
786
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1206
787
|
const existing = namedByPath.get(key);
|
|
1207
|
-
if (existing && Array.isArray(existing.name))
|
|
1208
|
-
|
|
1209
|
-
for (const n of name) merged.add(n);
|
|
1210
|
-
existing.name = [...merged];
|
|
1211
|
-
} else {
|
|
788
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
789
|
+
else {
|
|
1212
790
|
const newItem = {
|
|
1213
791
|
...curr,
|
|
1214
792
|
name
|
|
@@ -1217,7 +795,7 @@ function combineImports(imports, exports, source) {
|
|
|
1217
795
|
namedByPath.set(key, newItem);
|
|
1218
796
|
}
|
|
1219
797
|
} else {
|
|
1220
|
-
if (name && !isUsed(name)) continue;
|
|
798
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1221
799
|
const key = importKey(path, name, isTypeOnly);
|
|
1222
800
|
if (!seen.has(key)) {
|
|
1223
801
|
result.push(curr);
|
|
@@ -1253,7 +831,7 @@ function extractStringsFromNodes(nodes) {
|
|
|
1253
831
|
/**
|
|
1254
832
|
* Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
|
|
1255
833
|
*
|
|
1256
|
-
* Returns `
|
|
834
|
+
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1257
835
|
* identifier for type definitions or error messages.
|
|
1258
836
|
*
|
|
1259
837
|
* @example
|
|
@@ -1263,14 +841,14 @@ function extractStringsFromNodes(nodes) {
|
|
|
1263
841
|
* ```
|
|
1264
842
|
*/
|
|
1265
843
|
function resolveRefName(node) {
|
|
1266
|
-
if (!node || node.type !== "ref") return
|
|
1267
|
-
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ??
|
|
1268
|
-
return node.name ?? node.schema?.name ??
|
|
844
|
+
if (!node || node.type !== "ref") return null;
|
|
845
|
+
if (node.ref) return require_utils.extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
846
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1269
847
|
}
|
|
1270
848
|
/**
|
|
1271
849
|
* Collects every named schema referenced (transitively) from a node via ref edges.
|
|
1272
850
|
*
|
|
1273
|
-
* Refs are followed by name only
|
|
851
|
+
* Refs are followed by name only, the resolved `node.schema` is not traversed inline.
|
|
1274
852
|
* Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
|
|
1275
853
|
*
|
|
1276
854
|
* @example Collect refs from a single schema
|
|
@@ -1287,21 +865,26 @@ function resolveRefName(node) {
|
|
|
1287
865
|
* }
|
|
1288
866
|
* ```
|
|
1289
867
|
*/
|
|
1290
|
-
|
|
1291
|
-
|
|
868
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
869
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1292
870
|
collect(node, { schema(child) {
|
|
1293
871
|
if (child.type === "ref") {
|
|
1294
872
|
const name = resolveRefName(child);
|
|
1295
|
-
if (name)
|
|
873
|
+
if (name) refs.add(name);
|
|
1296
874
|
}
|
|
1297
875
|
} });
|
|
876
|
+
return refs;
|
|
877
|
+
});
|
|
878
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
879
|
+
if (!node) return out;
|
|
880
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1298
881
|
return out;
|
|
1299
882
|
}
|
|
1300
883
|
/**
|
|
1301
884
|
* Collects the names of all top-level schemas transitively used by a set of operations.
|
|
1302
885
|
*
|
|
1303
886
|
* An operation uses a schema when any of its parameters, request body content, or responses
|
|
1304
|
-
* reference it
|
|
887
|
+
* reference it, directly or indirectly through other named schemas.
|
|
1305
888
|
* The walk is iterative and safe against reference cycles.
|
|
1306
889
|
*
|
|
1307
890
|
* Use this together with `include` filters to determine which schemas from `components/schemas`
|
|
@@ -1310,10 +893,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1310
893
|
*
|
|
1311
894
|
* @example Only generate schemas referenced by included operations
|
|
1312
895
|
* ```ts
|
|
1313
|
-
* const includedOps =
|
|
1314
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
896
|
+
* const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
897
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1315
898
|
*
|
|
1316
|
-
* for (const schema of
|
|
899
|
+
* for (const schema of schemas) {
|
|
1317
900
|
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1318
901
|
* // … generate schema
|
|
1319
902
|
* }
|
|
@@ -1321,11 +904,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1321
904
|
*
|
|
1322
905
|
* @example Check whether a specific schema is needed
|
|
1323
906
|
* ```ts
|
|
1324
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
907
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1325
908
|
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
1326
909
|
* ```
|
|
1327
910
|
*/
|
|
1328
|
-
|
|
911
|
+
const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
|
|
912
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1329
913
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
1330
914
|
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1331
915
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -1337,22 +921,17 @@ function collectUsedSchemaNames(operations, schemas) {
|
|
|
1337
921
|
if (namedSchema) visitSchema(namedSchema);
|
|
1338
922
|
}
|
|
1339
923
|
}
|
|
1340
|
-
for (const op of operations) for (const schema of
|
|
924
|
+
for (const op of operations) for (const schema of collectLazy(op, {
|
|
1341
925
|
depth: "shallow",
|
|
1342
926
|
schema: (node) => node
|
|
1343
927
|
})) visitSchema(schema);
|
|
1344
928
|
return result;
|
|
1345
929
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1352
|
-
*
|
|
1353
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1354
|
-
*/
|
|
1355
|
-
function findCircularSchemas(schemas) {
|
|
930
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
931
|
+
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
932
|
+
}
|
|
933
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
934
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1356
935
|
const graph = /* @__PURE__ */ new Map();
|
|
1357
936
|
for (const schema of schemas) {
|
|
1358
937
|
if (!schema.name) continue;
|
|
@@ -1375,6 +954,19 @@ function findCircularSchemas(schemas) {
|
|
|
1375
954
|
}
|
|
1376
955
|
}
|
|
1377
956
|
return circular;
|
|
957
|
+
});
|
|
958
|
+
/**
|
|
959
|
+
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
960
|
+
*
|
|
961
|
+
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
962
|
+
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
963
|
+
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
964
|
+
*
|
|
965
|
+
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
966
|
+
*/
|
|
967
|
+
function findCircularSchemas(schemas) {
|
|
968
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
969
|
+
return findCircularSchemasMemo(schemas);
|
|
1378
970
|
}
|
|
1379
971
|
/**
|
|
1380
972
|
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
@@ -1382,23 +974,27 @@ function findCircularSchemas(schemas) {
|
|
|
1382
974
|
* Use `excludeName` to ignore refs to specific schemas (useful when self-references are handled separately).
|
|
1383
975
|
* Commonly used with `findCircularSchemas()` to detect where lazy wrappers are needed in code generation.
|
|
1384
976
|
*
|
|
1385
|
-
* @note Returns `true` for the first matching circular ref found
|
|
977
|
+
* @note Returns `true` for the first matching circular ref found. Use for fast dependency checks.
|
|
1386
978
|
*/
|
|
1387
979
|
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1388
980
|
if (!node || circularSchemas.size === 0) return false;
|
|
1389
|
-
|
|
1390
|
-
if (child.type !== "ref") return
|
|
981
|
+
for (const _ of collectLazy(node, { schema(child) {
|
|
982
|
+
if (child.type !== "ref") return null;
|
|
1391
983
|
const name = resolveRefName(child);
|
|
1392
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true :
|
|
1393
|
-
} })
|
|
984
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
985
|
+
} })) return true;
|
|
986
|
+
return false;
|
|
1394
987
|
}
|
|
1395
988
|
//#endregion
|
|
1396
989
|
//#region src/factory.ts
|
|
1397
990
|
/**
|
|
1398
|
-
*
|
|
991
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
992
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
993
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
1399
994
|
*
|
|
1400
|
-
* -
|
|
1401
|
-
* -
|
|
995
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
996
|
+
* - Non-required + nullable → `nullish: true`.
|
|
997
|
+
* - Required → both flags cleared.
|
|
1402
998
|
*/
|
|
1403
999
|
function syncOptionality(schema, required) {
|
|
1404
1000
|
const nullable = schema.nullable ?? false;
|
|
@@ -1409,6 +1005,29 @@ function syncOptionality(schema, required) {
|
|
|
1409
1005
|
};
|
|
1410
1006
|
}
|
|
1411
1007
|
/**
|
|
1008
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1009
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1010
|
+
* with the changes applied.
|
|
1011
|
+
*
|
|
1012
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract, pair it with the
|
|
1013
|
+
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1014
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1015
|
+
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
1016
|
+
*
|
|
1017
|
+
* @example
|
|
1018
|
+
* ```ts
|
|
1019
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1020
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
1021
|
+
* ```
|
|
1022
|
+
*/
|
|
1023
|
+
function update(node, changes) {
|
|
1024
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1025
|
+
...node,
|
|
1026
|
+
...changes
|
|
1027
|
+
};
|
|
1028
|
+
return node;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1412
1031
|
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
1413
1032
|
*
|
|
1414
1033
|
* @example
|
|
@@ -1427,11 +1046,31 @@ function createInput(overrides = {}) {
|
|
|
1427
1046
|
return {
|
|
1428
1047
|
schemas: [],
|
|
1429
1048
|
operations: [],
|
|
1049
|
+
meta: {
|
|
1050
|
+
circularNames: [],
|
|
1051
|
+
enumNames: []
|
|
1052
|
+
},
|
|
1430
1053
|
...overrides,
|
|
1431
1054
|
kind: "Input"
|
|
1432
1055
|
};
|
|
1433
1056
|
}
|
|
1434
1057
|
/**
|
|
1058
|
+
* Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* ```ts
|
|
1062
|
+
* const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
function createStreamInput(schemas, operations, meta) {
|
|
1066
|
+
return {
|
|
1067
|
+
kind: "Input",
|
|
1068
|
+
schemas,
|
|
1069
|
+
operations,
|
|
1070
|
+
meta
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1435
1074
|
* Creates an `OutputNode` with a stable default for `files`.
|
|
1436
1075
|
*
|
|
1437
1076
|
* @example
|
|
@@ -1453,40 +1092,40 @@ function createOutput(overrides = {}) {
|
|
|
1453
1092
|
};
|
|
1454
1093
|
}
|
|
1455
1094
|
/**
|
|
1456
|
-
* Creates
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
*
|
|
1466
|
-
* ```
|
|
1467
|
-
*
|
|
1468
|
-
* @example
|
|
1469
|
-
* ```ts
|
|
1470
|
-
* const operation = createOperation({
|
|
1471
|
-
* operationId: 'findPets',
|
|
1472
|
-
* method: 'GET',
|
|
1473
|
-
* path: '/pet/findByStatus',
|
|
1474
|
-
* tags: ['pet'],
|
|
1475
|
-
* })
|
|
1476
|
-
* ```
|
|
1095
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
1096
|
+
*/
|
|
1097
|
+
function createContent(props) {
|
|
1098
|
+
return {
|
|
1099
|
+
...props,
|
|
1100
|
+
kind: "Content"
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
|
|
1477
1105
|
*/
|
|
1106
|
+
function createRequestBody(props) {
|
|
1107
|
+
return {
|
|
1108
|
+
...props,
|
|
1109
|
+
kind: "RequestBody",
|
|
1110
|
+
content: props.content?.map(createContent)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1478
1113
|
function createOperation(props) {
|
|
1114
|
+
const { requestBody, ...rest } = props;
|
|
1115
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
1479
1116
|
return {
|
|
1480
1117
|
tags: [],
|
|
1481
1118
|
parameters: [],
|
|
1482
1119
|
responses: [],
|
|
1483
|
-
...
|
|
1484
|
-
|
|
1120
|
+
...rest,
|
|
1121
|
+
...isHttp ? { protocol: "http" } : {},
|
|
1122
|
+
kind: "Operation",
|
|
1123
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
1485
1124
|
};
|
|
1486
1125
|
}
|
|
1487
1126
|
/**
|
|
1488
1127
|
* Maps schema `type` to its underlying `primitive`.
|
|
1489
|
-
* Primitive types map to themselves
|
|
1128
|
+
* Primitive types map to themselves. Special string formats map to `'string'`.
|
|
1490
1129
|
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
1491
1130
|
*/
|
|
1492
1131
|
const TYPE_TO_PRIMITIVE = {
|
|
@@ -1595,19 +1234,29 @@ function createParameter(props) {
|
|
|
1595
1234
|
/**
|
|
1596
1235
|
* Creates a `ResponseNode`.
|
|
1597
1236
|
*
|
|
1237
|
+
* Response body schemas live inside `content`. For convenience a single legacy `schema`
|
|
1238
|
+
* (with optional `mediaType`/`keysToOmit`) is normalized into one `content` entry, so the same
|
|
1239
|
+
* schema is never stored both at the node root and inside `content`.
|
|
1240
|
+
*
|
|
1598
1241
|
* @example
|
|
1599
1242
|
* ```ts
|
|
1600
1243
|
* const response = createResponse({
|
|
1601
1244
|
* statusCode: '200',
|
|
1602
|
-
*
|
|
1603
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
1245
|
+
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
1604
1246
|
* })
|
|
1605
1247
|
* ```
|
|
1606
1248
|
*/
|
|
1607
1249
|
function createResponse(props) {
|
|
1250
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
1251
|
+
const entries = content ?? (schema ? [{
|
|
1252
|
+
contentType: mediaType ?? "application/json",
|
|
1253
|
+
schema,
|
|
1254
|
+
keysToOmit: keysToOmit ?? null
|
|
1255
|
+
}] : void 0);
|
|
1608
1256
|
return {
|
|
1609
|
-
...
|
|
1610
|
-
kind: "Response"
|
|
1257
|
+
...rest,
|
|
1258
|
+
kind: "Response",
|
|
1259
|
+
content: entries?.map(createContent)
|
|
1611
1260
|
};
|
|
1612
1261
|
}
|
|
1613
1262
|
/**
|
|
@@ -1627,7 +1276,7 @@ function createResponse(props) {
|
|
|
1627
1276
|
* // → params?: QueryParams
|
|
1628
1277
|
* ```
|
|
1629
1278
|
*
|
|
1630
|
-
* @example Param with default (implicitly optional
|
|
1279
|
+
* @example Param with default (implicitly optional. Cannot combine with `optional: true`)
|
|
1631
1280
|
* ```ts
|
|
1632
1281
|
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
1633
1282
|
* // → config: RequestConfig = {}
|
|
@@ -1684,7 +1333,7 @@ function createParamsType(props) {
|
|
|
1684
1333
|
* // call → { id, name }
|
|
1685
1334
|
* ```
|
|
1686
1335
|
*
|
|
1687
|
-
* @example Inline (spread)
|
|
1336
|
+
* @example Inline (spread), children emitted as individual top-level parameters
|
|
1688
1337
|
* ```ts
|
|
1689
1338
|
* createParameterGroup({
|
|
1690
1339
|
* properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
|
|
@@ -1786,9 +1435,9 @@ function createSource(props) {
|
|
|
1786
1435
|
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
1787
1436
|
*
|
|
1788
1437
|
* Computes:
|
|
1789
|
-
* - `id`
|
|
1790
|
-
* - `name`
|
|
1791
|
-
* - `extname`
|
|
1438
|
+
* - `id` SHA256 hash of the file path
|
|
1439
|
+
* - `name` `baseName` without extension
|
|
1440
|
+
* - `extname` extension extracted from `baseName`
|
|
1792
1441
|
*
|
|
1793
1442
|
* Deduplicates:
|
|
1794
1443
|
* - `sources` via `combineSources`
|
|
@@ -1816,13 +1465,24 @@ function createFile(input) {
|
|
|
1816
1465
|
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
1817
1466
|
const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
|
|
1818
1467
|
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
1819
|
-
const
|
|
1468
|
+
const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
|
|
1469
|
+
const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name) => Boolean(name)));
|
|
1470
|
+
const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
|
|
1471
|
+
const resolvedImports = combinedImports.filter((imp) => imp.path !== input.path).flatMap((imp) => {
|
|
1472
|
+
if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
|
|
1473
|
+
const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
|
|
1474
|
+
if (!kept.length) return [];
|
|
1475
|
+
return [kept.length === imp.name.length ? imp : {
|
|
1476
|
+
...imp,
|
|
1477
|
+
name: kept
|
|
1478
|
+
}];
|
|
1479
|
+
});
|
|
1820
1480
|
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
1821
1481
|
return {
|
|
1822
1482
|
kind: "File",
|
|
1823
1483
|
...input,
|
|
1824
|
-
id: (0, node_crypto.
|
|
1825
|
-
name: trimExtName(input.baseName),
|
|
1484
|
+
id: (0, node_crypto.hash)("sha256", input.path, "hex"),
|
|
1485
|
+
name: require_utils.trimExtName(input.baseName),
|
|
1826
1486
|
extname,
|
|
1827
1487
|
imports: resolvedImports,
|
|
1828
1488
|
exports: resolvedExports,
|
|
@@ -2016,24 +1676,466 @@ function createJsx(value) {
|
|
|
2016
1676
|
};
|
|
2017
1677
|
}
|
|
2018
1678
|
//#endregion
|
|
2019
|
-
//#region src/
|
|
1679
|
+
//#region src/signature.ts
|
|
1680
|
+
/**
|
|
1681
|
+
* The shape-affecting flags shared by every node kind: base primitive, format, and `nullable`.
|
|
1682
|
+
* Documentation and usage-slot flags (`optional`/`nullish`/`readOnly`/`writeOnly`) are
|
|
1683
|
+
* intentionally excluded, they describe the property slot, not the type.
|
|
1684
|
+
*/
|
|
1685
|
+
function flagsDescriptor(node) {
|
|
1686
|
+
return `${node.primitive ?? ""};${node.format ?? ""};${node.nullable ? 1 : 0}`;
|
|
1687
|
+
}
|
|
1688
|
+
function refTargetName(node) {
|
|
1689
|
+
if (node.ref) return require_utils.extractRefName(node.ref);
|
|
1690
|
+
return node.name ?? "";
|
|
1691
|
+
}
|
|
1692
|
+
const arrayTupleFields = [
|
|
1693
|
+
{
|
|
1694
|
+
kind: "children",
|
|
1695
|
+
key: "items",
|
|
1696
|
+
prefix: "i"
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
kind: "child",
|
|
1700
|
+
key: "rest",
|
|
1701
|
+
prefix: "r"
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
kind: "scalar",
|
|
1705
|
+
key: "min",
|
|
1706
|
+
prefix: "mn"
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
kind: "scalar",
|
|
1710
|
+
key: "max",
|
|
1711
|
+
prefix: "mx"
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
kind: "bool",
|
|
1715
|
+
key: "unique",
|
|
1716
|
+
prefix: "u"
|
|
1717
|
+
}
|
|
1718
|
+
];
|
|
1719
|
+
const numericFields = [
|
|
1720
|
+
{
|
|
1721
|
+
kind: "scalar",
|
|
1722
|
+
key: "min",
|
|
1723
|
+
prefix: "mn"
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
kind: "scalar",
|
|
1727
|
+
key: "max",
|
|
1728
|
+
prefix: "mx"
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
kind: "scalar",
|
|
1732
|
+
key: "exclusiveMinimum",
|
|
1733
|
+
prefix: "emn"
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
kind: "scalar",
|
|
1737
|
+
key: "exclusiveMaximum",
|
|
1738
|
+
prefix: "emx"
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
kind: "scalar",
|
|
1742
|
+
key: "multipleOf",
|
|
1743
|
+
prefix: "mo"
|
|
1744
|
+
}
|
|
1745
|
+
];
|
|
1746
|
+
const rangeFields = [{
|
|
1747
|
+
kind: "scalar",
|
|
1748
|
+
key: "min",
|
|
1749
|
+
prefix: "mn"
|
|
1750
|
+
}, {
|
|
1751
|
+
kind: "scalar",
|
|
1752
|
+
key: "max",
|
|
1753
|
+
prefix: "mx"
|
|
1754
|
+
}];
|
|
1755
|
+
/**
|
|
1756
|
+
* Maps each schema node `type` to the ordered list of shape-contributing fields.
|
|
1757
|
+
* Node types absent from this map (scalar types like boolean, null, any, etc.) fall
|
|
1758
|
+
* back to `${type}|${flags}` with no additional fields.
|
|
1759
|
+
*/
|
|
1760
|
+
const SHAPE_KEYS = {
|
|
1761
|
+
object: [
|
|
1762
|
+
{ kind: "objectProps" },
|
|
1763
|
+
{ kind: "additionalProps" },
|
|
1764
|
+
{ kind: "patternProps" },
|
|
1765
|
+
{
|
|
1766
|
+
kind: "scalar",
|
|
1767
|
+
key: "minProperties",
|
|
1768
|
+
prefix: "mn"
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
kind: "scalar",
|
|
1772
|
+
key: "maxProperties",
|
|
1773
|
+
prefix: "mx"
|
|
1774
|
+
}
|
|
1775
|
+
],
|
|
1776
|
+
array: arrayTupleFields,
|
|
1777
|
+
tuple: arrayTupleFields,
|
|
1778
|
+
union: [
|
|
1779
|
+
{
|
|
1780
|
+
kind: "scalar",
|
|
1781
|
+
key: "strategy",
|
|
1782
|
+
prefix: "s"
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
kind: "scalar",
|
|
1786
|
+
key: "discriminatorPropertyName",
|
|
1787
|
+
prefix: "d"
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
kind: "children",
|
|
1791
|
+
key: "members",
|
|
1792
|
+
prefix: "m"
|
|
1793
|
+
}
|
|
1794
|
+
],
|
|
1795
|
+
intersection: [{
|
|
1796
|
+
kind: "children",
|
|
1797
|
+
key: "members",
|
|
1798
|
+
prefix: "m"
|
|
1799
|
+
}],
|
|
1800
|
+
enum: [{ kind: "enumValues" }],
|
|
1801
|
+
ref: [{ kind: "refTarget" }],
|
|
1802
|
+
string: [
|
|
1803
|
+
{
|
|
1804
|
+
kind: "scalar",
|
|
1805
|
+
key: "min",
|
|
1806
|
+
prefix: "mn"
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
kind: "scalar",
|
|
1810
|
+
key: "max",
|
|
1811
|
+
prefix: "mx"
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
kind: "scalar",
|
|
1815
|
+
key: "pattern",
|
|
1816
|
+
prefix: "pt"
|
|
1817
|
+
}
|
|
1818
|
+
],
|
|
1819
|
+
number: numericFields,
|
|
1820
|
+
integer: numericFields,
|
|
1821
|
+
bigint: numericFields,
|
|
1822
|
+
url: [
|
|
1823
|
+
{
|
|
1824
|
+
kind: "scalar",
|
|
1825
|
+
key: "path",
|
|
1826
|
+
prefix: "path"
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
kind: "scalar",
|
|
1830
|
+
key: "min",
|
|
1831
|
+
prefix: "mn"
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
kind: "scalar",
|
|
1835
|
+
key: "max",
|
|
1836
|
+
prefix: "mx"
|
|
1837
|
+
}
|
|
1838
|
+
],
|
|
1839
|
+
uuid: rangeFields,
|
|
1840
|
+
email: rangeFields,
|
|
1841
|
+
datetime: [{
|
|
1842
|
+
kind: "bool",
|
|
1843
|
+
key: "offset",
|
|
1844
|
+
prefix: "o"
|
|
1845
|
+
}, {
|
|
1846
|
+
kind: "bool",
|
|
1847
|
+
key: "local",
|
|
1848
|
+
prefix: "l"
|
|
1849
|
+
}],
|
|
1850
|
+
date: [{
|
|
1851
|
+
kind: "scalar",
|
|
1852
|
+
key: "representation",
|
|
1853
|
+
prefix: "rep"
|
|
1854
|
+
}],
|
|
1855
|
+
time: [{
|
|
1856
|
+
kind: "scalar",
|
|
1857
|
+
key: "representation",
|
|
1858
|
+
prefix: "rep"
|
|
1859
|
+
}]
|
|
1860
|
+
};
|
|
1861
|
+
function serializeShapeField(field, node, record) {
|
|
1862
|
+
switch (field.kind) {
|
|
1863
|
+
case "scalar": return `${field.prefix}:${record[field.key] ?? ""}`;
|
|
1864
|
+
case "bool": return `${field.prefix}:${record[field.key] ? 1 : 0}`;
|
|
1865
|
+
case "child": {
|
|
1866
|
+
const child = record[field.key];
|
|
1867
|
+
return `${field.prefix}:${child ? signatureOf(child) : ""}`;
|
|
1868
|
+
}
|
|
1869
|
+
case "children": {
|
|
1870
|
+
const children = record[field.key] ?? [];
|
|
1871
|
+
return `${field.prefix}[${children.map((c) => signatureOf(c)).join(",")}]`;
|
|
1872
|
+
}
|
|
1873
|
+
case "objectProps": return `p[${(node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema)}`).join(",")}]`;
|
|
1874
|
+
case "additionalProps": {
|
|
1875
|
+
const obj = node;
|
|
1876
|
+
if (typeof obj.additionalProperties === "boolean") return `ab:${obj.additionalProperties}`;
|
|
1877
|
+
if (obj.additionalProperties) return `as:${signatureOf(obj.additionalProperties)}`;
|
|
1878
|
+
return "";
|
|
1879
|
+
}
|
|
1880
|
+
case "patternProps": {
|
|
1881
|
+
const obj = node;
|
|
1882
|
+
return `pp[${obj.patternProperties ? Object.keys(obj.patternProperties).sort().map((key) => `${key}=${signatureOf(obj.patternProperties[key])}`).join(",") : ""}]`;
|
|
1883
|
+
}
|
|
1884
|
+
case "enumValues": {
|
|
1885
|
+
const en = node;
|
|
1886
|
+
let values = "";
|
|
1887
|
+
if (en.namedEnumValues?.length) values = en.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
|
|
1888
|
+
else if (en.enumValues?.length) values = en.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
|
|
1889
|
+
return `v[${values}]`;
|
|
1890
|
+
}
|
|
1891
|
+
case "refTarget": return `->${refTargetName(node)}`;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Builds the local, shape-only descriptor for a node: its kind, flags, constraints, and its
|
|
1896
|
+
* children's signatures. {@link signatureOf} hashes this string. Children contribute their
|
|
1897
|
+
* fixed-length signature rather than their own full descriptor, which keeps the result bounded.
|
|
1898
|
+
*/
|
|
1899
|
+
function describeShape(node) {
|
|
1900
|
+
const flags = flagsDescriptor(node);
|
|
1901
|
+
const fields = SHAPE_KEYS[node.type];
|
|
1902
|
+
if (!fields) return `${node.type}|${flags}`;
|
|
1903
|
+
const record = node;
|
|
1904
|
+
const parts = [`${node.type}|${flags}`];
|
|
1905
|
+
for (const field of fields) parts.push(serializeShapeField(field, node, record));
|
|
1906
|
+
return parts.join("|");
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Persistent hash-consing cache: `SchemaNode` → signature digest, keyed by node identity.
|
|
1910
|
+
*
|
|
1911
|
+
* A `WeakMap` so entries are released once the node is garbage-collected, and so a node hashed
|
|
1912
|
+
* during dedupe planning is not re-hashed when the same tree is rewritten during streaming
|
|
1913
|
+
* (where `schemaSignature` and `applyDedupe` would otherwise each walk it from scratch). Reuse
|
|
1914
|
+
* across calls is sound because a signature depends only on a node's content, and schema nodes
|
|
1915
|
+
* are immutable once created, transforms allocate new objects rather than mutating in place.
|
|
1916
|
+
*/
|
|
1917
|
+
const signatureCache = /* @__PURE__ */ new WeakMap();
|
|
1918
|
+
/**
|
|
1919
|
+
* Hash-consing: each node's signature is a fixed-length digest of its local shape plus its
|
|
1920
|
+
* children's digests (a Merkle hash). Children contribute their 64-char hash instead of their
|
|
1921
|
+
* full nested descriptor, so a signature stays bounded regardless of subtree depth, and the
|
|
1922
|
+
* digest is identical across calls because it depends only on content, never on traversal
|
|
1923
|
+
* order. This keeps the keys built during planning consistent with the ones recomputed later
|
|
1924
|
+
* during streaming. {@link signatureCache} memoizes node → digest across every computation.
|
|
1925
|
+
*/
|
|
1926
|
+
function signatureOf(node) {
|
|
1927
|
+
const cached = signatureCache.get(node);
|
|
1928
|
+
if (cached !== void 0) return cached;
|
|
1929
|
+
const signature = (0, node_crypto.hash)("sha256", describeShape(node), "hex");
|
|
1930
|
+
signatureCache.set(node, signature);
|
|
1931
|
+
return signature;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Computes a deterministic, shape-only signature (a fixed-length content hash) for a schema node.
|
|
1935
|
+
*
|
|
1936
|
+
* Two schemas share a signature when they are structurally identical, ignoring
|
|
1937
|
+
* documentation (`name`, `title`, `description`, `example`, `default`, `deprecated`)
|
|
1938
|
+
* and usage-slot flags (`optional`, `nullish`, `readOnly`, `writeOnly`). `nullable`
|
|
1939
|
+
* is kept because it changes the produced type. `ref` nodes compare by target name,
|
|
1940
|
+
* which also keeps the algorithm terminating on circular shapes.
|
|
1941
|
+
*
|
|
1942
|
+
* @example Two enums with different descriptions share a signature
|
|
1943
|
+
* ```ts
|
|
1944
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'], description: 'x' })) ===
|
|
1945
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'] }))
|
|
1946
|
+
* ```
|
|
1947
|
+
*/
|
|
1948
|
+
function schemaSignature(node) {
|
|
1949
|
+
return signatureOf(node);
|
|
1950
|
+
}
|
|
1951
|
+
//#endregion
|
|
1952
|
+
//#region src/dedupe.ts
|
|
1953
|
+
/**
|
|
1954
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
1955
|
+
* usage-slot and documentation fields that are not part of the canonical type.
|
|
1956
|
+
*/
|
|
1957
|
+
function createRefNode(node, canonical) {
|
|
1958
|
+
return createSchema({
|
|
1959
|
+
type: "ref",
|
|
1960
|
+
name: canonical.name,
|
|
1961
|
+
ref: canonical.ref,
|
|
1962
|
+
optional: node.optional,
|
|
1963
|
+
nullish: node.nullish,
|
|
1964
|
+
readOnly: node.readOnly,
|
|
1965
|
+
writeOnly: node.writeOnly,
|
|
1966
|
+
deprecated: node.deprecated,
|
|
1967
|
+
description: node.description,
|
|
1968
|
+
default: node.default,
|
|
1969
|
+
example: node.example
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
function applyDedupe(node, canonicalBySignature, skipRootMatch = false) {
|
|
1973
|
+
if (canonicalBySignature.size === 0) return node;
|
|
1974
|
+
const root = node;
|
|
1975
|
+
return transform(node, { schema(schemaNode) {
|
|
1976
|
+
const signature = signatureOf(schemaNode);
|
|
1977
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
1978
|
+
const canonical = canonicalBySignature.get(signature);
|
|
1979
|
+
if (!canonical) return void 0;
|
|
1980
|
+
return createRefNode(schemaNode, canonical);
|
|
1981
|
+
} });
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Strips usage-slot flags from a hoisted definition and applies its canonical name.
|
|
1985
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
1986
|
+
*/
|
|
1987
|
+
function cleanDefinition(node, name) {
|
|
1988
|
+
return {
|
|
1989
|
+
...node,
|
|
1990
|
+
name,
|
|
1991
|
+
optional: void 0,
|
|
1992
|
+
nullish: void 0
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Scans a forest of schema and operation nodes and produces a {@link DedupePlan}.
|
|
1997
|
+
*
|
|
1998
|
+
* A shape that occurs at least `minOccurrences` times is deduplicated: if any occurrence
|
|
1999
|
+
* is a named top-level schema, that name becomes the canonical (so other top-level duplicates
|
|
2000
|
+
* and inline copies turn into references to it). Otherwise a new definition is hoisted using
|
|
2001
|
+
* `nameFor`. The plan is then applied per node with {@link applyDedupe}.
|
|
2002
|
+
*
|
|
2003
|
+
* @example
|
|
2004
|
+
* ```ts
|
|
2005
|
+
* const plan = buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2006
|
+
* isCandidate: (node) => node.type === 'enum' || node.type === 'object',
|
|
2007
|
+
* nameFor: (node) => node.name ?? null,
|
|
2008
|
+
* refFor: (name) => `#/components/schemas/${name}`,
|
|
2009
|
+
* })
|
|
2010
|
+
* ```
|
|
2011
|
+
*/
|
|
2012
|
+
function buildDedupePlan(roots, options) {
|
|
2013
|
+
const { isCandidate, nameFor, refFor, minOccurrences = 2 } = options;
|
|
2014
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
2015
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2016
|
+
function record(schemaNode) {
|
|
2017
|
+
const signature = signatureOf(schemaNode);
|
|
2018
|
+
if (!isCandidate(schemaNode)) return;
|
|
2019
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
2020
|
+
const group = groups.get(signature);
|
|
2021
|
+
if (group) {
|
|
2022
|
+
group.count++;
|
|
2023
|
+
if (isTopLevel && !group.topLevelName) group.topLevelName = schemaNode.name;
|
|
2024
|
+
} else groups.set(signature, {
|
|
2025
|
+
count: 1,
|
|
2026
|
+
representative: schemaNode,
|
|
2027
|
+
topLevelName: isTopLevel ? schemaNode.name : void 0
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
for (const root of roots) {
|
|
2031
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
2032
|
+
for (const schemaNode of collectLazy(root, { schema: (node) => node })) record(schemaNode);
|
|
2033
|
+
}
|
|
2034
|
+
const canonicalBySignature = /* @__PURE__ */ new Map();
|
|
2035
|
+
const pendingHoists = [];
|
|
2036
|
+
for (const [signature, group] of groups) {
|
|
2037
|
+
if (group.count < minOccurrences) continue;
|
|
2038
|
+
if (group.topLevelName) {
|
|
2039
|
+
canonicalBySignature.set(signature, {
|
|
2040
|
+
name: group.topLevelName,
|
|
2041
|
+
ref: refFor(group.topLevelName)
|
|
2042
|
+
});
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
const name = nameFor(group.representative, signature);
|
|
2046
|
+
if (!name) continue;
|
|
2047
|
+
canonicalBySignature.set(signature, {
|
|
2048
|
+
name,
|
|
2049
|
+
ref: refFor(name)
|
|
2050
|
+
});
|
|
2051
|
+
pendingHoists.push({
|
|
2052
|
+
name,
|
|
2053
|
+
representative: group.representative
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
return {
|
|
2057
|
+
canonicalBySignature,
|
|
2058
|
+
hoisted: pendingHoists.map(({ name, representative }) => cleanDefinition(applyDedupe(representative, canonicalBySignature, true), name))
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
//#endregion
|
|
2062
|
+
//#region src/dialect.ts
|
|
2063
|
+
/**
|
|
2064
|
+
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
2065
|
+
* `defineParser`, it adds no runtime behavior, it pins the dialect's type for
|
|
2066
|
+
* inference and gives adapter authors a discoverable anchor.
|
|
2067
|
+
*
|
|
2068
|
+
* @example
|
|
2069
|
+
* ```ts
|
|
2070
|
+
* export const oasDialect = defineSchemaDialect({
|
|
2071
|
+
* name: 'oas',
|
|
2072
|
+
* isNullable,
|
|
2073
|
+
* isReference,
|
|
2074
|
+
* isDiscriminator,
|
|
2075
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
2076
|
+
* resolveRef,
|
|
2077
|
+
* })
|
|
2078
|
+
* ```
|
|
2079
|
+
*/
|
|
2080
|
+
function defineSchemaDialect(dialect) {
|
|
2081
|
+
return dialect;
|
|
2082
|
+
}
|
|
2083
|
+
//#endregion
|
|
2084
|
+
//#region src/dispatch.ts
|
|
2020
2085
|
/**
|
|
2021
|
-
*
|
|
2086
|
+
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
2087
|
+
*
|
|
2088
|
+
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
2089
|
+
* The contract an adapter follows is intentionally minimal:
|
|
2090
|
+
*
|
|
2091
|
+
* context → [rule.match → rule.convert] → node
|
|
2092
|
+
*
|
|
2093
|
+
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
2094
|
+
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
2095
|
+
* context type and a new rules table, the traversal here is reused unchanged.
|
|
2096
|
+
*
|
|
2097
|
+
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
2098
|
+
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
2099
|
+
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
2100
|
+
* returns `null`, leaving the caller to apply its own fallback.
|
|
2022
2101
|
*
|
|
2023
|
-
*
|
|
2102
|
+
* @example
|
|
2103
|
+
* ```ts
|
|
2104
|
+
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
2105
|
+
* ```
|
|
2106
|
+
*/
|
|
2107
|
+
function dispatch(rules, context) {
|
|
2108
|
+
for (const rule of rules) {
|
|
2109
|
+
if (!rule.match(context)) continue;
|
|
2110
|
+
const node = rule.convert(context);
|
|
2111
|
+
if (node !== null && node !== void 0) return node;
|
|
2112
|
+
}
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
//#endregion
|
|
2116
|
+
//#region src/printer.ts
|
|
2117
|
+
/**
|
|
2118
|
+
* Defines a schema printer: a function that takes a `SchemaNode` and emits
|
|
2119
|
+
* code in your target language. Each plugin that produces code from schemas
|
|
2120
|
+
* (TypeScript types, Zod schemas, Faker factories) ships a printer built
|
|
2121
|
+
* with this helper.
|
|
2024
2122
|
*
|
|
2025
2123
|
* The builder receives resolved options and returns:
|
|
2026
|
-
* - `name` — a unique identifier for the printer
|
|
2027
|
-
* - `options` — options stored on the returned printer instance
|
|
2028
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
2029
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
2030
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
2031
|
-
* - This keeps recursion safe and avoids self-calls
|
|
2032
2124
|
*
|
|
2033
|
-
*
|
|
2125
|
+
* - `name` unique identifier for the printer.
|
|
2126
|
+
* - `options` stored on the returned printer instance.
|
|
2127
|
+
* - `nodes` map of `SchemaType` → handler. Handlers return the rendered
|
|
2128
|
+
* output (a string, a TypeScript AST node, ...) for that schema type.
|
|
2129
|
+
* - `print` (optional), top-level override exposed as `printer.print`.
|
|
2130
|
+
* Use `this.transform(node)` inside it to dispatch to `nodes` recursively.
|
|
2131
|
+
*
|
|
2132
|
+
* Without a `print` override, `printer.print` falls back to `printer.transform`
|
|
2133
|
+
* (the node-level dispatcher).
|
|
2034
2134
|
*
|
|
2035
|
-
* @example
|
|
2135
|
+
* @example Tiny Zod printer
|
|
2036
2136
|
* ```ts
|
|
2137
|
+
* import { definePrinter, type PrinterFactoryOptions } from '@kubb/ast'
|
|
2138
|
+
*
|
|
2037
2139
|
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
2038
2140
|
*
|
|
2039
2141
|
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
@@ -2042,7 +2144,9 @@ function createJsx(value) {
|
|
|
2042
2144
|
* nodes: {
|
|
2043
2145
|
* string: () => 'z.string()',
|
|
2044
2146
|
* object(node) {
|
|
2045
|
-
* const props = node.properties
|
|
2147
|
+
* const props = node.properties
|
|
2148
|
+
* .map((p) => `${p.name}: ${this.transform(p.schema)}`)
|
|
2149
|
+
* .join(', ')
|
|
2046
2150
|
* return `z.object({ ${props} })`
|
|
2047
2151
|
* },
|
|
2048
2152
|
* },
|
|
@@ -2070,7 +2174,7 @@ function createPrinterFactory(getKey) {
|
|
|
2070
2174
|
options: resolvedOptions,
|
|
2071
2175
|
transform: (node) => {
|
|
2072
2176
|
const key = getKey(node);
|
|
2073
|
-
if (key ===
|
|
2177
|
+
if (key === null) return null;
|
|
2074
2178
|
const handler = nodes[key];
|
|
2075
2179
|
if (!handler) return null;
|
|
2076
2180
|
return handler.call(context, node);
|
|
@@ -2087,30 +2191,16 @@ function createPrinterFactory(getKey) {
|
|
|
2087
2191
|
}
|
|
2088
2192
|
//#endregion
|
|
2089
2193
|
//#region src/resolvers.ts
|
|
2090
|
-
function findDiscriminator(mapping, ref) {
|
|
2091
|
-
if (!mapping || !ref) return null;
|
|
2092
|
-
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
2093
|
-
}
|
|
2094
|
-
function childName(parentName, propName) {
|
|
2095
|
-
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
2096
|
-
}
|
|
2097
|
-
function enumPropName(parentName, propName, enumSuffix) {
|
|
2098
|
-
return pascalCase([
|
|
2099
|
-
parentName,
|
|
2100
|
-
propName,
|
|
2101
|
-
enumSuffix
|
|
2102
|
-
].filter(Boolean).join(" "));
|
|
2103
|
-
}
|
|
2104
2194
|
/**
|
|
2105
2195
|
* Collects import entries for all `ref` schema nodes in `node`.
|
|
2106
2196
|
*/
|
|
2107
2197
|
function collectImports({ node, nameMapping, resolve }) {
|
|
2108
2198
|
return collect(node, { schema(schemaNode) {
|
|
2109
2199
|
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2110
|
-
if (!schemaRef?.ref) return;
|
|
2111
|
-
const rawName = extractRefName(schemaRef.ref);
|
|
2200
|
+
if (!schemaRef?.ref) return null;
|
|
2201
|
+
const rawName = require_utils.extractRefName(schemaRef.ref);
|
|
2112
2202
|
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2113
|
-
if (!result) return;
|
|
2203
|
+
if (!result) return null;
|
|
2114
2204
|
return result;
|
|
2115
2205
|
} });
|
|
2116
2206
|
}
|
|
@@ -2164,23 +2254,24 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
|
2164
2254
|
* ])
|
|
2165
2255
|
* ```
|
|
2166
2256
|
*/
|
|
2167
|
-
function
|
|
2168
|
-
|
|
2257
|
+
function* mergeAdjacentObjectsLazy(members) {
|
|
2258
|
+
let acc;
|
|
2259
|
+
for (const member of members) {
|
|
2169
2260
|
const objectMember = narrowSchema(member, "object");
|
|
2170
|
-
if (objectMember && !objectMember.name) {
|
|
2171
|
-
const
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
...
|
|
2176
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2261
|
+
if (objectMember && !objectMember.name && acc !== void 0) {
|
|
2262
|
+
const accObject = narrowSchema(acc, "object");
|
|
2263
|
+
if (accObject && !accObject.name) {
|
|
2264
|
+
acc = createSchema({
|
|
2265
|
+
...accObject,
|
|
2266
|
+
properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2177
2267
|
});
|
|
2178
|
-
|
|
2268
|
+
continue;
|
|
2179
2269
|
}
|
|
2180
2270
|
}
|
|
2181
|
-
acc
|
|
2182
|
-
|
|
2183
|
-
}
|
|
2271
|
+
if (acc !== void 0) yield acc;
|
|
2272
|
+
acc = member;
|
|
2273
|
+
}
|
|
2274
|
+
if (acc !== void 0) yield acc;
|
|
2184
2275
|
}
|
|
2185
2276
|
/**
|
|
2186
2277
|
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
@@ -2195,7 +2286,7 @@ function mergeAdjacentObjects(members) {
|
|
|
2195
2286
|
* ```
|
|
2196
2287
|
*/
|
|
2197
2288
|
function simplifyUnion(members) {
|
|
2198
|
-
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
2289
|
+
const scalarPrimitives = new Set(members.filter((member) => require_utils.isScalarPrimitive(member.type)).map((m) => m.type));
|
|
2199
2290
|
if (!scalarPrimitives.size) return members;
|
|
2200
2291
|
return members.filter((member) => {
|
|
2201
2292
|
const enumNode = narrowSchema(member, "enum");
|
|
@@ -2212,20 +2303,20 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2212
2303
|
const enumNode = narrowSchema(propNode, "enum");
|
|
2213
2304
|
if (enumNode?.primitive === "boolean") return {
|
|
2214
2305
|
...propNode,
|
|
2215
|
-
name:
|
|
2306
|
+
name: null
|
|
2216
2307
|
};
|
|
2217
2308
|
if (enumNode) return {
|
|
2218
2309
|
...propNode,
|
|
2219
|
-
name: enumPropName(parentName, propName, enumSuffix)
|
|
2310
|
+
name: require_utils.enumPropName(parentName, propName, enumSuffix)
|
|
2220
2311
|
};
|
|
2221
2312
|
return propNode;
|
|
2222
2313
|
}
|
|
2223
2314
|
//#endregion
|
|
2315
|
+
exports.applyDedupe = applyDedupe;
|
|
2316
|
+
exports.buildDedupePlan = buildDedupePlan;
|
|
2224
2317
|
exports.caseParams = caseParams;
|
|
2225
|
-
exports.childName = childName;
|
|
2226
2318
|
exports.collect = collect;
|
|
2227
2319
|
exports.collectImports = collectImports;
|
|
2228
|
-
exports.collectReferencedSchemaNames = collectReferencedSchemaNames;
|
|
2229
2320
|
exports.collectUsedSchemaNames = collectUsedSchemaNames;
|
|
2230
2321
|
exports.containsCircularRef = containsCircularRef;
|
|
2231
2322
|
exports.createArrowFunction = createArrowFunction;
|
|
@@ -2251,33 +2342,32 @@ exports.createProperty = createProperty;
|
|
|
2251
2342
|
exports.createResponse = createResponse;
|
|
2252
2343
|
exports.createSchema = createSchema;
|
|
2253
2344
|
exports.createSource = createSource;
|
|
2345
|
+
exports.createStreamInput = createStreamInput;
|
|
2254
2346
|
exports.createText = createText;
|
|
2255
2347
|
exports.createType = createType;
|
|
2256
2348
|
exports.definePrinter = definePrinter;
|
|
2257
|
-
exports.
|
|
2258
|
-
exports.
|
|
2349
|
+
exports.defineSchemaDialect = defineSchemaDialect;
|
|
2350
|
+
exports.dispatch = dispatch;
|
|
2259
2351
|
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
2260
2352
|
exports.findCircularSchemas = findCircularSchemas;
|
|
2261
|
-
exports.
|
|
2262
|
-
exports.
|
|
2353
|
+
exports.httpMethods = require_utils.httpMethods;
|
|
2354
|
+
exports.isHttpOperationNode = isHttpOperationNode;
|
|
2263
2355
|
exports.isInputNode = isInputNode;
|
|
2264
2356
|
exports.isOperationNode = isOperationNode;
|
|
2265
2357
|
exports.isOutputNode = isOutputNode;
|
|
2266
|
-
exports.isScalarPrimitive = isScalarPrimitive;
|
|
2267
2358
|
exports.isSchemaNode = isSchemaNode;
|
|
2268
2359
|
exports.isStringType = isStringType;
|
|
2269
|
-
exports.
|
|
2270
|
-
exports.mergeAdjacentObjects = mergeAdjacentObjects;
|
|
2360
|
+
exports.mergeAdjacentObjectsLazy = mergeAdjacentObjectsLazy;
|
|
2271
2361
|
exports.narrowSchema = narrowSchema;
|
|
2272
|
-
exports.
|
|
2273
|
-
exports.
|
|
2274
|
-
exports.schemaTypes = schemaTypes;
|
|
2362
|
+
exports.schemaSignature = schemaSignature;
|
|
2363
|
+
exports.schemaTypes = require_utils.schemaTypes;
|
|
2275
2364
|
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
2276
2365
|
exports.setEnumName = setEnumName;
|
|
2277
2366
|
exports.simplifyUnion = simplifyUnion;
|
|
2278
2367
|
exports.syncOptionality = syncOptionality;
|
|
2279
2368
|
exports.syncSchemaRef = syncSchemaRef;
|
|
2280
2369
|
exports.transform = transform;
|
|
2370
|
+
exports.update = update;
|
|
2281
2371
|
exports.walk = walk;
|
|
2282
2372
|
|
|
2283
2373
|
//# sourceMappingURL=index.cjs.map
|