@kubb/ast 5.0.0-beta.2 → 5.0.0-beta.21

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.js CHANGED
@@ -265,6 +265,46 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
265
265
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
266
266
  }
267
267
  //#endregion
268
+ //#region ../../internals/utils/src/promise.ts
269
+ /**
270
+ * Wraps `factory` with a keyed cache backed by the provided store.
271
+ *
272
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
273
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
274
+ * nest two `memoize` calls — the outer keyed by the first argument, the
275
+ * inner (created once per outer miss) keyed by the second.
276
+ *
277
+ * Because the cache is owned by the caller, it can be shared, inspected, or
278
+ * cleared independently of the memoized function.
279
+ *
280
+ * @example Single WeakMap key
281
+ * ```ts
282
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
283
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
284
+ * ```
285
+ *
286
+ * @example Single Map key (primitive)
287
+ * ```ts
288
+ * const cache = new Map<string, Resolver>()
289
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
290
+ * ```
291
+ *
292
+ * @example Two-level (object + primitive)
293
+ * ```ts
294
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
295
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
296
+ * fn(params)('camelcase')
297
+ * ```
298
+ */
299
+ function memoize(store, factory) {
300
+ return (key) => {
301
+ if (store.has(key)) return store.get(key);
302
+ const value = factory(key);
303
+ store.set(key, value);
304
+ return value;
305
+ };
306
+ }
307
+ //#endregion
268
308
  //#region ../../internals/utils/src/reserved.ts
269
309
  /**
270
310
  * JavaScript and Java reserved words.
@@ -392,11 +432,11 @@ function trimExtName(text) {
392
432
  * @example
393
433
  * ```ts
394
434
  * const schema = createSchema({ type: 'string' })
395
- * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
435
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
396
436
  * ```
397
437
  */
398
438
  function narrowSchema(node, type) {
399
- return node?.type === type ? node : void 0;
439
+ return node?.type === type ? node : null;
400
440
  }
401
441
  function isKind(kind) {
402
442
  return (node) => node.kind === kind;
@@ -445,12 +485,6 @@ const isOperationNode = isKind("Operation");
445
485
  * ```
446
486
  */
447
487
  const isSchemaNode = isKind("Schema");
448
- isKind("Property");
449
- isKind("Parameter");
450
- isKind("Response");
451
- isKind("FunctionParameter");
452
- isKind("ParameterGroup");
453
- isKind("FunctionParameters");
454
488
  //#endregion
455
489
  //#region src/refs.ts
456
490
  /**
@@ -516,32 +550,40 @@ function createLimit(concurrency) {
516
550
  * // returns parameters, requestBody schema (if present), and responses
517
551
  * ```
518
552
  */
519
- function getChildren(node, recurse) {
520
- switch (node.kind) {
521
- case "Input": return [...node.schemas, ...node.operations];
522
- case "Output": return [];
523
- case "Operation": return [
524
- ...node.parameters,
525
- ...node.requestBody?.content?.flatMap((c) => c.schema ? [c.schema] : []) ?? [],
526
- ...node.responses
527
- ];
528
- case "Schema": {
529
- const children = [];
530
- if (!recurse) return [];
531
- if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
532
- if ("items" in node && node.items) children.push(...node.items);
533
- if ("members" in node && node.members) children.push(...node.members);
534
- if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
535
- return children;
553
+ function* getChildren(node, recurse) {
554
+ if (node.kind === "Input") {
555
+ yield* node.schemas;
556
+ yield* node.operations;
557
+ return;
558
+ }
559
+ if (node.kind === "Output") return;
560
+ if (node.kind === "Operation") {
561
+ yield* node.parameters;
562
+ if (node.requestBody?.content) {
563
+ for (const c of node.requestBody.content) if (c.schema) yield c.schema;
536
564
  }
537
- case "Property": return [node.schema];
538
- case "Parameter": return [node.schema];
539
- case "Response": return node.schema ? [node.schema] : [];
540
- case "FunctionParameter":
541
- case "ParameterGroup":
542
- case "FunctionParameters":
543
- case "Type": return [];
544
- default: return [];
565
+ yield* node.responses;
566
+ return;
567
+ }
568
+ if (node.kind === "Schema") {
569
+ if (!recurse) return;
570
+ if ("properties" in node && node.properties.length > 0) yield* node.properties;
571
+ if ("items" in node && node.items) yield* node.items;
572
+ if ("members" in node && node.members) yield* node.members;
573
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) yield node.additionalProperties;
574
+ return;
575
+ }
576
+ if (node.kind === "Property") {
577
+ yield node.schema;
578
+ return;
579
+ }
580
+ if (node.kind === "Parameter") {
581
+ yield node.schema;
582
+ return;
583
+ }
584
+ if (node.kind === "Response") {
585
+ if (node.schema) yield node.schema;
586
+ return;
545
587
  }
546
588
  }
547
589
  /**
@@ -590,9 +632,6 @@ async function _walk(node, visitor, recurse, limit, parent) {
590
632
  case "Response":
591
633
  await limit(() => visitor.response?.(node, { parent }));
592
634
  break;
593
- case "FunctionParameter":
594
- case "ParameterGroup":
595
- case "FunctionParameters": break;
596
635
  }
597
636
  const children = getChildren(node, recurse);
598
637
  for (const child of children) await _walk(child, visitor, recurse, limit, node);
@@ -600,118 +639,95 @@ async function _walk(node, visitor, recurse, limit, parent) {
600
639
  function transform(node, options) {
601
640
  const { depth, parent, ...visitor } = options;
602
641
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
603
- switch (node.kind) {
604
- case "Input": {
605
- let input = node;
606
- const replaced = visitor.input?.(input, { parent });
607
- if (replaced) input = replaced;
608
- return {
609
- ...input,
610
- schemas: input.schemas.map((s) => transform(s, {
611
- ...options,
612
- parent: input
613
- })),
614
- operations: input.operations.map((op) => transform(op, {
615
- ...options,
616
- parent: input
617
- }))
618
- };
619
- }
620
- case "Output": {
621
- let output = node;
622
- const replaced = visitor.output?.(output, { parent });
623
- if (replaced) output = replaced;
624
- return output;
625
- }
626
- case "Operation": {
627
- let op = node;
628
- const replaced = visitor.operation?.(op, { parent });
629
- if (replaced) op = replaced;
630
- return {
631
- ...op,
632
- parameters: op.parameters.map((p) => transform(p, {
633
- ...options,
634
- parent: op
635
- })),
636
- requestBody: op.requestBody ? {
637
- ...op.requestBody,
638
- content: op.requestBody.content?.map((c) => ({
639
- ...c,
640
- schema: c.schema ? transform(c.schema, {
641
- ...options,
642
- parent: op
643
- }) : void 0
644
- }))
645
- } : void 0,
646
- responses: op.responses.map((r) => transform(r, {
647
- ...options,
648
- parent: op
642
+ if (node.kind === "Input") {
643
+ const input = visitor.input?.(node, { parent }) ?? node;
644
+ return {
645
+ ...input,
646
+ schemas: input.schemas.map((s) => transform(s, {
647
+ ...options,
648
+ parent: input
649
+ })),
650
+ operations: input.operations.map((op) => transform(op, {
651
+ ...options,
652
+ parent: input
653
+ }))
654
+ };
655
+ }
656
+ if (node.kind === "Output") return visitor.output?.(node, { parent }) ?? node;
657
+ if (node.kind === "Operation") {
658
+ const op = visitor.operation?.(node, { parent }) ?? node;
659
+ return {
660
+ ...op,
661
+ parameters: op.parameters.map((p) => transform(p, {
662
+ ...options,
663
+ parent: op
664
+ })),
665
+ requestBody: op.requestBody ? {
666
+ ...op.requestBody,
667
+ content: op.requestBody.content?.map((c) => ({
668
+ ...c,
669
+ schema: c.schema ? transform(c.schema, {
670
+ ...options,
671
+ parent: op
672
+ }) : void 0
649
673
  }))
650
- };
651
- }
652
- case "Schema": {
653
- let schema = node;
654
- const replaced = visitor.schema?.(schema, { parent });
655
- if (replaced) schema = replaced;
656
- const childOptions = {
674
+ } : void 0,
675
+ responses: op.responses.map((r) => transform(r, {
657
676
  ...options,
658
- parent: schema
659
- };
660
- return {
661
- ...schema,
662
- ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
663
- ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
664
- ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
665
- ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
666
- };
667
- }
668
- case "Property": {
669
- let prop = node;
670
- const replaced = visitor.property?.(prop, { parent });
671
- if (replaced) prop = replaced;
672
- return createProperty({
673
- ...prop,
674
- schema: transform(prop.schema, {
675
- ...options,
676
- parent: prop
677
- })
678
- });
679
- }
680
- case "Parameter": {
681
- let param = node;
682
- const replaced = visitor.parameter?.(param, { parent });
683
- if (replaced) param = replaced;
684
- return createParameter({
685
- ...param,
686
- schema: transform(param.schema, {
687
- ...options,
688
- parent: param
689
- })
690
- });
691
- }
692
- case "Response": {
693
- let response = node;
694
- const replaced = visitor.response?.(response, { parent });
695
- if (replaced) response = replaced;
696
- return {
697
- ...response,
698
- schema: transform(response.schema, {
699
- ...options,
700
- parent: response
701
- })
702
- };
703
- }
704
- case "FunctionParameter":
705
- case "ParameterGroup":
706
- case "FunctionParameters":
707
- case "Type": return node;
708
- default: return node;
677
+ parent: op
678
+ }))
679
+ };
680
+ }
681
+ if (node.kind === "Schema") {
682
+ const schema = visitor.schema?.(node, { parent }) ?? node;
683
+ const childOptions = {
684
+ ...options,
685
+ parent: schema
686
+ };
687
+ return {
688
+ ...schema,
689
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
690
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
691
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
692
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
693
+ };
694
+ }
695
+ if (node.kind === "Property") {
696
+ const prop = visitor.property?.(node, { parent }) ?? node;
697
+ return createProperty({
698
+ ...prop,
699
+ schema: transform(prop.schema, {
700
+ ...options,
701
+ parent: prop
702
+ })
703
+ });
709
704
  }
705
+ if (node.kind === "Parameter") {
706
+ const param = visitor.parameter?.(node, { parent }) ?? node;
707
+ return createParameter({
708
+ ...param,
709
+ schema: transform(param.schema, {
710
+ ...options,
711
+ parent: param
712
+ })
713
+ });
714
+ }
715
+ if (node.kind === "Response") {
716
+ const response = visitor.response?.(node, { parent }) ?? node;
717
+ return {
718
+ ...response,
719
+ schema: transform(response.schema, {
720
+ ...options,
721
+ parent: response
722
+ })
723
+ };
724
+ }
725
+ return node;
710
726
  }
711
727
  /**
712
728
  * Runs a depth-first synchronous collection pass.
713
729
  *
714
- * Non-`undefined` values returned by visitor callbacks are appended to the result.
730
+ * Non-`null` values returned by visitor callbacks are appended to the result.
715
731
  *
716
732
  * @example
717
733
  * ```ts
@@ -728,10 +744,9 @@ function transform(node, options) {
728
744
  * const values = collect(root, { depth: 'shallow', root: () => 'root' })
729
745
  * ```
730
746
  */
731
- function collect(node, options) {
747
+ function* collectLazy(node, options) {
732
748
  const { depth, parent, ...visitor } = options;
733
749
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
734
- const results = [];
735
750
  let v;
736
751
  switch (node.kind) {
737
752
  case "Input":
@@ -755,16 +770,15 @@ function collect(node, options) {
755
770
  case "Response":
756
771
  v = visitor.response?.(node, { parent });
757
772
  break;
758
- case "FunctionParameter":
759
- case "ParameterGroup":
760
- case "FunctionParameters": break;
761
773
  }
762
- if (v !== void 0) results.push(v);
763
- for (const child of getChildren(node, recurse)) for (const item of collect(child, {
774
+ if (v != null) yield v;
775
+ for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
764
776
  ...options,
765
777
  parent: node
766
- })) results.push(item);
767
- return results;
778
+ });
779
+ }
780
+ function collect(node, options) {
781
+ return Array.from(collectLazy(node, options));
768
782
  }
769
783
  //#endregion
770
784
  //#region src/utils.ts
@@ -818,15 +832,16 @@ function isStringType(node) {
818
832
  * the desired casing while preserving `OperationNode.parameters` for other consumers.
819
833
  * The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
820
834
  */
835
+ const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
836
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
837
+ return {
838
+ ...param,
839
+ name: transformed
840
+ };
841
+ })));
821
842
  function caseParams(params, casing) {
822
843
  if (!casing) return params;
823
- return params.map((param) => {
824
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
825
- return {
826
- ...param,
827
- name: transformed
828
- };
829
- });
844
+ return caseParamsMemo(params)(casing);
830
845
  }
831
846
  /**
832
847
  * Creates a single-property object schema used as a discriminator literal.
@@ -955,7 +970,7 @@ function createOperationParams(node, options) {
955
970
  }));
956
971
  } else {
957
972
  if (pathParams.length) if (pathParamsType === "inlineSpread") {
958
- const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
973
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
959
974
  params.push(createFunctionParameter({
960
975
  name: pathName,
961
976
  type: spreadType ? wrapType(spreadType) : void 0,
@@ -1031,13 +1046,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
1031
1046
  }
1032
1047
  /**
1033
1048
  * Derives a {@link ParamGroupType} from the resolver's group method.
1034
- * Returns `undefined` when the group name equals the individual param name (no real group).
1049
+ * Returns `null` when the group name equals the individual param name (no real group).
1035
1050
  */
1036
1051
  function resolveGroupType({ node, params, groupMethod, resolver }) {
1037
- if (!params.length) return;
1052
+ if (!params.length) return null;
1038
1053
  const firstParam = params[0];
1039
1054
  const groupName = groupMethod.call(resolver, node, firstParam);
1040
- if (groupName === resolver.resolveParamName(node, firstParam)) return;
1055
+ if (groupName === resolver.resolveParamName(node, firstParam)) return null;
1041
1056
  const allOptional = params.every((p) => !p.required);
1042
1057
  return {
1043
1058
  type: createParamsType({
@@ -1157,6 +1172,13 @@ function combineExports(exports) {
1157
1172
  function combineImports(imports, exports, source) {
1158
1173
  const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
1159
1174
  const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
1175
+ const importNameMemo = /* @__PURE__ */ new Map();
1176
+ const canonicalizeName = (n) => {
1177
+ if (typeof n === "string") return n;
1178
+ const key = `${n.propertyName}:${n.name ?? ""}`;
1179
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
1180
+ return importNameMemo.get(key);
1181
+ };
1160
1182
  const result = [];
1161
1183
  const namedByPath = /* @__PURE__ */ new Map();
1162
1184
  const seen = /* @__PURE__ */ new Set();
@@ -1170,7 +1192,7 @@ function combineImports(imports, exports, source) {
1170
1192
  const { path, isTypeOnly } = curr;
1171
1193
  let { name } = curr;
1172
1194
  if (Array.isArray(name)) {
1173
- name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
1195
+ name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
1174
1196
  if (!name.length) continue;
1175
1197
  const key = pathTypeKey(path, isTypeOnly);
1176
1198
  const existing = namedByPath.get(key);
@@ -1223,7 +1245,7 @@ function extractStringsFromNodes(nodes) {
1223
1245
  /**
1224
1246
  * Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
1225
1247
  *
1226
- * Returns `undefined` for non-ref nodes or when no name can be resolved. Use this to get a schema's
1248
+ * Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
1227
1249
  * identifier for type definitions or error messages.
1228
1250
  *
1229
1251
  * @example
@@ -1233,9 +1255,9 @@ function extractStringsFromNodes(nodes) {
1233
1255
  * ```
1234
1256
  */
1235
1257
  function resolveRefName(node) {
1236
- if (!node || node.type !== "ref") return void 0;
1237
- if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? void 0;
1238
- return node.name ?? node.schema?.name ?? void 0;
1258
+ if (!node || node.type !== "ref") return null;
1259
+ if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
1260
+ return node.name ?? node.schema?.name ?? null;
1239
1261
  }
1240
1262
  /**
1241
1263
  * Collects every named schema referenced (transitively) from a node via ref edges.
@@ -1257,14 +1279,19 @@ function resolveRefName(node) {
1257
1279
  * }
1258
1280
  * ```
1259
1281
  */
1260
- function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1261
- if (!node) return out;
1282
+ const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1283
+ const refs = /* @__PURE__ */ new Set();
1262
1284
  collect(node, { schema(child) {
1263
1285
  if (child.type === "ref") {
1264
1286
  const name = resolveRefName(child);
1265
- if (name) out.add(name);
1287
+ if (name) refs.add(name);
1266
1288
  }
1267
1289
  } });
1290
+ return refs;
1291
+ });
1292
+ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1293
+ if (!node) return out;
1294
+ for (const name of collectSchemaRefs(node)) out.add(name);
1268
1295
  return out;
1269
1296
  }
1270
1297
  /**
@@ -1280,10 +1307,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1280
1307
  *
1281
1308
  * @example Only generate schemas referenced by included operations
1282
1309
  * ```ts
1283
- * const includedOps = inputNode.operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
1284
- * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
1310
+ * const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
1311
+ * const allowed = collectUsedSchemaNames(includedOps, schemas)
1285
1312
  *
1286
- * for (const schema of inputNode.schemas) {
1313
+ * for (const schema of schemas) {
1287
1314
  * if (schema.name && !allowed.has(schema.name)) continue
1288
1315
  * // … generate schema
1289
1316
  * }
@@ -1291,11 +1318,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1291
1318
  *
1292
1319
  * @example Check whether a specific schema is needed
1293
1320
  * ```ts
1294
- * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
1321
+ * const allowed = collectUsedSchemaNames(includedOps, schemas)
1295
1322
  * allowed.has('OrderStatus') // false when no included operation references OrderStatus
1296
1323
  * ```
1297
1324
  */
1298
- function collectUsedSchemaNames(operations, schemas) {
1325
+ const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
1326
+ function computeUsedSchemaNames(operations, schemas) {
1299
1327
  const schemaMap = /* @__PURE__ */ new Map();
1300
1328
  for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
1301
1329
  const result = /* @__PURE__ */ new Set();
@@ -1307,22 +1335,17 @@ function collectUsedSchemaNames(operations, schemas) {
1307
1335
  if (namedSchema) visitSchema(namedSchema);
1308
1336
  }
1309
1337
  }
1310
- for (const op of operations) for (const schema of collect(op, {
1338
+ for (const op of operations) for (const schema of collectLazy(op, {
1311
1339
  depth: "shallow",
1312
1340
  schema: (node) => node
1313
1341
  })) visitSchema(schema);
1314
1342
  return result;
1315
1343
  }
1316
- /**
1317
- * Identifies all schemas that participate in circular dependency chains, including direct self-loops.
1318
- *
1319
- * Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
1320
- * in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
1321
- * Refs are followed by name only, keeping the algorithm linear in the schema graph size.
1322
- *
1323
- * @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
1324
- */
1325
- function findCircularSchemas(schemas) {
1344
+ function collectUsedSchemaNames(operations, schemas) {
1345
+ return collectUsedSchemaNamesMemo(operations)(schemas);
1346
+ }
1347
+ const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
1348
+ const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
1326
1349
  const graph = /* @__PURE__ */ new Map();
1327
1350
  for (const schema of schemas) {
1328
1351
  if (!schema.name) continue;
@@ -1345,6 +1368,19 @@ function findCircularSchemas(schemas) {
1345
1368
  }
1346
1369
  }
1347
1370
  return circular;
1371
+ });
1372
+ /**
1373
+ * Identifies all schemas that participate in circular dependency chains, including direct self-loops.
1374
+ *
1375
+ * Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
1376
+ * in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
1377
+ * Refs are followed by name only, keeping the algorithm linear in the schema graph size.
1378
+ *
1379
+ * @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
1380
+ */
1381
+ function findCircularSchemas(schemas) {
1382
+ if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
1383
+ return findCircularSchemasMemo(schemas);
1348
1384
  }
1349
1385
  /**
1350
1386
  * Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
@@ -1356,11 +1392,12 @@ function findCircularSchemas(schemas) {
1356
1392
  */
1357
1393
  function containsCircularRef(node, { circularSchemas, excludeName }) {
1358
1394
  if (!node || circularSchemas.size === 0) return false;
1359
- return collect(node, { schema(child) {
1360
- if (child.type !== "ref") return void 0;
1395
+ for (const _ of collectLazy(node, { schema(child) {
1396
+ if (child.type !== "ref") return null;
1361
1397
  const name = resolveRefName(child);
1362
- return name && name !== excludeName && circularSchemas.has(name) ? true : void 0;
1363
- } }).length > 0;
1398
+ return name && name !== excludeName && circularSchemas.has(name) ? true : null;
1399
+ } })) return true;
1400
+ return false;
1364
1401
  }
1365
1402
  //#endregion
1366
1403
  //#region src/factory.ts
@@ -1397,11 +1434,31 @@ function createInput(overrides = {}) {
1397
1434
  return {
1398
1435
  schemas: [],
1399
1436
  operations: [],
1437
+ meta: {
1438
+ circularNames: [],
1439
+ enumNames: []
1440
+ },
1400
1441
  ...overrides,
1401
1442
  kind: "Input"
1402
1443
  };
1403
1444
  }
1404
1445
  /**
1446
+ * Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
1447
+ *
1448
+ * @example
1449
+ * ```ts
1450
+ * const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
1451
+ * ```
1452
+ */
1453
+ function createStreamInput(schemas, operations, meta) {
1454
+ return {
1455
+ kind: "Input",
1456
+ schemas,
1457
+ operations,
1458
+ meta
1459
+ };
1460
+ }
1461
+ /**
1405
1462
  * Creates an `OutputNode` with a stable default for `files`.
1406
1463
  *
1407
1464
  * @example
@@ -2040,7 +2097,7 @@ function createPrinterFactory(getKey) {
2040
2097
  options: resolvedOptions,
2041
2098
  transform: (node) => {
2042
2099
  const key = getKey(node);
2043
- if (key === void 0) return null;
2100
+ if (key === null) return null;
2044
2101
  const handler = nodes[key];
2045
2102
  if (!handler) return null;
2046
2103
  return handler.call(context, node);
@@ -2077,10 +2134,10 @@ function enumPropName(parentName, propName, enumSuffix) {
2077
2134
  function collectImports({ node, nameMapping, resolve }) {
2078
2135
  return collect(node, { schema(schemaNode) {
2079
2136
  const schemaRef = narrowSchema(schemaNode, "ref");
2080
- if (!schemaRef?.ref) return;
2137
+ if (!schemaRef?.ref) return null;
2081
2138
  const rawName = extractRefName(schemaRef.ref);
2082
2139
  const result = resolve(nameMapping.get(rawName) ?? rawName);
2083
- if (!result) return;
2140
+ if (!result) return null;
2084
2141
  return result;
2085
2142
  } });
2086
2143
  }
@@ -2134,23 +2191,27 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
2134
2191
  * ])
2135
2192
  * ```
2136
2193
  */
2137
- function mergeAdjacentObjects(members) {
2138
- return members.reduce((acc, member) => {
2194
+ function* mergeAdjacentObjectsLazy(members) {
2195
+ let acc;
2196
+ for (const member of members) {
2139
2197
  const objectMember = narrowSchema(member, "object");
2140
- if (objectMember && !objectMember.name) {
2141
- const previous = acc.at(-1);
2142
- const previousObject = previous ? narrowSchema(previous, "object") : void 0;
2143
- if (previousObject && !previousObject.name) {
2144
- acc[acc.length - 1] = createSchema({
2145
- ...previousObject,
2146
- properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
2198
+ if (objectMember && !objectMember.name && acc !== void 0) {
2199
+ const accObject = narrowSchema(acc, "object");
2200
+ if (accObject && !accObject.name) {
2201
+ acc = createSchema({
2202
+ ...accObject,
2203
+ properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
2147
2204
  });
2148
- return acc;
2205
+ continue;
2149
2206
  }
2150
2207
  }
2151
- acc.push(member);
2152
- return acc;
2153
- }, []);
2208
+ if (acc !== void 0) yield acc;
2209
+ acc = member;
2210
+ }
2211
+ if (acc !== void 0) yield acc;
2212
+ }
2213
+ function mergeAdjacentObjects(members) {
2214
+ return [...mergeAdjacentObjectsLazy(members)];
2154
2215
  }
2155
2216
  /**
2156
2217
  * Removes enum members that are covered by broader scalar primitives in the same union.
@@ -2182,7 +2243,7 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
2182
2243
  const enumNode = narrowSchema(propNode, "enum");
2183
2244
  if (enumNode?.primitive === "boolean") return {
2184
2245
  ...propNode,
2185
- name: void 0
2246
+ name: null
2186
2247
  };
2187
2248
  if (enumNode) return {
2188
2249
  ...propNode,
@@ -2191,6 +2252,6 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
2191
2252
  return propNode;
2192
2253
  }
2193
2254
  //#endregion
2194
- export { caseParams, childName, collect, collectImports, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createResponse, createSchema, createSource, createText, createType, definePrinter, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, walk };
2255
+ export { caseParams, childName, collect, collectImports, collectLazy, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createResponse, createSchema, createSource, createStreamInput, createText, createType, definePrinter, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, mergeAdjacentObjectsLazy, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, walk };
2195
2256
 
2196
2257
  //# sourceMappingURL=index.js.map