Skip to content

Package

plugin-kysely-postgres v0.6.0

Changelog

seedcordseedcord

The whole Discord bot, typed end to end

Website · Guide · Reference · Discord

npm node bun license

About

@seedcord/plugin-kysely-postgres connects a seedcord bot to Postgres through Kysely. It opens the pool during startup, runs your migrations, loads every class under dir that carries @RegisterKyselyService, and exposes them under the key you attached it on.

You declare the schema once. Kysely types every query off it, so a renamed column breaks the build.

It runs on the gateway transport and on http's server runtime. Attaching it to an edge host is a compile error.

Until v1.0.0, minor versions can break.

Installation

pnpm add @seedcord/plugin-kysely-postgres kysely pg

kysely, pg, envapt, typescript, and @seedcord/core are peer dependencies.

Attach

attach takes a property name, the plugin class, and its options. Chain it off the constructor:

// bot.ts
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/gateway';
import { KyselyPostgres } from '@seedcord/plugin-kysely-postgres';

export const seedcord = new Seedcord(config).attach('sql', KyselyPostgres, {
    dir: resolve(import.meta.dirname, './services'),
    connectionString: Vars.databaseUrl,
    migrations: { path: resolve(import.meta.dirname, './migrations') }
});

export default seedcord;
// index.ts
import seedcord from './bot';

await seedcord.start();

attach returns the instance widened with the key, and seedcord codegen writes sql: (typeof Bot)['sql'] into seedcord-gen.d.ts off a default import of that module. Calling attach as a bare statement drops the widened type. A named-only export leaves codegen with nothing to import.

Attach before startup. A call after initialization throws CorePluginAfterInit.

Vars.databaseUrl stands in for an envapt accessor. process.env.DATABASE_URL types as string | undefined, which connectionString?: string rejects under the exactOptionalPropertyTypes that @seedcord/tsconfig turns on.

migrations.onStartup runs to latest by default.

Schema

Declare it once so every service and the plugin's own connection resolve table names from it:

declare module '@seedcord/plugin-kysely-postgres' {
    interface KyselyDatabase {
        schema: MyDatabase;
    }
}

Until that declaration exists, KyselyTable widens to string and any table name type-checks.

Services

The service reads the table whose name matches its key. Set table when the two differ:

import { KyselyService, RegisterKyselyService } from '@seedcord/plugin-kysely-postgres';

@RegisterKyselyService('users', { table: 'app_users' })
export class UsersService extends KyselyService<'app_users'> {
    public async findByUserId(userId: string) {
        return this.db.selectFrom(this.table).selectAll().where('user_id', '=', userId).executeTakeFirst();
    }
}

Name each key once so the lookup types resolve:

declare module '@seedcord/plugin-kysely-postgres' {
    interface KyselyServices {
        users: UsersService;
    }
}

Then call it from a handler through core:

const user = await this.core.sql.services.users.findByUserId(this.event.user.id);