# The bus

Send your own events between parts of your bot with the bus. Covers declaring a key, writing a subscriber, publishing, running once, listening with a callback, and the keys seedcord publishes.

Say a member levels up in `/award`, and three things should follow: an announcement, a role, and a log line. If the handler calls all three itself, adding a fourth follow-up means editing `/award` again.

The bus separates the two sides. `/award` publishes one named event, so its file holds only the award itself. Each follow-up moves into its own subscriber class, which runs when that event is published. A fourth follow-up is one more subscriber file, with `/award` left as it is.

Event handlers, middleware, subscribers, and plugin services get `core` too, so any of them can publish. If members also level up by chatting, a `messageCreate` handler publishes the same `levelUp`. The same three follow-ups run with none of their code copied.

Everything on the bus stays inside your bot. To show members something, like the announcement, a subscriber sends that message to Discord itself.

Start by declaring the key and its payload. Declaration merging puts them on the [`Subscriptions`](https://docs.seedcord.org/packages/core/latest/subscriptions) interface that your transport package exports.

```ts title="src/subscriptions.ts"
// the augmentation below fails to compile without this import
import '@seedcord/gateway';

declare module '@seedcord/gateway' {
    interface Subscriptions {
        levelUp: { userId: string; level: number };
    }
}
```

Now `levelUp` and its payload type exist everywhere in your project. `@Subscribe` offers it next to the keys seedcord publishes.

```ts
@Subscribe('
```

A subscriber class runs each time its key is published.

```ts title="src/subscribers/Announce.ts"
import { Subscribe, Subscriber } from '@seedcord/gateway';

@Subscribe('levelUp')
export class Announce extends Subscriber<'levelUp'> {
    public async execute(): Promise<void> {
        const { userId, level } = this.data;

        this.logger.info(`${userId} reached ${level}`);
    }
}
```

You write the key in [`@Subscribe`](https://docs.seedcord.org/packages/core/latest/subscribe) and again in the generic on [`Subscriber`](https://docs.seedcord.org/packages/gateway/latest/subscriber). The generic sets the type of `this.data`, here `{ userId, level }`. If the key in `@Subscribe` and the generic differ, the class fails to compile.

## Publishing

`core.bus.publish` takes the key and its payload. TypeScript checks both against what you declared.

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

@SlashRoute('award')
export class Award extends SlashHandler<'award'> {
    public async execute(): Promise<void> {
        const member = this.options.getUser('member');

        this.core.bus.publish('levelUp', {
            userId: member.id,
            level: 12
        });

        await this.reply(`Levelled up <@${member.id}>.`);
    }
}
```

> **Warning**
>
> `publish` returns before your subscribers finish, so code after the call can't read anything a subscriber wrote. If a subscriber or listener throws, seedcord catches the error and logs it on the `subscribers` log channel. The call to `publish` never sees it.

`publish` returns `true` if the key had at least one `on` listener. It counts those listeners only, so a key with three subscribers and no listeners returns `false`.

Subscribers on the same key start at the same time, so any of them can finish first. Don't make one subscriber depend on another's result, since nothing guarantees the order.

## Running once

Some follow-ups belong to the first publish only, like a "first level-up on the server" message. `@Subscribe` takes a `frequency` option for that, the same one [`@RegisterEvent`](https://docs.seedcord.org/packages/gateway/latest/register-event) takes.

```ts
import { Subscribe, Subscriber } from '@seedcord/gateway';

@Subscribe('levelUp', { frequency: 'once' })
export class FirstLevel extends Subscriber<'levelUp'> {
    public async execute(): Promise<void> {
        this.logger.info('somebody levelled for the first time');
    }
}
```

seedcord marks `FirstLevel` as run as soon as it starts. If it throws partway through, it still counts as run. The next publish won't retry it.

## Where to put subscribers

seedcord loads subscribers from the folder you set in `subscribers.path`. The scaffold sets it to `null`, so point it at your folder when you write your first subscriber.

```ts title="src/bot.ts"
export const seedcord = new Seedcord({
    bot,
    subscribers: {
        path: resolve(import.meta.dirname, './subscribers')
    }
});
```

If a class in that folder doesn't have `@Subscribe`, it still loads but never runs. The `subscriber-missing-decorators` lint rule flags the missing decorator while you write the class.

## Listening without a class

`core.bus` is a [`TypedEventEmitter`](https://docs.seedcord.org/packages/event-emitter/latest/typed-event-emitter), so a plain callback works too.

```ts
core.bus.on('levelUp', (data) => {
    console.log(data.level);
});

const [data] = await core.bus.waitFor('levelUp', {
    timeoutMs: 5000
});
```

`on`, `once`, and `off` each take a key and a callback. `waitFor` resolves with the next payload. Its optional second argument takes three fields:

* `filter`, a function that receives the payload and returns a boolean
* `timeoutMs`, how long to wait
* `signal`, an abort signal

If the timeout passes or the signal aborts, `waitFor` rejects with a `WaitForError`. Its `reason` is `'timeout'` or `'aborted'`.

Write a subscriber by default. It gets `this.core` and `this.logger` without any setup. seedcord loads its file at startup, so nothing in your code has to register it. Each publish runs a new instance, so a field on the class doesn't keep its value between publishes. A callback registered with `on` suits code that already holds `core`, like a plugin. `waitFor` suits code that has to pause until the next publish.

> **Warning**
>
> Don't call `core.bus.emit`. Your editor still offers it beside `publish`, crossed out as deprecated. It throws a `SeedcordTypeError` with the code `CoreBusEmitUnavailable`. Call `publish`, which reaches your subscribers and your `on` listeners together.

## The framework's own keys

seedcord publishes its own keys onto the same bus, like `interactionDispatched` once per [dispatch](/checks#what-a-dispatch-is). You subscribe to them the same way you subscribe to `levelUp`.

```ts
@Subscribe('interactionDispatched')
export class DispatchTimes extends Subscriber<'interactionDispatched'> {
    public async execute(): Promise<void> {
        const { routeId, durationMs } = this.data;

        this.logger.info(`${routeId} took ${durationMs}ms`);
    }
}
```

`durationMs` is how long that dispatch took. Only seedcord publishes these keys, so calling `publish` with one is a compile error.

```ts
// publish accepts only the keys you declared, levelUp on this page
core.bus.publish('interactionDispatched', {} as never);
```

[The framework's bus keys](/events/default-keys) lists every key, which transport publishes it, and its payload. To post one of them to a Discord channel, write a [`WebhookLog`](https://docs.seedcord.org/packages/gateway/latest/webhook-log). [Reporting faults](/replying/reporting) shows how.
