@kubb/ast 5.0.0-beta.55 → 5.0.0-beta.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +1710 -1745
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -46
- package/dist/index.js +1682 -1740
- package/dist/index.js.map +1 -1
- package/dist/{types-BL7RpQAE.d.ts → types-C5aVnRE1.d.ts} +1730 -1789
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/dedupe.ts +1 -1
- package/src/factory.ts +3 -763
- package/src/guards.ts +1 -53
- package/src/index.ts +35 -20
- package/src/mocks.ts +6 -1
- package/src/node.ts +128 -0
- package/src/nodes/base.ts +3 -2
- package/src/nodes/code.ts +115 -0
- package/src/nodes/content.ts +19 -0
- package/src/nodes/file.ts +54 -0
- package/src/nodes/function.ts +221 -146
- package/src/nodes/index.ts +11 -3
- package/src/nodes/input.ts +36 -0
- package/src/nodes/operation.ts +59 -1
- package/src/nodes/output.ts +23 -0
- package/src/nodes/parameter.ts +33 -0
- package/src/nodes/property.ts +36 -0
- package/src/nodes/requestBody.ts +23 -1
- package/src/nodes/response.ts +39 -1
- package/src/nodes/schema.ts +72 -0
- package/src/registry.ts +70 -0
- package/src/transformers.ts +2 -2
- package/src/types.ts +5 -3
- package/src/utils/ast.ts +116 -193
- package/src/visitor.ts +3 -47
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,131 @@ const require_utils = require("./utils-cdQ6Pzyi.cjs");
|
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_utils.__toESM(node_path, 1);
|
|
6
|
+
//#region src/node.ts
|
|
7
|
+
/**
|
|
8
|
+
* Builds a type guard that matches nodes of the given `kind`.
|
|
9
|
+
*/
|
|
10
|
+
function isKind(kind) {
|
|
11
|
+
return (node) => node.kind === kind;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
15
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
16
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
17
|
+
*
|
|
18
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
19
|
+
* - Non-required + nullable → `nullish: true`.
|
|
20
|
+
* - Required → both flags cleared.
|
|
21
|
+
*/
|
|
22
|
+
function syncOptionality(schema, required) {
|
|
23
|
+
const nullable = schema.nullable ?? false;
|
|
24
|
+
return {
|
|
25
|
+
...schema,
|
|
26
|
+
optional: !required && !nullable ? true : void 0,
|
|
27
|
+
nullish: !required && nullable ? true : void 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Defines a node once and derives its `create` builder, `is` guard, and traversal
|
|
32
|
+
* metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
|
|
33
|
+
* `kind`, so node construction lives in one place without scattered `as` casts.
|
|
34
|
+
*
|
|
35
|
+
* Set `rebuild: true` when the `build` hook derives fields from children. After a
|
|
36
|
+
* transform rewrites those children, the registry reruns `create` so the derived
|
|
37
|
+
* fields stay correct.
|
|
38
|
+
*
|
|
39
|
+
* @example Simple node
|
|
40
|
+
* ```ts
|
|
41
|
+
* const importDef = defineNode<ImportNode>({ kind: 'Import' })
|
|
42
|
+
* const createImport = importDef.create
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Node with a build hook that is rerun on transform
|
|
46
|
+
* ```ts
|
|
47
|
+
* const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
|
|
48
|
+
* kind: 'Property',
|
|
49
|
+
* build: (props) => ({ ...props, required: props.required ?? false }),
|
|
50
|
+
* children: ['schema'],
|
|
51
|
+
* visitorKey: 'property',
|
|
52
|
+
* rebuild: true,
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function defineNode(config) {
|
|
57
|
+
const { kind, defaults, build, children, visitorKey, rebuild } = config;
|
|
58
|
+
function create(input) {
|
|
59
|
+
const base = build ? build(input) : input;
|
|
60
|
+
return {
|
|
61
|
+
...defaults,
|
|
62
|
+
...base,
|
|
63
|
+
kind
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
kind,
|
|
68
|
+
create,
|
|
69
|
+
is: isKind(kind),
|
|
70
|
+
children,
|
|
71
|
+
visitorKey,
|
|
72
|
+
rebuild
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/nodes/schema.ts
|
|
77
|
+
/**
|
|
78
|
+
* Maps schema `type` to its underlying `primitive`.
|
|
79
|
+
* Primitive types map to themselves. Special string formats map to `'string'`.
|
|
80
|
+
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
81
|
+
*/
|
|
82
|
+
const TYPE_TO_PRIMITIVE = {
|
|
83
|
+
string: "string",
|
|
84
|
+
number: "number",
|
|
85
|
+
integer: "integer",
|
|
86
|
+
bigint: "bigint",
|
|
87
|
+
boolean: "boolean",
|
|
88
|
+
null: "null",
|
|
89
|
+
any: "any",
|
|
90
|
+
unknown: "unknown",
|
|
91
|
+
void: "void",
|
|
92
|
+
never: "never",
|
|
93
|
+
object: "object",
|
|
94
|
+
array: "array",
|
|
95
|
+
date: "date",
|
|
96
|
+
uuid: "string",
|
|
97
|
+
email: "string",
|
|
98
|
+
url: "string",
|
|
99
|
+
datetime: "string",
|
|
100
|
+
time: "string"
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Definition for the {@link SchemaNode}. Object schemas default `properties` to an
|
|
104
|
+
* empty array, and `primitive` is inferred from `type` when not explicitly provided.
|
|
105
|
+
*/
|
|
106
|
+
const schemaDef = defineNode({
|
|
107
|
+
kind: "Schema",
|
|
108
|
+
build: (props) => {
|
|
109
|
+
if (props.type === "object") return {
|
|
110
|
+
properties: [],
|
|
111
|
+
primitive: "object",
|
|
112
|
+
...props
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
primitive: TYPE_TO_PRIMITIVE[props.type],
|
|
116
|
+
...props
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
children: [
|
|
120
|
+
"properties",
|
|
121
|
+
"items",
|
|
122
|
+
"members",
|
|
123
|
+
"additionalProperties"
|
|
124
|
+
],
|
|
125
|
+
visitorKey: "schema"
|
|
126
|
+
});
|
|
127
|
+
function createSchema(props) {
|
|
128
|
+
return schemaDef.create(props);
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
6
131
|
//#region ../../internals/utils/src/fs.ts
|
|
7
132
|
/**
|
|
8
133
|
* Strips the file extension from a path or file name.
|
|
@@ -60,2040 +185,1857 @@ function memoize(store, factory) {
|
|
|
60
185
|
};
|
|
61
186
|
}
|
|
62
187
|
//#endregion
|
|
63
|
-
//#region src/
|
|
188
|
+
//#region src/signature.ts
|
|
64
189
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```ts
|
|
69
|
-
* const schema = createSchema({ type: 'string' })
|
|
70
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
71
|
-
* ```
|
|
190
|
+
* The flags shared by every node kind that affect its type: `primitive`, `format`, `nullable`.
|
|
72
191
|
*/
|
|
73
|
-
function
|
|
74
|
-
return node
|
|
75
|
-
}
|
|
76
|
-
function isKind(kind) {
|
|
77
|
-
return (node) => node.kind === kind;
|
|
192
|
+
function flagsDescriptor(node) {
|
|
193
|
+
return `${node.primitive ?? ""};${node.format ?? ""};${node.nullable ? 1 : 0}`;
|
|
78
194
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* @example
|
|
83
|
-
* ```ts
|
|
84
|
-
* if (isInputNode(node)) {
|
|
85
|
-
* console.log(node.schemas.length)
|
|
86
|
-
* }
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
const isInputNode = isKind("Input");
|
|
90
|
-
/**
|
|
91
|
-
* Returns `true` when the input is an `OutputNode`.
|
|
92
|
-
*
|
|
93
|
-
* @example
|
|
94
|
-
* ```ts
|
|
95
|
-
* if (isOutputNode(node)) {
|
|
96
|
-
* console.log(node.files.length)
|
|
97
|
-
* }
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
const isOutputNode = isKind("Output");
|
|
101
|
-
/**
|
|
102
|
-
* Returns `true` when the input is an `OperationNode`.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```ts
|
|
106
|
-
* if (isOperationNode(node)) {
|
|
107
|
-
* console.log(node.operationId)
|
|
108
|
-
* }
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
const isOperationNode = isKind("Operation");
|
|
112
|
-
/**
|
|
113
|
-
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```ts
|
|
117
|
-
* if (isHttpOperationNode(node)) {
|
|
118
|
-
* console.log(node.method, node.path)
|
|
119
|
-
* }
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
function isHttpOperationNode(node) {
|
|
123
|
-
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
195
|
+
function refTargetName(node) {
|
|
196
|
+
if (node.ref) return require_utils.extractRefName(node.ref);
|
|
197
|
+
return node.name ?? "";
|
|
124
198
|
}
|
|
199
|
+
const arrayTupleFields = [
|
|
200
|
+
{
|
|
201
|
+
kind: "children",
|
|
202
|
+
key: "items",
|
|
203
|
+
prefix: "i"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
kind: "child",
|
|
207
|
+
key: "rest",
|
|
208
|
+
prefix: "r"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
kind: "scalar",
|
|
212
|
+
key: "min",
|
|
213
|
+
prefix: "mn"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
kind: "scalar",
|
|
217
|
+
key: "max",
|
|
218
|
+
prefix: "mx"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
kind: "bool",
|
|
222
|
+
key: "unique",
|
|
223
|
+
prefix: "u"
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
const numericFields = [
|
|
227
|
+
{
|
|
228
|
+
kind: "scalar",
|
|
229
|
+
key: "min",
|
|
230
|
+
prefix: "mn"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
kind: "scalar",
|
|
234
|
+
key: "max",
|
|
235
|
+
prefix: "mx"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
kind: "scalar",
|
|
239
|
+
key: "exclusiveMinimum",
|
|
240
|
+
prefix: "emn"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
kind: "scalar",
|
|
244
|
+
key: "exclusiveMaximum",
|
|
245
|
+
prefix: "emx"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
kind: "scalar",
|
|
249
|
+
key: "multipleOf",
|
|
250
|
+
prefix: "mo"
|
|
251
|
+
}
|
|
252
|
+
];
|
|
253
|
+
const rangeFields = [{
|
|
254
|
+
kind: "scalar",
|
|
255
|
+
key: "min",
|
|
256
|
+
prefix: "mn"
|
|
257
|
+
}, {
|
|
258
|
+
kind: "scalar",
|
|
259
|
+
key: "max",
|
|
260
|
+
prefix: "mx"
|
|
261
|
+
}];
|
|
125
262
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* ```ts
|
|
130
|
-
* if (isSchemaNode(node)) {
|
|
131
|
-
* console.log(node.type)
|
|
132
|
-
* }
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
|
-
const isSchemaNode = isKind("Schema");
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/visitor.ts
|
|
138
|
-
/**
|
|
139
|
-
* Creates a small async concurrency limiter.
|
|
140
|
-
*
|
|
141
|
-
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```ts
|
|
145
|
-
* const limit = createLimit(2)
|
|
146
|
-
* for (const task of [taskA, taskB, taskC]) {
|
|
147
|
-
* await limit(() => task())
|
|
148
|
-
* }
|
|
149
|
-
* // only 2 tasks run at the same time
|
|
150
|
-
* ```
|
|
263
|
+
* Maps each node `type` to its ordered shape-contributing fields. Types absent from the map
|
|
264
|
+
* (boolean, null, any, and other scalars) fall back to `${type}|${flags}`.
|
|
151
265
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
266
|
+
const SHAPE_KEYS = {
|
|
267
|
+
object: [
|
|
268
|
+
{ kind: "objectProps" },
|
|
269
|
+
{ kind: "additionalProps" },
|
|
270
|
+
{ kind: "patternProps" },
|
|
271
|
+
{
|
|
272
|
+
kind: "scalar",
|
|
273
|
+
key: "minProperties",
|
|
274
|
+
prefix: "mn"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
kind: "scalar",
|
|
278
|
+
key: "maxProperties",
|
|
279
|
+
prefix: "mx"
|
|
159
280
|
}
|
|
160
|
-
}
|
|
161
|
-
return function limit(fn) {
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
queue.push(() => {
|
|
164
|
-
Promise.resolve(fn()).then(resolve, reject).finally(() => {
|
|
165
|
-
active--;
|
|
166
|
-
next();
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
next();
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
const visitorKeysByKind = {
|
|
174
|
-
Input: ["schemas", "operations"],
|
|
175
|
-
Operation: [
|
|
176
|
-
"parameters",
|
|
177
|
-
"requestBody",
|
|
178
|
-
"responses"
|
|
179
281
|
],
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
282
|
+
array: arrayTupleFields,
|
|
283
|
+
tuple: arrayTupleFields,
|
|
284
|
+
union: [
|
|
285
|
+
{
|
|
286
|
+
kind: "scalar",
|
|
287
|
+
key: "strategy",
|
|
288
|
+
prefix: "s"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
kind: "scalar",
|
|
292
|
+
key: "discriminatorPropertyName",
|
|
293
|
+
prefix: "d"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
kind: "children",
|
|
297
|
+
key: "members",
|
|
298
|
+
prefix: "m"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
intersection: [{
|
|
302
|
+
kind: "children",
|
|
303
|
+
key: "members",
|
|
304
|
+
prefix: "m"
|
|
305
|
+
}],
|
|
306
|
+
enum: [{ kind: "enumValues" }],
|
|
307
|
+
ref: [{ kind: "refTarget" }],
|
|
308
|
+
string: [
|
|
309
|
+
{
|
|
310
|
+
kind: "scalar",
|
|
311
|
+
key: "min",
|
|
312
|
+
prefix: "mn"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
kind: "scalar",
|
|
316
|
+
key: "max",
|
|
317
|
+
prefix: "mx"
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
kind: "scalar",
|
|
321
|
+
key: "pattern",
|
|
322
|
+
prefix: "pt"
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
number: numericFields,
|
|
326
|
+
integer: numericFields,
|
|
327
|
+
bigint: numericFields,
|
|
328
|
+
url: [
|
|
329
|
+
{
|
|
330
|
+
kind: "scalar",
|
|
331
|
+
key: "path",
|
|
332
|
+
prefix: "path"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
kind: "scalar",
|
|
336
|
+
key: "min",
|
|
337
|
+
prefix: "mn"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
kind: "scalar",
|
|
341
|
+
key: "max",
|
|
342
|
+
prefix: "mx"
|
|
343
|
+
}
|
|
188
344
|
],
|
|
189
|
-
|
|
190
|
-
|
|
345
|
+
uuid: rangeFields,
|
|
346
|
+
email: rangeFields,
|
|
347
|
+
datetime: [{
|
|
348
|
+
kind: "bool",
|
|
349
|
+
key: "offset",
|
|
350
|
+
prefix: "o"
|
|
351
|
+
}, {
|
|
352
|
+
kind: "bool",
|
|
353
|
+
key: "local",
|
|
354
|
+
prefix: "l"
|
|
355
|
+
}],
|
|
356
|
+
date: [{
|
|
357
|
+
kind: "scalar",
|
|
358
|
+
key: "representation",
|
|
359
|
+
prefix: "rep"
|
|
360
|
+
}],
|
|
361
|
+
time: [{
|
|
362
|
+
kind: "scalar",
|
|
363
|
+
key: "representation",
|
|
364
|
+
prefix: "rep"
|
|
365
|
+
}]
|
|
191
366
|
};
|
|
367
|
+
function serializeShapeField(field, node, record) {
|
|
368
|
+
switch (field.kind) {
|
|
369
|
+
case "scalar": return `${field.prefix}:${record[field.key] ?? ""}`;
|
|
370
|
+
case "bool": return `${field.prefix}:${record[field.key] ? 1 : 0}`;
|
|
371
|
+
case "child": {
|
|
372
|
+
const child = record[field.key];
|
|
373
|
+
return `${field.prefix}:${child ? signatureOf(child) : ""}`;
|
|
374
|
+
}
|
|
375
|
+
case "children": {
|
|
376
|
+
const children = record[field.key] ?? [];
|
|
377
|
+
return `${field.prefix}[${children.map((c) => signatureOf(c)).join(",")}]`;
|
|
378
|
+
}
|
|
379
|
+
case "objectProps": return `p[${(node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema)}`).join(",")}]`;
|
|
380
|
+
case "additionalProps": {
|
|
381
|
+
const obj = node;
|
|
382
|
+
if (typeof obj.additionalProperties === "boolean") return `ab:${obj.additionalProperties}`;
|
|
383
|
+
if (obj.additionalProperties) return `as:${signatureOf(obj.additionalProperties)}`;
|
|
384
|
+
return "";
|
|
385
|
+
}
|
|
386
|
+
case "patternProps": {
|
|
387
|
+
const obj = node;
|
|
388
|
+
return `pp[${obj.patternProperties ? Object.keys(obj.patternProperties).sort().map((key) => `${key}=${signatureOf(obj.patternProperties[key])}`).join(",") : ""}]`;
|
|
389
|
+
}
|
|
390
|
+
case "enumValues": {
|
|
391
|
+
const en = node;
|
|
392
|
+
let values = "";
|
|
393
|
+
if (en.namedEnumValues?.length) values = en.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
|
|
394
|
+
else if (en.enumValues?.length) values = en.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
|
|
395
|
+
return `v[${values}]`;
|
|
396
|
+
}
|
|
397
|
+
case "refTarget": return `->${refTargetName(node)}`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
192
400
|
/**
|
|
193
|
-
*
|
|
401
|
+
* Builds the local shape descriptor that {@link signatureOf} hashes: the node's kind, flags,
|
|
402
|
+
* constraints, and its children's signatures.
|
|
194
403
|
*/
|
|
195
|
-
function
|
|
196
|
-
|
|
404
|
+
function describeShape(node) {
|
|
405
|
+
const flags = flagsDescriptor(node);
|
|
406
|
+
const fields = SHAPE_KEYS[node.type];
|
|
407
|
+
if (!fields) return `${node.type}|${flags}`;
|
|
408
|
+
const record = node;
|
|
409
|
+
const parts = [`${node.type}|${flags}`];
|
|
410
|
+
for (const field of fields) parts.push(serializeShapeField(field, node, record));
|
|
411
|
+
return parts.join("|");
|
|
197
412
|
}
|
|
198
413
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
414
|
+
* Node → digest cache, keyed by identity. A `WeakMap` so entries die with the node, and so a tree
|
|
415
|
+
* hashed during dedupe planning is not walked again when it is rewritten during streaming. Reuse
|
|
416
|
+
* is safe because a signature depends only on content, and nodes are immutable once created.
|
|
417
|
+
*/
|
|
418
|
+
const signatureCache = /* @__PURE__ */ new WeakMap();
|
|
419
|
+
/**
|
|
420
|
+
* Computes a deterministic, shape-only signature (a content hash) for a schema node. Two schemas
|
|
421
|
+
* share a signature when they are structurally identical, ignoring documentation (`name`, `title`,
|
|
422
|
+
* `description`, `example`, `default`, `deprecated`) and usage-slot flags (`optional`, `nullish`,
|
|
423
|
+
* `readOnly`, `writeOnly`). `nullable` is kept because it changes the produced type, and `ref`
|
|
424
|
+
* nodes compare by target name, which also terminates on circular shapes.
|
|
202
425
|
*
|
|
203
|
-
* @example
|
|
426
|
+
* @example Two enums with different descriptions share a signature
|
|
204
427
|
* ```ts
|
|
205
|
-
*
|
|
206
|
-
*
|
|
428
|
+
* signatureOf(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'], description: 'x' })) ===
|
|
429
|
+
* signatureOf(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'] }))
|
|
207
430
|
* ```
|
|
208
431
|
*/
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const value = record[key];
|
|
216
|
-
if (Array.isArray(value)) {
|
|
217
|
-
for (const item of value) if (isNode(item)) yield item;
|
|
218
|
-
} else if (isNode(value)) yield value;
|
|
219
|
-
}
|
|
432
|
+
function signatureOf(node) {
|
|
433
|
+
const cached = signatureCache.get(node);
|
|
434
|
+
if (cached !== void 0) return cached;
|
|
435
|
+
const signature = (0, node_crypto.hash)("sha256", describeShape(node), "hex");
|
|
436
|
+
signatureCache.set(node, signature);
|
|
437
|
+
return signature;
|
|
220
438
|
}
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/nodes/code.ts
|
|
221
441
|
/**
|
|
222
|
-
*
|
|
223
|
-
* traversable node kinds have an entry. Every other kind resolves to
|
|
224
|
-
* `undefined` and is skipped.
|
|
442
|
+
* Definition for the {@link ConstNode}.
|
|
225
443
|
*/
|
|
226
|
-
const
|
|
227
|
-
Input: "input",
|
|
228
|
-
Output: "output",
|
|
229
|
-
Operation: "operation",
|
|
230
|
-
Schema: "schema",
|
|
231
|
-
Property: "property",
|
|
232
|
-
Parameter: "parameter",
|
|
233
|
-
Response: "response"
|
|
234
|
-
};
|
|
444
|
+
const constDef = defineNode({ kind: "Const" });
|
|
235
445
|
/**
|
|
236
|
-
*
|
|
237
|
-
* context. Returns the callback's result (a replacement node, a collected
|
|
238
|
-
* value, or `undefined` when no callback is registered for the kind).
|
|
446
|
+
* Creates a `ConstNode` representing a TypeScript `const` declaration.
|
|
239
447
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
448
|
+
* @example Exported constant with type and `as const`
|
|
449
|
+
* ```ts
|
|
450
|
+
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
451
|
+
* // export const pets: Pet[] = ... as const
|
|
452
|
+
* ```
|
|
243
453
|
*/
|
|
244
|
-
|
|
245
|
-
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
246
|
-
if (!key) return void 0;
|
|
247
|
-
const fn = visitor[key];
|
|
248
|
-
return fn?.(node, { parent });
|
|
249
|
-
}
|
|
454
|
+
const createConst = constDef.create;
|
|
250
455
|
/**
|
|
251
|
-
*
|
|
252
|
-
|
|
456
|
+
* Definition for the {@link TypeNode}.
|
|
457
|
+
*/
|
|
458
|
+
const typeDef = defineNode({ kind: "Type" });
|
|
459
|
+
/**
|
|
460
|
+
* Creates a `TypeNode` representing a TypeScript `type` alias declaration.
|
|
253
461
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```ts
|
|
464
|
+
* createType({ name: 'Pet', export: true })
|
|
465
|
+
* // export type Pet = ...
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
const createType = typeDef.create;
|
|
469
|
+
/**
|
|
470
|
+
* Definition for the {@link FunctionNode}.
|
|
471
|
+
*/
|
|
472
|
+
const functionDef = defineNode({ kind: "Function" });
|
|
473
|
+
/**
|
|
474
|
+
* Creates a `FunctionNode` representing a TypeScript `function` declaration.
|
|
257
475
|
*
|
|
258
|
-
* @example
|
|
476
|
+
* @example
|
|
259
477
|
* ```ts
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* console.log(node.operationId)
|
|
263
|
-
* },
|
|
264
|
-
* })
|
|
478
|
+
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
479
|
+
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
265
480
|
* ```
|
|
481
|
+
*/
|
|
482
|
+
const createFunction = functionDef.create;
|
|
483
|
+
/**
|
|
484
|
+
* Definition for the {@link ArrowFunctionNode}.
|
|
485
|
+
*/
|
|
486
|
+
const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
|
|
487
|
+
/**
|
|
488
|
+
* Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
|
|
266
489
|
*
|
|
267
|
-
* @example
|
|
490
|
+
* @example
|
|
268
491
|
* ```ts
|
|
269
|
-
*
|
|
492
|
+
* createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
|
|
493
|
+
* // export const double = (n: number) => ...
|
|
270
494
|
* ```
|
|
271
495
|
*/
|
|
272
|
-
|
|
273
|
-
return _walk(node, options, (options.depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
274
|
-
}
|
|
275
|
-
async function _walk(node, visitor, recurse, limit, parent) {
|
|
276
|
-
await limit(() => applyVisitor(node, visitor, parent));
|
|
277
|
-
const children = Array.from(getChildren(node, recurse));
|
|
278
|
-
if (children.length === 0) return;
|
|
279
|
-
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
|
|
280
|
-
}
|
|
281
|
-
function transform(node, options) {
|
|
282
|
-
const { depth, parent, ...visitor } = options;
|
|
283
|
-
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
284
|
-
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
285
|
-
if (rebuilt === node) return node;
|
|
286
|
-
const finalize = nodeFinalizers[rebuilt.kind];
|
|
287
|
-
return finalize ? finalize(rebuilt) : rebuilt;
|
|
288
|
-
}
|
|
496
|
+
const createArrowFunction = arrowFunctionDef.create;
|
|
289
497
|
/**
|
|
290
|
-
*
|
|
291
|
-
* resync schema optionality against their `required` flag once the schema may
|
|
292
|
-
* have changed.
|
|
498
|
+
* Definition for the {@link TextNode}.
|
|
293
499
|
*/
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
500
|
+
const textDef = defineNode({
|
|
501
|
+
kind: "Text",
|
|
502
|
+
build: (value) => ({ value })
|
|
503
|
+
});
|
|
298
504
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
505
|
+
* Creates a {@link TextNode} representing a raw string fragment in the source output.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* ```ts
|
|
509
|
+
* createText('return fetch(id)')
|
|
510
|
+
* // { kind: 'Text', value: 'return fetch(id)' }
|
|
511
|
+
* ```
|
|
302
512
|
*/
|
|
303
|
-
|
|
304
|
-
if (node.kind === "Schema" && !recurse) return node;
|
|
305
|
-
const keys = visitorKeysByKind[node.kind];
|
|
306
|
-
if (!keys) return node;
|
|
307
|
-
const record = node;
|
|
308
|
-
const childOptions = {
|
|
309
|
-
...options,
|
|
310
|
-
parent: node
|
|
311
|
-
};
|
|
312
|
-
let updates;
|
|
313
|
-
for (const key of keys) {
|
|
314
|
-
if (!(key in record)) continue;
|
|
315
|
-
const value = record[key];
|
|
316
|
-
if (Array.isArray(value)) {
|
|
317
|
-
let changed = false;
|
|
318
|
-
const mapped = value.map((item) => {
|
|
319
|
-
if (!isNode(item)) return item;
|
|
320
|
-
const next = transform(item, childOptions);
|
|
321
|
-
if (next !== item) changed = true;
|
|
322
|
-
return next;
|
|
323
|
-
});
|
|
324
|
-
if (changed) (updates ??= {})[key] = mapped;
|
|
325
|
-
} else if (isNode(value)) {
|
|
326
|
-
const next = transform(value, childOptions);
|
|
327
|
-
if (next !== value) (updates ??= {})[key] = next;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return updates ? {
|
|
331
|
-
...node,
|
|
332
|
-
...updates
|
|
333
|
-
} : node;
|
|
334
|
-
}
|
|
513
|
+
const createText = textDef.create;
|
|
335
514
|
/**
|
|
336
|
-
*
|
|
337
|
-
|
|
515
|
+
* Definition for the {@link BreakNode}.
|
|
516
|
+
*/
|
|
517
|
+
const breakDef = defineNode({
|
|
518
|
+
kind: "Break",
|
|
519
|
+
build: () => ({})
|
|
520
|
+
});
|
|
521
|
+
/**
|
|
522
|
+
* Creates a {@link BreakNode} representing a line break in the source output.
|
|
338
523
|
*
|
|
339
|
-
* @example
|
|
524
|
+
* @example
|
|
340
525
|
* ```ts
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* operation(node) {
|
|
344
|
-
* return node.operationId
|
|
345
|
-
* },
|
|
346
|
-
* })) {
|
|
347
|
-
* ids.push(id)
|
|
348
|
-
* }
|
|
526
|
+
* createBreak()
|
|
527
|
+
* // { kind: 'Break' }
|
|
349
528
|
* ```
|
|
350
529
|
*/
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
354
|
-
const v = applyVisitor(node, visitor, parent);
|
|
355
|
-
if (v != null) yield v;
|
|
356
|
-
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
357
|
-
...options,
|
|
358
|
-
parent: node
|
|
359
|
-
});
|
|
530
|
+
function createBreak() {
|
|
531
|
+
return breakDef.create();
|
|
360
532
|
}
|
|
361
533
|
/**
|
|
362
|
-
*
|
|
363
|
-
|
|
534
|
+
* Definition for the {@link JsxNode}.
|
|
535
|
+
*/
|
|
536
|
+
const jsxDef = defineNode({
|
|
537
|
+
kind: "Jsx",
|
|
538
|
+
build: (value) => ({ value })
|
|
539
|
+
});
|
|
540
|
+
/**
|
|
541
|
+
* Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
|
|
364
542
|
*
|
|
365
|
-
* @example
|
|
543
|
+
* @example
|
|
366
544
|
* ```ts
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* return node.operationId
|
|
370
|
-
* },
|
|
371
|
-
* })
|
|
545
|
+
* createJsx('<>\n <a href={href}>Open</a>\n</>')
|
|
546
|
+
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
372
547
|
* ```
|
|
373
548
|
*/
|
|
374
|
-
|
|
375
|
-
return Array.from(collectLazy(node, options));
|
|
376
|
-
}
|
|
549
|
+
const createJsx = jsxDef.create;
|
|
377
550
|
//#endregion
|
|
378
|
-
//#region src/
|
|
379
|
-
const plainStringTypes = new Set([
|
|
380
|
-
"string",
|
|
381
|
-
"uuid",
|
|
382
|
-
"email",
|
|
383
|
-
"url",
|
|
384
|
-
"datetime"
|
|
385
|
-
]);
|
|
551
|
+
//#region src/nodes/content.ts
|
|
386
552
|
/**
|
|
387
|
-
*
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
553
|
+
* Definition for the {@link ContentNode}.
|
|
554
|
+
*/
|
|
555
|
+
const contentDef = defineNode({
|
|
556
|
+
kind: "Content",
|
|
557
|
+
children: ["schema"]
|
|
558
|
+
});
|
|
559
|
+
/**
|
|
560
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
561
|
+
*/
|
|
562
|
+
const createContent = contentDef.create;
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/nodes/file.ts
|
|
565
|
+
/**
|
|
566
|
+
* Definition for the {@link ImportNode}.
|
|
567
|
+
*/
|
|
568
|
+
const importDef = defineNode({ kind: "Import" });
|
|
569
|
+
/**
|
|
570
|
+
* Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
|
|
391
571
|
*
|
|
392
|
-
* @example
|
|
572
|
+
* @example Named import
|
|
393
573
|
* ```ts
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
574
|
+
* createImport({ name: ['useState'], path: 'react' })
|
|
575
|
+
* // import { useState } from 'react'
|
|
397
576
|
* ```
|
|
398
577
|
*/
|
|
399
|
-
|
|
400
|
-
const ref = narrowSchema(node, "ref");
|
|
401
|
-
if (!ref) return node;
|
|
402
|
-
if (!ref.schema) return node;
|
|
403
|
-
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
404
|
-
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
405
|
-
return createSchema({
|
|
406
|
-
...ref.schema,
|
|
407
|
-
...definedOverrides
|
|
408
|
-
});
|
|
409
|
-
}
|
|
578
|
+
const createImport = importDef.create;
|
|
410
579
|
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* Covers `string`, `uuid`, `email`, `url`, and `datetime` types. For `date` and `time`
|
|
414
|
-
* types, returns `true` only when `representation` is `'string'` rather than `'date'`.
|
|
580
|
+
* Definition for the {@link ExportNode}.
|
|
415
581
|
*/
|
|
416
|
-
|
|
417
|
-
if (plainStringTypes.has(node.type)) return true;
|
|
418
|
-
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
419
|
-
if (temporal) return temporal.representation !== "date";
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
582
|
+
const exportDef = defineNode({ kind: "Export" });
|
|
422
583
|
/**
|
|
423
|
-
*
|
|
584
|
+
* Creates an `ExportNode` representing a language-agnostic export/public API declaration.
|
|
424
585
|
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
586
|
+
* @example Named export
|
|
587
|
+
* ```ts
|
|
588
|
+
* createExport({ name: ['Pet'], path: './Pet' })
|
|
589
|
+
* // export { Pet } from './Pet'
|
|
590
|
+
* ```
|
|
428
591
|
*/
|
|
429
|
-
const
|
|
430
|
-
const transformed = casing === "camelcase" || !require_utils.isValidVarName(param.name) ? require_utils.camelCase(param.name) : param.name;
|
|
431
|
-
return {
|
|
432
|
-
...param,
|
|
433
|
-
name: transformed
|
|
434
|
-
};
|
|
435
|
-
})));
|
|
436
|
-
function caseParams(params, casing) {
|
|
437
|
-
if (!casing) return params;
|
|
438
|
-
return caseParamsMemo(params)(casing);
|
|
439
|
-
}
|
|
592
|
+
const createExport = exportDef.create;
|
|
440
593
|
/**
|
|
441
|
-
*
|
|
594
|
+
* Definition for the {@link SourceNode}.
|
|
595
|
+
*/
|
|
596
|
+
const sourceDef = defineNode({ kind: "Source" });
|
|
597
|
+
/**
|
|
598
|
+
* Creates a `SourceNode` representing a fragment of source code within a file.
|
|
442
599
|
*
|
|
443
600
|
* @example
|
|
444
601
|
* ```ts
|
|
445
|
-
*
|
|
446
|
-
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
602
|
+
* createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
|
|
447
603
|
* ```
|
|
448
604
|
*/
|
|
449
|
-
|
|
450
|
-
return createSchema({
|
|
451
|
-
type: "object",
|
|
452
|
-
primitive: "object",
|
|
453
|
-
properties: [createProperty({
|
|
454
|
-
name: propertyName,
|
|
455
|
-
schema: createSchema({
|
|
456
|
-
type: "enum",
|
|
457
|
-
primitive: "string",
|
|
458
|
-
enumValues: [value]
|
|
459
|
-
}),
|
|
460
|
-
required: true
|
|
461
|
-
})]
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
function resolveParamsType({ node, param, resolver }) {
|
|
465
|
-
if (!resolver) return createParamsType({
|
|
466
|
-
variant: "reference",
|
|
467
|
-
name: param.schema.primitive ?? "unknown"
|
|
468
|
-
});
|
|
469
|
-
const individualName = resolver.resolveParamName(node, param);
|
|
470
|
-
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
471
|
-
const groupResolvers = {
|
|
472
|
-
path: resolver.resolvePathParamsName,
|
|
473
|
-
query: resolver.resolveQueryParamsName,
|
|
474
|
-
header: resolver.resolveHeaderParamsName
|
|
475
|
-
};
|
|
476
|
-
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
477
|
-
if (groupName && groupName !== individualName) return createParamsType({
|
|
478
|
-
variant: "member",
|
|
479
|
-
base: groupName,
|
|
480
|
-
key: param.name
|
|
481
|
-
});
|
|
482
|
-
return createParamsType({
|
|
483
|
-
variant: "reference",
|
|
484
|
-
name: individualName
|
|
485
|
-
});
|
|
486
|
-
}
|
|
605
|
+
const createSource = sourceDef.create;
|
|
487
606
|
/**
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
* Centralizes parameter grouping logic for all plugins. Provide a `resolver` for type name resolution
|
|
491
|
-
* and `extraParams` for plugin-specific trailing parameters (e.g., `options` objects).
|
|
492
|
-
* Supports three grouping modes: `object` (single destructured param), `inline` (separate params),
|
|
493
|
-
* and `inlineSpread` (rest parameter). Use `CreateOperationParamsOptions` to fine-tune output.
|
|
607
|
+
* Definition for the {@link FileNode}. The fully resolved builder lives in
|
|
608
|
+
* `createFile`, so this definition only supplies the guard.
|
|
494
609
|
*/
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const paramsName = paramNames?.params ?? "params";
|
|
499
|
-
const headersName = paramNames?.headers ?? "headers";
|
|
500
|
-
const pathName = paramNames?.path ?? "pathParams";
|
|
501
|
-
const wrapType = (type) => createParamsType({
|
|
502
|
-
variant: "reference",
|
|
503
|
-
name: typeWrapper ? typeWrapper(type) : type
|
|
504
|
-
});
|
|
505
|
-
const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
|
|
506
|
-
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
507
|
-
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
508
|
-
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
509
|
-
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
510
|
-
const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
511
|
-
const bodyRequired = node.requestBody?.required ?? false;
|
|
512
|
-
const queryGroupType = resolver ? resolveGroupType({
|
|
513
|
-
node,
|
|
514
|
-
params: queryParams,
|
|
515
|
-
groupMethod: resolver.resolveQueryParamsName,
|
|
516
|
-
resolver
|
|
517
|
-
}) : void 0;
|
|
518
|
-
const headerGroupType = resolver ? resolveGroupType({
|
|
519
|
-
node,
|
|
520
|
-
params: headerParams,
|
|
521
|
-
groupMethod: resolver.resolveHeaderParamsName,
|
|
522
|
-
resolver
|
|
523
|
-
}) : void 0;
|
|
524
|
-
const params = [];
|
|
525
|
-
if (paramsType === "object") {
|
|
526
|
-
const children = [
|
|
527
|
-
...pathParams.map((p) => {
|
|
528
|
-
const type = resolveParamsType({
|
|
529
|
-
node,
|
|
530
|
-
param: p,
|
|
531
|
-
resolver
|
|
532
|
-
});
|
|
533
|
-
return createFunctionParameter({
|
|
534
|
-
name: p.name,
|
|
535
|
-
type: wrapTypeNode(type),
|
|
536
|
-
optional: !p.required
|
|
537
|
-
});
|
|
538
|
-
}),
|
|
539
|
-
...bodyType ? [createFunctionParameter({
|
|
540
|
-
name: dataName,
|
|
541
|
-
type: bodyType,
|
|
542
|
-
optional: !bodyRequired
|
|
543
|
-
})] : [],
|
|
544
|
-
...buildGroupParam({
|
|
545
|
-
name: paramsName,
|
|
546
|
-
node,
|
|
547
|
-
params: queryParams,
|
|
548
|
-
groupType: queryGroupType,
|
|
549
|
-
resolver,
|
|
550
|
-
wrapType
|
|
551
|
-
}),
|
|
552
|
-
...buildGroupParam({
|
|
553
|
-
name: headersName,
|
|
554
|
-
node,
|
|
555
|
-
params: headerParams,
|
|
556
|
-
groupType: headerGroupType,
|
|
557
|
-
resolver,
|
|
558
|
-
wrapType
|
|
559
|
-
})
|
|
560
|
-
];
|
|
561
|
-
if (children.length) params.push(createParameterGroup({
|
|
562
|
-
properties: children,
|
|
563
|
-
default: children.every((c) => c.optional) ? "{}" : void 0
|
|
564
|
-
}));
|
|
565
|
-
} else {
|
|
566
|
-
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
567
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
568
|
-
params.push(createFunctionParameter({
|
|
569
|
-
name: pathName,
|
|
570
|
-
type: spreadType ? wrapType(spreadType) : void 0,
|
|
571
|
-
rest: true
|
|
572
|
-
}));
|
|
573
|
-
} else {
|
|
574
|
-
const pathChildren = pathParams.map((p) => {
|
|
575
|
-
const type = resolveParamsType({
|
|
576
|
-
node,
|
|
577
|
-
param: p,
|
|
578
|
-
resolver
|
|
579
|
-
});
|
|
580
|
-
return createFunctionParameter({
|
|
581
|
-
name: p.name,
|
|
582
|
-
type: wrapTypeNode(type),
|
|
583
|
-
optional: !p.required
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
params.push(createParameterGroup({
|
|
587
|
-
properties: pathChildren,
|
|
588
|
-
inline: pathParamsType === "inline",
|
|
589
|
-
default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
|
|
590
|
-
}));
|
|
591
|
-
}
|
|
592
|
-
if (bodyType) params.push(createFunctionParameter({
|
|
593
|
-
name: dataName,
|
|
594
|
-
type: bodyType,
|
|
595
|
-
optional: !bodyRequired
|
|
596
|
-
}));
|
|
597
|
-
params.push(...buildGroupParam({
|
|
598
|
-
name: paramsName,
|
|
599
|
-
node,
|
|
600
|
-
params: queryParams,
|
|
601
|
-
groupType: queryGroupType,
|
|
602
|
-
resolver,
|
|
603
|
-
wrapType
|
|
604
|
-
}));
|
|
605
|
-
params.push(...buildGroupParam({
|
|
606
|
-
name: headersName,
|
|
607
|
-
node,
|
|
608
|
-
params: headerParams,
|
|
609
|
-
groupType: headerGroupType,
|
|
610
|
-
resolver,
|
|
611
|
-
wrapType
|
|
612
|
-
}));
|
|
613
|
-
}
|
|
614
|
-
params.push(...extraParams);
|
|
615
|
-
return createFunctionParameters({ params });
|
|
616
|
-
}
|
|
610
|
+
const fileDef = defineNode({ kind: "File" });
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/nodes/function.ts
|
|
617
613
|
/**
|
|
618
|
-
*
|
|
619
|
-
|
|
614
|
+
* Definition for the {@link TypeLiteralNode}.
|
|
615
|
+
*/
|
|
616
|
+
const typeLiteralDef = defineNode({ kind: "TypeLiteral" });
|
|
617
|
+
/**
|
|
618
|
+
* Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
|
|
620
619
|
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```ts
|
|
622
|
+
* createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
|
|
623
|
+
* // { petId: string }
|
|
624
|
+
* ```
|
|
623
625
|
*/
|
|
624
|
-
|
|
625
|
-
if (groupType) return [createFunctionParameter({
|
|
626
|
-
name,
|
|
627
|
-
type: groupType.type.kind === "ParamsType" && groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
|
|
628
|
-
optional: groupType.optional
|
|
629
|
-
})];
|
|
630
|
-
if (params.length) return [createFunctionParameter({
|
|
631
|
-
name,
|
|
632
|
-
type: toStructType({
|
|
633
|
-
node,
|
|
634
|
-
params,
|
|
635
|
-
resolver
|
|
636
|
-
}),
|
|
637
|
-
optional: params.every((p) => !p.required)
|
|
638
|
-
})];
|
|
639
|
-
return [];
|
|
640
|
-
}
|
|
626
|
+
const createTypeLiteral = typeLiteralDef.create;
|
|
641
627
|
/**
|
|
642
|
-
*
|
|
643
|
-
* Returns `null` when the group name equals the individual param name (no real group).
|
|
628
|
+
* Definition for the {@link IndexedAccessTypeNode}.
|
|
644
629
|
*/
|
|
645
|
-
|
|
646
|
-
if (!params.length) return null;
|
|
647
|
-
const firstParam = params[0];
|
|
648
|
-
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
649
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
650
|
-
const allOptional = params.every((p) => !p.required);
|
|
651
|
-
return {
|
|
652
|
-
type: createParamsType({
|
|
653
|
-
variant: "reference",
|
|
654
|
-
name: groupName
|
|
655
|
-
}),
|
|
656
|
-
optional: allOptional
|
|
657
|
-
};
|
|
658
|
-
}
|
|
630
|
+
const indexedAccessTypeDef = defineNode({ kind: "IndexedAccessType" });
|
|
659
631
|
/**
|
|
660
|
-
*
|
|
632
|
+
* Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
|
|
661
633
|
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
variant: "struct",
|
|
668
|
-
properties: params.map((p) => ({
|
|
669
|
-
name: p.name,
|
|
670
|
-
optional: !p.required,
|
|
671
|
-
type: resolveParamsType({
|
|
672
|
-
node,
|
|
673
|
-
param: p,
|
|
674
|
-
resolver
|
|
675
|
-
})
|
|
676
|
-
}))
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
function sourceKey(source) {
|
|
680
|
-
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
681
|
-
}
|
|
682
|
-
function pathTypeKey(path, isTypeOnly) {
|
|
683
|
-
return `${path}:${isTypeOnly ?? false}`;
|
|
684
|
-
}
|
|
685
|
-
function exportKey(path, name, isTypeOnly, asAlias) {
|
|
686
|
-
return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
|
|
687
|
-
}
|
|
688
|
-
function importKey(path, name, isTypeOnly) {
|
|
689
|
-
return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Computes a multi-level sort key for exports and imports:
|
|
693
|
-
* non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
|
|
634
|
+
* @example
|
|
635
|
+
* ```ts
|
|
636
|
+
* createIndexedAccessType({ objectType: 'DeletePetPathParams', indexType: 'petId' })
|
|
637
|
+
* // DeletePetPathParams['petId']
|
|
638
|
+
* ```
|
|
694
639
|
*/
|
|
695
|
-
|
|
696
|
-
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
697
|
-
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
698
|
-
const hasName = node.name != null ? "1" : "0";
|
|
699
|
-
const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
|
|
700
|
-
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
701
|
-
}
|
|
640
|
+
const createIndexedAccessType = indexedAccessTypeDef.create;
|
|
702
641
|
/**
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* Unnamed sources are deduplicated by object reference. Returns a deduplicated array in original order.
|
|
642
|
+
* Definition for the {@link ObjectBindingPatternNode}.
|
|
706
643
|
*/
|
|
707
|
-
|
|
708
|
-
const seen = /* @__PURE__ */ new Map();
|
|
709
|
-
for (const source of sources) {
|
|
710
|
-
const key = sourceKey(source);
|
|
711
|
-
if (!seen.has(key)) seen.set(key, source);
|
|
712
|
-
}
|
|
713
|
-
return [...seen.values()];
|
|
714
|
-
}
|
|
644
|
+
const objectBindingPatternDef = defineNode({ kind: "ObjectBindingPattern" });
|
|
715
645
|
/**
|
|
716
|
-
*
|
|
646
|
+
* Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
|
|
717
647
|
*
|
|
718
|
-
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```ts
|
|
650
|
+
* createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
|
|
651
|
+
* // { id, name }
|
|
652
|
+
* ```
|
|
719
653
|
*/
|
|
720
|
-
|
|
721
|
-
const merged = new Set(existing);
|
|
722
|
-
for (const name of incoming) merged.add(name);
|
|
723
|
-
return [...merged];
|
|
724
|
-
}
|
|
654
|
+
const createObjectBindingPattern = objectBindingPatternDef.create;
|
|
725
655
|
/**
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
|
|
656
|
+
* Definition for the {@link FunctionParameterNode}. `optional` defaults to `false`.
|
|
657
|
+
* Passing `properties` builds a destructured group: an {@link ObjectBindingPatternNode} name
|
|
658
|
+
* paired with a {@link TypeLiteralNode} type.
|
|
730
659
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const newItem = {
|
|
749
|
-
...curr,
|
|
750
|
-
name: [...new Set(name)]
|
|
751
|
-
};
|
|
752
|
-
result.push(newItem);
|
|
753
|
-
namedByPath.set(key, newItem);
|
|
754
|
-
}
|
|
755
|
-
} else {
|
|
756
|
-
const key = exportKey(path, name, isTypeOnly, asAlias);
|
|
757
|
-
if (!seen.has(key)) {
|
|
758
|
-
result.push(curr);
|
|
759
|
-
seen.add(key);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
660
|
+
const functionParameterDef = defineNode({
|
|
661
|
+
kind: "FunctionParameter",
|
|
662
|
+
build: (input) => {
|
|
663
|
+
if ("properties" in input) return {
|
|
664
|
+
name: createObjectBindingPattern({ elements: input.properties.map((p) => ({ name: p.name })) }),
|
|
665
|
+
type: createTypeLiteral({ members: input.properties.map((p) => ({
|
|
666
|
+
name: p.name,
|
|
667
|
+
type: p.type,
|
|
668
|
+
optional: p.optional ?? false
|
|
669
|
+
})) }),
|
|
670
|
+
optional: input.optional ?? false,
|
|
671
|
+
...input.default !== void 0 ? { default: input.default } : {}
|
|
672
|
+
};
|
|
673
|
+
return {
|
|
674
|
+
optional: false,
|
|
675
|
+
...input
|
|
676
|
+
};
|
|
762
677
|
}
|
|
763
|
-
|
|
764
|
-
}
|
|
678
|
+
});
|
|
765
679
|
/**
|
|
766
|
-
*
|
|
680
|
+
* Creates a `FunctionParameterNode`. `optional` defaults to `false`.
|
|
767
681
|
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
682
|
+
* @example Optional param
|
|
683
|
+
* ```ts
|
|
684
|
+
* createFunctionParameter({ name: 'params', type: 'QueryParams', optional: true })
|
|
685
|
+
* // → params?: QueryParams
|
|
686
|
+
* ```
|
|
770
687
|
*
|
|
771
|
-
* @
|
|
688
|
+
* @example Destructured group
|
|
689
|
+
* ```ts
|
|
690
|
+
* createFunctionParameter({ properties: [{ name: 'id', type: 'string' }, { name: 'name', type: 'string', optional: true }], default: '{}' })
|
|
691
|
+
* // → { id, name }: { id: string; name?: string } = {}
|
|
692
|
+
* ```
|
|
772
693
|
*/
|
|
773
|
-
|
|
774
|
-
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
775
|
-
const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
|
|
776
|
-
const importNameMemo = /* @__PURE__ */ new Map();
|
|
777
|
-
const canonicalizeName = (n) => {
|
|
778
|
-
if (typeof n === "string") return n;
|
|
779
|
-
const key = `${n.propertyName}:${n.name ?? ""}`;
|
|
780
|
-
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
781
|
-
return importNameMemo.get(key);
|
|
782
|
-
};
|
|
783
|
-
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
784
|
-
for (const node of imports) {
|
|
785
|
-
if (!Array.isArray(node.name)) continue;
|
|
786
|
-
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
787
|
-
}
|
|
788
|
-
const result = [];
|
|
789
|
-
const namedByPath = /* @__PURE__ */ new Map();
|
|
790
|
-
const seen = /* @__PURE__ */ new Set();
|
|
791
|
-
const keyed = imports.map((node) => ({
|
|
792
|
-
node,
|
|
793
|
-
key: sortKey(node)
|
|
794
|
-
}));
|
|
795
|
-
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
796
|
-
for (const { node: curr } of keyed) {
|
|
797
|
-
if (curr.path === curr.root) continue;
|
|
798
|
-
const { path, isTypeOnly } = curr;
|
|
799
|
-
let { name } = curr;
|
|
800
|
-
if (Array.isArray(name)) {
|
|
801
|
-
name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
|
|
802
|
-
if (!name.length) continue;
|
|
803
|
-
const key = pathTypeKey(path, isTypeOnly);
|
|
804
|
-
const existing = namedByPath.get(key);
|
|
805
|
-
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
806
|
-
else {
|
|
807
|
-
const newItem = {
|
|
808
|
-
...curr,
|
|
809
|
-
name
|
|
810
|
-
};
|
|
811
|
-
result.push(newItem);
|
|
812
|
-
namedByPath.set(key, newItem);
|
|
813
|
-
}
|
|
814
|
-
} else {
|
|
815
|
-
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
816
|
-
const key = importKey(path, name, isTypeOnly);
|
|
817
|
-
if (!seen.has(key)) {
|
|
818
|
-
result.push(curr);
|
|
819
|
-
seen.add(key);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return result;
|
|
824
|
-
}
|
|
694
|
+
const createFunctionParameter = functionParameterDef.create;
|
|
825
695
|
/**
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
* Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
|
|
829
|
-
* and nested node content. Used internally to build the full source string for import filtering.
|
|
696
|
+
* Definition for the {@link FunctionParametersNode}.
|
|
830
697
|
*/
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
if (node.kind === "Text") return node.value;
|
|
836
|
-
if (node.kind === "Break") return "";
|
|
837
|
-
if (node.kind === "Jsx") return node.value;
|
|
838
|
-
const parts = [];
|
|
839
|
-
if ("params" in node && node.params) parts.push(node.params);
|
|
840
|
-
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
841
|
-
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
842
|
-
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
843
|
-
const nested = extractStringsFromNodes(node.nodes);
|
|
844
|
-
if (nested) parts.push(nested);
|
|
845
|
-
return parts.join("\n");
|
|
846
|
-
}).filter(Boolean).join("\n");
|
|
847
|
-
}
|
|
698
|
+
const functionParametersDef = defineNode({
|
|
699
|
+
kind: "FunctionParameters",
|
|
700
|
+
defaults: { params: [] }
|
|
701
|
+
});
|
|
848
702
|
/**
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
852
|
-
* identifier for type definitions or error messages.
|
|
703
|
+
* Creates a `FunctionParametersNode` from an ordered list of parameters.
|
|
853
704
|
*
|
|
854
705
|
* @example
|
|
855
706
|
* ```ts
|
|
856
|
-
*
|
|
857
|
-
* //
|
|
707
|
+
* const empty = createFunctionParameters()
|
|
708
|
+
* // { kind: 'FunctionParameters', params: [] }
|
|
858
709
|
* ```
|
|
859
710
|
*/
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
if (node.ref) return require_utils.extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
863
|
-
return node.name ?? node.schema?.name ?? null;
|
|
711
|
+
function createFunctionParameters(props = {}) {
|
|
712
|
+
return functionParametersDef.create(props);
|
|
864
713
|
}
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/nodes/input.ts
|
|
865
716
|
/**
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* Refs are followed by name only, the resolved `node.schema` is not traversed inline.
|
|
869
|
-
* Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
|
|
870
|
-
*
|
|
871
|
-
* @example Collect refs from a single schema
|
|
872
|
-
* ```ts
|
|
873
|
-
* const names = collectReferencedSchemaNames(petSchema)
|
|
874
|
-
* // → Set { 'Category', 'Tag' }
|
|
875
|
-
* ```
|
|
876
|
-
*
|
|
877
|
-
* @example Accumulate refs from multiple schemas into one set
|
|
878
|
-
* ```ts
|
|
879
|
-
* const out = new Set<string>()
|
|
880
|
-
* for (const schema of schemas) {
|
|
881
|
-
* collectReferencedSchemaNames(schema, out)
|
|
882
|
-
* }
|
|
883
|
-
* ```
|
|
717
|
+
* Definition for the {@link InputNode}.
|
|
884
718
|
*/
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
719
|
+
const inputDef = defineNode({
|
|
720
|
+
kind: "Input",
|
|
721
|
+
defaults: {
|
|
722
|
+
schemas: [],
|
|
723
|
+
operations: [],
|
|
724
|
+
meta: {
|
|
725
|
+
circularNames: [],
|
|
726
|
+
enumNames: []
|
|
891
727
|
}
|
|
892
|
-
}
|
|
893
|
-
|
|
728
|
+
},
|
|
729
|
+
children: ["schemas", "operations"],
|
|
730
|
+
visitorKey: "input"
|
|
894
731
|
});
|
|
895
|
-
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
896
|
-
if (!node) return out;
|
|
897
|
-
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
898
|
-
return out;
|
|
899
|
-
}
|
|
900
732
|
/**
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
* An operation uses a schema when any of its parameters, request body content, or responses
|
|
904
|
-
* reference it, directly or indirectly through other named schemas.
|
|
905
|
-
* The walk is iterative and safe against reference cycles.
|
|
906
|
-
*
|
|
907
|
-
* Use this together with `include` filters to determine which schemas from `components/schemas`
|
|
908
|
-
* are reachable from the allowed operations, so that schemas used only by excluded operations
|
|
909
|
-
* are not generated.
|
|
733
|
+
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
910
734
|
*
|
|
911
|
-
* @example
|
|
735
|
+
* @example
|
|
912
736
|
* ```ts
|
|
913
|
-
* const
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
* for (const schema of schemas) {
|
|
917
|
-
* if (schema.name && !allowed.has(schema.name)) continue
|
|
918
|
-
* // … generate schema
|
|
919
|
-
* }
|
|
737
|
+
* const input = createInput()
|
|
738
|
+
* // { kind: 'Input', schemas: [], operations: [] }
|
|
920
739
|
* ```
|
|
740
|
+
*/
|
|
741
|
+
function createInput(overrides = {}) {
|
|
742
|
+
return inputDef.create(overrides);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Creates a streaming `InputNode<true>` from pre-built `AsyncIterable` sources.
|
|
921
746
|
*
|
|
922
|
-
* @example
|
|
747
|
+
* @example
|
|
923
748
|
* ```ts
|
|
924
|
-
* const
|
|
925
|
-
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
749
|
+
* const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
|
|
926
750
|
* ```
|
|
927
751
|
*/
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
for (const name of directRefs) if (!result.has(name)) {
|
|
936
|
-
result.add(name);
|
|
937
|
-
const namedSchema = schemaMap.get(name);
|
|
938
|
-
if (namedSchema) visitSchema(namedSchema);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
for (const op of operations) for (const schema of collectLazy(op, {
|
|
942
|
-
depth: "shallow",
|
|
943
|
-
schema: (node) => node
|
|
944
|
-
})) visitSchema(schema);
|
|
945
|
-
return result;
|
|
946
|
-
}
|
|
947
|
-
function collectUsedSchemaNames(operations, schemas) {
|
|
948
|
-
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
752
|
+
function createStreamInput(schemas, operations, meta) {
|
|
753
|
+
return {
|
|
754
|
+
kind: "Input",
|
|
755
|
+
schemas,
|
|
756
|
+
operations,
|
|
757
|
+
meta
|
|
758
|
+
};
|
|
949
759
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
const graph = /* @__PURE__ */ new Map();
|
|
953
|
-
for (const schema of schemas) {
|
|
954
|
-
if (!schema.name) continue;
|
|
955
|
-
graph.set(schema.name, collectReferencedSchemaNames(schema));
|
|
956
|
-
}
|
|
957
|
-
const circular = /* @__PURE__ */ new Set();
|
|
958
|
-
for (const start of graph.keys()) {
|
|
959
|
-
const visited = /* @__PURE__ */ new Set();
|
|
960
|
-
const stack = [...graph.get(start) ?? []];
|
|
961
|
-
while (stack.length > 0) {
|
|
962
|
-
const node = stack.pop();
|
|
963
|
-
if (node === start) {
|
|
964
|
-
circular.add(start);
|
|
965
|
-
break;
|
|
966
|
-
}
|
|
967
|
-
if (visited.has(node)) continue;
|
|
968
|
-
visited.add(node);
|
|
969
|
-
const next = graph.get(node);
|
|
970
|
-
if (next) for (const r of next) stack.push(r);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
return circular;
|
|
974
|
-
});
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/nodes/requestBody.ts
|
|
975
762
|
/**
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
979
|
-
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
980
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
981
|
-
*
|
|
982
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
763
|
+
* Definition for the {@link RequestBodyNode}, normalizing each content entry into a `ContentNode`.
|
|
983
764
|
*/
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
765
|
+
const requestBodyDef = defineNode({
|
|
766
|
+
kind: "RequestBody",
|
|
767
|
+
build: (props) => ({
|
|
768
|
+
...props,
|
|
769
|
+
content: props.content?.map(createContent)
|
|
770
|
+
}),
|
|
771
|
+
children: ["content"]
|
|
772
|
+
});
|
|
988
773
|
/**
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
* Use `excludeName` to ignore refs to specific schemas (useful when self-references are handled separately).
|
|
992
|
-
* Commonly used with `findCircularSchemas()` to detect where lazy wrappers are needed in code generation.
|
|
993
|
-
*
|
|
994
|
-
* @note Returns `true` for the first matching circular ref found. Use for fast dependency checks.
|
|
774
|
+
* Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
|
|
995
775
|
*/
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
776
|
+
const createRequestBody = requestBodyDef.create;
|
|
777
|
+
//#endregion
|
|
778
|
+
//#region src/nodes/operation.ts
|
|
779
|
+
/**
|
|
780
|
+
* Definition for the {@link OperationNode}. HTTP operations (those carrying both
|
|
781
|
+
* `method` and `path`) are tagged with `protocol: 'http'`, and the request body is
|
|
782
|
+
* normalized into a `RequestBodyNode`.
|
|
783
|
+
*/
|
|
784
|
+
const operationDef = defineNode({
|
|
785
|
+
kind: "Operation",
|
|
786
|
+
build: (props) => {
|
|
787
|
+
const { requestBody, ...rest } = props;
|
|
788
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
789
|
+
return {
|
|
790
|
+
tags: [],
|
|
791
|
+
parameters: [],
|
|
792
|
+
responses: [],
|
|
793
|
+
...rest,
|
|
794
|
+
...isHttp ? { protocol: "http" } : {},
|
|
795
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
796
|
+
};
|
|
797
|
+
},
|
|
798
|
+
children: [
|
|
799
|
+
"parameters",
|
|
800
|
+
"requestBody",
|
|
801
|
+
"responses"
|
|
802
|
+
],
|
|
803
|
+
visitorKey: "operation"
|
|
804
|
+
});
|
|
805
|
+
function createOperation(props) {
|
|
806
|
+
return operationDef.create(props);
|
|
1004
807
|
}
|
|
1005
808
|
//#endregion
|
|
1006
|
-
//#region src/
|
|
809
|
+
//#region src/nodes/output.ts
|
|
1007
810
|
/**
|
|
1008
|
-
*
|
|
1009
|
-
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
1010
|
-
* object properties combine "required" and "nullable" into a single AST.
|
|
1011
|
-
*
|
|
1012
|
-
* - Non-required + non-nullable → `optional: true`.
|
|
1013
|
-
* - Non-required + nullable → `nullish: true`.
|
|
1014
|
-
* - Required → both flags cleared.
|
|
811
|
+
* Definition for the {@link OutputNode}.
|
|
1015
812
|
*/
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
nullish: !required && nullable ? true : void 0
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
813
|
+
const outputDef = defineNode({
|
|
814
|
+
kind: "Output",
|
|
815
|
+
defaults: { files: [] },
|
|
816
|
+
visitorKey: "output"
|
|
817
|
+
});
|
|
1024
818
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1027
|
-
* with the changes applied.
|
|
1028
|
-
*
|
|
1029
|
-
* Mirrors the TypeScript compiler's `factory.updateX` contract, pair it with the
|
|
1030
|
-
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1031
|
-
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1032
|
-
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
819
|
+
* Creates an `OutputNode` with a stable default for `files`.
|
|
1033
820
|
*
|
|
1034
821
|
* @example
|
|
1035
822
|
* ```ts
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
823
|
+
* const output = createOutput()
|
|
824
|
+
* // { kind: 'Output', files: [] }
|
|
1038
825
|
* ```
|
|
1039
826
|
*/
|
|
1040
|
-
function
|
|
1041
|
-
|
|
1042
|
-
...node,
|
|
1043
|
-
...changes
|
|
1044
|
-
};
|
|
1045
|
-
return node;
|
|
827
|
+
function createOutput(overrides = {}) {
|
|
828
|
+
return outputDef.create(overrides);
|
|
1046
829
|
}
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/nodes/parameter.ts
|
|
832
|
+
/**
|
|
833
|
+
* Definition for the {@link ParameterNode}. `required` defaults to `false` and the
|
|
834
|
+
* schema's `optional`/`nullish` flags are kept in sync with it.
|
|
835
|
+
*/
|
|
836
|
+
const parameterDef = defineNode({
|
|
837
|
+
kind: "Parameter",
|
|
838
|
+
build: (props) => {
|
|
839
|
+
const required = props.required ?? false;
|
|
840
|
+
return {
|
|
841
|
+
...props,
|
|
842
|
+
required,
|
|
843
|
+
schema: syncOptionality(props.schema, required)
|
|
844
|
+
};
|
|
845
|
+
},
|
|
846
|
+
children: ["schema"],
|
|
847
|
+
visitorKey: "parameter",
|
|
848
|
+
rebuild: true
|
|
849
|
+
});
|
|
1047
850
|
/**
|
|
1048
|
-
* Creates
|
|
851
|
+
* Creates a `ParameterNode`.
|
|
1049
852
|
*
|
|
1050
853
|
* @example
|
|
1051
854
|
* ```ts
|
|
1052
|
-
* const
|
|
1053
|
-
*
|
|
855
|
+
* const param = createParameter({
|
|
856
|
+
* name: 'petId',
|
|
857
|
+
* in: 'path',
|
|
858
|
+
* required: true,
|
|
859
|
+
* schema: createSchema({ type: 'string' }),
|
|
860
|
+
* })
|
|
1054
861
|
* ```
|
|
862
|
+
*/
|
|
863
|
+
const createParameter = parameterDef.create;
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/nodes/property.ts
|
|
866
|
+
/**
|
|
867
|
+
* Definition for the {@link PropertyNode}. `required` defaults to `false` and the
|
|
868
|
+
* schema's `optional`/`nullish` flags are kept in sync with it.
|
|
869
|
+
*/
|
|
870
|
+
const propertyDef = defineNode({
|
|
871
|
+
kind: "Property",
|
|
872
|
+
build: (props) => {
|
|
873
|
+
const required = props.required ?? false;
|
|
874
|
+
return {
|
|
875
|
+
...props,
|
|
876
|
+
required,
|
|
877
|
+
schema: syncOptionality(props.schema, required)
|
|
878
|
+
};
|
|
879
|
+
},
|
|
880
|
+
children: ["schema"],
|
|
881
|
+
visitorKey: "property",
|
|
882
|
+
rebuild: true
|
|
883
|
+
});
|
|
884
|
+
/**
|
|
885
|
+
* Creates a `PropertyNode`.
|
|
1055
886
|
*
|
|
1056
887
|
* @example
|
|
1057
888
|
* ```ts
|
|
1058
|
-
* const
|
|
1059
|
-
*
|
|
889
|
+
* const property = createProperty({
|
|
890
|
+
* name: 'status',
|
|
891
|
+
* required: true,
|
|
892
|
+
* schema: createSchema({ type: 'string', nullable: true }),
|
|
893
|
+
* })
|
|
894
|
+
* // required=true, no optional/nullish
|
|
1060
895
|
* ```
|
|
1061
896
|
*/
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
897
|
+
const createProperty = propertyDef.create;
|
|
898
|
+
//#endregion
|
|
899
|
+
//#region src/nodes/response.ts
|
|
900
|
+
/**
|
|
901
|
+
* Definition for the {@link ResponseNode}. A single legacy `schema` (with optional
|
|
902
|
+
* `mediaType`/`keysToOmit`) is normalized into one `content` entry.
|
|
903
|
+
*/
|
|
904
|
+
const responseDef = defineNode({
|
|
905
|
+
kind: "Response",
|
|
906
|
+
build: (props) => {
|
|
907
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
908
|
+
const entries = content ?? (schema ? [{
|
|
909
|
+
contentType: mediaType ?? "application/json",
|
|
910
|
+
schema,
|
|
911
|
+
keysToOmit: keysToOmit ?? null
|
|
912
|
+
}] : void 0);
|
|
913
|
+
return {
|
|
914
|
+
...rest,
|
|
915
|
+
content: entries?.map(createContent)
|
|
916
|
+
};
|
|
917
|
+
},
|
|
918
|
+
children: ["content"],
|
|
919
|
+
visitorKey: "response"
|
|
920
|
+
});
|
|
1074
921
|
/**
|
|
1075
|
-
* Creates a
|
|
922
|
+
* Creates a `ResponseNode`.
|
|
1076
923
|
*
|
|
1077
924
|
* @example
|
|
1078
925
|
* ```ts
|
|
1079
|
-
* const
|
|
926
|
+
* const response = createResponse({
|
|
927
|
+
* statusCode: '200',
|
|
928
|
+
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
929
|
+
* })
|
|
1080
930
|
* ```
|
|
1081
931
|
*/
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
932
|
+
const createResponse = responseDef.create;
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/registry.ts
|
|
935
|
+
/**
|
|
936
|
+
* Every node definition. Adding a node means adding its `defineNode` to one
|
|
937
|
+
* `nodes/*.ts` file and listing it here. The visitor tables below derive from it.
|
|
938
|
+
*/
|
|
939
|
+
const nodeDefs = [
|
|
940
|
+
inputDef,
|
|
941
|
+
outputDef,
|
|
942
|
+
operationDef,
|
|
943
|
+
requestBodyDef,
|
|
944
|
+
contentDef,
|
|
945
|
+
responseDef,
|
|
946
|
+
schemaDef,
|
|
947
|
+
propertyDef,
|
|
948
|
+
parameterDef,
|
|
949
|
+
functionParameterDef,
|
|
950
|
+
functionParametersDef,
|
|
951
|
+
typeLiteralDef,
|
|
952
|
+
indexedAccessTypeDef,
|
|
953
|
+
objectBindingPatternDef,
|
|
954
|
+
constDef,
|
|
955
|
+
typeDef,
|
|
956
|
+
functionDef,
|
|
957
|
+
arrowFunctionDef,
|
|
958
|
+
textDef,
|
|
959
|
+
breakDef,
|
|
960
|
+
jsxDef,
|
|
961
|
+
importDef,
|
|
962
|
+
exportDef,
|
|
963
|
+
sourceDef,
|
|
964
|
+
fileDef
|
|
965
|
+
];
|
|
1090
966
|
/**
|
|
1091
|
-
*
|
|
967
|
+
* Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
|
|
968
|
+
* Derived from each definition's `children`.
|
|
969
|
+
*/
|
|
970
|
+
const VISITOR_KEYS = Object.fromEntries(nodeDefs.flatMap((def) => def.children ? [[def.kind, def.children]] : []));
|
|
971
|
+
/**
|
|
972
|
+
* Maps a node kind to the matching visitor callback name. Derived from each
|
|
973
|
+
* definition's `visitorKey`.
|
|
974
|
+
*/
|
|
975
|
+
const VISITOR_KEY_BY_KIND = Object.fromEntries(nodeDefs.flatMap((def) => def.visitorKey ? [[def.kind, def.visitorKey]] : []));
|
|
976
|
+
/**
|
|
977
|
+
* Per-kind builders rerun after children are rebuilt. Derived from each
|
|
978
|
+
* definition's `rebuild` flag.
|
|
979
|
+
*/
|
|
980
|
+
const nodeRebuilders = Object.fromEntries(nodeDefs.flatMap((def) => def.rebuild ? [[def.kind, def.create]] : []));
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region src/visitor.ts
|
|
983
|
+
/**
|
|
984
|
+
* Creates a small async concurrency limiter.
|
|
1092
985
|
*
|
|
1093
|
-
*
|
|
1094
|
-
* ```ts
|
|
1095
|
-
* const output = createOutput()
|
|
1096
|
-
* // { kind: 'Output', files: [] }
|
|
1097
|
-
* ```
|
|
986
|
+
* At most `concurrency` tasks are in flight at once. Extra tasks are queued.
|
|
1098
987
|
*
|
|
1099
988
|
* @example
|
|
1100
989
|
* ```ts
|
|
1101
|
-
* const
|
|
990
|
+
* const limit = createLimit(2)
|
|
991
|
+
* for (const task of [taskA, taskB, taskC]) {
|
|
992
|
+
* await limit(() => task())
|
|
993
|
+
* }
|
|
994
|
+
* // only 2 tasks run at the same time
|
|
1102
995
|
* ```
|
|
1103
996
|
*/
|
|
1104
|
-
function
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
997
|
+
function createLimit(concurrency) {
|
|
998
|
+
let active = 0;
|
|
999
|
+
const queue = [];
|
|
1000
|
+
function next() {
|
|
1001
|
+
if (active < concurrency && queue.length > 0) {
|
|
1002
|
+
active++;
|
|
1003
|
+
queue.shift()();
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return function limit(fn) {
|
|
1007
|
+
return new Promise((resolve, reject) => {
|
|
1008
|
+
queue.push(() => {
|
|
1009
|
+
Promise.resolve(fn()).then(resolve, reject).finally(() => {
|
|
1010
|
+
active--;
|
|
1011
|
+
next();
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
next();
|
|
1015
|
+
});
|
|
1109
1016
|
};
|
|
1110
1017
|
}
|
|
1018
|
+
const visitorKeysByKind = VISITOR_KEYS;
|
|
1111
1019
|
/**
|
|
1112
|
-
*
|
|
1020
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
1113
1021
|
*/
|
|
1114
|
-
function
|
|
1115
|
-
return
|
|
1116
|
-
...props,
|
|
1117
|
-
kind: "Content"
|
|
1118
|
-
};
|
|
1022
|
+
function isNode(value) {
|
|
1023
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
1119
1024
|
}
|
|
1120
1025
|
/**
|
|
1121
|
-
*
|
|
1026
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
1027
|
+
*
|
|
1028
|
+
* `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
|
|
1029
|
+
*
|
|
1030
|
+
* @example
|
|
1031
|
+
* ```ts
|
|
1032
|
+
* const children = getChildren(operationNode, true)
|
|
1033
|
+
* // returns parameters, the request body, and responses
|
|
1034
|
+
* ```
|
|
1122
1035
|
*/
|
|
1123
|
-
function
|
|
1124
|
-
return
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
tags: [],
|
|
1135
|
-
parameters: [],
|
|
1136
|
-
responses: [],
|
|
1137
|
-
...rest,
|
|
1138
|
-
...isHttp ? { protocol: "http" } : {},
|
|
1139
|
-
kind: "Operation",
|
|
1140
|
-
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
/**
|
|
1144
|
-
* Maps schema `type` to its underlying `primitive`.
|
|
1145
|
-
* Primitive types map to themselves. Special string formats map to `'string'`.
|
|
1146
|
-
* Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
|
|
1147
|
-
*/
|
|
1148
|
-
const TYPE_TO_PRIMITIVE = {
|
|
1149
|
-
string: "string",
|
|
1150
|
-
number: "number",
|
|
1151
|
-
integer: "integer",
|
|
1152
|
-
bigint: "bigint",
|
|
1153
|
-
boolean: "boolean",
|
|
1154
|
-
null: "null",
|
|
1155
|
-
any: "any",
|
|
1156
|
-
unknown: "unknown",
|
|
1157
|
-
void: "void",
|
|
1158
|
-
never: "never",
|
|
1159
|
-
object: "object",
|
|
1160
|
-
array: "array",
|
|
1161
|
-
date: "date",
|
|
1162
|
-
uuid: "string",
|
|
1163
|
-
email: "string",
|
|
1164
|
-
url: "string",
|
|
1165
|
-
datetime: "string",
|
|
1166
|
-
time: "string"
|
|
1167
|
-
};
|
|
1168
|
-
function createSchema(props) {
|
|
1169
|
-
const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
|
|
1170
|
-
if (props["type"] === "object") return {
|
|
1171
|
-
properties: [],
|
|
1172
|
-
primitive: "object",
|
|
1173
|
-
...props,
|
|
1174
|
-
kind: "Schema"
|
|
1175
|
-
};
|
|
1176
|
-
return {
|
|
1177
|
-
primitive: inferredPrimitive,
|
|
1178
|
-
...props,
|
|
1179
|
-
kind: "Schema"
|
|
1180
|
-
};
|
|
1036
|
+
function* getChildren(node, recurse) {
|
|
1037
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
1038
|
+
const keys = visitorKeysByKind[node.kind];
|
|
1039
|
+
if (!keys) return;
|
|
1040
|
+
const record = node;
|
|
1041
|
+
for (const key of keys) {
|
|
1042
|
+
const value = record[key];
|
|
1043
|
+
if (Array.isArray(value)) {
|
|
1044
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
1045
|
+
} else if (isNode(value)) yield value;
|
|
1046
|
+
}
|
|
1181
1047
|
}
|
|
1182
1048
|
/**
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
* `
|
|
1186
|
-
* `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
|
|
1187
|
-
*
|
|
1188
|
-
* @example
|
|
1189
|
-
* ```ts
|
|
1190
|
-
* const property = createProperty({
|
|
1191
|
-
* name: 'status',
|
|
1192
|
-
* schema: createSchema({ type: 'string' }),
|
|
1193
|
-
* })
|
|
1194
|
-
* // required=false, schema.optional=true
|
|
1195
|
-
* ```
|
|
1049
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
1050
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
1051
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
1196
1052
|
*
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1200
|
-
* name: 'status',
|
|
1201
|
-
* required: true,
|
|
1202
|
-
* schema: createSchema({ type: 'string', nullable: true }),
|
|
1203
|
-
* })
|
|
1204
|
-
* // required=true, no optional/nullish
|
|
1205
|
-
* ```
|
|
1053
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
1054
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
1055
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
1206
1056
|
*/
|
|
1207
|
-
function
|
|
1208
|
-
const
|
|
1209
|
-
return
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
required,
|
|
1213
|
-
schema: syncOptionality(props.schema, required)
|
|
1214
|
-
};
|
|
1057
|
+
function applyVisitor(node, visitor, parent) {
|
|
1058
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
1059
|
+
if (!key) return void 0;
|
|
1060
|
+
const fn = visitor[key];
|
|
1061
|
+
return fn?.(node, { parent });
|
|
1215
1062
|
}
|
|
1216
1063
|
/**
|
|
1217
|
-
*
|
|
1064
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
1065
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
1218
1066
|
*
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1067
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
1068
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
1069
|
+
* work. Lower values reduce memory pressure.
|
|
1221
1070
|
*
|
|
1222
|
-
* @example
|
|
1071
|
+
* @example Log every operation
|
|
1223
1072
|
* ```ts
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1228
|
-
* schema: createSchema({ type: 'string' }),
|
|
1073
|
+
* await walk(root, {
|
|
1074
|
+
* operation(node) {
|
|
1075
|
+
* console.log(node.operationId)
|
|
1076
|
+
* },
|
|
1229
1077
|
* })
|
|
1230
1078
|
* ```
|
|
1231
1079
|
*
|
|
1232
|
-
* @example
|
|
1080
|
+
* @example Only visit the root node
|
|
1233
1081
|
* ```ts
|
|
1234
|
-
*
|
|
1235
|
-
* name: 'status',
|
|
1236
|
-
* in: 'query',
|
|
1237
|
-
* schema: createSchema({ type: 'string', nullable: true }),
|
|
1238
|
-
* })
|
|
1239
|
-
* // required=false, schema.nullish=true
|
|
1082
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
1240
1083
|
* ```
|
|
1241
1084
|
*/
|
|
1242
|
-
function
|
|
1243
|
-
|
|
1244
|
-
return {
|
|
1245
|
-
...props,
|
|
1246
|
-
kind: "Parameter",
|
|
1247
|
-
required,
|
|
1248
|
-
schema: syncOptionality(props.schema, required)
|
|
1249
|
-
};
|
|
1085
|
+
async function walk(node, options) {
|
|
1086
|
+
return _walk(node, options, (options.depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
1250
1087
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
* schema is never stored both at the node root and inside `content`.
|
|
1257
|
-
*
|
|
1258
|
-
* @example
|
|
1259
|
-
* ```ts
|
|
1260
|
-
* const response = createResponse({
|
|
1261
|
-
* statusCode: '200',
|
|
1262
|
-
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
1263
|
-
* })
|
|
1264
|
-
* ```
|
|
1265
|
-
*/
|
|
1266
|
-
function createResponse(props) {
|
|
1267
|
-
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
1268
|
-
const entries = content ?? (schema ? [{
|
|
1269
|
-
contentType: mediaType ?? "application/json",
|
|
1270
|
-
schema,
|
|
1271
|
-
keysToOmit: keysToOmit ?? null
|
|
1272
|
-
}] : void 0);
|
|
1273
|
-
return {
|
|
1274
|
-
...rest,
|
|
1275
|
-
kind: "Response",
|
|
1276
|
-
content: entries?.map(createContent)
|
|
1277
|
-
};
|
|
1088
|
+
async function _walk(node, visitor, recurse, limit, parent) {
|
|
1089
|
+
await limit(() => applyVisitor(node, visitor, parent));
|
|
1090
|
+
const children = Array.from(getChildren(node, recurse));
|
|
1091
|
+
if (children.length === 0) return;
|
|
1092
|
+
await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)));
|
|
1278
1093
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
* createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
|
|
1287
|
-
* // → petId: string
|
|
1288
|
-
* ```
|
|
1289
|
-
*
|
|
1290
|
-
* @example Optional param
|
|
1291
|
-
* ```ts
|
|
1292
|
-
* createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
|
|
1293
|
-
* // → params?: QueryParams
|
|
1294
|
-
* ```
|
|
1295
|
-
*
|
|
1296
|
-
* @example Param with default (implicitly optional. Cannot combine with `optional: true`)
|
|
1297
|
-
* ```ts
|
|
1298
|
-
* createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
|
|
1299
|
-
* // → config: RequestConfig = {}
|
|
1300
|
-
* ```
|
|
1301
|
-
*/
|
|
1302
|
-
function createFunctionParameter(props) {
|
|
1303
|
-
return {
|
|
1304
|
-
optional: false,
|
|
1305
|
-
...props,
|
|
1306
|
-
kind: "FunctionParameter"
|
|
1307
|
-
};
|
|
1094
|
+
function transform(node, options) {
|
|
1095
|
+
const { depth, parent, ...visitor } = options;
|
|
1096
|
+
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
1097
|
+
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
1098
|
+
if (rebuilt === node) return node;
|
|
1099
|
+
const rebuild = nodeRebuilders[rebuilt.kind];
|
|
1100
|
+
return rebuild ? rebuild(rebuilt) : rebuilt;
|
|
1308
1101
|
}
|
|
1309
1102
|
/**
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
* named field accessed from a group type. Each language's printer renders the variant
|
|
1314
|
-
* into its own syntax (TypeScript, Python, C#, Kotlin, …).
|
|
1315
|
-
*
|
|
1316
|
-
* @example Reference type (TypeScript: `QueryParams`)
|
|
1317
|
-
* ```ts
|
|
1318
|
-
* createParamsType({ variant: 'reference', name: 'QueryParams' })
|
|
1319
|
-
* ```
|
|
1320
|
-
*
|
|
1321
|
-
* @example Struct type (TypeScript: `{ petId: string }`)
|
|
1322
|
-
* ```ts
|
|
1323
|
-
* createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
|
|
1324
|
-
* ```
|
|
1325
|
-
*
|
|
1326
|
-
* @example Member type (TypeScript: `DeletePetPathParams['petId']`)
|
|
1327
|
-
* ```ts
|
|
1328
|
-
* createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
|
|
1329
|
-
* ```
|
|
1103
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
1104
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
1105
|
+
* `Schema` children are skipped in shallow mode.
|
|
1330
1106
|
*/
|
|
1331
|
-
function
|
|
1332
|
-
return
|
|
1333
|
-
|
|
1334
|
-
|
|
1107
|
+
function transformChildren(node, options, recurse) {
|
|
1108
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
1109
|
+
const keys = visitorKeysByKind[node.kind];
|
|
1110
|
+
if (!keys) return node;
|
|
1111
|
+
const record = node;
|
|
1112
|
+
const childOptions = {
|
|
1113
|
+
...options,
|
|
1114
|
+
parent: node
|
|
1335
1115
|
};
|
|
1116
|
+
let updates;
|
|
1117
|
+
for (const key of keys) {
|
|
1118
|
+
if (!(key in record)) continue;
|
|
1119
|
+
const value = record[key];
|
|
1120
|
+
if (Array.isArray(value)) {
|
|
1121
|
+
let changed = false;
|
|
1122
|
+
const mapped = value.map((item) => {
|
|
1123
|
+
if (!isNode(item)) return item;
|
|
1124
|
+
const next = transform(item, childOptions);
|
|
1125
|
+
if (next !== item) changed = true;
|
|
1126
|
+
return next;
|
|
1127
|
+
});
|
|
1128
|
+
if (changed) (updates ??= {})[key] = mapped;
|
|
1129
|
+
} else if (isNode(value)) {
|
|
1130
|
+
const next = transform(value, childOptions);
|
|
1131
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return updates ? {
|
|
1135
|
+
...node,
|
|
1136
|
+
...updates
|
|
1137
|
+
} : node;
|
|
1336
1138
|
}
|
|
1337
1139
|
/**
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* @example Grouped param (TypeScript declaration)
|
|
1341
|
-
* ```ts
|
|
1342
|
-
* createParameterGroup({
|
|
1343
|
-
* properties: [
|
|
1344
|
-
* createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
|
|
1345
|
-
* createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
|
|
1346
|
-
* ],
|
|
1347
|
-
* default: '{}',
|
|
1348
|
-
* })
|
|
1349
|
-
* // declaration → { id, name? }: { id: string; name?: string } = {}
|
|
1350
|
-
* // call → { id, name }
|
|
1351
|
-
* ```
|
|
1140
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
1141
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
1352
1142
|
*
|
|
1353
|
-
* @example
|
|
1143
|
+
* @example Collect every operationId
|
|
1354
1144
|
* ```ts
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
*
|
|
1145
|
+
* const ids: string[] = []
|
|
1146
|
+
* for (const id of collectLazy<string>(root, {
|
|
1147
|
+
* operation(node) {
|
|
1148
|
+
* return node.operationId
|
|
1149
|
+
* },
|
|
1150
|
+
* })) {
|
|
1151
|
+
* ids.push(id)
|
|
1152
|
+
* }
|
|
1361
1153
|
* ```
|
|
1362
1154
|
*/
|
|
1363
|
-
function
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1155
|
+
function* collectLazy(node, options) {
|
|
1156
|
+
const { depth, parent, ...visitor } = options;
|
|
1157
|
+
const recurse = (depth ?? require_utils.visitorDepths.deep) === require_utils.visitorDepths.deep;
|
|
1158
|
+
const v = applyVisitor(node, visitor, parent);
|
|
1159
|
+
if (v != null) yield v;
|
|
1160
|
+
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
1161
|
+
...options,
|
|
1162
|
+
parent: node
|
|
1163
|
+
});
|
|
1368
1164
|
}
|
|
1369
1165
|
/**
|
|
1370
|
-
*
|
|
1166
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
1167
|
+
* the visitor callbacks return.
|
|
1371
1168
|
*
|
|
1372
|
-
* @example
|
|
1169
|
+
* @example Collect every operationId
|
|
1373
1170
|
* ```ts
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
*
|
|
1378
|
-
* ],
|
|
1171
|
+
* const ids = collect<string>(root, {
|
|
1172
|
+
* operation(node) {
|
|
1173
|
+
* return node.operationId
|
|
1174
|
+
* },
|
|
1379
1175
|
* })
|
|
1380
1176
|
* ```
|
|
1381
|
-
*
|
|
1382
|
-
* @example
|
|
1383
|
-
* ```ts
|
|
1384
|
-
* const empty = createFunctionParameters()
|
|
1385
|
-
* // { kind: 'FunctionParameters', params: [] }
|
|
1386
|
-
* ```
|
|
1387
1177
|
*/
|
|
1388
|
-
function
|
|
1389
|
-
return
|
|
1390
|
-
params: [],
|
|
1391
|
-
...props,
|
|
1392
|
-
kind: "FunctionParameters"
|
|
1393
|
-
};
|
|
1178
|
+
function collect(node, options) {
|
|
1179
|
+
return Array.from(collectLazy(node, options));
|
|
1394
1180
|
}
|
|
1181
|
+
//#endregion
|
|
1182
|
+
//#region src/dedupe.ts
|
|
1395
1183
|
/**
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
* @example Named import
|
|
1399
|
-
* ```ts
|
|
1400
|
-
* createImport({ name: ['useState'], path: 'react' })
|
|
1401
|
-
* // import { useState } from 'react'
|
|
1402
|
-
* ```
|
|
1403
|
-
*
|
|
1404
|
-
* @example Type-only import
|
|
1405
|
-
* ```ts
|
|
1406
|
-
* createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
|
|
1407
|
-
* // import type { FC } from 'react'
|
|
1408
|
-
* ```
|
|
1184
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
1185
|
+
* usage-slot and documentation fields that are not part of the canonical type.
|
|
1409
1186
|
*/
|
|
1410
|
-
function
|
|
1411
|
-
return {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1187
|
+
function createRefNode(node, canonical) {
|
|
1188
|
+
return createSchema({
|
|
1189
|
+
type: "ref",
|
|
1190
|
+
name: canonical.name,
|
|
1191
|
+
ref: canonical.ref,
|
|
1192
|
+
optional: node.optional,
|
|
1193
|
+
nullish: node.nullish,
|
|
1194
|
+
readOnly: node.readOnly,
|
|
1195
|
+
writeOnly: node.writeOnly,
|
|
1196
|
+
deprecated: node.deprecated,
|
|
1197
|
+
description: node.description,
|
|
1198
|
+
default: node.default,
|
|
1199
|
+
example: node.example
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
function applyDedupe(node, plan, skipRootMatch = false) {
|
|
1203
|
+
const { canonicalBySignature, aliasNames } = plan;
|
|
1204
|
+
if (canonicalBySignature.size === 0 && aliasNames.size === 0) return node;
|
|
1205
|
+
const root = node;
|
|
1206
|
+
return transform(node, { schema(schemaNode) {
|
|
1207
|
+
if (schemaNode.type === "ref") {
|
|
1208
|
+
const target = schemaNode.ref ? require_utils.extractRefName(schemaNode.ref) : schemaNode.name;
|
|
1209
|
+
const canonical = target ? aliasNames.get(target) : void 0;
|
|
1210
|
+
return canonical ? {
|
|
1211
|
+
...schemaNode,
|
|
1212
|
+
name: canonical.name,
|
|
1213
|
+
ref: canonical.ref
|
|
1214
|
+
} : void 0;
|
|
1215
|
+
}
|
|
1216
|
+
const signature = signatureOf(schemaNode);
|
|
1217
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
1218
|
+
const canonical = canonicalBySignature.get(signature);
|
|
1219
|
+
if (!canonical) return void 0;
|
|
1220
|
+
return createRefNode(schemaNode, canonical);
|
|
1221
|
+
} });
|
|
1222
|
+
}
|
|
1416
1223
|
/**
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
* @example Named export
|
|
1420
|
-
* ```ts
|
|
1421
|
-
* createExport({ name: ['Pet'], path: './Pet' })
|
|
1422
|
-
* // export { Pet } from './Pet'
|
|
1423
|
-
* ```
|
|
1424
|
-
*
|
|
1425
|
-
* @example Wildcard export
|
|
1426
|
-
* ```ts
|
|
1427
|
-
* createExport({ path: './utils' })
|
|
1428
|
-
* // export * from './utils'
|
|
1429
|
-
* ```
|
|
1224
|
+
* Strips usage-slot flags from a hoisted definition and applies its canonical name.
|
|
1225
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
1430
1226
|
*/
|
|
1431
|
-
function
|
|
1227
|
+
function cleanDefinition(node, name) {
|
|
1432
1228
|
return {
|
|
1433
|
-
...
|
|
1434
|
-
|
|
1229
|
+
...node,
|
|
1230
|
+
name,
|
|
1231
|
+
optional: void 0,
|
|
1232
|
+
nullish: void 0
|
|
1435
1233
|
};
|
|
1436
1234
|
}
|
|
1437
1235
|
/**
|
|
1438
|
-
*
|
|
1236
|
+
* Scans a forest of schema and operation nodes and produces a {@link DedupePlan}.
|
|
1237
|
+
*
|
|
1238
|
+
* A shape that occurs at least `minOccurrences` times is deduplicated: if any occurrence
|
|
1239
|
+
* is a named top-level schema, the first one becomes the canonical (so other top-level
|
|
1240
|
+
* duplicates and inline copies turn into references to it). Every other top-level name with
|
|
1241
|
+
* the same content is recorded in `aliasNames`, so refs to it can be repointed at the
|
|
1242
|
+
* canonical. Otherwise a new definition is hoisted using `nameFor`. The plan is then applied
|
|
1243
|
+
* per node with {@link applyDedupe}.
|
|
1439
1244
|
*
|
|
1440
1245
|
* @example
|
|
1441
1246
|
* ```ts
|
|
1442
|
-
*
|
|
1247
|
+
* const plan = buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
1248
|
+
* isCandidate: (node) => node.type === 'enum' || node.type === 'object',
|
|
1249
|
+
* nameFor: (node) => node.name ?? null,
|
|
1250
|
+
* refFor: (name) => `#/components/schemas/${name}`,
|
|
1251
|
+
* })
|
|
1443
1252
|
* ```
|
|
1444
1253
|
*/
|
|
1445
|
-
function
|
|
1254
|
+
function buildDedupePlan(roots, options) {
|
|
1255
|
+
const { isCandidate, nameFor, refFor, minOccurrences = 2 } = options;
|
|
1256
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
1257
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1258
|
+
function record(schemaNode) {
|
|
1259
|
+
const signature = signatureOf(schemaNode);
|
|
1260
|
+
if (!isCandidate(schemaNode)) return;
|
|
1261
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
1262
|
+
const group = groups.get(signature);
|
|
1263
|
+
if (group) {
|
|
1264
|
+
group.count++;
|
|
1265
|
+
if (isTopLevel) group.topLevelNames.push(schemaNode.name);
|
|
1266
|
+
} else groups.set(signature, {
|
|
1267
|
+
count: 1,
|
|
1268
|
+
representative: schemaNode,
|
|
1269
|
+
topLevelNames: isTopLevel ? [schemaNode.name] : []
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
for (const root of roots) {
|
|
1273
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
1274
|
+
for (const schemaNode of collectLazy(root, { schema: (node) => node })) record(schemaNode);
|
|
1275
|
+
}
|
|
1276
|
+
const canonicalBySignature = /* @__PURE__ */ new Map();
|
|
1277
|
+
const aliasNames = /* @__PURE__ */ new Map();
|
|
1278
|
+
const pendingHoists = [];
|
|
1279
|
+
for (const [signature, group] of groups) {
|
|
1280
|
+
if (group.count < minOccurrences) continue;
|
|
1281
|
+
const [firstName, ...duplicateNames] = group.topLevelNames;
|
|
1282
|
+
if (firstName) {
|
|
1283
|
+
const canonical = {
|
|
1284
|
+
name: firstName,
|
|
1285
|
+
ref: refFor(firstName)
|
|
1286
|
+
};
|
|
1287
|
+
canonicalBySignature.set(signature, canonical);
|
|
1288
|
+
for (const duplicate of duplicateNames) aliasNames.set(duplicate, canonical);
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
const name = nameFor(group.representative, signature);
|
|
1292
|
+
if (!name) continue;
|
|
1293
|
+
canonicalBySignature.set(signature, {
|
|
1294
|
+
name,
|
|
1295
|
+
ref: refFor(name)
|
|
1296
|
+
});
|
|
1297
|
+
pendingHoists.push({
|
|
1298
|
+
name,
|
|
1299
|
+
representative: group.representative
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1446
1302
|
return {
|
|
1447
|
-
|
|
1448
|
-
|
|
1303
|
+
canonicalBySignature,
|
|
1304
|
+
aliasNames,
|
|
1305
|
+
hoisted: pendingHoists.map(({ name, representative }) => cleanDefinition(applyDedupe(representative, {
|
|
1306
|
+
canonicalBySignature,
|
|
1307
|
+
aliasNames
|
|
1308
|
+
}, true), name))
|
|
1449
1309
|
};
|
|
1450
1310
|
}
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region src/dialect.ts
|
|
1451
1313
|
/**
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1454
|
-
* Computes:
|
|
1455
|
-
* - `id` SHA256 hash of the file path
|
|
1456
|
-
* - `name` `baseName` without extension
|
|
1457
|
-
* - `extname` extension extracted from `baseName`
|
|
1458
|
-
*
|
|
1459
|
-
* Deduplicates:
|
|
1460
|
-
* - `sources` via `combineSources`
|
|
1461
|
-
* - `exports` via `combineExports`
|
|
1462
|
-
* - `imports` via `combineImports` (also filters unused imports)
|
|
1463
|
-
*
|
|
1464
|
-
* @throws {Error} when `baseName` has no extension.
|
|
1314
|
+
* Types a {@link SchemaDialect} for an adapter. Adds no runtime behavior and only pins the
|
|
1315
|
+
* dialect's type for inference.
|
|
1465
1316
|
*
|
|
1466
1317
|
* @example
|
|
1467
1318
|
* ```ts
|
|
1468
|
-
* const
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1319
|
+
* export const oasDialect = defineSchemaDialect({
|
|
1320
|
+
* name: 'oas',
|
|
1321
|
+
* isNullable,
|
|
1322
|
+
* isReference,
|
|
1323
|
+
* isDiscriminator,
|
|
1324
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
1325
|
+
* resolveRef,
|
|
1474
1326
|
* })
|
|
1475
|
-
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
1476
|
-
* // file.name = 'petStore'
|
|
1477
|
-
* // file.extname = '.ts'
|
|
1478
1327
|
* ```
|
|
1479
1328
|
*/
|
|
1480
|
-
function
|
|
1481
|
-
|
|
1482
|
-
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
1483
|
-
const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
|
|
1484
|
-
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
1485
|
-
const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
|
|
1486
|
-
const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name) => Boolean(name)));
|
|
1487
|
-
const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
|
|
1488
|
-
const resolvedImports = combinedImports.filter((imp) => imp.path !== input.path).flatMap((imp) => {
|
|
1489
|
-
if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
|
|
1490
|
-
const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
|
|
1491
|
-
if (!kept.length) return [];
|
|
1492
|
-
return [kept.length === imp.name.length ? imp : {
|
|
1493
|
-
...imp,
|
|
1494
|
-
name: kept
|
|
1495
|
-
}];
|
|
1496
|
-
});
|
|
1497
|
-
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
1498
|
-
return {
|
|
1499
|
-
kind: "File",
|
|
1500
|
-
...input,
|
|
1501
|
-
id: (0, node_crypto.hash)("sha256", input.path, "hex"),
|
|
1502
|
-
name: trimExtName(input.baseName),
|
|
1503
|
-
extname,
|
|
1504
|
-
imports: resolvedImports,
|
|
1505
|
-
exports: resolvedExports,
|
|
1506
|
-
sources: resolvedSources,
|
|
1507
|
-
meta: input.meta ?? {}
|
|
1508
|
-
};
|
|
1329
|
+
function defineSchemaDialect(dialect) {
|
|
1330
|
+
return dialect;
|
|
1509
1331
|
}
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region src/guards.ts
|
|
1510
1334
|
/**
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
* Mirrors the `Const` component from `@kubb/renderer-jsx`.
|
|
1514
|
-
* The component's `children` are represented as `nodes`.
|
|
1515
|
-
*
|
|
1516
|
-
* @example Simple constant
|
|
1517
|
-
* ```ts
|
|
1518
|
-
* createConst({ name: 'pet' })
|
|
1519
|
-
* // const pet = ...
|
|
1520
|
-
* ```
|
|
1521
|
-
*
|
|
1522
|
-
* @example Exported constant with type and `as const`
|
|
1523
|
-
* ```ts
|
|
1524
|
-
* createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
|
|
1525
|
-
* // export const pets: Pet[] = ... as const
|
|
1526
|
-
* ```
|
|
1335
|
+
* Narrows a `SchemaNode` to the variant that matches `type`.
|
|
1527
1336
|
*
|
|
1528
|
-
* @example
|
|
1337
|
+
* @example
|
|
1529
1338
|
* ```ts
|
|
1530
|
-
*
|
|
1531
|
-
*
|
|
1532
|
-
* export: true,
|
|
1533
|
-
* JSDoc: { comments: ['@description App configuration'] },
|
|
1534
|
-
* nodes: [],
|
|
1535
|
-
* })
|
|
1339
|
+
* const schema = createSchema({ type: 'string' })
|
|
1340
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
1536
1341
|
* ```
|
|
1537
1342
|
*/
|
|
1538
|
-
function
|
|
1539
|
-
return
|
|
1540
|
-
...props,
|
|
1541
|
-
kind: "Const"
|
|
1542
|
-
};
|
|
1343
|
+
function narrowSchema(node, type) {
|
|
1344
|
+
return node?.type === type ? node : null;
|
|
1543
1345
|
}
|
|
1544
1346
|
/**
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
* Mirrors the `Type` component from `@kubb/renderer-jsx`.
|
|
1548
|
-
* The component's `children` are represented as `nodes`.
|
|
1549
|
-
*
|
|
1550
|
-
* @example Simple type alias
|
|
1551
|
-
* ```ts
|
|
1552
|
-
* createType({ name: 'Pet' })
|
|
1553
|
-
* // type Pet = ...
|
|
1554
|
-
* ```
|
|
1347
|
+
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
1555
1348
|
*
|
|
1556
|
-
* @example
|
|
1349
|
+
* @example
|
|
1557
1350
|
* ```ts
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
* JSDoc: { comments: ['@description Status of a pet'] },
|
|
1562
|
-
* })
|
|
1563
|
-
* // export type PetStatus = ...
|
|
1351
|
+
* if (isHttpOperationNode(node)) {
|
|
1352
|
+
* console.log(node.method, node.path)
|
|
1353
|
+
* }
|
|
1564
1354
|
* ```
|
|
1565
1355
|
*/
|
|
1566
|
-
function
|
|
1567
|
-
return
|
|
1568
|
-
...props,
|
|
1569
|
-
kind: "Type"
|
|
1570
|
-
};
|
|
1356
|
+
function isHttpOperationNode(node) {
|
|
1357
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
1571
1358
|
}
|
|
1359
|
+
//#endregion
|
|
1360
|
+
//#region src/utils/ast.ts
|
|
1361
|
+
const plainStringTypes = new Set([
|
|
1362
|
+
"string",
|
|
1363
|
+
"uuid",
|
|
1364
|
+
"email",
|
|
1365
|
+
"url",
|
|
1366
|
+
"datetime"
|
|
1367
|
+
]);
|
|
1572
1368
|
/**
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1575
|
-
* Mirrors the `Function` component from `@kubb/renderer-jsx`.
|
|
1576
|
-
* The component's `children` are represented as `nodes`.
|
|
1577
|
-
*
|
|
1578
|
-
* @example Simple function
|
|
1579
|
-
* ```ts
|
|
1580
|
-
* createFunction({ name: 'getPet' })
|
|
1581
|
-
* // function getPet() { ... }
|
|
1582
|
-
* ```
|
|
1369
|
+
* Merges a ref node with its resolved schema, giving usage-site fields precedence.
|
|
1583
1370
|
*
|
|
1584
|
-
*
|
|
1585
|
-
*
|
|
1586
|
-
* createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
|
|
1587
|
-
* // export async function fetchPet(): Promise<Pet> { ... }
|
|
1588
|
-
* ```
|
|
1371
|
+
* Usage-site fields (`description`, `readOnly`, `nullable`, `deprecated`) on the ref node
|
|
1372
|
+
* override the same fields in the resolved `node.schema`. Non-ref nodes are returned unchanged.
|
|
1589
1373
|
*
|
|
1590
|
-
* @example
|
|
1374
|
+
* @example
|
|
1591
1375
|
* ```ts
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
*
|
|
1595
|
-
* generics: ['T'],
|
|
1596
|
-
* params: 'value: T',
|
|
1597
|
-
* returnType: 'T',
|
|
1598
|
-
* })
|
|
1599
|
-
* // export function identity<T>(value: T): T { ... }
|
|
1376
|
+
* // Ref with description override
|
|
1377
|
+
* const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A cute pet' })
|
|
1378
|
+
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
1600
1379
|
* ```
|
|
1601
1380
|
*/
|
|
1602
|
-
function
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
};
|
|
1381
|
+
function syncSchemaRef(node) {
|
|
1382
|
+
const ref = narrowSchema(node, "ref");
|
|
1383
|
+
if (!ref) return node;
|
|
1384
|
+
if (!ref.schema) return node;
|
|
1385
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
1386
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
1387
|
+
return createSchema({
|
|
1388
|
+
...ref.schema,
|
|
1389
|
+
...definedOverrides
|
|
1390
|
+
});
|
|
1607
1391
|
}
|
|
1608
1392
|
/**
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
* Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
|
|
1612
|
-
* The component's `children` are represented as `nodes`.
|
|
1613
|
-
*
|
|
1614
|
-
* @example Simple arrow function
|
|
1615
|
-
* ```ts
|
|
1616
|
-
* createArrowFunction({ name: 'getPet' })
|
|
1617
|
-
* // const getPet = () => { ... }
|
|
1618
|
-
* ```
|
|
1393
|
+
* Type guard that returns `true` when a schema emits as a plain `string` type.
|
|
1619
1394
|
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1395
|
+
* Covers `string`, `uuid`, `email`, `url`, and `datetime` types. For `date` and `time`
|
|
1396
|
+
* types, returns `true` only when `representation` is `'string'` rather than `'date'`.
|
|
1397
|
+
*/
|
|
1398
|
+
function isStringType(node) {
|
|
1399
|
+
if (plainStringTypes.has(node.type)) return true;
|
|
1400
|
+
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
1401
|
+
if (temporal) return temporal.representation !== "date";
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Applies casing rules to parameter names and returns a new parameter array.
|
|
1625
1406
|
*
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
* name: 'fetchPet',
|
|
1630
|
-
* export: true,
|
|
1631
|
-
* async: true,
|
|
1632
|
-
* generics: ['T'],
|
|
1633
|
-
* params: 'id: string',
|
|
1634
|
-
* returnType: 'T',
|
|
1635
|
-
* })
|
|
1636
|
-
* // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
|
|
1637
|
-
* ```
|
|
1407
|
+
* Use this before passing parameters to schema builders so output property keys match
|
|
1408
|
+
* the desired casing while preserving `OperationNode.parameters` for other consumers.
|
|
1409
|
+
* The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
|
|
1638
1410
|
*/
|
|
1639
|
-
|
|
1411
|
+
const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
|
|
1412
|
+
const transformed = casing === "camelcase" || !require_utils.isValidVarName(param.name) ? require_utils.camelCase(param.name) : param.name;
|
|
1640
1413
|
return {
|
|
1641
|
-
...
|
|
1642
|
-
|
|
1414
|
+
...param,
|
|
1415
|
+
name: transformed
|
|
1643
1416
|
};
|
|
1417
|
+
})));
|
|
1418
|
+
function caseParams(params, casing) {
|
|
1419
|
+
if (!casing) return params;
|
|
1420
|
+
return caseParamsMemo(params)(casing);
|
|
1644
1421
|
}
|
|
1645
1422
|
/**
|
|
1646
|
-
* Creates a
|
|
1647
|
-
*
|
|
1648
|
-
* Use this instead of bare strings when building `nodes` arrays so that every
|
|
1649
|
-
* entry in the array is a typed {@link CodeNode}.
|
|
1423
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
1650
1424
|
*
|
|
1651
1425
|
* @example
|
|
1652
1426
|
* ```ts
|
|
1653
|
-
*
|
|
1654
|
-
* // {
|
|
1427
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
1428
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
1655
1429
|
* ```
|
|
1656
1430
|
*/
|
|
1657
|
-
function
|
|
1658
|
-
return {
|
|
1659
|
-
|
|
1660
|
-
|
|
1431
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
1432
|
+
return createSchema({
|
|
1433
|
+
type: "object",
|
|
1434
|
+
primitive: "object",
|
|
1435
|
+
properties: [createProperty({
|
|
1436
|
+
name: propertyName,
|
|
1437
|
+
schema: createSchema({
|
|
1438
|
+
type: "enum",
|
|
1439
|
+
primitive: "string",
|
|
1440
|
+
enumValues: [value]
|
|
1441
|
+
}),
|
|
1442
|
+
required: true
|
|
1443
|
+
})]
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Resolves the {@link TypeExpression} for an individual parameter.
|
|
1448
|
+
*
|
|
1449
|
+
* Without a resolver, falls back to the schema primitive (a plain type-name string).
|
|
1450
|
+
* When the parameter belongs to a named group, emits an {@link IndexedAccessTypeNode}
|
|
1451
|
+
* (`GroupParams['petId']`); otherwise the resolved individual name as a plain string.
|
|
1452
|
+
*/
|
|
1453
|
+
function resolveParamType({ node, param, resolver }) {
|
|
1454
|
+
if (!resolver) return param.schema.primitive ?? "unknown";
|
|
1455
|
+
const individualName = resolver.resolveParamName(node, param);
|
|
1456
|
+
const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
|
|
1457
|
+
const groupResolvers = {
|
|
1458
|
+
path: resolver.resolvePathParamsName,
|
|
1459
|
+
query: resolver.resolveQueryParamsName,
|
|
1460
|
+
header: resolver.resolveHeaderParamsName
|
|
1661
1461
|
};
|
|
1462
|
+
const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
|
|
1463
|
+
if (groupName && groupName !== individualName) return createIndexedAccessType({
|
|
1464
|
+
objectType: groupName,
|
|
1465
|
+
indexType: param.name
|
|
1466
|
+
});
|
|
1467
|
+
return individualName;
|
|
1662
1468
|
}
|
|
1663
1469
|
/**
|
|
1664
|
-
*
|
|
1470
|
+
* Converts an `OperationNode` into function parameters for code generation.
|
|
1665
1471
|
*
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1472
|
+
* Centralizes parameter grouping logic for all plugins. `paramsType` chooses between one
|
|
1473
|
+
* destructured object parameter (`object`) and separate top-level parameters (`inline`), while
|
|
1474
|
+
* `pathParamsType` controls how path params render in inline mode. Provide a `resolver` for type
|
|
1475
|
+
* name resolution and `extraParams` for plugin-specific trailing parameters such as an `options` object.
|
|
1476
|
+
*/
|
|
1477
|
+
function createOperationParams(node, options) {
|
|
1478
|
+
const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
|
|
1479
|
+
const dataName = paramNames?.data ?? "data";
|
|
1480
|
+
const paramsName = paramNames?.params ?? "params";
|
|
1481
|
+
const headersName = paramNames?.headers ?? "headers";
|
|
1482
|
+
const pathName = paramNames?.path ?? "pathParams";
|
|
1483
|
+
const wrapType = (type) => typeWrapper ? typeWrapper(type) : type;
|
|
1484
|
+
const wrapTypeExpression = (type) => typeof type === "string" ? wrapType(type) : type;
|
|
1485
|
+
const casedParams = caseParams(node.parameters, paramsCasing);
|
|
1486
|
+
const pathParams = casedParams.filter((p) => p.in === "path");
|
|
1487
|
+
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
1488
|
+
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
1489
|
+
const toProperty = (param) => ({
|
|
1490
|
+
name: param.name,
|
|
1491
|
+
type: wrapTypeExpression(resolveParamType({
|
|
1492
|
+
node,
|
|
1493
|
+
param,
|
|
1494
|
+
resolver
|
|
1495
|
+
})),
|
|
1496
|
+
optional: !param.required
|
|
1497
|
+
});
|
|
1498
|
+
const emptyObjectDefault = (props) => props.every((p) => p.optional) ? "{}" : void 0;
|
|
1499
|
+
const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
|
|
1500
|
+
const bodyProperty = bodyType ? [{
|
|
1501
|
+
name: dataName,
|
|
1502
|
+
type: bodyType,
|
|
1503
|
+
optional: !(node.requestBody?.required ?? false)
|
|
1504
|
+
}] : [];
|
|
1505
|
+
const trailingGroups = [{
|
|
1506
|
+
name: paramsName,
|
|
1507
|
+
node,
|
|
1508
|
+
params: queryParams,
|
|
1509
|
+
groupType: resolveGroupType({
|
|
1510
|
+
node,
|
|
1511
|
+
params: queryParams,
|
|
1512
|
+
group: "query",
|
|
1513
|
+
resolver
|
|
1514
|
+
}),
|
|
1515
|
+
resolver,
|
|
1516
|
+
wrapType
|
|
1517
|
+
}, {
|
|
1518
|
+
name: headersName,
|
|
1519
|
+
node,
|
|
1520
|
+
params: headerParams,
|
|
1521
|
+
groupType: resolveGroupType({
|
|
1522
|
+
node,
|
|
1523
|
+
params: headerParams,
|
|
1524
|
+
group: "header",
|
|
1525
|
+
resolver
|
|
1526
|
+
}),
|
|
1527
|
+
resolver,
|
|
1528
|
+
wrapType
|
|
1529
|
+
}];
|
|
1530
|
+
const params = [];
|
|
1531
|
+
if (paramsType === "object") {
|
|
1532
|
+
const children = [
|
|
1533
|
+
...pathParams.map(toProperty),
|
|
1534
|
+
...bodyProperty,
|
|
1535
|
+
...trailingGroups.flatMap(buildGroupProperty)
|
|
1536
|
+
];
|
|
1537
|
+
if (children.length) params.push(createFunctionParameter({
|
|
1538
|
+
properties: children,
|
|
1539
|
+
default: emptyObjectDefault(children)
|
|
1540
|
+
}));
|
|
1541
|
+
} else {
|
|
1542
|
+
if (pathParamsType === "inlineSpread" && pathParams.length) {
|
|
1543
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
1544
|
+
params.push(createFunctionParameter({
|
|
1545
|
+
name: pathName,
|
|
1546
|
+
type: spreadType ? wrapType(spreadType) : void 0,
|
|
1547
|
+
rest: true
|
|
1548
|
+
}));
|
|
1549
|
+
} else if (pathParamsType === "inline") params.push(...pathParams.map((p) => createFunctionParameter(toProperty(p))));
|
|
1550
|
+
else if (pathParams.length) {
|
|
1551
|
+
const pathChildren = pathParams.map(toProperty);
|
|
1552
|
+
params.push(createFunctionParameter({
|
|
1553
|
+
properties: pathChildren,
|
|
1554
|
+
default: pathParamsDefault ?? emptyObjectDefault(pathChildren)
|
|
1555
|
+
}));
|
|
1556
|
+
}
|
|
1557
|
+
params.push(...bodyProperty.map((p) => createFunctionParameter(p)));
|
|
1558
|
+
params.push(...trailingGroups.flatMap(buildGroupParam));
|
|
1559
|
+
}
|
|
1560
|
+
params.push(...extraParams);
|
|
1561
|
+
return createFunctionParameters({ params });
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Builds the property descriptor for a query or header group.
|
|
1565
|
+
* Returns an empty array when there are no params to emit.
|
|
1668
1566
|
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
* createBreak()
|
|
1672
|
-
* // { kind: 'Break' }
|
|
1673
|
-
* ```
|
|
1567
|
+
* A pre-resolved `groupType` emits `name: GroupType`. Otherwise it builds an inline
|
|
1568
|
+
* {@link TypeLiteralNode} from the individual params.
|
|
1674
1569
|
*/
|
|
1675
|
-
function
|
|
1676
|
-
return {
|
|
1570
|
+
function buildGroupProperty({ name, node, params, groupType, resolver, wrapType }) {
|
|
1571
|
+
if (groupType) return [{
|
|
1572
|
+
name,
|
|
1573
|
+
type: typeof groupType.type === "string" ? wrapType(groupType.type) : groupType.type,
|
|
1574
|
+
optional: groupType.optional
|
|
1575
|
+
}];
|
|
1576
|
+
if (params.length) return [{
|
|
1577
|
+
name,
|
|
1578
|
+
type: buildTypeLiteral({
|
|
1579
|
+
node,
|
|
1580
|
+
params,
|
|
1581
|
+
resolver
|
|
1582
|
+
}),
|
|
1583
|
+
optional: params.every((p) => !p.required)
|
|
1584
|
+
}];
|
|
1585
|
+
return [];
|
|
1677
1586
|
}
|
|
1678
1587
|
/**
|
|
1679
|
-
*
|
|
1588
|
+
* Builds a single {@link FunctionParameterNode} for a query or header group.
|
|
1589
|
+
* Returns an empty array when there are no params to emit.
|
|
1680
1590
|
*
|
|
1681
|
-
*
|
|
1591
|
+
* A pre-resolved `groupType` emits `name: GroupType`. Otherwise it builds an inline
|
|
1592
|
+
* {@link TypeLiteralNode} from the individual params.
|
|
1593
|
+
*/
|
|
1594
|
+
function buildGroupParam(args) {
|
|
1595
|
+
return buildGroupProperty(args).map((p) => createFunctionParameter(p));
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Derives a {@link ParamGroupType} for a query or header group from the resolver.
|
|
1682
1599
|
*
|
|
1683
|
-
*
|
|
1684
|
-
*
|
|
1685
|
-
* createJsx('<>\n <a href={href}>Open</a>\n</>')
|
|
1686
|
-
* // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
|
|
1687
|
-
* ```
|
|
1600
|
+
* Returns `null` when there is no resolver, no params, or the group name equals the
|
|
1601
|
+
* individual param name (so there is no real group to emit).
|
|
1688
1602
|
*/
|
|
1689
|
-
function
|
|
1603
|
+
function resolveGroupType({ node, params, group, resolver }) {
|
|
1604
|
+
if (!resolver || !params.length) return null;
|
|
1605
|
+
const firstParam = params[0];
|
|
1606
|
+
const groupName = (group === "query" ? resolver.resolveQueryParamsName : resolver.resolveHeaderParamsName).call(resolver, node, firstParam);
|
|
1607
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
1690
1608
|
return {
|
|
1691
|
-
|
|
1692
|
-
|
|
1609
|
+
type: groupName,
|
|
1610
|
+
optional: params.every((p) => !p.required)
|
|
1693
1611
|
};
|
|
1694
1612
|
}
|
|
1695
|
-
//#endregion
|
|
1696
|
-
//#region src/signature.ts
|
|
1697
1613
|
/**
|
|
1698
|
-
*
|
|
1614
|
+
* Builds a {@link TypeLiteralNode} for an inline anonymous type grouping named fields.
|
|
1615
|
+
*
|
|
1616
|
+
* Used when query or header parameters have no dedicated group type name.
|
|
1617
|
+
* Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
|
|
1699
1618
|
*/
|
|
1700
|
-
function
|
|
1701
|
-
return
|
|
1619
|
+
function buildTypeLiteral({ node, params, resolver }) {
|
|
1620
|
+
return createTypeLiteral({ members: params.map((p) => ({
|
|
1621
|
+
name: p.name,
|
|
1622
|
+
type: resolveParamType({
|
|
1623
|
+
node,
|
|
1624
|
+
param: p,
|
|
1625
|
+
resolver
|
|
1626
|
+
}),
|
|
1627
|
+
optional: !p.required
|
|
1628
|
+
})) });
|
|
1702
1629
|
}
|
|
1703
|
-
function
|
|
1704
|
-
|
|
1705
|
-
|
|
1630
|
+
function sourceKey(source) {
|
|
1631
|
+
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
1632
|
+
}
|
|
1633
|
+
function pathTypeKey(path, isTypeOnly) {
|
|
1634
|
+
return `${path}:${isTypeOnly ?? false}`;
|
|
1635
|
+
}
|
|
1636
|
+
function exportKey(path, name, isTypeOnly, asAlias) {
|
|
1637
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
|
|
1638
|
+
}
|
|
1639
|
+
function importKey(path, name, isTypeOnly) {
|
|
1640
|
+
return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
|
|
1706
1641
|
}
|
|
1707
|
-
const arrayTupleFields = [
|
|
1708
|
-
{
|
|
1709
|
-
kind: "children",
|
|
1710
|
-
key: "items",
|
|
1711
|
-
prefix: "i"
|
|
1712
|
-
},
|
|
1713
|
-
{
|
|
1714
|
-
kind: "child",
|
|
1715
|
-
key: "rest",
|
|
1716
|
-
prefix: "r"
|
|
1717
|
-
},
|
|
1718
|
-
{
|
|
1719
|
-
kind: "scalar",
|
|
1720
|
-
key: "min",
|
|
1721
|
-
prefix: "mn"
|
|
1722
|
-
},
|
|
1723
|
-
{
|
|
1724
|
-
kind: "scalar",
|
|
1725
|
-
key: "max",
|
|
1726
|
-
prefix: "mx"
|
|
1727
|
-
},
|
|
1728
|
-
{
|
|
1729
|
-
kind: "bool",
|
|
1730
|
-
key: "unique",
|
|
1731
|
-
prefix: "u"
|
|
1732
|
-
}
|
|
1733
|
-
];
|
|
1734
|
-
const numericFields = [
|
|
1735
|
-
{
|
|
1736
|
-
kind: "scalar",
|
|
1737
|
-
key: "min",
|
|
1738
|
-
prefix: "mn"
|
|
1739
|
-
},
|
|
1740
|
-
{
|
|
1741
|
-
kind: "scalar",
|
|
1742
|
-
key: "max",
|
|
1743
|
-
prefix: "mx"
|
|
1744
|
-
},
|
|
1745
|
-
{
|
|
1746
|
-
kind: "scalar",
|
|
1747
|
-
key: "exclusiveMinimum",
|
|
1748
|
-
prefix: "emn"
|
|
1749
|
-
},
|
|
1750
|
-
{
|
|
1751
|
-
kind: "scalar",
|
|
1752
|
-
key: "exclusiveMaximum",
|
|
1753
|
-
prefix: "emx"
|
|
1754
|
-
},
|
|
1755
|
-
{
|
|
1756
|
-
kind: "scalar",
|
|
1757
|
-
key: "multipleOf",
|
|
1758
|
-
prefix: "mo"
|
|
1759
|
-
}
|
|
1760
|
-
];
|
|
1761
|
-
const rangeFields = [{
|
|
1762
|
-
kind: "scalar",
|
|
1763
|
-
key: "min",
|
|
1764
|
-
prefix: "mn"
|
|
1765
|
-
}, {
|
|
1766
|
-
kind: "scalar",
|
|
1767
|
-
key: "max",
|
|
1768
|
-
prefix: "mx"
|
|
1769
|
-
}];
|
|
1770
1642
|
/**
|
|
1771
|
-
*
|
|
1772
|
-
* (
|
|
1643
|
+
* Computes a multi-level sort key for exports and imports:
|
|
1644
|
+
* non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
|
|
1773
1645
|
*/
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
key
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
prefix: "path"
|
|
1841
|
-
},
|
|
1842
|
-
{
|
|
1843
|
-
kind: "scalar",
|
|
1844
|
-
key: "min",
|
|
1845
|
-
prefix: "mn"
|
|
1846
|
-
},
|
|
1847
|
-
{
|
|
1848
|
-
kind: "scalar",
|
|
1849
|
-
key: "max",
|
|
1850
|
-
prefix: "mx"
|
|
1851
|
-
}
|
|
1852
|
-
],
|
|
1853
|
-
uuid: rangeFields,
|
|
1854
|
-
email: rangeFields,
|
|
1855
|
-
datetime: [{
|
|
1856
|
-
kind: "bool",
|
|
1857
|
-
key: "offset",
|
|
1858
|
-
prefix: "o"
|
|
1859
|
-
}, {
|
|
1860
|
-
kind: "bool",
|
|
1861
|
-
key: "local",
|
|
1862
|
-
prefix: "l"
|
|
1863
|
-
}],
|
|
1864
|
-
date: [{
|
|
1865
|
-
kind: "scalar",
|
|
1866
|
-
key: "representation",
|
|
1867
|
-
prefix: "rep"
|
|
1868
|
-
}],
|
|
1869
|
-
time: [{
|
|
1870
|
-
kind: "scalar",
|
|
1871
|
-
key: "representation",
|
|
1872
|
-
prefix: "rep"
|
|
1873
|
-
}]
|
|
1874
|
-
};
|
|
1875
|
-
function serializeShapeField(field, node, record) {
|
|
1876
|
-
switch (field.kind) {
|
|
1877
|
-
case "scalar": return `${field.prefix}:${record[field.key] ?? ""}`;
|
|
1878
|
-
case "bool": return `${field.prefix}:${record[field.key] ? 1 : 0}`;
|
|
1879
|
-
case "child": {
|
|
1880
|
-
const child = record[field.key];
|
|
1881
|
-
return `${field.prefix}:${child ? signatureOf(child) : ""}`;
|
|
1882
|
-
}
|
|
1883
|
-
case "children": {
|
|
1884
|
-
const children = record[field.key] ?? [];
|
|
1885
|
-
return `${field.prefix}[${children.map((c) => signatureOf(c)).join(",")}]`;
|
|
1646
|
+
function sortKey(node) {
|
|
1647
|
+
const isArray = Array.isArray(node.name) ? "1" : "0";
|
|
1648
|
+
const typeOnly = node.isTypeOnly ? "0" : "1";
|
|
1649
|
+
const hasName = node.name != null ? "1" : "0";
|
|
1650
|
+
const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
|
|
1651
|
+
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Deduplicates and merges `SourceNode` objects by `name + isExportable + isTypeOnly`.
|
|
1655
|
+
*
|
|
1656
|
+
* Unnamed sources are deduplicated by object reference. Returns a deduplicated array in original order.
|
|
1657
|
+
*/
|
|
1658
|
+
function combineSources(sources) {
|
|
1659
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1660
|
+
for (const source of sources) {
|
|
1661
|
+
const key = sourceKey(source);
|
|
1662
|
+
if (!seen.has(key)) seen.set(key, source);
|
|
1663
|
+
}
|
|
1664
|
+
return [...seen.values()];
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
1668
|
+
*
|
|
1669
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
1670
|
+
*/
|
|
1671
|
+
function mergeNameArrays(existing, incoming) {
|
|
1672
|
+
const merged = new Set(existing);
|
|
1673
|
+
for (const name of incoming) merged.add(name);
|
|
1674
|
+
return [...merged];
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1678
|
+
*
|
|
1679
|
+
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
1680
|
+
* Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
|
|
1681
|
+
*/
|
|
1682
|
+
function combineExports(exports) {
|
|
1683
|
+
const result = [];
|
|
1684
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
1685
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1686
|
+
const keyed = exports.map((node) => ({
|
|
1687
|
+
node,
|
|
1688
|
+
key: sortKey(node)
|
|
1689
|
+
}));
|
|
1690
|
+
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
1691
|
+
for (const { node: curr } of keyed) {
|
|
1692
|
+
const { name, path, isTypeOnly, asAlias } = curr;
|
|
1693
|
+
if (Array.isArray(name)) {
|
|
1694
|
+
if (!name.length) continue;
|
|
1695
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
1696
|
+
const existing = namedByPath.get(key);
|
|
1697
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1698
|
+
else {
|
|
1699
|
+
const newItem = {
|
|
1700
|
+
...curr,
|
|
1701
|
+
name: [...new Set(name)]
|
|
1702
|
+
};
|
|
1703
|
+
result.push(newItem);
|
|
1704
|
+
namedByPath.set(key, newItem);
|
|
1705
|
+
}
|
|
1706
|
+
} else {
|
|
1707
|
+
const key = exportKey(path, name, isTypeOnly, asAlias);
|
|
1708
|
+
if (!seen.has(key)) {
|
|
1709
|
+
result.push(curr);
|
|
1710
|
+
seen.add(key);
|
|
1711
|
+
}
|
|
1886
1712
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1713
|
+
}
|
|
1714
|
+
return result;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Deduplicates and merges `ImportNode` objects, filtering out unused imports.
|
|
1718
|
+
*
|
|
1719
|
+
* Retains imports that are referenced in `source` or re-exported. Imports with the same path and
|
|
1720
|
+
* `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
|
|
1721
|
+
*
|
|
1722
|
+
* @note Use this when combining imports from multiple files to avoid duplicate declarations.
|
|
1723
|
+
*/
|
|
1724
|
+
function combineImports(imports, exports, source) {
|
|
1725
|
+
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
1726
|
+
const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
|
|
1727
|
+
const importNameMemo = /* @__PURE__ */ new Map();
|
|
1728
|
+
const canonicalizeName = (n) => {
|
|
1729
|
+
if (typeof n === "string") return n;
|
|
1730
|
+
const key = `${n.propertyName}:${n.name ?? ""}`;
|
|
1731
|
+
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
1732
|
+
return importNameMemo.get(key);
|
|
1733
|
+
};
|
|
1734
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
1735
|
+
for (const node of imports) {
|
|
1736
|
+
if (!Array.isArray(node.name)) continue;
|
|
1737
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
1738
|
+
}
|
|
1739
|
+
const result = [];
|
|
1740
|
+
const namedByPath = /* @__PURE__ */ new Map();
|
|
1741
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1742
|
+
const keyed = imports.map((node) => ({
|
|
1743
|
+
node,
|
|
1744
|
+
key: sortKey(node)
|
|
1745
|
+
}));
|
|
1746
|
+
keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
1747
|
+
for (const { node: curr } of keyed) {
|
|
1748
|
+
if (curr.path === curr.root) continue;
|
|
1749
|
+
const { path, isTypeOnly } = curr;
|
|
1750
|
+
let { name } = curr;
|
|
1751
|
+
if (Array.isArray(name)) {
|
|
1752
|
+
name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
|
|
1753
|
+
if (!name.length) continue;
|
|
1754
|
+
const key = pathTypeKey(path, isTypeOnly);
|
|
1755
|
+
const existing = namedByPath.get(key);
|
|
1756
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1757
|
+
else {
|
|
1758
|
+
const newItem = {
|
|
1759
|
+
...curr,
|
|
1760
|
+
name
|
|
1761
|
+
};
|
|
1762
|
+
result.push(newItem);
|
|
1763
|
+
namedByPath.set(key, newItem);
|
|
1764
|
+
}
|
|
1765
|
+
} else {
|
|
1766
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1767
|
+
const key = importKey(path, name, isTypeOnly);
|
|
1768
|
+
if (!seen.has(key)) {
|
|
1769
|
+
result.push(curr);
|
|
1770
|
+
seen.add(key);
|
|
1771
|
+
}
|
|
1893
1772
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1773
|
+
}
|
|
1774
|
+
return result;
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Extracts all string content from a `CodeNode` tree recursively.
|
|
1778
|
+
*
|
|
1779
|
+
* Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
|
|
1780
|
+
* and nested node content. Used internally to build the full source string for import filtering.
|
|
1781
|
+
*/
|
|
1782
|
+
function extractStringsFromNodes(nodes) {
|
|
1783
|
+
if (!nodes?.length) return "";
|
|
1784
|
+
return nodes.map((node) => {
|
|
1785
|
+
if (typeof node === "string") return node;
|
|
1786
|
+
if (node.kind === "Text") return node.value;
|
|
1787
|
+
if (node.kind === "Break") return "";
|
|
1788
|
+
if (node.kind === "Jsx") return node.value;
|
|
1789
|
+
const parts = [];
|
|
1790
|
+
if ("params" in node && node.params) parts.push(node.params);
|
|
1791
|
+
if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
|
|
1792
|
+
if ("returnType" in node && node.returnType) parts.push(node.returnType);
|
|
1793
|
+
if ("type" in node && typeof node.type === "string") parts.push(node.type);
|
|
1794
|
+
const nested = extractStringsFromNodes(node.nodes);
|
|
1795
|
+
if (nested) parts.push(nested);
|
|
1796
|
+
return parts.join("\n");
|
|
1797
|
+
}).filter(Boolean).join("\n");
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
|
|
1801
|
+
*
|
|
1802
|
+
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1803
|
+
* identifier for type definitions or error messages.
|
|
1804
|
+
*
|
|
1805
|
+
* @example
|
|
1806
|
+
* ```ts
|
|
1807
|
+
* resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' })
|
|
1808
|
+
* // => 'Pet'
|
|
1809
|
+
* ```
|
|
1810
|
+
*/
|
|
1811
|
+
function resolveRefName(node) {
|
|
1812
|
+
if (!node || node.type !== "ref") return null;
|
|
1813
|
+
if (node.ref) return require_utils.extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
1814
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Collects every named schema referenced (transitively) from a node via ref edges.
|
|
1818
|
+
*
|
|
1819
|
+
* Refs are followed by name only, the resolved `node.schema` is not traversed inline.
|
|
1820
|
+
* Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
|
|
1821
|
+
*
|
|
1822
|
+
* @example Collect refs from a single schema
|
|
1823
|
+
* ```ts
|
|
1824
|
+
* const names = collectReferencedSchemaNames(petSchema)
|
|
1825
|
+
* // → Set { 'Category', 'Tag' }
|
|
1826
|
+
* ```
|
|
1827
|
+
*
|
|
1828
|
+
* @example Accumulate refs from multiple schemas into one set
|
|
1829
|
+
* ```ts
|
|
1830
|
+
* const out = new Set<string>()
|
|
1831
|
+
* for (const schema of schemas) {
|
|
1832
|
+
* collectReferencedSchemaNames(schema, out)
|
|
1833
|
+
* }
|
|
1834
|
+
* ```
|
|
1835
|
+
*/
|
|
1836
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1837
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1838
|
+
collect(node, { schema(child) {
|
|
1839
|
+
if (child.type === "ref") {
|
|
1840
|
+
const name = resolveRefName(child);
|
|
1841
|
+
if (name) refs.add(name);
|
|
1897
1842
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1843
|
+
} });
|
|
1844
|
+
return refs;
|
|
1845
|
+
});
|
|
1846
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1847
|
+
if (!node) return out;
|
|
1848
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1849
|
+
return out;
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Collects the names of all top-level schemas transitively used by a set of operations.
|
|
1853
|
+
*
|
|
1854
|
+
* An operation uses a schema when any of its parameters, request body content, or responses
|
|
1855
|
+
* reference it, directly or indirectly through other named schemas.
|
|
1856
|
+
* The walk is iterative and safe against reference cycles.
|
|
1857
|
+
*
|
|
1858
|
+
* Use this together with `include` filters to determine which schemas from `components/schemas`
|
|
1859
|
+
* are reachable from the allowed operations, so that schemas used only by excluded operations
|
|
1860
|
+
* are not generated.
|
|
1861
|
+
*
|
|
1862
|
+
* @example Only generate schemas referenced by included operations
|
|
1863
|
+
* ```ts
|
|
1864
|
+
* const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
1865
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1866
|
+
*
|
|
1867
|
+
* for (const schema of schemas) {
|
|
1868
|
+
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1869
|
+
* // … generate schema
|
|
1870
|
+
* }
|
|
1871
|
+
* ```
|
|
1872
|
+
*
|
|
1873
|
+
* @example Check whether a specific schema is needed
|
|
1874
|
+
* ```ts
|
|
1875
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1876
|
+
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
1877
|
+
* ```
|
|
1878
|
+
*/
|
|
1879
|
+
const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
|
|
1880
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1881
|
+
const schemaMap = /* @__PURE__ */ new Map();
|
|
1882
|
+
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1883
|
+
const result = /* @__PURE__ */ new Set();
|
|
1884
|
+
function visitSchema(schema) {
|
|
1885
|
+
const directRefs = collectReferencedSchemaNames(schema);
|
|
1886
|
+
for (const name of directRefs) if (!result.has(name)) {
|
|
1887
|
+
result.add(name);
|
|
1888
|
+
const namedSchema = schemaMap.get(name);
|
|
1889
|
+
if (namedSchema) visitSchema(namedSchema);
|
|
1904
1890
|
}
|
|
1905
|
-
case "refTarget": return `->${refTargetName(node)}`;
|
|
1906
1891
|
}
|
|
1892
|
+
for (const op of operations) for (const schema of collectLazy(op, {
|
|
1893
|
+
depth: "shallow",
|
|
1894
|
+
schema: (node) => node
|
|
1895
|
+
})) visitSchema(schema);
|
|
1896
|
+
return result;
|
|
1907
1897
|
}
|
|
1898
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
1899
|
+
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
1900
|
+
}
|
|
1901
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1902
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1903
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1904
|
+
for (const schema of schemas) {
|
|
1905
|
+
if (!schema.name) continue;
|
|
1906
|
+
graph.set(schema.name, collectReferencedSchemaNames(schema));
|
|
1907
|
+
}
|
|
1908
|
+
const circular = /* @__PURE__ */ new Set();
|
|
1909
|
+
for (const start of graph.keys()) {
|
|
1910
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1911
|
+
const stack = [...graph.get(start) ?? []];
|
|
1912
|
+
while (stack.length > 0) {
|
|
1913
|
+
const node = stack.pop();
|
|
1914
|
+
if (node === start) {
|
|
1915
|
+
circular.add(start);
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
if (visited.has(node)) continue;
|
|
1919
|
+
visited.add(node);
|
|
1920
|
+
const next = graph.get(node);
|
|
1921
|
+
if (next) for (const r of next) stack.push(r);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return circular;
|
|
1925
|
+
});
|
|
1908
1926
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1927
|
+
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
1928
|
+
*
|
|
1929
|
+
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
1930
|
+
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
1931
|
+
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1932
|
+
*
|
|
1933
|
+
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1911
1934
|
*/
|
|
1912
|
-
function
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
if (!fields) return `${node.type}|${flags}`;
|
|
1916
|
-
const record = node;
|
|
1917
|
-
const parts = [`${node.type}|${flags}`];
|
|
1918
|
-
for (const field of fields) parts.push(serializeShapeField(field, node, record));
|
|
1919
|
-
return parts.join("|");
|
|
1935
|
+
function findCircularSchemas(schemas) {
|
|
1936
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1937
|
+
return findCircularSchemasMemo(schemas);
|
|
1920
1938
|
}
|
|
1921
1939
|
/**
|
|
1922
|
-
*
|
|
1923
|
-
* hashed during dedupe planning is not walked again when it is rewritten during streaming. Reuse
|
|
1924
|
-
* is safe because a signature depends only on content, and nodes are immutable once created.
|
|
1925
|
-
*/
|
|
1926
|
-
const signatureCache = /* @__PURE__ */ new WeakMap();
|
|
1927
|
-
/**
|
|
1928
|
-
* Computes a deterministic, shape-only signature (a content hash) for a schema node. Two schemas
|
|
1929
|
-
* share a signature when they are structurally identical, ignoring documentation (`name`, `title`,
|
|
1930
|
-
* `description`, `example`, `default`, `deprecated`) and usage-slot flags (`optional`, `nullish`,
|
|
1931
|
-
* `readOnly`, `writeOnly`). `nullable` is kept because it changes the produced type, and `ref`
|
|
1932
|
-
* nodes compare by target name, which also terminates on circular shapes.
|
|
1940
|
+
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
1933
1941
|
*
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
1936
|
-
*
|
|
1937
|
-
*
|
|
1938
|
-
* ```
|
|
1942
|
+
* Use `excludeName` to ignore refs to specific schemas (useful when self-references are handled separately).
|
|
1943
|
+
* Commonly used with `findCircularSchemas()` to detect where lazy wrappers are needed in code generation.
|
|
1944
|
+
*
|
|
1945
|
+
* @note Returns `true` for the first matching circular ref found. Use for fast dependency checks.
|
|
1939
1946
|
*/
|
|
1940
|
-
function
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1947
|
+
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1948
|
+
if (!node || circularSchemas.size === 0) return false;
|
|
1949
|
+
for (const _ of collectLazy(node, { schema(child) {
|
|
1950
|
+
if (child.type !== "ref") return null;
|
|
1951
|
+
const name = resolveRefName(child);
|
|
1952
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
1953
|
+
} })) return true;
|
|
1954
|
+
return false;
|
|
1946
1955
|
}
|
|
1947
1956
|
//#endregion
|
|
1948
|
-
//#region src/
|
|
1949
|
-
/**
|
|
1950
|
-
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
1951
|
-
* usage-slot and documentation fields that are not part of the canonical type.
|
|
1952
|
-
*/
|
|
1953
|
-
function createRefNode(node, canonical) {
|
|
1954
|
-
return createSchema({
|
|
1955
|
-
type: "ref",
|
|
1956
|
-
name: canonical.name,
|
|
1957
|
-
ref: canonical.ref,
|
|
1958
|
-
optional: node.optional,
|
|
1959
|
-
nullish: node.nullish,
|
|
1960
|
-
readOnly: node.readOnly,
|
|
1961
|
-
writeOnly: node.writeOnly,
|
|
1962
|
-
deprecated: node.deprecated,
|
|
1963
|
-
description: node.description,
|
|
1964
|
-
default: node.default,
|
|
1965
|
-
example: node.example
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
|
-
function applyDedupe(node, plan, skipRootMatch = false) {
|
|
1969
|
-
const { canonicalBySignature, aliasNames } = plan;
|
|
1970
|
-
if (canonicalBySignature.size === 0 && aliasNames.size === 0) return node;
|
|
1971
|
-
const root = node;
|
|
1972
|
-
return transform(node, { schema(schemaNode) {
|
|
1973
|
-
if (schemaNode.type === "ref") {
|
|
1974
|
-
const target = schemaNode.ref ? require_utils.extractRefName(schemaNode.ref) : schemaNode.name;
|
|
1975
|
-
const canonical = target ? aliasNames.get(target) : void 0;
|
|
1976
|
-
return canonical ? {
|
|
1977
|
-
...schemaNode,
|
|
1978
|
-
name: canonical.name,
|
|
1979
|
-
ref: canonical.ref
|
|
1980
|
-
} : void 0;
|
|
1981
|
-
}
|
|
1982
|
-
const signature = signatureOf(schemaNode);
|
|
1983
|
-
if (skipRootMatch && schemaNode === root) return void 0;
|
|
1984
|
-
const canonical = canonicalBySignature.get(signature);
|
|
1985
|
-
if (!canonical) return void 0;
|
|
1986
|
-
return createRefNode(schemaNode, canonical);
|
|
1987
|
-
} });
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Strips usage-slot flags from a hoisted definition and applies its canonical name.
|
|
1991
|
-
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
1992
|
-
*/
|
|
1993
|
-
function cleanDefinition(node, name) {
|
|
1994
|
-
return {
|
|
1995
|
-
...node,
|
|
1996
|
-
name,
|
|
1997
|
-
optional: void 0,
|
|
1998
|
-
nullish: void 0
|
|
1999
|
-
};
|
|
2000
|
-
}
|
|
1957
|
+
//#region src/factory.ts
|
|
2001
1958
|
/**
|
|
2002
|
-
*
|
|
1959
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1960
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1961
|
+
* with the changes applied.
|
|
2003
1962
|
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2007
|
-
*
|
|
2008
|
-
* canonical. Otherwise a new definition is hoisted using `nameFor`. The plan is then applied
|
|
2009
|
-
* per node with {@link applyDedupe}.
|
|
1963
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract, pair it with the
|
|
1964
|
+
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1965
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1966
|
+
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
2010
1967
|
*
|
|
2011
1968
|
* @example
|
|
2012
1969
|
* ```ts
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2015
|
-
* nameFor: (node) => node.name ?? null,
|
|
2016
|
-
* refFor: (name) => `#/components/schemas/${name}`,
|
|
2017
|
-
* })
|
|
1970
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1971
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
2018
1972
|
* ```
|
|
2019
1973
|
*/
|
|
2020
|
-
function
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
function record(schemaNode) {
|
|
2025
|
-
const signature = signatureOf(schemaNode);
|
|
2026
|
-
if (!isCandidate(schemaNode)) return;
|
|
2027
|
-
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
2028
|
-
const group = groups.get(signature);
|
|
2029
|
-
if (group) {
|
|
2030
|
-
group.count++;
|
|
2031
|
-
if (isTopLevel) group.topLevelNames.push(schemaNode.name);
|
|
2032
|
-
} else groups.set(signature, {
|
|
2033
|
-
count: 1,
|
|
2034
|
-
representative: schemaNode,
|
|
2035
|
-
topLevelNames: isTopLevel ? [schemaNode.name] : []
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
for (const root of roots) {
|
|
2039
|
-
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
2040
|
-
for (const schemaNode of collectLazy(root, { schema: (node) => node })) record(schemaNode);
|
|
2041
|
-
}
|
|
2042
|
-
const canonicalBySignature = /* @__PURE__ */ new Map();
|
|
2043
|
-
const aliasNames = /* @__PURE__ */ new Map();
|
|
2044
|
-
const pendingHoists = [];
|
|
2045
|
-
for (const [signature, group] of groups) {
|
|
2046
|
-
if (group.count < minOccurrences) continue;
|
|
2047
|
-
const [firstName, ...duplicateNames] = group.topLevelNames;
|
|
2048
|
-
if (firstName) {
|
|
2049
|
-
const canonical = {
|
|
2050
|
-
name: firstName,
|
|
2051
|
-
ref: refFor(firstName)
|
|
2052
|
-
};
|
|
2053
|
-
canonicalBySignature.set(signature, canonical);
|
|
2054
|
-
for (const duplicate of duplicateNames) aliasNames.set(duplicate, canonical);
|
|
2055
|
-
continue;
|
|
2056
|
-
}
|
|
2057
|
-
const name = nameFor(group.representative, signature);
|
|
2058
|
-
if (!name) continue;
|
|
2059
|
-
canonicalBySignature.set(signature, {
|
|
2060
|
-
name,
|
|
2061
|
-
ref: refFor(name)
|
|
2062
|
-
});
|
|
2063
|
-
pendingHoists.push({
|
|
2064
|
-
name,
|
|
2065
|
-
representative: group.representative
|
|
2066
|
-
});
|
|
2067
|
-
}
|
|
2068
|
-
return {
|
|
2069
|
-
canonicalBySignature,
|
|
2070
|
-
aliasNames,
|
|
2071
|
-
hoisted: pendingHoists.map(({ name, representative }) => cleanDefinition(applyDedupe(representative, {
|
|
2072
|
-
canonicalBySignature,
|
|
2073
|
-
aliasNames
|
|
2074
|
-
}, true), name))
|
|
1974
|
+
function update(node, changes) {
|
|
1975
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1976
|
+
...node,
|
|
1977
|
+
...changes
|
|
2075
1978
|
};
|
|
1979
|
+
return node;
|
|
2076
1980
|
}
|
|
2077
|
-
//#endregion
|
|
2078
|
-
//#region src/dialect.ts
|
|
2079
1981
|
/**
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
1982
|
+
* Creates a fully resolved `FileNode` from a file input descriptor.
|
|
1983
|
+
*
|
|
1984
|
+
* Computes:
|
|
1985
|
+
* - `id` SHA256 hash of the file path
|
|
1986
|
+
* - `name` `baseName` without extension
|
|
1987
|
+
* - `extname` extension extracted from `baseName`
|
|
1988
|
+
*
|
|
1989
|
+
* Deduplicates:
|
|
1990
|
+
* - `sources` via `combineSources`
|
|
1991
|
+
* - `exports` via `combineExports`
|
|
1992
|
+
* - `imports` via `combineImports` (also filters unused imports)
|
|
1993
|
+
*
|
|
1994
|
+
* @throws {Error} when `baseName` has no extension.
|
|
2082
1995
|
*
|
|
2083
1996
|
* @example
|
|
2084
1997
|
* ```ts
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2088
|
-
*
|
|
2089
|
-
*
|
|
2090
|
-
*
|
|
2091
|
-
* resolveRef,
|
|
1998
|
+
* const file = createFile({
|
|
1999
|
+
* baseName: 'petStore.ts',
|
|
2000
|
+
* path: 'src/models/petStore.ts',
|
|
2001
|
+
* sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
|
|
2002
|
+
* imports: [createImport({ name: ['z'], path: 'zod' })],
|
|
2003
|
+
* exports: [createExport({ name: ['Pet'], path: './petStore' })],
|
|
2092
2004
|
* })
|
|
2005
|
+
* // file.id = SHA256 hash of 'src/models/petStore.ts'
|
|
2006
|
+
* // file.name = 'petStore'
|
|
2007
|
+
* // file.extname = '.ts'
|
|
2093
2008
|
* ```
|
|
2094
2009
|
*/
|
|
2095
|
-
function
|
|
2096
|
-
|
|
2010
|
+
function createFile(input) {
|
|
2011
|
+
const extname = node_path.default.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
|
|
2012
|
+
if (!extname) throw new Error(`No extname found for ${input.baseName}`);
|
|
2013
|
+
const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
|
|
2014
|
+
const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
|
|
2015
|
+
const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
|
|
2016
|
+
const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name) => Boolean(name)));
|
|
2017
|
+
const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
|
|
2018
|
+
const resolvedImports = combinedImports.filter((imp) => imp.path !== input.path).flatMap((imp) => {
|
|
2019
|
+
if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
|
|
2020
|
+
const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
|
|
2021
|
+
if (!kept.length) return [];
|
|
2022
|
+
return [kept.length === imp.name.length ? imp : {
|
|
2023
|
+
...imp,
|
|
2024
|
+
name: kept
|
|
2025
|
+
}];
|
|
2026
|
+
});
|
|
2027
|
+
const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
|
|
2028
|
+
return {
|
|
2029
|
+
kind: "File",
|
|
2030
|
+
...input,
|
|
2031
|
+
id: (0, node_crypto.hash)("sha256", input.path, "hex"),
|
|
2032
|
+
name: trimExtName(input.baseName),
|
|
2033
|
+
extname,
|
|
2034
|
+
imports: resolvedImports,
|
|
2035
|
+
exports: resolvedExports,
|
|
2036
|
+
sources: resolvedSources,
|
|
2037
|
+
meta: input.meta ?? {}
|
|
2038
|
+
};
|
|
2097
2039
|
}
|
|
2098
2040
|
//#endregion
|
|
2099
2041
|
//#region src/printer.ts
|
|
@@ -2281,11 +2223,15 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2281
2223
|
}
|
|
2282
2224
|
//#endregion
|
|
2283
2225
|
exports.applyDedupe = applyDedupe;
|
|
2226
|
+
exports.arrowFunctionDef = arrowFunctionDef;
|
|
2227
|
+
exports.breakDef = breakDef;
|
|
2284
2228
|
exports.buildDedupePlan = buildDedupePlan;
|
|
2285
2229
|
exports.caseParams = caseParams;
|
|
2286
2230
|
exports.collect = collect;
|
|
2287
2231
|
exports.collectUsedSchemaNames = collectUsedSchemaNames;
|
|
2232
|
+
exports.constDef = constDef;
|
|
2288
2233
|
exports.containsCircularRef = containsCircularRef;
|
|
2234
|
+
exports.contentDef = contentDef;
|
|
2289
2235
|
exports.createArrowFunction = createArrowFunction;
|
|
2290
2236
|
exports.createBreak = createBreak;
|
|
2291
2237
|
exports.createConst = createConst;
|
|
@@ -2296,14 +2242,14 @@ exports.createFunction = createFunction;
|
|
|
2296
2242
|
exports.createFunctionParameter = createFunctionParameter;
|
|
2297
2243
|
exports.createFunctionParameters = createFunctionParameters;
|
|
2298
2244
|
exports.createImport = createImport;
|
|
2245
|
+
exports.createIndexedAccessType = createIndexedAccessType;
|
|
2299
2246
|
exports.createInput = createInput;
|
|
2300
2247
|
exports.createJsx = createJsx;
|
|
2248
|
+
exports.createObjectBindingPattern = createObjectBindingPattern;
|
|
2301
2249
|
exports.createOperation = createOperation;
|
|
2302
2250
|
exports.createOperationParams = createOperationParams;
|
|
2303
2251
|
exports.createOutput = createOutput;
|
|
2304
2252
|
exports.createParameter = createParameter;
|
|
2305
|
-
exports.createParameterGroup = createParameterGroup;
|
|
2306
|
-
exports.createParamsType = createParamsType;
|
|
2307
2253
|
exports.createPrinterFactory = createPrinterFactory;
|
|
2308
2254
|
exports.createProperty = createProperty;
|
|
2309
2255
|
exports.createResponse = createResponse;
|
|
@@ -2312,27 +2258,46 @@ exports.createSource = createSource;
|
|
|
2312
2258
|
exports.createStreamInput = createStreamInput;
|
|
2313
2259
|
exports.createText = createText;
|
|
2314
2260
|
exports.createType = createType;
|
|
2261
|
+
exports.createTypeLiteral = createTypeLiteral;
|
|
2262
|
+
exports.defineNode = defineNode;
|
|
2315
2263
|
exports.definePrinter = definePrinter;
|
|
2316
2264
|
exports.defineSchemaDialect = defineSchemaDialect;
|
|
2265
|
+
exports.exportDef = exportDef;
|
|
2317
2266
|
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
2267
|
+
exports.fileDef = fileDef;
|
|
2318
2268
|
exports.findCircularSchemas = findCircularSchemas;
|
|
2269
|
+
exports.functionDef = functionDef;
|
|
2270
|
+
exports.functionParameterDef = functionParameterDef;
|
|
2271
|
+
exports.functionParametersDef = functionParametersDef;
|
|
2319
2272
|
exports.httpMethods = require_utils.httpMethods;
|
|
2273
|
+
exports.importDef = importDef;
|
|
2274
|
+
exports.indexedAccessTypeDef = indexedAccessTypeDef;
|
|
2275
|
+
exports.inputDef = inputDef;
|
|
2320
2276
|
exports.isHttpOperationNode = isHttpOperationNode;
|
|
2321
|
-
exports.isInputNode = isInputNode;
|
|
2322
|
-
exports.isOperationNode = isOperationNode;
|
|
2323
|
-
exports.isOutputNode = isOutputNode;
|
|
2324
|
-
exports.isSchemaNode = isSchemaNode;
|
|
2325
2277
|
exports.isStringType = isStringType;
|
|
2278
|
+
exports.jsxDef = jsxDef;
|
|
2326
2279
|
exports.mergeAdjacentObjectsLazy = mergeAdjacentObjectsLazy;
|
|
2327
2280
|
exports.narrowSchema = narrowSchema;
|
|
2281
|
+
exports.objectBindingPatternDef = objectBindingPatternDef;
|
|
2282
|
+
exports.operationDef = operationDef;
|
|
2283
|
+
exports.outputDef = outputDef;
|
|
2284
|
+
exports.parameterDef = parameterDef;
|
|
2285
|
+
exports.propertyDef = propertyDef;
|
|
2286
|
+
exports.requestBodyDef = requestBodyDef;
|
|
2287
|
+
exports.responseDef = responseDef;
|
|
2288
|
+
exports.schemaDef = schemaDef;
|
|
2328
2289
|
exports.schemaTypes = require_utils.schemaTypes;
|
|
2329
2290
|
exports.setDiscriminatorEnum = setDiscriminatorEnum;
|
|
2330
2291
|
exports.setEnumName = setEnumName;
|
|
2331
2292
|
exports.signatureOf = signatureOf;
|
|
2332
2293
|
exports.simplifyUnion = simplifyUnion;
|
|
2294
|
+
exports.sourceDef = sourceDef;
|
|
2333
2295
|
exports.syncOptionality = syncOptionality;
|
|
2334
2296
|
exports.syncSchemaRef = syncSchemaRef;
|
|
2297
|
+
exports.textDef = textDef;
|
|
2335
2298
|
exports.transform = transform;
|
|
2299
|
+
exports.typeDef = typeDef;
|
|
2300
|
+
exports.typeLiteralDef = typeLiteralDef;
|
|
2336
2301
|
exports.update = update;
|
|
2337
2302
|
exports.walk = walk;
|
|
2338
2303
|
|