# The dispatch context

Pass values from a middleware to the rest of a dispatch with the dispatch context. Covers typing its keys and reading them in a handler, a gate, and a fault card.

A middleware often computes something the rest of the [dispatch](/checks#what-a-dispatch-is) needs, like the caller's account row or their language. Without somewhere to put it, every handler and gate would load that row again. The dispatch context is where you put that value.

Each interaction gets one *bag* of values, which your code reads and writes through `this.dispatch`. The handler, its middleware, its gates, and any card seedcord renders for that interaction all share the same bag.

The bag starts empty. [`DispatchState`](https://docs.seedcord.org/packages/types/latest/dispatch-state) is the interface that lists the keys the bag can hold, with a type for each. If you add a key to `DispatchState`, `this.dispatch` accepts that key with its type in every file in your project.

```ts title="src/dispatch-state.ts"
import '@seedcord/gateway';

declare module '@seedcord/gateway' {
    interface DispatchState {
        actor: string;
        locale: string;
    }
}
```

This file declares two keys, `actor` for the caller's username and `locale` for their language. The rest of the page uses both.

> **Note**
>
> TypeScript augments only a module the file already imports. If you leave that first `import` line out, TypeScript reports "Invalid module name in augmentation".

## Writing a value

Fill the bag from a middleware, which runs first on every dispatch.

```ts title="src/handlers/middlewares/Actor.ts"
import {
    InteractionMiddleware,
    RegisterInteractionMiddleware
} from '@seedcord/gateway';

@RegisterInteractionMiddleware({ priority: -10 })
export class Actor extends InteractionMiddleware {
    public async execute(): Promise<void> {
        this.dispatch.set('actor', this.event.user.username);
        await Promise.resolve();
    }
}
```

## Reading it back

`this.dispatch` is on every handler. [`require`](https://docs.seedcord.org/packages/core/latest/dispatch-context#require) returns the value with `undefined` stripped from its type. If the value is `undefined`, it throws.

```ts title="src/handlers/Whoami.ts"
import { SlashHandler, SlashRoute } from '@seedcord/gateway';

@SlashRoute('whoami')
export class Whoami extends SlashHandler<'whoami'> {
    public async execute(): Promise<void> {
        await this.reply(`hello ${this.dispatch.require('actor')}`);
    }
}
```

Your editor offers the keys `DispatchState` declares.

```ts
this.dispatch.require('
```

If the value is `undefined`, `require` throws a `SeedcordError` with the code `DispatchStateMissing`, and its message names the key you asked for. That covers a key nothing wrote, and a key written as `undefined`. A `null` value comes back as is. Usually the middleware that writes the key never registered, or its `{ kinds }` filter skipped this interaction.

Everything `this.dispatch` carries:

{/* prettier-ignore-start */}

| member                                                                                    | what it does                                                     |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [`id`](https://docs.seedcord.org/packages/core/latest/dispatch-context#id)                | **the one unique id of this dispatch**                           |
| [`set(key, value)`](https://docs.seedcord.org/packages/core/latest/dispatch-context#set)  | writes a value for the rest of this dispatch                     |
| [`get(key)`](https://docs.seedcord.org/packages/core/latest/dispatch-context#get)         | reads a value, or `undefined` if it isn't set                    |
| [`require(key)`](https://docs.seedcord.org/packages/core/latest/dispatch-context#require) | reads a value, throws `DispatchStateMissing` if it's `undefined` |
| [`routeId`](https://docs.seedcord.org/packages/core/latest/dispatch-context#route-id)     | the dispatched handler as `kind:route`, like `slash:daily`       |

{/* prettier-ignore-end */}

The next dispatch of the same route gets a new `id`. The [bus events](/events/default-keys) a dispatch publishes carry its `id` as `dispatchId`. Put `id` in your own log lines to tie a handler's logs to its middleware's.

Use `get` where the value is genuinely optional.

### Reading it from a gate

A gate's context carries the same bag. `KnownActor` below refuses on something a middleware computed.

```ts title="src/gates/KnownActor.ts"
import { defineGate, Silence } from '@seedcord/gateway';

export const KnownActor = defineGate('KnownActor', (ctx) => {
    if (!ctx.dispatch.get('actor')) throw new Silence('no actor');
});
```

[`GateContextBase`](https://docs.seedcord.org/packages/core/latest/gate-context-base) declares the bag, so any gate that reads it fits any handler on either transport. Its [other fields](/checks/your-own) carry the caller's ids and permission bits.

> **Warning**
>
> Every middleware runs *before* the first gate, which is what makes this read safe. If the handler writes a value, every gate has already run, so no gate can read it.

### Reading it from a `Notice`

[`RenderContext`](https://docs.seedcord.org/packages/types/latest/render-context) carries the bag too, which matters most when seedcord builds the reply itself, like the one for an unknown fault. seedcord constructs that `Notice` with only the fault's uuid, so `render(ctx)` is the one place your code reads the dispatch.

```ts title="src/errors/TranslatedNotice.ts"
import { Notice } from '@seedcord/gateway';

import type {
    RenderContext,
    ReplyResponse
} from '@seedcord/gateway';

export class TranslatedNotice extends Notice {
    public constructor(private readonly uuid: string) {
        super(`unknown fault ${uuid}`);
    }

    public render(ctx: RenderContext): ReplyResponse {
        const locale = ctx.dispatch.get('locale') ?? 'en';
        const text = translate(locale, 'fault.generic');

        return {
            components: [new FaultCard(text, this.uuid).component]
        };
    }
}
```

Once you point `errors.defaultError` at it, every unknown fault replies through `TranslatedNotice`.

```ts title="src/bot.ts"
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/gateway';
import { GatewayIntentBits } from 'discord.js';

export const seedcord = new Seedcord({
    bot: {
        commands: { path: null },
        events: { path: null },
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        }
    },
    subscribers: { path: null },
    errors: { defaultError: TranslatedNotice }
});
```

On gateway, an event gets a bag too, shared by every handler registered for it. [Event middleware](/events/middleware#passing-a-value-along) covers how that bag fills.
