@kubb/core 5.0.0-beta.53 → 5.0.0-beta.55

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
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_memoryStorage = require("./memoryStorage-CIEzDI6b.cjs");
2
+ const require_memoryStorage = require("./memoryStorage-skOz0dXZ.cjs");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_util = require("node:util");
5
5
  let node_fs_promises = require("node:fs/promises");
@@ -98,37 +98,75 @@ function randomCliColor(text) {
98
98
  //#endregion
99
99
  //#region ../../internals/utils/src/runtime.ts
100
100
  /**
101
- * Returns `true` when the current process is running under Bun.
101
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
102
102
  *
103
- * Detection keys off the global `Bun` object rather than `process.versions`,
104
- * because Bun polyfills `process.versions.node` for Node compatibility and would
105
- * otherwise look like Node.
106
- *
107
- * @example
108
- * ```ts
109
- * if (isBun()) {
110
- * await Bun.write(path, data)
111
- * }
112
- * ```
103
+ * Prefer the shared {@link runtime} instance over constructing your own.
113
104
  */
114
- function isBun() {
115
- return typeof Bun !== "undefined";
116
- }
117
- //#endregion
118
- //#region ../../internals/utils/src/fs.ts
105
+ var Runtime = class {
106
+ /**
107
+ * `true` when the current process is running under Bun.
108
+ *
109
+ * Detection keys off the global `Bun` object rather than `process.versions`,
110
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
111
+ * otherwise look like Node.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * if (runtime.isBun) {
116
+ * await Bun.write(path, data)
117
+ * }
118
+ * ```
119
+ */
120
+ get isBun() {
121
+ return typeof Bun !== "undefined";
122
+ }
123
+ /**
124
+ * `true` when the current process is running under Deno.
125
+ */
126
+ get isDeno() {
127
+ return typeof globalThis.Deno !== "undefined";
128
+ }
129
+ /**
130
+ * `true` when the current process is running under Node.
131
+ *
132
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
133
+ */
134
+ get isNode() {
135
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
136
+ }
137
+ /**
138
+ * Name of the runtime executing the current process.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
143
+ * ```
144
+ */
145
+ get name() {
146
+ if (this.isBun) return "bun";
147
+ if (this.isDeno) return "deno";
148
+ return "node";
149
+ }
150
+ /**
151
+ * Version of the active runtime, or an empty string when it cannot be read.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
156
+ * ```
157
+ */
158
+ get version() {
159
+ if (this.isBun) return process.versions.bun ?? "";
160
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
161
+ return process.versions?.node ?? "";
162
+ }
163
+ };
119
164
  /**
120
- * Reads the file at `path` as a UTF-8 string.
121
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
122
- *
123
- * @example
124
- * ```ts
125
- * const source = await read('./src/Pet.ts')
126
- * ```
165
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
127
166
  */
128
- async function read(path) {
129
- if (isBun()) return Bun.file(path).text();
130
- return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
131
- }
167
+ const runtime = new Runtime();
168
+ //#endregion
169
+ //#region ../../internals/utils/src/fs.ts
132
170
  /**
133
171
  * Writes `data` to `path`, trimming leading/trailing whitespace before saving.
134
172
  * Skips the write when the trimmed content is empty or identical to what is already on disk.
@@ -146,7 +184,7 @@ async function write(path, data, options = {}) {
146
184
  const trimmed = data.trim();
147
185
  if (trimmed === "") return null;
148
186
  const resolved = (0, node_path.resolve)(path);
149
- if (isBun()) {
187
+ if (runtime.isBun) {
150
188
  const file = Bun.file(resolved);
151
189
  if ((await file.exists() ? await file.text() : null) === trimmed) return null;
152
190
  await Bun.write(resolved, trimmed);
@@ -178,8 +216,6 @@ async function clean(path) {
178
216
  force: true
179
217
  });
180
218
  }
181
- //#endregion
182
- //#region ../../internals/utils/src/path.ts
183
219
  /**
184
220
  * Converts a filesystem path to use POSIX (`/`) separators.
185
221
  *
@@ -197,6 +233,29 @@ async function clean(path) {
197
233
  function toPosixPath(filePath) {
198
234
  return filePath.replaceAll("\\", "/");
199
235
  }
236
+ /**
237
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
238
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
239
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
240
+ *
241
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
242
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
243
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
244
+ * absolute path, letting generated files escape the configured output directory.
245
+ *
246
+ * @example Nested path from a dotted name
247
+ * `toFilePath('pet.petId') // 'pet/petId'`
248
+ *
249
+ * @example PascalCase the final segment
250
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
251
+ *
252
+ * @example Suffix applied to the final segment only
253
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
254
+ */
255
+ function toFilePath(name, caseLast = require_memoryStorage.camelCase) {
256
+ const parts = name.split(/\.(?=[a-zA-Z])/);
257
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : require_memoryStorage.camelCase(part)).filter(Boolean).join("/");
258
+ }
200
259
  //#endregion
201
260
  //#region ../../internals/utils/src/promise.ts
202
261
  function* chunks(arr, size) {
@@ -427,99 +486,80 @@ function isIdentifier(name) {
427
486
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
428
487
  }
429
488
  //#endregion
430
- //#region ../../internals/utils/src/urlPath.ts
489
+ //#region ../../internals/utils/src/url.ts
490
+ function transformParam(raw, casing) {
491
+ const param = isValidVarName(raw) ? raw : require_memoryStorage.camelCase(raw);
492
+ return casing === "camelcase" ? require_memoryStorage.camelCase(param) : param;
493
+ }
494
+ function toParamsObject(path, { replacer, casing } = {}) {
495
+ const params = {};
496
+ for (const match of path.matchAll(/\{([^}]+)\}/g)) {
497
+ const param = transformParam(match[1], casing);
498
+ const key = replacer ? replacer(param) : param;
499
+ params[key] = key;
500
+ }
501
+ return Object.keys(params).length > 0 ? params : null;
502
+ }
431
503
  /**
432
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
433
- *
434
- * @example
435
- * const p = new URLPath('/pet/{petId}')
436
- * p.URL // '/pet/:petId'
437
- * p.template // '`/pet/${petId}`'
504
+ * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
438
505
  */
439
- var URLPath = class {
506
+ var Url = class Url {
440
507
  /**
441
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
442
- */
443
- path;
444
- #options;
445
- constructor(path, options = {}) {
446
- this.path = path;
447
- this.#options = options;
448
- }
449
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
508
+ * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
450
509
  *
451
510
  * @example
452
- * ```ts
453
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
454
- * ```
511
+ * Url.canParse('https://petstore.swagger.io/v2') // true
512
+ * Url.canParse('/pet/{petId}') // false
455
513
  */
456
- get URL() {
457
- return this.toURLPath();
514
+ static canParse(url, base) {
515
+ return URL.canParse(url, base);
458
516
  }
459
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
517
+ /**
518
+ * Converts an OpenAPI/Swagger path to Express-style colon syntax.
460
519
  *
461
520
  * @example
462
- * ```ts
463
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
464
- * new URLPath('/pet/{petId}').isURL // false
465
- * ```
521
+ * Url.toPath('/pet/{petId}') // '/pet/:petId'
466
522
  */
467
- get isURL() {
468
- try {
469
- return !!new URL(this.path).href;
470
- } catch {
471
- return false;
472
- }
523
+ static toPath(path) {
524
+ return path.replace(/\{([^}]+)\}/g, ":$1");
473
525
  }
474
526
  /**
475
- * Converts the OpenAPI path to a TypeScript template literal string.
527
+ * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
528
+ * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
529
+ * and `casing` controls parameter identifier casing.
476
530
  *
477
531
  * @example
478
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
479
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
480
- */
481
- get template() {
482
- return this.toTemplateString();
483
- }
484
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
532
+ * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
485
533
  *
486
534
  * @example
487
- * ```ts
488
- * new URLPath('/pet/{petId}').object
489
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
490
- * ```
535
+ * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
491
536
  */
492
- get object() {
493
- return this.toObject();
537
+ static toTemplateString(path, { prefix, replacer, casing } = {}) {
538
+ const result = path.split(/\{([^}]+)\}/).map((part, i) => {
539
+ if (i % 2 === 0) return part;
540
+ const param = transformParam(part, casing);
541
+ return `\${${replacer ? replacer(param) : param}}`;
542
+ }).join("");
543
+ return `\`${prefix ?? ""}${result}\``;
494
544
  }
495
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
545
+ /**
546
+ * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
547
+ * expression when `stringify` is set.
496
548
  *
497
549
  * @example
498
- * ```ts
499
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
500
- * new URLPath('/pet').params // undefined
501
- * ```
502
- */
503
- get params() {
504
- return this.getParams();
505
- }
506
- #transformParam(raw) {
507
- const param = isValidVarName(raw) ? raw : require_memoryStorage.camelCase(raw);
508
- return this.#options.casing === "camelcase" ? require_memoryStorage.camelCase(param) : param;
509
- }
510
- /**
511
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
550
+ * Url.toObject('/pet/{petId}')
551
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
512
552
  */
513
- #eachParam(fn) {
514
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
515
- const raw = match[1];
516
- fn(raw, this.#transformParam(raw));
517
- }
518
- }
519
- toObject({ type = "path", replacer, stringify } = {}) {
553
+ static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
520
554
  const object = {
521
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
522
- params: this.getParams()
555
+ url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
556
+ replacer,
557
+ casing
558
+ }),
559
+ params: toParamsObject(path, {
560
+ replacer,
561
+ casing
562
+ })
523
563
  };
524
564
  if (stringify) {
525
565
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -528,50 +568,6 @@ var URLPath = class {
528
568
  }
529
569
  return object;
530
570
  }
531
- /**
532
- * Converts the OpenAPI path to a TypeScript template literal string.
533
- * An optional `replacer` can transform each extracted parameter name before interpolation.
534
- *
535
- * @example
536
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
537
- */
538
- toTemplateString({ prefix, replacer } = {}) {
539
- const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
540
- if (i % 2 === 0) return part;
541
- const param = this.#transformParam(part);
542
- return `\${${replacer ? replacer(param) : param}}`;
543
- }).join("");
544
- return `\`${prefix ?? ""}${result}\``;
545
- }
546
- /**
547
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
548
- * An optional `replacer` transforms each parameter name in both key and value positions.
549
- * Returns `undefined` when no path parameters are found.
550
- *
551
- * @example
552
- * ```ts
553
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
554
- * // { petId: 'petId', tagId: 'tagId' }
555
- * ```
556
- */
557
- getParams(replacer) {
558
- const params = {};
559
- this.#eachParam((_raw, param) => {
560
- const key = replacer ? replacer(param) : param;
561
- params[key] = key;
562
- });
563
- return Object.keys(params).length > 0 ? params : void 0;
564
- }
565
- /** Converts the OpenAPI path to Express-style colon syntax.
566
- *
567
- * @example
568
- * ```ts
569
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
570
- * ```
571
- */
572
- toURLPath() {
573
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
574
- }
575
571
  };
576
572
  //#endregion
577
573
  //#region src/createAdapter.ts
@@ -609,43 +605,11 @@ function createAdapter(build) {
609
605
  return (options) => build(options ?? {});
610
606
  }
611
607
  //#endregion
612
- //#region src/createCache.ts
613
- /**
614
- * Defines a custom cache backend. The builder receives user options and returns a
615
- * {@link Cache}. Reach for this when the filesystem backend doesn't fit, for
616
- * example to store snapshots in Redis or a database.
617
- *
618
- * @example In-memory cache (the built-in implementation)
619
- * ```ts
620
- * import { createCache } from '@kubb/core'
621
- *
622
- * export const memoryCache = createCache(() => {
623
- * const store = new Map<string, CachedSnapshot>()
624
- *
625
- * return {
626
- * name: 'memory',
627
- * async restore({ key }) {
628
- * return store.get(key) ?? null
629
- * },
630
- * async persist({ key, snapshot }) {
631
- * store.set(key, snapshot)
632
- * },
633
- * }
634
- * })
635
- * ```
636
- */
637
- function createCache(build) {
638
- return (options) => build(options ?? {});
639
- }
640
- //#endregion
641
- //#region package.json
642
- var version = "5.0.0-beta.53";
643
- //#endregion
644
608
  //#region src/diagnostics.ts
645
609
  /**
646
610
  * Docs major, derived from the package version so the link tracks the published major.
647
611
  */
648
- const docsMajor = version.split(".")[0] ?? "5";
612
+ const docsMajor = "5.0.0-beta.55".split(".")[0] ?? "5";
649
613
  /**
650
614
  * Narrows a {@link Diagnostic} to the variant for `code`, or `null` when it does not match.
651
615
  *
@@ -1069,88 +1033,6 @@ var Diagnostics = class Diagnostics {
1069
1033
  }
1070
1034
  };
1071
1035
  //#endregion
1072
- //#region src/Fingerprint.ts
1073
- /**
1074
- * Computes the cache key for an incremental build. All methods are static, so call them as
1075
- * `Fingerprint.compute(...)` and `Fingerprint.stringify(...)`. The key holds no absolute
1076
- * paths or modification times, so it never depends on where the project lives on disk.
1077
- */
1078
- var Fingerprint = class Fingerprint {
1079
- /**
1080
- * Bumped when the snapshot format or fingerprint inputs change in an incompatible way, so stale
1081
- * cache entries from older Kubb builds are never reused.
1082
- */
1083
- static version = 1;
1084
- /**
1085
- * Deterministically serializes a value to JSON: object keys are sorted recursively and
1086
- * `undefined` values and functions are dropped. Two structurally equal configs produce the same
1087
- * string regardless of key order, which keeps the fingerprint stable across machines.
1088
- */
1089
- static stringify(value) {
1090
- return JSON.stringify(Fingerprint.#normalize(value));
1091
- }
1092
- /**
1093
- * Computes a cache key from everything that affects the generated output: the spec content, the
1094
- * output-shaping config, each plugin's name and options, the running
1095
- * `@kubb/core` version, and the cache format version. Returns `null` when the input can't be
1096
- * fingerprinted (remote URL or no adapter source), which disables caching for that build.
1097
- */
1098
- static async compute({ config, adapterSource, version }) {
1099
- if (!adapterSource) return null;
1100
- const spec = await Fingerprint.#readSpec(adapterSource, config.root);
1101
- if (spec === null) return null;
1102
- const input = {
1103
- cacheVersion: Fingerprint.version,
1104
- version,
1105
- spec,
1106
- name: config.name,
1107
- output: config.output,
1108
- adapter: config.adapter?.name,
1109
- parsers: config.parsers.map((parser) => parser.name),
1110
- plugins: config.plugins.map((plugin) => ({
1111
- name: plugin.name,
1112
- options: plugin.options
1113
- }))
1114
- };
1115
- return (0, node_crypto.createHash)("sha256").update(Fingerprint.stringify(input)).digest("hex");
1116
- }
1117
- static #normalize(value) {
1118
- if (value === null || typeof value !== "object") return typeof value === "function" ? void 0 : value;
1119
- if (Array.isArray(value)) return value.map((item) => Fingerprint.#normalize(item));
1120
- const source = value;
1121
- const result = {};
1122
- for (const key of Object.keys(source).sort()) {
1123
- const normalized = Fingerprint.#normalize(source[key]);
1124
- if (normalized !== void 0) result[key] = normalized;
1125
- }
1126
- return result;
1127
- }
1128
- /**
1129
- * Reads the spec content that feeds the fingerprint. Returns `null` for a remote URL source
1130
- * (hashing remote content would mean fetching it on every run) or when a file can't be read, so a
1131
- * missing or virtual spec disables caching instead of failing the build.
1132
- */
1133
- static async #readSpec(source, root) {
1134
- if (source.type === "data") return {
1135
- kind: "data",
1136
- data: typeof source.data === "string" ? source.data : Fingerprint.stringify(source.data)
1137
- };
1138
- const paths = source.type === "paths" ? source.paths : [source.path];
1139
- if (paths.some((path) => new URLPath(path).isURL)) return null;
1140
- try {
1141
- return {
1142
- kind: "path",
1143
- contents: await Promise.all(paths.map(async (path) => ({
1144
- path: (0, node_path.relative)(root, path),
1145
- content: await (0, node_fs_promises.readFile)(path, "utf8")
1146
- })))
1147
- };
1148
- } catch {
1149
- return null;
1150
- }
1151
- }
1152
- };
1153
- //#endregion
1154
1036
  //#region src/definePlugin.ts
1155
1037
  /**
1156
1038
  * Merges the `output.mode` default into the output config and validates the combination.
@@ -1254,12 +1136,12 @@ function matchesSchemaPattern(node, type, pattern) {
1254
1136
  /**
1255
1137
  * Default name resolver used by `defineResolver`.
1256
1138
  *
1257
- * - `camelCase` for `function` and `file` types.
1139
+ * - `camelCase` for `file`, with dotted names split into `/`-joined nested paths.
1258
1140
  * - `PascalCase` for `type`.
1259
- * - `camelCase` for everything else.
1141
+ * - `camelCase` for `function` and everything else.
1260
1142
  */
1261
1143
  function defaultResolver(name, type) {
1262
- if (type === "file" || type === "function") return require_memoryStorage.camelCase(name, { isFile: type === "file" });
1144
+ if (type === "file") return toFilePath(name);
1263
1145
  if (type === "type") return require_memoryStorage.pascalCase(name);
1264
1146
  return require_memoryStorage.camelCase(name);
1265
1147
  }
@@ -1678,8 +1560,7 @@ var KubbDriver = class {
1678
1560
  config;
1679
1561
  options;
1680
1562
  /**
1681
- * The streaming `InputStreamNode` produced by the adapter.
1682
- * Always set after adapter setup, parse-only adapters are wrapped automatically.
1563
+ * The streaming `InputNode<true>` produced by the adapter. * Always set after adapter setup, parse-only adapters are wrapped automatically.
1683
1564
  */
1684
1565
  inputNode = null;
1685
1566
  adapter = null;
@@ -1936,11 +1817,8 @@ var KubbDriver = class {
1936
1817
  await hooks.emit("kubb:files:processing:start", { files });
1937
1818
  });
1938
1819
  const updateBuffer = [];
1939
- const cacheEnabled = Boolean(config.cache);
1940
- const snapshotSources = /* @__PURE__ */ new Map();
1941
1820
  processor.hooks.on("update", (item) => {
1942
1821
  updateBuffer.push(item);
1943
- if (cacheEnabled && item.source !== void 0) snapshotSources.set(item.file.path, item.source);
1944
1822
  });
1945
1823
  processor.hooks.on("end", async (files) => {
1946
1824
  await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
@@ -1956,19 +1834,7 @@ var KubbDriver = class {
1956
1834
  this.fileManager.hooks.on("upsert", onFileUpsert);
1957
1835
  return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
1958
1836
  try {
1959
- const cache = config.cache;
1960
1837
  const outputRoot = (0, node_path.resolve)(config.root, config.output.path);
1961
- const cacheKey = cache ? await Fingerprint.compute({
1962
- config,
1963
- adapterSource: this.#adapterSource,
1964
- version
1965
- }) : null;
1966
- if (cache && cacheKey && await this.#restoreSnapshot({
1967
- cache,
1968
- cacheKey,
1969
- outputRoot,
1970
- storage
1971
- })) return { diagnostics: Diagnostics.dedupe(diagnostics) };
1972
1838
  await this.#parseInput();
1973
1839
  await this.emitSetupHooks();
1974
1840
  if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
@@ -2029,12 +1895,6 @@ var KubbDriver = class {
2029
1895
  config,
2030
1896
  outputDir: outputRoot
2031
1897
  });
2032
- if (cache && cacheKey && !Diagnostics.hasError(diagnostics)) await this.#persistSnapshot({
2033
- cache,
2034
- cacheKey,
2035
- outputRoot,
2036
- sources: snapshotSources
2037
- });
2038
1898
  return { diagnostics: Diagnostics.dedupe(diagnostics) };
2039
1899
  } catch (caughtError) {
2040
1900
  diagnostics.push(Diagnostics.from(caughtError));
@@ -2044,43 +1904,6 @@ var KubbDriver = class {
2044
1904
  }
2045
1905
  });
2046
1906
  }
2047
- /**
2048
- * Writes a restored snapshot straight to storage and emits `kubb:build:end`. Returns `true` on a
2049
- * hit (the build is done), `false` on a miss so the caller falls through to a full build.
2050
- */
2051
- async #restoreSnapshot({ cache, cacheKey, outputRoot, storage }) {
2052
- const snapshot = await cache.restore({ key: cacheKey });
2053
- if (!snapshot) return false;
2054
- const queue = [];
2055
- for (const [relativePath, source] of Object.entries(snapshot.files)) {
2056
- const absolutePath = (0, node_path.join)(outputRoot, relativePath);
2057
- this.fileManager.upsert((0, _kubb_ast.createFile)({
2058
- path: absolutePath,
2059
- baseName: (0, node_path.basename)(relativePath)
2060
- }));
2061
- queue.push(storage.setItem(absolutePath, source));
2062
- if (queue.length >= 50) await Promise.all(queue.splice(0));
2063
- }
2064
- await Promise.all(queue);
2065
- await this.hooks.emit("kubb:build:end", {
2066
- files: this.fileManager.files,
2067
- config: this.config,
2068
- outputDir: outputRoot
2069
- });
2070
- return true;
2071
- }
2072
- /**
2073
- * Stores this run's rendered output, keyed by the input fingerprint, so the next unchanged build
2074
- * restores it instead of regenerating. `sources` is keyed by absolute path and relativized here.
2075
- */
2076
- async #persistSnapshot({ cache, cacheKey, outputRoot, sources }) {
2077
- const files = {};
2078
- for (const [absolutePath, source] of sources) files[(0, node_path.relative)(outputRoot, absolutePath)] = source;
2079
- await cache.persist({
2080
- key: cacheKey,
2081
- snapshot: { files }
2082
- });
2083
- }
2084
1907
  #filesPayload() {
2085
1908
  const driver = this;
2086
1909
  return {
@@ -2464,7 +2287,7 @@ function inputToAdapterSource(config) {
2464
2287
  type: "data",
2465
2288
  data: input.data
2466
2289
  };
2467
- if (new URLPath(input.path).isURL) return {
2290
+ if (Url.canParse(input.path)) return {
2468
2291
  type: "path",
2469
2292
  path: input.path
2470
2293
  };
@@ -2482,11 +2305,11 @@ function inputToAdapterSource(config) {
2482
2305
  * Keys are resolved against `process.cwd()`, so root-relative paths such as
2483
2306
  * `src/gen/api/getPets.ts` are written to the correct location without extra configuration.
2484
2307
  *
2485
- * Internally uses the `write` utility from `@internals/utils`, which:
2486
- * - trims leading/trailing whitespace before writing
2487
- * - skips the write when file content is already identical (deduplication)
2488
- * - creates missing parent directories automatically
2489
- * - supports Bun's native file API when running under Bun
2308
+ * Writes are deduplicated and directory-safe:
2309
+ * - leading and trailing whitespace is trimmed before writing
2310
+ * - the write is skipped when the file content is already identical
2311
+ * - missing parent directories are created automatically
2312
+ * - Bun's native file API is used when running under Bun
2490
2313
  *
2491
2314
  * @example
2492
2315
  * ```ts
@@ -2525,7 +2348,7 @@ const fsStorage = require_memoryStorage.createStorage(() => ({
2525
2348
  },
2526
2349
  async getKeys(base) {
2527
2350
  const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
2528
- if (isBun()) {
2351
+ if (runtime.isBun) {
2529
2352
  const bunGlob = new Bun.Glob("**/*");
2530
2353
  return Array.fromAsync(bunGlob.scan({
2531
2354
  cwd: resolvedBase,
@@ -2598,7 +2421,6 @@ function resolveConfig(userConfig) {
2598
2421
  ...userConfig.output
2599
2422
  },
2600
2423
  storage: userConfig.storage ?? fsStorage(),
2601
- cache: userConfig.cache === false ? void 0 : userConfig.cache,
2602
2424
  reporters: userConfig.reporters ?? [],
2603
2425
  plugins: userConfig.plugins ?? []
2604
2426
  };
@@ -3061,134 +2883,9 @@ function defineParser(parser) {
3061
2883
  return parser;
3062
2884
  }
3063
2885
  //#endregion
3064
- //#region src/caches/fsCache.ts
3065
- /**
3066
- * Reads and prunes the local cache manifest. All methods are static, so call them as
3067
- * `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the
3068
- * cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`.
3069
- */
3070
- var Manifest = class Manifest {
3071
- /**
3072
- * On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a
3073
- * mismatch makes the whole local cache read as empty.
3074
- */
3075
- static version = 1;
3076
- /**
3077
- * Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads
3078
- * as an empty manifest.
3079
- */
3080
- static async read(dir) {
3081
- try {
3082
- const parsed = JSON.parse(await read((0, node_path.join)(dir, "manifest.json")));
3083
- if (parsed.version !== Manifest.version || typeof parsed.entries !== "object") return Manifest.#empty();
3084
- return parsed;
3085
- } catch {
3086
- return Manifest.#empty();
3087
- }
3088
- }
3089
- /**
3090
- * Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the
3091
- * surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO.
3092
- */
3093
- static prune(manifest, { maxEntries, ttlDays, now }) {
3094
- const ttlMs = ttlDays * 24 * 60 * 60 * 1e3;
3095
- const removed = [];
3096
- const kept = [];
3097
- for (const [key, entry] of Object.entries(manifest.entries)) if (now - entry.lastAccess > ttlMs) removed.push(key);
3098
- else kept.push([key, entry]);
3099
- if (kept.length > maxEntries) {
3100
- kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess);
3101
- for (const [key] of kept.splice(maxEntries)) removed.push(key);
3102
- }
3103
- return {
3104
- manifest: {
3105
- version: Manifest.version,
3106
- entries: Object.fromEntries(kept)
3107
- },
3108
- removed
3109
- };
3110
- }
3111
- static #empty() {
3112
- return {
3113
- version: Manifest.version,
3114
- entries: {}
3115
- };
3116
- }
3117
- };
3118
- function blobName(relativePath) {
3119
- return `${(0, node_crypto.createHash)("sha256").update(relativePath).digest("hex")}.blob`;
3120
- }
3121
- /**
3122
- * Local filesystem cache. Stores each build snapshot as content blobs plus an index,
3123
- * tracked by a manifest under `node_modules/.cache/kubb/` (the Nx and Vitest
3124
- * convention). Least-recently-used and expired entries are pruned on every persist.
3125
- *
3126
- * @example
3127
- * ```ts
3128
- * import { fsCache } from '@kubb/core'
3129
- *
3130
- * export default defineConfig({
3131
- * cache: fsCache(),
3132
- * })
3133
- * ```
3134
- */
3135
- const fsCache = createCache((options = {}) => {
3136
- const dir = (0, node_path.resolve)(options.dir ?? (0, node_path.join)("node_modules", ".cache", "kubb"));
3137
- const maxEntries = options.maxEntries ?? 50;
3138
- const ttlDays = options.ttlDays ?? 7;
3139
- const blobsDir = (0, node_path.join)(dir, "blobs");
3140
- const manifestPath = (0, node_path.join)(dir, "manifest.json");
3141
- return {
3142
- name: "fs",
3143
- async restore({ key }) {
3144
- const manifest = await Manifest.read(dir);
3145
- const entry = manifest.entries[key];
3146
- if (!entry) return null;
3147
- try {
3148
- const index = JSON.parse(await read((0, node_path.join)(blobsDir, key, "index.json")));
3149
- const files = {};
3150
- for (const { path, blob } of index) files[path] = await read((0, node_path.join)(blobsDir, key, blob));
3151
- entry.lastAccess = Date.now();
3152
- await write(manifestPath, JSON.stringify(manifest)).catch(() => {});
3153
- return { files };
3154
- } catch {
3155
- return null;
3156
- }
3157
- },
3158
- async persist({ key, snapshot }) {
3159
- const entryDir = (0, node_path.join)(blobsDir, key);
3160
- const index = [];
3161
- for (const [path, source] of Object.entries(snapshot.files)) {
3162
- const blob = blobName(path);
3163
- await write((0, node_path.join)(entryDir, blob), source);
3164
- index.push({
3165
- path,
3166
- blob
3167
- });
3168
- }
3169
- await write((0, node_path.join)(entryDir, "index.json"), JSON.stringify(index));
3170
- const manifest = await Manifest.read(dir);
3171
- const now = Date.now();
3172
- manifest.entries[key] = {
3173
- files: index.map((item) => item.path),
3174
- createdAt: now,
3175
- lastAccess: now
3176
- };
3177
- const pruned = Manifest.prune(manifest, {
3178
- maxEntries,
3179
- ttlDays,
3180
- now
3181
- });
3182
- await Promise.all(pruned.removed.map((removedKey) => clean((0, node_path.join)(blobsDir, removedKey))));
3183
- await write(manifestPath, JSON.stringify(pruned.manifest));
3184
- }
3185
- };
3186
- });
3187
- //#endregion
3188
2886
  exports.AsyncEventEmitter = require_memoryStorage.AsyncEventEmitter;
3189
2887
  exports.Diagnostics = Diagnostics;
3190
2888
  exports.KubbDriver = KubbDriver;
3191
- exports.URLPath = URLPath;
3192
2889
  Object.defineProperty(exports, "ast", {
3193
2890
  enumerable: true,
3194
2891
  get: function() {
@@ -3197,7 +2894,6 @@ Object.defineProperty(exports, "ast", {
3197
2894
  });
3198
2895
  exports.cliReporter = cliReporter;
3199
2896
  exports.createAdapter = createAdapter;
3200
- exports.createCache = createCache;
3201
2897
  exports.createKubb = createKubb;
3202
2898
  exports.createRenderer = createRenderer;
3203
2899
  exports.createReporter = createReporter;
@@ -3207,7 +2903,6 @@ exports.defineParser = defineParser;
3207
2903
  exports.definePlugin = definePlugin;
3208
2904
  exports.defineResolver = defineResolver;
3209
2905
  exports.fileReporter = fileReporter;
3210
- exports.fsCache = fsCache;
3211
2906
  exports.fsStorage = fsStorage;
3212
2907
  exports.jsonReporter = jsonReporter;
3213
2908
  exports.logLevel = logLevel;