seedcord 0.1.0-alpha.1 → 0.1.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
@@ -10,7 +10,6 @@ var winston = require('winston');
10
10
  var crypto2 = require('crypto');
11
11
  var events = require('events');
12
12
  var http = require('http');
13
- var mongoose2 = require('mongoose');
14
13
 
15
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
15
 
@@ -35,7 +34,6 @@ function _interopNamespace(e) {
35
34
  var chalk6__default = /*#__PURE__*/_interopDefault(chalk6);
36
35
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
37
36
  var crypto2__namespace = /*#__PURE__*/_interopNamespace(crypto2);
38
- var mongoose2__default = /*#__PURE__*/_interopDefault(mongoose2);
39
37
 
40
38
  var __defProp = Object.defineProperty;
41
39
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -63,9 +61,7 @@ var Globals = class extends envapt.Envapter {
63
61
  // Unknown Exception Webhook URL
64
62
  static unknownExceptionWebhookUrl;
65
63
  // Variables
66
- /**
67
- * The default color of the bot's embeds. Can simply override by `Globals.botColor =`
68
- */
64
+ /** Default color for bot embeds - can be overridden by setting Globals.botColor */
69
65
  static botColor = this.isProduction ? "#fe565a" : "#3fa045";
70
66
  };
71
67
  _ts_decorate([
@@ -111,9 +107,9 @@ var BuilderTypes = {
111
107
  menu_mentionable: discord_js.MentionableSelectMenuBuilder,
112
108
  menu_role: discord_js.RoleSelectMenuBuilder,
113
109
  modal: discord_js.ModalBuilder,
110
+ context_menu: discord_js.ContextMenuCommandBuilder,
114
111
  subcommand: discord_js.SlashCommandSubcommandBuilder,
115
- group: discord_js.SlashCommandSubcommandGroupBuilder,
116
- context_menu: discord_js.ContextMenuCommandBuilder
112
+ group: discord_js.SlashCommandSubcommandGroupBuilder
117
113
  };
118
114
  var RowTypes = {
119
115
  button: discord_js.ActionRowBuilder,
@@ -137,9 +133,12 @@ var BaseComponent = class BaseComponent2 {
137
133
  this._component = new ComponentClass();
138
134
  }
139
135
  /**
140
- * @description Returns the instantiated component
141
- * @note Use this for configuring the component using its instance setters.
142
- * @usage `this.instance.someMethod()`
136
+ * Gets the component instance for configuration
137
+ *
138
+ * Use this to access Discord.js builder methods like setTitle(), setDescription(), etc.
139
+ *
140
+ * Use this in your component classes to configure the builder
141
+ * @example this.instance.setTitle('My Modal')
143
142
  */
144
143
  get instance() {
145
144
  return this._component;
@@ -160,6 +159,16 @@ var BuilderComponent = class extends BaseComponent {
160
159
  get component() {
161
160
  return this.instance;
162
161
  }
162
+ /**
163
+ * Builds a customId string for interactive components
164
+ *
165
+ * Creates customIds in the format "prefix:arg1-arg2-arg3" for buttons, modals, etc.
166
+ * Arguments are joined with hyphens and separated from prefix with a colon.
167
+ *
168
+ * @param prefix - The route prefix that handlers will match against
169
+ * @param args - Additional arguments to encode in the customId
170
+ * @returns Formatted customId string
171
+ */
163
172
  buildCustomId(prefix, ...args) {
164
173
  if (args.length === 0) return prefix;
165
174
  return `${prefix}:${args.join("-")}`;
@@ -181,6 +190,11 @@ var ModalRow = class ModalRow2 extends RowComponent {
181
190
  static {
182
191
  __name(this, "ModalRow");
183
192
  }
193
+ /**
194
+ * Creates a new modal action row with the specified component.
195
+ *
196
+ * @param component - The modal field component to wrap in an action row
197
+ */
184
198
  constructor(component) {
185
199
  super("modal");
186
200
  this.instance.addComponents(component);
@@ -202,6 +216,9 @@ var BaseErrorEmbed = class extends BuilderComponent {
202
216
  static {
203
217
  __name(this, "BaseErrorEmbed");
204
218
  }
219
+ /**
220
+ * Creates a new error embed with default configuration.
221
+ */
205
222
  constructor() {
206
223
  super("embed");
207
224
  this.instance.setTitle("Cannot Proceed");
@@ -218,27 +235,32 @@ var CustomError = class extends Error {
218
235
  super(message), this.message = message;
219
236
  Error.captureStackTrace(this, this.constructor);
220
237
  }
238
+ /**
239
+ * Whether this error should be emitted to logs
240
+ *
241
+ * Controls logging behavior. Errors with emit=true will always be logged,
242
+ * while emit=false errors may be suppressed in production.
243
+ *
244
+ * @returns True if the error should be logged
245
+ */
221
246
  get emit() {
222
247
  return this._emit;
223
248
  }
224
249
  };
225
250
 
226
251
  // src/bot/errors/Database.ts
227
- var DatabaseConnectionFailure = class extends CustomError {
228
- static {
229
- __name(this, "DatabaseConnectionFailure");
230
- }
231
- constructor(message = "Failed to connect to the database.") {
232
- super(message);
233
- this.response.setDescription("Failed to connect to the database.");
234
- }
235
- };
236
252
  var DatabaseError = class extends CustomError {
237
253
  static {
238
254
  __name(this, "DatabaseError");
239
255
  }
240
256
  uuid;
241
257
  _emit = true;
258
+ /**
259
+ * Creates a new DatabaseError.
260
+ *
261
+ * @param message - The error message describing what went wrong
262
+ * @param uuid - A unique identifier for this specific error instance
263
+ */
242
264
  constructor(message, uuid) {
243
265
  super(message), this.uuid = uuid;
244
266
  this.name = "DatabaseError";
@@ -346,48 +368,125 @@ ${parts.join(" ")}`;
346
368
  transports: transportsArray
347
369
  });
348
370
  }
371
+ /**
372
+ * Logs an error message with optional additional data.
373
+ *
374
+ * @param msg - The error message to log
375
+ * @param args - Additional data to include in the log entry
376
+ */
349
377
  error(msg, ...args) {
350
378
  this.logger.error(msg, ...args);
351
379
  }
380
+ /**
381
+ * Logs a warning message with optional additional data.
382
+ *
383
+ * @param msg - The warning message to log
384
+ * @param args - Additional data to include in the log entry
385
+ */
352
386
  warn(msg, ...args) {
353
387
  this.logger.warn(msg, ...args);
354
388
  }
389
+ /**
390
+ * Logs an informational message with optional additional data.
391
+ *
392
+ * @param msg - The informational message to log
393
+ * @param args - Additional data to include in the log entry
394
+ */
355
395
  info(msg, ...args) {
356
396
  this.logger.info(msg, ...args);
357
397
  }
398
+ /**
399
+ * Logs an HTTP-related message with optional additional data.
400
+ *
401
+ * @param msg - The HTTP message to log
402
+ * @param args - Additional data to include in the log entry
403
+ */
358
404
  http(msg, ...args) {
359
405
  this.logger.http(msg, ...args);
360
406
  }
407
+ /**
408
+ * Logs a verbose message with optional additional data.
409
+ *
410
+ * @param msg - The verbose message to log
411
+ * @param args - Additional data to include in the log entry
412
+ */
361
413
  verbose(msg, ...args) {
362
414
  this.logger.verbose(msg, ...args);
363
415
  }
416
+ /**
417
+ * Logs a debug message with optional additional data.
418
+ *
419
+ * @param msg - The debug message to log
420
+ * @param args - Additional data to include in the log entry
421
+ */
364
422
  debug(msg, ...args) {
365
423
  this.logger.debug(msg, ...args);
366
424
  }
425
+ /**
426
+ * Logs a silly/trace level message with optional additional data.
427
+ *
428
+ * @param msg - The silly message to log
429
+ * @param args - Additional data to include in the log entry
430
+ */
367
431
  silly(msg, ...args) {
368
432
  this.logger.silly(msg, ...args);
369
433
  }
370
- // eslint-disable-next-line @typescript-eslint/naming-convention
434
+ /**
435
+ * Static method to log an error message with a specific prefix.
436
+ * Creates or retrieves a logger instance for the given prefix.
437
+ *
438
+ * @param prefix - The logger prefix/label to use
439
+ * @param msg - The error message to log
440
+ * @param args - Additional data to include in the log entry
441
+ */
371
442
  static Error(prefix, msg, ...args) {
372
443
  const logger = this.instance(prefix);
373
444
  logger.error(msg, ...args);
374
445
  }
375
- // eslint-disable-next-line @typescript-eslint/naming-convention
446
+ /**
447
+ * Static method to log an informational message with a specific prefix.
448
+ * Creates or retrieves a logger instance for the given prefix.
449
+ *
450
+ * @param prefix - The logger prefix/label to use
451
+ * @param msg - The informational message to log
452
+ * @param args - Additional data to include in the log entry
453
+ */
376
454
  static Info(prefix, msg, ...args) {
377
455
  const logger = this.instance(prefix);
378
456
  logger.info(msg, ...args);
379
457
  }
380
- // eslint-disable-next-line @typescript-eslint/naming-convention
458
+ /**
459
+ * Static method to log a warning message with a specific prefix.
460
+ * Creates or retrieves a logger instance for the given prefix.
461
+ *
462
+ * @param prefix - The logger prefix/label to use
463
+ * @param msg - The warning message to log
464
+ * @param args - Additional data to include in the log entry
465
+ */
381
466
  static Warn(prefix, msg, ...args) {
382
467
  const logger = this.instance(prefix);
383
468
  logger.warn(msg, ...args);
384
469
  }
385
- // eslint-disable-next-line @typescript-eslint/naming-convention
470
+ /**
471
+ * Static method to log a debug message with a specific prefix.
472
+ * Creates or retrieves a logger instance for the given prefix.
473
+ *
474
+ * @param prefix - The logger prefix/label to use
475
+ * @param msg - The debug message to log
476
+ * @param args - Additional data to include in the log entry
477
+ */
386
478
  static Debug(prefix, msg, ...args) {
387
479
  const logger = this.instance(prefix);
388
480
  logger.debug(msg, ...args);
389
481
  }
390
- // eslint-disable-next-line @typescript-eslint/naming-convention
482
+ /**
483
+ * Static method to log a silly/trace level message with a specific prefix.
484
+ * Creates or retrieves a logger instance for the given prefix.
485
+ *
486
+ * @param prefix - The logger prefix/label to use
487
+ * @param msg - The silly message to log
488
+ * @param args - Additional data to include in the log entry
489
+ */
391
490
  static Silly(prefix, msg, ...args) {
392
491
  const logger = this.instance(prefix);
393
492
  logger.silly(msg, ...args);
@@ -498,17 +597,17 @@ async function traverseDirectory(dir, callback) {
498
597
  }
499
598
  }
500
599
  __name(traverseDirectory, "traverseDirectory");
501
- function throwCustomError(error, message, customError) {
600
+ function throwCustomError(error, message, CustomError2) {
502
601
  const uuid = crypto.randomUUID();
503
602
  Logger.Error("Throwing Custom Error", error.name);
504
- if (typeof customError === typeof DatabaseError) {
603
+ if (typeof CustomError2 === typeof DatabaseError) {
505
604
  const errorMessage = error instanceof Error ? error.message : message;
506
- throw new customError(errorMessage, uuid);
605
+ throw new CustomError2(errorMessage, uuid);
507
606
  } else {
508
607
  if (error instanceof Error) {
509
- throw new customError(`${message}: ${error.message ? error.message : error.toString()}`);
608
+ throw new CustomError2(`${message}: ${error.message ? error.message : error.toString()}`);
510
609
  } else {
511
- throw new customError(message);
610
+ throw new CustomError2(message);
512
611
  }
513
612
  }
514
613
  }
@@ -647,14 +746,18 @@ var BaseHandler = class BaseHandler2 {
647
746
  return this.event;
648
747
  }
649
748
  /**
650
- * Get the arguments passed from the customId
651
- * For example, if customId is "accept:user123-guild456", args will be ["user123", "guild456"]
749
+ * Gets arguments parsed from interaction customId
750
+ *
751
+ * Arguments are extracted from customId using ":" and "-" separators.
752
+ * For customId "accept:user123-guild456", returns ["user123", "guild456"]
652
753
  */
653
754
  getArgs() {
654
755
  return this.args;
655
756
  }
656
757
  /**
657
- * Get a specific argument by index
758
+ * Gets a specific argument by index from parsed customId
759
+ * @param index - Zero-based index of the argument to retrieve
760
+ * @returns The argument at the specified index, or undefined if not found
658
761
  */
659
762
  getArg(index) {
660
763
  return this.args[index];
@@ -680,6 +783,7 @@ var AutocompleteHandler = class extends BaseHandler {
680
783
  static {
681
784
  __name(this, "AutocompleteHandler");
682
785
  }
786
+ /** The currently focused autocomplete option (Based on what you set in \@AutocompleteRoute) */
683
787
  focused;
684
788
  constructor(event, core, args) {
685
789
  super(event, core, args);
@@ -886,11 +990,23 @@ var ErrorHandlingUtils = class {
886
990
  __name(this, "ErrorHandlingUtils");
887
991
  }
888
992
  static logger = new Logger("Errors");
889
- static handleError(error, core, guild, user) {
993
+ /**
994
+ * Processes an error and extracts the standardized response, if available.
995
+ *
996
+ * Handles different error types (CustomError, DatabaseError) with appropriate
997
+ * logging, side effects, and user-facing error messages.
998
+ *
999
+ * @param error - The error to process
1000
+ * @param core - The core framework instance
1001
+ * @param guild - The guild where the error occurred (if any)
1002
+ * @param user - The user who triggered the error (if any)
1003
+ * @returns Object containing UUID and formatted error response embed
1004
+ */
1005
+ static extractErrorResponse(error, core, guild, user) {
890
1006
  const uuid = crypto2__namespace.randomUUID();
891
1007
  if (error instanceof CustomError) {
892
1008
  if (error instanceof DatabaseError) {
893
- core.hooks.emit("unknownException", {
1009
+ core.effects.emit("unknownException", {
894
1010
  uuid,
895
1011
  error,
896
1012
  guild,
@@ -906,7 +1022,7 @@ var ErrorHandlingUtils = class {
906
1022
  };
907
1023
  }
908
1024
  this.logger.error(uuid, error);
909
- core.hooks.emit("unknownException", {
1025
+ core.effects.emit("unknownException", {
910
1026
  uuid,
911
1027
  error,
912
1028
  guild,
@@ -945,7 +1061,7 @@ function Catchable(options) {
945
1061
  if (!(error instanceof Error)) throw error;
946
1062
  this.setErrored();
947
1063
  if (log) console.error(error);
948
- const { response } = ErrorHandlingUtils.handleError(error, this.core, interaction.guild, interaction.user);
1064
+ const { response } = ErrorHandlingUtils.extractErrorResponse(error, this.core, interaction.guild, interaction.user);
949
1065
  const res = {
950
1066
  embeds: [
951
1067
  response
@@ -1314,6 +1430,27 @@ var Pluggable = class _Pluggable {
1314
1430
  this.isInitialized = true;
1315
1431
  return this;
1316
1432
  }
1433
+ /**
1434
+ * Attaches a plugin to this instance
1435
+ *
1436
+ * Plugins provide external functionality and are initialized during the specified startup phase.
1437
+ * The plugin instance becomes available as a property in `core` wherever it's available.
1438
+ *
1439
+ * Make sure to augment the {@link Core} interface with the plugin type to ensure TypeScript recognizes it and provides intellisense.
1440
+ *
1441
+ * @typeParam Key - The property name for accessing the plugin
1442
+ * @typeParam Ctor - The plugin constructor type
1443
+ * @param key - Property name to access the plugin instance
1444
+ * @param Plugin - Plugin constructor class
1445
+ * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
1446
+ * @param args - Additional arguments to pass to the plugin constructor
1447
+ * @returns This instance with the plugin attached as a typed property
1448
+ * @throws An {@link Error} When called after initialization or if key already exists
1449
+ * @example
1450
+ * ```typescript
1451
+ * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', dbName: 'seedcord' })
1452
+ * ```
1453
+ */
1317
1454
  attach(key, Plugin2, startupPhase, ...args) {
1318
1455
  if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
1319
1456
  if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
@@ -1345,7 +1482,21 @@ var CoordinatedLifecycle = class {
1345
1482
  this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
1346
1483
  }
1347
1484
  /**
1348
- * Add a task to a specific phase
1485
+ * Adds a lifecycle task to a specific phase.
1486
+ *
1487
+ * Tasks are executed in phase order during lifecycle operations.
1488
+ * Each task has a timeout to prevent hanging operations.
1489
+ *
1490
+ * @param phase - The lifecycle phase to add the task to
1491
+ * @param taskName - Unique name for the task (used for logging and removal)
1492
+ * @param task - Async function to execute during the phase
1493
+ * @param timeoutMs - Maximum time allowed for task execution in milliseconds
1494
+ * @example
1495
+ * ```typescript
1496
+ * lifecycle.addTask(StartupPhase.Services, 'start-database', async () => {
1497
+ * await database.connect();
1498
+ * }, 10000);
1499
+ * ```
1349
1500
  */
1350
1501
  addTask(phase, taskName, task, timeoutMs) {
1351
1502
  if (!this.canAddTask()) return;
@@ -1359,7 +1510,11 @@ var CoordinatedLifecycle = class {
1359
1510
  this.logger.debug(`${chalk6__default.default.italic("Added")} ${this.getTaskType()} task ${chalk6__default.default.bold.cyan(taskName)} to phase ${chalk6__default.default.bold.magenta(this.phaseEnum[phase])}`);
1360
1511
  }
1361
1512
  /**
1362
- * Remove a task by name from a specific phase
1513
+ * Removes a lifecycle task from a specific phase.
1514
+ *
1515
+ * @param phase - The lifecycle phase to remove the task from
1516
+ * @param taskName - Name of the task to remove
1517
+ * @returns True if the task was found and removed, false otherwise
1363
1518
  */
1364
1519
  removeTask(phase, taskName) {
1365
1520
  if (!this.canRemoveTask()) return false;
@@ -1493,19 +1648,40 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
1493
1648
  });
1494
1649
  }
1495
1650
  /**
1496
- * Add a task to a specific shutdown phase
1651
+ * Adds a task to a specific shutdown phase with timeout.
1652
+ *
1653
+ * @param phase - The shutdown phase from {@link ShutdownPhase}
1654
+ * @param taskName - Unique identifier for the task
1655
+ * @param task - Async function to execute
1656
+ * @param timeoutMs - Task timeout in milliseconds (default: 5000)
1497
1657
  */
1498
1658
  addTask(phase, taskName, task, timeoutMs = 5e3) {
1499
1659
  super.addTask(phase, taskName, task, timeoutMs);
1500
1660
  }
1501
1661
  /**
1502
- * Remove a task by name from a specific phase
1662
+ * Removes a task from a specific shutdown phase.
1663
+ *
1664
+ * @param phase - The shutdown phase to remove from
1665
+ * @param taskName - Name of the task to remove
1666
+ * @returns True if task was found and removed
1503
1667
  */
1504
1668
  removeTask(phase, taskName) {
1505
1669
  return super.removeTask(phase, taskName);
1506
1670
  }
1507
1671
  /**
1508
- * Start the coordinated shutdown sequence
1672
+ * Executes the coordinated shutdown sequence.
1673
+ *
1674
+ * Runs all registered tasks across shutdown phases in reverse order.
1675
+ * Tasks within each phase are executed in parallel for faster shutdown.
1676
+ * Process exits with the specified code when complete.
1677
+ *
1678
+ * @param exitCode - Process exit code (default: 0)
1679
+ * @returns Promise that resolves when shutdown is complete
1680
+ * @example
1681
+ * ```typescript
1682
+ * shutdown.addTask(ShutdownPhase.Services, 'database', () => db.disconnect(), 5000);
1683
+ * await shutdown.run(0); // Graceful shutdown
1684
+ * ```
1509
1685
  */
1510
1686
  async run(exitCode = 0) {
1511
1687
  if (this.isShuttingDown) {
@@ -1559,6 +1735,10 @@ var Bot = class extends Plugin {
1559
1735
  events;
1560
1736
  commands;
1561
1737
  emojiInjector;
1738
+ /**
1739
+ * @param core - Seedcord core instance
1740
+ * @internal
1741
+ */
1562
1742
  constructor(core) {
1563
1743
  super(core), this.core = core;
1564
1744
  this._client = new discord_js.Client(core.config.bot.clientOptions);
@@ -1568,6 +1748,10 @@ var Bot = class extends Plugin {
1568
1748
  this.emojiInjector = new EmojiInjector(this.core);
1569
1749
  this.core.shutdown.addTask(ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop());
1570
1750
  }
1751
+ /**
1752
+ * Initializes Discord client and all controllers
1753
+ * @internal
1754
+ */
1571
1755
  async init() {
1572
1756
  if (this.isInitialized) {
1573
1757
  return;
@@ -1580,15 +1764,25 @@ var Bot = class extends Plugin {
1580
1764
  await this.commands.setCommands();
1581
1765
  await this.emojiInjector.init();
1582
1766
  }
1767
+ /**
1768
+ * Stops the bot and cleans up connections
1769
+ * @internal
1770
+ */
1583
1771
  async stop() {
1584
1772
  this._client.removeAllListeners();
1585
1773
  await this.logout();
1586
1774
  }
1775
+ /**
1776
+ * Logs the bot into Discord using the configured token
1777
+ */
1587
1778
  async login() {
1588
1779
  await this._client.login(Globals.botToken);
1589
1780
  this.logger.info(`Logged in as ${chalk6__default.default.bold.magenta(this._client.user?.username)}!`);
1590
1781
  return this;
1591
1782
  }
1783
+ /**
1784
+ * Logs out and destroys the Discord client connection
1785
+ */
1592
1786
  async logout() {
1593
1787
  await this._client.destroy();
1594
1788
  this.logger.info(chalk6__default.default.bold.red("Logged out of Discord!"));
@@ -1598,22 +1792,28 @@ var Bot = class extends Plugin {
1598
1792
  }
1599
1793
  };
1600
1794
 
1601
- // src/hooks/decorators/RegisterHook.ts
1602
- var HookMetadataKey = Symbol("hook:metadata");
1603
- function RegisterHook(hook) {
1795
+ // src/effects/decorators/RegisterEffect.ts
1796
+ var EffectMetadataKey = Symbol("effect:metadata");
1797
+ function RegisterEffect(effect) {
1604
1798
  return function(constructor) {
1605
- Reflect.defineMetadata(HookMetadataKey, hook, constructor);
1799
+ Reflect.defineMetadata(EffectMetadataKey, effect, constructor);
1606
1800
  };
1607
1801
  }
1608
- __name(RegisterHook, "RegisterHook");
1802
+ __name(RegisterEffect, "RegisterEffect");
1609
1803
 
1610
- // src/hooks/interfaces/HookHandler.ts
1611
- var HookHandler = class {
1804
+ // src/effects/interfaces/EffectsHandler.ts
1805
+ var EffectsHandler = class {
1612
1806
  static {
1613
- __name(this, "HookHandler");
1807
+ __name(this, "EffectsHandler");
1614
1808
  }
1615
1809
  data;
1616
1810
  core;
1811
+ /**
1812
+ * Creates a new effects handler instance.
1813
+ *
1814
+ * @param data - The effect event data
1815
+ * @param core - The core framework instance
1816
+ */
1617
1817
  constructor(data, core) {
1618
1818
  this.data = data;
1619
1819
  this.core = core;
@@ -1622,8 +1822,8 @@ var HookHandler = class {
1622
1822
  }
1623
1823
  };
1624
1824
 
1625
- // src/hooks/interfaces/abstracts/WebhookLog.ts
1626
- var WebhookLog = class extends HookHandler {
1825
+ // src/effects/interfaces/abstracts/WebhookLog.ts
1826
+ var WebhookLog = class extends EffectsHandler {
1627
1827
  static {
1628
1828
  __name(this, "WebhookLog");
1629
1829
  }
@@ -1632,7 +1832,7 @@ var WebhookLog = class extends HookHandler {
1632
1832
  }
1633
1833
  };
1634
1834
 
1635
- // src/hooks/default/UnknownException.ts
1835
+ // src/effects/default/UnknownException.ts
1636
1836
  function _ts_decorate3(decorators, target, key, desc) {
1637
1837
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1638
1838
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1658,7 +1858,7 @@ var UnknownException = class extends WebhookLog {
1658
1858
  }
1659
1859
  };
1660
1860
  UnknownException = _ts_decorate3([
1661
- RegisterHook("unknownException")
1861
+ RegisterEffect("unknownException")
1662
1862
  ], UnknownException);
1663
1863
  var UnhandledErrorEmbed = class UnhandledErrorEmbed2 extends BuilderComponent {
1664
1864
  static {
@@ -1695,83 +1895,107 @@ var UnhandledErrorEmbed = class UnhandledErrorEmbed2 extends BuilderComponent {
1695
1895
  ]);
1696
1896
  }
1697
1897
  };
1698
- var HookEmitter = class {
1898
+ var EffectsEmitter = class {
1699
1899
  static {
1700
- __name(this, "HookEmitter");
1900
+ __name(this, "EffectsEmitter");
1701
1901
  }
1702
1902
  emitter = new events.EventEmitter();
1903
+ /**
1904
+ * Registers a listener for the specified side effect.
1905
+ *
1906
+ * @typeParam KeyOfEffects - The side effect name type
1907
+ * @param event - The side effect name to listen for
1908
+ * @param listener - Function to call when the event is emitted
1909
+ * @returns This EffectsEmitter instance for chaining
1910
+ */
1703
1911
  on(event, listener) {
1704
1912
  this.emitter.on(event, listener);
1705
1913
  return this;
1706
1914
  }
1915
+ /**
1916
+ * Registers a one-time listener for the specified side effect.
1917
+ *
1918
+ * @typeParam KeyOfEffects - The side effect name type
1919
+ * @param event - The side effect name to listen for once
1920
+ * @param listener - Function to call when the event is emitted
1921
+ * @returns This EffectsEmitter instance for chaining
1922
+ */
1707
1923
  once(event, listener) {
1708
1924
  this.emitter.once(event, listener);
1709
1925
  return this;
1710
1926
  }
1927
+ /**
1928
+ * Emits a side effect with the provided data.
1929
+ *
1930
+ * @typeParam KeyOfEffects - The side effect name type
1931
+ * @param event - The side effect name to emit
1932
+ * @param data - The data to pass to registered listeners
1933
+ * @returns True if the event had listeners, false otherwise
1934
+ */
1711
1935
  emit(event, data) {
1712
1936
  return this.emitter.emit(event, data);
1713
1937
  }
1714
1938
  };
1715
1939
 
1716
- // src/hooks/HookController.ts
1717
- var HookController = class extends Plugin {
1940
+ // src/effects/EffectsRegistry.ts
1941
+ var EffectsRegistry = class extends Plugin {
1718
1942
  static {
1719
- __name(this, "HookController");
1943
+ __name(this, "EffectsRegistry");
1720
1944
  }
1721
1945
  core;
1722
- logger = new Logger("Hooks");
1946
+ logger = new Logger("Effects");
1723
1947
  isInitialized = false;
1724
- hookMap = /* @__PURE__ */ new Map();
1725
- emitter = new HookEmitter();
1948
+ effectsMap = /* @__PURE__ */ new Map();
1949
+ emitter = new EffectsEmitter();
1726
1950
  constructor(core) {
1727
1951
  super(core), this.core = core;
1728
1952
  }
1729
1953
  async init() {
1730
1954
  if (this.isInitialized) return;
1731
1955
  this.isInitialized = true;
1732
- const hooksDir = this.core.config.hooks.path;
1733
- this.logger.info(chalk6__default.default.bold(hooksDir));
1734
- this.registerHook("unknownException", UnknownException);
1735
- await this.loadHooks(hooksDir);
1736
- this.attachHooks();
1737
- const totalHooks = Array.from(this.hookMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1738
- this.logger.info(`${chalk6__default.default.bold.green("Loaded")}: ${chalk6__default.default.bold.magenta(totalHooks)} hooks`);
1739
- }
1740
- async loadHooks(dir) {
1956
+ const effectsDir = this.core.config.effects.path;
1957
+ this.logger.info(chalk6__default.default.bold(effectsDir));
1958
+ this.registerEffect("unknownException", UnknownException);
1959
+ await this.loadEffects(effectsDir);
1960
+ this.attachEffects();
1961
+ const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1962
+ this.logger.info(`${chalk6__default.default.bold.green("Loaded")}: ${chalk6__default.default.bold.magenta(totalEffects)} side effects`);
1963
+ }
1964
+ async loadEffects(dir) {
1741
1965
  await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
1742
1966
  for (const exportName of Object.keys(imported)) {
1743
1967
  const val = imported[exportName];
1744
- if (this.isHookHandler(val)) {
1745
- const hookName = Reflect.getMetadata(HookMetadataKey, val);
1746
- if (hookName) {
1747
- this.registerHook(hookName, val);
1968
+ if (this.isEffectHandler(val)) {
1969
+ const effectName = Reflect.getMetadata(EffectMetadataKey, val);
1970
+ if (effectName) {
1971
+ this.registerEffect(effectName, val);
1748
1972
  this.logger.info(`${chalk6__default.default.italic("Registered")} ${chalk6__default.default.bold.yellow(val.name)} from ${chalk6__default.default.gray(relativePath)}`);
1749
1973
  }
1750
1974
  }
1751
1975
  }
1752
1976
  });
1753
1977
  }
1754
- registerHook(hookName, handler) {
1755
- let handlers = this.hookMap.get(hookName);
1978
+ registerEffect(effectName, handler) {
1979
+ let handlers = this.effectsMap.get(effectName);
1756
1980
  if (!handlers) {
1757
1981
  handlers = [];
1758
- this.hookMap.set(hookName, handlers);
1982
+ this.effectsMap.set(effectName, handlers);
1759
1983
  }
1760
1984
  handlers.push(handler);
1761
1985
  }
1762
- isHookHandler(obj) {
1986
+ isEffectHandler(obj) {
1763
1987
  if (typeof obj !== "function") return false;
1764
- return obj.prototype instanceof HookHandler;
1988
+ return obj.prototype instanceof EffectsHandler;
1765
1989
  }
1766
- attachHooks() {
1767
- for (const [hookName, handlerCtors] of this.hookMap) {
1768
- this.emitter.on(hookName, (data) => {
1990
+ attachEffects() {
1991
+ for (const [effectName, handlerCtors] of this.effectsMap) {
1992
+ this.emitter.on(effectName, (data) => {
1769
1993
  for (const HandlerCtor of handlerCtors) {
1770
1994
  try {
1771
1995
  const instance = new HandlerCtor(data, this.core);
1772
1996
  void instance.execute();
1773
1997
  } catch (err) {
1774
- this.logger.error(`Error in hook ${String(hookName)} handler ${HandlerCtor.name}:`, err);
1998
+ this.logger.error(`Error in side effect ${String(effectName)} handler ${HandlerCtor.name}:`, err);
1775
1999
  }
1776
2000
  }
1777
2001
  });
@@ -1827,6 +2051,11 @@ var HealthCheck = class extends Plugin {
1827
2051
  });
1828
2052
  });
1829
2053
  }
2054
+ /**
2055
+ * Stops the health check server.
2056
+ *
2057
+ * @returns Promise that resolves when the server is closed
2058
+ */
1830
2059
  stop() {
1831
2060
  return new Promise((shutdownResolve) => {
1832
2061
  this.server?.close(() => {
@@ -1865,7 +2094,12 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
1865
2094
  super("CoordinatedStartup", PHASE_ORDER2, StartupPhase);
1866
2095
  }
1867
2096
  /**
1868
- * Add a task to a specific startup phase
2097
+ * Adds a task to a specific startup phase with timeout.
2098
+ *
2099
+ * @param phase - The startup phase from {@link StartupPhase}
2100
+ * @param taskName - Unique identifier for the task
2101
+ * @param task - Async function to execute
2102
+ * @param timeoutMs - Task timeout in milliseconds (default: 10000)
1869
2103
  */
1870
2104
  addTask(phase, taskName, task, timeoutMs = 1e4) {
1871
2105
  super.addTask(phase, taskName, task, timeoutMs);
@@ -1902,7 +2136,20 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
1902
2136
  return results;
1903
2137
  }
1904
2138
  /**
1905
- * Start the coordinated startup sequence
2139
+ * Executes the coordinated startup sequence.
2140
+ *
2141
+ * Runs all registered tasks across startup phases in the correct order.
2142
+ * Each phase completes before the next phase begins. Tasks within a phase
2143
+ * are executed sequentially to maintain predictable initialization.
2144
+ *
2145
+ * @returns Promise that resolves when startup is complete
2146
+ * @throws An {@link Error} If startup fails or is called multiple times
2147
+ * @example
2148
+ * ```typescript
2149
+ * const startup = new CoordinatedStartup();
2150
+ * startup.addTask(StartupPhase.Services, 'database', () => db.connect(), 10000);
2151
+ * await startup.run();
2152
+ * ```
1906
2153
  */
1907
2154
  async run() {
1908
2155
  if (this.hasStarted) {
@@ -1962,11 +2209,22 @@ var Seedcord = class _Seedcord extends Pluggable {
1962
2209
  }
1963
2210
  config;
1964
2211
  static isInstantiated = false;
2212
+ /** @see {@link CoordinatedShutdown} */
1965
2213
  shutdown;
2214
+ /** @see {@link CoordinatedStartup} */
1966
2215
  startup;
1967
- hooks;
2216
+ /** @see {@link EffectsRegistry} */
2217
+ effects;
2218
+ /** @see {@link Bot} */
1968
2219
  bot;
2220
+ /** @see {@link HealthCheck} */
1969
2221
  healthCheck;
2222
+ /**
2223
+ * Creates a new Seedcord instance
2224
+ *
2225
+ * @param config - Bot configuration including paths and Discord client options
2226
+ * @throws An {@link Error} When attempting to create multiple instances (singleton)
2227
+ */
1970
2228
  constructor(config) {
1971
2229
  const shutdown = new CoordinatedShutdown();
1972
2230
  const startup = new CoordinatedStartup();
@@ -1977,16 +2235,20 @@ var Seedcord = class _Seedcord extends Pluggable {
1977
2235
  throw new Error("Seedcord can only be instantiated once. Use the existing instance instead.");
1978
2236
  }
1979
2237
  _Seedcord.isInstantiated = true;
1980
- this.hooks = new HookController(this);
2238
+ this.effects = new EffectsRegistry(this);
1981
2239
  this.bot = new Bot(this);
1982
2240
  this.healthCheck = new HealthCheck(this);
1983
2241
  this.registerStartupTasks();
1984
2242
  }
2243
+ /**
2244
+ * Registers default startup tasks
2245
+ * @internal
2246
+ */
1985
2247
  registerStartupTasks() {
1986
- this.startup.addTask(StartupPhase.Configuration, "Hook Initialization", async () => {
1987
- this.hooks.logger.info(chalk6__default.default.bold("Initializing"));
1988
- await this.hooks.init();
1989
- this.hooks.logger.info(chalk6__default.default.bold("Initialized"));
2248
+ this.startup.addTask(StartupPhase.Configuration, "Effect Initialization", async () => {
2249
+ this.effects.logger.info(chalk6__default.default.bold("Initializing"));
2250
+ await this.effects.init();
2251
+ this.effects.logger.info(chalk6__default.default.bold("Initialized"));
1990
2252
  });
1991
2253
  this.startup.addTask(StartupPhase.Instantiation, "Bot Initialization", async () => {
1992
2254
  this.bot.logger.info(chalk6__default.default.bold("Initializing"));
@@ -1999,6 +2261,11 @@ var Seedcord = class _Seedcord extends Pluggable {
1999
2261
  this.healthCheck.logger.info(chalk6__default.default.bold("Initialized"));
2000
2262
  });
2001
2263
  }
2264
+ /**
2265
+ * Starts the bot and runs all initialization tasks
2266
+ *
2267
+ * @returns This Seedcord instance when fully initialized
2268
+ */
2002
2269
  async start() {
2003
2270
  await super.init();
2004
2271
  return this;
@@ -2028,11 +2295,11 @@ function EventCatchable(log) {
2028
2295
  this.getEvent()
2029
2296
  ];
2030
2297
  const msg = eventArgs.find((x) => x instanceof discord_js.Message);
2031
- const result = ErrorHandlingUtils.handleError(err, this.core, msg?.guild ?? null, msg?.author ?? null);
2298
+ const { response } = ErrorHandlingUtils.extractErrorResponse(err, this.core, msg?.guild ?? null, msg?.author ?? null);
2032
2299
  if (!msg) return;
2033
2300
  await msg.reply({
2034
2301
  embeds: [
2035
- result.response
2302
+ response
2036
2303
  ],
2037
2304
  components: []
2038
2305
  });
@@ -2051,19 +2318,32 @@ var CooldownManager = class {
2051
2318
  Err;
2052
2319
  msg;
2053
2320
  map = /* @__PURE__ */ new Map();
2321
+ /**
2322
+ * Creates a new CooldownManager instance.
2323
+ *
2324
+ * @param opts - Configuration options for the cooldown behavior
2325
+ */
2054
2326
  constructor(opts = {}) {
2055
2327
  this.window = opts.cooldown ?? 1e3;
2056
2328
  this.Err = opts.err ?? Error;
2057
2329
  this.msg = opts.message ?? "Cooldown active";
2058
2330
  }
2059
- /** Record usage without any checks. */
2331
+ /**
2332
+ * Records usage timestamp for a key without any cooldown checks.
2333
+ *
2334
+ * @param key - The unique identifier for the cooldown entry
2335
+ */
2060
2336
  set(key) {
2061
2337
  this.map.set(key, Date.now());
2062
2338
  }
2063
2339
  /**
2064
- * Verify cooldown for `key`.\
2065
- * If active → throws the custom error.\
2066
- * If not active updates timestamp and returns void.
2340
+ * Verifies cooldown status for a key and updates timestamp if not active.
2341
+ *
2342
+ * If the cooldown is still active, throws the configured error.
2343
+ * If not active, updates the timestamp and returns successfully.
2344
+ *
2345
+ * @param key - The unique identifier to check cooldown for
2346
+ * @throws An {@link Err} When the cooldown is still active for the given key
2067
2347
  */
2068
2348
  check(key) {
2069
2349
  const now = Date.now();
@@ -2077,145 +2357,29 @@ var CooldownManager = class {
2077
2357
  }
2078
2358
  this.map.set(key, now);
2079
2359
  }
2080
- /** Returns true if the key is still cooling down (does not update timestamp). */
2360
+ /**
2361
+ * Checks if a key is currently cooling down without updating timestamp.
2362
+ *
2363
+ * @param key - The unique identifier to check
2364
+ * @returns True if the key is still cooling down, false otherwise
2365
+ */
2081
2366
  isActive(key) {
2082
2367
  const last = this.map.get(key);
2083
2368
  return last !== void 0 && Date.now() - last < this.window;
2084
2369
  }
2085
- /** Remove a key from the map (useful for manual resets). */
2086
- clear(key) {
2087
- this.map.delete(key);
2088
- }
2089
- };
2090
- var ModelMetadataKey = Symbol("db:model");
2091
- function DatabaseModel(collection) {
2092
- return (target, propertyKey) => {
2093
- const schema = target[propertyKey];
2094
- const name = String(collection);
2095
- const model = mongoose2__default.default.model(name, schema);
2096
- Reflect.defineMetadata(ModelMetadataKey, model, target);
2097
- };
2098
- }
2099
- __name(DatabaseModel, "DatabaseModel");
2100
-
2101
- // src/mongo/decorators/DatabaseService.ts
2102
- var ServiceMetadataKey = Symbol("db:serviceKey");
2103
- function DatabaseService(key) {
2104
- return (ctor) => {
2105
- Reflect.defineMetadata(ServiceMetadataKey, key, ctor);
2106
- };
2107
- }
2108
- __name(DatabaseService, "DatabaseService");
2109
-
2110
- // src/mongo/BaseService.ts
2111
- var BaseService = class {
2112
- static {
2113
- __name(this, "BaseService");
2114
- }
2115
- db;
2116
- core;
2117
- model;
2118
- constructor(db, core) {
2119
- this.db = db;
2120
- this.core = core;
2121
- const ctor = this.constructor;
2122
- const key = Reflect.getMetadata(ServiceMetadataKey, ctor);
2123
- if (!key) throw new Error(`Missing @DatabaseService on ${ctor.name}`);
2124
- const model = Reflect.getMetadata(ModelMetadataKey, ctor);
2125
- if (!model) throw new Error(`Missing @DatabaseModel on ${ctor.name}`);
2126
- this.model = model;
2127
- db._register(key, this);
2128
- }
2129
- };
2130
-
2131
- // src/mongo/Mongo.ts
2132
- var Mongo = class extends Plugin {
2133
- static {
2134
- __name(this, "Mongo");
2135
- }
2136
- core;
2137
- options;
2138
- logger = new Logger("MongoDB");
2139
- isInitialised = false;
2140
- uri;
2141
2370
  /**
2142
- * Map of all loaded services.
2143
- * Keys come from `@DatabaseService('key')`
2371
+ * Removes a key from the cooldown map.
2372
+ *
2373
+ * @param key - The unique identifier to remove (useful for manual resets)
2144
2374
  */
2145
- services = {};
2146
- constructor(core, options) {
2147
- super(core), this.core = core, this.options = options;
2148
- this.uri = options.uri;
2149
- this.core.shutdown.addTask(ShutdownPhase.ExternalResources, "stop-database", async () => await this.stop());
2150
- }
2151
- async init() {
2152
- if (this.isInitialised) return;
2153
- this.isInitialised = true;
2154
- await this.connect();
2155
- await this.loadServices();
2156
- }
2157
- async stop() {
2158
- await this.disconnect();
2159
- }
2160
- async connect() {
2161
- await mongoose2__default.default.connect(this.uri, {
2162
- dbName: this.options.dbName,
2163
- ...Globals.isProduction && {
2164
- tls: true,
2165
- ssl: true
2166
- }
2167
- }).then((i) => this.logger.info(`Connected to MongoDB: ${chalk6__default.default.bold.magenta(i.connection.name)}`)).catch((err) => throwCustomError(err, "Could not connect to MongoDB", DatabaseConnectionFailure));
2168
- }
2169
- async disconnect() {
2170
- await mongoose2__default.default.disconnect().then(() => this.logger.info(chalk6__default.default.red.bold("Disconnected from MongoDB"))).catch((err) => this.logger.error(`Could not disconnect from MongoDB: ${err.message}`));
2171
- }
2172
- async loadServices() {
2173
- const servicesDir = this.options.servicesDir;
2174
- this.logger.info(chalk6__default.default.bold(servicesDir));
2175
- await traverseDirectory(servicesDir, (_full, rel, mod) => {
2176
- for (const Service of Object.values(mod)) {
2177
- if (this.isServiceClass(Service)) {
2178
- const instance = new Service(this, this.core);
2179
- this.logger.info(`${chalk6__default.default.italic("Registered")} ${chalk6__default.default.bold.yellow(instance.constructor.name)} from ${chalk6__default.default.gray(rel)}`);
2180
- }
2181
- }
2182
- });
2183
- this.logger.info(`${chalk6__default.default.bold.green("Loaded")}: ${chalk6__default.default.magenta(Object.keys(this.services).length)} services`);
2184
- }
2185
- isServiceClass(obj) {
2186
- return typeof obj === "function" && obj.prototype instanceof BaseService && Reflect.hasMetadata(ServiceMetadataKey, obj);
2187
- }
2188
- _register(key, instance) {
2189
- this.services[key] = instance;
2375
+ clear(key) {
2376
+ this.map.delete(key);
2190
2377
  }
2191
2378
  };
2192
2379
 
2193
- // src/mongo/decorators/DBCatchable.ts
2194
- function DBCatchable(errorMessage) {
2195
- return function(_target, _propertyKey, descriptor) {
2196
- const originalMethod = descriptor.value;
2197
- descriptor.value = async function(...args) {
2198
- if (!originalMethod) {
2199
- throw new Error("Method not found");
2200
- }
2201
- try {
2202
- return await originalMethod.apply(this, args);
2203
- } catch (error) {
2204
- if (!(error instanceof CustomError)) {
2205
- throwCustomError(error, errorMessage, DatabaseError);
2206
- } else {
2207
- throw error;
2208
- }
2209
- }
2210
- };
2211
- };
2212
- }
2213
- __name(DBCatchable, "DBCatchable");
2214
-
2215
2380
  exports.AutocompleteHandler = AutocompleteHandler;
2216
2381
  exports.AutocompleteRoute = AutocompleteRoute;
2217
2382
  exports.BaseErrorEmbed = BaseErrorEmbed;
2218
- exports.BaseService = BaseService;
2219
2383
  exports.Bot = Bot;
2220
2384
  exports.BuilderComponent = BuilderComponent;
2221
2385
  exports.ButtonRoute = ButtonRoute;
@@ -2227,18 +2391,16 @@ exports.CooldownManager = CooldownManager;
2227
2391
  exports.CoordinatedShutdown = CoordinatedShutdown;
2228
2392
  exports.CoordinatedStartup = CoordinatedStartup;
2229
2393
  exports.CustomError = CustomError;
2230
- exports.DBCatchable = DBCatchable;
2231
- exports.DatabaseModel = DatabaseModel;
2232
- exports.DatabaseService = DatabaseService;
2394
+ exports.DatabaseError = DatabaseError;
2395
+ exports.EffectMetadataKey = EffectMetadataKey;
2396
+ exports.EffectsEmitter = EffectsEmitter;
2397
+ exports.EffectsHandler = EffectsHandler;
2398
+ exports.EffectsRegistry = EffectsRegistry;
2233
2399
  exports.EventCatchable = EventCatchable;
2234
2400
  exports.EventHandler = EventHandler;
2235
2401
  exports.EventMetadataKey = EventMetadataKey;
2236
2402
  exports.Globals = Globals;
2237
2403
  exports.HealthCheck = HealthCheck;
2238
- exports.HookController = HookController;
2239
- exports.HookEmitter = HookEmitter;
2240
- exports.HookHandler = HookHandler;
2241
- exports.HookMetadataKey = HookMetadataKey;
2242
2404
  exports.InteractionHandler = InteractionHandler;
2243
2405
  exports.InteractionMetadataKey = InteractionMetadataKey;
2244
2406
  exports.InteractionMiddleware = InteractionMiddleware;
@@ -2246,18 +2408,15 @@ exports.InteractionRoutes = InteractionRoutes;
2246
2408
  exports.Logger = Logger;
2247
2409
  exports.ModalComponent = ModalComponent;
2248
2410
  exports.ModalRoute = ModalRoute;
2249
- exports.ModelMetadataKey = ModelMetadataKey;
2250
- exports.Mongo = Mongo;
2251
2411
  exports.Pluggable = Pluggable;
2252
2412
  exports.Plugin = Plugin;
2253
2413
  exports.RegisterCommand = RegisterCommand;
2414
+ exports.RegisterEffect = RegisterEffect;
2254
2415
  exports.RegisterEvent = RegisterEvent;
2255
- exports.RegisterHook = RegisterHook;
2256
2416
  exports.RowComponent = RowComponent;
2257
2417
  exports.Seedcord = Seedcord;
2258
2418
  exports.SelectMenuRoute = SelectMenuRoute;
2259
2419
  exports.SelectMenuType = SelectMenuType;
2260
- exports.ServiceMetadataKey = ServiceMetadataKey;
2261
2420
  exports.ShutdownPhase = ShutdownPhase;
2262
2421
  exports.SlashRoute = SlashRoute;
2263
2422
  exports.StartupPhase = StartupPhase;