@wolfstar/http-framework 6.2.2 → 6.3.0-next-20261007181329

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/README.md CHANGED
@@ -270,23 +270,152 @@ client.on(Events.CommandError, (error, context) => {
270
270
  | `Events.CommandNameMissing` | `commandNameMissing` | `interaction, response` |
271
271
  | `Events.CommandNameUnknown` | `commandNameUnknown` | `interaction, response` |
272
272
  | `Events.CommandMethodUnknown` | `commandMethodUnknown` | `context` |
273
+ | `Events.PreChatInputCommandRun` | `preChatInputCommandRun` | `context` |
274
+ | `Events.PreContextMenuCommandRun` | `preContextMenuCommandRun` | `context` |
275
+ | `Events.ChatInputCommandAccepted` | `chatInputCommandAccepted` | `context` |
276
+ | `Events.ContextMenuCommandAccepted` | `contextMenuCommandAccepted` | `context` |
273
277
  | `Events.CommandRun` | `commandRun` | `context` |
274
278
  | `Events.CommandSuccess` | `commandSuccess` | `context, value: unknown` |
275
279
  | `Events.CommandError` | `commandError` | `error: unknown, context` |
280
+ | `Events.ChatInputCommandDenied` | `chatInputCommandDenied` | `error: UserError, context` |
281
+ | `Events.ContextMenuCommandDenied` | `contextMenuCommandDenied` | `error: UserError, context` |
276
282
  | `Events.CommandFinish` | `commandFinish` | `context` |
283
+ | `Events.AutocompleteAccepted` | `autocompleteAccepted` | `context` |
277
284
  | `Events.AutocompleteRun` | `autocompleteRun` | `context` |
278
285
  | `Events.AutocompleteSuccess` | `autocompleteSuccess` | `context, value: unknown` |
279
286
  | `Events.AutocompleteError` | `autocompleteError` | `error: unknown, context` |
287
+ | `Events.AutocompleteDenied` | `autocompleteDenied` | `error: UserError, context` |
280
288
  | `Events.AutocompleteFinish` | `autocompleteFinish` | `context` |
281
289
  | `Events.InteractionHandlerNameInvalid` | `interactionHandlerNameInvalid` | `interaction, response` |
282
290
  | `Events.InteractionHandlerNameUnknown` | `interactionHandlerNameUnknown` | `interaction, response` |
291
+ | `Events.InteractionHandlerAccepted` | `interactionHandlerAccepted` | `context` |
283
292
  | `Events.InteractionHandlerRun` | `interactionHandlerRun` | `context` |
284
293
  | `Events.InteractionHandlerSuccess` | `interactionHandlerSuccess` | `context, value: unknown` |
285
294
  | `Events.InteractionHandlerError` | `interactionHandlerError` | `error: unknown, context` |
295
+ | `Events.InteractionHandlerDenied` | `interactionHandlerDenied` | `error: UserError, context` |
286
296
  | `Events.InteractionHandlerFinish` | `interactionHandlerFinish` | `context` |
287
297
 
288
298
  The Hot Module Reloading events are listed in [their own section](#hot-module-reloading).
289
299
 
300
+ #### Expected errors: `*Denied` events
301
+
302
+ When a command, an autocomplete handler, or an interaction handler throws a `UserError` (or a subclass such as
303
+ `PreconditionError` and `ArgumentError`), the failure is an expected one, meant to be shown to the user. The client emits
304
+ it as `chatInputCommandDenied` or `contextMenuCommandDenied` (commands, by the type of the command), `autocompleteDenied`,
305
+ or `interactionHandlerDenied` instead of `commandError`, `autocompleteError`, or `interactionHandlerError`. Anything that
306
+ is not a `UserError` keeps going to the `*Error` events, so a listener that reports bugs, such as the Sentry one of
307
+ `@wolfstar/shared-http-pieces`, no longer sees the expected ones.
308
+
309
+ ```typescript
310
+ import { Events, Listener, type ClientEventCommandContext, type UserError } from '@wolfstar/http-framework';
311
+
312
+ export class UserListener extends Listener {
313
+ public constructor(context: Listener.LoaderContext) {
314
+ super(context, { event: Events.ChatInputCommandDenied });
315
+ }
316
+
317
+ public run(error: UserError, context: ClientEventCommandContext) {
318
+ this.container.logger.info(`${context.command.name} denied: ${error.identifier}`);
319
+ }
320
+ }
321
+ ```
322
+
323
+ The check is `isUserError(error)`, exported for the same use: it accepts `instanceof UserError`, and also an `Error` with a
324
+ string `identifier` and a `context` property, since a bundle that inlines the framework has its own `UserError` class.
325
+ The `error` event and the HTTP response are not affected: both behave as for any other thrown error.
326
+
327
+ > [!WARNING]
328
+ > Before 6.3.0 a thrown `UserError` was emitted as `commandError` (or `autocompleteError` / `interactionHandlerError`).
329
+ > A listener on those events that handles `UserError` or `PreconditionError` has to move to the `*Denied` events.
330
+
331
+ #### Preconditions
332
+
333
+ A precondition is a check that runs before a chat input or context menu command does, before the autocomplete of a
334
+ command, and before an interaction handler. When it denies the interaction, the method is not run and the error is
335
+ emitted as `chatInputCommandDenied`, `contextMenuCommandDenied`, `autocompleteDenied` or `interactionHandlerDenied`, the
336
+ same events as a `UserError` the command throws.
337
+
338
+ The events of a command are, in order: `preChatInputCommandRun` (or `preContextMenuCommandRun`), the preconditions, then
339
+ `chatInputCommandAccepted` (or `contextMenuCommandAccepted`) and `commandRun`, or the `*Denied` event, then
340
+ `commandSuccess` or `commandError`, and `commandFinish`. An autocomplete and an interaction handler emit
341
+ `autocompleteAccepted` and `interactionHandlerAccepted` right before `autocompleteRun` and `interactionHandlerRun`.
342
+ `commandRun` is no longer emitted for a command that was denied.
343
+
344
+ A precondition is a piece: a class that extends `Precondition`, in the `preconditions` directory, loaded into the
345
+ `preconditions` store. It implements `chatInputRun` and/or `contextMenuRun`, and answers with `this.ok()` or
346
+ `this.error(...)`, which builds a `PreconditionError` for the piece.
347
+
348
+ ```typescript
349
+ import { Precondition } from '@wolfstar/http-framework';
350
+
351
+ export class UserPrecondition extends Precondition {
352
+ public override chatInputRun(interaction: Precondition.ChatInputInteraction) {
353
+ return interaction.user.id === process.env.OWNER_ID ? this.ok() : this.error({ message: 'Only the owner can use this command.' });
354
+ }
355
+ }
356
+ ```
357
+
358
+ A command lists the preconditions it needs by name, in the `preconditions` option. An entry is the name, the name with a
359
+ `context` that the piece receives as its last argument, or a function for a check that does not need a piece of its own.
360
+ They run in the order listed and the first denial stops the rest.
361
+
362
+ ```typescript
363
+ import { err, ok } from '@sapphire/result';
364
+ import { Command, PreconditionError, type PreconditionFunction } from '@wolfstar/http-framework';
365
+
366
+ const NotOnMonday: PreconditionFunction = () => (new Date().getDay() === 1 ? err(new PreconditionError({ precondition: 'NotOnMonday' })) : ok());
367
+
368
+ export class UserCommand extends Command {
369
+ public constructor(context: Command.LoaderContext, options: Command.Options) {
370
+ super(context, { ...options, preconditions: ['UserPrecondition', { name: 'Role', context: { roleId: '737141877803057244' } }, NotOnMonday] });
371
+ }
372
+ }
373
+ ```
374
+
375
+ An entry can also be an array. The list of a command is an `AND`: every entry has to pass. An array nested in it is an
376
+ `OR`: one entry has to pass, and when none does, the last error is the denial. An array nested in that is an `AND` again,
377
+ and so on, so the following is `Connect && (Moderator || (DJ && SongAuthor))`:
378
+
379
+ ```typescript
380
+ super(context, { ...options, preconditions: ['Connect', ['Moderator', ['DJ', 'SongAuthor']]] });
381
+ ```
382
+
383
+ The entries run one after the other and stop as soon as the result is known. To start all of them at once, write the list
384
+ as `{ entries, mode: PreconditionRunMode.Parallel }`; the arrays nested in it inherit the mode, every entry is run, and
385
+ the denial is the first error in order for an `AND` and the last one for an `OR`.
386
+
387
+ A precondition with a `position` is global: it runs for every command, in ascending order of `position`, before the ones
388
+ of the command. A piece can also implement `autocompleteRun` and `interactionHandlerRun`, and an interaction handler has
389
+ the same `preconditions` option as a command.
390
+
391
+ ```typescript
392
+ import { Precondition } from '@wolfstar/http-framework';
393
+
394
+ export class UserPrecondition extends Precondition {
395
+ public constructor(context: Precondition.LoaderContext) {
396
+ super(context, { position: 10 });
397
+ }
398
+
399
+ // ...
400
+ }
401
+ ```
402
+
403
+ An entry that does not apply to an autocomplete or an interaction handler is left out of the result of its array, so it
404
+ cannot make an `OR` pass.
405
+
406
+ A piece that has no method for the chat input or context menu command it is asked to check denies it with
407
+ `Identifiers.PreconditionMissingChatInputHandler` or `Identifiers.PreconditionMissingContextMenuHandler`. For an
408
+ autocomplete or an interaction handler a piece with no `autocompleteRun` or `interactionHandlerRun` is skipped instead,
409
+ global ones included, so a check written for the commands does not deny their autocomplete or the buttons of the bot, and
410
+ the functions of a command are not run for its autocomplete. A name that is not in the store denies with
411
+ `Identifiers.PreconditionUnavailable`. A precondition that throws is handled like a command that throws: a `UserError`
412
+ goes to the `*Denied` events, anything else to `commandError`.
413
+
414
+ This follows the preconditions of `@sapphire/framework`, except for the message flow, which has no use over HTTP, the
415
+ built-in checks (`Cooldown`, `NSFW`, `GuildOnly`, ...), and the typed registry of precondition names. The checks for
416
+ autocomplete and interaction handlers, and their `Accepted` events, are not in Sapphire. The permission decorators of
417
+ `@wolfstar/decorators` keep working as before.
418
+
290
419
  Listeners declared as pieces can use the enum too:
291
420
 
292
421
  ```typescript