2.0.0-beta.63 - Durable Object Transfers & Action Bindings
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.
Move a Durable Object to another Worker
Section titled “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).
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:
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.
Bind resources inside Actions
Section titled “Bind resources inside Actions”Actions 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):
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
Section titled “alchemy login works anywhere”alchemy login no longer requires an alchemy.run.ts
(#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 loginNo alchemy.run.ts found — using Alchemy's built-in providers.◆ Select providers to log in with│ ◻ AWS│ ◻ Axiom│ ◻ Cloudflare│ ◼ GitHub (configured)│ ◻ Neon│ ◻ PlanetscaleAlready-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.
The website moved to alchemy.run
Section titled “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). The
redirect is itself deployed by Alchemy — a
Cloudflare.Ruleset on the adopted zone:
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
Section titled “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). It
got its own post: A Test Runner for Agents and
Humans.
Also in this release
Section titled “Also in this release”- Durable Object metadata packs into a single script tag (#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) — editing only a sibling package used to skip the deploy and serve stale code.
- Alchemy’s Vite plugin no longer double-registers
(#833) —
apps that also build standalone keep the plugin in their
vite.config.ts; Alchemy signals its own injection viaALCHEMY_CLOUDFLARE_VITE_INJECTED. Thanks Alex! - MySQL replicas reconcile in place
(#825) —
changing
replicason a PlanetScale MySQL database resizes the keyspace instead of replacing the database. - Unresolved Outputs never persist in state
(#835) —
an interrupted create could snapshot live
Outputproxies into state and poison later reads. - Auto-bound
Configresolves in request handlers (#829) — re-yielding aConfiginside a handler decoded the secret marker JSON instead of the value. proxyChainvalues passEffect.isEffect(#827) — chained Drizzle clients now work with predicate-dispatched operators likeEffect.catchTag. Thanks Julian Archila!