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

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
@@ -813,13 +974,13 @@ function buildDefaultBanner({ title, description, version, config }) {
813
974
  * @example Disabled default banner
814
975
  * ```ts
815
976
  * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
816
- * // → undefined
977
+ * // → null
817
978
  * ```
818
979
  */
819
980
  function defaultResolveBanner(meta, { output, config }) {
820
981
  if (typeof output?.banner === "function") return output.banner(meta);
821
982
  if (typeof output?.banner === "string") return output.banner;
822
- if (config.output.defaultBanner === false) return;
983
+ if (config.output.defaultBanner === false) return null;
823
984
  return buildDefaultBanner({
824
985
  title: meta?.title,
825
986
  version: meta?.version,
@@ -848,6 +1009,7 @@ function defaultResolveBanner(meta, { output, config }) {
848
1009
  function defaultResolveFooter(meta, { output }) {
849
1010
  if (typeof output?.footer === "function") return output.footer(meta);
850
1011
  if (typeof output?.footer === "string") return output.footer;
1012
+ return null;
851
1013
  }
852
1014
  /**
853
1015
  * Defines a resolver for a plugin, injecting built-in defaults for name casing,
@@ -958,111 +1120,241 @@ function mergeFile(a, b) {
958
1120
  ...a,
959
1121
  banner: b.banner,
960
1122
  footer: b.footer,
961
- sources: [...a.sources || [], ...b.sources || []],
962
- imports: [...a.imports || [], ...b.imports || []],
963
- exports: [...a.exports || [], ...b.exports || []]
1123
+ sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
1124
+ imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
1125
+ exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
964
1126
  };
965
1127
  }
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;
1128
+ function isIndexPath(path) {
1129
+ return path.endsWith("/index.ts") || path === "index.ts";
1130
+ }
1131
+ function compareFiles(a, b) {
1132
+ const lenDiff = a.path.length - b.path.length;
1133
+ if (lenDiff !== 0) return lenDiff;
1134
+ const aIsIndex = isIndexPath(a.path);
1135
+ const bIsIndex = isIndexPath(b.path);
1136
+ if (aIsIndex && !bIsIndex) return 1;
1137
+ if (!aIsIndex && bIsIndex) return -1;
1138
+ return 0;
977
1139
  }
978
1140
  /**
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).
1141
+ * In-memory file store for generated files. Files sharing a `path` are merged
1142
+ * (sources/imports/exports concatenated). The `files` getter is sorted by
1143
+ * path length (barrel `index.ts` last within a bucket).
983
1144
  *
984
1145
  * @example
985
1146
  * ```ts
986
- * import { FileManager } from '@kubb/core'
987
- *
988
1147
  * const manager = new FileManager()
989
1148
  * manager.upsert(myFile)
990
- * console.log(manager.files) // all stored files
1149
+ * manager.files // sorted view
991
1150
  * ```
992
1151
  */
993
1152
  var FileManager = class {
994
1153
  #cache = /* @__PURE__ */ new Map();
995
- #filesCache = null;
1154
+ #sorted = null;
1155
+ #onUpsert = null;
996
1156
  /**
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.
1157
+ * Registers a callback invoked with the resolved {@link FileNode} on every
1158
+ * `add` / `upsert`. Used by the build loop to track newly written files
1159
+ * without keeping its own scan-based diff. Single subscriber by design
1160
+ * setting again replaces the previous callback. Pass `null` to detach.
1000
1161
  */
1162
+ setOnUpsert(callback) {
1163
+ this.#onUpsert = callback;
1164
+ }
1001
1165
  add(...files) {
1002
1166
  return this.#store(files, false);
1003
1167
  }
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
1168
  upsert(...files) {
1010
1169
  return this.#store(files, true);
1011
1170
  }
1012
1171
  #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);
1172
+ const batch = files.length > 1 ? this.#dedupe(files) : files;
1173
+ const resolved = [];
1174
+ for (const file of batch) {
1175
+ const existing = this.#cache.get(file.path);
1176
+ const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : file;
1177
+ this.#cache.set(merged.path, merged);
1178
+ resolved.push(merged);
1179
+ this.#onUpsert?.(merged);
1019
1180
  }
1020
- this.#filesCache = null;
1021
- return resolvedFiles;
1181
+ if (resolved.length > 0) this.#sorted = null;
1182
+ return resolved;
1183
+ }
1184
+ #dedupe(files) {
1185
+ const seen = /* @__PURE__ */ new Map();
1186
+ for (const file of files) {
1187
+ const prev = seen.get(file.path);
1188
+ seen.set(file.path, prev ? mergeFile(prev, file) : file);
1189
+ }
1190
+ return [...seen.values()];
1022
1191
  }
1023
1192
  getByPath(path) {
1024
1193
  return this.#cache.get(path) ?? null;
1025
1194
  }
1026
1195
  deleteByPath(path) {
1027
- this.#cache.delete(path);
1028
- this.#filesCache = null;
1196
+ if (!this.#cache.delete(path)) return;
1197
+ this.#sorted = null;
1029
1198
  }
1030
1199
  clear() {
1031
1200
  this.#cache.clear();
1032
- this.#filesCache = null;
1201
+ this.#sorted = null;
1033
1202
  }
1034
1203
  /**
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.
1204
+ * Releases all stored files. Called by the core after `kubb:build:end`.
1037
1205
  */
1038
1206
  dispose() {
1039
1207
  this.clear();
1208
+ this.#onUpsert = null;
1040
1209
  }
1041
1210
  [Symbol.dispose]() {
1042
1211
  this.dispose();
1043
1212
  }
1044
1213
  /**
1045
- * All stored files, sorted by path length (shorter paths first).
1214
+ * All stored files in stable sort order (shortest path first, barrel files
1215
+ * last within a length bucket). Returns a cached view — do not mutate.
1046
1216
  */
1047
1217
  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;
1218
+ return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
1219
+ }
1220
+ };
1221
+ //#endregion
1222
+ //#region src/FileProcessor.ts
1223
+ function joinSources(file) {
1224
+ const sources = file.sources;
1225
+ if (sources.length === 0) return "";
1226
+ const parts = [];
1227
+ for (const source of sources) {
1228
+ const s = (0, _kubb_ast.extractStringsFromNodes)(source.nodes);
1229
+ if (s) parts.push(s);
1230
+ }
1231
+ return parts.join("\n\n");
1232
+ }
1233
+ /**
1234
+ * Converts a single file to a string using the registered parsers.
1235
+ * Falls back to joining source values when no matching parser is found.
1236
+ *
1237
+ * @internal
1238
+ */
1239
+ var FileProcessor = class {
1240
+ events = new AsyncEventEmitter();
1241
+ parse(file, { parsers, extension } = {}) {
1242
+ const parseExtName = extension?.[file.extname] || void 0;
1243
+ if (!parsers || !file.extname) return joinSources(file);
1244
+ const parser = parsers.get(file.extname);
1245
+ if (!parser) return joinSources(file);
1246
+ return parser.parse(file, { extname: parseExtName });
1247
+ }
1248
+ *stream(files, options = {}) {
1249
+ const total = files.length;
1250
+ if (total === 0) return;
1251
+ let processed = 0;
1252
+ for (const file of files) {
1253
+ const source = this.parse(file, options);
1254
+ processed++;
1255
+ yield {
1256
+ file,
1257
+ source,
1258
+ processed,
1259
+ total,
1260
+ percentage: processed / total * 100
1261
+ };
1262
+ }
1263
+ }
1264
+ async run(files, options = {}) {
1265
+ await this.events.emit("start", files);
1266
+ for (const { file, source, processed, total, percentage } of this.stream(files, options)) await this.events.emit("update", {
1267
+ file,
1268
+ source,
1269
+ processed,
1270
+ percentage,
1271
+ total
1057
1272
  });
1058
- return this.#filesCache;
1273
+ await this.events.emit("end", files);
1274
+ return files;
1275
+ }
1276
+ /**
1277
+ * Clears all registered event listeners.
1278
+ */
1279
+ dispose() {
1280
+ this.events.removeAll();
1281
+ }
1282
+ [Symbol.dispose]() {
1283
+ this.dispose();
1059
1284
  }
1060
1285
  };
1061
1286
  //#endregion
1287
+ //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
1288
+ function _usingCtx() {
1289
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
1290
+ var n = Error();
1291
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
1292
+ };
1293
+ var e = {};
1294
+ var n = [];
1295
+ function using(r, e) {
1296
+ if (null != e) {
1297
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
1298
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
1299
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
1300
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
1301
+ t && (o = function o() {
1302
+ try {
1303
+ t.call(e);
1304
+ } catch (r) {
1305
+ return Promise.reject(r);
1306
+ }
1307
+ }), n.push({
1308
+ v: e,
1309
+ d: o,
1310
+ a: r
1311
+ });
1312
+ } else r && n.push({
1313
+ d: e,
1314
+ a: r
1315
+ });
1316
+ return e;
1317
+ }
1318
+ return {
1319
+ e,
1320
+ u: using.bind(null, !1),
1321
+ a: using.bind(null, !0),
1322
+ d: function d() {
1323
+ var o;
1324
+ var t = this.e;
1325
+ var s = 0;
1326
+ function next() {
1327
+ for (; o = n.pop();) try {
1328
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
1329
+ if (o.d) {
1330
+ var r = o.d.call(o.v);
1331
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
1332
+ } else s |= 1;
1333
+ } catch (r) {
1334
+ return err(r);
1335
+ }
1336
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
1337
+ if (t !== e) throw t;
1338
+ }
1339
+ function err(n) {
1340
+ return t = t !== e ? new r(n, t) : n, next();
1341
+ }
1342
+ return next();
1343
+ }
1344
+ };
1345
+ }
1346
+ //#endregion
1062
1347
  //#region src/KubbDriver.ts
1063
1348
  function enforceOrder(enforce) {
1064
1349
  return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
1065
1350
  }
1351
+ const OPERATION_FILTER_TYPES = new Set([
1352
+ "tag",
1353
+ "operationId",
1354
+ "path",
1355
+ "method",
1356
+ "contentType"
1357
+ ]);
1066
1358
  var KubbDriver = class KubbDriver {
1067
1359
  config;
1068
1360
  options;
@@ -1082,8 +1374,8 @@ var KubbDriver = class KubbDriver {
1082
1374
  * The streaming `InputStreamNode` produced by the adapter.
1083
1375
  * Always set after adapter setup — parse-only adapters are wrapped automatically.
1084
1376
  */
1085
- inputNode = void 0;
1086
- adapter = void 0;
1377
+ inputNode = null;
1378
+ adapter = null;
1087
1379
  /**
1088
1380
  * Studio session state, kept together so `dispose()` can reset it atomically.
1089
1381
  *
@@ -1094,9 +1386,9 @@ var KubbDriver = class KubbDriver {
1094
1386
  * per studio session, even when `openInStudio()` is called multiple times.
1095
1387
  */
1096
1388
  #studio = {
1097
- source: void 0,
1389
+ source: null,
1098
1390
  isOpen: false,
1099
- inputNode: void 0
1391
+ inputNode: null
1100
1392
  };
1101
1393
  #middlewareListeners = [];
1102
1394
  /**
@@ -1105,6 +1397,7 @@ var KubbDriver = class KubbDriver {
1105
1397
  * add files; this property gives direct read/write access when needed.
1106
1398
  */
1107
1399
  fileManager = new FileManager();
1400
+ #fileProcessor = new FileProcessor();
1108
1401
  plugins = /* @__PURE__ */ new Map();
1109
1402
  /**
1110
1403
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
@@ -1117,7 +1410,7 @@ var KubbDriver = class KubbDriver {
1117
1410
  constructor(config, options) {
1118
1411
  this.config = config;
1119
1412
  this.options = options;
1120
- this.adapter = config.adapter;
1413
+ this.adapter = config.adapter ?? null;
1121
1414
  }
1122
1415
  async setup() {
1123
1416
  const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
@@ -1328,6 +1621,334 @@ var KubbDriver = class KubbDriver {
1328
1621
  return this.#eventGeneratorPlugins.has(pluginName);
1329
1622
  }
1330
1623
  /**
1624
+ * Runs the full plugin pipeline. Returns timings/failures collected so far even
1625
+ * when an outer hook throws — the orchestrator preserves partial state by capturing
1626
+ * the error into `error` instead of propagating.
1627
+ */
1628
+ async run({ storage }) {
1629
+ const hooks = this.hooks;
1630
+ const config = this.config;
1631
+ const failedPlugins = /* @__PURE__ */ new Set();
1632
+ const pluginTimings = /* @__PURE__ */ new Map();
1633
+ const parsersMap = /* @__PURE__ */ new Map();
1634
+ for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
1635
+ const pendingFiles = /* @__PURE__ */ new Map();
1636
+ this.fileManager.setOnUpsert((file) => {
1637
+ pendingFiles.set(file.path, file);
1638
+ });
1639
+ try {
1640
+ const flushPending = async () => {
1641
+ if (pendingFiles.size === 0) return;
1642
+ const files = [...pendingFiles.values()];
1643
+ pendingFiles.clear();
1644
+ await hooks.emit("kubb:debug", {
1645
+ date: /* @__PURE__ */ new Date(),
1646
+ logs: [`Writing ${files.length} files...`]
1647
+ });
1648
+ await hooks.emit("kubb:files:processing:start", { files });
1649
+ const items = [...this.#fileProcessor.stream(files, {
1650
+ parsers: parsersMap,
1651
+ extension: config.output.extension
1652
+ })];
1653
+ await hooks.emit("kubb:files:processing:update", { files: items.map(({ file, source, processed, total, percentage }) => ({
1654
+ file,
1655
+ source,
1656
+ processed,
1657
+ total,
1658
+ percentage,
1659
+ config
1660
+ })) });
1661
+ const queue = [];
1662
+ for (const { file, source } of items) if (source) {
1663
+ queue.push(storage.setItem(file.path, source));
1664
+ if (queue.length >= 50) await Promise.all(queue.splice(0));
1665
+ }
1666
+ await Promise.all(queue);
1667
+ await hooks.emit("kubb:files:processing:end", { files });
1668
+ await hooks.emit("kubb:debug", {
1669
+ date: /* @__PURE__ */ new Date(),
1670
+ logs: [`✓ File write process completed for ${files.length} files`]
1671
+ });
1672
+ };
1673
+ await this.emitSetupHooks();
1674
+ if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
1675
+ config,
1676
+ adapter: this.adapter,
1677
+ meta: this.inputNode.meta,
1678
+ getPlugin: this.getPlugin.bind(this)
1679
+ }, this.#filesPayload()));
1680
+ const generatorPlugins = [];
1681
+ for (const plugin of this.plugins.values()) {
1682
+ const context = this.getContext(plugin);
1683
+ const hrStart = process.hrtime();
1684
+ try {
1685
+ await hooks.emit("kubb:plugin:start", { plugin });
1686
+ await hooks.emit("kubb:debug", {
1687
+ date: /* @__PURE__ */ new Date(),
1688
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1689
+ });
1690
+ } catch (caughtError) {
1691
+ const error = caughtError;
1692
+ const duration = getElapsedMs(hrStart);
1693
+ pluginTimings.set(plugin.name, duration);
1694
+ await this.#emitPluginEnd({
1695
+ plugin,
1696
+ duration,
1697
+ success: false,
1698
+ error
1699
+ });
1700
+ failedPlugins.add({
1701
+ plugin,
1702
+ error
1703
+ });
1704
+ continue;
1705
+ }
1706
+ if (plugin.generators?.length || this.hasEventGenerators(plugin.name)) {
1707
+ generatorPlugins.push({
1708
+ plugin,
1709
+ context,
1710
+ hrStart
1711
+ });
1712
+ continue;
1713
+ }
1714
+ const duration = getElapsedMs(hrStart);
1715
+ pluginTimings.set(plugin.name, duration);
1716
+ await this.#emitPluginEnd({
1717
+ plugin,
1718
+ duration,
1719
+ success: true
1720
+ });
1721
+ await hooks.emit("kubb:debug", {
1722
+ date: /* @__PURE__ */ new Date(),
1723
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1724
+ });
1725
+ }
1726
+ if (generatorPlugins.length > 0) if (this.inputNode) {
1727
+ const { timings, failed } = await this.#runGenerators(generatorPlugins, flushPending);
1728
+ await flushPending();
1729
+ for (const [name, duration] of timings) pluginTimings.set(name, duration);
1730
+ for (const entry of failed) failedPlugins.add(entry);
1731
+ } else for (const { plugin, hrStart } of generatorPlugins) {
1732
+ const duration = getElapsedMs(hrStart);
1733
+ pluginTimings.set(plugin.name, duration);
1734
+ await this.#emitPluginEnd({
1735
+ plugin,
1736
+ duration,
1737
+ success: true
1738
+ });
1739
+ }
1740
+ await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
1741
+ await flushPending();
1742
+ const files = this.fileManager.files;
1743
+ await hooks.emit("kubb:build:end", {
1744
+ files,
1745
+ config,
1746
+ outputDir: (0, node_path.resolve)(config.root, config.output.path)
1747
+ });
1748
+ return {
1749
+ failedPlugins,
1750
+ pluginTimings
1751
+ };
1752
+ } catch (caughtError) {
1753
+ return {
1754
+ failedPlugins,
1755
+ pluginTimings,
1756
+ error: caughtError
1757
+ };
1758
+ } finally {
1759
+ this.fileManager.setOnUpsert(null);
1760
+ }
1761
+ }
1762
+ #filesPayload() {
1763
+ const driver = this;
1764
+ return {
1765
+ get files() {
1766
+ return driver.fileManager.files;
1767
+ },
1768
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1769
+ };
1770
+ }
1771
+ #emitPluginEnd({ plugin, duration, success, error }) {
1772
+ return this.hooks.emit("kubb:plugin:end", Object.assign({
1773
+ plugin,
1774
+ duration,
1775
+ success,
1776
+ ...error ? { error } : {},
1777
+ config: this.config
1778
+ }, this.#filesPayload()));
1779
+ }
1780
+ async #runGenerators(entries, flushPending) {
1781
+ const timings = /* @__PURE__ */ new Map();
1782
+ const failed = /* @__PURE__ */ new Set();
1783
+ const driver = this;
1784
+ const { schemas, operations } = this.inputNode;
1785
+ const states = entries.map(({ plugin, context, hrStart }) => {
1786
+ const { exclude, include, override } = plugin.options;
1787
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
1788
+ const hasInclude = Array.isArray(include) && include.length > 0;
1789
+ const hasOverride = Array.isArray(override) && override.length > 0;
1790
+ return {
1791
+ plugin,
1792
+ generatorContext: {
1793
+ ...context,
1794
+ resolver: this.getResolver(plugin.name)
1795
+ },
1796
+ generators: plugin.generators ?? [],
1797
+ hrStart,
1798
+ failed: false,
1799
+ error: null,
1800
+ optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
1801
+ allowedSchemaNames: null
1802
+ };
1803
+ });
1804
+ const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
1805
+ const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
1806
+ const pruningStates = states.filter(({ plugin }) => {
1807
+ const { include } = plugin.options;
1808
+ return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
1809
+ });
1810
+ if (pruningStates.length > 0) {
1811
+ const allSchemas = [];
1812
+ for await (const schema of schemas) allSchemas.push(schema);
1813
+ const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
1814
+ for await (const operation of operations) for (const state of pruningStates) {
1815
+ const { exclude, include, override } = state.plugin.options;
1816
+ if (state.generatorContext.resolver.resolveOptions(operation, {
1817
+ options: state.plugin.options,
1818
+ exclude,
1819
+ include,
1820
+ override
1821
+ }) !== null) includedOpsByState.get(state)?.push(operation);
1822
+ }
1823
+ for (const state of pruningStates) {
1824
+ state.allowedSchemaNames = (0, _kubb_ast.collectUsedSchemaNames)(includedOpsByState.get(state) ?? [], allSchemas);
1825
+ includedOpsByState.delete(state);
1826
+ }
1827
+ }
1828
+ const resolveRendererFor = (gen, state) => gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
1829
+ const dispatchSchema = async (state, node) => {
1830
+ if (state.failed) return;
1831
+ try {
1832
+ const { plugin, generatorContext, generators } = state;
1833
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1834
+ if (state.allowedSchemaNames !== null && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
1835
+ const { exclude, include, override } = plugin.options;
1836
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1837
+ options: plugin.options,
1838
+ exclude,
1839
+ include,
1840
+ override
1841
+ });
1842
+ if (options === null) return;
1843
+ const ctx = {
1844
+ ...generatorContext,
1845
+ options
1846
+ };
1847
+ for (const gen of generators) {
1848
+ if (!gen.schema) continue;
1849
+ const raw = gen.schema(transformedNode, ctx);
1850
+ const applied = applyHookResult({
1851
+ result: isPromise(raw) ? await raw : raw,
1852
+ driver,
1853
+ rendererFactory: resolveRendererFor(gen, state)
1854
+ });
1855
+ if (isPromise(applied)) await applied;
1856
+ }
1857
+ if (emitsSchemaHook) await this.hooks.emit("kubb:generate:schema", transformedNode, ctx);
1858
+ } catch (caughtError) {
1859
+ state.failed = true;
1860
+ state.error = caughtError;
1861
+ }
1862
+ };
1863
+ const dispatchOperation = async (state, node) => {
1864
+ if (state.failed) return;
1865
+ try {
1866
+ const { plugin, generatorContext, generators } = state;
1867
+ const transformedNode = plugin.transformer ? (0, _kubb_ast.transform)(node, plugin.transformer) : node;
1868
+ const { exclude, include, override } = plugin.options;
1869
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1870
+ options: plugin.options,
1871
+ exclude,
1872
+ include,
1873
+ override
1874
+ });
1875
+ if (options === null) return;
1876
+ const ctx = {
1877
+ ...generatorContext,
1878
+ options
1879
+ };
1880
+ for (const gen of generators) {
1881
+ if (!gen.operation) continue;
1882
+ const raw = gen.operation(transformedNode, ctx);
1883
+ const applied = applyHookResult({
1884
+ result: isPromise(raw) ? await raw : raw,
1885
+ driver,
1886
+ rendererFactory: resolveRendererFor(gen, state)
1887
+ });
1888
+ if (isPromise(applied)) await applied;
1889
+ }
1890
+ if (emitsOperationHook) await this.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1891
+ } catch (caughtError) {
1892
+ state.failed = true;
1893
+ state.error = caughtError;
1894
+ }
1895
+ };
1896
+ const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((s) => s.generators.some((g) => !!g.operations));
1897
+ const collectedOperations = needsCollectedOperations ? [] : void 0;
1898
+ await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
1899
+ concurrency: 8,
1900
+ flush: flushPending
1901
+ });
1902
+ await forBatches(operations, (nodes) => {
1903
+ if (needsCollectedOperations) collectedOperations.push(...nodes);
1904
+ return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
1905
+ }, {
1906
+ concurrency: 8,
1907
+ flush: flushPending
1908
+ });
1909
+ for (const state of states) {
1910
+ if (!state.failed && needsCollectedOperations) try {
1911
+ const { plugin, generatorContext, generators } = state;
1912
+ const ctx = {
1913
+ ...generatorContext,
1914
+ options: plugin.options
1915
+ };
1916
+ for (const gen of generators) {
1917
+ if (!gen.operations) continue;
1918
+ await applyHookResult({
1919
+ result: await gen.operations(collectedOperations, ctx),
1920
+ driver,
1921
+ rendererFactory: resolveRendererFor(gen, state)
1922
+ });
1923
+ }
1924
+ await this.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
1925
+ } catch (caughtError) {
1926
+ state.failed = true;
1927
+ state.error = caughtError;
1928
+ }
1929
+ const duration = getElapsedMs(state.hrStart);
1930
+ timings.set(state.plugin.name, duration);
1931
+ await this.#emitPluginEnd({
1932
+ plugin: state.plugin,
1933
+ duration,
1934
+ success: !state.failed,
1935
+ error: state.failed && state.error ? state.error : void 0
1936
+ });
1937
+ if (state.failed && state.error) failed.add({
1938
+ plugin: state.plugin,
1939
+ error: state.error
1940
+ });
1941
+ await this.hooks.emit("kubb:debug", {
1942
+ date: /* @__PURE__ */ new Date(),
1943
+ logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
1944
+ });
1945
+ }
1946
+ return {
1947
+ timings,
1948
+ failed
1949
+ };
1950
+ }
1951
+ /**
1331
1952
  * Unregisters all plugin lifecycle listeners from the shared event emitter.
1332
1953
  * Called at the end of a build to prevent listener leaks across repeated builds.
1333
1954
  *
@@ -1340,11 +1961,12 @@ var KubbDriver = class KubbDriver {
1340
1961
  this.#resolvers.clear();
1341
1962
  this.#defaultResolvers.clear();
1342
1963
  this.fileManager.dispose();
1343
- this.inputNode = void 0;
1964
+ this.#fileProcessor.dispose();
1965
+ this.inputNode = null;
1344
1966
  this.#studio = {
1345
- source: void 0,
1967
+ source: null,
1346
1968
  isOpen: false,
1347
- inputNode: void 0
1969
+ inputNode: null
1348
1970
  };
1349
1971
  for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
1350
1972
  }
@@ -1464,10 +2086,15 @@ function applyHookResult({ result, driver, rendererFactory }) {
1464
2086
  }
1465
2087
  if (!rendererFactory) return;
1466
2088
  const renderer = rendererFactory();
1467
- if (renderer.stream) {
1468
- for (const file of renderer.stream(result)) driver.fileManager.upsert(file);
1469
- renderer.unmount();
2089
+ if (renderer.stream) try {
2090
+ var _usingCtx$1 = _usingCtx();
2091
+ const r = _usingCtx$1.u(renderer);
2092
+ for (const file of r.stream(result)) driver.fileManager.upsert(file);
1470
2093
  return;
2094
+ } catch (_) {
2095
+ _usingCtx$1.e = _;
2096
+ } finally {
2097
+ _usingCtx$1.d();
1471
2098
  }
1472
2099
  return applyAsyncRender({
1473
2100
  renderer,
@@ -1476,9 +2103,16 @@ function applyHookResult({ result, driver, rendererFactory }) {
1476
2103
  });
1477
2104
  }
1478
2105
  async function applyAsyncRender({ renderer, result, driver }) {
1479
- await renderer.render(result);
1480
- driver.fileManager.upsert(...renderer.files);
1481
- renderer.unmount();
2106
+ try {
2107
+ var _usingCtx3 = _usingCtx();
2108
+ const r = _usingCtx3.u(renderer);
2109
+ await r.render(result);
2110
+ driver.fileManager.upsert(...r.files);
2111
+ } catch (_) {
2112
+ _usingCtx3.e = _;
2113
+ } finally {
2114
+ _usingCtx3.d();
2115
+ }
1482
2116
  }
1483
2117
  function inputToAdapterSource(config) {
1484
2118
  const input = config.input;
@@ -1497,6 +2131,18 @@ function inputToAdapterSource(config) {
1497
2131
  };
1498
2132
  }
1499
2133
  //#endregion
2134
+ Object.defineProperty(exports, "AsyncEventEmitter", {
2135
+ enumerable: true,
2136
+ get: function() {
2137
+ return AsyncEventEmitter;
2138
+ }
2139
+ });
2140
+ Object.defineProperty(exports, "BuildError", {
2141
+ enumerable: true,
2142
+ get: function() {
2143
+ return BuildError;
2144
+ }
2145
+ });
1500
2146
  Object.defineProperty(exports, "DEFAULT_BANNER", {
1501
2147
  enumerable: true,
1502
2148
  get: function() {
@@ -1521,6 +2167,12 @@ Object.defineProperty(exports, "FileManager", {
1521
2167
  return FileManager;
1522
2168
  }
1523
2169
  });
2170
+ Object.defineProperty(exports, "FileProcessor", {
2171
+ enumerable: true,
2172
+ get: function() {
2173
+ return FileProcessor;
2174
+ }
2175
+ });
1524
2176
  Object.defineProperty(exports, "KubbDriver", {
1525
2177
  enumerable: true,
1526
2178
  get: function() {
@@ -1545,6 +2197,12 @@ Object.defineProperty(exports, "__toESM", {
1545
2197
  return __toESM;
1546
2198
  }
1547
2199
  });
2200
+ Object.defineProperty(exports, "_usingCtx", {
2201
+ enumerable: true,
2202
+ get: function() {
2203
+ return _usingCtx;
2204
+ }
2205
+ });
1548
2206
  Object.defineProperty(exports, "applyHookResult", {
1549
2207
  enumerable: true,
1550
2208
  get: function() {
@@ -1563,29 +2221,11 @@ Object.defineProperty(exports, "defineResolver", {
1563
2221
  return defineResolver;
1564
2222
  }
1565
2223
  });
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
2224
  Object.defineProperty(exports, "logLevel", {
1579
2225
  enumerable: true,
1580
2226
  get: function() {
1581
2227
  return logLevel;
1582
2228
  }
1583
2229
  });
1584
- Object.defineProperty(exports, "withDrain", {
1585
- enumerable: true,
1586
- get: function() {
1587
- return withDrain;
1588
- }
1589
- });
1590
2230
 
1591
- //# sourceMappingURL=KubbDriver-BXSnJ3qM.cjs.map
2231
+ //# sourceMappingURL=KubbDriver-BBRa5CH2.cjs.map