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