# Plugins

Add your own services to a bot with plugins, which seedcord starts and stops with it, and read them from any handler.

Most bots need something beside Discord, like a database, a cache, or a client for another API. Wiring one up by hand means starting it before the bot logs in, handing it to every handler through a global or an import, and stopping it on shutdown before the process exits.

A plugin packages that work. It starts and stops at defined points in your bot's lifecycle, takes its own options, and logs on its own channel. You attach it under a key you pick, then read it from `this.core` anywhere `core` is available, like a handler.

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

import { Seedcord } from '@seedcord/gateway';
import { Mongoose } from '@seedcord/plugin-mongoose';
import { GatewayIntentBits } from 'discord.js';

export const seedcord = new Seedcord({
    bot: {
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        },
        commands: {
            path: resolve(import.meta.dirname, './commands')
        },
        events: { path: null }
    },
    subscribers: { path: null }
}).attach('db', Mongoose, {
    dir: resolve(import.meta.dirname, './services'),
    uri: 'mongodb://localhost:27017/',
    name: 'seedcord'
});

export default seedcord;
```

[`attach`](https://seedcord.org/docs/packages/gateway/latest/classes/seedcord#attach) takes the key, the plugin class, and whatever that class's constructor takes after the host. Here that's `'db'`, `Mongoose`, and its options object. If a plugin's constructor takes only the host, attach it with two arguments.

Keep the `export default`. `seedcord codegen` imports that default export to [type `this.core.db`](https://seedcord.org/guide/plugins/typing/). Without it, `this.core.db` doesn't compile.

## Reading it from a handler

`this.core` carries every attached plugin under its key.

```ts title="src/handlers/History.ts"
@SlashRoute('history')
export class History extends SlashHandler<'history'> {
    public async execute(): Promise<void> {
        const found =
            await this.core.db.services.users.findByName('ada');

        await this.reply(
            found ? `Found ${found.username}.` : 'No record.'
        );
    }
}
```

`History` reaches the mongoose service through `this.core.db`.

The sample declares its `Core` block by hand so it compiles on this page. In your project, `seedcord codegen` writes that block for you.

## Attaching more than one

A bot can attach more than one plugin, each under its own key. Chain another `attach` for each one. Inside one startup phase the plugins start one after another, in the order you attached them. If your plugin's `init()` reads another plugin, attach that one first. Otherwise your `init()` runs before the other plugin has started.

```ts
export const seedcord = new Seedcord({
    bot: {
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        },
        commands: {
            path: resolve(import.meta.dirname, './commands')
        },
        events: { path: null }
    },
    subscribers: { path: null }
})
    .attach('db', Mongoose, {
        dir: resolve(import.meta.dirname, './mongo'),
        uri: 'mongodb://localhost:27017/',
        name: 'seedcord'
    })
    .attach('sql', KyselyPostgres, {
        dir: resolve(import.meta.dirname, './postgres'),
        connectionString: 'postgres://localhost:5432/seedcord',
        migrations: {
            path: resolve(import.meta.dirname, './migrations')
        }
    });
```

### Grouping plugins under one name

Your own services are plugins too, so each one takes its own key on `core`. Ten of them means ten keys, `core.usersService`, `core.ticketsService`, `core.economyService`, and seven more, in one list with your database.

Put a dot in the key to attach the plugin under a group.

```ts
export default seedcord
    .attach('services.users', Users)
    .attach('services.tickets', Tickets);
```

`services` here is a plain object on `core`. Each plugin takes the key after the dot, so a handler reads one as `this.core.services.users`. seedcord still starts both in attach order and stops them during shutdown.

A key takes one dot. `seedcord codegen` writes one row for the group, which types every plugin inside it.

A name holds one plugin or one group. If `services.users` exists, attaching a plugin at `services` fails to compile. Nesting under `db`, which already holds your database, fails the same way.

```ts
seedcord
    .attach('services.users', Users)
    .attach('services', Tickets);
```

## No plugin types in your handler?

`this.core.db` has the type `Mongoose` in `History` because the first sample exports what `attach` returns. If you call `attach` as a statement and then export `seedcord` itself, the plugin still starts. TypeScript loses its type, since nothing keeps the type that call returns.

```ts
const seedcord = new Seedcord(config);
seedcord.attach('db', Mongoose, options);

type Db = (typeof seedcord)['db'];
```

The last line is the lookup codegen writes into `src/seedcord-gen.d.ts`. You won't see this error there, because the scaffold's tsconfig turns on `skipLibCheck`, which skips type errors in `.d.ts` files. `this.core.db` compiles as `any` instead. Your editor stops offering `services`, and a typo like `this.core.db.servics` still compiles.

## The key is also a log channel

Every mongoose log line prints on the `db` channel, since a plugin logs on the channel named after its attach key. You can then filter or silence one plugin's logs by the key you gave it. A grouped plugin keeps its whole key, dot included, as its channel. seedcord already logs on reserved channels of its own, so those names can't be keys.

```ts
seedcord.attach('commands', Mongoose, {
    dir: resolve(import.meta.dirname, './services'),
    uri: 'mongodb://localhost:27017/',
    name: 'seedcord'
});
```

> **Warning**
>
> `commands` is one of those reserved channels, so that attach fails to compile. If the compiler can't see the key, like one read from an environment variable, `attach` throws `CorePluginReservedChannel` when it runs. [Logging](https://seedcord.org/guide/tooling/logging/) lists the reserved channels with what each one carries.

`attach` also throws in these cases when it runs:

* If the bot has already started, it throws `CorePluginAfterInit`.
* If another plugin or seedcord itself already uses the key on `core`, like `bus`, it throws `CorePluginKeyExists`.
* Nesting under a name the bot already uses, like `db`, throws `CorePluginGroupTaken`.
* Attaching a plugin at a name that holds a group throws `CorePluginKeyHoldsGroup`.
* If the key has an empty part or a second dot, it throws `CorePluginKeyMalformed`.
* If the plugin extends [`Plugin`](https://seedcord.org/docs/packages/core/latest/classes/plugin) from a second copy of `@seedcord/core`, it throws `CorePluginFromOtherCore`. This usually happens when the plugin and your `@seedcord` packages need different versions of `@seedcord/core`.

## A plugin declares where it runs

Some plugins only work in one setup, like one that reads discord.js objects on gateway. A plugin can declare the transport it supports, `'gateway'` or `'http'`, and the runtime, `'server'` or `'edge'`. Both default to `'any'`, which attaches to any bot. If you attach a plugin to a bot that doesn't match, TypeScript reports the mismatch in your editor before the bot runs.

```txt output
this plugin declares transport 'gateway' but this bot runs 'http'
```

> **Http only**
>
> An edge bot can't use plugins. `createSeedcord` from `@seedcord/http/edge` returns a request handler, which doesn't have `attach`.

[Typing a plugin](https://seedcord.org/guide/plugins/typing/) explains the codegen step behind `this.core.db`. [The lifecycle](https://seedcord.org/guide/plugins/lifecycle/) says when a plugin starts and when it stops. To write one, start at [Writing your own](https://seedcord.org/guide/plugins/your-own/).
