alchemy 0.12.7 → 0.12.9

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.
Files changed (51) hide show
  1. package/lib/cloudflare/bound.d.ts +1 -1
  2. package/lib/cloudflare/bound.d.ts.map +1 -1
  3. package/lib/cloudflare/bucket.d.ts +8 -2
  4. package/lib/cloudflare/bucket.d.ts.map +1 -1
  5. package/lib/cloudflare/bucket.js +1 -1
  6. package/lib/cloudflare/bucket.js.map +1 -1
  7. package/lib/cloudflare/d1-database.d.ts +8 -2
  8. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  9. package/lib/cloudflare/d1-database.js +2 -2
  10. package/lib/cloudflare/d1-database.js.map +1 -1
  11. package/lib/cloudflare/event-source.d.ts +26 -0
  12. package/lib/cloudflare/event-source.d.ts.map +1 -0
  13. package/lib/cloudflare/event-source.js +9 -0
  14. package/lib/cloudflare/event-source.js.map +1 -0
  15. package/lib/cloudflare/index.d.ts +1 -0
  16. package/lib/cloudflare/index.d.ts.map +1 -1
  17. package/lib/cloudflare/index.js +1 -0
  18. package/lib/cloudflare/index.js.map +1 -1
  19. package/lib/cloudflare/queue-consumer.d.ts +162 -0
  20. package/lib/cloudflare/queue-consumer.d.ts.map +1 -0
  21. package/lib/cloudflare/queue-consumer.js +197 -0
  22. package/lib/cloudflare/queue-consumer.js.map +1 -0
  23. package/lib/cloudflare/queue.d.ts +15 -3
  24. package/lib/cloudflare/queue.d.ts.map +1 -1
  25. package/lib/cloudflare/queue.js +8 -2
  26. package/lib/cloudflare/queue.js.map +1 -1
  27. package/lib/cloudflare/r2-rest-state-store.js +1 -1
  28. package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
  29. package/lib/cloudflare/website.d.ts.map +1 -1
  30. package/lib/cloudflare/website.js +8 -10
  31. package/lib/cloudflare/website.js.map +1 -1
  32. package/lib/cloudflare/worker.d.ts +13 -2
  33. package/lib/cloudflare/worker.d.ts.map +1 -1
  34. package/lib/cloudflare/worker.js +67 -3
  35. package/lib/cloudflare/worker.js.map +1 -1
  36. package/lib/cloudflare/wrangler.json.d.ts +13 -3
  37. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  38. package/lib/cloudflare/wrangler.json.js +27 -5
  39. package/lib/cloudflare/wrangler.json.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/cloudflare/bound.ts +2 -2
  42. package/src/cloudflare/bucket.ts +9 -2
  43. package/src/cloudflare/d1-database.ts +10 -3
  44. package/src/cloudflare/event-source.ts +33 -0
  45. package/src/cloudflare/index.ts +1 -0
  46. package/src/cloudflare/queue-consumer.ts +433 -0
  47. package/src/cloudflare/queue.ts +83 -63
  48. package/src/cloudflare/r2-rest-state-store.ts +1 -1
  49. package/src/cloudflare/website.ts +9 -13
  50. package/src/cloudflare/worker.ts +102 -5
  51. package/src/cloudflare/wrangler.json.ts +52 -8
@@ -6,7 +6,7 @@ import { Resource } from "../resource.js";
6
6
  import { getContentType } from "../util/content-type.js";
7
7
  import { withExponentialBackoff } from "../util/retry.js";
8
8
  import { slugify } from "../util/slugify.js";
9
- import { handleApiError } from "./api-error.js";
9
+ import { CloudflareApiError, handleApiError } from "./api-error.js";
10
10
  import {
11
11
  type CloudflareApi,
12
12
  type CloudflareApiOptions,
@@ -16,6 +16,13 @@ import type { Assets } from "./assets.js";
16
16
  import type { Bindings, WorkerBindingSpec } from "./bindings.js";
17
17
  import type { Bound } from "./bound.js";
18
18
  import type { DurableObjectNamespace } from "./durable-object-namespace.js";
19
+ import { type EventSource, isQueueEventSource } from "./event-source.js";
20
+ import {
21
+ QueueConsumer,
22
+ deleteQueueConsumer,
23
+ listQueueConsumers,
24
+ } from "./queue-consumer.js";
25
+ import { isQueue } from "./queue.js";
19
26
  import type { WorkerScriptMetadata } from "./worker-metadata.js";
20
27
  import type { SingleStepMigration } from "./worker-migration.js";
21
28
  import { type Workflow, upsertWorkflow } from "./workflow.js";
@@ -98,9 +105,10 @@ export interface WorkerProps<B extends Bindings = Bindings>
98
105
 
99
106
  /**
100
107
  * Name for the worker
101
- * This is mandatory - must be explicitly specified
108
+ *
109
+ * @default id
102
110
  */
103
- name: string;
111
+ name?: string;
104
112
 
105
113
  /**
106
114
  * Bindings to attach to the worker
@@ -169,6 +177,12 @@ export interface WorkerProps<B extends Bindings = Bindings>
169
177
  * @see https://developers.cloudflare.com/workers/configuration/cron-triggers/#examples
170
178
  */
171
179
  crons?: string[];
180
+
181
+ /**
182
+ * Event sources that this worker will consume.
183
+ * Can include queues, streams, or other event sources.
184
+ */
185
+ eventSources?: EventSource[];
172
186
  }
173
187
 
174
188
  /**
@@ -184,6 +198,11 @@ export interface Worker<B extends Bindings = Bindings>
184
198
  */
185
199
  id: string;
186
200
 
201
+ /**
202
+ * The name of the worker
203
+ */
204
+ name: string;
205
+
187
206
  /**
188
207
  * Time at which the worker was created
189
208
  */
@@ -326,7 +345,7 @@ export const Worker = Resource(
326
345
  const api = await createCloudflareApi(props);
327
346
 
328
347
  // Use the provided name
329
- const workerName = props.name;
348
+ const workerName = props.name ?? id;
330
349
 
331
350
  // Validate input - we need either script, entryPoint, or bundle
332
351
  if (!props.script && !props.entrypoint) {
@@ -379,7 +398,7 @@ export const Worker = Resource(
379
398
  );
380
399
  }
381
400
 
382
- const compatibilityDate = props.compatibilityDate ?? "2024-09-09";
401
+ const compatibilityDate = props.compatibilityDate ?? "2025-03-01";
383
402
  const compatibilityFlags = props.compatibilityFlags ?? [];
384
403
 
385
404
  // Prepare metadata with bindings
@@ -404,6 +423,24 @@ export const Worker = Resource(
404
423
  });
405
424
  }
406
425
 
426
+ await Promise.all(
427
+ props.eventSources?.map((eventSource) => {
428
+ if (isQueue(eventSource) || isQueueEventSource(eventSource)) {
429
+ const queue = isQueue(eventSource) ? eventSource : eventSource.queue;
430
+ return QueueConsumer(`${queue.id}-consumer`, {
431
+ queue,
432
+ scriptName: workerName,
433
+ accountId: api.accountId,
434
+ settings: isQueueEventSource(eventSource)
435
+ ? eventSource.settings
436
+ : undefined,
437
+ });
438
+ } else {
439
+ throw new Error(`Unsupported event source type: ${eventSource}`);
440
+ }
441
+ }) ?? []
442
+ );
443
+
407
444
  // TODO: it is less than ideal that this can fail, resulting in state problem
408
445
  await this.set("bindings", props.bindings);
409
446
 
@@ -451,6 +488,7 @@ export const Worker = Resource(
451
488
  observability: scriptMetadata.observability,
452
489
  createdAt: now,
453
490
  updatedAt: now,
491
+ eventSources: props.eventSources,
454
492
  url: workerUrl,
455
493
  // Include assets configuration in the output
456
494
  assets: props.assets,
@@ -467,6 +505,9 @@ async function deleteWorker<B extends Bindings>(
467
505
  api: CloudflareApi,
468
506
  workerName: string
469
507
  ) {
508
+ // Delete any queue consumers attached to this worker first
509
+ await deleteQueueConsumers(ctx, api, workerName);
510
+
470
511
  // Delete worker
471
512
  const deleteResponse = await api.delete(
472
513
  `/accounts/${api.accountId}/workers/scripts/${workerName}`
@@ -1222,3 +1263,59 @@ async function calculateFileMetadata(
1222
1263
  size: fileContent.length,
1223
1264
  };
1224
1265
  }
1266
+
1267
+ /**
1268
+ * Lists and deletes all queue consumers for a specific worker
1269
+ * @param ctx Worker context containing eventSources
1270
+ * @param api CloudflareApi instance
1271
+ * @param workerName Name of the worker script
1272
+ */
1273
+ async function deleteQueueConsumers<B extends Bindings>(
1274
+ ctx: Context<Worker<B>>,
1275
+ api: CloudflareApi,
1276
+ workerName: string
1277
+ ): Promise<void> {
1278
+ const eventSources = ctx.output?.eventSources || [];
1279
+
1280
+ // Extract queue IDs from event sources
1281
+ const queueIds = eventSources.flatMap((eventSource) => {
1282
+ if (isQueue(eventSource)) {
1283
+ return [eventSource.id];
1284
+ } else if (isQueueEventSource(eventSource)) {
1285
+ return [eventSource.queue.id];
1286
+ }
1287
+ return [];
1288
+ });
1289
+
1290
+ // Process each queue associated with this worker
1291
+ await Promise.all(
1292
+ queueIds.map(async (queueId) => {
1293
+ try {
1294
+ // List all consumers for this queue
1295
+ const consumers = await listQueueConsumers(api, queueId);
1296
+
1297
+ // Filter consumers by worker name
1298
+ const workerConsumers = consumers.filter(
1299
+ (consumer) => consumer.scriptName === workerName
1300
+ );
1301
+
1302
+ // Delete all consumers for this worker in parallel
1303
+ await Promise.all(
1304
+ workerConsumers.map(async (consumer) => {
1305
+ console.log(
1306
+ `Deleting queue consumer ${consumer.id} for worker ${workerName}`
1307
+ );
1308
+ // Use the deleteQueueConsumer function from queue-consumer.ts
1309
+ await deleteQueueConsumer(api, consumer.queueId, consumer.id);
1310
+ })
1311
+ );
1312
+ } catch (err) {
1313
+ if (err instanceof CloudflareApiError && err.status === 404) {
1314
+ // this is OK
1315
+ } else {
1316
+ throw err;
1317
+ }
1318
+ }
1319
+ })
1320
+ );
1321
+ }
@@ -3,6 +3,9 @@ import { StaticJsonFile } from "../fs/static-json-file.js";
3
3
  import { Resource } from "../resource.js";
4
4
  import type { Bindings } from "./bindings.js";
5
5
  import type { DurableObjectNamespace } from "./durable-object-namespace.js";
6
+ import type { EventSource } from "./event-source.js";
7
+ import { isQueueEventSource } from "./event-source.js";
8
+ import { isQueue } from "./queue.js";
6
9
  import type { Worker } from "./worker.js";
7
10
 
8
11
  /**
@@ -80,7 +83,7 @@ export const WranglerJson = Resource(
80
83
 
81
84
  // Process bindings if they exist
82
85
  if (worker.bindings) {
83
- processBindings(spec, worker.bindings);
86
+ processBindings(spec, worker.bindings, worker.eventSources);
84
87
  }
85
88
 
86
89
  // Add environment variables as vars
@@ -166,9 +169,16 @@ export interface WranglerJsonSpec {
166
169
  * Queue bindings
167
170
  */
168
171
  queues?: {
169
- binding: string;
170
- queue: string;
171
- }[];
172
+ producers: { queue: string; binding: string }[];
173
+ consumers: {
174
+ queue: string;
175
+ max_batch_size?: number;
176
+ max_concurrency?: number;
177
+ max_retries?: number;
178
+ max_wait_time_ms?: number;
179
+ retry_delay?: number;
180
+ }[];
181
+ };
172
182
 
173
183
  /**
174
184
  * Service bindings
@@ -238,7 +248,11 @@ export interface WranglerJsonSpec {
238
248
  /**
239
249
  * Process worker bindings into wrangler.json format
240
250
  */
241
- function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
251
+ function processBindings(
252
+ spec: WranglerJsonSpec,
253
+ bindings: Bindings,
254
+ eventSources: EventSource[] | undefined
255
+ ): void {
242
256
  // Arrays to collect different binding types
243
257
  const kvNamespaces: { binding: string; id: string }[] = [];
244
258
  const durableObjects: {
@@ -258,9 +272,39 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
258
272
  database_name: string;
259
273
  migrations_dir?: string;
260
274
  }[] = [];
261
- const queues: { binding: string; queue: string }[] = [];
275
+ const queues: {
276
+ producers: { queue: string; binding: string }[];
277
+ consumers: {
278
+ queue: string;
279
+ max_batch_size?: number;
280
+ max_concurrency?: number;
281
+ max_retries?: number;
282
+ max_wait_time_ms?: number;
283
+ retry_delay?: number;
284
+ }[];
285
+ } = {
286
+ producers: [],
287
+ consumers: [],
288
+ };
289
+
262
290
  const vectorizeIndexes: { binding: string; index_name: string }[] = [];
263
291
 
292
+ for (const eventSource of eventSources ?? []) {
293
+ if (isQueueEventSource(eventSource)) {
294
+ queues.consumers.push({
295
+ queue: eventSource.queue.id,
296
+ max_batch_size: eventSource.settings?.batchSize,
297
+ max_concurrency: eventSource.settings?.maxConcurrency,
298
+ max_retries: eventSource.settings?.maxRetries,
299
+ max_wait_time_ms: eventSource.settings?.maxWaitTimeMs,
300
+ retry_delay: eventSource.settings?.retryDelay,
301
+ });
302
+ } else if (isQueue(eventSource)) {
303
+ queues.consumers.push({
304
+ queue: eventSource.id,
305
+ });
306
+ }
307
+ }
264
308
  // Process each binding
265
309
  for (const [bindingName, binding] of Object.entries(bindings)) {
266
310
  if (typeof binding === "string") {
@@ -320,7 +364,7 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
320
364
  migrations_dir: binding.migrationsDir,
321
365
  });
322
366
  } else if (binding.type === "queue") {
323
- queues.push({
367
+ queues.producers.push({
324
368
  binding: bindingName,
325
369
  queue: binding.name,
326
370
  });
@@ -355,7 +399,7 @@ function processBindings(spec: WranglerJsonSpec, bindings: Bindings): void {
355
399
  spec.d1_databases = d1Databases;
356
400
  }
357
401
 
358
- if (queues.length > 0) {
402
+ if (queues.consumers.length > 0) {
359
403
  spec.queues = queues;
360
404
  }
361
405