alchemy 0.7.0 → 0.7.1
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 +3 -1
- package/lib/cloudflare/bound.d.ts +3 -1
- package/lib/cloudflare/d1-database.d.ts +134 -0
- package/lib/cloudflare/d1-database.js +161 -0
- package/lib/cloudflare/index.d.ts +3 -0
- package/lib/cloudflare/index.js +3 -0
- package/lib/cloudflare/kv-namespace.d.ts +0 -1
- package/lib/cloudflare/kv-namespace.js +0 -3
- package/lib/cloudflare/queue.d.ts +147 -0
- package/lib/cloudflare/queue.js +192 -0
- package/lib/cloudflare/worker.js +22 -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 +7 -3
- package/src/cloudflare/bound.ts +7 -1
- package/src/cloudflare/d1-database.ts +354 -0
- package/src/cloudflare/index.ts +3 -0
- package/src/cloudflare/kv-namespace.ts +0 -6
- package/src/cloudflare/queue.ts +371 -0
- package/src/cloudflare/worker.ts +20 -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";
|
|
@@ -319,8 +318,10 @@ export const Worker = Resource(
|
|
|
319
318
|
|
|
320
319
|
// Construct the output
|
|
321
320
|
return this({
|
|
321
|
+
...props,
|
|
322
322
|
type: "service",
|
|
323
323
|
id,
|
|
324
|
+
entrypoint: props.entrypoint,
|
|
324
325
|
name: workerName,
|
|
325
326
|
script: scriptContent,
|
|
326
327
|
format: props.format || "esm", // Include format in the output
|
|
@@ -521,17 +522,23 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
521
522
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
522
523
|
// Create a copy of the binding to avoid modifying the original
|
|
523
524
|
|
|
524
|
-
if (
|
|
525
|
+
if (typeof binding === "string") {
|
|
525
526
|
meta.bindings.push({
|
|
526
|
-
type: "
|
|
527
|
+
type: "plain_text",
|
|
527
528
|
name: bindingName,
|
|
528
|
-
|
|
529
|
+
text: binding,
|
|
529
530
|
});
|
|
530
|
-
} else if (
|
|
531
|
+
} else if (binding.type === "d1") {
|
|
531
532
|
meta.bindings.push({
|
|
532
|
-
type: "
|
|
533
|
+
type: "d1",
|
|
533
534
|
name: bindingName,
|
|
534
|
-
|
|
535
|
+
id: binding.id,
|
|
536
|
+
});
|
|
537
|
+
} else if (binding.type === "kv_namespace") {
|
|
538
|
+
meta.bindings.push({
|
|
539
|
+
type: "kv_namespace",
|
|
540
|
+
name: bindingName,
|
|
541
|
+
namespace_id: binding.namespaceId,
|
|
535
542
|
});
|
|
536
543
|
} else if (binding.type === "service") {
|
|
537
544
|
meta.bindings.push({
|
|
@@ -577,6 +584,12 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
577
584
|
});
|
|
578
585
|
// it's unclear whether this is needed, but it works both ways
|
|
579
586
|
configureClassMigration(binding, binding.id, binding.className);
|
|
587
|
+
} else if (binding.type === "queue") {
|
|
588
|
+
meta.bindings.push({
|
|
589
|
+
type: "queue",
|
|
590
|
+
name: bindingName,
|
|
591
|
+
queue_name: binding.name,
|
|
592
|
+
});
|
|
580
593
|
} else {
|
|
581
594
|
// @ts-expect-error - we should never reach here
|
|
582
595
|
throw new Error(`Unsupported binding type: ${binding.type}`);
|