@kubb/core 5.0.0-beta.41 → 5.0.0-beta.43

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.
@@ -1,7 +1,9 @@
1
1
  import "./chunk-C0LytTxp.js";
2
2
  import { EventEmitter } from "node:events";
3
3
  import { styleText } from "node:util";
4
- import path, { extname, resolve } from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { readFile } from "node:fs/promises";
6
+ import path, { basename, extname, join, relative, resolve } from "node:path";
5
7
  import { collectUsedSchemaNames, createFile, createStreamInput, extractStringsFromNodes, isOperationNode, isSchemaNode, transform } from "@kubb/ast";
6
8
  import { AsyncLocalStorage } from "node:async_hooks";
7
9
  //#region ../../internals/utils/src/errors.ts
@@ -635,7 +637,7 @@ var URLPath = class {
635
637
  };
636
638
  //#endregion
637
639
  //#region package.json
638
- var version = "5.0.0-beta.41";
640
+ var version = "5.0.0-beta.43";
639
641
  //#endregion
640
642
  //#region src/constants.ts
641
643
  /**
@@ -1204,6 +1206,89 @@ function createStorage(build) {
1204
1206
  return (options) => build(options ?? {});
1205
1207
  }
1206
1208
  //#endregion
1209
+ //#region src/Fingerprint.ts
1210
+ /**
1211
+ * Computes the cache key for an incremental build. All methods are static, so call them as
1212
+ * `Fingerprint.compute(...)` and `Fingerprint.stringify(...)`. The key holds no absolute
1213
+ * paths or modification times, so it never depends on where the project lives on disk.
1214
+ */
1215
+ var Fingerprint = class Fingerprint {
1216
+ /**
1217
+ * Bumped when the snapshot format or fingerprint inputs change in an incompatible way, so stale
1218
+ * cache entries from older Kubb builds are never reused.
1219
+ */
1220
+ static version = 1;
1221
+ /**
1222
+ * Deterministically serializes a value to JSON: object keys are sorted recursively and
1223
+ * `undefined` values and functions are dropped. Two structurally equal configs produce the same
1224
+ * string regardless of key order, which keeps the fingerprint stable across machines.
1225
+ */
1226
+ static stringify(value) {
1227
+ return JSON.stringify(Fingerprint.#normalize(value));
1228
+ }
1229
+ /**
1230
+ * Computes a cache key from everything that affects the generated output: the spec content, the
1231
+ * output-shaping config, each plugin's name and options, the middleware names, the running
1232
+ * `@kubb/core` version, and the cache format version. Returns `null` when the input can't be
1233
+ * fingerprinted (remote URL or no adapter source), which disables caching for that build.
1234
+ */
1235
+ static async compute({ config, adapterSource, version }) {
1236
+ if (!adapterSource) return null;
1237
+ const spec = await Fingerprint.#readSpec(adapterSource, config.root);
1238
+ if (spec === null) return null;
1239
+ const input = {
1240
+ cacheVersion: Fingerprint.version,
1241
+ version,
1242
+ spec,
1243
+ name: config.name,
1244
+ output: config.output,
1245
+ adapter: config.adapter?.name,
1246
+ parsers: config.parsers.map((parser) => parser.name),
1247
+ plugins: config.plugins.map((plugin) => ({
1248
+ name: plugin.name,
1249
+ options: plugin.options
1250
+ })),
1251
+ middleware: (config.middleware ?? []).map((middleware) => middleware.name)
1252
+ };
1253
+ return createHash("sha256").update(Fingerprint.stringify(input)).digest("hex");
1254
+ }
1255
+ static #normalize(value) {
1256
+ if (value === null || typeof value !== "object") return typeof value === "function" ? void 0 : value;
1257
+ if (Array.isArray(value)) return value.map((item) => Fingerprint.#normalize(item));
1258
+ const source = value;
1259
+ const result = {};
1260
+ for (const key of Object.keys(source).sort()) {
1261
+ const normalized = Fingerprint.#normalize(source[key]);
1262
+ if (normalized !== void 0) result[key] = normalized;
1263
+ }
1264
+ return result;
1265
+ }
1266
+ /**
1267
+ * Reads the spec content that feeds the fingerprint. Returns `null` for a remote URL source
1268
+ * (hashing remote content would mean fetching it on every run) or when a file can't be read, so a
1269
+ * missing or virtual spec disables caching instead of failing the build.
1270
+ */
1271
+ static async #readSpec(source, root) {
1272
+ if (source.type === "data") return {
1273
+ kind: "data",
1274
+ data: typeof source.data === "string" ? source.data : Fingerprint.stringify(source.data)
1275
+ };
1276
+ const paths = source.type === "paths" ? source.paths : [source.path];
1277
+ if (paths.some((path) => new URLPath(path).isURL)) return null;
1278
+ try {
1279
+ return {
1280
+ kind: "path",
1281
+ contents: await Promise.all(paths.map(async (path) => ({
1282
+ path: relative(root, path),
1283
+ content: await readFile(path, "utf8")
1284
+ })))
1285
+ };
1286
+ } catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+ };
1291
+ //#endregion
1207
1292
  //#region src/definePlugin.ts
1208
1293
  /**
1209
1294
  * Wraps a plugin factory and returns a function that accepts user options and
@@ -1975,7 +2060,7 @@ var Transform = class {
1975
2060
  }
1976
2061
  };
1977
2062
  //#endregion
1978
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/usingCtx.js
2063
+ //#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js
1979
2064
  function _usingCtx() {
1980
2065
  var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
1981
2066
  var n = Error();
@@ -2098,9 +2183,10 @@ var KubbDriver = class KubbDriver {
2098
2183
  }
2099
2184
  async setup() {
2100
2185
  const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
2186
+ const dependenciesByName = new Map(normalized.map((plugin) => [plugin.name, new Set(plugin.dependencies ?? [])]));
2101
2187
  normalized.sort((a, b) => {
2102
- if (b.dependencies?.includes(a.name)) return -1;
2103
- if (a.dependencies?.includes(b.name)) return 1;
2188
+ if (dependenciesByName.get(b.name)?.has(a.name)) return -1;
2189
+ if (dependenciesByName.get(a.name)?.has(b.name)) return 1;
2104
2190
  return enforceOrder(a.enforce) - enforceOrder(b.enforce);
2105
2191
  });
2106
2192
  for (const plugin of normalized) {
@@ -2325,8 +2411,10 @@ var KubbDriver = class KubbDriver {
2325
2411
  await hooks.emit("kubb:files:processing:start", { files });
2326
2412
  });
2327
2413
  const updateBuffer = [];
2414
+ const snapshotSources = /* @__PURE__ */ new Map();
2328
2415
  processor.hooks.on("update", (item) => {
2329
2416
  updateBuffer.push(item);
2417
+ if (item.source !== void 0) snapshotSources.set(item.file.path, item.source);
2330
2418
  });
2331
2419
  processor.hooks.on("end", async (files) => {
2332
2420
  await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
@@ -2342,6 +2430,19 @@ var KubbDriver = class KubbDriver {
2342
2430
  this.fileManager.hooks.on("upsert", onFileUpsert);
2343
2431
  return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
2344
2432
  try {
2433
+ const cache = config.cache;
2434
+ const outputRoot = resolve(config.root, config.output.path);
2435
+ const cacheKey = cache ? await Fingerprint.compute({
2436
+ config,
2437
+ adapterSource: this.#adapterSource,
2438
+ version
2439
+ }) : null;
2440
+ if (cache && cacheKey && await this.#restoreSnapshot({
2441
+ cache,
2442
+ cacheKey,
2443
+ outputRoot,
2444
+ storage
2445
+ })) return { diagnostics: Diagnostics.dedupe(diagnostics) };
2345
2446
  await this.#parseInput();
2346
2447
  await this.emitSetupHooks();
2347
2448
  if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
@@ -2400,7 +2501,13 @@ var KubbDriver = class KubbDriver {
2400
2501
  await hooks.emit("kubb:build:end", {
2401
2502
  files: this.fileManager.files,
2402
2503
  config,
2403
- outputDir: resolve(config.root, config.output.path)
2504
+ outputDir: outputRoot
2505
+ });
2506
+ if (cache && cacheKey && !Diagnostics.hasError(diagnostics)) await this.#persistSnapshot({
2507
+ cache,
2508
+ cacheKey,
2509
+ outputRoot,
2510
+ sources: snapshotSources
2404
2511
  });
2405
2512
  return { diagnostics: Diagnostics.dedupe(diagnostics) };
2406
2513
  } catch (caughtError) {
@@ -2411,6 +2518,40 @@ var KubbDriver = class KubbDriver {
2411
2518
  }
2412
2519
  });
2413
2520
  }
2521
+ /**
2522
+ * Writes a restored snapshot straight to storage and emits `kubb:build:end`. Returns `true` on a
2523
+ * hit (the build is done), `false` on a miss so the caller falls through to a full build.
2524
+ */
2525
+ async #restoreSnapshot({ cache, cacheKey, outputRoot, storage }) {
2526
+ const snapshot = await cache.restore({ key: cacheKey });
2527
+ if (!snapshot) return false;
2528
+ for (const [relativePath, source] of Object.entries(snapshot.files)) {
2529
+ const absolutePath = join(outputRoot, relativePath);
2530
+ this.fileManager.upsert(createFile({
2531
+ path: absolutePath,
2532
+ baseName: basename(relativePath)
2533
+ }));
2534
+ await storage.setItem(absolutePath, source);
2535
+ }
2536
+ await this.hooks.emit("kubb:build:end", {
2537
+ files: this.fileManager.files,
2538
+ config: this.config,
2539
+ outputDir: outputRoot
2540
+ });
2541
+ return true;
2542
+ }
2543
+ /**
2544
+ * Stores this run's rendered output, keyed by the input fingerprint, so the next unchanged build
2545
+ * restores it instead of regenerating. `sources` is keyed by absolute path and relativized here.
2546
+ */
2547
+ async #persistSnapshot({ cache, cacheKey, outputRoot, sources }) {
2548
+ const files = {};
2549
+ for (const [absolutePath, source] of sources) files[relative(outputRoot, absolutePath)] = source;
2550
+ await cache.persist({
2551
+ key: cacheKey,
2552
+ snapshot: { files }
2553
+ });
2554
+ }
2414
2555
  #filesPayload() {
2415
2556
  const driver = this;
2416
2557
  return {
@@ -2487,10 +2628,8 @@ var KubbDriver = class KubbDriver {
2487
2628
  const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
2488
2629
  const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
2489
2630
  const emitsOperationsHook = this.hooks.listenerCount("kubb:generate:operations") > 0;
2490
- const schemasBuffer = [];
2491
- for await (const schema of schemas) schemasBuffer.push(schema);
2492
- const operationsBuffer = [];
2493
- for await (const operation of operations) operationsBuffer.push(operation);
2631
+ const schemasBuffer = await Array.fromAsync(schemas);
2632
+ const operationsBuffer = await Array.fromAsync(operations);
2494
2633
  const pruningStates = states.filter(({ plugin }) => {
2495
2634
  const { include } = plugin.options;
2496
2635
  return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
@@ -2857,4 +2996,4 @@ const memoryStorage = createStorage(() => {
2857
2996
  //#endregion
2858
2997
  export { FileManager as a, createStorage as c, URLPath as d, formatMs as f, BuildError as g, AsyncEventEmitter as h, FileProcessor as i, Diagnostics as l, camelCase as m, KubbDriver as n, defineResolver as o, getElapsedMs as p, _usingCtx as r, definePlugin as s, memoryStorage as t, OTLP_ENDPOINT as u };
2859
2998
 
2860
- //# sourceMappingURL=memoryStorage-DA1bnMte.js.map
2999
+ //# sourceMappingURL=memoryStorage-C0n47hZ2.js.map