@kubb/ast 5.0.0-beta.61 → 5.0.0-beta.63

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.
Files changed (95) hide show
  1. package/dist/{defineMacro-BLIR6k-j.d.ts → defineMacro-BATi7xoC.d.ts} +11 -20
  2. package/dist/{defineMacro-Bv9R_9a2.cjs → defineMacro-CEZHaCXE.cjs} +13 -21
  3. package/dist/defineMacro-CEZHaCXE.cjs.map +1 -0
  4. package/dist/{defineMacro-BTXvS8nI.js → defineMacro-Shz8f6SG.js} +12 -19
  5. package/dist/defineMacro-Shz8f6SG.js.map +1 -0
  6. package/dist/factory.cjs +88 -28
  7. package/dist/factory.cjs.map +1 -0
  8. package/dist/factory.d.ts +6 -41
  9. package/dist/factory.js +58 -3
  10. package/dist/factory.js.map +1 -0
  11. package/dist/{index-BzjwdK2M.d.ts → index-B9cc8MBS.d.ts} +164 -194
  12. package/dist/index.cjs +144 -274
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +43 -37
  15. package/dist/index.js +109 -235
  16. package/dist/index.js.map +1 -1
  17. package/dist/macros.cjs +41 -28
  18. package/dist/macros.cjs.map +1 -1
  19. package/dist/macros.d.ts +14 -12
  20. package/dist/macros.js +34 -21
  21. package/dist/macros.js.map +1 -1
  22. package/dist/{operationParams-BZ07xDm0.d.ts → operationParams-k5CKwSWZ.d.ts} +9 -2
  23. package/dist/{visitor-DJ6ZEJvq.js → refs-BjNDuCBD.js} +116 -159
  24. package/dist/refs-BjNDuCBD.js.map +1 -0
  25. package/dist/{visitor-DpKZ9Tk0.cjs → refs-u5SDdyV7.cjs} +140 -195
  26. package/dist/refs-u5SDdyV7.cjs.map +1 -0
  27. package/dist/{response-hnSw2NKE.cjs → schema-BkvrrOAr.cjs} +496 -231
  28. package/dist/schema-BkvrrOAr.cjs.map +1 -0
  29. package/dist/{response-KUdWiDWw.js → schema-Cbnxmz4b.js} +472 -220
  30. package/dist/schema-Cbnxmz4b.js.map +1 -0
  31. package/dist/{types-DyDzizSf.d.ts → types-BB_xgRJ3.d.ts} +49 -123
  32. package/dist/types.d.ts +5 -6
  33. package/dist/{utils-BLJwyza-.cjs → utils-CEepwqmb.cjs} +76 -69
  34. package/dist/utils-CEepwqmb.cjs.map +1 -0
  35. package/dist/{utils-CF_-Pn_c.js → utils-DaXkewb1.js} +58 -51
  36. package/dist/utils-DaXkewb1.js.map +1 -0
  37. package/dist/utils.cjs +11 -11
  38. package/dist/utils.d.ts +53 -30
  39. package/dist/utils.js +3 -3
  40. package/package.json +1 -1
  41. package/src/constants.ts +3 -36
  42. package/src/defineMacro.ts +20 -13
  43. package/src/{node.ts → defineNode.ts} +12 -38
  44. package/src/{printer.ts → definePrinter.ts} +5 -3
  45. package/src/dialect.ts +52 -19
  46. package/src/factory.ts +7 -94
  47. package/src/index.ts +7 -9
  48. package/src/infer.ts +1 -1
  49. package/src/macros/macroDiscriminatorEnum.ts +7 -1
  50. package/src/macros/macroEnumName.ts +10 -2
  51. package/src/macros/macroSimplifyUnion.ts +29 -19
  52. package/src/nodes/code.ts +40 -80
  53. package/src/nodes/content.ts +2 -7
  54. package/src/nodes/file.ts +107 -18
  55. package/src/nodes/function.ts +46 -46
  56. package/src/nodes/index.ts +2 -3
  57. package/src/nodes/input.ts +13 -9
  58. package/src/nodes/operation.ts +8 -5
  59. package/src/nodes/output.ts +1 -1
  60. package/src/nodes/parameter.ts +5 -5
  61. package/src/nodes/property.ts +5 -5
  62. package/src/nodes/requestBody.ts +6 -13
  63. package/src/nodes/response.ts +93 -8
  64. package/src/nodes/schema.ts +5 -5
  65. package/src/optionality.ts +15 -0
  66. package/src/registry.ts +1 -1
  67. package/src/types.ts +4 -6
  68. package/src/utils/codegen.ts +6 -7
  69. package/src/utils/extractStringsFromNodes.ts +1 -0
  70. package/src/utils/fileMerge.ts +7 -8
  71. package/src/utils/operationParams.ts +9 -9
  72. package/src/utils/refs.ts +14 -18
  73. package/src/utils/schemaGraph.ts +41 -33
  74. package/src/utils/schemaMerge.ts +2 -2
  75. package/src/visitor.ts +3 -14
  76. package/dist/defineMacro-BTXvS8nI.js.map +0 -1
  77. package/dist/defineMacro-Bv9R_9a2.cjs.map +0 -1
  78. package/dist/extractStringsFromNodes-Cja-xxx5.js +0 -29
  79. package/dist/extractStringsFromNodes-Cja-xxx5.js.map +0 -1
  80. package/dist/extractStringsFromNodes-DKgDjFO0.cjs +0 -34
  81. package/dist/extractStringsFromNodes-DKgDjFO0.cjs.map +0 -1
  82. package/dist/extractStringsFromNodes-p4mX1TQD.d.ts +0 -14
  83. package/dist/factory-CZNOGI-N.js +0 -283
  84. package/dist/factory-CZNOGI-N.js.map +0 -1
  85. package/dist/factory-DG1CVkEb.cjs +0 -300
  86. package/dist/factory-DG1CVkEb.cjs.map +0 -1
  87. package/dist/response-KUdWiDWw.js.map +0 -1
  88. package/dist/response-hnSw2NKE.cjs.map +0 -1
  89. package/dist/utils-BLJwyza-.cjs.map +0 -1
  90. package/dist/utils-CF_-Pn_c.js.map +0 -1
  91. package/dist/visitor-DJ6ZEJvq.js.map +0 -1
  92. package/dist/visitor-DpKZ9Tk0.cjs.map +0 -1
  93. package/src/dedupe.ts +0 -239
  94. package/src/mocks.ts +0 -56
  95. package/src/nodes/http.ts +0 -85
@@ -33,58 +33,52 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  enumerable: true
34
34
  }) : target, mod));
35
35
  //#endregion
36
- //#region src/node.ts
36
+ let node_crypto = require("node:crypto");
37
+ let node_path = require("node:path");
38
+ node_path = __toESM(node_path, 1);
39
+ //#region src/defineNode.ts
37
40
  /**
38
- * Builds a type guard that matches nodes of the given `kind`.
41
+ * Visitor callback names, one per traversable node kind, in traversal order.
42
+ * Kept in sync with the keys of `Visitor` in `visitor.ts`.
39
43
  */
40
- function isKind(kind) {
41
- return (node) => node.kind === kind;
42
- }
44
+ const visitorKeys = [
45
+ "input",
46
+ "output",
47
+ "operation",
48
+ "schema",
49
+ "property",
50
+ "parameter",
51
+ "response"
52
+ ];
43
53
  /**
44
- * Updates a schema's `optional` and `nullish` flags from a parent's `required`
45
- * value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
46
- * object properties combine "required" and "nullable" into a single AST.
47
- *
48
- * - Non-required + non-nullable → `optional: true`.
49
- * - Non-required + nullable → `nullish: true`.
50
- * - Required → both flags cleared.
54
+ * Builds a type guard that matches nodes of the given `kind`.
51
55
  */
52
- function syncOptionality(schema, required) {
53
- const nullable = schema.nullable ?? false;
54
- return {
55
- ...schema,
56
- optional: !required && !nullable ? true : void 0,
57
- nullish: !required && nullable ? true : void 0
58
- };
56
+ function isKind(kind) {
57
+ return (node) => node?.kind === kind;
59
58
  }
60
59
  /**
61
60
  * Defines a node once and derives its `create` builder, `is` guard, and traversal
62
61
  * metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
63
62
  * `kind`, so node construction lives in one place without scattered `as` casts.
64
63
  *
65
- * Set `rebuild: true` when the `build` hook derives fields from children. After a
66
- * transform rewrites those children, the registry reruns `create` so the derived
67
- * fields stay correct.
68
- *
69
64
  * @example Simple node
70
65
  * ```ts
71
66
  * const importDef = defineNode<ImportNode>({ kind: 'Import' })
72
67
  * const createImport = importDef.create
73
68
  * ```
74
69
  *
75
- * @example Node with a build hook that is rerun on transform
70
+ * @example Node with a build hook
76
71
  * ```ts
77
72
  * const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
78
73
  * kind: 'Property',
79
74
  * build: (props) => ({ ...props, required: props.required ?? false }),
80
75
  * children: ['schema'],
81
76
  * visitorKey: 'property',
82
- * rebuild: true,
83
77
  * })
84
78
  * ```
85
79
  */
86
80
  function defineNode(config) {
87
- const { kind, defaults, build, children, visitorKey, rebuild } = config;
81
+ const { kind, defaults, build, children, visitorKey } = config;
88
82
  function create(input) {
89
83
  const base = build ? build(input) : input;
90
84
  return {
@@ -98,110 +92,48 @@ function defineNode(config) {
98
92
  create,
99
93
  is: isKind(kind),
100
94
  children,
101
- visitorKey,
102
- rebuild
95
+ visitorKey
103
96
  };
104
97
  }
105
98
  //#endregion
106
- //#region src/nodes/schema.ts
99
+ //#region src/nodes/code.ts
107
100
  /**
108
- * Maps schema `type` to its underlying `primitive`.
109
- * Primitive types map to themselves. Special string formats map to `'string'`.
110
- * Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
101
+ * Definition for the {@link ConstNode}.
111
102
  */
112
- const TYPE_TO_PRIMITIVE = {
113
- string: "string",
114
- number: "number",
115
- integer: "integer",
116
- bigint: "bigint",
117
- boolean: "boolean",
118
- null: "null",
119
- any: "any",
120
- unknown: "unknown",
121
- void: "void",
122
- never: "never",
123
- object: "object",
124
- array: "array",
125
- date: "date",
126
- uuid: "string",
127
- email: "string",
128
- url: "string",
129
- datetime: "string",
130
- time: "string"
131
- };
103
+ const constDef = defineNode({ kind: "Const" });
132
104
  /**
133
- * Definition for the {@link SchemaNode}. Object schemas default `properties` to an
134
- * empty array, and `primitive` is inferred from `type` when not explicitly provided.
105
+ * Definition for the {@link TypeNode}.
135
106
  */
136
- const schemaDef = defineNode({
137
- kind: "Schema",
138
- build: (props) => {
139
- if (props.type === "object") return {
140
- properties: [],
141
- primitive: "object",
142
- ...props
143
- };
144
- return {
145
- primitive: TYPE_TO_PRIMITIVE[props.type],
146
- ...props
147
- };
148
- },
149
- children: [
150
- "properties",
151
- "items",
152
- "members",
153
- "additionalProperties"
154
- ],
155
- visitorKey: "schema"
156
- });
157
- function createSchema(props) {
158
- return schemaDef.create(props);
159
- }
160
- //#endregion
161
- //#region ../../internals/utils/src/casing.ts
107
+ const typeDef = defineNode({ kind: "Type" });
162
108
  /**
163
- * Shared implementation for camelCase and PascalCase conversion.
164
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
165
- * and capitalizes each word according to `pascal`.
166
- *
167
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
109
+ * Definition for the {@link FunctionNode}.
168
110
  */
169
- function toCamelOrPascal(text, pascal) {
170
- 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) => {
171
- if (word.length > 1 && word === word.toUpperCase()) return word;
172
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
173
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
174
- }
111
+ const functionDef = defineNode({ kind: "Function" });
175
112
  /**
176
- * Converts `text` to camelCase.
177
- *
178
- * @example Word boundaries
179
- * `camelCase('hello-world') // 'helloWorld'`
180
- *
181
- * @example With a prefix
182
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
113
+ * Definition for the {@link ArrowFunctionNode}.
183
114
  */
184
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
185
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
186
- }
115
+ const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
187
116
  /**
188
- * Converts `text` to PascalCase.
189
- *
190
- * @example Word boundaries
191
- * `pascalCase('hello-world') // 'HelloWorld'`
192
- *
193
- * @example With a suffix
194
- * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
117
+ * Definition for the {@link TextNode}.
195
118
  */
196
- function pascalCase(text, { prefix = "", suffix = "" } = {}) {
197
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
198
- }
199
- //#endregion
200
- //#region src/nodes/code.ts
119
+ const textDef = defineNode({
120
+ kind: "Text",
121
+ build: (value) => ({ value })
122
+ });
201
123
  /**
202
- * Definition for the {@link ConstNode}.
124
+ * Definition for the {@link BreakNode}.
203
125
  */
204
- const constDef = defineNode({ kind: "Const" });
126
+ const breakDef = defineNode({
127
+ kind: "Break",
128
+ build: () => ({})
129
+ });
130
+ /**
131
+ * Definition for the {@link JsxNode}.
132
+ */
133
+ const jsxDef = defineNode({
134
+ kind: "Jsx",
135
+ build: (value) => ({ value })
136
+ });
205
137
  /**
206
138
  * Creates a `ConstNode` representing a TypeScript `const` declaration.
207
139
  *
@@ -213,10 +145,6 @@ const constDef = defineNode({ kind: "Const" });
213
145
  */
214
146
  const createConst = constDef.create;
215
147
  /**
216
- * Definition for the {@link TypeNode}.
217
- */
218
- const typeDef = defineNode({ kind: "Type" });
219
- /**
220
148
  * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
221
149
  *
222
150
  * @example
@@ -227,10 +155,6 @@ const typeDef = defineNode({ kind: "Type" });
227
155
  */
228
156
  const createType = typeDef.create;
229
157
  /**
230
- * Definition for the {@link FunctionNode}.
231
- */
232
- const functionDef = defineNode({ kind: "Function" });
233
- /**
234
158
  * Creates a `FunctionNode` representing a TypeScript `function` declaration.
235
159
  *
236
160
  * @example
@@ -241,10 +165,6 @@ const functionDef = defineNode({ kind: "Function" });
241
165
  */
242
166
  const createFunction = functionDef.create;
243
167
  /**
244
- * Definition for the {@link ArrowFunctionNode}.
245
- */
246
- const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
247
- /**
248
168
  * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
249
169
  *
250
170
  * @example
@@ -255,13 +175,6 @@ const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
255
175
  */
256
176
  const createArrowFunction = arrowFunctionDef.create;
257
177
  /**
258
- * Definition for the {@link TextNode}.
259
- */
260
- const textDef = defineNode({
261
- kind: "Text",
262
- build: (value) => ({ value })
263
- });
264
- /**
265
178
  * Creates a {@link TextNode} representing a raw string fragment in the source output.
266
179
  *
267
180
  * @example
@@ -272,13 +185,6 @@ const textDef = defineNode({
272
185
  */
273
186
  const createText = textDef.create;
274
187
  /**
275
- * Definition for the {@link BreakNode}.
276
- */
277
- const breakDef = defineNode({
278
- kind: "Break",
279
- build: () => ({})
280
- });
281
- /**
282
188
  * Creates a {@link BreakNode} representing a line break in the source output.
283
189
  *
284
190
  * @example
@@ -291,13 +197,6 @@ function createBreak() {
291
197
  return breakDef.create();
292
198
  }
293
199
  /**
294
- * Definition for the {@link JsxNode}.
295
- */
296
- const jsxDef = defineNode({
297
- kind: "Jsx",
298
- build: (value) => ({ value })
299
- });
300
- /**
301
200
  * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
302
201
  *
303
202
  * @example
@@ -321,12 +220,252 @@ const contentDef = defineNode({
321
220
  */
322
221
  const createContent = contentDef.create;
323
222
  //#endregion
223
+ //#region ../../internals/utils/src/casing.ts
224
+ /**
225
+ * Shared implementation for camelCase and PascalCase conversion.
226
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
227
+ * and capitalizes each word according to `pascal`.
228
+ *
229
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
230
+ */
231
+ function toCamelOrPascal(text, pascal) {
232
+ 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) => {
233
+ if (word.length > 1 && word === word.toUpperCase()) return word;
234
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
235
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
236
+ }
237
+ /**
238
+ * Converts `text` to camelCase.
239
+ *
240
+ * @example Word boundaries
241
+ * `camelCase('hello-world') // 'helloWorld'`
242
+ *
243
+ * @example With a prefix
244
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
245
+ */
246
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
247
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
248
+ }
249
+ /**
250
+ * Converts `text` to PascalCase.
251
+ *
252
+ * @example Word boundaries
253
+ * `pascalCase('hello-world') // 'HelloWorld'`
254
+ *
255
+ * @example With a suffix
256
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
257
+ */
258
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
259
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
260
+ }
261
+ //#endregion
262
+ //#region ../../internals/utils/src/fs.ts
263
+ /**
264
+ * Strips the file extension from a path or file name.
265
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
266
+ *
267
+ * @example
268
+ * trimExtName('petStore.ts') // 'petStore'
269
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
270
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
271
+ * trimExtName('noExtension') // 'noExtension'
272
+ */
273
+ function trimExtName(text) {
274
+ const dotIndex = text.lastIndexOf(".");
275
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
276
+ return text;
277
+ }
278
+ //#endregion
279
+ //#region src/utils/extractStringsFromNodes.ts
280
+ /**
281
+ * Extracts all string content from a `CodeNode` tree recursively.
282
+ *
283
+ * Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
284
+ * and nested node content. Used to build the full source string for import filtering.
285
+ */
286
+ function extractStringsFromNodes(nodes) {
287
+ if (!nodes?.length) return "";
288
+ return nodes.map((node) => {
289
+ if (typeof node === "string") return node;
290
+ if (node.kind === "Text") return node.value;
291
+ if (node.kind === "Break") return "";
292
+ if (node.kind === "Jsx") return node.value;
293
+ const parts = [];
294
+ if ("params" in node && node.params) parts.push(node.params);
295
+ if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
296
+ if ("returnType" in node && node.returnType) parts.push(node.returnType);
297
+ if ("type" in node && typeof node.type === "string") parts.push(node.type);
298
+ const nested = extractStringsFromNodes(node.nodes);
299
+ if (nested) parts.push(nested);
300
+ return parts.join("\n");
301
+ }).filter(Boolean).join("\n");
302
+ }
303
+ //#endregion
304
+ //#region src/utils/fileMerge.ts
305
+ function sourceKey(source) {
306
+ return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
307
+ }
308
+ function pathTypeKey(path, isTypeOnly) {
309
+ return `${path}:${isTypeOnly ?? false}`;
310
+ }
311
+ function exportKey(path, name, isTypeOnly, asAlias) {
312
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
313
+ }
314
+ function importKey(path, name, isTypeOnly) {
315
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
316
+ }
317
+ /**
318
+ * Computes a multi-level sort key for exports and imports:
319
+ * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
320
+ */
321
+ function sortKey(node) {
322
+ const isArray = Array.isArray(node.name) ? "1" : "0";
323
+ const typeOnly = node.isTypeOnly ? "0" : "1";
324
+ const hasName = node.name != null ? "1" : "0";
325
+ const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
326
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
327
+ }
328
+ /**
329
+ * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
330
+ * key. Unnamed sources fall back to their extracted node strings as the name part of the key. Returns
331
+ * the deduplicated array in original order.
332
+ */
333
+ function combineSources(sources) {
334
+ const seen = /* @__PURE__ */ new Map();
335
+ for (const source of sources) {
336
+ const key = sourceKey(source);
337
+ if (!seen.has(key)) seen.set(key, source);
338
+ }
339
+ return [...seen.values()];
340
+ }
341
+ /**
342
+ * Merges `incoming` names into `existing`, preserving order and dropping duplicates.
343
+ *
344
+ * Shared by `combineExports` and `combineImports` for the same-path name-merge case.
345
+ */
346
+ function mergeNameArrays(existing, incoming) {
347
+ const merged = new Set(existing);
348
+ for (const name of incoming) merged.add(name);
349
+ return [...merged];
350
+ }
351
+ /**
352
+ * Deduplicates and merges `ExportNode` objects by path and type.
353
+ *
354
+ * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
355
+ * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
356
+ */
357
+ function combineExports(exports) {
358
+ const result = [];
359
+ const namedByPath = /* @__PURE__ */ new Map();
360
+ const seen = /* @__PURE__ */ new Set();
361
+ const keyed = exports.map((node) => ({
362
+ node,
363
+ key: sortKey(node)
364
+ }));
365
+ keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
366
+ for (const { node: curr } of keyed) {
367
+ const { name, path, isTypeOnly, asAlias } = curr;
368
+ if (Array.isArray(name)) {
369
+ if (!name.length) continue;
370
+ const key = pathTypeKey(path, isTypeOnly);
371
+ const existing = namedByPath.get(key);
372
+ if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
373
+ else {
374
+ const newItem = {
375
+ ...curr,
376
+ name: [...new Set(name)]
377
+ };
378
+ result.push(newItem);
379
+ namedByPath.set(key, newItem);
380
+ }
381
+ } else {
382
+ const key = exportKey(path, name, isTypeOnly, asAlias);
383
+ if (!seen.has(key)) {
384
+ result.push(curr);
385
+ seen.add(key);
386
+ }
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+ /**
392
+ * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
393
+ *
394
+ * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
395
+ * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
396
+ */
397
+ function combineImports(imports, exports, source) {
398
+ const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
399
+ const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
400
+ const importNameMemo = /* @__PURE__ */ new Map();
401
+ const canonicalizeName = (n) => {
402
+ if (typeof n === "string") return n;
403
+ const key = `${n.propertyName}:${n.name ?? ""}`;
404
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
405
+ return importNameMemo.get(key);
406
+ };
407
+ const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
408
+ for (const node of imports) {
409
+ if (!Array.isArray(node.name)) continue;
410
+ if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
411
+ }
412
+ const result = [];
413
+ const namedByPath = /* @__PURE__ */ new Map();
414
+ const seen = /* @__PURE__ */ new Set();
415
+ const keyed = imports.map((node) => ({
416
+ node,
417
+ key: sortKey(node)
418
+ }));
419
+ keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
420
+ for (const { node: curr } of keyed) {
421
+ if (curr.path === curr.root) continue;
422
+ const { path, isTypeOnly } = curr;
423
+ let { name } = curr;
424
+ if (Array.isArray(name)) {
425
+ name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
426
+ if (!name.length) continue;
427
+ const key = pathTypeKey(path, isTypeOnly);
428
+ const existing = namedByPath.get(key);
429
+ if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
430
+ else {
431
+ const newItem = {
432
+ ...curr,
433
+ name
434
+ };
435
+ result.push(newItem);
436
+ namedByPath.set(key, newItem);
437
+ }
438
+ } else {
439
+ if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
440
+ const key = importKey(path, name, isTypeOnly);
441
+ if (!seen.has(key)) {
442
+ result.push(curr);
443
+ seen.add(key);
444
+ }
445
+ }
446
+ }
447
+ return result;
448
+ }
449
+ //#endregion
324
450
  //#region src/nodes/file.ts
325
451
  /**
326
452
  * Definition for the {@link ImportNode}.
327
453
  */
328
454
  const importDef = defineNode({ kind: "Import" });
329
455
  /**
456
+ * Definition for the {@link ExportNode}.
457
+ */
458
+ const exportDef = defineNode({ kind: "Export" });
459
+ /**
460
+ * Definition for the {@link SourceNode}.
461
+ */
462
+ const sourceDef = defineNode({ kind: "Source" });
463
+ /**
464
+ * Definition for the {@link FileNode}. The fully resolved builder lives in
465
+ * `createFile`, so this definition only supplies the guard.
466
+ */
467
+ const fileDef = defineNode({ kind: "File" });
468
+ /**
330
469
  * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
331
470
  *
332
471
  * @example Named import
@@ -337,10 +476,6 @@ const importDef = defineNode({ kind: "Import" });
337
476
  */
338
477
  const createImport = importDef.create;
339
478
  /**
340
- * Definition for the {@link ExportNode}.
341
- */
342
- const exportDef = defineNode({ kind: "Export" });
343
- /**
344
479
  * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
345
480
  *
346
481
  * @example Named export
@@ -351,10 +486,6 @@ const exportDef = defineNode({ kind: "Export" });
351
486
  */
352
487
  const createExport = exportDef.create;
353
488
  /**
354
- * Definition for the {@link SourceNode}.
355
- */
356
- const sourceDef = defineNode({ kind: "Source" });
357
- /**
358
489
  * Creates a `SourceNode` representing a fragment of source code within a file.
359
490
  *
360
491
  * @example
@@ -364,10 +495,64 @@ const sourceDef = defineNode({ kind: "Source" });
364
495
  */
365
496
  const createSource = sourceDef.create;
366
497
  /**
367
- * Definition for the {@link FileNode}. The fully resolved builder lives in
368
- * `createFile`, so this definition only supplies the guard.
498
+ * Creates a fully resolved `FileNode` from a file input descriptor.
499
+ *
500
+ * Computes:
501
+ * - `id` SHA256 hash of the file path
502
+ * - `name` `baseName` without extension
503
+ * - `extname` extension extracted from `baseName`
504
+ *
505
+ * Deduplicates:
506
+ * - `sources` via `combineSources`
507
+ * - `exports` via `combineExports`
508
+ * - `imports` via `combineImports` (also filters unused imports)
509
+ *
510
+ * @throws {Error} when `baseName` has no extension.
511
+ *
512
+ * @example
513
+ * ```ts
514
+ * const file = createFile({
515
+ * baseName: 'petStore.ts',
516
+ * path: 'src/models/petStore.ts',
517
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
518
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
519
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
520
+ * })
521
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
522
+ * // file.name = 'petStore'
523
+ * // file.extname = '.ts'
524
+ * ```
369
525
  */
370
- const fileDef = defineNode({ kind: "File" });
526
+ function createFile(input) {
527
+ const extname = node_path.default.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
528
+ if (!extname) throw new Error(`No extname found for ${input.baseName}`);
529
+ const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
530
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
531
+ const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
532
+ const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name) => Boolean(name)));
533
+ const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
534
+ const resolvedImports = combinedImports.filter((imp) => imp.path !== input.path).flatMap((imp) => {
535
+ if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
536
+ const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
537
+ if (!kept.length) return [];
538
+ return [kept.length === imp.name.length ? imp : {
539
+ ...imp,
540
+ name: kept
541
+ }];
542
+ });
543
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
544
+ return {
545
+ kind: "File",
546
+ ...input,
547
+ id: (0, node_crypto.hash)("sha256", input.path, "hex"),
548
+ name: trimExtName(input.baseName),
549
+ extname,
550
+ imports: resolvedImports,
551
+ exports: resolvedExports,
552
+ sources: resolvedSources,
553
+ meta: input.meta ?? {}
554
+ };
555
+ }
371
556
  //#endregion
372
557
  //#region src/nodes/function.ts
373
558
  /**
@@ -375,44 +560,14 @@ const fileDef = defineNode({ kind: "File" });
375
560
  */
376
561
  const typeLiteralDef = defineNode({ kind: "TypeLiteral" });
377
562
  /**
378
- * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
379
- *
380
- * @example
381
- * ```ts
382
- * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
383
- * // { petId: string }
384
- * ```
385
- */
386
- const createTypeLiteral = typeLiteralDef.create;
387
- /**
388
563
  * Definition for the {@link IndexedAccessTypeNode}.
389
564
  */
390
565
  const indexedAccessTypeDef = defineNode({ kind: "IndexedAccessType" });
391
566
  /**
392
- * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
393
- *
394
- * @example
395
- * ```ts
396
- * createIndexedAccessType({ objectType: 'DeletePetPathParams', indexType: 'petId' })
397
- * // DeletePetPathParams['petId']
398
- * ```
399
- */
400
- const createIndexedAccessType = indexedAccessTypeDef.create;
401
- /**
402
567
  * Definition for the {@link ObjectBindingPatternNode}.
403
568
  */
404
569
  const objectBindingPatternDef = defineNode({ kind: "ObjectBindingPattern" });
405
570
  /**
406
- * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
407
- *
408
- * @example
409
- * ```ts
410
- * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
411
- * // { id, name }
412
- * ```
413
- */
414
- const createObjectBindingPattern = objectBindingPatternDef.create;
415
- /**
416
571
  * Definition for the {@link FunctionParameterNode}. `optional` defaults to `false`.
417
572
  * Passing `properties` builds a destructured group: an {@link ObjectBindingPatternNode} name
418
573
  * paired with a {@link TypeLiteralNode} type.
@@ -437,6 +592,43 @@ const functionParameterDef = defineNode({
437
592
  }
438
593
  });
439
594
  /**
595
+ * Definition for the {@link FunctionParametersNode}.
596
+ */
597
+ const functionParametersDef = defineNode({
598
+ kind: "FunctionParameters",
599
+ defaults: { params: [] }
600
+ });
601
+ /**
602
+ * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
603
+ *
604
+ * @example
605
+ * ```ts
606
+ * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
607
+ * // { petId: string }
608
+ * ```
609
+ */
610
+ const createTypeLiteral = typeLiteralDef.create;
611
+ /**
612
+ * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
613
+ *
614
+ * @example
615
+ * ```ts
616
+ * createIndexedAccessType({ target: 'DeletePetPathParams', key: 'petId' })
617
+ * // DeletePetPathParams['petId']
618
+ * ```
619
+ */
620
+ const createIndexedAccessType = indexedAccessTypeDef.create;
621
+ /**
622
+ * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
627
+ * // { id, name }
628
+ * ```
629
+ */
630
+ const createObjectBindingPattern = objectBindingPatternDef.create;
631
+ /**
440
632
  * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
441
633
  *
442
634
  * @example Optional param
@@ -453,13 +645,6 @@ const functionParameterDef = defineNode({
453
645
  */
454
646
  const createFunctionParameter = functionParameterDef.create;
455
647
  /**
456
- * Definition for the {@link FunctionParametersNode}.
457
- */
458
- const functionParametersDef = defineNode({
459
- kind: "FunctionParameters",
460
- defaults: { params: [] }
461
- });
462
- /**
463
648
  * Creates a `FunctionParametersNode` from an ordered list of parameters.
464
649
  *
465
650
  * @example
@@ -491,8 +676,8 @@ const inputDef = defineNode({
491
676
  });
492
677
  /**
493
678
  * Creates an `InputNode`. Pass `stream: true` for the streaming variant whose `schemas` and
494
- * `operations` are `AsyncIterable` sources and whose `meta` is optional. Otherwise it builds the
495
- * eager variant with array `schemas`/`operations` and the defaulted `meta`.
679
+ * `operations` are `AsyncIterable` sources. Otherwise it builds the eager variant with array
680
+ * `schemas`/`operations`. Both variants get the defaulted `meta`.
496
681
  *
497
682
  * @example Eager
498
683
  * ```ts
@@ -509,6 +694,10 @@ function createInput(options = {}) {
509
694
  const { stream, ...overrides } = options;
510
695
  if (stream) return {
511
696
  kind: "Input",
697
+ meta: {
698
+ circularNames: [],
699
+ enumNames: []
700
+ },
512
701
  ...overrides
513
702
  };
514
703
  return inputDef.create(overrides);
@@ -516,18 +705,15 @@ function createInput(options = {}) {
516
705
  //#endregion
517
706
  //#region src/nodes/requestBody.ts
518
707
  /**
519
- * Definition for the {@link RequestBodyNode}, normalizing each content entry into a `ContentNode`.
708
+ * Definition for the {@link RequestBodyNode}. Content entries are built upfront with
709
+ * {@link createContent}, mirroring how `parameters` and `responses` take prebuilt nodes.
520
710
  */
521
711
  const requestBodyDef = defineNode({
522
712
  kind: "RequestBody",
523
- build: (props) => ({
524
- ...props,
525
- content: props.content?.map(createContent)
526
- }),
527
713
  children: ["content"]
528
714
  });
529
715
  /**
530
- * Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
716
+ * Creates a `RequestBodyNode`.
531
717
  */
532
718
  const createRequestBody = requestBodyDef.create;
533
719
  //#endregion
@@ -584,10 +770,24 @@ function createOutput(overrides = {}) {
584
770
  return outputDef.create(overrides);
585
771
  }
586
772
  //#endregion
773
+ //#region src/optionality.ts
774
+ /**
775
+ * Generic JSON Schema optionality: a non-required field is optional, and a
776
+ * non-required nullable field is nullish.
777
+ */
778
+ function optionality(schema, required) {
779
+ const nullable = schema.nullable ?? false;
780
+ return {
781
+ ...schema,
782
+ optional: !required && !nullable ? true : void 0,
783
+ nullish: !required && nullable ? true : void 0
784
+ };
785
+ }
786
+ //#endregion
587
787
  //#region src/nodes/parameter.ts
588
788
  /**
589
- * Definition for the {@link ParameterNode}. `required` defaults to `false` and the
590
- * schema's `optional`/`nullish` flags are kept in sync with it.
789
+ * Definition for the {@link ParameterNode}. `required` defaults to `false`, and the schema's
790
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
591
791
  */
592
792
  const parameterDef = defineNode({
593
793
  kind: "Parameter",
@@ -596,12 +796,11 @@ const parameterDef = defineNode({
596
796
  return {
597
797
  ...props,
598
798
  required,
599
- schema: syncOptionality(props.schema, required)
799
+ schema: optionality(props.schema, required)
600
800
  };
601
801
  },
602
802
  children: ["schema"],
603
- visitorKey: "parameter",
604
- rebuild: true
803
+ visitorKey: "parameter"
605
804
  });
606
805
  /**
607
806
  * Creates a `ParameterNode`.
@@ -620,8 +819,8 @@ const createParameter = parameterDef.create;
620
819
  //#endregion
621
820
  //#region src/nodes/property.ts
622
821
  /**
623
- * Definition for the {@link PropertyNode}. `required` defaults to `false` and the
624
- * schema's `optional`/`nullish` flags are kept in sync with it.
822
+ * Definition for the {@link PropertyNode}. `required` defaults to `false`, and the schema's
823
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
625
824
  */
626
825
  const propertyDef = defineNode({
627
826
  kind: "Property",
@@ -630,12 +829,11 @@ const propertyDef = defineNode({
630
829
  return {
631
830
  ...props,
632
831
  required,
633
- schema: syncOptionality(props.schema, required)
832
+ schema: optionality(props.schema, required)
634
833
  };
635
834
  },
636
835
  children: ["schema"],
637
- visitorKey: "property",
638
- rebuild: true
836
+ visitorKey: "property"
639
837
  });
640
838
  /**
641
839
  * Creates a `PropertyNode`.
@@ -661,14 +859,14 @@ const responseDef = defineNode({
661
859
  kind: "Response",
662
860
  build: (props) => {
663
861
  const { schema, mediaType, keysToOmit, content, ...rest } = props;
664
- const entries = content ?? (schema ? [{
862
+ const entries = content ?? (schema ? [createContent({
665
863
  contentType: mediaType ?? "application/json",
666
864
  schema,
667
865
  keysToOmit: keysToOmit ?? null
668
- }] : void 0);
866
+ })] : void 0);
669
867
  return {
670
868
  ...rest,
671
- content: entries?.map(createContent)
869
+ content: entries
672
870
  };
673
871
  },
674
872
  children: ["content"],
@@ -681,12 +879,67 @@ const responseDef = defineNode({
681
879
  * ```ts
682
880
  * const response = createResponse({
683
881
  * statusCode: '200',
684
- * content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
882
+ * content: [createContent({ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) })],
685
883
  * })
686
884
  * ```
687
885
  */
688
886
  const createResponse = responseDef.create;
689
887
  //#endregion
888
+ //#region src/nodes/schema.ts
889
+ /**
890
+ * Maps schema `type` to its underlying `primitive`.
891
+ * Primitive types map to themselves and special string formats map to `'string'`.
892
+ * Any type not listed here (such as `ref`, `enum`, `union`, `intersection`, `tuple`, `ipv4`, `ipv6`, `blob`) has no `primitive`.
893
+ */
894
+ const TYPE_TO_PRIMITIVE = {
895
+ string: "string",
896
+ number: "number",
897
+ integer: "integer",
898
+ bigint: "bigint",
899
+ boolean: "boolean",
900
+ null: "null",
901
+ any: "any",
902
+ unknown: "unknown",
903
+ void: "void",
904
+ never: "never",
905
+ object: "object",
906
+ array: "array",
907
+ date: "date",
908
+ uuid: "string",
909
+ email: "string",
910
+ url: "string",
911
+ datetime: "string",
912
+ time: "string"
913
+ };
914
+ /**
915
+ * Definition for the {@link SchemaNode}. Object schemas default `properties` to an
916
+ * empty array, and `primitive` is inferred from `type` when not explicitly provided.
917
+ */
918
+ const schemaDef = defineNode({
919
+ kind: "Schema",
920
+ build: (props) => {
921
+ if (props.type === "object") return {
922
+ properties: [],
923
+ primitive: "object",
924
+ ...props
925
+ };
926
+ return {
927
+ primitive: TYPE_TO_PRIMITIVE[props.type],
928
+ ...props
929
+ };
930
+ },
931
+ children: [
932
+ "properties",
933
+ "items",
934
+ "members",
935
+ "additionalProperties"
936
+ ],
937
+ visitorKey: "schema"
938
+ });
939
+ function createSchema(props) {
940
+ return schemaDef.create(props);
941
+ }
942
+ //#endregion
690
943
  Object.defineProperty(exports, "__exportAll", {
691
944
  enumerable: true,
692
945
  get: function() {
@@ -699,12 +952,6 @@ Object.defineProperty(exports, "__name", {
699
952
  return __name;
700
953
  }
701
954
  });
702
- Object.defineProperty(exports, "__toESM", {
703
- enumerable: true,
704
- get: function() {
705
- return __toESM;
706
- }
707
- });
708
955
  Object.defineProperty(exports, "arrowFunctionDef", {
709
956
  enumerable: true,
710
957
  get: function() {
@@ -765,6 +1012,12 @@ Object.defineProperty(exports, "createExport", {
765
1012
  return createExport;
766
1013
  }
767
1014
  });
1015
+ Object.defineProperty(exports, "createFile", {
1016
+ enumerable: true,
1017
+ get: function() {
1018
+ return createFile;
1019
+ }
1020
+ });
768
1021
  Object.defineProperty(exports, "createFunction", {
769
1022
  enumerable: true,
770
1023
  get: function() {
@@ -891,6 +1144,12 @@ Object.defineProperty(exports, "exportDef", {
891
1144
  return exportDef;
892
1145
  }
893
1146
  });
1147
+ Object.defineProperty(exports, "extractStringsFromNodes", {
1148
+ enumerable: true,
1149
+ get: function() {
1150
+ return extractStringsFromNodes;
1151
+ }
1152
+ });
894
1153
  Object.defineProperty(exports, "fileDef", {
895
1154
  enumerable: true,
896
1155
  get: function() {
@@ -951,6 +1210,12 @@ Object.defineProperty(exports, "operationDef", {
951
1210
  return operationDef;
952
1211
  }
953
1212
  });
1213
+ Object.defineProperty(exports, "optionality", {
1214
+ enumerable: true,
1215
+ get: function() {
1216
+ return optionality;
1217
+ }
1218
+ });
954
1219
  Object.defineProperty(exports, "outputDef", {
955
1220
  enumerable: true,
956
1221
  get: function() {
@@ -999,12 +1264,6 @@ Object.defineProperty(exports, "sourceDef", {
999
1264
  return sourceDef;
1000
1265
  }
1001
1266
  });
1002
- Object.defineProperty(exports, "syncOptionality", {
1003
- enumerable: true,
1004
- get: function() {
1005
- return syncOptionality;
1006
- }
1007
- });
1008
1267
  Object.defineProperty(exports, "textDef", {
1009
1268
  enumerable: true,
1010
1269
  get: function() {
@@ -1023,5 +1282,11 @@ Object.defineProperty(exports, "typeLiteralDef", {
1023
1282
  return typeLiteralDef;
1024
1283
  }
1025
1284
  });
1285
+ Object.defineProperty(exports, "visitorKeys", {
1286
+ enumerable: true,
1287
+ get: function() {
1288
+ return visitorKeys;
1289
+ }
1290
+ });
1026
1291
 
1027
- //# sourceMappingURL=response-hnSw2NKE.cjs.map
1292
+ //# sourceMappingURL=schema-BkvrrOAr.cjs.map