UNPKG

@nichoth/replicache-supabase

Version:
103 lines 4.23 kB
import Debug from '@nichoth/debug'; import pgInit from 'pg-promise'; import { putEntry, getEntry, getEntries, delEntry } from './data.js'; import { getConnectionString } from './supabase.js'; const debug = Debug(); const pgp = pgInit(); const { isolationLevel, TransactionMode } = pgp.txMode; const dbp = (async () => { const db = await pgp(getConnectionString()); await tx(createDatabase, db); return db; })(); // Helper to make sure we always access database at serializable level. export async function tx(fn, db) { if (!db) { db = await dbp; } return await db.tx({ mode: new TransactionMode({ tiLevel: isolationLevel.serializable }) }, fn); } export async function createDatabase(tx) { if (await schemaExists(tx)) return; debug('creating database'); await createSchemaVersion1(tx); } export async function createSchemaVersion1(tx) { await tx.none(`create table replicache_space ( id text primary key not null, version integer not null)`); await tx.none('insert into replicache_space (id, version) values (\'global\', 0)'); await tx.none(`create table replicache_client_group ( id text primary key not null, user_id text not null)`); await tx.none(`create table replicache_client ( id text primary key not null, client_group_id text not null references replicache_client_group(id), last_mutation_id integer not null, last_modified_version integer not null)`); await tx.none('create index on replicache_client (client_group_id, last_modified_version)'); await tx.none(`create table entry ( key text not null, value text not null, deleted boolean not null, last_modified_version integer not null)`); await tx.none('create unique index on entry (key)'); await tx.none('create index on entry (deleted)'); await tx.none('create index on entry (last_modified_version)'); // We are going to be using the supabase realtime api from the client to // receive pokes. This requires js access to db. We use RLS to restrict this // access to only what is needed: read access to the space table. All this // gives JS is the version of the space which is harmless. Everything else is // auth'd through cookie auth using normal web application patterns. await tx.none('alter table replicache_space enable row level security'); await tx.none('alter table replicache_client_group enable row level security'); await tx.none('alter table replicache_client enable row level security'); await tx.none('alter table replicache_client enable row level security'); await tx.none(`create policy anon_read_replicache_space on replicache_space for select to anon using (true)`); // Here we enable the supabase realtime api and monitor updates to the // replicache_space table. await tx.none(`alter publication supabase_realtime add table replicache_space`); await tx.none(`alter publication supabase_realtime set (publish = 'update');`); } async function schemaExists(tx) { const spaceExists = await tx.one(`select exists( select from pg_tables where schemaname = 'public' and tablename = 'replicache_space')`); return spaceExists.exists; } export async function getGlobalVersion(executor) { const row = await executor.one('select version from replicache_space'); const { version } = row; return version; } // Implements the Storage interface required by replicache-transaction in terms // of our Postgres database. export class PostgresStorage { _version; _executor; constructor(version, executor) { this._version = version; this._executor = executor; } putEntry(key, value) { return putEntry(this._executor, key, value, this._version); } async hasEntry(key) { const v = await this.getEntry(key); return v !== undefined; } getEntry(key) { return getEntry(this._executor, key); } getEntries(fromKey) { return getEntries(this._executor, fromKey); } delEntry(key) { return delEntry(this._executor, key, this._version); } } //# sourceMappingURL=db.js.map