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.
- package/lib/cloudflare/bindings.d.ts +16 -2
- package/lib/cloudflare/bound.d.ts +5 -1
- package/lib/cloudflare/bucket.js +1 -0
- package/lib/cloudflare/d1-database.d.ts +151 -0
- package/lib/cloudflare/d1-database.js +200 -0
- package/lib/cloudflare/index.d.ts +4 -0
- package/lib/cloudflare/index.js +4 -0
- package/lib/cloudflare/kv-namespace.d.ts +0 -1
- package/lib/cloudflare/kv-namespace.js +0 -3
- package/lib/cloudflare/pipeline.d.ts +335 -0
- package/lib/cloudflare/pipeline.js +250 -0
- package/lib/cloudflare/queue.d.ts +147 -0
- package/lib/cloudflare/queue.js +192 -0
- package/lib/cloudflare/worker.d.ts +9 -0
- package/lib/cloudflare/worker.js +31 -7
- package/lib/cloudflare/wrangler.json.d.ts +82 -185
- package/lib/cloudflare/wrangler.json.js +124 -17
- package/package.json +1 -1
- package/src/cloudflare/bindings.ts +22 -3
- package/src/cloudflare/bound.ts +11 -1
- package/src/cloudflare/bucket.ts +1 -0
- package/src/cloudflare/d1-database.ts +412 -0
- package/src/cloudflare/index.ts +4 -0
- package/src/cloudflare/kv-namespace.ts +0 -6
- package/src/cloudflare/pipeline.ts +642 -0
- package/src/cloudflare/queue.ts +371 -0
- package/src/cloudflare/worker.ts +41 -7
- package/src/cloudflare/wrangler.json.ts +239 -218
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import {
|
|
4
|
+
CloudflareApi,
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApiOptions,
|
|
7
|
+
} from "./api";
|
|
8
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Settings for a Cloudflare Queue
|
|
12
|
+
*/
|
|
13
|
+
export interface QueueSettings {
|
|
14
|
+
/**
|
|
15
|
+
* Delay in seconds before message delivery
|
|
16
|
+
* Queue will not deliver messages until this time has elapsed
|
|
17
|
+
*/
|
|
18
|
+
deliveryDelay?: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether delivery is paused
|
|
22
|
+
* If true, the queue will not deliver messages to consumers
|
|
23
|
+
*/
|
|
24
|
+
deliveryPaused?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Period in seconds to retain messages
|
|
28
|
+
* Messages will be automatically deleted after this time
|
|
29
|
+
*/
|
|
30
|
+
messageRetentionPeriod?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Properties for creating or updating a Cloudflare Queue
|
|
35
|
+
*/
|
|
36
|
+
export interface QueueProps extends CloudflareApiOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Name of the queue
|
|
39
|
+
* Required during creation
|
|
40
|
+
* Cannot be changed after creation
|
|
41
|
+
*/
|
|
42
|
+
name: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Settings for the queue
|
|
46
|
+
* These can be updated after queue creation
|
|
47
|
+
*/
|
|
48
|
+
settings?: QueueSettings;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether to delete the queue.
|
|
52
|
+
* If set to false, the queue will remain but the resource will be removed from state
|
|
53
|
+
*
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
delete?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Output returned after Cloudflare Queue creation/update
|
|
61
|
+
*/
|
|
62
|
+
export interface Queue extends Resource<"cloudflare::Queue">, QueueProps {
|
|
63
|
+
/**
|
|
64
|
+
* Type identifier for Cloudflare Queue
|
|
65
|
+
*/
|
|
66
|
+
type: "queue";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The unique ID of the queue
|
|
70
|
+
*/
|
|
71
|
+
id: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Time when the queue was created
|
|
75
|
+
*/
|
|
76
|
+
createdOn: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Modified timestamp
|
|
80
|
+
*/
|
|
81
|
+
modifiedOn: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface CloudflareQueueResponse {
|
|
85
|
+
result: {
|
|
86
|
+
queue_id?: string;
|
|
87
|
+
queue_name: string;
|
|
88
|
+
created_on?: string;
|
|
89
|
+
modified_on?: string;
|
|
90
|
+
settings?: {
|
|
91
|
+
delivery_delay?: number;
|
|
92
|
+
delivery_paused?: boolean;
|
|
93
|
+
message_retention_period?: number;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
success: boolean;
|
|
97
|
+
errors: Array<{ code: number; message: string }>;
|
|
98
|
+
messages: string[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates and manages Cloudflare Queues.
|
|
103
|
+
*
|
|
104
|
+
* Queues provide a managed queue system for reliable message delivery
|
|
105
|
+
* between workers and other systems.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // Create a basic queue with default settings
|
|
109
|
+
* const basicQueue = await Queue("my-app-queue", {
|
|
110
|
+
* name: "my-app-queue"
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* // Create a queue with custom settings
|
|
115
|
+
* const customQueue = await Queue("delayed-queue", {
|
|
116
|
+
* name: "delayed-queue",
|
|
117
|
+
* settings: {
|
|
118
|
+
* deliveryDelay: 30, // 30 second delay before message delivery
|
|
119
|
+
* messageRetentionPeriod: 86400 // Store messages for 1 day
|
|
120
|
+
* }
|
|
121
|
+
* });
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Create a paused queue for later activation
|
|
125
|
+
* const pausedQueue = await Queue("paused-queue", {
|
|
126
|
+
* name: "paused-queue",
|
|
127
|
+
* settings: {
|
|
128
|
+
* deliveryPaused: true
|
|
129
|
+
* }
|
|
130
|
+
* });
|
|
131
|
+
*
|
|
132
|
+
* @see https://developers.cloudflare.com/queues/
|
|
133
|
+
*/
|
|
134
|
+
export const Queue = Resource(
|
|
135
|
+
"cloudflare::Queue",
|
|
136
|
+
async function (
|
|
137
|
+
this: Context<Queue>,
|
|
138
|
+
id: string,
|
|
139
|
+
props: QueueProps
|
|
140
|
+
): Promise<Queue> {
|
|
141
|
+
const api = await createCloudflareApi(props);
|
|
142
|
+
const queueName = props.name || id;
|
|
143
|
+
|
|
144
|
+
if (this.phase === "delete") {
|
|
145
|
+
console.log("Deleting Cloudflare Queue:", queueName);
|
|
146
|
+
if (props.delete !== false) {
|
|
147
|
+
// Delete Queue
|
|
148
|
+
await deleteQueue(api, this.output?.id);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Return void (a deleted queue has no content)
|
|
152
|
+
return this.destroy();
|
|
153
|
+
} else {
|
|
154
|
+
let queueData: CloudflareQueueResponse;
|
|
155
|
+
|
|
156
|
+
if (this.phase === "create") {
|
|
157
|
+
console.log("Creating Cloudflare Queue:", queueName);
|
|
158
|
+
queueData = await createQueue(api, queueName, props);
|
|
159
|
+
} else {
|
|
160
|
+
// Update operation
|
|
161
|
+
if (this.output?.id) {
|
|
162
|
+
console.log("Updating Cloudflare Queue:", queueName);
|
|
163
|
+
|
|
164
|
+
// Check if name is being changed, which is not allowed
|
|
165
|
+
if (props.name !== this.output.name) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"Cannot update Queue name after creation. Queue name is immutable."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update the queue with new settings
|
|
172
|
+
queueData = await updateQueue(api, this.output.id, props);
|
|
173
|
+
} else {
|
|
174
|
+
// If no ID exists, fall back to creating a new queue
|
|
175
|
+
console.log(
|
|
176
|
+
"No existing Queue ID found, creating new Cloudflare Queue:",
|
|
177
|
+
queueName
|
|
178
|
+
);
|
|
179
|
+
queueData = await createQueue(api, queueName, props);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return this({
|
|
184
|
+
type: "queue",
|
|
185
|
+
id: queueData.result.queue_id || "",
|
|
186
|
+
name: queueName,
|
|
187
|
+
settings: queueData.result.settings
|
|
188
|
+
? {
|
|
189
|
+
deliveryDelay: queueData.result.settings.delivery_delay,
|
|
190
|
+
deliveryPaused: queueData.result.settings.delivery_paused,
|
|
191
|
+
messageRetentionPeriod:
|
|
192
|
+
queueData.result.settings.message_retention_period,
|
|
193
|
+
}
|
|
194
|
+
: undefined,
|
|
195
|
+
createdOn: queueData.result.created_on || new Date().toISOString(),
|
|
196
|
+
modifiedOn: queueData.result.modified_on || new Date().toISOString(),
|
|
197
|
+
accountId: api.accountId,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a new Cloudflare Queue
|
|
205
|
+
*/
|
|
206
|
+
export async function createQueue(
|
|
207
|
+
api: CloudflareApi,
|
|
208
|
+
queueName: string,
|
|
209
|
+
props: QueueProps
|
|
210
|
+
): Promise<CloudflareQueueResponse> {
|
|
211
|
+
// Prepare the create payload
|
|
212
|
+
const createPayload: any = {
|
|
213
|
+
queue_name: queueName,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Add settings if provided
|
|
217
|
+
if (props.settings) {
|
|
218
|
+
createPayload.settings = {};
|
|
219
|
+
|
|
220
|
+
if (props.settings.deliveryDelay !== undefined) {
|
|
221
|
+
createPayload.settings.delivery_delay = props.settings.deliveryDelay;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (props.settings.deliveryPaused !== undefined) {
|
|
225
|
+
createPayload.settings.delivery_paused = props.settings.deliveryPaused;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (props.settings.messageRetentionPeriod !== undefined) {
|
|
229
|
+
createPayload.settings.message_retention_period =
|
|
230
|
+
props.settings.messageRetentionPeriod;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const createResponse = await api.post(
|
|
235
|
+
`/accounts/${api.accountId}/queues`,
|
|
236
|
+
createPayload
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!createResponse.ok) {
|
|
240
|
+
return await handleApiError(createResponse, "creating", "Queue", queueName);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return (await createResponse.json()) as CloudflareQueueResponse;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get a Cloudflare Queue
|
|
248
|
+
*/
|
|
249
|
+
export async function getQueue(
|
|
250
|
+
api: CloudflareApi,
|
|
251
|
+
queueId: string
|
|
252
|
+
): Promise<CloudflareQueueResponse> {
|
|
253
|
+
const response = await api.get(
|
|
254
|
+
`/accounts/${api.accountId}/queues/${queueId}`
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
return await handleApiError(response, "getting", "Queue", queueId);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return (await response.json()) as CloudflareQueueResponse;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Delete a Cloudflare Queue
|
|
266
|
+
*/
|
|
267
|
+
export async function deleteQueue(
|
|
268
|
+
api: CloudflareApi,
|
|
269
|
+
queueId?: string
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
if (!queueId) {
|
|
272
|
+
console.log("No Queue ID provided, skipping delete");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Delete Queue
|
|
277
|
+
const deleteResponse = await api.delete(
|
|
278
|
+
`/accounts/${api.accountId}/queues/${queueId}`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
282
|
+
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
283
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
284
|
+
}));
|
|
285
|
+
throw new CloudflareApiError(
|
|
286
|
+
`Error deleting Cloudflare Queue '${queueId}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
287
|
+
deleteResponse
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Update a Cloudflare Queue
|
|
294
|
+
*
|
|
295
|
+
* Note: According to Cloudflare API, the queue name cannot be changed after creation.
|
|
296
|
+
* Only the settings can be updated.
|
|
297
|
+
*/
|
|
298
|
+
export async function updateQueue(
|
|
299
|
+
api: CloudflareApi,
|
|
300
|
+
queueId: string,
|
|
301
|
+
props: QueueProps
|
|
302
|
+
): Promise<CloudflareQueueResponse> {
|
|
303
|
+
// Prepare the update payload - only include settings
|
|
304
|
+
const updatePayload: any = {};
|
|
305
|
+
|
|
306
|
+
// Add settings if provided
|
|
307
|
+
if (props.settings) {
|
|
308
|
+
updatePayload.settings = {};
|
|
309
|
+
|
|
310
|
+
if (props.settings.deliveryDelay !== undefined) {
|
|
311
|
+
updatePayload.settings.delivery_delay = props.settings.deliveryDelay;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (props.settings.deliveryPaused !== undefined) {
|
|
315
|
+
updatePayload.settings.delivery_paused = props.settings.deliveryPaused;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (props.settings.messageRetentionPeriod !== undefined) {
|
|
319
|
+
updatePayload.settings.message_retention_period =
|
|
320
|
+
props.settings.messageRetentionPeriod;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Use PATCH for partial updates (only settings can be updated)
|
|
325
|
+
const updateResponse = await api.patch(
|
|
326
|
+
`/accounts/${api.accountId}/queues/${queueId}`,
|
|
327
|
+
updatePayload
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (!updateResponse.ok) {
|
|
331
|
+
return await handleApiError(updateResponse, "updating", "Queue", queueId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return (await updateResponse.json()) as CloudflareQueueResponse;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* List all Cloudflare Queues in an account
|
|
339
|
+
*/
|
|
340
|
+
export async function listQueues(
|
|
341
|
+
api: CloudflareApi
|
|
342
|
+
): Promise<{ name: string; id: string }[]> {
|
|
343
|
+
const response = await api.get(`/accounts/${api.accountId}/queues`);
|
|
344
|
+
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
throw new CloudflareApiError(
|
|
347
|
+
`Failed to list queues: ${response.statusText}`,
|
|
348
|
+
response
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const data = (await response.json()) as {
|
|
353
|
+
success: boolean;
|
|
354
|
+
errors?: Array<{ code: number; message: string }>;
|
|
355
|
+
result?: Array<{
|
|
356
|
+
queue_name: string;
|
|
357
|
+
queue_id: string;
|
|
358
|
+
}>;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
if (!data.success) {
|
|
362
|
+
const errorMessage = data.errors?.[0]?.message || "Unknown error";
|
|
363
|
+
throw new Error(`Failed to list queues: ${errorMessage}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Transform API response
|
|
367
|
+
return (data.result || []).map((queue) => ({
|
|
368
|
+
name: queue.queue_name,
|
|
369
|
+
id: queue.queue_id,
|
|
370
|
+
}));
|
|
371
|
+
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { type Assets } from "./assets";
|
|
|
15
15
|
import { type Bindings, type WorkerBindingSpec } from "./bindings";
|
|
16
16
|
import type { Bound } from "./bound";
|
|
17
17
|
import { type DurableObjectNamespace } from "./durable-object-namespace";
|
|
18
|
-
import { isKVNamespace } from "./kv-namespace";
|
|
19
18
|
import type { WorkerScriptMetadata } from "./worker-metadata";
|
|
20
19
|
import type { SingleStepMigration } from "./worker-migration";
|
|
21
20
|
import { upsertWorkflow, type Workflow } from "./workflow";
|
|
@@ -100,6 +99,17 @@ export interface WorkerProps<B extends Bindings = Bindings>
|
|
|
100
99
|
* Whether to adopt the Worker if it already exists when creating
|
|
101
100
|
*/
|
|
102
101
|
adopt?: boolean;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The compatibility date for the worker
|
|
105
|
+
* @default "2024-09-09"
|
|
106
|
+
*/
|
|
107
|
+
compatibilityDate?: string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The compatibility flags for the worker
|
|
111
|
+
*/
|
|
112
|
+
compatibilityFlags?: string[];
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
/**
|
|
@@ -319,8 +329,10 @@ export const Worker = Resource(
|
|
|
319
329
|
|
|
320
330
|
// Construct the output
|
|
321
331
|
return this({
|
|
332
|
+
...props,
|
|
322
333
|
type: "service",
|
|
323
334
|
id,
|
|
335
|
+
entrypoint: props.entrypoint,
|
|
324
336
|
name: workerName,
|
|
325
337
|
script: scriptContent,
|
|
326
338
|
format: props.format || "esm", // Include format in the output
|
|
@@ -455,6 +467,8 @@ class NotFoundError extends Error {
|
|
|
455
467
|
}
|
|
456
468
|
|
|
457
469
|
interface WorkerMetadata {
|
|
470
|
+
compatibility_date: string;
|
|
471
|
+
compatibility_flags?: string[];
|
|
458
472
|
bindings: WorkerBindingSpec[];
|
|
459
473
|
observability: {
|
|
460
474
|
enabled: boolean;
|
|
@@ -489,6 +503,8 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
489
503
|
): Promise<WorkerMetadata> {
|
|
490
504
|
// Prepare metadata with bindings
|
|
491
505
|
const meta: WorkerMetadata = {
|
|
506
|
+
compatibility_date: props.compatibilityDate ?? "2024-09-09",
|
|
507
|
+
compatibility_flags: props.compatibilityFlags,
|
|
492
508
|
bindings: [],
|
|
493
509
|
observability: {
|
|
494
510
|
enabled: props.observability?.enabled !== false,
|
|
@@ -521,17 +537,23 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
521
537
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
522
538
|
// Create a copy of the binding to avoid modifying the original
|
|
523
539
|
|
|
524
|
-
if (
|
|
540
|
+
if (typeof binding === "string") {
|
|
525
541
|
meta.bindings.push({
|
|
526
|
-
type: "
|
|
542
|
+
type: "plain_text",
|
|
527
543
|
name: bindingName,
|
|
528
|
-
|
|
544
|
+
text: binding,
|
|
529
545
|
});
|
|
530
|
-
} else if (
|
|
546
|
+
} else if (binding.type === "d1") {
|
|
531
547
|
meta.bindings.push({
|
|
532
|
-
type: "
|
|
548
|
+
type: "d1",
|
|
533
549
|
name: bindingName,
|
|
534
|
-
|
|
550
|
+
id: binding.id,
|
|
551
|
+
});
|
|
552
|
+
} else if (binding.type === "kv_namespace") {
|
|
553
|
+
meta.bindings.push({
|
|
554
|
+
type: "kv_namespace",
|
|
555
|
+
name: bindingName,
|
|
556
|
+
namespace_id: binding.namespaceId,
|
|
535
557
|
});
|
|
536
558
|
} else if (binding.type === "service") {
|
|
537
559
|
meta.bindings.push({
|
|
@@ -577,6 +599,18 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
577
599
|
});
|
|
578
600
|
// it's unclear whether this is needed, but it works both ways
|
|
579
601
|
configureClassMigration(binding, binding.id, binding.className);
|
|
602
|
+
} else if (binding.type === "queue") {
|
|
603
|
+
meta.bindings.push({
|
|
604
|
+
type: "queue",
|
|
605
|
+
name: bindingName,
|
|
606
|
+
queue_name: binding.name,
|
|
607
|
+
});
|
|
608
|
+
} else if (binding.type === "pipeline") {
|
|
609
|
+
meta.bindings.push({
|
|
610
|
+
type: "pipelines",
|
|
611
|
+
name: bindingName,
|
|
612
|
+
pipeline: binding.name,
|
|
613
|
+
});
|
|
580
614
|
} else {
|
|
581
615
|
// @ts-expect-error - we should never reach here
|
|
582
616
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|