Skip to content

2.0.0-beta.54 - Queues in Dev Mode

Cloudflare Queues now work end-to-end in alchemy dev. The local Cloudflare runtime emulates both the queue and its consumer, so the same Worker that produces and consumes messages in production runs unchanged on your laptop — send a message on one route and your consumer handler fires locally, no Cloudflare round-trip.

One Worker, both halves: Cloudflare.Queue.bind gives you the producer, Cloudflare.messages(queue).subscribe registers the consumer (#571):

export default class MyWorker extends Cloudflare.Worker<MyWorker>()(
"MyWorker",
{ main: import.meta.filename },
Effect.gen(function* () {
const queue = yield* Cloudflare.Queue("MyQueue");
const producer = yield* Cloudflare.Queue.bind(queue);
// consumer — fires locally under `alchemy dev`
yield* Cloudflare.messages<{ text: string }>(queue).subscribe(
(stream) => Stream.runForEach(stream, (msg) => handle(msg.body)),
);
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest;
const body = yield* request.json;
yield* producer.send(body).pipe(Effect.orDie);
return yield* HttpServerResponse.json({ sent: body });
}),
};
}).pipe(
Effect.provide([
Cloudflare.QueueBindingLive,
Cloudflare.QueueEventSourceLive,
]),
),
) {}

Under alchemy dev, the queue and consumer are provisioned by local providers: they get dev: IDs instead of live Cloudflare IDs, and the local runtime keeps its storage under .alchemy/local.

On your first real alchemy deploy, the providers’ diff logic recognizes the dev: ID and upgrades the resource in place — an update that creates the live queue and consumer under the same logical resource, never a replacement. And destroying a stack that only ever ran locally skips the Cloudflare API entirely.

QueueConsumer also now persists deadLetterQueue and settings (batch size, concurrency, retries — see the new exported QueueConsumerSettings interface) in its attributes, so consumer config reconciles like everything else.

  • alchemy dev no longer requires Cloudflare credentials at startup — the local runtime resolves the account ID lazily, only when a live API call actually needs it (#578).
  • PlanetScale providers are more resilient to live API timing — branch promote/demote retries while a cluster resize is still finalizing, Postgres resizes wait for the branch to finish provisioning first, and polling budgets grew from 10 to 30 minutes (60 for Postgres cluster-size changes) (#576).