# Event middleware

Run code before your event handlers with event middleware. Covers stopping an event, choosing which events it runs for, the order, passing values along, cleaning up in after(), and where middleware files go.

Suppose three handlers respond to `messageCreate`, and none of them should answer a muted member. Each handler would repeat the same mute lookup. If you add a fourth handler and forget the lookup, it answers anyway.

An event middleware runs that check once, before every handler registered for the event. If the middleware throws, none of those handlers run.

```ts title="src/events/middlewares/SkipMuted.ts"
import {
    EventMiddleware,
    RegisterEventMiddleware,
    Silence
} from '@seedcord/gateway';
import { Events } from 'discord.js';

@RegisterEventMiddleware({
    events: [Events.MessageCreate]
})
export class SkipMuted extends EventMiddleware<Events.MessageCreate> {
    public async execute(): Promise<void> {
        const [message] = this.event;

        if (await isMuted(message.author.id)) {
            throw new Silence('author is muted');
        }
    }
}
```

[`@RegisterEventMiddleware`](https://docs.seedcord.org/packages/gateway/latest/register-event-middleware) takes one options object. Interactions have [their own middleware](/checks/middleware), registered with [`@RegisterInteractionMiddleware`](https://docs.seedcord.org/packages/core/latest/register-interaction-middleware), which filters on `{ kinds }`.

## Stopping the event

Any throw stops the event. What happens next depends on the class you throw. `SkipMuted` throws a [`Silence`](https://docs.seedcord.org/packages/core/latest/silence), which ends the event without reporting anything. seedcord logs its reason, `'author is muted'`, as a debug line on the `errors` log channel. To turn that line off, set `errors.logSilences` to `false`. If a `Silence` has no reason, seedcord doesn't log anything. Anything else is reported the same way as a handler's [throw](/replying/throwing).

That stops one [event dispatch](/events#several-handlers-for-one-event). The next message runs every middleware again from the start.

## Which events it runs for

You write the events twice, once in `{ events }` and once in the generic on [`EventMiddleware`](https://docs.seedcord.org/packages/gateway/latest/event-middleware). If the two differ, the decorator line fails to compile. `SkipMuted` lists one event, so [`this.event`](https://docs.seedcord.org/packages/gateway/latest/event-middleware#event) is that event's payload.

With several events, the payload could be any of them, so `this.event` is `never`. [`this.eventName`](https://docs.seedcord.org/packages/gateway/latest/event-middleware#event-name) returns the event that fired, which is enough for work that doesn't read the payload, like counting.

```ts title="src/events/middlewares/CountMessages.ts"
@RegisterEventMiddleware({
    events: [Events.MessageCreate, Events.MessageDelete],
    priority: 1
})
export class CountMessages extends EventMiddleware<
    Events.MessageCreate | Events.MessageDelete
> {
    public async execute(): Promise<void> {
        const name = this.eventName;
        this.logger.debug(`${name} fired`);
    }
}
```

`name` is `Events.MessageCreate | Events.MessageDelete`. Without `{ events }`, the middleware runs on every event your handlers registered. Leave the generic off too, since a generic without `{ events }` fails to compile.

```ts title="src/events/middlewares/Trace.ts"
@RegisterEventMiddleware({ priority: 2 })
export class Trace extends EventMiddleware {
    public async execute(): Promise<void> {
        this.logger.debug(`${this.eventName} reached the chain`);
    }
}
```

`Trace` reads only `this.eventName`, which is all a catchall can read. An event middleware doesn't have `match`, because it runs the same code for every event it lists. When your check reads the payload of several events, write one middleware class per event.

## Order

`priority` takes any finite number, including negative ones. The middleware with the lower number runs first. If you leave `priority` out, the middleware gets `0`. That puts `SkipMuted` first at `0`, then `CountMessages` at `1`, then `Trace` at `2`. All three run before seedcord builds the first handler, as [the event dispatch order](/events#several-handlers-for-one-event) lays out.

If two middleware share a number, they run in the order seedcord loaded their files. That order can change when you add, rename, or move a middleware file, so give anything order-dependent its own number.

## Passing a value along

A middleware often looks up something every handler needs, like the server's language. Without somewhere to put it, each handler would look it up again.

Each event dispatch gets one *bag* of values, which your code reads and writes through `this.dispatch`. The middleware and every handler for that event share it. `Locale` writes the language once. Each handler reads it back.

```ts title="src/events/middlewares/Locale.ts"
@RegisterEventMiddleware({ events: [Events.MessageCreate] })
export class Locale extends EventMiddleware<Events.MessageCreate> {
    public async execute(): Promise<void> {
        const [message] = this.event;
        const guildLocale = message.guild?.preferredLocale;

        this.dispatch.set('locale', guildLocale ?? 'en-US');
        await Promise.resolve();
    }
}
```

You declare the key and read it back the same way you do on [an interaction dispatch](/checks/dispatch-context). An interaction's bag has one handler reading it. An event's bag is shared by every handler registered for that event. On an event, `routeId` reads `event:<name>`. The bag lasts until every handler for that event has finished and every middleware's `after()` has run. The next time the event fires, it gets a new, empty bag.

## After the event

A middleware that opens something in `execute()`, like a timer or a lock, needs a place to close it once the handlers are done. That place is `after()`, an optional method seedcord calls when the event finishes.

A middleware gets its `after()` call as long as its own `execute()` started, even if that `execute()` threw. Say middleware `A` runs before middleware `B`.

* If both ran, `B.after()` runs first, then `A.after()`.
* If `A` threw, only `A.after()` runs. `B` never started, so it doesn't get one.

```ts title="src/events/middlewares/Timing.ts"
@RegisterEventMiddleware()
export class Timing extends EventMiddleware {
    private startedAt = 0;

    public async execute(): Promise<void> {
        this.startedAt = performance.now();
        await Promise.resolve();
    }

    public override async after(
        result: EventDispatchResult
    ): Promise<void> {
        const elapsed = Math.round(
            performance.now() - this.startedAt
        );

        this.logger.debug(
            `${this.eventName}: ${result.handlers.length} in ${elapsed}ms`
        );
        await Promise.resolve();
    }
}
```

The base class declares `after()`, so `Timing` marks its version `override`. Leaving that off is a compile error under TypeScript's `noImplicitOverride`.

`after()` receives an [`EventDispatchResult`](https://docs.seedcord.org/packages/core/latest/event-dispatch-result) that says how the event ended. The `Timing` sample logs how many handlers ran, from `result.handlers`.

{/* prettier-ignore-start */}

| `outcome`   | when                                                              | `caught`         | `handlers`                     |
| ----------- | ----------------------------------------------------------------- | ---------------- | ------------------------------ |
| `'handled'` | every middleware finished without throwing                        | not present      | one entry per handler that ran |
| `'refused'` | a middleware threw a `Silence`, or a `Notice` with `report` false | the thrown value | empty                          |
| `'failed'`  | a middleware threw a reported `Notice` or any other error         | the thrown value | empty                          |

{/* prettier-ignore-end */}

`outcome` only covers the middleware. If a handler throws, `outcome` still reads `'handled'`. That handler's own result is in its `handlers` entry, which carries the handler's class name as `handler`, plus its own `outcome` and `caught` with the same three values.

An `after()` that throws doesn't stop the other `after()` calls. If `B.after()` throws, seedcord logs the error and still calls `A.after()`.

## Where to put middleware

seedcord loads event middleware from the folder you set in `bot.events.middlewares`. If you leave the key out, seedcord skips the middleware scan. Leave it out when your bot doesn't use any event middleware.

```ts title="src/bot.ts"
export const seedcord = new Seedcord({
    bot: {
        ...rest,
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        events: {
            path: resolve(import.meta.dirname, './events'),
            middlewares: resolve(
                import.meta.dirname,
                './events/middlewares'
            )
        }
    },
    subscribers: { path: null }
});
```

seedcord imports every file in that `./events/middlewares` folder. If a class there doesn't have `@RegisterEventMiddleware`, it still loads but never runs, the same as an event handler without `@RegisterEvent`. The `middleware-missing-register-decorator` lint rule flags the missing decorator while you write the class.

> **Warning**
>
> Middleware runs only when a handler is about to run. seedcord checks for a handler left to run before it starts any middleware. A catchall middleware therefore skips two kinds of events:
>
> * an event that no handler registered
> * an event whose handlers were all registered `once` and have already run

> **Gateway only**
>
> `@RegisterEventMiddleware` and `EventMiddleware` ship on `@seedcord/gateway`. An http bot doesn't receive client events to run them on.

[Message events](/events/messages) come with rules that middleware can't work around.
