@kubb/core 5.0.0-alpha.31 → 5.0.0-alpha.32

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
@@ -2,18 +2,17 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_chunk = require("./chunk-MlS0t1Af.cjs");
5
+ const require_chunk = require("./chunk-ByKO4r7w.cjs");
6
6
  let node_events = require("node:events");
7
7
  let node_fs = require("node:fs");
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_path = require("node:path");
10
10
  node_path = require_chunk.__toESM(node_path);
11
11
  let _kubb_ast = require("@kubb/ast");
12
- let _kubb_react_fabric = require("@kubb/react-fabric");
13
- let _kubb_react_fabric_plugins = require("@kubb/react-fabric/plugins");
14
12
  let node_perf_hooks = require("node:perf_hooks");
15
13
  let fflate = require("fflate");
16
14
  let tinyexec = require("tinyexec");
15
+ let _kubb_react_fabric = require("@kubb/react-fabric");
17
16
  let _kubb_react_fabric_jsx_runtime = require("@kubb/react-fabric/jsx-runtime");
18
17
  let node_process = require("node:process");
19
18
  let remeda = require("remeda");
@@ -339,6 +338,24 @@ async function clean(path) {
339
338
  });
340
339
  }
341
340
  //#endregion
341
+ //#region ../../internals/utils/src/string.ts
342
+ /**
343
+ * Strips the file extension from a path or file name.
344
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
345
+ *
346
+ * @example
347
+ * trimExtName('petStore.ts') // 'petStore'
348
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
349
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
350
+ * trimExtName('noExtension') // 'noExtension'
351
+ */
352
+ function trimExtName$1(text) {
353
+ const dotIndex = text.lastIndexOf(".");
354
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
355
+ return text;
356
+ }
357
+ require_chunk.__name(trimExtName$1, "trimExtName");
358
+ //#endregion
342
359
  //#region ../../internals/utils/src/promise.ts
343
360
  /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
344
361
  *
@@ -711,78 +728,6 @@ const formatters = {
711
728
  }
712
729
  };
713
730
  //#endregion
714
- //#region src/defineParser.ts
715
- /**
716
- * Defines a parser with type safety.
717
- *
718
- * Use this function to create parsers that transform generated files to strings
719
- * based on their extension.
720
- *
721
- * @example
722
- * ```ts
723
- * import { defineParser } from '@kubb/core'
724
- *
725
- * export const jsonParser = defineParser({
726
- * name: 'json',
727
- * extNames: ['.json'],
728
- * parse(file) {
729
- * return file.sources.map((s) => s.value).join('\n')
730
- * },
731
- * })
732
- * ```
733
- */
734
- function defineParser(parser) {
735
- return {
736
- install() {},
737
- type: "parser",
738
- ...parser
739
- };
740
- }
741
- //#endregion
742
- //#region src/devtools.ts
743
- /**
744
- * Encodes a `RootNode` as a compressed, URL-safe string.
745
- *
746
- * The JSON representation is deflate-compressed with {@link deflateSync} before
747
- * base64url encoding, which typically reduces payload size by 70–80 % and
748
- * keeps URLs well within browser and server path-length limits.
749
- *
750
- * Use {@link decodeAst} to reverse.
751
- */
752
- function encodeAst(root) {
753
- const compressed = (0, fflate.deflateSync)(new TextEncoder().encode(JSON.stringify(root)));
754
- return Buffer.from(compressed).toString("base64url");
755
- }
756
- /**
757
- * Constructs the Kubb Studio URL for the given `RootNode`.
758
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
759
- * The `root` is encoded and attached as the `?root=` query parameter so Studio
760
- * can decode and render it without a round-trip to any server.
761
- */
762
- function getStudioUrl(root, studioUrl, options = {}) {
763
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(root)}`;
764
- }
765
- /**
766
- * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
767
- *
768
- * Falls back to printing the URL if the browser cannot be launched.
769
- */
770
- async function openInStudio(root, studioUrl, options = {}) {
771
- const url = getStudioUrl(root, studioUrl, options);
772
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
773
- const args = process.platform === "win32" ? [
774
- "/c",
775
- "start",
776
- "",
777
- url
778
- ] : [url];
779
- try {
780
- await (0, tinyexec.x)(cmd, args);
781
- } catch {
782
- console.log(`\n ${url}\n`);
783
- }
784
- }
785
- //#endregion
786
731
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
787
732
  var Node = class {
788
733
  value;
@@ -910,6 +855,194 @@ function validateConcurrency(concurrency) {
910
855
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
911
856
  }
912
857
  //#endregion
858
+ //#region src/FileProcessor.ts
859
+ function joinSources(file) {
860
+ return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
861
+ }
862
+ /**
863
+ * Converts a single file to a string using the registered parsers.
864
+ * Falls back to joining source values when no matching parser is found.
865
+ */
866
+ var FileProcessor = class {
867
+ #limit = pLimit(100);
868
+ async parse(file, { parsers, extension } = {}) {
869
+ const parseExtName = extension?.[file.extname] || void 0;
870
+ if (!parsers || !file.extname) return joinSources(file);
871
+ const parser = parsers.get(file.extname);
872
+ if (!parser) return joinSources(file);
873
+ return parser.parse(file, { extname: parseExtName });
874
+ }
875
+ async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
876
+ await onStart?.(files);
877
+ const total = files.length;
878
+ let processed = 0;
879
+ const processOne = async (file) => {
880
+ const source = await this.parse(file, {
881
+ extension,
882
+ parsers
883
+ });
884
+ const currentProcessed = ++processed;
885
+ const percentage = currentProcessed / total * 100;
886
+ await onUpdate?.({
887
+ file,
888
+ source,
889
+ processed: currentProcessed,
890
+ percentage,
891
+ total
892
+ });
893
+ };
894
+ if (mode === "sequential") for (const file of files) await processOne(file);
895
+ else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
896
+ await onEnd?.(files);
897
+ return files;
898
+ }
899
+ };
900
+ //#endregion
901
+ //#region src/devtools.ts
902
+ /**
903
+ * Encodes an `InputNode` as a compressed, URL-safe string.
904
+ *
905
+ * The JSON representation is deflate-compressed with {@link deflateSync} before
906
+ * base64url encoding, which typically reduces payload size by 70–80 % and
907
+ * keeps URLs well within browser and server path-length limits.
908
+ *
909
+ * Use {@link decodeAst} to reverse.
910
+ */
911
+ function encodeAst(input) {
912
+ const compressed = (0, fflate.deflateSync)(new TextEncoder().encode(JSON.stringify(input)));
913
+ return Buffer.from(compressed).toString("base64url");
914
+ }
915
+ /**
916
+ * Constructs the Kubb Studio URL for the given `InputNode`.
917
+ * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
918
+ * The `input` is encoded and attached as the `?root=` query parameter so Studio
919
+ * can decode and render it without a round-trip to any server.
920
+ */
921
+ function getStudioUrl(input, studioUrl, options = {}) {
922
+ return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
923
+ }
924
+ /**
925
+ * Opens the Kubb Studio URL for the given `InputNode` in the default browser —
926
+ *
927
+ * Falls back to printing the URL if the browser cannot be launched.
928
+ */
929
+ async function openInStudio(input, studioUrl, options = {}) {
930
+ const url = getStudioUrl(input, studioUrl, options);
931
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
932
+ const args = process.platform === "win32" ? [
933
+ "/c",
934
+ "start",
935
+ "",
936
+ url
937
+ ] : [url];
938
+ try {
939
+ await (0, tinyexec.x)(cmd, args);
940
+ } catch {
941
+ console.log(`\n ${url}\n`);
942
+ }
943
+ }
944
+ //#endregion
945
+ //#region src/FileManager.ts
946
+ function mergeFile(a, b) {
947
+ return {
948
+ ...a,
949
+ sources: [...a.sources || [], ...b.sources || []],
950
+ imports: [...a.imports || [], ...b.imports || []],
951
+ exports: [...a.exports || [], ...b.exports || []]
952
+ };
953
+ }
954
+ /**
955
+ * In-memory file store for generated files.
956
+ *
957
+ * Files with the same `path` are merged — sources, imports, and exports are concatenated.
958
+ * The `files` getter returns all stored files sorted by path length (shortest first).
959
+ *
960
+ * @example
961
+ * ```ts
962
+ * import { FileManager } from '@kubb/core'
963
+ *
964
+ * const manager = new FileManager()
965
+ * manager.upsert(myFile)
966
+ * console.log(manager.files) // all stored files
967
+ * ```
968
+ */
969
+ var FileManager = class {
970
+ #cache = /* @__PURE__ */ new Map();
971
+ #filesCache = null;
972
+ /**
973
+ * Adds one or more files. Files with the same path are merged — sources, imports,
974
+ * and exports from all calls with the same path are concatenated together.
975
+ */
976
+ add(...files) {
977
+ const resolvedFiles = [];
978
+ const mergedFiles = /* @__PURE__ */ new Map();
979
+ files.forEach((file) => {
980
+ const existing = mergedFiles.get(file.path);
981
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
982
+ else mergedFiles.set(file.path, file);
983
+ });
984
+ for (const file of mergedFiles.values()) {
985
+ const resolvedFile = (0, _kubb_ast.createFile)(file);
986
+ this.#cache.set(resolvedFile.path, resolvedFile);
987
+ this.#filesCache = null;
988
+ resolvedFiles.push(resolvedFile);
989
+ }
990
+ return resolvedFiles;
991
+ }
992
+ /**
993
+ * Adds or merges one or more files.
994
+ * If a file with the same path already exists, its sources/imports/exports are merged together.
995
+ */
996
+ upsert(...files) {
997
+ const resolvedFiles = [];
998
+ const mergedFiles = /* @__PURE__ */ new Map();
999
+ files.forEach((file) => {
1000
+ const existing = mergedFiles.get(file.path);
1001
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
1002
+ else mergedFiles.set(file.path, file);
1003
+ });
1004
+ for (const file of mergedFiles.values()) {
1005
+ const existing = this.#cache.get(file.path);
1006
+ const resolvedFile = (0, _kubb_ast.createFile)(existing ? mergeFile(existing, file) : file);
1007
+ this.#cache.set(resolvedFile.path, resolvedFile);
1008
+ this.#filesCache = null;
1009
+ resolvedFiles.push(resolvedFile);
1010
+ }
1011
+ return resolvedFiles;
1012
+ }
1013
+ getByPath(path) {
1014
+ return this.#cache.get(path) ?? null;
1015
+ }
1016
+ deleteByPath(path) {
1017
+ this.#cache.delete(path);
1018
+ this.#filesCache = null;
1019
+ }
1020
+ clear() {
1021
+ this.#cache.clear();
1022
+ this.#filesCache = null;
1023
+ }
1024
+ /**
1025
+ * All stored files, sorted by path length (shorter paths first).
1026
+ * Barrel/index files (e.g. index.ts) are sorted last within each length bucket.
1027
+ */
1028
+ get files() {
1029
+ if (this.#filesCache) return this.#filesCache;
1030
+ const keys = [...this.#cache.keys()].sort((a, b) => {
1031
+ if (a.length !== b.length) return a.length - b.length;
1032
+ const aIsIndex = trimExtName$1(a).endsWith("index");
1033
+ if (aIsIndex !== trimExtName$1(b).endsWith("index")) return aIsIndex ? 1 : -1;
1034
+ return 0;
1035
+ });
1036
+ const files = [];
1037
+ for (const key of keys) {
1038
+ const file = this.#cache.get(key);
1039
+ if (file) files.push(file);
1040
+ }
1041
+ this.#filesCache = files;
1042
+ return files;
1043
+ }
1044
+ };
1045
+ //#endregion
913
1046
  //#region src/utils/executeStrategies.ts
914
1047
  /**
915
1048
  * Runs promise functions in sequence, threading each result into the next call.
@@ -976,12 +1109,18 @@ var PluginDriver = class {
976
1109
  config;
977
1110
  options;
978
1111
  /**
979
- * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
1112
+ * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
980
1113
  * the build pipeline after the adapter's `parse()` resolves.
981
1114
  */
982
- rootNode = void 0;
1115
+ inputNode = void 0;
983
1116
  adapter = void 0;
984
1117
  #studioIsOpen = false;
1118
+ /**
1119
+ * Central file store for all generated files.
1120
+ * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
1121
+ * add files; this property gives direct read/write access when needed.
1122
+ */
1123
+ fileManager = new FileManager();
985
1124
  plugins = /* @__PURE__ */ new Map();
986
1125
  constructor(config, options) {
987
1126
  this.config = config;
@@ -1006,7 +1145,6 @@ var PluginDriver = class {
1006
1145
  getContext(plugin) {
1007
1146
  const driver = this;
1008
1147
  const baseContext = {
1009
- fabric: driver.options.fabric,
1010
1148
  config: driver.config,
1011
1149
  get root() {
1012
1150
  return (0, node_path.resolve)(driver.config.root, driver.config.output.path);
@@ -1020,13 +1158,13 @@ var PluginDriver = class {
1020
1158
  requirePlugin: driver.requirePlugin.bind(driver),
1021
1159
  driver,
1022
1160
  addFile: async (...files) => {
1023
- await this.options.fabric.addFile(...files);
1161
+ driver.fileManager.add(...files);
1024
1162
  },
1025
1163
  upsertFile: async (...files) => {
1026
- await this.options.fabric.upsertFile(...files);
1164
+ driver.fileManager.upsert(...files);
1027
1165
  },
1028
- get rootNode() {
1029
- return driver.rootNode;
1166
+ get inputNode() {
1167
+ return driver.inputNode;
1030
1168
  },
1031
1169
  get adapter() {
1032
1170
  return driver.adapter;
@@ -1049,10 +1187,10 @@ var PluginDriver = class {
1049
1187
  openInStudio(options) {
1050
1188
  if (!driver.config.devtools || driver.#studioIsOpen) return;
1051
1189
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1052
- if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1190
+ if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1053
1191
  driver.#studioIsOpen = true;
1054
1192
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1055
- return openInStudio(driver.rootNode, studioUrl, options);
1193
+ return openInStudio(driver.inputNode, studioUrl, options);
1056
1194
  }
1057
1195
  };
1058
1196
  const mergedExtras = {};
@@ -1081,14 +1219,14 @@ var PluginDriver = class {
1081
1219
  options
1082
1220
  });
1083
1221
  if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1084
- return {
1222
+ return (0, _kubb_ast.createFile)({
1085
1223
  path,
1086
1224
  baseName: (0, node_path.basename)(path),
1087
1225
  meta: { pluginName },
1088
1226
  sources: [],
1089
1227
  imports: [],
1090
1228
  exports: []
1091
- };
1229
+ });
1092
1230
  }
1093
1231
  /**
1094
1232
  * @deprecated use resolvers context instead
@@ -1372,19 +1510,19 @@ var PluginDriver = class {
1372
1510
  /**
1373
1511
  * Handles the return value of a plugin AST hook or generator method.
1374
1512
  *
1375
- * - React element → rendered via an isolated react-fabric context, files merged into `fabric`
1376
- * - `Array<KubbFile.File>` → upserted directly into `fabric`
1513
+ * - React element → rendered via an isolated react-fabric context, files stored in `driver.fileManager`
1514
+ * - `Array<FileNode>` → upserted directly into `driver.fileManager`
1377
1515
  * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1378
1516
  */
1379
- async function applyHookResult(result, fabric) {
1517
+ async function applyHookResult(result, driver) {
1380
1518
  if (!result) return;
1381
1519
  if (Array.isArray(result)) {
1382
- await fabric.upsertFile(...result);
1520
+ driver.fileManager.upsert(...result);
1383
1521
  return;
1384
1522
  }
1385
1523
  const fabricChild = (0, _kubb_react_fabric.createReactFabric)();
1386
1524
  await fabricChild.render(/* @__PURE__ */ (0, _kubb_react_fabric_jsx_runtime.jsx)(_kubb_react_fabric.Fabric, { children: result }));
1387
- fabric.context.fileManager.upsert(...fabricChild.files);
1525
+ driver.fileManager.upsert(...fabricChild.files);
1388
1526
  fabricChild.unmount();
1389
1527
  }
1390
1528
  //#endregion
@@ -1485,7 +1623,7 @@ const fsStorage = createStorage(() => ({
1485
1623
  }));
1486
1624
  //#endregion
1487
1625
  //#region package.json
1488
- var version = "5.0.0-alpha.31";
1626
+ var version = "5.0.0-alpha.32";
1489
1627
  //#endregion
1490
1628
  //#region src/utils/diagnostics.ts
1491
1629
  /**
@@ -1507,7 +1645,7 @@ function getDiagnosticInfo() {
1507
1645
  //#region src/utils/TreeNode.ts
1508
1646
  /**
1509
1647
  * Tree structure used to build per-directory barrel (`index.ts`) files from a
1510
- * flat list of generated {@link KubbFile.File} entries.
1648
+ * flat list of generated {@link FileNode} entries.
1511
1649
  *
1512
1650
  * Each node represents either a directory or a file within the output tree.
1513
1651
  * Use {@link TreeNode.build} to construct a root node from a file list, then
@@ -1668,36 +1806,36 @@ function getBarrelFilesByRoot(root, files) {
1668
1806
  const cachedFiles = /* @__PURE__ */ new Map();
1669
1807
  TreeNode.build(files, root)?.forEach((treeNode) => {
1670
1808
  if (!treeNode?.children || !treeNode.parent?.data.path) return;
1671
- const barrelFile = {
1809
+ const barrelFile = (0, _kubb_ast.createFile)({
1672
1810
  path: (0, node_path.join)(treeNode.parent?.data.path, "index.ts"),
1673
1811
  baseName: "index.ts",
1674
1812
  exports: [],
1675
1813
  imports: [],
1676
1814
  sources: []
1677
- };
1815
+ });
1678
1816
  const previousBarrelFile = cachedFiles.get(barrelFile.path);
1679
1817
  treeNode.leaves.forEach((item) => {
1680
1818
  if (!item.data.name) return;
1681
1819
  (item.data.file?.sources || []).forEach((source) => {
1682
1820
  if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1683
1821
  if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1684
- barrelFile.exports.push({
1822
+ barrelFile.exports.push((0, _kubb_ast.createExport)({
1685
1823
  name: [source.name],
1686
1824
  path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1687
1825
  isTypeOnly: source.isTypeOnly
1688
- });
1689
- barrelFile.sources.push({
1826
+ }));
1827
+ barrelFile.sources.push((0, _kubb_ast.createSource)({
1690
1828
  name: source.name,
1691
1829
  isTypeOnly: source.isTypeOnly,
1692
1830
  value: "",
1693
1831
  isExportable: false,
1694
1832
  isIndexable: false
1695
- });
1833
+ }));
1696
1834
  });
1697
1835
  });
1698
1836
  if (previousBarrelFile) {
1699
1837
  previousBarrelFile.sources.push(...barrelFile.sources);
1700
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
1838
+ previousBarrelFile.exports.push(...barrelFile.exports);
1701
1839
  } else cachedFiles.set(barrelFile.path, barrelFile);
1702
1840
  });
1703
1841
  return [...cachedFiles.values()];
@@ -1723,7 +1861,7 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1723
1861
  if (type === "all") return barrelFiles.map((file) => {
1724
1862
  return {
1725
1863
  ...file,
1726
- exports: file.exports?.map((exportItem) => {
1864
+ exports: file.exports.map((exportItem) => {
1727
1865
  return {
1728
1866
  ...exportItem,
1729
1867
  name: void 0
@@ -1754,7 +1892,7 @@ function isInputPath(config) {
1754
1892
  * - Validates the input path (when applicable).
1755
1893
  * - Applies config defaults (`root`, `output.*`, `devtools`).
1756
1894
  * - Creates the Fabric instance and wires storage, format, and lint hooks.
1757
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
1895
+ * - Runs the adapter (if configured) to produce the universal `InputNode`.
1758
1896
  * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
1759
1897
  *
1760
1898
  * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
@@ -1822,53 +1960,7 @@ async function setup(options) {
1822
1960
  });
1823
1961
  await storage?.clear((0, node_path.resolve)(config.root, config.output.path));
1824
1962
  }
1825
- const fabric = (0, _kubb_react_fabric.createFabric)();
1826
- fabric.use(_kubb_react_fabric_plugins.fsPlugin);
1827
- for (const parser of config.parsers) fabric.use(parser);
1828
- fabric.use(defineParser({
1829
- name: "fallback",
1830
- extNames: void 0,
1831
- parse(file) {
1832
- return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
1833
- }
1834
- }));
1835
- fabric.context.on("files:processing:start", (files) => {
1836
- events.emit("files:processing:start", files);
1837
- events.emit("debug", {
1838
- date: /* @__PURE__ */ new Date(),
1839
- logs: [`Writing ${files.length} files...`]
1840
- });
1841
- });
1842
- fabric.context.on("file:processing:update", async (params) => {
1843
- const { file, source } = params;
1844
- await events.emit("file:processing:update", {
1845
- ...params,
1846
- config,
1847
- source
1848
- });
1849
- if (source) {
1850
- const key = (0, node_path.relative)((0, node_path.resolve)(config.root), file.path);
1851
- await storage?.setItem(key, source);
1852
- sources.set(file.path, source);
1853
- }
1854
- });
1855
- fabric.context.on("files:processing:end", async (files) => {
1856
- await events.emit("files:processing:end", files);
1857
- await events.emit("debug", {
1858
- date: /* @__PURE__ */ new Date(),
1859
- logs: [`✓ File write process completed for ${files.length} files`]
1860
- });
1861
- });
1862
- await events.emit("debug", {
1863
- date: /* @__PURE__ */ new Date(),
1864
- logs: [
1865
- "✓ Fabric initialized",
1866
- ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1867
- ` • Barrel type: ${config.output.barrelType || "none"}`
1868
- ]
1869
- });
1870
1963
  const driver = new PluginDriver(config, {
1871
- fabric,
1872
1964
  events,
1873
1965
  concurrency: 15
1874
1966
  });
@@ -1880,21 +1972,21 @@ async function setup(options) {
1880
1972
  logs: [`Running adapter: ${adapter.name}`]
1881
1973
  });
1882
1974
  driver.adapter = adapter;
1883
- driver.rootNode = await adapter.parse(source);
1975
+ driver.inputNode = await adapter.parse(source);
1884
1976
  await events.emit("debug", {
1885
1977
  date: /* @__PURE__ */ new Date(),
1886
1978
  logs: [
1887
- `✓ Adapter '${adapter.name}' resolved RootNode`,
1888
- ` • Schemas: ${driver.rootNode.schemas.length}`,
1889
- ` • Operations: ${driver.rootNode.operations.length}`
1979
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
1980
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
1981
+ ` • Operations: ${driver.inputNode.operations.length}`
1890
1982
  ]
1891
1983
  });
1892
1984
  return {
1893
1985
  config,
1894
1986
  events,
1895
- fabric,
1896
1987
  driver,
1897
- sources
1988
+ sources,
1989
+ storage
1898
1990
  };
1899
1991
  }
1900
1992
  /**
@@ -1904,7 +1996,7 @@ async function setup(options) {
1904
1996
  * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1905
1997
  */
1906
1998
  async function build(options, overrides) {
1907
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1999
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1908
2000
  if (error) throw error;
1909
2001
  if (failedPlugins.size > 0) {
1910
2002
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1912,7 +2004,6 @@ async function build(options, overrides) {
1912
2004
  }
1913
2005
  return {
1914
2006
  failedPlugins,
1915
- fabric,
1916
2007
  files,
1917
2008
  driver,
1918
2009
  pluginTimings,
@@ -1927,15 +2018,15 @@ async function build(options, overrides) {
1927
2018
  * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1928
2019
  * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1929
2020
  * - Return values are handled via `applyHookResult`: React elements are rendered,
1930
- * `KubbFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
2021
+ * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
1931
2022
  * - Barrel files are generated automatically when `output.barrelType` is set.
1932
2023
  */
1933
2024
  async function runPluginAstHooks(plugin, context) {
1934
- const { adapter, rootNode, resolver, fabric } = context;
2025
+ const { adapter, inputNode, resolver, driver } = context;
1935
2026
  const { exclude, include, override } = plugin.options;
1936
- if (!adapter || !rootNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
2027
+ if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`);
1937
2028
  const collectedOperations = [];
1938
- await (0, _kubb_ast.walk)(rootNode, {
2029
+ await (0, _kubb_ast.walk)(inputNode, {
1939
2030
  depth: "shallow",
1940
2031
  async schema(node) {
1941
2032
  if (!plugin.schema) return;
@@ -1947,7 +2038,7 @@ async function runPluginAstHooks(plugin, context) {
1947
2038
  override
1948
2039
  });
1949
2040
  if (options === null) return;
1950
- await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
2041
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), driver);
1951
2042
  },
1952
2043
  async operation(node) {
1953
2044
  const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
@@ -1959,24 +2050,24 @@ async function runPluginAstHooks(plugin, context) {
1959
2050
  });
1960
2051
  if (options !== null) {
1961
2052
  collectedOperations.push(transformedNode);
1962
- if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
2053
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), driver);
1963
2054
  }
1964
2055
  }
1965
2056
  });
1966
- if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
2057
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), driver);
1967
2058
  }
1968
2059
  /**
1969
2060
  * Runs a full Kubb build and captures errors instead of throwing.
1970
2061
  *
1971
2062
  * - Installs each plugin in order, recording failures in `failedPlugins`.
1972
2063
  * - Generates the root barrel file when `output.barrelType` is set.
1973
- * - Writes all files through Fabric.
2064
+ * - Writes all files through the driver's FileManager and FileProcessor.
1974
2065
  *
1975
2066
  * Returns a {@link BuildOutput} even on failure — inspect `error` and
1976
2067
  * `failedPlugins` to determine whether the build succeeded.
1977
2068
  */
1978
2069
  async function safeBuild(options, overrides) {
1979
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
2070
+ const { driver, events, sources, storage } = overrides ? overrides : await setup(options);
1980
2071
  const failedPlugins = /* @__PURE__ */ new Set();
1981
2072
  const pluginTimings = /* @__PURE__ */ new Map();
1982
2073
  const config = driver.config;
@@ -1996,7 +2087,7 @@ async function safeBuild(options, overrides) {
1996
2087
  await plugin.buildStart.call(context);
1997
2088
  if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1998
2089
  if (output) {
1999
- const barrelFiles = await getBarrelFiles(fabric.files, {
2090
+ const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
2000
2091
  type: output.barrelType ?? "named",
2001
2092
  root,
2002
2093
  output,
@@ -2050,15 +2141,15 @@ async function safeBuild(options, overrides) {
2050
2141
  ` • Path: ${rootPath}`
2051
2142
  ]
2052
2143
  });
2053
- const barrelFiles = fabric.files.filter((file) => {
2144
+ const barrelFiles = driver.fileManager.files.filter((file) => {
2054
2145
  return file.sources.some((source) => source.isIndexable);
2055
2146
  });
2056
2147
  await events.emit("debug", {
2057
2148
  date: /* @__PURE__ */ new Date(),
2058
2149
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`]
2059
2150
  });
2060
- const existingBarrel = fabric.files.find((f) => f.path === rootPath);
2061
- const rootFile = {
2151
+ const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath);
2152
+ const rootFile = (0, _kubb_ast.createFile)({
2062
2153
  path: rootPath,
2063
2154
  baseName: BARREL_FILENAME,
2064
2155
  exports: buildBarrelExports({
@@ -2067,26 +2158,59 @@ async function safeBuild(options, overrides) {
2067
2158
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
2068
2159
  config,
2069
2160
  driver
2070
- }),
2161
+ }).map((e) => (0, _kubb_ast.createExport)(e)),
2071
2162
  sources: [],
2072
2163
  imports: [],
2073
2164
  meta: {}
2074
- };
2075
- await fabric.upsertFile(rootFile);
2165
+ });
2166
+ driver.fileManager.upsert(rootFile);
2076
2167
  await events.emit("debug", {
2077
2168
  date: /* @__PURE__ */ new Date(),
2078
2169
  logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`]
2079
2170
  });
2080
2171
  }
2081
- const files = [...fabric.files];
2082
- await fabric.write({ extension: config.output.extension });
2172
+ const files = driver.fileManager.files;
2173
+ const parsersMap = /* @__PURE__ */ new Map();
2174
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
2175
+ const fileProcessor = new FileProcessor();
2176
+ await events.emit("debug", {
2177
+ date: /* @__PURE__ */ new Date(),
2178
+ logs: [`Writing ${files.length} files...`]
2179
+ });
2180
+ await fileProcessor.run(files, {
2181
+ parsers: parsersMap,
2182
+ extension: config.output.extension,
2183
+ onStart: async (processingFiles) => {
2184
+ await events.emit("files:processing:start", processingFiles);
2185
+ },
2186
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
2187
+ await events.emit("file:processing:update", {
2188
+ file,
2189
+ source,
2190
+ processed,
2191
+ total,
2192
+ percentage,
2193
+ config
2194
+ });
2195
+ if (source) {
2196
+ await storage?.setItem(file.path, source);
2197
+ sources.set(file.path, source);
2198
+ }
2199
+ },
2200
+ onEnd: async (processedFiles) => {
2201
+ await events.emit("files:processing:end", processedFiles);
2202
+ await events.emit("debug", {
2203
+ date: /* @__PURE__ */ new Date(),
2204
+ logs: [`✓ File write process completed for ${processedFiles.length} files`]
2205
+ });
2206
+ }
2207
+ });
2083
2208
  for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2084
2209
  const context = driver.getContext(plugin);
2085
2210
  await plugin.buildEnd.call(context);
2086
2211
  }
2087
2212
  return {
2088
2213
  failedPlugins,
2089
- fabric,
2090
2214
  files,
2091
2215
  driver,
2092
2216
  pluginTimings,
@@ -2095,7 +2219,6 @@ async function safeBuild(options, overrides) {
2095
2219
  } catch (error) {
2096
2220
  return {
2097
2221
  failedPlugins,
2098
- fabric,
2099
2222
  files: [],
2100
2223
  driver,
2101
2224
  pluginTimings,
@@ -2116,11 +2239,11 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
2116
2239
  if (!pluginOptions || pluginOptions.output?.barrelType === false) return [];
2117
2240
  const exportName = config.output.barrelType === "all" ? void 0 : source.name ? [source.name] : void 0;
2118
2241
  if (exportName?.some((n) => existingExports.has(n))) return [];
2119
- return [{
2242
+ return [(0, _kubb_ast.createExport)({
2120
2243
  name: exportName,
2121
2244
  path: getRelativePath(rootDir, file.path),
2122
2245
  isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly
2123
- }];
2246
+ })];
2124
2247
  });
2125
2248
  });
2126
2249
  }
@@ -2232,19 +2355,19 @@ function mergeGenerators(generators) {
2232
2355
  async schema(node, options) {
2233
2356
  for (const gen of generators) {
2234
2357
  if (!gen.schema) continue;
2235
- await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2358
+ await applyHookResult(await gen.schema.call(this, node, options), this.driver);
2236
2359
  }
2237
2360
  },
2238
2361
  async operation(node, options) {
2239
2362
  for (const gen of generators) {
2240
2363
  if (!gen.operation) continue;
2241
- await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2364
+ await applyHookResult(await gen.operation.call(this, node, options), this.driver);
2242
2365
  }
2243
2366
  },
2244
2367
  async operations(nodes, options) {
2245
2368
  for (const gen of generators) {
2246
2369
  if (!gen.operations) continue;
2247
- await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2370
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.driver);
2248
2371
  }
2249
2372
  }
2250
2373
  };
@@ -2267,6 +2390,34 @@ function defineLogger(logger) {
2267
2390
  return logger;
2268
2391
  }
2269
2392
  //#endregion
2393
+ //#region src/defineParser.ts
2394
+ /**
2395
+ * Defines a parser with type safety.
2396
+ *
2397
+ * Use this function to create parsers that transform generated files to strings
2398
+ * based on their extension.
2399
+ *
2400
+ * @example
2401
+ * ```ts
2402
+ * import { defineParser } from '@kubb/core'
2403
+ *
2404
+ * export const jsonParser = defineParser({
2405
+ * name: 'json',
2406
+ * extNames: ['.json'],
2407
+ * parse(file) {
2408
+ * return file.sources.map((s) => s.value).join('\n')
2409
+ * },
2410
+ * })
2411
+ * ```
2412
+ */
2413
+ function defineParser(parser) {
2414
+ return {
2415
+ install() {},
2416
+ type: "parser",
2417
+ ...parser
2418
+ };
2419
+ }
2420
+ //#endregion
2270
2421
  //#region src/definePresets.ts
2271
2422
  /**
2272
2423
  * Creates a typed presets registry object — a named collection of {@link Preset} entries.
@@ -2420,7 +2571,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
2420
2571
  /**
2421
2572
  * Default file resolver used by `defineResolver`.
2422
2573
  *
2423
- * Resolves a `KubbFile.File` by combining name resolution (`resolver.default`) with
2574
+ * Resolves a `FileNode` by combining name resolution (`resolver.default`) with
2424
2575
  * path resolution (`resolver.resolvePath`). The resolved file always has empty
2425
2576
  * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2426
2577
  *
@@ -2453,14 +2604,14 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2453
2604
  tag,
2454
2605
  path: groupPath
2455
2606
  }, context);
2456
- return {
2607
+ return (0, _kubb_ast.createFile)({
2457
2608
  path: filePath,
2458
2609
  baseName: node_path.default.basename(filePath),
2459
2610
  meta: { pluginName: this.pluginName },
2460
2611
  sources: [],
2461
2612
  imports: [],
2462
2613
  exports: []
2463
- };
2614
+ });
2464
2615
  }
2465
2616
  /**
2466
2617
  * Generates the default "Generated by Kubb" banner from config and optional node metadata.
@@ -2512,13 +2663,13 @@ function buildDefaultBanner({ title, description, version, config }) {
2512
2663
  *
2513
2664
  * @example Function banner with node
2514
2665
  * ```ts
2515
- * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2666
+ * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
2516
2667
  * // → '// v3.0.0'
2517
2668
  * ```
2518
2669
  *
2519
2670
  * @example No user banner — Kubb notice with OAS metadata
2520
2671
  * ```ts
2521
- * defaultResolveBanner(rootNode, { config })
2672
+ * defaultResolveBanner(inputNode, { config })
2522
2673
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
2523
2674
  * ```
2524
2675
  *
@@ -2554,7 +2705,7 @@ function defaultResolveBanner(node, { output, config }) {
2554
2705
  *
2555
2706
  * @example Function footer with node
2556
2707
  * ```ts
2557
- * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2708
+ * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
2558
2709
  * // → '// Pet Store'
2559
2710
  * ```
2560
2711
  */
@@ -2570,7 +2721,7 @@ function defaultResolveFooter(node, { output }) {
2570
2721
  * - `default` — name casing strategy (camelCase / PascalCase)
2571
2722
  * - `resolveOptions` — include/exclude/override filtering
2572
2723
  * - `resolvePath` — output path computation
2573
- * - `resolveFile` — full `KubbFile.File` construction
2724
+ * - `resolveFile` — full `FileNode` construction
2574
2725
  *
2575
2726
  * Methods in the builder have access to `this` (the full resolver object), so they
2576
2727
  * can call other resolver methods without circular imports.
@@ -2620,9 +2771,6 @@ function defineResolver(build) {
2620
2771
  };
2621
2772
  }
2622
2773
  //#endregion
2623
- //#region src/KubbFile.ts
2624
- var KubbFile_exports = /* @__PURE__ */ require_chunk.__exportAll({});
2625
- //#endregion
2626
2774
  //#region src/storages/memoryStorage.ts
2627
2775
  /**
2628
2776
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -2927,12 +3075,6 @@ function satisfiesDependency(dependency, version, cwd) {
2927
3075
  //#endregion
2928
3076
  exports.AsyncEventEmitter = AsyncEventEmitter;
2929
3077
  exports.FunctionParams = FunctionParams;
2930
- Object.defineProperty(exports, "KubbFile", {
2931
- enumerable: true,
2932
- get: function() {
2933
- return KubbFile_exports;
2934
- }
2935
- });
2936
3078
  exports.PluginDriver = PluginDriver;
2937
3079
  exports.URLPath = URLPath;
2938
3080
  exports.build = build;