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