1import * as Effect from "effect/Effect";
2import {
3 inlineSqlParams,
4 MigrationError,
5 type SqlExecutor,
6} from "../../SQL/Migrations/index.ts";
7
8/**
9 * The minimal query surface the migration flow needs: run (possibly
10 * multi-statement) SQL and return the D1 HTTP API's result envelope. Two
11 * implementations exist:
12 *
13 * - the cloud executor (distilled's `d1.queryDatabase`);
14 * - the local executor (`LocalD1Gateway.ts`), which tunnels the same
15 * protocol into the local workerd D1 simulator.
16 */
17export interface D1QueryResult {
18 result: Array<{
19 results?: unknown;
20 success?: boolean | null;
21 meta?: unknown;
22 }>;
23}
24
25export type D1SqlExecutor<E = unknown, R = never> = (
26 sql: string,
27) => Effect.Effect<D1QueryResult, E, R>;
28
29/**
30 * Adapt a raw D1 executor into the registry's {@link SqlExecutor} contract
31 * (see `src/SQL/Migrations/`). Parameters are inlined as SQL literals — the
32 * D1 HTTP protocol path here is raw-SQL-only, and the only parametrized
33 * queries are the migration formats' own bookkeeping. Batches join into a
34 * single multi-statement query: D1 over HTTP has no transactions, so one
35 * batched call is the closest available unit (matching wrangler's own
36 * behavior).
37 */
38export const makeD1MigrationExecutor = <E>(
39 raw: D1SqlExecutor<E>,
40): SqlExecutor => ({
41 dialect: "sqlite",
42 query: (sql, params) =>
43 raw(
44 params && params.length > 0
45 ? inlineSqlParams(sql, params, "sqlite")
46 : sql,
47 ).pipe(
48 Effect.map(
49 (result) =>
50 (result.result[0]?.results ?? []) as Array<Record<string, unknown>>,
51 ),
52 Effect.mapError(
53 (cause) =>
54 new MigrationError({
55 message: `D1 query failed: ${String(cause)}`,
56 cause,
57 }),
58 ),
59 ),
60 batch: (statements) =>
61 raw(
62 statements
63 .map((statement) => statement.trim())
64 .filter((statement) => statement.length > 0)
65 .map((statement) =>
66 statement.endsWith(";") ? statement : `${statement};`,
67 )
68 .join("\n"),
69 ).pipe(
70 Effect.asVoid,
71 Effect.mapError(
72 (cause) =>
73 new MigrationError({
74 message: `D1 migration batch failed: ${String(cause)}`,
75 cause,
76 }),
77 ),
78 ),
79});