fix(db): pin the tenant scope to one connection (coreci-chat#14)
ci / test-pglite (PGlite, default) (push) Failing after 1m50s
ci / test-postgres (Postgres 16, G-022 RLS) (push) Failing after 1m32s
ci / test-pglite (PGlite, default) (pull_request) Failing after 1m50s
ci / test-postgres (Postgres 16, G-022 RLS) (pull_request) Failing after 1m33s
ci / test-pglite (PGlite, default) (push) Failing after 1m50s
ci / test-postgres (Postgres 16, G-022 RLS) (push) Failing after 1m32s
ci / test-pglite (PGlite, default) (pull_request) Failing after 1m50s
ci / test-postgres (Postgres 16, G-022 RLS) (pull_request) Failing after 1m33s
This commit is contained in:
@@ -49,13 +49,44 @@ export async function createDb(opts: CreateDbOptions = {}): Promise<DbClient> {
|
||||
client.release();
|
||||
}
|
||||
},
|
||||
// One connection for the whole transaction (coreci-chat#14): a pool
|
||||
// hands out a different connection per statement, so a transaction-local
|
||||
// setting (the tenant scope SET LOCAL app.tenant_id) would otherwise
|
||||
// be set on one connection and read on another.
|
||||
async transaction<T>(fn: (client: DbClient) => Promise<T>): Promise<T> {
|
||||
const client = await pool.connect();
|
||||
const tx: DbClient = {
|
||||
async query<R = Record<string, unknown>>(text: string, params?: unknown[]) {
|
||||
const res = await client.query(text, params);
|
||||
return { rows: res.rows as R[], rowCount: res.rowCount ?? 0 };
|
||||
},
|
||||
async exec(text: string) {
|
||||
await client.query(text);
|
||||
},
|
||||
};
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
const out = await fn(tx);
|
||||
await client.query("COMMIT");
|
||||
return out;
|
||||
} catch (err) {
|
||||
try {
|
||||
await client.query("ROLLBACK");
|
||||
} catch {
|
||||
/* ignore rollback failure; the original error is more important */
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// PGlite (dev/test). Real Postgres in WASM, supports RLS + triggers.
|
||||
const { PGlite } = await import("@electric-sql/pglite");
|
||||
const pg = await PGlite.create();
|
||||
return {
|
||||
const single: DbClient = {
|
||||
async query<T = Record<string, unknown>>(text: string, params?: unknown[]) {
|
||||
const res = await pg.query(text, params);
|
||||
return { rows: res.rows as T[], rowCount: (res.affectedRows ?? res.rows.length) };
|
||||
@@ -64,4 +95,24 @@ export async function createDb(opts: CreateDbOptions = {}): Promise<DbClient> {
|
||||
await pg.exec(text);
|
||||
},
|
||||
};
|
||||
return {
|
||||
...single,
|
||||
// PGlite is a single in-process connection: the transaction and the
|
||||
// tenant scope always land on it.
|
||||
async transaction<T>(fn: (client: DbClient) => Promise<T>): Promise<T> {
|
||||
await pg.query("BEGIN");
|
||||
try {
|
||||
const out = await fn(single);
|
||||
await pg.query("COMMIT");
|
||||
return out;
|
||||
} catch (err) {
|
||||
try {
|
||||
await pg.query("ROLLBACK");
|
||||
} catch {
|
||||
/* ignore rollback failure; the original error is more important */
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user