Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions website/src/content/docs/blog/2026-07-17-beta-63.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
title: 2.0.0-beta.63 - Durable Object Transfers & Action Bindings
date: 2026-07-17T06:00:00Z
category: release
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.
---

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](https://alchemy.run).

## Move a Durable Object to another Worker

A Durable Object class can now move between Workers **with its
data intact** — same instances, same storage — via Cloudflare's
`transferred_classes` migration
([#803](https://github.com/alchemy-run/alchemy/pull/803)).

Moves are always declared. A class that disappears from one
Worker and appears on another is ambiguous between "transfer the
data" and "delete it, start fresh", so Alchemy never guesses.
The class states where it came from:

```diff lang="typescript"
export class Counter extends Cloudflare.DurableObject<
Counter,
{
increment: () => Effect.Effect<number, never, RuntimeContext>;
get: () => Effect.Effect<number, never, RuntimeContext>;
}
->()("Counter") {}
+>()("Counter", { transferredFrom: "WorkerA" }) {}
```

Whichever Worker hosts `Counter` next transfers the namespace
from `WorkerA`. `transferredFrom` accepts a Worker logical id, a
physical script name (for cross-stack moves), the Worker class
itself, or a thunk for import cycles.

The declaration is a host *history*: after a second move you
write `transferredFrom: ["WorkerA", "WorkerB"]`, and the
namespace transfers from whichever listed host currently holds
it — so a stage that lagged behind or skipped a release still
converges. Once a transfer completes, the declaration is inert
and safe to keep in code forever.

If you move a host without declaring it, the deploy fails
**before any upload** with the typed
`DurableObjectTransferRequired` error, naming the exact
declaration to add. Durable Object data is never silently
destroyed or forked.

Docs: [Bind to another Worker's Durable
Object](/cloudflare/compute/cross-worker-durable-object).

## Bind resources inside Actions

[Actions](/blog/2026-05-13-beta-38) run arbitrary Effects during
deploy — seed a database, ping Slack, invalidate a cache. They
can now **bind resources** the same way a Worker does, and read
resource **Outputs**
([#807](https://github.com/alchemy-run/alchemy/pull/807)):

```typescript
const Seed = Alchemy.Action(
"Seed",
Effect.gen(function* () {
const db = yield* Cloudflare.D1.QueryDatabase(database);
const databaseId = yield* database.databaseId;

return Effect.fn(function* () {
yield* db.exec("CREATE TABLE IF NOT EXISTS users (id TEXT, name TEXT)");
return { databaseId: yield* databaseId };
});
}).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseLocal)),
);
```

The new `*Local` layers make this work. They're a third binding
variant alongside `*Binding` (native Worker binding) and `*Http`
(scoped API token): a `*Local` layer authenticates with your
**current CLI credentials** and returns the same typed client,
so an Action running on your machine or in CI talks to the same
data plane your Worker would. Every Cloudflare capability with
an HTTP data plane has one — D1, KV, R2, Queues, DNS, Vectorize,
Tunnel, AI Search, Browser Rendering, and more.

Reading `yield* database.databaseId` inside an Action also
records a dependency edge, so the Action waits for the database
to exist before it runs.

## `alchemy login` works anywhere

`alchemy login` no longer requires an `alchemy.run.ts`
([#842](https://github.com/alchemy-run/alchemy/pull/842)). With
no entrypoint in sight, it offers every built-in auth provider
and configures the ones you pick, so you can set up or refresh
credentials from any folder:

```
$ cd /some/empty/dir && alchemy login
No alchemy.run.ts found — using Alchemy's built-in providers.
◆ Select providers to log in with
│ ◻ AWS
│ ◻ Axiom
│ ◻ Cloudflare
│ ◼ GitHub (configured)
│ ◻ Neon
│ ◻ Planetscale
```

Already-configured providers are pre-selected and labeled. In
CI the prompt is skipped and all built-ins are configured
non-interactively. With an `alchemy.run.ts` present, behavior is
unchanged.

Docs: [alchemy login](/cli/login).

## The website moved to alchemy.run

The v2 site now serves from the apex domain, and
`v2.alchemy.run` 301s to it at the edge
([#806](https://github.com/alchemy-run/alchemy/pull/806)). The
redirect is itself deployed by Alchemy — a
`Cloudflare.Ruleset` on the adopted zone:

```typescript
yield* Cloudflare.Ruleset.Ruleset("V2Redirect", {
zone,
phase: "http_request_dynamic_redirect",
rules: [{
expression: 'http.host eq "v2.alchemy.run"',
action: "redirect",
actionParameters: {
fromValue: {
targetUrl: { expression: 'concat("https://alchemy.run", http.request.uri.path)' },
preserveQueryString: true,
statusCode: 301,
},
},
}],
});
```

## A new test runner

This release also lands `alchemy-test`, the Effect-native test
runner that executes our 4,000+ live-cloud test suite in a
single bun process
([#848](https://github.com/alchemy-run/alchemy/pull/848)). It
got its own post: [A Test Runner for Agents and
Humans](/blog/2026-07-22-a-test-runner-for-agents).

## Also in this release

- **Durable Object metadata packs into a single script tag**
([#824](https://github.com/alchemy-run/alchemy/pull/824)) —
one tag per DO binding blew Cloudflare's 10-tag-per-Worker
limit at 7+ DOs; all mappings now fit in one tag.
- **Vite-based Workers hash sibling workspace packages**
([#822](https://github.com/alchemy-run/alchemy/pull/822)) —
editing only a sibling package used to skip the deploy and
serve stale code.
- **Alchemy's Vite plugin no longer double-registers**
([#833](https://github.com/alchemy-run/alchemy/pull/833)) —
apps that also build standalone keep the plugin in their
`vite.config.ts`; Alchemy signals its own injection via
`ALCHEMY_CLOUDFLARE_VITE_INJECTED`. Thanks **Alex**!
- **MySQL replicas reconcile in place**
([#825](https://github.com/alchemy-run/alchemy/pull/825)) —
changing `replicas` on a PlanetScale MySQL database resizes
the keyspace instead of replacing the database.
- **Unresolved Outputs never persist in state**
([#835](https://github.com/alchemy-run/alchemy/pull/835)) —
an interrupted create could snapshot live `Output` proxies
into state and poison later reads.
- **Auto-bound `Config` resolves in request handlers**
([#829](https://github.com/alchemy-run/alchemy/pull/829)) —
re-yielding a `Config` inside a handler decoded the secret
marker JSON instead of the value.
- **`proxyChain` values pass `Effect.isEffect`**
([#827](https://github.com/alchemy-run/alchemy/pull/827)) —
chained Drizzle clients now work with predicate-dispatched
operators like `Effect.catchTag`. Thanks **Julian Archila**!

## Where to go next

- [Bind to another Worker's Durable Object](/cloudflare/compute/cross-worker-durable-object)
- [alchemy login](/cli/login)
- [A Test Runner for Agents and Humans](/blog/2026-07-22-a-test-runner-for-agents)
- [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta63)
- [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)
Loading