@kubb/ast 5.5.5 → 5.6.0

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
@@ -331,63 +331,6 @@ const contentDef = defineNode({
331
331
  */
332
332
  const createContent = contentDef.create;
333
333
  //#endregion
334
- //#region ../../internals/utils/src/fs.ts
335
- /**
336
- * Strips the file extension from a path or file name.
337
- * Only removes the last `.ext` segment when the dot is not part of a directory name.
338
- *
339
- * @example
340
- * trimExtName('petStore.ts') // 'petStore'
341
- * trimExtName('/src/models/pet.ts') // '/src/models/pet'
342
- * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
343
- * trimExtName('noExtension') // 'noExtension'
344
- */
345
- function trimExtName(text) {
346
- const dotIndex = text.lastIndexOf(".");
347
- if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
348
- return text;
349
- }
350
- //#endregion
351
- //#region ../../internals/utils/src/promise.ts
352
- /**
353
- * Wraps `factory` with a keyed cache backed by the provided store.
354
- *
355
- * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
356
- * collected) or a `Map` for primitive keys. For multi-argument functions,
357
- * nest two `memoize` calls — the outer keyed by the first argument, the
358
- * inner (created once per outer miss) keyed by the second.
359
- *
360
- * Because the cache is owned by the caller, it can be shared, inspected, or
361
- * cleared independently of the memoized function.
362
- *
363
- * @example Single WeakMap key
364
- * ```ts
365
- * const cache = new WeakMap<SchemaNode, Set<string>>()
366
- * const getRefs = memoize(cache, (node) => collectRefs(node))
367
- * ```
368
- *
369
- * @example Single Map key (primitive)
370
- * ```ts
371
- * const cache = new Map<string, Resolver>()
372
- * const getResolver = memoize(cache, (name) => buildResolver(name))
373
- * ```
374
- *
375
- * @example Two-level (object + primitive)
376
- * ```ts
377
- * const outer = new WeakMap<Params[], Map<string, Params[]>>()
378
- * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
379
- * fn(params)('camelcase')
380
- * ```
381
- */
382
- function memoize(store, factory) {
383
- return (key) => {
384
- if (store.has(key)) return store.get(key);
385
- const value = factory(key);
386
- store.set(key, value);
387
- return value;
388
- };
389
- }
390
- //#endregion
391
334
  //#region src/utils/extractStringsFromNodes.ts
392
335
  /**
393
336
  * Extracts all string content from a `CodeNode` tree recursively.
@@ -403,15 +346,11 @@ function extractStringsFromNodes(nodes) {
403
346
  if (node) collected.push(node);
404
347
  continue;
405
348
  }
406
- if (node.kind === "Text") {
349
+ if (node.kind === "Text" || node.kind === "Jsx") {
407
350
  if (node.value) collected.push(node.value);
408
351
  continue;
409
352
  }
410
353
  if (node.kind === "Break") continue;
411
- if (node.kind === "Jsx") {
412
- if (node.value) collected.push(node.value);
413
- continue;
414
- }
415
354
  const parts = [];
416
355
  if ("params" in node && node.params) parts.push(node.params);
417
356
  if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
@@ -431,22 +370,11 @@ const IDENTIFIER_RUN = /[\w$]+/g;
431
370
  */
432
371
  const INDEX_ABOVE_IMPORTS = 128;
433
372
  /**
434
- * Every unbroken run of identifier characters in the source.
373
+ * Returns the local binding an import name item introduces: the alias when there is one, else the
374
+ * imported name.
435
375
  */
436
- function collectIdentifiers(source) {
437
- return new Set(source.match(IDENTIFIER_RUN));
438
- }
439
- function sourceKey(source) {
440
- return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
441
- }
442
- function pathTypeKey(path, isTypeOnly) {
443
- return `${path}:${isTypeOnly ?? false}`;
444
- }
445
- function exportKey(path, name, isTypeOnly, asAlias) {
446
- return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
447
- }
448
- function importKey(path, name, isTypeOnly) {
449
- return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
376
+ function importLocalName(item) {
377
+ return typeof item === "string" ? item : item.name ?? item.propertyName;
450
378
  }
451
379
  /**
452
380
  * Computes a multi-level sort key for exports and imports:
@@ -460,6 +388,36 @@ function sortKey(node) {
460
388
  return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
461
389
  }
462
390
  /**
391
+ * Sorts members by {@link sortKey}. Keys are computed once per node, not per comparison.
392
+ */
393
+ function sortMembers(nodes) {
394
+ return nodes.map((node) => ({
395
+ node,
396
+ key: sortKey(node)
397
+ })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ node }) => node);
398
+ }
399
+ /**
400
+ * Returns a collector for array-named members. A member whose path and `isTypeOnly` were already
401
+ * seen folds its names into that entry, otherwise it joins `result` as a new entry.
402
+ */
403
+ function mergeByPath(result) {
404
+ const byPath = /* @__PURE__ */ new Map();
405
+ return ({ node, names }) => {
406
+ const key = `${node.path}:${node.isTypeOnly ?? false}`;
407
+ const existing = byPath.get(key);
408
+ if (existing && Array.isArray(existing.name)) {
409
+ existing.name = [.../* @__PURE__ */ new Set([...existing.name, ...names])];
410
+ return;
411
+ }
412
+ const item = {
413
+ ...node,
414
+ name: names
415
+ };
416
+ result.push(item);
417
+ byPath.set(key, item);
418
+ };
419
+ }
420
+ /**
463
421
  * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
464
422
  * key. Unnamed sources fall back to their extracted node strings as the name part of the key. Returns
465
423
  * the deduplicated array in original order.
@@ -467,22 +425,12 @@ function sortKey(node) {
467
425
  function combineSources(sources) {
468
426
  const seen = /* @__PURE__ */ new Map();
469
427
  for (const source of sources) {
470
- const key = sourceKey(source);
428
+ const key = `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
471
429
  if (!seen.has(key)) seen.set(key, source);
472
430
  }
473
431
  return [...seen.values()];
474
432
  }
475
433
  /**
476
- * Merges `incoming` names into `existing`, preserving order and dropping duplicates.
477
- *
478
- * Shared by `combineExports` and `combineImports` for the same-path name-merge case.
479
- */
480
- function mergeNameArrays(existing, incoming) {
481
- const merged = new Set(existing);
482
- for (const name of incoming) merged.add(name);
483
- return [...merged];
484
- }
485
- /**
486
434
  * Deduplicates and merges `ExportNode` objects by path and type.
487
435
  *
488
436
  * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
@@ -490,34 +438,21 @@ function mergeNameArrays(existing, incoming) {
490
438
  */
491
439
  function combineExports(exports) {
492
440
  const result = [];
493
- const namedByPath = /* @__PURE__ */ new Map();
441
+ const merge = mergeByPath(result);
494
442
  const seen = /* @__PURE__ */ new Set();
495
- const keyed = exports.map((node) => ({
496
- node,
497
- key: sortKey(node)
498
- }));
499
- keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
500
- for (const { node: curr } of keyed) {
443
+ for (const curr of sortMembers(exports)) {
501
444
  const { name, path, isTypeOnly, asAlias } = curr;
502
445
  if (Array.isArray(name)) {
503
- if (!name.length) continue;
504
- const key = pathTypeKey(path, isTypeOnly);
505
- const existing = namedByPath.get(key);
506
- if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
507
- else {
508
- const newItem = {
509
- ...curr,
510
- name: [...new Set(name)]
511
- };
512
- result.push(newItem);
513
- namedByPath.set(key, newItem);
514
- }
515
- } else {
516
- const key = exportKey(path, name, isTypeOnly, asAlias);
517
- if (!seen.has(key)) {
518
- result.push(curr);
519
- seen.add(key);
520
- }
446
+ if (name.length) merge({
447
+ node: curr,
448
+ names: [...new Set(name)]
449
+ });
450
+ continue;
451
+ }
452
+ const key = `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
453
+ if (!seen.has(key)) {
454
+ result.push(curr);
455
+ seen.add(key);
521
456
  }
522
457
  }
523
458
  return result;
@@ -530,7 +465,7 @@ function combineExports(exports) {
530
465
  */
531
466
  function combineImports(imports, exports, source) {
532
467
  const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
533
- const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? collectIdentifiers(source) : null;
468
+ const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
534
469
  const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
535
470
  const importNameMemo = /* @__PURE__ */ new Map();
536
471
  const canonicalizeName = (n) => {
@@ -540,43 +475,26 @@ function combineImports(imports, exports, source) {
540
475
  return importNameMemo.get(key);
541
476
  };
542
477
  const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
543
- for (const node of imports) {
544
- if (!Array.isArray(node.name)) continue;
545
- if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
546
- }
478
+ for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);
547
479
  const result = [];
548
- const namedByPath = /* @__PURE__ */ new Map();
480
+ const merge = mergeByPath(result);
549
481
  const seen = /* @__PURE__ */ new Set();
550
- const keyed = imports.map((node) => ({
551
- node,
552
- key: sortKey(node)
553
- }));
554
- keyed.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
555
- for (const { node: curr } of keyed) {
482
+ for (const curr of sortMembers(imports)) {
556
483
  if (curr.path === curr.root) continue;
557
- const { path, isTypeOnly } = curr;
558
- let { name } = curr;
484
+ const { name, path, isTypeOnly } = curr;
559
485
  if (Array.isArray(name)) {
560
- name = [...new Set(name.map(canonicalizeName))].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName));
561
- if (!name.length) continue;
562
- const key = pathTypeKey(path, isTypeOnly);
563
- const existing = namedByPath.get(key);
564
- if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
565
- else {
566
- const newItem = {
567
- ...curr,
568
- name
569
- };
570
- result.push(newItem);
571
- namedByPath.set(key, newItem);
572
- }
573
- } else {
574
- if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
575
- const key = importKey(path, name, isTypeOnly);
576
- if (!seen.has(key)) {
577
- result.push(curr);
578
- seen.add(key);
579
- }
486
+ const names = [...new Set(name.map(canonicalizeName))].filter((item) => isUsed(importLocalName(item)));
487
+ if (names.length) merge({
488
+ node: curr,
489
+ names
490
+ });
491
+ continue;
492
+ }
493
+ if (!isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
494
+ const key = `${path}:${name}:${isTypeOnly ?? false}`;
495
+ if (!seen.has(key)) {
496
+ result.push(curr);
497
+ seen.add(key);
580
498
  }
581
499
  }
582
500
  return result;
@@ -681,12 +599,10 @@ function createFile(input) {
681
599
  if (item.name) localNames.add(item.name);
682
600
  }
683
601
  const source = sourceParts.join("\n") || void 0;
684
- const combinedImports = combineImports(input.imports, resolvedExports, source);
685
- const nameOf = (item) => typeof item === "string" ? item : item.name ?? item.propertyName;
686
- return combinedImports.flatMap((imp) => {
602
+ return combineImports(input.imports, resolvedExports, source).flatMap((imp) => {
687
603
  if (imp.path === input.path) return [];
688
604
  if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
689
- const kept = imp.name.filter((item) => !localNames.has(nameOf(item)));
605
+ const kept = imp.name.filter((item) => !localNames.has(importLocalName(item)));
690
606
  if (!kept.length) return [];
691
607
  return [kept.length === imp.name.length ? imp : {
692
608
  ...imp,
@@ -699,7 +615,7 @@ function createFile(input) {
699
615
  kind: "File",
700
616
  ...input,
701
617
  id: hash("sha256", input.path, "hex"),
702
- name: trimExtName(input.baseName),
618
+ name: path.basename(input.baseName, extname),
703
619
  extname,
704
620
  imports: resolvedImports,
705
621
  exports: resolvedExports,
@@ -714,14 +630,15 @@ function createFile(input) {
714
630
  */
715
631
  const inputDef = defineNode({
716
632
  kind: "Input",
717
- defaults: {
633
+ build: (props) => ({
718
634
  schemas: [],
719
635
  operations: [],
720
636
  meta: {
721
637
  circularNames: [],
722
638
  enumNames: []
723
- }
724
- },
639
+ },
640
+ ...props
641
+ }),
725
642
  children: ["schemas", "operations"],
726
643
  visitorKey: "input"
727
644
  });
@@ -819,22 +736,27 @@ function optionality(schema, required) {
819
736
  nullish: !required && nullable ? true : void 0
820
737
  };
821
738
  }
739
+ /**
740
+ * Defaults `required` to `false` and derives the schema's `optional`/`nullish` flags from it.
741
+ * Shared build step of `createProperty` and `createParameter`.
742
+ */
743
+ function withRequired(props) {
744
+ const required = props.required ?? false;
745
+ return {
746
+ ...props,
747
+ required,
748
+ schema: optionality(props.schema, required)
749
+ };
750
+ }
822
751
  //#endregion
823
752
  //#region src/nodes/parameter.ts
824
753
  /**
825
754
  * Definition for the {@link ParameterNode}. `required` defaults to `false`, and the schema's
826
- * `optional`/`nullish` flags are derived from it through {@link optionality}.
755
+ * `optional`/`nullish` flags are derived from it through {@link withRequired}.
827
756
  */
828
757
  const parameterDef = defineNode({
829
758
  kind: "Parameter",
830
- build: (props) => {
831
- const required = props.required ?? false;
832
- return {
833
- ...props,
834
- required,
835
- schema: optionality(props.schema, required)
836
- };
837
- },
759
+ build: withRequired,
838
760
  children: ["schema"],
839
761
  visitorKey: "parameter"
840
762
  });
@@ -856,18 +778,11 @@ const createParameter = parameterDef.create;
856
778
  //#region src/nodes/property.ts
857
779
  /**
858
780
  * Definition for the {@link PropertyNode}. `required` defaults to `false`, and the schema's
859
- * `optional`/`nullish` flags are derived from it through {@link optionality}.
781
+ * `optional`/`nullish` flags are derived from it through {@link withRequired}.
860
782
  */
861
783
  const propertyDef = defineNode({
862
784
  kind: "Property",
863
- build: (props) => {
864
- const required = props.required ?? false;
865
- return {
866
- ...props,
867
- required,
868
- schema: optionality(props.schema, required)
869
- };
870
- },
785
+ build: withRequired,
871
786
  children: ["schema"],
872
787
  visitorKey: "property"
873
788
  });
@@ -968,7 +883,9 @@ const schemaDef = defineNode({
968
883
  "properties",
969
884
  "items",
970
885
  "members",
971
- "additionalProperties"
886
+ "additionalProperties",
887
+ "patternProperties",
888
+ "rest"
972
889
  ],
973
890
  visitorKey: "schema"
974
891
  });
@@ -1023,9 +940,21 @@ function isNode(value) {
1023
940
  return typeof value === "object" && value !== null && typeof value.kind === "string";
1024
941
  }
1025
942
  /**
1026
- * Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
1027
- *
1028
- * `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
943
+ * Returns `true` for a plain object that maps names to nodes, such as `patternProperties`.
944
+ */
945
+ function isNodeRecord(value) {
946
+ return typeof value === "object" && value !== null && !Array.isArray(value);
947
+ }
948
+ /**
949
+ * Returns the child fields of `node` to walk, from {@link VISITOR_KEYS}, or `undefined` when there
950
+ * is nothing to walk. `Schema` children are only walked when `recurse` is `true`.
951
+ */
952
+ function childKeys(node, recurse) {
953
+ if (node.kind === "Schema" && !recurse) return void 0;
954
+ return visitorKeysByKind[node.kind];
955
+ }
956
+ /**
957
+ * Returns the immediate traversable children of `node`.
1029
958
  *
1030
959
  * @example
1031
960
  * ```ts
@@ -1034,8 +963,7 @@ function isNode(value) {
1034
963
  * ```
1035
964
  */
1036
965
  function* getChildren(node, recurse) {
1037
- if (node.kind === "Schema" && !recurse) return;
1038
- const keys = visitorKeysByKind[node.kind];
966
+ const keys = childKeys(node, recurse);
1039
967
  if (!keys) return;
1040
968
  const record = node;
1041
969
  for (const key of keys) {
@@ -1044,7 +972,13 @@ function* getChildren(node, recurse) {
1044
972
  for (const item of value) if (isNode(item)) yield item;
1045
973
  continue;
1046
974
  }
1047
- if (isNode(value)) yield value;
975
+ if (isNode(value)) {
976
+ yield value;
977
+ continue;
978
+ }
979
+ if (isNodeRecord(value)) {
980
+ for (const item of Object.values(value)) if (isNode(item)) yield item;
981
+ }
1048
982
  }
1049
983
  }
1050
984
  /**
@@ -1062,6 +996,30 @@ function applyVisitor(node, visitor, parent) {
1062
996
  const fn = visitor[key];
1063
997
  return fn?.(node, { parent });
1064
998
  }
999
+ /**
1000
+ * Synchronous depth-first transform. Each visitor callback can return a
1001
+ * replacement node. Returning `undefined` keeps the original.
1002
+ *
1003
+ * The original tree is never mutated, a new tree is returned. Pass
1004
+ * `depth: 'shallow'` to skip recursion into children.
1005
+ *
1006
+ * @example Prefix every operationId
1007
+ * ```ts
1008
+ * const next = transform(root, {
1009
+ * operation(node) {
1010
+ * return { ...node, operationId: `prefixed_${node.operationId}` }
1011
+ * },
1012
+ * })
1013
+ * ```
1014
+ *
1015
+ * @example Replace only the root node
1016
+ * ```ts
1017
+ * const next = transform(root, {
1018
+ * depth: 'shallow',
1019
+ * input: (node) => ({ ...node, meta: { ...node.meta, title: 'Rewritten' } }),
1020
+ * })
1021
+ * ```
1022
+ */
1065
1023
  function transform(node, options) {
1066
1024
  const { depth, parent, ...visitor } = options;
1067
1025
  return transformNode(node, visitor, (depth ?? visitorDepths.deep) === visitorDepths.deep, parent);
@@ -1075,13 +1033,11 @@ function transformNode(node, visitor, recurse, parent) {
1075
1033
  return transformChildren(applyVisitor(node, visitor, parent) ?? node, visitor, recurse);
1076
1034
  }
1077
1035
  /**
1078
- * Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
1079
- * each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
1080
- * `Schema` children are skipped in shallow mode.
1036
+ * Immutably rebuilds a node's children, transforming each child node and leaving non-node values
1037
+ * (e.g. `additionalProperties: true`) intact.
1081
1038
  */
1082
1039
  function transformChildren(node, visitor, recurse) {
1083
- if (node.kind === "Schema" && !recurse) return node;
1084
- const keys = visitorKeysByKind[node.kind];
1040
+ const keys = childKeys(node, recurse);
1085
1041
  if (!keys) return node;
1086
1042
  const record = node;
1087
1043
  let updates;
@@ -1104,6 +1060,15 @@ function transformChildren(node, visitor, recurse) {
1104
1060
  if (isNode(value)) {
1105
1061
  const next = transformNode(value, visitor, recurse, node);
1106
1062
  if (next !== value) (updates ??= {})[key] = next;
1063
+ continue;
1064
+ }
1065
+ if (isNodeRecord(value)) {
1066
+ let mapped;
1067
+ for (const [name, item] of Object.entries(value)) {
1068
+ const next = isNode(item) ? transformNode(item, visitor, recurse, node) : item;
1069
+ if (next !== item) (mapped ??= { ...value })[name] = next;
1070
+ }
1071
+ if (mapped) (updates ??= {})[key] = mapped;
1107
1072
  }
1108
1073
  }
1109
1074
  if (!updates) return node;
@@ -1297,21 +1262,18 @@ function createPrinter(build) {
1297
1262
  ...overrides
1298
1263
  } : nodes;
1299
1264
  const collectedImports = [];
1265
+ const dispatch = (table) => (node) => {
1266
+ const handler = table[node.type];
1267
+ if (!handler) return null;
1268
+ return handler.call(context, node);
1269
+ };
1300
1270
  const context = {
1301
1271
  options: resolvedOptions,
1302
1272
  import: (node) => {
1303
1273
  collectedImports.push(node);
1304
1274
  },
1305
- transform: (node) => {
1306
- const handler = merged[node.type];
1307
- if (!handler) return null;
1308
- return handler.call(context, node);
1309
- },
1310
- base: (node) => {
1311
- const handler = nodes[node.type];
1312
- if (!handler) return null;
1313
- return handler.call(context, node);
1314
- }
1275
+ transform: dispatch(merged),
1276
+ base: dispatch(nodes)
1315
1277
  };
1316
1278
  return {
1317
1279
  name,
@@ -1344,6 +1306,29 @@ function resolveRefName(node) {
1344
1306
  return node.name ?? node.schema?.name ?? null;
1345
1307
  }
1346
1308
  /**
1309
+ * Merges a ref node with its resolved schema, giving the fields set at the usage site precedence.
1310
+ *
1311
+ * Every field on the ref except `kind`, `type`, `name`, `ref`, and `schema` overrides the same field
1312
+ * of the resolved `schema` (for example `description`, `nullable`, `readOnly`, `deprecated`). Fields
1313
+ * left `undefined` on the ref do not shadow the resolved schema. A ref without a resolved `schema`
1314
+ * is returned as is.
1315
+ *
1316
+ * @example
1317
+ * ```ts
1318
+ * const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A pet', schema: petSchema })
1319
+ * mergeRefWithSchema(ref) // petSchema's fields with description 'A pet'
1320
+ * ```
1321
+ */
1322
+ function mergeRefWithSchema(node) {
1323
+ if (!node.schema) return node;
1324
+ const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = node;
1325
+ const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== void 0));
1326
+ return {
1327
+ ...node.schema,
1328
+ ...definedOverrides
1329
+ };
1330
+ }
1331
+ /**
1347
1332
  * Returns `true` when a schema is a bare ref without modifiers, metadata, or omit keys.
1348
1333
  *
1349
1334
  * Modifiers inherited from the resolved schema are included in the check because a ref emits the
@@ -1351,21 +1336,62 @@ function resolveRefName(node) {
1351
1336
  */
1352
1337
  function isBareRef(node, keysToOmit) {
1353
1338
  if (!node || node.type !== "ref") return false;
1354
- const meta = (() => {
1355
- if (!node.schema) return node;
1356
- const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = node;
1357
- const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== void 0));
1358
- return {
1359
- ...node.schema,
1360
- ...definedOverrides
1361
- };
1362
- })();
1339
+ const meta = mergeRefWithSchema(node);
1363
1340
  return !meta.nullable && !meta.optional && !meta.nullish && meta.default === void 0 && !meta.description && !meta.examples?.length && !keysToOmit?.length;
1364
1341
  }
1365
1342
  //#endregion
1343
+ //#region ../../internals/utils/src/promise.ts
1344
+ /**
1345
+ * Wraps `factory` with a keyed cache backed by the provided store.
1346
+ *
1347
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
1348
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
1349
+ * nest two `memoize` calls — the outer keyed by the first argument, the
1350
+ * inner (created once per outer miss) keyed by the second.
1351
+ *
1352
+ * Because the cache is owned by the caller, it can be shared, inspected, or
1353
+ * cleared independently of the memoized function.
1354
+ *
1355
+ * @example Single WeakMap key
1356
+ * ```ts
1357
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
1358
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
1359
+ * ```
1360
+ *
1361
+ * @example Single Map key (primitive)
1362
+ * ```ts
1363
+ * const cache = new Map<string, Resolver>()
1364
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
1365
+ * ```
1366
+ *
1367
+ * @example Two-level (object + primitive)
1368
+ * ```ts
1369
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
1370
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
1371
+ * fn(params)('camelcase')
1372
+ * ```
1373
+ */
1374
+ function memoize(store, factory) {
1375
+ return (key) => {
1376
+ if (store.has(key)) return store.get(key);
1377
+ const value = factory(key);
1378
+ store.set(key, value);
1379
+ return value;
1380
+ };
1381
+ }
1382
+ //#endregion
1366
1383
  //#region src/utils/schemaGraph.ts
1367
1384
  /**
1368
- * Memoized inner pass that walks a single node and returns the names of every schema it references.
1385
+ * Collects the names of every schema a node's subtree references, resolved with `resolveRefName`.
1386
+ * Each ref contributes its name only, so the schema it points to is never traversed. The walk is
1387
+ * memoized by node identity, which lets `findCircularSchemas` and `containsCircularRef` share one
1388
+ * scan per schema.
1389
+ *
1390
+ * @example
1391
+ * ```ts
1392
+ * collectSchemaRefs(petSchema)
1393
+ * // Set { 'Category', 'Tag' }
1394
+ * ```
1369
1395
  */
1370
1396
  const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1371
1397
  const refs = /* @__PURE__ */ new Set();
@@ -1378,31 +1404,6 @@ const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1378
1404
  return refs;
1379
1405
  });
1380
1406
  /**
1381
- * Collects the names of every ref found anywhere inside a node's own subtree.
1382
- *
1383
- * Each ref contributes its name only, so the schema it points to is never traversed here. Pass `out`
1384
- * to accumulate names from several nodes into one set.
1385
- *
1386
- * @example Collect refs from a single schema
1387
- * ```ts
1388
- * const names = collectReferencedSchemaNames(petSchema)
1389
- * // Set { 'Category', 'Tag' }
1390
- * ```
1391
- *
1392
- * @example Accumulate refs from multiple schemas into one set
1393
- * ```ts
1394
- * const out = new Set<string>()
1395
- * for (const schema of schemas) {
1396
- * collectReferencedSchemaNames(schema, out)
1397
- * }
1398
- * ```
1399
- */
1400
- function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1401
- if (!node) return out;
1402
- for (const name of collectSchemaRefs(node)) out.add(name);
1403
- return out;
1404
- }
1405
- /**
1406
1407
  * Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
1407
1408
  * first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
1408
1409
  * `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
@@ -1410,8 +1411,7 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
1410
1411
  *
1411
1412
  * Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
1412
1413
  * same file (a union member created by name) is left out. That leaves exactly the set
1413
- * `resolver.imports` emits. This is the ordered, import-facing counterpart to
1414
- * {@link collectReferencedSchemaNames}, which returns an unordered set for graph analysis.
1414
+ * `resolver.imports` emits.
1415
1415
  *
1416
1416
  * @example
1417
1417
  * ```ts
@@ -1432,24 +1432,6 @@ const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) =>
1432
1432
  } });
1433
1433
  return names;
1434
1434
  });
1435
- function computeUsedSchemaNames(operations, schemas) {
1436
- const schemaMap = /* @__PURE__ */ new Map();
1437
- for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
1438
- const result = /* @__PURE__ */ new Set();
1439
- function visitSchema(schema) {
1440
- const directRefs = collectReferencedSchemaNames(schema);
1441
- for (const name of directRefs) if (!result.has(name)) {
1442
- result.add(name);
1443
- const namedSchema = schemaMap.get(name);
1444
- if (namedSchema) visitSchema(namedSchema);
1445
- }
1446
- }
1447
- for (const op of operations) for (const schema of collect(op, {
1448
- depth: "shallow",
1449
- schema: (node) => node
1450
- })) visitSchema(schema);
1451
- return result;
1452
- }
1453
1435
  /**
1454
1436
  * Collects the names of all top-level schemas transitively used by a set of operations.
1455
1437
  *
@@ -1471,9 +1453,22 @@ function computeUsedSchemaNames(operations, schemas) {
1471
1453
  * ```
1472
1454
  */
1473
1455
  function collectUsedSchemaNames(operations, schemas) {
1474
- return computeUsedSchemaNames(operations, schemas);
1456
+ const schemaMap = /* @__PURE__ */ new Map();
1457
+ for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
1458
+ const result = /* @__PURE__ */ new Set();
1459
+ function visitSchema(schema) {
1460
+ for (const name of collectSchemaRefs(schema)) if (!result.has(name)) {
1461
+ result.add(name);
1462
+ const namedSchema = schemaMap.get(name);
1463
+ if (namedSchema) visitSchema(namedSchema);
1464
+ }
1465
+ }
1466
+ for (const op of operations) for (const schema of collect(op, {
1467
+ depth: "shallow",
1468
+ schema: (node) => node
1469
+ })) visitSchema(schema);
1470
+ return result;
1475
1471
  }
1476
- const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
1477
1472
  /**
1478
1473
  * Finds every schema that takes part in a circular dependency chain in a schema dependency graph
1479
1474
  * that maps each schema name to the names it references directly.
@@ -1510,14 +1505,6 @@ function findCircularSchemasFromGraph(graph) {
1510
1505
  }
1511
1506
  return circular;
1512
1507
  }
1513
- const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
1514
- const graph = /* @__PURE__ */ new Map();
1515
- for (const schema of schemas) {
1516
- if (!schema.name) continue;
1517
- graph.set(schema.name, collectReferencedSchemaNames(schema));
1518
- }
1519
- return findCircularSchemasFromGraph(graph);
1520
- });
1521
1508
  /**
1522
1509
  * Finds every schema that takes part in a circular dependency chain, including direct self-loops.
1523
1510
  *
@@ -1528,8 +1515,9 @@ const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas)
1528
1515
  * @note Call this once on the full graph, then check individual schemas with `containsCircularRef()`.
1529
1516
  */
1530
1517
  function findCircularSchemas(schemas) {
1531
- if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
1532
- return findCircularSchemasMemo(schemas);
1518
+ const graph = /* @__PURE__ */ new Map();
1519
+ for (const schema of schemas) if (schema.name) graph.set(schema.name, collectSchemaRefs(schema));
1520
+ return findCircularSchemasFromGraph(graph);
1533
1521
  }
1534
1522
  //#endregion
1535
1523
  //#region src/utils/schemaProperties.ts
@@ -1704,6 +1692,7 @@ var exports_exports = /* @__PURE__ */ __exportAll({
1704
1692
  breakDef: () => breakDef,
1705
1693
  collect: () => collect,
1706
1694
  collectImportedRefNames: () => collectImportedRefNames,
1695
+ collectSchemaRefs: () => collectSchemaRefs,
1707
1696
  collectSync: () => collectSync,
1708
1697
  collectUsedSchemaNames: () => collectUsedSchemaNames,
1709
1698
  composeMacros: () => composeMacros,
@@ -1726,6 +1715,7 @@ var exports_exports = /* @__PURE__ */ __exportAll({
1726
1715
  isBareRef: () => isBareRef,
1727
1716
  isHttpOperationNode: () => isHttpOperationNode,
1728
1717
  jsxDef: () => jsxDef,
1718
+ mergeRefWithSchema: () => mergeRefWithSchema,
1729
1719
  narrowSchema: () => narrowSchema,
1730
1720
  nodeDefs: () => nodeDefs,
1731
1721
  operationDef: () => operationDef,
@@ -1745,6 +1735,6 @@ var exports_exports = /* @__PURE__ */ __exportAll({
1745
1735
  typeDef: () => typeDef
1746
1736
  });
1747
1737
  //#endregion
1748
- export { applyMacros, arrowFunctionDef, exports_exports as ast, breakDef, collect, collectImportedRefNames, collectSync, collectUsedSchemaNames, composeMacros, constDef, contentDef, createPrinter, defineMacro, defineNode, exportDef, extractStringsFromNodes, factory_exports as factory, fileDef, findCircularSchemas, findCircularSchemasFromGraph, functionDef, getSchemaLiteralValues, importDef, inferDiscriminatorPropertyName, inputDef, isBareRef, isHttpOperationNode, jsxDef, narrowSchema, nodeDefs, operationDef, optionality, outputDef, parameterDef, propertyDef, requestBodyDef, resolveRefName, resolveSchemaProperties, responseDef, schemaDef, schemaTypes, sourceDef, textDef, transform, typeDef };
1738
+ export { applyMacros, arrowFunctionDef, exports_exports as ast, breakDef, collect, collectImportedRefNames, collectSchemaRefs, collectSync, collectUsedSchemaNames, composeMacros, constDef, contentDef, createPrinter, defineMacro, defineNode, exportDef, extractStringsFromNodes, factory_exports as factory, fileDef, findCircularSchemas, findCircularSchemasFromGraph, functionDef, getSchemaLiteralValues, importDef, inferDiscriminatorPropertyName, inputDef, isBareRef, isHttpOperationNode, jsxDef, mergeRefWithSchema, narrowSchema, nodeDefs, operationDef, optionality, outputDef, parameterDef, propertyDef, requestBodyDef, resolveRefName, resolveSchemaProperties, responseDef, schemaDef, schemaTypes, sourceDef, textDef, transform, typeDef };
1749
1739
 
1750
1740
  //# sourceMappingURL=index.js.map