1---
2title: 2.0.0-beta.35 - Queue Streams & R2 Domains
3date: 2026-05-08
4category: release
5excerpt: A Stream-shaped Effect consumer for Cloudflare Queues, R2 bucket custom domains, non-string env bindings on Workers, Neon logical replication, and a round of CLI quality-of-life.
6---
7
8`v2.0.0-beta.35` is out. The headline addition is an
9Effect-native, `Stream`-shaped consumer API for Cloudflare
10Queues — write a queue handler the same way you'd write
11any other Effect pipeline. Plus R2 bucket custom domains,
12non-string Worker env bindings, Neon logical replication,
13and a handful of CLI polish. Community contributors get
14inline shoutouts; full credits in the
15[Contributors section](#contributors).
16
17## `Cloudflare.Queues.messages(queue).subscribe(...)` — Effect-native queue consumer
18
19Process Cloudflare Queue batches as a typed Effect `Stream`,
20with `ack`/`retry` exposed per message. The handler returns
21when the batch is done; failed messages get retried up to
22`maxRetries`. Same Worker, same Effect runtime, same
23bindings — the queue consumer is just another `yield*` in
24the init phase.
25
26A real producer-and-consumer pair: a Worker accepts POSTs
27to `/queue/send`, the consumer writes each message to R2
28as JSON so the result is observable. Bindings already on
29the Worker (`bucket`, `queue`) are in scope inside the
30stream handler — the consumer is a peer to the Worker's
31init phase, not a separate file.
32
33```typescript
34import * as Cloudflare from "alchemy/Cloudflare";
35import * as Effect from "effect/Effect";
36import * as Stream from "effect/Stream";
37import { Bucket } from "./Bucket.ts";
38import { Queue } from "./Queue.ts";
39
40interface QueueMessageBody {
41 id: string;
42 text: string;
43 sentAt: number;
44}
45
46export default class Api extends Cloudflare.Worker<Api>()(
47 "Api",
48 { main: import.meta.filename },
49 Effect.gen(function* () {
50 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
51 const queueResource = yield* Queue;
52 const queue = yield* Cloudflare.QueueBinding.bind(queueResource);
53
54 // Consumer side. Each batch arrives as a Stream of Message<Body>;
55 // success ack()s every message, failure retry()s. Crash mid-batch and
56 // unprocessed messages are redelivered.
57 yield* Cloudflare.Queues.messages<QueueMessageBody>(queueResource, {
58 batchSize: 25,
59 maxRetries: 3,
60 }).subscribe((stream) =>
61 Stream.runForEach(stream, (msg) =>
62 bucket
63 .put(`/queue/${msg.body.id}`, JSON.stringify(msg.body), {
64 httpMetadata: { contentType: "application/json" },
65 })
66 .pipe(Effect.asVoid),
67 ),
68 );
69
70 return {
71 // Producer side: POST /queue/send adds a message.
72 fetch: Effect.gen(function* () {
73 const request = yield* HttpServerRequest;
74 if (request.url === "/queue/send" && request.method === "POST") {
75 const text = yield* request.text;
76 const msg: QueueMessageBody = {
77 id: crypto.randomUUID(),
78 text,
79 sentAt: Date.now(),
80 };
81 yield* queue.send(msg);
82 return yield* HttpServerResponse.json({ sent: msg }, { status: 202 });
83 }
84 return HttpServerResponse.text("ok");
85 }),
86 };
87 }).pipe(
88 Effect.provide(
89 Layer.mergeAll(
90 Cloudflare.R2.ReadWriteBucketBinding,
91 Cloudflare.QueueBindingLive,
92 Cloudflare.Queues.EventSourceLive, // required for `messages(...)` to dispatch
93 ),
94 ),
95 ),
96) {}
97```
98
99The full round-trip lives in
100[`examples/cloudflare-worker/src/Api.ts`](https://github.com/alchemy-run/alchemy/blob/main/examples/cloudflare-worker/src/Api.ts).
101[Tutorial › Queue consumer](/cloudflare/messaging/queues)
102
103## R2 bucket custom domains
104
105Attach custom hostnames to an R2 bucket directly from the
106resource declaration. Add or remove entries by editing the
107array — Alchemy reconciles the live attachments on the
108next deploy.
109
110```typescript
111const Assets = yield* Cloudflare.R2.Bucket("Assets", {
112 customDomains: [
113 "assets.example.com",
114 "cdn.example.com",
115 ],
116});
117```
118
119Each domain points at the bucket's public origin; pair
120with a Cloudflare `Zone` if you want the DNS record
121provisioned in the same stack.
122
123— *Thanks to **Alex** ([#241](https://github.com/alchemy-run/alchemy/pull/241))
124for the contribution.*
125
126## Non-string env bindings on Workers
127
128Worker `bindings` now accept non-string values directly —
129numbers, booleans, objects — without manual JSON encoding
130on either side. Alchemy serializes at deploy and the typed
131runtime binding gives you the original shape back.
132
133```typescript
134const Api = yield* Cloudflare.Worker("Api", {
135 main: "./src/worker.ts",
136 bindings: {
137 MAX_ITEMS: 100, // number
138 DEBUG: true, // boolean
139 FEATURES: { beta: true, alpha: false }, // object
140 },
141});
142
143// inside the Worker
144const limit = env.MAX_ITEMS; // ^? number — 100
145const debug = env.DEBUG; // ^? boolean — true
146const features = env.FEATURES; // ^? { beta: boolean; alpha: boolean }
147```
148
149Previously you'd round-trip through `JSON.stringify` at
150the binding site and `JSON.parse` on the runtime side
151with an `as` cast for typing. Now it's just `env.MAX_ITEMS`.
152
153— *Thanks to **Michael K**
154([#269](https://github.com/alchemy-run/alchemy/pull/269))
155for the contribution.*
156
157## Neon logical replication
158
159Opt-in flag on `Neon.Project` for enabling logical
160replication — needed for downstream CDC tools, change-data
161pipelines, and some replicas. Defaults to off.
162
163```typescript
164const Project = yield* Neon.Project("App", {
165 region: "aws-us-east-1",
166 enableLogicalReplication: true,
167});
168```
169
170Toggling the flag on an existing project applies in place —
171no replacement needed.
172
173— *Thanks to **Baptiste Arnaud**
174([#268](https://github.com/alchemy-run/alchemy/pull/268))
175for the contribution.*
176
177## CLI: version-on-npm warning
178
179`alchemy` now checks the latest version on npm at startup
180and prints a one-line nudge when you're behind. Skips in
181CI / non-TTY automatically.
182
183```text
184$ alchemy deploy
185ℹ A newer version of alchemy (2.0.0-beta.36) is available.
186 Run `bun add alchemy@latest` to upgrade.
187```
188
189## CLI: plain-text renderer in non-interactive terminals
190
191The interactive deploy UI now downgrades to a plain-text
192progress renderer when run outside a TTY (CI logs, piped
193output). No more ANSI control sequences cluttering CI logs.
194
195## CLI: `alchemy cloudflare` / `alchemy aws` namespaces
196
197Cloud-scoped subcommands are now grouped under provider
198namespaces — e.g. `alchemy cloudflare state` for inspecting
199the Cloudflare-backed state store. Top-level commands
200(`deploy`, `destroy`, `plan`, `dev`, `test`) are unchanged.
201
202```sh
203alchemy cloudflare state ls # list state entries
204alchemy aws state get <key> # read a state entry
205```
206
207## Fixes worth knowing about
208
209- **Auth providers moved into layers.** The `R` requirement
210 on stack effects is `never` again — credential resolution
211 is internal to the provider layer instead of leaking into
212 the user-facing stack type.
213- **Dev mode Node compatibility & profiles.** Several
214 Node-specific runtime issues in `alchemy dev` fixed,
215 including profile handling for AWS/Cloudflare credentials.
216- **`Consumer.listConsumers` paginates.** Previously
217 truncated at the API's default page size when a Worker
218 was listed as a consumer on many queues. Now paginates
219 and surfaces conflicting consumer registrations with a
220 clear error.
221- **R2 custom domain interface simplified** to a plain
222 string array (was a verbose object shape).
223- **Drizzle schema flag tightened** — only marked as
224 updated when migrations actually drift, not on every
225 `drizzle-kit generate` run.
226- **Vite `builder.sharedConfigBuild` disabled.** Fixes a
227 class of build failures when bundling Workers via the
228 Vite plugin.
229- **Distilled Cloudflare runtime bumped to 0.3.1.**
230
231## Contributors
232
233Big thank-you to everyone who shipped code in this beta:
234
235- **Alex** — R2 bucket custom domains ([#241](https://github.com/alchemy-run/alchemy/pull/241))
236- **Michael K** — non-string env bindings on Workers ([#269](https://github.com/alchemy-run/alchemy/pull/269))
237- **Baptiste Arnaud** — Neon `enableLogicalReplication` ([#268](https://github.com/alchemy-run/alchemy/pull/268))
238
239## Where to go next
240
241- [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta35)
242- [Compare v2.0.0-beta.34 → v2.0.0-beta.35](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.34...v2.0.0-beta.35)