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

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.js CHANGED
@@ -1,184 +1,9 @@
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";
3
- import { EventEmitter } from "node:events";
1
+ import "./chunk--u3MIqq1.js";
2
+ import { a as FileManager, c as DEFAULT_BANNER, d as logLevel, f as URLPath, i as FileProcessor, l as DEFAULT_EXTENSION, m as BuildError, o as defineResolver, p as AsyncEventEmitter, r as _usingCtx, s as definePlugin, t as KubbDriver, u as DEFAULT_STUDIO_URL } from "./KubbDriver-CFx2DdhF.js";
4
3
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
4
  import { dirname, join, resolve } from "node:path";
6
5
  import * as ast from "@kubb/ast";
7
- import { collectUsedSchemaNames, extractStringsFromNodes, transform, walk } from "@kubb/ast";
8
6
  import { version } from "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 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
7
  //#region ../../internals/utils/src/fs.ts
183
8
  /**
184
9
  * Resolves to `true` when the file or directory at `path` exists.
@@ -245,491 +70,81 @@ async function clean(path) {
245
70
  });
246
71
  }
247
72
  //#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
73
  //#region src/createAdapter.ts
498
74
  /**
499
- * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
75
+ * Defines a custom adapter that translates a spec format into Kubb's universal
76
+ * AST. Use this when you need to consume GraphQL, gRPC, AsyncAPI, or another
77
+ * domain-specific schema. Built-in adapters: `@kubb/adapter-oas` for
78
+ * OpenAPI/Swagger documents.
500
79
  *
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.
80
+ * Adapters must return an `InputNode` from `parse`. That node is what every
81
+ * plugin in the build consumes.
505
82
  *
506
83
  * @example
507
84
  * ```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
- * })
85
+ * import { createAdapter, ast, type AdapterFactoryOptions } from '@kubb/core'
86
+ *
87
+ * type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
518
88
  *
519
- * // Instantiate:
520
- * const adapter = myAdapter({ validate: true })
89
+ * export const myAdapter = createAdapter<MyAdapter>((options) => ({
90
+ * name: 'my-adapter',
91
+ * options,
92
+ * document: null,
93
+ * async parse(_source) {
94
+ * // Convert `source` (path or inline data) into an InputNode.
95
+ * return ast.createInput()
96
+ * },
97
+ * getImports: () => [],
98
+ * async validate() {
99
+ * // Throw or call ctx.error here when the spec is invalid.
100
+ * },
101
+ * }))
521
102
  * ```
522
103
  */
523
104
  function createAdapter(build) {
524
105
  return (options) => build(options ?? {});
525
106
  }
526
107
  //#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
- };
108
+ //#region package.json
109
+ var version$1 = "5.0.0-beta.30";
701
110
  //#endregion
702
111
  //#region src/createStorage.ts
703
112
  /**
704
- * Factory for implementing custom storage backends that control where generated files are written.
705
- *
706
- * Takes a builder function `(options: TOptions) => Storage` and returns a factory `(options?: TOptions) => Storage`.
707
- * Kubb provides filesystem and in-memory implementations out of the box.
113
+ * Defines a custom storage backend. The builder receives user options and
114
+ * returns a `Storage` implementation. Kubb ships with filesystem and
115
+ * in-memory storages reach for this when you need to write generated files
116
+ * elsewhere (cloud storage, a database, a remote API).
708
117
  *
709
- * @note Call the returned factory with optional options to instantiate the storage adapter.
710
- *
711
- * @example
118
+ * @example In-memory storage (the built-in implementation)
712
119
  * ```ts
713
120
  * import { createStorage } from '@kubb/core'
714
121
  *
715
122
  * export const memoryStorage = createStorage(() => {
716
123
  * const store = new Map<string, string>()
124
+ *
717
125
  * return {
718
126
  * name: 'memory',
719
- * async hasItem(key) { return store.has(key) },
720
- * async getItem(key) { return store.get(key) ?? null },
721
- * async setItem(key, value) { store.set(key, value) },
722
- * async removeItem(key) { store.delete(key) },
127
+ * async hasItem(key) {
128
+ * return store.has(key)
129
+ * },
130
+ * async getItem(key) {
131
+ * return store.get(key) ?? null
132
+ * },
133
+ * async setItem(key, value) {
134
+ * store.set(key, value)
135
+ * },
136
+ * async removeItem(key) {
137
+ * store.delete(key)
138
+ * },
723
139
  * async getKeys(base) {
724
140
  * const keys = [...store.keys()]
725
141
  * return base ? keys.filter((k) => k.startsWith(base)) : keys
726
142
  * },
727
- * async clear(base) { if (!base) store.clear() },
143
+ * async clear(base) {
144
+ * if (!base) store.clear()
145
+ * },
728
146
  * }
729
147
  * })
730
- *
731
- * // Instantiate:
732
- * const storage = memoryStorage()
733
148
  * ```
734
149
  */
735
150
  function createStorage(build) {
@@ -738,12 +153,6 @@ function createStorage(build) {
738
153
  //#endregion
739
154
  //#region src/storages/fsStorage.ts
740
155
  /**
741
- * Detects the filesystem error used to indicate that a path does not exist.
742
- */
743
- function isMissingPathError(error) {
744
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
745
- }
746
- /**
747
156
  * Built-in filesystem storage driver.
748
157
  *
749
158
  * This is the default storage when no `storage` option is configured in the root config.
@@ -774,17 +183,15 @@ const fsStorage = createStorage(() => ({
774
183
  try {
775
184
  await access(resolve(key));
776
185
  return true;
777
- } catch (error) {
778
- if (isMissingPathError(error)) return false;
779
- throw new Error(`Failed to access storage item "${key}"`, { cause: error });
186
+ } catch (_error) {
187
+ return false;
780
188
  }
781
189
  },
782
190
  async getItem(key) {
783
191
  try {
784
192
  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 });
193
+ } catch (_error) {
194
+ return null;
788
195
  }
789
196
  },
790
197
  async setItem(key, value) {
@@ -794,23 +201,22 @@ const fsStorage = createStorage(() => ({
794
201
  await rm(resolve(key), { force: true });
795
202
  },
796
203
  async getKeys(base) {
797
- const keys = [];
798
204
  const resolvedBase = resolve(base ?? process.cwd());
799
- async function walk(dir, prefix) {
205
+ async function* walk(dir, prefix) {
800
206
  let entries;
801
207
  try {
802
208
  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 });
209
+ } catch (_error) {
210
+ return;
806
211
  }
807
212
  for (const entry of entries) {
808
213
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
809
- if (entry.isDirectory()) await walk(join(dir, entry.name), rel);
810
- else keys.push(rel);
214
+ if (entry.isDirectory()) yield* walk(join(dir, entry.name), rel);
215
+ else yield rel;
811
216
  }
812
217
  }
813
- await walk(resolvedBase, "");
218
+ const keys = [];
219
+ for await (const key of walk(resolvedBase, "")) keys.push(key);
814
220
  return keys;
815
221
  },
816
222
  async clear(base) {
@@ -819,475 +225,272 @@ const fsStorage = createStorage(() => ({
819
225
  }
820
226
  }));
821
227
  //#endregion
822
- //#region package.json
823
- var version$1 = "5.0.0-beta.3";
824
- //#endregion
825
- //#region src/utils/diagnostics.ts
228
+ //#region src/createKubb.ts
826
229
  /**
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.
230
+ * Builds a `Storage` view scoped to the file paths produced by the current build.
231
+ * Reads delegate to the underlying `storage` so source bytes stay where they were
232
+ * written; writes register the key so subsequent reads and `getKeys` are scoped
233
+ * to this build's output.
831
234
  */
832
- function getDiagnosticInfo() {
833
- return {
834
- nodeVersion: version,
835
- KubbVersion: version$1,
836
- platform: process.platform,
837
- arch: process.arch,
838
- cwd: process.cwd()
839
- };
840
- }
841
- //#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
- //#region src/createKubb.ts
848
- async function setup(userConfig, options = {}) {
849
- const hooks = options.hooks ?? new AsyncEventEmitter();
850
- const sources = /* @__PURE__ */ new Map();
851
- 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
- await hooks.emit("kubb:debug", {
854
- date: /* @__PURE__ */ new Date(),
855
- logs: [
856
- "Configuration:",
857
- ` • Name: ${userConfig.name || "unnamed"}`,
858
- ` • Root: ${userConfig.root || process.cwd()}`,
859
- ` • Output: ${userConfig.output?.path || "not specified"}`,
860
- ` • Plugins: ${userConfig.plugins?.length || 0}`,
861
- "Output Settings:",
862
- ` • Storage: ${userConfig.storage ? `custom(${userConfig.storage.name})` : userConfig.output?.write === false ? "disabled" : "filesystem (default)"}`,
863
- ` • Formatter: ${userConfig.output?.format || "none"}`,
864
- ` • Linter: ${userConfig.output?.lint || "none"}`,
865
- "Environment:",
866
- Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
867
- ]
868
- });
869
- try {
870
- if (isInputPath(userConfig) && !new URLPath(userConfig.input.path).isURL) {
871
- await exists(userConfig.input.path);
872
- await hooks.emit("kubb:debug", {
873
- date: /* @__PURE__ */ new Date(),
874
- logs: [`✓ Input file validated: ${userConfig.input.path}`]
875
- });
876
- }
877
- } catch (caughtError) {
878
- if (isInputPath(userConfig)) {
879
- const error = caughtError;
880
- 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 });
235
+ function createSourcesView(storage) {
236
+ const paths = /* @__PURE__ */ new Set();
237
+ return createStorage(() => ({
238
+ name: `${storage.name}:sources`,
239
+ async hasItem(key) {
240
+ return paths.has(key) && await storage.hasItem(key);
241
+ },
242
+ async getItem(key) {
243
+ return paths.has(key) ? storage.getItem(key) : null;
244
+ },
245
+ async setItem(key, value) {
246
+ paths.add(key);
247
+ await storage.setItem(key, value);
248
+ },
249
+ async removeItem(key) {
250
+ paths.delete(key);
251
+ await storage.removeItem(key);
252
+ },
253
+ async getKeys(base) {
254
+ if (!base) return [...paths];
255
+ const result = [];
256
+ for (const key of paths) if (key.startsWith(base)) result.push(key);
257
+ return result;
258
+ },
259
+ async clear() {
260
+ paths.clear();
261
+ await storage.clear();
881
262
  }
882
- }
883
- if (!userConfig.adapter) throw new Error("Adapter should be defined");
884
- const config = {
263
+ }))();
264
+ }
265
+ function resolveConfig(userConfig) {
266
+ return {
885
267
  ...userConfig,
886
268
  root: userConfig.root || process.cwd(),
887
269
  parsers: userConfig.parsers ?? [],
888
- adapter: userConfig.adapter,
889
270
  output: {
890
271
  format: false,
891
272
  lint: false,
892
- write: true,
893
273
  extension: DEFAULT_EXTENSION,
894
274
  defaultBanner: DEFAULT_BANNER,
895
275
  ...userConfig.output
896
276
  },
277
+ storage: userConfig.storage ?? fsStorage(),
897
278
  devtools: userConfig.devtools ? {
898
279
  studioUrl: DEFAULT_STUDIO_URL,
899
280
  ...typeof userConfig.devtools === "boolean" ? {} : userConfig.devtools
900
281
  } : void 0,
901
- plugins: userConfig.plugins
282
+ plugins: userConfig.plugins ?? []
902
283
  };
903
- const storage = config.output.write === false ? null : config.storage ?? fsStorage();
904
- if (config.output.clean) {
905
- await hooks.emit("kubb:debug", {
906
- date: /* @__PURE__ */ new Date(),
907
- logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
908
- });
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);
915
- }
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
- });
284
+ }
285
+ /**
286
+ * Returns a snapshot of the current runtime environment.
287
+ *
288
+ * Useful for attaching context to debug logs and error reports so that
289
+ * issues can be reproduced without manual information gathering.
290
+ */
291
+ function getDiagnosticInfo() {
934
292
  return {
935
- config,
936
- hooks,
937
- driver,
938
- sources,
939
- storage
293
+ nodeVersion: version,
294
+ KubbVersion: version$1,
295
+ platform: process.platform,
296
+ arch: process.arch,
297
+ cwd: process.cwd()
940
298
  };
941
299
  }
300
+ function isInputPath(config) {
301
+ return typeof config?.input === "object" && config.input !== null && "path" in config.input;
302
+ }
942
303
  /**
943
- * Walks the AST and dispatches nodes to a plugin's direct AST hooks
944
- * (`schema`, `operation`, `operations`).
304
+ * Kubb code-generation instance bound to a single config entry. Resolves the user
305
+ * config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
306
+ * the `setup → build` lifecycle.
307
+ *
308
+ * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
945
309
  *
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.
310
+ * @example
311
+ * ```ts
312
+ * const kubb = createKubb(userConfig)
313
+ * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
314
+ * const { files, failedPlugins } = await kubb.safeBuild()
315
+ * ```
951
316
  */
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;
317
+ var Kubb = class {
318
+ hooks;
319
+ #userConfig;
320
+ #config = null;
321
+ #driver = null;
322
+ #storage = null;
323
+ constructor(userConfig, options = {}) {
324
+ this.#userConfig = userConfig;
325
+ this.hooks = options.hooks ?? new AsyncEventEmitter();
326
+ }
327
+ get storage() {
328
+ if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
329
+ return this.#storage;
330
+ }
331
+ get driver() {
332
+ if (!this.#driver) throw new Error("[kubb] setup() must be called before accessing driver");
333
+ return this.#driver;
334
+ }
335
+ get config() {
336
+ if (!this.#config) throw new Error("[kubb] setup() must be called before accessing config");
337
+ return this.#config;
958
338
  }
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
339
+ /**
340
+ * Resolves config and initializes the driver. `build()` calls this automatically.
341
+ */
342
+ async setup() {
343
+ const config = resolveConfig(this.#userConfig);
344
+ const driver = new KubbDriver(config, { hooks: this.hooks });
345
+ const storage = createSourcesView(config.storage);
346
+ await this.hooks.emit("kubb:debug", {
347
+ date: /* @__PURE__ */ new Date(),
348
+ logs: this.#configLogs(config)
349
+ });
350
+ if (isInputPath(this.#userConfig) && !new URLPath(this.#userConfig.input.path).isURL) try {
351
+ await exists(this.#userConfig.input.path);
352
+ await this.hooks.emit("kubb:debug", {
353
+ date: /* @__PURE__ */ new Date(),
354
+ logs: [`✓ Input file validated: ${this.#userConfig.input.path}`]
991
355
  });
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));
1000
- }
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
356
+ } catch (caughtError) {
357
+ 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 });
358
+ }
359
+ if (config.output.clean) {
360
+ await this.hooks.emit("kubb:debug", {
361
+ date: /* @__PURE__ */ new Date(),
362
+ logs: ["Cleaning output directories", ` • Output: ${config.output.path}`]
1010
363
  });
1011
- if (options !== null) {
1012
- collectedOperations.push(transformedNode);
1013
- const ctx = {
1014
- ...generatorContext,
1015
- options
1016
- };
1017
- for (const gen of generators) {
1018
- if (!gen.operation) continue;
1019
- await applyHookResult(await gen.operation(transformedNode, ctx), driver, resolveRenderer(gen));
1020
- }
1021
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1022
- }
364
+ await config.storage.clear(resolve(config.root, config.output.path));
1023
365
  }
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));
366
+ await driver.setup();
367
+ this.#config = config;
368
+ this.#driver = driver;
369
+ this.#storage = storage;
370
+ }
371
+ /**
372
+ * Runs the full pipeline and throws on any plugin error.
373
+ * Automatically calls `setup()` if needed.
374
+ */
375
+ async build() {
376
+ const out = await this.safeBuild();
377
+ if (out.error) throw out.error;
378
+ if (out.failedPlugins.size > 0) {
379
+ const errors = [...out.failedPlugins].map(({ error }) => error);
380
+ throw new BuildError(`Build Error with ${out.failedPlugins.size} failed plugins`, { errors });
1033
381
  }
1034
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
382
+ return out;
1035
383
  }
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}`]
1063
- });
1064
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
1065
- const duration = getElapsedMs(hrStart);
1066
- pluginTimings.set(plugin.name, duration);
1067
- await hooks.emit("kubb:plugin:end", {
1068
- plugin,
1069
- duration,
1070
- success: true,
1071
- config,
1072
- get files() {
1073
- return driver.fileManager.files;
1074
- },
1075
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1076
- });
1077
- await hooks.emit("kubb:debug", {
1078
- date: /* @__PURE__ */ new Date(),
1079
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1080
- });
1081
- } catch (caughtError) {
1082
- const error = caughtError;
1083
- const errorTimestamp = /* @__PURE__ */ new Date();
1084
- const duration = getElapsedMs(hrStart);
1085
- await hooks.emit("kubb:plugin:end", {
1086
- plugin,
1087
- duration,
1088
- success: false,
1089
- error,
1090
- config,
1091
- get files() {
1092
- return driver.fileManager.files;
1093
- },
1094
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1095
- });
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
- }
384
+ /**
385
+ * Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
386
+ * Automatically calls `setup()` if needed.
387
+ */
388
+ async safeBuild() {
389
+ try {
390
+ var _usingCtx$1 = _usingCtx();
391
+ if (!this.#driver) await this.setup();
392
+ const cleanup = _usingCtx$1.u(this);
393
+ const driver = cleanup.driver;
394
+ const storage = cleanup.storage;
395
+ const { failedPlugins, pluginTimings, error } = await driver.run({ storage });
396
+ return {
397
+ failedPlugins,
398
+ files: driver.fileManager.files,
399
+ driver,
400
+ pluginTimings,
401
+ storage,
402
+ ...error ? { error } : {}
403
+ };
404
+ } catch (_) {
405
+ _usingCtx$1.e = _;
406
+ } finally {
407
+ _usingCtx$1.d();
1111
408
  }
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
- };
1176
- } finally {
1177
- driver.dispose();
1178
409
  }
1179
- }
1180
- async function build(setupResult) {
1181
- const { files, driver, failedPlugins, pluginTimings, error, sources } = await safeBuild(setupResult);
1182
- if (error) throw error;
1183
- if (failedPlugins.size > 0) {
1184
- const errors = [...failedPlugins].map(({ error }) => error);
1185
- throw new BuildError(`Build Error with ${failedPlugins.size} failed plugins`, { errors });
410
+ dispose() {
411
+ this.#driver?.dispose();
1186
412
  }
1187
- return {
1188
- failedPlugins,
1189
- files,
1190
- driver,
1191
- pluginTimings,
1192
- error: void 0,
1193
- sources
1194
- };
1195
- }
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
- };
1209
- return {
1210
- type: "path",
1211
- path: resolve(config.root, config.input.path)
1212
- };
1213
- }
413
+ [Symbol.dispose]() {
414
+ this.dispose();
415
+ }
416
+ #configLogs(config) {
417
+ const u = this.#userConfig;
418
+ const diag = getDiagnosticInfo();
419
+ return [
420
+ "Configuration:",
421
+ ` • Name: ${u.name || "unnamed"}`,
422
+ ` • Root: ${u.root || process.cwd()}`,
423
+ ` • Output: ${u.output?.path || "not specified"}`,
424
+ ` • Plugins: ${u.plugins?.length || 0}`,
425
+ "Output Settings:",
426
+ ` • Storage: ${config.storage.name}`,
427
+ ` • Formatter: ${u.output?.format || "none"}`,
428
+ ` • Linter: ${u.output?.lint || "none"}`,
429
+ `Running adapter: ${config.adapter?.name || "none"}`,
430
+ "Environment:",
431
+ Object.entries(diag).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
432
+ ];
433
+ }
434
+ };
1214
435
  /**
1215
- * Creates a Kubb instance bound to a single config entry.
1216
- *
1217
- * 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`)
1219
- * across the `setup → build` lifecycle. Attach event listeners to `kubb.hooks` before
1220
- * calling `setup()` or `build()`.
436
+ * Constructs a {@link Kubb} build orchestrator from a user config. Equivalent
437
+ * to `new Kubb(userConfig, options)` and the canonical public entry point.
1221
438
  *
1222
439
  * @example
1223
440
  * ```ts
1224
- * const kubb = createKubb(userConfig)
441
+ * import { createKubb } from '@kubb/core'
442
+ * import { adapterOas } from '@kubb/adapter-oas'
443
+ * import { pluginTs } from '@kubb/plugin-ts'
1225
444
  *
1226
- * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
1227
- * console.log(`${plugin.name} completed in ${duration}ms`)
445
+ * const kubb = createKubb({
446
+ * input: { path: './petStore.yaml' },
447
+ * output: { path: './src/gen' },
448
+ * adapter: adapterOas(),
449
+ * plugins: [pluginTs()],
1228
450
  * })
1229
451
  *
1230
- * const { files, failedPlugins } = await kubb.safeBuild()
452
+ * await kubb.build()
1231
453
  * ```
1232
454
  */
1233
455
  function createKubb(userConfig, options = {}) {
1234
- const hooks = options.hooks ?? new AsyncEventEmitter();
1235
- let setupResult;
1236
- const instance = {
1237
- get hooks() {
1238
- return hooks;
1239
- },
1240
- get sources() {
1241
- return setupResult?.sources ?? /* @__PURE__ */ new Map();
1242
- },
1243
- get driver() {
1244
- return setupResult?.driver;
1245
- },
1246
- get config() {
1247
- return setupResult?.config;
1248
- },
1249
- async setup() {
1250
- setupResult = await setup(userConfig, { hooks });
1251
- },
1252
- async build() {
1253
- if (!setupResult) await instance.setup();
1254
- return build(setupResult);
1255
- },
1256
- async safeBuild() {
1257
- if (!setupResult) await instance.setup();
1258
- return safeBuild(setupResult);
1259
- }
1260
- };
1261
- return instance;
456
+ return new Kubb(userConfig, options);
1262
457
  }
1263
458
  //#endregion
1264
459
  //#region src/createRenderer.ts
1265
460
  /**
1266
- * Creates a renderer factory for use in generator definitions.
461
+ * Defines a renderer factory. Renderers turn the generator's return value
462
+ * (JSX, a template string, a tree of any shape) into `FileNode`s that get
463
+ * written to disk.
1267
464
  *
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.
465
+ * Use this to support output formats beyond JSX for instance, a Handlebars
466
+ * renderer, a string-template renderer, or a renderer that writes binary
467
+ * files. Plugins and generators pick the renderer to use via the `renderer`
468
+ * field on `defineGenerator`.
1271
469
  *
1272
- * @example
470
+ * @example A minimal renderer that wraps a custom runtime
1273
471
  * ```ts
1274
- * // packages/renderer-jsx/src/index.ts
1275
- * export const jsxRenderer = createRenderer(() => {
1276
- * const runtime = new Runtime()
472
+ * import { createRenderer } from '@kubb/core'
473
+ *
474
+ * export const myRenderer = createRenderer(() => {
475
+ * const runtime = new MyRuntime()
1277
476
  * return {
1278
- * async render(element) { await runtime.render(element) },
1279
- * get files() { return runtime.nodes },
1280
- * unmount(error) { runtime.unmount(error) },
477
+ * async render(element) {
478
+ * await runtime.render(element)
479
+ * },
480
+ * get files() {
481
+ * return runtime.files
482
+ * },
483
+ * dispose() {
484
+ * runtime.dispose()
485
+ * },
486
+ * unmount(error) {
487
+ * runtime.dispose(error)
488
+ * },
489
+ * [Symbol.dispose]() {
490
+ * this.dispose()
491
+ * },
1281
492
  * }
1282
493
  * })
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
494
  * ```
1292
495
  */
1293
496
  function createRenderer(factory) {
@@ -1296,9 +499,32 @@ function createRenderer(factory) {
1296
499
  //#endregion
1297
500
  //#region src/defineGenerator.ts
1298
501
  /**
1299
- * Defines a generator. Returns the object as-is with correct `this` typings.
1300
- * `applyHookResult` handles renderer elements and `File[]` uniformly using
1301
- * the generator's declared `renderer` factory.
502
+ * Defines a generator: a unit of work that runs during the plugin's AST walk
503
+ * and produces files. Plugins register generators via `ctx.addGenerator()`
504
+ * inside `kubb:plugin:setup`.
505
+ *
506
+ * The returned object is the input as-is, but with `this` types preserved so
507
+ * `schema`/`operation`/`operations` methods are correctly typed against the
508
+ * plugin's `PluginFactoryOptions`. Renderer elements and `FileNode[]` returns
509
+ * are both handled by the runtime — pick whichever style fits.
510
+ *
511
+ * @example JSX-based schema generator
512
+ * ```tsx
513
+ * import { defineGenerator } from '@kubb/core'
514
+ * import { jsxRenderer } from '@kubb/renderer-jsx'
515
+ *
516
+ * export const typeGenerator = defineGenerator({
517
+ * name: 'typescript',
518
+ * renderer: jsxRenderer,
519
+ * schema(node, ctx) {
520
+ * return (
521
+ * <File path={`${ctx.root}/${node.name}.ts`}>
522
+ * <Type node={node} resolver={ctx.resolver} />
523
+ * </File>
524
+ * )
525
+ * },
526
+ * })
527
+ * ```
1302
528
  */
1303
529
  function defineGenerator(generator) {
1304
530
  return generator;
@@ -1306,15 +532,33 @@ function defineGenerator(generator) {
1306
532
  //#endregion
1307
533
  //#region src/defineLogger.ts
1308
534
  /**
1309
- * Wraps a logger definition into a typed {@link Logger}.
535
+ * Defines a typed logger. Use the second type parameter to declare a return
536
+ * value from `install`, which is handy when the logger exposes a sink factory
537
+ * or cleanup callback to the caller.
1310
538
  *
1311
- * @example
539
+ * @example Basic logger
1312
540
  * ```ts
541
+ * import { defineLogger } from '@kubb/core'
542
+ *
1313
543
  * export const myLogger = defineLogger({
1314
544
  * name: 'my-logger',
1315
- * install(context, options) {
1316
- * context.on('kubb:info', (message) => console.log('ℹ', message))
1317
- * context.on('kubb:error', (error) => console.error('✗', error.message))
545
+ * install(context) {
546
+ * context.on('kubb:info', ({ message }) => console.log('ℹ', message))
547
+ * context.on('kubb:error', ({ error }) => console.error('✗', error.message))
548
+ * },
549
+ * })
550
+ * ```
551
+ *
552
+ * @example Logger that returns a hook sink factory
553
+ * ```ts
554
+ * import { defineLogger, type LoggerOptions } from '@kubb/core'
555
+ * import type { HookSinkFactory } from './sinks'
556
+ *
557
+ * export const myLogger = defineLogger<LoggerOptions, HookSinkFactory>({
558
+ * name: 'my-logger',
559
+ * install(context) {
560
+ * // … register event handlers …
561
+ * return () => ({ onStdout: console.log })
1318
562
  * },
1319
563
  * })
1320
564
  * ```
@@ -1325,18 +569,17 @@ function defineLogger(logger) {
1325
569
  //#endregion
1326
570
  //#region src/defineMiddleware.ts
1327
571
  /**
1328
- * Creates a middleware factory using the hook-style `hooks` API.
572
+ * Creates a middleware factory. Middleware fires after every plugin handler
573
+ * for the same event, which makes it the natural place for post-processing
574
+ * (barrel files, lint runs, audit logs).
1329
575
  *
1330
- * Middleware handlers fire after all plugin handlers for any given event, making them ideal for post-processing, logging, and auditing.
1331
- * Per-build state (such as accumulators) belongs inside the factory closure so each `createKubb` invocation gets its own isolated instance.
576
+ * Per-build state belongs inside the factory closure so each `createKubb`
577
+ * invocation gets its own isolated instance.
1332
578
  *
1333
- * @note The factory can accept typed options. See examples for using options and per-build state patterns.
1334
- *
1335
- * @example
579
+ * @example Stateless middleware
1336
580
  * ```ts
1337
581
  * import { defineMiddleware } from '@kubb/core'
1338
582
  *
1339
- * // Stateless middleware
1340
583
  * export const logMiddleware = defineMiddleware(() => ({
1341
584
  * name: 'log-middleware',
1342
585
  * hooks: {
@@ -1345,8 +588,12 @@ function defineLogger(logger) {
1345
588
  * },
1346
589
  * },
1347
590
  * }))
591
+ * ```
592
+ *
593
+ * @example Middleware with options and per-build state
594
+ * ```ts
595
+ * import { defineMiddleware } from '@kubb/core'
1348
596
  *
1349
- * // Middleware with options and per-build state
1350
597
  * export const prefixMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
1351
598
  * const seen = new Set<string>()
1352
599
  * return {
@@ -1366,20 +613,23 @@ function defineMiddleware(factory) {
1366
613
  //#endregion
1367
614
  //#region src/defineParser.ts
1368
615
  /**
1369
- * Defines a parser with type safety. Creates parsers that transform generated files to strings based on their extension.
1370
- *
1371
- * @note Call the returned factory with optional options to instantiate the parser.
616
+ * Defines a parser with type-safe `this`. Used to register handlers for new
617
+ * file extensions or to plug a non-TypeScript output into the build.
1372
618
  *
1373
619
  * @example
1374
620
  * ```ts
1375
- * import { defineParser } from '@kubb/core'
621
+ * import { defineParser, ast } from '@kubb/core'
1376
622
  *
1377
623
  * export const jsonParser = defineParser({
1378
624
  * name: 'json',
1379
625
  * extNames: ['.json'],
1380
626
  * parse(file) {
1381
- * const { extractStringsFromNodes } = await import('@kubb/ast')
1382
- * return file.sources.map((s) => extractStringsFromNodes(s.nodes ?? [])).join('\n')
627
+ * return file.sources
628
+ * .map((source) => ast.extractStringsFromNodes(source.nodes ?? []))
629
+ * .join('\n')
630
+ * },
631
+ * print(...nodes) {
632
+ * return nodes.map(String).join('\n')
1383
633
  * },
1384
634
  * })
1385
635
  * ```
@@ -1388,34 +638,6 @@ function defineParser(parser) {
1388
638
  return parser;
1389
639
  }
1390
640
  //#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
641
  //#region src/storages/memoryStorage.ts
1420
642
  /**
1421
643
  * In-memory storage driver. Useful for testing and dry-run scenarios where
@@ -1466,6 +688,6 @@ const memoryStorage = createStorage(() => {
1466
688
  };
1467
689
  });
1468
690
  //#endregion
1469
- export { AsyncEventEmitter, FileManager, FileProcessor, PluginDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
691
+ export { AsyncEventEmitter, FileManager, FileProcessor, KubbDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
1470
692
 
1471
693
  //# sourceMappingURL=index.js.map