@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
@@ -1,56 +1,49 @@
1
1
  import "./chunk-CNktS9qV.js";
2
- //#region src/node.ts
2
+ import { hash } from "node:crypto";
3
+ import path from "node:path";
4
+ //#region src/defineNode.ts
5
+ /**
6
+ * Visitor callback names, one per traversable node kind, in traversal order.
7
+ * Kept in sync with the keys of `Visitor` in `visitor.ts`.
8
+ */
9
+ const visitorKeys = [
10
+ "input",
11
+ "output",
12
+ "operation",
13
+ "schema",
14
+ "property",
15
+ "parameter",
16
+ "response"
17
+ ];
3
18
  /**
4
19
  * Builds a type guard that matches nodes of the given `kind`.
5
20
  */
6
21
  function isKind(kind) {
7
- return (node) => node.kind === kind;
8
- }
9
- /**
10
- * Updates a schema's `optional` and `nullish` flags from a parent's `required`
11
- * value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
12
- * object properties combine "required" and "nullable" into a single AST.
13
- *
14
- * - Non-required + non-nullable → `optional: true`.
15
- * - Non-required + nullable → `nullish: true`.
16
- * - Required → both flags cleared.
17
- */
18
- function syncOptionality(schema, required) {
19
- const nullable = schema.nullable ?? false;
20
- return {
21
- ...schema,
22
- optional: !required && !nullable ? true : void 0,
23
- nullish: !required && nullable ? true : void 0
24
- };
22
+ return (node) => node?.kind === kind;
25
23
  }
26
24
  /**
27
25
  * Defines a node once and derives its `create` builder, `is` guard, and traversal
28
26
  * metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
29
27
  * `kind`, so node construction lives in one place without scattered `as` casts.
30
28
  *
31
- * Set `rebuild: true` when the `build` hook derives fields from children. After a
32
- * transform rewrites those children, the registry reruns `create` so the derived
33
- * fields stay correct.
34
- *
35
29
  * @example Simple node
36
30
  * ```ts
37
31
  * const importDef = defineNode<ImportNode>({ kind: 'Import' })
38
32
  * const createImport = importDef.create
39
33
  * ```
40
34
  *
41
- * @example Node with a build hook that is rerun on transform
35
+ * @example Node with a build hook
42
36
  * ```ts
43
37
  * const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
44
38
  * kind: 'Property',
45
39
  * build: (props) => ({ ...props, required: props.required ?? false }),
46
40
  * children: ['schema'],
47
41
  * visitorKey: 'property',
48
- * rebuild: true,
49
42
  * })
50
43
  * ```
51
44
  */
52
45
  function defineNode(config) {
53
- const { kind, defaults, build, children, visitorKey, rebuild } = config;
46
+ const { kind, defaults, build, children, visitorKey } = config;
54
47
  function create(input) {
55
48
  const base = build ? build(input) : input;
56
49
  return {
@@ -64,110 +57,48 @@ function defineNode(config) {
64
57
  create,
65
58
  is: isKind(kind),
66
59
  children,
67
- visitorKey,
68
- rebuild
60
+ visitorKey
69
61
  };
70
62
  }
71
63
  //#endregion
72
- //#region src/nodes/schema.ts
64
+ //#region src/nodes/code.ts
73
65
  /**
74
- * Maps schema `type` to its underlying `primitive`.
75
- * Primitive types map to themselves. Special string formats map to `'string'`.
76
- * Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
66
+ * Definition for the {@link ConstNode}.
77
67
  */
78
- const TYPE_TO_PRIMITIVE = {
79
- string: "string",
80
- number: "number",
81
- integer: "integer",
82
- bigint: "bigint",
83
- boolean: "boolean",
84
- null: "null",
85
- any: "any",
86
- unknown: "unknown",
87
- void: "void",
88
- never: "never",
89
- object: "object",
90
- array: "array",
91
- date: "date",
92
- uuid: "string",
93
- email: "string",
94
- url: "string",
95
- datetime: "string",
96
- time: "string"
97
- };
68
+ const constDef = defineNode({ kind: "Const" });
98
69
  /**
99
- * Definition for the {@link SchemaNode}. Object schemas default `properties` to an
100
- * empty array, and `primitive` is inferred from `type` when not explicitly provided.
70
+ * Definition for the {@link TypeNode}.
101
71
  */
102
- const schemaDef = defineNode({
103
- kind: "Schema",
104
- build: (props) => {
105
- if (props.type === "object") return {
106
- properties: [],
107
- primitive: "object",
108
- ...props
109
- };
110
- return {
111
- primitive: TYPE_TO_PRIMITIVE[props.type],
112
- ...props
113
- };
114
- },
115
- children: [
116
- "properties",
117
- "items",
118
- "members",
119
- "additionalProperties"
120
- ],
121
- visitorKey: "schema"
122
- });
123
- function createSchema(props) {
124
- return schemaDef.create(props);
125
- }
126
- //#endregion
127
- //#region ../../internals/utils/src/casing.ts
72
+ const typeDef = defineNode({ kind: "Type" });
128
73
  /**
129
- * Shared implementation for camelCase and PascalCase conversion.
130
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
131
- * and capitalizes each word according to `pascal`.
132
- *
133
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
74
+ * Definition for the {@link FunctionNode}.
134
75
  */
135
- function toCamelOrPascal(text, pascal) {
136
- 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) => {
137
- if (word.length > 1 && word === word.toUpperCase()) return word;
138
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
139
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
140
- }
76
+ const functionDef = defineNode({ kind: "Function" });
141
77
  /**
142
- * Converts `text` to camelCase.
143
- *
144
- * @example Word boundaries
145
- * `camelCase('hello-world') // 'helloWorld'`
146
- *
147
- * @example With a prefix
148
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
78
+ * Definition for the {@link ArrowFunctionNode}.
149
79
  */
150
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
151
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
152
- }
80
+ const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
153
81
  /**
154
- * Converts `text` to PascalCase.
155
- *
156
- * @example Word boundaries
157
- * `pascalCase('hello-world') // 'HelloWorld'`
158
- *
159
- * @example With a suffix
160
- * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
82
+ * Definition for the {@link TextNode}.
161
83
  */
162
- function pascalCase(text, { prefix = "", suffix = "" } = {}) {
163
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
164
- }
165
- //#endregion
166
- //#region src/nodes/code.ts
84
+ const textDef = defineNode({
85
+ kind: "Text",
86
+ build: (value) => ({ value })
87
+ });
167
88
  /**
168
- * Definition for the {@link ConstNode}.
89
+ * Definition for the {@link BreakNode}.
169
90
  */
170
- const constDef = defineNode({ kind: "Const" });
91
+ const breakDef = defineNode({
92
+ kind: "Break",
93
+ build: () => ({})
94
+ });
95
+ /**
96
+ * Definition for the {@link JsxNode}.
97
+ */
98
+ const jsxDef = defineNode({
99
+ kind: "Jsx",
100
+ build: (value) => ({ value })
101
+ });
171
102
  /**
172
103
  * Creates a `ConstNode` representing a TypeScript `const` declaration.
173
104
  *
@@ -179,10 +110,6 @@ const constDef = defineNode({ kind: "Const" });
179
110
  */
180
111
  const createConst = constDef.create;
181
112
  /**
182
- * Definition for the {@link TypeNode}.
183
- */
184
- const typeDef = defineNode({ kind: "Type" });
185
- /**
186
113
  * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
187
114
  *
188
115
  * @example
@@ -193,10 +120,6 @@ const typeDef = defineNode({ kind: "Type" });
193
120
  */
194
121
  const createType = typeDef.create;
195
122
  /**
196
- * Definition for the {@link FunctionNode}.
197
- */
198
- const functionDef = defineNode({ kind: "Function" });
199
- /**
200
123
  * Creates a `FunctionNode` representing a TypeScript `function` declaration.
201
124
  *
202
125
  * @example
@@ -207,10 +130,6 @@ const functionDef = defineNode({ kind: "Function" });
207
130
  */
208
131
  const createFunction = functionDef.create;
209
132
  /**
210
- * Definition for the {@link ArrowFunctionNode}.
211
- */
212
- const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
213
- /**
214
133
  * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
215
134
  *
216
135
  * @example
@@ -221,13 +140,6 @@ const arrowFunctionDef = defineNode({ kind: "ArrowFunction" });
221
140
  */
222
141
  const createArrowFunction = arrowFunctionDef.create;
223
142
  /**
224
- * Definition for the {@link TextNode}.
225
- */
226
- const textDef = defineNode({
227
- kind: "Text",
228
- build: (value) => ({ value })
229
- });
230
- /**
231
143
  * Creates a {@link TextNode} representing a raw string fragment in the source output.
232
144
  *
233
145
  * @example
@@ -238,13 +150,6 @@ const textDef = defineNode({
238
150
  */
239
151
  const createText = textDef.create;
240
152
  /**
241
- * Definition for the {@link BreakNode}.
242
- */
243
- const breakDef = defineNode({
244
- kind: "Break",
245
- build: () => ({})
246
- });
247
- /**
248
153
  * Creates a {@link BreakNode} representing a line break in the source output.
249
154
  *
250
155
  * @example
@@ -257,13 +162,6 @@ function createBreak() {
257
162
  return breakDef.create();
258
163
  }
259
164
  /**
260
- * Definition for the {@link JsxNode}.
261
- */
262
- const jsxDef = defineNode({
263
- kind: "Jsx",
264
- build: (value) => ({ value })
265
- });
266
- /**
267
165
  * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
268
166
  *
269
167
  * @example
@@ -287,12 +185,252 @@ const contentDef = defineNode({
287
185
  */
288
186
  const createContent = contentDef.create;
289
187
  //#endregion
188
+ //#region ../../internals/utils/src/casing.ts
189
+ /**
190
+ * Shared implementation for camelCase and PascalCase conversion.
191
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
192
+ * and capitalizes each word according to `pascal`.
193
+ *
194
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
195
+ */
196
+ function toCamelOrPascal(text, pascal) {
197
+ 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) => {
198
+ if (word.length > 1 && word === word.toUpperCase()) return word;
199
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
200
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
201
+ }
202
+ /**
203
+ * Converts `text` to camelCase.
204
+ *
205
+ * @example Word boundaries
206
+ * `camelCase('hello-world') // 'helloWorld'`
207
+ *
208
+ * @example With a prefix
209
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
210
+ */
211
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
212
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
213
+ }
214
+ /**
215
+ * Converts `text` to PascalCase.
216
+ *
217
+ * @example Word boundaries
218
+ * `pascalCase('hello-world') // 'HelloWorld'`
219
+ *
220
+ * @example With a suffix
221
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
222
+ */
223
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
224
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
225
+ }
226
+ //#endregion
227
+ //#region ../../internals/utils/src/fs.ts
228
+ /**
229
+ * Strips the file extension from a path or file name.
230
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
231
+ *
232
+ * @example
233
+ * trimExtName('petStore.ts') // 'petStore'
234
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
235
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
236
+ * trimExtName('noExtension') // 'noExtension'
237
+ */
238
+ function trimExtName(text) {
239
+ const dotIndex = text.lastIndexOf(".");
240
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
241
+ return text;
242
+ }
243
+ //#endregion
244
+ //#region src/utils/extractStringsFromNodes.ts
245
+ /**
246
+ * Extracts all string content from a `CodeNode` tree recursively.
247
+ *
248
+ * Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
249
+ * and nested node content. Used to build the full source string for import filtering.
250
+ */
251
+ function extractStringsFromNodes(nodes) {
252
+ if (!nodes?.length) return "";
253
+ return nodes.map((node) => {
254
+ if (typeof node === "string") return node;
255
+ if (node.kind === "Text") return node.value;
256
+ if (node.kind === "Break") return "";
257
+ if (node.kind === "Jsx") return node.value;
258
+ const parts = [];
259
+ if ("params" in node && node.params) parts.push(node.params);
260
+ if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
261
+ if ("returnType" in node && node.returnType) parts.push(node.returnType);
262
+ if ("type" in node && typeof node.type === "string") parts.push(node.type);
263
+ const nested = extractStringsFromNodes(node.nodes);
264
+ if (nested) parts.push(nested);
265
+ return parts.join("\n");
266
+ }).filter(Boolean).join("\n");
267
+ }
268
+ //#endregion
269
+ //#region src/utils/fileMerge.ts
270
+ function sourceKey(source) {
271
+ return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
272
+ }
273
+ function pathTypeKey(path, isTypeOnly) {
274
+ return `${path}:${isTypeOnly ?? false}`;
275
+ }
276
+ function exportKey(path, name, isTypeOnly, asAlias) {
277
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
278
+ }
279
+ function importKey(path, name, isTypeOnly) {
280
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
281
+ }
282
+ /**
283
+ * Computes a multi-level sort key for exports and imports:
284
+ * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
285
+ */
286
+ function sortKey(node) {
287
+ const isArray = Array.isArray(node.name) ? "1" : "0";
288
+ const typeOnly = node.isTypeOnly ? "0" : "1";
289
+ const hasName = node.name != null ? "1" : "0";
290
+ const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
291
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
292
+ }
293
+ /**
294
+ * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
295
+ * key. Unnamed sources fall back to their extracted node strings as the name part of the key. Returns
296
+ * the deduplicated array in original order.
297
+ */
298
+ function combineSources(sources) {
299
+ const seen = /* @__PURE__ */ new Map();
300
+ for (const source of sources) {
301
+ const key = sourceKey(source);
302
+ if (!seen.has(key)) seen.set(key, source);
303
+ }
304
+ return [...seen.values()];
305
+ }
306
+ /**
307
+ * Merges `incoming` names into `existing`, preserving order and dropping duplicates.
308
+ *
309
+ * Shared by `combineExports` and `combineImports` for the same-path name-merge case.
310
+ */
311
+ function mergeNameArrays(existing, incoming) {
312
+ const merged = new Set(existing);
313
+ for (const name of incoming) merged.add(name);
314
+ return [...merged];
315
+ }
316
+ /**
317
+ * Deduplicates and merges `ExportNode` objects by path and type.
318
+ *
319
+ * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
320
+ * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
321
+ */
322
+ function combineExports(exports) {
323
+ const result = [];
324
+ const namedByPath = /* @__PURE__ */ new Map();
325
+ const seen = /* @__PURE__ */ new Set();
326
+ const keyed = exports.map((node) => ({
327
+ node,
328
+ key: sortKey(node)
329
+ }));
330
+ keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
331
+ for (const { node: curr } of keyed) {
332
+ const { name, path, isTypeOnly, asAlias } = curr;
333
+ if (Array.isArray(name)) {
334
+ if (!name.length) continue;
335
+ const key = pathTypeKey(path, isTypeOnly);
336
+ const existing = namedByPath.get(key);
337
+ if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
338
+ else {
339
+ const newItem = {
340
+ ...curr,
341
+ name: [...new Set(name)]
342
+ };
343
+ result.push(newItem);
344
+ namedByPath.set(key, newItem);
345
+ }
346
+ } else {
347
+ const key = exportKey(path, name, isTypeOnly, asAlias);
348
+ if (!seen.has(key)) {
349
+ result.push(curr);
350
+ seen.add(key);
351
+ }
352
+ }
353
+ }
354
+ return result;
355
+ }
356
+ /**
357
+ * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
358
+ *
359
+ * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
360
+ * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
361
+ */
362
+ function combineImports(imports, exports, source) {
363
+ const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
364
+ const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
365
+ const importNameMemo = /* @__PURE__ */ new Map();
366
+ const canonicalizeName = (n) => {
367
+ if (typeof n === "string") return n;
368
+ const key = `${n.propertyName}:${n.name ?? ""}`;
369
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
370
+ return importNameMemo.get(key);
371
+ };
372
+ const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
373
+ for (const node of imports) {
374
+ if (!Array.isArray(node.name)) continue;
375
+ if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
376
+ }
377
+ const result = [];
378
+ const namedByPath = /* @__PURE__ */ new Map();
379
+ const seen = /* @__PURE__ */ new Set();
380
+ const keyed = imports.map((node) => ({
381
+ node,
382
+ key: sortKey(node)
383
+ }));
384
+ keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
385
+ for (const { node: curr } of keyed) {
386
+ if (curr.path === curr.root) continue;
387
+ const { path, isTypeOnly } = curr;
388
+ let { name } = curr;
389
+ if (Array.isArray(name)) {
390
+ name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
391
+ if (!name.length) continue;
392
+ const key = pathTypeKey(path, isTypeOnly);
393
+ const existing = namedByPath.get(key);
394
+ if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
395
+ else {
396
+ const newItem = {
397
+ ...curr,
398
+ name
399
+ };
400
+ result.push(newItem);
401
+ namedByPath.set(key, newItem);
402
+ }
403
+ } else {
404
+ if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
405
+ const key = importKey(path, name, isTypeOnly);
406
+ if (!seen.has(key)) {
407
+ result.push(curr);
408
+ seen.add(key);
409
+ }
410
+ }
411
+ }
412
+ return result;
413
+ }
414
+ //#endregion
290
415
  //#region src/nodes/file.ts
291
416
  /**
292
417
  * Definition for the {@link ImportNode}.
293
418
  */
294
419
  const importDef = defineNode({ kind: "Import" });
295
420
  /**
421
+ * Definition for the {@link ExportNode}.
422
+ */
423
+ const exportDef = defineNode({ kind: "Export" });
424
+ /**
425
+ * Definition for the {@link SourceNode}.
426
+ */
427
+ const sourceDef = defineNode({ kind: "Source" });
428
+ /**
429
+ * Definition for the {@link FileNode}. The fully resolved builder lives in
430
+ * `createFile`, so this definition only supplies the guard.
431
+ */
432
+ const fileDef = defineNode({ kind: "File" });
433
+ /**
296
434
  * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
297
435
  *
298
436
  * @example Named import
@@ -303,10 +441,6 @@ const importDef = defineNode({ kind: "Import" });
303
441
  */
304
442
  const createImport = importDef.create;
305
443
  /**
306
- * Definition for the {@link ExportNode}.
307
- */
308
- const exportDef = defineNode({ kind: "Export" });
309
- /**
310
444
  * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
311
445
  *
312
446
  * @example Named export
@@ -317,10 +451,6 @@ const exportDef = defineNode({ kind: "Export" });
317
451
  */
318
452
  const createExport = exportDef.create;
319
453
  /**
320
- * Definition for the {@link SourceNode}.
321
- */
322
- const sourceDef = defineNode({ kind: "Source" });
323
- /**
324
454
  * Creates a `SourceNode` representing a fragment of source code within a file.
325
455
  *
326
456
  * @example
@@ -330,10 +460,64 @@ const sourceDef = defineNode({ kind: "Source" });
330
460
  */
331
461
  const createSource = sourceDef.create;
332
462
  /**
333
- * Definition for the {@link FileNode}. The fully resolved builder lives in
334
- * `createFile`, so this definition only supplies the guard.
463
+ * Creates a fully resolved `FileNode` from a file input descriptor.
464
+ *
465
+ * Computes:
466
+ * - `id` SHA256 hash of the file path
467
+ * - `name` `baseName` without extension
468
+ * - `extname` extension extracted from `baseName`
469
+ *
470
+ * Deduplicates:
471
+ * - `sources` via `combineSources`
472
+ * - `exports` via `combineExports`
473
+ * - `imports` via `combineImports` (also filters unused imports)
474
+ *
475
+ * @throws {Error} when `baseName` has no extension.
476
+ *
477
+ * @example
478
+ * ```ts
479
+ * const file = createFile({
480
+ * baseName: 'petStore.ts',
481
+ * path: 'src/models/petStore.ts',
482
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
483
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
484
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
485
+ * })
486
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
487
+ * // file.name = 'petStore'
488
+ * // file.extname = '.ts'
489
+ * ```
335
490
  */
336
- const fileDef = defineNode({ kind: "File" });
491
+ function createFile(input) {
492
+ const extname = path.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
493
+ if (!extname) throw new Error(`No extname found for ${input.baseName}`);
494
+ const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
495
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
496
+ const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
497
+ const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name) => Boolean(name)));
498
+ const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
499
+ const resolvedImports = combinedImports.filter((imp) => imp.path !== input.path).flatMap((imp) => {
500
+ if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
501
+ const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
502
+ if (!kept.length) return [];
503
+ return [kept.length === imp.name.length ? imp : {
504
+ ...imp,
505
+ name: kept
506
+ }];
507
+ });
508
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
509
+ return {
510
+ kind: "File",
511
+ ...input,
512
+ id: hash("sha256", input.path, "hex"),
513
+ name: trimExtName(input.baseName),
514
+ extname,
515
+ imports: resolvedImports,
516
+ exports: resolvedExports,
517
+ sources: resolvedSources,
518
+ meta: input.meta ?? {}
519
+ };
520
+ }
337
521
  //#endregion
338
522
  //#region src/nodes/function.ts
339
523
  /**
@@ -341,44 +525,14 @@ const fileDef = defineNode({ kind: "File" });
341
525
  */
342
526
  const typeLiteralDef = defineNode({ kind: "TypeLiteral" });
343
527
  /**
344
- * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
345
- *
346
- * @example
347
- * ```ts
348
- * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
349
- * // { petId: string }
350
- * ```
351
- */
352
- const createTypeLiteral = typeLiteralDef.create;
353
- /**
354
528
  * Definition for the {@link IndexedAccessTypeNode}.
355
529
  */
356
530
  const indexedAccessTypeDef = defineNode({ kind: "IndexedAccessType" });
357
531
  /**
358
- * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
359
- *
360
- * @example
361
- * ```ts
362
- * createIndexedAccessType({ objectType: 'DeletePetPathParams', indexType: 'petId' })
363
- * // DeletePetPathParams['petId']
364
- * ```
365
- */
366
- const createIndexedAccessType = indexedAccessTypeDef.create;
367
- /**
368
532
  * Definition for the {@link ObjectBindingPatternNode}.
369
533
  */
370
534
  const objectBindingPatternDef = defineNode({ kind: "ObjectBindingPattern" });
371
535
  /**
372
- * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
373
- *
374
- * @example
375
- * ```ts
376
- * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
377
- * // { id, name }
378
- * ```
379
- */
380
- const createObjectBindingPattern = objectBindingPatternDef.create;
381
- /**
382
536
  * Definition for the {@link FunctionParameterNode}. `optional` defaults to `false`.
383
537
  * Passing `properties` builds a destructured group: an {@link ObjectBindingPatternNode} name
384
538
  * paired with a {@link TypeLiteralNode} type.
@@ -403,6 +557,43 @@ const functionParameterDef = defineNode({
403
557
  }
404
558
  });
405
559
  /**
560
+ * Definition for the {@link FunctionParametersNode}.
561
+ */
562
+ const functionParametersDef = defineNode({
563
+ kind: "FunctionParameters",
564
+ defaults: { params: [] }
565
+ });
566
+ /**
567
+ * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
572
+ * // { petId: string }
573
+ * ```
574
+ */
575
+ const createTypeLiteral = typeLiteralDef.create;
576
+ /**
577
+ * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * createIndexedAccessType({ target: 'DeletePetPathParams', key: 'petId' })
582
+ * // DeletePetPathParams['petId']
583
+ * ```
584
+ */
585
+ const createIndexedAccessType = indexedAccessTypeDef.create;
586
+ /**
587
+ * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
588
+ *
589
+ * @example
590
+ * ```ts
591
+ * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
592
+ * // { id, name }
593
+ * ```
594
+ */
595
+ const createObjectBindingPattern = objectBindingPatternDef.create;
596
+ /**
406
597
  * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
407
598
  *
408
599
  * @example Optional param
@@ -419,13 +610,6 @@ const functionParameterDef = defineNode({
419
610
  */
420
611
  const createFunctionParameter = functionParameterDef.create;
421
612
  /**
422
- * Definition for the {@link FunctionParametersNode}.
423
- */
424
- const functionParametersDef = defineNode({
425
- kind: "FunctionParameters",
426
- defaults: { params: [] }
427
- });
428
- /**
429
613
  * Creates a `FunctionParametersNode` from an ordered list of parameters.
430
614
  *
431
615
  * @example
@@ -457,8 +641,8 @@ const inputDef = defineNode({
457
641
  });
458
642
  /**
459
643
  * Creates an `InputNode`. Pass `stream: true` for the streaming variant whose `schemas` and
460
- * `operations` are `AsyncIterable` sources and whose `meta` is optional. Otherwise it builds the
461
- * eager variant with array `schemas`/`operations` and the defaulted `meta`.
644
+ * `operations` are `AsyncIterable` sources. Otherwise it builds the eager variant with array
645
+ * `schemas`/`operations`. Both variants get the defaulted `meta`.
462
646
  *
463
647
  * @example Eager
464
648
  * ```ts
@@ -475,6 +659,10 @@ function createInput(options = {}) {
475
659
  const { stream, ...overrides } = options;
476
660
  if (stream) return {
477
661
  kind: "Input",
662
+ meta: {
663
+ circularNames: [],
664
+ enumNames: []
665
+ },
478
666
  ...overrides
479
667
  };
480
668
  return inputDef.create(overrides);
@@ -482,18 +670,15 @@ function createInput(options = {}) {
482
670
  //#endregion
483
671
  //#region src/nodes/requestBody.ts
484
672
  /**
485
- * Definition for the {@link RequestBodyNode}, normalizing each content entry into a `ContentNode`.
673
+ * Definition for the {@link RequestBodyNode}. Content entries are built upfront with
674
+ * {@link createContent}, mirroring how `parameters` and `responses` take prebuilt nodes.
486
675
  */
487
676
  const requestBodyDef = defineNode({
488
677
  kind: "RequestBody",
489
- build: (props) => ({
490
- ...props,
491
- content: props.content?.map(createContent)
492
- }),
493
678
  children: ["content"]
494
679
  });
495
680
  /**
496
- * Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
681
+ * Creates a `RequestBodyNode`.
497
682
  */
498
683
  const createRequestBody = requestBodyDef.create;
499
684
  //#endregion
@@ -550,10 +735,24 @@ function createOutput(overrides = {}) {
550
735
  return outputDef.create(overrides);
551
736
  }
552
737
  //#endregion
738
+ //#region src/optionality.ts
739
+ /**
740
+ * Generic JSON Schema optionality: a non-required field is optional, and a
741
+ * non-required nullable field is nullish.
742
+ */
743
+ function optionality(schema, required) {
744
+ const nullable = schema.nullable ?? false;
745
+ return {
746
+ ...schema,
747
+ optional: !required && !nullable ? true : void 0,
748
+ nullish: !required && nullable ? true : void 0
749
+ };
750
+ }
751
+ //#endregion
553
752
  //#region src/nodes/parameter.ts
554
753
  /**
555
- * Definition for the {@link ParameterNode}. `required` defaults to `false` and the
556
- * schema's `optional`/`nullish` flags are kept in sync with it.
754
+ * Definition for the {@link ParameterNode}. `required` defaults to `false`, and the schema's
755
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
557
756
  */
558
757
  const parameterDef = defineNode({
559
758
  kind: "Parameter",
@@ -562,12 +761,11 @@ const parameterDef = defineNode({
562
761
  return {
563
762
  ...props,
564
763
  required,
565
- schema: syncOptionality(props.schema, required)
764
+ schema: optionality(props.schema, required)
566
765
  };
567
766
  },
568
767
  children: ["schema"],
569
- visitorKey: "parameter",
570
- rebuild: true
768
+ visitorKey: "parameter"
571
769
  });
572
770
  /**
573
771
  * Creates a `ParameterNode`.
@@ -586,8 +784,8 @@ const createParameter = parameterDef.create;
586
784
  //#endregion
587
785
  //#region src/nodes/property.ts
588
786
  /**
589
- * Definition for the {@link PropertyNode}. `required` defaults to `false` and the
590
- * schema's `optional`/`nullish` flags are kept in sync with it.
787
+ * Definition for the {@link PropertyNode}. `required` defaults to `false`, and the schema's
788
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
591
789
  */
592
790
  const propertyDef = defineNode({
593
791
  kind: "Property",
@@ -596,12 +794,11 @@ const propertyDef = defineNode({
596
794
  return {
597
795
  ...props,
598
796
  required,
599
- schema: syncOptionality(props.schema, required)
797
+ schema: optionality(props.schema, required)
600
798
  };
601
799
  },
602
800
  children: ["schema"],
603
- visitorKey: "property",
604
- rebuild: true
801
+ visitorKey: "property"
605
802
  });
606
803
  /**
607
804
  * Creates a `PropertyNode`.
@@ -627,14 +824,14 @@ const responseDef = defineNode({
627
824
  kind: "Response",
628
825
  build: (props) => {
629
826
  const { schema, mediaType, keysToOmit, content, ...rest } = props;
630
- const entries = content ?? (schema ? [{
827
+ const entries = content ?? (schema ? [createContent({
631
828
  contentType: mediaType ?? "application/json",
632
829
  schema,
633
830
  keysToOmit: keysToOmit ?? null
634
- }] : void 0);
831
+ })] : void 0);
635
832
  return {
636
833
  ...rest,
637
- content: entries?.map(createContent)
834
+ content: entries
638
835
  };
639
836
  },
640
837
  children: ["content"],
@@ -647,12 +844,67 @@ const responseDef = defineNode({
647
844
  * ```ts
648
845
  * const response = createResponse({
649
846
  * statusCode: '200',
650
- * content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
847
+ * content: [createContent({ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) })],
651
848
  * })
652
849
  * ```
653
850
  */
654
851
  const createResponse = responseDef.create;
655
852
  //#endregion
656
- export { syncOptionality as $, importDef as A, createFunction as B, objectBindingPatternDef as C, createSource as D, createImport as E, breakDef as F, jsxDef as G, createText as H, constDef as I, camelCase as J, textDef as K, createArrowFunction as L, contentDef as M, createContent as N, exportDef as O, arrowFunctionDef as P, defineNode as Q, createBreak as R, indexedAccessTypeDef as S, createExport as T, createType as U, createJsx as V, functionDef as W, createSchema as X, pascalCase as Y, schemaDef as Z, createIndexedAccessType as _, createParameter as a, functionParameterDef as b, outputDef as c, createRequestBody as d, requestBodyDef as f, createFunctionParameters as g, createFunctionParameter as h, propertyDef as i, sourceDef as j, fileDef as k, createOperation as l, inputDef as m, responseDef as n, parameterDef as o, createInput as p, typeDef as q, createProperty as r, createOutput as s, createResponse as t, operationDef as u, createObjectBindingPattern as v, typeLiteralDef as w, functionParametersDef as x, createTypeLiteral as y, createConst as z };
853
+ //#region src/nodes/schema.ts
854
+ /**
855
+ * Maps schema `type` to its underlying `primitive`.
856
+ * Primitive types map to themselves and special string formats map to `'string'`.
857
+ * Any type not listed here (such as `ref`, `enum`, `union`, `intersection`, `tuple`, `ipv4`, `ipv6`, `blob`) has no `primitive`.
858
+ */
859
+ const TYPE_TO_PRIMITIVE = {
860
+ string: "string",
861
+ number: "number",
862
+ integer: "integer",
863
+ bigint: "bigint",
864
+ boolean: "boolean",
865
+ null: "null",
866
+ any: "any",
867
+ unknown: "unknown",
868
+ void: "void",
869
+ never: "never",
870
+ object: "object",
871
+ array: "array",
872
+ date: "date",
873
+ uuid: "string",
874
+ email: "string",
875
+ url: "string",
876
+ datetime: "string",
877
+ time: "string"
878
+ };
879
+ /**
880
+ * Definition for the {@link SchemaNode}. Object schemas default `properties` to an
881
+ * empty array, and `primitive` is inferred from `type` when not explicitly provided.
882
+ */
883
+ const schemaDef = defineNode({
884
+ kind: "Schema",
885
+ build: (props) => {
886
+ if (props.type === "object") return {
887
+ properties: [],
888
+ primitive: "object",
889
+ ...props
890
+ };
891
+ return {
892
+ primitive: TYPE_TO_PRIMITIVE[props.type],
893
+ ...props
894
+ };
895
+ },
896
+ children: [
897
+ "properties",
898
+ "items",
899
+ "members",
900
+ "additionalProperties"
901
+ ],
902
+ visitorKey: "schema"
903
+ });
904
+ function createSchema(props) {
905
+ return schemaDef.create(props);
906
+ }
907
+ //#endregion
908
+ export { textDef as $, createImport as A, createContent as B, functionParameterDef as C, typeLiteralDef as D, objectBindingPatternDef as E, sourceDef as F, createBreak as G, breakDef as H, extractStringsFromNodes as I, createJsx as J, createConst as K, camelCase as L, exportDef as M, fileDef as N, createExport as O, importDef as P, jsxDef as Q, pascalCase as R, createTypeLiteral as S, indexedAccessTypeDef as T, constDef as U, arrowFunctionDef as V, createArrowFunction as W, createType as X, createText as Y, functionDef as Z, inputDef as _, createProperty as a, createIndexedAccessType as b, parameterDef as c, outputDef as d, typeDef as et, createOperation as f, createInput as g, requestBodyDef as h, responseDef as i, createSource as j, createFile as k, optionality as l, createRequestBody as m, schemaDef as n, visitorKeys as nt, propertyDef as o, operationDef as p, createFunction as q, createResponse as r, createParameter as s, createSchema as t, defineNode as tt, createOutput as u, createFunctionParameter as v, functionParametersDef as w, createObjectBindingPattern as x, createFunctionParameters as y, contentDef as z };
657
909
 
658
- //# sourceMappingURL=response-KUdWiDWw.js.map
910
+ //# sourceMappingURL=schema-Cbnxmz4b.js.map