feat(cloudflare/workers): declare Durable Object class transfers between workers#803
Open
sam-goodwin wants to merge 5 commits into
Open
feat(cloudflare/workers): declare Durable Object class transfers between workers#803sam-goodwin wants to merge 5 commits into
sam-goodwin wants to merge 5 commits into
Conversation
…erredFrom Moving a Durable Object class from one Worker to another previously failed: the former host emitted a deleted_classes migration in the same upload as the new cross-script binding, which Cloudflare rejects — and applying the delete would have destroyed the namespace's data anyway. - add `transferredFrom` to DurableObject declarations (props form + class form); the new host's deploy drives Cloudflare's data-preserving transferred_classes migration - the former host observes namespace ownership before emitting deleted_classes: a class transferred away is skipped, a class still hosted locally but re-bound cross-script fails before any upload with the typed DurableObjectTransferRequired error - exclude transfer-marked classes from the precreate placeholder (Cloudflare forbids pre-creating the destination class of a transfer) Closes #799 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Install the packages built from this commit: alchemy bun add alchemy@https://pkg.ing/alchemy/21b9a8c@alchemy.run/better-auth bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/21b9a8c@alchemy.run/pr-package bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/21b9a8c |
Host moves are now inferred like class renames — no configuration needed
when both Workers deploy from the same stack:
- a cross-script DO declaration whose scriptName carries resource
provenance (worker.workerName, Counter.from(Worker)) binds a literal
transfer hint {logicalId, className, fromWorkerId} onto the target at
plan time (no Outputs, so no dependency edges)
- the target's deploy resolves the hint against observed cloud state —
the source must currently host the namespace and carry the matching
alchemy:stack/stage/id/do tags — and ships transferred_classes; the
tag match is what distinguishes a host move from the documented
two-hosts-two-namespaces pattern
- hint-confirmed transfer classes are excluded from the precreate
placeholder (Cloudflare forbids pre-creating a transfer destination)
- transferredFrom remains only as the explicit escape hatch for
cross-stack moves, which the plan fundamentally cannot see; ambiguous
inference (multiple qualifying sources) fails with the typed
AmbiguousDurableObjectTransfer error instead of guessing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t history Remove hint-based inference — a class deleted on one worker and created on another is ambiguous between "move the data" and "delete + fresh namespace", so moves are always declared, never guessed: - transferredFrom now accepts a host *history* (string | string[]); entries name the former host by Worker logical id (same stack+stage, resolved via alchemy:id/stack/stage ownership tags among scripts observed to host the class) or by physical script name (cross-stack) - the namespace transfers from whichever listed host currently holds it, so lagging stages and skipped intermediate releases converge; when none does (fresh stage, transfer complete) the declaration is inert - an undeclared move still fails before any upload with DurableObjectTransferRequired naming the exact declaration to add; multiple matching sources fail with AmbiguousDurableObjectTransfer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ker guide Adds a 'Move the Durable Object to a new host' walkthrough to the cross-worker Durable Object guide: declaring transferredFrom on the class, swapping host/consumer roles, what the deploy does (transferred_classes, data intact, inert afterwards), the DurableObjectTransferRequired interlock, host history for chained moves, and the pure-move/cross-stack rules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
transferredFrom entries can now be the Worker class or resource itself, or
a thunk for forward references and import cycles:
Cloudflare.DurableObject("Counter", { transferredFrom: b }) // resource
DurableObject<C>()("Counter", { transferredFrom: () => WorkerA }) // class via thunk
References are normalized at plan time to the worker's logical id — never
its Output — because consuming the former host's Output would add a
new-host->former-host dependency edge and invert the deploy order the
transfer requires. A same-stack workerName Output is likewise reduced to
its source resource's logical id via provenance; only opaque Outputs
(cross-stack stackRefs) pass through as values. Platform classes now
expose a static LogicalId to make the class form extractable.
Adds a live test for the documented two-deploy pure move, driven by a
resource reference, and covers the thunk form in the physical-name test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moving a Durable Object class to another Worker is now supported via Cloudflare's data-preserving
transferred_classesmigration. Moves are always declared — a class deleted on one worker and created on another is ambiguous between "move the data" and "delete it, start fresh" (the same reason Terraform requiresmovedblocks), so Alchemy never guesses. The new host names where the class came from:transferredFromsemanticsEach entry names a former host by its Worker logical id in the same stack + stage (resolved via the
alchemy:id/alchemy:stack/alchemy:stageownership tags among scripts observed to currently host the class) or by its physical script name (required for cross-stack moves).Besides strings, entries accept the Worker class or resource itself, or a thunk for forward references and import cycles:
References are normalized at plan time to the worker's logical id — never its Output — since consuming the former host's Output would add a new-host→former-host dependency edge and invert the deploy order the transfer requires. A same-stack
workerNameOutput is likewise reduced to its source resource's logical id via provenance.It's a host history:
transferredFrom: ["WorkerB", "WorkerA"]transfers from whichever listed host currently holds the namespace, so stages that lag behind or skipped an intermediate release still converge. When none does (fresh stage, transfer already completed) the declaration is inert — safe to keep in code indefinitely.Multiple matching sources (e.g. an orphaned script left by a
namechange) fail with the typedAmbiguousDurableObjectTransferrather than guessing whose data to move.Safety interlocks
DurableObjectTransferRequired, naming the exact declaration to add. This replaces Cloudflare's opaquedeleted_classes-vs-binding rejection from the issue; data is never silently destroyed or forked.deleted_classesagainst observed namespace ownership (with a short bounded re-observation for listing lag): a class that transferred away is skipped, so the former host converges on its own.transferredFromand deploy, then remove from the former host — since nothing orders a single deploy's transfer before the former host's delete. Documented on the resource.Verified live in
DurableObjectNamespace.test.ts: a move declared by logical id preserves data across the transfer and is inert on redeploy; a move declared by physical script name via a thunk (the cross-stack shape) transfers as well; the documented two-deploy pure move driven by a resource reference transfers and the former host's DO removal converges without a delete; an undeclared move fails withDurableObjectTransferRequired.Closes #799
🤖 Generated with Claude Code