1import * as Effect from "effect/Effect";
2import type { Client } from "pg";
3import { MigrationError, type SqlExecutor } from "./Format.ts";
4
5/**
6 * Adapt an open `pg` client into the registry's {@link SqlExecutor}.
7 * Batches run in a transaction so a migration and its bookkeeping INSERT
8 * commit (or roll back) together.
9 *
10 * The `pg` import is type-only — how the client is opened (Neon connection
11 * URI, PlanetScale temporary role, …) stays with the database provider.
12 */
13export const makePgMigrationExecutor = (client: Client): SqlExecutor => ({
14 dialect: "postgres",
15 query: (sql, params) =>
16 Effect.tryPromise({
17 try: () =>
18 client
19 .query(sql, (params ?? []) as unknown[])
20 .then((result) => result.rows as Array<Record<string, unknown>>),
21 catch: (cause) =>
22 new MigrationError({
23 message: `postgres query failed: ${String(cause)}`,
24 cause,
25 }),
26 }),
27 batch: (statements) =>
28 Effect.tryPromise({
29 try: async () => {
30 await client.query("BEGIN");
31 try {
32 for (const statement of statements) {
33 await client.query(statement);
34 }
35 await client.query("COMMIT");
36 } catch (error) {
37 await client.query("ROLLBACK").catch(() => {});
38 throw error;
39 }
40 },
41 catch: (cause) =>
42 new MigrationError({
43 message: `postgres migration batch failed: ${String(cause)}`,
44 cause,
45 }),
46 }),
47});