@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.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import "./chunk--u3MIqq1.js";
2
- import { a as definePlugin, c as DEFAULT_STUDIO_URL, i as defineResolver, l as logLevel, n as applyHookResult, o as DEFAULT_BANNER, r as FileManager, s as DEFAULT_EXTENSION, t as PluginDriver, u as camelCase } from "./PluginDriver-uNex0SAr.js";
2
+ import { a as definePlugin, c as DEFAULT_STUDIO_URL, d as forBatches, f as isPromise, i as defineResolver, l as logLevel, n as applyHookResult, o as DEFAULT_BANNER, p as withDrain, r as FileManager, s as DEFAULT_EXTENSION, t as KubbDriver, u as URLPath } from "./KubbDriver-Cxii_rBp.js";
3
3
  import { EventEmitter } from "node:events";
4
4
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import * as ast from "@kubb/ast";
7
- import { collectUsedSchemaNames, extractStringsFromNodes, transform, walk } from "@kubb/ast";
7
+ import { collectUsedSchemaNames, extractStringsFromNodes, transform } from "@kubb/ast";
8
8
  import { version } from "node:process";
9
9
  //#region ../../internals/utils/src/errors.ts
10
10
  /**
@@ -248,268 +248,6 @@ async function clean(path) {
248
248
  });
249
249
  }
250
250
  //#endregion
251
- //#region ../../internals/utils/src/promise.ts
252
- /** Returns `true` when `result` is a thenable `Promise`.
253
- *
254
- * @example
255
- * ```ts
256
- * isPromise(Promise.resolve(1)) // true
257
- * isPromise(42) // false
258
- * ```
259
- */
260
- function isPromise(result) {
261
- return result !== null && result !== void 0 && typeof result["then"] === "function";
262
- }
263
- //#endregion
264
- //#region ../../internals/utils/src/reserved.ts
265
- /**
266
- * JavaScript and Java reserved words.
267
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
268
- */
269
- const reservedWords = new Set([
270
- "abstract",
271
- "arguments",
272
- "boolean",
273
- "break",
274
- "byte",
275
- "case",
276
- "catch",
277
- "char",
278
- "class",
279
- "const",
280
- "continue",
281
- "debugger",
282
- "default",
283
- "delete",
284
- "do",
285
- "double",
286
- "else",
287
- "enum",
288
- "eval",
289
- "export",
290
- "extends",
291
- "false",
292
- "final",
293
- "finally",
294
- "float",
295
- "for",
296
- "function",
297
- "goto",
298
- "if",
299
- "implements",
300
- "import",
301
- "in",
302
- "instanceof",
303
- "int",
304
- "interface",
305
- "let",
306
- "long",
307
- "native",
308
- "new",
309
- "null",
310
- "package",
311
- "private",
312
- "protected",
313
- "public",
314
- "return",
315
- "short",
316
- "static",
317
- "super",
318
- "switch",
319
- "synchronized",
320
- "this",
321
- "throw",
322
- "throws",
323
- "transient",
324
- "true",
325
- "try",
326
- "typeof",
327
- "var",
328
- "void",
329
- "volatile",
330
- "while",
331
- "with",
332
- "yield",
333
- "Array",
334
- "Date",
335
- "hasOwnProperty",
336
- "Infinity",
337
- "isFinite",
338
- "isNaN",
339
- "isPrototypeOf",
340
- "length",
341
- "Math",
342
- "name",
343
- "NaN",
344
- "Number",
345
- "Object",
346
- "prototype",
347
- "String",
348
- "toString",
349
- "undefined",
350
- "valueOf"
351
- ]);
352
- /**
353
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
354
- *
355
- * @example
356
- * ```ts
357
- * isValidVarName('status') // true
358
- * isValidVarName('class') // false (reserved word)
359
- * isValidVarName('42foo') // false (starts with digit)
360
- * ```
361
- */
362
- function isValidVarName(name) {
363
- if (!name || reservedWords.has(name)) return false;
364
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
365
- }
366
- //#endregion
367
- //#region ../../internals/utils/src/urlPath.ts
368
- /**
369
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
370
- *
371
- * @example
372
- * const p = new URLPath('/pet/{petId}')
373
- * p.URL // '/pet/:petId'
374
- * p.template // '`/pet/${petId}`'
375
- */
376
- var URLPath = class {
377
- /**
378
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
379
- */
380
- path;
381
- #options;
382
- constructor(path, options = {}) {
383
- this.path = path;
384
- this.#options = options;
385
- }
386
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
387
- *
388
- * @example
389
- * ```ts
390
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
391
- * ```
392
- */
393
- get URL() {
394
- return this.toURLPath();
395
- }
396
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
397
- *
398
- * @example
399
- * ```ts
400
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
401
- * new URLPath('/pet/{petId}').isURL // false
402
- * ```
403
- */
404
- get isURL() {
405
- try {
406
- return !!new URL(this.path).href;
407
- } catch {
408
- return false;
409
- }
410
- }
411
- /**
412
- * Converts the OpenAPI path to a TypeScript template literal string.
413
- *
414
- * @example
415
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
416
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
417
- */
418
- get template() {
419
- return this.toTemplateString();
420
- }
421
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
422
- *
423
- * @example
424
- * ```ts
425
- * new URLPath('/pet/{petId}').object
426
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
427
- * ```
428
- */
429
- get object() {
430
- return this.toObject();
431
- }
432
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
433
- *
434
- * @example
435
- * ```ts
436
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
437
- * new URLPath('/pet').params // undefined
438
- * ```
439
- */
440
- get params() {
441
- return this.getParams();
442
- }
443
- #transformParam(raw) {
444
- const param = isValidVarName(raw) ? raw : camelCase(raw);
445
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
446
- }
447
- /**
448
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
449
- */
450
- #eachParam(fn) {
451
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
452
- const raw = match[1];
453
- fn(raw, this.#transformParam(raw));
454
- }
455
- }
456
- toObject({ type = "path", replacer, stringify } = {}) {
457
- const object = {
458
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
459
- params: this.getParams()
460
- };
461
- if (stringify) {
462
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
463
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
464
- return `{ url: '${object.url}' }`;
465
- }
466
- return object;
467
- }
468
- /**
469
- * Converts the OpenAPI path to a TypeScript template literal string.
470
- * An optional `replacer` can transform each extracted parameter name before interpolation.
471
- *
472
- * @example
473
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
474
- */
475
- toTemplateString({ prefix = "", replacer } = {}) {
476
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
477
- if (i % 2 === 0) return part;
478
- const param = this.#transformParam(part);
479
- return `\${${replacer ? replacer(param) : param}}`;
480
- }).join("")}\``;
481
- }
482
- /**
483
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
484
- * An optional `replacer` transforms each parameter name in both key and value positions.
485
- * Returns `undefined` when no path parameters are found.
486
- *
487
- * @example
488
- * ```ts
489
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
490
- * // { petId: 'petId', tagId: 'tagId' }
491
- * ```
492
- */
493
- getParams(replacer) {
494
- const params = {};
495
- this.#eachParam((_raw, param) => {
496
- const key = replacer ? replacer(param) : param;
497
- params[key] = key;
498
- });
499
- return Object.keys(params).length > 0 ? params : void 0;
500
- }
501
- /** Converts the OpenAPI path to Express-style colon syntax.
502
- *
503
- * @example
504
- * ```ts
505
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
506
- * ```
507
- */
508
- toURLPath() {
509
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
510
- }
511
- };
512
- //#endregion
513
251
  //#region src/createAdapter.ts
514
252
  /**
515
253
  * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
@@ -541,7 +279,7 @@ function createAdapter(build) {
541
279
  }
542
280
  //#endregion
543
281
  //#region package.json
544
- var version$1 = "5.0.0-beta.19";
282
+ var version$1 = "5.0.0-beta.20";
545
283
  //#endregion
546
284
  //#region src/createStorage.ts
547
285
  /**
@@ -815,7 +553,6 @@ async function setup(userConfig, options = {}) {
815
553
  ...userConfig,
816
554
  root: userConfig.root || process.cwd(),
817
555
  parsers: userConfig.parsers ?? [],
818
- adapter: userConfig.adapter,
819
556
  output: {
820
557
  format: false,
821
558
  lint: false,
@@ -830,7 +567,7 @@ async function setup(userConfig, options = {}) {
830
567
  } : void 0,
831
568
  plugins: userConfig.plugins ?? []
832
569
  };
833
- const driver = new PluginDriver(config, { hooks });
570
+ const driver = new KubbDriver(config, { hooks });
834
571
  const storage = createSourcesView(config.storage);
835
572
  const diagnosticInfo = getDiagnosticInfo();
836
573
  await hooks.emit("kubb:debug", {
@@ -845,6 +582,7 @@ async function setup(userConfig, options = {}) {
845
582
  ` • Storage: ${config.storage.name}`,
846
583
  ` • Formatter: ${userConfig.output?.format || "none"}`,
847
584
  ` • Linter: ${userConfig.output?.lint || "none"}`,
585
+ `Running adapter: ${config.adapter?.name || "none"}`,
848
586
  "Environment:",
849
587
  Object.entries(diagnosticInfo).map(([key, value]) => ` • ${key}: ${value}`).join("\n")
850
588
  ]
@@ -870,48 +608,7 @@ async function setup(userConfig, options = {}) {
870
608
  });
871
609
  await config.storage.clear(resolve(config.root, config.output.path));
872
610
  }
873
- const middlewareListeners = [];
874
- function registerMiddlewareHook(event, middlewareHooks) {
875
- const handler = middlewareHooks[event];
876
- if (handler) {
877
- hooks.on(event, handler);
878
- middlewareListeners.push([event, handler]);
879
- }
880
- }
881
- for (const middleware of config.middleware ?? []) for (const event of Object.keys(middleware.hooks)) registerMiddlewareHook(event, middleware.hooks);
882
- if (config.adapter) {
883
- const source = inputToAdapterSource(config);
884
- await hooks.emit("kubb:debug", {
885
- date: /* @__PURE__ */ new Date(),
886
- logs: [`Running adapter: ${config.adapter.name}`]
887
- });
888
- driver.adapter = config.adapter;
889
- if (config.adapter.count && config.adapter.stream) {
890
- const { schemas: schemaCount, operations: operationCount } = await config.adapter.count(source);
891
- if (schemaCount > 100) {
892
- driver.inputStreamNode = await config.adapter.stream(source);
893
- await hooks.emit("kubb:debug", {
894
- date: /* @__PURE__ */ new Date(),
895
- logs: [
896
- `✓ Adapter '${config.adapter.name}' streaming InputStreamNode`,
897
- ` • Schemas: ${schemaCount} (threshold: 100)`,
898
- ` • Operations: ${operationCount}`
899
- ]
900
- });
901
- }
902
- }
903
- if (!driver.inputStreamNode) {
904
- driver.inputNode = await config.adapter.parse(source);
905
- await hooks.emit("kubb:debug", {
906
- date: /* @__PURE__ */ new Date(),
907
- logs: [
908
- `✓ Adapter '${config.adapter.name}' resolved InputNode`,
909
- ` • Schemas: ${driver.inputNode.schemas.length}`,
910
- ` • Operations: ${driver.inputNode.operations.length}`
911
- ]
912
- });
913
- }
914
- }
611
+ await driver.setup();
915
612
  return {
916
613
  config,
917
614
  hooks,
@@ -922,262 +619,6 @@ async function setup(userConfig, options = {}) {
922
619
  };
923
620
  function dispose() {
924
621
  driver.dispose();
925
- for (const [event, handler] of middlewareListeners) hooks.off(event, handler);
926
- }
927
- }
928
- /**
929
- * Single-pass fan-out for streaming mode.
930
- *
931
- * Iterates `inputStreamNode.schemas` and `.operations` exactly once, distributing each
932
- * node to every plugin in parallel. This replaces the N-pass-per-plugin pattern (where
933
- * each plugin got its own `for await` iterator) with a single parse pass fanned to all
934
- * plugins — eliminating the N×parse-time overhead for multi-plugin builds.
935
- *
936
- * Each plugin still gets independent `plugin:start` / `plugin:end` events and its own
937
- * timing, but the schema and operation nodes are parsed only once total.
938
- */
939
- async function runPluginStreamHooks({ entries, driver, pluginTimings, failedPlugins }) {
940
- const inputStreamNode = driver.inputStreamNode;
941
- function resolveRendererFor(gen, state) {
942
- return gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
943
- }
944
- const states = entries.map(({ plugin, context, hrStart }) => {
945
- const { exclude, include, override } = plugin.options;
946
- const hasExclude = Array.isArray(exclude) && exclude.length > 0;
947
- const hasInclude = Array.isArray(include) && include.length > 0;
948
- const hasOverride = Array.isArray(override) && override.length > 0;
949
- return {
950
- plugin,
951
- generatorContext: {
952
- ...context,
953
- resolver: driver.getResolver(plugin.name)
954
- },
955
- generators: plugin.generators ?? [],
956
- hrStart,
957
- failed: false,
958
- error: void 0,
959
- optionsAreStatic: !hasExclude && !hasInclude && !hasOverride
960
- };
961
- });
962
- async function dispatchSchema(state, node) {
963
- if (state.failed) return;
964
- try {
965
- const { plugin, generatorContext, generators } = state;
966
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
967
- const { exclude, include, override } = plugin.options;
968
- const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
969
- options: plugin.options,
970
- exclude,
971
- include,
972
- override
973
- });
974
- if (options === null) return;
975
- const ctx = {
976
- ...generatorContext,
977
- options
978
- };
979
- for (const gen of generators) {
980
- if (!gen.schema) continue;
981
- const raw = gen.schema(transformedNode, ctx);
982
- const applied = applyHookResult({
983
- result: isPromise(raw) ? await raw : raw,
984
- driver,
985
- rendererFactory: resolveRendererFor(gen, state)
986
- });
987
- if (isPromise(applied)) await applied;
988
- }
989
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
990
- } catch (caughtError) {
991
- state.failed = true;
992
- state.error = caughtError;
993
- }
994
- }
995
- async function dispatchOperation(state, node) {
996
- if (state.failed) return;
997
- try {
998
- const { plugin, generatorContext, generators } = state;
999
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1000
- const { exclude, include, override } = plugin.options;
1001
- const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1002
- options: plugin.options,
1003
- exclude,
1004
- include,
1005
- override
1006
- });
1007
- if (options === null) return;
1008
- const ctx = {
1009
- ...generatorContext,
1010
- options
1011
- };
1012
- for (const gen of generators) {
1013
- if (!gen.operation) continue;
1014
- const raw = gen.operation(transformedNode, ctx);
1015
- const applied = applyHookResult({
1016
- result: isPromise(raw) ? await raw : raw,
1017
- driver,
1018
- rendererFactory: resolveRendererFor(gen, state)
1019
- });
1020
- if (isPromise(applied)) await applied;
1021
- }
1022
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1023
- } catch (caughtError) {
1024
- state.failed = true;
1025
- state.error = caughtError;
1026
- }
1027
- }
1028
- for await (const node of inputStreamNode.schemas) await Promise.all(states.map((state) => dispatchSchema(state, node)));
1029
- const collectedOperations = [];
1030
- for await (const node of inputStreamNode.operations) {
1031
- collectedOperations.push(node);
1032
- await Promise.all(states.map((state) => dispatchOperation(state, node)));
1033
- }
1034
- for (const state of states) {
1035
- if (!state.failed) try {
1036
- const { plugin, generatorContext, generators } = state;
1037
- const ctx = {
1038
- ...generatorContext,
1039
- options: plugin.options
1040
- };
1041
- for (const gen of generators) {
1042
- if (!gen.operations) continue;
1043
- await applyHookResult({
1044
- result: await gen.operations(collectedOperations, ctx),
1045
- driver,
1046
- rendererFactory: resolveRendererFor(gen, state)
1047
- });
1048
- }
1049
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
1050
- } catch (caughtError) {
1051
- state.failed = true;
1052
- state.error = caughtError;
1053
- }
1054
- const duration = getElapsedMs(state.hrStart);
1055
- pluginTimings.set(state.plugin.name, duration);
1056
- await driver.hooks.emit("kubb:plugin:end", {
1057
- plugin: state.plugin,
1058
- duration,
1059
- success: !state.failed,
1060
- ...state.failed && state.error ? { error: state.error } : {},
1061
- config: driver.config,
1062
- get files() {
1063
- return driver.fileManager.files;
1064
- },
1065
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1066
- });
1067
- if (state.failed && state.error) failedPlugins.add({
1068
- plugin: state.plugin,
1069
- error: state.error
1070
- });
1071
- await driver.hooks.emit("kubb:debug", {
1072
- date: /* @__PURE__ */ new Date(),
1073
- logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
1074
- });
1075
- }
1076
- }
1077
- /**
1078
- * Walks the AST and dispatches nodes to a plugin's direct AST hooks
1079
- * (`schema`, `operation`, `operations`).
1080
- *
1081
- * When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
1082
- * `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
1083
- * of top-level schema names transitively reachable from the included operations and skips
1084
- * schemas that fall outside that set. This ensures that component schemas referenced
1085
- * exclusively by excluded operations are not generated.
1086
- */
1087
- async function runPluginAstHooks(plugin, context) {
1088
- const { adapter, inputNode, resolver, driver } = context;
1089
- const { exclude, include, override } = plugin.options;
1090
- if (!adapter || !inputNode) throw new Error(`[${plugin.name}] No adapter found. Add an OAS adapter (e.g. adapterOas()) before this plugin in your Kubb config.`);
1091
- function resolveRenderer(gen) {
1092
- return gen.renderer === null ? void 0 : gen.renderer ?? plugin.renderer ?? context.config.renderer;
1093
- }
1094
- const generators = plugin.generators ?? [];
1095
- const collectedOperations = [];
1096
- const generatorContext = {
1097
- ...context,
1098
- resolver: driver.getResolver(plugin.name)
1099
- };
1100
- const operationFilterTypes = new Set([
1101
- "tag",
1102
- "operationId",
1103
- "path",
1104
- "method",
1105
- "contentType"
1106
- ]);
1107
- const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false;
1108
- const hasSchemaNameIncludes = include?.some(({ type }) => type === "schemaName") ?? false;
1109
- const allowedSchemaNames = (() => {
1110
- if (!hasOperationBasedIncludes || hasSchemaNameIncludes) return void 0;
1111
- return collectUsedSchemaNames(inputNode.operations.filter((op) => resolver.resolveOptions(op, {
1112
- options: plugin.options,
1113
- exclude,
1114
- include,
1115
- override
1116
- }) !== null), inputNode.schemas);
1117
- })();
1118
- await walk(inputNode, {
1119
- depth: "shallow",
1120
- async schema(node) {
1121
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1122
- if (allowedSchemaNames !== void 0 && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) return;
1123
- const options = resolver.resolveOptions(transformedNode, {
1124
- options: plugin.options,
1125
- exclude,
1126
- include,
1127
- override
1128
- });
1129
- if (options === null) return;
1130
- const ctx = {
1131
- ...generatorContext,
1132
- options
1133
- };
1134
- await Promise.all(generators.filter((gen) => gen.schema).map(async (gen) => {
1135
- return applyHookResult({
1136
- result: await gen.schema(transformedNode, ctx),
1137
- driver,
1138
- rendererFactory: resolveRenderer(gen)
1139
- });
1140
- }));
1141
- await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
1142
- },
1143
- async operation(node) {
1144
- const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1145
- const options = resolver.resolveOptions(transformedNode, {
1146
- options: plugin.options,
1147
- exclude,
1148
- include,
1149
- override
1150
- });
1151
- if (options === null) return;
1152
- collectedOperations.push(transformedNode);
1153
- const ctx = {
1154
- ...generatorContext,
1155
- options
1156
- };
1157
- await Promise.all(generators.filter((gen) => gen.operation).map(async (gen) => {
1158
- return applyHookResult({
1159
- result: await gen.operation(transformedNode, ctx),
1160
- driver,
1161
- rendererFactory: resolveRenderer(gen)
1162
- });
1163
- }));
1164
- await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1165
- }
1166
- });
1167
- if (collectedOperations.length > 0) {
1168
- const ctx = {
1169
- ...generatorContext,
1170
- options: plugin.options
1171
- };
1172
- for (const gen of generators) {
1173
- if (!gen.operations) continue;
1174
- await applyHookResult({
1175
- result: await gen.operations(collectedOperations, ctx),
1176
- driver,
1177
- rendererFactory: resolveRenderer(gen)
1178
- });
1179
- }
1180
- await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
1181
622
  }
1182
623
  }
1183
624
  async function safeBuild(setupResult) {
@@ -1190,8 +631,8 @@ async function safeBuild(setupResult) {
1190
631
  const config = driver.config;
1191
632
  const writtenPaths = /* @__PURE__ */ new Set();
1192
633
  const parsersMap = /* @__PURE__ */ new Map();
1193
- for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1194
634
  const fileProcessor = new FileProcessor();
635
+ for (const parser of config.parsers) if (parser.extNames) for (const extname of parser.extNames) parsersMap.set(extname, parser);
1195
636
  async function flushPendingFiles() {
1196
637
  const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path));
1197
638
  if (files.length === 0) return;
@@ -1204,116 +645,235 @@ async function safeBuild(setupResult) {
1204
645
  parsers: parsersMap,
1205
646
  extension: config.output.extension
1206
647
  });
648
+ const queue = [];
1207
649
  for (const { file, source, processed, total, percentage } of stream) {
1208
- await hooks.emit("kubb:file:processing:update", {
1209
- file,
1210
- source,
1211
- processed,
1212
- total,
1213
- percentage,
1214
- config
1215
- });
1216
- if (source) await storage.setItem(file.path, source);
1217
650
  writtenPaths.add(file.path);
651
+ queue.push((async () => {
652
+ await hooks.emit("kubb:file:processing:update", {
653
+ file,
654
+ source,
655
+ processed,
656
+ total,
657
+ percentage,
658
+ config
659
+ });
660
+ if (source) await storage.setItem(file.path, source);
661
+ })());
662
+ if (queue.length >= 50) await Promise.all(queue.splice(0));
1218
663
  }
664
+ await Promise.all(queue);
1219
665
  await hooks.emit("kubb:files:processing:end", { files });
1220
666
  await hooks.emit("kubb:debug", {
1221
667
  date: /* @__PURE__ */ new Date(),
1222
668
  logs: [`✓ File write process completed for ${files.length} files`]
1223
669
  });
1224
670
  }
671
+ async function dispatchOperationsToGenerators(generators, collectedOperations, ctx, rendererFor) {
672
+ for (const gen of generators) {
673
+ if (!gen.operations) continue;
674
+ await applyHookResult({
675
+ result: await gen.operations(collectedOperations, ctx),
676
+ driver,
677
+ rendererFactory: rendererFor(gen)
678
+ });
679
+ }
680
+ await driver.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
681
+ }
682
+ /**
683
+ * Single-pass fan-out: iterates all schemas and operations once, distributing each node
684
+ * to every generator-plugin in parallel. This replaces the N-pass-per-plugin pattern
685
+ * (each plugin getting its own iterator) with one parse pass fanned to all plugins,
686
+ * eliminating the N×parse-time overhead for multi-plugin builds.
687
+ */
688
+ async function runPlugins(entries) {
689
+ const { schemas, operations } = driver.inputNode;
690
+ const operationFilterTypes = new Set([
691
+ "tag",
692
+ "operationId",
693
+ "path",
694
+ "method",
695
+ "contentType"
696
+ ]);
697
+ const states = entries.map(({ plugin, context, hrStart }) => {
698
+ const { exclude, include, override } = plugin.options;
699
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
700
+ const hasInclude = Array.isArray(include) && include.length > 0;
701
+ const hasOverride = Array.isArray(override) && override.length > 0;
702
+ return {
703
+ plugin,
704
+ generatorContext: {
705
+ ...context,
706
+ resolver: driver.getResolver(plugin.name)
707
+ },
708
+ generators: plugin.generators ?? [],
709
+ hrStart,
710
+ failed: false,
711
+ error: void 0,
712
+ optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
713
+ allowedSchemaNames: void 0
714
+ };
715
+ });
716
+ const pruningStates = states.filter(({ plugin }) => {
717
+ const { include } = plugin.options;
718
+ return (include?.some(({ type }) => operationFilterTypes.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
719
+ });
720
+ if (pruningStates.length > 0) {
721
+ const allSchemas = [];
722
+ for await (const schema of schemas) allSchemas.push(schema);
723
+ const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
724
+ for await (const operation of operations) for (const state of pruningStates) {
725
+ const { exclude, include, override } = state.plugin.options;
726
+ if (state.generatorContext.resolver.resolveOptions(operation, {
727
+ options: state.plugin.options,
728
+ exclude,
729
+ include,
730
+ override
731
+ }) !== null) includedOpsByState.get(state)?.push(operation);
732
+ }
733
+ for (const state of pruningStates) state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas);
734
+ }
735
+ function resolveRendererFor(gen, state) {
736
+ return gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
737
+ }
738
+ async function dispatchSchema(state, node) {
739
+ if (state.failed) return;
740
+ try {
741
+ const { plugin, generatorContext, generators } = state;
742
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
743
+ if (state.allowedSchemaNames !== void 0 && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
744
+ const { exclude, include, override } = plugin.options;
745
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
746
+ options: plugin.options,
747
+ exclude,
748
+ include,
749
+ override
750
+ });
751
+ if (options === null) return;
752
+ const ctx = {
753
+ ...generatorContext,
754
+ options
755
+ };
756
+ for (const gen of generators) {
757
+ if (!gen.schema) continue;
758
+ const raw = gen.schema(transformedNode, ctx);
759
+ const applied = applyHookResult({
760
+ result: isPromise(raw) ? await raw : raw,
761
+ driver,
762
+ rendererFactory: resolveRendererFor(gen, state)
763
+ });
764
+ if (isPromise(applied)) await applied;
765
+ }
766
+ await driver.hooks.emit("kubb:generate:schema", transformedNode, ctx);
767
+ } catch (caughtError) {
768
+ state.failed = true;
769
+ state.error = caughtError;
770
+ }
771
+ }
772
+ async function dispatchOperation(state, node) {
773
+ if (state.failed) return;
774
+ try {
775
+ const { plugin, generatorContext, generators } = state;
776
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
777
+ const { exclude, include, override } = plugin.options;
778
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
779
+ options: plugin.options,
780
+ exclude,
781
+ include,
782
+ override
783
+ });
784
+ if (options === null) return;
785
+ const ctx = {
786
+ ...generatorContext,
787
+ options
788
+ };
789
+ for (const gen of generators) {
790
+ if (!gen.operation) continue;
791
+ const raw = gen.operation(transformedNode, ctx);
792
+ const applied = applyHookResult({
793
+ result: isPromise(raw) ? await raw : raw,
794
+ driver,
795
+ rendererFactory: resolveRendererFor(gen, state)
796
+ });
797
+ if (isPromise(applied)) await applied;
798
+ }
799
+ await driver.hooks.emit("kubb:generate:operation", transformedNode, ctx);
800
+ } catch (caughtError) {
801
+ state.failed = true;
802
+ state.error = caughtError;
803
+ }
804
+ }
805
+ await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
806
+ concurrency: 8,
807
+ flush: flushPendingFiles
808
+ });
809
+ const collectedOperations = [];
810
+ await forBatches(operations, (nodes) => {
811
+ collectedOperations.push(...nodes);
812
+ return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
813
+ }, {
814
+ concurrency: 8,
815
+ flush: flushPendingFiles
816
+ });
817
+ for (const state of states) {
818
+ if (!state.failed) try {
819
+ const { plugin, generatorContext, generators } = state;
820
+ await dispatchOperationsToGenerators(generators, collectedOperations, {
821
+ ...generatorContext,
822
+ options: plugin.options
823
+ }, (gen) => resolveRendererFor(gen, state));
824
+ } catch (caughtError) {
825
+ state.failed = true;
826
+ state.error = caughtError;
827
+ }
828
+ const duration = getElapsedMs(state.hrStart);
829
+ pluginTimings.set(state.plugin.name, duration);
830
+ await driver.hooks.emit("kubb:plugin:end", {
831
+ plugin: state.plugin,
832
+ duration,
833
+ success: !state.failed,
834
+ ...state.failed && state.error ? { error: state.error } : {},
835
+ config: driver.config,
836
+ get files() {
837
+ return driver.fileManager.files;
838
+ },
839
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
840
+ });
841
+ if (state.failed && state.error) failedPlugins.add({
842
+ plugin: state.plugin,
843
+ error: state.error
844
+ });
845
+ await driver.hooks.emit("kubb:debug", {
846
+ date: /* @__PURE__ */ new Date(),
847
+ logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
848
+ });
849
+ }
850
+ }
1225
851
  try {
1226
852
  await driver.emitSetupHooks();
1227
- if (driver.adapter && (driver.inputNode || driver.inputStreamNode)) await hooks.emit("kubb:build:start", {
853
+ if (driver.adapter && driver.inputNode) await hooks.emit("kubb:build:start", {
1228
854
  config,
1229
855
  adapter: driver.adapter,
1230
- inputNode: driver.inputNode ?? {
1231
- kind: "Input",
1232
- schemas: [],
1233
- operations: [],
1234
- meta: driver.inputStreamNode?.meta
1235
- },
856
+ meta: driver.inputNode.meta,
1236
857
  getPlugin: driver.getPlugin.bind(driver),
1237
858
  get files() {
1238
859
  return driver.fileManager.files;
1239
860
  },
1240
861
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1241
862
  });
1242
- if (driver.inputStreamNode) {
1243
- const streamPluginEntries = [];
1244
- for (const plugin of driver.plugins.values()) {
1245
- const context = driver.getContext(plugin);
1246
- const hrStart = process.hrtime();
1247
- await hooks.emit("kubb:plugin:start", { plugin });
1248
- await hooks.emit("kubb:debug", {
1249
- date: /* @__PURE__ */ new Date(),
1250
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1251
- });
1252
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
1253
- streamPluginEntries.push({
1254
- plugin,
1255
- context,
1256
- hrStart
1257
- });
1258
- continue;
1259
- }
1260
- const duration = getElapsedMs(hrStart);
1261
- pluginTimings.set(plugin.name, duration);
1262
- await hooks.emit("kubb:plugin:end", {
1263
- plugin,
1264
- duration,
1265
- success: true,
1266
- config,
1267
- get files() {
1268
- return driver.fileManager.files;
1269
- },
1270
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1271
- });
1272
- await hooks.emit("kubb:debug", {
1273
- date: /* @__PURE__ */ new Date(),
1274
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1275
- });
1276
- }
1277
- if (streamPluginEntries.length > 0) {
1278
- await runPluginStreamHooks({
1279
- entries: streamPluginEntries,
1280
- driver,
1281
- pluginTimings,
1282
- failedPlugins
1283
- });
1284
- await flushPendingFiles();
1285
- }
1286
- } else for (const plugin of driver.plugins.values()) {
863
+ const generatorPlugins = [];
864
+ for (const plugin of driver.plugins.values()) {
1287
865
  const context = driver.getContext(plugin);
1288
866
  const hrStart = process.hrtime();
1289
867
  try {
1290
- const timestamp = /* @__PURE__ */ new Date();
1291
868
  await hooks.emit("kubb:plugin:start", { plugin });
1292
- await hooks.emit("kubb:debug", {
1293
- date: timestamp,
1294
- logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1295
- });
1296
- if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) await runPluginAstHooks(plugin, context);
1297
- const duration = getElapsedMs(hrStart);
1298
- pluginTimings.set(plugin.name, duration);
1299
- await hooks.emit("kubb:plugin:end", {
1300
- plugin,
1301
- duration,
1302
- success: true,
1303
- config,
1304
- get files() {
1305
- return driver.fileManager.files;
1306
- },
1307
- upsertFile: (...files) => driver.fileManager.upsert(...files)
1308
- });
1309
869
  await hooks.emit("kubb:debug", {
1310
870
  date: /* @__PURE__ */ new Date(),
1311
- logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
871
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1312
872
  });
1313
873
  } catch (caughtError) {
1314
874
  const error = caughtError;
1315
- const errorTimestamp = /* @__PURE__ */ new Date();
1316
875
  const duration = getElapsedMs(hrStart);
876
+ pluginTimings.set(plugin.name, duration);
1317
877
  await hooks.emit("kubb:plugin:end", {
1318
878
  plugin,
1319
879
  duration,
@@ -1325,22 +885,51 @@ async function safeBuild(setupResult) {
1325
885
  },
1326
886
  upsertFile: (...files) => driver.fileManager.upsert(...files)
1327
887
  });
1328
- await hooks.emit("kubb:debug", {
1329
- date: errorTimestamp,
1330
- logs: [
1331
- "✗ Plugin start failed",
1332
- ` • Plugin Name: ${plugin.name}`,
1333
- ` • Error: ${error.constructor.name} - ${error.message}`,
1334
- " • Stack Trace:",
1335
- error.stack || "No stack trace available"
1336
- ]
1337
- });
1338
888
  failedPlugins.add({
1339
889
  plugin,
1340
890
  error
1341
891
  });
892
+ continue;
893
+ }
894
+ if (plugin.generators?.length || driver.hasEventGenerators(plugin.name)) {
895
+ generatorPlugins.push({
896
+ plugin,
897
+ context,
898
+ hrStart
899
+ });
900
+ continue;
1342
901
  }
1343
- await flushPendingFiles();
902
+ const duration = getElapsedMs(hrStart);
903
+ pluginTimings.set(plugin.name, duration);
904
+ await hooks.emit("kubb:plugin:end", {
905
+ plugin,
906
+ duration,
907
+ success: true,
908
+ config,
909
+ get files() {
910
+ return driver.fileManager.files;
911
+ },
912
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
913
+ });
914
+ await hooks.emit("kubb:debug", {
915
+ date: /* @__PURE__ */ new Date(),
916
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
917
+ });
918
+ }
919
+ if (generatorPlugins.length > 0) if (driver.inputNode) await withDrain(() => runPlugins(generatorPlugins), flushPendingFiles);
920
+ else for (const { plugin, hrStart } of generatorPlugins) {
921
+ const duration = getElapsedMs(hrStart);
922
+ pluginTimings.set(plugin.name, duration);
923
+ await hooks.emit("kubb:plugin:end", {
924
+ plugin,
925
+ duration,
926
+ success: true,
927
+ config,
928
+ get files() {
929
+ return driver.fileManager.files;
930
+ },
931
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
932
+ });
1344
933
  }
1345
934
  await hooks.emit("kubb:plugins:end", {
1346
935
  config,
@@ -1413,22 +1002,6 @@ function getDiagnosticInfo() {
1413
1002
  function isInputPath(config) {
1414
1003
  return typeof config?.input === "object" && config.input !== null && "path" in config.input;
1415
1004
  }
1416
- function inputToAdapterSource(config) {
1417
- const input = config.input;
1418
- if (!input) throw new Error("[kubb] input is required when using an adapter. Provide input.path or input.data in your config.");
1419
- if ("data" in input) return {
1420
- type: "data",
1421
- data: input.data
1422
- };
1423
- if (new URLPath(input.path).isURL) return {
1424
- type: "path",
1425
- path: input.path
1426
- };
1427
- return {
1428
- type: "path",
1429
- path: resolve(config.root, input.path)
1430
- };
1431
- }
1432
1005
  /**
1433
1006
  * Creates a Kubb instance bound to a single config entry.
1434
1007
  *
@@ -1661,6 +1234,6 @@ const memoryStorage = createStorage(() => {
1661
1234
  };
1662
1235
  });
1663
1236
  //#endregion
1664
- export { AsyncEventEmitter, FileManager, FileProcessor, PluginDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
1237
+ export { AsyncEventEmitter, FileManager, FileProcessor, KubbDriver, URLPath, ast, createAdapter, createKubb, createRenderer, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fsStorage, isInputPath, logLevel, memoryStorage };
1665
1238
 
1666
1239
  //# sourceMappingURL=index.js.map