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