@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.cjs CHANGED
@@ -288,6 +288,46 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
288
288
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
289
289
  }
290
290
  //#endregion
291
+ //#region ../../internals/utils/src/promise.ts
292
+ /**
293
+ * Wraps `factory` with a keyed cache backed by the provided store.
294
+ *
295
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
296
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
297
+ * nest two `memoize` calls — the outer keyed by the first argument, the
298
+ * inner (created once per outer miss) keyed by the second.
299
+ *
300
+ * Because the cache is owned by the caller, it can be shared, inspected, or
301
+ * cleared independently of the memoized function.
302
+ *
303
+ * @example Single WeakMap key
304
+ * ```ts
305
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
306
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
307
+ * ```
308
+ *
309
+ * @example Single Map key (primitive)
310
+ * ```ts
311
+ * const cache = new Map<string, Resolver>()
312
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
313
+ * ```
314
+ *
315
+ * @example Two-level (object + primitive)
316
+ * ```ts
317
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
318
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
319
+ * fn(params)('camelcase')
320
+ * ```
321
+ */
322
+ function memoize(store, factory) {
323
+ return (key) => {
324
+ if (store.has(key)) return store.get(key);
325
+ const value = factory(key);
326
+ store.set(key, value);
327
+ return value;
328
+ };
329
+ }
330
+ //#endregion
291
331
  //#region ../../internals/utils/src/reserved.ts
292
332
  /**
293
333
  * JavaScript and Java reserved words.
@@ -415,11 +455,11 @@ function trimExtName(text) {
415
455
  * @example
416
456
  * ```ts
417
457
  * const schema = createSchema({ type: 'string' })
418
- * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
458
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
419
459
  * ```
420
460
  */
421
461
  function narrowSchema(node, type) {
422
- return node?.type === type ? node : void 0;
462
+ return node?.type === type ? node : null;
423
463
  }
424
464
  function isKind(kind) {
425
465
  return (node) => node.kind === kind;
@@ -468,12 +508,6 @@ const isOperationNode = isKind("Operation");
468
508
  * ```
469
509
  */
470
510
  const isSchemaNode = isKind("Schema");
471
- isKind("Property");
472
- isKind("Parameter");
473
- isKind("Response");
474
- isKind("FunctionParameter");
475
- isKind("ParameterGroup");
476
- isKind("FunctionParameters");
477
511
  //#endregion
478
512
  //#region src/refs.ts
479
513
  /**
@@ -539,32 +573,40 @@ function createLimit(concurrency) {
539
573
  * // returns parameters, requestBody schema (if present), and responses
540
574
  * ```
541
575
  */
542
- function getChildren(node, recurse) {
543
- switch (node.kind) {
544
- case "Input": return [...node.schemas, ...node.operations];
545
- case "Output": return [];
546
- case "Operation": return [
547
- ...node.parameters,
548
- ...node.requestBody?.content?.flatMap((c) => c.schema ? [c.schema] : []) ?? [],
549
- ...node.responses
550
- ];
551
- case "Schema": {
552
- const children = [];
553
- if (!recurse) return [];
554
- if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
555
- if ("items" in node && node.items) children.push(...node.items);
556
- if ("members" in node && node.members) children.push(...node.members);
557
- if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
558
- return children;
576
+ function* getChildren(node, recurse) {
577
+ if (node.kind === "Input") {
578
+ yield* node.schemas;
579
+ yield* node.operations;
580
+ return;
581
+ }
582
+ if (node.kind === "Output") return;
583
+ if (node.kind === "Operation") {
584
+ yield* node.parameters;
585
+ if (node.requestBody?.content) {
586
+ for (const c of node.requestBody.content) if (c.schema) yield c.schema;
559
587
  }
560
- case "Property": return [node.schema];
561
- case "Parameter": return [node.schema];
562
- case "Response": return node.schema ? [node.schema] : [];
563
- case "FunctionParameter":
564
- case "ParameterGroup":
565
- case "FunctionParameters":
566
- case "Type": return [];
567
- default: return [];
588
+ yield* node.responses;
589
+ return;
590
+ }
591
+ if (node.kind === "Schema") {
592
+ if (!recurse) return;
593
+ if ("properties" in node && node.properties.length > 0) yield* node.properties;
594
+ if ("items" in node && node.items) yield* node.items;
595
+ if ("members" in node && node.members) yield* node.members;
596
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) yield node.additionalProperties;
597
+ return;
598
+ }
599
+ if (node.kind === "Property") {
600
+ yield node.schema;
601
+ return;
602
+ }
603
+ if (node.kind === "Parameter") {
604
+ yield node.schema;
605
+ return;
606
+ }
607
+ if (node.kind === "Response") {
608
+ if (node.schema) yield node.schema;
609
+ return;
568
610
  }
569
611
  }
570
612
  /**
@@ -613,9 +655,6 @@ async function _walk(node, visitor, recurse, limit, parent) {
613
655
  case "Response":
614
656
  await limit(() => visitor.response?.(node, { parent }));
615
657
  break;
616
- case "FunctionParameter":
617
- case "ParameterGroup":
618
- case "FunctionParameters": break;
619
658
  }
620
659
  const children = getChildren(node, recurse);
621
660
  for (const child of children) await _walk(child, visitor, recurse, limit, node);
@@ -623,118 +662,95 @@ async function _walk(node, visitor, recurse, limit, parent) {
623
662
  function transform(node, options) {
624
663
  const { depth, parent, ...visitor } = options;
625
664
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
626
- switch (node.kind) {
627
- case "Input": {
628
- let input = node;
629
- const replaced = visitor.input?.(input, { parent });
630
- if (replaced) input = replaced;
631
- return {
632
- ...input,
633
- schemas: input.schemas.map((s) => transform(s, {
634
- ...options,
635
- parent: input
636
- })),
637
- operations: input.operations.map((op) => transform(op, {
638
- ...options,
639
- parent: input
640
- }))
641
- };
642
- }
643
- case "Output": {
644
- let output = node;
645
- const replaced = visitor.output?.(output, { parent });
646
- if (replaced) output = replaced;
647
- return output;
648
- }
649
- case "Operation": {
650
- let op = node;
651
- const replaced = visitor.operation?.(op, { parent });
652
- if (replaced) op = replaced;
653
- return {
654
- ...op,
655
- parameters: op.parameters.map((p) => transform(p, {
656
- ...options,
657
- parent: op
658
- })),
659
- requestBody: op.requestBody ? {
660
- ...op.requestBody,
661
- content: op.requestBody.content?.map((c) => ({
662
- ...c,
663
- schema: c.schema ? transform(c.schema, {
664
- ...options,
665
- parent: op
666
- }) : void 0
667
- }))
668
- } : void 0,
669
- responses: op.responses.map((r) => transform(r, {
670
- ...options,
671
- parent: op
665
+ if (node.kind === "Input") {
666
+ const input = visitor.input?.(node, { parent }) ?? node;
667
+ return {
668
+ ...input,
669
+ schemas: input.schemas.map((s) => transform(s, {
670
+ ...options,
671
+ parent: input
672
+ })),
673
+ operations: input.operations.map((op) => transform(op, {
674
+ ...options,
675
+ parent: input
676
+ }))
677
+ };
678
+ }
679
+ if (node.kind === "Output") return visitor.output?.(node, { parent }) ?? node;
680
+ if (node.kind === "Operation") {
681
+ const op = visitor.operation?.(node, { parent }) ?? node;
682
+ return {
683
+ ...op,
684
+ parameters: op.parameters.map((p) => transform(p, {
685
+ ...options,
686
+ parent: op
687
+ })),
688
+ requestBody: op.requestBody ? {
689
+ ...op.requestBody,
690
+ content: op.requestBody.content?.map((c) => ({
691
+ ...c,
692
+ schema: c.schema ? transform(c.schema, {
693
+ ...options,
694
+ parent: op
695
+ }) : void 0
672
696
  }))
673
- };
674
- }
675
- case "Schema": {
676
- let schema = node;
677
- const replaced = visitor.schema?.(schema, { parent });
678
- if (replaced) schema = replaced;
679
- const childOptions = {
697
+ } : void 0,
698
+ responses: op.responses.map((r) => transform(r, {
680
699
  ...options,
681
- parent: schema
682
- };
683
- return {
684
- ...schema,
685
- ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
686
- ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
687
- ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
688
- ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
689
- };
690
- }
691
- case "Property": {
692
- let prop = node;
693
- const replaced = visitor.property?.(prop, { parent });
694
- if (replaced) prop = replaced;
695
- return createProperty({
696
- ...prop,
697
- schema: transform(prop.schema, {
698
- ...options,
699
- parent: prop
700
- })
701
- });
702
- }
703
- case "Parameter": {
704
- let param = node;
705
- const replaced = visitor.parameter?.(param, { parent });
706
- if (replaced) param = replaced;
707
- return createParameter({
708
- ...param,
709
- schema: transform(param.schema, {
710
- ...options,
711
- parent: param
712
- })
713
- });
714
- }
715
- case "Response": {
716
- let response = node;
717
- const replaced = visitor.response?.(response, { parent });
718
- if (replaced) response = replaced;
719
- return {
720
- ...response,
721
- schema: transform(response.schema, {
722
- ...options,
723
- parent: response
724
- })
725
- };
726
- }
727
- case "FunctionParameter":
728
- case "ParameterGroup":
729
- case "FunctionParameters":
730
- case "Type": return node;
731
- default: return node;
700
+ parent: op
701
+ }))
702
+ };
703
+ }
704
+ if (node.kind === "Schema") {
705
+ const schema = visitor.schema?.(node, { parent }) ?? node;
706
+ const childOptions = {
707
+ ...options,
708
+ parent: schema
709
+ };
710
+ return {
711
+ ...schema,
712
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
713
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
714
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
715
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
716
+ };
717
+ }
718
+ if (node.kind === "Property") {
719
+ const prop = visitor.property?.(node, { parent }) ?? node;
720
+ return createProperty({
721
+ ...prop,
722
+ schema: transform(prop.schema, {
723
+ ...options,
724
+ parent: prop
725
+ })
726
+ });
732
727
  }
728
+ if (node.kind === "Parameter") {
729
+ const param = visitor.parameter?.(node, { parent }) ?? node;
730
+ return createParameter({
731
+ ...param,
732
+ schema: transform(param.schema, {
733
+ ...options,
734
+ parent: param
735
+ })
736
+ });
737
+ }
738
+ if (node.kind === "Response") {
739
+ const response = visitor.response?.(node, { parent }) ?? node;
740
+ return {
741
+ ...response,
742
+ schema: transform(response.schema, {
743
+ ...options,
744
+ parent: response
745
+ })
746
+ };
747
+ }
748
+ return node;
733
749
  }
734
750
  /**
735
751
  * Runs a depth-first synchronous collection pass.
736
752
  *
737
- * Non-`undefined` values returned by visitor callbacks are appended to the result.
753
+ * Non-`null` values returned by visitor callbacks are appended to the result.
738
754
  *
739
755
  * @example
740
756
  * ```ts
@@ -751,10 +767,9 @@ function transform(node, options) {
751
767
  * const values = collect(root, { depth: 'shallow', root: () => 'root' })
752
768
  * ```
753
769
  */
754
- function collect(node, options) {
770
+ function* collectLazy(node, options) {
755
771
  const { depth, parent, ...visitor } = options;
756
772
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
757
- const results = [];
758
773
  let v;
759
774
  switch (node.kind) {
760
775
  case "Input":
@@ -778,16 +793,15 @@ function collect(node, options) {
778
793
  case "Response":
779
794
  v = visitor.response?.(node, { parent });
780
795
  break;
781
- case "FunctionParameter":
782
- case "ParameterGroup":
783
- case "FunctionParameters": break;
784
796
  }
785
- if (v !== void 0) results.push(v);
786
- for (const child of getChildren(node, recurse)) for (const item of collect(child, {
797
+ if (v != null) yield v;
798
+ for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
787
799
  ...options,
788
800
  parent: node
789
- })) results.push(item);
790
- return results;
801
+ });
802
+ }
803
+ function collect(node, options) {
804
+ return Array.from(collectLazy(node, options));
791
805
  }
792
806
  //#endregion
793
807
  //#region src/utils.ts
@@ -841,15 +855,16 @@ function isStringType(node) {
841
855
  * the desired casing while preserving `OperationNode.parameters` for other consumers.
842
856
  * The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
843
857
  */
858
+ const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
859
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
860
+ return {
861
+ ...param,
862
+ name: transformed
863
+ };
864
+ })));
844
865
  function caseParams(params, casing) {
845
866
  if (!casing) return params;
846
- return params.map((param) => {
847
- const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
848
- return {
849
- ...param,
850
- name: transformed
851
- };
852
- });
867
+ return caseParamsMemo(params)(casing);
853
868
  }
854
869
  /**
855
870
  * Creates a single-property object schema used as a discriminator literal.
@@ -978,7 +993,7 @@ function createOperationParams(node, options) {
978
993
  }));
979
994
  } else {
980
995
  if (pathParams.length) if (pathParamsType === "inlineSpread") {
981
- const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
996
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
982
997
  params.push(createFunctionParameter({
983
998
  name: pathName,
984
999
  type: spreadType ? wrapType(spreadType) : void 0,
@@ -1054,13 +1069,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
1054
1069
  }
1055
1070
  /**
1056
1071
  * Derives a {@link ParamGroupType} from the resolver's group method.
1057
- * Returns `undefined` when the group name equals the individual param name (no real group).
1072
+ * Returns `null` when the group name equals the individual param name (no real group).
1058
1073
  */
1059
1074
  function resolveGroupType({ node, params, groupMethod, resolver }) {
1060
- if (!params.length) return;
1075
+ if (!params.length) return null;
1061
1076
  const firstParam = params[0];
1062
1077
  const groupName = groupMethod.call(resolver, node, firstParam);
1063
- if (groupName === resolver.resolveParamName(node, firstParam)) return;
1078
+ if (groupName === resolver.resolveParamName(node, firstParam)) return null;
1064
1079
  const allOptional = params.every((p) => !p.required);
1065
1080
  return {
1066
1081
  type: createParamsType({
@@ -1180,6 +1195,13 @@ function combineExports(exports) {
1180
1195
  function combineImports(imports, exports, source) {
1181
1196
  const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
1182
1197
  const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
1198
+ const importNameMemo = /* @__PURE__ */ new Map();
1199
+ const canonicalizeName = (n) => {
1200
+ if (typeof n === "string") return n;
1201
+ const key = `${n.propertyName}:${n.name ?? ""}`;
1202
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
1203
+ return importNameMemo.get(key);
1204
+ };
1183
1205
  const result = [];
1184
1206
  const namedByPath = /* @__PURE__ */ new Map();
1185
1207
  const seen = /* @__PURE__ */ new Set();
@@ -1193,7 +1215,7 @@ function combineImports(imports, exports, source) {
1193
1215
  const { path, isTypeOnly } = curr;
1194
1216
  let { name } = curr;
1195
1217
  if (Array.isArray(name)) {
1196
- name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
1218
+ name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
1197
1219
  if (!name.length) continue;
1198
1220
  const key = pathTypeKey(path, isTypeOnly);
1199
1221
  const existing = namedByPath.get(key);
@@ -1246,7 +1268,7 @@ function extractStringsFromNodes(nodes) {
1246
1268
  /**
1247
1269
  * Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
1248
1270
  *
1249
- * Returns `undefined` for non-ref nodes or when no name can be resolved. Use this to get a schema's
1271
+ * Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
1250
1272
  * identifier for type definitions or error messages.
1251
1273
  *
1252
1274
  * @example
@@ -1256,9 +1278,9 @@ function extractStringsFromNodes(nodes) {
1256
1278
  * ```
1257
1279
  */
1258
1280
  function resolveRefName(node) {
1259
- if (!node || node.type !== "ref") return void 0;
1260
- if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? void 0;
1261
- return node.name ?? node.schema?.name ?? void 0;
1281
+ if (!node || node.type !== "ref") return null;
1282
+ if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
1283
+ return node.name ?? node.schema?.name ?? null;
1262
1284
  }
1263
1285
  /**
1264
1286
  * Collects every named schema referenced (transitively) from a node via ref edges.
@@ -1280,14 +1302,19 @@ function resolveRefName(node) {
1280
1302
  * }
1281
1303
  * ```
1282
1304
  */
1283
- function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1284
- if (!node) return out;
1305
+ const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1306
+ const refs = /* @__PURE__ */ new Set();
1285
1307
  collect(node, { schema(child) {
1286
1308
  if (child.type === "ref") {
1287
1309
  const name = resolveRefName(child);
1288
- if (name) out.add(name);
1310
+ if (name) refs.add(name);
1289
1311
  }
1290
1312
  } });
1313
+ return refs;
1314
+ });
1315
+ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1316
+ if (!node) return out;
1317
+ for (const name of collectSchemaRefs(node)) out.add(name);
1291
1318
  return out;
1292
1319
  }
1293
1320
  /**
@@ -1303,10 +1330,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1303
1330
  *
1304
1331
  * @example Only generate schemas referenced by included operations
1305
1332
  * ```ts
1306
- * const includedOps = inputNode.operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
1307
- * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
1333
+ * const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
1334
+ * const allowed = collectUsedSchemaNames(includedOps, schemas)
1308
1335
  *
1309
- * for (const schema of inputNode.schemas) {
1336
+ * for (const schema of schemas) {
1310
1337
  * if (schema.name && !allowed.has(schema.name)) continue
1311
1338
  * // … generate schema
1312
1339
  * }
@@ -1314,11 +1341,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1314
1341
  *
1315
1342
  * @example Check whether a specific schema is needed
1316
1343
  * ```ts
1317
- * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
1344
+ * const allowed = collectUsedSchemaNames(includedOps, schemas)
1318
1345
  * allowed.has('OrderStatus') // false when no included operation references OrderStatus
1319
1346
  * ```
1320
1347
  */
1321
- function collectUsedSchemaNames(operations, schemas) {
1348
+ const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
1349
+ function computeUsedSchemaNames(operations, schemas) {
1322
1350
  const schemaMap = /* @__PURE__ */ new Map();
1323
1351
  for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
1324
1352
  const result = /* @__PURE__ */ new Set();
@@ -1330,22 +1358,17 @@ function collectUsedSchemaNames(operations, schemas) {
1330
1358
  if (namedSchema) visitSchema(namedSchema);
1331
1359
  }
1332
1360
  }
1333
- for (const op of operations) for (const schema of collect(op, {
1361
+ for (const op of operations) for (const schema of collectLazy(op, {
1334
1362
  depth: "shallow",
1335
1363
  schema: (node) => node
1336
1364
  })) visitSchema(schema);
1337
1365
  return result;
1338
1366
  }
1339
- /**
1340
- * Identifies all schemas that participate in circular dependency chains, including direct self-loops.
1341
- *
1342
- * Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
1343
- * in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
1344
- * Refs are followed by name only, keeping the algorithm linear in the schema graph size.
1345
- *
1346
- * @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
1347
- */
1348
- function findCircularSchemas(schemas) {
1367
+ function collectUsedSchemaNames(operations, schemas) {
1368
+ return collectUsedSchemaNamesMemo(operations)(schemas);
1369
+ }
1370
+ const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
1371
+ const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
1349
1372
  const graph = /* @__PURE__ */ new Map();
1350
1373
  for (const schema of schemas) {
1351
1374
  if (!schema.name) continue;
@@ -1368,6 +1391,19 @@ function findCircularSchemas(schemas) {
1368
1391
  }
1369
1392
  }
1370
1393
  return circular;
1394
+ });
1395
+ /**
1396
+ * Identifies all schemas that participate in circular dependency chains, including direct self-loops.
1397
+ *
1398
+ * Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
1399
+ * in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
1400
+ * Refs are followed by name only, keeping the algorithm linear in the schema graph size.
1401
+ *
1402
+ * @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
1403
+ */
1404
+ function findCircularSchemas(schemas) {
1405
+ if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
1406
+ return findCircularSchemasMemo(schemas);
1371
1407
  }
1372
1408
  /**
1373
1409
  * Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
@@ -1379,11 +1415,12 @@ function findCircularSchemas(schemas) {
1379
1415
  */
1380
1416
  function containsCircularRef(node, { circularSchemas, excludeName }) {
1381
1417
  if (!node || circularSchemas.size === 0) return false;
1382
- return collect(node, { schema(child) {
1383
- if (child.type !== "ref") return void 0;
1418
+ for (const _ of collectLazy(node, { schema(child) {
1419
+ if (child.type !== "ref") return null;
1384
1420
  const name = resolveRefName(child);
1385
- return name && name !== excludeName && circularSchemas.has(name) ? true : void 0;
1386
- } }).length > 0;
1421
+ return name && name !== excludeName && circularSchemas.has(name) ? true : null;
1422
+ } })) return true;
1423
+ return false;
1387
1424
  }
1388
1425
  //#endregion
1389
1426
  //#region src/factory.ts
@@ -1420,11 +1457,31 @@ function createInput(overrides = {}) {
1420
1457
  return {
1421
1458
  schemas: [],
1422
1459
  operations: [],
1460
+ meta: {
1461
+ circularNames: [],
1462
+ enumNames: []
1463
+ },
1423
1464
  ...overrides,
1424
1465
  kind: "Input"
1425
1466
  };
1426
1467
  }
1427
1468
  /**
1469
+ * Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
1470
+ *
1471
+ * @example
1472
+ * ```ts
1473
+ * const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
1474
+ * ```
1475
+ */
1476
+ function createStreamInput(schemas, operations, meta) {
1477
+ return {
1478
+ kind: "Input",
1479
+ schemas,
1480
+ operations,
1481
+ meta
1482
+ };
1483
+ }
1484
+ /**
1428
1485
  * Creates an `OutputNode` with a stable default for `files`.
1429
1486
  *
1430
1487
  * @example
@@ -2063,7 +2120,7 @@ function createPrinterFactory(getKey) {
2063
2120
  options: resolvedOptions,
2064
2121
  transform: (node) => {
2065
2122
  const key = getKey(node);
2066
- if (key === void 0) return null;
2123
+ if (key === null) return null;
2067
2124
  const handler = nodes[key];
2068
2125
  if (!handler) return null;
2069
2126
  return handler.call(context, node);
@@ -2100,10 +2157,10 @@ function enumPropName(parentName, propName, enumSuffix) {
2100
2157
  function collectImports({ node, nameMapping, resolve }) {
2101
2158
  return collect(node, { schema(schemaNode) {
2102
2159
  const schemaRef = narrowSchema(schemaNode, "ref");
2103
- if (!schemaRef?.ref) return;
2160
+ if (!schemaRef?.ref) return null;
2104
2161
  const rawName = extractRefName(schemaRef.ref);
2105
2162
  const result = resolve(nameMapping.get(rawName) ?? rawName);
2106
- if (!result) return;
2163
+ if (!result) return null;
2107
2164
  return result;
2108
2165
  } });
2109
2166
  }
@@ -2157,23 +2214,27 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
2157
2214
  * ])
2158
2215
  * ```
2159
2216
  */
2160
- function mergeAdjacentObjects(members) {
2161
- return members.reduce((acc, member) => {
2217
+ function* mergeAdjacentObjectsLazy(members) {
2218
+ let acc;
2219
+ for (const member of members) {
2162
2220
  const objectMember = narrowSchema(member, "object");
2163
- if (objectMember && !objectMember.name) {
2164
- const previous = acc.at(-1);
2165
- const previousObject = previous ? narrowSchema(previous, "object") : void 0;
2166
- if (previousObject && !previousObject.name) {
2167
- acc[acc.length - 1] = createSchema({
2168
- ...previousObject,
2169
- properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
2221
+ if (objectMember && !objectMember.name && acc !== void 0) {
2222
+ const accObject = narrowSchema(acc, "object");
2223
+ if (accObject && !accObject.name) {
2224
+ acc = createSchema({
2225
+ ...accObject,
2226
+ properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
2170
2227
  });
2171
- return acc;
2228
+ continue;
2172
2229
  }
2173
2230
  }
2174
- acc.push(member);
2175
- return acc;
2176
- }, []);
2231
+ if (acc !== void 0) yield acc;
2232
+ acc = member;
2233
+ }
2234
+ if (acc !== void 0) yield acc;
2235
+ }
2236
+ function mergeAdjacentObjects(members) {
2237
+ return [...mergeAdjacentObjectsLazy(members)];
2177
2238
  }
2178
2239
  /**
2179
2240
  * Removes enum members that are covered by broader scalar primitives in the same union.
@@ -2205,7 +2266,7 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
2205
2266
  const enumNode = narrowSchema(propNode, "enum");
2206
2267
  if (enumNode?.primitive === "boolean") return {
2207
2268
  ...propNode,
2208
- name: void 0
2269
+ name: null
2209
2270
  };
2210
2271
  if (enumNode) return {
2211
2272
  ...propNode,
@@ -2218,6 +2279,7 @@ exports.caseParams = caseParams;
2218
2279
  exports.childName = childName;
2219
2280
  exports.collect = collect;
2220
2281
  exports.collectImports = collectImports;
2282
+ exports.collectLazy = collectLazy;
2221
2283
  exports.collectReferencedSchemaNames = collectReferencedSchemaNames;
2222
2284
  exports.collectUsedSchemaNames = collectUsedSchemaNames;
2223
2285
  exports.containsCircularRef = containsCircularRef;
@@ -2244,6 +2306,7 @@ exports.createProperty = createProperty;
2244
2306
  exports.createResponse = createResponse;
2245
2307
  exports.createSchema = createSchema;
2246
2308
  exports.createSource = createSource;
2309
+ exports.createStreamInput = createStreamInput;
2247
2310
  exports.createText = createText;
2248
2311
  exports.createType = createType;
2249
2312
  exports.definePrinter = definePrinter;
@@ -2261,6 +2324,7 @@ exports.isSchemaNode = isSchemaNode;
2261
2324
  exports.isStringType = isStringType;
2262
2325
  exports.mediaTypes = mediaTypes;
2263
2326
  exports.mergeAdjacentObjects = mergeAdjacentObjects;
2327
+ exports.mergeAdjacentObjectsLazy = mergeAdjacentObjectsLazy;
2264
2328
  exports.narrowSchema = narrowSchema;
2265
2329
  exports.nodeKinds = nodeKinds;
2266
2330
  exports.resolveRefName = resolveRefName;