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

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.
@@ -24,11 +24,155 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
+ let node_events = require("node:events");
27
28
  let node_path = require("node:path");
28
29
  node_path = __toESM(node_path, 1);
29
30
  let _kubb_ast = require("@kubb/ast");
30
31
  let fflate = require("fflate");
31
32
  let tinyexec = require("tinyexec");
33
+ //#region ../../internals/utils/src/errors.ts
34
+ /**
35
+ * Thrown when one or more errors occur during a Kubb build.
36
+ * Carries the full list of underlying errors on `errors`.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
41
+ * ```
42
+ */
43
+ var BuildError = class extends Error {
44
+ errors;
45
+ constructor(message, options) {
46
+ super(message, { cause: options.cause });
47
+ this.name = "BuildError";
48
+ this.errors = options.errors;
49
+ }
50
+ };
51
+ /**
52
+ * Coerces an unknown thrown value to an `Error` instance.
53
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * try { ... } catch(err) {
58
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
59
+ * }
60
+ * ```
61
+ */
62
+ function toError(value) {
63
+ return value instanceof Error ? value : new Error(String(value));
64
+ }
65
+ //#endregion
66
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
67
+ /**
68
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
69
+ * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
74
+ * emitter.on('build', async (name) => { console.log(name) })
75
+ * await emitter.emit('build', 'petstore') // all listeners awaited
76
+ * ```
77
+ */
78
+ var AsyncEventEmitter = class {
79
+ /**
80
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
81
+ * @default 10
82
+ */
83
+ constructor(maxListener = 10) {
84
+ this.#emitter.setMaxListeners(maxListener);
85
+ }
86
+ #emitter = new node_events.EventEmitter();
87
+ /**
88
+ * Emits `eventName` and awaits all registered listeners sequentially.
89
+ * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * await emitter.emit('build', 'petstore')
94
+ * ```
95
+ */
96
+ emit(eventName, ...eventArgs) {
97
+ const listeners = this.#emitter.listeners(eventName);
98
+ if (listeners.length === 0) return;
99
+ return this.#emitAll(eventName, listeners, eventArgs);
100
+ }
101
+ async #emitAll(eventName, listeners, eventArgs) {
102
+ for (const listener of listeners) try {
103
+ await listener(...eventArgs);
104
+ } catch (err) {
105
+ let serializedArgs;
106
+ try {
107
+ serializedArgs = JSON.stringify(eventArgs);
108
+ } catch {
109
+ serializedArgs = String(eventArgs);
110
+ }
111
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
112
+ }
113
+ }
114
+ /**
115
+ * Registers a persistent listener for `eventName`.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * emitter.on('build', async (name) => { console.log(name) })
120
+ * ```
121
+ */
122
+ on(eventName, handler) {
123
+ this.#emitter.on(eventName, handler);
124
+ }
125
+ /**
126
+ * Registers a one-shot listener that removes itself after the first invocation.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * emitter.onOnce('build', async (name) => { console.log(name) })
131
+ * ```
132
+ */
133
+ onOnce(eventName, handler) {
134
+ const wrapper = (...args) => {
135
+ this.off(eventName, wrapper);
136
+ return handler(...args);
137
+ };
138
+ this.on(eventName, wrapper);
139
+ }
140
+ /**
141
+ * Removes a previously registered listener.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * emitter.off('build', handler)
146
+ * ```
147
+ */
148
+ off(eventName, handler) {
149
+ this.#emitter.off(eventName, handler);
150
+ }
151
+ /**
152
+ * Returns the number of listeners registered for `eventName`.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * emitter.on('build', handler)
157
+ * emitter.listenerCount('build') // 1
158
+ * ```
159
+ */
160
+ listenerCount(eventName) {
161
+ return this.#emitter.listenerCount(eventName);
162
+ }
163
+ /**
164
+ * Removes all listeners from every event channel.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * emitter.removeAll()
169
+ * ```
170
+ */
171
+ removeAll() {
172
+ this.#emitter.removeAllListeners();
173
+ }
174
+ };
175
+ //#endregion
32
176
  //#region ../../internals/utils/src/casing.ts
33
177
  /**
34
178
  * Shared implementation for camelCase and PascalCase conversion.
@@ -93,6 +237,39 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
93
237
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
94
238
  }
95
239
  //#endregion
240
+ //#region ../../internals/utils/src/time.ts
241
+ /**
242
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
243
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * const start = process.hrtime()
248
+ * doWork()
249
+ * getElapsedMs(start) // 42.35
250
+ * ```
251
+ */
252
+ function getElapsedMs(hrStart) {
253
+ const [seconds, nanoseconds] = process.hrtime(hrStart);
254
+ const ms = seconds * 1e3 + nanoseconds / 1e6;
255
+ return Math.round(ms * 100) / 100;
256
+ }
257
+ /**
258
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * formatMs(250) // '250ms'
263
+ * formatMs(1500) // '1.50s'
264
+ * formatMs(90000) // '1m 30.0s'
265
+ * ```
266
+ */
267
+ function formatMs(ms) {
268
+ if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
269
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
270
+ return `${Math.round(ms)}ms`;
271
+ }
272
+ //#endregion
96
273
  //#region ../../internals/utils/src/promise.ts
97
274
  function* chunks(arr, size) {
98
275
  for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
@@ -135,22 +312,6 @@ async function forBatches(source, process, options) {
135
312
  if (flush) await flush();
136
313
  }
137
314
  }
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
315
  /** Returns `true` when `result` is a thenable `Promise`.
155
316
  *
156
317
  * @example
@@ -430,12 +591,13 @@ var URLPath = class {
430
591
  * @example
431
592
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
432
593
  */
433
- toTemplateString({ prefix = "", replacer } = {}) {
434
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
594
+ toTemplateString({ prefix, replacer } = {}) {
595
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
435
596
  if (i % 2 === 0) return part;
436
597
  const param = this.#transformParam(part);
437
598
  return `\${${replacer ? replacer(param) : param}}`;
438
- }).join("")}\``;
599
+ }).join("");
600
+ return `\`${prefix ?? ""}${result}\``;
439
601
  }
440
602
  /**
441
603
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -813,13 +975,13 @@ function buildDefaultBanner({ title, description, version, config }) {
813
975
  * @example Disabled default banner
814
976
  * ```ts
815
977
  * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
816
- * // → undefined
978
+ * // → null
817
979
  * ```
818
980
  */
819
981
  function defaultResolveBanner(meta, { output, config }) {
820
982
  if (typeof output?.banner === "function") return output.banner(meta);
821
983
  if (typeof output?.banner === "string") return output.banner;
822
- if (config.output.defaultBanner === false) return;
984
+ if (config.output.defaultBanner === false) return null;
823
985
  return buildDefaultBanner({
824
986
  title: meta?.title,
825
987
  version: meta?.version,
@@ -848,6 +1010,7 @@ function defaultResolveBanner(meta, { output, config }) {
848
1010
  function defaultResolveFooter(meta, { output }) {
849
1011
  if (typeof output?.footer === "function") return output.footer(meta);
850
1012
  if (typeof output?.footer === "string") return output.footer;
1013
+ return null;
851
1014
  }
852
1015
  /**
853
1016
  * Defines a resolver for a plugin, injecting built-in defaults for name casing,
@@ -958,111 +1121,241 @@ function mergeFile(a, b) {
958
1121
  ...a,
959
1122
  banner: b.banner,
960
1123
  footer: b.footer,
961
- sources: [...a.sources || [], ...b.sources || []],
962
- imports: [...a.imports || [], ...b.imports || []],
963
- exports: [...a.exports || [], ...b.exports || []]
1124
+ sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
1125
+ imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
1126
+ exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
964
1127
  };
965
1128
  }
966
- /**
967
- * Collapses a list of files so that duplicates sharing the same `path` are merged
968
- * in arrival order. Keeps the original order of first occurrence.
969
- */
970
- function mergeFilesByPath(files) {
971
- const merged = /* @__PURE__ */ new Map();
972
- for (const file of files) {
973
- const existing = merged.get(file.path);
974
- merged.set(file.path, existing ? mergeFile(existing, file) : file);
975
- }
976
- return merged;
1129
+ function isIndexPath(path) {
1130
+ return path.endsWith("/index.ts") || path === "index.ts";
1131
+ }
1132
+ function compareFiles(a, b) {
1133
+ const lenDiff = a.path.length - b.path.length;
1134
+ if (lenDiff !== 0) return lenDiff;
1135
+ const aIsIndex = isIndexPath(a.path);
1136
+ const bIsIndex = isIndexPath(b.path);
1137
+ if (aIsIndex && !bIsIndex) return 1;
1138
+ if (!aIsIndex && bIsIndex) return -1;
1139
+ return 0;
977
1140
  }
978
1141
  /**
979
- * In-memory file store for generated files.
980
- *
981
- * Files with the same `path` are merged sources, imports, and exports are concatenated.
982
- * The `files` getter returns all stored files sorted by path length (shortest first).
1142
+ * In-memory file store for generated files. Files sharing a `path` are merged
1143
+ * (sources/imports/exports concatenated). The `files` getter is sorted by
1144
+ * path length (barrel `index.ts` last within a bucket).
983
1145
  *
984
1146
  * @example
985
1147
  * ```ts
986
- * import { FileManager } from '@kubb/core'
987
- *
988
1148
  * const manager = new FileManager()
989
1149
  * manager.upsert(myFile)
990
- * console.log(manager.files) // all stored files
1150
+ * manager.files // sorted view
991
1151
  * ```
992
1152
  */
993
1153
  var FileManager = class {
994
1154
  #cache = /* @__PURE__ */ new Map();
995
- #filesCache = null;
1155
+ #sorted = null;
1156
+ #onUpsert = null;
996
1157
  /**
997
- * Adds one or more files. Incoming files with the same path are merged
998
- * (sources/imports/exports concatenated), but existing cache entries are
999
- * replaced use {@link upsert} when you want to merge into the cache too.
1158
+ * Registers a callback invoked with the resolved {@link FileNode} on every
1159
+ * `add` / `upsert`. Used by the build loop to track newly written files
1160
+ * without keeping its own scan-based diff. Single subscriber by design
1161
+ * setting again replaces the previous callback. Pass `null` to detach.
1000
1162
  */
1163
+ setOnUpsert(callback) {
1164
+ this.#onUpsert = callback;
1165
+ }
1001
1166
  add(...files) {
1002
1167
  return this.#store(files, false);
1003
1168
  }
1004
- /**
1005
- * Adds or merges one or more files.
1006
- * If a file with the same path already exists in the cache, its
1007
- * sources/imports/exports are merged into the incoming file.
1008
- */
1009
1169
  upsert(...files) {
1010
1170
  return this.#store(files, true);
1011
1171
  }
1012
1172
  #store(files, mergeExisting) {
1013
- const resolvedFiles = [];
1014
- for (const file of mergeFilesByPath(files).values()) {
1015
- const existing = mergeExisting ? this.#cache.get(file.path) : void 0;
1016
- const resolvedFile = (0, _kubb_ast.createFile)(existing ? mergeFile(existing, file) : file);
1017
- this.#cache.set(resolvedFile.path, resolvedFile);
1018
- resolvedFiles.push(resolvedFile);
1173
+ const batch = files.length > 1 ? this.#dedupe(files) : files;
1174
+ const resolved = [];
1175
+ for (const file of batch) {
1176
+ const existing = this.#cache.get(file.path);
1177
+ const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file);
1178
+ this.#cache.set(merged.path, merged);
1179
+ resolved.push(merged);
1180
+ this.#onUpsert?.(merged);
1181
+ }
1182
+ if (resolved.length > 0) this.#sorted = null;
1183
+ return resolved;
1184
+ }
1185
+ #dedupe(files) {
1186
+ const seen = /* @__PURE__ */ new Map();
1187
+ for (const file of files) {
1188
+ const prev = seen.get(file.path);
1189
+ seen.set(file.path, prev ? mergeFile(prev, file) : file);
1019
1190
  }
1020
- this.#filesCache = null;
1021
- return resolvedFiles;
1191
+ return [...seen.values()];
1022
1192
  }
1023
1193
  getByPath(path) {
1024
1194
  return this.#cache.get(path) ?? null;
1025
1195
  }
1026
1196
  deleteByPath(path) {
1027
- this.#cache.delete(path);
1028
- this.#filesCache = null;
1197
+ if (!this.#cache.delete(path)) return;
1198
+ this.#sorted = null;
1029
1199
  }
1030
1200
  clear() {
1031
1201
  this.#cache.clear();
1032
- this.#filesCache = null;
1202
+ this.#sorted = null;
1033
1203
  }
1034
1204
  /**
1035
- * Releases all stored files. Called by the core after `kubb:build:end` to
1036
- * free the per-plugin FileNode caches for the rest of the process lifetime.
1205
+ * Releases all stored files. Called by the core after `kubb:build:end`.
1037
1206
  */
1038
1207
  dispose() {
1039
1208
  this.clear();
1209
+ this.#onUpsert = null;
1040
1210
  }
1041
1211
  [Symbol.dispose]() {
1042
1212
  this.dispose();
1043
1213
  }
1044
1214
  /**
1045
- * All stored files, sorted by path length (shorter paths first).
1215
+ * All stored files in stable sort order (shortest path first, barrel files
1216
+ * last within a length bucket). Returns a cached view — do not mutate.
1046
1217
  */
1047
1218
  get files() {
1048
- if (this.#filesCache) return this.#filesCache;
1049
- this.#filesCache = [...this.#cache.values()].sort((a, b) => {
1050
- const lenDiff = a.path.length - b.path.length;
1051
- if (lenDiff !== 0) return lenDiff;
1052
- const aIsIndex = a.path.endsWith("/index.ts") || a.path === "index.ts";
1053
- const bIsIndex = b.path.endsWith("/index.ts") || b.path === "index.ts";
1054
- if (aIsIndex && !bIsIndex) return 1;
1055
- if (!aIsIndex && bIsIndex) return -1;
1056
- return 0;
1219
+ return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
1220
+ }
1221
+ };
1222
+ //#endregion
1223
+ //#region src/FileProcessor.ts
1224
+ function joinSources(file) {
1225
+ const sources = file.sources;
1226
+ if (sources.length === 0) return "";
1227
+ const parts = [];
1228
+ for (const source of sources) {
1229
+ const s = (0, _kubb_ast.extractStringsFromNodes)(source.nodes);
1230
+ if (s) parts.push(s);
1231
+ }
1232
+ return parts.join("\n\n");
1233
+ }
1234
+ /**
1235
+ * Converts a single file to a string using the registered parsers.
1236
+ * Falls back to joining source values when no matching parser is found.
1237
+ *
1238
+ * @internal
1239
+ */
1240
+ var FileProcessor = class {
1241
+ events = new AsyncEventEmitter();
1242
+ parse(file, { parsers, extension } = {}) {
1243
+ const parseExtName = extension?.[file.extname] || void 0;
1244
+ if (!parsers || !file.extname) return joinSources(file);
1245
+ const parser = parsers.get(file.extname);
1246
+ if (!parser) return joinSources(file);
1247
+ return parser.parse(file, { extname: parseExtName });
1248
+ }
1249
+ *stream(files, options = {}) {
1250
+ const total = files.length;
1251
+ if (total === 0) return;
1252
+ let processed = 0;
1253
+ for (const file of files) {
1254
+ const source = this.parse(file, options);
1255
+ processed++;
1256
+ yield {
1257
+ file,
1258
+ source,
1259
+ processed,
1260
+ total,
1261
+ percentage: processed / total * 100
1262
+ };
1263
+ }
1264
+ }
1265
+ async run(files, options = {}) {
1266
+ await this.events.emit("start", files);
1267
+ for (const { file, source, processed, total, percentage } of this.stream(files, options)) await this.events.emit("update", {
1268
+ file,
1269
+ source,
1270
+ processed,
1271
+ percentage,
1272
+ total
1057
1273
  });
1058
- return this.#filesCache;
1274
+ await this.events.emit("end", files);
1275
+ return files;
1276
+ }
1277
+ /**
1278
+ * Clears all registered event listeners.
1279
+ */
1280
+ dispose() {
1281
+ this.events.removeAll();
1282
+ }
1283
+ [Symbol.dispose]() {
1284
+ this.dispose();
1059
1285
  }
1060
1286
  };
1061
1287
  //#endregion
1288
+ //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
1289
+ function _usingCtx() {
1290
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
1291
+ var n = Error();
1292
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
1293
+ };
1294
+ var e = {};
1295
+ var n = [];
1296
+ function using(r, e) {
1297
+ if (null != e) {
1298
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
1299
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
1300
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
1301
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
1302
+ t && (o = function o() {
1303
+ try {
1304
+ t.call(e);
1305
+ } catch (r) {
1306
+ return Promise.reject(r);
1307
+ }
1308
+ }), n.push({
1309
+ v: e,
1310
+ d: o,
1311
+ a: r
1312
+ });
1313
+ } else r && n.push({
1314
+ d: e,
1315
+ a: r
1316
+ });
1317
+ return e;
1318
+ }
1319
+ return {
1320
+ e,
1321
+ u: using.bind(null, !1),
1322
+ a: using.bind(null, !0),
1323
+ d: function d() {
1324
+ var o;
1325
+ var t = this.e;
1326
+ var s = 0;
1327
+ function next() {
1328
+ for (; o = n.pop();) try {
1329
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
1330
+ if (o.d) {
1331
+ var r = o.d.call(o.v);
1332
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
1333
+ } else s |= 1;
1334
+ } catch (r) {
1335
+ return err(r);
1336
+ }
1337
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
1338
+ if (t !== e) throw t;
1339
+ }
1340
+ function err(n) {
1341
+ return t = t !== e ? new r(n, t) : n, next();
1342
+ }
1343
+ return next();
1344
+ }
1345
+ };
1346
+ }
1347
+ //#endregion
1062
1348
  //#region src/KubbDriver.ts
1063
1349
  function enforceOrder(enforce) {
1064
1350
  return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
1065
1351
  }
1352
+ const OPERATION_FILTER_TYPES = new Set([
1353
+ "tag",
1354
+ "operationId",
1355
+ "path",
1356
+ "method",
1357
+ "contentType"
1358
+ ]);
1066
1359
  var KubbDriver = class KubbDriver {
1067
1360
  config;
1068
1361
  options;
@@ -1082,8 +1375,8 @@ var KubbDriver = class KubbDriver {
1082
1375
  * The streaming `InputStreamNode` produced by the adapter.
1083
1376
  * Always set after adapter setup — parse-only adapters are wrapped automatically.
1084
1377
  */
1085
- inputNode = void 0;
1086
- adapter = void 0;
1378
+ inputNode = null;
1379
+ adapter = null;
1087
1380
  /**
1088
1381
  * Studio session state, kept together so `dispose()` can reset it atomically.
1089
1382
  *
@@ -1094,9 +1387,9 @@ var KubbDriver = class KubbDriver {
1094
1387
  * per studio session, even when `openInStudio()` is called multiple times.
1095
1388
  */
1096
1389
  #studio = {
1097
- source: void 0,
1390
+ source: null,
1098
1391
  isOpen: false,
1099
- inputNode: void 0
1392
+ inputNode: null
1100
1393
  };
1101
1394
  #middlewareListeners = [];
1102
1395
  /**
@@ -1105,6 +1398,7 @@ var KubbDriver = class KubbDriver {
1105
1398
  * add files; this property gives direct read/write access when needed.
1106
1399
  */
1107
1400
  fileManager = new FileManager();
1401
+ #fileProcessor = new FileProcessor();
1108
1402
  plugins = /* @__PURE__ */ new Map();
1109
1403
  /**
1110
1404
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
@@ -1117,7 +1411,7 @@ var KubbDriver = class KubbDriver {
1117
1411
  constructor(config, options) {
1118
1412
  this.config = config;
1119
1413
  this.options = options;
1120
- this.adapter = config.adapter;
1414
+ this.adapter = config.adapter ?? null;
1121
1415
  }
1122
1416
  async setup() {
1123
1417
  const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
@@ -1328,6 +1622,344 @@ var KubbDriver = class KubbDriver {
1328
1622
  return this.#eventGeneratorPlugins.has(pluginName);
1329
1623
  }
1330
1624
  /**
1625
+ * Runs the full plugin pipeline. Returns timings/failures collected so far even
1626
+ * when an outer hook throws — the orchestrator preserves partial state by capturing
1627
+ * the error into `error` instead of propagating.
1628
+ */
1629
+ async run({ storage }) {
1630
+ const hooks = this.hooks;
1631
+ const config = this.config;
1632
+ const failedPlugins = /* @__PURE__ */ new Set();
1633
+ const pluginTimings = /* @__PURE__ */ new Map();
1634
+ const parsersMap = /* @__PURE__ */ new Map();
1635
+ for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
1636
+ const pendingFiles = /* @__PURE__ */ new Map();
1637
+ this.fileManager.setOnUpsert((file) => {
1638
+ pendingFiles.set(file.path, file);
1639
+ });
1640
+ try {
1641
+ const flushPending = async () => {
1642
+ if (pendingFiles.size === 0) return;
1643
+ const files = [...pendingFiles.values()];
1644
+ pendingFiles.clear();
1645
+ await hooks.emit("kubb:debug", {
1646
+ date: /* @__PURE__ */ new Date(),
1647
+ logs: [`Writing ${files.length} files...`]
1648
+ });
1649
+ await hooks.emit("kubb:files:processing:start", { files });
1650
+ const items = [...this.#fileProcessor.stream(files, {
1651
+ parsers: parsersMap,
1652
+ extension: config.output.extension
1653
+ })];
1654
+ await hooks.emit("kubb:files:processing:update", { files: items.map(({ file, source, processed, total, percentage }) => ({
1655
+ file,
1656
+ source,
1657
+ processed,
1658
+ total,
1659
+ percentage,
1660
+ config
1661
+ })) });
1662
+ const queue = [];
1663
+ for (const { file, source } of items) if (source) {
1664
+ queue.push(storage.setItem(file.path, source));
1665
+ if (queue.length >= 50) await Promise.all(queue.splice(0));
1666
+ }
1667
+ await Promise.all(queue);
1668
+ await hooks.emit("kubb:files:processing:end", { files });
1669
+ await hooks.emit("kubb:debug", {
1670
+ date: /* @__PURE__ */ new Date(),
1671
+ logs: [`✓ File write process completed for ${files.length} files`]
1672
+ });
1673
+ };
1674
+ await this.emitSetupHooks();
1675
+ if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
1676
+ config,
1677
+ adapter: this.adapter,
1678
+ meta: this.inputNode.meta,
1679
+ getPlugin: this.getPlugin.bind(this)
1680
+ }, this.#filesPayload()));
1681
+ const generatorPlugins = [];
1682
+ for (const plugin of this.plugins.values()) {
1683
+ const context = this.getContext(plugin);
1684
+ const hrStart = process.hrtime();
1685
+ try {
1686
+ await hooks.emit("kubb:plugin:start", { plugin });
1687
+ await hooks.emit("kubb:debug", {
1688
+ date: /* @__PURE__ */ new Date(),
1689
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1690
+ });
1691
+ } catch (caughtError) {
1692
+ const error = caughtError;
1693
+ const duration = getElapsedMs(hrStart);
1694
+ pluginTimings.set(plugin.name, duration);
1695
+ await this.#emitPluginEnd({
1696
+ plugin,
1697
+ duration,
1698
+ success: false,
1699
+ error
1700
+ });
1701
+ failedPlugins.add({
1702
+ plugin,
1703
+ error
1704
+ });
1705
+ continue;
1706
+ }
1707
+ if (plugin.generators?.length || this.hasEventGenerators(plugin.name)) {
1708
+ generatorPlugins.push({
1709
+ plugin,
1710
+ context,
1711
+ hrStart
1712
+ });
1713
+ continue;
1714
+ }
1715
+ const duration = getElapsedMs(hrStart);
1716
+ pluginTimings.set(plugin.name, duration);
1717
+ await this.#emitPluginEnd({
1718
+ plugin,
1719
+ duration,
1720
+ success: true
1721
+ });
1722
+ await hooks.emit("kubb:debug", {
1723
+ date: /* @__PURE__ */ new Date(),
1724
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1725
+ });
1726
+ }
1727
+ if (generatorPlugins.length > 0) if (this.inputNode) {
1728
+ const { timings, failed } = await this.#runGenerators(generatorPlugins, flushPending);
1729
+ await flushPending();
1730
+ for (const [name, duration] of timings) pluginTimings.set(name, duration);
1731
+ for (const entry of failed) failedPlugins.add(entry);
1732
+ } else for (const { plugin, hrStart } of generatorPlugins) {
1733
+ const duration = getElapsedMs(hrStart);
1734
+ pluginTimings.set(plugin.name, duration);
1735
+ await this.#emitPluginEnd({
1736
+ plugin,
1737
+ duration,
1738
+ success: true
1739
+ });
1740
+ }
1741
+ await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
1742
+ await flushPending();
1743
+ const files = this.fileManager.files;
1744
+ await hooks.emit("kubb:build:end", {
1745
+ files,
1746
+ config,
1747
+ outputDir: (0, node_path.resolve)(config.root, config.output.path)
1748
+ });
1749
+ return {
1750
+ failedPlugins,
1751
+ pluginTimings
1752
+ };
1753
+ } catch (caughtError) {
1754
+ return {
1755
+ failedPlugins,
1756
+ pluginTimings,
1757
+ error: caughtError
1758
+ };
1759
+ } finally {
1760
+ this.fileManager.setOnUpsert(null);
1761
+ }
1762
+ }
1763
+ #filesPayload() {
1764
+ const driver = this;
1765
+ return {
1766
+ get files() {
1767
+ return driver.fileManager.files;
1768
+ },
1769
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1770
+ };
1771
+ }
1772
+ #emitPluginEnd({ plugin, duration, success, error }) {
1773
+ return this.hooks.emit("kubb:plugin:end", Object.assign({
1774
+ plugin,
1775
+ duration,
1776
+ success,
1777
+ ...error ? { error } : {},
1778
+ config: this.config
1779
+ }, this.#filesPayload()));
1780
+ }
1781
+ async #runGenerators(entries, flushPending) {
1782
+ const timings = /* @__PURE__ */ new Map();
1783
+ const failed = /* @__PURE__ */ new Set();
1784
+ const driver = this;
1785
+ const { schemas, operations } = this.inputNode;
1786
+ const states = entries.map(({ plugin, context, hrStart }) => {
1787
+ const { exclude, include, override } = plugin.options;
1788
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
1789
+ const hasInclude = Array.isArray(include) && include.length > 0;
1790
+ const hasOverride = Array.isArray(override) && override.length > 0;
1791
+ return {
1792
+ plugin,
1793
+ generatorContext: {
1794
+ ...context,
1795
+ resolver: this.getResolver(plugin.name)
1796
+ },
1797
+ generators: plugin.generators ?? [],
1798
+ hrStart,
1799
+ failed: false,
1800
+ error: null,
1801
+ optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
1802
+ allowedSchemaNames: null
1803
+ };
1804
+ });
1805
+ const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
1806
+ const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
1807
+ const pruningStates = states.filter(({ plugin }) => {
1808
+ const { include } = plugin.options;
1809
+ return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
1810
+ });
1811
+ if (pruningStates.length > 0) {
1812
+ const allSchemas = [];
1813
+ for await (const schema of schemas) allSchemas.push(schema);
1814
+ const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
1815
+ for await (const operation of operations) for (const state of pruningStates) {
1816
+ const { exclude, include, override } = state.plugin.options;
1817
+ if (state.generatorContext.resolver.resolveOptions(operation, {
1818
+ options: state.plugin.options,
1819
+ exclude,
1820
+ include,
1821
+ override
1822
+ }) !== null) includedOpsByState.get(state)?.push(operation);
1823
+ }
1824
+ for (const state of pruningStates) {
1825
+ state.allowedSchemaNames = (0, _kubb_ast.collectUsedSchemaNames)(includedOpsByState.get(state) ?? [], allSchemas);
1826
+ includedOpsByState.delete(state);
1827
+ }
1828
+ }
1829
+ const resolveRendererFor = (gen, state) => gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
1830
+ const dispatchSchema = async (state, node) => {
1831
+ if (state.failed) return;
1832
+ try {
1833
+ const { plugin, generatorContext, generators } = state;
1834
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1835
+ if (state.allowedSchemaNames !== null && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
1836
+ const { exclude, include, override } = plugin.options;
1837
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1838
+ options: plugin.options,
1839
+ exclude,
1840
+ include,
1841
+ override
1842
+ });
1843
+ if (options === null) return;
1844
+ const ctx = {
1845
+ ...generatorContext,
1846
+ options
1847
+ };
1848
+ for (const gen of generators) {
1849
+ if (!gen.schema) continue;
1850
+ const raw = gen.schema(transformedNode, ctx);
1851
+ const applied = applyHookResult({
1852
+ result: isPromise(raw) ? await raw : raw,
1853
+ driver,
1854
+ rendererFactory: resolveRendererFor(gen, state)
1855
+ });
1856
+ if (isPromise(applied)) await applied;
1857
+ }
1858
+ if (emitsSchemaHook) await this.hooks.emit("kubb:generate:schema", transformedNode, ctx);
1859
+ } catch (caughtError) {
1860
+ state.failed = true;
1861
+ state.error = caughtError;
1862
+ }
1863
+ };
1864
+ const dispatchOperation = async (state, node) => {
1865
+ if (state.failed) return;
1866
+ try {
1867
+ const { plugin, generatorContext, generators } = state;
1868
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1869
+ const { exclude, include, override } = plugin.options;
1870
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1871
+ options: plugin.options,
1872
+ exclude,
1873
+ include,
1874
+ override
1875
+ });
1876
+ if (options === null) return;
1877
+ const ctx = {
1878
+ ...generatorContext,
1879
+ options
1880
+ };
1881
+ for (const gen of generators) {
1882
+ if (!gen.operation) continue;
1883
+ const raw = gen.operation(transformedNode, ctx);
1884
+ const applied = applyHookResult({
1885
+ result: isPromise(raw) ? await raw : raw,
1886
+ driver,
1887
+ rendererFactory: resolveRendererFor(gen, state)
1888
+ });
1889
+ if (isPromise(applied)) await applied;
1890
+ }
1891
+ if (emitsOperationHook) await this.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1892
+ } catch (caughtError) {
1893
+ state.failed = true;
1894
+ state.error = caughtError;
1895
+ }
1896
+ };
1897
+ const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((s) => s.generators.some((g) => !!g.operations));
1898
+ const collectedOperations = needsCollectedOperations ? [] : void 0;
1899
+ await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
1900
+ concurrency: 8,
1901
+ flush: flushPending
1902
+ });
1903
+ await forBatches(operations, (nodes) => {
1904
+ if (needsCollectedOperations) collectedOperations.push(...nodes);
1905
+ return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
1906
+ }, {
1907
+ concurrency: 8,
1908
+ flush: flushPending
1909
+ });
1910
+ for (const state of states) {
1911
+ if (!state.failed && needsCollectedOperations) try {
1912
+ const { plugin, generatorContext, generators } = state;
1913
+ const ctx = {
1914
+ ...generatorContext,
1915
+ options: plugin.options
1916
+ };
1917
+ const pluginOperations = state.optionsAreStatic ? collectedOperations : collectedOperations.filter((node) => {
1918
+ const transformed = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1919
+ const { exclude, include, override } = plugin.options;
1920
+ return generatorContext.resolver.resolveOptions(transformed, {
1921
+ options: plugin.options,
1922
+ exclude,
1923
+ include,
1924
+ override
1925
+ }) !== null;
1926
+ });
1927
+ for (const gen of generators) {
1928
+ if (!gen.operations) continue;
1929
+ await applyHookResult({
1930
+ result: await gen.operations(pluginOperations, ctx),
1931
+ driver,
1932
+ rendererFactory: resolveRendererFor(gen, state)
1933
+ });
1934
+ }
1935
+ await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
1936
+ } catch (caughtError) {
1937
+ state.failed = true;
1938
+ state.error = caughtError;
1939
+ }
1940
+ const duration = getElapsedMs(state.hrStart);
1941
+ timings.set(state.plugin.name, duration);
1942
+ await this.#emitPluginEnd({
1943
+ plugin: state.plugin,
1944
+ duration,
1945
+ success: !state.failed,
1946
+ error: state.failed && state.error ? state.error : void 0
1947
+ });
1948
+ if (state.failed && state.error) failed.add({
1949
+ plugin: state.plugin,
1950
+ error: state.error
1951
+ });
1952
+ await this.hooks.emit("kubb:debug", {
1953
+ date: /* @__PURE__ */ new Date(),
1954
+ logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
1955
+ });
1956
+ }
1957
+ return {
1958
+ timings,
1959
+ failed
1960
+ };
1961
+ }
1962
+ /**
1331
1963
  * Unregisters all plugin lifecycle listeners from the shared event emitter.
1332
1964
  * Called at the end of a build to prevent listener leaks across repeated builds.
1333
1965
  *
@@ -1340,11 +1972,12 @@ var KubbDriver = class KubbDriver {
1340
1972
  this.#resolvers.clear();
1341
1973
  this.#defaultResolvers.clear();
1342
1974
  this.fileManager.dispose();
1343
- this.inputNode = void 0;
1975
+ this.#fileProcessor.dispose();
1976
+ this.inputNode = null;
1344
1977
  this.#studio = {
1345
- source: void 0,
1978
+ source: null,
1346
1979
  isOpen: false,
1347
- inputNode: void 0
1980
+ inputNode: null
1348
1981
  };
1349
1982
  for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
1350
1983
  }
@@ -1464,10 +2097,15 @@ function applyHookResult({ result, driver, rendererFactory }) {
1464
2097
  }
1465
2098
  if (!rendererFactory) return;
1466
2099
  const renderer = rendererFactory();
1467
- if (renderer.stream) {
1468
- for (const file of renderer.stream(result)) driver.fileManager.upsert(file);
1469
- renderer.unmount();
2100
+ if (renderer.stream) try {
2101
+ var _usingCtx$1 = _usingCtx();
2102
+ const r = _usingCtx$1.u(renderer);
2103
+ for (const file of r.stream(result)) driver.fileManager.upsert(file);
1470
2104
  return;
2105
+ } catch (_) {
2106
+ _usingCtx$1.e = _;
2107
+ } finally {
2108
+ _usingCtx$1.d();
1471
2109
  }
1472
2110
  return applyAsyncRender({
1473
2111
  renderer,
@@ -1476,9 +2114,16 @@ function applyHookResult({ result, driver, rendererFactory }) {
1476
2114
  });
1477
2115
  }
1478
2116
  async function applyAsyncRender({ renderer, result, driver }) {
1479
- await renderer.render(result);
1480
- driver.fileManager.upsert(...renderer.files);
1481
- renderer.unmount();
2117
+ try {
2118
+ var _usingCtx3 = _usingCtx();
2119
+ const r = _usingCtx3.u(renderer);
2120
+ await r.render(result);
2121
+ driver.fileManager.upsert(...r.files);
2122
+ } catch (_) {
2123
+ _usingCtx3.e = _;
2124
+ } finally {
2125
+ _usingCtx3.d();
2126
+ }
1482
2127
  }
1483
2128
  function inputToAdapterSource(config) {
1484
2129
  const input = config.input;
@@ -1497,6 +2142,18 @@ function inputToAdapterSource(config) {
1497
2142
  };
1498
2143
  }
1499
2144
  //#endregion
2145
+ Object.defineProperty(exports, "AsyncEventEmitter", {
2146
+ enumerable: true,
2147
+ get: function() {
2148
+ return AsyncEventEmitter;
2149
+ }
2150
+ });
2151
+ Object.defineProperty(exports, "BuildError", {
2152
+ enumerable: true,
2153
+ get: function() {
2154
+ return BuildError;
2155
+ }
2156
+ });
1500
2157
  Object.defineProperty(exports, "DEFAULT_BANNER", {
1501
2158
  enumerable: true,
1502
2159
  get: function() {
@@ -1521,6 +2178,12 @@ Object.defineProperty(exports, "FileManager", {
1521
2178
  return FileManager;
1522
2179
  }
1523
2180
  });
2181
+ Object.defineProperty(exports, "FileProcessor", {
2182
+ enumerable: true,
2183
+ get: function() {
2184
+ return FileProcessor;
2185
+ }
2186
+ });
1524
2187
  Object.defineProperty(exports, "KubbDriver", {
1525
2188
  enumerable: true,
1526
2189
  get: function() {
@@ -1545,6 +2208,12 @@ Object.defineProperty(exports, "__toESM", {
1545
2208
  return __toESM;
1546
2209
  }
1547
2210
  });
2211
+ Object.defineProperty(exports, "_usingCtx", {
2212
+ enumerable: true,
2213
+ get: function() {
2214
+ return _usingCtx;
2215
+ }
2216
+ });
1548
2217
  Object.defineProperty(exports, "applyHookResult", {
1549
2218
  enumerable: true,
1550
2219
  get: function() {
@@ -1563,29 +2232,11 @@ Object.defineProperty(exports, "defineResolver", {
1563
2232
  return defineResolver;
1564
2233
  }
1565
2234
  });
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
- });
1578
2235
  Object.defineProperty(exports, "logLevel", {
1579
2236
  enumerable: true,
1580
2237
  get: function() {
1581
2238
  return logLevel;
1582
2239
  }
1583
2240
  });
1584
- Object.defineProperty(exports, "withDrain", {
1585
- enumerable: true,
1586
- get: function() {
1587
- return withDrain;
1588
- }
1589
- });
1590
2241
 
1591
- //# sourceMappingURL=KubbDriver-BXSnJ3qM.cjs.map
2242
+ //# sourceMappingURL=KubbDriver-DLha_xyo.cjs.map