@nichoth/replicache-supabase
Version:
Use replicache with supabase
103 lines • 4.23 kB
JavaScript
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