@kubb/core 5.0.0-beta.3 → 5.0.0-beta.31

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 (46) hide show
  1. package/README.md +8 -38
  2. package/dist/KubbDriver-CFx2DdhF.js +2131 -0
  3. package/dist/KubbDriver-CFx2DdhF.js.map +1 -0
  4. package/dist/KubbDriver-vyD7F0Ip.cjs +2252 -0
  5. package/dist/KubbDriver-vyD7F0Ip.cjs.map +1 -0
  6. package/dist/{types-CC09VtBt.d.ts → createKubb-6zii1jo-.d.ts} +1610 -1257
  7. package/dist/index.cjs +351 -1125
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +3 -186
  10. package/dist/index.js +341 -1119
  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 +78 -61
  19. package/src/FileProcessor.ts +48 -38
  20. package/src/KubbDriver.ts +930 -0
  21. package/src/constants.ts +11 -6
  22. package/src/createAdapter.ts +113 -17
  23. package/src/createKubb.ts +1039 -478
  24. package/src/createRenderer.ts +58 -27
  25. package/src/createStorage.ts +36 -23
  26. package/src/defineGenerator.ts +127 -15
  27. package/src/defineLogger.ts +66 -7
  28. package/src/defineMiddleware.ts +19 -17
  29. package/src/defineParser.ts +30 -13
  30. package/src/definePlugin.ts +329 -14
  31. package/src/defineResolver.ts +365 -167
  32. package/src/devtools.ts +8 -1
  33. package/src/index.ts +2 -2
  34. package/src/mocks.ts +11 -14
  35. package/src/storages/fsStorage.ts +13 -37
  36. package/src/types.ts +48 -1292
  37. package/dist/PluginDriver-BXibeQk-.cjs +0 -1036
  38. package/dist/PluginDriver-BXibeQk-.cjs.map +0 -1
  39. package/dist/PluginDriver-DV3p2Hky.js +0 -945
  40. package/dist/PluginDriver-DV3p2Hky.js.map +0 -1
  41. package/src/Kubb.ts +0 -300
  42. package/src/PluginDriver.ts +0 -424
  43. package/src/renderNode.ts +0 -35
  44. package/src/utils/diagnostics.ts +0 -18
  45. package/src/utils/isInputPath.ts +0 -10
  46. package/src/utils/packageJSON.ts +0 -99
package/dist/index.cjs CHANGED
@@ -1,184 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_PluginDriver = require("./PluginDriver-BXibeQk-.cjs");
3
- let node_events = require("node:events");
2
+ const require_KubbDriver = require("./KubbDriver-vyD7F0Ip.cjs");
4
3
  let node_fs_promises = require("node:fs/promises");
5
4
  let node_path = require("node:path");
6
5
  let _kubb_ast = require("@kubb/ast");
7
- _kubb_ast = require_PluginDriver.__toESM(_kubb_ast, 1);
6
+ _kubb_ast = require_KubbDriver.__toESM(_kubb_ast, 1);
8
7
  let node_process = require("node:process");
9
- //#region ../../internals/utils/src/errors.ts
10
- /**
11
- * Thrown when one or more errors occur during a Kubb build.
12
- * Carries the full list of underlying errors on `errors`.
13
- *
14
- * @example
15
- * ```ts
16
- * throw new BuildError('Build failed', { errors: [err1, err2] })
17
- * ```
18
- */
19
- var BuildError = class extends Error {
20
- errors;
21
- constructor(message, options) {
22
- super(message, { cause: options.cause });
23
- this.name = "BuildError";
24
- this.errors = options.errors;
25
- }
26
- };
27
- /**
28
- * Coerces an unknown thrown value to an `Error` instance.
29
- * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
30
- *
31
- * @example
32
- * ```ts
33
- * try { ... } catch(err) {
34
- * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
35
- * }
36
- * ```
37
- */
38
- function toError(value) {
39
- return value instanceof Error ? value : new Error(String(value));
40
- }
41
- //#endregion
42
- //#region ../../internals/utils/src/asyncEventEmitter.ts
43
- /**
44
- * Typed `EventEmitter` that awaits all async listeners before resolving.
45
- * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
46
- *
47
- * @example
48
- * ```ts
49
- * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
50
- * emitter.on('build', async (name) => { console.log(name) })
51
- * await emitter.emit('build', 'petstore') // all listeners awaited
52
- * ```
53
- */
54
- var AsyncEventEmitter = class {
55
- /**
56
- * Maximum number of listeners per event before Node emits a memory-leak warning.
57
- * @default 10
58
- */
59
- constructor(maxListener = 10) {
60
- this.#emitter.setMaxListeners(maxListener);
61
- }
62
- #emitter = new node_events.EventEmitter();
63
- /**
64
- * Emits `eventName` and awaits all registered listeners sequentially.
65
- * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
66
- *
67
- * @example
68
- * ```ts
69
- * await emitter.emit('build', 'petstore')
70
- * ```
71
- */
72
- async emit(eventName, ...eventArgs) {
73
- const listeners = this.#emitter.listeners(eventName);
74
- if (listeners.length === 0) return;
75
- for (const listener of listeners) try {
76
- await listener(...eventArgs);
77
- } catch (err) {
78
- let serializedArgs;
79
- try {
80
- serializedArgs = JSON.stringify(eventArgs);
81
- } catch {
82
- serializedArgs = String(eventArgs);
83
- }
84
- throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
85
- }
86
- }
87
- /**
88
- * Registers a persistent listener for `eventName`.
89
- *
90
- * @example
91
- * ```ts
92
- * emitter.on('build', async (name) => { console.log(name) })
93
- * ```
94
- */
95
- on(eventName, handler) {
96
- this.#emitter.on(eventName, handler);
97
- }
98
- /**
99
- * Registers a one-shot listener that removes itself after the first invocation.
100
- *
101
- * @example
102
- * ```ts
103
- * emitter.onOnce('build', async (name) => { console.log(name) })
104
- * ```
105
- */
106
- onOnce(eventName, handler) {
107
- const wrapper = (...args) => {
108
- this.off(eventName, wrapper);
109
- return handler(...args);
110
- };
111
- this.on(eventName, wrapper);
112
- }
113
- /**
114
- * Removes a previously registered listener.
115
- *
116
- * @example
117
- * ```ts
118
- * emitter.off('build', handler)
119
- * ```
120
- */
121
- off(eventName, handler) {
122
- this.#emitter.off(eventName, handler);
123
- }
124
- /**
125
- * Returns the number of listeners registered for `eventName`.
126
- *
127
- * @example
128
- * ```ts
129
- * emitter.on('build', handler)
130
- * emitter.listenerCount('build') // 1
131
- * ```
132
- */
133
- listenerCount(eventName) {
134
- return this.#emitter.listenerCount(eventName);
135
- }
136
- /**
137
- * Removes all listeners from every event channel.
138
- *
139
- * @example
140
- * ```ts
141
- * emitter.removeAll()
142
- * ```
143
- */
144
- removeAll() {
145
- this.#emitter.removeAllListeners();
146
- }
147
- };
148
- //#endregion
149
- //#region ../../internals/utils/src/time.ts
150
- /**
151
- * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
152
- * Rounds to 2 decimal places for sub-millisecond precision without noise.
153
- *
154
- * @example
155
- * ```ts
156
- * const start = process.hrtime()
157
- * doWork()
158
- * getElapsedMs(start) // 42.35
159
- * ```
160
- */
161
- function getElapsedMs(hrStart) {
162
- const [seconds, nanoseconds] = process.hrtime(hrStart);
163
- const ms = seconds * 1e3 + nanoseconds / 1e6;
164
- return Math.round(ms * 100) / 100;
165
- }
166
- /**
167
- * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
168
- *
169
- * @example
170
- * ```ts
171
- * formatMs(250) // '250ms'
172
- * formatMs(1500) // '1.50s'
173
- * formatMs(90000) // '1m 30.0s'
174
- * ```
175
- */
176
- function formatMs(ms) {
177
- if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
178
- if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
179
- return `${Math.round(ms)}ms`;
180
- }
181
- //#endregion
182
8
  //#region ../../internals/utils/src/fs.ts
183
9
  /**
184
10
  * Resolves to `true` when the file or directory at `path` exists.
@@ -245,488 +71,81 @@ async function clean(path) {
245
71
  });
246
72
  }
247
73
  //#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 : require_PluginDriver.camelCase(raw);
429
- return this.#options.casing === "camelcase" ? require_PluginDriver.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
74
  //#region src/createAdapter.ts
498
75
  /**
499
- * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
76
+ * Defines a custom adapter that translates a spec format into Kubb's universal
77
+ * AST. Use this when you need to consume GraphQL, gRPC, AsyncAPI, or another
78
+ * domain-specific schema. Built-in adapters: `@kubb/adapter-oas` for
79
+ * OpenAPI/Swagger documents.
500
80
  *
501
- * Use this to support GraphQL schemas, gRPC definitions, AsyncAPI, or custom domain-specific languages.
502
- * Built-in adapters include `@kubb/adapter-oas` for OpenAPI and Swagger documents.
503
- *
504
- * @note Adapters must parse their input format to Kubb's `InputNode` structure.
81
+ * Adapters must return an `InputNode` from `parse`. That node is what every
82
+ * plugin in the build consumes.
505
83
  *
506
84
  * @example
507
85
  * ```ts
508
- * export const myAdapter = createAdapter<MyAdapter>((options) => {
509
- * return {
510
- * name: 'my-adapter',
511
- * options,
512
- * async parse(source) {
513
- * // Transform source format to InputNode
514
- * return { ... }
515
- * },
516
- * }
517
- * })
86
+ * import { createAdapter, ast, type AdapterFactoryOptions } from '@kubb/core'
87
+ *
88
+ * type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
518
89
  *
519
- * // Instantiate:
520
- * const adapter = myAdapter({ validate: true })
90
+ * export const myAdapter = createAdapter<MyAdapter>((options) => ({
91
+ * name: 'my-adapter',
92
+ * options,
93
+ * document: null,
94
+ * async parse(_source) {
95
+ * // Convert `source` (path or inline data) into an InputNode.
96
+ * return ast.createInput()
97
+ * },
98
+ * getImports: () => [],
99
+ * async validate() {
100
+ * // Throw or call ctx.error here when the spec is invalid.
101
+ * },
102
+ * }))
521
103
  * ```
522
104
  */
523
105
  function createAdapter(build) {
524
106
  return (options) => build(options ?? {});
525
107
  }
526
108
  //#endregion
527
- //#region ../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
528
- var Node = class {
529
- value;
530
- next;
531
- constructor(value) {
532
- this.value = value;
533
- }
534
- };
535
- var Queue = class {
536
- #head;
537
- #tail;
538
- #size;
539
- constructor() {
540
- this.clear();
541
- }
542
- enqueue(value) {
543
- const node = new Node(value);
544
- if (this.#head) {
545
- this.#tail.next = node;
546
- this.#tail = node;
547
- } else {
548
- this.#head = node;
549
- this.#tail = node;
550
- }
551
- this.#size++;
552
- }
553
- dequeue() {
554
- const current = this.#head;
555
- if (!current) return;
556
- this.#head = this.#head.next;
557
- this.#size--;
558
- if (!this.#head) this.#tail = void 0;
559
- return current.value;
560
- }
561
- peek() {
562
- if (!this.#head) return;
563
- return this.#head.value;
564
- }
565
- clear() {
566
- this.#head = void 0;
567
- this.#tail = void 0;
568
- this.#size = 0;
569
- }
570
- get size() {
571
- return this.#size;
572
- }
573
- *[Symbol.iterator]() {
574
- let current = this.#head;
575
- while (current) {
576
- yield current.value;
577
- current = current.next;
578
- }
579
- }
580
- *drain() {
581
- while (this.#head) yield this.dequeue();
582
- }
583
- };
584
- //#endregion
585
- //#region ../../node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
586
- function pLimit(concurrency) {
587
- let rejectOnClear = false;
588
- if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
589
- validateConcurrency(concurrency);
590
- if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
591
- const queue = new Queue();
592
- let activeCount = 0;
593
- const resumeNext = () => {
594
- if (activeCount < concurrency && queue.size > 0) {
595
- activeCount++;
596
- queue.dequeue().run();
597
- }
598
- };
599
- const next = () => {
600
- activeCount--;
601
- resumeNext();
602
- };
603
- const run = async (function_, resolve, arguments_) => {
604
- const result = (async () => function_(...arguments_))();
605
- resolve(result);
606
- try {
607
- await result;
608
- } catch {}
609
- next();
610
- };
611
- const enqueue = (function_, resolve, reject, arguments_) => {
612
- const queueItem = { reject };
613
- new Promise((internalResolve) => {
614
- queueItem.run = internalResolve;
615
- queue.enqueue(queueItem);
616
- }).then(run.bind(void 0, function_, resolve, arguments_));
617
- if (activeCount < concurrency) resumeNext();
618
- };
619
- const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
620
- enqueue(function_, resolve, reject, arguments_);
621
- });
622
- Object.defineProperties(generator, {
623
- activeCount: { get: () => activeCount },
624
- pendingCount: { get: () => queue.size },
625
- clearQueue: { value() {
626
- if (!rejectOnClear) {
627
- queue.clear();
628
- return;
629
- }
630
- const abortError = AbortSignal.abort().reason;
631
- while (queue.size > 0) queue.dequeue().reject(abortError);
632
- } },
633
- concurrency: {
634
- get: () => concurrency,
635
- set(newConcurrency) {
636
- validateConcurrency(newConcurrency);
637
- concurrency = newConcurrency;
638
- queueMicrotask(() => {
639
- while (activeCount < concurrency && queue.size > 0) resumeNext();
640
- });
641
- }
642
- },
643
- map: { async value(iterable, function_) {
644
- const promises = Array.from(iterable, (value, index) => this(function_, value, index));
645
- return Promise.all(promises);
646
- } }
647
- });
648
- return generator;
649
- }
650
- function validateConcurrency(concurrency) {
651
- if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
652
- }
653
- //#endregion
654
- //#region src/FileProcessor.ts
655
- function joinSources(file) {
656
- return file.sources.map((item) => (0, _kubb_ast.extractStringsFromNodes)(item.nodes)).filter(Boolean).join("\n\n");
657
- }
658
- /**
659
- * Converts a single file to a string using the registered parsers.
660
- * Falls back to joining source values when no matching parser is found.
661
- *
662
- * @internal
663
- */
664
- var FileProcessor = class {
665
- #limit = pLimit(100);
666
- async parse(file, { parsers, extension } = {}) {
667
- const parseExtName = extension?.[file.extname] || void 0;
668
- if (!parsers || !file.extname) return joinSources(file);
669
- const parser = parsers.get(file.extname);
670
- if (!parser) return joinSources(file);
671
- return parser.parse(file, { extname: parseExtName });
672
- }
673
- async run(files, { parsers, mode = "sequential", extension, onStart, onEnd, onUpdate } = {}) {
674
- await onStart?.(files);
675
- const total = files.length;
676
- let processed = 0;
677
- const processOne = async (file) => {
678
- const source = await this.parse(file, {
679
- extension,
680
- parsers
681
- });
682
- const currentProcessed = ++processed;
683
- const percentage = currentProcessed / total * 100;
684
- await onUpdate?.({
685
- file,
686
- source,
687
- processed: currentProcessed,
688
- percentage,
689
- total
690
- });
691
- };
692
- if (mode === "sequential") for (const file of files) await processOne(file);
693
- else await Promise.all(files.map((file) => this.#limit(() => processOne(file))));
694
- await onEnd?.(files);
695
- return files;
696
- }
697
- };
109
+ //#region package.json
110
+ var version = "5.0.0-beta.31";
698
111
  //#endregion
699
112
  //#region src/createStorage.ts
700
113
  /**
701
- * Factory for implementing custom storage backends that control where generated files are written.
702
- *
703
- * Takes a builder function `(options: TOptions) => Storage` and returns a factory `(options?: TOptions) => Storage`.
704
- * Kubb provides filesystem and in-memory implementations out of the box.
705
- *
706
- * @note Call the returned factory with optional options to instantiate the storage adapter.
114
+ * Defines a custom storage backend. The builder receives user options and
115
+ * returns a `Storage` implementation. Kubb ships with filesystem and
116
+ * in-memory storages reach for this when you need to write generated files
117
+ * elsewhere (cloud storage, a database, a remote API).
707
118
  *
708
- * @example
119
+ * @example In-memory storage (the built-in implementation)
709
120
  * ```ts
710
121
  * import { createStorage } from '@kubb/core'
711
122
  *
712
123
  * export const memoryStorage = createStorage(() => {
713
124
  * const store = new Map<string, string>()
125
+ *
714
126
  * return {
715
127
  * name: 'memory',
716
- * async hasItem(key) { return store.has(key) },
717
- * async getItem(key) { return store.get(key) ?? null },
718
- * async setItem(key, value) { store.set(key, value) },
719
- * async removeItem(key) { store.delete(key) },
128
+ * async hasItem(key) {
129
+ * return store.has(key)
130
+ * },
131
+ * async getItem(key) {
132
+ * return store.get(key) ?? null
133
+ * },
134
+ * async setItem(key, value) {
135
+ * store.set(key, value)
136
+ * },
137
+ * async removeItem(key) {
138
+ * store.delete(key)
139
+ * },
720
140
  * async getKeys(base) {
721
141
  * const keys = [...store.keys()]
722
142
  * return base ? keys.filter((k) => k.startsWith(base)) : keys
723
143
  * },
724
- * async clear(base) { if (!base) store.clear() },
144
+ * async clear(base) {
145
+ * if (!base) store.clear()
146
+ * },
725
147
  * }
726
148
  * })
727
- *
728
- * // Instantiate:
729
- * const storage = memoryStorage()
730
149
  * ```
731
150
  */
732
151
  function createStorage(build) {
@@ -735,12 +154,6 @@ function createStorage(build) {
735
154
  //#endregion
736
155
  //#region src/storages/fsStorage.ts
737
156
  /**
738
- * Detects the filesystem error used to indicate that a path does not exist.
739
- */
740
- function isMissingPathError(error) {
741
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
742
- }
743
- /**
744
157
  * Built-in filesystem storage driver.
745
158
  *
746
159
  * This is the default storage when no `storage` option is configured in the root config.
@@ -771,17 +184,15 @@ const fsStorage = createStorage(() => ({
771
184
  try {
772
185
  await (0, node_fs_promises.access)((0, node_path.resolve)(key));
773
186
  return true;
774
- } catch (error) {
775
- if (isMissingPathError(error)) return false;
776
- throw new Error(`Failed to access storage item "${key}"`, { cause: error });
187
+ } catch (_error) {
188
+ return false;
777
189
  }
778
190
  },
779
191
  async getItem(key) {
780
192
  try {
781
193
  return await (0, node_fs_promises.readFile)((0, node_path.resolve)(key), "utf8");
782
- } catch (error) {
783
- if (isMissingPathError(error)) return null;
784
- throw new Error(`Failed to read storage item "${key}"`, { cause: error });
194
+ } catch (_error) {
195
+ return null;
785
196
  }
786
197
  },
787
198
  async setItem(key, value) {
@@ -791,23 +202,22 @@ const fsStorage = createStorage(() => ({
791
202
  await (0, node_fs_promises.rm)((0, node_path.resolve)(key), { force: true });
792
203
  },
793
204
  async getKeys(base) {
794
- const keys = [];
795
205
  const resolvedBase = (0, node_path.resolve)(base ?? process.cwd());
796
- async function walk(dir, prefix) {
206
+ async function* walk(dir, prefix) {
797
207
  let entries;
798
208
  try {
799
209
  entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
800
- } catch (error) {
801
- if (isMissingPathError(error)) return;
802
- throw new Error(`Failed to list storage keys under "${resolvedBase}"`, { cause: error });
210
+ } catch (_error) {
211
+ return;
803
212
  }
804
213
  for (const entry of entries) {
805
214
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
806
- if (entry.isDirectory()) await walk((0, node_path.join)(dir, entry.name), rel);
807
- else keys.push(rel);
215
+ if (entry.isDirectory()) yield* walk((0, node_path.join)(dir, entry.name), rel);
216
+ else yield rel;
808
217
  }
809
218
  }
810
- await walk(resolvedBase, "");
219
+ const keys = [];
220
+ for await (const key of walk(resolvedBase, "")) keys.push(key);
811
221
  return keys;
812
222
  },
813
223
  async clear(base) {
@@ -816,475 +226,272 @@ const fsStorage = createStorage(() => ({
816
226
  }
817
227
  }));
818
228
  //#endregion
819
- //#region package.json
820
- var version = "5.0.0-beta.3";
821
- //#endregion
822
- //#region src/utils/diagnostics.ts
229
+ //#region src/createKubb.ts
823
230
  /**
824
- * Returns a snapshot of the current runtime environment.
825
- *
826
- * Useful for attaching context to debug logs and error reports so that
827
- * issues can be reproduced without manual information gathering.
231
+ * Builds a `Storage` view scoped to the file paths produced by the current build.
232
+ * Reads delegate to the underlying `storage` so source bytes stay where they were
233
+ * written; writes register the key so subsequent reads and `getKeys` are scoped
234
+ * to this build's output.
828
235
  */
829
- function getDiagnosticInfo() {
830
- return {
831
- nodeVersion: node_process.version,
832
- KubbVersion: version,
833
- platform: process.platform,
834
- arch: process.arch,
835
- cwd: process.cwd()
836
- };
837
- }
838
- //#endregion
839
- //#region src/utils/isInputPath.ts
840
- function isInputPath(config) {
841
- return typeof config?.input === "object" && config.input !== null && "path" in config.input;
842
- }
843
- //#endregion
844
- //#region src/createKubb.ts
845
- async function setup(userConfig, options = {}) {
846
- const hooks = options.hooks ?? new AsyncEventEmitter();
847
- const sources = /* @__PURE__ */ new Map();
848
- const diagnosticInfo = getDiagnosticInfo();
849
- if (Array.isArray(userConfig.input)) await hooks.emit("kubb:warn", { message: "This feature is still under development — use with caution" });
850
- await hooks.emit("kubb:debug", {
851
- date: /* @__PURE__ */ new Date(),
852
- logs: [
853
- "Configuration:",
854
- ` • Name: ${userConfig.name || "unnamed"}`,
855
- ` • Root: ${userConfig.root || process.cwd()}`,
856
- ` • Output: ${userConfig.output?.path || "not specified"}`,
857
- ` • Plugins: ${userConfig.plugins?.length || 0}`,
858
- "Output Settings:",
859
- ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
860
- ` • Formatter: ${userConfig.output?.format || "none"}`,
861
- ` • Linter: ${userConfig.output?.lint || "none"}`,
862
- "Environment:",
863
- Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
864
- ]
865
- });
866
- try {
867
- if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
868
- await exists(userConfig.input.path);
869
- await hooks.emit("kubb:debug", {
870
- date: /* @__PURE__ */ new Date(),
871
- logs: [`✓ Input file validated: ${userConfig.input.path}`]
872
- });
873
- }
874
- } catch (caughtError) {
875
- if (isInputPath(userConfig)) {
876
- const error = caughtError;
877
- 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 });
236
+ function createSourcesView(storage) {
237
+ const paths = /* @__PURE__ */ new Set();
238
+ return createStorage(() => ({
239
+ name: `${storage.name}:sources`,
240
+ async hasItem(key) {
241
+ return paths.has(key) && await storage.hasItem(key);
242
+ },
243
+ async getItem(key) {
244
+ return paths.has(key) ? storage.getItem(key) : null;
245
+ },
246
+ async setItem(key, value) {
247
+ paths.add(key);
248
+ await storage.setItem(key, value);
249
+ },
250
+ async removeItem(key) {
251
+ paths.delete(key);
252
+ await storage.removeItem(key);
253
+ },
254
+ async getKeys(base) {
255
+ if (!base) return [...paths];
256
+ const result = [];
257
+ for (const key of paths) if (key.startsWith(base)) result.push(key);
258
+ return result;
259
+ },
260
+ async clear() {
261
+ paths.clear();
262
+ await storage.clear();
878
263
  }
879
- }
880
- if (!userConfig.adapter) throw new Error("Adapter should be defined");
881
- const config = {
264
+ }))();
265
+ }
266
+ function resolveConfig(userConfig) {
267
+ return {
882
268
  ...userConfig,
883
269
  root: userConfig.root || process.cwd(),
884
270
  parsers: userConfig.parsers ?? [],
885
- adapter: userConfig.adapter,
886
271
  output: {
887
272
  format: false,
888
273
  lint: false,
889
- write: true,
890
- extension: require_PluginDriver.DEFAULT_EXTENSION,
891
- defaultBanner: require_PluginDriver.DEFAULT_BANNER,
274
+ extension: require_KubbDriver.DEFAULT_EXTENSION,
275
+ defaultBanner: require_KubbDriver.DEFAULT_BANNER,
892
276
  ...userConfig.output
893
277
  },
278
+ storage: userConfig.storage ?? fsStorage(),
894
279
  devtools: userConfig.devtools ? {
895
- studioUrl: require_PluginDriver.DEFAULT_STUDIO_URL,
280
+ studioUrl: require_KubbDriver.DEFAULT_STUDIO_URL,
896
281
  ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
897
282
  } : void 0,
898
- plugins: userConfig.plugins
283
+ plugins: userConfig.plugins ?? []
899
284
  };
900
- const storage = config.output.write === false ? null : config.storage ?? fsStorage();
901
- if (config.output.clean) {
902
- await hooks.emit("kubb:debug", {
903
- date: /* @__PURE__ */ new Date(),
904
- logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
905
- });
906
- await storage?.clear((0, node_path.resolve)(config.root, config.output.path));
907
- }
908
- const driver = new require_PluginDriver.PluginDriver(config, { hooks });
909
- function registerMiddlewareHook(event, middlewareHooks) {
910
- const handler = middlewareHooks[event];
911
- if (handler) hooks.on(event, handler);
912
- }
913
- for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
914
- const adapter = config.adapter;
915
- if (!adapter) throw new Error("No adapter configured. Please provide an adapter in your kubb.config.ts.");
916
- const source = inputToAdapterSource(config);
917
- await hooks.emit("kubb:debug", {
918
- date: /* @__PURE__ */ new Date(),
919
- logs: [`Running adapter: ${adapter.name}`]
920
- });
921
- driver.adapter = adapter;
922
- driver.inputNode = await adapter.parse(source);
923
- await hooks.emit("kubb:debug", {
924
- date: /* @__PURE__ */ new Date(),
925
- logs: [
926
- `✓ Adapter '${adapter.name}' resolved InputNode`,
927
- ` • Schemas: ${driver.inputNode.schemas.length}`,
928
- ` • Operations: ${driver.inputNode.operations.length}`
929
- ]
930
- });
285
+ }
286
+ /**
287
+ * Returns a snapshot of the current runtime environment.
288
+ *
289
+ * Useful for attaching context to debug logs and error reports so that
290
+ * issues can be reproduced without manual information gathering.
291
+ */
292
+ function getDiagnosticInfo() {
931
293
  return {
932
- config,
933
- hooks,
934
- driver,
935
- sources,
936
- storage
294
+ nodeVersion: node_process.version,
295
+ KubbVersion: version,
296
+ platform: process.platform,
297
+ arch: process.arch,
298
+ cwd: process.cwd()
937
299
  };
938
300
  }
301
+ function isInputPath(config) {
302
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
303
+ }
939
304
  /**
940
- * Walks the AST and dispatches nodes to a plugin's direct AST hooks
941
- * (`schema`, `operation`, `operations`).
305
+ * Kubb code-generation instance bound to a single config entry. Resolves the user
306
+ * config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
307
+ * the `setup → build` lifecycle.
308
+ *
309
+ * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
942
310
  *
943
- * When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
944
- * `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
945
- * of top-level schema names transitively reachable from the included operations and skips
946
- * schemas that fall outside that set. This ensures that component schemas referenced
947
- * exclusively by excluded operations are not generated.
311
+ * @example
312
+ * ```ts
313
+ * const kubb = createKubb(userConfig)
314
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
315
+ * const { files, failedPlugins } = await kubb.safeBuild()
316
+ * ```
948
317
  */
949
- async function runPluginAstHooks(plugin, context) {
950
- const { adapter, inputNode, resolver, driver } = context;
951
- const { exclude, include, override } = plugin.options;
952
- 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.`);
953
- function resolveRenderer(gen) {
954
- return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
318
+ var Kubb = class {
319
+ hooks;
320
+ #userConfig;
321
+ #config = null;
322
+ #driver = null;
323
+ #storage = null;
324
+ constructor(userConfig, options = {}) {
325
+ this.#userConfig = userConfig;
326
+ this.hooks = options.hooks ?? new require_KubbDriver.AsyncEventEmitter();
327
+ }
328
+ get storage() {
329
+ if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
330
+ return this.#storage;
331
+ }
332
+ get driver() {
333
+ if (!this.#driver) throw new Error("[kubb] setup() must be called before accessing driver");
334
+ return this.#driver;
335
+ }
336
+ get config() {
337
+ if (!this.#config) throw new Error("[kubb] setup() must be called before accessing config");
338
+ return this.#config;
955
339
  }
956
- const generators = plugin.generators ?? [];
957
- const collectedOperations = [];
958
- const generatorContext = {
959
- ...context,
960
- resolver: driver.getResolver(plugin.name)
961
- };
962
- const operationFilterTypes = new Set([
963
- "tag",
964
- "operationId",
965
- "path",
966
- "method",
967
- "contentType"
968
- ]);
969
- const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false;
970
- const hasSchemaNameIncludes = include?.some(({ type }) => type === "schemaName") ?? false;
971
- let allowedSchemaNames;
972
- if (hasOperationBasedIncludes && !hasSchemaNameIncludes) allowedSchemaNames = (0, _kubb_ast.collectUsedSchemaNames)(inputNode.operations.filter((op) => resolver.resolveOptions(op, {
973
- options: plugin.options,
974
- exclude,
975
- include,
976
- override
977
- }) !== null), inputNode.schemas);
978
- await (0, _kubb_ast.walk)(inputNode, {
979
- depth: "shallow",
980
- async schema(node) {
981
- const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
982
- if (allowedSchemaNames !== void 0 && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) return;
983
- const options = resolver.resolveOptions(transformedNode, {
984
- options: plugin.options,
985
- exclude,
986
- include,
987
- override
340
+ /**
341
+ * Resolves config and initializes the driver. `build()` calls this automatically.
342
+ */
343
+ async setup() {
344
+ const config = resolveConfig(this.#userConfig);
345
+ const driver = new require_KubbDriver.KubbDriver(config, { hooks: this.hooks });
346
+ const storage = createSourcesView(config.storage);
347
+ await this.hooks.emit("kubb:debug", {
348
+ date: /* @__PURE__ */ new Date(),
349
+ logs: this.#configLogs(config)
350
+ });
351
+ if (isInputPath(this.#userConfig) && !new require_KubbDriver.URLPath(this.#userConfig.input.path).isURL) try {
352
+ await exists(this.#userConfig.input.path);
353
+ await this.hooks.emit("kubb:debug", {
354
+ date: /* @__PURE__ */ new Date(),
355
+ logs: [`✓ Input file validated: ${this.#userConfig.input.path}`]
988
356
  });
989
- if (options === null) return;
990
- const ctx = {
991
- ...generatorContext,
992
- options
993
- };
994
- for (const gen of generators) {
995
- if (!gen.schema) continue;
996
- await require_PluginDriver.applyHookResult(await gen.schema(transformedNode, ctx), driver, resolveRenderer(gen));
997
- }
998
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
999
- },
1000
- async operation(node) {
1001
- const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1002
- const options = resolver.resolveOptions(transformedNode, {
1003
- options: plugin.options,
1004
- exclude,
1005
- include,
1006
- override
357
+ } catch (caughtError) {
358
+ throw new Error(`Cannot read file/URL defined in \`input.path\` or set with \`kubb generate PATH\` in the CLI of your Kubb config ${this.#userConfig.input.path}`, { cause: caughtError });
359
+ }
360
+ if (config.output.clean) {
361
+ await this.hooks.emit("kubb:debug", {
362
+ date: /* @__PURE__ */ new Date(),
363
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1007
364
  });
1008
- if (options !== null) {
1009
- collectedOperations.push(transformedNode);
1010
- const ctx = {
1011
- ...generatorContext,
1012
- options
1013
- };
1014
- for (const gen of generators) {
1015
- if (!gen.operation) continue;
1016
- await require_PluginDriver.applyHookResult(await gen.operation(transformedNode, ctx), driver, resolveRenderer(gen));
1017
- }
1018
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1019
- }
365
+ await config.storage.clear((0, node_path.resolve)(config.root, config.output.path));
1020
366
  }
1021
- });
1022
- if (collectedOperations.length > 0) {
1023
- const ctx = {
1024
- ...generatorContext,
1025
- options: plugin.options
1026
- };
1027
- for (const gen of generators) {
1028
- if (!gen.operations) continue;
1029
- await require_PluginDriver.applyHookResult(await gen.operations(collectedOperations, ctx), driver, resolveRenderer(gen));
367
+ await driver.setup();
368
+ this.#config = config;
369
+ this.#driver = driver;
370
+ this.#storage = storage;
371
+ }
372
+ /**
373
+ * Runs the full pipeline and throws on any plugin error.
374
+ * Automatically calls `setup()` if needed.
375
+ */
376
+ async build() {
377
+ const out = await this.safeBuild();
378
+ if (out.error) throw out.error;
379
+ if (out.failedPlugins.size > 0) {
380
+ const errors = [...out.failedPlugins].map(({ error }) => error);
381
+ throw new require_KubbDriver.BuildError(`Build Error with ${out.failedPlugins.size} failed plugins`, { errors });
1030
382
  }
1031
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
383
+ return out;
1032
384
  }
1033
- }
1034
- async function safeBuild(setupResult) {
1035
- const { driver, hooks, sources, storage } = setupResult;
1036
- const failedPlugins = /* @__PURE__ */ new Set();
1037
- const pluginTimings = /* @__PURE__ */ new Map();
1038
- const config = driver.config;
1039
- try {
1040
- await driver.emitSetupHooks();
1041
- if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
1042
- config,
1043
- adapter: driver.adapter,
1044
- inputNode: driver.inputNode,
1045
- getPlugin: driver.getPlugin.bind(driver),
1046
- get files() {
1047
- return driver.fileManager.files;
1048
- },
1049
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1050
- });
1051
- for (const plugin of driver.plugins.values()) {
1052
- const context = driver.getContext(plugin);
1053
- const hrStart = process.hrtime();
1054
- try {
1055
- const timestamp = /* @__PURE__ */ new Date();
1056
- await hooks.emit("kubb:plugin:start", { plugin });
1057
- await hooks.emit("kubb:debug", {
1058
- date: timestamp,
1059
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1060
- });
1061
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
1062
- const duration = getElapsedMs(hrStart);
1063
- pluginTimings.set(plugin.name, duration);
1064
- await hooks.emit("kubb:plugin:end", {
1065
- plugin,
1066
- duration,
1067
- success: true,
1068
- config,
1069
- get files() {
1070
- return driver.fileManager.files;
1071
- },
1072
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1073
- });
1074
- await hooks.emit("kubb:debug", {
1075
- date: /* @__PURE__ */ new Date(),
1076
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1077
- });
1078
- } catch (caughtError) {
1079
- const error = caughtError;
1080
- const errorTimestamp = /* @__PURE__ */ new Date();
1081
- const duration = getElapsedMs(hrStart);
1082
- await hooks.emit("kubb:plugin:end", {
1083
- plugin,
1084
- duration,
1085
- success: false,
1086
- error,
1087
- config,
1088
- get files() {
1089
- return driver.fileManager.files;
1090
- },
1091
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1092
- });
1093
- await hooks.emit("kubb:debug", {
1094
- date: errorTimestamp,
1095
- logs: [
1096
- "✗ Plugin start failed",
1097
- ` • Plugin Name: ${plugin.name}`,
1098
- ` • Error: ${error.constructor.name} - ${error.message}`,
1099
- " • Stack Trace:",
1100
- error.stack || "No stack trace available"
1101
- ]
1102
- });
1103
- failedPlugins.add({
1104
- plugin,
1105
- error
1106
- });
1107
- }
385
+ /**
386
+ * Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
387
+ * Automatically calls `setup()` if needed.
388
+ */
389
+ async safeBuild() {
390
+ try {
391
+ var _usingCtx$1 = require_KubbDriver._usingCtx();
392
+ if (!this.#driver) await this.setup();
393
+ const cleanup = _usingCtx$1.u(this);
394
+ const driver = cleanup.driver;
395
+ const storage = cleanup.storage;
396
+ const { failedPlugins, pluginTimings, error } = await driver.run({ storage });
397
+ return {
398
+ failedPlugins,
399
+ files: driver.fileManager.files,
400
+ driver,
401
+ pluginTimings,
402
+ storage,
403
+ ...error ? { error } : {}
404
+ };
405
+ } catch (_) {
406
+ _usingCtx$1.e = _;
407
+ } finally {
408
+ _usingCtx$1.d();
1108
409
  }
1109
- await hooks.emit("kubb:plugins:end", {
1110
- config,
1111
- get files() {
1112
- return driver.fileManager.files;
1113
- },
1114
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1115
- });
1116
- const files = driver.fileManager.files;
1117
- const parsersMap = /* @__PURE__ */ new Map();
1118
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1119
- const fileProcessor = new FileProcessor();
1120
- await hooks.emit("kubb:debug", {
1121
- date: /* @__PURE__ */ new Date(),
1122
- logs: [`Writing ${files.length} files...`]
1123
- });
1124
- await fileProcessor.run(files, {
1125
- parsers: parsersMap,
1126
- extension: config.output.extension,
1127
- onStart: async (processingFiles) => {
1128
- await hooks.emit("kubb:files:processing:start", { files: processingFiles });
1129
- },
1130
- onUpdate: async ({ file, source, processed, total, percentage }) => {
1131
- await hooks.emit("kubb:file:processing:update", {
1132
- file,
1133
- source,
1134
- processed,
1135
- total,
1136
- percentage,
1137
- config
1138
- });
1139
- if (source) {
1140
- await storage?.setItem(file.path, source);
1141
- sources.set(file.path, source);
1142
- }
1143
- },
1144
- onEnd: async (processedFiles) => {
1145
- await hooks.emit("kubb:files:processing:end", { files: processedFiles });
1146
- await hooks.emit("kubb:debug", {
1147
- date: /* @__PURE__ */ new Date(),
1148
- logs: [`✓ File write process completed for ${processedFiles.length} files`]
1149
- });
1150
- }
1151
- });
1152
- await hooks.emit("kubb:build:end", {
1153
- files,
1154
- config,
1155
- outputDir: (0, node_path.resolve)(config.root, config.output.path)
1156
- });
1157
- return {
1158
- failedPlugins,
1159
- files,
1160
- driver,
1161
- pluginTimings,
1162
- sources
1163
- };
1164
- } catch (error) {
1165
- return {
1166
- failedPlugins,
1167
- files: [],
1168
- driver,
1169
- pluginTimings,
1170
- error,
1171
- sources
1172
- };
1173
- } finally {
1174
- driver.dispose();
1175
410
  }
1176
- }
1177
- async function build(setupResult) {
1178
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
1179
- if (error) throw error;
1180
- if (failedPlugins.size > 0) {
1181
- const errors = [...failedPlugins].map(({ error }) => error);
1182
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
411
+ dispose() {
412
+ this.#driver?.dispose();
1183
413
  }
1184
- return {
1185
- failedPlugins,
1186
- files,
1187
- driver,
1188
- pluginTimings,
1189
- error: void 0,
1190
- sources
1191
- };
1192
- }
1193
- function inputToAdapterSource(config) {
1194
- if (Array.isArray(config.input)) return {
1195
- type: "paths",
1196
- paths: config.input.map((i) => new URLPath(i.path).isURL ? i.path : (0, node_path.resolve)(config.root, i.path))
1197
- };
1198
- if ("data" in config.input) return {
1199
- type: "data",
1200
- data: config.input.data
1201
- };
1202
- if (new URLPath(config.input.path).isURL) return {
1203
- type: "path",
1204
- path: config.input.path
1205
- };
1206
- return {
1207
- type: "path",
1208
- path: (0, node_path.resolve)(config.root, config.input.path)
1209
- };
1210
- }
414
+ [Symbol.dispose]() {
415
+ this.dispose();
416
+ }
417
+ #configLogs(config) {
418
+ const u = this.#userConfig;
419
+ const diag = getDiagnosticInfo();
420
+ return [
421
+ "Configuration:",
422
+ ` • Name: ${u.name || "unnamed"}`,
423
+ ` • Root: ${u.root || process.cwd()}`,
424
+ ` • Output: ${u.output?.path || "not specified"}`,
425
+ ` • Plugins: ${u.plugins?.length || 0}`,
426
+ "Output Settings:",
427
+ ` • Storage: ${config.storage.name}`,
428
+ ` • Formatter: ${u.output?.format || "none"}`,
429
+ ` • Linter: ${u.output?.lint || "none"}`,
430
+ `Running adapter: ${config.adapter?.name || "none"}`,
431
+ "Environment:",
432
+ Object.entries(diag).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
433
+ ];
434
+ }
435
+ };
1211
436
  /**
1212
- * Creates a Kubb instance bound to a single config entry.
1213
- *
1214
- * Accepts a user-facing config shape and resolves it to a full {@link Config} during
1215
- * `setup()`. The instance then holds shared state (`hooks`, `sources`, `driver`, `config`)
1216
- * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1217
- * calling `setup()` or `build()`.
437
+ * Constructs a {@link Kubb} build orchestrator from a user config. Equivalent
438
+ * to `new Kubb(userConfig, options)` and the canonical public entry point.
1218
439
  *
1219
440
  * @example
1220
441
  * ```ts
1221
- * const kubb = createKubb(userConfig)
442
+ * import { createKubb } from '@kubb/core'
443
+ * import { adapterOas } from '@kubb/adapter-oas'
444
+ * import { pluginTs } from '@kubb/plugin-ts'
1222
445
  *
1223
- * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1224
- * console.log(`${plugin.name} completed in ${duration}ms`)
446
+ * const kubb = createKubb({
447
+ * input: { path: './petStore.yaml' },
448
+ * output: { path: './src/gen' },
449
+ * adapter: adapterOas(),
450
+ * plugins: [pluginTs()],
1225
451
  * })
1226
452
  *
1227
- * const { files, failedPlugins } = await kubb.safeBuild()
453
+ * await kubb.build()
1228
454
  * ```
1229
455
  */
1230
456
  function createKubb(userConfig, options = {}) {
1231
- const hooks = options.hooks ?? new AsyncEventEmitter();
1232
- let setupResult;
1233
- const instance = {
1234
- get hooks() {
1235
- return hooks;
1236
- },
1237
- get sources() {
1238
- return setupResult?.sources ?? /* @__PURE__ */ new Map();
1239
- },
1240
- get driver() {
1241
- return setupResult?.driver;
1242
- },
1243
- get config() {
1244
- return setupResult?.config;
1245
- },
1246
- async setup() {
1247
- setupResult = await setup(userConfig, { hooks });
1248
- },
1249
- async build() {
1250
- if (!setupResult) await instance.setup();
1251
- return build(setupResult);
1252
- },
1253
- async safeBuild() {
1254
- if (!setupResult) await instance.setup();
1255
- return safeBuild(setupResult);
1256
- }
1257
- };
1258
- return instance;
457
+ return new Kubb(userConfig, options);
1259
458
  }
1260
459
  //#endregion
1261
460
  //#region src/createRenderer.ts
1262
461
  /**
1263
- * Creates a renderer factory for use in generator definitions.
462
+ * Defines a renderer factory. Renderers turn the generator's return value
463
+ * (JSX, a template string, a tree of any shape) into `FileNode`s that get
464
+ * written to disk.
1264
465
  *
1265
- * Wrap your renderer factory function with this helper to register it as the
1266
- * renderer for a generator. Core will call this factory once per render cycle
1267
- * to obtain a fresh renderer instance.
466
+ * Use this to support output formats beyond JSX for instance, a Handlebars
467
+ * renderer, a string-template renderer, or a renderer that writes binary
468
+ * files. Plugins and generators pick the renderer to use via the `renderer`
469
+ * field on `defineGenerator`.
1268
470
  *
1269
- * @example
471
+ * @example A minimal renderer that wraps a custom runtime
1270
472
  * ```ts
1271
- * // packages/renderer-jsx/src/index.ts
1272
- * export const jsxRenderer = createRenderer(() => {
1273
- * const runtime = new Runtime()
473
+ * import { createRenderer } from '@kubb/core'
474
+ *
475
+ * export const myRenderer = createRenderer(() => {
476
+ * const runtime = new MyRuntime()
1274
477
  * return {
1275
- * async render(element) { await runtime.render(element) },
1276
- * get files() { return runtime.nodes },
1277
- * unmount(error) { runtime.unmount(error) },
478
+ * async render(element) {
479
+ * await runtime.render(element)
480
+ * },
481
+ * get files() {
482
+ * return runtime.files
483
+ * },
484
+ * dispose() {
485
+ * runtime.dispose()
486
+ * },
487
+ * unmount(error) {
488
+ * runtime.dispose(error)
489
+ * },
490
+ * [Symbol.dispose]() {
491
+ * this.dispose()
492
+ * },
1278
493
  * }
1279
494
  * })
1280
- *
1281
- * // packages/plugin-zod/src/generators/zodGenerator.tsx
1282
- * import { jsxRenderer } from '@kubb/renderer-jsx'
1283
- * export const zodGenerator = defineGenerator<PluginZod>({
1284
- * name: 'zod',
1285
- * renderer: jsxRenderer,
1286
- * schema(node, options) { return <File ...>...</File> },
1287
- * })
1288
495
  * ```
1289
496
  */
1290
497
  function createRenderer(factory) {
@@ -1293,9 +500,32 @@ function createRenderer(factory) {
1293
500
  //#endregion
1294
501
  //#region src/defineGenerator.ts
1295
502
  /**
1296
- * Defines a generator. Returns the object as-is with correct `this` typings.
1297
- * `applyHookResult` handles renderer elements and `File[]` uniformly using
1298
- * the generator's declared `renderer` factory.
503
+ * Defines a generator: a unit of work that runs during the plugin's AST walk
504
+ * and produces files. Plugins register generators via `ctx.addGenerator()`
505
+ * inside `kubb:plugin:setup`.
506
+ *
507
+ * The returned object is the input as-is, but with `this` types preserved so
508
+ * `schema`/`operation`/`operations` methods are correctly typed against the
509
+ * plugin's `PluginFactoryOptions`. Renderer elements and `FileNode[]` returns
510
+ * are both handled by the runtime — pick whichever style fits.
511
+ *
512
+ * @example JSX-based schema generator
513
+ * ```tsx
514
+ * import { defineGenerator } from '@kubb/core'
515
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
516
+ *
517
+ * export const typeGenerator = defineGenerator({
518
+ * name: 'typescript',
519
+ * renderer: jsxRenderer,
520
+ * schema(node, ctx) {
521
+ * return (
522
+ * <File path={`${ctx.root}/${node.name}.ts`}>
523
+ * <Type node={node} resolver={ctx.resolver} />
524
+ * </File>
525
+ * )
526
+ * },
527
+ * })
528
+ * ```
1299
529
  */
1300
530
  function defineGenerator(generator) {
1301
531
  return generator;
@@ -1303,15 +533,33 @@ function defineGenerator(generator) {
1303
533
  //#endregion
1304
534
  //#region src/defineLogger.ts
1305
535
  /**
1306
- * Wraps a logger definition into a typed {@link Logger}.
536
+ * Defines a typed logger. Use the second type parameter to declare a return
537
+ * value from `install`, which is handy when the logger exposes a sink factory
538
+ * or cleanup callback to the caller.
1307
539
  *
1308
- * @example
540
+ * @example Basic logger
1309
541
  * ```ts
542
+ * import { defineLogger } from '@kubb/core'
543
+ *
1310
544
  * export const myLogger = defineLogger({
1311
545
  * name: 'my-logger',
1312
- * install(context, options) {
1313
- * context.on('kubb:info', (message) => console.log('ℹ', message))
1314
- * context.on('kubb:error', (error) => console.error('✗', error.message))
546
+ * install(context) {
547
+ * context.on('kubb:info', ({ message }) => console.log('ℹ', message))
548
+ * context.on('kubb:error', ({ error }) => console.error('✗', error.message))
549
+ * },
550
+ * })
551
+ * ```
552
+ *
553
+ * @example Logger that returns a hook sink factory
554
+ * ```ts
555
+ * import { defineLogger, type LoggerOptions } from '@kubb/core'
556
+ * import type { HookSinkFactory } from './sinks'
557
+ *
558
+ * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
559
+ * name: 'my-logger',
560
+ * install(context) {
561
+ * // … register event handlers …
562
+ * return () => ({ onStdout: console.log })
1315
563
  * },
1316
564
  * })
1317
565
  * ```
@@ -1322,18 +570,17 @@ function defineLogger(logger) {
1322
570
  //#endregion
1323
571
  //#region src/defineMiddleware.ts
1324
572
  /**
1325
- * Creates a middleware factory using the hook-style `hooks` API.
573
+ * Creates a middleware factory. Middleware fires after every plugin handler
574
+ * for the same event, which makes it the natural place for post-processing
575
+ * (barrel files, lint runs, audit logs).
1326
576
  *
1327
- * Middleware handlers fire after all plugin handlers for any given event, making them ideal for post-processing, logging, and auditing.
1328
- * Per-build state (such as accumulators) belongs inside the factory closure so each `createKubb` invocation gets its own isolated instance.
577
+ * Per-build state belongs inside the factory closure so each `createKubb`
578
+ * invocation gets its own isolated instance.
1329
579
  *
1330
- * @note The factory can accept typed options. See examples for using options and per-build state patterns.
1331
- *
1332
- * @example
580
+ * @example Stateless middleware
1333
581
  * ```ts
1334
582
  * import { defineMiddleware } from '@kubb/core'
1335
583
  *
1336
- * // Stateless middleware
1337
584
  * export const logMiddleware = defineMiddleware(() => ({
1338
585
  * name: 'log-middleware',
1339
586
  * hooks: {
@@ -1342,8 +589,12 @@ function defineLogger(logger) {
1342
589
  * },
1343
590
  * },
1344
591
  * }))
592
+ * ```
593
+ *
594
+ * @example Middleware with options and per-build state
595
+ * ```ts
596
+ * import { defineMiddleware } from '@kubb/core'
1345
597
  *
1346
- * // Middleware with options and per-build state
1347
598
  * export const prefixMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
1348
599
  * const seen = new Set<string>()
1349
600
  * return {
@@ -1363,20 +614,23 @@ function defineMiddleware(factory) {
1363
614
  //#endregion
1364
615
  //#region src/defineParser.ts
1365
616
  /**
1366
- * Defines a parser with type safety. Creates parsers that transform generated files to strings based on their extension.
1367
- *
1368
- * @note Call the returned factory with optional options to instantiate the parser.
617
+ * Defines a parser with type-safe `this`. Used to register handlers for new
618
+ * file extensions or to plug a non-TypeScript output into the build.
1369
619
  *
1370
620
  * @example
1371
621
  * ```ts
1372
- * import { defineParser } from '@kubb/core'
622
+ * import { defineParser, ast } from '@kubb/core'
1373
623
  *
1374
624
  * export const jsonParser = defineParser({
1375
625
  * name: 'json',
1376
626
  * extNames: ['.json'],
1377
627
  * parse(file) {
1378
- * const { extractStringsFromNodes } = await import('@kubb/ast')
1379
- * return file.sources.map((s) => extractStringsFromNodes(s.nodes ?? [])).join('\n')
628
+ * return file.sources
629
+ * .map((source) => ast.extractStringsFromNodes(source.nodes ?? []))
630
+ * .join('\n')
631
+ * },
632
+ * print(...nodes) {
633
+ * return nodes.map(String).join('\n')
1380
634
  * },
1381
635
  * })
1382
636
  * ```
@@ -1385,34 +639,6 @@ function defineParser(parser) {
1385
639
  return parser;
1386
640
  }
1387
641
  //#endregion
1388
- //#region src/definePlugin.ts
1389
- /**
1390
- * Wraps a factory function and returns a typed `Plugin` with lifecycle handlers grouped under `hooks`.
1391
- *
1392
- * Handlers live in a single `hooks` object (inspired by Astro integrations).
1393
- * All lifecycle events from `KubbHooks` are available for subscription.
1394
- *
1395
- * @note For real plugins, use a `PluginFactoryOptions` type parameter to get type-safe context in `kubb:plugin:setup`.
1396
- * Plugin names should follow the convention `plugin-<feature>` (e.g., `plugin-react-query`, `plugin-zod`).
1397
- *
1398
- * @example
1399
- * ```ts
1400
- * import { definePlugin } from '@kubb/core'
1401
- *
1402
- * export const pluginTs = definePlugin((options: { prefix?: string } = {}) => ({
1403
- * name: 'plugin-ts',
1404
- * hooks: {
1405
- * 'kubb:plugin:setup'(ctx) {
1406
- * ctx.setResolver(resolverTs)
1407
- * },
1408
- * },
1409
- * }))
1410
- * ```
1411
- */
1412
- function definePlugin(factory) {
1413
- return (options) => factory(options ?? {});
1414
- }
1415
- //#endregion
1416
642
  //#region src/storages/memoryStorage.ts
1417
643
  /**
1418
644
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -1463,11 +689,11 @@ const memoryStorage = createStorage(() => {
1463
689
  };
1464
690
  });
1465
691
  //#endregion
1466
- exports.AsyncEventEmitter = AsyncEventEmitter;
1467
- exports.FileManager = require_PluginDriver.FileManager;
1468
- exports.FileProcessor = FileProcessor;
1469
- exports.PluginDriver = require_PluginDriver.PluginDriver;
1470
- exports.URLPath = URLPath;
692
+ exports.AsyncEventEmitter = require_KubbDriver.AsyncEventEmitter;
693
+ exports.FileManager = require_KubbDriver.FileManager;
694
+ exports.FileProcessor = require_KubbDriver.FileProcessor;
695
+ exports.KubbDriver = require_KubbDriver.KubbDriver;
696
+ exports.URLPath = require_KubbDriver.URLPath;
1471
697
  Object.defineProperty(exports, "ast", {
1472
698
  enumerable: true,
1473
699
  get: function() {
@@ -1482,11 +708,11 @@ exports.defineGenerator = defineGenerator;
1482
708
  exports.defineLogger = defineLogger;
1483
709
  exports.defineMiddleware = defineMiddleware;
1484
710
  exports.defineParser = defineParser;
1485
- exports.definePlugin = definePlugin;
1486
- exports.defineResolver = require_PluginDriver.defineResolver;
711
+ exports.definePlugin = require_KubbDriver.definePlugin;
712
+ exports.defineResolver = require_KubbDriver.defineResolver;
1487
713
  exports.fsStorage = fsStorage;
1488
714
  exports.isInputPath = isInputPath;
1489
- exports.logLevel = require_PluginDriver.logLevel;
715
+ exports.logLevel = require_KubbDriver.logLevel;
1490
716
  exports.memoryStorage = memoryStorage;
1491
717
 
1492
718
  //# sourceMappingURL=index.cjs.map