diff --git a/.tool-versions b/.tool-versions index 73d0a9db..900b9e20 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -deno 1.45.5 \ No newline at end of file +deno 1.46.3 \ No newline at end of file diff --git a/deno.json b/deno.json index 64331504..d5db42c8 100644 --- a/deno.json +++ b/deno.json @@ -28,6 +28,7 @@ "@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.47", "@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "@db/sqlite": "jsr:@db/sqlite@^0.11.1", + "@electric-sql/pglite": "npm:@electric-sql/pglite@^0.2.5", "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@lambdalisue/async": "jsr:@lambdalisue/async@^2.1.1", @@ -37,6 +38,7 @@ "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", + "@soapbox/kysely-pglite": "jsr:@soapbox/kysely-pglite@^0.0.5", "@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0", "@std/assert": "jsr:@std/assert@^0.225.1", "@std/cli": "jsr:@std/cli@^0.223.0", diff --git a/deno.lock b/deno.lock index cb040522..e59e513c 100644 --- a/deno.lock +++ b/deno.lock @@ -22,6 +22,7 @@ "jsr:@nostrify/nostrify@^0.30.1": "jsr:@nostrify/nostrify@0.30.1", "jsr:@nostrify/types@^0.30.0": "jsr:@nostrify/types@0.30.0", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", + "jsr:@soapbox/kysely-pglite@^0.0.5": "jsr:@soapbox/kysely-pglite@0.0.5", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -53,6 +54,7 @@ "jsr:@std/path@^0.213.1": "jsr:@std/path@0.213.1", "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", "jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0", + "npm:@electric-sql/pglite@^0.2.5": "npm:@electric-sql/pglite@0.2.5", "npm:@isaacs/ttlcache@^1.4.1": "npm:@isaacs/ttlcache@1.4.1", "npm:@noble/hashes@^1.4.0": "npm:@noble/hashes@1.4.0", "npm:@scure/base@^1.1.6": "npm:@scure/base@1.1.6", @@ -257,6 +259,12 @@ "npm:kysely@^0.27.2" ] }, + "@soapbox/kysely-pglite@0.0.5": { + "integrity": "87fc586d46cffede8dcc18598f41411db296130c14f22b044d6a5538fd6e59b5", + "dependencies": [ + "npm:kysely@^0.27.4" + ] + }, "@soapbox/stickynotes@0.4.0": { "integrity": "60bfe61ab3d7e04bf708273b1e2d391a59534bdf29e54160e98d7afd328ca1ec" }, @@ -397,6 +405,10 @@ } }, "npm": { + "@electric-sql/pglite@0.2.5": { + "integrity": "sha512-LrMX2kX0mCVN4xkhIDv1KBVukWtoOI/+P9MDQgHX5QEeZCi5S60LZOa0VWXjufPEz7mJtbuXWJRujD++t0gsHA==", + "dependencies": {} + }, "@isaacs/ttlcache@1.4.1": { "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "dependencies": {} @@ -1920,6 +1932,7 @@ "jsr:@nostrify/db@^0.31.2", "jsr:@nostrify/nostrify@^0.30.1", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", + "jsr:@soapbox/kysely-pglite@^0.0.5", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", "jsr:@std/cli@^0.223.0", @@ -1930,6 +1943,7 @@ "jsr:@std/json@^0.223.0", "jsr:@std/media-types@^0.224.1", "jsr:@std/streams@^0.223.0", + "npm:@electric-sql/pglite@^0.2.5", "npm:@isaacs/ttlcache@^1.4.1", "npm:@noble/secp256k1@^2.0.0", "npm:@scure/base@^1.1.6", diff --git a/src/config.ts b/src/config.ts index 7954e744..145ed9e4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -82,7 +82,7 @@ class Conf { * ``` */ static get databaseUrl(): string { - return Deno.env.get('DATABASE_URL') ?? 'sqlite://data/db.sqlite3'; + return Deno.env.get('DATABASE_URL') ?? 'pglite://data/pgdata'; } static db = { get url(): url.UrlWithStringQuery { @@ -92,6 +92,7 @@ class Conf { switch (Conf.db.url.protocol) { case 'sqlite:': return 'sqlite'; + case 'pglite:': case 'postgres:': case 'postgresql:': return 'postgres'; diff --git a/src/db/DittoDB.ts b/src/db/DittoDB.ts index 5ed5d15e..40d03e5f 100644 --- a/src/db/DittoDB.ts +++ b/src/db/DittoDB.ts @@ -5,6 +5,7 @@ import { NDatabaseSchema, NPostgresSchema } from '@nostrify/db'; import { FileMigrationProvider, Kysely, Migrator } from 'kysely'; import { Conf } from '@/config.ts'; +import { DittoPglite } from '@/db/adapters/DittoPglite.ts'; import { DittoPostgres } from '@/db/adapters/DittoPostgres.ts'; import { DittoSQLite } from '@/db/adapters/DittoSQLite.ts'; import { DittoTables } from '@/db/DittoTables.ts'; @@ -30,12 +31,17 @@ export class DittoDB { static async _getInstance(): Promise { const result = {} as DittoDatabase; - switch (Conf.db.dialect) { - case 'sqlite': + switch (Conf.db.url.protocol) { + case 'sqlite:': result.dialect = 'sqlite'; result.kysely = await DittoSQLite.getInstance(); break; - case 'postgres': + case 'pglite:': + result.dialect = 'postgres'; + result.kysely = await DittoPglite.getInstance(); + break; + case 'postgres:': + case 'postgresql:': result.dialect = 'postgres'; result.kysely = await DittoPostgres.getInstance(); break; diff --git a/src/db/adapters/DittoPglite.ts b/src/db/adapters/DittoPglite.ts new file mode 100644 index 00000000..ef035a3b --- /dev/null +++ b/src/db/adapters/DittoPglite.ts @@ -0,0 +1,56 @@ +import { PGlite } from '@electric-sql/pglite'; +import { NPostgresSchema } from '@nostrify/db'; +import { PgliteDialect } from '@soapbox/kysely-pglite'; +import { Kysely } from 'kysely'; + +import { Conf } from '@/config.ts'; +import { DittoTables } from '@/db/DittoTables.ts'; +import { KyselyLogger } from '@/db/KyselyLogger.ts'; + +export class DittoPglite { + static db: Kysely & Kysely | undefined; + + // deno-lint-ignore require-await + static async getInstance(): Promise & Kysely> { + if (!this.db) { + this.db = new Kysely({ + dialect: new PgliteDialect({ + database: new PGlite(this.path), + }), + log: KyselyLogger, + }) as Kysely & Kysely; + } + + return this.db; + } + + static get poolSize() { + return 1; + } + + static get availableConnections(): number { + return 1; + } + + /** Get the relative or absolute path based on the `DATABASE_URL`. */ + static get path(): string | undefined { + if (Conf.databaseUrl === 'pglite://:memory:') { + return undefined; + } + + const { host, pathname } = Conf.db.url; + + if (!pathname) return ''; + + // Get relative path. + if (host === '') { + return pathname; + } else if (host === '.') { + return pathname; + } else if (host) { + return host + pathname; + } + + return ''; + } +}