diff --git a/packages/db/DittoDB.test.ts b/packages/db/DittoDB.test.ts deleted file mode 100644 index 1a283319..00000000 --- a/packages/db/DittoDB.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DittoDB } from './DittoDB.ts'; - -Deno.test('DittoDB', async () => { - const db = DittoDB.create('memory://'); - await DittoDB.migrate(db.kysely); -}); diff --git a/packages/db/DittoDB.ts b/packages/db/DittoDB.ts index f3442808..99ab4c70 100644 --- a/packages/db/DittoDB.ts +++ b/packages/db/DittoDB.ts @@ -1,69 +1,15 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; +import type { Kysely } from 'kysely'; -import { logi } from '@soapbox/logi'; -import { FileMigrationProvider, type Kysely, Migrator } from 'kysely'; - -import { DittoPglite } from './adapters/DittoPglite.ts'; -import { DittoPostgres } from './adapters/DittoPostgres.ts'; - -import type { JsonValue } from '@std/json'; -import type { DittoDatabase, DittoDatabaseOpts } from './DittoDatabase.ts'; import type { DittoTables } from './DittoTables.ts'; -export class DittoDB { - /** Open a new database connection. */ - static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { - const { protocol } = new URL(databaseUrl); - - switch (protocol) { - case 'file:': - case 'memory:': - return DittoPglite.create(databaseUrl, opts); - case 'postgres:': - case 'postgresql:': - return DittoPostgres.create(databaseUrl, opts); - default: - throw new Error('Unsupported database URL.'); - } - } - - /** Migrate the database to the latest version. */ - static async migrate(kysely: Kysely) { - const migrator = new Migrator({ - db: kysely, - provider: new FileMigrationProvider({ - fs, - path, - migrationFolder: new URL(import.meta.resolve('./migrations')).pathname, - }), - }); - - logi({ level: 'info', ns: 'ditto.db.migration', msg: 'Running migrations...', state: 'started' }); - const { results, error } = await migrator.migrateToLatest(); - - if (error) { - logi({ - level: 'fatal', - ns: 'ditto.db.migration', - msg: 'Migration failed.', - state: 'failed', - results: results as unknown as JsonValue, - error: error instanceof Error ? error : null, - }); - Deno.exit(1); - } else { - if (!results?.length) { - logi({ level: 'info', ns: 'ditto.db.migration', msg: 'Everything up-to-date.', state: 'skipped' }); - } else { - logi({ - level: 'info', - ns: 'ditto.db.migration', - msg: 'Migrations finished!', - state: 'migrated', - results: results as unknown as JsonValue, - }); - } - } - } +export interface DittoDB extends AsyncDisposable { + readonly kysely: Kysely; + readonly poolSize: number; + readonly availableConnections: number; + listen(channel: string, callback: (payload: string) => void): void; +} + +export interface DittoDBOpts { + poolSize?: number; + debug?: 0 | 1 | 2 | 3 | 4 | 5; } diff --git a/packages/db/DittoDatabase.test.ts b/packages/db/DittoDatabase.test.ts new file mode 100644 index 00000000..a91affd5 --- /dev/null +++ b/packages/db/DittoDatabase.test.ts @@ -0,0 +1,6 @@ +import { DittoDatabase } from './DittoDatabase.ts'; + +Deno.test('DittoDatabase', async () => { + const db = DittoDatabase.create('memory://'); + await DittoDatabase.migrate(db.kysely); +}); diff --git a/packages/db/DittoDatabase.ts b/packages/db/DittoDatabase.ts index ebe97cec..916402dd 100644 --- a/packages/db/DittoDatabase.ts +++ b/packages/db/DittoDatabase.ts @@ -1,15 +1,69 @@ -import type { Kysely } from 'kysely'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { logi } from '@soapbox/logi'; +import { FileMigrationProvider, type Kysely, Migrator } from 'kysely'; + +import { DittoPglite } from './adapters/DittoPglite.ts'; +import { DittoPostgres } from './adapters/DittoPostgres.ts'; + +import type { JsonValue } from '@std/json'; +import type { DittoDB, DittoDBOpts } from './DittoDB.ts'; import type { DittoTables } from './DittoTables.ts'; -export interface DittoDatabase extends AsyncDisposable { - readonly kysely: Kysely; - readonly poolSize: number; - readonly availableConnections: number; - listen(channel: string, callback: (payload: string) => void): void; -} +export class DittoDatabase { + /** Open a new database connection. */ + static create(databaseUrl: string, opts?: DittoDBOpts): DittoDB { + const { protocol } = new URL(databaseUrl); -export interface DittoDatabaseOpts { - poolSize?: number; - debug?: 0 | 1 | 2 | 3 | 4 | 5; + switch (protocol) { + case 'file:': + case 'memory:': + return DittoPglite.create(databaseUrl, opts); + case 'postgres:': + case 'postgresql:': + return DittoPostgres.create(databaseUrl, opts); + default: + throw new Error('Unsupported database URL.'); + } + } + + /** Migrate the database to the latest version. */ + static async migrate(kysely: Kysely) { + const migrator = new Migrator({ + db: kysely, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: new URL(import.meta.resolve('./migrations')).pathname, + }), + }); + + logi({ level: 'info', ns: 'ditto.db.migration', msg: 'Running migrations...', state: 'started' }); + const { results, error } = await migrator.migrateToLatest(); + + if (error) { + logi({ + level: 'fatal', + ns: 'ditto.db.migration', + msg: 'Migration failed.', + state: 'failed', + results: results as unknown as JsonValue, + error: error instanceof Error ? error : null, + }); + Deno.exit(1); + } else { + if (!results?.length) { + logi({ level: 'info', ns: 'ditto.db.migration', msg: 'Everything up-to-date.', state: 'skipped' }); + } else { + logi({ + level: 'info', + ns: 'ditto.db.migration', + msg: 'Migrations finished!', + state: 'migrated', + results: results as unknown as JsonValue, + }); + } + } + } } diff --git a/packages/db/adapters/DittoPglite.ts b/packages/db/adapters/DittoPglite.ts index 5e7e6ca4..9a4ad657 100644 --- a/packages/db/adapters/DittoPglite.ts +++ b/packages/db/adapters/DittoPglite.ts @@ -6,11 +6,11 @@ import { Kysely } from 'kysely'; import { KyselyLogger } from '../KyselyLogger.ts'; import { isWorker } from '../utils/worker.ts'; -import type { DittoDatabase, DittoDatabaseOpts } from '../DittoDatabase.ts'; +import type { DittoDB, DittoDBOpts } from '../DittoDB.ts'; import type { DittoTables } from '../DittoTables.ts'; export class DittoPglite { - static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { + static create(databaseUrl: string, opts?: DittoDBOpts): DittoDB { const url = new URL(databaseUrl); if (url.protocol === 'file:' && isWorker()) { diff --git a/packages/db/adapters/DittoPostgres.ts b/packages/db/adapters/DittoPostgres.ts index b62a878b..6657a8d6 100644 --- a/packages/db/adapters/DittoPostgres.ts +++ b/packages/db/adapters/DittoPostgres.ts @@ -14,11 +14,11 @@ import postgres from 'postgres'; import { KyselyLogger } from '../KyselyLogger.ts'; -import type { DittoDatabase, DittoDatabaseOpts } from '../DittoDatabase.ts'; +import type { DittoDB, DittoDBOpts } from '../DittoDB.ts'; import type { DittoTables } from '../DittoTables.ts'; export class DittoPostgres { - static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { + static create(databaseUrl: string, opts?: DittoDBOpts): DittoDB { const pg = postgres(databaseUrl, { max: opts?.poolSize }); const kysely = new Kysely({ diff --git a/packages/db/mod.ts b/packages/db/mod.ts index 39521f20..14c7669c 100644 --- a/packages/db/mod.ts +++ b/packages/db/mod.ts @@ -1,4 +1,4 @@ -export { DittoDB } from './DittoDB.ts'; +export { DittoDatabase } from './DittoDatabase.ts'; -export type { DittoDatabase } from './DittoDatabase.ts'; +export type { DittoDB } from './DittoDB.ts'; export type { DittoTables } from './DittoTables.ts'; diff --git a/packages/ditto/storages.ts b/packages/ditto/storages.ts index ff7b2954..dedd4081 100644 --- a/packages/ditto/storages.ts +++ b/packages/ditto/storages.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file require-await -import { type DittoDatabase, DittoDB } from '@ditto/db'; +import { DittoDatabase, type DittoDB } from '@ditto/db'; import { NPool, NRelay1 } from '@nostrify/nostrify'; import { logi } from '@soapbox/logi'; @@ -12,25 +12,25 @@ import { seedZapSplits } from '@/utils/zap-split.ts'; export class Storages { private static _db: Promise | undefined; - private static _database: Promise | undefined; + private static _database: Promise | undefined; private static _admin: Promise | undefined; private static _client: Promise> | undefined; - public static async database(): Promise { + public static async database(): Promise { if (!this._database) { this._database = (async () => { - const db = DittoDB.create(Conf.databaseUrl, { + const db = DittoDatabase.create(Conf.databaseUrl, { poolSize: Conf.pg.poolSize, debug: Conf.pgliteDebug, }); - await DittoDB.migrate(db.kysely); + await DittoDatabase.migrate(db.kysely); return db; })(); } return this._database; } - public static async kysely(): Promise { + public static async kysely(): Promise { const { kysely } = await this.database(); return kysely; } diff --git a/packages/ditto/storages/DittoPgStore.ts b/packages/ditto/storages/DittoPgStore.ts index a921a309..98fad50b 100644 --- a/packages/ditto/storages/DittoPgStore.ts +++ b/packages/ditto/storages/DittoPgStore.ts @@ -1,6 +1,6 @@ // deno-lint-ignore-file require-await -import { DittoDatabase, DittoTables } from '@ditto/db'; +import { type DittoDB, type DittoTables } from '@ditto/db'; import { detectLanguage } from '@ditto/lang'; import { NPostgres, NPostgresSchema } from '@nostrify/db'; import { dbEventsCounter, internalSubscriptionsSizeGauge } from '@ditto/metrics'; @@ -50,7 +50,7 @@ interface TagConditionOpts { /** Options for the EventsDB store. */ interface DittoPgStoreOpts { /** Kysely instance to use. */ - db: DittoDatabase; + db: DittoDB; /** Pubkey of the admin account. */ pubkey: string; /** Timeout in milliseconds for database queries. */ diff --git a/packages/ditto/test.ts b/packages/ditto/test.ts index c363963f..c245eb21 100644 --- a/packages/ditto/test.ts +++ b/packages/ditto/test.ts @@ -1,4 +1,4 @@ -import { DittoDB } from '@ditto/db'; +import { DittoDatabase } from '@ditto/db'; import { NostrEvent } from '@nostrify/nostrify'; import { Conf } from '@/config.ts'; @@ -13,9 +13,9 @@ export async function eventFixture(name: string): Promise { /** Create a database for testing. It uses `DATABASE_URL`, or creates an in-memory database by default. */ export async function createTestDB(opts?: { pure?: boolean }) { - const db = DittoDB.create(Conf.databaseUrl, { poolSize: 1 }); + const db = DittoDatabase.create(Conf.databaseUrl, { poolSize: 1 }); - await DittoDB.migrate(db.kysely); + await DittoDatabase.migrate(db.kysely); const store = new DittoPgStore({ db, diff --git a/packages/ditto/workers/policy.worker.ts b/packages/ditto/workers/policy.worker.ts index 852c24b5..89ca0158 100644 --- a/packages/ditto/workers/policy.worker.ts +++ b/packages/ditto/workers/policy.worker.ts @@ -1,4 +1,4 @@ -import { DittoDB } from '@ditto/db'; +import { DittoDatabase } from '@ditto/db'; import '@soapbox/safe-fetch/load'; import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; import { ReadOnlyPolicy } from '@nostrify/policies'; @@ -30,7 +30,7 @@ export class CustomPolicy implements NPolicy { async init({ path, databaseUrl, pubkey }: PolicyInit): Promise { const Policy = (await import(path)).default; - const db = DittoDB.create(databaseUrl, { poolSize: 1 }); + const db = DittoDatabase.create(databaseUrl, { poolSize: 1 }); const store = new DittoPgStore({ db,