@kubb/ast 5.13.0 → 5.14.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.cjs CHANGED
@@ -30,11 +30,35 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  enumerable: true
31
31
  }) : target, mod));
32
32
  //#endregion
33
+ let node_crypto = require("node:crypto");
33
34
  let node_path = require("node:path");
34
35
  node_path = __toESM(node_path, 1);
35
- let node_crypto = require("node:crypto");
36
36
  //#region src/constants.ts
37
37
  /**
38
+ * Node kind discriminators used by every AST node (for example `node.kind === nodeKinds.text`).
39
+ */
40
+ const nodeKinds = {
41
+ input: "Input",
42
+ operation: "Operation",
43
+ schema: "Schema",
44
+ property: "Property",
45
+ parameter: "Parameter",
46
+ response: "Response",
47
+ requestBody: "RequestBody",
48
+ content: "Content",
49
+ type: "Type",
50
+ file: "File",
51
+ import: "Import",
52
+ export: "Export",
53
+ source: "Source",
54
+ const: "Const",
55
+ function: "Function",
56
+ arrowFunction: "ArrowFunction",
57
+ text: "Text",
58
+ break: "Break",
59
+ jsx: "Jsx"
60
+ };
61
+ /**
38
62
  * Schema type discriminators used by all AST schema nodes.
39
63
  *
40
64
  * Each value is a stable discriminator across the AST (for example `schema.type === schemaTypes.object`).
@@ -195,6 +219,7 @@ const VISITOR_KEYS = {
195
219
  "members",
196
220
  "additionalProperties",
197
221
  "patternProperties",
222
+ "propertyNames",
198
223
  "rest"
199
224
  ],
200
225
  Property: ["schema"],
@@ -242,25 +267,101 @@ function isNodeRecord(value) {
242
267
  * ```
243
268
  */
244
269
  function getChildren(node, recurse) {
270
+ if (node.kind === nodeKinds.schema && !recurse) return [];
245
271
  const children = [];
246
- const keys = node.kind === "Schema" && !recurse ? void 0 : VISITOR_KEYS[node.kind];
247
- if (!keys) return children;
272
+ visitChildNodes(node, (child) => {
273
+ children.push(child);
274
+ return false;
275
+ });
276
+ return children;
277
+ }
278
+ /**
279
+ * Visits each direct child node in the AST's traversal order.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * forEachChild(operationNode, (child) => {
284
+ * console.log(child.kind)
285
+ * })
286
+ * ```
287
+ */
288
+ function forEachChild(node, visit) {
289
+ visitChildNodes(node, (child) => {
290
+ visit(child);
291
+ return false;
292
+ });
293
+ }
294
+ function visitChildNodes(node, visit) {
295
+ const keys = VISITOR_KEYS[node.kind];
296
+ if (!keys) return false;
248
297
  const record = node;
249
298
  for (const key of keys) {
250
299
  const value = record[key];
251
300
  if (Array.isArray(value)) {
252
- for (const item of value) if (isNode(item)) children.push(item);
301
+ for (const item of value) if (isNode(item) && visit(item)) return true;
253
302
  continue;
254
303
  }
255
304
  if (isNode(value)) {
256
- children.push(value);
305
+ if (visit(value)) return true;
257
306
  continue;
258
307
  }
259
- if (isNodeRecord(value)) {
260
- for (const item of Object.values(value)) if (isNode(item)) children.push(item);
308
+ if (isNodeRecord(value)) for (const name in value) {
309
+ if (!Object.hasOwn(value, name)) continue;
310
+ const item = value[name];
311
+ if (isNode(item) && visit(item)) return true;
261
312
  }
262
313
  }
263
- return children;
314
+ return false;
315
+ }
316
+ /**
317
+ * Visits each schema in a node's child tree in depth-first order. Includes `node` when it is a schema.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * forEachSchema(operationNode, (schema) => {
322
+ * console.log(schema.type)
323
+ * })
324
+ * ```
325
+ */
326
+ function forEachSchema(node, visit) {
327
+ const walk = (current) => {
328
+ if (current.kind === nodeKinds.schema) visit(current);
329
+ forEachChild(current, walk);
330
+ };
331
+ walk(node);
332
+ }
333
+ /**
334
+ * Finds the first schema that matches `predicate` in depth-first order. Set `followRefs` to include resolved ref targets.
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * const firstString = findSchema(operationNode, {
339
+ * predicate: (schema) => schema.type === 'string',
340
+ * })
341
+ * ```
342
+ */
343
+ function findSchema(node, { predicate, followRefs = false }) {
344
+ const seen = followRefs ? /* @__PURE__ */ new WeakSet() : void 0;
345
+ const walk = (current) => {
346
+ if (current.kind === nodeKinds.schema) {
347
+ if (predicate(current)) return current;
348
+ if (seen) {
349
+ if (seen.has(current)) return void 0;
350
+ seen.add(current);
351
+ if (current.type === "ref" && current.schema) {
352
+ const found = walk(current.schema);
353
+ if (found) return found;
354
+ }
355
+ }
356
+ }
357
+ let found;
358
+ visitChildNodes(current, (child) => {
359
+ found = walk(child);
360
+ return found !== void 0;
361
+ });
362
+ if (found) return found;
363
+ };
364
+ return walk(node);
264
365
  }
265
366
  /**
266
367
  * Runs the visitor callback that matches `node.kind` with the traversal
@@ -318,7 +419,7 @@ function transformNode(node, visitor, recurse, parent) {
318
419
  * (e.g. `additionalProperties: true`) intact.
319
420
  */
320
421
  function transformChildren(node, visitor, recurse) {
321
- const keys = node.kind === "Schema" && !recurse ? void 0 : VISITOR_KEYS[node.kind];
422
+ const keys = node.kind === nodeKinds.schema && !recurse ? void 0 : VISITOR_KEYS[node.kind];
322
423
  if (!keys) return node;
323
424
  const record = node;
324
425
  let updates;
@@ -603,11 +704,11 @@ function extractStringsFromNodes(nodes) {
603
704
  if (node) collected.push(node);
604
705
  continue;
605
706
  }
606
- if (node.kind === "Text" || node.kind === "Jsx") {
707
+ if (node.kind === nodeKinds.text || node.kind === nodeKinds.jsx) {
607
708
  if (node.value) collected.push(node.value);
608
709
  continue;
609
710
  }
610
- if (node.kind === "Break") continue;
711
+ if (node.kind === nodeKinds.break) continue;
611
712
  const parts = [];
612
713
  if ("params" in node && node.params) parts.push(node.params);
613
714
  if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
@@ -620,167 +721,594 @@ function extractStringsFromNodes(nodes) {
620
721
  return collected.join("\n");
621
722
  }
622
723
  //#endregion
623
- //#region src/utils/refs.ts
624
- /** Returns the last segment of a `$ref` pointer, for example `'Pet'` for `#/components/schemas/Pet`. */
625
- function refName(ref) {
626
- return ref.split("/").at(-1) ?? ref;
627
- }
724
+ //#region src/utils/combineFileMembers.ts
725
+ const IDENTIFIER_RUN = /[\w$]+/g;
628
726
  /**
629
- * Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
630
- * the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
631
- * then `name`, then the nested `schema.name`.
632
- *
633
- * Returns `null` for non-ref nodes or when no name resolves.
634
- *
635
- * @example
636
- * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'`
637
- *
638
- * @example Collision-renamed target
639
- * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Order', targetName: 'OrderSchema' }) // 'OrderSchema'`
727
+ * How many imports a file needs before indexing the source beats scanning it once per name.
640
728
  */
641
- function resolveRefName(node) {
642
- if (!node || node.type !== "ref") return null;
643
- if (node.targetName) return node.targetName;
644
- if (node.ref) return refName(node.ref);
645
- return node.name ?? node.schema?.name ?? null;
729
+ const INDEX_ABOVE_IMPORTS = 128;
730
+ /**
731
+ * Returns the local binding an import name item introduces: the alias when there is one, else the
732
+ * imported name.
733
+ */
734
+ function importLocalName(item) {
735
+ return typeof item === "string" ? item : item.name ?? item.propertyName;
646
736
  }
647
- /** Fields of a ref node that describe the ref itself, so they never override the resolved schema. */
648
- const REF_OWN_KEYS = /* @__PURE__ */ new Set([
649
- "kind",
650
- "type",
651
- "name",
652
- "ref",
653
- "schema"
654
- ]);
655
737
  /**
656
- * Merges a ref node with its resolved schema, giving the fields set at the usage site precedence.
657
- *
658
- * Every field on the ref except `kind`, `type`, `name`, `ref`, and `schema` overrides the same field
659
- * of the resolved `schema` (for example `description`, `nullable`, `readOnly`, `deprecated`). Fields
660
- * left `undefined` on the ref do not shadow the resolved schema. A ref without a resolved `schema`
661
- * is returned as is.
662
- *
663
- * @example
664
- * ```ts
665
- * const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A pet', schema: petSchema })
666
- * mergeRefWithSchema(ref) // petSchema's fields with description 'A pet'
667
- * ```
738
+ * Computes a multi-level sort key for exports and imports:
739
+ * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
668
740
  */
669
- function mergeRefWithSchema(node) {
670
- if (!node.schema) return node;
671
- const merged = { ...node.schema };
672
- for (const key of Object.keys(node)) {
673
- const value = node[key];
674
- if (value !== void 0 && !REF_OWN_KEYS.has(key)) merged[key] = value;
675
- }
676
- return merged;
741
+ function sortKey(node) {
742
+ const isArray = Array.isArray(node.name) ? "1" : "0";
743
+ const typeOnly = node.isTypeOnly ? "0" : "1";
744
+ const hasName = node.name != null ? "1" : "0";
745
+ const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
746
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
677
747
  }
678
748
  /**
679
- * Returns `true` when a schema is a bare ref without modifiers, metadata, or omit keys.
680
- *
681
- * Modifiers inherited from the resolved schema are included in the check because a ref emits the
682
- * referenced schema variable with those modifiers already applied.
749
+ * Sorts members by {@link sortKey}. Keys are computed once per node, not per comparison.
683
750
  */
684
- function isBareRef(node, keysToOmit) {
685
- if (!node || node.type !== "ref") return false;
686
- const meta = mergeRefWithSchema(node);
687
- return !meta.nullable && !meta.optional && !meta.nullish && meta.default === void 0 && !meta.description && !meta.examples?.length && !keysToOmit?.length;
751
+ function sortMembers(nodes) {
752
+ return nodes.map((node) => ({
753
+ node,
754
+ key: sortKey(node)
755
+ })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ node }) => node);
688
756
  }
689
- //#endregion
690
- //#region ../../internals/utils/src/promise.ts
691
757
  /**
692
- * Wraps `factory` with a keyed cache backed by the provided store.
693
- *
694
- * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
695
- * collected) or a `Map` for primitive keys. For multi-argument functions,
696
- * nest two `memoize` calls — the outer keyed by the first argument, the
697
- * inner (created once per outer miss) keyed by the second.
698
- *
699
- * Because the cache is owned by the caller, it can be shared, inspected, or
700
- * cleared independently of the memoized function.
701
- *
702
- * @example Single WeakMap key
703
- * ```ts
704
- * const cache = new WeakMap<SchemaNode, Set<string>>()
705
- * const getRefs = memoize(cache, (node) => collectRefs(node))
706
- * ```
707
- *
708
- * @example Single Map key (primitive)
709
- * ```ts
710
- * const cache = new Map<string, Resolver>()
711
- * const getResolver = memoize(cache, (name) => buildResolver(name))
712
- * ```
713
- *
714
- * @example Two-level (object + primitive)
715
- * ```ts
716
- * const outer = new WeakMap<Params[], Map<string, Params[]>>()
717
- * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
718
- * fn(params)('camelcase')
719
- * ```
758
+ * Returns a collector for array-named members. A member whose path and `isTypeOnly` were already
759
+ * seen folds its names into that entry, otherwise it joins `result` as a new entry.
720
760
  */
721
- function memoize(store, factory) {
722
- return (key) => {
723
- if (store.has(key)) return store.get(key);
724
- const value = factory(key);
725
- store.set(key, value);
726
- return value;
761
+ function mergeByPath(result) {
762
+ const byPath = /* @__PURE__ */ new Map();
763
+ return ({ node, names }) => {
764
+ const key = `${node.path}:${node.isTypeOnly ?? false}`;
765
+ const existing = byPath.get(key);
766
+ if (existing) {
767
+ for (const name of names) {
768
+ if (existing.seen.has(name)) continue;
769
+ existing.seen.add(name);
770
+ existing.names.push(name);
771
+ }
772
+ return;
773
+ }
774
+ const item = {
775
+ ...node,
776
+ name: [...names]
777
+ };
778
+ result.push(item);
779
+ byPath.set(key, {
780
+ names: item.name,
781
+ seen: new Set(names)
782
+ });
727
783
  };
728
784
  }
729
- //#endregion
730
- //#region src/utils/schemaGraph.ts
731
785
  /**
732
- * Collects the names of every schema a node's subtree references, resolved with `resolveRefName`.
733
- * Each ref contributes its name only, so the schema it points to is never traversed. The walk is
734
- * memoized by node identity, which lets `findCircularSchemas` and `containsCircularRef` share one
735
- * scan per schema.
736
- *
737
- * @example
738
- * ```ts
739
- * collectSchemaRefs(petSchema)
740
- * // Set { 'Category', 'Tag' }
741
- * ```
786
+ * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
787
+ * key. An unnamed source only drops when the same node appears twice, so markdown can repeat a
788
+ * paragraph or a table. Returns the deduplicated array in original order.
742
789
  */
743
- const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
744
- const refs = /* @__PURE__ */ new Set();
745
- collectSync(node, { schema(child) {
746
- if (child.type === "ref") {
747
- const name = resolveRefName(child);
748
- if (name) refs.add(name);
749
- }
750
- } });
751
- return refs;
752
- });
790
+ function combineSources(sources) {
791
+ const seen = /* @__PURE__ */ new Set();
792
+ return sources.filter((source) => {
793
+ const key = source.name != null ? `${source.name}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}` : source;
794
+ if (seen.has(key)) return false;
795
+ seen.add(key);
796
+ return true;
797
+ });
798
+ }
753
799
  /**
754
- * Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
755
- * first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
756
- * `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
757
- * re-scanning the same schema per plugin.
758
- *
759
- * Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
760
- * same file (a union member created by name) is left out. That leaves exactly the set
761
- * `resolver.imports` emits.
800
+ * Deduplicates and merges `ExportNode` objects by path and type.
762
801
  *
763
- * @example
764
- * ```ts
765
- * collectImportedRefNames(petSchema)
766
- * // ['Category', 'Tag']
767
- * ```
802
+ * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
803
+ * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
768
804
  */
769
- const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
805
+ function combineExports(exports) {
806
+ const result = [];
807
+ const merge = mergeByPath(result);
770
808
  const seen = /* @__PURE__ */ new Set();
771
- const names = [];
772
- collectSync(node, { schema(child) {
773
- if (child.type !== "ref" || !child.ref) return;
774
- const name = resolveRefName(child);
775
- if (name && !seen.has(name)) {
776
- seen.add(name);
777
- names.push(name);
809
+ for (const curr of sortMembers(exports)) {
810
+ const { name, path, isTypeOnly, asAlias } = curr;
811
+ if (Array.isArray(name)) {
812
+ if (name.length) merge({
813
+ node: curr,
814
+ names: [...new Set(name)]
815
+ });
816
+ continue;
778
817
  }
779
- } });
780
- return names;
781
- });
782
- /**
783
- * Collects the names of all top-level schemas transitively used by a set of operations.
818
+ const key = `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
819
+ if (!seen.has(key)) {
820
+ result.push(curr);
821
+ seen.add(key);
822
+ }
823
+ }
824
+ return result;
825
+ }
826
+ /**
827
+ * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
828
+ *
829
+ * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
830
+ * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
831
+ */
832
+ function combineImports(imports, exports, source) {
833
+ const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
834
+ const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
835
+ const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
836
+ const importNameMemo = /* @__PURE__ */ new Map();
837
+ const canonicalizeName = (n) => {
838
+ if (typeof n === "string") return n;
839
+ const key = `${n.propertyName}:${n.name ?? ""}`;
840
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
841
+ return importNameMemo.get(key);
842
+ };
843
+ const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
844
+ for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);
845
+ const result = [];
846
+ const merge = mergeByPath(result);
847
+ const seen = /* @__PURE__ */ new Set();
848
+ for (const curr of sortMembers(imports)) {
849
+ if (curr.path === curr.root) continue;
850
+ const { name, path, isTypeOnly } = curr;
851
+ if (Array.isArray(name)) {
852
+ const names = [...new Set(name.map(canonicalizeName))].filter((item) => isUsed(importLocalName(item)));
853
+ if (names.length) merge({
854
+ node: curr,
855
+ names
856
+ });
857
+ continue;
858
+ }
859
+ if (!isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
860
+ const key = `${path}:${name}:${isTypeOnly ?? false}`;
861
+ if (!seen.has(key)) {
862
+ result.push(curr);
863
+ seen.add(key);
864
+ }
865
+ }
866
+ return result;
867
+ }
868
+ //#endregion
869
+ //#region src/nodes/base.ts
870
+ /** Builds a `createX` factory for a node whose only computed field is `kind`. */
871
+ function nodeFactory(kind) {
872
+ return (input) => {
873
+ return {
874
+ ...input,
875
+ kind
876
+ };
877
+ };
878
+ }
879
+ //#endregion
880
+ //#region src/nodes/file.ts
881
+ /**
882
+ * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
883
+ *
884
+ * @example Named import
885
+ * ```ts
886
+ * createImport({ name: ['useState'], path: 'react' })
887
+ * // import { useState } from 'react'
888
+ * ```
889
+ */
890
+ const createImport = nodeFactory(nodeKinds.import);
891
+ /**
892
+ * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
893
+ *
894
+ * @example Named export
895
+ * ```ts
896
+ * createExport({ name: ['Pet'], path: './Pet' })
897
+ * // export { Pet } from './Pet'
898
+ * ```
899
+ */
900
+ const createExport = nodeFactory(nodeKinds.export);
901
+ /**
902
+ * Creates a `SourceNode` representing a fragment of source code within a file.
903
+ *
904
+ * @example
905
+ * ```ts
906
+ * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
907
+ * ```
908
+ */
909
+ const createSource = nodeFactory(nodeKinds.source);
910
+ /**
911
+ * Creates a fully resolved `FileNode` from a file input descriptor.
912
+ *
913
+ * Computes:
914
+ * - `id` SHA256 hash of the file path
915
+ * - `name` `baseName` without extension
916
+ * - `extname` extension extracted from `baseName`
917
+ *
918
+ * Deduplicates:
919
+ * - `sources` via `combineSources`
920
+ * - `exports` via `combineExports`
921
+ * - `imports` via `combineImports` (also filters unused imports)
922
+ *
923
+ * @throws {Error} when `baseName` has no extension.
924
+ *
925
+ * @example
926
+ * ```ts
927
+ * const file = createFile({
928
+ * baseName: 'petStore.ts',
929
+ * path: 'src/models/petStore.ts',
930
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
931
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
932
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
933
+ * })
934
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
935
+ * // file.name = 'petStore'
936
+ * // file.extname = '.ts'
937
+ * ```
938
+ *
939
+ * @example Copy a real file into the output
940
+ * ```ts
941
+ * const file = createFile({
942
+ * baseName: 'client.ts',
943
+ * path: 'src/gen/client.ts',
944
+ * copy: '/abs/path/to/templates/client.ts',
945
+ * })
946
+ * ```
947
+ */
948
+ function createFile(input) {
949
+ const extname = node_path.default.extname(input.baseName);
950
+ if (!extname) throw new Error(`No extname found for ${input.baseName}`);
951
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
952
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
953
+ const resolvedImports = (() => {
954
+ if (!input.imports?.length) return [];
955
+ const sourceParts = [];
956
+ const localNames = /* @__PURE__ */ new Set();
957
+ for (const item of resolvedSources) {
958
+ const extracted = item.nodes && extractStringsFromNodes(item.nodes);
959
+ if (extracted) sourceParts.push(extracted);
960
+ if (item.name) localNames.add(item.name);
961
+ }
962
+ const source = sourceParts.join("\n") || void 0;
963
+ return combineImports(input.imports, resolvedExports, source).flatMap((imp) => {
964
+ if (imp.path === input.path) return [];
965
+ if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
966
+ const kept = imp.name.filter((item) => !localNames.has(importLocalName(item)));
967
+ if (!kept.length) return [];
968
+ return [kept.length === imp.name.length ? imp : {
969
+ ...imp,
970
+ name: kept
971
+ }];
972
+ });
973
+ })();
974
+ return {
975
+ ...input,
976
+ kind: nodeKinds.file,
977
+ id: (0, node_crypto.hash)("sha256", input.path, "hex"),
978
+ name: node_path.default.basename(input.baseName, extname),
979
+ extname,
980
+ imports: resolvedImports,
981
+ exports: resolvedExports,
982
+ sources: resolvedSources,
983
+ meta: input.meta ?? {}
984
+ };
985
+ }
986
+ //#endregion
987
+ //#region src/utils/inline.ts
988
+ /**
989
+ * Whether every source of `file` is inline. Other files absorb such a file, so it is never written.
990
+ */
991
+ function isVirtualFile(file) {
992
+ return file.sources.length > 0 && file.sources.every((source) => source.inline);
993
+ }
994
+ /**
995
+ * Replaces every name a file imports from a virtual file (see {@link isVirtualFile}) with that name's
996
+ * `inline.expression`, until no inline names are left. The definitions' own imports move to the importing file.
997
+ */
998
+ function inlineFiles(files, { language }) {
999
+ const virtualFiles = new Map(files.filter(isVirtualFile).map((file) => [file.path, file]));
1000
+ const result = {
1001
+ files: [],
1002
+ virtualPaths: [...virtualFiles.keys()],
1003
+ problems: []
1004
+ };
1005
+ if (virtualFiles.size === 0) return result;
1006
+ const definitions = /* @__PURE__ */ new Map();
1007
+ function define(path, name, from, lang) {
1008
+ const key = `${path}\0${name}`;
1009
+ if (definitions.has(key)) {
1010
+ const definition = definitions.get(key);
1011
+ if (definition === null) result.problems.push({
1012
+ path: from,
1013
+ message: `"${name}" from "${path}" is defined in terms of itself.`
1014
+ });
1015
+ return definition ?? void 0;
1016
+ }
1017
+ const file = virtualFiles.get(path);
1018
+ const inline = file.sources.find((source) => source.name === name)?.inline;
1019
+ if (!inline) {
1020
+ result.problems.push({
1021
+ path: from,
1022
+ message: `"${name}" is imported from "${path}", which never writes it and has no inline definition for it.`
1023
+ });
1024
+ return;
1025
+ }
1026
+ definitions.set(key, null);
1027
+ const imports = [];
1028
+ const { text, used } = replaceNames(inline.expression, file, lang, imports);
1029
+ imports.push(...file.imports.flatMap((imp) => virtualFiles.has(imp.path) ? [] : pickNames(imp, used)));
1030
+ const definition = {
1031
+ expression: text,
1032
+ imports
1033
+ };
1034
+ definitions.set(key, definition);
1035
+ return definition;
1036
+ }
1037
+ function replaceNames(input, file, lang, imports) {
1038
+ const used = /* @__PURE__ */ new Set();
1039
+ return {
1040
+ text: lang.replaceIdentifiers(input, (local) => {
1041
+ const target = findTarget(file, local);
1042
+ const definition = target && define(target.path, target.name, file.path, lang);
1043
+ if (!definition) {
1044
+ used.add(local);
1045
+ return;
1046
+ }
1047
+ imports.push(...definition.imports);
1048
+ return lang.wrap(definition.expression);
1049
+ }),
1050
+ used
1051
+ };
1052
+ }
1053
+ function findTarget(file, local) {
1054
+ if (virtualFiles.has(file.path) && file.sources.some((source) => source.name === local)) return {
1055
+ path: file.path,
1056
+ name: local
1057
+ };
1058
+ for (const imp of file.imports) {
1059
+ const item = virtualFiles.has(imp.path) && [imp.name].flat().find((item) => importLocalName(item) === local);
1060
+ if (item) return {
1061
+ path: imp.path,
1062
+ name: typeof item === "string" ? item : item.propertyName
1063
+ };
1064
+ }
1065
+ }
1066
+ for (const file of files) {
1067
+ if (virtualFiles.has(file.path) || !file.imports.some((imp) => virtualFiles.has(imp.path))) continue;
1068
+ const lang = language(file);
1069
+ if (!lang) {
1070
+ result.problems.push({
1071
+ path: file.path,
1072
+ message: `No parser for "${file.extname}" files supports inline sources.`
1073
+ });
1074
+ continue;
1075
+ }
1076
+ const carried = [];
1077
+ const sources = file.sources.map((source) => ({
1078
+ ...source,
1079
+ nodes: rewriteNodes(source.nodes, (text) => replaceNames(text, file, lang, carried).text)
1080
+ }));
1081
+ const imports = [...file.imports.filter((imp) => !virtualFiles.has(imp.path)), ...carried.map((imp) => imp.root ? {
1082
+ ...imp,
1083
+ root: file.path
1084
+ } : imp)];
1085
+ result.files.push(createFile({
1086
+ ...file,
1087
+ sources,
1088
+ imports
1089
+ }));
1090
+ }
1091
+ return result;
1092
+ }
1093
+ function pickNames(imp, used) {
1094
+ if (!Array.isArray(imp.name)) return used.has(imp.name) ? [imp] : [];
1095
+ const name = imp.name.filter((item) => used.has(importLocalName(item)));
1096
+ return name.length > 0 ? [{
1097
+ ...imp,
1098
+ name
1099
+ }] : [];
1100
+ }
1101
+ function rewriteNodes(nodes, rewrite) {
1102
+ return nodes?.map((node) => {
1103
+ if (typeof node === "string") return rewrite(node);
1104
+ switch (node.kind) {
1105
+ case nodeKinds.break: return node;
1106
+ case nodeKinds.text:
1107
+ case nodeKinds.jsx: return {
1108
+ ...node,
1109
+ value: rewrite(node.value)
1110
+ };
1111
+ case nodeKinds.function:
1112
+ case nodeKinds.arrowFunction: return {
1113
+ ...node,
1114
+ params: node.params && rewrite(node.params),
1115
+ generics: Array.isArray(node.generics) ? node.generics.map(rewrite) : node.generics && rewrite(node.generics),
1116
+ returnType: node.returnType && rewrite(node.returnType),
1117
+ nodes: rewriteNodes(node.nodes, rewrite)
1118
+ };
1119
+ case nodeKinds.const: return {
1120
+ ...node,
1121
+ type: node.type && rewrite(node.type),
1122
+ nodes: rewriteNodes(node.nodes, rewrite)
1123
+ };
1124
+ default: return {
1125
+ ...node,
1126
+ nodes: rewriteNodes(node.nodes, rewrite)
1127
+ };
1128
+ }
1129
+ });
1130
+ }
1131
+ //#endregion
1132
+ //#region src/utils/refs.ts
1133
+ /** Returns the last segment of a `$ref` pointer, for example `'Pet'` for `#/components/schemas/Pet`. */
1134
+ function refName(ref) {
1135
+ return ref.split("/").at(-1) ?? ref;
1136
+ }
1137
+ /**
1138
+ * Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
1139
+ * the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
1140
+ * then `name`, then the nested `schema.name`.
1141
+ *
1142
+ * Returns `null` for non-ref nodes or when no name resolves.
1143
+ *
1144
+ * @example
1145
+ * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'`
1146
+ *
1147
+ * @example Collision-renamed target
1148
+ * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Order', targetName: 'OrderSchema' }) // 'OrderSchema'`
1149
+ */
1150
+ function resolveRefName(node) {
1151
+ if (!node || node.type !== "ref") return null;
1152
+ if (node.targetName) return node.targetName;
1153
+ if (node.ref) return refName(node.ref);
1154
+ return node.name ?? node.schema?.name ?? null;
1155
+ }
1156
+ /**
1157
+ * Follows a resolved ref chain to its target schema. Returns an unresolved or cyclic ref as is.
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * const target = resolveRef(refSchema)
1162
+ * ```
1163
+ */
1164
+ function resolveRef(node) {
1165
+ if (node.type !== "ref" || !node.schema) return node;
1166
+ const seen = /* @__PURE__ */ new WeakSet();
1167
+ let current = node;
1168
+ while (current.type === "ref" && current.schema) {
1169
+ if (seen.has(current)) return current;
1170
+ seen.add(current);
1171
+ current = current.schema;
1172
+ }
1173
+ return current;
1174
+ }
1175
+ /** Fields of a ref node that describe the ref itself, so they never override the resolved schema. */
1176
+ const REF_OWN_KEYS = /* @__PURE__ */ new Set([
1177
+ "kind",
1178
+ "type",
1179
+ "name",
1180
+ "ref",
1181
+ "schema"
1182
+ ]);
1183
+ /**
1184
+ * Merges a ref node with its resolved schema, giving the fields set at the usage site precedence.
1185
+ *
1186
+ * Every field on the ref except `kind`, `type`, `name`, `ref`, and `schema` overrides the same field
1187
+ * of the resolved `schema` (for example `description`, `nullable`, `readOnly`, `deprecated`). Fields
1188
+ * left `undefined` on the ref do not shadow the resolved schema. A ref without a resolved `schema`
1189
+ * is returned as is.
1190
+ *
1191
+ * @example
1192
+ * ```ts
1193
+ * const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A pet', schema: petSchema })
1194
+ * mergeRefWithSchema(ref) // petSchema's fields with description 'A pet'
1195
+ * ```
1196
+ */
1197
+ function mergeRefWithSchema(node) {
1198
+ if (!node.schema) return node;
1199
+ const merged = { ...node.schema };
1200
+ for (const key of Object.keys(node)) {
1201
+ const value = node[key];
1202
+ if (value !== void 0 && !REF_OWN_KEYS.has(key)) merged[key] = value;
1203
+ }
1204
+ return merged;
1205
+ }
1206
+ /**
1207
+ * Returns `true` when a schema is a bare ref without modifiers, metadata, or omit keys.
1208
+ *
1209
+ * Modifiers inherited from the resolved schema are included in the check because a ref emits the
1210
+ * referenced schema variable with those modifiers already applied.
1211
+ */
1212
+ function isBareRef(node, keysToOmit) {
1213
+ if (!node || node.type !== "ref") return false;
1214
+ const meta = mergeRefWithSchema(node);
1215
+ return !meta.nullable && !meta.optional && !meta.nullish && meta.default === void 0 && !meta.description && !meta.examples?.length && !keysToOmit?.length;
1216
+ }
1217
+ //#endregion
1218
+ //#region ../../internals/utils/src/promise.ts
1219
+ /**
1220
+ * Wraps `factory` with a keyed cache backed by the provided store.
1221
+ *
1222
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
1223
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
1224
+ * nest two `memoize` calls — the outer keyed by the first argument, the
1225
+ * inner (created once per outer miss) keyed by the second.
1226
+ *
1227
+ * Because the cache is owned by the caller, it can be shared, inspected, or
1228
+ * cleared independently of the memoized function.
1229
+ *
1230
+ * @example Single WeakMap key
1231
+ * ```ts
1232
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
1233
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
1234
+ * ```
1235
+ *
1236
+ * @example Single Map key (primitive)
1237
+ * ```ts
1238
+ * const cache = new Map<string, Resolver>()
1239
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
1240
+ * ```
1241
+ *
1242
+ * @example Two-level (object + primitive)
1243
+ * ```ts
1244
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
1245
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
1246
+ * fn(params)('camelcase')
1247
+ * ```
1248
+ */
1249
+ function memoize(store, factory) {
1250
+ return (key) => {
1251
+ if (store.has(key)) return store.get(key);
1252
+ const value = factory(key);
1253
+ store.set(key, value);
1254
+ return value;
1255
+ };
1256
+ }
1257
+ //#endregion
1258
+ //#region src/utils/schemaGraph.ts
1259
+ /**
1260
+ * Collects the names of every schema a node's subtree references, resolved with `resolveRefName`.
1261
+ * Each ref contributes its name only, so the schema it points to is never traversed. The walk is
1262
+ * memoized by node identity, which lets `findCircularSchemas` and `containsCircularRef` share one
1263
+ * scan per schema.
1264
+ *
1265
+ * @example
1266
+ * ```ts
1267
+ * collectSchemaRefs(petSchema)
1268
+ * // Set { 'Category', 'Tag' }
1269
+ * ```
1270
+ */
1271
+ const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1272
+ const refs = /* @__PURE__ */ new Set();
1273
+ collectSync(node, { schema(child) {
1274
+ if (child.type === "ref") {
1275
+ const name = resolveRefName(child);
1276
+ if (name) refs.add(name);
1277
+ }
1278
+ } });
1279
+ return refs;
1280
+ });
1281
+ /**
1282
+ * Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
1283
+ * first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
1284
+ * `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
1285
+ * re-scanning the same schema per plugin.
1286
+ *
1287
+ * Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
1288
+ * same file (a union member created by name) is left out. That leaves exactly the set
1289
+ * `resolver.imports` emits.
1290
+ *
1291
+ * @example
1292
+ * ```ts
1293
+ * collectImportedRefNames(petSchema)
1294
+ * // ['Category', 'Tag']
1295
+ * ```
1296
+ */
1297
+ const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1298
+ const seen = /* @__PURE__ */ new Set();
1299
+ const names = [];
1300
+ collectSync(node, { schema(child) {
1301
+ if (child.type !== "ref" || !child.ref) return;
1302
+ const name = resolveRefName(child);
1303
+ if (name && !seen.has(name)) {
1304
+ seen.add(name);
1305
+ names.push(name);
1306
+ }
1307
+ } });
1308
+ return names;
1309
+ });
1310
+ /**
1311
+ * Collects the names of all top-level schemas transitively used by a set of operations.
784
1312
  *
785
1313
  * An operation uses a schema when its parameters, request body, or responses reference it, directly
786
1314
  * or through other named schemas. Once a name is added to the result it is not revisited, so
@@ -974,25 +1502,14 @@ function inferDiscriminatorPropertyName(members) {
974
1502
  }
975
1503
  for (const name of candidates ?? []) {
976
1504
  const values = members.map((member) => {
977
- const literals = new Set(resolveSchemaProperties({
978
- node: member,
979
- propertyName: name
980
- }).flatMap((property) => getSchemaLiteralValues(property.schema)));
981
- return literals.size === 1 ? [...literals][0] : void 0;
982
- });
983
- if (values.every((value) => value !== void 0) && new Set(values).size === members.length) return name;
984
- }
985
- }
986
- //#endregion
987
- //#region src/nodes/base.ts
988
- /** Builds a `createX` factory for a node whose only computed field is `kind`. */
989
- function nodeFactory(kind) {
990
- return (input) => {
991
- return {
992
- ...input,
993
- kind
994
- };
995
- };
1505
+ const literals = new Set(resolveSchemaProperties({
1506
+ node: member,
1507
+ propertyName: name
1508
+ }).flatMap((property) => getSchemaLiteralValues(property.schema)));
1509
+ return literals.size === 1 ? [...literals][0] : void 0;
1510
+ });
1511
+ if (values.every((value) => value !== void 0) && new Set(values).size === members.length) return name;
1512
+ }
996
1513
  }
997
1514
  //#endregion
998
1515
  //#region src/nodes/code.ts
@@ -1005,7 +1522,7 @@ function nodeFactory(kind) {
1005
1522
  * // export const pets: Pet[] = ... as const
1006
1523
  * ```
1007
1524
  */
1008
- const createConst = nodeFactory("Const");
1525
+ const createConst = nodeFactory(nodeKinds.const);
1009
1526
  /**
1010
1527
  * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
1011
1528
  *
@@ -1015,7 +1532,7 @@ const createConst = nodeFactory("Const");
1015
1532
  * // export type Pet = ...
1016
1533
  * ```
1017
1534
  */
1018
- const createType = nodeFactory("Type");
1535
+ const createType = nodeFactory(nodeKinds.type);
1019
1536
  /**
1020
1537
  * Creates a `FunctionNode` representing a TypeScript `function` declaration.
1021
1538
  *
@@ -1025,7 +1542,7 @@ const createType = nodeFactory("Type");
1025
1542
  * // export async function fetchPet(): Promise<Pet> { ... }
1026
1543
  * ```
1027
1544
  */
1028
- const createFunction = nodeFactory("Function");
1545
+ const createFunction = nodeFactory(nodeKinds.function);
1029
1546
  /**
1030
1547
  * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
1031
1548
  *
@@ -1035,7 +1552,7 @@ const createFunction = nodeFactory("Function");
1035
1552
  * // export const double = (n: number) => ...
1036
1553
  * ```
1037
1554
  */
1038
- const createArrowFunction = nodeFactory("ArrowFunction");
1555
+ const createArrowFunction = nodeFactory(nodeKinds.arrowFunction);
1039
1556
  /**
1040
1557
  * Creates a {@link TextNode} representing a raw string fragment in the source output.
1041
1558
  *
@@ -1047,7 +1564,7 @@ const createArrowFunction = nodeFactory("ArrowFunction");
1047
1564
  */
1048
1565
  function createText(value) {
1049
1566
  return {
1050
- kind: "Text",
1567
+ kind: nodeKinds.text,
1051
1568
  value
1052
1569
  };
1053
1570
  }
@@ -1061,7 +1578,7 @@ function createText(value) {
1061
1578
  * ```
1062
1579
  */
1063
1580
  function createBreak() {
1064
- return { kind: "Break" };
1581
+ return { kind: nodeKinds.break };
1065
1582
  }
1066
1583
  /**
1067
1584
  * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
@@ -1074,7 +1591,7 @@ function createBreak() {
1074
1591
  */
1075
1592
  function createJsx(value) {
1076
1593
  return {
1077
- kind: "Jsx",
1594
+ kind: nodeKinds.jsx,
1078
1595
  value
1079
1596
  };
1080
1597
  }
@@ -1083,259 +1600,7 @@ function createJsx(value) {
1083
1600
  /**
1084
1601
  * Creates a `ContentNode` for a single request-body or response content type.
1085
1602
  */
1086
- const createContent = nodeFactory("Content");
1087
- //#endregion
1088
- //#region src/utils/combineFileMembers.ts
1089
- const IDENTIFIER_RUN = /[\w$]+/g;
1090
- /**
1091
- * How many imports a file needs before indexing the source beats scanning it once per name.
1092
- */
1093
- const INDEX_ABOVE_IMPORTS = 128;
1094
- /**
1095
- * Returns the local binding an import name item introduces: the alias when there is one, else the
1096
- * imported name.
1097
- */
1098
- function importLocalName(item) {
1099
- return typeof item === "string" ? item : item.name ?? item.propertyName;
1100
- }
1101
- /**
1102
- * Computes a multi-level sort key for exports and imports:
1103
- * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
1104
- */
1105
- function sortKey(node) {
1106
- const isArray = Array.isArray(node.name) ? "1" : "0";
1107
- const typeOnly = node.isTypeOnly ? "0" : "1";
1108
- const hasName = node.name != null ? "1" : "0";
1109
- const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
1110
- return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
1111
- }
1112
- /**
1113
- * Sorts members by {@link sortKey}. Keys are computed once per node, not per comparison.
1114
- */
1115
- function sortMembers(nodes) {
1116
- return nodes.map((node) => ({
1117
- node,
1118
- key: sortKey(node)
1119
- })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ node }) => node);
1120
- }
1121
- /**
1122
- * Returns a collector for array-named members. A member whose path and `isTypeOnly` were already
1123
- * seen folds its names into that entry, otherwise it joins `result` as a new entry.
1124
- */
1125
- function mergeByPath(result) {
1126
- const byPath = /* @__PURE__ */ new Map();
1127
- return ({ node, names }) => {
1128
- const key = `${node.path}:${node.isTypeOnly ?? false}`;
1129
- const existing = byPath.get(key);
1130
- if (existing) {
1131
- for (const name of names) {
1132
- if (existing.seen.has(name)) continue;
1133
- existing.seen.add(name);
1134
- existing.names.push(name);
1135
- }
1136
- return;
1137
- }
1138
- const item = {
1139
- ...node,
1140
- name: [...names]
1141
- };
1142
- result.push(item);
1143
- byPath.set(key, {
1144
- names: item.name,
1145
- seen: new Set(names)
1146
- });
1147
- };
1148
- }
1149
- /**
1150
- * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
1151
- * key. An unnamed source only drops when the same node appears twice, so markdown can repeat a
1152
- * paragraph or a table. Returns the deduplicated array in original order.
1153
- */
1154
- function combineSources(sources) {
1155
- const seen = /* @__PURE__ */ new Set();
1156
- return sources.filter((source) => {
1157
- const key = source.name != null ? `${source.name}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}` : source;
1158
- if (seen.has(key)) return false;
1159
- seen.add(key);
1160
- return true;
1161
- });
1162
- }
1163
- /**
1164
- * Deduplicates and merges `ExportNode` objects by path and type.
1165
- *
1166
- * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
1167
- * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
1168
- */
1169
- function combineExports(exports) {
1170
- const result = [];
1171
- const merge = mergeByPath(result);
1172
- const seen = /* @__PURE__ */ new Set();
1173
- for (const curr of sortMembers(exports)) {
1174
- const { name, path, isTypeOnly, asAlias } = curr;
1175
- if (Array.isArray(name)) {
1176
- if (name.length) merge({
1177
- node: curr,
1178
- names: [...new Set(name)]
1179
- });
1180
- continue;
1181
- }
1182
- const key = `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
1183
- if (!seen.has(key)) {
1184
- result.push(curr);
1185
- seen.add(key);
1186
- }
1187
- }
1188
- return result;
1189
- }
1190
- /**
1191
- * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
1192
- *
1193
- * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
1194
- * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
1195
- */
1196
- function combineImports(imports, exports, source) {
1197
- const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
1198
- const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
1199
- const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
1200
- const importNameMemo = /* @__PURE__ */ new Map();
1201
- const canonicalizeName = (n) => {
1202
- if (typeof n === "string") return n;
1203
- const key = `${n.propertyName}:${n.name ?? ""}`;
1204
- if (!importNameMemo.has(key)) importNameMemo.set(key, n);
1205
- return importNameMemo.get(key);
1206
- };
1207
- const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
1208
- for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);
1209
- const result = [];
1210
- const merge = mergeByPath(result);
1211
- const seen = /* @__PURE__ */ new Set();
1212
- for (const curr of sortMembers(imports)) {
1213
- if (curr.path === curr.root) continue;
1214
- const { name, path, isTypeOnly } = curr;
1215
- if (Array.isArray(name)) {
1216
- const names = [...new Set(name.map(canonicalizeName))].filter((item) => isUsed(importLocalName(item)));
1217
- if (names.length) merge({
1218
- node: curr,
1219
- names
1220
- });
1221
- continue;
1222
- }
1223
- if (!isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
1224
- const key = `${path}:${name}:${isTypeOnly ?? false}`;
1225
- if (!seen.has(key)) {
1226
- result.push(curr);
1227
- seen.add(key);
1228
- }
1229
- }
1230
- return result;
1231
- }
1232
- //#endregion
1233
- //#region src/nodes/file.ts
1234
- /**
1235
- * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
1236
- *
1237
- * @example Named import
1238
- * ```ts
1239
- * createImport({ name: ['useState'], path: 'react' })
1240
- * // import { useState } from 'react'
1241
- * ```
1242
- */
1243
- const createImport = nodeFactory("Import");
1244
- /**
1245
- * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
1246
- *
1247
- * @example Named export
1248
- * ```ts
1249
- * createExport({ name: ['Pet'], path: './Pet' })
1250
- * // export { Pet } from './Pet'
1251
- * ```
1252
- */
1253
- const createExport = nodeFactory("Export");
1254
- /**
1255
- * Creates a `SourceNode` representing a fragment of source code within a file.
1256
- *
1257
- * @example
1258
- * ```ts
1259
- * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
1260
- * ```
1261
- */
1262
- const createSource = nodeFactory("Source");
1263
- /**
1264
- * Creates a fully resolved `FileNode` from a file input descriptor.
1265
- *
1266
- * Computes:
1267
- * - `id` SHA256 hash of the file path
1268
- * - `name` `baseName` without extension
1269
- * - `extname` extension extracted from `baseName`
1270
- *
1271
- * Deduplicates:
1272
- * - `sources` via `combineSources`
1273
- * - `exports` via `combineExports`
1274
- * - `imports` via `combineImports` (also filters unused imports)
1275
- *
1276
- * @throws {Error} when `baseName` has no extension.
1277
- *
1278
- * @example
1279
- * ```ts
1280
- * const file = createFile({
1281
- * baseName: 'petStore.ts',
1282
- * path: 'src/models/petStore.ts',
1283
- * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
1284
- * imports: [createImport({ name: ['z'], path: 'zod' })],
1285
- * exports: [createExport({ name: ['Pet'], path: './petStore' })],
1286
- * })
1287
- * // file.id = SHA256 hash of 'src/models/petStore.ts'
1288
- * // file.name = 'petStore'
1289
- * // file.extname = '.ts'
1290
- * ```
1291
- *
1292
- * @example Copy a real file into the output
1293
- * ```ts
1294
- * const file = createFile({
1295
- * baseName: 'client.ts',
1296
- * path: 'src/gen/client.ts',
1297
- * copy: '/abs/path/to/templates/client.ts',
1298
- * })
1299
- * ```
1300
- */
1301
- function createFile(input) {
1302
- const extname = node_path.default.extname(input.baseName);
1303
- if (!extname) throw new Error(`No extname found for ${input.baseName}`);
1304
- const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
1305
- const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
1306
- const resolvedImports = (() => {
1307
- if (!input.imports?.length) return [];
1308
- const sourceParts = [];
1309
- const localNames = /* @__PURE__ */ new Set();
1310
- for (const item of resolvedSources) {
1311
- const extracted = item.nodes && extractStringsFromNodes(item.nodes);
1312
- if (extracted) sourceParts.push(extracted);
1313
- if (item.name) localNames.add(item.name);
1314
- }
1315
- const source = sourceParts.join("\n") || void 0;
1316
- return combineImports(input.imports, resolvedExports, source).flatMap((imp) => {
1317
- if (imp.path === input.path) return [];
1318
- if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
1319
- const kept = imp.name.filter((item) => !localNames.has(importLocalName(item)));
1320
- if (!kept.length) return [];
1321
- return [kept.length === imp.name.length ? imp : {
1322
- ...imp,
1323
- name: kept
1324
- }];
1325
- });
1326
- })();
1327
- return {
1328
- ...input,
1329
- kind: "File",
1330
- id: (0, node_crypto.hash)("sha256", input.path, "hex"),
1331
- name: node_path.default.basename(input.baseName, extname),
1332
- extname,
1333
- imports: resolvedImports,
1334
- exports: resolvedExports,
1335
- sources: resolvedSources,
1336
- meta: input.meta ?? {}
1337
- };
1338
- }
1603
+ const createContent = nodeFactory(nodeKinds.content);
1339
1604
  //#endregion
1340
1605
  //#region src/nodes/input.ts
1341
1606
  /**
@@ -1357,7 +1622,7 @@ function createInput(overrides = {}) {
1357
1622
  enumNames: []
1358
1623
  },
1359
1624
  ...overrides,
1360
- kind: "Input"
1625
+ kind: nodeKinds.input
1361
1626
  };
1362
1627
  }
1363
1628
  //#endregion
@@ -1365,7 +1630,7 @@ function createInput(overrides = {}) {
1365
1630
  /**
1366
1631
  * Creates a `RequestBodyNode`.
1367
1632
  */
1368
- const createRequestBody = nodeFactory("RequestBody");
1633
+ const createRequestBody = nodeFactory(nodeKinds.requestBody);
1369
1634
  //#endregion
1370
1635
  //#region src/nodes/operation.ts
1371
1636
  function createOperation(props) {
@@ -1376,7 +1641,7 @@ function createOperation(props) {
1376
1641
  parameters: [],
1377
1642
  responses: [],
1378
1643
  ...rest,
1379
- kind: "Operation",
1644
+ kind: nodeKinds.operation,
1380
1645
  ...isHttp ? { protocol: "http" } : {},
1381
1646
  requestBody: requestBody ? createRequestBody(requestBody) : void 0
1382
1647
  };
@@ -1403,7 +1668,7 @@ function createParameter(input) {
1403
1668
  ...input,
1404
1669
  required,
1405
1670
  schema: optionality(input.schema, required),
1406
- kind: "Parameter"
1671
+ kind: nodeKinds.parameter
1407
1672
  };
1408
1673
  }
1409
1674
  //#endregion
@@ -1428,7 +1693,7 @@ function createProperty(input) {
1428
1693
  ...input,
1429
1694
  required,
1430
1695
  schema: optionality(input.schema, required),
1431
- kind: "Property"
1696
+ kind: nodeKinds.property
1432
1697
  };
1433
1698
  }
1434
1699
  //#endregion
@@ -1455,7 +1720,7 @@ function createResponse(props) {
1455
1720
  return {
1456
1721
  ...rest,
1457
1722
  content: entries,
1458
- kind: "Response"
1723
+ kind: nodeKinds.response
1459
1724
  };
1460
1725
  }
1461
1726
  //#endregion
@@ -1490,12 +1755,12 @@ function createSchema(props) {
1490
1755
  properties: [],
1491
1756
  primitive: "object",
1492
1757
  ...props,
1493
- kind: "Schema"
1758
+ kind: nodeKinds.schema
1494
1759
  };
1495
1760
  return {
1496
1761
  primitive: TYPE_TO_PRIMITIVE[props.type],
1497
1762
  ...props,
1498
- kind: "Schema"
1763
+ kind: nodeKinds.schema
1499
1764
  };
1500
1765
  }
1501
1766
  //#endregion
@@ -1538,14 +1803,21 @@ var src_exports = /* @__PURE__ */ __exportAll({
1538
1803
  factory: () => nodes_exports,
1539
1804
  findCircularSchemas: () => findCircularSchemas,
1540
1805
  findCircularSchemasFromGraph: () => findCircularSchemasFromGraph,
1806
+ findSchema: () => findSchema,
1807
+ forEachChild: () => forEachChild,
1808
+ forEachSchema: () => forEachSchema,
1541
1809
  getSchemaLiteralValues: () => getSchemaLiteralValues,
1542
1810
  inferDiscriminatorPropertyName: () => inferDiscriminatorPropertyName,
1811
+ inlineFiles: () => inlineFiles,
1543
1812
  isBareRef: () => isBareRef,
1544
1813
  isHttpOperationNode: () => isHttpOperationNode,
1814
+ isVirtualFile: () => isVirtualFile,
1545
1815
  mergeRefWithSchema: () => mergeRefWithSchema,
1546
1816
  narrowSchema: () => narrowSchema,
1817
+ nodeKinds: () => nodeKinds,
1547
1818
  optionality: () => optionality,
1548
1819
  refName: () => refName,
1820
+ resolveRef: () => resolveRef,
1549
1821
  resolveRefName: () => resolveRefName,
1550
1822
  resolveSchemaProperties: () => resolveSchemaProperties,
1551
1823
  schemaTypes: () => schemaTypes,
@@ -1576,14 +1848,21 @@ Object.defineProperty(exports, "factory", {
1576
1848
  });
1577
1849
  exports.findCircularSchemas = findCircularSchemas;
1578
1850
  exports.findCircularSchemasFromGraph = findCircularSchemasFromGraph;
1851
+ exports.findSchema = findSchema;
1852
+ exports.forEachChild = forEachChild;
1853
+ exports.forEachSchema = forEachSchema;
1579
1854
  exports.getSchemaLiteralValues = getSchemaLiteralValues;
1580
1855
  exports.inferDiscriminatorPropertyName = inferDiscriminatorPropertyName;
1856
+ exports.inlineFiles = inlineFiles;
1581
1857
  exports.isBareRef = isBareRef;
1582
1858
  exports.isHttpOperationNode = isHttpOperationNode;
1859
+ exports.isVirtualFile = isVirtualFile;
1583
1860
  exports.mergeRefWithSchema = mergeRefWithSchema;
1584
1861
  exports.narrowSchema = narrowSchema;
1862
+ exports.nodeKinds = nodeKinds;
1585
1863
  exports.optionality = optionality;
1586
1864
  exports.refName = refName;
1865
+ exports.resolveRef = resolveRef;
1587
1866
  exports.resolveRefName = resolveRefName;
1588
1867
  exports.resolveSchemaProperties = resolveSchemaProperties;
1589
1868
  exports.schemaTypes = schemaTypes;