@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.
@@ -1,8 +1,152 @@
1
1
  import "./chunk--u3MIqq1.js";
2
+ import { EventEmitter } from "node:events";
2
3
  import path, { extname, resolve } from "node:path";
3
- import { createFile, createStreamInput, isOperationNode, isSchemaNode } from "@kubb/ast";
4
+ import { collectUsedSchemaNames, createFile, createStreamInput, extractStringsFromNodes, isOperationNode, isSchemaNode, transform } from "@kubb/ast";
4
5
  import { deflateSync } from "fflate";
5
6
  import { x } from "tinyexec";
7
+ //#region ../../internals/utils/src/errors.ts
8
+ /**
9
+ * Thrown when one or more errors occur during a Kubb build.
10
+ * Carries the full list of underlying errors on `errors`.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * throw new BuildError('Build failed', { errors: [err1, err2] })
15
+ * ```
16
+ */
17
+ var BuildError = class extends Error {
18
+ errors;
19
+ constructor(message, options) {
20
+ super(message, { cause: options.cause });
21
+ this.name = "BuildError";
22
+ this.errors = options.errors;
23
+ }
24
+ };
25
+ /**
26
+ * Coerces an unknown thrown value to an `Error` instance.
27
+ * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * try { ... } catch(err) {
32
+ * throw new BuildError('Build failed', { cause: toError(err), errors: [] })
33
+ * }
34
+ * ```
35
+ */
36
+ function toError(value) {
37
+ return value instanceof Error ? value : new Error(String(value));
38
+ }
39
+ //#endregion
40
+ //#region ../../internals/utils/src/asyncEventEmitter.ts
41
+ /**
42
+ * Typed `EventEmitter` that awaits all async listeners before resolving.
43
+ * Wraps Node's `EventEmitter` with full TypeScript event-map inference.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
48
+ * emitter.on('build', async (name) => { console.log(name) })
49
+ * await emitter.emit('build', 'petstore') // all listeners awaited
50
+ * ```
51
+ */
52
+ var AsyncEventEmitter = class {
53
+ /**
54
+ * Maximum number of listeners per event before Node emits a memory-leak warning.
55
+ * @default 10
56
+ */
57
+ constructor(maxListener = 10) {
58
+ this.#emitter.setMaxListeners(maxListener);
59
+ }
60
+ #emitter = new EventEmitter();
61
+ /**
62
+ * Emits `eventName` and awaits all registered listeners sequentially.
63
+ * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * await emitter.emit('build', 'petstore')
68
+ * ```
69
+ */
70
+ emit(eventName, ...eventArgs) {
71
+ const listeners = this.#emitter.listeners(eventName);
72
+ if (listeners.length === 0) return;
73
+ return this.#emitAll(eventName, listeners, eventArgs);
74
+ }
75
+ async #emitAll(eventName, listeners, eventArgs) {
76
+ for (const listener of listeners) try {
77
+ await listener(...eventArgs);
78
+ } catch (err) {
79
+ let serializedArgs;
80
+ try {
81
+ serializedArgs = JSON.stringify(eventArgs);
82
+ } catch {
83
+ serializedArgs = String(eventArgs);
84
+ }
85
+ throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
86
+ }
87
+ }
88
+ /**
89
+ * Registers a persistent listener for `eventName`.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * emitter.on('build', async (name) => { console.log(name) })
94
+ * ```
95
+ */
96
+ on(eventName, handler) {
97
+ this.#emitter.on(eventName, handler);
98
+ }
99
+ /**
100
+ * Registers a one-shot listener that removes itself after the first invocation.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * emitter.onOnce('build', async (name) => { console.log(name) })
105
+ * ```
106
+ */
107
+ onOnce(eventName, handler) {
108
+ const wrapper = (...args) => {
109
+ this.off(eventName, wrapper);
110
+ return handler(...args);
111
+ };
112
+ this.on(eventName, wrapper);
113
+ }
114
+ /**
115
+ * Removes a previously registered listener.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * emitter.off('build', handler)
120
+ * ```
121
+ */
122
+ off(eventName, handler) {
123
+ this.#emitter.off(eventName, handler);
124
+ }
125
+ /**
126
+ * Returns the number of listeners registered for `eventName`.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * emitter.on('build', handler)
131
+ * emitter.listenerCount('build') // 1
132
+ * ```
133
+ */
134
+ listenerCount(eventName) {
135
+ return this.#emitter.listenerCount(eventName);
136
+ }
137
+ /**
138
+ * Removes all listeners from every event channel.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * emitter.removeAll()
143
+ * ```
144
+ */
145
+ removeAll() {
146
+ this.#emitter.removeAllListeners();
147
+ }
148
+ };
149
+ //#endregion
6
150
  //#region ../../internals/utils/src/casing.ts
7
151
  /**
8
152
  * Shared implementation for camelCase and PascalCase conversion.
@@ -67,6 +211,39 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
67
211
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
68
212
  }
69
213
  //#endregion
214
+ //#region ../../internals/utils/src/time.ts
215
+ /**
216
+ * Calculates elapsed time in milliseconds from a high-resolution `process.hrtime` start time.
217
+ * Rounds to 2 decimal places for sub-millisecond precision without noise.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const start = process.hrtime()
222
+ * doWork()
223
+ * getElapsedMs(start) // 42.35
224
+ * ```
225
+ */
226
+ function getElapsedMs(hrStart) {
227
+ const [seconds, nanoseconds] = process.hrtime(hrStart);
228
+ const ms = seconds * 1e3 + nanoseconds / 1e6;
229
+ return Math.round(ms * 100) / 100;
230
+ }
231
+ /**
232
+ * Converts a millisecond duration into a human-readable string (`ms`, `s`, or `m s`).
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * formatMs(250) // '250ms'
237
+ * formatMs(1500) // '1.50s'
238
+ * formatMs(90000) // '1m 30.0s'
239
+ * ```
240
+ */
241
+ function formatMs(ms) {
242
+ if (ms >= 6e4) return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(1)}s`;
243
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(2)}s`;
244
+ return `${Math.round(ms)}ms`;
245
+ }
246
+ //#endregion
70
247
  //#region ../../internals/utils/src/promise.ts
71
248
  function* chunks(arr, size) {
72
249
  for (let i = 0; i < arr.length; i += size) yield arr.slice(i, i + size);
@@ -109,22 +286,6 @@ async function forBatches(source, process, options) {
109
286
  if (flush) await flush();
110
287
  }
111
288
  }
112
- /**
113
- * Runs `work`, passing `flush` as its periodic-flush callback, then calls
114
- * `flush` once more to drain any items that did not cross a flush boundary.
115
- *
116
- * @example
117
- * ```ts
118
- * await withDrain(
119
- * (flush) => processItems(items, { flush }),
120
- * () => writeRemainingFiles(),
121
- * )
122
- * ```
123
- */
124
- async function withDrain(work, flush) {
125
- await work(flush);
126
- await flush();
127
- }
128
289
  /** Returns `true` when `result` is a thenable `Promise`.
129
290
  *
130
291
  * @example
@@ -787,13 +948,13 @@ function buildDefaultBanner({ title, description, version, config }) {
787
948
  * @example Disabled default banner
788
949
  * ```ts
789
950
  * defaultResolveBanner(undefined, { config: { output: { defaultBanner: false }, ...config } })
790
- * // → undefined
951
+ * // → null
791
952
  * ```
792
953
  */
793
954
  function defaultResolveBanner(meta, { output, config }) {
794
955
  if (typeof output?.banner === "function") return output.banner(meta);
795
956
  if (typeof output?.banner === "string") return output.banner;
796
- if (config.output.defaultBanner === false) return;
957
+ if (config.output.defaultBanner === false) return null;
797
958
  return buildDefaultBanner({
798
959
  title: meta?.title,
799
960
  version: meta?.version,
@@ -822,6 +983,7 @@ function defaultResolveBanner(meta, { output, config }) {
822
983
  function defaultResolveFooter(meta, { output }) {
823
984
  if (typeof output?.footer === "function") return output.footer(meta);
824
985
  if (typeof output?.footer === "string") return output.footer;
986
+ return null;
825
987
  }
826
988
  /**
827
989
  * Defines a resolver for a plugin, injecting built-in defaults for name casing,
@@ -932,111 +1094,241 @@ function mergeFile(a, b) {
932
1094
  ...a,
933
1095
  banner: b.banner,
934
1096
  footer: b.footer,
935
- sources: [...a.sources || [], ...b.sources || []],
936
- imports: [...a.imports || [], ...b.imports || []],
937
- exports: [...a.exports || [], ...b.exports || []]
1097
+ sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
1098
+ imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
1099
+ exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
938
1100
  };
939
1101
  }
940
- /**
941
- * Collapses a list of files so that duplicates sharing the same `path` are merged
942
- * in arrival order. Keeps the original order of first occurrence.
943
- */
944
- function mergeFilesByPath(files) {
945
- const merged = /* @__PURE__ */ new Map();
946
- for (const file of files) {
947
- const existing = merged.get(file.path);
948
- merged.set(file.path, existing ? mergeFile(existing, file) : file);
949
- }
950
- return merged;
1102
+ function isIndexPath(path) {
1103
+ return path.endsWith("/index.ts") || path === "index.ts";
1104
+ }
1105
+ function compareFiles(a, b) {
1106
+ const lenDiff = a.path.length - b.path.length;
1107
+ if (lenDiff !== 0) return lenDiff;
1108
+ const aIsIndex = isIndexPath(a.path);
1109
+ const bIsIndex = isIndexPath(b.path);
1110
+ if (aIsIndex && !bIsIndex) return 1;
1111
+ if (!aIsIndex && bIsIndex) return -1;
1112
+ return 0;
951
1113
  }
952
1114
  /**
953
- * In-memory file store for generated files.
954
- *
955
- * Files with the same `path` are merged sources, imports, and exports are concatenated.
956
- * The `files` getter returns all stored files sorted by path length (shortest first).
1115
+ * In-memory file store for generated files. Files sharing a `path` are merged
1116
+ * (sources/imports/exports concatenated). The `files` getter is sorted by
1117
+ * path length (barrel `index.ts` last within a bucket).
957
1118
  *
958
1119
  * @example
959
1120
  * ```ts
960
- * import { FileManager } from '@kubb/core'
961
- *
962
1121
  * const manager = new FileManager()
963
1122
  * manager.upsert(myFile)
964
- * console.log(manager.files) // all stored files
1123
+ * manager.files // sorted view
965
1124
  * ```
966
1125
  */
967
1126
  var FileManager = class {
968
1127
  #cache = /* @__PURE__ */ new Map();
969
- #filesCache = null;
1128
+ #sorted = null;
1129
+ #onUpsert = null;
970
1130
  /**
971
- * Adds one or more files. Incoming files with the same path are merged
972
- * (sources/imports/exports concatenated), but existing cache entries are
973
- * replaced use {@link upsert} when you want to merge into the cache too.
1131
+ * Registers a callback invoked with the resolved {@link FileNode} on every
1132
+ * `add` / `upsert`. Used by the build loop to track newly written files
1133
+ * without keeping its own scan-based diff. Single subscriber by design
1134
+ * setting again replaces the previous callback. Pass `null` to detach.
974
1135
  */
1136
+ setOnUpsert(callback) {
1137
+ this.#onUpsert = callback;
1138
+ }
975
1139
  add(...files) {
976
1140
  return this.#store(files, false);
977
1141
  }
978
- /**
979
- * Adds or merges one or more files.
980
- * If a file with the same path already exists in the cache, its
981
- * sources/imports/exports are merged into the incoming file.
982
- */
983
1142
  upsert(...files) {
984
1143
  return this.#store(files, true);
985
1144
  }
986
1145
  #store(files, mergeExisting) {
987
- const resolvedFiles = [];
988
- for (const file of mergeFilesByPath(files).values()) {
989
- const existing = mergeExisting ? this.#cache.get(file.path) : void 0;
990
- const resolvedFile = createFile(existing ? mergeFile(existing, file) : file);
991
- this.#cache.set(resolvedFile.path, resolvedFile);
992
- resolvedFiles.push(resolvedFile);
1146
+ const batch = files.length > 1 ? this.#dedupe(files) : files;
1147
+ const resolved = [];
1148
+ for (const file of batch) {
1149
+ const existing = this.#cache.get(file.path);
1150
+ const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : file;
1151
+ this.#cache.set(merged.path, merged);
1152
+ resolved.push(merged);
1153
+ this.#onUpsert?.(merged);
993
1154
  }
994
- this.#filesCache = null;
995
- return resolvedFiles;
1155
+ if (resolved.length > 0) this.#sorted = null;
1156
+ return resolved;
1157
+ }
1158
+ #dedupe(files) {
1159
+ const seen = /* @__PURE__ */ new Map();
1160
+ for (const file of files) {
1161
+ const prev = seen.get(file.path);
1162
+ seen.set(file.path, prev ? mergeFile(prev, file) : file);
1163
+ }
1164
+ return [...seen.values()];
996
1165
  }
997
1166
  getByPath(path) {
998
1167
  return this.#cache.get(path) ?? null;
999
1168
  }
1000
1169
  deleteByPath(path) {
1001
- this.#cache.delete(path);
1002
- this.#filesCache = null;
1170
+ if (!this.#cache.delete(path)) return;
1171
+ this.#sorted = null;
1003
1172
  }
1004
1173
  clear() {
1005
1174
  this.#cache.clear();
1006
- this.#filesCache = null;
1175
+ this.#sorted = null;
1007
1176
  }
1008
1177
  /**
1009
- * Releases all stored files. Called by the core after `kubb:build:end` to
1010
- * free the per-plugin FileNode caches for the rest of the process lifetime.
1178
+ * Releases all stored files. Called by the core after `kubb:build:end`.
1011
1179
  */
1012
1180
  dispose() {
1013
1181
  this.clear();
1182
+ this.#onUpsert = null;
1014
1183
  }
1015
1184
  [Symbol.dispose]() {
1016
1185
  this.dispose();
1017
1186
  }
1018
1187
  /**
1019
- * All stored files, sorted by path length (shorter paths first).
1188
+ * All stored files in stable sort order (shortest path first, barrel files
1189
+ * last within a length bucket). Returns a cached view — do not mutate.
1020
1190
  */
1021
1191
  get files() {
1022
- if (this.#filesCache) return this.#filesCache;
1023
- this.#filesCache = [...this.#cache.values()].sort((a, b) => {
1024
- const lenDiff = a.path.length - b.path.length;
1025
- if (lenDiff !== 0) return lenDiff;
1026
- const aIsIndex = a.path.endsWith("/index.ts") || a.path === "index.ts";
1027
- const bIsIndex = b.path.endsWith("/index.ts") || b.path === "index.ts";
1028
- if (aIsIndex && !bIsIndex) return 1;
1029
- if (!aIsIndex && bIsIndex) return -1;
1030
- return 0;
1192
+ return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
1193
+ }
1194
+ };
1195
+ //#endregion
1196
+ //#region src/FileProcessor.ts
1197
+ function joinSources(file) {
1198
+ const sources = file.sources;
1199
+ if (sources.length === 0) return "";
1200
+ const parts = [];
1201
+ for (const source of sources) {
1202
+ const s = extractStringsFromNodes(source.nodes);
1203
+ if (s) parts.push(s);
1204
+ }
1205
+ return parts.join("\n\n");
1206
+ }
1207
+ /**
1208
+ * Converts a single file to a string using the registered parsers.
1209
+ * Falls back to joining source values when no matching parser is found.
1210
+ *
1211
+ * @internal
1212
+ */
1213
+ var FileProcessor = class {
1214
+ events = new AsyncEventEmitter();
1215
+ parse(file, { parsers, extension } = {}) {
1216
+ const parseExtName = extension?.[file.extname] || void 0;
1217
+ if (!parsers || !file.extname) return joinSources(file);
1218
+ const parser = parsers.get(file.extname);
1219
+ if (!parser) return joinSources(file);
1220
+ return parser.parse(file, { extname: parseExtName });
1221
+ }
1222
+ *stream(files, options = {}) {
1223
+ const total = files.length;
1224
+ if (total === 0) return;
1225
+ let processed = 0;
1226
+ for (const file of files) {
1227
+ const source = this.parse(file, options);
1228
+ processed++;
1229
+ yield {
1230
+ file,
1231
+ source,
1232
+ processed,
1233
+ total,
1234
+ percentage: processed / total * 100
1235
+ };
1236
+ }
1237
+ }
1238
+ async run(files, options = {}) {
1239
+ await this.events.emit("start", files);
1240
+ for (const { file, source, processed, total, percentage } of this.stream(files, options)) await this.events.emit("update", {
1241
+ file,
1242
+ source,
1243
+ processed,
1244
+ percentage,
1245
+ total
1031
1246
  });
1032
- return this.#filesCache;
1247
+ await this.events.emit("end", files);
1248
+ return files;
1249
+ }
1250
+ /**
1251
+ * Clears all registered event listeners.
1252
+ */
1253
+ dispose() {
1254
+ this.events.removeAll();
1255
+ }
1256
+ [Symbol.dispose]() {
1257
+ this.dispose();
1033
1258
  }
1034
1259
  };
1035
1260
  //#endregion
1261
+ //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
1262
+ function _usingCtx() {
1263
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
1264
+ var n = Error();
1265
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
1266
+ };
1267
+ var e = {};
1268
+ var n = [];
1269
+ function using(r, e) {
1270
+ if (null != e) {
1271
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
1272
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
1273
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
1274
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
1275
+ t && (o = function o() {
1276
+ try {
1277
+ t.call(e);
1278
+ } catch (r) {
1279
+ return Promise.reject(r);
1280
+ }
1281
+ }), n.push({
1282
+ v: e,
1283
+ d: o,
1284
+ a: r
1285
+ });
1286
+ } else r && n.push({
1287
+ d: e,
1288
+ a: r
1289
+ });
1290
+ return e;
1291
+ }
1292
+ return {
1293
+ e,
1294
+ u: using.bind(null, !1),
1295
+ a: using.bind(null, !0),
1296
+ d: function d() {
1297
+ var o;
1298
+ var t = this.e;
1299
+ var s = 0;
1300
+ function next() {
1301
+ for (; o = n.pop();) try {
1302
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
1303
+ if (o.d) {
1304
+ var r = o.d.call(o.v);
1305
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
1306
+ } else s |= 1;
1307
+ } catch (r) {
1308
+ return err(r);
1309
+ }
1310
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
1311
+ if (t !== e) throw t;
1312
+ }
1313
+ function err(n) {
1314
+ return t = t !== e ? new r(n, t) : n, next();
1315
+ }
1316
+ return next();
1317
+ }
1318
+ };
1319
+ }
1320
+ //#endregion
1036
1321
  //#region src/KubbDriver.ts
1037
1322
  function enforceOrder(enforce) {
1038
1323
  return enforce === "pre" ? -1 : enforce === "post" ? 1 : 0;
1039
1324
  }
1325
+ const OPERATION_FILTER_TYPES = new Set([
1326
+ "tag",
1327
+ "operationId",
1328
+ "path",
1329
+ "method",
1330
+ "contentType"
1331
+ ]);
1040
1332
  var KubbDriver = class KubbDriver {
1041
1333
  config;
1042
1334
  options;
@@ -1056,8 +1348,8 @@ var KubbDriver = class KubbDriver {
1056
1348
  * The streaming `InputStreamNode` produced by the adapter.
1057
1349
  * Always set after adapter setup — parse-only adapters are wrapped automatically.
1058
1350
  */
1059
- inputNode = void 0;
1060
- adapter = void 0;
1351
+ inputNode = null;
1352
+ adapter = null;
1061
1353
  /**
1062
1354
  * Studio session state, kept together so `dispose()` can reset it atomically.
1063
1355
  *
@@ -1068,9 +1360,9 @@ var KubbDriver = class KubbDriver {
1068
1360
  * per studio session, even when `openInStudio()` is called multiple times.
1069
1361
  */
1070
1362
  #studio = {
1071
- source: void 0,
1363
+ source: null,
1072
1364
  isOpen: false,
1073
- inputNode: void 0
1365
+ inputNode: null
1074
1366
  };
1075
1367
  #middlewareListeners = [];
1076
1368
  /**
@@ -1079,6 +1371,7 @@ var KubbDriver = class KubbDriver {
1079
1371
  * add files; this property gives direct read/write access when needed.
1080
1372
  */
1081
1373
  fileManager = new FileManager();
1374
+ #fileProcessor = new FileProcessor();
1082
1375
  plugins = /* @__PURE__ */ new Map();
1083
1376
  /**
1084
1377
  * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
@@ -1091,7 +1384,7 @@ var KubbDriver = class KubbDriver {
1091
1384
  constructor(config, options) {
1092
1385
  this.config = config;
1093
1386
  this.options = options;
1094
- this.adapter = config.adapter;
1387
+ this.adapter = config.adapter ?? null;
1095
1388
  }
1096
1389
  async setup() {
1097
1390
  const normalized = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin));
@@ -1302,6 +1595,334 @@ var KubbDriver = class KubbDriver {
1302
1595
  return this.#eventGeneratorPlugins.has(pluginName);
1303
1596
  }
1304
1597
  /**
1598
+ * Runs the full plugin pipeline. Returns timings/failures collected so far even
1599
+ * when an outer hook throws — the orchestrator preserves partial state by capturing
1600
+ * the error into `error` instead of propagating.
1601
+ */
1602
+ async run({ storage }) {
1603
+ const hooks = this.hooks;
1604
+ const config = this.config;
1605
+ const failedPlugins = /* @__PURE__ */ new Set();
1606
+ const pluginTimings = /* @__PURE__ */ new Map();
1607
+ const parsersMap = /* @__PURE__ */ new Map();
1608
+ for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
1609
+ const pendingFiles = /* @__PURE__ */ new Map();
1610
+ this.fileManager.setOnUpsert((file) => {
1611
+ pendingFiles.set(file.path, file);
1612
+ });
1613
+ try {
1614
+ const flushPending = async () => {
1615
+ if (pendingFiles.size === 0) return;
1616
+ const files = [...pendingFiles.values()];
1617
+ pendingFiles.clear();
1618
+ await hooks.emit("kubb:debug", {
1619
+ date: /* @__PURE__ */ new Date(),
1620
+ logs: [`Writing ${files.length} files...`]
1621
+ });
1622
+ await hooks.emit("kubb:files:processing:start", { files });
1623
+ const items = [...this.#fileProcessor.stream(files, {
1624
+ parsers: parsersMap,
1625
+ extension: config.output.extension
1626
+ })];
1627
+ await hooks.emit("kubb:files:processing:update", { files: items.map(({ file, source, processed, total, percentage }) => ({
1628
+ file,
1629
+ source,
1630
+ processed,
1631
+ total,
1632
+ percentage,
1633
+ config
1634
+ })) });
1635
+ const queue = [];
1636
+ for (const { file, source } of items) if (source) {
1637
+ queue.push(storage.setItem(file.path, source));
1638
+ if (queue.length >= 50) await Promise.all(queue.splice(0));
1639
+ }
1640
+ await Promise.all(queue);
1641
+ await hooks.emit("kubb:files:processing:end", { files });
1642
+ await hooks.emit("kubb:debug", {
1643
+ date: /* @__PURE__ */ new Date(),
1644
+ logs: [`✓ File write process completed for ${files.length} files`]
1645
+ });
1646
+ };
1647
+ await this.emitSetupHooks();
1648
+ if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
1649
+ config,
1650
+ adapter: this.adapter,
1651
+ meta: this.inputNode.meta,
1652
+ getPlugin: this.getPlugin.bind(this)
1653
+ }, this.#filesPayload()));
1654
+ const generatorPlugins = [];
1655
+ for (const plugin of this.plugins.values()) {
1656
+ const context = this.getContext(plugin);
1657
+ const hrStart = process.hrtime();
1658
+ try {
1659
+ await hooks.emit("kubb:plugin:start", { plugin });
1660
+ await hooks.emit("kubb:debug", {
1661
+ date: /* @__PURE__ */ new Date(),
1662
+ logs: ["Starting plugin...", ` • Plugin Name: ${plugin.name}`]
1663
+ });
1664
+ } catch (caughtError) {
1665
+ const error = caughtError;
1666
+ const duration = getElapsedMs(hrStart);
1667
+ pluginTimings.set(plugin.name, duration);
1668
+ await this.#emitPluginEnd({
1669
+ plugin,
1670
+ duration,
1671
+ success: false,
1672
+ error
1673
+ });
1674
+ failedPlugins.add({
1675
+ plugin,
1676
+ error
1677
+ });
1678
+ continue;
1679
+ }
1680
+ if (plugin.generators?.length || this.hasEventGenerators(plugin.name)) {
1681
+ generatorPlugins.push({
1682
+ plugin,
1683
+ context,
1684
+ hrStart
1685
+ });
1686
+ continue;
1687
+ }
1688
+ const duration = getElapsedMs(hrStart);
1689
+ pluginTimings.set(plugin.name, duration);
1690
+ await this.#emitPluginEnd({
1691
+ plugin,
1692
+ duration,
1693
+ success: true
1694
+ });
1695
+ await hooks.emit("kubb:debug", {
1696
+ date: /* @__PURE__ */ new Date(),
1697
+ logs: [`✓ Plugin started successfully (${formatMs(duration)})`]
1698
+ });
1699
+ }
1700
+ if (generatorPlugins.length > 0) if (this.inputNode) {
1701
+ const { timings, failed } = await this.#runGenerators(generatorPlugins, flushPending);
1702
+ await flushPending();
1703
+ for (const [name, duration] of timings) pluginTimings.set(name, duration);
1704
+ for (const entry of failed) failedPlugins.add(entry);
1705
+ } else for (const { plugin, hrStart } of generatorPlugins) {
1706
+ const duration = getElapsedMs(hrStart);
1707
+ pluginTimings.set(plugin.name, duration);
1708
+ await this.#emitPluginEnd({
1709
+ plugin,
1710
+ duration,
1711
+ success: true
1712
+ });
1713
+ }
1714
+ await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
1715
+ await flushPending();
1716
+ const files = this.fileManager.files;
1717
+ await hooks.emit("kubb:build:end", {
1718
+ files,
1719
+ config,
1720
+ outputDir: resolve(config.root, config.output.path)
1721
+ });
1722
+ return {
1723
+ failedPlugins,
1724
+ pluginTimings
1725
+ };
1726
+ } catch (caughtError) {
1727
+ return {
1728
+ failedPlugins,
1729
+ pluginTimings,
1730
+ error: caughtError
1731
+ };
1732
+ } finally {
1733
+ this.fileManager.setOnUpsert(null);
1734
+ }
1735
+ }
1736
+ #filesPayload() {
1737
+ const driver = this;
1738
+ return {
1739
+ get files() {
1740
+ return driver.fileManager.files;
1741
+ },
1742
+ upsertFile: (...files) => driver.fileManager.upsert(...files)
1743
+ };
1744
+ }
1745
+ #emitPluginEnd({ plugin, duration, success, error }) {
1746
+ return this.hooks.emit("kubb:plugin:end", Object.assign({
1747
+ plugin,
1748
+ duration,
1749
+ success,
1750
+ ...error ? { error } : {},
1751
+ config: this.config
1752
+ }, this.#filesPayload()));
1753
+ }
1754
+ async #runGenerators(entries, flushPending) {
1755
+ const timings = /* @__PURE__ */ new Map();
1756
+ const failed = /* @__PURE__ */ new Set();
1757
+ const driver = this;
1758
+ const { schemas, operations } = this.inputNode;
1759
+ const states = entries.map(({ plugin, context, hrStart }) => {
1760
+ const { exclude, include, override } = plugin.options;
1761
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
1762
+ const hasInclude = Array.isArray(include) && include.length > 0;
1763
+ const hasOverride = Array.isArray(override) && override.length > 0;
1764
+ return {
1765
+ plugin,
1766
+ generatorContext: {
1767
+ ...context,
1768
+ resolver: this.getResolver(plugin.name)
1769
+ },
1770
+ generators: plugin.generators ?? [],
1771
+ hrStart,
1772
+ failed: false,
1773
+ error: null,
1774
+ optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
1775
+ allowedSchemaNames: null
1776
+ };
1777
+ });
1778
+ const emitsSchemaHook = this.hooks.listenerCount("kubb:generate:schema") > 0;
1779
+ const emitsOperationHook = this.hooks.listenerCount("kubb:generate:operation") > 0;
1780
+ const pruningStates = states.filter(({ plugin }) => {
1781
+ const { include } = plugin.options;
1782
+ return (include?.some(({ type }) => OPERATION_FILTER_TYPES.has(type)) ?? false) && !(include?.some(({ type }) => type === "schemaName") ?? false);
1783
+ });
1784
+ if (pruningStates.length > 0) {
1785
+ const allSchemas = [];
1786
+ for await (const schema of schemas) allSchemas.push(schema);
1787
+ const includedOpsByState = new Map(pruningStates.map((s) => [s, []]));
1788
+ for await (const operation of operations) for (const state of pruningStates) {
1789
+ const { exclude, include, override } = state.plugin.options;
1790
+ if (state.generatorContext.resolver.resolveOptions(operation, {
1791
+ options: state.plugin.options,
1792
+ exclude,
1793
+ include,
1794
+ override
1795
+ }) !== null) includedOpsByState.get(state)?.push(operation);
1796
+ }
1797
+ for (const state of pruningStates) {
1798
+ state.allowedSchemaNames = collectUsedSchemaNames(includedOpsByState.get(state) ?? [], allSchemas);
1799
+ includedOpsByState.delete(state);
1800
+ }
1801
+ }
1802
+ const resolveRendererFor = (gen, state) => gen.renderer === null ? void 0 : gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer;
1803
+ const dispatchSchema = async (state, node) => {
1804
+ if (state.failed) return;
1805
+ try {
1806
+ const { plugin, generatorContext, generators } = state;
1807
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1808
+ if (state.allowedSchemaNames !== null && transformedNode.name && !state.allowedSchemaNames.has(transformedNode.name)) return;
1809
+ const { exclude, include, override } = plugin.options;
1810
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1811
+ options: plugin.options,
1812
+ exclude,
1813
+ include,
1814
+ override
1815
+ });
1816
+ if (options === null) return;
1817
+ const ctx = {
1818
+ ...generatorContext,
1819
+ options
1820
+ };
1821
+ for (const gen of generators) {
1822
+ if (!gen.schema) continue;
1823
+ const raw = gen.schema(transformedNode, ctx);
1824
+ const applied = applyHookResult({
1825
+ result: isPromise(raw) ? await raw : raw,
1826
+ driver,
1827
+ rendererFactory: resolveRendererFor(gen, state)
1828
+ });
1829
+ if (isPromise(applied)) await applied;
1830
+ }
1831
+ if (emitsSchemaHook) await this.hooks.emit("kubb:generate:schema", transformedNode, ctx);
1832
+ } catch (caughtError) {
1833
+ state.failed = true;
1834
+ state.error = caughtError;
1835
+ }
1836
+ };
1837
+ const dispatchOperation = async (state, node) => {
1838
+ if (state.failed) return;
1839
+ try {
1840
+ const { plugin, generatorContext, generators } = state;
1841
+ const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node;
1842
+ const { exclude, include, override } = plugin.options;
1843
+ const options = state.optionsAreStatic ? plugin.options : generatorContext.resolver.resolveOptions(transformedNode, {
1844
+ options: plugin.options,
1845
+ exclude,
1846
+ include,
1847
+ override
1848
+ });
1849
+ if (options === null) return;
1850
+ const ctx = {
1851
+ ...generatorContext,
1852
+ options
1853
+ };
1854
+ for (const gen of generators) {
1855
+ if (!gen.operation) continue;
1856
+ const raw = gen.operation(transformedNode, ctx);
1857
+ const applied = applyHookResult({
1858
+ result: isPromise(raw) ? await raw : raw,
1859
+ driver,
1860
+ rendererFactory: resolveRendererFor(gen, state)
1861
+ });
1862
+ if (isPromise(applied)) await applied;
1863
+ }
1864
+ if (emitsOperationHook) await this.hooks.emit("kubb:generate:operation", transformedNode, ctx);
1865
+ } catch (caughtError) {
1866
+ state.failed = true;
1867
+ state.error = caughtError;
1868
+ }
1869
+ };
1870
+ const needsCollectedOperations = this.hooks.listenerCount("kubb:generate:operations") > 0 || states.some((s) => s.generators.some((g) => !!g.operations));
1871
+ const collectedOperations = needsCollectedOperations ? [] : void 0;
1872
+ await forBatches(schemas, (nodes) => Promise.all(nodes.flatMap((n) => states.map((state) => dispatchSchema(state, n)))), {
1873
+ concurrency: 8,
1874
+ flush: flushPending
1875
+ });
1876
+ await forBatches(operations, (nodes) => {
1877
+ if (needsCollectedOperations) collectedOperations.push(...nodes);
1878
+ return Promise.all(nodes.flatMap((n) => states.map((state) => dispatchOperation(state, n))));
1879
+ }, {
1880
+ concurrency: 8,
1881
+ flush: flushPending
1882
+ });
1883
+ for (const state of states) {
1884
+ if (!state.failed && needsCollectedOperations) try {
1885
+ const { plugin, generatorContext, generators } = state;
1886
+ const ctx = {
1887
+ ...generatorContext,
1888
+ options: plugin.options
1889
+ };
1890
+ for (const gen of generators) {
1891
+ if (!gen.operations) continue;
1892
+ await applyHookResult({
1893
+ result: await gen.operations(collectedOperations, ctx),
1894
+ driver,
1895
+ rendererFactory: resolveRendererFor(gen, state)
1896
+ });
1897
+ }
1898
+ await this.hooks.emit("kubb:generate:operations", collectedOperations, ctx);
1899
+ } catch (caughtError) {
1900
+ state.failed = true;
1901
+ state.error = caughtError;
1902
+ }
1903
+ const duration = getElapsedMs(state.hrStart);
1904
+ timings.set(state.plugin.name, duration);
1905
+ await this.#emitPluginEnd({
1906
+ plugin: state.plugin,
1907
+ duration,
1908
+ success: !state.failed,
1909
+ error: state.failed && state.error ? state.error : void 0
1910
+ });
1911
+ if (state.failed && state.error) failed.add({
1912
+ plugin: state.plugin,
1913
+ error: state.error
1914
+ });
1915
+ await this.hooks.emit("kubb:debug", {
1916
+ date: /* @__PURE__ */ new Date(),
1917
+ logs: [state.failed ? "✗ Plugin start failed" : `✓ Plugin started successfully (${formatMs(duration)})`]
1918
+ });
1919
+ }
1920
+ return {
1921
+ timings,
1922
+ failed
1923
+ };
1924
+ }
1925
+ /**
1305
1926
  * Unregisters all plugin lifecycle listeners from the shared event emitter.
1306
1927
  * Called at the end of a build to prevent listener leaks across repeated builds.
1307
1928
  *
@@ -1314,11 +1935,12 @@ var KubbDriver = class KubbDriver {
1314
1935
  this.#resolvers.clear();
1315
1936
  this.#defaultResolvers.clear();
1316
1937
  this.fileManager.dispose();
1317
- this.inputNode = void 0;
1938
+ this.#fileProcessor.dispose();
1939
+ this.inputNode = null;
1318
1940
  this.#studio = {
1319
- source: void 0,
1941
+ source: null,
1320
1942
  isOpen: false,
1321
- inputNode: void 0
1943
+ inputNode: null
1322
1944
  };
1323
1945
  for (const [event, handler] of this.#middlewareListeners) this.hooks.off(event, handler);
1324
1946
  }
@@ -1438,10 +2060,15 @@ function applyHookResult({ result, driver, rendererFactory }) {
1438
2060
  }
1439
2061
  if (!rendererFactory) return;
1440
2062
  const renderer = rendererFactory();
1441
- if (renderer.stream) {
1442
- for (const file of renderer.stream(result)) driver.fileManager.upsert(file);
1443
- renderer.unmount();
2063
+ if (renderer.stream) try {
2064
+ var _usingCtx$1 = _usingCtx();
2065
+ const r = _usingCtx$1.u(renderer);
2066
+ for (const file of r.stream(result)) driver.fileManager.upsert(file);
1444
2067
  return;
2068
+ } catch (_) {
2069
+ _usingCtx$1.e = _;
2070
+ } finally {
2071
+ _usingCtx$1.d();
1445
2072
  }
1446
2073
  return applyAsyncRender({
1447
2074
  renderer,
@@ -1450,9 +2077,16 @@ function applyHookResult({ result, driver, rendererFactory }) {
1450
2077
  });
1451
2078
  }
1452
2079
  async function applyAsyncRender({ renderer, result, driver }) {
1453
- await renderer.render(result);
1454
- driver.fileManager.upsert(...renderer.files);
1455
- renderer.unmount();
2080
+ try {
2081
+ var _usingCtx3 = _usingCtx();
2082
+ const r = _usingCtx3.u(renderer);
2083
+ await r.render(result);
2084
+ driver.fileManager.upsert(...r.files);
2085
+ } catch (_) {
2086
+ _usingCtx3.e = _;
2087
+ } finally {
2088
+ _usingCtx3.d();
2089
+ }
1456
2090
  }
1457
2091
  function inputToAdapterSource(config) {
1458
2092
  const input = config.input;
@@ -1471,6 +2105,6 @@ function inputToAdapterSource(config) {
1471
2105
  };
1472
2106
  }
1473
2107
  //#endregion
1474
- export { definePlugin as a, DEFAULT_STUDIO_URL as c, forBatches as d, isPromise as f, defineResolver as i, logLevel as l, applyHookResult as n, DEFAULT_BANNER as o, withDrain as p, FileManager as r, DEFAULT_EXTENSION as s, KubbDriver as t, URLPath as u };
2108
+ export { FileManager as a, DEFAULT_BANNER as c, logLevel as d, URLPath as f, FileProcessor as i, DEFAULT_EXTENSION as l, BuildError as m, applyHookResult as n, defineResolver as o, AsyncEventEmitter as p, _usingCtx as r, definePlugin as s, KubbDriver as t, DEFAULT_STUDIO_URL as u };
1475
2109
 
1476
- //# sourceMappingURL=KubbDriver-Cxii_rBp.js.map
2110
+ //# sourceMappingURL=KubbDriver-Cq1isv2P.js.map