1import type { Credentials } from "@distilled.cloud/cloudflare/Credentials";
2import * as d1 from "@distilled.cloud/cloudflare/d1";
3import * as Effect from "effect/Effect";
4import type * as HttpClient from "effect/http/HttpClient";
5
6export interface ExportD1DatabaseOptions {
7 accountId: string;
8 databaseId: string;
9 dumpOptions?: {
10 tables?: string[];
11 noSchema?: boolean;
12 noData?: boolean;
13 };
14}
15
16export interface ExportD1DatabaseResult {
17 filename: string;
18 signedUrl: string;
19}
20
21/**
22 * Initiates an export of a Cloudflare D1 database and returns a signed
23 * download URL. Recursively polls with the bookmark until the export
24 * completes or fails.
25 */
26export const exportDatabase = (
27 options: ExportD1DatabaseOptions,
28): Effect.Effect<
29 ExportD1DatabaseResult,
30 d1.ExportDatabaseError,
31 Credentials | HttpClient.HttpClient
32> =>
33 Effect.gen(function* () {
34 const exportDb = yield* d1.exportDatabase;
35
36 const poll = (
37 currentBookmark?: string,
38 ): Effect.Effect<ExportD1DatabaseResult, d1.ExportDatabaseError, never> =>
39 Effect.gen(function* () {
40 const data = yield* exportDb({
41 accountId: options.accountId,
42 databaseId: options.databaseId,
43 outputFormat: "polling",
44 currentBookmark,
45 dumpOptions: options.dumpOptions,
46 });
47
48 if (data.status === "complete" && data.result) {
49 if (!data.result.filename || !data.result.signedUrl) {
50 return yield* Effect.die(
51 "D1 export completed but missing filename/signedUrl",
52 );
53 }
54 return {
55 filename: data.result.filename,
56 signedUrl: data.result.signedUrl,
57 };
58 }
59 if (data.status === "error") {
60 return yield* Effect.die(data.error ?? "Error during D1 export");
61 }
62 return yield* poll(data.atBookmark ?? undefined);
63 });
64
65 return yield* poll();
66 });