@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.js CHANGED
@@ -1,5 +1,5 @@
1
- import path from "node:path";
2
1
  import { hash } from "node:crypto";
2
+ import path from "node:path";
3
3
  //#region \0rolldown/runtime.js
4
4
  var __defProp = Object.defineProperty;
5
5
  var __exportAll = (all, no_symbols) => {
@@ -14,6 +14,30 @@ var __exportAll = (all, no_symbols) => {
14
14
  //#endregion
15
15
  //#region src/constants.ts
16
16
  /**
17
+ * Node kind discriminators used by every AST node (for example `node.kind === nodeKinds.text`).
18
+ */
19
+ const nodeKinds = {
20
+ input: "Input",
21
+ operation: "Operation",
22
+ schema: "Schema",
23
+ property: "Property",
24
+ parameter: "Parameter",
25
+ response: "Response",
26
+ requestBody: "RequestBody",
27
+ content: "Content",
28
+ type: "Type",
29
+ file: "File",
30
+ import: "Import",
31
+ export: "Export",
32
+ source: "Source",
33
+ const: "Const",
34
+ function: "Function",
35
+ arrowFunction: "ArrowFunction",
36
+ text: "Text",
37
+ break: "Break",
38
+ jsx: "Jsx"
39
+ };
40
+ /**
17
41
  * Schema type discriminators used by all AST schema nodes.
18
42
  *
19
43
  * Each value is a stable discriminator across the AST (for example `schema.type === schemaTypes.object`).
@@ -174,6 +198,7 @@ const VISITOR_KEYS = {
174
198
  "members",
175
199
  "additionalProperties",
176
200
  "patternProperties",
201
+ "propertyNames",
177
202
  "rest"
178
203
  ],
179
204
  Property: ["schema"],
@@ -221,25 +246,101 @@ function isNodeRecord(value) {
221
246
  * ```
222
247
  */
223
248
  function getChildren(node, recurse) {
249
+ if (node.kind === nodeKinds.schema && !recurse) return [];
224
250
  const children = [];
225
- const keys = node.kind === "Schema" && !recurse ? void 0 : VISITOR_KEYS[node.kind];
226
- if (!keys) return children;
251
+ visitChildNodes(node, (child) => {
252
+ children.push(child);
253
+ return false;
254
+ });
255
+ return children;
256
+ }
257
+ /**
258
+ * Visits each direct child node in the AST's traversal order.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * forEachChild(operationNode, (child) => {
263
+ * console.log(child.kind)
264
+ * })
265
+ * ```
266
+ */
267
+ function forEachChild(node, visit) {
268
+ visitChildNodes(node, (child) => {
269
+ visit(child);
270
+ return false;
271
+ });
272
+ }
273
+ function visitChildNodes(node, visit) {
274
+ const keys = VISITOR_KEYS[node.kind];
275
+ if (!keys) return false;
227
276
  const record = node;
228
277
  for (const key of keys) {
229
278
  const value = record[key];
230
279
  if (Array.isArray(value)) {
231
- for (const item of value) if (isNode(item)) children.push(item);
280
+ for (const item of value) if (isNode(item) && visit(item)) return true;
232
281
  continue;
233
282
  }
234
283
  if (isNode(value)) {
235
- children.push(value);
284
+ if (visit(value)) return true;
236
285
  continue;
237
286
  }
238
- if (isNodeRecord(value)) {
239
- for (const item of Object.values(value)) if (isNode(item)) children.push(item);
287
+ if (isNodeRecord(value)) for (const name in value) {
288
+ if (!Object.hasOwn(value, name)) continue;
289
+ const item = value[name];
290
+ if (isNode(item) && visit(item)) return true;
240
291
  }
241
292
  }
242
- return children;
293
+ return false;
294
+ }
295
+ /**
296
+ * Visits each schema in a node's child tree in depth-first order. Includes `node` when it is a schema.
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * forEachSchema(operationNode, (schema) => {
301
+ * console.log(schema.type)
302
+ * })
303
+ * ```
304
+ */
305
+ function forEachSchema(node, visit) {
306
+ const walk = (current) => {
307
+ if (current.kind === nodeKinds.schema) visit(current);
308
+ forEachChild(current, walk);
309
+ };
310
+ walk(node);
311
+ }
312
+ /**
313
+ * Finds the first schema that matches `predicate` in depth-first order. Set `followRefs` to include resolved ref targets.
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * const firstString = findSchema(operationNode, {
318
+ * predicate: (schema) => schema.type === 'string',
319
+ * })
320
+ * ```
321
+ */
322
+ function findSchema(node, { predicate, followRefs = false }) {
323
+ const seen = followRefs ? /* @__PURE__ */ new WeakSet() : void 0;
324
+ const walk = (current) => {
325
+ if (current.kind === nodeKinds.schema) {
326
+ if (predicate(current)) return current;
327
+ if (seen) {
328
+ if (seen.has(current)) return void 0;
329
+ seen.add(current);
330
+ if (current.type === "ref" && current.schema) {
331
+ const found = walk(current.schema);
332
+ if (found) return found;
333
+ }
334
+ }
335
+ }
336
+ let found;
337
+ visitChildNodes(current, (child) => {
338
+ found = walk(child);
339
+ return found !== void 0;
340
+ });
341
+ if (found) return found;
342
+ };
343
+ return walk(node);
243
344
  }
244
345
  /**
245
346
  * Runs the visitor callback that matches `node.kind` with the traversal
@@ -297,7 +398,7 @@ function transformNode(node, visitor, recurse, parent) {
297
398
  * (e.g. `additionalProperties: true`) intact.
298
399
  */
299
400
  function transformChildren(node, visitor, recurse) {
300
- const keys = node.kind === "Schema" && !recurse ? void 0 : VISITOR_KEYS[node.kind];
401
+ const keys = node.kind === nodeKinds.schema && !recurse ? void 0 : VISITOR_KEYS[node.kind];
301
402
  if (!keys) return node;
302
403
  const record = node;
303
404
  let updates;
@@ -582,11 +683,11 @@ function extractStringsFromNodes(nodes) {
582
683
  if (node) collected.push(node);
583
684
  continue;
584
685
  }
585
- if (node.kind === "Text" || node.kind === "Jsx") {
686
+ if (node.kind === nodeKinds.text || node.kind === nodeKinds.jsx) {
586
687
  if (node.value) collected.push(node.value);
587
688
  continue;
588
689
  }
589
- if (node.kind === "Break") continue;
690
+ if (node.kind === nodeKinds.break) continue;
590
691
  const parts = [];
591
692
  if ("params" in node && node.params) parts.push(node.params);
592
693
  if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
@@ -599,167 +700,594 @@ function extractStringsFromNodes(nodes) {
599
700
  return collected.join("\n");
600
701
  }
601
702
  //#endregion
602
- //#region src/utils/refs.ts
603
- /** Returns the last segment of a `$ref` pointer, for example `'Pet'` for `#/components/schemas/Pet`. */
604
- function refName(ref) {
605
- return ref.split("/").at(-1) ?? ref;
606
- }
703
+ //#region src/utils/combineFileMembers.ts
704
+ const IDENTIFIER_RUN = /[\w$]+/g;
607
705
  /**
608
- * Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
609
- * the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
610
- * then `name`, then the nested `schema.name`.
611
- *
612
- * Returns `null` for non-ref nodes or when no name resolves.
613
- *
614
- * @example
615
- * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'`
616
- *
617
- * @example Collision-renamed target
618
- * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Order', targetName: 'OrderSchema' }) // 'OrderSchema'`
706
+ * How many imports a file needs before indexing the source beats scanning it once per name.
619
707
  */
620
- function resolveRefName(node) {
621
- if (!node || node.type !== "ref") return null;
622
- if (node.targetName) return node.targetName;
623
- if (node.ref) return refName(node.ref);
624
- return node.name ?? node.schema?.name ?? null;
708
+ const INDEX_ABOVE_IMPORTS = 128;
709
+ /**
710
+ * Returns the local binding an import name item introduces: the alias when there is one, else the
711
+ * imported name.
712
+ */
713
+ function importLocalName(item) {
714
+ return typeof item === "string" ? item : item.name ?? item.propertyName;
625
715
  }
626
- /** Fields of a ref node that describe the ref itself, so they never override the resolved schema. */
627
- const REF_OWN_KEYS = /* @__PURE__ */ new Set([
628
- "kind",
629
- "type",
630
- "name",
631
- "ref",
632
- "schema"
633
- ]);
634
716
  /**
635
- * Merges a ref node with its resolved schema, giving the fields set at the usage site precedence.
636
- *
637
- * Every field on the ref except `kind`, `type`, `name`, `ref`, and `schema` overrides the same field
638
- * of the resolved `schema` (for example `description`, `nullable`, `readOnly`, `deprecated`). Fields
639
- * left `undefined` on the ref do not shadow the resolved schema. A ref without a resolved `schema`
640
- * is returned as is.
641
- *
642
- * @example
643
- * ```ts
644
- * const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A pet', schema: petSchema })
645
- * mergeRefWithSchema(ref) // petSchema's fields with description 'A pet'
646
- * ```
717
+ * Computes a multi-level sort key for exports and imports:
718
+ * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
647
719
  */
648
- function mergeRefWithSchema(node) {
649
- if (!node.schema) return node;
650
- const merged = { ...node.schema };
651
- for (const key of Object.keys(node)) {
652
- const value = node[key];
653
- if (value !== void 0 && !REF_OWN_KEYS.has(key)) merged[key] = value;
654
- }
655
- return merged;
720
+ function sortKey(node) {
721
+ const isArray = Array.isArray(node.name) ? "1" : "0";
722
+ const typeOnly = node.isTypeOnly ? "0" : "1";
723
+ const hasName = node.name != null ? "1" : "0";
724
+ const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
725
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
656
726
  }
657
727
  /**
658
- * Returns `true` when a schema is a bare ref without modifiers, metadata, or omit keys.
659
- *
660
- * Modifiers inherited from the resolved schema are included in the check because a ref emits the
661
- * referenced schema variable with those modifiers already applied.
728
+ * Sorts members by {@link sortKey}. Keys are computed once per node, not per comparison.
662
729
  */
663
- function isBareRef(node, keysToOmit) {
664
- if (!node || node.type !== "ref") return false;
665
- const meta = mergeRefWithSchema(node);
666
- return !meta.nullable && !meta.optional && !meta.nullish && meta.default === void 0 && !meta.description && !meta.examples?.length && !keysToOmit?.length;
730
+ function sortMembers(nodes) {
731
+ return nodes.map((node) => ({
732
+ node,
733
+ key: sortKey(node)
734
+ })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ node }) => node);
667
735
  }
668
- //#endregion
669
- //#region ../../internals/utils/src/promise.ts
670
736
  /**
671
- * Wraps `factory` with a keyed cache backed by the provided store.
672
- *
673
- * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
674
- * collected) or a `Map` for primitive keys. For multi-argument functions,
675
- * nest two `memoize` calls — the outer keyed by the first argument, the
676
- * inner (created once per outer miss) keyed by the second.
677
- *
678
- * Because the cache is owned by the caller, it can be shared, inspected, or
679
- * cleared independently of the memoized function.
680
- *
681
- * @example Single WeakMap key
682
- * ```ts
683
- * const cache = new WeakMap<SchemaNode, Set<string>>()
684
- * const getRefs = memoize(cache, (node) => collectRefs(node))
685
- * ```
686
- *
687
- * @example Single Map key (primitive)
688
- * ```ts
689
- * const cache = new Map<string, Resolver>()
690
- * const getResolver = memoize(cache, (name) => buildResolver(name))
691
- * ```
692
- *
693
- * @example Two-level (object + primitive)
694
- * ```ts
695
- * const outer = new WeakMap<Params[], Map<string, Params[]>>()
696
- * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
697
- * fn(params)('camelcase')
698
- * ```
737
+ * Returns a collector for array-named members. A member whose path and `isTypeOnly` were already
738
+ * seen folds its names into that entry, otherwise it joins `result` as a new entry.
699
739
  */
700
- function memoize(store, factory) {
701
- return (key) => {
702
- if (store.has(key)) return store.get(key);
703
- const value = factory(key);
704
- store.set(key, value);
705
- return value;
740
+ function mergeByPath(result) {
741
+ const byPath = /* @__PURE__ */ new Map();
742
+ return ({ node, names }) => {
743
+ const key = `${node.path}:${node.isTypeOnly ?? false}`;
744
+ const existing = byPath.get(key);
745
+ if (existing) {
746
+ for (const name of names) {
747
+ if (existing.seen.has(name)) continue;
748
+ existing.seen.add(name);
749
+ existing.names.push(name);
750
+ }
751
+ return;
752
+ }
753
+ const item = {
754
+ ...node,
755
+ name: [...names]
756
+ };
757
+ result.push(item);
758
+ byPath.set(key, {
759
+ names: item.name,
760
+ seen: new Set(names)
761
+ });
706
762
  };
707
763
  }
708
- //#endregion
709
- //#region src/utils/schemaGraph.ts
710
764
  /**
711
- * Collects the names of every schema a node's subtree references, resolved with `resolveRefName`.
712
- * Each ref contributes its name only, so the schema it points to is never traversed. The walk is
713
- * memoized by node identity, which lets `findCircularSchemas` and `containsCircularRef` share one
714
- * scan per schema.
715
- *
716
- * @example
717
- * ```ts
718
- * collectSchemaRefs(petSchema)
719
- * // Set { 'Category', 'Tag' }
720
- * ```
765
+ * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
766
+ * key. An unnamed source only drops when the same node appears twice, so markdown can repeat a
767
+ * paragraph or a table. Returns the deduplicated array in original order.
721
768
  */
722
- const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
723
- const refs = /* @__PURE__ */ new Set();
724
- collectSync(node, { schema(child) {
725
- if (child.type === "ref") {
726
- const name = resolveRefName(child);
727
- if (name) refs.add(name);
728
- }
729
- } });
730
- return refs;
731
- });
769
+ function combineSources(sources) {
770
+ const seen = /* @__PURE__ */ new Set();
771
+ return sources.filter((source) => {
772
+ const key = source.name != null ? `${source.name}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}` : source;
773
+ if (seen.has(key)) return false;
774
+ seen.add(key);
775
+ return true;
776
+ });
777
+ }
732
778
  /**
733
- * Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
734
- * first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
735
- * `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
736
- * re-scanning the same schema per plugin.
737
- *
738
- * Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
739
- * same file (a union member created by name) is left out. That leaves exactly the set
740
- * `resolver.imports` emits.
779
+ * Deduplicates and merges `ExportNode` objects by path and type.
741
780
  *
742
- * @example
743
- * ```ts
744
- * collectImportedRefNames(petSchema)
745
- * // ['Category', 'Tag']
746
- * ```
781
+ * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
782
+ * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
747
783
  */
748
- const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
784
+ function combineExports(exports) {
785
+ const result = [];
786
+ const merge = mergeByPath(result);
749
787
  const seen = /* @__PURE__ */ new Set();
750
- const names = [];
751
- collectSync(node, { schema(child) {
752
- if (child.type !== "ref" || !child.ref) return;
753
- const name = resolveRefName(child);
754
- if (name && !seen.has(name)) {
755
- seen.add(name);
756
- names.push(name);
788
+ for (const curr of sortMembers(exports)) {
789
+ const { name, path, isTypeOnly, asAlias } = curr;
790
+ if (Array.isArray(name)) {
791
+ if (name.length) merge({
792
+ node: curr,
793
+ names: [...new Set(name)]
794
+ });
795
+ continue;
757
796
  }
758
- } });
759
- return names;
760
- });
761
- /**
762
- * Collects the names of all top-level schemas transitively used by a set of operations.
797
+ const key = `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
798
+ if (!seen.has(key)) {
799
+ result.push(curr);
800
+ seen.add(key);
801
+ }
802
+ }
803
+ return result;
804
+ }
805
+ /**
806
+ * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
807
+ *
808
+ * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
809
+ * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
810
+ */
811
+ function combineImports(imports, exports, source) {
812
+ const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
813
+ const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
814
+ const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
815
+ const importNameMemo = /* @__PURE__ */ new Map();
816
+ const canonicalizeName = (n) => {
817
+ if (typeof n === "string") return n;
818
+ const key = `${n.propertyName}:${n.name ?? ""}`;
819
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n);
820
+ return importNameMemo.get(key);
821
+ };
822
+ const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
823
+ for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);
824
+ const result = [];
825
+ const merge = mergeByPath(result);
826
+ const seen = /* @__PURE__ */ new Set();
827
+ for (const curr of sortMembers(imports)) {
828
+ if (curr.path === curr.root) continue;
829
+ const { name, path, isTypeOnly } = curr;
830
+ if (Array.isArray(name)) {
831
+ const names = [...new Set(name.map(canonicalizeName))].filter((item) => isUsed(importLocalName(item)));
832
+ if (names.length) merge({
833
+ node: curr,
834
+ names
835
+ });
836
+ continue;
837
+ }
838
+ if (!isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
839
+ const key = `${path}:${name}:${isTypeOnly ?? false}`;
840
+ if (!seen.has(key)) {
841
+ result.push(curr);
842
+ seen.add(key);
843
+ }
844
+ }
845
+ return result;
846
+ }
847
+ //#endregion
848
+ //#region src/nodes/base.ts
849
+ /** Builds a `createX` factory for a node whose only computed field is `kind`. */
850
+ function nodeFactory(kind) {
851
+ return (input) => {
852
+ return {
853
+ ...input,
854
+ kind
855
+ };
856
+ };
857
+ }
858
+ //#endregion
859
+ //#region src/nodes/file.ts
860
+ /**
861
+ * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
862
+ *
863
+ * @example Named import
864
+ * ```ts
865
+ * createImport({ name: ['useState'], path: 'react' })
866
+ * // import { useState } from 'react'
867
+ * ```
868
+ */
869
+ const createImport = nodeFactory(nodeKinds.import);
870
+ /**
871
+ * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
872
+ *
873
+ * @example Named export
874
+ * ```ts
875
+ * createExport({ name: ['Pet'], path: './Pet' })
876
+ * // export { Pet } from './Pet'
877
+ * ```
878
+ */
879
+ const createExport = nodeFactory(nodeKinds.export);
880
+ /**
881
+ * Creates a `SourceNode` representing a fragment of source code within a file.
882
+ *
883
+ * @example
884
+ * ```ts
885
+ * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
886
+ * ```
887
+ */
888
+ const createSource = nodeFactory(nodeKinds.source);
889
+ /**
890
+ * Creates a fully resolved `FileNode` from a file input descriptor.
891
+ *
892
+ * Computes:
893
+ * - `id` SHA256 hash of the file path
894
+ * - `name` `baseName` without extension
895
+ * - `extname` extension extracted from `baseName`
896
+ *
897
+ * Deduplicates:
898
+ * - `sources` via `combineSources`
899
+ * - `exports` via `combineExports`
900
+ * - `imports` via `combineImports` (also filters unused imports)
901
+ *
902
+ * @throws {Error} when `baseName` has no extension.
903
+ *
904
+ * @example
905
+ * ```ts
906
+ * const file = createFile({
907
+ * baseName: 'petStore.ts',
908
+ * path: 'src/models/petStore.ts',
909
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
910
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
911
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
912
+ * })
913
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
914
+ * // file.name = 'petStore'
915
+ * // file.extname = '.ts'
916
+ * ```
917
+ *
918
+ * @example Copy a real file into the output
919
+ * ```ts
920
+ * const file = createFile({
921
+ * baseName: 'client.ts',
922
+ * path: 'src/gen/client.ts',
923
+ * copy: '/abs/path/to/templates/client.ts',
924
+ * })
925
+ * ```
926
+ */
927
+ function createFile(input) {
928
+ const extname = path.extname(input.baseName);
929
+ if (!extname) throw new Error(`No extname found for ${input.baseName}`);
930
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
931
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
932
+ const resolvedImports = (() => {
933
+ if (!input.imports?.length) return [];
934
+ const sourceParts = [];
935
+ const localNames = /* @__PURE__ */ new Set();
936
+ for (const item of resolvedSources) {
937
+ const extracted = item.nodes && extractStringsFromNodes(item.nodes);
938
+ if (extracted) sourceParts.push(extracted);
939
+ if (item.name) localNames.add(item.name);
940
+ }
941
+ const source = sourceParts.join("\n") || void 0;
942
+ return combineImports(input.imports, resolvedExports, source).flatMap((imp) => {
943
+ if (imp.path === input.path) return [];
944
+ if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
945
+ const kept = imp.name.filter((item) => !localNames.has(importLocalName(item)));
946
+ if (!kept.length) return [];
947
+ return [kept.length === imp.name.length ? imp : {
948
+ ...imp,
949
+ name: kept
950
+ }];
951
+ });
952
+ })();
953
+ return {
954
+ ...input,
955
+ kind: nodeKinds.file,
956
+ id: hash("sha256", input.path, "hex"),
957
+ name: path.basename(input.baseName, extname),
958
+ extname,
959
+ imports: resolvedImports,
960
+ exports: resolvedExports,
961
+ sources: resolvedSources,
962
+ meta: input.meta ?? {}
963
+ };
964
+ }
965
+ //#endregion
966
+ //#region src/utils/inline.ts
967
+ /**
968
+ * Whether every source of `file` is inline. Other files absorb such a file, so it is never written.
969
+ */
970
+ function isVirtualFile(file) {
971
+ return file.sources.length > 0 && file.sources.every((source) => source.inline);
972
+ }
973
+ /**
974
+ * Replaces every name a file imports from a virtual file (see {@link isVirtualFile}) with that name's
975
+ * `inline.expression`, until no inline names are left. The definitions' own imports move to the importing file.
976
+ */
977
+ function inlineFiles(files, { language }) {
978
+ const virtualFiles = new Map(files.filter(isVirtualFile).map((file) => [file.path, file]));
979
+ const result = {
980
+ files: [],
981
+ virtualPaths: [...virtualFiles.keys()],
982
+ problems: []
983
+ };
984
+ if (virtualFiles.size === 0) return result;
985
+ const definitions = /* @__PURE__ */ new Map();
986
+ function define(path, name, from, lang) {
987
+ const key = `${path}\0${name}`;
988
+ if (definitions.has(key)) {
989
+ const definition = definitions.get(key);
990
+ if (definition === null) result.problems.push({
991
+ path: from,
992
+ message: `"${name}" from "${path}" is defined in terms of itself.`
993
+ });
994
+ return definition ?? void 0;
995
+ }
996
+ const file = virtualFiles.get(path);
997
+ const inline = file.sources.find((source) => source.name === name)?.inline;
998
+ if (!inline) {
999
+ result.problems.push({
1000
+ path: from,
1001
+ message: `"${name}" is imported from "${path}", which never writes it and has no inline definition for it.`
1002
+ });
1003
+ return;
1004
+ }
1005
+ definitions.set(key, null);
1006
+ const imports = [];
1007
+ const { text, used } = replaceNames(inline.expression, file, lang, imports);
1008
+ imports.push(...file.imports.flatMap((imp) => virtualFiles.has(imp.path) ? [] : pickNames(imp, used)));
1009
+ const definition = {
1010
+ expression: text,
1011
+ imports
1012
+ };
1013
+ definitions.set(key, definition);
1014
+ return definition;
1015
+ }
1016
+ function replaceNames(input, file, lang, imports) {
1017
+ const used = /* @__PURE__ */ new Set();
1018
+ return {
1019
+ text: lang.replaceIdentifiers(input, (local) => {
1020
+ const target = findTarget(file, local);
1021
+ const definition = target && define(target.path, target.name, file.path, lang);
1022
+ if (!definition) {
1023
+ used.add(local);
1024
+ return;
1025
+ }
1026
+ imports.push(...definition.imports);
1027
+ return lang.wrap(definition.expression);
1028
+ }),
1029
+ used
1030
+ };
1031
+ }
1032
+ function findTarget(file, local) {
1033
+ if (virtualFiles.has(file.path) && file.sources.some((source) => source.name === local)) return {
1034
+ path: file.path,
1035
+ name: local
1036
+ };
1037
+ for (const imp of file.imports) {
1038
+ const item = virtualFiles.has(imp.path) && [imp.name].flat().find((item) => importLocalName(item) === local);
1039
+ if (item) return {
1040
+ path: imp.path,
1041
+ name: typeof item === "string" ? item : item.propertyName
1042
+ };
1043
+ }
1044
+ }
1045
+ for (const file of files) {
1046
+ if (virtualFiles.has(file.path) || !file.imports.some((imp) => virtualFiles.has(imp.path))) continue;
1047
+ const lang = language(file);
1048
+ if (!lang) {
1049
+ result.problems.push({
1050
+ path: file.path,
1051
+ message: `No parser for "${file.extname}" files supports inline sources.`
1052
+ });
1053
+ continue;
1054
+ }
1055
+ const carried = [];
1056
+ const sources = file.sources.map((source) => ({
1057
+ ...source,
1058
+ nodes: rewriteNodes(source.nodes, (text) => replaceNames(text, file, lang, carried).text)
1059
+ }));
1060
+ const imports = [...file.imports.filter((imp) => !virtualFiles.has(imp.path)), ...carried.map((imp) => imp.root ? {
1061
+ ...imp,
1062
+ root: file.path
1063
+ } : imp)];
1064
+ result.files.push(createFile({
1065
+ ...file,
1066
+ sources,
1067
+ imports
1068
+ }));
1069
+ }
1070
+ return result;
1071
+ }
1072
+ function pickNames(imp, used) {
1073
+ if (!Array.isArray(imp.name)) return used.has(imp.name) ? [imp] : [];
1074
+ const name = imp.name.filter((item) => used.has(importLocalName(item)));
1075
+ return name.length > 0 ? [{
1076
+ ...imp,
1077
+ name
1078
+ }] : [];
1079
+ }
1080
+ function rewriteNodes(nodes, rewrite) {
1081
+ return nodes?.map((node) => {
1082
+ if (typeof node === "string") return rewrite(node);
1083
+ switch (node.kind) {
1084
+ case nodeKinds.break: return node;
1085
+ case nodeKinds.text:
1086
+ case nodeKinds.jsx: return {
1087
+ ...node,
1088
+ value: rewrite(node.value)
1089
+ };
1090
+ case nodeKinds.function:
1091
+ case nodeKinds.arrowFunction: return {
1092
+ ...node,
1093
+ params: node.params && rewrite(node.params),
1094
+ generics: Array.isArray(node.generics) ? node.generics.map(rewrite) : node.generics && rewrite(node.generics),
1095
+ returnType: node.returnType && rewrite(node.returnType),
1096
+ nodes: rewriteNodes(node.nodes, rewrite)
1097
+ };
1098
+ case nodeKinds.const: return {
1099
+ ...node,
1100
+ type: node.type && rewrite(node.type),
1101
+ nodes: rewriteNodes(node.nodes, rewrite)
1102
+ };
1103
+ default: return {
1104
+ ...node,
1105
+ nodes: rewriteNodes(node.nodes, rewrite)
1106
+ };
1107
+ }
1108
+ });
1109
+ }
1110
+ //#endregion
1111
+ //#region src/utils/refs.ts
1112
+ /** Returns the last segment of a `$ref` pointer, for example `'Pet'` for `#/components/schemas/Pet`. */
1113
+ function refName(ref) {
1114
+ return ref.split("/").at(-1) ?? ref;
1115
+ }
1116
+ /**
1117
+ * Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
1118
+ * the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
1119
+ * then `name`, then the nested `schema.name`.
1120
+ *
1121
+ * Returns `null` for non-ref nodes or when no name resolves.
1122
+ *
1123
+ * @example
1124
+ * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'`
1125
+ *
1126
+ * @example Collision-renamed target
1127
+ * `resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Order', targetName: 'OrderSchema' }) // 'OrderSchema'`
1128
+ */
1129
+ function resolveRefName(node) {
1130
+ if (!node || node.type !== "ref") return null;
1131
+ if (node.targetName) return node.targetName;
1132
+ if (node.ref) return refName(node.ref);
1133
+ return node.name ?? node.schema?.name ?? null;
1134
+ }
1135
+ /**
1136
+ * Follows a resolved ref chain to its target schema. Returns an unresolved or cyclic ref as is.
1137
+ *
1138
+ * @example
1139
+ * ```ts
1140
+ * const target = resolveRef(refSchema)
1141
+ * ```
1142
+ */
1143
+ function resolveRef(node) {
1144
+ if (node.type !== "ref" || !node.schema) return node;
1145
+ const seen = /* @__PURE__ */ new WeakSet();
1146
+ let current = node;
1147
+ while (current.type === "ref" && current.schema) {
1148
+ if (seen.has(current)) return current;
1149
+ seen.add(current);
1150
+ current = current.schema;
1151
+ }
1152
+ return current;
1153
+ }
1154
+ /** Fields of a ref node that describe the ref itself, so they never override the resolved schema. */
1155
+ const REF_OWN_KEYS = /* @__PURE__ */ new Set([
1156
+ "kind",
1157
+ "type",
1158
+ "name",
1159
+ "ref",
1160
+ "schema"
1161
+ ]);
1162
+ /**
1163
+ * Merges a ref node with its resolved schema, giving the fields set at the usage site precedence.
1164
+ *
1165
+ * Every field on the ref except `kind`, `type`, `name`, `ref`, and `schema` overrides the same field
1166
+ * of the resolved `schema` (for example `description`, `nullable`, `readOnly`, `deprecated`). Fields
1167
+ * left `undefined` on the ref do not shadow the resolved schema. A ref without a resolved `schema`
1168
+ * is returned as is.
1169
+ *
1170
+ * @example
1171
+ * ```ts
1172
+ * const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A pet', schema: petSchema })
1173
+ * mergeRefWithSchema(ref) // petSchema's fields with description 'A pet'
1174
+ * ```
1175
+ */
1176
+ function mergeRefWithSchema(node) {
1177
+ if (!node.schema) return node;
1178
+ const merged = { ...node.schema };
1179
+ for (const key of Object.keys(node)) {
1180
+ const value = node[key];
1181
+ if (value !== void 0 && !REF_OWN_KEYS.has(key)) merged[key] = value;
1182
+ }
1183
+ return merged;
1184
+ }
1185
+ /**
1186
+ * Returns `true` when a schema is a bare ref without modifiers, metadata, or omit keys.
1187
+ *
1188
+ * Modifiers inherited from the resolved schema are included in the check because a ref emits the
1189
+ * referenced schema variable with those modifiers already applied.
1190
+ */
1191
+ function isBareRef(node, keysToOmit) {
1192
+ if (!node || node.type !== "ref") return false;
1193
+ const meta = mergeRefWithSchema(node);
1194
+ return !meta.nullable && !meta.optional && !meta.nullish && meta.default === void 0 && !meta.description && !meta.examples?.length && !keysToOmit?.length;
1195
+ }
1196
+ //#endregion
1197
+ //#region ../../internals/utils/src/promise.ts
1198
+ /**
1199
+ * Wraps `factory` with a keyed cache backed by the provided store.
1200
+ *
1201
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
1202
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
1203
+ * nest two `memoize` calls — the outer keyed by the first argument, the
1204
+ * inner (created once per outer miss) keyed by the second.
1205
+ *
1206
+ * Because the cache is owned by the caller, it can be shared, inspected, or
1207
+ * cleared independently of the memoized function.
1208
+ *
1209
+ * @example Single WeakMap key
1210
+ * ```ts
1211
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
1212
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
1213
+ * ```
1214
+ *
1215
+ * @example Single Map key (primitive)
1216
+ * ```ts
1217
+ * const cache = new Map<string, Resolver>()
1218
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
1219
+ * ```
1220
+ *
1221
+ * @example Two-level (object + primitive)
1222
+ * ```ts
1223
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
1224
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
1225
+ * fn(params)('camelcase')
1226
+ * ```
1227
+ */
1228
+ function memoize(store, factory) {
1229
+ return (key) => {
1230
+ if (store.has(key)) return store.get(key);
1231
+ const value = factory(key);
1232
+ store.set(key, value);
1233
+ return value;
1234
+ };
1235
+ }
1236
+ //#endregion
1237
+ //#region src/utils/schemaGraph.ts
1238
+ /**
1239
+ * Collects the names of every schema a node's subtree references, resolved with `resolveRefName`.
1240
+ * Each ref contributes its name only, so the schema it points to is never traversed. The walk is
1241
+ * memoized by node identity, which lets `findCircularSchemas` and `containsCircularRef` share one
1242
+ * scan per schema.
1243
+ *
1244
+ * @example
1245
+ * ```ts
1246
+ * collectSchemaRefs(petSchema)
1247
+ * // Set { 'Category', 'Tag' }
1248
+ * ```
1249
+ */
1250
+ const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1251
+ const refs = /* @__PURE__ */ new Set();
1252
+ collectSync(node, { schema(child) {
1253
+ if (child.type === "ref") {
1254
+ const name = resolveRefName(child);
1255
+ if (name) refs.add(name);
1256
+ }
1257
+ } });
1258
+ return refs;
1259
+ });
1260
+ /**
1261
+ * Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
1262
+ * first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
1263
+ * `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
1264
+ * re-scanning the same schema per plugin.
1265
+ *
1266
+ * Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
1267
+ * same file (a union member created by name) is left out. That leaves exactly the set
1268
+ * `resolver.imports` emits.
1269
+ *
1270
+ * @example
1271
+ * ```ts
1272
+ * collectImportedRefNames(petSchema)
1273
+ * // ['Category', 'Tag']
1274
+ * ```
1275
+ */
1276
+ const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
1277
+ const seen = /* @__PURE__ */ new Set();
1278
+ const names = [];
1279
+ collectSync(node, { schema(child) {
1280
+ if (child.type !== "ref" || !child.ref) return;
1281
+ const name = resolveRefName(child);
1282
+ if (name && !seen.has(name)) {
1283
+ seen.add(name);
1284
+ names.push(name);
1285
+ }
1286
+ } });
1287
+ return names;
1288
+ });
1289
+ /**
1290
+ * Collects the names of all top-level schemas transitively used by a set of operations.
763
1291
  *
764
1292
  * An operation uses a schema when its parameters, request body, or responses reference it, directly
765
1293
  * or through other named schemas. Once a name is added to the result it is not revisited, so
@@ -953,25 +1481,14 @@ function inferDiscriminatorPropertyName(members) {
953
1481
  }
954
1482
  for (const name of candidates ?? []) {
955
1483
  const values = members.map((member) => {
956
- const literals = new Set(resolveSchemaProperties({
957
- node: member,
958
- propertyName: name
959
- }).flatMap((property) => getSchemaLiteralValues(property.schema)));
960
- return literals.size === 1 ? [...literals][0] : void 0;
961
- });
962
- if (values.every((value) => value !== void 0) && new Set(values).size === members.length) return name;
963
- }
964
- }
965
- //#endregion
966
- //#region src/nodes/base.ts
967
- /** Builds a `createX` factory for a node whose only computed field is `kind`. */
968
- function nodeFactory(kind) {
969
- return (input) => {
970
- return {
971
- ...input,
972
- kind
973
- };
974
- };
1484
+ const literals = new Set(resolveSchemaProperties({
1485
+ node: member,
1486
+ propertyName: name
1487
+ }).flatMap((property) => getSchemaLiteralValues(property.schema)));
1488
+ return literals.size === 1 ? [...literals][0] : void 0;
1489
+ });
1490
+ if (values.every((value) => value !== void 0) && new Set(values).size === members.length) return name;
1491
+ }
975
1492
  }
976
1493
  //#endregion
977
1494
  //#region src/nodes/code.ts
@@ -984,7 +1501,7 @@ function nodeFactory(kind) {
984
1501
  * // export const pets: Pet[] = ... as const
985
1502
  * ```
986
1503
  */
987
- const createConst = nodeFactory("Const");
1504
+ const createConst = nodeFactory(nodeKinds.const);
988
1505
  /**
989
1506
  * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
990
1507
  *
@@ -994,7 +1511,7 @@ const createConst = nodeFactory("Const");
994
1511
  * // export type Pet = ...
995
1512
  * ```
996
1513
  */
997
- const createType = nodeFactory("Type");
1514
+ const createType = nodeFactory(nodeKinds.type);
998
1515
  /**
999
1516
  * Creates a `FunctionNode` representing a TypeScript `function` declaration.
1000
1517
  *
@@ -1004,7 +1521,7 @@ const createType = nodeFactory("Type");
1004
1521
  * // export async function fetchPet(): Promise<Pet> { ... }
1005
1522
  * ```
1006
1523
  */
1007
- const createFunction = nodeFactory("Function");
1524
+ const createFunction = nodeFactory(nodeKinds.function);
1008
1525
  /**
1009
1526
  * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
1010
1527
  *
@@ -1014,7 +1531,7 @@ const createFunction = nodeFactory("Function");
1014
1531
  * // export const double = (n: number) => ...
1015
1532
  * ```
1016
1533
  */
1017
- const createArrowFunction = nodeFactory("ArrowFunction");
1534
+ const createArrowFunction = nodeFactory(nodeKinds.arrowFunction);
1018
1535
  /**
1019
1536
  * Creates a {@link TextNode} representing a raw string fragment in the source output.
1020
1537
  *
@@ -1026,7 +1543,7 @@ const createArrowFunction = nodeFactory("ArrowFunction");
1026
1543
  */
1027
1544
  function createText(value) {
1028
1545
  return {
1029
- kind: "Text",
1546
+ kind: nodeKinds.text,
1030
1547
  value
1031
1548
  };
1032
1549
  }
@@ -1040,7 +1557,7 @@ function createText(value) {
1040
1557
  * ```
1041
1558
  */
1042
1559
  function createBreak() {
1043
- return { kind: "Break" };
1560
+ return { kind: nodeKinds.break };
1044
1561
  }
1045
1562
  /**
1046
1563
  * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
@@ -1053,7 +1570,7 @@ function createBreak() {
1053
1570
  */
1054
1571
  function createJsx(value) {
1055
1572
  return {
1056
- kind: "Jsx",
1573
+ kind: nodeKinds.jsx,
1057
1574
  value
1058
1575
  };
1059
1576
  }
@@ -1062,259 +1579,7 @@ function createJsx(value) {
1062
1579
  /**
1063
1580
  * Creates a `ContentNode` for a single request-body or response content type.
1064
1581
  */
1065
- const createContent = nodeFactory("Content");
1066
- //#endregion
1067
- //#region src/utils/combineFileMembers.ts
1068
- const IDENTIFIER_RUN = /[\w$]+/g;
1069
- /**
1070
- * How many imports a file needs before indexing the source beats scanning it once per name.
1071
- */
1072
- const INDEX_ABOVE_IMPORTS = 128;
1073
- /**
1074
- * Returns the local binding an import name item introduces: the alias when there is one, else the
1075
- * imported name.
1076
- */
1077
- function importLocalName(item) {
1078
- return typeof item === "string" ? item : item.name ?? item.propertyName;
1079
- }
1080
- /**
1081
- * Computes a multi-level sort key for exports and imports:
1082
- * non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
1083
- */
1084
- function sortKey(node) {
1085
- const isArray = Array.isArray(node.name) ? "1" : "0";
1086
- const typeOnly = node.isTypeOnly ? "0" : "1";
1087
- const hasName = node.name != null ? "1" : "0";
1088
- const name = Array.isArray(node.name) ? node.name.toSorted().join("\0") : node.name ?? "";
1089
- return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
1090
- }
1091
- /**
1092
- * Sorts members by {@link sortKey}. Keys are computed once per node, not per comparison.
1093
- */
1094
- function sortMembers(nodes) {
1095
- return nodes.map((node) => ({
1096
- node,
1097
- key: sortKey(node)
1098
- })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ node }) => node);
1099
- }
1100
- /**
1101
- * Returns a collector for array-named members. A member whose path and `isTypeOnly` were already
1102
- * seen folds its names into that entry, otherwise it joins `result` as a new entry.
1103
- */
1104
- function mergeByPath(result) {
1105
- const byPath = /* @__PURE__ */ new Map();
1106
- return ({ node, names }) => {
1107
- const key = `${node.path}:${node.isTypeOnly ?? false}`;
1108
- const existing = byPath.get(key);
1109
- if (existing) {
1110
- for (const name of names) {
1111
- if (existing.seen.has(name)) continue;
1112
- existing.seen.add(name);
1113
- existing.names.push(name);
1114
- }
1115
- return;
1116
- }
1117
- const item = {
1118
- ...node,
1119
- name: [...names]
1120
- };
1121
- result.push(item);
1122
- byPath.set(key, {
1123
- names: item.name,
1124
- seen: new Set(names)
1125
- });
1126
- };
1127
- }
1128
- /**
1129
- * Deduplicates `SourceNode` objects by `name + isExportable + isTypeOnly`, keeping the first of each
1130
- * key. An unnamed source only drops when the same node appears twice, so markdown can repeat a
1131
- * paragraph or a table. Returns the deduplicated array in original order.
1132
- */
1133
- function combineSources(sources) {
1134
- const seen = /* @__PURE__ */ new Set();
1135
- return sources.filter((source) => {
1136
- const key = source.name != null ? `${source.name}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}` : source;
1137
- if (seen.has(key)) return false;
1138
- seen.add(key);
1139
- return true;
1140
- });
1141
- }
1142
- /**
1143
- * Deduplicates and merges `ExportNode` objects by path and type.
1144
- *
1145
- * Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
1146
- * Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
1147
- */
1148
- function combineExports(exports) {
1149
- const result = [];
1150
- const merge = mergeByPath(result);
1151
- const seen = /* @__PURE__ */ new Set();
1152
- for (const curr of sortMembers(exports)) {
1153
- const { name, path, isTypeOnly, asAlias } = curr;
1154
- if (Array.isArray(name)) {
1155
- if (name.length) merge({
1156
- node: curr,
1157
- names: [...new Set(name)]
1158
- });
1159
- continue;
1160
- }
1161
- const key = `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
1162
- if (!seen.has(key)) {
1163
- result.push(curr);
1164
- seen.add(key);
1165
- }
1166
- }
1167
- return result;
1168
- }
1169
- /**
1170
- * Deduplicates and merges `ImportNode` objects, filtering out unused imports.
1171
- *
1172
- * Retains imports that are referenced in `source` or re-exported. Imports with the same path and
1173
- * `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
1174
- */
1175
- function combineImports(imports, exports, source) {
1176
- const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
1177
- const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
1178
- const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
1179
- const importNameMemo = /* @__PURE__ */ new Map();
1180
- const canonicalizeName = (n) => {
1181
- if (typeof n === "string") return n;
1182
- const key = `${n.propertyName}:${n.name ?? ""}`;
1183
- if (!importNameMemo.has(key)) importNameMemo.set(key, n);
1184
- return importNameMemo.get(key);
1185
- };
1186
- const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
1187
- for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);
1188
- const result = [];
1189
- const merge = mergeByPath(result);
1190
- const seen = /* @__PURE__ */ new Set();
1191
- for (const curr of sortMembers(imports)) {
1192
- if (curr.path === curr.root) continue;
1193
- const { name, path, isTypeOnly } = curr;
1194
- if (Array.isArray(name)) {
1195
- const names = [...new Set(name.map(canonicalizeName))].filter((item) => isUsed(importLocalName(item)));
1196
- if (names.length) merge({
1197
- node: curr,
1198
- names
1199
- });
1200
- continue;
1201
- }
1202
- if (!isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
1203
- const key = `${path}:${name}:${isTypeOnly ?? false}`;
1204
- if (!seen.has(key)) {
1205
- result.push(curr);
1206
- seen.add(key);
1207
- }
1208
- }
1209
- return result;
1210
- }
1211
- //#endregion
1212
- //#region src/nodes/file.ts
1213
- /**
1214
- * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
1215
- *
1216
- * @example Named import
1217
- * ```ts
1218
- * createImport({ name: ['useState'], path: 'react' })
1219
- * // import { useState } from 'react'
1220
- * ```
1221
- */
1222
- const createImport = nodeFactory("Import");
1223
- /**
1224
- * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
1225
- *
1226
- * @example Named export
1227
- * ```ts
1228
- * createExport({ name: ['Pet'], path: './Pet' })
1229
- * // export { Pet } from './Pet'
1230
- * ```
1231
- */
1232
- const createExport = nodeFactory("Export");
1233
- /**
1234
- * Creates a `SourceNode` representing a fragment of source code within a file.
1235
- *
1236
- * @example
1237
- * ```ts
1238
- * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
1239
- * ```
1240
- */
1241
- const createSource = nodeFactory("Source");
1242
- /**
1243
- * Creates a fully resolved `FileNode` from a file input descriptor.
1244
- *
1245
- * Computes:
1246
- * - `id` SHA256 hash of the file path
1247
- * - `name` `baseName` without extension
1248
- * - `extname` extension extracted from `baseName`
1249
- *
1250
- * Deduplicates:
1251
- * - `sources` via `combineSources`
1252
- * - `exports` via `combineExports`
1253
- * - `imports` via `combineImports` (also filters unused imports)
1254
- *
1255
- * @throws {Error} when `baseName` has no extension.
1256
- *
1257
- * @example
1258
- * ```ts
1259
- * const file = createFile({
1260
- * baseName: 'petStore.ts',
1261
- * path: 'src/models/petStore.ts',
1262
- * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
1263
- * imports: [createImport({ name: ['z'], path: 'zod' })],
1264
- * exports: [createExport({ name: ['Pet'], path: './petStore' })],
1265
- * })
1266
- * // file.id = SHA256 hash of 'src/models/petStore.ts'
1267
- * // file.name = 'petStore'
1268
- * // file.extname = '.ts'
1269
- * ```
1270
- *
1271
- * @example Copy a real file into the output
1272
- * ```ts
1273
- * const file = createFile({
1274
- * baseName: 'client.ts',
1275
- * path: 'src/gen/client.ts',
1276
- * copy: '/abs/path/to/templates/client.ts',
1277
- * })
1278
- * ```
1279
- */
1280
- function createFile(input) {
1281
- const extname = path.extname(input.baseName);
1282
- if (!extname) throw new Error(`No extname found for ${input.baseName}`);
1283
- const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
1284
- const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
1285
- const resolvedImports = (() => {
1286
- if (!input.imports?.length) return [];
1287
- const sourceParts = [];
1288
- const localNames = /* @__PURE__ */ new Set();
1289
- for (const item of resolvedSources) {
1290
- const extracted = item.nodes && extractStringsFromNodes(item.nodes);
1291
- if (extracted) sourceParts.push(extracted);
1292
- if (item.name) localNames.add(item.name);
1293
- }
1294
- const source = sourceParts.join("\n") || void 0;
1295
- return combineImports(input.imports, resolvedExports, source).flatMap((imp) => {
1296
- if (imp.path === input.path) return [];
1297
- if (!Array.isArray(imp.name)) return typeof imp.name === "string" && localNames.has(imp.name) ? [] : [imp];
1298
- const kept = imp.name.filter((item) => !localNames.has(importLocalName(item)));
1299
- if (!kept.length) return [];
1300
- return [kept.length === imp.name.length ? imp : {
1301
- ...imp,
1302
- name: kept
1303
- }];
1304
- });
1305
- })();
1306
- return {
1307
- ...input,
1308
- kind: "File",
1309
- id: hash("sha256", input.path, "hex"),
1310
- name: path.basename(input.baseName, extname),
1311
- extname,
1312
- imports: resolvedImports,
1313
- exports: resolvedExports,
1314
- sources: resolvedSources,
1315
- meta: input.meta ?? {}
1316
- };
1317
- }
1582
+ const createContent = nodeFactory(nodeKinds.content);
1318
1583
  //#endregion
1319
1584
  //#region src/nodes/input.ts
1320
1585
  /**
@@ -1336,7 +1601,7 @@ function createInput(overrides = {}) {
1336
1601
  enumNames: []
1337
1602
  },
1338
1603
  ...overrides,
1339
- kind: "Input"
1604
+ kind: nodeKinds.input
1340
1605
  };
1341
1606
  }
1342
1607
  //#endregion
@@ -1344,7 +1609,7 @@ function createInput(overrides = {}) {
1344
1609
  /**
1345
1610
  * Creates a `RequestBodyNode`.
1346
1611
  */
1347
- const createRequestBody = nodeFactory("RequestBody");
1612
+ const createRequestBody = nodeFactory(nodeKinds.requestBody);
1348
1613
  //#endregion
1349
1614
  //#region src/nodes/operation.ts
1350
1615
  function createOperation(props) {
@@ -1355,7 +1620,7 @@ function createOperation(props) {
1355
1620
  parameters: [],
1356
1621
  responses: [],
1357
1622
  ...rest,
1358
- kind: "Operation",
1623
+ kind: nodeKinds.operation,
1359
1624
  ...isHttp ? { protocol: "http" } : {},
1360
1625
  requestBody: requestBody ? createRequestBody(requestBody) : void 0
1361
1626
  };
@@ -1382,7 +1647,7 @@ function createParameter(input) {
1382
1647
  ...input,
1383
1648
  required,
1384
1649
  schema: optionality(input.schema, required),
1385
- kind: "Parameter"
1650
+ kind: nodeKinds.parameter
1386
1651
  };
1387
1652
  }
1388
1653
  //#endregion
@@ -1407,7 +1672,7 @@ function createProperty(input) {
1407
1672
  ...input,
1408
1673
  required,
1409
1674
  schema: optionality(input.schema, required),
1410
- kind: "Property"
1675
+ kind: nodeKinds.property
1411
1676
  };
1412
1677
  }
1413
1678
  //#endregion
@@ -1434,7 +1699,7 @@ function createResponse(props) {
1434
1699
  return {
1435
1700
  ...rest,
1436
1701
  content: entries,
1437
- kind: "Response"
1702
+ kind: nodeKinds.response
1438
1703
  };
1439
1704
  }
1440
1705
  //#endregion
@@ -1469,12 +1734,12 @@ function createSchema(props) {
1469
1734
  properties: [],
1470
1735
  primitive: "object",
1471
1736
  ...props,
1472
- kind: "Schema"
1737
+ kind: nodeKinds.schema
1473
1738
  };
1474
1739
  return {
1475
1740
  primitive: TYPE_TO_PRIMITIVE[props.type],
1476
1741
  ...props,
1477
- kind: "Schema"
1742
+ kind: nodeKinds.schema
1478
1743
  };
1479
1744
  }
1480
1745
  //#endregion
@@ -1517,20 +1782,27 @@ var src_exports = /* @__PURE__ */ __exportAll({
1517
1782
  factory: () => nodes_exports,
1518
1783
  findCircularSchemas: () => findCircularSchemas,
1519
1784
  findCircularSchemasFromGraph: () => findCircularSchemasFromGraph,
1785
+ findSchema: () => findSchema,
1786
+ forEachChild: () => forEachChild,
1787
+ forEachSchema: () => forEachSchema,
1520
1788
  getSchemaLiteralValues: () => getSchemaLiteralValues,
1521
1789
  inferDiscriminatorPropertyName: () => inferDiscriminatorPropertyName,
1790
+ inlineFiles: () => inlineFiles,
1522
1791
  isBareRef: () => isBareRef,
1523
1792
  isHttpOperationNode: () => isHttpOperationNode,
1793
+ isVirtualFile: () => isVirtualFile,
1524
1794
  mergeRefWithSchema: () => mergeRefWithSchema,
1525
1795
  narrowSchema: () => narrowSchema,
1796
+ nodeKinds: () => nodeKinds,
1526
1797
  optionality: () => optionality,
1527
1798
  refName: () => refName,
1799
+ resolveRef: () => resolveRef,
1528
1800
  resolveRefName: () => resolveRefName,
1529
1801
  resolveSchemaProperties: () => resolveSchemaProperties,
1530
1802
  schemaTypes: () => schemaTypes,
1531
1803
  transform: () => transform
1532
1804
  });
1533
1805
  //#endregion
1534
- export { applyMacros, src_exports as ast, collect, collectImportedRefNames, collectSchemaRefs, collectSync, collectUsedSchemaNames, composeMacros, createPrinter, defineMacro, extractStringsFromNodes, nodes_exports as factory, findCircularSchemas, findCircularSchemasFromGraph, getSchemaLiteralValues, inferDiscriminatorPropertyName, isBareRef, isHttpOperationNode, mergeRefWithSchema, narrowSchema, optionality, refName, resolveRefName, resolveSchemaProperties, schemaTypes, transform };
1806
+ export { applyMacros, src_exports as ast, collect, collectImportedRefNames, collectSchemaRefs, collectSync, collectUsedSchemaNames, composeMacros, createPrinter, defineMacro, extractStringsFromNodes, nodes_exports as factory, findCircularSchemas, findCircularSchemasFromGraph, findSchema, forEachChild, forEachSchema, getSchemaLiteralValues, inferDiscriminatorPropertyName, inlineFiles, isBareRef, isHttpOperationNode, isVirtualFile, mergeRefWithSchema, narrowSchema, nodeKinds, optionality, refName, resolveRef, resolveRefName, resolveSchemaProperties, schemaTypes, transform };
1535
1807
 
1536
1808
  //# sourceMappingURL=index.js.map