@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.js CHANGED
@@ -1,14 +1,13 @@
1
- import { t as __exportAll } from "./chunk-O_arW02_.js";
1
+ import { t as __name } from "./chunk--u3MIqq1.js";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
- import path, { basename, dirname, extname, join, posix, relative, resolve } from "node:path";
6
- import { composeTransformers, composeTransformers as composeTransformers$1, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
7
- import { Fabric, createFabric, createReactFabric } from "@kubb/react-fabric";
8
- import { fsPlugin } from "@kubb/react-fabric/plugins";
5
+ import path, { basename, dirname, extname, join, posix, resolve } from "node:path";
6
+ import { composeTransformers, composeTransformers as composeTransformers$1, createExport, createFile, createSource, definePrinter, isOperationNode, isSchemaNode, transform, walk } from "@kubb/ast";
9
7
  import { performance } from "node:perf_hooks";
10
8
  import { deflateSync } from "fflate";
11
9
  import { x } from "tinyexec";
10
+ import { Fabric, createReactFabric } from "@kubb/react-fabric";
12
11
  import { jsx } from "@kubb/react-fabric/jsx-runtime";
13
12
  import { version } from "node:process";
14
13
  import { sortBy } from "remeda";
@@ -333,6 +332,24 @@ async function clean(path) {
333
332
  });
334
333
  }
335
334
  //#endregion
335
+ //#region ../../internals/utils/src/string.ts
336
+ /**
337
+ * Strips the file extension from a path or file name.
338
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
339
+ *
340
+ * @example
341
+ * trimExtName('petStore.ts') // 'petStore'
342
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
343
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
344
+ * trimExtName('noExtension') // 'noExtension'
345
+ */
346
+ function trimExtName$1(text) {
347
+ const dotIndex = text.lastIndexOf(".");
348
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
349
+ return text;
350
+ }
351
+ __name(trimExtName$1, "trimExtName");
352
+ //#endregion
336
353
  //#region ../../internals/utils/src/promise.ts
337
354
  /** Returns `true` when `result` is a rejected `Promise.allSettled` result with a typed `reason`.
338
355
  *
@@ -705,78 +722,6 @@ const formatters = {
705
722
  }
706
723
  };
707
724
  //#endregion
708
- //#region src/defineParser.ts
709
- /**
710
- * Defines a parser with type safety.
711
- *
712
- * Use this function to create parsers that transform generated files to strings
713
- * based on their extension.
714
- *
715
- * @example
716
- * ```ts
717
- * import { defineParser } from '@kubb/core'
718
- *
719
- * export const jsonParser = defineParser({
720
- * name: 'json',
721
- * extNames: ['.json'],
722
- * parse(file) {
723
- * return file.sources.map((s) => s.value).join('\n')
724
- * },
725
- * })
726
- * ```
727
- */
728
- function defineParser(parser) {
729
- return {
730
- install() {},
731
- type: "parser",
732
- ...parser
733
- };
734
- }
735
- //#endregion
736
- //#region src/devtools.ts
737
- /**
738
- * Encodes a `RootNode` as a compressed, URL-safe string.
739
- *
740
- * The JSON representation is deflate-compressed with {@link deflateSync} before
741
- * base64url encoding, which typically reduces payload size by 70–80 % and
742
- * keeps URLs well within browser and server path-length limits.
743
- *
744
- * Use {@link decodeAst} to reverse.
745
- */
746
- function encodeAst(root) {
747
- const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(root)));
748
- return Buffer.from(compressed).toString("base64url");
749
- }
750
- /**
751
- * Constructs the Kubb Studio URL for the given `RootNode`.
752
- * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
753
- * The `root` is encoded and attached as the `?root=` query parameter so Studio
754
- * can decode and render it without a round-trip to any server.
755
- */
756
- function getStudioUrl(root, studioUrl, options = {}) {
757
- return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(root)}`;
758
- }
759
- /**
760
- * Opens the Kubb Studio URL for the given `RootNode` in the default browser —
761
- *
762
- * Falls back to printing the URL if the browser cannot be launched.
763
- */
764
- async function openInStudio(root, studioUrl, options = {}) {
765
- const url = getStudioUrl(root, studioUrl, options);
766
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
767
- const args = process.platform === "win32" ? [
768
- "/c",
769
- "start",
770
- "",
771
- url
772
- ] : [url];
773
- try {
774
- await x(cmd, args);
775
- } catch {
776
- console.log(`\n ${url}\n`);
777
- }
778
- }
779
- //#endregion
780
725
  //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
781
726
  var Node = class {
782
727
  value;
@@ -904,6 +849,194 @@ function validateConcurrency(concurrency) {
904
849
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
905
850
  }
906
851
  //#endregion
852
+ //#region src/FileProcessor.ts
853
+ function joinSources(file) {
854
+ return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
855
+ }
856
+ /**
857
+ * Converts a single file to a string using the registered parsers.
858
+ * Falls back to joining source values when no matching parser is found.
859
+ */
860
+ var FileProcessor = class {
861
+ #limit = pLimit(100);
862
+ async parse(file, { parsers, extension } = {}) {
863
+ const parseExtName = extension?.[file.extname] || void 0;
864
+ if (!parsers || !file.extname) return joinSources(file);
865
+ const parser = parsers.get(file.extname);
866
+ if (!parser) return joinSources(file);
867
+ return parser.parse(file, { extname: parseExtName });
868
+ }
869
+ async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
870
+ await onStart?.(files);
871
+ const total = files.length;
872
+ let processed = 0;
873
+ const processOne = async (file) => {
874
+ const source = await this.parse(file, {
875
+ extension,
876
+ parsers
877
+ });
878
+ const currentProcessed = ++processed;
879
+ const percentage = currentProcessed / total * 100;
880
+ await onUpdate?.({
881
+ file,
882
+ source,
883
+ processed: currentProcessed,
884
+ percentage,
885
+ total
886
+ });
887
+ };
888
+ if (mode === "sequential") for (const file of files) await processOne(file);
889
+ else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
890
+ await onEnd?.(files);
891
+ return files;
892
+ }
893
+ };
894
+ //#endregion
895
+ //#region src/devtools.ts
896
+ /**
897
+ * Encodes an `InputNode` as a compressed, URL-safe string.
898
+ *
899
+ * The JSON representation is deflate-compressed with {@link deflateSync} before
900
+ * base64url encoding, which typically reduces payload size by 70–80 % and
901
+ * keeps URLs well within browser and server path-length limits.
902
+ *
903
+ * Use {@link decodeAst} to reverse.
904
+ */
905
+ function encodeAst(input) {
906
+ const compressed = deflateSync(new TextEncoder().encode(JSON.stringify(input)));
907
+ return Buffer.from(compressed).toString("base64url");
908
+ }
909
+ /**
910
+ * Constructs the Kubb Studio URL for the given `InputNode`.
911
+ * When `options.ast` is `true`, navigates to the AST inspector (`/ast`).
912
+ * The `input` is encoded and attached as the `?root=` query parameter so Studio
913
+ * can decode and render it without a round-trip to any server.
914
+ */
915
+ function getStudioUrl(input, studioUrl, options = {}) {
916
+ return `${studioUrl.replace(/\/$/, "")}${options.ast ? "/ast" : ""}?root=${encodeAst(input)}`;
917
+ }
918
+ /**
919
+ * Opens the Kubb Studio URL for the given `InputNode` in the default browser —
920
+ *
921
+ * Falls back to printing the URL if the browser cannot be launched.
922
+ */
923
+ async function openInStudio(input, studioUrl, options = {}) {
924
+ const url = getStudioUrl(input, studioUrl, options);
925
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
926
+ const args = process.platform === "win32" ? [
927
+ "/c",
928
+ "start",
929
+ "",
930
+ url
931
+ ] : [url];
932
+ try {
933
+ await x(cmd, args);
934
+ } catch {
935
+ console.log(`\n ${url}\n`);
936
+ }
937
+ }
938
+ //#endregion
939
+ //#region src/FileManager.ts
940
+ function mergeFile(a, b) {
941
+ return {
942
+ ...a,
943
+ sources: [...a.sources || [], ...b.sources || []],
944
+ imports: [...a.imports || [], ...b.imports || []],
945
+ exports: [...a.exports || [], ...b.exports || []]
946
+ };
947
+ }
948
+ /**
949
+ * In-memory file store for generated files.
950
+ *
951
+ * Files with the same `path` are merged — sources, imports, and exports are concatenated.
952
+ * The `files` getter returns all stored files sorted by path length (shortest first).
953
+ *
954
+ * @example
955
+ * ```ts
956
+ * import { FileManager } from '@kubb/core'
957
+ *
958
+ * const manager = new FileManager()
959
+ * manager.upsert(myFile)
960
+ * console.log(manager.files) // all stored files
961
+ * ```
962
+ */
963
+ var FileManager = class {
964
+ #cache = /* @__PURE__ */ new Map();
965
+ #filesCache = null;
966
+ /**
967
+ * Adds one or more files. Files with the same path are merged — sources, imports,
968
+ * and exports from all calls with the same path are concatenated together.
969
+ */
970
+ add(...files) {
971
+ const resolvedFiles = [];
972
+ const mergedFiles = /* @__PURE__ */ new Map();
973
+ files.forEach((file) => {
974
+ const existing = mergedFiles.get(file.path);
975
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
976
+ else mergedFiles.set(file.path, file);
977
+ });
978
+ for (const file of mergedFiles.values()) {
979
+ const resolvedFile = createFile(file);
980
+ this.#cache.set(resolvedFile.path, resolvedFile);
981
+ this.#filesCache = null;
982
+ resolvedFiles.push(resolvedFile);
983
+ }
984
+ return resolvedFiles;
985
+ }
986
+ /**
987
+ * Adds or merges one or more files.
988
+ * If a file with the same path already exists, its sources/imports/exports are merged together.
989
+ */
990
+ upsert(...files) {
991
+ const resolvedFiles = [];
992
+ const mergedFiles = /* @__PURE__ */ new Map();
993
+ files.forEach((file) => {
994
+ const existing = mergedFiles.get(file.path);
995
+ if (existing) mergedFiles.set(file.path, mergeFile(existing, file));
996
+ else mergedFiles.set(file.path, file);
997
+ });
998
+ for (const file of mergedFiles.values()) {
999
+ const existing = this.#cache.get(file.path);
1000
+ const resolvedFile = createFile(existing ? mergeFile(existing, file) : file);
1001
+ this.#cache.set(resolvedFile.path, resolvedFile);
1002
+ this.#filesCache = null;
1003
+ resolvedFiles.push(resolvedFile);
1004
+ }
1005
+ return resolvedFiles;
1006
+ }
1007
+ getByPath(path) {
1008
+ return this.#cache.get(path) ?? null;
1009
+ }
1010
+ deleteByPath(path) {
1011
+ this.#cache.delete(path);
1012
+ this.#filesCache = null;
1013
+ }
1014
+ clear() {
1015
+ this.#cache.clear();
1016
+ this.#filesCache = null;
1017
+ }
1018
+ /**
1019
+ * All stored files, sorted by path length (shorter paths first).
1020
+ * Barrel/index files (e.g. index.ts) are sorted last within each length bucket.
1021
+ */
1022
+ get files() {
1023
+ if (this.#filesCache) return this.#filesCache;
1024
+ const keys = [...this.#cache.keys()].sort((a, b) => {
1025
+ if (a.length !== b.length) return a.length - b.length;
1026
+ const aIsIndex = trimExtName$1(a).endsWith("index");
1027
+ if (aIsIndex !== trimExtName$1(b).endsWith("index")) return aIsIndex ? 1 : -1;
1028
+ return 0;
1029
+ });
1030
+ const files = [];
1031
+ for (const key of keys) {
1032
+ const file = this.#cache.get(key);
1033
+ if (file) files.push(file);
1034
+ }
1035
+ this.#filesCache = files;
1036
+ return files;
1037
+ }
1038
+ };
1039
+ //#endregion
907
1040
  //#region src/utils/executeStrategies.ts
908
1041
  /**
909
1042
  * Runs promise functions in sequence, threading each result into the next call.
@@ -970,12 +1103,18 @@ var PluginDriver = class {
970
1103
  config;
971
1104
  options;
972
1105
  /**
973
- * The universal `@kubb/ast` `RootNode` produced by the adapter, set by
1106
+ * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
974
1107
  * the build pipeline after the adapter's `parse()` resolves.
975
1108
  */
976
- rootNode = void 0;
1109
+ inputNode = void 0;
977
1110
  adapter = void 0;
978
1111
  #studioIsOpen = false;
1112
+ /**
1113
+ * Central file store for all generated files.
1114
+ * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
1115
+ * add files; this property gives direct read/write access when needed.
1116
+ */
1117
+ fileManager = new FileManager();
979
1118
  plugins = /* @__PURE__ */ new Map();
980
1119
  constructor(config, options) {
981
1120
  this.config = config;
@@ -1000,7 +1139,6 @@ var PluginDriver = class {
1000
1139
  getContext(plugin) {
1001
1140
  const driver = this;
1002
1141
  const baseContext = {
1003
- fabric: driver.options.fabric,
1004
1142
  config: driver.config,
1005
1143
  get root() {
1006
1144
  return resolve(driver.config.root, driver.config.output.path);
@@ -1014,13 +1152,13 @@ var PluginDriver = class {
1014
1152
  requirePlugin: driver.requirePlugin.bind(driver),
1015
1153
  driver,
1016
1154
  addFile: async (...files) => {
1017
- await this.options.fabric.addFile(...files);
1155
+ driver.fileManager.add(...files);
1018
1156
  },
1019
1157
  upsertFile: async (...files) => {
1020
- await this.options.fabric.upsertFile(...files);
1158
+ driver.fileManager.upsert(...files);
1021
1159
  },
1022
- get rootNode() {
1023
- return driver.rootNode;
1160
+ get inputNode() {
1161
+ return driver.inputNode;
1024
1162
  },
1025
1163
  get adapter() {
1026
1164
  return driver.adapter;
@@ -1043,10 +1181,10 @@ var PluginDriver = class {
1043
1181
  openInStudio(options) {
1044
1182
  if (!driver.config.devtools || driver.#studioIsOpen) return;
1045
1183
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1046
- if (!driver.rootNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1184
+ if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1047
1185
  driver.#studioIsOpen = true;
1048
1186
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://studio.kubb.dev";
1049
- return openInStudio(driver.rootNode, studioUrl, options);
1187
+ return openInStudio(driver.inputNode, studioUrl, options);
1050
1188
  }
1051
1189
  };
1052
1190
  const mergedExtras = {};
@@ -1075,14 +1213,14 @@ var PluginDriver = class {
1075
1213
  options
1076
1214
  });
1077
1215
  if (!path) throw new Error(`Filepath should be defined for resolvedName "${resolvedName}" and pluginName "${pluginName}"`);
1078
- return {
1216
+ return createFile({
1079
1217
  path,
1080
1218
  baseName: basename(path),
1081
1219
  meta: { pluginName },
1082
1220
  sources: [],
1083
1221
  imports: [],
1084
1222
  exports: []
1085
- };
1223
+ });
1086
1224
  }
1087
1225
  /**
1088
1226
  * @deprecated use resolvers context instead
@@ -1366,19 +1504,19 @@ var PluginDriver = class {
1366
1504
  /**
1367
1505
  * Handles the return value of a plugin AST hook or generator method.
1368
1506
  *
1369
- * - React element → rendered via an isolated react-fabric context, files merged into `fabric`
1370
- * - `Array<KubbFile.File>` → upserted directly into `fabric`
1507
+ * - React element → rendered via an isolated react-fabric context, files stored in `driver.fileManager`
1508
+ * - `Array<FileNode>` → upserted directly into `driver.fileManager`
1371
1509
  * - `void` / `null` / `undefined` → no-op (plugin handled it via `this.upsertFile`)
1372
1510
  */
1373
- async function applyHookResult(result, fabric) {
1511
+ async function applyHookResult(result, driver) {
1374
1512
  if (!result) return;
1375
1513
  if (Array.isArray(result)) {
1376
- await fabric.upsertFile(...result);
1514
+ driver.fileManager.upsert(...result);
1377
1515
  return;
1378
1516
  }
1379
1517
  const fabricChild = createReactFabric();
1380
1518
  await fabricChild.render(/* @__PURE__ */ jsx(Fabric, { children: result }));
1381
- fabric.context.fileManager.upsert(...fabricChild.files);
1519
+ driver.fileManager.upsert(...fabricChild.files);
1382
1520
  fabricChild.unmount();
1383
1521
  }
1384
1522
  //#endregion
@@ -1479,7 +1617,7 @@ const fsStorage = createStorage(() => ({
1479
1617
  }));
1480
1618
  //#endregion
1481
1619
  //#region package.json
1482
- var version$1 = "5.0.0-alpha.31";
1620
+ var version$1 = "5.0.0-alpha.32";
1483
1621
  //#endregion
1484
1622
  //#region src/utils/diagnostics.ts
1485
1623
  /**
@@ -1501,7 +1639,7 @@ function getDiagnosticInfo() {
1501
1639
  //#region src/utils/TreeNode.ts
1502
1640
  /**
1503
1641
  * Tree structure used to build per-directory barrel (`index.ts`) files from a
1504
- * flat list of generated {@link KubbFile.File} entries.
1642
+ * flat list of generated {@link FileNode} entries.
1505
1643
  *
1506
1644
  * Each node represents either a directory or a file within the output tree.
1507
1645
  * Use {@link TreeNode.build} to construct a root node from a file list, then
@@ -1662,36 +1800,36 @@ function getBarrelFilesByRoot(root, files) {
1662
1800
  const cachedFiles = /* @__PURE__ */ new Map();
1663
1801
  TreeNode.build(files, root)?.forEach((treeNode) => {
1664
1802
  if (!treeNode?.children || !treeNode.parent?.data.path) return;
1665
- const barrelFile = {
1803
+ const barrelFile = createFile({
1666
1804
  path: join(treeNode.parent?.data.path, "index.ts"),
1667
1805
  baseName: "index.ts",
1668
1806
  exports: [],
1669
1807
  imports: [],
1670
1808
  sources: []
1671
- };
1809
+ });
1672
1810
  const previousBarrelFile = cachedFiles.get(barrelFile.path);
1673
1811
  treeNode.leaves.forEach((item) => {
1674
1812
  if (!item.data.name) return;
1675
1813
  (item.data.file?.sources || []).forEach((source) => {
1676
1814
  if (!item.data.file?.path || !source.isIndexable || !source.name) return;
1677
1815
  if (previousBarrelFile?.sources.some((item) => item.name === source.name && item.isTypeOnly === source.isTypeOnly)) return;
1678
- barrelFile.exports.push({
1816
+ barrelFile.exports.push(createExport({
1679
1817
  name: [source.name],
1680
1818
  path: getRelativePath(treeNode.parent?.data.path, item.data.path),
1681
1819
  isTypeOnly: source.isTypeOnly
1682
- });
1683
- barrelFile.sources.push({
1820
+ }));
1821
+ barrelFile.sources.push(createSource({
1684
1822
  name: source.name,
1685
1823
  isTypeOnly: source.isTypeOnly,
1686
1824
  value: "",
1687
1825
  isExportable: false,
1688
1826
  isIndexable: false
1689
- });
1827
+ }));
1690
1828
  });
1691
1829
  });
1692
1830
  if (previousBarrelFile) {
1693
1831
  previousBarrelFile.sources.push(...barrelFile.sources);
1694
- previousBarrelFile.exports?.push(...barrelFile.exports || []);
1832
+ previousBarrelFile.exports.push(...barrelFile.exports);
1695
1833
  } else cachedFiles.set(barrelFile.path, barrelFile);
1696
1834
  });
1697
1835
  return [...cachedFiles.values()];
@@ -1717,7 +1855,7 @@ async function getBarrelFiles(files, { type, meta = {}, root, output }) {
1717
1855
  if (type === "all") return barrelFiles.map((file) => {
1718
1856
  return {
1719
1857
  ...file,
1720
- exports: file.exports?.map((exportItem) => {
1858
+ exports: file.exports.map((exportItem) => {
1721
1859
  return {
1722
1860
  ...exportItem,
1723
1861
  name: void 0
@@ -1748,7 +1886,7 @@ function isInputPath(config) {
1748
1886
  * - Validates the input path (when applicable).
1749
1887
  * - Applies config defaults (`root`, `output.*`, `devtools`).
1750
1888
  * - Creates the Fabric instance and wires storage, format, and lint hooks.
1751
- * - Runs the adapter (if configured) to produce the universal `RootNode`.
1889
+ * - Runs the adapter (if configured) to produce the universal `InputNode`.
1752
1890
  * When no adapter is supplied and `@kubb/adapter-oas` is installed as an
1753
1891
  *
1754
1892
  * Pass the returned {@link SetupResult} directly to {@link safeBuild} or {@link build}
@@ -1816,53 +1954,7 @@ async function setup(options) {
1816
1954
  });
1817
1955
  await storage?.clear(resolve(config.root, config.output.path));
1818
1956
  }
1819
- const fabric = createFabric();
1820
- fabric.use(fsPlugin);
1821
- for (const parser of config.parsers) fabric.use(parser);
1822
- fabric.use(defineParser({
1823
- name: "fallback",
1824
- extNames: void 0,
1825
- parse(file) {
1826
- return file.sources.map((item) => item.value).filter((value) => value != null).join("\n\n");
1827
- }
1828
- }));
1829
- fabric.context.on("files:processing:start", (files) => {
1830
- events.emit("files:processing:start", files);
1831
- events.emit("debug", {
1832
- date: /* @__PURE__ */ new Date(),
1833
- logs: [`Writing ${files.length} files...`]
1834
- });
1835
- });
1836
- fabric.context.on("file:processing:update", async (params) => {
1837
- const { file, source } = params;
1838
- await events.emit("file:processing:update", {
1839
- ...params,
1840
- config,
1841
- source
1842
- });
1843
- if (source) {
1844
- const key = relative(resolve(config.root), file.path);
1845
- await storage?.setItem(key, source);
1846
- sources.set(file.path, source);
1847
- }
1848
- });
1849
- fabric.context.on("files:processing:end", async (files) => {
1850
- await events.emit("files:processing:end", files);
1851
- await events.emit("debug", {
1852
- date: /* @__PURE__ */ new Date(),
1853
- logs: [`✓ File write process completed for ${files.length} files`]
1854
- });
1855
- });
1856
- await events.emit("debug", {
1857
- date: /* @__PURE__ */ new Date(),
1858
- logs: [
1859
- "✓ Fabric initialized",
1860
- ` • Storage: ${storage ? storage.name : "disabled (dry-run)"}`,
1861
- ` • Barrel type: ${config.output.barrelType || "none"}`
1862
- ]
1863
- });
1864
1957
  const driver = new PluginDriver(config, {
1865
- fabric,
1866
1958
  events,
1867
1959
  concurrency: 15
1868
1960
  });
@@ -1874,21 +1966,21 @@ async function setup(options) {
1874
1966
  logs: [`Running adapter: ${adapter.name}`]
1875
1967
  });
1876
1968
  driver.adapter = adapter;
1877
- driver.rootNode = await adapter.parse(source);
1969
+ driver.inputNode = await adapter.parse(source);
1878
1970
  await events.emit("debug", {
1879
1971
  date: /* @__PURE__ */ new Date(),
1880
1972
  logs: [
1881
- `✓ Adapter '${adapter.name}' resolved RootNode`,
1882
- ` • Schemas: ${driver.rootNode.schemas.length}`,
1883
- ` • Operations: ${driver.rootNode.operations.length}`
1973
+ `✓ Adapter '${adapter.name}' resolved InputNode`,
1974
+ ` • Schemas: ${driver.inputNode.schemas.length}`,
1975
+ ` • Operations: ${driver.inputNode.operations.length}`
1884
1976
  ]
1885
1977
  });
1886
1978
  return {
1887
1979
  config,
1888
1980
  events,
1889
- fabric,
1890
1981
  driver,
1891
- sources
1982
+ sources,
1983
+ storage
1892
1984
  };
1893
1985
  }
1894
1986
  /**
@@ -1898,7 +1990,7 @@ async function setup(options) {
1898
1990
  * Pass an existing {@link SetupResult} via `overrides` to skip the setup phase.
1899
1991
  */
1900
1992
  async function build(options, overrides) {
1901
- const { fabric, files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1993
+ const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(options, overrides);
1902
1994
  if (error) throw error;
1903
1995
  if (failedPlugins.size > 0) {
1904
1996
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1906,7 +1998,6 @@ async function build(options, overrides) {
1906
1998
  }
1907
1999
  return {
1908
2000
  failedPlugins,
1909
- fabric,
1910
2001
  files,
1911
2002
  driver,
1912
2003
  pluginTimings,
@@ -1921,15 +2012,15 @@ async function build(options, overrides) {
1921
2012
  * - Each hook accepts a single handler **or an array** — all entries are called in sequence.
1922
2013
  * - Nodes that are excluded by `exclude`/`include` plugin options are skipped automatically.
1923
2014
  * - Return values are handled via `applyHookResult`: React elements are rendered,
1924
- * `KubbFile.File[]` are written via upsert, and `void` is a no-op (manual handling).
2015
+ * `FileNode[]` are written via upsert, and `void` is a no-op (manual handling).
1925
2016
  * - Barrel files are generated automatically when `output.barrelType` is set.
1926
2017
  */
1927
2018
  async function runPluginAstHooks(plugin, context) {
1928
- const { adapter, rootNode, resolver, fabric } = context;
2019
+ const { adapter, inputNode, resolver, driver } = context;
1929
2020
  const { exclude, include, override } = plugin.options;
1930
- 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.`);
2021
+ 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.`);
1931
2022
  const collectedOperations = [];
1932
- await walk(rootNode, {
2023
+ await walk(inputNode, {
1933
2024
  depth: "shallow",
1934
2025
  async schema(node) {
1935
2026
  if (!plugin.schema) return;
@@ -1941,7 +2032,7 @@ async function runPluginAstHooks(plugin, context) {
1941
2032
  override
1942
2033
  });
1943
2034
  if (options === null) return;
1944
- await applyHookResult(await plugin.schema.call(context, transformedNode, options), fabric);
2035
+ await applyHookResult(await plugin.schema.call(context, transformedNode, options), driver);
1945
2036
  },
1946
2037
  async operation(node) {
1947
2038
  const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
@@ -1953,24 +2044,24 @@ async function runPluginAstHooks(plugin, context) {
1953
2044
  });
1954
2045
  if (options !== null) {
1955
2046
  collectedOperations.push(transformedNode);
1956
- if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), fabric);
2047
+ if (plugin.operation) await applyHookResult(await plugin.operation.call(context, transformedNode, options), driver);
1957
2048
  }
1958
2049
  }
1959
2050
  });
1960
- if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), fabric);
2051
+ if (plugin.operations && collectedOperations.length > 0) await applyHookResult(await plugin.operations.call(context, collectedOperations, plugin.options), driver);
1961
2052
  }
1962
2053
  /**
1963
2054
  * Runs a full Kubb build and captures errors instead of throwing.
1964
2055
  *
1965
2056
  * - Installs each plugin in order, recording failures in `failedPlugins`.
1966
2057
  * - Generates the root barrel file when `output.barrelType` is set.
1967
- * - Writes all files through Fabric.
2058
+ * - Writes all files through the driver's FileManager and FileProcessor.
1968
2059
  *
1969
2060
  * Returns a {@link BuildOutput} even on failure — inspect `error` and
1970
2061
  * `failedPlugins` to determine whether the build succeeded.
1971
2062
  */
1972
2063
  async function safeBuild(options, overrides) {
1973
- const { fabric, driver, events, sources } = overrides ? overrides : await setup(options);
2064
+ const { driver, events, sources, storage } = overrides ? overrides : await setup(options);
1974
2065
  const failedPlugins = /* @__PURE__ */ new Set();
1975
2066
  const pluginTimings = /* @__PURE__ */ new Map();
1976
2067
  const config = driver.config;
@@ -1990,7 +2081,7 @@ async function safeBuild(options, overrides) {
1990
2081
  await plugin.buildStart.call(context);
1991
2082
  if (plugin.schema || plugin.operation || plugin.operations) await runPluginAstHooks(plugin, context);
1992
2083
  if (output) {
1993
- const barrelFiles = await getBarrelFiles(fabric.files, {
2084
+ const barrelFiles = await getBarrelFiles(driver.fileManager.files, {
1994
2085
  type: output.barrelType ?? "named",
1995
2086
  root,
1996
2087
  output,
@@ -2044,15 +2135,15 @@ async function safeBuild(options, overrides) {
2044
2135
  ` • Path: ${rootPath}`
2045
2136
  ]
2046
2137
  });
2047
- const barrelFiles = fabric.files.filter((file) => {
2138
+ const barrelFiles = driver.fileManager.files.filter((file) => {
2048
2139
  return file.sources.some((source) => source.isIndexable);
2049
2140
  });
2050
2141
  await events.emit("debug", {
2051
2142
  date: /* @__PURE__ */ new Date(),
2052
2143
  logs: [`Found ${barrelFiles.length} indexable files for barrel export`]
2053
2144
  });
2054
- const existingBarrel = fabric.files.find((f) => f.path === rootPath);
2055
- const rootFile = {
2145
+ const existingBarrel = driver.fileManager.files.find((f) => f.path === rootPath);
2146
+ const rootFile = createFile({
2056
2147
  path: rootPath,
2057
2148
  baseName: BARREL_FILENAME,
2058
2149
  exports: buildBarrelExports({
@@ -2061,26 +2152,59 @@ async function safeBuild(options, overrides) {
2061
2152
  existingExports: new Set(existingBarrel?.exports?.flatMap((e) => Array.isArray(e.name) ? e.name : [e.name]).filter((n) => Boolean(n)) ?? []),
2062
2153
  config,
2063
2154
  driver
2064
- }),
2155
+ }).map((e) => createExport(e)),
2065
2156
  sources: [],
2066
2157
  imports: [],
2067
2158
  meta: {}
2068
- };
2069
- await fabric.upsertFile(rootFile);
2159
+ });
2160
+ driver.fileManager.upsert(rootFile);
2070
2161
  await events.emit("debug", {
2071
2162
  date: /* @__PURE__ */ new Date(),
2072
2163
  logs: [`✓ Generated barrel file (${rootFile.exports?.length || 0} exports)`]
2073
2164
  });
2074
2165
  }
2075
- const files = [...fabric.files];
2076
- await fabric.write({ extension: config.output.extension });
2166
+ const files = driver.fileManager.files;
2167
+ const parsersMap = /* @__PURE__ */ new Map();
2168
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
2169
+ const fileProcessor = new FileProcessor();
2170
+ await events.emit("debug", {
2171
+ date: /* @__PURE__ */ new Date(),
2172
+ logs: [`Writing ${files.length} files...`]
2173
+ });
2174
+ await fileProcessor.run(files, {
2175
+ parsers: parsersMap,
2176
+ extension: config.output.extension,
2177
+ onStart: async (processingFiles) => {
2178
+ await events.emit("files:processing:start", processingFiles);
2179
+ },
2180
+ onUpdate: async ({ file, source, processed, total, percentage }) => {
2181
+ await events.emit("file:processing:update", {
2182
+ file,
2183
+ source,
2184
+ processed,
2185
+ total,
2186
+ percentage,
2187
+ config
2188
+ });
2189
+ if (source) {
2190
+ await storage?.setItem(file.path, source);
2191
+ sources.set(file.path, source);
2192
+ }
2193
+ },
2194
+ onEnd: async (processedFiles) => {
2195
+ await events.emit("files:processing:end", processedFiles);
2196
+ await events.emit("debug", {
2197
+ date: /* @__PURE__ */ new Date(),
2198
+ logs: [`✓ File write process completed for ${processedFiles.length} files`]
2199
+ });
2200
+ }
2201
+ });
2077
2202
  for (const plugin of driver.plugins.values()) if (plugin.buildEnd) {
2078
2203
  const context = driver.getContext(plugin);
2079
2204
  await plugin.buildEnd.call(context);
2080
2205
  }
2081
2206
  return {
2082
2207
  failedPlugins,
2083
- fabric,
2084
2208
  files,
2085
2209
  driver,
2086
2210
  pluginTimings,
@@ -2089,7 +2213,6 @@ async function safeBuild(options, overrides) {
2089
2213
  } catch (error) {
2090
2214
  return {
2091
2215
  failedPlugins,
2092
- fabric,
2093
2216
  files: [],
2094
2217
  driver,
2095
2218
  pluginTimings,
@@ -2110,11 +2233,11 @@ function buildBarrelExports({ barrelFiles, rootDir, existingExports, config, dri
2110
2233
  if (!pluginOptions || pluginOptions.output?.barrelType === false) return [];
2111
2234
  const exportName = config.output.barrelType === "all" ? void 0 : source.name ? [source.name] : void 0;
2112
2235
  if (exportName?.some((n) => existingExports.has(n))) return [];
2113
- return [{
2236
+ return [createExport({
2114
2237
  name: exportName,
2115
2238
  path: getRelativePath(rootDir, file.path),
2116
2239
  isTypeOnly: config.output.barrelType === "all" ? containsOnlyTypes : source.isTypeOnly
2117
- }];
2240
+ })];
2118
2241
  });
2119
2242
  });
2120
2243
  }
@@ -2226,19 +2349,19 @@ function mergeGenerators(generators) {
2226
2349
  async schema(node, options) {
2227
2350
  for (const gen of generators) {
2228
2351
  if (!gen.schema) continue;
2229
- await applyHookResult(await gen.schema.call(this, node, options), this.fabric);
2352
+ await applyHookResult(await gen.schema.call(this, node, options), this.driver);
2230
2353
  }
2231
2354
  },
2232
2355
  async operation(node, options) {
2233
2356
  for (const gen of generators) {
2234
2357
  if (!gen.operation) continue;
2235
- await applyHookResult(await gen.operation.call(this, node, options), this.fabric);
2358
+ await applyHookResult(await gen.operation.call(this, node, options), this.driver);
2236
2359
  }
2237
2360
  },
2238
2361
  async operations(nodes, options) {
2239
2362
  for (const gen of generators) {
2240
2363
  if (!gen.operations) continue;
2241
- await applyHookResult(await gen.operations.call(this, nodes, options), this.fabric);
2364
+ await applyHookResult(await gen.operations.call(this, nodes, options), this.driver);
2242
2365
  }
2243
2366
  }
2244
2367
  };
@@ -2261,6 +2384,34 @@ function defineLogger(logger) {
2261
2384
  return logger;
2262
2385
  }
2263
2386
  //#endregion
2387
+ //#region src/defineParser.ts
2388
+ /**
2389
+ * Defines a parser with type safety.
2390
+ *
2391
+ * Use this function to create parsers that transform generated files to strings
2392
+ * based on their extension.
2393
+ *
2394
+ * @example
2395
+ * ```ts
2396
+ * import { defineParser } from '@kubb/core'
2397
+ *
2398
+ * export const jsonParser = defineParser({
2399
+ * name: 'json',
2400
+ * extNames: ['.json'],
2401
+ * parse(file) {
2402
+ * return file.sources.map((s) => s.value).join('\n')
2403
+ * },
2404
+ * })
2405
+ * ```
2406
+ */
2407
+ function defineParser(parser) {
2408
+ return {
2409
+ install() {},
2410
+ type: "parser",
2411
+ ...parser
2412
+ };
2413
+ }
2414
+ //#endregion
2264
2415
  //#region src/definePresets.ts
2265
2416
  /**
2266
2417
  * Creates a typed presets registry object — a named collection of {@link Preset} entries.
@@ -2414,7 +2565,7 @@ function defaultResolvePath({ baseName, pathMode, tag, path: groupPath }, { root
2414
2565
  /**
2415
2566
  * Default file resolver used by `defineResolver`.
2416
2567
  *
2417
- * Resolves a `KubbFile.File` by combining name resolution (`resolver.default`) with
2568
+ * Resolves a `FileNode` by combining name resolution (`resolver.default`) with
2418
2569
  * path resolution (`resolver.resolvePath`). The resolved file always has empty
2419
2570
  * `sources`, `imports`, and `exports` arrays — consumers populate those separately.
2420
2571
  *
@@ -2447,14 +2598,14 @@ function defaultResolveFile({ name, extname, tag, path: groupPath }, context) {
2447
2598
  tag,
2448
2599
  path: groupPath
2449
2600
  }, context);
2450
- return {
2601
+ return createFile({
2451
2602
  path: filePath,
2452
2603
  baseName: path.basename(filePath),
2453
2604
  meta: { pluginName: this.pluginName },
2454
2605
  sources: [],
2455
2606
  imports: [],
2456
2607
  exports: []
2457
- };
2608
+ });
2458
2609
  }
2459
2610
  /**
2460
2611
  * Generates the default "Generated by Kubb" banner from config and optional node metadata.
@@ -2506,13 +2657,13 @@ function buildDefaultBanner({ title, description, version, config }) {
2506
2657
  *
2507
2658
  * @example Function banner with node
2508
2659
  * ```ts
2509
- * defaultResolveBanner(rootNode, { output: { banner: (node) => `// v${node.version}` }, config })
2660
+ * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
2510
2661
  * // → '// v3.0.0'
2511
2662
  * ```
2512
2663
  *
2513
2664
  * @example No user banner — Kubb notice with OAS metadata
2514
2665
  * ```ts
2515
- * defaultResolveBanner(rootNode, { config })
2666
+ * defaultResolveBanner(inputNode, { config })
2516
2667
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
2517
2668
  * ```
2518
2669
  *
@@ -2548,7 +2699,7 @@ function defaultResolveBanner(node, { output, config }) {
2548
2699
  *
2549
2700
  * @example Function footer with node
2550
2701
  * ```ts
2551
- * defaultResolveFooter(rootNode, { output: { footer: (node) => `// ${node.title}` }, config })
2702
+ * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
2552
2703
  * // → '// Pet Store'
2553
2704
  * ```
2554
2705
  */
@@ -2564,7 +2715,7 @@ function defaultResolveFooter(node, { output }) {
2564
2715
  * - `default` — name casing strategy (camelCase / PascalCase)
2565
2716
  * - `resolveOptions` — include/exclude/override filtering
2566
2717
  * - `resolvePath` — output path computation
2567
- * - `resolveFile` — full `KubbFile.File` construction
2718
+ * - `resolveFile` — full `FileNode` construction
2568
2719
  *
2569
2720
  * Methods in the builder have access to `this` (the full resolver object), so they
2570
2721
  * can call other resolver methods without circular imports.
@@ -2614,9 +2765,6 @@ function defineResolver(build) {
2614
2765
  };
2615
2766
  }
2616
2767
  //#endregion
2617
- //#region src/KubbFile.ts
2618
- var KubbFile_exports = /* @__PURE__ */ __exportAll({});
2619
- //#endregion
2620
2768
  //#region src/storages/memoryStorage.ts
2621
2769
  /**
2622
2770
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -2919,6 +3067,6 @@ function satisfiesDependency(dependency, version, cwd) {
2919
3067
  return satisfies(semVer, version);
2920
3068
  }
2921
3069
  //#endregion
2922
- export { AsyncEventEmitter, FunctionParams, KubbFile_exports as KubbFile, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, defineParser, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
3070
+ export { AsyncEventEmitter, FunctionParams, PluginDriver, URLPath, build, build as default, buildDefaultBanner, composeTransformers, createAdapter, createPlugin, createStorage, defaultResolveBanner, defaultResolveFile, defaultResolveFooter, defaultResolveOptions, defaultResolvePath, defineConfig, defineGenerator, defineLogger, defineParser, definePresets, definePrinter, defineResolver, detectFormatter, detectLinter, formatters, fsStorage, getBarrelFiles, getConfigs, getMode, getPreset, isInputPath, linters, logLevel, memoryStorage, mergeGenerators, safeBuild, satisfiesDependency, setup };
2923
3071
 
2924
3072
  //# sourceMappingURL=index.js.map