@kubb/core 5.0.0-beta.85 → 5.0.0-beta.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { a as toFilePath, c as BuildError, i as clean, l as getErrorMessage, n as FileManager, o as toPosixPath, r as AsyncEventEmitter, s as write, t as _usingCtx, u as camelCase } from "./usingCtx-BABTfo1g.js";
2
+ import { a as toFilePath, c as BuildError, d as camelCase, i as clean, l as getErrorMessage, n as FileManager, o as toPosixPath, r as Hookable, s as write, t as _usingCtx, u as toError } from "./usingCtx-Cnrm3TcM.js";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { stripVTControlCharacters, styleText } from "node:util";
5
5
  import { hash } from "node:crypto";
@@ -170,7 +170,7 @@ function memoize(store, factory) {
170
170
  }
171
171
  //#endregion
172
172
  //#region package.json
173
- var version = "5.0.0-beta.85";
173
+ var version = "5.0.0-beta.87";
174
174
  //#endregion
175
175
  //#region src/constants.ts
176
176
  /**
@@ -517,12 +517,12 @@ var Diagnostics = class Diagnostics {
517
517
  return true;
518
518
  }
519
519
  /**
520
- * Emits a diagnostic on the run's `kubb:diagnostic` event so the loggers render it live.
521
- * Use it instead of calling `hooks.emit('kubb:diagnostic', ...)` directly. To collect a
520
+ * Emits a diagnostic on the run's `kubb:diagnostic` hook so the loggers render it live.
521
+ * Use it instead of calling `hooks.callHook('kubb:diagnostic', ...)` directly. To collect a
522
522
  * diagnostic into the build result from deep in a run, use {@link Diagnostics.report} instead.
523
523
  */
524
524
  static async emit(hooks, diagnostic) {
525
- await hooks.emit("kubb:diagnostic", { diagnostic });
525
+ await hooks.callHook("kubb:diagnostic", { diagnostic });
526
526
  }
527
527
  /**
528
528
  * Coerces any thrown value into a {@link ProblemDiagnostic}. A {@link Diagnostics.Error}
@@ -740,7 +740,7 @@ function definePlugin(factory) {
740
740
  return (options) => factory(options ?? {});
741
741
  }
742
742
  //#endregion
743
- //#region src/createResolver.ts
743
+ //#region src/Resolver.ts
744
744
  function isNamespace(value) {
745
745
  return typeof value === "object" && value !== null && !Array.isArray(value);
746
746
  }
@@ -804,7 +804,7 @@ var Resolver = class Resolver {
804
804
  */
805
805
  static merge(base, override) {
806
806
  const patch = override instanceof Resolver ? override.#options : override;
807
- return createResolver({
807
+ return new Resolver({
808
808
  ...base.#options,
809
809
  ...patch
810
810
  });
@@ -996,6 +996,8 @@ var Resolver = class Resolver {
996
996
  return Resolver.#resolveUserText(output?.footer, meta, file) ?? null;
997
997
  }
998
998
  };
999
+ //#endregion
1000
+ //#region src/createResolver.ts
999
1001
  /**
1000
1002
  * Defines a plugin resolver, the object that decides what every generated symbol and file
1001
1003
  * path is called. Override the top-level `name` and `file` to set the plugin's conventions,
@@ -1101,35 +1103,7 @@ const ENFORCE_ORDER = {
1101
1103
  pre: -1,
1102
1104
  post: 1
1103
1105
  };
1104
- /**
1105
- * Orders plugins so every dependency runs before its dependents (Kahn's algorithm), with
1106
- * `enforce` (`'pre'` before normal before `'post'`) and declaration order as tiebreaks.
1107
- * A pairwise `Array.sort` comparator cannot do this: dependency relations are not transitive
1108
- * at the comparator level, so a chain where A depends on B and B depends on C could come out
1109
- * wrong when A and C are never compared directly. Dependencies on plugins missing from the
1110
- * config are ignored here and surface later through `requirePlugin`.
1111
- */
1112
- function sortPlugins(plugins) {
1113
- const queue = [...plugins].sort((a, b) => (a.enforce ? ENFORCE_ORDER[a.enforce] : 0) - (b.enforce ? ENFORCE_ORDER[b.enforce] : 0));
1114
- const names = new Set(queue.map((plugin) => plugin.name));
1115
- const blockedBy = new Map(queue.map((plugin) => [plugin.name, new Set(plugin.dependencies?.filter((name) => names.has(name) && name !== plugin.name))]));
1116
- const sorted = [];
1117
- while (queue.length > 0) {
1118
- const index = queue.findIndex((plugin) => blockedBy.get(plugin.name)?.size === 0);
1119
- if (index === -1) throw new Diagnostics.Error({
1120
- code: Diagnostics.code.invalidPluginOptions,
1121
- severity: "error",
1122
- message: `Plugin dependencies form a cycle: ${queue.map((plugin) => plugin.name).join(" → ")}.`,
1123
- help: "Remove one of the `dependencies` entries so the plugins can be ordered.",
1124
- location: { kind: "config" }
1125
- });
1126
- const [plugin] = queue.splice(index, 1);
1127
- if (!plugin) break;
1128
- sorted.push(plugin);
1129
- for (const blockers of blockedBy.values()) blockers.delete(plugin.name);
1130
- }
1131
- return sorted;
1132
- }
1106
+ const enforceWeight = (plugin) => plugin.enforce ? ENFORCE_ORDER[plugin.enforce] : 0;
1133
1107
  var KubbDriver = class {
1134
1108
  config;
1135
1109
  options;
@@ -1151,17 +1125,17 @@ var KubbDriver = class {
1151
1125
  fileManager = new FileManager();
1152
1126
  plugins = /* @__PURE__ */ new Map();
1153
1127
  /**
1154
- * Tracks which plugins have generators registered via `addGenerator()` (event-based path).
1155
- * Used by the build loop to decide whether to emit generator events for a given plugin.
1128
+ * Tracks which plugins have generators registered via `addGenerator()` (hook-based path).
1129
+ * Used by the build loop to decide whether to emit generator hooks for a given plugin.
1156
1130
  */
1157
- #eventGeneratorPlugins = /* @__PURE__ */ new Set();
1131
+ #hookGeneratorPlugins = /* @__PURE__ */ new Set();
1158
1132
  #resolvers = /* @__PURE__ */ new Map();
1159
1133
  #defaultResolvers = /* @__PURE__ */ new Map();
1160
1134
  /**
1161
- * Tracks every listener the driver added (plugin, generator) so `dispose()` can remove them
1162
- * in one pass. External `hooks.on(...)` listeners are not tracked.
1135
+ * Removers for every listener the driver added (plugin, generator) so `dispose()` can detach
1136
+ * them in one pass. External `hooks.hook(...)` listeners are not tracked.
1163
1137
  */
1164
- #listeners = [];
1138
+ #unhooks = [];
1165
1139
  /**
1166
1140
  * Transform registry. Plugins populate it during `kubb:plugin:setup` via `addMacro`/`setMacros`,
1167
1141
  * and `#runGenerators` reads it once per `(plugin, node)` pair through `applyTo`.
@@ -1173,52 +1147,67 @@ var KubbDriver = class {
1173
1147
  this.adapter = config.adapter ?? null;
1174
1148
  }
1175
1149
  /**
1176
- * Attaches a listener to the shared emitter and tracks it so `dispose()` can remove it later.
1177
- * Listeners attached directly via `hooks.on(...)` are not tracked and survive disposal.
1178
- */
1179
- #trackListener(event, handler) {
1180
- this.hooks.on(event, handler);
1181
- this.#listeners.push([event, handler]);
1182
- }
1183
- /**
1184
1150
  * Normalizes every configured plugin, orders them, and registers their lifecycle handlers.
1185
1151
  * A plugin that another lists as a dependency runs first, then `enforce: 'pre'` before
1186
1152
  * `'post'`. When the config has an adapter, the adapter source is resolved from the input
1187
1153
  * so `run` can parse it later.
1188
1154
  */
1189
1155
  async setup() {
1190
- const normalized = sortPlugins(this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin)));
1156
+ const normalized = this.#sortPlugins(this.config.plugins.map((rawPlugin) => {
1157
+ return {
1158
+ name: rawPlugin.name,
1159
+ dependencies: rawPlugin.dependencies,
1160
+ enforce: rawPlugin.enforce,
1161
+ hooks: rawPlugin.hooks,
1162
+ options: rawPlugin.options ?? {
1163
+ output: {
1164
+ path: ".",
1165
+ mode: "directory"
1166
+ },
1167
+ exclude: [],
1168
+ override: []
1169
+ }
1170
+ };
1171
+ }));
1191
1172
  for (const plugin of normalized) {
1192
1173
  this.#registerPlugin(plugin);
1193
1174
  this.plugins.set(plugin.name, plugin);
1194
1175
  }
1195
1176
  if (this.config.adapter) this.#adapterSource = inputToAdapterSource(this.config);
1196
1177
  }
1178
+ /**
1179
+ * Orders plugins so every dependency runs before its dependents (Kahn's algorithm), with
1180
+ * `enforce` (`'pre'` before normal before `'post'`) and declaration order as tiebreaks.
1181
+ * A pairwise `Array.sort` comparator cannot do this: dependency relations are not transitive
1182
+ * at the comparator level, so a chain where A depends on B and B depends on C could come out
1183
+ * wrong when A and C are never compared directly. Dependencies on plugins missing from the
1184
+ * config are ignored here and surface later through `requirePlugin`.
1185
+ */
1186
+ #sortPlugins(plugins) {
1187
+ const queue = [...plugins].sort((a, b) => enforceWeight(a) - enforceWeight(b));
1188
+ const names = new Set(queue.map((plugin) => plugin.name));
1189
+ const blockedBy = new Map(queue.map((plugin) => [plugin.name, new Set(plugin.dependencies?.filter((name) => names.has(name) && name !== plugin.name))]));
1190
+ const sorted = [];
1191
+ for (const _ of plugins) {
1192
+ const index = queue.findIndex((plugin) => blockedBy.get(plugin.name)?.size === 0);
1193
+ if (index === -1) throw new Diagnostics.Error({
1194
+ code: Diagnostics.code.invalidPluginOptions,
1195
+ severity: "error",
1196
+ message: `Plugin dependencies form a cycle: ${queue.map((plugin) => plugin.name).join(" → ")}.`,
1197
+ help: "Remove one of the `dependencies` entries so the plugins can be ordered.",
1198
+ location: { kind: "config" }
1199
+ });
1200
+ const [plugin] = queue.splice(index, 1);
1201
+ if (!plugin) break;
1202
+ sorted.push(plugin);
1203
+ for (const blockers of blockedBy.values()) blockers.delete(plugin.name);
1204
+ }
1205
+ return sorted;
1206
+ }
1197
1207
  get hooks() {
1198
1208
  return this.options.hooks;
1199
1209
  }
1200
1210
  /**
1201
- * Builds a `NormalizedPlugin` from a hook-style plugin, filling in default
1202
- * options. Registering its lifecycle handlers on the `AsyncEventEmitter` is
1203
- * done separately by `#registerPlugin`.
1204
- */
1205
- #normalizePlugin(plugin) {
1206
- return {
1207
- name: plugin.name,
1208
- dependencies: plugin.dependencies,
1209
- enforce: plugin.enforce,
1210
- hooks: plugin.hooks,
1211
- options: plugin.options ?? {
1212
- output: {
1213
- path: ".",
1214
- mode: "directory"
1215
- },
1216
- exclude: [],
1217
- override: []
1218
- }
1219
- };
1220
- }
1221
- /**
1222
1211
  * Parses the adapter source into `this.inputNode`. Idempotent, so repeated calls from
1223
1212
  * `run` do not re-parse.
1224
1213
  */
@@ -1227,86 +1216,68 @@ var KubbDriver = class {
1227
1216
  this.inputNode = await this.adapter.parse(this.#adapterSource);
1228
1217
  }
1229
1218
  /**
1230
- * Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.
1231
- *
1232
- * The `kubb:plugin:setup` listener wraps the global context in a plugin-specific one so
1233
- * `addGenerator`, `setResolver`, and `setMacros` target the right `normalizedPlugin`.
1234
- * Every other `KubbHooks` event registers as a pass-through listener that external tooling
1235
- * can observe via `hooks.on(...)`.
1219
+ * Registers a plugin's lifecycle hooks on the shared `Hookable` as pass-through listeners that
1220
+ * external tooling can observe via `hooks.hook(...)`. The returned remover is tracked for
1221
+ * `dispose`. `kubb:plugin:setup` is skipped here; `setupHooks` invokes it directly with a
1222
+ * plugin-scoped context.
1236
1223
  *
1237
1224
  * @internal
1238
1225
  */
1239
1226
  #registerPlugin(plugin) {
1240
1227
  const { hooks } = plugin;
1241
1228
  if (!hooks) return;
1242
- if (hooks["kubb:plugin:setup"]) {
1243
- const setupHandler = (globalCtx) => {
1244
- const pluginCtx = {
1245
- ...globalCtx,
1246
- options: plugin.options ?? {},
1247
- addGenerator: (...generators) => {
1248
- for (const generator of generators.flat()) this.registerGenerator(plugin.name, generator);
1249
- },
1250
- setResolver: (resolver) => {
1251
- this.setPluginResolver(plugin.name, resolver);
1252
- },
1253
- addMacro: (macro) => {
1254
- this.#transforms.add(plugin.name, macro);
1255
- },
1256
- setMacros: (macros) => {
1257
- this.#transforms.set(plugin.name, macros);
1258
- },
1259
- setOptions: (opts) => {
1260
- plugin.options = {
1261
- ...plugin.options,
1262
- ...opts
1263
- };
1264
- if (plugin.options.output) {
1265
- const group = "group" in plugin.options ? plugin.options.group : void 0;
1266
- plugin.options.output = normalizeOutput({
1267
- output: plugin.options.output,
1268
- group,
1269
- pluginName: plugin.name
1270
- });
1271
- }
1272
- },
1273
- injectFile: (userFileNode) => {
1274
- this.fileManager.add(ast.factory.createFile(userFileNode));
1275
- }
1276
- };
1277
- return hooks["kubb:plugin:setup"](pluginCtx);
1278
- };
1279
- this.#trackListener("kubb:plugin:setup", setupHandler);
1280
- }
1281
- for (const event of Object.keys(hooks)) {
1282
- if (event === "kubb:plugin:setup") continue;
1283
- const handler = hooks[event];
1284
- if (!handler) continue;
1285
- this.#trackListener(event, handler);
1286
- }
1229
+ const { "kubb:plugin:setup": _setup, ...configHooks } = hooks;
1230
+ this.#unhooks.push(this.hooks.addHooks(configHooks));
1287
1231
  }
1288
1232
  /**
1289
- * Emits the `kubb:plugin:setup` event so that all registered hook-style plugin listeners
1290
- * can configure generators, resolvers, macros and renderers before `buildStart` runs.
1291
- *
1292
- * Called once from `run` before the plugin execution loop begins.
1233
+ * Runs each plugin's `kubb:plugin:setup` handler, in plugin order, with a context scoped to that
1234
+ * plugin so `addGenerator`, `setResolver`, `addMacro`, `setMacros`, and `setOptions` target its
1235
+ * `NormalizedPlugin` entry. Called once from `run` before the plugin execution loop begins, so
1236
+ * plugins can configure generators, resolvers, macros, and options before `buildStart`.
1293
1237
  */
1294
- async emitSetupHooks() {
1238
+ async setupHooks() {
1295
1239
  const noop = () => {};
1296
- await this.hooks.emit("kubb:plugin:setup", {
1297
- config: this.config,
1298
- options: {},
1299
- addGenerator: noop,
1300
- setResolver: noop,
1301
- addMacro: noop,
1302
- setMacros: noop,
1303
- setOptions: noop,
1304
- injectFile: noop,
1305
- updateConfig: noop
1306
- });
1240
+ for (const plugin of this.plugins.values()) {
1241
+ const setup = plugin.hooks?.["kubb:plugin:setup"];
1242
+ if (!setup) continue;
1243
+ await setup({
1244
+ config: this.config,
1245
+ options: plugin.options ?? {},
1246
+ updateConfig: noop,
1247
+ addGenerator: (...generators) => {
1248
+ for (const generator of generators) this.registerGenerator(plugin.name, generator);
1249
+ },
1250
+ setResolver: (resolver) => {
1251
+ this.setPluginResolver(plugin.name, resolver);
1252
+ },
1253
+ addMacro: (macro) => {
1254
+ this.#transforms.add(plugin.name, macro);
1255
+ },
1256
+ setMacros: (macros) => {
1257
+ this.#transforms.set(plugin.name, macros);
1258
+ },
1259
+ setOptions: (opts) => {
1260
+ plugin.options = {
1261
+ ...plugin.options,
1262
+ ...opts
1263
+ };
1264
+ if (plugin.options.output) {
1265
+ const group = "group" in plugin.options ? plugin.options.group : void 0;
1266
+ plugin.options.output = normalizeOutput({
1267
+ output: plugin.options.output,
1268
+ group,
1269
+ pluginName: plugin.name
1270
+ });
1271
+ }
1272
+ },
1273
+ injectFile: (userFileNode) => {
1274
+ this.fileManager.add(ast.factory.createFile(userFileNode));
1275
+ }
1276
+ });
1277
+ }
1307
1278
  }
1308
1279
  /**
1309
- * Registers a generator for the given plugin on the shared event emitter.
1280
+ * Registers a generator for the given plugin on the shared hook emitter.
1310
1281
  *
1311
1282
  * The generator's `schema`, `operation`, and `operations` methods are registered as
1312
1283
  * listeners on `kubb:generate:schema`, `kubb:generate:operation`, and `kubb:generate:operations`
@@ -1319,9 +1290,9 @@ var KubbDriver = class {
1319
1290
  * Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
1320
1291
  */
1321
1292
  registerGenerator(pluginName, generator) {
1322
- const register = (event, method) => {
1323
- if (!method) return;
1324
- const handler = async (node, ctx) => {
1293
+ const wrap = (method) => {
1294
+ if (!method) return void 0;
1295
+ return async (node, ctx) => {
1325
1296
  if (ctx.plugin.name !== pluginName) return;
1326
1297
  const result = await method(node, ctx);
1327
1298
  await this.dispatch({
@@ -1329,22 +1300,23 @@ var KubbDriver = class {
1329
1300
  renderer: generator.renderer
1330
1301
  });
1331
1302
  };
1332
- this.#trackListener(event, handler);
1333
1303
  };
1334
- register("kubb:generate:schema", generator.schema);
1335
- register("kubb:generate:operation", generator.operation);
1336
- register("kubb:generate:operations", generator.operations);
1337
- this.#eventGeneratorPlugins.add(pluginName);
1304
+ this.#unhooks.push(this.hooks.addHooks({
1305
+ "kubb:generate:schema": wrap(generator.schema),
1306
+ "kubb:generate:operation": wrap(generator.operation),
1307
+ "kubb:generate:operations": wrap(generator.operations)
1308
+ }));
1309
+ this.#hookGeneratorPlugins.add(pluginName);
1338
1310
  }
1339
1311
  /**
1340
1312
  * Returns `true` when at least one generator was registered for the given plugin
1341
1313
  * via `addGenerator()` in `kubb:plugin:setup`.
1342
1314
  *
1343
- * Used by the build loop to decide whether to walk the AST and emit generator events
1315
+ * Used by the build loop to decide whether to walk the AST and emit generator hooks
1344
1316
  * for a plugin.
1345
1317
  */
1346
- hasEventGenerators(pluginName) {
1347
- return this.#eventGeneratorPlugins.has(pluginName);
1318
+ hasHookGenerators(pluginName) {
1319
+ return this.#hookGeneratorPlugins.has(pluginName);
1348
1320
  }
1349
1321
  /**
1350
1322
  * Runs the full plugin pipeline. Returns the diagnostics collected so far even
@@ -1358,29 +1330,31 @@ var KubbDriver = class {
1358
1330
  const parsersMap = /* @__PURE__ */ new Map();
1359
1331
  for (const parser of config.parsers) if (parser.extNames) for (const ext of parser.extNames) parsersMap.set(ext, parser);
1360
1332
  const onWriteStart = async (files) => {
1361
- await hooks.emit("kubb:files:processing:start", { files });
1333
+ await hooks.callHook("kubb:files:processing:start", { files });
1362
1334
  };
1363
1335
  const updateBuffer = [];
1364
1336
  const onWriteUpdate = (item) => {
1365
1337
  updateBuffer.push(item);
1366
1338
  };
1367
1339
  const onWriteEnd = async (files) => {
1368
- await hooks.emit("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
1340
+ await hooks.callHook("kubb:files:processing:update", { files: updateBuffer.map((item) => ({
1369
1341
  ...item,
1370
1342
  config
1371
1343
  })) });
1372
1344
  updateBuffer.length = 0;
1373
- await hooks.emit("kubb:files:processing:end", { files });
1345
+ await hooks.callHook("kubb:files:processing:end", { files });
1374
1346
  };
1375
- fileManager.hooks.on("start", onWriteStart);
1376
- fileManager.hooks.on("update", onWriteUpdate);
1377
- fileManager.hooks.on("end", onWriteEnd);
1347
+ const unhookWrites = fileManager.hooks.addHooks({
1348
+ start: onWriteStart,
1349
+ update: onWriteUpdate,
1350
+ end: onWriteEnd
1351
+ });
1378
1352
  return Diagnostics.scope((diagnostic) => diagnostics.push(diagnostic), async () => {
1379
1353
  try {
1380
1354
  const outputRoot = resolve(config.root, config.output.path);
1381
1355
  await this.#parseInput();
1382
- await this.emitSetupHooks();
1383
- if (this.adapter && this.inputNode) await hooks.emit("kubb:build:start", Object.assign({
1356
+ await this.setupHooks();
1357
+ if (this.adapter && this.inputNode) await hooks.callHook("kubb:build:start", Object.assign({
1384
1358
  config,
1385
1359
  adapter: this.adapter,
1386
1360
  meta: this.inputNode.meta,
@@ -1391,9 +1365,9 @@ var KubbDriver = class {
1391
1365
  const context = this.getContext(plugin);
1392
1366
  const hrStart = process.hrtime();
1393
1367
  try {
1394
- await hooks.emit("kubb:plugin:start", { plugin });
1368
+ await hooks.callHook("kubb:plugin:start", { plugin });
1395
1369
  } catch (caughtError) {
1396
- const error = caughtError;
1370
+ const error = toError(caughtError);
1397
1371
  const duration = getElapsedMs(hrStart);
1398
1372
  await this.#emitPluginEnd({
1399
1373
  plugin,
@@ -1410,7 +1384,7 @@ var KubbDriver = class {
1410
1384
  }));
1411
1385
  continue;
1412
1386
  }
1413
- if (this.hasEventGenerators(plugin.name)) {
1387
+ if (this.hasHookGenerators(plugin.name)) {
1414
1388
  generatorPlugins.push({
1415
1389
  plugin,
1416
1390
  context,
@@ -1430,13 +1404,13 @@ var KubbDriver = class {
1430
1404
  });
1431
1405
  }
1432
1406
  diagnostics.push(...await this.#runGenerators(generatorPlugins));
1433
- await hooks.emit("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
1407
+ await hooks.callHook("kubb:plugins:end", Object.assign({ config }, this.#filesPayload()));
1434
1408
  await fileManager.write(fileManager.files, {
1435
1409
  storage,
1436
1410
  parsers: parsersMap,
1437
1411
  extension: config.output.extension
1438
1412
  });
1439
- await hooks.emit("kubb:build:end", {
1413
+ await hooks.callHook("kubb:build:end", {
1440
1414
  files: this.fileManager.files,
1441
1415
  config,
1442
1416
  outputDir: outputRoot
@@ -1446,9 +1420,7 @@ var KubbDriver = class {
1446
1420
  diagnostics.push(Diagnostics.from(caughtError));
1447
1421
  return { diagnostics: Diagnostics.dedupe(diagnostics) };
1448
1422
  } finally {
1449
- fileManager.hooks.off("start", onWriteStart);
1450
- fileManager.hooks.off("update", onWriteUpdate);
1451
- fileManager.hooks.off("end", onWriteEnd);
1423
+ unhookWrites();
1452
1424
  }
1453
1425
  });
1454
1426
  }
@@ -1462,7 +1434,7 @@ var KubbDriver = class {
1462
1434
  };
1463
1435
  }
1464
1436
  #emitPluginEnd({ plugin, duration, success, error }) {
1465
- return this.hooks.emit("kubb:plugin:end", Object.assign({
1437
+ return this.hooks.callHook("kubb:plugin:end", Object.assign({
1466
1438
  plugin,
1467
1439
  duration,
1468
1440
  success,
@@ -1555,12 +1527,12 @@ var KubbDriver = class {
1555
1527
  if (!resolved) continue;
1556
1528
  const { transformedNode, options } = resolved;
1557
1529
  if (allowedSchemaNames !== null && transformedNode.name && !allowedSchemaNames.has(transformedNode.name)) continue;
1558
- await this.hooks.emit("kubb:generate:schema", transformedNode, {
1530
+ await this.hooks.callHook("kubb:generate:schema", transformedNode, {
1559
1531
  ...generatorContext,
1560
1532
  options
1561
1533
  });
1562
1534
  } catch (caughtError) {
1563
- error = caughtError;
1535
+ error = toError(caughtError);
1564
1536
  }
1565
1537
  }
1566
1538
  if (emitsOperationHook) for (const node of operations) {
@@ -1568,12 +1540,12 @@ var KubbDriver = class {
1568
1540
  try {
1569
1541
  const resolved = resolveForPlugin(node);
1570
1542
  if (!resolved) continue;
1571
- await this.hooks.emit("kubb:generate:operation", resolved.transformedNode, {
1543
+ await this.hooks.callHook("kubb:generate:operation", resolved.transformedNode, {
1572
1544
  ...generatorContext,
1573
1545
  options: resolved.options
1574
1546
  });
1575
1547
  } catch (caughtError) {
1576
- error = caughtError;
1548
+ error = toError(caughtError);
1577
1549
  }
1578
1550
  }
1579
1551
  if (!error && emitsOperationsHook) try {
@@ -1586,9 +1558,9 @@ var KubbDriver = class {
1586
1558
  if (resolved) acc.push(resolved.transformedNode);
1587
1559
  return acc;
1588
1560
  }, []);
1589
- await this.hooks.emit("kubb:generate:operations", pluginOperations, ctx);
1561
+ await this.hooks.callHook("kubb:generate:operations", pluginOperations, ctx);
1590
1562
  } catch (caughtError) {
1591
- error = caughtError;
1563
+ error = toError(caughtError);
1592
1564
  }
1593
1565
  const duration = getElapsedMs(hrStart);
1594
1566
  await this.#emitPluginEnd({
@@ -1645,9 +1617,9 @@ var KubbDriver = class {
1645
1617
  * @internal
1646
1618
  */
1647
1619
  dispose() {
1648
- for (const [event, handler] of this.#listeners) this.hooks.off(event, handler);
1649
- this.#listeners.length = 0;
1650
- this.#eventGeneratorPlugins.clear();
1620
+ for (const unhook of this.#unhooks) unhook();
1621
+ this.#unhooks.length = 0;
1622
+ this.#hookGeneratorPlugins.clear();
1651
1623
  this.#transforms.dispose();
1652
1624
  this.#resolvers.clear();
1653
1625
  this.#defaultResolvers.clear();
@@ -1740,18 +1712,18 @@ var KubbDriver = class {
1740
1712
  return this.plugins.get(pluginName);
1741
1713
  }
1742
1714
  requirePlugin(pluginName, context) {
1743
- const plugin = this.plugins.get(pluginName);
1744
- if (!plugin) {
1745
- const requiredBy = context?.requiredBy;
1746
- throw new Diagnostics.Error({
1747
- code: Diagnostics.code.pluginNotFound,
1748
- severity: "error",
1749
- message: requiredBy ? `Plugin "${pluginName}" is required by "${requiredBy}" but not found. Make sure it is included in your Kubb config.` : `Plugin "${pluginName}" is required but not found. Make sure it is included in your Kubb config.`,
1750
- help: requiredBy ? `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts (required by "${requiredBy}"), or remove the dependency on it.` : `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts, or remove the dependency on it.`,
1751
- location: { kind: "config" }
1752
- });
1753
- }
1754
- return plugin;
1715
+ const plugin = this.getPlugin(pluginName);
1716
+ if (plugin) return plugin;
1717
+ const requiredBy = context?.requiredBy;
1718
+ const by = requiredBy ? ` by "${requiredBy}"` : "";
1719
+ const help = requiredBy ? ` (required by "${requiredBy}")` : "";
1720
+ throw new Diagnostics.Error({
1721
+ code: Diagnostics.code.pluginNotFound,
1722
+ severity: "error",
1723
+ message: `Plugin "${pluginName}" is required${by} but not found. Make sure it is included in your Kubb config.`,
1724
+ help: `Add "${pluginName}" to the \`plugins\` array in kubb.config.ts${help}, or remove the dependency on it.`,
1725
+ location: { kind: "config" }
1726
+ });
1755
1727
  }
1756
1728
  };
1757
1729
  function inputToAdapterSource(config) {
@@ -1940,12 +1912,12 @@ function resolveConfig(userConfig) {
1940
1912
  * `createKubb` takes a plain config object (the shape `defineConfig` produces),
1941
1913
  * not a fluent builder.
1942
1914
  *
1943
- * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
1915
+ * Attach hook listeners to `.hooks` before calling `setup()` or `build()`.
1944
1916
  *
1945
1917
  * @example
1946
1918
  * ```ts
1947
1919
  * const kubb = createKubb(userConfig)
1948
- * kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
1920
+ * kubb.hooks.hook('kubb:plugin:end', ({ plugin, duration }) => console.log(plugin.name, duration))
1949
1921
  * const { files, diagnostics } = await kubb.safeBuild()
1950
1922
  * ```
1951
1923
  */
@@ -1956,7 +1928,7 @@ var Kubb = class {
1956
1928
  #storage = null;
1957
1929
  constructor(userConfig, options = {}) {
1958
1930
  this.config = resolveConfig(userConfig);
1959
- this.hooks = options.hooks ?? new AsyncEventEmitter();
1931
+ this.hooks = options.hooks ?? new Hookable();
1960
1932
  }
1961
1933
  get storage() {
1962
1934
  if (!this.#storage) throw new Error("[kubb] setup() must be called before accessing storage");
@@ -1999,9 +1971,9 @@ var Kubb = class {
1999
1971
  try {
2000
1972
  var _usingCtx$1 = _usingCtx();
2001
1973
  if (!this.#driver) await this.setup();
2002
- const cleanup = _usingCtx$1.u(this);
2003
- const driver = cleanup.driver;
2004
- const storage = cleanup.storage;
1974
+ const self = _usingCtx$1.u(this);
1975
+ const driver = self.driver;
1976
+ const storage = self.storage;
2005
1977
  const { diagnostics } = await driver.run({ storage });
2006
1978
  return {
2007
1979
  diagnostics,
@@ -2062,7 +2034,7 @@ const logLevel = {
2062
2034
  /**
2063
2035
  * Defines a reporter. When the definition has a `drain`, the returned reporter buffers each value
2064
2036
  * `report` returns and hands the array to `drain` once, then clears it. Without a `drain`, nothing
2065
- * is buffered. Wiring the reporter onto the run's events is the host's job, so the reporter only
2037
+ * is buffered. Wiring the reporter onto the run's hooks is the host's job, so the reporter only
2066
2038
  * ever deals with a {@link GenerationResult}.
2067
2039
  *
2068
2040
  * @example
@@ -2246,7 +2218,7 @@ function buildTimingSection(report) {
2246
2218
  * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`).
2247
2219
  *
2248
2220
  * @note It captures the collected diagnostics once a config finishes, not the live
2249
- * `kubb:info`/`kubb:plugin` event stream. Color is stripped so the file stays plain text even when
2221
+ * `kubb:info`/`kubb:plugin` hook stream. Color is stripped so the file stays plain text even when
2250
2222
  * the run is attached to a TTY.
2251
2223
  */
2252
2224
  const fileReporter = createReporter({
@@ -2434,6 +2406,6 @@ const memoryStorage = createStorage(() => {
2434
2406
  };
2435
2407
  });
2436
2408
  //#endregion
2437
- export { AsyncEventEmitter, Diagnostics, KubbDriver, Resolver, cliReporter, createAdapter, createKubb, createRenderer, createReporter, createResolver, createStorage, defineGenerator, defineParser, definePlugin, fileReporter, fsStorage, jsonReporter, logLevel, memoryStorage };
2409
+ export { Diagnostics, Hookable, KubbDriver, Resolver, cliReporter, createAdapter, createKubb, createRenderer, createReporter, createResolver, createStorage, defineGenerator, defineParser, definePlugin, fileReporter, fsStorage, jsonReporter, logLevel, memoryStorage };
2438
2410
 
2439
2411
  //# sourceMappingURL=index.js.map