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