alchemy 0.15.10 → 0.15.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Calculates MD5 hash for a string
4
+ */
5
+ function getMd5Hash(data) {
6
+ return createHash("md5").update(data).digest("hex");
7
+ }
8
+ /**
9
+ * Initiates an import of SQL into a Cloudflare D1 database.
10
+ * Implements a flow to get an upload URL, upload the SQL file, and poll for status updates.
11
+ *
12
+ * Based on Cloudflare API:
13
+ * https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/import/
14
+ *
15
+ * @param api The CloudflareApi instance to use for requests
16
+ * @param options Options including the database ID and SQL data to import
17
+ * @returns An object containing the import results and status upon completion
18
+ * @throws Will throw an error if the API call fails or the import process reports an error
19
+ */
20
+ export async function importD1Database(api, options) {
21
+ // Convert data to string for hashing if it's not already
22
+ let sqlDataString;
23
+ if (typeof options.sqlData === "string") {
24
+ sqlDataString = options.sqlData;
25
+ }
26
+ else if (options.sqlData instanceof Blob) {
27
+ sqlDataString = await options.sqlData.text();
28
+ }
29
+ else {
30
+ // BufferSource (ArrayBuffer, etc.)
31
+ sqlDataString = new TextDecoder().decode(options.sqlData);
32
+ }
33
+ // Calculate MD5 hash of SQL data
34
+ const etag = getMd5Hash(sqlDataString);
35
+ // Step 1: Initialize the import process to get an upload URL
36
+ const initResponse = await api.post(`/accounts/${api.accountId}/d1/database/${options.databaseId}/import`, {
37
+ action: "init",
38
+ etag,
39
+ });
40
+ if (!initResponse.ok) {
41
+ const errorData = (await initResponse.json());
42
+ throw new Error(`Failed to initialize D1 database import: ${errorData.error || errorData.errors?.[0]?.message || "Unknown error"}`);
43
+ }
44
+ const initData = (await initResponse.json());
45
+ const uploadResult = initData.result;
46
+ if (!uploadResult.success || !uploadResult.upload_url) {
47
+ throw new Error(uploadResult.error || "Failed to get upload URL for D1 import");
48
+ }
49
+ // Step 2: Upload the SQL file
50
+ await uploadSqlFile(uploadResult.upload_url, options.sqlData, options.filename || uploadResult.filename);
51
+ // Step 3: Ingest the uploaded file
52
+ const ingestResponse = await api.post(`/accounts/${api.accountId}/d1/database/${options.databaseId}/import`, {
53
+ action: "ingest",
54
+ etag,
55
+ filename: uploadResult.filename,
56
+ });
57
+ if (!ingestResponse.ok) {
58
+ const errorData = (await ingestResponse.json());
59
+ throw new Error(`Failed to ingest D1 database upload: ${errorData.error || errorData.errors?.[0]?.message || "Unknown error"}`);
60
+ }
61
+ const ingestData = (await ingestResponse.json());
62
+ const ingestResult = ingestData.result;
63
+ if (!ingestResult.success) {
64
+ throw new Error(ingestResult.error || "Failed to start ingestion for D1 import");
65
+ }
66
+ if (!ingestResult.at_bookmark) {
67
+ throw new Error("Ingest response did not contain a bookmark for polling");
68
+ }
69
+ // Step 4: Poll for status
70
+ return pollImportStatus(api, options.databaseId, ingestResult.at_bookmark);
71
+ }
72
+ /**
73
+ * Uploads an SQL file to the provided URL
74
+ */
75
+ async function uploadSqlFile(uploadUrl, sqlData, filename) {
76
+ // Convert data to the right format if needed
77
+ let blob;
78
+ if (typeof sqlData === "string") {
79
+ blob = new Blob([sqlData], { type: "application/sql" });
80
+ }
81
+ else if (sqlData instanceof Blob) {
82
+ blob = sqlData;
83
+ }
84
+ else {
85
+ // BufferSource (ArrayBuffer, etc.)
86
+ blob = new Blob([sqlData], { type: "application/sql" });
87
+ }
88
+ // Create a FormData object with the file
89
+ const formData = new FormData();
90
+ formData.append("file", blob, filename || "import.sql");
91
+ const response = await fetch(uploadUrl, {
92
+ method: "PUT",
93
+ body: blob,
94
+ headers: {
95
+ "Content-Type": "application/sql",
96
+ },
97
+ });
98
+ if (!response.ok) {
99
+ throw new Error(`Failed to upload SQL file: ${response.statusText}`);
100
+ }
101
+ }
102
+ /**
103
+ * Polls for the import status with the provided bookmark
104
+ */
105
+ async function pollImportStatus(api, databaseId, bookmark) {
106
+ if (!bookmark) {
107
+ throw new Error("Cannot poll an import without a bookmark");
108
+ }
109
+ const response = await api.post(`/accounts/${api.accountId}/d1/database/${databaseId}/import`, {
110
+ action: "poll",
111
+ current_bookmark: bookmark,
112
+ });
113
+ if (!response.ok) {
114
+ const errorData = (await response.json());
115
+ throw new Error(`Failed to poll D1 import status: ${errorData.error || errorData.errors?.[0]?.message || "Unknown error"}`);
116
+ }
117
+ const data = (await response.json()).result;
118
+ if (!data.success) {
119
+ throw new Error(data.error || "Unknown error during D1 import");
120
+ }
121
+ // Log messages for visibility
122
+ if (data.messages && data.messages.length > 0) {
123
+ for (const message of data.messages) {
124
+ console.log(`D1 Import: ${message}`);
125
+ }
126
+ }
127
+ if (data.status === "complete" && data.result) {
128
+ return {
129
+ filename: data.filename || "import.sql",
130
+ result: data.result,
131
+ status: data.status,
132
+ success: data.success,
133
+ };
134
+ }
135
+ else if (data.status === "error") {
136
+ throw new Error(data.error || "Error during D1 import");
137
+ }
138
+ else {
139
+ // Continue polling with bookmark
140
+ return pollImportStatus(api, databaseId, data.at_bookmark);
141
+ }
142
+ }
143
+ //# sourceMappingURL=d1-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-import.js","sourceRoot":"","sources":["../../src/cloudflare/d1-import.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,10 @@ export * from "./bucket.js";
11
11
  export * from "./bundle/external.js";
12
12
  export * from "./bundle/local-dev-cloudflare-shim.js";
13
13
  export * from "./custom-domain.js";
14
+ export * from "./d1-clone.js";
14
15
  export * from "./d1-database.js";
16
+ export * from "./d1-export.js";
17
+ export * from "./d1-import.js";
15
18
  export * from "./dns-records.js";
16
19
  export * from "./durable-object-namespace.js";
17
20
  export * from "./hyperdrive.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uCAAuC,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uCAAuC,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
@@ -11,7 +11,10 @@ export * from "./bucket.js";
11
11
  export * from "./bundle/external.js";
12
12
  export * from "./bundle/local-dev-cloudflare-shim.js";
13
13
  export * from "./custom-domain.js";
14
+ export * from "./d1-clone.js";
14
15
  export * from "./d1-database.js";
16
+ export * from "./d1-export.js";
17
+ export * from "./d1-import.js";
15
18
  export * from "./dns-records.js";
16
19
  export * from "./durable-object-namespace.js";
17
20
  export * from "./hyperdrive.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uCAAuC,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uCAAuC,CAAC;AACtD,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Pauses execution for a specified number of milliseconds.
3
+ * @param ms The number of milliseconds to sleep.
4
+ * @returns A promise that resolves after the specified time.
5
+ */
6
+ export declare function sleep(ms: number): Promise<void>;
7
+ //# sourceMappingURL=sleep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/util/sleep.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Pauses execution for a specified number of milliseconds.
3
+ * @param ms The number of milliseconds to sleep.
4
+ * @returns A promise that resolves after the specified time.
5
+ */
6
+ export function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/util/sleep.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.15.10",
3
+ "version": "0.15.11",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -0,0 +1,113 @@
1
+ import type { CloudflareApi } from "./api.js";
2
+ import {
3
+ exportD1Database,
4
+ type ExportD1DatabaseOptions,
5
+ type ExportD1DatabaseResult,
6
+ } from "./d1-export.js";
7
+ import {
8
+ importD1Database,
9
+ type ImportD1DatabaseOptions,
10
+ type ImportD1DatabaseResult,
11
+ } from "./d1-import.js";
12
+
13
+ /**
14
+ * Options for cloning a D1 database
15
+ */
16
+ export interface CloneD1DatabaseOptions {
17
+ /**
18
+ * The ID of the source D1 database to clone from
19
+ */
20
+ sourceDatabaseId: string;
21
+
22
+ /**
23
+ * The ID of the target D1 database to clone to
24
+ */
25
+ targetDatabaseId: string;
26
+
27
+ /**
28
+ * Optional export options for the source database
29
+ */
30
+ exportOptions?: Omit<ExportD1DatabaseOptions, "databaseId">;
31
+
32
+ /**
33
+ * Optional import options for the target database
34
+ */
35
+ importOptions?: Omit<ImportD1DatabaseOptions, "databaseId" | "sqlData">;
36
+ }
37
+
38
+ /**
39
+ * Response from the clone D1 database operation
40
+ */
41
+ export interface CloneD1DatabaseResult {
42
+ /**
43
+ * Result of the export operation
44
+ */
45
+ exportResult: ExportD1DatabaseResult;
46
+
47
+ /**
48
+ * Result of the import operation
49
+ */
50
+ importResult: ImportD1DatabaseResult;
51
+
52
+ /**
53
+ * Whether the clone operation was successful
54
+ */
55
+ success: boolean;
56
+ }
57
+
58
+ /**
59
+ * Clones a D1 database by exporting from a source database and importing to a target database.
60
+ * Handles the full workflow:
61
+ * 1. Export source database to get a signed URL
62
+ * 2. Fetch SQL content from the signed URL
63
+ * 3. Import SQL content into the target database
64
+ *
65
+ * @param api The CloudflareApi instance to use for requests
66
+ * @param options Options including source and target database IDs
67
+ * @returns An object containing results from both export and import operations
68
+ * @throws Will throw an error if any part of the clone process fails
69
+ */
70
+ export async function cloneD1Database(
71
+ api: CloudflareApi,
72
+ options: CloneD1DatabaseOptions,
73
+ ): Promise<CloneD1DatabaseResult> {
74
+ const exportResult = await exportD1Database(api, {
75
+ databaseId: options.sourceDatabaseId,
76
+ ...options.exportOptions,
77
+ });
78
+
79
+ if (!exportResult.success) {
80
+ throw new Error(
81
+ `Failed to export source database: ${options.sourceDatabaseId}`,
82
+ );
83
+ }
84
+
85
+ // Step 2: Fetch SQL content from the signed URL
86
+ const sqlResponse = await fetch(exportResult.signed_url);
87
+
88
+ if (!sqlResponse.ok) {
89
+ throw new Error(`Failed to fetch SQL content: ${sqlResponse.statusText}`);
90
+ }
91
+
92
+ const sqlData = await sqlResponse.text();
93
+
94
+ // Step 3: Import SQL content into target database
95
+ const importResult = await importD1Database(api, {
96
+ databaseId: options.targetDatabaseId,
97
+ sqlData,
98
+ filename: exportResult.filename,
99
+ ...options.importOptions,
100
+ });
101
+
102
+ if (!importResult.success) {
103
+ throw new Error(
104
+ `Failed to import to target database: ${options.targetDatabaseId}`,
105
+ );
106
+ }
107
+
108
+ return {
109
+ exportResult,
110
+ importResult,
111
+ success: true,
112
+ };
113
+ }
@@ -2,10 +2,11 @@ import type { Context } from "../context.js";
2
2
  import { Resource } from "../resource.js";
3
3
  import { CloudflareApiError, handleApiError } from "./api-error.js";
4
4
  import {
5
- type CloudflareApi,
6
5
  createCloudflareApi,
6
+ type CloudflareApi,
7
7
  type CloudflareApiOptions,
8
8
  } from "./api.js";
9
+ import { cloneD1Database } from "./d1-clone.js";
9
10
  import { applyMigrations, listMigrationsFiles } from "./d1-migrations.js";
10
11
 
11
12
  /**
@@ -61,6 +62,17 @@ export interface D1DatabaseProps extends CloudflareApiOptions {
61
62
  */
62
63
  adopt?: boolean;
63
64
 
65
+ /**
66
+ * Clone data from an existing database to this new database.
67
+ * Only applicable during creation phase.
68
+ *
69
+ * Can be specified as:
70
+ * - A D1Database object
71
+ * - An object with an id property
72
+ * - An object with a name property (will look up the ID by name)
73
+ */
74
+ clone?: D1Database | { id: string } | { name: string };
75
+
64
76
  /**
65
77
  * These files will be generated internally with the D1Database wrapper function when migrationsDir is specified
66
78
  *
@@ -123,20 +135,6 @@ export type D1Database = Resource<"cloudflare::D1Database"> &
123
135
  };
124
136
  };
125
137
 
126
- export async function D1Database(
127
- id: string,
128
- props: Omit<D1DatabaseProps, "migrationsFiles">,
129
- ) {
130
- const migrationsFiles = props.migrationsDir
131
- ? await listMigrationsFiles(props.migrationsDir)
132
- : [];
133
-
134
- return D1DatabaseResource(id, {
135
- ...props,
136
- migrationsFiles,
137
- });
138
- }
139
-
140
138
  /**
141
139
  * Creates and manages Cloudflare D1 Databases.
142
140
  *
@@ -173,8 +171,43 @@ export async function D1Database(
173
171
  * migrationsDir: "./migrations",
174
172
  * });
175
173
  *
174
+ * @example
175
+ * // Clone an existing database by ID
176
+ * const clonedDb = await D1Database("cloned-db", {
177
+ * name: "cloned-db",
178
+ * clone: otherDb
179
+ * });
180
+ *
181
+ * @example
182
+ * // Clone an existing database by ID
183
+ * const clonedDb = await D1Database("cloned-db", {
184
+ * name: "cloned-db",
185
+ * clone: { id: "existing-db-uuid" }
186
+ * });
187
+ *
188
+ * @example
189
+ * // Clone an existing database by name
190
+ * const clonedDb = await D1Database("cloned-db", {
191
+ * name: "cloned-db",
192
+ * clone: { name: "existing-db-name" }
193
+ * });
194
+ *
176
195
  * @see https://developers.cloudflare.com/d1/
177
196
  */
197
+ export async function D1Database(
198
+ id: string,
199
+ props: Omit<D1DatabaseProps, "migrationsFiles">,
200
+ ) {
201
+ const migrationsFiles = props.migrationsDir
202
+ ? await listMigrationsFiles(props.migrationsDir)
203
+ : [];
204
+
205
+ return D1DatabaseResource(id, {
206
+ ...props,
207
+ migrationsFiles,
208
+ });
209
+ }
210
+
178
211
  export const D1DatabaseResource = Resource(
179
212
  "cloudflare::D1Database",
180
213
  async function (
@@ -202,6 +235,11 @@ export const D1DatabaseResource = Resource(
202
235
  console.log("Creating D1 database:", databaseName);
203
236
  try {
204
237
  dbData = await createDatabase(api, databaseName, props);
238
+
239
+ // If clone property is provided, perform cloning after database creation
240
+ if (props.clone && dbData.result.uuid) {
241
+ await cloneDb(api, props.clone, dbData.result.uuid);
242
+ }
205
243
  } catch (error) {
206
244
  // Check if this is a "database already exists" error and adopt is enabled
207
245
  if (
@@ -480,3 +518,49 @@ export async function updateDatabase(
480
518
 
481
519
  return (await updateResponse.json()) as CloudflareD1Response;
482
520
  }
521
+
522
+ /**
523
+ * Helper function to clone data from a source database to a target database
524
+ * Resolves the source database ID from different input formats and performs the cloning operation
525
+ *
526
+ * @param api CloudflareApi instance
527
+ * @param sourceDb Source database specification (can be an ID, a name, or a D1Database object)
528
+ * @param targetDbId Target database ID
529
+ */
530
+ async function cloneDb(
531
+ api: CloudflareApi,
532
+ sourceDb: D1Database | { id: string } | { name: string },
533
+ targetDbId: string,
534
+ ): Promise<void> {
535
+ let sourceId: string;
536
+
537
+ // Determine source database ID
538
+ if ("id" in sourceDb && sourceDb.id) {
539
+ // Use provided ID directly
540
+ sourceId = sourceDb.id;
541
+ } else if ("name" in sourceDb && sourceDb.name) {
542
+ // Look up ID by name
543
+ const databases = await listDatabases(api, sourceDb.name);
544
+ const foundDb = databases.find((db) => db.name === sourceDb.name);
545
+
546
+ if (!foundDb) {
547
+ throw new Error(
548
+ `Source database with name '${sourceDb.name}' not found for cloning`,
549
+ );
550
+ }
551
+
552
+ sourceId = foundDb.id;
553
+ } else if ("type" in sourceDb && sourceDb.type === "d1" && "id" in sourceDb) {
554
+ // It's a D1Database object
555
+ sourceId = sourceDb.id;
556
+ } else {
557
+ throw new Error("Invalid clone property: must provide either id or name");
558
+ }
559
+
560
+ // Perform the cloning
561
+ console.log(`Cloning data from database ${sourceId} to ${targetDbId}`);
562
+ await cloneD1Database(api, {
563
+ sourceDatabaseId: sourceId,
564
+ targetDatabaseId: targetDbId,
565
+ });
566
+ }
@@ -0,0 +1,173 @@
1
+ import type { CloudflareApi } from "./api.js"; // Ensure CloudflareApi is exported if not already
2
+
3
+ /**
4
+ * Options for exporting a D1 database
5
+ */
6
+ export interface ExportD1DatabaseOptions {
7
+ /**
8
+ * The ID of the D1 database to export
9
+ */
10
+ databaseId: string;
11
+
12
+ /**
13
+ * Optional dump options to control what is exported
14
+ */
15
+ dumpOptions?: {
16
+ /**
17
+ * Optional list of tables to export
18
+ */
19
+ tables?: string[];
20
+
21
+ /**
22
+ * Whether to exclude schema in the export
23
+ */
24
+ no_schema?: boolean;
25
+
26
+ /**
27
+ * Whether to exclude data in the export
28
+ */
29
+ no_data?: boolean;
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Response from the export D1 database operation
35
+ */
36
+ export interface ExportD1DatabaseResult {
37
+ /**
38
+ * The filename of the exported database
39
+ */
40
+ filename: string;
41
+
42
+ /**
43
+ * The signed URL for downloading the exported database
44
+ */
45
+ signed_url: string;
46
+
47
+ /**
48
+ * The status of the export operation
49
+ */
50
+ status: "complete";
51
+
52
+ /**
53
+ * Whether the export operation was successful
54
+ */
55
+ success: boolean;
56
+ }
57
+
58
+ /**
59
+ * Error response from Cloudflare API
60
+ */
61
+ interface CloudflareErrorResponse {
62
+ error?: string;
63
+ errors?: Array<{
64
+ message: string;
65
+ }>;
66
+ }
67
+
68
+ /**
69
+ * Response during polling for D1 database export
70
+ */
71
+ interface ExportPollingResponse {
72
+ /**
73
+ * Bookmark for continuing the polling process
74
+ */
75
+ at_bookmark?: string;
76
+
77
+ /**
78
+ * Error message if any
79
+ */
80
+ error?: string;
81
+
82
+ /**
83
+ * Progress messages from the export process
84
+ */
85
+ messages: string[];
86
+
87
+ /**
88
+ * Result data if export is complete
89
+ */
90
+ result?: {
91
+ filename: string;
92
+ signed_url: string;
93
+ };
94
+
95
+ /**
96
+ * Status of the export process
97
+ */
98
+ status: "complete" | "error" | "processing";
99
+
100
+ /**
101
+ * Whether the current polling request was successful
102
+ */
103
+ success: boolean;
104
+
105
+ /**
106
+ * Type of operation
107
+ */
108
+ type: "export";
109
+ }
110
+
111
+ /**
112
+ * Initiates an export of a Cloudflare D1 database and returns a download URL.
113
+ * Implements recursive polling with bookmark handling, similar to wrangler.
114
+ * Waits indefinitely until the export process completes or fails.
115
+ *
116
+ * Based on Cloudflare API:
117
+ * https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/export/
118
+ * and wrangler implementation.
119
+ *
120
+ * @param options Options including the database ID and optional dump parameters.
121
+ * @returns An object containing the download URL, filename, and status upon completion.
122
+ * @throws Will throw an error if the API call fails or the export process reports an error.
123
+ */
124
+ export async function exportD1Database(
125
+ api: CloudflareApi,
126
+ options: ExportD1DatabaseOptions,
127
+ currentBookmark?: string,
128
+ ): Promise<ExportD1DatabaseResult> {
129
+ const response = await api.post(
130
+ `/accounts/${api.accountId}/d1/database/${options.databaseId}/export`,
131
+ {
132
+ output_format: "polling",
133
+ dump_options: options.dumpOptions || {},
134
+ current_bookmark: currentBookmark,
135
+ },
136
+ );
137
+
138
+ if (!response.ok) {
139
+ const errorData = (await response.json()) as CloudflareErrorResponse;
140
+ throw new Error(
141
+ `Failed to export D1 database: ${errorData.error || errorData.errors?.[0]?.message || "Unknown error"}`,
142
+ );
143
+ }
144
+
145
+ const { result: data } = (await response.json()) as {
146
+ result: ExportPollingResponse;
147
+ };
148
+
149
+ if (!data.success) {
150
+ throw new Error(data.error || "Unknown error during D1 export");
151
+ }
152
+
153
+ // Log messages for visibility
154
+ if (data.messages && data.messages.length > 0) {
155
+ for (const message of data.messages) {
156
+ console.log(`D1 Export: ${message}`);
157
+ }
158
+ }
159
+
160
+ if (data.status === "complete" && data.result) {
161
+ return {
162
+ filename: data.result.filename,
163
+ signed_url: data.result.signed_url,
164
+ status: data.status,
165
+ success: data.success,
166
+ };
167
+ } else if (data.status === "error") {
168
+ throw new Error(data.error || "Error during D1 export");
169
+ } else {
170
+ // Continue polling with bookmark
171
+ return exportD1Database(api, options, data.at_bookmark);
172
+ }
173
+ }