@kubb/core 5.0.0-beta.2 → 5.0.0-beta.20

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.
Files changed (42) hide show
  1. package/README.md +8 -38
  2. package/dist/{PluginDriver-BXibeQk-.cjs → KubbDriver-BXSnJ3qM.cjs} +719 -164
  3. package/dist/KubbDriver-BXSnJ3qM.cjs.map +1 -0
  4. package/dist/{PluginDriver-DV3p2Hky.js → KubbDriver-Cxii_rBp.js} +693 -162
  5. package/dist/KubbDriver-Cxii_rBp.js.map +1 -0
  6. package/dist/{types-CC09VtBt.d.ts → createKubb-Dcmtjqds.d.ts} +1395 -1238
  7. package/dist/index.cjs +556 -785
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +2 -185
  10. package/dist/index.js +551 -783
  11. package/dist/index.js.map +1 -1
  12. package/dist/mocks.cjs +30 -21
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +5 -5
  15. package/dist/mocks.js +29 -20
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +6 -18
  18. package/src/FileManager.ts +12 -0
  19. package/src/FileProcessor.ts +37 -38
  20. package/src/{PluginDriver.ts → KubbDriver.ts} +249 -86
  21. package/src/constants.ts +11 -6
  22. package/src/createAdapter.ts +84 -1
  23. package/src/createKubb.ts +1336 -297
  24. package/src/createRenderer.ts +23 -22
  25. package/src/defineGenerator.ts +96 -7
  26. package/src/defineLogger.ts +42 -3
  27. package/src/defineMiddleware.ts +1 -1
  28. package/src/defineParser.ts +1 -1
  29. package/src/definePlugin.ts +304 -8
  30. package/src/defineResolver.ts +268 -147
  31. package/src/devtools.ts +8 -1
  32. package/src/index.ts +2 -2
  33. package/src/mocks.ts +11 -14
  34. package/src/storages/fsStorage.ts +13 -37
  35. package/src/types.ts +38 -1292
  36. package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
  37. package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
  38. package/src/Kubb.ts +0 -300
  39. package/src/renderNode.ts +0 -35
  40. package/src/utils/diagnostics.ts +0 -18
  41. package/src/utils/isInputPath.ts +0 -10
  42. package/src/utils/packageJSON.ts +0 -99
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { t as __name } from "./chunk--u3MIqq1.js";
2
- import { a as DEFAULT_BANNER, c as logLevel, i as defineResolver, l as camelCase, n as applyHookResult, o as DEFAULT_EXTENSION, r as FileManager, s as DEFAULT_STUDIO_URL, t as PluginDriver } from "./PluginDriver-DV3p2Hky.js";
1
+ import "./chunk--u3MIqq1.js";
2
+ import { a as definePlugin, c as DEFAULT_STUDIO_URL, d as forBatches, f as isPromise, i as defineResolver, l as logLevel, n as applyHookResult, o as DEFAULT_BANNER, p as withDrain, r as FileManager, s as DEFAULT_EXTENSION, t as KubbDriver, u as URLPath } from "./KubbDriver-Cxii_rBp.js";
3
3
  import { EventEmitter } from "node:events";
4
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import * as ast from "@kubb/ast";
7
- import { collectUsedSchemaNames, extractStringsFromNodes, transform, walk } from "@kubb/ast";
7
+ import { collectUsedSchemaNames, extractStringsFromNodes, transform } from "@kubb/ast";
8
8
  import { version } from "node:process";
9
9
  //#region ../../internals/utils/src/errors.ts
10
10
  /**
@@ -69,9 +69,12 @@ var AsyncEventEmitter = class {
69
69
  * await emitter.emit('build', 'petstore')
70
70
  * ```
71
71
  */
72
- async emit(eventName, ...eventArgs) {
72
+ emit(eventName, ...eventArgs) {
73
73
  const listeners = this.#emitter.listeners(eventName);
74
74
  if (listeners.length === 0) return;
75
+ return this.#emitAll(eventName, listeners, eventArgs);
76
+ }
77
+ async #emitAll(eventName, listeners, eventArgs) {
75
78
  for (const listener of listeners) try {
76
79
  await listener(...eventArgs);
77
80
  } catch (err) {
@@ -245,255 +248,6 @@ async function clean(path) {
245
248
  });
246
249
  }
247
250
  //#endregion
248
- //#region ../../internals/utils/src/reserved.ts
249
- /**
250
- * JavaScript and Java reserved words.
251
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
252
- */
253
- const reservedWords = new Set([
254
- "abstract",
255
- "arguments",
256
- "boolean",
257
- "break",
258
- "byte",
259
- "case",
260
- "catch",
261
- "char",
262
- "class",
263
- "const",
264
- "continue",
265
- "debugger",
266
- "default",
267
- "delete",
268
- "do",
269
- "double",
270
- "else",
271
- "enum",
272
- "eval",
273
- "export",
274
- "extends",
275
- "false",
276
- "final",
277
- "finally",
278
- "float",
279
- "for",
280
- "function",
281
- "goto",
282
- "if",
283
- "implements",
284
- "import",
285
- "in",
286
- "instanceof",
287
- "int",
288
- "interface",
289
- "let",
290
- "long",
291
- "native",
292
- "new",
293
- "null",
294
- "package",
295
- "private",
296
- "protected",
297
- "public",
298
- "return",
299
- "short",
300
- "static",
301
- "super",
302
- "switch",
303
- "synchronized",
304
- "this",
305
- "throw",
306
- "throws",
307
- "transient",
308
- "true",
309
- "try",
310
- "typeof",
311
- "var",
312
- "void",
313
- "volatile",
314
- "while",
315
- "with",
316
- "yield",
317
- "Array",
318
- "Date",
319
- "hasOwnProperty",
320
- "Infinity",
321
- "isFinite",
322
- "isNaN",
323
- "isPrototypeOf",
324
- "length",
325
- "Math",
326
- "name",
327
- "NaN",
328
- "Number",
329
- "Object",
330
- "prototype",
331
- "String",
332
- "toString",
333
- "undefined",
334
- "valueOf"
335
- ]);
336
- /**
337
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
338
- *
339
- * @example
340
- * ```ts
341
- * isValidVarName('status') // true
342
- * isValidVarName('class') // false (reserved word)
343
- * isValidVarName('42foo') // false (starts with digit)
344
- * ```
345
- */
346
- function isValidVarName(name) {
347
- if (!name || reservedWords.has(name)) return false;
348
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
349
- }
350
- //#endregion
351
- //#region ../../internals/utils/src/urlPath.ts
352
- /**
353
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
354
- *
355
- * @example
356
- * const p = new URLPath('/pet/{petId}')
357
- * p.URL // '/pet/:petId'
358
- * p.template // '`/pet/${petId}`'
359
- */
360
- var URLPath = class {
361
- /**
362
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
363
- */
364
- path;
365
- #options;
366
- constructor(path, options = {}) {
367
- this.path = path;
368
- this.#options = options;
369
- }
370
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
371
- *
372
- * @example
373
- * ```ts
374
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
375
- * ```
376
- */
377
- get URL() {
378
- return this.toURLPath();
379
- }
380
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
381
- *
382
- * @example
383
- * ```ts
384
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
385
- * new URLPath('/pet/{petId}').isURL // false
386
- * ```
387
- */
388
- get isURL() {
389
- try {
390
- return !!new URL(this.path).href;
391
- } catch {
392
- return false;
393
- }
394
- }
395
- /**
396
- * Converts the OpenAPI path to a TypeScript template literal string.
397
- *
398
- * @example
399
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
400
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
401
- */
402
- get template() {
403
- return this.toTemplateString();
404
- }
405
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
406
- *
407
- * @example
408
- * ```ts
409
- * new URLPath('/pet/{petId}').object
410
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
411
- * ```
412
- */
413
- get object() {
414
- return this.toObject();
415
- }
416
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
417
- *
418
- * @example
419
- * ```ts
420
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
421
- * new URLPath('/pet').params // undefined
422
- * ```
423
- */
424
- get params() {
425
- return this.getParams();
426
- }
427
- #transformParam(raw) {
428
- const param = isValidVarName(raw) ? raw : camelCase(raw);
429
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
430
- }
431
- /**
432
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
433
- */
434
- #eachParam(fn) {
435
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
436
- const raw = match[1];
437
- fn(raw, this.#transformParam(raw));
438
- }
439
- }
440
- toObject({ type = "path", replacer, stringify } = {}) {
441
- const object = {
442
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
443
- params: this.getParams()
444
- };
445
- if (stringify) {
446
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
447
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
448
- return `{ url: '${object.url}' }`;
449
- }
450
- return object;
451
- }
452
- /**
453
- * Converts the OpenAPI path to a TypeScript template literal string.
454
- * An optional `replacer` can transform each extracted parameter name before interpolation.
455
- *
456
- * @example
457
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
458
- */
459
- toTemplateString({ prefix = "", replacer } = {}) {
460
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
461
- if (i % 2 === 0) return part;
462
- const param = this.#transformParam(part);
463
- return `\${${replacer ? replacer(param) : param}}`;
464
- }).join("")}\``;
465
- }
466
- /**
467
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
468
- * An optional `replacer` transforms each parameter name in both key and value positions.
469
- * Returns `undefined` when no path parameters are found.
470
- *
471
- * @example
472
- * ```ts
473
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
474
- * // { petId: 'petId', tagId: 'tagId' }
475
- * ```
476
- */
477
- getParams(replacer) {
478
- const params = {};
479
- this.#eachParam((_raw, param) => {
480
- const key = replacer ? replacer(param) : param;
481
- params[key] = key;
482
- });
483
- return Object.keys(params).length > 0 ? params : void 0;
484
- }
485
- /** Converts the OpenAPI path to Express-style colon syntax.
486
- *
487
- * @example
488
- * ```ts
489
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
490
- * ```
491
- */
492
- toURLPath() {
493
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
494
- }
495
- };
496
- //#endregion
497
251
  //#region src/createAdapter.ts
498
252
  /**
499
253
  * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
@@ -524,180 +278,8 @@ function createAdapter(build) {
524
278
  return (options) => build(options ?? {});
525
279
  }
526
280
  //#endregion
527
- //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
528
- var Node$1 = class {
529
- static {
530
- __name(this, "Node");
531
- }
532
- value;
533
- next;
534
- constructor(value) {
535
- this.value = value;
536
- }
537
- };
538
- var Queue = class {
539
- #head;
540
- #tail;
541
- #size;
542
- constructor() {
543
- this.clear();
544
- }
545
- enqueue(value) {
546
- const node = new Node$1(value);
547
- if (this.#head) {
548
- this.#tail.next = node;
549
- this.#tail = node;
550
- } else {
551
- this.#head = node;
552
- this.#tail = node;
553
- }
554
- this.#size++;
555
- }
556
- dequeue() {
557
- const current = this.#head;
558
- if (!current) return;
559
- this.#head = this.#head.next;
560
- this.#size--;
561
- if (!this.#head) this.#tail = void 0;
562
- return current.value;
563
- }
564
- peek() {
565
- if (!this.#head) return;
566
- return this.#head.value;
567
- }
568
- clear() {
569
- this.#head = void 0;
570
- this.#tail = void 0;
571
- this.#size = 0;
572
- }
573
- get size() {
574
- return this.#size;
575
- }
576
- *[Symbol.iterator]() {
577
- let current = this.#head;
578
- while (current) {
579
- yield current.value;
580
- current = current.next;
581
- }
582
- }
583
- *drain() {
584
- while (this.#head) yield this.dequeue();
585
- }
586
- };
587
- //#endregion
588
- //#region ../../node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
589
- function pLimit(concurrency) {
590
- let rejectOnClear = false;
591
- if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
592
- validateConcurrency(concurrency);
593
- if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
594
- const queue = new Queue();
595
- let activeCount = 0;
596
- const resumeNext = () => {
597
- if (activeCount < concurrency && queue.size > 0) {
598
- activeCount++;
599
- queue.dequeue().run();
600
- }
601
- };
602
- const next = () => {
603
- activeCount--;
604
- resumeNext();
605
- };
606
- const run = async (function_, resolve, arguments_) => {
607
- const result = (async () => function_(...arguments_))();
608
- resolve(result);
609
- try {
610
- await result;
611
- } catch {}
612
- next();
613
- };
614
- const enqueue = (function_, resolve, reject, arguments_) => {
615
- const queueItem = { reject };
616
- new Promise((internalResolve) => {
617
- queueItem.run = internalResolve;
618
- queue.enqueue(queueItem);
619
- }).then(run.bind(void 0, function_, resolve, arguments_));
620
- if (activeCount < concurrency) resumeNext();
621
- };
622
- const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
623
- enqueue(function_, resolve, reject, arguments_);
624
- });
625
- Object.defineProperties(generator, {
626
- activeCount: { get: () => activeCount },
627
- pendingCount: { get: () => queue.size },
628
- clearQueue: { value() {
629
- if (!rejectOnClear) {
630
- queue.clear();
631
- return;
632
- }
633
- const abortError = AbortSignal.abort().reason;
634
- while (queue.size > 0) queue.dequeue().reject(abortError);
635
- } },
636
- concurrency: {
637
- get: () => concurrency,
638
- set(newConcurrency) {
639
- validateConcurrency(newConcurrency);
640
- concurrency = newConcurrency;
641
- queueMicrotask(() => {
642
- while (activeCount < concurrency && queue.size > 0) resumeNext();
643
- });
644
- }
645
- },
646
- map: { async value(iterable, function_) {
647
- const promises = Array.from(iterable, (value, index) => this(function_, value, index));
648
- return Promise.all(promises);
649
- } }
650
- });
651
- return generator;
652
- }
653
- function validateConcurrency(concurrency) {
654
- if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
655
- }
656
- //#endregion
657
- //#region src/FileProcessor.ts
658
- function joinSources(file) {
659
- return file.sources.map((item) => extractStringsFromNodes(item.nodes)).filter(Boolean).join("\n\n");
660
- }
661
- /**
662
- * Converts a single file to a string using the registered parsers.
663
- * Falls back to joining source values when no matching parser is found.
664
- *
665
- * @internal
666
- */
667
- var FileProcessor = class {
668
- #limit = pLimit(100);
669
- async parse(file, { parsers, extension } = {}) {
670
- const parseExtName = extension?.[file.extname] || void 0;
671
- if (!parsers || !file.extname) return joinSources(file);
672
- const parser = parsers.get(file.extname);
673
- if (!parser) return joinSources(file);
674
- return parser.parse(file, { extname: parseExtName });
675
- }
676
- async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
677
- await onStart?.(files);
678
- const total = files.length;
679
- let processed = 0;
680
- const processOne = async (file) => {
681
- const source = await this.parse(file, {
682
- extension,
683
- parsers
684
- });
685
- const currentProcessed = ++processed;
686
- const percentage = currentProcessed / total * 100;
687
- await onUpdate?.({
688
- file,
689
- source,
690
- processed: currentProcessed,
691
- percentage,
692
- total
693
- });
694
- };
695
- if (mode === "sequential") for (const file of files) await processOne(file);
696
- else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
697
- await onEnd?.(files);
698
- return files;
699
- }
700
- };
281
+ //#region package.json
282
+ var version$1 = "5.0.0-beta.20";
701
283
  //#endregion
702
284
  //#region src/createStorage.ts
703
285
  /**
@@ -736,13 +318,63 @@ function createStorage(build) {
736
318
  return (options) => build(options ?? {});
737
319
  }
738
320
  //#endregion
739
- //#region src/storages/fsStorage.ts
321
+ //#region src/FileProcessor.ts
322
+ function joinSources(file) {
323
+ const sources = file.sources;
324
+ if (sources.length === 0) return "";
325
+ const parts = [];
326
+ for (const source of sources) {
327
+ const s = extractStringsFromNodes(source.nodes);
328
+ if (s) parts.push(s);
329
+ }
330
+ return parts.join("\n\n");
331
+ }
740
332
  /**
741
- * Detects the filesystem error used to indicate that a path does not exist.
333
+ * Converts a single file to a string using the registered parsers.
334
+ * Falls back to joining source values when no matching parser is found.
335
+ *
336
+ * @internal
742
337
  */
743
- function isMissingPathError(error) {
744
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
745
- }
338
+ var FileProcessor = class {
339
+ events = new AsyncEventEmitter();
340
+ parse(file, { parsers, extension } = {}) {
341
+ const parseExtName = extension?.[file.extname] || void 0;
342
+ if (!parsers || !file.extname) return joinSources(file);
343
+ const parser = parsers.get(file.extname);
344
+ if (!parser) return joinSources(file);
345
+ return parser.parse(file, { extname: parseExtName });
346
+ }
347
+ *stream(files, options = {}) {
348
+ const total = files.length;
349
+ if (total === 0) return;
350
+ let processed = 0;
351
+ for (const file of files) {
352
+ const source = this.parse(file, options);
353
+ processed++;
354
+ yield {
355
+ file,
356
+ source,
357
+ processed,
358
+ total,
359
+ percentage: processed / total * 100
360
+ };
361
+ }
362
+ }
363
+ async run(files, options = {}) {
364
+ await this.events.emit("start", files);
365
+ for (const { file, source, processed, total, percentage } of this.stream(files, options)) await this.events.emit("update", {
366
+ file,
367
+ source,
368
+ processed,
369
+ percentage,
370
+ total
371
+ });
372
+ await this.events.emit("end", files);
373
+ return files;
374
+ }
375
+ };
376
+ //#endregion
377
+ //#region src/storages/fsStorage.ts
746
378
  /**
747
379
  * Built-in filesystem storage driver.
748
380
  *
@@ -774,17 +406,15 @@ const fsStorage = createStorage(() => ({
774
406
  try {
775
407
  await access(resolve(key));
776
408
  return true;
777
- } catch (error) {
778
- if (isMissingPathError(error)) return false;
779
- throw new Error(`Failed to access storage item "${key}"`, { cause: error });
409
+ } catch (_error) {
410
+ return false;
780
411
  }
781
412
  },
782
413
  async getItem(key) {
783
414
  try {
784
415
  return await readFile(resolve(key), "utf8");
785
- } catch (error) {
786
- if (isMissingPathError(error)) return null;
787
- throw new Error(`Failed to read storage item "${key}"`, { cause: error });
416
+ } catch (_error) {
417
+ return null;
788
418
  }
789
419
  },
790
420
  async setItem(key, value) {
@@ -794,23 +424,22 @@ const fsStorage = createStorage(() => ({
794
424
  await rm(resolve(key), { force: true });
795
425
  },
796
426
  async getKeys(base) {
797
- const keys = [];
798
427
  const resolvedBase = resolve(base ?? process.cwd());
799
- async function walk(dir, prefix) {
428
+ async function* walk(dir, prefix) {
800
429
  let entries;
801
430
  try {
802
431
  entries = await readdir(dir, { withFileTypes: true });
803
- } catch (error) {
804
- if (isMissingPathError(error)) return;
805
- throw new Error(`Failed to list storage keys under "${resolvedBase}"`, { cause: error });
432
+ } catch (_error) {
433
+ return;
806
434
  }
807
435
  for (const entry of entries) {
808
436
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
809
- if (entry.isDirectory()) await walk(join(dir, entry.name), rel);
810
- else keys.push(rel);
437
+ if (entry.isDirectory()) yield* walk(join(dir, entry.name), rel);
438
+ else yield rel;
811
439
  }
812
440
  }
813
- await walk(resolvedBase, "");
441
+ const keys = [];
442
+ for await (const key of walk(resolvedBase, "")) keys.push(key);
814
443
  return keys;
815
444
  },
816
445
  async clear(base) {
@@ -819,37 +448,128 @@ const fsStorage = createStorage(() => ({
819
448
  }
820
449
  }));
821
450
  //#endregion
822
- //#region package.json
823
- var version$1 = "5.0.0-beta.2";
824
- //#endregion
825
- //#region src/utils/diagnostics.ts
826
- /**
827
- * Returns a snapshot of the current runtime environment.
828
- *
829
- * Useful for attaching context to debug logs and error reports so that
830
- * issues can be reproduced without manual information gathering.
831
- */
832
- function getDiagnosticInfo() {
451
+ //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
452
+ function _usingCtx() {
453
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
454
+ var n = Error();
455
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
456
+ };
457
+ var e = {};
458
+ var n = [];
459
+ function using(r, e) {
460
+ if (null != e) {
461
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
462
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
463
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
464
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
465
+ t && (o = function o() {
466
+ try {
467
+ t.call(e);
468
+ } catch (r) {
469
+ return Promise.reject(r);
470
+ }
471
+ }), n.push({
472
+ v: e,
473
+ d: o,
474
+ a: r
475
+ });
476
+ } else r && n.push({
477
+ d: e,
478
+ a: r
479
+ });
480
+ return e;
481
+ }
833
482
  return {
834
- nodeVersion: version,
835
- KubbVersion: version$1,
836
- platform: process.platform,
837
- arch: process.arch,
838
- cwd: process.cwd()
483
+ e,
484
+ u: using.bind(null, !1),
485
+ a: using.bind(null, !0),
486
+ d: function d() {
487
+ var o;
488
+ var t = this.e;
489
+ var s = 0;
490
+ function next() {
491
+ for (; o = n.pop();) try {
492
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
493
+ if (o.d) {
494
+ var r = o.d.call(o.v);
495
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
496
+ } else s |= 1;
497
+ } catch (r) {
498
+ return err(r);
499
+ }
500
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
501
+ if (t !== e) throw t;
502
+ }
503
+ function err(n) {
504
+ return t = t !== e ? new r(n, t) : n, next();
505
+ }
506
+ return next();
507
+ }
839
508
  };
840
509
  }
841
510
  //#endregion
842
- //#region src/utils/isInputPath.ts
843
- function isInputPath(config) {
844
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
845
- }
846
- //#endregion
847
511
  //#region src/createKubb.ts
512
+ /**
513
+ * Builds a `Storage` view scoped to the file paths produced by the current build.
514
+ *
515
+ * Reads delegate to the underlying `storage` (typically `fsStorage()`) so source bytes
516
+ * stay where they were written instead of being held in an extra in-memory map.
517
+ * Writing via `setItem` stores the content in the underlying storage and registers the
518
+ * key so subsequent reads and `getKeys` are scoped to this build's output.
519
+ */
520
+ function createSourcesView(storage) {
521
+ const paths = /* @__PURE__ */ new Set();
522
+ return createStorage(() => ({
523
+ name: `${storage.name}:sources`,
524
+ async hasItem(key) {
525
+ return paths.has(key) && await storage.hasItem(key);
526
+ },
527
+ async getItem(key) {
528
+ return paths.has(key) ? storage.getItem(key) : null;
529
+ },
530
+ async setItem(key, value) {
531
+ paths.add(key);
532
+ await storage.setItem(key, value);
533
+ },
534
+ async removeItem(key) {
535
+ paths.delete(key);
536
+ await storage.removeItem(key);
537
+ },
538
+ async getKeys(base) {
539
+ if (!base) return [...paths];
540
+ const result = [];
541
+ for (const key of paths) if (key.startsWith(base)) result.push(key);
542
+ return result;
543
+ },
544
+ async clear() {
545
+ paths.clear();
546
+ await storage.clear();
547
+ }
548
+ }))();
549
+ }
848
550
  async function setup(userConfig, options = {}) {
849
551
  const hooks = options.hooks ?? new AsyncEventEmitter();
850
- const sources = /* @__PURE__ */ new Map();
552
+ const config = {
553
+ ...userConfig,
554
+ root: userConfig.root || process.cwd(),
555
+ parsers: userConfig.parsers ?? [],
556
+ output: {
557
+ format: false,
558
+ lint: false,
559
+ extension: DEFAULT_EXTENSION,
560
+ defaultBanner: DEFAULT_BANNER,
561
+ ...userConfig.output
562
+ },
563
+ storage: userConfig.storage ?? fsStorage(),
564
+ devtools: userConfig.devtools ? {
565
+ studioUrl: DEFAULT_STUDIO_URL,
566
+ ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
567
+ } : void 0,
568
+ plugins: userConfig.plugins ?? []
569
+ };
570
+ const driver = new KubbDriver(config, { hooks });
571
+ const storage = createSourcesView(config.storage);
851
572
  const diagnosticInfo = getDiagnosticInfo();
852
- if (Array.isArray(userConfig.input)) await hooks.emit("kubb:warn", { message: "This feature is still under development — use with caution" });
853
573
  await hooks.emit("kubb:debug", {
854
574
  date: /* @__PURE__ */ new Date(),
855
575
  logs: [
@@ -859,9 +579,10 @@ async function setup(userConfig, options = {}) {
859
579
  ` • Output: ${userConfig.output?.path || "not specified"}`,
860
580
  ` • Plugins: ${userConfig.plugins?.length || 0}`,
861
581
  "Output Settings:",
862
- ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
582
+ ` • Storage: ${config.storage.name}`,
863
583
  ` • Formatter: ${userConfig.output?.format || "none"}`,
864
584
  ` • Linter: ${userConfig.output?.lint || "none"}`,
585
+ `Running adapter: ${config.adapter?.name || "none"}`,
865
586
  "Environment:",
866
587
  Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
867
588
  ]
@@ -880,188 +601,304 @@ async function setup(userConfig, options = {}) {
880
601
  throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${userConfig.input.path}`, { cause: error });
881
602
  }
882
603
  }
883
- if (!userConfig.adapter) throw new Error("Adapter should be defined");
884
- const config = {
885
- ...userConfig,
886
- root: userConfig.root || process.cwd(),
887
- parsers: userConfig.parsers ?? [],
888
- adapter: userConfig.adapter,
889
- output: {
890
- format: false,
891
- lint: false,
892
- write: true,
893
- extension: DEFAULT_EXTENSION,
894
- defaultBanner: DEFAULT_BANNER,
895
- ...userConfig.output
896
- },
897
- devtools: userConfig.devtools ? {
898
- studioUrl: DEFAULT_STUDIO_URL,
899
- ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
900
- } : void 0,
901
- plugins: userConfig.plugins
902
- };
903
- const storage = config.output.write === false ? null : config.storage ?? fsStorage();
904
604
  if (config.output.clean) {
905
605
  await hooks.emit("kubb:debug", {
906
606
  date: /* @__PURE__ */ new Date(),
907
607
  logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
908
608
  });
909
- await storage?.clear(resolve(config.root, config.output.path));
910
- }
911
- const driver = new PluginDriver(config, { hooks });
912
- function registerMiddlewareHook(event, middlewareHooks) {
913
- const handler = middlewareHooks[event];
914
- if (handler) hooks.on(event, handler);
609
+ await config.storage.clear(resolve(config.root, config.output.path));
915
610
  }
916
- for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
917
- const adapter = config.adapter;
918
- if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
919
- const source = inputToAdapterSource(config);
920
- await hooks.emit("kubb:debug", {
921
- date: /* @__PURE__ */ new Date(),
922
- logs: [`Running adapter: ${adapter.name}`]
923
- });
924
- driver.adapter = adapter;
925
- driver.inputNode = await adapter.parse(source);
926
- await hooks.emit("kubb:debug", {
927
- date: /* @__PURE__ */ new Date(),
928
- logs: [
929
- `✓ Adapter '${adapter.name}' resolved InputNode`,
930
- ` • Schemas: ${driver.inputNode.schemas.length}`,
931
- ` • Operations: ${driver.inputNode.operations.length}`
932
- ]
933
- });
611
+ await driver.setup();
934
612
  return {
935
613
  config,
936
614
  hooks,
937
615
  driver,
938
- sources,
939
- storage
616
+ storage,
617
+ dispose,
618
+ [Symbol.dispose]: dispose
940
619
  };
941
- }
942
- /**
943
- * Walks the AST and dispatches nodes to a plugin's direct AST hooks
944
- * (`schema`, `operation`, `operations`).
945
- *
946
- * When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
947
- * `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
948
- * of top-level schema names transitively reachable from the included operations and skips
949
- * schemas that fall outside that set. This ensures that component schemas referenced
950
- * exclusively by excluded operations are not generated.
951
- */
952
- async function runPluginAstHooks(plugin, context) {
953
- const { adapter, inputNode, resolver, driver } = context;
954
- const { exclude, include, override } = plugin.options;
955
- 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.`);
956
- function resolveRenderer(gen) {
957
- return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
620
+ function dispose() {
621
+ driver.dispose();
958
622
  }
959
- const generators = plugin.generators ?? [];
960
- const collectedOperations = [];
961
- const generatorContext = {
962
- ...context,
963
- resolver: driver.getResolver(plugin.name)
964
- };
965
- const operationFilterTypes = new Set([
966
- "tag",
967
- "operationId",
968
- "path",
969
- "method",
970
- "contentType"
971
- ]);
972
- const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false;
973
- const hasSchemaNameIncludes = include?.some(({ type }) => type === "schemaName") ?? false;
974
- let allowedSchemaNames;
975
- if (hasOperationBasedIncludes && !hasSchemaNameIncludes) allowedSchemaNames = collectUsedSchemaNames(inputNode.operations.filter((op) => resolver.resolveOptions(op, {
976
- options: plugin.options,
977
- exclude,
978
- include,
979
- override
980
- }) !== null), inputNode.schemas);
981
- await walk(inputNode, {
982
- depth: "shallow",
983
- async schema(node) {
984
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
985
- if (allowedSchemaNames !== void 0 && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) return;
986
- const options = resolver.resolveOptions(transformedNode, {
987
- options: plugin.options,
988
- exclude,
989
- include,
990
- override
623
+ }
624
+ async function safeBuild(setupResult) {
625
+ try {
626
+ var _usingCtx$1 = _usingCtx();
627
+ _usingCtx$1.u(setupResult);
628
+ const { driver, hooks, storage } = setupResult;
629
+ const failedPlugins = /* @__PURE__ */ new Set();
630
+ const pluginTimings = /* @__PURE__ */ new Map();
631
+ const config = driver.config;
632
+ const writtenPaths = /* @__PURE__ */ new Set();
633
+ const parsersMap = /* @__PURE__ */ new Map();
634
+ const fileProcessor = new FileProcessor();
635
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
636
+ async function flushPendingFiles() {
637
+ const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path));
638
+ if (files.length === 0) return;
639
+ await hooks.emit("kubb:debug", {
640
+ date: /* @__PURE__ */ new Date(),
641
+ logs: [`Writing ${files.length} files...`]
991
642
  });
992
- if (options === null) return;
993
- const ctx = {
994
- ...generatorContext,
995
- options
996
- };
997
- for (const gen of generators) {
998
- if (!gen.schema) continue;
999
- await applyHookResult(await gen.schema(transformedNode, ctx), driver, resolveRenderer(gen));
643
+ await hooks.emit("kubb:files:processing:start", { files });
644
+ const stream = fileProcessor.stream(files, {
645
+ parsers: parsersMap,
646
+ extension: config.output.extension
647
+ });
648
+ const queue = [];
649
+ for (const { file, source, processed, total, percentage } of stream) {
650
+ writtenPaths.add(file.path);
651
+ queue.push((async () => {
652
+ await hooks.emit("kubb:file:processing:update", {
653
+ file,
654
+ source,
655
+ processed,
656
+ total,
657
+ percentage,
658
+ config
659
+ });
660
+ if (source) await storage.setItem(file.path, source);
661
+ })());
662
+ if (queue.length >= 50) await Promise.all(queue.splice(0));
1000
663
  }
1001
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
1002
- },
1003
- async operation(node) {
1004
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1005
- const options = resolver.resolveOptions(transformedNode, {
1006
- options: plugin.options,
1007
- exclude,
1008
- include,
1009
- override
664
+ await Promise.all(queue);
665
+ await hooks.emit("kubb:files:processing:end", { files });
666
+ await hooks.emit("kubb:debug", {
667
+ date: /* @__PURE__ */ new Date(),
668
+ logs: [`✓ File write process completed for ${files.length} files`]
1010
669
  });
1011
- if (options !== null) {
1012
- collectedOperations.push(transformedNode);
1013
- const ctx = {
1014
- ...generatorContext,
1015
- options
670
+ }
671
+ async function dispatchOperationsToGenerators(generators, collectedOperations, ctx, rendererFor) {
672
+ for (const gen of generators) {
673
+ if (!gen.operations) continue;
674
+ await applyHookResult({
675
+ result: await gen.operations(collectedOperations, ctx),
676
+ driver,
677
+ rendererFactory: rendererFor(gen)
678
+ });
679
+ }
680
+ await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
681
+ }
682
+ /**
683
+ * Single-pass fan-out: iterates all schemas and operations once, distributing each node
684
+ * to every generator-plugin in parallel. This replaces the N-pass-per-plugin pattern
685
+ * (each plugin getting its own iterator) with one parse pass fanned to all plugins,
686
+ * eliminating the N×parse-time overhead for multi-plugin builds.
687
+ */
688
+ async function runPlugins(entries) {
689
+ const { schemas, operations } = driver.inputNode;
690
+ const operationFilterTypes = new Set([
691
+ "tag",
692
+ "operationId",
693
+ "path",
694
+ "method",
695
+ "contentType"
696
+ ]);
697
+ const states = entries.map(({ plugin, context, hrStart }) => {
698
+ const { exclude, include, override } = plugin.options;
699
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
700
+ const hasInclude = Array.isArray(include) && include.length > 0;
701
+ const hasOverride = Array.isArray(override) && override.length > 0;
702
+ return {
703
+ plugin,
704
+ generatorContext: {
705
+ ...context,
706
+ resolver: driver.getResolver(plugin.name)
707
+ },
708
+ generators: plugin.generators ?? [],
709
+ hrStart,
710
+ failed: false,
711
+ error: void 0,
712
+ optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
713
+ allowedSchemaNames: void 0
1016
714
  };
1017
- for (const gen of generators) {
1018
- if (!gen.operation) continue;
1019
- await applyHookResult(await gen.operation(transformedNode, ctx), driver, resolveRenderer(gen));
715
+ });
716
+ const pruningStates = states.filter(({ plugin }) => {
717
+ const { include } = plugin.options;
718
+ return (include?.some(({ type }) => operationFilterTypes.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
719
+ });
720
+ if (pruningStates.length > 0) {
721
+ const allSchemas = [];
722
+ for await (const schema of schemas) allSchemas.push(schema);
723
+ const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
724
+ for await (const operation of operations) for (const state of pruningStates) {
725
+ const { exclude, include, override } = state.plugin.options;
726
+ if (state.generatorContext.resolver.resolveOptions(operation, {
727
+ options: state.plugin.options,
728
+ exclude,
729
+ include,
730
+ override
731
+ }) !== null) includedOpsByState.get(state)?.push(operation);
1020
732
  }
1021
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
733
+ for (const state of pruningStates) state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas);
1022
734
  }
1023
- }
1024
- });
1025
- if (collectedOperations.length > 0) {
1026
- const ctx = {
1027
- ...generatorContext,
1028
- options: plugin.options
1029
- };
1030
- for (const gen of generators) {
1031
- if (!gen.operations) continue;
1032
- await applyHookResult(await gen.operations(collectedOperations, ctx), driver, resolveRenderer(gen));
1033
- }
1034
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
1035
- }
1036
- }
1037
- async function safeBuild(setupResult) {
1038
- const { driver, hooks, sources, storage } = setupResult;
1039
- const failedPlugins = /* @__PURE__ */ new Set();
1040
- const pluginTimings = /* @__PURE__ */ new Map();
1041
- const config = driver.config;
1042
- try {
1043
- await driver.emitSetupHooks();
1044
- if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
1045
- config,
1046
- adapter: driver.adapter,
1047
- inputNode: driver.inputNode,
1048
- getPlugin: driver.getPlugin.bind(driver),
1049
- get files() {
1050
- return driver.fileManager.files;
1051
- },
1052
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1053
- });
1054
- for (const plugin of driver.plugins.values()) {
1055
- const context = driver.getContext(plugin);
1056
- const hrStart = process.hrtime();
1057
- try {
1058
- const timestamp = /* @__PURE__ */ new Date();
1059
- await hooks.emit("kubb:plugin:start", { plugin });
1060
- await hooks.emit("kubb:debug", {
1061
- date: timestamp,
1062
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
735
+ function resolveRendererFor(gen, state) {
736
+ return gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
737
+ }
738
+ async function dispatchSchema(state, node) {
739
+ if (state.failed) return;
740
+ try {
741
+ const { plugin, generatorContext, generators } = state;
742
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
743
+ if (state.allowedSchemaNames !== void 0 && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
744
+ const { exclude, include, override } = plugin.options;
745
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
746
+ options: plugin.options,
747
+ exclude,
748
+ include,
749
+ override
750
+ });
751
+ if (options === null) return;
752
+ const ctx = {
753
+ ...generatorContext,
754
+ options
755
+ };
756
+ for (const gen of generators) {
757
+ if (!gen.schema) continue;
758
+ const raw = gen.schema(transformedNode, ctx);
759
+ const applied = applyHookResult({
760
+ result: isPromise(raw) ? await raw : raw,
761
+ driver,
762
+ rendererFactory: resolveRendererFor(gen, state)
763
+ });
764
+ if (isPromise(applied)) await applied;
765
+ }
766
+ await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
767
+ } catch (caughtError) {
768
+ state.failed = true;
769
+ state.error = caughtError;
770
+ }
771
+ }
772
+ async function dispatchOperation(state, node) {
773
+ if (state.failed) return;
774
+ try {
775
+ const { plugin, generatorContext, generators } = state;
776
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
777
+ const { exclude, include, override } = plugin.options;
778
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
779
+ options: plugin.options,
780
+ exclude,
781
+ include,
782
+ override
783
+ });
784
+ if (options === null) return;
785
+ const ctx = {
786
+ ...generatorContext,
787
+ options
788
+ };
789
+ for (const gen of generators) {
790
+ if (!gen.operation) continue;
791
+ const raw = gen.operation(transformedNode, ctx);
792
+ const applied = applyHookResult({
793
+ result: isPromise(raw) ? await raw : raw,
794
+ driver,
795
+ rendererFactory: resolveRendererFor(gen, state)
796
+ });
797
+ if (isPromise(applied)) await applied;
798
+ }
799
+ await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
800
+ } catch (caughtError) {
801
+ state.failed = true;
802
+ state.error = caughtError;
803
+ }
804
+ }
805
+ await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
806
+ concurrency: 8,
807
+ flush: flushPendingFiles
808
+ });
809
+ const collectedOperations = [];
810
+ await forBatches(operations, (nodes) => {
811
+ collectedOperations.push(...nodes);
812
+ return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
813
+ }, {
814
+ concurrency: 8,
815
+ flush: flushPendingFiles
816
+ });
817
+ for (const state of states) {
818
+ if (!state.failed) try {
819
+ const { plugin, generatorContext, generators } = state;
820
+ await dispatchOperationsToGenerators(generators, collectedOperations, {
821
+ ...generatorContext,
822
+ options: plugin.options
823
+ }, (gen) => resolveRendererFor(gen, state));
824
+ } catch (caughtError) {
825
+ state.failed = true;
826
+ state.error = caughtError;
827
+ }
828
+ const duration = getElapsedMs(state.hrStart);
829
+ pluginTimings.set(state.plugin.name, duration);
830
+ await driver.hooks.emit("kubb:plugin:end", {
831
+ plugin: state.plugin,
832
+ duration,
833
+ success: !state.failed,
834
+ ...state.failed && state.error ? { error: state.error } : {},
835
+ config: driver.config,
836
+ get files() {
837
+ return driver.fileManager.files;
838
+ },
839
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1063
840
  });
1064
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
841
+ if (state.failed && state.error) failedPlugins.add({
842
+ plugin: state.plugin,
843
+ error: state.error
844
+ });
845
+ await driver.hooks.emit("kubb:debug", {
846
+ date: /* @__PURE__ */ new Date(),
847
+ logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
848
+ });
849
+ }
850
+ }
851
+ try {
852
+ await driver.emitSetupHooks();
853
+ if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
854
+ config,
855
+ adapter: driver.adapter,
856
+ meta: driver.inputNode.meta,
857
+ getPlugin: driver.getPlugin.bind(driver),
858
+ get files() {
859
+ return driver.fileManager.files;
860
+ },
861
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
862
+ });
863
+ const generatorPlugins = [];
864
+ for (const plugin of driver.plugins.values()) {
865
+ const context = driver.getContext(plugin);
866
+ const hrStart = process.hrtime();
867
+ try {
868
+ await hooks.emit("kubb:plugin:start", { plugin });
869
+ await hooks.emit("kubb:debug", {
870
+ date: /* @__PURE__ */ new Date(),
871
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
872
+ });
873
+ } catch (caughtError) {
874
+ const error = caughtError;
875
+ const duration = getElapsedMs(hrStart);
876
+ pluginTimings.set(plugin.name, duration);
877
+ await hooks.emit("kubb:plugin:end", {
878
+ plugin,
879
+ duration,
880
+ success: false,
881
+ error,
882
+ config,
883
+ get files() {
884
+ return driver.fileManager.files;
885
+ },
886
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
887
+ });
888
+ failedPlugins.add({
889
+ plugin,
890
+ error
891
+ });
892
+ continue;
893
+ }
894
+ if (plugin.generators?.length || driver.hasEventGenerators(plugin.name)) {
895
+ generatorPlugins.push({
896
+ plugin,
897
+ context,
898
+ hrStart
899
+ });
900
+ continue;
901
+ }
1065
902
  const duration = getElapsedMs(hrStart);
1066
903
  pluginTimings.set(plugin.name, duration);
1067
904
  await hooks.emit("kubb:plugin:end", {
@@ -1078,107 +915,61 @@ async function safeBuild(setupResult) {
1078
915
  date: /* @__PURE__ */ new Date(),
1079
916
  logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1080
917
  });
1081
- } catch (caughtError) {
1082
- const error = caughtError;
1083
- const errorTimestamp = /* @__PURE__ */ new Date();
918
+ }
919
+ if (generatorPlugins.length > 0) if (driver.inputNode) await withDrain(() => runPlugins(generatorPlugins), flushPendingFiles);
920
+ else for (const { plugin, hrStart } of generatorPlugins) {
1084
921
  const duration = getElapsedMs(hrStart);
922
+ pluginTimings.set(plugin.name, duration);
1085
923
  await hooks.emit("kubb:plugin:end", {
1086
924
  plugin,
1087
925
  duration,
1088
- success: false,
1089
- error,
926
+ success: true,
1090
927
  config,
1091
928
  get files() {
1092
929
  return driver.fileManager.files;
1093
930
  },
1094
931
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1095
932
  });
1096
- await hooks.emit("kubb:debug", {
1097
- date: errorTimestamp,
1098
- logs: [
1099
- "✗ Plugin start failed",
1100
- ` • Plugin Name: ${plugin.name}`,
1101
- ` • Error: ${error.constructor.name} - ${error.message}`,
1102
- " • Stack Trace:",
1103
- error.stack || "No stack trace available"
1104
- ]
1105
- });
1106
- failedPlugins.add({
1107
- plugin,
1108
- error
1109
- });
1110
933
  }
934
+ await hooks.emit("kubb:plugins:end", {
935
+ config,
936
+ get files() {
937
+ return driver.fileManager.files;
938
+ },
939
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
940
+ });
941
+ await flushPendingFiles();
942
+ const files = driver.fileManager.files;
943
+ await hooks.emit("kubb:build:end", {
944
+ files,
945
+ config,
946
+ outputDir: resolve(config.root, config.output.path)
947
+ });
948
+ return {
949
+ failedPlugins,
950
+ files,
951
+ driver,
952
+ pluginTimings,
953
+ storage
954
+ };
955
+ } catch (error) {
956
+ return {
957
+ failedPlugins,
958
+ files: [],
959
+ driver,
960
+ pluginTimings,
961
+ error,
962
+ storage
963
+ };
1111
964
  }
1112
- await hooks.emit("kubb:plugins:end", {
1113
- config,
1114
- get files() {
1115
- return driver.fileManager.files;
1116
- },
1117
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1118
- });
1119
- const files = driver.fileManager.files;
1120
- const parsersMap = /* @__PURE__ */ new Map();
1121
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1122
- const fileProcessor = new FileProcessor();
1123
- await hooks.emit("kubb:debug", {
1124
- date: /* @__PURE__ */ new Date(),
1125
- logs: [`Writing ${files.length} files...`]
1126
- });
1127
- await fileProcessor.run(files, {
1128
- parsers: parsersMap,
1129
- extension: config.output.extension,
1130
- onStart: async (processingFiles) => {
1131
- await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1132
- },
1133
- onUpdate: async ({ file, source, processed, total, percentage }) => {
1134
- await hooks.emit("kubb:file:processing:update", {
1135
- file,
1136
- source,
1137
- processed,
1138
- total,
1139
- percentage,
1140
- config
1141
- });
1142
- if (source) {
1143
- await storage?.setItem(file.path, source);
1144
- sources.set(file.path, source);
1145
- }
1146
- },
1147
- onEnd: async (processedFiles) => {
1148
- await hooks.emit("kubb:files:processing:end", { files: processedFiles });
1149
- await hooks.emit("kubb:debug", {
1150
- date: /* @__PURE__ */ new Date(),
1151
- logs: [`✓ File write process completed for ${processedFiles.length} files`]
1152
- });
1153
- }
1154
- });
1155
- await hooks.emit("kubb:build:end", {
1156
- files,
1157
- config,
1158
- outputDir: resolve(config.root, config.output.path)
1159
- });
1160
- return {
1161
- failedPlugins,
1162
- files,
1163
- driver,
1164
- pluginTimings,
1165
- sources
1166
- };
1167
- } catch (error) {
1168
- return {
1169
- failedPlugins,
1170
- files: [],
1171
- driver,
1172
- pluginTimings,
1173
- error,
1174
- sources
1175
- };
965
+ } catch (_) {
966
+ _usingCtx$1.e = _;
1176
967
  } finally {
1177
- driver.dispose();
968
+ _usingCtx$1.d();
1178
969
  }
1179
970
  }
1180
971
  async function build(setupResult) {
1181
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
972
+ const { files, driver, failedPlugins, pluginTimings, error, storage } = await safeBuild(setupResult);
1182
973
  if (error) throw error;
1183
974
  if (failedPlugins.size > 0) {
1184
975
  const errors = [...failedPlugins].map(({ error }) => error);
@@ -1190,32 +981,32 @@ async function build(setupResult) {
1190
981
  driver,
1191
982
  pluginTimings,
1192
983
  error: void 0,
1193
- sources
984
+ storage
1194
985
  };
1195
986
  }
1196
- function inputToAdapterSource(config) {
1197
- if (Array.isArray(config.input)) return {
1198
- type: "paths",
1199
- paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : resolve(config.root, i.path))
1200
- };
1201
- if ("data" in config.input) return {
1202
- type: "data",
1203
- data: config.input.data
1204
- };
1205
- if (new URLPath(config.input.path).isURL) return {
1206
- type: "path",
1207
- path: config.input.path
1208
- };
987
+ /**
988
+ * Returns a snapshot of the current runtime environment.
989
+ *
990
+ * Useful for attaching context to debug logs and error reports so that
991
+ * issues can be reproduced without manual information gathering.
992
+ */
993
+ function getDiagnosticInfo() {
1209
994
  return {
1210
- type: "path",
1211
- path: resolve(config.root, config.input.path)
995
+ nodeVersion: version,
996
+ KubbVersion: version$1,
997
+ platform: process.platform,
998
+ arch: process.arch,
999
+ cwd: process.cwd()
1212
1000
  };
1213
1001
  }
1002
+ function isInputPath(config) {
1003
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1004
+ }
1214
1005
  /**
1215
1006
  * Creates a Kubb instance bound to a single config entry.
1216
1007
  *
1217
1008
  * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1218
- * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1009
+ * `setup()`. The instance then holds shared state (`hooks`, `storage`, `driver`, `config`)
1219
1010
  * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1220
1011
  * calling `setup()` or `build()`.
1221
1012
  *
@@ -1237,14 +1028,17 @@ function createKubb(userConfig, options = {}) {
1237
1028
  get hooks() {
1238
1029
  return hooks;
1239
1030
  },
1240
- get sources() {
1241
- return setupResult?.sources ?? /* @__PURE__ */ new Map();
1031
+ get storage() {
1032
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing storage");
1033
+ return setupResult.storage;
1242
1034
  },
1243
1035
  get driver() {
1244
- return setupResult?.driver;
1036
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing driver");
1037
+ return setupResult.driver;
1245
1038
  },
1246
1039
  get config() {
1247
- return setupResult?.config;
1040
+ if (!setupResult) throw new Error("[kubb] setup() must be called before accessing config");
1041
+ return setupResult.config;
1248
1042
  },
1249
1043
  async setup() {
1250
1044
  setupResult = await setup(userConfig, { hooks });
@@ -1263,15 +1057,10 @@ function createKubb(userConfig, options = {}) {
1263
1057
  //#endregion
1264
1058
  //#region src/createRenderer.ts
1265
1059
  /**
1266
- * Creates a renderer factory for use in generator definitions.
1267
- *
1268
- * Wrap your renderer factory function with this helper to register it as the
1269
- * renderer for a generator. Core will call this factory once per render cycle
1270
- * to obtain a fresh renderer instance.
1060
+ * Wraps a renderer factory for use in generator definitions.
1271
1061
  *
1272
1062
  * @example
1273
1063
  * ```ts
1274
- * // packages/renderer-jsx/src/index.ts
1275
1064
  * export const jsxRenderer = createRenderer(() => {
1276
1065
  * const runtime = new Runtime()
1277
1066
  * return {
@@ -1280,14 +1069,6 @@ function createKubb(userConfig, options = {}) {
1280
1069
  * unmount(error) { runtime.unmount(error) },
1281
1070
  * }
1282
1071
  * })
1283
- *
1284
- * // packages/plugin-zod/src/generators/zodGenerator.tsx
1285
- * import { jsxRenderer } from '@kubb/renderer-jsx'
1286
- * export const zodGenerator = defineGenerator<PluginZod>({
1287
- * name: 'zod',
1288
- * renderer: jsxRenderer,
1289
- * schema(node, options) { return <File ...>...</File> },
1290
- * })
1291
1072
  * ```
1292
1073
  */
1293
1074
  function createRenderer(factory) {
@@ -1308,7 +1089,11 @@ function defineGenerator(generator) {
1308
1089
  /**
1309
1090
  * Wraps a logger definition into a typed {@link Logger}.
1310
1091
  *
1311
- * @example
1092
+ * The optional second type parameter `TInstallReturn` allows loggers to return
1093
+ * a value from `install` — for example, a sink factory that the caller can
1094
+ * forward to hook execution.
1095
+ *
1096
+ * @example Basic logger
1312
1097
  * ```ts
1313
1098
  * export const myLogger = defineLogger({
1314
1099
  * name: 'my-logger',
@@ -1318,6 +1103,17 @@ function defineGenerator(generator) {
1318
1103
  * },
1319
1104
  * })
1320
1105
  * ```
1106
+ *
1107
+ * @example Logger that returns a hook sink factory
1108
+ * ```ts
1109
+ * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
1110
+ * name: 'my-logger',
1111
+ * install(context, options) {
1112
+ * // … register event handlers …
1113
+ * return (commandWithArgs) => ({ onStdout: console.log })
1114
+ * },
1115
+ * })
1116
+ * ```
1321
1117
  */
1322
1118
  function defineLogger(logger) {
1323
1119
  return logger;
@@ -1388,34 +1184,6 @@ function defineParser(parser) {
1388
1184
  return parser;
1389
1185
  }
1390
1186
  //#endregion
1391
- //#region src/definePlugin.ts
1392
- /**
1393
- * Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
1394
- *
1395
- * Handlers live in a single `hooks` object (inspired by Astro integrations).
1396
- * All lifecycle events from `KubbHooks` are available for subscription.
1397
- *
1398
- * @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
1399
- * Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
1400
- *
1401
- * @example
1402
- * ```ts
1403
- * import { definePlugin } from '@kubb/core'
1404
- *
1405
- * export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
1406
- * name: 'plugin-ts',
1407
- * hooks: {
1408
- * 'kubb:plugin:setup'(ctx) {
1409
- * ctx.setResolver(resolverTs)
1410
- * },
1411
- * },
1412
- * }))
1413
- * ```
1414
- */
1415
- function definePlugin(factory) {
1416
- return (options) => factory(options ?? {});
1417
- }
1418
- //#endregion
1419
1187
  //#region src/storages/memoryStorage.ts
1420
1188
  /**
1421
1189
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -1466,6 +1234,6 @@ const memoryStorage = createStorage(() => {
1466
1234
  };
1467
1235
  });
1468
1236
  //#endregion
1469
- export { AsyncEventEmitter, FileManager, FileProcessor, PluginDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
1237
+ export { AsyncEventEmitter, FileManager, FileProcessor, KubbDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
1470
1238
 
1471
1239
  //# sourceMappingURL=index.js.map