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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import "./chunk--u3MIqq1.js";
2
2
  import path, { extname, resolve } from "node:path";
3
- import { createFile, isOperationNode, isSchemaNode } from "@kubb/ast";
3
+ import { createFile, createStreamInput, isOperationNode, isSchemaNode } from "@kubb/ast";
4
4
  import { deflateSync } from "fflate";
5
5
  import { x } from "tinyexec";
6
6
  //#region ../../internals/utils/src/casing.ts
@@ -67,6 +67,381 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
67
67
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
68
68
  }
69
69
  //#endregion
70
+ //#region ../../internals/utils/src/promise.ts
71
+ function* chunks(arr, size) {
72
+ for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
73
+ }
74
+ /**
75
+ * Slices `source` into batches of `concurrency` items and awaits `process` for each batch.
76
+ * Accepts both plain arrays (sync) and `AsyncIterable` (streaming).
77
+ *
78
+ * `process` controls whether items inside a batch run in parallel; this helper only
79
+ * controls batch size and per-batch flushing.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * // parallel dispatch inside each batch
84
+ * await forBatches(schemas, (batch) => Promise.all(batch.map(process)), { concurrency: 8 })
85
+ *
86
+ * // async iterable with a flush after every batch
87
+ * await forBatches(stream.schemas, (batch) => dispatch(batch), { concurrency: 8, flush })
88
+ * ```
89
+ */
90
+ async function forBatches(source, process, options) {
91
+ const { concurrency, flush } = options;
92
+ if (Array.isArray(source)) {
93
+ for (const batch of chunks(source, concurrency)) {
94
+ await process(batch);
95
+ if (flush) await flush();
96
+ }
97
+ return;
98
+ }
99
+ const batch = [];
100
+ for await (const item of source) {
101
+ batch.push(item);
102
+ if (batch.length >= concurrency) {
103
+ await process(batch.splice(0));
104
+ if (flush) await flush();
105
+ }
106
+ }
107
+ if (batch.length > 0) {
108
+ await process(batch.splice(0));
109
+ if (flush) await flush();
110
+ }
111
+ }
112
+ /**
113
+ * Runs `work`, passing `flush` as its periodic-flush callback, then calls
114
+ * `flush` once more to drain any items that did not cross a flush boundary.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * await withDrain(
119
+ * (flush) => processItems(items, { flush }),
120
+ * () => writeRemainingFiles(),
121
+ * )
122
+ * ```
123
+ */
124
+ async function withDrain(work, flush) {
125
+ await work(flush);
126
+ await flush();
127
+ }
128
+ /** Returns `true` when `result` is a thenable `Promise`.
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * isPromise(Promise.resolve(1)) // true
133
+ * isPromise(42) // false
134
+ * ```
135
+ */
136
+ function isPromise(result) {
137
+ return result !== null && result !== void 0 && typeof result["then"] === "function";
138
+ }
139
+ /**
140
+ * Wraps `factory` with a keyed cache backed by the provided store.
141
+ *
142
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
143
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
144
+ * nest two `memoize` calls — the outer keyed by the first argument, the
145
+ * inner (created once per outer miss) keyed by the second.
146
+ *
147
+ * Because the cache is owned by the caller, it can be shared, inspected, or
148
+ * cleared independently of the memoized function.
149
+ *
150
+ * @example Single WeakMap key
151
+ * ```ts
152
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
153
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
154
+ * ```
155
+ *
156
+ * @example Single Map key (primitive)
157
+ * ```ts
158
+ * const cache = new Map<string, Resolver>()
159
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
160
+ * ```
161
+ *
162
+ * @example Two-level (object + primitive)
163
+ * ```ts
164
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
165
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
166
+ * fn(params)('camelcase')
167
+ * ```
168
+ */
169
+ function memoize(store, factory) {
170
+ return (key) => {
171
+ if (store.has(key)) return store.get(key);
172
+ const value = factory(key);
173
+ store.set(key, value);
174
+ return value;
175
+ };
176
+ }
177
+ /**
178
+ * Wraps a plain array in a reusable `AsyncIterable`.
179
+ * Each `[Symbol.asyncIterator]()` call returns a fresh generator so the
180
+ * iterable can be consumed multiple times (e.g. once per plugin pre-scan).
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * const stream = arrayToAsyncIterable([1, 2, 3])
185
+ * for await (const n of stream) console.log(n) // 1, 2, 3
186
+ * ```
187
+ */
188
+ function arrayToAsyncIterable(arr) {
189
+ return { [Symbol.asyncIterator]() {
190
+ return (async function* () {
191
+ yield* arr;
192
+ })();
193
+ } };
194
+ }
195
+ //#endregion
196
+ //#region ../../internals/utils/src/reserved.ts
197
+ /**
198
+ * JavaScript and Java reserved words.
199
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
200
+ */
201
+ const reservedWords = new Set([
202
+ "abstract",
203
+ "arguments",
204
+ "boolean",
205
+ "break",
206
+ "byte",
207
+ "case",
208
+ "catch",
209
+ "char",
210
+ "class",
211
+ "const",
212
+ "continue",
213
+ "debugger",
214
+ "default",
215
+ "delete",
216
+ "do",
217
+ "double",
218
+ "else",
219
+ "enum",
220
+ "eval",
221
+ "export",
222
+ "extends",
223
+ "false",
224
+ "final",
225
+ "finally",
226
+ "float",
227
+ "for",
228
+ "function",
229
+ "goto",
230
+ "if",
231
+ "implements",
232
+ "import",
233
+ "in",
234
+ "instanceof",
235
+ "int",
236
+ "interface",
237
+ "let",
238
+ "long",
239
+ "native",
240
+ "new",
241
+ "null",
242
+ "package",
243
+ "private",
244
+ "protected",
245
+ "public",
246
+ "return",
247
+ "short",
248
+ "static",
249
+ "super",
250
+ "switch",
251
+ "synchronized",
252
+ "this",
253
+ "throw",
254
+ "throws",
255
+ "transient",
256
+ "true",
257
+ "try",
258
+ "typeof",
259
+ "var",
260
+ "void",
261
+ "volatile",
262
+ "while",
263
+ "with",
264
+ "yield",
265
+ "Array",
266
+ "Date",
267
+ "hasOwnProperty",
268
+ "Infinity",
269
+ "isFinite",
270
+ "isNaN",
271
+ "isPrototypeOf",
272
+ "length",
273
+ "Math",
274
+ "name",
275
+ "NaN",
276
+ "Number",
277
+ "Object",
278
+ "prototype",
279
+ "String",
280
+ "toString",
281
+ "undefined",
282
+ "valueOf"
283
+ ]);
284
+ /**
285
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * isValidVarName('status') // true
290
+ * isValidVarName('class') // false (reserved word)
291
+ * isValidVarName('42foo') // false (starts with digit)
292
+ * ```
293
+ */
294
+ function isValidVarName(name) {
295
+ if (!name || reservedWords.has(name)) return false;
296
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
297
+ }
298
+ //#endregion
299
+ //#region ../../internals/utils/src/urlPath.ts
300
+ /**
301
+ * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
302
+ *
303
+ * @example
304
+ * const p = new URLPath('/pet/{petId}')
305
+ * p.URL // '/pet/:petId'
306
+ * p.template // '`/pet/${petId}`'
307
+ */
308
+ var URLPath = class {
309
+ /**
310
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
311
+ */
312
+ path;
313
+ #options;
314
+ constructor(path, options = {}) {
315
+ this.path = path;
316
+ this.#options = options;
317
+ }
318
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
323
+ * ```
324
+ */
325
+ get URL() {
326
+ return this.toURLPath();
327
+ }
328
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
333
+ * new URLPath('/pet/{petId}').isURL // false
334
+ * ```
335
+ */
336
+ get isURL() {
337
+ try {
338
+ return !!new URL(this.path).href;
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+ /**
344
+ * Converts the OpenAPI path to a TypeScript template literal string.
345
+ *
346
+ * @example
347
+ * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
348
+ * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
349
+ */
350
+ get template() {
351
+ return this.toTemplateString();
352
+ }
353
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * new URLPath('/pet/{petId}').object
358
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
359
+ * ```
360
+ */
361
+ get object() {
362
+ return this.toObject();
363
+ }
364
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
369
+ * new URLPath('/pet').params // undefined
370
+ * ```
371
+ */
372
+ get params() {
373
+ return this.getParams();
374
+ }
375
+ #transformParam(raw) {
376
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
377
+ return this.#options.casing === "camelcase" ? camelCase(param) : param;
378
+ }
379
+ /**
380
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
381
+ */
382
+ #eachParam(fn) {
383
+ for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
384
+ const raw = match[1];
385
+ fn(raw, this.#transformParam(raw));
386
+ }
387
+ }
388
+ toObject({ type = "path", replacer, stringify } = {}) {
389
+ const object = {
390
+ url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
391
+ params: this.getParams()
392
+ };
393
+ if (stringify) {
394
+ if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
395
+ if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
396
+ return `{ url: '${object.url}' }`;
397
+ }
398
+ return object;
399
+ }
400
+ /**
401
+ * Converts the OpenAPI path to a TypeScript template literal string.
402
+ * An optional `replacer` can transform each extracted parameter name before interpolation.
403
+ *
404
+ * @example
405
+ * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
406
+ */
407
+ toTemplateString({ prefix = "", replacer } = {}) {
408
+ return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
409
+ if (i % 2 === 0) return part;
410
+ const param = this.#transformParam(part);
411
+ return `\${${replacer ? replacer(param) : param}}`;
412
+ }).join("")}\``;
413
+ }
414
+ /**
415
+ * Extracts all `{param}` segments from the path and returns them as a key-value map.
416
+ * An optional `replacer` transforms each parameter name in both key and value positions.
417
+ * Returns `undefined` when no path parameters are found.
418
+ *
419
+ * @example
420
+ * ```ts
421
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
422
+ * // { petId: 'petId', tagId: 'tagId' }
423
+ * ```
424
+ */
425
+ getParams(replacer) {
426
+ const params = {};
427
+ this.#eachParam((_raw, param) => {
428
+ const key = replacer ? replacer(param) : param;
429
+ params[key] = key;
430
+ });
431
+ return Object.keys(params).length > 0 ? params : void 0;
432
+ }
433
+ /** Converts the OpenAPI path to Express-style colon syntax.
434
+ *
435
+ * @example
436
+ * ```ts
437
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
438
+ * ```
439
+ */
440
+ toURLPath() {
441
+ return this.path.replace(/\{([^}]+)\}/g, ":$1");
442
+ }
443
+ };
444
+ //#endregion
70
445
  //#region src/constants.ts
71
446
  /**
72
447
  * Base URL for the Kubb Studio web app.
@@ -147,14 +522,12 @@ function testPattern(value, pattern) {
147
522
  * Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
148
523
  */
149
524
  function matchesOperationPattern(node, type, pattern) {
150
- switch (type) {
151
- case "tag": return node.tags.some((tag) => testPattern(tag, pattern));
152
- case "operationId": return testPattern(node.operationId, pattern);
153
- case "path": return testPattern(node.path, pattern);
154
- case "method": return testPattern(node.method.toLowerCase(), pattern);
155
- case "contentType": return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
156
- default: return false;
157
- }
525
+ if (type === "tag") return node.tags.some((tag) => testPattern(tag, pattern));
526
+ if (type === "operationId") return testPattern(node.operationId, pattern);
527
+ if (type === "path") return testPattern(node.path, pattern);
528
+ if (type === "method") return testPattern(node.method.toLowerCase(), pattern);
529
+ if (type === "contentType") return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
530
+ return false;
158
531
  }
159
532
  /**
160
533
  * Checks if a schema matches a pattern for a given filter type (`schemaName`).
@@ -162,10 +535,8 @@ function matchesOperationPattern(node, type, pattern) {
162
535
  * Returns `null` when the filter type doesn't apply to schemas.
163
536
  */
164
537
  function matchesSchemaPattern(node, type, pattern) {
165
- switch (type) {
166
- case "schemaName": return node.name ? testPattern(node.name, pattern) : false;
167
- default: return null;
168
- }
538
+ if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
539
+ return null;
169
540
  }
170
541
  /**
171
542
  * Default name resolver used by `defineResolver`.
@@ -388,10 +759,9 @@ function buildDefaultBanner({ title, description, version, config }) {
388
759
  *
389
760
  * A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
390
761
  * When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
391
- * from the OAS spec when a `node` is provided).
762
+ * from the document metadata when `meta` is provided).
392
763
  *
393
- * - When `output.banner` is a function and `node` is provided, returns `output.banner(node)`.
394
- * - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
764
+ * - When `output.banner` is a function, calls it with `meta` and returns the result.
395
765
  * - When `output.banner` is a string, returns it directly.
396
766
  * - When `config.output.defaultBanner` is `false`, returns `undefined`.
397
767
  * - Otherwise returns the Kubb "Generated by Kubb" notice.
@@ -402,15 +772,15 @@ function buildDefaultBanner({ title, description, version, config }) {
402
772
  * // → '// my banner'
403
773
  * ```
404
774
  *
405
- * @example Function banner with node
775
+ * @example Function banner with metadata
406
776
  * ```ts
407
- * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
777
+ * defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
408
778
  * // → '// v3.0.0'
409
779
  * ```
410
780
  *
411
781
  * @example No user banner — Kubb notice with OAS metadata
412
782
  * ```ts
413
- * defaultResolveBanner(inputNode, { config })
783
+ * defaultResolveBanner(meta, { config })
414
784
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
415
785
  * ```
416
786
  *
@@ -420,21 +790,20 @@ function buildDefaultBanner({ title, description, version, config }) {
420
790
  * // → undefined
421
791
  * ```
422
792
  */
423
- function defaultResolveBanner(node, { output, config }) {
424
- if (typeof output?.banner === "function") return output.banner(node);
793
+ function defaultResolveBanner(meta, { output, config }) {
794
+ if (typeof output?.banner === "function") return output.banner(meta);
425
795
  if (typeof output?.banner === "string") return output.banner;
426
796
  if (config.output.defaultBanner === false) return;
427
797
  return buildDefaultBanner({
428
- title: node?.meta?.title,
429
- version: node?.meta?.version,
798
+ title: meta?.title,
799
+ version: meta?.version,
430
800
  config
431
801
  });
432
802
  }
433
803
  /**
434
804
  * Default footer resolver — returns the footer string for a generated file.
435
805
  *
436
- * - When `output.footer` is a function and `node` is provided, calls it with the node.
437
- * - When `output.footer` is a function and `node` is absent, returns `undefined`.
806
+ * - When `output.footer` is a function, calls it with `meta` and returns the result.
438
807
  * - When `output.footer` is a string, returns it directly.
439
808
  * - Otherwise returns `undefined`.
440
809
  *
@@ -444,14 +813,14 @@ function defaultResolveBanner(node, { output, config }) {
444
813
  * // → '// end of file'
445
814
  * ```
446
815
  *
447
- * @example Function footer with node
816
+ * @example Function footer with metadata
448
817
  * ```ts
449
- * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
818
+ * defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
450
819
  * // → '// Pet Store'
451
820
  * ```
452
821
  */
453
- function defaultResolveFooter(node, { output }) {
454
- if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
822
+ function defaultResolveFooter(meta, { output }) {
823
+ if (typeof output?.footer === "function") return output.footer(meta);
455
824
  if (typeof output?.footer === "string") return output.footer;
456
825
  }
457
826
  /**
@@ -664,11 +1033,11 @@ var FileManager = class {
664
1033
  }
665
1034
  };
666
1035
  //#endregion
667
- //#region src/PluginDriver.ts
1036
+ //#region src/KubbDriver.ts
668
1037
  function enforceOrder(enforce) {
669
1038
  return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
670
1039
  }
671
- var PluginDriver = class PluginDriver {
1040
+ var KubbDriver = class KubbDriver {
672
1041
  config;
673
1042
  options;
674
1043
  /**
@@ -676,25 +1045,34 @@ var PluginDriver = class PluginDriver {
676
1045
  *
677
1046
  * @example
678
1047
  * ```ts
679
- * PluginDriver.getMode('src/gen/types.ts') // 'single'
680
- * PluginDriver.getMode('src/gen/types') // 'split'
1048
+ * KubbDriver.getMode('src/gen/types.ts') // 'single'
1049
+ * KubbDriver.getMode('src/gen/types') // 'split'
681
1050
  * ```
682
1051
  */
683
1052
  static getMode(fileOrFolder) {
684
1053
  return getMode(fileOrFolder);
685
1054
  }
686
1055
  /**
687
- * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
688
- * the build pipeline after the adapter's `parse()` resolves.
1056
+ * The streaming `InputStreamNode` produced by the adapter.
1057
+ * Always set after adapter setup — parse-only adapters are wrapped automatically.
689
1058
  */
690
1059
  inputNode = void 0;
1060
+ adapter = void 0;
691
1061
  /**
692
- * Set when the adapter returns a streaming `InputStreamNode` (large specs).
693
- * Mutually exclusive with `inputNode` — exactly one is set after adapter setup.
1062
+ * Studio session state, kept together so `dispose()` can reset it atomically.
1063
+ *
1064
+ * - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
1065
+ * Intentionally outlives the build; cleared by `dispose()`.
1066
+ * - `isOpen` prevents opening the studio more than once per build.
1067
+ * - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
1068
+ * per studio session, even when `openInStudio()` is called multiple times.
694
1069
  */
695
- inputStreamNode = void 0;
696
- adapter = void 0;
697
- #studioIsOpen = false;
1070
+ #studio = {
1071
+ source: void 0,
1072
+ isOpen: false,
1073
+ inputNode: void 0
1074
+ };
1075
+ #middlewareListeners = [];
698
1076
  /**
699
1077
  * Central file store for all generated files.
700
1078
  * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
@@ -706,23 +1084,29 @@ var PluginDriver = class PluginDriver {
706
1084
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
707
1085
  * Used by the build loop to decide whether to emit generator events for a given plugin.
708
1086
  */
709
- #pluginsWithEventGenerators = /* @__PURE__ */ new Set();
1087
+ #eventGeneratorPlugins = /* @__PURE__ */ new Set();
710
1088
  #resolvers = /* @__PURE__ */ new Map();
711
1089
  #defaultResolvers = /* @__PURE__ */ new Map();
712
1090
  #hookListeners = /* @__PURE__ */ new Map();
713
1091
  constructor(config, options) {
714
1092
  this.config = config;
715
1093
  this.options = options;
716
- config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin)).filter((plugin) => {
717
- if (typeof plugin.apply === "function") return plugin.apply(config);
718
- return true;
719
- }).sort((a, b) => {
1094
+ this.adapter = config.adapter;
1095
+ }
1096
+ async setup() {
1097
+ const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
1098
+ normalized.sort((a, b) => {
720
1099
  if (b.dependencies?.includes(a.name)) return -1;
721
1100
  if (a.dependencies?.includes(b.name)) return 1;
722
1101
  return enforceOrder(a.enforce) - enforceOrder(b.enforce);
723
- }).forEach((plugin) => {
724
- this.plugins.set(plugin.name, plugin);
725
1102
  });
1103
+ for (const plugin of normalized) {
1104
+ if (plugin.apply) plugin.apply(this.config);
1105
+ this.#registerPlugin(plugin);
1106
+ this.plugins.set(plugin.name, plugin);
1107
+ }
1108
+ if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
1109
+ if (this.config.adapter) await this.#registerAdapter(this.config.adapter);
726
1110
  }
727
1111
  get hooks() {
728
1112
  return this.options.hooks;
@@ -731,19 +1115,48 @@ var PluginDriver = class PluginDriver {
731
1115
  * Creates an `NormalizedPlugin` from a hook-style plugin and registers
732
1116
  * its lifecycle handlers on the `AsyncEventEmitter`.
733
1117
  */
734
- #normalizePlugin(hookPlugin) {
735
- const normalizedPlugin = {
736
- name: hookPlugin.name,
737
- dependencies: hookPlugin.dependencies,
738
- enforce: hookPlugin.enforce,
739
- options: {
1118
+ #normalizePlugin(plugin) {
1119
+ const normalized = {
1120
+ name: plugin.name,
1121
+ dependencies: plugin.dependencies,
1122
+ enforce: plugin.enforce,
1123
+ hooks: plugin.hooks,
1124
+ options: plugin.options ?? {
740
1125
  output: { path: "." },
741
1126
  exclude: [],
742
1127
  override: []
743
1128
  }
744
1129
  };
745
- this.registerPluginHooks(hookPlugin, normalizedPlugin);
746
- return normalizedPlugin;
1130
+ if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
1131
+ return normalized;
1132
+ }
1133
+ async #registerAdapter(adapter) {
1134
+ const source = inputToAdapterSource(this.config);
1135
+ this.#studio.source = source;
1136
+ if (adapter.stream) {
1137
+ this.inputNode = await adapter.stream(source);
1138
+ await this.hooks.emit("kubb:debug", {
1139
+ date: /* @__PURE__ */ new Date(),
1140
+ logs: [`✓ Adapter '${adapter.name}' producing input stream`]
1141
+ });
1142
+ } else {
1143
+ const inputNode = await adapter.parse(source);
1144
+ this.inputNode = createStreamInput(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
1145
+ await this.hooks.emit("kubb:debug", {
1146
+ date: /* @__PURE__ */ new Date(),
1147
+ logs: [
1148
+ `✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
1149
+ ` • Schemas: ${inputNode.schemas.length}`,
1150
+ ` • Operations: ${inputNode.operations.length}`
1151
+ ]
1152
+ });
1153
+ }
1154
+ }
1155
+ #registerMiddleware(event, middlewareHooks) {
1156
+ const handler = middlewareHooks[event];
1157
+ if (!handler) return;
1158
+ this.hooks.on(event, handler);
1159
+ this.#middlewareListeners.push([event, handler]);
747
1160
  }
748
1161
  /**
749
1162
  * Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
@@ -760,29 +1173,29 @@ var PluginDriver = class PluginDriver {
760
1173
  *
761
1174
  * @internal
762
1175
  */
763
- registerPluginHooks(hookPlugin, normalizedPlugin) {
764
- const { hooks } = hookPlugin;
1176
+ #registerPlugin(plugin) {
1177
+ const { hooks } = plugin;
765
1178
  if (!hooks) return;
766
1179
  if (hooks["kubb:plugin:setup"]) {
767
1180
  const setupHandler = (globalCtx) => {
768
1181
  const pluginCtx = {
769
1182
  ...globalCtx,
770
- options: hookPlugin.options ?? {},
1183
+ options: plugin.options ?? {},
771
1184
  addGenerator: (gen) => {
772
- this.registerGenerator(normalizedPlugin.name, gen);
1185
+ this.registerGenerator(plugin.name, gen);
773
1186
  },
774
1187
  setResolver: (resolver) => {
775
- this.setPluginResolver(normalizedPlugin.name, resolver);
1188
+ this.setPluginResolver(plugin.name, resolver);
776
1189
  },
777
1190
  setTransformer: (visitor) => {
778
- normalizedPlugin.transformer = visitor;
1191
+ plugin.transformer = visitor;
779
1192
  },
780
1193
  setRenderer: (renderer) => {
781
- normalizedPlugin.renderer = renderer;
1194
+ plugin.renderer = renderer;
782
1195
  },
783
1196
  setOptions: (opts) => {
784
- normalizedPlugin.options = {
785
- ...normalizedPlugin.options,
1197
+ plugin.options = {
1198
+ ...plugin.options,
786
1199
  ...opts
787
1200
  };
788
1201
  },
@@ -876,7 +1289,7 @@ var PluginDriver = class PluginDriver {
876
1289
  this.hooks.on("kubb:generate:operations", operationsHandler);
877
1290
  this.#trackHookListener("kubb:generate:operations", operationsHandler);
878
1291
  }
879
- this.#pluginsWithEventGenerators.add(pluginName);
1292
+ this.#eventGeneratorPlugins.add(pluginName);
880
1293
  }
881
1294
  /**
882
1295
  * Returns `true` when at least one generator was registered for the given plugin
@@ -885,8 +1298,8 @@ var PluginDriver = class PluginDriver {
885
1298
  * Used by the build loop to decide whether to walk the AST and emit generator events
886
1299
  * for a plugin that has no static `plugin.generators`.
887
1300
  */
888
- hasRegisteredGenerators(pluginName) {
889
- return this.#pluginsWithEventGenerators.has(pluginName);
1301
+ hasEventGenerators(pluginName) {
1302
+ return this.#eventGeneratorPlugins.has(pluginName);
890
1303
  }
891
1304
  /**
892
1305
  * Unregisters all plugin lifecycle listeners from the shared event emitter.
@@ -897,12 +1310,17 @@ var PluginDriver = class PluginDriver {
897
1310
  dispose() {
898
1311
  for (const [event, handlers] of this.#hookListeners) for (const handler of handlers) this.hooks.off(event, handler);
899
1312
  this.#hookListeners.clear();
900
- this.#pluginsWithEventGenerators.clear();
1313
+ this.#eventGeneratorPlugins.clear();
901
1314
  this.#resolvers.clear();
902
1315
  this.#defaultResolvers.clear();
903
1316
  this.fileManager.dispose();
904
1317
  this.inputNode = void 0;
905
- this.inputStreamNode = void 0;
1318
+ this.#studio = {
1319
+ source: void 0,
1320
+ isOpen: false,
1321
+ inputNode: void 0
1322
+ };
1323
+ for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
906
1324
  }
907
1325
  [Symbol.dispose]() {
908
1326
  this.dispose();
@@ -915,16 +1333,10 @@ var PluginDriver = class PluginDriver {
915
1333
  }
916
1334
  handlers.add(handler);
917
1335
  }
918
- #createDefaultResolver(pluginName) {
919
- const existingResolver = this.#defaultResolvers.get(pluginName);
920
- if (existingResolver) return existingResolver;
921
- const resolver = defineResolver(() => ({
922
- name: "default",
923
- pluginName
924
- }));
925
- this.#defaultResolvers.set(pluginName, resolver);
926
- return resolver;
927
- }
1336
+ #getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
1337
+ name: "default",
1338
+ pluginName
1339
+ })));
928
1340
  /**
929
1341
  * Merges `partial` with the plugin's default resolver and stores the result.
930
1342
  * Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
@@ -932,7 +1344,7 @@ var PluginDriver = class PluginDriver {
932
1344
  */
933
1345
  setPluginResolver(pluginName, partial) {
934
1346
  const merged = {
935
- ...this.#createDefaultResolver(pluginName),
1347
+ ...this.#getDefaultResolver(pluginName),
936
1348
  ...partial
937
1349
  };
938
1350
  this.#resolvers.set(pluginName, merged);
@@ -940,7 +1352,7 @@ var PluginDriver = class PluginDriver {
940
1352
  if (plugin) plugin.resolver = merged;
941
1353
  }
942
1354
  getResolver(pluginName) {
943
- return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#createDefaultResolver(pluginName);
1355
+ return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#getDefaultResolver(pluginName);
944
1356
  }
945
1357
  getContext(plugin) {
946
1358
  const driver = this;
@@ -950,7 +1362,7 @@ var PluginDriver = class PluginDriver {
950
1362
  return resolve(driver.config.root, driver.config.output.path);
951
1363
  },
952
1364
  getMode(output) {
953
- return PluginDriver.getMode(resolve(driver.config.root, driver.config.output.path, output.path));
1365
+ return KubbDriver.getMode(resolve(driver.config.root, driver.config.output.path, output.path));
954
1366
  },
955
1367
  hooks: driver.hooks,
956
1368
  plugin,
@@ -964,13 +1376,10 @@ var PluginDriver = class PluginDriver {
964
1376
  upsertFile: async (...files) => {
965
1377
  driver.fileManager.upsert(...files);
966
1378
  },
967
- get inputNode() {
968
- if (driver.inputNode) return driver.inputNode;
969
- return {
970
- kind: "Input",
971
- schemas: [],
972
- operations: [],
973
- meta: driver.inputStreamNode?.meta
1379
+ get meta() {
1380
+ return driver.inputNode?.meta ?? {
1381
+ circularNames: [],
1382
+ enumNames: []
974
1383
  };
975
1384
  },
976
1385
  get adapter() {
@@ -991,13 +1400,14 @@ var PluginDriver = class PluginDriver {
991
1400
  info(message) {
992
1401
  driver.hooks.emit("kubb:info", { message });
993
1402
  },
994
- openInStudio(options) {
995
- if (!driver.config.devtools || driver.#studioIsOpen) return;
1403
+ async openInStudio(options) {
1404
+ if (!driver.config.devtools || driver.#studio.isOpen) return;
996
1405
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
997
- if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
998
- driver.#studioIsOpen = true;
1406
+ if (!driver.adapter || !driver.#studio.source) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1407
+ driver.#studio.isOpen = true;
999
1408
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
1000
- return openInStudio(driver.inputNode, studioUrl, options);
1409
+ driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
1410
+ return openInStudio(await driver.#studio.inputNode, studioUrl, options);
1001
1411
  }
1002
1412
  };
1003
1413
  }
@@ -1044,7 +1454,23 @@ async function applyAsyncRender({ renderer, result, driver }) {
1044
1454
  driver.fileManager.upsert(...renderer.files);
1045
1455
  renderer.unmount();
1046
1456
  }
1457
+ function inputToAdapterSource(config) {
1458
+ const input = config.input;
1459
+ if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
1460
+ if ("data" in input) return {
1461
+ type: "data",
1462
+ data: input.data
1463
+ };
1464
+ if (new URLPath(input.path).isURL) return {
1465
+ type: "path",
1466
+ path: input.path
1467
+ };
1468
+ return {
1469
+ type: "path",
1470
+ path: resolve(config.root, input.path)
1471
+ };
1472
+ }
1047
1473
  //#endregion
1048
- export { definePlugin as a, DEFAULT_STUDIO_URL as c, defineResolver as i, logLevel as l, applyHookResult as n, DEFAULT_BANNER as o, FileManager as r, DEFAULT_EXTENSION as s, PluginDriver as t, camelCase as u };
1474
+ export { definePlugin as a, DEFAULT_STUDIO_URL as c, forBatches as d, isPromise as f, defineResolver as i, logLevel as l, applyHookResult as n, DEFAULT_BANNER as o, withDrain as p, FileManager as r, DEFAULT_EXTENSION as s, KubbDriver as t, URLPath as u };
1049
1475
 
1050
- //# sourceMappingURL=PluginDriver-uNex0SAr.js.map
1476
+ //# sourceMappingURL=KubbDriver-Cxii_rBp.js.map