1---
2title: 2.0.0-beta.49 - DNS Bindings & Output.flatMap
3date: 2026-06-01T19:00:00Z
4category: release
5excerpt: v2.0.0-beta.49 adds runtime DNS record bindings, an `Output.flatMap` combinator (plus method-style `.map`/`.flatMap`), and a hotfix that ensures `Config` values yielded in a Worker are always bound as `secret_text`, not plain text.
6---
7
8:::caution
9**Security hotfix — upgrade if you bind secrets via `Config`.**
10Some `Config` values `yield*`ed in a Worker's Init phase were
11being bound as **plain text** instead of `secret_text`. beta.49
12ensures every yielded `Config` is bound as `secret_text`. If
13you've deployed secrets through `Config` on an earlier beta,
14upgrade and redeploy so they're stored encrypted.
15:::
16
17`v2.0.0-beta.49` ships runtime **DNS record bindings**, a new
18**`Output.flatMap`** combinator, and the `secret_text` fix above.
19
20## DNS record bindings
21
22Same ergonomics as the [Tunnel bindings](/blog/2026-06-01-beta-48)
23from beta.48: bind a `Zone` once in Init, and a Worker can manage
24that zone's DNS records at runtime through a typed client. Calls
25take no `zoneId` — the bound zone fixes it.
26
27```typescript
28const Zone = Cloudflare.Zone.Zone("MyZone", { name: "example.com" });
29
30export class Worker extends Cloudflare.Worker<Worker>()(
31 "Worker",
32 { main: import.meta.filename },
33 Effect.gen(function* () {
34 // bind the Zone (it's an Effect) — or `yield* Zone` first
35 const dns = yield* Cloudflare.DnsReadWrite.bind(Zone);
36
37 return {
38 fetch: Effect.gen(function* () {
39 const { result } = yield* dns.createDnsRecord({
40 type: "A",
41 name: "app.example.com",
42 content: "192.0.2.1",
43 ttl: 1,
44 });
45 yield* dns.deleteDnsRecord(result.id);
46 return yield* HttpServerResponse.json({ id: result.id });
47 }),
48 };
49 }).pipe(Effect.provide(Cloudflare.DnsReadWriteLive)),
50) {}
51```
52
53Pick the surface you need:
54
55- `Cloudflare.DNS.ReadDns` — `getDnsRecord`, `listDnsRecords`
56- `Cloudflare.DNS.WriteDns` — `createDnsRecord`, `updateDnsRecord`, `patchDnsRecord`, `deleteDnsRecord`, `batchDnsRecords`
57- `Cloudflare.DNS.ReadWriteDns` — the full record CRUD surface
58
59:::note[Under the covers]
60Like the Tunnel bindings, binding a DNS client creates a
61least-privilege `AccountApiToken` for the Worker, scoped to that
62**single zone** with just the `DNS Read` / `DNS Write`
63permissions, and binds its value (plus the zone id) into the
64Worker as `secret_text`. The policy resource is the zone itself,
65derived from the zone's id:
66
67```typescript
68resources: {
69 [`com.cloudflare.api.account.zone.${zoneId}`]: "*",
70}
71```
72:::
73
74([#511](https://github.com/alchemy-run/alchemy/pull/511))
75
76## `Output.flatMap`
77
78`Output.flatMap` resolves an `Output<A>`, hands the value to `f`,
79and flattens the `Output<B>` that `f` returns — the missing piece
80alongside `Output.map` and `Output.mapEffect`:
81
82```typescript
83const expr = zone.zoneId.pipe(
84 Output.flatMap((zoneId) =>
85 Output.interpolate`com.cloudflare.api.account.zone.${zoneId}`,
86 ),
87);
88```
89
90The combinators are now also callable as **methods** directly on
91an `Output`, with `apply`/`effect` kept as aliases:
92
93```typescript
94output.map(fn); // ApplyExpr
95output.mapEffect(fn); // EffectExpr
96output.flatMap(fn); // FlatMapExpr
97```
98
99Like the other combinators, `flatMap` short-circuits while its
100source is unresolved, so it composes cleanly in the deploy graph.
101([#512](https://github.com/alchemy-run/alchemy/pull/512))
102
103## The `secret_text` fix in detail
104
105Two related changes land the fix:
106
107- **Yielded `Config` always binds as `secret_text`** — the
108 Worker now records every `Config` resolved in Init as a secret
109 binding, so a value can't slip through as plain text
110 ([#510](https://github.com/alchemy-run/alchemy/pull/510)).
111- **Don't unwrap non-redacted values** — when reading a bound
112 value back at runtime, Alchemy now only calls `Redacted.value`
113 on values that are actually `Redacted`, leaving plain values
114 untouched.
115
116## Where to go next
117
118- [Concepts › Secrets and Config](/environments/secrets)
119- [beta.48 — Tunnel bindings](/blog/2026-06-01-beta-48)
120- [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta49)
121- [Compare v2.0.0-beta.48 → v2.0.0-beta.49](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.48...v2.0.0-beta.49)