alchemy 0.7.0 → 0.7.2

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.
@@ -6,8 +6,11 @@
6
6
  import type { Secret } from "../secret";
7
7
  import type { Assets } from "./assets";
8
8
  import type { R2Bucket } from "./bucket";
9
+ import type { D1Database } from "./d1-database";
9
10
  import type { DurableObjectNamespace } from "./durable-object-namespace";
10
11
  import type { KVNamespace } from "./kv-namespace";
12
+ import type { Pipeline } from "./pipeline";
13
+ import type { Queue } from "./queue";
11
14
  import type { Worker } from "./worker";
12
15
  import type { Workflow } from "./workflow";
13
16
  export type Bindings = {
@@ -16,11 +19,11 @@ export type Bindings = {
16
19
  /**
17
20
  * L2 Binding Resources.
18
21
  */
19
- export type Binding = DurableObjectNamespace | KVNamespace | Worker | Workflow | R2Bucket | Secret | string | Assets;
22
+ export type Binding = Assets | D1Database | DurableObjectNamespace | KVNamespace | Pipeline | Queue | R2Bucket | Secret | string | Worker | Workflow;
20
23
  /**
21
24
  * Union type for all Worker binding types (API spec)
22
25
  */
23
- export type WorkerBindingSpec = WorkerBindingAI | WorkerBindingAnalyticsEngine | WorkerBindingAssets | WorkerBindingBrowserRendering | WorkerBindingD1 | WorkerBindingDispatchNamespace | WorkerBindingDurableObjectNamespace | WorkerBindingHyperdrive | WorkerBindingJson | WorkerBindingKVNamespace | WorkerBindingMTLSCertificate | WorkerBindingPlainText | WorkerBindingQueue | WorkerBindingR2Bucket | WorkerBindingSecretText | WorkerBindingService | WorkerBindingStaticContent | WorkerBindingTailConsumer | WorkerBindingVectorize | WorkerBindingVersionMetadata | WorkerBindingWasmModule | WorkerBindingWorkflow;
26
+ export type WorkerBindingSpec = WorkerBindingAI | WorkerBindingAnalyticsEngine | WorkerBindingAssets | WorkerBindingBrowserRendering | WorkerBindingD1 | WorkerBindingDispatchNamespace | WorkerBindingDurableObjectNamespace | WorkerBindingHyperdrive | WorkerBindingJson | WorkerBindingKVNamespace | WorkerBindingMTLSCertificate | WorkerBindingPipeline | WorkerBindingPlainText | WorkerBindingQueue | WorkerBindingR2Bucket | WorkerBindingSecretText | WorkerBindingService | WorkerBindingStaticContent | WorkerBindingTailConsumer | WorkerBindingVectorize | WorkerBindingVersionMetadata | WorkerBindingWasmModule | WorkerBindingWorkflow;
24
27
  /**
25
28
  * AI binding type
26
29
  */
@@ -276,3 +279,14 @@ export interface WorkerBindingWorkflow {
276
279
  */
277
280
  script_name?: string;
278
281
  }
282
+ /**
283
+ * Pipeline binding type
284
+ */
285
+ export interface WorkerBindingPipeline {
286
+ /** The name of the binding */
287
+ name: string;
288
+ /** Type identifier for Pipeline binding */
289
+ type: "pipelines";
290
+ /** Pipeline name */
291
+ pipeline: string;
292
+ }
@@ -1,9 +1,13 @@
1
+ import type { Pipeline } from "cloudflare:pipelines";
1
2
  import type { Secret } from "../secret";
2
3
  import type { Assets } from "./assets";
3
4
  import type { Binding } from "./bindings";
4
5
  import type { R2Bucket as _R2Bucket } from "./bucket";
6
+ import type { D1Database as _D1Database } from "./d1-database";
5
7
  import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace";
6
8
  import type { KVNamespace as _KVNamespace } from "./kv-namespace";
9
+ import type { Pipeline as _Pipeline } from "./pipeline";
10
+ import type { Queue as _Queue } from "./queue";
7
11
  import type { Worker as _Worker } from "./worker";
8
12
  import type { Workflow as _Workflow } from "./workflow";
9
- export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : T extends _Workflow<infer P> ? Workflow<P> : Service;
13
+ export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : T extends _Workflow<infer P> ? Workflow<P> : T extends _D1Database ? D1Database : T extends _Queue ? Queue : T extends _Pipeline<infer R> ? Pipeline<R> : Service;
@@ -205,6 +205,7 @@ export async function emptyBucket(r2, bucketName, jurisdiction) {
205
205
  let totalDeleted = 0;
206
206
  try {
207
207
  do {
208
+ console.log(`Listing objects in bucket ${bucketName}`);
208
209
  // List objects in the bucket
209
210
  const { objects, continuationToken: nextToken } = await listObjects(r2, bucketName, continuationToken, jurisdiction);
210
211
  continuationToken = nextToken;
@@ -0,0 +1,151 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { CloudflareApi, type CloudflareApiOptions } from "./api";
4
+ /**
5
+ * Properties for creating or updating a D1 Database
6
+ */
7
+ export interface D1DatabaseProps extends CloudflareApiOptions {
8
+ /**
9
+ * Name of the database
10
+ */
11
+ name: string;
12
+ /**
13
+ * Optional primary location hint for the database
14
+ * Indicates the primary geographical location data will be stored
15
+ */
16
+ primaryLocationHint?: "wnam" | "enam" | "weur" | "eeur" | "apac" | "auto" | string;
17
+ /**
18
+ * Read replication configuration
19
+ * Only mutable property during updates
20
+ */
21
+ readReplication?: {
22
+ /**
23
+ * Read replication mode
24
+ * - auto: Automatic read replication
25
+ * - disabled: No read replication
26
+ */
27
+ mode: "auto" | "disabled";
28
+ };
29
+ /**
30
+ * Whether to delete the database.
31
+ * If set to false, the database will remain but the resource will be removed from state
32
+ *
33
+ * @default true
34
+ */
35
+ delete?: boolean;
36
+ /**
37
+ * Whether to adopt an existing database with the same name if it exists
38
+ * If true and a database with the same name exists, it will be adopted rather than creating a new one
39
+ *
40
+ * @default false
41
+ */
42
+ adopt?: boolean;
43
+ }
44
+ /**
45
+ * Output returned after D1 Database creation/update
46
+ */
47
+ export interface D1Database extends Resource<"cloudflare::D1Database">, D1DatabaseProps {
48
+ type: "d1";
49
+ /**
50
+ * The unique ID of the database (UUID)
51
+ */
52
+ id: string;
53
+ /**
54
+ * File size of the database
55
+ */
56
+ fileSize: number;
57
+ /**
58
+ * Number of tables in the database
59
+ */
60
+ numTables: number;
61
+ /**
62
+ * Version of the database
63
+ */
64
+ version: string;
65
+ /**
66
+ * Read replication configuration
67
+ */
68
+ readReplication?: {
69
+ /**
70
+ * Read replication mode
71
+ */
72
+ mode: "auto" | "disabled";
73
+ };
74
+ }
75
+ /**
76
+ * Creates and manages Cloudflare D1 Databases.
77
+ *
78
+ * D1 Databases provide serverless SQL databases built on SQLite with
79
+ * automatic data replication for high availability.
80
+ *
81
+ * @example
82
+ * // Create a basic D1 database with default settings
83
+ * const basicDatabase = await D1Database("my-app-db", {
84
+ * name: "my-app-db"
85
+ * });
86
+ *
87
+ * @example
88
+ * // Create a database with location hint for optimal performance
89
+ * const westUsDatabase = await D1Database("west-us-db", {
90
+ * name: "west-us-db",
91
+ * primaryLocationHint: "wnam"
92
+ * });
93
+ *
94
+ * @example
95
+ * // Adopt an existing database if it already exists instead of failing
96
+ * const existingDb = await D1Database("existing-db", {
97
+ * name: "existing-db",
98
+ * adopt: true,
99
+ * readReplication: {
100
+ * mode: "auto"
101
+ * }
102
+ * });
103
+ *
104
+ * @see https://developers.cloudflare.com/d1/
105
+ */
106
+ export declare const D1Database: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<D1Database>, id: string, props: D1DatabaseProps) => Promise<D1Database>);
107
+ interface CloudflareD1Response {
108
+ result: {
109
+ uuid?: string;
110
+ name: string;
111
+ file_size: number;
112
+ num_tables: number;
113
+ version: string;
114
+ primary_location_hint?: string;
115
+ read_replication?: {
116
+ mode: "auto" | "disabled";
117
+ };
118
+ };
119
+ success: boolean;
120
+ errors: Array<{
121
+ code: number;
122
+ message: string;
123
+ }>;
124
+ messages: string[];
125
+ }
126
+ /**
127
+ * Create a new D1 database
128
+ */
129
+ export declare function createDatabase(api: CloudflareApi, databaseName: string, props: D1DatabaseProps): Promise<CloudflareD1Response>;
130
+ /**
131
+ * Get a D1 database
132
+ */
133
+ export declare function getDatabase(api: CloudflareApi, databaseId?: string): Promise<CloudflareD1Response>;
134
+ /**
135
+ * Delete a D1 database
136
+ */
137
+ export declare function deleteDatabase(api: CloudflareApi, databaseId?: string): Promise<void>;
138
+ /**
139
+ * List all D1 databases in an account
140
+ */
141
+ export declare function listDatabases(api: CloudflareApi, name?: string): Promise<{
142
+ name: string;
143
+ id: string;
144
+ }[]>;
145
+ /**
146
+ * Update a D1 database
147
+ *
148
+ * Note: According to Cloudflare API, only read_replication.mode can be modified during updates.
149
+ */
150
+ export declare function updateDatabase(api: CloudflareApi, databaseId: string, props: D1DatabaseProps): Promise<CloudflareD1Response>;
151
+ export {};
@@ -0,0 +1,200 @@
1
+ import { Resource } from "../resource";
2
+ import { CloudflareApi, createCloudflareApi, } from "./api";
3
+ import { CloudflareApiError, handleApiError } from "./api-error";
4
+ /**
5
+ * Creates and manages Cloudflare D1 Databases.
6
+ *
7
+ * D1 Databases provide serverless SQL databases built on SQLite with
8
+ * automatic data replication for high availability.
9
+ *
10
+ * @example
11
+ * // Create a basic D1 database with default settings
12
+ * const basicDatabase = await D1Database("my-app-db", {
13
+ * name: "my-app-db"
14
+ * });
15
+ *
16
+ * @example
17
+ * // Create a database with location hint for optimal performance
18
+ * const westUsDatabase = await D1Database("west-us-db", {
19
+ * name: "west-us-db",
20
+ * primaryLocationHint: "wnam"
21
+ * });
22
+ *
23
+ * @example
24
+ * // Adopt an existing database if it already exists instead of failing
25
+ * const existingDb = await D1Database("existing-db", {
26
+ * name: "existing-db",
27
+ * adopt: true,
28
+ * readReplication: {
29
+ * mode: "auto"
30
+ * }
31
+ * });
32
+ *
33
+ * @see https://developers.cloudflare.com/d1/
34
+ */
35
+ export const D1Database = Resource("cloudflare::D1Database", async function (id, props) {
36
+ const api = await createCloudflareApi(props);
37
+ const databaseName = props.name || id;
38
+ if (this.phase === "delete") {
39
+ console.log("Deleting D1 database:", databaseName);
40
+ if (props.delete !== false) {
41
+ // Delete D1 database
42
+ console.log("Deleting D1 database:", databaseName);
43
+ await deleteDatabase(api, this.output?.id);
44
+ }
45
+ // Return void (a deleted database has no content)
46
+ return this.destroy();
47
+ }
48
+ else {
49
+ let dbData;
50
+ if (this.phase === "create") {
51
+ console.log("Creating D1 database:", databaseName);
52
+ try {
53
+ dbData = await createDatabase(api, databaseName, props);
54
+ }
55
+ catch (error) {
56
+ // Check if this is a "database already exists" error and adopt is enabled
57
+ if (props.adopt &&
58
+ error instanceof CloudflareApiError &&
59
+ error.message.includes("already exists")) {
60
+ console.log(`Database ${databaseName} already exists, adopting it`);
61
+ // Find the existing database by name
62
+ const databases = await listDatabases(api, databaseName);
63
+ const existingDb = databases.find((db) => db.name === databaseName);
64
+ if (!existingDb) {
65
+ throw new Error(`Failed to find existing database '${databaseName}' for adoption`);
66
+ }
67
+ // Get the database details using its ID
68
+ dbData = await getDatabase(api, existingDb.id);
69
+ // Update the database with the provided properties
70
+ if (props.readReplication) {
71
+ console.log(`Updating adopted database ${databaseName} with new properties`);
72
+ dbData = await updateDatabase(api, existingDb.id, props);
73
+ }
74
+ }
75
+ else {
76
+ // Re-throw the error if adopt is false or it's not a "database already exists" error
77
+ throw error;
78
+ }
79
+ }
80
+ }
81
+ else {
82
+ // Update operation
83
+ if (this.output?.id) {
84
+ console.log("Updating D1 database:", databaseName);
85
+ // Update the database with new properties
86
+ dbData = await updateDatabase(api, this.output.id, props);
87
+ }
88
+ else {
89
+ // If no ID exists, fall back to creating a new database
90
+ console.log("No existing database ID found, creating new D1 database:", databaseName);
91
+ dbData = await createDatabase(api, databaseName, props);
92
+ }
93
+ }
94
+ return this({
95
+ type: "d1",
96
+ id: dbData.result.uuid || "",
97
+ name: databaseName,
98
+ fileSize: dbData.result.file_size,
99
+ numTables: dbData.result.num_tables,
100
+ version: dbData.result.version,
101
+ readReplication: dbData.result.read_replication,
102
+ primaryLocationHint: props.primaryLocationHint,
103
+ accountId: api.accountId,
104
+ });
105
+ }
106
+ });
107
+ /**
108
+ * Create a new D1 database
109
+ */
110
+ export async function createDatabase(api, databaseName, props) {
111
+ // Create new D1 database
112
+ const createPayload = {
113
+ name: databaseName,
114
+ };
115
+ if (props.primaryLocationHint) {
116
+ createPayload.primary_location_hint = props.primaryLocationHint;
117
+ }
118
+ const createResponse = await api.post(`/accounts/${api.accountId}/d1/database`, createPayload);
119
+ if (!createResponse.ok) {
120
+ return await handleApiError(createResponse, "creating", "D1 database", databaseName);
121
+ }
122
+ return (await createResponse.json());
123
+ }
124
+ /**
125
+ * Get a D1 database
126
+ */
127
+ export async function getDatabase(api, databaseId) {
128
+ if (!databaseId) {
129
+ throw new Error("Database ID is required");
130
+ }
131
+ const response = await api.get(`/accounts/${api.accountId}/d1/database/${databaseId}`);
132
+ if (!response.ok) {
133
+ return await handleApiError(response, "getting", "D1 database", databaseId);
134
+ }
135
+ return (await response.json());
136
+ }
137
+ /**
138
+ * Delete a D1 database
139
+ */
140
+ export async function deleteDatabase(api, databaseId) {
141
+ if (!databaseId) {
142
+ console.log("No database ID provided, skipping delete");
143
+ return;
144
+ }
145
+ // Delete D1 database
146
+ const deleteResponse = await api.delete(`/accounts/${api.accountId}/d1/database/${databaseId}`);
147
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
148
+ const errorData = await deleteResponse.json().catch(() => ({
149
+ errors: [{ message: deleteResponse.statusText }],
150
+ }));
151
+ throw new CloudflareApiError(`Error deleting D1 database '${databaseId}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`, deleteResponse);
152
+ }
153
+ }
154
+ /**
155
+ * List all D1 databases in an account
156
+ */
157
+ export async function listDatabases(api, name) {
158
+ // Construct query string if name is provided
159
+ const queryParams = name ? `?name=${encodeURIComponent(name)}` : "";
160
+ const response = await api.get(`/accounts/${api.accountId}/d1/database${queryParams}`);
161
+ if (!response.ok) {
162
+ throw new CloudflareApiError(`Failed to list databases: ${response.statusText}`, response);
163
+ }
164
+ const data = (await response.json());
165
+ if (!data.success) {
166
+ const errorMessage = data.errors?.[0]?.message || "Unknown error";
167
+ throw new Error(`Failed to list databases: ${errorMessage}`);
168
+ }
169
+ // Transform API response
170
+ return (data.result || []).map((db) => ({
171
+ name: db.name,
172
+ id: db.uuid,
173
+ }));
174
+ }
175
+ /**
176
+ * Update a D1 database
177
+ *
178
+ * Note: According to Cloudflare API, only read_replication.mode can be modified during updates.
179
+ */
180
+ export async function updateDatabase(api, databaseId, props) {
181
+ // Get current database state to check for non-mutable changes
182
+ const currentDB = await getDatabase(api, databaseId);
183
+ // Only read_replication can be modified in update
184
+ if (props.primaryLocationHint &&
185
+ props.primaryLocationHint !== currentDB.result.primary_location_hint) {
186
+ throw new Error("Cannot update primaryLocationHint after database creation. Only readReplication.mode can be modified.");
187
+ }
188
+ const updatePayload = {};
189
+ // Only include read_replication in update payload
190
+ if (props.readReplication) {
191
+ updatePayload.read_replication = {
192
+ mode: props.readReplication.mode,
193
+ };
194
+ }
195
+ const updateResponse = await api.patch(`/accounts/${api.accountId}/d1/database/${databaseId}`, updatePayload);
196
+ if (!updateResponse.ok) {
197
+ return await handleApiError(updateResponse, "updating", "D1 database", databaseId);
198
+ }
199
+ return (await updateResponse.json());
200
+ }
@@ -1,13 +1,17 @@
1
1
  export * from "./account-api-token";
2
2
  export * from "./api";
3
+ export * from "./api-error";
3
4
  export * from "./assets";
4
5
  export * from "./bindings";
5
6
  export * from "./bucket";
6
7
  export * from "./custom-domain";
8
+ export * from "./d1-database";
7
9
  export * from "./dns-records";
8
10
  export * from "./durable-object-namespace";
9
11
  export * from "./kv-namespace";
10
12
  export * from "./permission-groups";
13
+ export * from "./pipeline";
14
+ export * from "./queue";
11
15
  export * from "./r2-rest-state-store";
12
16
  export * from "./worker";
13
17
  export { Workflow } from "./workflow";
@@ -1,13 +1,17 @@
1
1
  export * from "./account-api-token";
2
2
  export * from "./api";
3
+ export * from "./api-error";
3
4
  export * from "./assets";
4
5
  export * from "./bindings";
5
6
  export * from "./bucket";
6
7
  export * from "./custom-domain";
8
+ export * from "./d1-database";
7
9
  export * from "./dns-records";
8
10
  export * from "./durable-object-namespace";
9
11
  export * from "./kv-namespace";
10
12
  export * from "./permission-groups";
13
+ export * from "./pipeline";
14
+ export * from "./queue";
11
15
  export * from "./r2-rest-state-store";
12
16
  export * from "./worker";
13
17
  export { Workflow } from "./workflow";
@@ -1,7 +1,6 @@
1
1
  import type { Context } from "../context";
2
2
  import { Resource } from "../resource";
3
3
  import { CloudflareApi, type CloudflareApiOptions } from "./api";
4
- export declare function isKVNamespace(resource: any): resource is KVNamespace;
5
4
  /**
6
5
  * Properties for creating or updating a KV Namespace
7
6
  */
@@ -2,9 +2,6 @@ import { Resource } from "../resource";
2
2
  import { withExponentialBackoff } from "../util/retry";
3
3
  import { CloudflareApi, createCloudflareApi, } from "./api";
4
4
  import { handleApiError } from "./api-error";
5
- export function isKVNamespace(resource) {
6
- return (resource && typeof resource === "object" && resource.type === "kv_namespace");
7
- }
8
5
  /**
9
6
  * A Cloudflare KV Namespace is a key-value store that can be used to store data for your application.
10
7
  *