@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.
@@ -93,6 +93,381 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
93
93
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
94
94
  }
95
95
  //#endregion
96
+ //#region ../../internals/utils/src/promise.ts
97
+ function* chunks(arr, size) {
98
+ for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
99
+ }
100
+ /**
101
+ * Slices `source` into batches of `concurrency` items and awaits `process` for each batch.
102
+ * Accepts both plain arrays (sync) and `AsyncIterable` (streaming).
103
+ *
104
+ * `process` controls whether items inside a batch run in parallel; this helper only
105
+ * controls batch size and per-batch flushing.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * // parallel dispatch inside each batch
110
+ * await forBatches(schemas, (batch) => Promise.all(batch.map(process)), { concurrency: 8 })
111
+ *
112
+ * // async iterable with a flush after every batch
113
+ * await forBatches(stream.schemas, (batch) => dispatch(batch), { concurrency: 8, flush })
114
+ * ```
115
+ */
116
+ async function forBatches(source, process, options) {
117
+ const { concurrency, flush } = options;
118
+ if (Array.isArray(source)) {
119
+ for (const batch of chunks(source, concurrency)) {
120
+ await process(batch);
121
+ if (flush) await flush();
122
+ }
123
+ return;
124
+ }
125
+ const batch = [];
126
+ for await (const item of source) {
127
+ batch.push(item);
128
+ if (batch.length >= concurrency) {
129
+ await process(batch.splice(0));
130
+ if (flush) await flush();
131
+ }
132
+ }
133
+ if (batch.length > 0) {
134
+ await process(batch.splice(0));
135
+ if (flush) await flush();
136
+ }
137
+ }
138
+ /**
139
+ * Runs `work`, passing `flush` as its periodic-flush callback, then calls
140
+ * `flush` once more to drain any items that did not cross a flush boundary.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * await withDrain(
145
+ * (flush) => processItems(items, { flush }),
146
+ * () => writeRemainingFiles(),
147
+ * )
148
+ * ```
149
+ */
150
+ async function withDrain(work, flush) {
151
+ await work(flush);
152
+ await flush();
153
+ }
154
+ /** Returns `true` when `result` is a thenable `Promise`.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * isPromise(Promise.resolve(1)) // true
159
+ * isPromise(42) // false
160
+ * ```
161
+ */
162
+ function isPromise(result) {
163
+ return result !== null && result !== void 0 && typeof result["then"] === "function";
164
+ }
165
+ /**
166
+ * Wraps `factory` with a keyed cache backed by the provided store.
167
+ *
168
+ * Pass a `WeakMap` for object keys (results are GC-eligible when the key is
169
+ * collected) or a `Map` for primitive keys. For multi-argument functions,
170
+ * nest two `memoize` calls — the outer keyed by the first argument, the
171
+ * inner (created once per outer miss) keyed by the second.
172
+ *
173
+ * Because the cache is owned by the caller, it can be shared, inspected, or
174
+ * cleared independently of the memoized function.
175
+ *
176
+ * @example Single WeakMap key
177
+ * ```ts
178
+ * const cache = new WeakMap<SchemaNode, Set<string>>()
179
+ * const getRefs = memoize(cache, (node) => collectRefs(node))
180
+ * ```
181
+ *
182
+ * @example Single Map key (primitive)
183
+ * ```ts
184
+ * const cache = new Map<string, Resolver>()
185
+ * const getResolver = memoize(cache, (name) => buildResolver(name))
186
+ * ```
187
+ *
188
+ * @example Two-level (object + primitive)
189
+ * ```ts
190
+ * const outer = new WeakMap<Params[], Map<string, Params[]>>()
191
+ * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
192
+ * fn(params)('camelcase')
193
+ * ```
194
+ */
195
+ function memoize(store, factory) {
196
+ return (key) => {
197
+ if (store.has(key)) return store.get(key);
198
+ const value = factory(key);
199
+ store.set(key, value);
200
+ return value;
201
+ };
202
+ }
203
+ /**
204
+ * Wraps a plain array in a reusable `AsyncIterable`.
205
+ * Each `[Symbol.asyncIterator]()` call returns a fresh generator so the
206
+ * iterable can be consumed multiple times (e.g. once per plugin pre-scan).
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const stream = arrayToAsyncIterable([1, 2, 3])
211
+ * for await (const n of stream) console.log(n) // 1, 2, 3
212
+ * ```
213
+ */
214
+ function arrayToAsyncIterable(arr) {
215
+ return { [Symbol.asyncIterator]() {
216
+ return (async function* () {
217
+ yield* arr;
218
+ })();
219
+ } };
220
+ }
221
+ //#endregion
222
+ //#region ../../internals/utils/src/reserved.ts
223
+ /**
224
+ * JavaScript and Java reserved words.
225
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
226
+ */
227
+ const reservedWords = new Set([
228
+ "abstract",
229
+ "arguments",
230
+ "boolean",
231
+ "break",
232
+ "byte",
233
+ "case",
234
+ "catch",
235
+ "char",
236
+ "class",
237
+ "const",
238
+ "continue",
239
+ "debugger",
240
+ "default",
241
+ "delete",
242
+ "do",
243
+ "double",
244
+ "else",
245
+ "enum",
246
+ "eval",
247
+ "export",
248
+ "extends",
249
+ "false",
250
+ "final",
251
+ "finally",
252
+ "float",
253
+ "for",
254
+ "function",
255
+ "goto",
256
+ "if",
257
+ "implements",
258
+ "import",
259
+ "in",
260
+ "instanceof",
261
+ "int",
262
+ "interface",
263
+ "let",
264
+ "long",
265
+ "native",
266
+ "new",
267
+ "null",
268
+ "package",
269
+ "private",
270
+ "protected",
271
+ "public",
272
+ "return",
273
+ "short",
274
+ "static",
275
+ "super",
276
+ "switch",
277
+ "synchronized",
278
+ "this",
279
+ "throw",
280
+ "throws",
281
+ "transient",
282
+ "true",
283
+ "try",
284
+ "typeof",
285
+ "var",
286
+ "void",
287
+ "volatile",
288
+ "while",
289
+ "with",
290
+ "yield",
291
+ "Array",
292
+ "Date",
293
+ "hasOwnProperty",
294
+ "Infinity",
295
+ "isFinite",
296
+ "isNaN",
297
+ "isPrototypeOf",
298
+ "length",
299
+ "Math",
300
+ "name",
301
+ "NaN",
302
+ "Number",
303
+ "Object",
304
+ "prototype",
305
+ "String",
306
+ "toString",
307
+ "undefined",
308
+ "valueOf"
309
+ ]);
310
+ /**
311
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * isValidVarName('status') // true
316
+ * isValidVarName('class') // false (reserved word)
317
+ * isValidVarName('42foo') // false (starts with digit)
318
+ * ```
319
+ */
320
+ function isValidVarName(name) {
321
+ if (!name || reservedWords.has(name)) return false;
322
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
323
+ }
324
+ //#endregion
325
+ //#region ../../internals/utils/src/urlPath.ts
326
+ /**
327
+ * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
328
+ *
329
+ * @example
330
+ * const p = new URLPath('/pet/{petId}')
331
+ * p.URL // '/pet/:petId'
332
+ * p.template // '`/pet/${petId}`'
333
+ */
334
+ var URLPath = class {
335
+ /**
336
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
337
+ */
338
+ path;
339
+ #options;
340
+ constructor(path, options = {}) {
341
+ this.path = path;
342
+ this.#options = options;
343
+ }
344
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
349
+ * ```
350
+ */
351
+ get URL() {
352
+ return this.toURLPath();
353
+ }
354
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
359
+ * new URLPath('/pet/{petId}').isURL // false
360
+ * ```
361
+ */
362
+ get isURL() {
363
+ try {
364
+ return !!new URL(this.path).href;
365
+ } catch {
366
+ return false;
367
+ }
368
+ }
369
+ /**
370
+ * Converts the OpenAPI path to a TypeScript template literal string.
371
+ *
372
+ * @example
373
+ * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
374
+ * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
375
+ */
376
+ get template() {
377
+ return this.toTemplateString();
378
+ }
379
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * new URLPath('/pet/{petId}').object
384
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
385
+ * ```
386
+ */
387
+ get object() {
388
+ return this.toObject();
389
+ }
390
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
391
+ *
392
+ * @example
393
+ * ```ts
394
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
395
+ * new URLPath('/pet').params // undefined
396
+ * ```
397
+ */
398
+ get params() {
399
+ return this.getParams();
400
+ }
401
+ #transformParam(raw) {
402
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
403
+ return this.#options.casing === "camelcase" ? camelCase(param) : param;
404
+ }
405
+ /**
406
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
407
+ */
408
+ #eachParam(fn) {
409
+ for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
410
+ const raw = match[1];
411
+ fn(raw, this.#transformParam(raw));
412
+ }
413
+ }
414
+ toObject({ type = "path", replacer, stringify } = {}) {
415
+ const object = {
416
+ url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
417
+ params: this.getParams()
418
+ };
419
+ if (stringify) {
420
+ if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
421
+ if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
422
+ return `{ url: '${object.url}' }`;
423
+ }
424
+ return object;
425
+ }
426
+ /**
427
+ * Converts the OpenAPI path to a TypeScript template literal string.
428
+ * An optional `replacer` can transform each extracted parameter name before interpolation.
429
+ *
430
+ * @example
431
+ * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
432
+ */
433
+ toTemplateString({ prefix = "", replacer } = {}) {
434
+ return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
435
+ if (i % 2 === 0) return part;
436
+ const param = this.#transformParam(part);
437
+ return `\${${replacer ? replacer(param) : param}}`;
438
+ }).join("")}\``;
439
+ }
440
+ /**
441
+ * Extracts all `{param}` segments from the path and returns them as a key-value map.
442
+ * An optional `replacer` transforms each parameter name in both key and value positions.
443
+ * Returns `undefined` when no path parameters are found.
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
448
+ * // { petId: 'petId', tagId: 'tagId' }
449
+ * ```
450
+ */
451
+ getParams(replacer) {
452
+ const params = {};
453
+ this.#eachParam((_raw, param) => {
454
+ const key = replacer ? replacer(param) : param;
455
+ params[key] = key;
456
+ });
457
+ return Object.keys(params).length > 0 ? params : void 0;
458
+ }
459
+ /** Converts the OpenAPI path to Express-style colon syntax.
460
+ *
461
+ * @example
462
+ * ```ts
463
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
464
+ * ```
465
+ */
466
+ toURLPath() {
467
+ return this.path.replace(/\{([^}]+)\}/g, ":$1");
468
+ }
469
+ };
470
+ //#endregion
96
471
  //#region src/constants.ts
97
472
  /**
98
473
  * Base URL for the Kubb Studio web app.
@@ -173,14 +548,12 @@ function testPattern(value, pattern) {
173
548
  * Checks if an operation matches a pattern for a given filter type (`tag`, `operationId`, `path`, `method`).
174
549
  */
175
550
  function matchesOperationPattern(node, type, pattern) {
176
- switch (type) {
177
- case "tag": return node.tags.some((tag) => testPattern(tag, pattern));
178
- case "operationId": return testPattern(node.operationId, pattern);
179
- case "path": return testPattern(node.path, pattern);
180
- case "method": return testPattern(node.method.toLowerCase(), pattern);
181
- case "contentType": return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
182
- default: return false;
183
- }
551
+ if (type === "tag") return node.tags.some((tag) => testPattern(tag, pattern));
552
+ if (type === "operationId") return testPattern(node.operationId, pattern);
553
+ if (type === "path") return testPattern(node.path, pattern);
554
+ if (type === "method") return testPattern(node.method.toLowerCase(), pattern);
555
+ if (type === "contentType") return node.requestBody?.content?.some((c) => testPattern(c.contentType, pattern)) ?? false;
556
+ return false;
184
557
  }
185
558
  /**
186
559
  * Checks if a schema matches a pattern for a given filter type (`schemaName`).
@@ -188,10 +561,8 @@ function matchesOperationPattern(node, type, pattern) {
188
561
  * Returns `null` when the filter type doesn't apply to schemas.
189
562
  */
190
563
  function matchesSchemaPattern(node, type, pattern) {
191
- switch (type) {
192
- case "schemaName": return node.name ? testPattern(node.name, pattern) : false;
193
- default: return null;
194
- }
564
+ if (type === "schemaName") return node.name ? testPattern(node.name, pattern) : false;
565
+ return null;
195
566
  }
196
567
  /**
197
568
  * Default name resolver used by `defineResolver`.
@@ -414,10 +785,9 @@ function buildDefaultBanner({ title, description, version, config }) {
414
785
  *
415
786
  * A user-supplied `output.banner` overrides the default Kubb "Generated by Kubb" notice.
416
787
  * When no `output.banner` is set, the Kubb notice is used (including `title` and `version`
417
- * from the OAS spec when a `node` is provided).
788
+ * from the document metadata when `meta` is provided).
418
789
  *
419
- * - When `output.banner` is a function and `node` is provided, returns `output.banner(node)`.
420
- * - When `output.banner` is a function and `node` is absent, falls back to the Kubb notice.
790
+ * - When `output.banner` is a function, calls it with `meta` and returns the result.
421
791
  * - When `output.banner` is a string, returns it directly.
422
792
  * - When `config.output.defaultBanner` is `false`, returns `undefined`.
423
793
  * - Otherwise returns the Kubb "Generated by Kubb" notice.
@@ -428,15 +798,15 @@ function buildDefaultBanner({ title, description, version, config }) {
428
798
  * // → '// my banner'
429
799
  * ```
430
800
  *
431
- * @example Function banner with node
801
+ * @example Function banner with metadata
432
802
  * ```ts
433
- * defaultResolveBanner(inputNode, { output: { banner: (node) => `// v${node.version}` }, config })
803
+ * defaultResolveBanner(meta, { output: { banner: (m) => `// v${m?.version}` }, config })
434
804
  * // → '// v3.0.0'
435
805
  * ```
436
806
  *
437
807
  * @example No user banner — Kubb notice with OAS metadata
438
808
  * ```ts
439
- * defaultResolveBanner(inputNode, { config })
809
+ * defaultResolveBanner(meta, { config })
440
810
  * // → '/** Generated by Kubb ... Title: Pet Store ... *\/'
441
811
  * ```
442
812
  *
@@ -446,21 +816,20 @@ function buildDefaultBanner({ title, description, version, config }) {
446
816
  * // → undefined
447
817
  * ```
448
818
  */
449
- function defaultResolveBanner(node, { output, config }) {
450
- if (typeof output?.banner === "function") return output.banner(node);
819
+ function defaultResolveBanner(meta, { output, config }) {
820
+ if (typeof output?.banner === "function") return output.banner(meta);
451
821
  if (typeof output?.banner === "string") return output.banner;
452
822
  if (config.output.defaultBanner === false) return;
453
823
  return buildDefaultBanner({
454
- title: node?.meta?.title,
455
- version: node?.meta?.version,
824
+ title: meta?.title,
825
+ version: meta?.version,
456
826
  config
457
827
  });
458
828
  }
459
829
  /**
460
830
  * Default footer resolver — returns the footer string for a generated file.
461
831
  *
462
- * - When `output.footer` is a function and `node` is provided, calls it with the node.
463
- * - When `output.footer` is a function and `node` is absent, returns `undefined`.
832
+ * - When `output.footer` is a function, calls it with `meta` and returns the result.
464
833
  * - When `output.footer` is a string, returns it directly.
465
834
  * - Otherwise returns `undefined`.
466
835
  *
@@ -470,14 +839,14 @@ function defaultResolveBanner(node, { output, config }) {
470
839
  * // → '// end of file'
471
840
  * ```
472
841
  *
473
- * @example Function footer with node
842
+ * @example Function footer with metadata
474
843
  * ```ts
475
- * defaultResolveFooter(inputNode, { output: { footer: (node) => `// ${node.title}` }, config })
844
+ * defaultResolveFooter(meta, { output: { footer: (m) => `// ${m?.title}` }, config })
476
845
  * // → '// Pet Store'
477
846
  * ```
478
847
  */
479
- function defaultResolveFooter(node, { output }) {
480
- if (typeof output?.footer === "function") return node ? output.footer(node) : void 0;
848
+ function defaultResolveFooter(meta, { output }) {
849
+ if (typeof output?.footer === "function") return output.footer(meta);
481
850
  if (typeof output?.footer === "string") return output.footer;
482
851
  }
483
852
  /**
@@ -690,11 +1059,11 @@ var FileManager = class {
690
1059
  }
691
1060
  };
692
1061
  //#endregion
693
- //#region src/PluginDriver.ts
1062
+ //#region src/KubbDriver.ts
694
1063
  function enforceOrder(enforce) {
695
1064
  return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
696
1065
  }
697
- var PluginDriver = class PluginDriver {
1066
+ var KubbDriver = class KubbDriver {
698
1067
  config;
699
1068
  options;
700
1069
  /**
@@ -702,25 +1071,34 @@ var PluginDriver = class PluginDriver {
702
1071
  *
703
1072
  * @example
704
1073
  * ```ts
705
- * PluginDriver.getMode('src/gen/types.ts') // 'single'
706
- * PluginDriver.getMode('src/gen/types') // 'split'
1074
+ * KubbDriver.getMode('src/gen/types.ts') // 'single'
1075
+ * KubbDriver.getMode('src/gen/types') // 'split'
707
1076
  * ```
708
1077
  */
709
1078
  static getMode(fileOrFolder) {
710
1079
  return getMode(fileOrFolder);
711
1080
  }
712
1081
  /**
713
- * The universal `@kubb/ast` `InputNode` produced by the adapter, set by
714
- * the build pipeline after the adapter's `parse()` resolves.
1082
+ * The streaming `InputStreamNode` produced by the adapter.
1083
+ * Always set after adapter setup — parse-only adapters are wrapped automatically.
715
1084
  */
716
1085
  inputNode = void 0;
1086
+ adapter = void 0;
717
1087
  /**
718
- * Set when the adapter returns a streaming `InputStreamNode` (large specs).
719
- * Mutually exclusive with `inputNode` — exactly one is set after adapter setup.
1088
+ * Studio session state, kept together so `dispose()` can reset it atomically.
1089
+ *
1090
+ * - `source` holds the raw adapter source so `adapter.parse()` can be called lazily.
1091
+ * Intentionally outlives the build; cleared by `dispose()`.
1092
+ * - `isOpen` prevents opening the studio more than once per build.
1093
+ * - `inputNode` caches the parse promise so `adapter.parse()` is called at most once
1094
+ * per studio session, even when `openInStudio()` is called multiple times.
720
1095
  */
721
- inputStreamNode = void 0;
722
- adapter = void 0;
723
- #studioIsOpen = false;
1096
+ #studio = {
1097
+ source: void 0,
1098
+ isOpen: false,
1099
+ inputNode: void 0
1100
+ };
1101
+ #middlewareListeners = [];
724
1102
  /**
725
1103
  * Central file store for all generated files.
726
1104
  * Plugins should use `this.addFile()` / `this.upsertFile()` (via their context) to
@@ -732,23 +1110,29 @@ var PluginDriver = class PluginDriver {
732
1110
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
733
1111
  * Used by the build loop to decide whether to emit generator events for a given plugin.
734
1112
  */
735
- #pluginsWithEventGenerators = /* @__PURE__ */ new Set();
1113
+ #eventGeneratorPlugins = /* @__PURE__ */ new Set();
736
1114
  #resolvers = /* @__PURE__ */ new Map();
737
1115
  #defaultResolvers = /* @__PURE__ */ new Map();
738
1116
  #hookListeners = /* @__PURE__ */ new Map();
739
1117
  constructor(config, options) {
740
1118
  this.config = config;
741
1119
  this.options = options;
742
- config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin)).filter((plugin) => {
743
- if (typeof plugin.apply === "function") return plugin.apply(config);
744
- return true;
745
- }).sort((a, b) => {
1120
+ this.adapter = config.adapter;
1121
+ }
1122
+ async setup() {
1123
+ const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
1124
+ normalized.sort((a, b) => {
746
1125
  if (b.dependencies?.includes(a.name)) return -1;
747
1126
  if (a.dependencies?.includes(b.name)) return 1;
748
1127
  return enforceOrder(a.enforce) - enforceOrder(b.enforce);
749
- }).forEach((plugin) => {
750
- this.plugins.set(plugin.name, plugin);
751
1128
  });
1129
+ for (const plugin of normalized) {
1130
+ if (plugin.apply) plugin.apply(this.config);
1131
+ this.#registerPlugin(plugin);
1132
+ this.plugins.set(plugin.name, plugin);
1133
+ }
1134
+ if (this.config.middleware) for (const middleware of this.config.middleware) for (const event of Object.keys(middleware.hooks)) this.#registerMiddleware(event, middleware.hooks);
1135
+ if (this.config.adapter) await this.#registerAdapter(this.config.adapter);
752
1136
  }
753
1137
  get hooks() {
754
1138
  return this.options.hooks;
@@ -757,19 +1141,48 @@ var PluginDriver = class PluginDriver {
757
1141
  * Creates an `NormalizedPlugin` from a hook-style plugin and registers
758
1142
  * its lifecycle handlers on the `AsyncEventEmitter`.
759
1143
  */
760
- #normalizePlugin(hookPlugin) {
761
- const normalizedPlugin = {
762
- name: hookPlugin.name,
763
- dependencies: hookPlugin.dependencies,
764
- enforce: hookPlugin.enforce,
765
- options: {
1144
+ #normalizePlugin(plugin) {
1145
+ const normalized = {
1146
+ name: plugin.name,
1147
+ dependencies: plugin.dependencies,
1148
+ enforce: plugin.enforce,
1149
+ hooks: plugin.hooks,
1150
+ options: plugin.options ?? {
766
1151
  output: { path: "." },
767
1152
  exclude: [],
768
1153
  override: []
769
1154
  }
770
1155
  };
771
- this.registerPluginHooks(hookPlugin, normalizedPlugin);
772
- return normalizedPlugin;
1156
+ if ("apply" in plugin && typeof plugin.apply === "function") normalized.apply = plugin.apply;
1157
+ return normalized;
1158
+ }
1159
+ async #registerAdapter(adapter) {
1160
+ const source = inputToAdapterSource(this.config);
1161
+ this.#studio.source = source;
1162
+ if (adapter.stream) {
1163
+ this.inputNode = await adapter.stream(source);
1164
+ await this.hooks.emit("kubb:debug", {
1165
+ date: /* @__PURE__ */ new Date(),
1166
+ logs: [`✓ Adapter '${adapter.name}' producing input stream`]
1167
+ });
1168
+ } else {
1169
+ const inputNode = await adapter.parse(source);
1170
+ this.inputNode = (0, _kubb_ast.createStreamInput)(arrayToAsyncIterable(inputNode.schemas), arrayToAsyncIterable(inputNode.operations), inputNode.meta);
1171
+ await this.hooks.emit("kubb:debug", {
1172
+ date: /* @__PURE__ */ new Date(),
1173
+ logs: [
1174
+ `✓ Adapter '${adapter.name}' resolved InputNode (wrapped as stream)`,
1175
+ ` • Schemas: ${inputNode.schemas.length}`,
1176
+ ` • Operations: ${inputNode.operations.length}`
1177
+ ]
1178
+ });
1179
+ }
1180
+ }
1181
+ #registerMiddleware(event, middlewareHooks) {
1182
+ const handler = middlewareHooks[event];
1183
+ if (!handler) return;
1184
+ this.hooks.on(event, handler);
1185
+ this.#middlewareListeners.push([event, handler]);
773
1186
  }
774
1187
  /**
775
1188
  * Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
@@ -786,29 +1199,29 @@ var PluginDriver = class PluginDriver {
786
1199
  *
787
1200
  * @internal
788
1201
  */
789
- registerPluginHooks(hookPlugin, normalizedPlugin) {
790
- const { hooks } = hookPlugin;
1202
+ #registerPlugin(plugin) {
1203
+ const { hooks } = plugin;
791
1204
  if (!hooks) return;
792
1205
  if (hooks["kubb:plugin:setup"]) {
793
1206
  const setupHandler = (globalCtx) => {
794
1207
  const pluginCtx = {
795
1208
  ...globalCtx,
796
- options: hookPlugin.options ?? {},
1209
+ options: plugin.options ?? {},
797
1210
  addGenerator: (gen) => {
798
- this.registerGenerator(normalizedPlugin.name, gen);
1211
+ this.registerGenerator(plugin.name, gen);
799
1212
  },
800
1213
  setResolver: (resolver) => {
801
- this.setPluginResolver(normalizedPlugin.name, resolver);
1214
+ this.setPluginResolver(plugin.name, resolver);
802
1215
  },
803
1216
  setTransformer: (visitor) => {
804
- normalizedPlugin.transformer = visitor;
1217
+ plugin.transformer = visitor;
805
1218
  },
806
1219
  setRenderer: (renderer) => {
807
- normalizedPlugin.renderer = renderer;
1220
+ plugin.renderer = renderer;
808
1221
  },
809
1222
  setOptions: (opts) => {
810
- normalizedPlugin.options = {
811
- ...normalizedPlugin.options,
1223
+ plugin.options = {
1224
+ ...plugin.options,
812
1225
  ...opts
813
1226
  };
814
1227
  },
@@ -902,7 +1315,7 @@ var PluginDriver = class PluginDriver {
902
1315
  this.hooks.on("kubb:generate:operations", operationsHandler);
903
1316
  this.#trackHookListener("kubb:generate:operations", operationsHandler);
904
1317
  }
905
- this.#pluginsWithEventGenerators.add(pluginName);
1318
+ this.#eventGeneratorPlugins.add(pluginName);
906
1319
  }
907
1320
  /**
908
1321
  * Returns `true` when at least one generator was registered for the given plugin
@@ -911,8 +1324,8 @@ var PluginDriver = class PluginDriver {
911
1324
  * Used by the build loop to decide whether to walk the AST and emit generator events
912
1325
  * for a plugin that has no static `plugin.generators`.
913
1326
  */
914
- hasRegisteredGenerators(pluginName) {
915
- return this.#pluginsWithEventGenerators.has(pluginName);
1327
+ hasEventGenerators(pluginName) {
1328
+ return this.#eventGeneratorPlugins.has(pluginName);
916
1329
  }
917
1330
  /**
918
1331
  * Unregisters all plugin lifecycle listeners from the shared event emitter.
@@ -923,12 +1336,17 @@ var PluginDriver = class PluginDriver {
923
1336
  dispose() {
924
1337
  for (const [event, handlers] of this.#hookListeners) for (const handler of handlers) this.hooks.off(event, handler);
925
1338
  this.#hookListeners.clear();
926
- this.#pluginsWithEventGenerators.clear();
1339
+ this.#eventGeneratorPlugins.clear();
927
1340
  this.#resolvers.clear();
928
1341
  this.#defaultResolvers.clear();
929
1342
  this.fileManager.dispose();
930
1343
  this.inputNode = void 0;
931
- this.inputStreamNode = void 0;
1344
+ this.#studio = {
1345
+ source: void 0,
1346
+ isOpen: false,
1347
+ inputNode: void 0
1348
+ };
1349
+ for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
932
1350
  }
933
1351
  [Symbol.dispose]() {
934
1352
  this.dispose();
@@ -941,16 +1359,10 @@ var PluginDriver = class PluginDriver {
941
1359
  }
942
1360
  handlers.add(handler);
943
1361
  }
944
- #createDefaultResolver(pluginName) {
945
- const existingResolver = this.#defaultResolvers.get(pluginName);
946
- if (existingResolver) return existingResolver;
947
- const resolver = defineResolver(() => ({
948
- name: "default",
949
- pluginName
950
- }));
951
- this.#defaultResolvers.set(pluginName, resolver);
952
- return resolver;
953
- }
1362
+ #getDefaultResolver = memoize(this.#defaultResolvers, (pluginName) => defineResolver(() => ({
1363
+ name: "default",
1364
+ pluginName
1365
+ })));
954
1366
  /**
955
1367
  * Merges `partial` with the plugin's default resolver and stores the result.
956
1368
  * Also mirrors it onto `plugin.resolver` so callers using `getPlugin(name).resolver`
@@ -958,7 +1370,7 @@ var PluginDriver = class PluginDriver {
958
1370
  */
959
1371
  setPluginResolver(pluginName, partial) {
960
1372
  const merged = {
961
- ...this.#createDefaultResolver(pluginName),
1373
+ ...this.#getDefaultResolver(pluginName),
962
1374
  ...partial
963
1375
  };
964
1376
  this.#resolvers.set(pluginName, merged);
@@ -966,7 +1378,7 @@ var PluginDriver = class PluginDriver {
966
1378
  if (plugin) plugin.resolver = merged;
967
1379
  }
968
1380
  getResolver(pluginName) {
969
- return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#createDefaultResolver(pluginName);
1381
+ return this.#resolvers.get(pluginName) ?? this.plugins.get(pluginName)?.resolver ?? this.#getDefaultResolver(pluginName);
970
1382
  }
971
1383
  getContext(plugin) {
972
1384
  const driver = this;
@@ -976,7 +1388,7 @@ var PluginDriver = class PluginDriver {
976
1388
  return (0, node_path.resolve)(driver.config.root, driver.config.output.path);
977
1389
  },
978
1390
  getMode(output) {
979
- return PluginDriver.getMode((0, node_path.resolve)(driver.config.root, driver.config.output.path, output.path));
1391
+ return KubbDriver.getMode((0, node_path.resolve)(driver.config.root, driver.config.output.path, output.path));
980
1392
  },
981
1393
  hooks: driver.hooks,
982
1394
  plugin,
@@ -990,13 +1402,10 @@ var PluginDriver = class PluginDriver {
990
1402
  upsertFile: async (...files) => {
991
1403
  driver.fileManager.upsert(...files);
992
1404
  },
993
- get inputNode() {
994
- if (driver.inputNode) return driver.inputNode;
995
- return {
996
- kind: "Input",
997
- schemas: [],
998
- operations: [],
999
- meta: driver.inputStreamNode?.meta
1405
+ get meta() {
1406
+ return driver.inputNode?.meta ?? {
1407
+ circularNames: [],
1408
+ enumNames: []
1000
1409
  };
1001
1410
  },
1002
1411
  get adapter() {
@@ -1017,13 +1426,14 @@ var PluginDriver = class PluginDriver {
1017
1426
  info(message) {
1018
1427
  driver.hooks.emit("kubb:info", { message });
1019
1428
  },
1020
- openInStudio(options) {
1021
- if (!driver.config.devtools || driver.#studioIsOpen) return;
1429
+ async openInStudio(options) {
1430
+ if (!driver.config.devtools || driver.#studio.isOpen) return;
1022
1431
  if (typeof driver.config.devtools !== "object") throw new Error("Devtools must be an object");
1023
- if (!driver.inputNode || !driver.adapter) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1024
- driver.#studioIsOpen = true;
1432
+ if (!driver.adapter || !driver.#studio.source) throw new Error("adapter is not defined, make sure you have set the parser in kubb.config.ts");
1433
+ driver.#studio.isOpen = true;
1025
1434
  const studioUrl = driver.config.devtools?.studioUrl ?? "https://kubb.studio";
1026
- return openInStudio(driver.inputNode, studioUrl, options);
1435
+ driver.#studio.inputNode ??= Promise.resolve(driver.adapter.parse(driver.#studio.source));
1436
+ return openInStudio(await driver.#studio.inputNode, studioUrl, options);
1027
1437
  }
1028
1438
  };
1029
1439
  }
@@ -1070,6 +1480,22 @@ async function applyAsyncRender({ renderer, result, driver }) {
1070
1480
  driver.fileManager.upsert(...renderer.files);
1071
1481
  renderer.unmount();
1072
1482
  }
1483
+ function inputToAdapterSource(config) {
1484
+ const input = config.input;
1485
+ if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
1486
+ if ("data" in input) return {
1487
+ type: "data",
1488
+ data: input.data
1489
+ };
1490
+ if (new URLPath(input.path).isURL) return {
1491
+ type: "path",
1492
+ path: input.path
1493
+ };
1494
+ return {
1495
+ type: "path",
1496
+ path: (0, node_path.resolve)(config.root, input.path)
1497
+ };
1498
+ }
1073
1499
  //#endregion
1074
1500
  Object.defineProperty(exports, "DEFAULT_BANNER", {
1075
1501
  enumerable: true,
@@ -1095,10 +1521,16 @@ Object.defineProperty(exports, "FileManager", {
1095
1521
  return FileManager;
1096
1522
  }
1097
1523
  });
1098
- Object.defineProperty(exports, "PluginDriver", {
1524
+ Object.defineProperty(exports, "KubbDriver", {
1525
+ enumerable: true,
1526
+ get: function() {
1527
+ return KubbDriver;
1528
+ }
1529
+ });
1530
+ Object.defineProperty(exports, "URLPath", {
1099
1531
  enumerable: true,
1100
1532
  get: function() {
1101
- return PluginDriver;
1533
+ return URLPath;
1102
1534
  }
1103
1535
  });
1104
1536
  Object.defineProperty(exports, "__name", {
@@ -1119,12 +1551,6 @@ Object.defineProperty(exports, "applyHookResult", {
1119
1551
  return applyHookResult;
1120
1552
  }
1121
1553
  });
1122
- Object.defineProperty(exports, "camelCase", {
1123
- enumerable: true,
1124
- get: function() {
1125
- return camelCase;
1126
- }
1127
- });
1128
1554
  Object.defineProperty(exports, "definePlugin", {
1129
1555
  enumerable: true,
1130
1556
  get: function() {
@@ -1137,11 +1563,29 @@ Object.defineProperty(exports, "defineResolver", {
1137
1563
  return defineResolver;
1138
1564
  }
1139
1565
  });
1566
+ Object.defineProperty(exports, "forBatches", {
1567
+ enumerable: true,
1568
+ get: function() {
1569
+ return forBatches;
1570
+ }
1571
+ });
1572
+ Object.defineProperty(exports, "isPromise", {
1573
+ enumerable: true,
1574
+ get: function() {
1575
+ return isPromise;
1576
+ }
1577
+ });
1140
1578
  Object.defineProperty(exports, "logLevel", {
1141
1579
  enumerable: true,
1142
1580
  get: function() {
1143
1581
  return logLevel;
1144
1582
  }
1145
1583
  });
1584
+ Object.defineProperty(exports, "withDrain", {
1585
+ enumerable: true,
1586
+ get: function() {
1587
+ return withDrain;
1588
+ }
1589
+ });
1146
1590
 
1147
- //# sourceMappingURL=PluginDriver-DXp767s2.cjs.map
1591
+ //# sourceMappingURL=KubbDriver-BXSnJ3qM.cjs.map