1---
2title: 2.0.0-beta.36 - Images & Hyperdrive Bindings
3date: 2026-05-09
4category: release
5excerpt: Cloudflare Images binding, Hyperdrive as a Worker binding, R2 object lifecycle rules, and a dev-mode networking fix.
6---
7
8`v2.0.0-beta.36` is mostly a Cloudflare-themed release —
9three new Worker bindings (Images, Hyperdrive,
10R2 lifecycle policies), all from outside the core team.
11Props to the contributors throughout, and full credits in
12the [Contributors section](#contributors).
13
14## Cloudflare Images binding
15
16Cloudflare's image transformation runtime, exposed as a
17Worker binding. The Effect-native client takes an Effect
18`Stream<Uint8Array>` input — typically the request body —
19and either streams a transformed image back out or returns
20typed metadata.
21
22```typescript
23// alchemy.run.ts — declare the pipeline resource
24export const Pipeline = yield* Cloudflare.Images.Images({ name: "PIPELINE" });
25
26// in your Worker
27export default class ImageWorker extends Cloudflare.Worker<ImageWorker>()(
28 "ImageWorker",
29 { main: import.meta.filename },
30 Effect.gen(function* () {
31 const images = yield* Cloudflare.Images.bind(Pipeline);
32
33 return {
34 fetch: Effect.gen(function* () {
35 const request = yield* HttpServerRequest;
36
37 // Probe the upload — returns format, width, height, etc.
38 if (request.url.endsWith("/info")) {
39 const info = yield* images.info(request.stream);
40 return yield* HttpServerResponse.json(info);
41 }
42
43 // Transform: resize to 512×512 WebP, stream the result back.
44 const transformed = yield* images
45 .input(request.stream)
46 .transform({ width: 512, height: 512 })
47 .output({ format: "image/webp" });
48 return HttpServerResponse.stream(transformed.body);
49 }),
50 };
51 }).pipe(Effect.provide(Cloudflare.ImagesBindingLive)),
52) {}
53```
54
55— *Thanks to **Alex** ([#237](https://github.com/alchemy-run/alchemy/pull/237))
56for the contribution.*
57[`Images.ts`](https://github.com/alchemy-run/alchemy/blob/main/packages/alchemy/src/Cloudflare/Images/Images.ts)
58
59## Hyperdrive in Worker bindings
60
61`Hyperdrive` resources are bindable on Workers the same way
62as R2, KV, D1. Two flavors to know:
63
64**Declare a Hyperdrive over an existing origin string.**
65Useful when the database lives outside of Alchemy (existing
66RDS, Supabase, etc.):
67
68```typescript
69const Hyperdrive = yield* Cloudflare.Hyperdrive("DbPool", {
70 origin: { connectionString: process.env.POSTGRES_URL! },
71});
72```
73
74**Wire it onto a Neon branch you already deployed.**
75`Neon.Branch` exposes a pre-parsed `origin` output that
76`Cloudflare.Hyperdrive` takes directly — Alchemy orders the
77deploy graph correctly:
78
79```typescript
80// src/Db.ts
81export const NeonDb = Effect.gen(function* () {
82 const project = yield* Neon.Project("app-db", { region: "aws-us-east-1" });
83 const branch = yield* Neon.Branch("app-branch", { project });
84 return { project, branch };
85});
86
87export const Hyperdrive = Effect.gen(function* () {
88 const { branch } = yield* NeonDb;
89 return yield* Cloudflare.Hyperdrive("app-hyperdrive", {
90 origin: branch.origin, // direct (non-pooled) Neon endpoint
91 });
92});
93```
94
95Bind it on the Worker and read the pooled connection
96string at runtime — Hyperdrive does the connection pooling
97so the Worker can spin up a fresh `pg` client per request
98without exhausting the database:
99
100```typescript
101export default class Api extends Cloudflare.Worker<Api>()(
102 "Api",
103 {
104 main: import.meta.filename,
105 compatibility: { flags: ["nodejs_compat"] },
106 },
107 Effect.gen(function* () {
108 const hd = yield* Cloudflare.Hyperdrive.bind(Hyperdrive);
109
110 return {
111 fetch: Effect.gen(function* () {
112 const connectionString = yield* hd.connectionString;
113 const rows = yield* Effect.promise(async () => {
114 const client = new Client({ connectionString });
115 await client.connect();
116 try {
117 const r = await client.query("SELECT now() as now");
118 return r.rows;
119 } finally {
120 await client.end().catch(() => {});
121 }
122 });
123 return yield* HttpServerResponse.json({ rows });
124 }),
125 };
126 }).pipe(Effect.provide(Cloudflare.HyperdriveConnectionLive)),
127) {}
128```
129
130— *Thanks to **Baptiste Arnaud**
131([#282](https://github.com/alchemy-run/alchemy/pull/282))
132for the contribution.*
133[Tutorial › Hyperdrive](/cloudflare/data/hyperdrive)
134
135## R2 lifecycle rules
136
137Object lifecycle policies on `Bucket` — age-based
138deletion, storage-class transitions, multipart-upload
139abort — declared inline alongside the rest of the bucket
140config. Max 1000 rules per bucket; pass an empty array (or
141omit) to clear all rules.
142
143```typescript
144const Logs = yield* Cloudflare.R2.Bucket("Logs", {
145 lifecycleRules: [
146 {
147 id: "archive-then-delete",
148 prefix: "logs/",
149 // After 60 days: move to Infrequent Access.
150 storageClassTransitions: [
151 {
152 condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
153 storageClass: "InfrequentAccess",
154 },
155 ],
156 // After 365 days: delete entirely.
157 deleteObjectsTransition: {
158 condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
159 },
160 // Stale multipart uploads — drop after 7 days.
161 abortMultipartUploadsTransition: {
162 condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
163 },
164 },
165 ],
166});
167```
168
169Each entry is reconciled in place — edit the array and the
170next deploy applies the delta against R2's live policy.
171
172— *Thanks to **Baptiste Arnaud**
173([#284](https://github.com/alchemy-run/alchemy/pull/284))
174for the contribution.* See [Cloudflare R2 docs › Object
175lifecycles](https://developers.cloudflare.com/r2/buckets/object-lifecycles/)
176for the underlying behavior.
177
178## Fixes worth knowing about
179
180- **`*.localhost` resolution in dev mode.** `bun alchemy
181 dev` now routes `*.localhost` hostnames through a
182 custom undici dispatcher, so cross-Worker `fetch` calls
183 to e.g. `https://backend.localhost` resolve to the local
184 sidecar instead of failing DNS.
185
186## Performance
187
188- **Smoke tests install canaries at the workspace root**
189 rather than per-fixture. Batched smoke runs are
190 noticeably faster on cold caches.
191
192## Contributors
193
194Big thank-you to everyone who shipped code in this beta:
195
196- **Alex** — Cloudflare Images binding ([#237](https://github.com/alchemy-run/alchemy/pull/237))
197- **Baptiste Arnaud** — Hyperdrive in Worker bindings ([#282](https://github.com/alchemy-run/alchemy/pull/282))
198- **Baptiste Arnaud** — R2 lifecycle rules ([#284](https://github.com/alchemy-run/alchemy/pull/284))
199
200## Where to go next
201
202- [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta36)
203- [Compare v2.0.0-beta.35 → v2.0.0-beta.36](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.35...v2.0.0-beta.36)