Merge branch 'dittodb' into 'main'

Swap the names of DittoDB and DittoDatabase, rename DittoDatabase to DittoPolyPg

See merge request soapbox-pub/ditto!681
This commit is contained in:
Alex Gleason 2025-02-20 17:36:52 +00:00
commit f37a9758a5
12 changed files with 106 additions and 105 deletions

View file

@ -1,6 +0,0 @@
import { DittoDB } from './DittoDB.ts';
Deno.test('DittoDB', async () => {
const db = DittoDB.create('memory://');
await DittoDB.migrate(db.kysely);
});

View file

@ -1,69 +1,15 @@
import fs from 'node:fs/promises'; import type { Kysely } from 'kysely';
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 { DittoDatabase, DittoDatabaseOpts } from './DittoDatabase.ts';
import type { DittoTables } from './DittoTables.ts'; import type { DittoTables } from './DittoTables.ts';
export class DittoDB { export interface DittoDB extends AsyncDisposable {
/** Open a new database connection. */ readonly kysely: Kysely<DittoTables>;
static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { readonly poolSize: number;
const { protocol } = new URL(databaseUrl); readonly availableConnections: number;
listen(channel: string, callback: (payload: string) => void): void;
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. */ export interface DittoDBOpts {
static async migrate(kysely: Kysely<DittoTables>) { poolSize?: number;
const migrator = new Migrator({ debug?: 0 | 1 | 2 | 3 | 4 | 5;
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,
});
}
}
}
} }

View file

@ -1,15 +0,0 @@
import type { Kysely } from 'kysely';
import type { DittoTables } from './DittoTables.ts';
export interface DittoDatabase extends AsyncDisposable {
readonly kysely: Kysely<DittoTables>;
readonly poolSize: number;
readonly availableConnections: number;
listen(channel: string, callback: (payload: string) => void): void;
}
export interface DittoDatabaseOpts {
poolSize?: number;
debug?: 0 | 1 | 2 | 3 | 4 | 5;
}

View file

@ -6,11 +6,11 @@ import { Kysely } from 'kysely';
import { KyselyLogger } from '../KyselyLogger.ts'; import { KyselyLogger } from '../KyselyLogger.ts';
import { isWorker } from '../utils/worker.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'; import type { DittoTables } from '../DittoTables.ts';
export class DittoPglite { export class DittoPglite {
static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { static create(databaseUrl: string, opts?: DittoDBOpts): DittoDB {
const url = new URL(databaseUrl); const url = new URL(databaseUrl);
if (url.protocol === 'file:' && isWorker()) { if (url.protocol === 'file:' && isWorker()) {

View file

@ -0,0 +1,6 @@
import { DittoPolyPg } from './DittoPolyPg.ts';
Deno.test('DittoPolyPg', async () => {
const db = DittoPolyPg.create('memory://');
await DittoPolyPg.migrate(db.kysely);
});

View file

@ -0,0 +1,70 @@
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 './DittoPglite.ts';
import { DittoPostgres } from './DittoPostgres.ts';
import type { JsonValue } from '@std/json';
import type { DittoDB, DittoDBOpts } from '../DittoDB.ts';
import type { DittoTables } from '../DittoTables.ts';
/** Creates either a PGlite or Postgres connection depending on the databaseUrl. */
export class DittoPolyPg {
/** Open a new database connection. */
static create(databaseUrl: string, opts?: DittoDBOpts): DittoDB {
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<DittoTables>) {
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,
});
throw new Error('Migration failed.');
} 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,
});
}
}
}
}

View file

@ -14,11 +14,11 @@ import postgres from 'postgres';
import { KyselyLogger } from '../KyselyLogger.ts'; 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'; import type { DittoTables } from '../DittoTables.ts';
export class DittoPostgres { 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 pg = postgres(databaseUrl, { max: opts?.poolSize });
const kysely = new Kysely<DittoTables>({ const kysely = new Kysely<DittoTables>({

View file

@ -1,4 +1,4 @@
export { DittoDB } from './DittoDB.ts'; export { DittoPolyPg } from './adapters/DittoPolyPg.ts';
export type { DittoDatabase } from './DittoDatabase.ts'; export type { DittoDB } from './DittoDB.ts';
export type { DittoTables } from './DittoTables.ts'; export type { DittoTables } from './DittoTables.ts';

View file

@ -1,5 +1,5 @@
// deno-lint-ignore-file require-await // deno-lint-ignore-file require-await
import { type DittoDatabase, DittoDB } from '@ditto/db'; import { type DittoDB, DittoPolyPg } from '@ditto/db';
import { NPool, NRelay1 } from '@nostrify/nostrify'; import { NPool, NRelay1 } from '@nostrify/nostrify';
import { logi } from '@soapbox/logi'; import { logi } from '@soapbox/logi';
@ -12,25 +12,25 @@ import { seedZapSplits } from '@/utils/zap-split.ts';
export class Storages { export class Storages {
private static _db: Promise<DittoPgStore> | undefined; private static _db: Promise<DittoPgStore> | undefined;
private static _database: Promise<DittoDatabase> | undefined; private static _database: Promise<DittoDB> | undefined;
private static _admin: Promise<AdminStore> | undefined; private static _admin: Promise<AdminStore> | undefined;
private static _client: Promise<NPool<NRelay1>> | undefined; private static _client: Promise<NPool<NRelay1>> | undefined;
public static async database(): Promise<DittoDatabase> { public static async database(): Promise<DittoDB> {
if (!this._database) { if (!this._database) {
this._database = (async () => { this._database = (async () => {
const db = DittoDB.create(Conf.databaseUrl, { const db = DittoPolyPg.create(Conf.databaseUrl, {
poolSize: Conf.pg.poolSize, poolSize: Conf.pg.poolSize,
debug: Conf.pgliteDebug, debug: Conf.pgliteDebug,
}); });
await DittoDB.migrate(db.kysely); await DittoPolyPg.migrate(db.kysely);
return db; return db;
})(); })();
} }
return this._database; return this._database;
} }
public static async kysely(): Promise<DittoDatabase['kysely']> { public static async kysely(): Promise<DittoDB['kysely']> {
const { kysely } = await this.database(); const { kysely } = await this.database();
return kysely; return kysely;
} }

View file

@ -1,6 +1,6 @@
// deno-lint-ignore-file require-await // 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 { detectLanguage } from '@ditto/lang';
import { NPostgres, NPostgresSchema } from '@nostrify/db'; import { NPostgres, NPostgresSchema } from '@nostrify/db';
import { dbEventsCounter, internalSubscriptionsSizeGauge } from '@ditto/metrics'; import { dbEventsCounter, internalSubscriptionsSizeGauge } from '@ditto/metrics';
@ -50,7 +50,7 @@ interface TagConditionOpts {
/** Options for the EventsDB store. */ /** Options for the EventsDB store. */
interface DittoPgStoreOpts { interface DittoPgStoreOpts {
/** Kysely instance to use. */ /** Kysely instance to use. */
db: DittoDatabase; db: DittoDB;
/** Pubkey of the admin account. */ /** Pubkey of the admin account. */
pubkey: string; pubkey: string;
/** Timeout in milliseconds for database queries. */ /** Timeout in milliseconds for database queries. */

View file

@ -1,4 +1,4 @@
import { DittoDB } from '@ditto/db'; import { DittoPolyPg } from '@ditto/db';
import { NostrEvent } from '@nostrify/nostrify'; import { NostrEvent } from '@nostrify/nostrify';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
@ -13,9 +13,9 @@ export async function eventFixture(name: string): Promise<NostrEvent> {
/** Create a database for testing. It uses `DATABASE_URL`, or creates an in-memory database by default. */ /** Create a database for testing. It uses `DATABASE_URL`, or creates an in-memory database by default. */
export async function createTestDB(opts?: { pure?: boolean }) { export async function createTestDB(opts?: { pure?: boolean }) {
const db = DittoDB.create(Conf.databaseUrl, { poolSize: 1 }); const db = DittoPolyPg.create(Conf.databaseUrl, { poolSize: 1 });
await DittoDB.migrate(db.kysely); await DittoPolyPg.migrate(db.kysely);
const store = new DittoPgStore({ const store = new DittoPgStore({
db, db,

View file

@ -1,4 +1,4 @@
import { DittoDB } from '@ditto/db'; import { DittoPolyPg } from '@ditto/db';
import '@soapbox/safe-fetch/load'; import '@soapbox/safe-fetch/load';
import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify'; import { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/nostrify';
import { ReadOnlyPolicy } from '@nostrify/policies'; import { ReadOnlyPolicy } from '@nostrify/policies';
@ -30,7 +30,7 @@ export class CustomPolicy implements NPolicy {
async init({ path, databaseUrl, pubkey }: PolicyInit): Promise<void> { async init({ path, databaseUrl, pubkey }: PolicyInit): Promise<void> {
const Policy = (await import(path)).default; const Policy = (await import(path)).default;
const db = DittoDB.create(databaseUrl, { poolSize: 1 }); const db = DittoPolyPg.create(databaseUrl, { poolSize: 1 });
const store = new DittoPgStore({ const store = new DittoPgStore({
db, db,