@seedcord/services 0.3.3 → 0.5.0

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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Envapter, Envapt } from 'envapt';
2
2
  import { format, transports, createLogger } from 'winston';
3
+ import chalk2 from 'chalk';
3
4
  import { createServer } from 'http';
4
- import chalk from 'chalk';
5
5
  import { EventEmitter } from 'events';
6
6
 
7
7
  var __defProp = Object.defineProperty;
@@ -230,6 +230,230 @@ ${parts.join(" ")}`;
230
230
  logger.silly(msg, ...args);
231
231
  }
232
232
  };
233
+
234
+ // src/CooldownManager.ts
235
+ var CooldownManager = class {
236
+ static {
237
+ __name(this, "CooldownManager");
238
+ }
239
+ window;
240
+ Err;
241
+ msg;
242
+ map = /* @__PURE__ */ new Map();
243
+ /**
244
+ * Creates a new CooldownManager instance.
245
+ *
246
+ * @param opts - Configuration options for the cooldown behavior
247
+ */
248
+ constructor(opts = {}) {
249
+ this.window = opts.cooldown ?? 1e3;
250
+ this.Err = opts.err ?? Error;
251
+ this.msg = opts.message ?? "Cooldown active";
252
+ }
253
+ /**
254
+ * Records usage timestamp for a key without any cooldown checks.
255
+ *
256
+ * @param key - The unique identifier for the cooldown entry
257
+ */
258
+ set(key) {
259
+ this.map.set(key, Date.now());
260
+ }
261
+ /**
262
+ * Verifies cooldown status for a key and updates timestamp if not active.
263
+ *
264
+ * If the cooldown is still active, throws the configured error.
265
+ * If not active, updates the timestamp and returns successfully.
266
+ *
267
+ * @param key - The unique identifier to check cooldown for
268
+ * @throws An {@link Err} When the cooldown is still active for the given key
269
+ */
270
+ check(key) {
271
+ const now = Date.now();
272
+ const last = this.map.get(key);
273
+ const remaining = this.window - (now - (last ?? 0));
274
+ if (Envapter.isDevelopment && remaining > 0) {
275
+ Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
276
+ }
277
+ if (last !== void 0 && remaining > 0) {
278
+ throw new this.Err(this.msg, remaining);
279
+ }
280
+ this.map.set(key, now);
281
+ }
282
+ /**
283
+ * Checks if a key is currently cooling down without updating timestamp.
284
+ *
285
+ * @param key - The unique identifier to check
286
+ * @returns True if the key is still cooling down, false otherwise
287
+ */
288
+ isActive(key) {
289
+ const last = this.map.get(key);
290
+ return last !== void 0 && Date.now() - last < this.window;
291
+ }
292
+ /**
293
+ * Removes a key from the cooldown map.
294
+ *
295
+ * @param key - The unique identifier to remove (useful for manual resets)
296
+ */
297
+ clear(key) {
298
+ this.map.delete(key);
299
+ }
300
+ };
301
+
302
+ // src/Errors/ErrorCodes.ts
303
+ var SeedcordErrorCode = /* @__PURE__ */ (function(SeedcordErrorCode2) {
304
+ SeedcordErrorCode2[SeedcordErrorCode2["ConfigMissingDiscordToken"] = 1001] = "ConfigMissingDiscordToken";
305
+ SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookMissing"] = 1002] = "ConfigUnknownExceptionWebhookMissing";
306
+ SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookInvalid"] = 1003] = "ConfigUnknownExceptionWebhookInvalid";
307
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddAfterCompletion"] = 1101] = "LifecycleAddAfterCompletion";
308
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddDuringRun"] = 1102] = "LifecycleAddDuringRun";
309
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecycleRemoveDuringRun"] = 1103] = "LifecycleRemoveDuringRun";
310
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecycleUnknownPhase"] = 1104] = "LifecycleUnknownPhase";
311
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecyclePhaseFailures"] = 1105] = "LifecyclePhaseFailures";
312
+ SeedcordErrorCode2[SeedcordErrorCode2["LifecycleTaskTimeout"] = 1106] = "LifecycleTaskTimeout";
313
+ SeedcordErrorCode2[SeedcordErrorCode2["CoreSingletonViolation"] = 1201] = "CoreSingletonViolation";
314
+ SeedcordErrorCode2[SeedcordErrorCode2["CorePluginAfterInit"] = 1202] = "CorePluginAfterInit";
315
+ SeedcordErrorCode2[SeedcordErrorCode2["CorePluginKeyExists"] = 1203] = "CorePluginKeyExists";
316
+ SeedcordErrorCode2[SeedcordErrorCode2["CoreBotRoleMissing"] = 1204] = "CoreBotRoleMissing";
317
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInteractionEventFilter"] = 1301] = "DecoratorInteractionEventFilter";
318
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorMethodNotFound"] = 1302] = "DecoratorMethodNotFound";
319
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandAlreadyRegistered"] = 1303] = "DecoratorCommandAlreadyRegistered";
320
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGlobalWithGuilds"] = 1304] = "DecoratorCommandGlobalWithGuilds";
321
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGuildWithoutGuilds"] = 1305] = "DecoratorCommandGuildWithoutGuilds";
322
+ SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInvalidMiddlewarePriority"] = 1306] = "DecoratorInvalidMiddlewarePriority";
323
+ SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInputType"] = 1401] = "UtilHexInputType";
324
+ SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInvalid"] = 1402] = "UtilHexInvalid";
325
+ SeedcordErrorCode2[SeedcordErrorCode2["UtilInvalidSlashRouteArgument"] = 1403] = "UtilInvalidSlashRouteArgument";
326
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoServiceDecoratorMissing"] = 2101] = "PluginMongoServiceDecoratorMissing";
327
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoModelDecoratorMissing"] = 2102] = "PluginMongoModelDecoratorMissing";
328
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoConnectionFailed"] = 2103] = "PluginMongoConnectionFailed";
329
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceDecoratorMissing"] = 2201] = "PluginKpgServiceDecoratorMissing";
330
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceTableMissing"] = 2202] = "PluginKpgServiceTableMissing";
331
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidStepCount"] = 2203] = "PluginKpgInvalidStepCount";
332
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnknownDirection"] = 2204] = "PluginKpgUnknownDirection";
333
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnresolvedMigrationsPath"] = 2205] = "PluginKpgUnresolvedMigrationsPath";
334
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNoMigrationFiles"] = 2206] = "PluginKpgNoMigrationFiles";
335
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidMigrationModule"] = 2207] = "PluginKpgInvalidMigrationModule";
336
+ SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNonErrorFailure"] = 2208] = "PluginKpgNonErrorFailure";
337
+ return SeedcordErrorCode2;
338
+ })({});
339
+
340
+ // src/Errors/ErrorMessages.ts
341
+ var messages = {
342
+ [SeedcordErrorCode.ConfigMissingDiscordToken]: () => "Missing DISCORD_BOT_TOKEN environment variable.",
343
+ [SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing]: () => "Missing UNKNOWN_EXCEPTION_WEBHOOK_URL environment variable.",
344
+ [SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid]: () => "Invalid UNKNOWN_EXCEPTION_WEBHOOK_URL value.",
345
+ [SeedcordErrorCode.LifecycleAddAfterCompletion]: () => "Cannot add tasks after startup sequence has already completed.",
346
+ [SeedcordErrorCode.LifecycleAddDuringRun]: () => "Cannot add tasks while startup sequence is in progress.",
347
+ [SeedcordErrorCode.LifecycleRemoveDuringRun]: () => "Cannot remove tasks while startup sequence is in progress.",
348
+ [SeedcordErrorCode.LifecycleUnknownPhase]: (phase) => `Unknown phase: ${String(phase)}.`,
349
+ [SeedcordErrorCode.LifecyclePhaseFailures]: (phase, failures) => `Phase ${phase} completed with ${failures} failed task${failures === 1 ? "" : "s"}.`,
350
+ [SeedcordErrorCode.LifecycleTaskTimeout]: (taskName, timeout) => `Task "${taskName}" timed out after ${timeout}ms.`,
351
+ [SeedcordErrorCode.CoreSingletonViolation]: () => "Seedcord can only be instantiated once. Use the existing instance instead.",
352
+ [SeedcordErrorCode.CorePluginAfterInit]: () => "Cannot attach a plugin after initialization.",
353
+ [SeedcordErrorCode.CorePluginKeyExists]: (key) => `Plugin with key "${key}" already exists.`,
354
+ [SeedcordErrorCode.CoreBotRoleMissing]: (guildId) => guildId ? `Bot role not found in guild ${guildId}.` : "Bot role not found in guild.",
355
+ [SeedcordErrorCode.DecoratorInteractionEventFilter]: () => "Interaction middleware cannot specify event filters.",
356
+ [SeedcordErrorCode.DecoratorMethodNotFound]: () => "Decorator could not locate the original method. Ensure the method exists before applying the decorator.",
357
+ [SeedcordErrorCode.DecoratorCommandAlreadyRegistered]: (commandName, existingScope, requestedScope) => `Command "${commandName}" is already registered as a "${existingScope}" command and cannot be re-registered as a "${requestedScope}" command.`,
358
+ [SeedcordErrorCode.DecoratorCommandGlobalWithGuilds]: () => 'RegisterCommand("global") cannot have guilds specified.',
359
+ [SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds]: () => 'RegisterCommand("guild") requires a non-empty guilds array.',
360
+ [SeedcordErrorCode.DecoratorInvalidMiddlewarePriority]: () => "Middleware priority must be a finite number.",
361
+ [SeedcordErrorCode.UtilHexInputType]: () => "hexToNumber expects a string input.",
362
+ [SeedcordErrorCode.UtilHexInvalid]: () => "Invalid hex string.",
363
+ [SeedcordErrorCode.UtilInvalidSlashRouteArgument]: () => "Invalid argument passed to buildSlashRoute.",
364
+ [SeedcordErrorCode.PluginMongoServiceDecoratorMissing]: (className) => `Missing @RegisterMongoService on ${className}.`,
365
+ [SeedcordErrorCode.PluginMongoModelDecoratorMissing]: (className) => `Missing @RegisterMongoModel on ${className}.`,
366
+ [SeedcordErrorCode.PluginMongoConnectionFailed]: (databaseName) => databaseName ? `Could not connect to MongoDB (${databaseName}).` : "Could not connect to MongoDB.",
367
+ [SeedcordErrorCode.PluginKpgServiceDecoratorMissing]: (className) => `Missing @RegisterKpgService on ${className}.`,
368
+ [SeedcordErrorCode.PluginKpgServiceTableMissing]: (className) => `Missing table metadata for ${className}. Provide a table via @RegisterKpgService().`,
369
+ [SeedcordErrorCode.PluginKpgInvalidStepCount]: () => "Migration step count must be a non-negative integer.",
370
+ [SeedcordErrorCode.PluginKpgUnknownDirection]: (direction) => `Unknown migration direction: ${String(direction)}.`,
371
+ [SeedcordErrorCode.PluginKpgUnresolvedMigrationsPath]: (label) => `Unable to resolve migrations at path: ${label}.`,
372
+ [SeedcordErrorCode.PluginKpgNoMigrationFiles]: () => "No migration files provided.",
373
+ [SeedcordErrorCode.PluginKpgInvalidMigrationModule]: (filePath) => `Migration file ${filePath} must export async functions up and down.`,
374
+ [SeedcordErrorCode.PluginKpgNonErrorFailure]: (message) => `Migration failure: ${message}.`
375
+ };
376
+ function formatSeedcordErrorMessage(code, args) {
377
+ const formatter = messages[code];
378
+ const resolvedArgs = args ?? [];
379
+ return formatter(...resolvedArgs);
380
+ }
381
+ __name(formatSeedcordErrorMessage, "formatSeedcordErrorMessage");
382
+ function resolveIdentifier(code) {
383
+ return SeedcordErrorCode[code];
384
+ }
385
+ __name(resolveIdentifier, "resolveIdentifier");
386
+ function resolveMessage(code, args) {
387
+ return formatSeedcordErrorMessage(code, args);
388
+ }
389
+ __name(resolveMessage, "resolveMessage");
390
+ function formatErrorName(name, _identifier, code) {
391
+ return `${chalk2.bold.red(name)}[${chalk2.gray(code)}]`;
392
+ }
393
+ __name(formatErrorName, "formatErrorName");
394
+ var SeedcordError = class extends Error {
395
+ static {
396
+ __name(this, "SeedcordError");
397
+ }
398
+ code;
399
+ identifier;
400
+ constructor(code, args, options) {
401
+ const message = resolveMessage(code, args);
402
+ super(message, options);
403
+ this.code = code;
404
+ this.identifier = resolveIdentifier(code);
405
+ this.name = formatErrorName(new.target.name, this.identifier, this.code);
406
+ Object.setPrototypeOf(this, new.target.prototype);
407
+ if (typeof Error.captureStackTrace === "function") {
408
+ Error.captureStackTrace(this, new.target);
409
+ }
410
+ }
411
+ };
412
+ var SeedcordTypeError = class extends TypeError {
413
+ static {
414
+ __name(this, "SeedcordTypeError");
415
+ }
416
+ code;
417
+ identifier;
418
+ constructor(code, args, options) {
419
+ const message = resolveMessage(code, args);
420
+ super(message, options);
421
+ this.code = code;
422
+ this.identifier = resolveIdentifier(code);
423
+ this.name = formatErrorName(new.target.name, this.identifier, this.code);
424
+ Object.setPrototypeOf(this, new.target.prototype);
425
+ if (typeof Error.captureStackTrace === "function") {
426
+ Error.captureStackTrace(this, new.target);
427
+ }
428
+ }
429
+ };
430
+ var SeedcordRangeError = class extends RangeError {
431
+ static {
432
+ __name(this, "SeedcordRangeError");
433
+ }
434
+ code;
435
+ identifier;
436
+ constructor(code, args, options) {
437
+ const message = resolveMessage(code, args);
438
+ super(message, options);
439
+ this.code = code;
440
+ this.identifier = resolveIdentifier(code);
441
+ this.name = formatErrorName(new.target.name, this.identifier, this.code);
442
+ Object.setPrototypeOf(this, new.target.prototype);
443
+ if (typeof Error.captureStackTrace === "function") {
444
+ Error.captureStackTrace(this, new.target);
445
+ }
446
+ }
447
+ };
448
+ var SeedcordErrors = {
449
+ Error: SeedcordError,
450
+ TypeError: SeedcordTypeError,
451
+ RangeError: SeedcordRangeError
452
+ };
453
+ function isSeedcordError(error) {
454
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "number" && "identifier" in error && typeof error.identifier === "string";
455
+ }
456
+ __name(isSeedcordError, "isSeedcordError");
233
457
  var CoordinatedLifecycle = class {
234
458
  static {
235
459
  __name(this, "CoordinatedLifecycle");
@@ -265,13 +489,17 @@ var CoordinatedLifecycle = class {
265
489
  addTask(phase, taskName, task, timeoutMs) {
266
490
  if (!this.canAddTask()) return;
267
491
  const tasks = this.tasksMap.get(phase);
268
- if (!tasks) throw new Error(`Unknown phase: ${phase}`);
492
+ if (!tasks) {
493
+ throw new SeedcordError(SeedcordErrorCode.LifecycleUnknownPhase, [
494
+ phase
495
+ ]);
496
+ }
269
497
  tasks.push({
270
498
  name: taskName,
271
499
  task,
272
500
  timeout: timeoutMs
273
501
  });
274
- this.logger.debug(`${chalk.italic("Added")} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} to phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
502
+ this.logger.debug(`${chalk2.italic("Added")} ${this.getTaskType()} task ${chalk2.bold.cyan(taskName)} to phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
275
503
  }
276
504
  /**
277
505
  * Removes a lifecycle task from a specific phase.
@@ -289,7 +517,7 @@ var CoordinatedLifecycle = class {
289
517
  this.tasksMap.set(phase, filteredTasks);
290
518
  const removed = initialLength !== filteredTasks.length;
291
519
  if (removed) {
292
- this.logger.debug(`${chalk.italic("Removed")} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} from phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
520
+ this.logger.debug(`${chalk2.italic("Removed")} ${this.getTaskType()} task ${chalk2.bold.cyan(taskName)} from phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
293
521
  }
294
522
  return removed;
295
523
  }
@@ -299,18 +527,20 @@ var CoordinatedLifecycle = class {
299
527
  async runPhase(phase) {
300
528
  const tasks = this.tasksMap.get(phase) ?? [];
301
529
  if (tasks.length === 0) {
302
- this.logger.warn(`No tasks to run in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
530
+ this.logger.warn(`No tasks to run in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
303
531
  return;
304
532
  }
305
- this.logger.info(`${chalk.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk.bold.magenta(this.phaseEnum[phase])} with ${chalk.bold.cyan(tasks.length)} tasks`);
533
+ this.logger.info(`${chalk2.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk2.bold.magenta(this.phaseEnum[phase])} with ${chalk2.bold.cyan(tasks.length)} tasks`);
306
534
  this.emit(`phase:${phase}:start`);
307
535
  const results = await this.executeTasksInPhase(phase, tasks);
308
536
  const failures = results.filter((r) => r.status === "rejected").length;
309
537
  if (failures > 0) {
310
- const errorMessage = `Phase ${chalk.bold.magenta(this.phaseEnum[phase])} completed with ${chalk.bold.red(failures)} failed tasks`;
311
- throw new Error(errorMessage);
538
+ throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [
539
+ chalk2.bold.magenta(this.phaseEnum[phase]),
540
+ failures
541
+ ]);
312
542
  } else {
313
- this.logger.info(`Phase ${chalk.bold.magenta(this.phaseEnum[phase])} ${chalk.bold.green("completed successfully")}`);
543
+ this.logger.info(`Phase ${chalk2.bold.magenta(this.phaseEnum[phase])} ${chalk2.bold.green("completed successfully")}`);
314
544
  }
315
545
  this.emit(`phase:${phase}:complete`);
316
546
  }
@@ -318,19 +548,22 @@ var CoordinatedLifecycle = class {
318
548
  * Run a single task with timeout
319
549
  */
320
550
  async runTaskWithTimeout(phase, task) {
321
- this.logger.info(`${chalk.italic("Starting")} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
551
+ this.logger.info(`${chalk2.italic("Starting")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
322
552
  try {
323
553
  await Promise.race([
324
554
  task.task(),
325
555
  new Promise((_, reject) => {
326
556
  setTimeout(() => {
327
- reject(new Error(`Task '${task.name}' timed out after ${task.timeout}ms`));
557
+ reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [
558
+ task.name,
559
+ task.timeout
560
+ ]));
328
561
  }, task.timeout);
329
562
  })
330
563
  ]);
331
- this.logger.info(`${chalk.italic("Completed")} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
564
+ this.logger.info(`${chalk2.italic("Completed")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
332
565
  } catch (error) {
333
- this.logger.error(`${chalk.italic("Failed")} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}:`, error);
566
+ this.logger.error(`${chalk2.italic("Failed")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}:`, error);
334
567
  throw error;
335
568
  }
336
569
  }
@@ -418,11 +651,11 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
418
651
  registerSignalHandlers() {
419
652
  if (!this.isShutdownEnabled) return;
420
653
  process.on("SIGTERM", () => {
421
- this.logger.info(`Received ${chalk.yellow.bold("SIGTERM")} signal`);
654
+ this.logger.info(`Received ${chalk2.yellow.bold("SIGTERM")} signal`);
422
655
  void this.run(0);
423
656
  });
424
657
  process.on("SIGINT", () => {
425
- this.logger.info(`Received ${chalk.yellow.bold("SIGINT")} signal`);
658
+ this.logger.info(`Received ${chalk2.yellow.bold("SIGINT")} signal`);
426
659
  void this.run(0);
427
660
  });
428
661
  }
@@ -469,19 +702,19 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
469
702
  }
470
703
  this.isShuttingDown = true;
471
704
  this.exitCode = exitCode;
472
- this.logger.info(`${chalk.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk.bold.cyan(exitCode)}`);
705
+ this.logger.info(`${chalk2.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk2.bold.cyan(exitCode)}`);
473
706
  this.emit("shutdown:start");
474
707
  try {
475
708
  for (const phase of PHASE_ORDER) {
476
709
  await this.runPhase(phase);
477
710
  }
478
- this.logger.info(`${chalk.bold.green("Coordinated shutdown completed")} successfully`);
711
+ this.logger.info(`${chalk2.bold.green("Coordinated shutdown completed")} successfully`);
479
712
  this.emit("shutdown:complete");
480
713
  } catch (error) {
481
- this.logger.error(`${chalk.bold.red("Coordinated shutdown failed")}`);
714
+ this.logger.error(`${chalk2.bold.red("Coordinated shutdown failed")}`);
482
715
  this.emit("shutdown:error", error);
483
716
  } finally {
484
- this.logger.info(`${chalk.bold.red("Exiting")} process with code ${chalk.bold.cyan(this.exitCode)}`);
717
+ this.logger.info(`${chalk2.bold.red("Exiting")} process with code ${chalk2.bold.cyan(this.exitCode)}`);
485
718
  setTimeout(() => {
486
719
  process.exit(this.exitCode);
487
720
  }, LOG_FLUSH_DELAY_MS);
@@ -557,7 +790,7 @@ var HealthCheck = class {
557
790
  this.server.on("error", reject);
558
791
  this.server.once("listening", () => {
559
792
  const address = this.host ?? "localhost";
560
- this.logger.info(`${chalk.green.bold("\u2713")} Health check server listening on ${chalk.cyan(`http://${address}:${this.port}${this.path}`)}`);
793
+ this.logger.info(`${chalk2.green.bold("\u2713")} Health check server listening on ${chalk2.cyan(`http://${address}:${this.port}${this.path}`)}`);
561
794
  resolve();
562
795
  });
563
796
  if (this.host) {
@@ -580,7 +813,7 @@ var HealthCheck = class {
580
813
  return new Promise((resolve) => {
581
814
  server.once("close", () => resolve());
582
815
  server.close(() => {
583
- this.logger.info(chalk.bold.red("Health check server stopped"));
816
+ this.logger.info(chalk2.bold.red("Health check server stopped"));
584
817
  });
585
818
  });
586
819
  }
@@ -603,70 +836,136 @@ _ts_decorate2([
603
836
  Envapt("HEALTH_CHECK_HOST"),
604
837
  _ts_metadata2("design:type", Object)
605
838
  ], HealthCheck.prototype, "host", void 0);
606
- var CooldownManager = class {
839
+ var StrictEventEmitter = class extends EventEmitter {
607
840
  static {
608
- __name(this, "CooldownManager");
841
+ __name(this, "StrictEventEmitter");
609
842
  }
610
- window;
611
- Err;
612
- msg;
613
- map = /* @__PURE__ */ new Map();
614
843
  /**
615
- * Creates a new CooldownManager instance.
844
+ * Registers a persistent listener with tuple-safe arguments for the given event.
616
845
  *
617
- * @param opts - Configuration options for the cooldown behavior
846
+ * @param event - The event name to attach to
847
+ * @param listener - Callback operating on the typed argument tuple for the event
848
+ * @returns This emitter instance for chaining
618
849
  */
619
- constructor(opts = {}) {
620
- this.window = opts.cooldown ?? 1e3;
621
- this.Err = opts.err ?? Error;
622
- this.msg = opts.message ?? "Cooldown active";
850
+ on(event, listener) {
851
+ return super.on(event, listener);
623
852
  }
624
853
  /**
625
- * Records usage timestamp for a key without any cooldown checks.
854
+ * Registers a one time listener that is removed after the first invocation.
626
855
  *
627
- * @param key - The unique identifier for the cooldown entry
856
+ * @param event - The event name to attach to
857
+ * @param listener - Callback operating on the typed argument tuple for the event
858
+ * @returns This emitter instance for chaining
628
859
  */
629
- set(key) {
630
- this.map.set(key, Date.now());
860
+ once(event, listener) {
861
+ return super.once(event, listener);
631
862
  }
632
863
  /**
633
- * Verifies cooldown status for a key and updates timestamp if not active.
864
+ * Removes a previously registered listener for the given event.
634
865
  *
635
- * If the cooldown is still active, throws the configured error.
636
- * If not active, updates the timestamp and returns successfully.
866
+ * @param event - The event name whose listener should be removed
867
+ * @param listener - Callback originally registered for the event
868
+ * @returns This emitter instance for chaining
869
+ */
870
+ off(event, listener) {
871
+ return super.off(event, listener);
872
+ }
873
+ /**
874
+ * Alias of {@link StrictEventEmitter.on} for compatibility with Node.js EventEmitter APIs.
637
875
  *
638
- * @param key - The unique identifier to check cooldown for
639
- * @throws An {@link Err} When the cooldown is still active for the given key
876
+ * @param event - The event name to attach to
877
+ * @param listener - Callback operating on the typed argument tuple for the event
878
+ * @returns This emitter instance for chaining
640
879
  */
641
- check(key) {
642
- const now = Date.now();
643
- const last = this.map.get(key);
644
- const remaining = this.window - (now - (last ?? 0));
645
- if (Envapter.isDevelopment && remaining > 0) {
646
- Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
647
- }
648
- if (last !== void 0 && remaining > 0) {
649
- throw new this.Err(this.msg, remaining);
650
- }
651
- this.map.set(key, now);
880
+ addListener(event, listener) {
881
+ return this.on(event, listener);
652
882
  }
653
883
  /**
654
- * Checks if a key is currently cooling down without updating timestamp.
884
+ * Alias of {@link StrictEventEmitter.off} for compatibility with Node.js EventEmitter APIs.
655
885
  *
656
- * @param key - The unique identifier to check
657
- * @returns True if the key is still cooling down, false otherwise
886
+ * @param event - The event name whose listener should be removed
887
+ * @param listener - Callback originally registered for the event
888
+ * @returns This emitter instance for chaining
658
889
  */
659
- isActive(key) {
660
- const last = this.map.get(key);
661
- return last !== void 0 && Date.now() - last < this.window;
890
+ removeListener(event, listener) {
891
+ return super.removeListener(event, listener);
662
892
  }
663
893
  /**
664
- * Removes a key from the cooldown map.
894
+ * Emits an event with the strictly typed argument tuple for the event name.
665
895
  *
666
- * @param key - The unique identifier to remove (useful for manual resets)
896
+ * @param event - The event name to emit
897
+ * @param args - Tuple payload for the event
898
+ * @returns True when the event had listeners, false otherwise
667
899
  */
668
- clear(key) {
669
- this.map.delete(key);
900
+ emit(event, ...args) {
901
+ return super.emit(event, ...args);
902
+ }
903
+ /**
904
+ * Retrieves the listener list for a given event with the correct tuple signature.
905
+ *
906
+ * @param event - The event name to inspect
907
+ * @returns Array of listeners registered for the event
908
+ */
909
+ listeners(event) {
910
+ return super.listeners(event);
911
+ }
912
+ /**
913
+ * Counts listeners for an event without widening the return type of {@link EventEmitter.listenerCount}.
914
+ *
915
+ * @param event - The event name to inspect
916
+ * @returns The total number of listeners registered for the event
917
+ */
918
+ listenerCountTyped(event) {
919
+ return super.listenerCount(event);
920
+ }
921
+ /**
922
+ * Returns the list of event names known to the emitter with the mapped key type.
923
+ *
924
+ * @returns Array of event keys supported by the emitter
925
+ */
926
+ eventNamesTyped() {
927
+ return super.eventNames();
928
+ }
929
+ /**
930
+ * Waits for an event to be emitted, resolving with the listener arguments tuple once triggered.
931
+ * Supports optional abort signals and timeouts for cancellation semantics.
932
+ *
933
+ * @param event - The event name to wait for
934
+ * @param opts - Optional abort signal or timeout in milliseconds
935
+ * @returns Promise resolving with the emitted argument tuple; rejects when aborted or timed out
936
+ */
937
+ waitFor(event, opts) {
938
+ return new Promise((resolve, reject) => {
939
+ const onEvent = /* @__PURE__ */ __name((...args) => {
940
+ cleanup();
941
+ resolve(args);
942
+ }, "onEvent");
943
+ const onAbort = /* @__PURE__ */ __name(() => {
944
+ cleanup();
945
+ reject(Object.assign(new Error("Aborted"), {
946
+ name: "AbortError"
947
+ }));
948
+ }, "onAbort");
949
+ let timeoutId = null;
950
+ const cleanup = /* @__PURE__ */ __name(() => {
951
+ this.off(event, onEvent);
952
+ opts?.signal?.removeEventListener("abort", onAbort);
953
+ if (timeoutId) clearTimeout(timeoutId);
954
+ }, "cleanup");
955
+ this.once(event, onEvent);
956
+ if (opts?.signal) {
957
+ if (opts.signal.aborted) return onAbort();
958
+ opts.signal.addEventListener("abort", onAbort, {
959
+ once: true
960
+ });
961
+ }
962
+ if (opts?.timeoutMs !== void 0) {
963
+ timeoutId = setTimeout(() => {
964
+ cleanup();
965
+ reject(new Error("Timed out"));
966
+ }, opts.timeoutMs);
967
+ }
968
+ });
670
969
  }
671
970
  };
672
971
  var StartupPhase = /* @__PURE__ */ (function(StartupPhase2) {
@@ -710,16 +1009,16 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
710
1009
  }
711
1010
  canAddTask() {
712
1011
  if (this.hasStarted) {
713
- throw new Error("Cannot add tasks after startup sequence has already completed");
1012
+ throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);
714
1013
  }
715
1014
  if (this.isStartingUp) {
716
- throw new Error("Cannot add tasks while startup sequence is in progress");
1015
+ throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);
717
1016
  }
718
1017
  return true;
719
1018
  }
720
1019
  canRemoveTask() {
721
1020
  if (this.isStartingUp) {
722
- throw new Error("Cannot remove tasks while startup sequence is in progress");
1021
+ throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);
723
1022
  }
724
1023
  return true;
725
1024
  }
@@ -769,15 +1068,15 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
769
1068
  return;
770
1069
  }
771
1070
  this.isStartingUp = true;
772
- this.logger.info(`${chalk.bold.green("Starting")} coordinated startup sequence`);
1071
+ this.logger.info(`${chalk2.bold.green("Starting")} coordinated startup sequence`);
773
1072
  this.emit("startup:start");
774
1073
  try {
775
1074
  for (const phase of PHASE_ORDER2) await this.runPhase(phase);
776
1075
  this.hasStarted = true;
777
- this.logger.info(`${chalk.bold.green("Coordinated startup completed")} successfully`);
1076
+ this.logger.info(`${chalk2.bold.green("Coordinated startup completed")} successfully`);
778
1077
  this.emit("startup:complete");
779
1078
  } catch (error) {
780
- this.logger.error(`${chalk.bold.red("Coordinated startup failed")}`);
1079
+ this.logger.error(`${chalk2.bold.red("Coordinated startup failed")}`);
781
1080
  this.emit("startup:error", error);
782
1081
  throw error;
783
1082
  } finally {
@@ -810,6 +1109,6 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
810
1109
  }
811
1110
  };
812
1111
 
813
- export { CooldownManager, CoordinatedLifecycle, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Logger, ShutdownPhase, StartupPhase };
1112
+ export { CooldownManager, CoordinatedLifecycle, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Logger, SeedcordError, SeedcordErrorCode, SeedcordErrors, SeedcordRangeError, SeedcordTypeError, ShutdownPhase, StartupPhase, StrictEventEmitter, formatSeedcordErrorMessage, isSeedcordError, messages as seedcordErrorMessages };
814
1113
  //# sourceMappingURL=index.mjs.map
815
1114
  //# sourceMappingURL=index.mjs.map