@kubb/ast 5.0.0-beta.1 → 5.0.0-beta.100
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 +56 -36
- package/dist/index.cjs +1071 -1665
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +136 -3335
- package/dist/index.js +1022 -1620
- package/dist/index.js.map +1 -1
- package/dist/rolldown-runtime-CNktS9qV.js +17 -0
- package/dist/types-DK2c0yY4.d.ts +2764 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +9 -9
- package/dist/chunk--u3MIqq1.js +0 -8
- package/src/constants.ts +0 -228
- package/src/factory.ts +0 -742
- package/src/guards.ts +0 -110
- package/src/index.ts +0 -45
- package/src/infer.ts +0 -130
- package/src/mocks.ts +0 -176
- package/src/nodes/base.ts +0 -56
- package/src/nodes/code.ts +0 -304
- package/src/nodes/file.ts +0 -230
- package/src/nodes/function.ts +0 -223
- package/src/nodes/http.ts +0 -119
- package/src/nodes/index.ts +0 -86
- package/src/nodes/operation.ts +0 -111
- package/src/nodes/output.ts +0 -26
- package/src/nodes/parameter.ts +0 -41
- package/src/nodes/property.ts +0 -34
- package/src/nodes/response.ts +0 -43
- package/src/nodes/root.ts +0 -64
- package/src/nodes/schema.ts +0 -656
- package/src/printer.ts +0 -250
- package/src/refs.ts +0 -67
- package/src/resolvers.ts +0 -45
- package/src/transformers.ts +0 -159
- package/src/types.ts +0 -70
- package/src/utils.ts +0 -833
- package/src/visitor.ts +0 -592
package/dist/index.js
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-CNktS9qV.js";
|
|
2
|
+
import { hash } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
//#region src/constants.ts
|
|
5
5
|
const visitorDepths = {
|
|
6
6
|
shallow: "shallow",
|
|
7
7
|
deep: "deep"
|
|
8
8
|
};
|
|
9
|
-
const nodeKinds = {
|
|
10
|
-
input: "Input",
|
|
11
|
-
output: "Output",
|
|
12
|
-
operation: "Operation",
|
|
13
|
-
schema: "Schema",
|
|
14
|
-
property: "Property",
|
|
15
|
-
parameter: "Parameter",
|
|
16
|
-
response: "Response",
|
|
17
|
-
functionParameter: "FunctionParameter",
|
|
18
|
-
parameterGroup: "ParameterGroup",
|
|
19
|
-
functionParameters: "FunctionParameters",
|
|
20
|
-
type: "Type",
|
|
21
|
-
file: "File",
|
|
22
|
-
import: "Import",
|
|
23
|
-
export: "Export",
|
|
24
|
-
source: "Source",
|
|
25
|
-
text: "Text",
|
|
26
|
-
break: "Break"
|
|
27
|
-
};
|
|
28
9
|
/**
|
|
29
10
|
* Schema type discriminators used by all AST schema nodes.
|
|
30
11
|
*
|
|
31
|
-
*
|
|
32
|
-
* Grouped by category: primitives (`string`, `number`, `boolean`), structural types (`object`, `array`, `union`),
|
|
33
|
-
* and format-specific types (`date`, `uuid`, `email`). Use `isScalarPrimitive()` to check for scalar types.
|
|
12
|
+
* Each value is a stable discriminator across the AST (for example `schema.type === schemaTypes.object`).
|
|
34
13
|
*/
|
|
35
14
|
const schemaTypes = {
|
|
36
15
|
/**
|
|
@@ -50,7 +29,7 @@ const schemaTypes = {
|
|
|
50
29
|
*/
|
|
51
30
|
bigint: "bigint",
|
|
52
31
|
/**
|
|
53
|
-
* Boolean value
|
|
32
|
+
* Boolean value.
|
|
54
33
|
*/
|
|
55
34
|
boolean: "boolean",
|
|
56
35
|
/**
|
|
@@ -138,935 +117,314 @@ const schemaTypes = {
|
|
|
138
117
|
*/
|
|
139
118
|
never: "never"
|
|
140
119
|
};
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/guards.ts
|
|
141
122
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* Use `isScalarPrimitive()` to safely check whether a type is a scalar primitive.
|
|
145
|
-
*/
|
|
146
|
-
const SCALAR_PRIMITIVE_TYPES = new Set([
|
|
147
|
-
"string",
|
|
148
|
-
"number",
|
|
149
|
-
"integer",
|
|
150
|
-
"bigint",
|
|
151
|
-
"boolean"
|
|
152
|
-
]);
|
|
153
|
-
/**
|
|
154
|
-
* Type guard that returns `true` when `type` is a scalar primitive schema type.
|
|
123
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
155
124
|
*
|
|
156
|
-
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const schema = createSchema({ type: 'string' })
|
|
128
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
129
|
+
* ```
|
|
157
130
|
*/
|
|
158
|
-
function
|
|
159
|
-
return
|
|
131
|
+
function narrowSchema(node, type) {
|
|
132
|
+
return node?.type === type ? node : null;
|
|
160
133
|
}
|
|
161
134
|
/**
|
|
162
|
-
*
|
|
135
|
+
* Narrows an `OperationNode` to an `HttpOperationNode` so `method` and `path` are present.
|
|
163
136
|
*
|
|
164
|
-
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* if (isHttpOperationNode(node)) {
|
|
140
|
+
* console.log(node.method, node.path)
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
165
143
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
put: "PUT",
|
|
170
|
-
patch: "PATCH",
|
|
171
|
-
delete: "DELETE",
|
|
172
|
-
head: "HEAD",
|
|
173
|
-
options: "OPTIONS",
|
|
174
|
-
trace: "TRACE"
|
|
175
|
-
};
|
|
176
|
-
/**
|
|
177
|
-
* Common MIME types used in request/response content negotiation.
|
|
178
|
-
*
|
|
179
|
-
* Covers JSON, XML, form data, PDFs, images, audio, and video formats.
|
|
180
|
-
* Use these as keys when serializing request/response bodies.
|
|
181
|
-
*/
|
|
182
|
-
const mediaTypes = {
|
|
183
|
-
applicationJson: "application/json",
|
|
184
|
-
applicationXml: "application/xml",
|
|
185
|
-
applicationFormUrlEncoded: "application/x-www-form-urlencoded",
|
|
186
|
-
applicationOctetStream: "application/octet-stream",
|
|
187
|
-
applicationPdf: "application/pdf",
|
|
188
|
-
applicationZip: "application/zip",
|
|
189
|
-
applicationGraphql: "application/graphql",
|
|
190
|
-
multipartFormData: "multipart/form-data",
|
|
191
|
-
textPlain: "text/plain",
|
|
192
|
-
textHtml: "text/html",
|
|
193
|
-
textCsv: "text/csv",
|
|
194
|
-
textXml: "text/xml",
|
|
195
|
-
imagePng: "image/png",
|
|
196
|
-
imageJpeg: "image/jpeg",
|
|
197
|
-
imageGif: "image/gif",
|
|
198
|
-
imageWebp: "image/webp",
|
|
199
|
-
imageSvgXml: "image/svg+xml",
|
|
200
|
-
audioMpeg: "audio/mpeg",
|
|
201
|
-
videoMp4: "video/mp4"
|
|
202
|
-
};
|
|
144
|
+
function isHttpOperationNode(node) {
|
|
145
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
146
|
+
}
|
|
203
147
|
//#endregion
|
|
204
|
-
//#region
|
|
148
|
+
//#region src/defineNode.ts
|
|
205
149
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* and capitalizes each word according to `pascal`.
|
|
209
|
-
*
|
|
210
|
-
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
150
|
+
* Visitor callback names, one per traversable node kind, in traversal order.
|
|
151
|
+
* Kept in sync with the keys of `Visitor` in `visitor.ts`.
|
|
211
152
|
*/
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
153
|
+
const visitorKeys = [
|
|
154
|
+
"input",
|
|
155
|
+
"output",
|
|
156
|
+
"operation",
|
|
157
|
+
"schema",
|
|
158
|
+
"property",
|
|
159
|
+
"parameter",
|
|
160
|
+
"response"
|
|
161
|
+
];
|
|
219
162
|
/**
|
|
220
|
-
*
|
|
221
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
222
|
-
* Segments are joined with `/` to form a file path.
|
|
223
|
-
*
|
|
224
|
-
* Only splits on dots followed by a letter so that version numbers
|
|
225
|
-
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
226
|
-
*
|
|
227
|
-
* Empty segments are filtered before joining. They arise when the text starts with
|
|
228
|
-
* a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
|
|
229
|
-
* and `'..'` transforms to an empty string). Without this filter the join would produce
|
|
230
|
-
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
|
|
231
|
-
* generated files to escape the configured output directory.
|
|
163
|
+
* Builds a type guard that matches nodes of the given `kind`.
|
|
232
164
|
*/
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Converts `text` to camelCase.
|
|
239
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
243
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
244
|
-
*/
|
|
245
|
-
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
246
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
247
|
-
prefix,
|
|
248
|
-
suffix
|
|
249
|
-
} : {}));
|
|
250
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
165
|
+
function isKind(kind) {
|
|
166
|
+
return (node) => node?.kind === kind;
|
|
251
167
|
}
|
|
252
168
|
/**
|
|
253
|
-
*
|
|
254
|
-
*
|
|
169
|
+
* Defines a node once and derives its `create` builder, `is` guard, and traversal
|
|
170
|
+
* metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
|
|
171
|
+
* `kind`, so node construction lives in one place without scattered `as` casts.
|
|
255
172
|
*
|
|
256
|
-
* @example
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
262
|
-
prefix,
|
|
263
|
-
suffix
|
|
264
|
-
}) : camelCase(part));
|
|
265
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
266
|
-
}
|
|
267
|
-
//#endregion
|
|
268
|
-
//#region ../../internals/utils/src/reserved.ts
|
|
269
|
-
/**
|
|
270
|
-
* JavaScript and Java reserved words.
|
|
271
|
-
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
272
|
-
*/
|
|
273
|
-
const reservedWords = new Set([
|
|
274
|
-
"abstract",
|
|
275
|
-
"arguments",
|
|
276
|
-
"boolean",
|
|
277
|
-
"break",
|
|
278
|
-
"byte",
|
|
279
|
-
"case",
|
|
280
|
-
"catch",
|
|
281
|
-
"char",
|
|
282
|
-
"class",
|
|
283
|
-
"const",
|
|
284
|
-
"continue",
|
|
285
|
-
"debugger",
|
|
286
|
-
"default",
|
|
287
|
-
"delete",
|
|
288
|
-
"do",
|
|
289
|
-
"double",
|
|
290
|
-
"else",
|
|
291
|
-
"enum",
|
|
292
|
-
"eval",
|
|
293
|
-
"export",
|
|
294
|
-
"extends",
|
|
295
|
-
"false",
|
|
296
|
-
"final",
|
|
297
|
-
"finally",
|
|
298
|
-
"float",
|
|
299
|
-
"for",
|
|
300
|
-
"function",
|
|
301
|
-
"goto",
|
|
302
|
-
"if",
|
|
303
|
-
"implements",
|
|
304
|
-
"import",
|
|
305
|
-
"in",
|
|
306
|
-
"instanceof",
|
|
307
|
-
"int",
|
|
308
|
-
"interface",
|
|
309
|
-
"let",
|
|
310
|
-
"long",
|
|
311
|
-
"native",
|
|
312
|
-
"new",
|
|
313
|
-
"null",
|
|
314
|
-
"package",
|
|
315
|
-
"private",
|
|
316
|
-
"protected",
|
|
317
|
-
"public",
|
|
318
|
-
"return",
|
|
319
|
-
"short",
|
|
320
|
-
"static",
|
|
321
|
-
"super",
|
|
322
|
-
"switch",
|
|
323
|
-
"synchronized",
|
|
324
|
-
"this",
|
|
325
|
-
"throw",
|
|
326
|
-
"throws",
|
|
327
|
-
"transient",
|
|
328
|
-
"true",
|
|
329
|
-
"try",
|
|
330
|
-
"typeof",
|
|
331
|
-
"var",
|
|
332
|
-
"void",
|
|
333
|
-
"volatile",
|
|
334
|
-
"while",
|
|
335
|
-
"with",
|
|
336
|
-
"yield",
|
|
337
|
-
"Array",
|
|
338
|
-
"Date",
|
|
339
|
-
"hasOwnProperty",
|
|
340
|
-
"Infinity",
|
|
341
|
-
"isFinite",
|
|
342
|
-
"isNaN",
|
|
343
|
-
"isPrototypeOf",
|
|
344
|
-
"length",
|
|
345
|
-
"Math",
|
|
346
|
-
"name",
|
|
347
|
-
"NaN",
|
|
348
|
-
"Number",
|
|
349
|
-
"Object",
|
|
350
|
-
"prototype",
|
|
351
|
-
"String",
|
|
352
|
-
"toString",
|
|
353
|
-
"undefined",
|
|
354
|
-
"valueOf"
|
|
355
|
-
]);
|
|
356
|
-
/**
|
|
357
|
-
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
173
|
+
* @example Simple node
|
|
174
|
+
* ```ts
|
|
175
|
+
* const importDef = defineNode<ImportNode>({ kind: 'Import' })
|
|
176
|
+
* const createImport = importDef.create
|
|
177
|
+
* ```
|
|
358
178
|
*
|
|
359
|
-
* @example
|
|
179
|
+
* @example Node with a build hook
|
|
360
180
|
* ```ts
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
181
|
+
* const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
|
|
182
|
+
* kind: 'Property',
|
|
183
|
+
* build: (props) => ({ ...props, required: props.required ?? false }),
|
|
184
|
+
* children: ['schema'],
|
|
185
|
+
* visitorKey: 'property',
|
|
186
|
+
* })
|
|
364
187
|
* ```
|
|
365
188
|
*/
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
189
|
+
function defineNode(config) {
|
|
190
|
+
const { kind, defaults, build, children, visitorKey } = config;
|
|
191
|
+
function create(input) {
|
|
192
|
+
const base = build ? build(input) : input;
|
|
193
|
+
const node = {
|
|
194
|
+
kind,
|
|
195
|
+
...defaults,
|
|
196
|
+
...base
|
|
197
|
+
};
|
|
198
|
+
node.kind = kind;
|
|
199
|
+
return node;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
kind,
|
|
203
|
+
create,
|
|
204
|
+
is: isKind(kind),
|
|
205
|
+
children,
|
|
206
|
+
visitorKey
|
|
207
|
+
};
|
|
369
208
|
}
|
|
370
209
|
//#endregion
|
|
371
|
-
//#region
|
|
210
|
+
//#region src/nodes/code.ts
|
|
372
211
|
/**
|
|
373
|
-
*
|
|
374
|
-
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
375
|
-
*
|
|
376
|
-
* @example
|
|
377
|
-
* trimExtName('petStore.ts') // 'petStore'
|
|
378
|
-
* trimExtName('/src/models/pet.ts') // '/src/models/pet'
|
|
379
|
-
* trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
|
|
380
|
-
* trimExtName('noExtension') // 'noExtension'
|
|
212
|
+
* Definition for the {@link ConstNode}.
|
|
381
213
|
*/
|
|
382
|
-
|
|
383
|
-
const dotIndex = text.lastIndexOf(".");
|
|
384
|
-
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
385
|
-
return text;
|
|
386
|
-
}
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region src/guards.ts
|
|
214
|
+
const constDef = defineNode({ kind: "Const" });
|
|
389
215
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* @example
|
|
393
|
-
* ```ts
|
|
394
|
-
* const schema = createSchema({ type: 'string' })
|
|
395
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
|
|
396
|
-
* ```
|
|
216
|
+
* Definition for the {@link TypeNode}.
|
|
397
217
|
*/
|
|
398
|
-
|
|
399
|
-
return node?.type === type ? node : void 0;
|
|
400
|
-
}
|
|
401
|
-
function isKind(kind) {
|
|
402
|
-
return (node) => node.kind === kind;
|
|
403
|
-
}
|
|
218
|
+
const typeDef = defineNode({ kind: "Type" });
|
|
404
219
|
/**
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* @example
|
|
408
|
-
* ```ts
|
|
409
|
-
* if (isInputNode(node)) {
|
|
410
|
-
* console.log(node.schemas.length)
|
|
411
|
-
* }
|
|
412
|
-
* ```
|
|
220
|
+
* Definition for the {@link FunctionNode}.
|
|
413
221
|
*/
|
|
414
|
-
const
|
|
222
|
+
const functionDef = defineNode({ kind: "Function" });
|
|
415
223
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* @example
|
|
419
|
-
* ```ts
|
|
420
|
-
* if (isOutputNode(node)) {
|
|
421
|
-
* console.log(node.files.length)
|
|
422
|
-
* }
|
|
423
|
-
* ```
|
|
224
|
+
* Definition for the {@link ArrowFunctionNode}.
|
|
424
225
|
*/
|
|
425
|
-
const
|
|
226
|
+
const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
|
|
426
227
|
/**
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
* @example
|
|
430
|
-
* ```ts
|
|
431
|
-
* if (isOperationNode(node)) {
|
|
432
|
-
* console.log(node.operationId)
|
|
433
|
-
* }
|
|
434
|
-
* ```
|
|
228
|
+
* Definition for the {@link TextNode}.
|
|
435
229
|
*/
|
|
436
|
-
const
|
|
230
|
+
const textDef = defineNode({
|
|
231
|
+
kind: "Text",
|
|
232
|
+
build: (value) => ({ value })
|
|
233
|
+
});
|
|
437
234
|
/**
|
|
438
|
-
*
|
|
235
|
+
* Definition for the {@link BreakNode}.
|
|
236
|
+
*/
|
|
237
|
+
const breakDef = defineNode({
|
|
238
|
+
kind: "Break",
|
|
239
|
+
build: () => ({})
|
|
240
|
+
});
|
|
241
|
+
/**
|
|
242
|
+
* Definition for the {@link JsxNode}.
|
|
243
|
+
*/
|
|
244
|
+
const jsxDef = defineNode({
|
|
245
|
+
kind: "Jsx",
|
|
246
|
+
build: (value) => ({ value })
|
|
247
|
+
});
|
|
248
|
+
/**
|
|
249
|
+
* Creates a `ConstNode` representing a TypeScript `const` declaration.
|
|
439
250
|
*
|
|
440
|
-
* @example
|
|
251
|
+
* @example Exported constant with type and `as const`
|
|
441
252
|
* ```ts
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* }
|
|
253
|
+
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
254
|
+
* // export const pets: Pet[] = ... as const
|
|
445
255
|
* ```
|
|
446
256
|
*/
|
|
447
|
-
const
|
|
448
|
-
isKind("Property");
|
|
449
|
-
isKind("Parameter");
|
|
450
|
-
isKind("Response");
|
|
451
|
-
isKind("FunctionParameter");
|
|
452
|
-
isKind("ParameterGroup");
|
|
453
|
-
isKind("FunctionParameters");
|
|
454
|
-
//#endregion
|
|
455
|
-
//#region src/refs.ts
|
|
257
|
+
const createConst = constDef.create;
|
|
456
258
|
/**
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
* Example: `#/components/schemas/Pet` becomes `Pet`.
|
|
259
|
+
* Creates a `TypeNode` representing a TypeScript `type` alias declaration.
|
|
460
260
|
*
|
|
461
261
|
* @example
|
|
462
262
|
* ```ts
|
|
463
|
-
*
|
|
263
|
+
* createType({ name: 'Pet', export: true })
|
|
264
|
+
* // export type Pet = ...
|
|
464
265
|
* ```
|
|
465
266
|
*/
|
|
466
|
-
|
|
467
|
-
return ref.split("/").at(-1) ?? ref;
|
|
468
|
-
}
|
|
469
|
-
//#endregion
|
|
470
|
-
//#region src/visitor.ts
|
|
267
|
+
const createType = typeDef.create;
|
|
471
268
|
/**
|
|
472
|
-
* Creates a
|
|
473
|
-
*
|
|
474
|
-
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
269
|
+
* Creates a `FunctionNode` representing a TypeScript `function` declaration.
|
|
475
270
|
*
|
|
476
271
|
* @example
|
|
477
272
|
* ```ts
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* await limit(() => task())
|
|
481
|
-
* }
|
|
482
|
-
* // only 2 tasks run at the same time
|
|
273
|
+
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
274
|
+
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
483
275
|
* ```
|
|
484
276
|
*/
|
|
485
|
-
|
|
486
|
-
let active = 0;
|
|
487
|
-
const queue = [];
|
|
488
|
-
function next() {
|
|
489
|
-
if (active < concurrency && queue.length > 0) {
|
|
490
|
-
active++;
|
|
491
|
-
queue.shift()();
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return function limit(fn) {
|
|
495
|
-
return new Promise((resolve, reject) => {
|
|
496
|
-
queue.push(() => {
|
|
497
|
-
Promise.resolve(fn()).then(resolve, reject).finally(() => {
|
|
498
|
-
active--;
|
|
499
|
-
next();
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
next();
|
|
503
|
-
});
|
|
504
|
-
};
|
|
505
|
-
}
|
|
277
|
+
const createFunction = functionDef.create;
|
|
506
278
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
|
|
510
|
-
* `additionalProperties`) are only included
|
|
511
|
-
* when `recurse` is `true`; shallow mode skips them.
|
|
279
|
+
* Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
|
|
512
280
|
*
|
|
513
281
|
* @example
|
|
514
282
|
* ```ts
|
|
515
|
-
*
|
|
516
|
-
* //
|
|
283
|
+
* createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
|
|
284
|
+
* // export const double = (n: number) => ...
|
|
517
285
|
* ```
|
|
518
286
|
*/
|
|
519
|
-
|
|
520
|
-
switch (node.kind) {
|
|
521
|
-
case "Input": return [...node.schemas, ...node.operations];
|
|
522
|
-
case "Output": return [];
|
|
523
|
-
case "Operation": return [
|
|
524
|
-
...node.parameters,
|
|
525
|
-
...node.requestBody?.content?.flatMap((c) => c.schema ? [c.schema] : []) ?? [],
|
|
526
|
-
...node.responses
|
|
527
|
-
];
|
|
528
|
-
case "Schema": {
|
|
529
|
-
const children = [];
|
|
530
|
-
if (!recurse) return [];
|
|
531
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
532
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
533
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
534
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
535
|
-
return children;
|
|
536
|
-
}
|
|
537
|
-
case "Property": return [node.schema];
|
|
538
|
-
case "Parameter": return [node.schema];
|
|
539
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
540
|
-
case "FunctionParameter":
|
|
541
|
-
case "ParameterGroup":
|
|
542
|
-
case "FunctionParameters":
|
|
543
|
-
case "Type": return [];
|
|
544
|
-
default: return [];
|
|
545
|
-
}
|
|
546
|
-
}
|
|
287
|
+
const createArrowFunction = arrowFunctionDef.create;
|
|
547
288
|
/**
|
|
548
|
-
*
|
|
549
|
-
* Sibling nodes at each level are visited concurrently up to `options.concurrency`
|
|
550
|
-
* (default: `WALK_CONCURRENCY`).
|
|
289
|
+
* Creates a {@link TextNode} representing a raw string fragment in the source output.
|
|
551
290
|
*
|
|
552
291
|
* @example
|
|
553
292
|
* ```ts
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
* console.log(node.operationId)
|
|
557
|
-
* },
|
|
558
|
-
* })
|
|
293
|
+
* createText('return fetch(id)')
|
|
294
|
+
* // { kind: 'Text', value: 'return fetch(id)' }
|
|
559
295
|
* ```
|
|
296
|
+
*/
|
|
297
|
+
const createText = textDef.create;
|
|
298
|
+
/**
|
|
299
|
+
* Creates a {@link BreakNode} representing a line break in the source output.
|
|
560
300
|
*
|
|
561
301
|
* @example
|
|
562
302
|
* ```ts
|
|
563
|
-
*
|
|
564
|
-
*
|
|
303
|
+
* createBreak()
|
|
304
|
+
* // { kind: 'Break' }
|
|
565
305
|
* ```
|
|
566
306
|
*/
|
|
567
|
-
|
|
568
|
-
return
|
|
569
|
-
}
|
|
570
|
-
async function _walk(node, visitor, recurse, limit, parent) {
|
|
571
|
-
switch (node.kind) {
|
|
572
|
-
case "Input":
|
|
573
|
-
await limit(() => visitor.input?.(node, { parent }));
|
|
574
|
-
break;
|
|
575
|
-
case "Output":
|
|
576
|
-
await limit(() => visitor.output?.(node, { parent }));
|
|
577
|
-
break;
|
|
578
|
-
case "Operation":
|
|
579
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
580
|
-
break;
|
|
581
|
-
case "Schema":
|
|
582
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
583
|
-
break;
|
|
584
|
-
case "Property":
|
|
585
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
586
|
-
break;
|
|
587
|
-
case "Parameter":
|
|
588
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
589
|
-
break;
|
|
590
|
-
case "Response":
|
|
591
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
592
|
-
break;
|
|
593
|
-
case "FunctionParameter":
|
|
594
|
-
case "ParameterGroup":
|
|
595
|
-
case "FunctionParameters": break;
|
|
596
|
-
}
|
|
597
|
-
const children = getChildren(node, recurse);
|
|
598
|
-
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
599
|
-
}
|
|
600
|
-
function transform(node, options) {
|
|
601
|
-
const { depth, parent, ...visitor } = options;
|
|
602
|
-
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
603
|
-
switch (node.kind) {
|
|
604
|
-
case "Input": {
|
|
605
|
-
let input = node;
|
|
606
|
-
const replaced = visitor.input?.(input, { parent });
|
|
607
|
-
if (replaced) input = replaced;
|
|
608
|
-
return {
|
|
609
|
-
...input,
|
|
610
|
-
schemas: input.schemas.map((s) => transform(s, {
|
|
611
|
-
...options,
|
|
612
|
-
parent: input
|
|
613
|
-
})),
|
|
614
|
-
operations: input.operations.map((op) => transform(op, {
|
|
615
|
-
...options,
|
|
616
|
-
parent: input
|
|
617
|
-
}))
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
case "Output": {
|
|
621
|
-
let output = node;
|
|
622
|
-
const replaced = visitor.output?.(output, { parent });
|
|
623
|
-
if (replaced) output = replaced;
|
|
624
|
-
return output;
|
|
625
|
-
}
|
|
626
|
-
case "Operation": {
|
|
627
|
-
let op = node;
|
|
628
|
-
const replaced = visitor.operation?.(op, { parent });
|
|
629
|
-
if (replaced) op = replaced;
|
|
630
|
-
return {
|
|
631
|
-
...op,
|
|
632
|
-
parameters: op.parameters.map((p) => transform(p, {
|
|
633
|
-
...options,
|
|
634
|
-
parent: op
|
|
635
|
-
})),
|
|
636
|
-
requestBody: op.requestBody ? {
|
|
637
|
-
...op.requestBody,
|
|
638
|
-
content: op.requestBody.content?.map((c) => ({
|
|
639
|
-
...c,
|
|
640
|
-
schema: c.schema ? transform(c.schema, {
|
|
641
|
-
...options,
|
|
642
|
-
parent: op
|
|
643
|
-
}) : void 0
|
|
644
|
-
}))
|
|
645
|
-
} : void 0,
|
|
646
|
-
responses: op.responses.map((r) => transform(r, {
|
|
647
|
-
...options,
|
|
648
|
-
parent: op
|
|
649
|
-
}))
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
case "Schema": {
|
|
653
|
-
let schema = node;
|
|
654
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
655
|
-
if (replaced) schema = replaced;
|
|
656
|
-
const childOptions = {
|
|
657
|
-
...options,
|
|
658
|
-
parent: schema
|
|
659
|
-
};
|
|
660
|
-
return {
|
|
661
|
-
...schema,
|
|
662
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
663
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
664
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
665
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
case "Property": {
|
|
669
|
-
let prop = node;
|
|
670
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
671
|
-
if (replaced) prop = replaced;
|
|
672
|
-
return createProperty({
|
|
673
|
-
...prop,
|
|
674
|
-
schema: transform(prop.schema, {
|
|
675
|
-
...options,
|
|
676
|
-
parent: prop
|
|
677
|
-
})
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
case "Parameter": {
|
|
681
|
-
let param = node;
|
|
682
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
683
|
-
if (replaced) param = replaced;
|
|
684
|
-
return createParameter({
|
|
685
|
-
...param,
|
|
686
|
-
schema: transform(param.schema, {
|
|
687
|
-
...options,
|
|
688
|
-
parent: param
|
|
689
|
-
})
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
case "Response": {
|
|
693
|
-
let response = node;
|
|
694
|
-
const replaced = visitor.response?.(response, { parent });
|
|
695
|
-
if (replaced) response = replaced;
|
|
696
|
-
return {
|
|
697
|
-
...response,
|
|
698
|
-
schema: transform(response.schema, {
|
|
699
|
-
...options,
|
|
700
|
-
parent: response
|
|
701
|
-
})
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
case "FunctionParameter":
|
|
705
|
-
case "ParameterGroup":
|
|
706
|
-
case "FunctionParameters":
|
|
707
|
-
case "Type": return node;
|
|
708
|
-
default: return node;
|
|
709
|
-
}
|
|
307
|
+
function createBreak() {
|
|
308
|
+
return breakDef.create();
|
|
710
309
|
}
|
|
711
310
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
715
|
-
*
|
|
716
|
-
* @example
|
|
717
|
-
* ```ts
|
|
718
|
-
* const ids = collect(root, {
|
|
719
|
-
* operation(node) {
|
|
720
|
-
* return node.operationId
|
|
721
|
-
* },
|
|
722
|
-
* })
|
|
723
|
-
* ```
|
|
311
|
+
* Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
|
|
724
312
|
*
|
|
725
313
|
* @example
|
|
726
314
|
* ```ts
|
|
727
|
-
*
|
|
728
|
-
*
|
|
315
|
+
* createJsx('<>\n <a href={href}>Open</a>\n</>')
|
|
316
|
+
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
729
317
|
* ```
|
|
730
318
|
*/
|
|
731
|
-
|
|
732
|
-
const { depth, parent, ...visitor } = options;
|
|
733
|
-
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
734
|
-
const results = [];
|
|
735
|
-
let v;
|
|
736
|
-
switch (node.kind) {
|
|
737
|
-
case "Input":
|
|
738
|
-
v = visitor.input?.(node, { parent });
|
|
739
|
-
break;
|
|
740
|
-
case "Output":
|
|
741
|
-
v = visitor.output?.(node, { parent });
|
|
742
|
-
break;
|
|
743
|
-
case "Operation":
|
|
744
|
-
v = visitor.operation?.(node, { parent });
|
|
745
|
-
break;
|
|
746
|
-
case "Schema":
|
|
747
|
-
v = visitor.schema?.(node, { parent });
|
|
748
|
-
break;
|
|
749
|
-
case "Property":
|
|
750
|
-
v = visitor.property?.(node, { parent });
|
|
751
|
-
break;
|
|
752
|
-
case "Parameter":
|
|
753
|
-
v = visitor.parameter?.(node, { parent });
|
|
754
|
-
break;
|
|
755
|
-
case "Response":
|
|
756
|
-
v = visitor.response?.(node, { parent });
|
|
757
|
-
break;
|
|
758
|
-
case "FunctionParameter":
|
|
759
|
-
case "ParameterGroup":
|
|
760
|
-
case "FunctionParameters": break;
|
|
761
|
-
}
|
|
762
|
-
if (v !== void 0) results.push(v);
|
|
763
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
764
|
-
...options,
|
|
765
|
-
parent: node
|
|
766
|
-
})) results.push(item);
|
|
767
|
-
return results;
|
|
768
|
-
}
|
|
319
|
+
const createJsx = jsxDef.create;
|
|
769
320
|
//#endregion
|
|
770
|
-
//#region src/
|
|
771
|
-
const plainStringTypes = new Set([
|
|
772
|
-
"string",
|
|
773
|
-
"uuid",
|
|
774
|
-
"email",
|
|
775
|
-
"url",
|
|
776
|
-
"datetime"
|
|
777
|
-
]);
|
|
321
|
+
//#region src/nodes/content.ts
|
|
778
322
|
/**
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
* Usage-site fields (`description`, `readOnly`, `nullable`, `deprecated`) on the ref node
|
|
782
|
-
* override the same fields in the resolved `node.schema`. Non-ref nodes are returned unchanged.
|
|
783
|
-
*
|
|
784
|
-
* @example
|
|
785
|
-
* ```ts
|
|
786
|
-
* // Ref with description override
|
|
787
|
-
* const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A cute pet' })
|
|
788
|
-
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
789
|
-
* ```
|
|
323
|
+
* Definition for the {@link ContentNode}.
|
|
790
324
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
796
|
-
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
797
|
-
return createSchema({
|
|
798
|
-
...ref.schema,
|
|
799
|
-
...definedOverrides
|
|
800
|
-
});
|
|
801
|
-
}
|
|
325
|
+
const contentDef = defineNode({
|
|
326
|
+
kind: "Content",
|
|
327
|
+
children: ["schema"]
|
|
328
|
+
});
|
|
802
329
|
/**
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
* Covers `string`, `uuid`, `email`, `url`, and `datetime` types. For `date` and `time`
|
|
806
|
-
* types, returns `true` only when `representation` is `'string'` rather than `'date'`.
|
|
330
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
807
331
|
*/
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
if (temporal) return temporal.representation !== "date";
|
|
812
|
-
return false;
|
|
813
|
-
}
|
|
332
|
+
const createContent = contentDef.create;
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region ../../internals/utils/src/fs.ts
|
|
814
335
|
/**
|
|
815
|
-
*
|
|
336
|
+
* Strips the file extension from a path or file name.
|
|
337
|
+
* Only removes the last `.ext` segment when the dot is not part of a directory name.
|
|
816
338
|
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
339
|
+
* @example
|
|
340
|
+
* trimExtName('petStore.ts') // 'petStore'
|
|
341
|
+
* trimExtName('/src/models/pet.ts') // '/src/models/pet'
|
|
342
|
+
* trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
|
|
343
|
+
* trimExtName('noExtension') // 'noExtension'
|
|
820
344
|
*/
|
|
821
|
-
function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return {
|
|
826
|
-
...param,
|
|
827
|
-
name: transformed
|
|
828
|
-
};
|
|
829
|
-
});
|
|
345
|
+
function trimExtName(text) {
|
|
346
|
+
const dotIndex = text.lastIndexOf(".");
|
|
347
|
+
if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
|
|
348
|
+
return text;
|
|
830
349
|
}
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region ../../internals/utils/src/promise.ts
|
|
831
352
|
/**
|
|
832
|
-
*
|
|
353
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
833
354
|
*
|
|
834
|
-
*
|
|
355
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
356
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
357
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
358
|
+
* inner (created once per outer miss) keyed by the second.
|
|
359
|
+
*
|
|
360
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
361
|
+
* cleared independently of the memoized function.
|
|
362
|
+
*
|
|
363
|
+
* @example Single WeakMap key
|
|
364
|
+
* ```ts
|
|
365
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
366
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
367
|
+
* ```
|
|
368
|
+
*
|
|
369
|
+
* @example Single Map key (primitive)
|
|
370
|
+
* ```ts
|
|
371
|
+
* const cache = new Map<string, Resolver>()
|
|
372
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
373
|
+
* ```
|
|
374
|
+
*
|
|
375
|
+
* @example Two-level (object + primitive)
|
|
835
376
|
* ```ts
|
|
836
|
-
*
|
|
837
|
-
*
|
|
377
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
378
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
379
|
+
* fn(params)('camelcase')
|
|
838
380
|
* ```
|
|
839
381
|
*/
|
|
840
|
-
function
|
|
841
|
-
return
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
schema: createSchema({
|
|
847
|
-
type: "enum",
|
|
848
|
-
primitive: "string",
|
|
849
|
-
enumValues: [value]
|
|
850
|
-
}),
|
|
851
|
-
required: true
|
|
852
|
-
})]
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
function resolveParamsType({ node, param, resolver }) {
|
|
856
|
-
if (!resolver) return createParamsType({
|
|
857
|
-
variant: "reference",
|
|
858
|
-
name: param.schema.primitive ?? "unknown"
|
|
859
|
-
});
|
|
860
|
-
const individualName = resolver.resolveParamName(node, param);
|
|
861
|
-
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
862
|
-
const groupResolvers = {
|
|
863
|
-
path: resolver.resolvePathParamsName,
|
|
864
|
-
query: resolver.resolveQueryParamsName,
|
|
865
|
-
header: resolver.resolveHeaderParamsName
|
|
382
|
+
function memoize(store, factory) {
|
|
383
|
+
return (key) => {
|
|
384
|
+
if (store.has(key)) return store.get(key);
|
|
385
|
+
const value = factory(key);
|
|
386
|
+
store.set(key, value);
|
|
387
|
+
return value;
|
|
866
388
|
};
|
|
867
|
-
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
868
|
-
if (groupName && groupName !== individualName) return createParamsType({
|
|
869
|
-
variant: "member",
|
|
870
|
-
base: groupName,
|
|
871
|
-
key: param.name
|
|
872
|
-
});
|
|
873
|
-
return createParamsType({
|
|
874
|
-
variant: "reference",
|
|
875
|
-
name: individualName
|
|
876
|
-
});
|
|
877
389
|
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/utils/extractStringsFromNodes.ts
|
|
878
392
|
/**
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
* and
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const pathName = paramNames?.path ?? "pathParams";
|
|
892
|
-
const wrapType = (type) => createParamsType({
|
|
893
|
-
variant: "reference",
|
|
894
|
-
name: typeWrapper ? typeWrapper(type) : type
|
|
895
|
-
});
|
|
896
|
-
const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
|
|
897
|
-
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
898
|
-
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
899
|
-
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
900
|
-
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
901
|
-
const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
902
|
-
const bodyRequired = node.requestBody?.required ?? false;
|
|
903
|
-
const queryGroupType = resolver ? resolveGroupType({
|
|
904
|
-
node,
|
|
905
|
-
params: queryParams,
|
|
906
|
-
groupMethod: resolver.resolveQueryParamsName,
|
|
907
|
-
resolver
|
|
908
|
-
}) : void 0;
|
|
909
|
-
const headerGroupType = resolver ? resolveGroupType({
|
|
910
|
-
node,
|
|
911
|
-
params: headerParams,
|
|
912
|
-
groupMethod: resolver.resolveHeaderParamsName,
|
|
913
|
-
resolver
|
|
914
|
-
}) : void 0;
|
|
915
|
-
const params = [];
|
|
916
|
-
if (paramsType === "object") {
|
|
917
|
-
const children = [
|
|
918
|
-
...pathParams.map((p) => {
|
|
919
|
-
const type = resolveParamsType({
|
|
920
|
-
node,
|
|
921
|
-
param: p,
|
|
922
|
-
resolver
|
|
923
|
-
});
|
|
924
|
-
return createFunctionParameter({
|
|
925
|
-
name: p.name,
|
|
926
|
-
type: wrapTypeNode(type),
|
|
927
|
-
optional: !p.required
|
|
928
|
-
});
|
|
929
|
-
}),
|
|
930
|
-
...bodyType ? [createFunctionParameter({
|
|
931
|
-
name: dataName,
|
|
932
|
-
type: bodyType,
|
|
933
|
-
optional: !bodyRequired
|
|
934
|
-
})] : [],
|
|
935
|
-
...buildGroupParam({
|
|
936
|
-
name: paramsName,
|
|
937
|
-
node,
|
|
938
|
-
params: queryParams,
|
|
939
|
-
groupType: queryGroupType,
|
|
940
|
-
resolver,
|
|
941
|
-
wrapType
|
|
942
|
-
}),
|
|
943
|
-
...buildGroupParam({
|
|
944
|
-
name: headersName,
|
|
945
|
-
node,
|
|
946
|
-
params: headerParams,
|
|
947
|
-
groupType: headerGroupType,
|
|
948
|
-
resolver,
|
|
949
|
-
wrapType
|
|
950
|
-
})
|
|
951
|
-
];
|
|
952
|
-
if (children.length) params.push(createParameterGroup({
|
|
953
|
-
properties: children,
|
|
954
|
-
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
955
|
-
}));
|
|
956
|
-
} else {
|
|
957
|
-
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
958
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
|
|
959
|
-
params.push(createFunctionParameter({
|
|
960
|
-
name: pathName,
|
|
961
|
-
type: spreadType ? wrapType(spreadType) : void 0,
|
|
962
|
-
rest: true
|
|
963
|
-
}));
|
|
964
|
-
} else {
|
|
965
|
-
const pathChildren = pathParams.map((p) => {
|
|
966
|
-
const type = resolveParamsType({
|
|
967
|
-
node,
|
|
968
|
-
param: p,
|
|
969
|
-
resolver
|
|
970
|
-
});
|
|
971
|
-
return createFunctionParameter({
|
|
972
|
-
name: p.name,
|
|
973
|
-
type: wrapTypeNode(type),
|
|
974
|
-
optional: !p.required
|
|
975
|
-
});
|
|
976
|
-
});
|
|
977
|
-
params.push(createParameterGroup({
|
|
978
|
-
properties: pathChildren,
|
|
979
|
-
inline: pathParamsType === "inline",
|
|
980
|
-
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
981
|
-
}));
|
|
393
|
+
* Extracts all string content from a `CodeNode` tree recursively.
|
|
394
|
+
*
|
|
395
|
+
* Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
|
|
396
|
+
* and nested node content. Used to build the full source string for import filtering.
|
|
397
|
+
*/
|
|
398
|
+
function extractStringsFromNodes(nodes) {
|
|
399
|
+
if (!nodes?.length) return "";
|
|
400
|
+
const collected = [];
|
|
401
|
+
for (const node of nodes) {
|
|
402
|
+
if (typeof node === "string") {
|
|
403
|
+
if (node) collected.push(node);
|
|
404
|
+
continue;
|
|
982
405
|
}
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
groupType: headerGroupType,
|
|
1001
|
-
resolver,
|
|
1002
|
-
wrapType
|
|
1003
|
-
}));
|
|
406
|
+
if (node.kind === "Text") {
|
|
407
|
+
if (node.value) collected.push(node.value);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (node.kind === "Break") continue;
|
|
411
|
+
if (node.kind === "Jsx") {
|
|
412
|
+
if (node.value) collected.push(node.value);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const parts = [];
|
|
416
|
+
if ("params" in node && node.params) parts.push(node.params);
|
|
417
|
+
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
418
|
+
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
419
|
+
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
420
|
+
const nested = extractStringsFromNodes(node.nodes);
|
|
421
|
+
if (nested) parts.push(nested);
|
|
422
|
+
if (parts.length) collected.push(parts.join("\n"));
|
|
1004
423
|
}
|
|
1005
|
-
|
|
1006
|
-
return createFunctionParameters({ params });
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
1010
|
-
* Returns an empty array when there are no params to emit.
|
|
1011
|
-
*
|
|
1012
|
-
* If a pre-resolved `groupType` is provided it emits `name: GroupType`.
|
|
1013
|
-
* Otherwise, it builds an inline struct from the individual params.
|
|
1014
|
-
*/
|
|
1015
|
-
function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
|
|
1016
|
-
if (groupType) return [createFunctionParameter({
|
|
1017
|
-
name,
|
|
1018
|
-
type: groupType.type.kind === "ParamsType" && groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
1019
|
-
optional: groupType.optional
|
|
1020
|
-
})];
|
|
1021
|
-
if (params.length) return [createFunctionParameter({
|
|
1022
|
-
name,
|
|
1023
|
-
type: toStructType({
|
|
1024
|
-
node,
|
|
1025
|
-
params,
|
|
1026
|
-
resolver
|
|
1027
|
-
}),
|
|
1028
|
-
optional: params.every((p) => !p.required)
|
|
1029
|
-
})];
|
|
1030
|
-
return [];
|
|
1031
|
-
}
|
|
1032
|
-
/**
|
|
1033
|
-
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1034
|
-
* Returns `undefined` when the group name equals the individual param name (no real group).
|
|
1035
|
-
*/
|
|
1036
|
-
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1037
|
-
if (!params.length) return;
|
|
1038
|
-
const firstParam = params[0];
|
|
1039
|
-
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1040
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1041
|
-
const allOptional = params.every((p) => !p.required);
|
|
1042
|
-
return {
|
|
1043
|
-
type: createParamsType({
|
|
1044
|
-
variant: "reference",
|
|
1045
|
-
name: groupName
|
|
1046
|
-
}),
|
|
1047
|
-
optional: allOptional
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
|
|
1052
|
-
*
|
|
1053
|
-
* Used when query or header parameters have no dedicated group type name.
|
|
1054
|
-
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
1055
|
-
*/
|
|
1056
|
-
function toStructType({ node, params, resolver }) {
|
|
1057
|
-
return createParamsType({
|
|
1058
|
-
variant: "struct",
|
|
1059
|
-
properties: params.map((p) => ({
|
|
1060
|
-
name: p.name,
|
|
1061
|
-
optional: !p.required,
|
|
1062
|
-
type: resolveParamsType({
|
|
1063
|
-
node,
|
|
1064
|
-
param: p,
|
|
1065
|
-
resolver
|
|
1066
|
-
})
|
|
1067
|
-
}))
|
|
1068
|
-
});
|
|
424
|
+
return collected.join("\n");
|
|
1069
425
|
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/utils/combineFileMembers.ts
|
|
1070
428
|
function sourceKey(source) {
|
|
1071
429
|
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
1072
430
|
}
|
|
@@ -1081,19 +439,19 @@ function importKey(path, name, isTypeOnly) {
|
|
|
1081
439
|
}
|
|
1082
440
|
/**
|
|
1083
441
|
* Computes a multi-level sort key for exports and imports:
|
|
1084
|
-
* non-array names first (wildcards/namespace aliases)
|
|
442
|
+
* non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
|
|
1085
443
|
*/
|
|
1086
444
|
function sortKey(node) {
|
|
1087
445
|
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
1088
446
|
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
1089
447
|
const hasName = node.name != null ? "1" : "0";
|
|
1090
|
-
const name = Array.isArray(node.name) ?
|
|
448
|
+
const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
|
|
1091
449
|
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
1092
450
|
}
|
|
1093
451
|
/**
|
|
1094
|
-
* Deduplicates
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
452
|
+
* Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
|
|
453
|
+
* key. Unnamed sources fall back to their extracted node strings as the name part of the key. Returns
|
|
454
|
+
* the deduplicated array in original order.
|
|
1097
455
|
*/
|
|
1098
456
|
function combineSources(sources) {
|
|
1099
457
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -1104,6 +462,16 @@ function combineSources(sources) {
|
|
|
1104
462
|
return [...seen.values()];
|
|
1105
463
|
}
|
|
1106
464
|
/**
|
|
465
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
466
|
+
*
|
|
467
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
468
|
+
*/
|
|
469
|
+
function mergeNameArrays(existing, incoming) {
|
|
470
|
+
const merged = new Set(existing);
|
|
471
|
+
for (const name of incoming) merged.add(name);
|
|
472
|
+
return [...merged];
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
1107
475
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1108
476
|
*
|
|
1109
477
|
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
@@ -1124,11 +492,8 @@ function combineExports(exports) {
|
|
|
1124
492
|
if (!name.length) continue;
|
|
1125
493
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1126
494
|
const existing = namedByPath.get(key);
|
|
1127
|
-
if (existing && Array.isArray(existing.name))
|
|
1128
|
-
|
|
1129
|
-
for (const n of name) merged.add(n);
|
|
1130
|
-
existing.name = [...merged];
|
|
1131
|
-
} else {
|
|
495
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
496
|
+
else {
|
|
1132
497
|
const newItem = {
|
|
1133
498
|
...curr,
|
|
1134
499
|
name: [...new Set(name)]
|
|
@@ -1151,12 +516,22 @@ function combineExports(exports) {
|
|
|
1151
516
|
*
|
|
1152
517
|
* Retains imports that are referenced in `source` or re-exported. Imports with the same path and
|
|
1153
518
|
* `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
|
|
1154
|
-
*
|
|
1155
|
-
* @note Use this when combining imports from multiple files to avoid duplicate declarations.
|
|
1156
519
|
*/
|
|
1157
520
|
function combineImports(imports, exports, source) {
|
|
1158
521
|
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
1159
522
|
const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
|
|
523
|
+
const importNameMemo = /* @__PURE__ */ new Map();
|
|
524
|
+
const canonicalizeName = (n) => {
|
|
525
|
+
if (typeof n === "string") return n;
|
|
526
|
+
const key = `${n.propertyName}:${n.name ?? ""}`;
|
|
527
|
+
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
528
|
+
return importNameMemo.get(key);
|
|
529
|
+
};
|
|
530
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
531
|
+
for (const node of imports) {
|
|
532
|
+
if (!Array.isArray(node.name)) continue;
|
|
533
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
534
|
+
}
|
|
1160
535
|
const result = [];
|
|
1161
536
|
const namedByPath = /* @__PURE__ */ new Map();
|
|
1162
537
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1170,15 +545,12 @@ function combineImports(imports, exports, source) {
|
|
|
1170
545
|
const { path, isTypeOnly } = curr;
|
|
1171
546
|
let { name } = curr;
|
|
1172
547
|
if (Array.isArray(name)) {
|
|
1173
|
-
name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.propertyName));
|
|
548
|
+
name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
|
|
1174
549
|
if (!name.length) continue;
|
|
1175
550
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1176
551
|
const existing = namedByPath.get(key);
|
|
1177
|
-
if (existing && Array.isArray(existing.name))
|
|
1178
|
-
|
|
1179
|
-
for (const n of name) merged.add(n);
|
|
1180
|
-
existing.name = [...merged];
|
|
1181
|
-
} else {
|
|
552
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
553
|
+
else {
|
|
1182
554
|
const newItem = {
|
|
1183
555
|
...curr,
|
|
1184
556
|
name
|
|
@@ -1187,7 +559,7 @@ function combineImports(imports, exports, source) {
|
|
|
1187
559
|
namedByPath.set(key, newItem);
|
|
1188
560
|
}
|
|
1189
561
|
} else {
|
|
1190
|
-
if (name && !isUsed(name)) continue;
|
|
562
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1191
563
|
const key = importKey(path, name, isTypeOnly);
|
|
1192
564
|
if (!seen.has(key)) {
|
|
1193
565
|
result.push(curr);
|
|
@@ -1197,284 +569,266 @@ function combineImports(imports, exports, source) {
|
|
|
1197
569
|
}
|
|
1198
570
|
return result;
|
|
1199
571
|
}
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region src/nodes/file.ts
|
|
1200
574
|
/**
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
* Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
|
|
1204
|
-
* and nested node content. Used internally to build the full source string for import filtering.
|
|
575
|
+
* Definition for the {@link ImportNode}.
|
|
1205
576
|
*/
|
|
1206
|
-
|
|
1207
|
-
if (!nodes?.length) return "";
|
|
1208
|
-
return nodes.map((node) => {
|
|
1209
|
-
if (typeof node === "string") return node;
|
|
1210
|
-
if (node.kind === "Text") return node.value;
|
|
1211
|
-
if (node.kind === "Break") return "";
|
|
1212
|
-
if (node.kind === "Jsx") return node.value;
|
|
1213
|
-
const parts = [];
|
|
1214
|
-
if ("params" in node && node.params) parts.push(node.params);
|
|
1215
|
-
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
1216
|
-
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
1217
|
-
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
1218
|
-
const nested = extractStringsFromNodes(node.nodes);
|
|
1219
|
-
if (nested) parts.push(nested);
|
|
1220
|
-
return parts.join("\n");
|
|
1221
|
-
}).filter(Boolean).join("\n");
|
|
1222
|
-
}
|
|
577
|
+
const importDef = defineNode({ kind: "Import" });
|
|
1223
578
|
/**
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
* Returns `undefined` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1227
|
-
* identifier for type definitions or error messages.
|
|
1228
|
-
*
|
|
1229
|
-
* @example
|
|
1230
|
-
* ```ts
|
|
1231
|
-
* resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' })
|
|
1232
|
-
* // => 'Pet'
|
|
1233
|
-
* ```
|
|
579
|
+
* Definition for the {@link ExportNode}.
|
|
1234
580
|
*/
|
|
1235
|
-
|
|
1236
|
-
if (!node || node.type !== "ref") return void 0;
|
|
1237
|
-
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? void 0;
|
|
1238
|
-
return node.name ?? node.schema?.name ?? void 0;
|
|
1239
|
-
}
|
|
581
|
+
const exportDef = defineNode({ kind: "Export" });
|
|
1240
582
|
/**
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
* Refs are followed by name only — the resolved `node.schema` is not traversed inline.
|
|
1244
|
-
* Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
|
|
1245
|
-
*
|
|
1246
|
-
* @note Returns a Set of schema names for efficient membership testing.
|
|
1247
|
-
*/
|
|
1248
|
-
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1249
|
-
if (!node) return out;
|
|
1250
|
-
collect(node, { schema(child) {
|
|
1251
|
-
if (child.type === "ref") {
|
|
1252
|
-
const name = resolveRefName(child);
|
|
1253
|
-
if (name) out.add(name);
|
|
1254
|
-
}
|
|
1255
|
-
} });
|
|
1256
|
-
return out;
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
1260
|
-
*
|
|
1261
|
-
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
1262
|
-
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
1263
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1264
|
-
*
|
|
1265
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1266
|
-
*/
|
|
1267
|
-
function findCircularSchemas(schemas) {
|
|
1268
|
-
const graph = /* @__PURE__ */ new Map();
|
|
1269
|
-
for (const schema of schemas) {
|
|
1270
|
-
if (!schema.name) continue;
|
|
1271
|
-
graph.set(schema.name, collectReferencedSchemaNames(schema));
|
|
1272
|
-
}
|
|
1273
|
-
const circular = /* @__PURE__ */ new Set();
|
|
1274
|
-
for (const start of graph.keys()) {
|
|
1275
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1276
|
-
const stack = [...graph.get(start) ?? []];
|
|
1277
|
-
while (stack.length > 0) {
|
|
1278
|
-
const node = stack.pop();
|
|
1279
|
-
if (node === start) {
|
|
1280
|
-
circular.add(start);
|
|
1281
|
-
break;
|
|
1282
|
-
}
|
|
1283
|
-
if (visited.has(node)) continue;
|
|
1284
|
-
visited.add(node);
|
|
1285
|
-
const next = graph.get(node);
|
|
1286
|
-
if (next) for (const r of next) stack.push(r);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
return circular;
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
1293
|
-
*
|
|
1294
|
-
* Use `excludeName` to ignore refs to specific schemas (useful when self-references are handled separately).
|
|
1295
|
-
* Commonly used with `findCircularSchemas()` to detect where lazy wrappers are needed in code generation.
|
|
1296
|
-
*
|
|
1297
|
-
* @note Returns `true` for the first matching circular ref found; use for fast dependency checks.
|
|
583
|
+
* Definition for the {@link SourceNode}.
|
|
1298
584
|
*/
|
|
1299
|
-
|
|
1300
|
-
if (!node || circularSchemas.size === 0) return false;
|
|
1301
|
-
return collect(node, { schema(child) {
|
|
1302
|
-
if (child.type !== "ref") return void 0;
|
|
1303
|
-
const name = resolveRefName(child);
|
|
1304
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true : void 0;
|
|
1305
|
-
} }).length > 0;
|
|
1306
|
-
}
|
|
1307
|
-
//#endregion
|
|
1308
|
-
//#region src/factory.ts
|
|
585
|
+
const sourceDef = defineNode({ kind: "Source" });
|
|
1309
586
|
/**
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
* - `optional` is set for non-required, non-nullable schemas.
|
|
1313
|
-
* - `nullish` is set for non-required, nullable schemas.
|
|
587
|
+
* Definition for the {@link FileNode}. The fully resolved builder lives in
|
|
588
|
+
* `createFile`, so this definition only supplies the guard.
|
|
1314
589
|
*/
|
|
1315
|
-
|
|
1316
|
-
const nullable = schema.nullable ?? false;
|
|
1317
|
-
return {
|
|
1318
|
-
...schema,
|
|
1319
|
-
optional: !required && !nullable ? true : void 0,
|
|
1320
|
-
nullish: !required && nullable ? true : void 0
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
590
|
+
const fileDef = defineNode({ kind: "File" });
|
|
1323
591
|
/**
|
|
1324
|
-
* Creates an `
|
|
1325
|
-
*
|
|
1326
|
-
* @example
|
|
1327
|
-
* ```ts
|
|
1328
|
-
* const input = createInput()
|
|
1329
|
-
* // { kind: 'Input', schemas: [], operations: [] }
|
|
1330
|
-
* ```
|
|
592
|
+
* Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
|
|
1331
593
|
*
|
|
1332
|
-
* @example
|
|
594
|
+
* @example Named import
|
|
1333
595
|
* ```ts
|
|
1334
|
-
*
|
|
1335
|
-
* //
|
|
596
|
+
* createImport({ name: ['useState'], path: 'react' })
|
|
597
|
+
* // import { useState } from 'react'
|
|
1336
598
|
* ```
|
|
1337
599
|
*/
|
|
1338
|
-
|
|
1339
|
-
return {
|
|
1340
|
-
schemas: [],
|
|
1341
|
-
operations: [],
|
|
1342
|
-
...overrides,
|
|
1343
|
-
kind: "Input"
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
600
|
+
const createImport = importDef.create;
|
|
1346
601
|
/**
|
|
1347
|
-
* Creates an `
|
|
602
|
+
* Creates an `ExportNode` representing a language-agnostic export/public API declaration.
|
|
1348
603
|
*
|
|
1349
|
-
* @example
|
|
604
|
+
* @example Named export
|
|
1350
605
|
* ```ts
|
|
1351
|
-
*
|
|
1352
|
-
* // {
|
|
606
|
+
* createExport({ name: ['Pet'], path: './Pet' })
|
|
607
|
+
* // export { Pet } from './Pet'
|
|
1353
608
|
* ```
|
|
609
|
+
*/
|
|
610
|
+
const createExport = exportDef.create;
|
|
611
|
+
/**
|
|
612
|
+
* Creates a `SourceNode` representing a fragment of source code within a file.
|
|
1354
613
|
*
|
|
1355
614
|
* @example
|
|
1356
615
|
* ```ts
|
|
1357
|
-
*
|
|
616
|
+
* createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
|
|
1358
617
|
* ```
|
|
1359
618
|
*/
|
|
1360
|
-
|
|
1361
|
-
return {
|
|
1362
|
-
files: [],
|
|
1363
|
-
...overrides,
|
|
1364
|
-
kind: "Output"
|
|
1365
|
-
};
|
|
1366
|
-
}
|
|
619
|
+
const createSource = sourceDef.create;
|
|
1367
620
|
/**
|
|
1368
|
-
* Creates
|
|
621
|
+
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
622
|
+
*
|
|
623
|
+
* Computes:
|
|
624
|
+
* - `id` SHA256 hash of the file path
|
|
625
|
+
* - `name` `baseName` without extension
|
|
626
|
+
* - `extname` extension extracted from `baseName`
|
|
627
|
+
*
|
|
628
|
+
* Deduplicates:
|
|
629
|
+
* - `sources` via `combineSources`
|
|
630
|
+
* - `exports` via `combineExports`
|
|
631
|
+
* - `imports` via `combineImports` (also filters unused imports)
|
|
632
|
+
*
|
|
633
|
+
* @throws {Error} when `baseName` has no extension.
|
|
1369
634
|
*
|
|
1370
635
|
* @example
|
|
1371
636
|
* ```ts
|
|
1372
|
-
* const
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
637
|
+
* const file = createFile({
|
|
638
|
+
* baseName: 'petStore.ts',
|
|
639
|
+
* path: 'src/models/petStore.ts',
|
|
640
|
+
* sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
|
|
641
|
+
* imports: [createImport({ name: ['z'], path: 'zod' })],
|
|
642
|
+
* exports: [createExport({ name: ['Pet'], path: './petStore' })],
|
|
1376
643
|
* })
|
|
1377
|
-
* //
|
|
644
|
+
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
645
|
+
* // file.name = 'petStore'
|
|
646
|
+
* // file.extname = '.ts'
|
|
1378
647
|
* ```
|
|
1379
648
|
*
|
|
1380
|
-
* @example
|
|
649
|
+
* @example Copy a real file into the output verbatim
|
|
1381
650
|
* ```ts
|
|
1382
|
-
* const
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
* tags: ['pet'],
|
|
651
|
+
* const file = createFile({
|
|
652
|
+
* baseName: 'client.ts',
|
|
653
|
+
* path: 'src/gen/client.ts',
|
|
654
|
+
* copy: '/abs/path/to/templates/client.ts',
|
|
1387
655
|
* })
|
|
1388
656
|
* ```
|
|
1389
657
|
*/
|
|
1390
|
-
function
|
|
658
|
+
function createFile(input) {
|
|
659
|
+
const extname = path.extname(input.baseName);
|
|
660
|
+
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
661
|
+
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
662
|
+
const resolvedImports = (() => {
|
|
663
|
+
if (!input.imports?.length) return [];
|
|
664
|
+
const sourceParts = [];
|
|
665
|
+
const localNames = /* @__PURE__ */ new Set();
|
|
666
|
+
for (const item of input.sources ?? []) {
|
|
667
|
+
const extracted = item.nodes && extractStringsFromNodes(item.nodes);
|
|
668
|
+
if (extracted) sourceParts.push(extracted);
|
|
669
|
+
if (item.name) localNames.add(item.name);
|
|
670
|
+
}
|
|
671
|
+
const source = sourceParts.join("\n") || void 0;
|
|
672
|
+
const combinedImports = combineImports(input.imports, resolvedExports, source);
|
|
673
|
+
const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
|
|
674
|
+
return combinedImports.flatMap((imp) => {
|
|
675
|
+
if (imp.path === input.path) return [];
|
|
676
|
+
if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
|
|
677
|
+
const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
|
|
678
|
+
if (!kept.length) return [];
|
|
679
|
+
return [kept.length === imp.name.length ? imp : {
|
|
680
|
+
...imp,
|
|
681
|
+
name: kept
|
|
682
|
+
}];
|
|
683
|
+
});
|
|
684
|
+
})();
|
|
685
|
+
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
1391
686
|
return {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
687
|
+
kind: "File",
|
|
688
|
+
...input,
|
|
689
|
+
id: hash("sha256", input.path, "hex"),
|
|
690
|
+
name: trimExtName(input.baseName),
|
|
691
|
+
extname,
|
|
692
|
+
imports: resolvedImports,
|
|
693
|
+
exports: resolvedExports,
|
|
694
|
+
sources: resolvedSources,
|
|
695
|
+
meta: input.meta ?? {}
|
|
1397
696
|
};
|
|
1398
697
|
}
|
|
698
|
+
//#endregion
|
|
699
|
+
//#region src/nodes/input.ts
|
|
1399
700
|
/**
|
|
1400
|
-
*
|
|
1401
|
-
* Primitive types map to themselves; special string formats map to `'string'`.
|
|
1402
|
-
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
701
|
+
* Definition for the {@link InputNode}.
|
|
1403
702
|
*/
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
date: "date",
|
|
1418
|
-
uuid: "string",
|
|
1419
|
-
email: "string",
|
|
1420
|
-
url: "string",
|
|
1421
|
-
datetime: "string",
|
|
1422
|
-
time: "string"
|
|
1423
|
-
};
|
|
1424
|
-
function createSchema(props) {
|
|
1425
|
-
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
1426
|
-
if (props["type"] === "object") return {
|
|
1427
|
-
properties: [],
|
|
1428
|
-
primitive: "object",
|
|
1429
|
-
...props,
|
|
1430
|
-
kind: "Schema"
|
|
1431
|
-
};
|
|
1432
|
-
return {
|
|
1433
|
-
primitive: inferredPrimitive,
|
|
1434
|
-
...props,
|
|
1435
|
-
kind: "Schema"
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
703
|
+
const inputDef = defineNode({
|
|
704
|
+
kind: "Input",
|
|
705
|
+
defaults: {
|
|
706
|
+
schemas: [],
|
|
707
|
+
operations: [],
|
|
708
|
+
meta: {
|
|
709
|
+
circularNames: [],
|
|
710
|
+
enumNames: []
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
children: ["schemas", "operations"],
|
|
714
|
+
visitorKey: "input"
|
|
715
|
+
});
|
|
1438
716
|
/**
|
|
1439
|
-
* Creates
|
|
1440
|
-
*
|
|
1441
|
-
* `required` defaults to `false`.
|
|
1442
|
-
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
717
|
+
* Creates an `InputNode`, defaulting `schemas`/`operations` to empty arrays and `meta` per
|
|
718
|
+
* {@link inputDef}.
|
|
1443
719
|
*
|
|
1444
720
|
* @example
|
|
1445
721
|
* ```ts
|
|
1446
|
-
* const
|
|
1447
|
-
*
|
|
1448
|
-
* schema: createSchema({ type: 'string' }),
|
|
1449
|
-
* })
|
|
1450
|
-
* // required=false, schema.optional=true
|
|
722
|
+
* const input = createInput()
|
|
723
|
+
* // { kind: 'Input', schemas: [], operations: [] }
|
|
1451
724
|
* ```
|
|
725
|
+
*/
|
|
726
|
+
function createInput(overrides = {}) {
|
|
727
|
+
return inputDef.create(overrides);
|
|
728
|
+
}
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region src/nodes/requestBody.ts
|
|
731
|
+
/**
|
|
732
|
+
* Definition for the {@link RequestBodyNode}. Content entries are built upfront with
|
|
733
|
+
* {@link createContent}, mirroring how `parameters` and `responses` take prebuilt nodes.
|
|
734
|
+
*/
|
|
735
|
+
const requestBodyDef = defineNode({
|
|
736
|
+
kind: "RequestBody",
|
|
737
|
+
children: ["content"]
|
|
738
|
+
});
|
|
739
|
+
/**
|
|
740
|
+
* Creates a `RequestBodyNode`.
|
|
741
|
+
*/
|
|
742
|
+
const createRequestBody = requestBodyDef.create;
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/nodes/operation.ts
|
|
745
|
+
/**
|
|
746
|
+
* Definition for the {@link OperationNode}. HTTP operations (those carrying both
|
|
747
|
+
* `method` and `path`) are tagged with `protocol: 'http'`, and the request body is
|
|
748
|
+
* normalized into a `RequestBodyNode`.
|
|
749
|
+
*/
|
|
750
|
+
const operationDef = defineNode({
|
|
751
|
+
kind: "Operation",
|
|
752
|
+
build: (props) => {
|
|
753
|
+
const { requestBody, ...rest } = props;
|
|
754
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
755
|
+
return {
|
|
756
|
+
tags: [],
|
|
757
|
+
parameters: [],
|
|
758
|
+
responses: [],
|
|
759
|
+
...rest,
|
|
760
|
+
...isHttp ? { protocol: "http" } : {},
|
|
761
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
762
|
+
};
|
|
763
|
+
},
|
|
764
|
+
children: [
|
|
765
|
+
"parameters",
|
|
766
|
+
"requestBody",
|
|
767
|
+
"responses"
|
|
768
|
+
],
|
|
769
|
+
visitorKey: "operation"
|
|
770
|
+
});
|
|
771
|
+
function createOperation(props) {
|
|
772
|
+
return operationDef.create(props);
|
|
773
|
+
}
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/nodes/output.ts
|
|
776
|
+
/**
|
|
777
|
+
* Definition for the {@link OutputNode}.
|
|
778
|
+
*/
|
|
779
|
+
const outputDef = defineNode({
|
|
780
|
+
kind: "Output",
|
|
781
|
+
defaults: { files: [] },
|
|
782
|
+
visitorKey: "output"
|
|
783
|
+
});
|
|
784
|
+
/**
|
|
785
|
+
* Creates an `OutputNode` with a stable default for `files`.
|
|
1452
786
|
*
|
|
1453
787
|
* @example
|
|
1454
788
|
* ```ts
|
|
1455
|
-
* const
|
|
1456
|
-
*
|
|
1457
|
-
* required: true,
|
|
1458
|
-
* schema: createSchema({ type: 'string', nullable: true }),
|
|
1459
|
-
* })
|
|
1460
|
-
* // required=true, no optional/nullish
|
|
789
|
+
* const output = createOutput()
|
|
790
|
+
* // { kind: 'Output', files: [] }
|
|
1461
791
|
* ```
|
|
1462
792
|
*/
|
|
1463
|
-
function
|
|
1464
|
-
|
|
793
|
+
function createOutput(overrides = {}) {
|
|
794
|
+
return outputDef.create(overrides);
|
|
795
|
+
}
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region src/optionality.ts
|
|
798
|
+
/**
|
|
799
|
+
* Generic JSON Schema optionality: a non-required field is optional, and a
|
|
800
|
+
* non-required nullable field is nullish.
|
|
801
|
+
*/
|
|
802
|
+
function optionality(schema, required) {
|
|
803
|
+
const nullable = schema.nullable ?? false;
|
|
1465
804
|
return {
|
|
1466
|
-
...
|
|
1467
|
-
|
|
1468
|
-
required
|
|
1469
|
-
schema: syncOptionality(props.schema, required)
|
|
805
|
+
...schema,
|
|
806
|
+
optional: !required && !nullable ? true : void 0,
|
|
807
|
+
nullish: !required && nullable ? true : void 0
|
|
1470
808
|
};
|
|
1471
809
|
}
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/nodes/parameter.ts
|
|
812
|
+
/**
|
|
813
|
+
* Definition for the {@link ParameterNode}. `required` defaults to `false`, and the schema's
|
|
814
|
+
* `optional`/`nullish` flags are derived from it through {@link optionality}.
|
|
815
|
+
*/
|
|
816
|
+
const parameterDef = defineNode({
|
|
817
|
+
kind: "Parameter",
|
|
818
|
+
build: (props) => {
|
|
819
|
+
const required = props.required ?? false;
|
|
820
|
+
return {
|
|
821
|
+
...props,
|
|
822
|
+
required,
|
|
823
|
+
schema: optionality(props.schema, required)
|
|
824
|
+
};
|
|
825
|
+
},
|
|
826
|
+
children: ["schema"],
|
|
827
|
+
visitorKey: "parameter"
|
|
828
|
+
});
|
|
1472
829
|
/**
|
|
1473
830
|
* Creates a `ParameterNode`.
|
|
1474
831
|
*
|
|
1475
|
-
* `required` defaults to `false`.
|
|
1476
|
-
* Nested schema flags are set from `required` and `schema.nullable`.
|
|
1477
|
-
*
|
|
1478
832
|
* @example
|
|
1479
833
|
* ```ts
|
|
1480
834
|
* const param = createParameter({
|
|
@@ -1484,26 +838,64 @@ function createProperty(props) {
|
|
|
1484
838
|
* schema: createSchema({ type: 'string' }),
|
|
1485
839
|
* })
|
|
1486
840
|
* ```
|
|
841
|
+
*/
|
|
842
|
+
const createParameter = parameterDef.create;
|
|
843
|
+
//#endregion
|
|
844
|
+
//#region src/nodes/property.ts
|
|
845
|
+
/**
|
|
846
|
+
* Definition for the {@link PropertyNode}. `required` defaults to `false`, and the schema's
|
|
847
|
+
* `optional`/`nullish` flags are derived from it through {@link optionality}.
|
|
848
|
+
*/
|
|
849
|
+
const propertyDef = defineNode({
|
|
850
|
+
kind: "Property",
|
|
851
|
+
build: (props) => {
|
|
852
|
+
const required = props.required ?? false;
|
|
853
|
+
return {
|
|
854
|
+
...props,
|
|
855
|
+
required,
|
|
856
|
+
schema: optionality(props.schema, required)
|
|
857
|
+
};
|
|
858
|
+
},
|
|
859
|
+
children: ["schema"],
|
|
860
|
+
visitorKey: "property"
|
|
861
|
+
});
|
|
862
|
+
/**
|
|
863
|
+
* Creates a `PropertyNode`.
|
|
1487
864
|
*
|
|
1488
865
|
* @example
|
|
1489
866
|
* ```ts
|
|
1490
|
-
* const
|
|
867
|
+
* const property = createProperty({
|
|
1491
868
|
* name: 'status',
|
|
1492
|
-
*
|
|
869
|
+
* required: true,
|
|
1493
870
|
* schema: createSchema({ type: 'string', nullable: true }),
|
|
1494
871
|
* })
|
|
1495
|
-
* // required=
|
|
872
|
+
* // required=true, no optional/nullish
|
|
1496
873
|
* ```
|
|
1497
874
|
*/
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
875
|
+
const createProperty = propertyDef.create;
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/nodes/response.ts
|
|
878
|
+
/**
|
|
879
|
+
* Definition for the {@link ResponseNode}. A single legacy `schema` (with optional
|
|
880
|
+
* `mediaType`/`keysToOmit`) is normalized into one `content` entry.
|
|
881
|
+
*/
|
|
882
|
+
const responseDef = defineNode({
|
|
883
|
+
kind: "Response",
|
|
884
|
+
build: (props) => {
|
|
885
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
886
|
+
const entries = content ?? (schema ? [createContent({
|
|
887
|
+
contentType: mediaType ?? "application/json",
|
|
888
|
+
schema,
|
|
889
|
+
keysToOmit: keysToOmit ?? null
|
|
890
|
+
})] : void 0);
|
|
891
|
+
return {
|
|
892
|
+
...rest,
|
|
893
|
+
content: entries
|
|
894
|
+
};
|
|
895
|
+
},
|
|
896
|
+
children: ["content"],
|
|
897
|
+
visitorKey: "response"
|
|
898
|
+
});
|
|
1507
899
|
/**
|
|
1508
900
|
* Creates a `ResponseNode`.
|
|
1509
901
|
*
|
|
@@ -1511,628 +903,638 @@ function createParameter(props) {
|
|
|
1511
903
|
* ```ts
|
|
1512
904
|
* const response = createResponse({
|
|
1513
905
|
* statusCode: '200',
|
|
1514
|
-
*
|
|
1515
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
906
|
+
* content: [createContent({ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) })],
|
|
1516
907
|
* })
|
|
1517
908
|
* ```
|
|
1518
909
|
*/
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
kind: "Response"
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Creates a `FunctionParameterNode`.
|
|
1527
|
-
*
|
|
1528
|
-
* `optional` defaults to `false`.
|
|
1529
|
-
*
|
|
1530
|
-
* @example Required typed param
|
|
1531
|
-
* ```ts
|
|
1532
|
-
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
|
|
1533
|
-
* // → petId: string
|
|
1534
|
-
* ```
|
|
1535
|
-
*
|
|
1536
|
-
* @example Optional param
|
|
1537
|
-
* ```ts
|
|
1538
|
-
* createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
1539
|
-
* // → params?: QueryParams
|
|
1540
|
-
* ```
|
|
1541
|
-
*
|
|
1542
|
-
* @example Param with default (implicitly optional; cannot combine with `optional: true`)
|
|
1543
|
-
* ```ts
|
|
1544
|
-
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
1545
|
-
* // → config: RequestConfig = {}
|
|
1546
|
-
* ```
|
|
1547
|
-
*/
|
|
1548
|
-
function createFunctionParameter(props) {
|
|
1549
|
-
return {
|
|
1550
|
-
optional: false,
|
|
1551
|
-
...props,
|
|
1552
|
-
kind: "FunctionParameter"
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
910
|
+
const createResponse = responseDef.create;
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/nodes/schema.ts
|
|
1555
913
|
/**
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1559
|
-
* named field accessed from a group type. Each language's printer renders the variant
|
|
1560
|
-
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
1561
|
-
*
|
|
1562
|
-
* @example Reference type (TypeScript: `QueryParams`)
|
|
1563
|
-
* ```ts
|
|
1564
|
-
* createParamsType({ variant: 'reference', name: 'QueryParams' })
|
|
1565
|
-
* ```
|
|
1566
|
-
*
|
|
1567
|
-
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
1568
|
-
* ```ts
|
|
1569
|
-
* createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
|
|
1570
|
-
* ```
|
|
1571
|
-
*
|
|
1572
|
-
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
1573
|
-
* ```ts
|
|
1574
|
-
* createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
1575
|
-
* ```
|
|
914
|
+
* Maps schema `type` to its underlying `primitive`.
|
|
915
|
+
* Primitive types map to themselves and special string formats map to `'string'`.
|
|
916
|
+
* Any type not listed here (such as `ref`, `enum`, `union`, `intersection`, `tuple`, `ipv4`, `ipv6`, `blob`) has no `primitive`.
|
|
1576
917
|
*/
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
918
|
+
const TYPE_TO_PRIMITIVE = {
|
|
919
|
+
string: "string",
|
|
920
|
+
number: "number",
|
|
921
|
+
integer: "integer",
|
|
922
|
+
bigint: "bigint",
|
|
923
|
+
boolean: "boolean",
|
|
924
|
+
null: "null",
|
|
925
|
+
any: "any",
|
|
926
|
+
unknown: "unknown",
|
|
927
|
+
void: "void",
|
|
928
|
+
never: "never",
|
|
929
|
+
object: "object",
|
|
930
|
+
array: "array",
|
|
931
|
+
date: "date",
|
|
932
|
+
uuid: "string",
|
|
933
|
+
email: "string",
|
|
934
|
+
url: "string",
|
|
935
|
+
datetime: "string",
|
|
936
|
+
time: "string"
|
|
937
|
+
};
|
|
1583
938
|
/**
|
|
1584
|
-
*
|
|
1585
|
-
*
|
|
1586
|
-
* @example Grouped param (TypeScript declaration)
|
|
1587
|
-
* ```ts
|
|
1588
|
-
* createParameterGroup({
|
|
1589
|
-
* properties: [
|
|
1590
|
-
* createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1591
|
-
* createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
|
|
1592
|
-
* ],
|
|
1593
|
-
* default: '{}',
|
|
1594
|
-
* })
|
|
1595
|
-
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
1596
|
-
* // call → { id, name }
|
|
1597
|
-
* ```
|
|
1598
|
-
*
|
|
1599
|
-
* @example Inline (spread) — children emitted as individual top-level parameters
|
|
1600
|
-
* ```ts
|
|
1601
|
-
* createParameterGroup({
|
|
1602
|
-
* properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
|
|
1603
|
-
* inline: true,
|
|
1604
|
-
* })
|
|
1605
|
-
* // declaration → petId: string
|
|
1606
|
-
* // call → petId
|
|
1607
|
-
* ```
|
|
939
|
+
* Definition for the {@link SchemaNode}. Object schemas default `properties` to an
|
|
940
|
+
* empty array, and `primitive` is inferred from `type` when not explicitly provided.
|
|
1608
941
|
*/
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
942
|
+
const schemaDef = defineNode({
|
|
943
|
+
kind: "Schema",
|
|
944
|
+
build: (props) => {
|
|
945
|
+
if (props.type === "object") return {
|
|
946
|
+
properties: [],
|
|
947
|
+
primitive: "object",
|
|
948
|
+
...props
|
|
949
|
+
};
|
|
950
|
+
return {
|
|
951
|
+
primitive: TYPE_TO_PRIMITIVE[props.type],
|
|
952
|
+
...props
|
|
953
|
+
};
|
|
954
|
+
},
|
|
955
|
+
children: [
|
|
956
|
+
"properties",
|
|
957
|
+
"items",
|
|
958
|
+
"members",
|
|
959
|
+
"additionalProperties"
|
|
960
|
+
],
|
|
961
|
+
visitorKey: "schema"
|
|
962
|
+
});
|
|
963
|
+
function createSchema(props) {
|
|
964
|
+
return schemaDef.create(props);
|
|
1614
965
|
}
|
|
966
|
+
//#endregion
|
|
967
|
+
//#region src/registry.ts
|
|
968
|
+
/**
|
|
969
|
+
* Every node definition. Adding a node means adding its `defineNode` to one
|
|
970
|
+
* `nodes/*.ts` file and listing it here. The visitor tables in `visitor.ts` derive from it.
|
|
971
|
+
*/
|
|
972
|
+
const nodeDefs = [
|
|
973
|
+
inputDef,
|
|
974
|
+
outputDef,
|
|
975
|
+
operationDef,
|
|
976
|
+
requestBodyDef,
|
|
977
|
+
contentDef,
|
|
978
|
+
responseDef,
|
|
979
|
+
schemaDef,
|
|
980
|
+
propertyDef,
|
|
981
|
+
parameterDef,
|
|
982
|
+
constDef,
|
|
983
|
+
typeDef,
|
|
984
|
+
functionDef,
|
|
985
|
+
arrowFunctionDef,
|
|
986
|
+
textDef,
|
|
987
|
+
breakDef,
|
|
988
|
+
jsxDef,
|
|
989
|
+
importDef,
|
|
990
|
+
exportDef,
|
|
991
|
+
sourceDef,
|
|
992
|
+
fileDef
|
|
993
|
+
];
|
|
994
|
+
//#endregion
|
|
995
|
+
//#region src/visitor.ts
|
|
1615
996
|
/**
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
* @example
|
|
1619
|
-
* ```ts
|
|
1620
|
-
* createFunctionParameters({
|
|
1621
|
-
* params: [
|
|
1622
|
-
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1623
|
-
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
|
|
1624
|
-
* ],
|
|
1625
|
-
* })
|
|
1626
|
-
* ```
|
|
1627
|
-
*
|
|
1628
|
-
* @example
|
|
1629
|
-
* ```ts
|
|
1630
|
-
* const empty = createFunctionParameters()
|
|
1631
|
-
* // { kind: 'FunctionParameters', params: [] }
|
|
1632
|
-
* ```
|
|
997
|
+
* Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
|
|
998
|
+
* Derived from each definition's `children`.
|
|
1633
999
|
*/
|
|
1634
|
-
|
|
1635
|
-
return {
|
|
1636
|
-
params: [],
|
|
1637
|
-
...props,
|
|
1638
|
-
kind: "FunctionParameters"
|
|
1639
|
-
};
|
|
1640
|
-
}
|
|
1000
|
+
const VISITOR_KEYS = Object.fromEntries(nodeDefs.flatMap((def) => def.children ? [[def.kind, def.children]] : []));
|
|
1641
1001
|
/**
|
|
1642
|
-
*
|
|
1643
|
-
*
|
|
1644
|
-
* @example Named import
|
|
1645
|
-
* ```ts
|
|
1646
|
-
* createImport({ name: ['useState'], path: 'react' })
|
|
1647
|
-
* // import { useState } from 'react'
|
|
1648
|
-
* ```
|
|
1649
|
-
*
|
|
1650
|
-
* @example Type-only import
|
|
1651
|
-
* ```ts
|
|
1652
|
-
* createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
|
|
1653
|
-
* // import type { FC } from 'react'
|
|
1654
|
-
* ```
|
|
1002
|
+
* Maps a node kind to the matching visitor callback name. Derived from each
|
|
1003
|
+
* definition's `visitorKey`.
|
|
1655
1004
|
*/
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1005
|
+
const VISITOR_KEY_BY_KIND = Object.fromEntries(nodeDefs.flatMap((def) => def.visitorKey ? [[def.kind, def.visitorKey]] : []));
|
|
1006
|
+
const visitorKeysByKind = VISITOR_KEYS;
|
|
1007
|
+
/**
|
|
1008
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
1009
|
+
*/
|
|
1010
|
+
function isNode(value) {
|
|
1011
|
+
return typeof value === "object" && value !== null && typeof value.kind === "string";
|
|
1661
1012
|
}
|
|
1662
1013
|
/**
|
|
1663
|
-
*
|
|
1014
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
1664
1015
|
*
|
|
1665
|
-
*
|
|
1666
|
-
* ```ts
|
|
1667
|
-
* createExport({ name: ['Pet'], path: './Pet' })
|
|
1668
|
-
* // export { Pet } from './Pet'
|
|
1669
|
-
* ```
|
|
1016
|
+
* `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
|
|
1670
1017
|
*
|
|
1671
|
-
* @example
|
|
1018
|
+
* @example
|
|
1672
1019
|
* ```ts
|
|
1673
|
-
*
|
|
1674
|
-
* //
|
|
1020
|
+
* const children = getChildren(operationNode, true)
|
|
1021
|
+
* // returns parameters, the request body, and responses
|
|
1675
1022
|
* ```
|
|
1676
1023
|
*/
|
|
1677
|
-
function
|
|
1678
|
-
return
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1024
|
+
function* getChildren(node, recurse) {
|
|
1025
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
1026
|
+
const keys = visitorKeysByKind[node.kind];
|
|
1027
|
+
if (!keys) return;
|
|
1028
|
+
const record = node;
|
|
1029
|
+
for (const key of keys) {
|
|
1030
|
+
const value = record[key];
|
|
1031
|
+
if (Array.isArray(value)) {
|
|
1032
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
1033
|
+
} else if (isNode(value)) yield value;
|
|
1034
|
+
}
|
|
1682
1035
|
}
|
|
1683
1036
|
/**
|
|
1684
|
-
*
|
|
1037
|
+
* Runs the visitor callback that matches `node.kind` with the traversal
|
|
1038
|
+
* context. The result is a replacement node, a collected value, or `undefined`
|
|
1039
|
+
* when no callback is registered for the kind.
|
|
1685
1040
|
*
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1689
|
-
* ```
|
|
1041
|
+
* Shared by `transform` and `collect` so node-kind dispatch lives in one place.
|
|
1042
|
+
* `TResult` is the caller's expected return: the same node type for `transform`,
|
|
1043
|
+
* the collected value type for `collect`.
|
|
1690
1044
|
*/
|
|
1691
|
-
function
|
|
1045
|
+
function applyVisitor(node, visitor, parent) {
|
|
1046
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
1047
|
+
if (!key) return void 0;
|
|
1048
|
+
const fn = visitor[key];
|
|
1049
|
+
return fn?.(node, { parent });
|
|
1050
|
+
}
|
|
1051
|
+
function transform(node, options) {
|
|
1052
|
+
const { depth, parent, ...visitor } = options;
|
|
1053
|
+
return transformNode(node, visitor, (depth ?? visitorDepths.deep) === visitorDepths.deep, parent);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Visits a single node, then immutably rebuilds its children. Returns the original
|
|
1057
|
+
* reference when neither the visitor nor the child rebuild changed anything, so callers
|
|
1058
|
+
* can detect "nothing changed" by identity and ancestors avoid reallocating.
|
|
1059
|
+
*/
|
|
1060
|
+
function transformNode(node, visitor, recurse, parent) {
|
|
1061
|
+
return transformChildren(applyVisitor(node, visitor, parent) ?? node, visitor, recurse);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
1065
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
1066
|
+
* `Schema` children are skipped in shallow mode.
|
|
1067
|
+
*/
|
|
1068
|
+
function transformChildren(node, visitor, recurse) {
|
|
1069
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
1070
|
+
const keys = visitorKeysByKind[node.kind];
|
|
1071
|
+
if (!keys) return node;
|
|
1072
|
+
const record = node;
|
|
1073
|
+
let updates;
|
|
1074
|
+
for (const key of keys) {
|
|
1075
|
+
if (!(key in record)) continue;
|
|
1076
|
+
const value = record[key];
|
|
1077
|
+
if (Array.isArray(value)) {
|
|
1078
|
+
let mapped;
|
|
1079
|
+
for (const [i, item] of value.entries()) {
|
|
1080
|
+
const next = isNode(item) ? transformNode(item, visitor, recurse, node) : item;
|
|
1081
|
+
if (mapped) {
|
|
1082
|
+
mapped.push(next);
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
if (next !== item) mapped = [...value.slice(0, i), next];
|
|
1086
|
+
}
|
|
1087
|
+
if (mapped) (updates ??= {})[key] = mapped;
|
|
1088
|
+
} else if (isNode(value)) {
|
|
1089
|
+
const next = transformNode(value, visitor, recurse, node);
|
|
1090
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (!updates) return node;
|
|
1692
1094
|
return {
|
|
1693
|
-
...
|
|
1694
|
-
|
|
1095
|
+
...node,
|
|
1096
|
+
...updates
|
|
1695
1097
|
};
|
|
1696
1098
|
}
|
|
1697
1099
|
/**
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
* Computes:
|
|
1701
|
-
* - `id` — SHA256 hash of the file path
|
|
1702
|
-
* - `name` — `baseName` without extension
|
|
1703
|
-
* - `extname` — extension extracted from `baseName`
|
|
1704
|
-
*
|
|
1705
|
-
* Deduplicates:
|
|
1706
|
-
* - `sources` via `combineSources`
|
|
1707
|
-
* - `exports` via `combineExports`
|
|
1708
|
-
* - `imports` via `combineImports` (also filters unused imports)
|
|
1100
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
1101
|
+
* the visitor callbacks. Use `collectSync` for the eager array form.
|
|
1709
1102
|
*
|
|
1710
|
-
* @
|
|
1711
|
-
*
|
|
1712
|
-
* @example
|
|
1103
|
+
* @example Collect every operationId
|
|
1713
1104
|
* ```ts
|
|
1714
|
-
* const
|
|
1715
|
-
*
|
|
1716
|
-
*
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
* // file.name = 'petStore'
|
|
1723
|
-
* // file.extname = '.ts'
|
|
1105
|
+
* const ids: string[] = []
|
|
1106
|
+
* for (const id of collect<string>(root, {
|
|
1107
|
+
* operation(node) {
|
|
1108
|
+
* return node.operationId
|
|
1109
|
+
* },
|
|
1110
|
+
* })) {
|
|
1111
|
+
* ids.push(id)
|
|
1112
|
+
* }
|
|
1724
1113
|
* ```
|
|
1725
1114
|
*/
|
|
1726
|
-
function
|
|
1727
|
-
const
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
kind: "File",
|
|
1735
|
-
...input,
|
|
1736
|
-
id: createHash("sha256").update(input.path).digest("hex"),
|
|
1737
|
-
name: trimExtName(input.baseName),
|
|
1738
|
-
extname,
|
|
1739
|
-
imports: resolvedImports,
|
|
1740
|
-
exports: resolvedExports,
|
|
1741
|
-
sources: resolvedSources,
|
|
1742
|
-
meta: input.meta ?? {}
|
|
1743
|
-
};
|
|
1115
|
+
function* collect(node, options) {
|
|
1116
|
+
const { depth, parent, ...visitor } = options;
|
|
1117
|
+
yield* collectNode(node, visitor, (depth ?? visitorDepths.deep) === visitorDepths.deep, parent);
|
|
1118
|
+
}
|
|
1119
|
+
function* collectNode(node, visitor, recurse, parent) {
|
|
1120
|
+
const v = applyVisitor(node, visitor, parent);
|
|
1121
|
+
if (v != null) yield v;
|
|
1122
|
+
for (const child of getChildren(node, recurse)) yield* collectNode(child, visitor, recurse, node);
|
|
1744
1123
|
}
|
|
1745
1124
|
/**
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
* Mirrors the `Const` component from `@kubb/renderer-jsx`.
|
|
1749
|
-
* The component's `children` are represented as `nodes`.
|
|
1750
|
-
*
|
|
1751
|
-
* @example Simple constant
|
|
1752
|
-
* ```ts
|
|
1753
|
-
* createConst({ name: 'pet' })
|
|
1754
|
-
* // const pet = ...
|
|
1755
|
-
* ```
|
|
1756
|
-
*
|
|
1757
|
-
* @example Exported constant with type and `as const`
|
|
1758
|
-
* ```ts
|
|
1759
|
-
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
1760
|
-
* // export const pets: Pet[] = ... as const
|
|
1761
|
-
* ```
|
|
1125
|
+
* Eager depth-first collection pass. Gathers every non-null value the visitor
|
|
1126
|
+
* callbacks return into an array.
|
|
1762
1127
|
*
|
|
1763
|
-
* @example
|
|
1128
|
+
* @example Collect every operationId
|
|
1764
1129
|
* ```ts
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1769
|
-
* nodes: [],
|
|
1130
|
+
* const ids = collectSync<string>(root, {
|
|
1131
|
+
* operation(node) {
|
|
1132
|
+
* return node.operationId
|
|
1133
|
+
* },
|
|
1770
1134
|
* })
|
|
1771
1135
|
* ```
|
|
1772
1136
|
*/
|
|
1773
|
-
function
|
|
1774
|
-
return
|
|
1775
|
-
...props,
|
|
1776
|
-
kind: "Const"
|
|
1777
|
-
};
|
|
1137
|
+
function collectSync(node, options) {
|
|
1138
|
+
return Array.from(collect(node, options));
|
|
1778
1139
|
}
|
|
1140
|
+
//#endregion
|
|
1141
|
+
//#region src/defineMacro.ts
|
|
1779
1142
|
/**
|
|
1780
|
-
*
|
|
1781
|
-
*
|
|
1782
|
-
* Mirrors the `Type` component from `@kubb/renderer-jsx`.
|
|
1783
|
-
* The component's `children` are represented as `nodes`.
|
|
1784
|
-
*
|
|
1785
|
-
* @example Simple type alias
|
|
1786
|
-
* ```ts
|
|
1787
|
-
* createType({ name: 'Pet' })
|
|
1788
|
-
* // type Pet = ...
|
|
1789
|
-
* ```
|
|
1790
|
-
*
|
|
1791
|
-
* @example Exported type with JSDoc
|
|
1792
|
-
* ```ts
|
|
1793
|
-
* createType({
|
|
1794
|
-
* name: 'PetStatus',
|
|
1795
|
-
* export: true,
|
|
1796
|
-
* JSDoc: { comments: ['@description Status of a pet'] },
|
|
1797
|
-
* })
|
|
1798
|
-
* // export type PetStatus = ...
|
|
1799
|
-
* ```
|
|
1143
|
+
* Sort weight for an `enforce` hint. `pre` sorts before unmarked items and `post` after, so a plain
|
|
1144
|
+
* list keeps its authored order.
|
|
1800
1145
|
*/
|
|
1801
|
-
function
|
|
1802
|
-
return
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
};
|
|
1146
|
+
function enforceWeight(enforce) {
|
|
1147
|
+
if (enforce === "pre") return 0;
|
|
1148
|
+
if (enforce === "post") return 2;
|
|
1149
|
+
return 1;
|
|
1806
1150
|
}
|
|
1807
1151
|
/**
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
* Mirrors the `Function` component from `@kubb/renderer-jsx`.
|
|
1811
|
-
* The component's `children` are represented as `nodes`.
|
|
1812
|
-
*
|
|
1813
|
-
* @example Simple function
|
|
1814
|
-
* ```ts
|
|
1815
|
-
* createFunction({ name: 'getPet' })
|
|
1816
|
-
* // function getPet() { ... }
|
|
1817
|
-
* ```
|
|
1818
|
-
*
|
|
1819
|
-
* @example Exported async function with return type
|
|
1820
|
-
* ```ts
|
|
1821
|
-
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
1822
|
-
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
1823
|
-
* ```
|
|
1152
|
+
* Types a macro for inference and a single construction site, mirroring `definePlugin`.
|
|
1153
|
+
* Adds no runtime behavior.
|
|
1824
1154
|
*
|
|
1825
|
-
* @example
|
|
1155
|
+
* @example
|
|
1826
1156
|
* ```ts
|
|
1827
|
-
*
|
|
1828
|
-
* name: '
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
* returnType: 'T',
|
|
1157
|
+
* const macroUntagged = defineMacro({
|
|
1158
|
+
* name: 'untagged',
|
|
1159
|
+
* operation(node) {
|
|
1160
|
+
* return node.tags?.length ? undefined : { ...node, tags: ['untagged'] }
|
|
1161
|
+
* },
|
|
1833
1162
|
* })
|
|
1834
|
-
* // export function identity<T>(value: T): T { ... }
|
|
1835
1163
|
* ```
|
|
1836
1164
|
*/
|
|
1837
|
-
function
|
|
1838
|
-
return
|
|
1839
|
-
...props,
|
|
1840
|
-
kind: "Function"
|
|
1841
|
-
};
|
|
1165
|
+
function defineMacro(macro) {
|
|
1166
|
+
return macro;
|
|
1842
1167
|
}
|
|
1843
1168
|
/**
|
|
1844
|
-
*
|
|
1845
|
-
*
|
|
1846
|
-
*
|
|
1847
|
-
* The component's `children` are represented as `nodes`.
|
|
1848
|
-
*
|
|
1849
|
-
* @example Simple arrow function
|
|
1850
|
-
* ```ts
|
|
1851
|
-
* createArrowFunction({ name: 'getPet' })
|
|
1852
|
-
* // const getPet = () => { ... }
|
|
1853
|
-
* ```
|
|
1854
|
-
*
|
|
1855
|
-
* @example Single-line exported arrow function
|
|
1856
|
-
* ```ts
|
|
1857
|
-
* createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
|
|
1858
|
-
* // export const double = (n: number) => ...
|
|
1859
|
-
* ```
|
|
1860
|
-
*
|
|
1861
|
-
* @example Async arrow function with generics
|
|
1862
|
-
* ```ts
|
|
1863
|
-
* createArrowFunction({
|
|
1864
|
-
* name: 'fetchPet',
|
|
1865
|
-
* export: true,
|
|
1866
|
-
* async: true,
|
|
1867
|
-
* generics: ['T'],
|
|
1868
|
-
* params: 'id: string',
|
|
1869
|
-
* returnType: 'T',
|
|
1870
|
-
* })
|
|
1871
|
-
* // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
|
|
1872
|
-
* ```
|
|
1169
|
+
* Runs every macro's callback for one node kind in order, chaining the result so each macro sees
|
|
1170
|
+
* the previous macro's output. Returns `undefined` when nothing changed, so `transform` keeps the
|
|
1171
|
+
* original reference (structural sharing).
|
|
1873
1172
|
*/
|
|
1874
|
-
function
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1173
|
+
function chain({ macros, key, node, context }) {
|
|
1174
|
+
let current = node;
|
|
1175
|
+
for (const macro of macros) {
|
|
1176
|
+
const callback = macro[key];
|
|
1177
|
+
if (!callback) continue;
|
|
1178
|
+
if (macro.when && !macro.when(current)) continue;
|
|
1179
|
+
const next = callback(current, context);
|
|
1180
|
+
if (next != null) current = next;
|
|
1181
|
+
}
|
|
1182
|
+
return current === node ? void 0 : current;
|
|
1879
1183
|
}
|
|
1880
1184
|
/**
|
|
1881
|
-
*
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1185
|
+
* Folds an ordered list of macros into a single {@link Visitor} that `transform` (and the per-plugin
|
|
1186
|
+
* transform layer in `@kubb/core`) can run. Macros are stable-sorted by `enforce`, then applied
|
|
1187
|
+
* sequentially per node so later macros see earlier output. This differs from a plain visitor, which
|
|
1188
|
+
* has no names, ordering, or composition.
|
|
1885
1189
|
*
|
|
1886
1190
|
* @example
|
|
1887
1191
|
* ```ts
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1192
|
+
* const visitor = composeMacros([macroSimplifyUnion, macroDiscriminatorEnum])
|
|
1193
|
+
* const next = transform(root, visitor)
|
|
1890
1194
|
* ```
|
|
1891
1195
|
*/
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1196
|
+
function composeMacros(macros) {
|
|
1197
|
+
const ordered = [...macros].sort((a, b) => enforceWeight(a.enforce) - enforceWeight(b.enforce));
|
|
1198
|
+
const visitor = {};
|
|
1199
|
+
for (const key of visitorKeys) {
|
|
1200
|
+
if (!ordered.some((macro) => typeof macro[key] === "function")) continue;
|
|
1201
|
+
const callback = (node, context) => chain({
|
|
1202
|
+
macros: ordered,
|
|
1203
|
+
key,
|
|
1204
|
+
node,
|
|
1205
|
+
context
|
|
1206
|
+
});
|
|
1207
|
+
visitor[key] = callback;
|
|
1208
|
+
}
|
|
1209
|
+
return visitor;
|
|
1897
1210
|
}
|
|
1898
1211
|
/**
|
|
1899
|
-
*
|
|
1900
|
-
*
|
|
1901
|
-
*
|
|
1902
|
-
* when joined with `\n` by `printNodes`, produces a blank line.
|
|
1212
|
+
* Runs a list of macros over a node tree and returns the rewritten tree. Keeps `transform`'s
|
|
1213
|
+
* structural sharing, so an empty or no-op macro list returns the same reference. Pass
|
|
1214
|
+
* `depth: 'shallow'` to rewrite the root node only.
|
|
1903
1215
|
*
|
|
1904
1216
|
* @example
|
|
1905
1217
|
* ```ts
|
|
1906
|
-
*
|
|
1907
|
-
* // { kind: 'Break' }
|
|
1218
|
+
* const next = applyMacros(root, [macroIntegerToString])
|
|
1908
1219
|
* ```
|
|
1909
|
-
*/
|
|
1910
|
-
function createBreak() {
|
|
1911
|
-
return { kind: "Break" };
|
|
1912
|
-
}
|
|
1913
|
-
/**
|
|
1914
|
-
* Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
|
|
1915
|
-
*
|
|
1916
|
-
* Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
|
|
1917
1220
|
*
|
|
1918
|
-
* @example
|
|
1221
|
+
* @example Apply to the root node only
|
|
1919
1222
|
* ```ts
|
|
1920
|
-
*
|
|
1921
|
-
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
1223
|
+
* const named = applyMacros(node, [macroEnumName({ parentName, propName, enumSuffix })], { depth: 'shallow' })
|
|
1922
1224
|
* ```
|
|
1923
1225
|
*/
|
|
1924
|
-
function
|
|
1925
|
-
return
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1226
|
+
function applyMacros(root, macros, options) {
|
|
1227
|
+
if (macros.length === 0) return root;
|
|
1228
|
+
return transform(root, {
|
|
1229
|
+
...composeMacros(macros),
|
|
1230
|
+
...options
|
|
1231
|
+
});
|
|
1929
1232
|
}
|
|
1930
1233
|
//#endregion
|
|
1931
|
-
//#region src/
|
|
1234
|
+
//#region src/createPrinter.ts
|
|
1932
1235
|
/**
|
|
1933
|
-
* Creates a schema printer
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
1236
|
+
* Creates a schema printer: a function that takes a `SchemaNode` and emits
|
|
1237
|
+
* code in your target language. Each plugin that produces code from schemas
|
|
1238
|
+
* (TypeScript types, Zod schemas, Faker factories) ships a printer built
|
|
1239
|
+
* with this helper.
|
|
1936
1240
|
*
|
|
1937
1241
|
* The builder receives resolved options and returns:
|
|
1938
|
-
* - `name` — a unique identifier for the printer
|
|
1939
|
-
* - `options` — options stored on the returned printer instance
|
|
1940
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
1941
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
1942
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
1943
|
-
* - This keeps recursion safe and avoids self-calls
|
|
1944
1242
|
*
|
|
1945
|
-
*
|
|
1243
|
+
* - `name` unique identifier for the printer.
|
|
1244
|
+
* - `options` stored on the returned printer instance.
|
|
1245
|
+
* - `nodes` map of `SchemaType` → handler. Handlers return the rendered
|
|
1246
|
+
* output (a string, a TypeScript AST node, ...) for that schema type.
|
|
1247
|
+
* - `overrides` (optional), user-supplied handlers that win over `nodes`.
|
|
1248
|
+
* An override can call `this.base(node)` to reuse the handler it replaced.
|
|
1249
|
+
* - `print` (optional), top-level override exposed as `printer.print`.
|
|
1250
|
+
* Use `this.transform(node)` inside it to dispatch to `nodes` recursively.
|
|
1251
|
+
*
|
|
1252
|
+
* Without a `print` override, `printer.print` falls back to `printer.transform`
|
|
1253
|
+
* (the node-level dispatcher).
|
|
1946
1254
|
*
|
|
1947
|
-
* @example
|
|
1255
|
+
* @example Tiny Zod printer
|
|
1948
1256
|
* ```ts
|
|
1257
|
+
* import { createPrinter, type PrinterFactoryOptions } from '@kubb/ast'
|
|
1258
|
+
*
|
|
1949
1259
|
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
1950
1260
|
*
|
|
1951
|
-
* export const zodPrinter =
|
|
1261
|
+
* export const zodPrinter = createPrinter<PrinterZod>((options) => ({
|
|
1952
1262
|
* name: 'zod',
|
|
1953
1263
|
* options: { strict: options.strict ?? true },
|
|
1954
1264
|
* nodes: {
|
|
1955
1265
|
* string: () => 'z.string()',
|
|
1956
1266
|
* object(node) {
|
|
1957
|
-
* const props = node.properties
|
|
1267
|
+
* const props = node.properties
|
|
1268
|
+
* .map((p) => `${p.name}: ${this.transform(p.schema)}`)
|
|
1269
|
+
* .join(', ')
|
|
1958
1270
|
* return `z.object({ ${props} })`
|
|
1959
1271
|
* },
|
|
1960
1272
|
* },
|
|
1961
1273
|
* }))
|
|
1962
1274
|
* ```
|
|
1963
1275
|
*/
|
|
1964
|
-
function
|
|
1965
|
-
return
|
|
1276
|
+
function createPrinter(build) {
|
|
1277
|
+
return (options) => {
|
|
1278
|
+
const { name, options: resolvedOptions, nodes, overrides, print: printOverride } = build(options ?? {});
|
|
1279
|
+
const merged = overrides ? {
|
|
1280
|
+
...nodes,
|
|
1281
|
+
...overrides
|
|
1282
|
+
} : nodes;
|
|
1283
|
+
const context = {
|
|
1284
|
+
options: resolvedOptions,
|
|
1285
|
+
transform: (node) => {
|
|
1286
|
+
const handler = merged[node.type];
|
|
1287
|
+
if (!handler) return null;
|
|
1288
|
+
return handler.call(context, node);
|
|
1289
|
+
},
|
|
1290
|
+
base: (node) => {
|
|
1291
|
+
const handler = nodes[node.type];
|
|
1292
|
+
if (!handler) return null;
|
|
1293
|
+
return handler.call(context, node);
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
return {
|
|
1297
|
+
name,
|
|
1298
|
+
options: resolvedOptions,
|
|
1299
|
+
transform: context.transform,
|
|
1300
|
+
print: printOverride ? printOverride.bind(context) : context.transform
|
|
1301
|
+
};
|
|
1302
|
+
};
|
|
1966
1303
|
}
|
|
1304
|
+
//#endregion
|
|
1305
|
+
//#region src/utils/refs.ts
|
|
1967
1306
|
/**
|
|
1968
|
-
*
|
|
1969
|
-
|
|
1307
|
+
* Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
|
|
1308
|
+
* the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
|
|
1309
|
+
* then `name`, then the nested `schema.name`.
|
|
1310
|
+
*
|
|
1311
|
+
* Returns `null` for non-ref nodes or when no name resolves.
|
|
1312
|
+
*
|
|
1970
1313
|
* @example
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
1974
|
-
* )
|
|
1975
|
-
* ```
|
|
1314
|
+
* `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'`
|
|
1315
|
+
*
|
|
1316
|
+
* @example Collision-renamed target
|
|
1317
|
+
* `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Order', targetName: 'OrderSchema' }) // 'OrderSchema'`
|
|
1976
1318
|
*/
|
|
1977
|
-
function
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
options: resolvedOptions,
|
|
1983
|
-
transform: (node) => {
|
|
1984
|
-
const key = getKey(node);
|
|
1985
|
-
if (key === void 0) return null;
|
|
1986
|
-
const handler = nodes[key];
|
|
1987
|
-
if (!handler) return null;
|
|
1988
|
-
return handler.call(context, node);
|
|
1989
|
-
}
|
|
1990
|
-
};
|
|
1991
|
-
return {
|
|
1992
|
-
name,
|
|
1993
|
-
options: resolvedOptions,
|
|
1994
|
-
transform: context.transform,
|
|
1995
|
-
print: printOverride ? printOverride.bind(context) : context.transform
|
|
1996
|
-
};
|
|
1997
|
-
};
|
|
1998
|
-
};
|
|
1319
|
+
function resolveRefName(node) {
|
|
1320
|
+
if (!node || node.type !== "ref") return null;
|
|
1321
|
+
if (node.targetName) return node.targetName;
|
|
1322
|
+
if (node.ref) return node.ref.split("/").at(-1) ?? node.ref;
|
|
1323
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1999
1324
|
}
|
|
2000
1325
|
//#endregion
|
|
2001
|
-
//#region src/
|
|
2002
|
-
function findDiscriminator(mapping, ref) {
|
|
2003
|
-
if (!mapping || !ref) return null;
|
|
2004
|
-
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
2005
|
-
}
|
|
2006
|
-
function childName(parentName, propName) {
|
|
2007
|
-
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
2008
|
-
}
|
|
2009
|
-
function enumPropName(parentName, propName, enumSuffix) {
|
|
2010
|
-
return pascalCase([
|
|
2011
|
-
parentName,
|
|
2012
|
-
propName,
|
|
2013
|
-
enumSuffix
|
|
2014
|
-
].filter(Boolean).join(" "));
|
|
2015
|
-
}
|
|
1326
|
+
//#region src/utils/schemaGraph.ts
|
|
2016
1327
|
/**
|
|
2017
|
-
*
|
|
1328
|
+
* Memoized inner pass that walks a single node and returns the names of every schema it references.
|
|
2018
1329
|
*/
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
if (
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
return result;
|
|
1330
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1331
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1332
|
+
collectSync(node, { schema(child) {
|
|
1333
|
+
if (child.type === "ref") {
|
|
1334
|
+
const name = resolveRefName(child);
|
|
1335
|
+
if (name) refs.add(name);
|
|
1336
|
+
}
|
|
2027
1337
|
} });
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
//#region src/transformers.ts
|
|
1338
|
+
return refs;
|
|
1339
|
+
});
|
|
2031
1340
|
/**
|
|
2032
|
-
*
|
|
1341
|
+
* Collects the names of every ref found anywhere inside a node's own subtree.
|
|
2033
1342
|
*
|
|
2034
|
-
*
|
|
2035
|
-
*
|
|
1343
|
+
* Each ref contributes its name only, so the schema it points to is never traversed here. Pass `out`
|
|
1344
|
+
* to accumulate names from several nodes into one set.
|
|
2036
1345
|
*
|
|
2037
|
-
* @example
|
|
1346
|
+
* @example Collect refs from a single schema
|
|
2038
1347
|
* ```ts
|
|
2039
|
-
* const
|
|
2040
|
-
*
|
|
2041
|
-
*
|
|
2042
|
-
*
|
|
2043
|
-
*
|
|
1348
|
+
* const names = collectReferencedSchemaNames(petSchema)
|
|
1349
|
+
* // Set { 'Category', 'Tag' }
|
|
1350
|
+
* ```
|
|
1351
|
+
*
|
|
1352
|
+
* @example Accumulate refs from multiple schemas into one set
|
|
1353
|
+
* ```ts
|
|
1354
|
+
* const out = new Set<string>()
|
|
1355
|
+
* for (const schema of schemas) {
|
|
1356
|
+
* collectReferencedSchemaNames(schema, out)
|
|
1357
|
+
* }
|
|
2044
1358
|
* ```
|
|
2045
1359
|
*/
|
|
2046
|
-
function
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
});
|
|
1360
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1361
|
+
if (!node) return out;
|
|
1362
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1363
|
+
return out;
|
|
1364
|
+
}
|
|
1365
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1366
|
+
const schemaMap = /* @__PURE__ */ new Map();
|
|
1367
|
+
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1368
|
+
const result = /* @__PURE__ */ new Set();
|
|
1369
|
+
function visitSchema(schema) {
|
|
1370
|
+
const directRefs = collectReferencedSchemaNames(schema);
|
|
1371
|
+
for (const name of directRefs) if (!result.has(name)) {
|
|
1372
|
+
result.add(name);
|
|
1373
|
+
const namedSchema = schemaMap.get(name);
|
|
1374
|
+
if (namedSchema) visitSchema(namedSchema);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
for (const op of operations) for (const schema of collect(op, {
|
|
1378
|
+
depth: "shallow",
|
|
1379
|
+
schema: (node) => node
|
|
1380
|
+
})) visitSchema(schema);
|
|
1381
|
+
return result;
|
|
2067
1382
|
}
|
|
2068
1383
|
/**
|
|
2069
|
-
*
|
|
1384
|
+
* Collects the names of all top-level schemas transitively used by a set of operations.
|
|
2070
1385
|
*
|
|
2071
|
-
*
|
|
1386
|
+
* An operation uses a schema when its parameters, request body, or responses reference it, directly
|
|
1387
|
+
* or through other named schemas. Once a name is added to the result it is not revisited, so
|
|
1388
|
+
* reference cycles terminate.
|
|
1389
|
+
*
|
|
1390
|
+
* Pair it with `include` filters so schemas reachable only from excluded operations stay ungenerated.
|
|
1391
|
+
*
|
|
1392
|
+
* @example Only generate schemas referenced by included operations
|
|
2072
1393
|
* ```ts
|
|
2073
|
-
* const
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
*
|
|
1394
|
+
* const includedOps = operations.filter((op) => resolver.default.options(op, { options, include }) !== null)
|
|
1395
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1396
|
+
*
|
|
1397
|
+
* for (const schema of schemas) {
|
|
1398
|
+
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1399
|
+
* // generate schema
|
|
1400
|
+
* }
|
|
2077
1401
|
* ```
|
|
2078
1402
|
*/
|
|
2079
|
-
function
|
|
2080
|
-
return
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
1403
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
1404
|
+
return computeUsedSchemaNames(operations, schemas);
|
|
1405
|
+
}
|
|
1406
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1407
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1408
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1409
|
+
for (const schema of schemas) {
|
|
1410
|
+
if (!schema.name) continue;
|
|
1411
|
+
graph.set(schema.name, collectReferencedSchemaNames(schema));
|
|
1412
|
+
}
|
|
1413
|
+
const circular = /* @__PURE__ */ new Set();
|
|
1414
|
+
for (const start of graph.keys()) {
|
|
1415
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1416
|
+
const stack = [...graph.get(start) ?? []];
|
|
1417
|
+
while (stack.length > 0) {
|
|
1418
|
+
const node = stack.pop();
|
|
1419
|
+
if (node === start) {
|
|
1420
|
+
circular.add(start);
|
|
1421
|
+
break;
|
|
2091
1422
|
}
|
|
1423
|
+
if (visited.has(node)) continue;
|
|
1424
|
+
visited.add(node);
|
|
1425
|
+
const next = graph.get(node);
|
|
1426
|
+
if (next) for (const r of next) stack.push(r);
|
|
2092
1427
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
}
|
|
1428
|
+
}
|
|
1429
|
+
return circular;
|
|
1430
|
+
});
|
|
2097
1431
|
/**
|
|
2098
|
-
*
|
|
1432
|
+
* Finds every schema that takes part in a circular dependency chain, including direct self-loops.
|
|
1433
|
+
*
|
|
1434
|
+
* Wrap the returned schema positions in a deferred construct (a lazy getter or `z.lazy(() => …)`) so
|
|
1435
|
+
* the generated code does not recurse forever. Refs are followed by name only, so the walk stays
|
|
1436
|
+
* linear in the size of the schema graph.
|
|
1437
|
+
*
|
|
1438
|
+
* @note Call this once on the full graph, then check individual schemas with `containsCircularRef()`.
|
|
1439
|
+
*/
|
|
1440
|
+
function findCircularSchemas(schemas) {
|
|
1441
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1442
|
+
return findCircularSchemasMemo(schemas);
|
|
1443
|
+
}
|
|
1444
|
+
//#endregion
|
|
1445
|
+
//#region src/factory.ts
|
|
1446
|
+
var factory_exports = /* @__PURE__ */ __exportAll({
|
|
1447
|
+
createArrowFunction: () => createArrowFunction,
|
|
1448
|
+
createBreak: () => createBreak,
|
|
1449
|
+
createConst: () => createConst,
|
|
1450
|
+
createContent: () => createContent,
|
|
1451
|
+
createExport: () => createExport,
|
|
1452
|
+
createFile: () => createFile,
|
|
1453
|
+
createFunction: () => createFunction,
|
|
1454
|
+
createImport: () => createImport,
|
|
1455
|
+
createInput: () => createInput,
|
|
1456
|
+
createJsx: () => createJsx,
|
|
1457
|
+
createOperation: () => createOperation,
|
|
1458
|
+
createOutput: () => createOutput,
|
|
1459
|
+
createParameter: () => createParameter,
|
|
1460
|
+
createProperty: () => createProperty,
|
|
1461
|
+
createRequestBody: () => createRequestBody,
|
|
1462
|
+
createResponse: () => createResponse,
|
|
1463
|
+
createSchema: () => createSchema,
|
|
1464
|
+
createSource: () => createSource,
|
|
1465
|
+
createText: () => createText,
|
|
1466
|
+
createType: () => createType,
|
|
1467
|
+
update: () => update
|
|
1468
|
+
});
|
|
1469
|
+
/**
|
|
1470
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1471
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1472
|
+
* with the changes applied.
|
|
1473
|
+
*
|
|
1474
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract. Pair it with the
|
|
1475
|
+
* structural sharing in {@link transform} so a no-op rewrite does not allocate and
|
|
1476
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is shallow,
|
|
1477
|
+
* so a structurally equal but newly allocated array or object counts as a change.
|
|
2099
1478
|
*
|
|
2100
1479
|
* @example
|
|
2101
1480
|
* ```ts
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2104
|
-
* createSchema({ type: 'string' }),
|
|
2105
|
-
* ])
|
|
2106
|
-
* // keeps only string member
|
|
1481
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1482
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
2107
1483
|
* ```
|
|
2108
1484
|
*/
|
|
2109
|
-
function
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const enumNode = narrowSchema(member, "enum");
|
|
2114
|
-
if (!enumNode) return true;
|
|
2115
|
-
const primitive = enumNode.primitive;
|
|
2116
|
-
if (!primitive) return true;
|
|
2117
|
-
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
2118
|
-
if (scalarPrimitives.has(primitive)) return false;
|
|
2119
|
-
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
2120
|
-
return true;
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
2124
|
-
const enumNode = narrowSchema(propNode, "enum");
|
|
2125
|
-
if (enumNode?.primitive === "boolean") return {
|
|
2126
|
-
...propNode,
|
|
2127
|
-
name: void 0
|
|
1485
|
+
function update(node, changes) {
|
|
1486
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1487
|
+
...node,
|
|
1488
|
+
...changes
|
|
2128
1489
|
};
|
|
2129
|
-
|
|
2130
|
-
...propNode,
|
|
2131
|
-
name: enumPropName(parentName, propName, enumSuffix)
|
|
2132
|
-
};
|
|
2133
|
-
return propNode;
|
|
1490
|
+
return node;
|
|
2134
1491
|
}
|
|
2135
1492
|
//#endregion
|
|
2136
|
-
|
|
1493
|
+
//#region src/exports.ts
|
|
1494
|
+
var exports_exports = /* @__PURE__ */ __exportAll({
|
|
1495
|
+
applyMacros: () => applyMacros,
|
|
1496
|
+
arrowFunctionDef: () => arrowFunctionDef,
|
|
1497
|
+
breakDef: () => breakDef,
|
|
1498
|
+
collect: () => collect,
|
|
1499
|
+
collectSync: () => collectSync,
|
|
1500
|
+
collectUsedSchemaNames: () => collectUsedSchemaNames,
|
|
1501
|
+
combineExports: () => combineExports,
|
|
1502
|
+
combineImports: () => combineImports,
|
|
1503
|
+
combineSources: () => combineSources,
|
|
1504
|
+
composeMacros: () => composeMacros,
|
|
1505
|
+
constDef: () => constDef,
|
|
1506
|
+
contentDef: () => contentDef,
|
|
1507
|
+
createPrinter: () => createPrinter,
|
|
1508
|
+
defineMacro: () => defineMacro,
|
|
1509
|
+
defineNode: () => defineNode,
|
|
1510
|
+
exportDef: () => exportDef,
|
|
1511
|
+
extractStringsFromNodes: () => extractStringsFromNodes,
|
|
1512
|
+
factory: () => factory_exports,
|
|
1513
|
+
fileDef: () => fileDef,
|
|
1514
|
+
findCircularSchemas: () => findCircularSchemas,
|
|
1515
|
+
functionDef: () => functionDef,
|
|
1516
|
+
importDef: () => importDef,
|
|
1517
|
+
inputDef: () => inputDef,
|
|
1518
|
+
isHttpOperationNode: () => isHttpOperationNode,
|
|
1519
|
+
jsxDef: () => jsxDef,
|
|
1520
|
+
narrowSchema: () => narrowSchema,
|
|
1521
|
+
nodeDefs: () => nodeDefs,
|
|
1522
|
+
operationDef: () => operationDef,
|
|
1523
|
+
optionality: () => optionality,
|
|
1524
|
+
outputDef: () => outputDef,
|
|
1525
|
+
parameterDef: () => parameterDef,
|
|
1526
|
+
propertyDef: () => propertyDef,
|
|
1527
|
+
requestBodyDef: () => requestBodyDef,
|
|
1528
|
+
resolveRefName: () => resolveRefName,
|
|
1529
|
+
responseDef: () => responseDef,
|
|
1530
|
+
schemaDef: () => schemaDef,
|
|
1531
|
+
schemaTypes: () => schemaTypes,
|
|
1532
|
+
sourceDef: () => sourceDef,
|
|
1533
|
+
textDef: () => textDef,
|
|
1534
|
+
transform: () => transform,
|
|
1535
|
+
typeDef: () => typeDef
|
|
1536
|
+
});
|
|
1537
|
+
//#endregion
|
|
1538
|
+
export { applyMacros, arrowFunctionDef, exports_exports as ast, breakDef, collect, collectSync, collectUsedSchemaNames, combineExports, combineImports, combineSources, composeMacros, constDef, contentDef, createPrinter, defineMacro, defineNode, exportDef, extractStringsFromNodes, factory_exports as factory, fileDef, findCircularSchemas, functionDef, importDef, inputDef, isHttpOperationNode, jsxDef, narrowSchema, nodeDefs, operationDef, optionality, outputDef, parameterDef, propertyDef, requestBodyDef, resolveRefName, responseDef, schemaDef, schemaTypes, sourceDef, textDef, transform, typeDef };
|
|
2137
1539
|
|
|
2138
1540
|
//# sourceMappingURL=index.js.map
|