| 1 | --- |
| 2 | title: 2.0.0-beta.63 - Durable Object Transfers & Action Bindings |
| 3 | date: 2026-07-17T06:00:00Z |
| 4 | category: release |
| 5 | excerpt: v2.0.0-beta.63 moves Durable Object classes between Workers with their data intact, lets Actions bind resources and read Outputs with your CLI credentials, and makes `alchemy login` work from any folder. The website now lives at alchemy.run. |
| 6 | --- |
| 7 | |
| 8 | beta.63 moves **Durable Object classes between Workers** with |
| 9 | their data intact, lets **Actions bind resources** and read |
| 10 | Outputs with your CLI credentials, and makes **`alchemy login`** |
| 11 | work from any folder. The website now lives at |
| 12 | [alchemy.run](https://alchemy.run). |
| 13 | |
| 14 | ## Move a Durable Object to another Worker |
| 15 | |
| 16 | A Durable Object class can now move between Workers **with its |
| 17 | data intact** — same instances, same storage — via Cloudflare's |
| 18 | `transferred_classes` migration |
| 19 | ([#803](https://github.com/alchemy-run/alchemy/pull/803)). |
| 20 | |
| 21 | Moves are always declared. A class that disappears from one |
| 22 | Worker and appears on another is ambiguous between "transfer the |
| 23 | data" and "delete it, start fresh", so Alchemy never guesses. |
| 24 | The class states where it came from: |
| 25 | |
| 26 | ```diff lang="typescript" |
| 27 | export class Counter extends Cloudflare.DurableObject< |
| 28 | Counter, |
| 29 | { |
| 30 | increment: () => Effect.Effect<number, never, RuntimeContext>; |
| 31 | get: () => Effect.Effect<number, never, RuntimeContext>; |
| 32 | } |
| 33 | ->()("Counter") {} |
| 34 | +>()("Counter", { transferredFrom: "WorkerA" }) {} |
| 35 | ``` |
| 36 | |
| 37 | Whichever Worker hosts `Counter` next transfers the namespace |
| 38 | from `WorkerA`. `transferredFrom` accepts a Worker logical id, a |
| 39 | physical script name (for cross-stack moves), the Worker class |
| 40 | itself, or a thunk for import cycles. |
| 41 | |
| 42 | The declaration is a host *history*: after a second move you |
| 43 | write `transferredFrom: ["WorkerA", "WorkerB"]`, and the |
| 44 | namespace transfers from whichever listed host currently holds |
| 45 | it — so a stage that lagged behind or skipped a release still |
| 46 | converges. Once a transfer completes, the declaration is inert |
| 47 | and safe to keep in code forever. |
| 48 | |
| 49 | If you move a host without declaring it, the deploy fails |
| 50 | **before any upload** with the typed |
| 51 | `DurableObjectTransferRequired` error, naming the exact |
| 52 | declaration to add. Durable Object data is never silently |
| 53 | destroyed or forked. |
| 54 | |
| 55 | Docs: [Bind to another Worker's Durable |
| 56 | Object](/cloudflare/compute/cross-worker-durable-object). |
| 57 | |
| 58 | ## Bind resources inside Actions |
| 59 | |
| 60 | [Actions](/blog/2026-05-13-beta-38) run arbitrary Effects during |
| 61 | deploy — seed a database, ping Slack, invalidate a cache. They |
| 62 | can now **bind resources** the same way a Worker does, and read |
| 63 | resource **Outputs** |
| 64 | ([#807](https://github.com/alchemy-run/alchemy/pull/807)): |
| 65 | |
| 66 | ```typescript |
| 67 | const Seed = Alchemy.Action( |
| 68 | "Seed", |
| 69 | Effect.gen(function* () { |
| 70 | const db = yield* Cloudflare.D1.QueryDatabase(database); |
| 71 | const databaseId = yield* database.databaseId; |
| 72 | |
| 73 | return Effect.fn(function* () { |
| 74 | yield* db.exec("CREATE TABLE IF NOT EXISTS users (id TEXT, name TEXT)"); |
| 75 | return { databaseId: yield* databaseId }; |
| 76 | }); |
| 77 | }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseLocal)), |
| 78 | ); |
| 79 | ``` |
| 80 | |
| 81 | The new `*Local` layers make this work. They're a third binding |
| 82 | variant alongside `*Binding` (native Worker binding) and `*Http` |
| 83 | (scoped API token): a `*Local` layer authenticates with your |
| 84 | **current CLI credentials** and returns the same typed client, |
| 85 | so an Action running on your machine or in CI talks to the same |
| 86 | data plane your Worker would. Every Cloudflare capability with |
| 87 | an HTTP data plane has one — D1, KV, R2, Queues, DNS, Vectorize, |
| 88 | Tunnel, AI Search, Browser Rendering, and more. |
| 89 | |
| 90 | Reading `yield* database.databaseId` inside an Action also |
| 91 | records a dependency edge, so the Action waits for the database |
| 92 | to exist before it runs. |
| 93 | |
| 94 | ## `alchemy login` works anywhere |
| 95 | |
| 96 | `alchemy login` no longer requires an `alchemy.run.ts` |
| 97 | ([#842](https://github.com/alchemy-run/alchemy/pull/842)). With |
| 98 | no entrypoint in sight, it offers every built-in auth provider |
| 99 | and configures the ones you pick, so you can set up or refresh |
| 100 | credentials from any folder: |
| 101 | |
| 102 | ``` |
| 103 | $ cd /some/empty/dir && alchemy login |
| 104 | No alchemy.run.ts found — using Alchemy's built-in providers. |
| 105 | ◆ Select providers to log in with |
| 106 | │ ◻ AWS |
| 107 | │ ◻ Axiom |
| 108 | │ ◻ Cloudflare |
| 109 | │ ◼ GitHub (configured) |
| 110 | │ ◻ Neon |
| 111 | │ ◻ Planetscale |
| 112 | ``` |
| 113 | |
| 114 | Already-configured providers are pre-selected and labeled. In |
| 115 | CI the prompt is skipped and all built-ins are configured |
| 116 | non-interactively. With an `alchemy.run.ts` present, behavior is |
| 117 | unchanged. |
| 118 | |
| 119 | Docs: [alchemy login](/cli/login). |
| 120 | |
| 121 | ## The website moved to alchemy.run |
| 122 | |
| 123 | The v2 site now serves from the apex domain, and |
| 124 | `v2.alchemy.run` 301s to it at the edge |
| 125 | ([#806](https://github.com/alchemy-run/alchemy/pull/806)). The |
| 126 | redirect is itself deployed by Alchemy — a |
| 127 | `Cloudflare.Ruleset` on the adopted zone: |
| 128 | |
| 129 | ```typescript |
| 130 | yield* Cloudflare.Ruleset.Ruleset("V2Redirect", { |
| 131 | zone, |
| 132 | phase: "http_request_dynamic_redirect", |
| 133 | rules: [{ |
| 134 | expression: 'http.host eq "v2.alchemy.run"', |
| 135 | action: "redirect", |
| 136 | actionParameters: { |
| 137 | fromValue: { |
| 138 | targetUrl: { expression: 'concat("https://alchemy.run", http.request.uri.path)' }, |
| 139 | preserveQueryString: true, |
| 140 | statusCode: 301, |
| 141 | }, |
| 142 | }, |
| 143 | }], |
| 144 | }); |
| 145 | ``` |
| 146 | |
| 147 | ## A new test runner |
| 148 | |
| 149 | This release also lands `alchemy-test`, the Effect-native test |
| 150 | runner that executes our 4,000+ live-cloud test suite in a |
| 151 | single bun process |
| 152 | ([#848](https://github.com/alchemy-run/alchemy/pull/848)). It |
| 153 | got its own post: [A Test Runner for Agents and |
| 154 | Humans](/blog/2026-07-22-a-test-runner-for-agents). |
| 155 | |
| 156 | ## Also in this release |
| 157 | |
| 158 | - **Durable Object metadata packs into a single script tag** |
| 159 | ([#824](https://github.com/alchemy-run/alchemy/pull/824)) — |
| 160 | one tag per DO binding blew Cloudflare's 10-tag-per-Worker |
| 161 | limit at 7+ DOs; all mappings now fit in one tag. |
| 162 | - **Vite-based Workers hash sibling workspace packages** |
| 163 | ([#822](https://github.com/alchemy-run/alchemy/pull/822)) — |
| 164 | editing only a sibling package used to skip the deploy and |
| 165 | serve stale code. |
| 166 | - **Alchemy's Vite plugin no longer double-registers** |
| 167 | ([#833](https://github.com/alchemy-run/alchemy/pull/833)) — |
| 168 | apps that also build standalone keep the plugin in their |
| 169 | `vite.config.ts`; Alchemy signals its own injection via |
| 170 | `ALCHEMY_CLOUDFLARE_VITE_INJECTED`. Thanks **Alex**! |
| 171 | - **MySQL replicas reconcile in place** |
| 172 | ([#825](https://github.com/alchemy-run/alchemy/pull/825)) — |
| 173 | changing `replicas` on a PlanetScale MySQL database resizes |
| 174 | the keyspace instead of replacing the database. |
| 175 | - **Unresolved Outputs never persist in state** |
| 176 | ([#835](https://github.com/alchemy-run/alchemy/pull/835)) — |
| 177 | an interrupted create could snapshot live `Output` proxies |
| 178 | into state and poison later reads. |
| 179 | - **Auto-bound `Config` resolves in request handlers** |
| 180 | ([#829](https://github.com/alchemy-run/alchemy/pull/829)) — |
| 181 | re-yielding a `Config` inside a handler decoded the secret |
| 182 | marker JSON instead of the value. |
| 183 | - **`proxyChain` values pass `Effect.isEffect`** |
| 184 | ([#827](https://github.com/alchemy-run/alchemy/pull/827)) — |
| 185 | chained Drizzle clients now work with predicate-dispatched |
| 186 | operators like `Effect.catchTag`. Thanks **Julian Archila**! |
| 187 | |
| 188 | ## Where to go next |
| 189 | |
| 190 | - [Bind to another Worker's Durable Object](/cloudflare/compute/cross-worker-durable-object) |
| 191 | - [alchemy login](/cli/login) |
| 192 | - [A Test Runner for Agents and Humans](/blog/2026-07-22-a-test-runner-for-agents) |
| 193 | - [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta63) |
| 194 | - [Compare v2.0.0-beta.62 → v2.0.0-beta.63](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.62...v2.0.0-beta.63) |