@julanzw/ttoolbox-discordjs-framework 1.3.0 → 1.4.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.
@@ -173,4 +173,97 @@ export declare abstract class Command {
173
173
  * Set the error reporter for this command
174
174
  */
175
175
  setErrorReporter(reporter: ErrorReporter): void;
176
+ /**
177
+ * Lifecycle hook called before command execution.
178
+ *
179
+ * Can be used for:
180
+ * - Custom validation
181
+ * - Logging/analytics
182
+ * - Loading user preferences
183
+ * - Rate limiting checks
184
+ * - And more...
185
+ *
186
+ * If this hook throws an error or returns false, execution is stopped.
187
+ *
188
+ * @param interaction - The command interaction
189
+ * @returns true to continue execution, false to stop (or throw an error)
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * protected async beforeExecute(interaction: ChatInputCommandInteraction): Promise<boolean> {
194
+ * // Log command usage
195
+ * console.log(`${interaction.user.tag} used ${this.name}`);
196
+ *
197
+ * // Check custom rate limit
198
+ * if (await this.isRateLimited(interaction.user.id)) {
199
+ * await interaction.reply('You are rate limited!');
200
+ * return false; // Stop execution
201
+ * }
202
+ *
203
+ * return true; // Continue
204
+ * }
205
+ * ```
206
+ */
207
+ protected beforeExecute?(interaction: ChatInputCommandInteraction, client: Client): Promise<boolean | void>;
208
+ /**
209
+ * Lifecycle hook called after successful command execution.
210
+ *
211
+ * Can be used for:
212
+ * - Analytics tracking
213
+ * - Cleanup tasks
214
+ * - Success logging
215
+ * - Updating usage statistics
216
+ * - And more...
217
+ *
218
+ * Note: This is **NOT** called if the command throws an error (use onError for that).
219
+ *
220
+ * @param interaction - The command interaction
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * protected async afterExecute(interaction: ChatInputCommandInteraction): Promise<void> {
225
+ * // Track command usage
226
+ * await analytics.track('command_used', {
227
+ * command: this.name,
228
+ * user: interaction.user.id,
229
+ * });
230
+ *
231
+ * // Update user stats
232
+ * await incrementCommandCount(interaction.user.id);
233
+ * }
234
+ * ```
235
+ */
236
+ protected afterExecute?(interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
237
+ /**
238
+ * Lifecycle hook called when command execution fails.
239
+ *
240
+ * Can be used for:
241
+ * - Custom error handling
242
+ * - Error reporting to external services
243
+ * - User-friendly error messages
244
+ * - Cleanup after errors
245
+ * - And more...
246
+ *
247
+ * The error is rethrown after this hook.
248
+ *
249
+ * @param interaction - The command interaction
250
+ * @param error - The error that occurred
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * protected async onError(interaction: ChatInputCommandInteraction, error: Error): Promise<void> {
255
+ * // Send to Sentry
256
+ * Sentry.captureException(error, {
257
+ * tags: { command: this.name },
258
+ * user: { id: interaction.user.id },
259
+ * });
260
+ *
261
+ * // Custom error message
262
+ * if (error.message.includes('DATABASE')) {
263
+ * await interaction.reply('Database is temporarily unavailable. Try again later.');
264
+ * }
265
+ * }
266
+ * ```
267
+ */
268
+ protected onError?(interaction: ChatInputCommandInteraction, error: Error, client: Client): Promise<void>;
176
269
  }
@@ -116,7 +116,24 @@ export class Command {
116
116
  const error = this.validate(interaction);
117
117
  if (error)
118
118
  return await safeReply(interaction, error, true);
119
- await this.run(interaction, client);
119
+ if (this.beforeExecute) {
120
+ const shouldContinue = await this.beforeExecute(interaction, client);
121
+ if (shouldContinue === false) {
122
+ return;
123
+ }
124
+ }
125
+ try {
126
+ await this.run(interaction, client);
127
+ if (this.afterExecute) {
128
+ await this.afterExecute(interaction, client);
129
+ }
130
+ }
131
+ catch (err) {
132
+ if (this.onError) {
133
+ await this.onError(interaction, err, client);
134
+ }
135
+ throw err;
136
+ }
120
137
  });
121
138
  }
122
139
  /**
@@ -35,20 +35,6 @@ export declare abstract class SubcommandGroup {
35
35
  protected logger?: ILogger;
36
36
  /** ErrorReporter instance to use inside the subcommand group */
37
37
  private errorReporter?;
38
- /**
39
- * Safely executes a function with error handling and logging.
40
- *
41
- * Wraps the execution in a try-catch block, logs successful executions,
42
- * and automatically handles errors by logging them and sending a user-friendly
43
- * error message.
44
- *
45
- * @param subcommandName - The name of the subcommand
46
- * @param scope - The logging scope for this execution
47
- * @param interaction - The command interaction
48
- * @param fn - The function to execute
49
- * @private
50
- */
51
- private safeExecute;
52
38
  /**
53
39
  * Executes the appropriate subcommand based on the user's interaction.
54
40
  *
@@ -131,4 +117,19 @@ export declare abstract class SubcommandGroup {
131
117
  * Set the error reporter for this subcommand group.
132
118
  */
133
119
  setErrorReporter(reporter: ErrorReporter): this;
120
+ /**
121
+ * Lifecycle hook called before any subcommand in this group executes.
122
+ *
123
+ * @param interaction - The command interaction
124
+ * @returns true to continue, false to stop execution
125
+ */
126
+ protected beforeExecute?(interaction: ChatInputCommandInteraction, client: Client): Promise<boolean | void>;
127
+ /**
128
+ * Lifecycle hook called after any subcommand in this group executes successfully.
129
+ */
130
+ protected afterExecute?(interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
131
+ /**
132
+ * Lifecycle hook called when any subcommand in this group fails.
133
+ */
134
+ protected onError?(interaction: ChatInputCommandInteraction, error: Error, client: Client): Promise<void>;
134
135
  }
@@ -1,5 +1,4 @@
1
1
  import { SlashCommandBuilder, } from 'discord.js';
2
- import { safeReply } from '../utils/editAndReply.js';
3
2
  /**
4
3
  * Abstract base class for Discord slash command groups with subcommands.
5
4
  *
@@ -23,36 +22,6 @@ import { safeReply } from '../utils/editAndReply.js';
23
22
  * ```
24
23
  */
25
24
  export class SubcommandGroup {
26
- /**
27
- * Safely executes a function with error handling and logging.
28
- *
29
- * Wraps the execution in a try-catch block, logs successful executions,
30
- * and automatically handles errors by logging them and sending a user-friendly
31
- * error message.
32
- *
33
- * @param subcommandName - The name of the subcommand
34
- * @param scope - The logging scope for this execution
35
- * @param interaction - The command interaction
36
- * @param fn - The function to execute
37
- * @private
38
- */
39
- async safeExecute(subcommandName, scope, interaction, fn) {
40
- try {
41
- await fn();
42
- this.logger?.log(`${this.name} (${subcommandName}) command executed`, 'info', scope);
43
- }
44
- catch (err) {
45
- this.logger?.log('An Error occured' + err, 'error', scope, true);
46
- await this.errorReporter?.reportError(err, `Command: ${subcommandName} in SubcommandGroup: ${this.name}`, {
47
- user: interaction.user.tag,
48
- userId: interaction.user.id,
49
- guild: interaction.guild?.name,
50
- guildId: interaction.guildId,
51
- channel: interaction.channel?.id,
52
- });
53
- return await safeReply(interaction, 'An unexpected error occurred.');
54
- }
55
- }
56
25
  /**
57
26
  * Executes the appropriate subcommand based on the user's interaction.
58
27
  *
@@ -79,8 +48,24 @@ export class SubcommandGroup {
79
48
  if (!subcommand) {
80
49
  throw new Error(`Unknown subcommand: ${subcommandName}`);
81
50
  }
82
- const scope = `${subcommand.name}_EXECUTION`;
83
- await this.safeExecute(subcommandName, scope, interaction, () => subcommand.execute(interaction, client));
51
+ if (this.beforeExecute) {
52
+ const shouldContinue = await this.beforeExecute(interaction, client);
53
+ if (shouldContinue === false) {
54
+ return;
55
+ }
56
+ }
57
+ try {
58
+ await subcommand.execute(interaction, client);
59
+ if (this.afterExecute) {
60
+ await this.afterExecute(interaction, client);
61
+ }
62
+ }
63
+ catch (err) {
64
+ if (this.onError) {
65
+ await this.onError(interaction, err, client);
66
+ }
67
+ throw err;
68
+ }
84
69
  }
85
70
  /**
86
71
  * Converts the command group to Discord API JSON format for registration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@julanzw/ttoolbox-discordjs-framework",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A Discord.js command framework with built-in handlers and utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",