[wrangler] Add experimental support for declarative Durable Object exports#14382
[wrangler] Add experimental support for declarative Durable Object exports#14382petebacondarwin wants to merge 26 commits into
Conversation
🦋 Changeset detectedLatest commit: 73b30d8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Codeowners approval required for this PR:
Show detailed file reviewers
|
|
The validation is well-tested. I've now completed a thorough review of the PR. Let me do a final assessment of the most significant findings:
Let me verify one last potential genuine concern: whether The implementation is thorough, well-documented, well-tested, and gated behind an experimental env var. I examined the core logic paths (validation, conversion, type inference, local-dev DO map, deploy/versions-upload payload resolution, error rendering, env-var gating) and the test coverage. I did not find logic bugs, security issues, backward-compatibility violations, or incorrect API behavior. Backward compatibility is preserved (the LGTM |
9a7ea63 to
8616a55
Compare
|
✅ All changesets look good |
@cloudflare/autoconfig
create-cloudflare
@cloudflare/deploy-helpers
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-auth
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
Addresses the Devin AI review on PR #14382: #14382 (comment) When `X_DO_EXPORTS=true` is set and `exports` has entries in config, `wrangler versions upload` would previously forward the payload to the versions endpoint, which silently drops it (lifecycle changes go through the deploy path only — same long-standing constraint as `migrations`, documented at https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). The user was then left with either a no-op or a confusing legacy migrations error (10061) downstream. Add an early fail-fast guard at the top of `versionsUpload()` that raises a `UserError` pointing the user at `wrangler deploy` before any API calls. The same fail-fast should be extended to `migrations` + `versions upload` for symmetry — tracked as a follow-up alongside DEVX-2572, intentionally out of scope here. Adds a unit test covering the new guard.
penalosa
left a comment
There was a problem hiding this comment.
This is looking good, although I haven't read through the tests yet. I think the main high level thing is that:
- there are lots of comments, quite a few of which jut restate the code
- lots of the comments mention internal things like the spec link/tickets, which won't be helpful for external users
Addresses the Devin AI review on PR #14382: #14382 (comment) When `X_DO_EXPORTS=true` is set and `exports` has entries in config, `wrangler versions upload` would previously forward the payload to the versions endpoint, which silently drops it (lifecycle changes go through the deploy path only — same long-standing constraint as `migrations`, documented at https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). The user was then left with either a no-op or a confusing legacy migrations error (10061) downstream. Add an early fail-fast guard at the top of `versionsUpload()` that raises a `UserError` pointing the user at `wrangler deploy` before any API calls. The same fail-fast should be extended to `migrations` + `versions upload` for symmetry — tracked as a follow-up alongside DEVX-2572, intentionally out of scope here. Adds a unit test covering the new guard.
2f62e37 to
3a45700
Compare
|
Addressed the review feedback in 91d5bc8:
|
jamesopstad
left a comment
There was a problem hiding this comment.
Posting my comments so far and will continue reviewing the rest of the code later on.
jamesopstad
left a comment
There was a problem hiding this comment.
This is looking great! There's still some usage of "tombstone" in user-facing JSDoc so it would be good to clean all that up.
Addresses the Devin AI review on PR #14382: #14382 (comment) When `X_DO_EXPORTS=true` is set and `exports` has entries in config, `wrangler versions upload` would previously forward the payload to the versions endpoint, which silently drops it (lifecycle changes go through the deploy path only — same long-standing constraint as `migrations`, documented at https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). The user was then left with either a no-op or a confusing legacy migrations error (10061) downstream. Add an early fail-fast guard at the top of `versionsUpload()` that raises a `UserError` pointing the user at `wrangler deploy` before any API calls. The same fail-fast should be extended to `migrations` + `versions upload` for symmetry — tracked as a follow-up alongside DEVX-2572, intentionally out of scope here. Adds a unit test covering the new guard.
3afeaf6 to
3204dea
Compare
Addresses the Devin AI review on PR #14382: #14382 (comment) When `X_DO_EXPORTS=true` is set and `exports` has entries in config, `wrangler versions upload` would previously forward the payload to the versions endpoint, which silently drops it (lifecycle changes go through the deploy path only — same long-standing constraint as `migrations`, documented at https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). The user was then left with either a no-op or a confusing legacy migrations error (10061) downstream. Add an early fail-fast guard at the top of `versionsUpload()` that raises a `UserError` pointing the user at `wrangler deploy` before any API calls. The same fail-fast should be extended to `migrations` + `versions upload` for symmetry — tracked as a follow-up alongside DEVX-2572, intentionally out of scope here. Adds a unit test covering the new guard.
23ac341 to
3cc75dd
Compare
…ebab-case Update the new declarative Durable Object `exports` config (still gated behind `X_DO_EXPORTS`) to match the renamed EWC API surface: - Property `transfer_to_script` -> `transfer_to` on the `transferred` tombstone variant. - Enum values for `type`, `state`, and `storage` are now kebab-case on the wire: `durable_object` -> `durable-object`, `expecting_transfer` -> `expecting-transfer`, `legacy_kv` -> `legacy-kv`. - Reconciliation response envelope fields renamed for symmetry: `to_script` -> `transfer_to`, `from_script` -> `transfer_from`. Touches the config types in `@cloudflare/workers-utils`, the validator, the `@cloudflare/config` converter (the kebab->snake `snakeStorage` helper is now redundant and removed), the deploy-helpers reconciliation renderer, the wrangler runtime consumers, the fixture, the E2E test, and the changeset.
…op `transfer_` prefix on transfer output
- Input field on `state: "transferred"` tombstone: `transfer_to` -> `transferred_to`
(mirrors EWC MR !9791; matches `renamed_to` on `state: "renamed"`).
- @cloudflare/config helper field: `transferTo` -> `transferredTo` for symmetry
with `renamedTo`.
- Reconciliation output types: `ExportsReconciliationTransfer.transfer_to` -> `to`
and `ExportsReconciliationTransferPending.transfer_from` -> `from`, matching
the EWC output wire format (`to`/`from` after !9781) and the existing
`ExportsReconciliationRename { from; to }` shape.
Touches the config types in @cloudflare/workers-utils, the validator,
@cloudflare/config schema + converter, the deploy-helpers reconciliation
renderer, the e2e test, the unit tests, and the changeset.
…TOML to JSONC The e2e suite `durable-objects-exports.test.ts` was seeding 11 `wrangler.toml` fixtures via `helper.seed()`. Rewrite each as `wrangler.jsonc` to match the monorepo convention (AGENTS.md: config examples must use `wrangler.json` (JSONC), not `wrangler.toml`) and the canonical fixture in `fixtures/durable-objects-exports-app/wrangler.jsonc`. Also drops the residual `/ wrangler.toml` mention from the feature changeset so the docs page only steers users at the JSONC variant. No semantic change to the e2e flow — same payloads, same assertions, just encoded in JSONC.
The suite is now gated only by CLOUDFLARE_ACCOUNT_ID, matching the standard wrangler e2e pattern. Tests will fail against staging EWC until the `exports_reconciliation` account entitlement is enabled on the e2e account — that failure is the regression signal we want. Also drops the unused turbo passthrough entry and fixes a stale renderer path in the file-level JSDoc.
…`exports` Expand the inline comment on `canUseNewVersionsDeploymentsApi` in deploy.ts to spell out the user-visible trade-off: a single `wrangler deploy` invocation with `exports` falls back to the legacy PUT endpoint and therefore cannot use gradual rollouts. Users who need gradual rollouts with the declarative flow can still use `wrangler versions upload` + `wrangler versions deploy`. Adds a TODO to drop this branch once the versions response handler plumbs the reconciliation envelope. Mirrors the limitation in the changeset so the constraint is surfaced in release notes. Addresses #14210 (comment).
…rver scenario tag The backend returns `config_references_nonexistent_class` for the class-declared-in-exports-but-not-in-code failure mode, not the speculative `config_export_not_in_code` tag the e2e tests were written against. Update the two affected assertions (one each in the `wrangler deploy` and `wrangler versions upload` blocks) and refresh the file header comment to reflect the now-known tag values.
Three related corrections, all surfaced by running the e2e suite against staging EWC: 1. `wrangler versions upload` cannot apply Durable Object lifecycle changes — same product-design constraint as the legacy `migrations` array (see https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). Drop the `wrangler versions upload` describe block from the e2e file, remove the dead `exports_reconciliation` request/response plumbing from `versions-upload.ts` (imports, response field, success render call, error-path branch), and rewrite the `canUseNewVersionsDeploymentsApi` comment in `deploy.ts` so the `migrations === undefined` and `exports === undefined` branches are explained as the same product-design constraint rather than a temporary server-side gap. 2. Phase 1 of the cross-script transfer flow (`state: "expecting-transfer"` on the target) does not yet support a self-referential `durable_objects.bindings` entry for the transferring class — EWC's binding resolver does not route self-ref bindings through the source's namespace during phase 1 ("TBD #4 lock" in the spec). Drop the binding from step 2 of the `cross-script transfer` describe block, mirroring the EWC integration test `TestExportsTombstone_TransferTwoPhaseWorkflow`, and document the constraint inline. 3. Add a step 4 to the `cross-script transfer` block so the suite also demonstrates the supported rollout: after phase 2 commits the handoff, the target redeploys with the `Widget` binding and the reconciler reports a clean no-op (scenario 2: match). The four-step pattern is now spelled out in the file docstring and the changeset for users following the same flow.
Addresses the Devin AI review on PR #14382: #14382 (comment) When `X_DO_EXPORTS=true` is set and `exports` has entries in config, `wrangler versions upload` would previously forward the payload to the versions endpoint, which silently drops it (lifecycle changes go through the deploy path only — same long-standing constraint as `migrations`, documented at https://developers.cloudflare.com/workers/configuration/versions-and-deployments/#durable-object-migrations). The user was then left with either a no-op or a confusing legacy migrations error (10061) downstream. Add an early fail-fast guard at the top of `versionsUpload()` that raises a `UserError` pointing the user at `wrangler deploy` before any API calls. The same fail-fast should be extended to `migrations` + `versions upload` for symmetry — tracked as a follow-up alongside DEVX-2572, intentionally out of scope here. Adds a unit test covering the new guard.
…lout pending) Removes the client-side fail-fast guard that the previous commit (3a45700) added — the EWC versions POST controller accepts `exports` per do_exports_version_test.go, so Wrangler should forward the payload to the server rather than rejecting it locally. Production rollout for the versions endpoint is still in progress, so the server today returns error 10061 ("Cannot create binding … configure a migration") for declarative `exports` on `wrangler versions upload`. The changeset documents this and points users at `wrangler deploy` until the rollout completes. Tests: - Replaces the negative fail-fast unit test with a positive client-side test asserting that `metadata.exports` is forwarded and `metadata.migrations` is omitted when `X_DO_EXPORTS=true`. - Adds a gate-off unit test covering the `assertDoExportsEnabledIfConfigured("versions upload")` validator. - Adds an e2e block with bootstrap (`wrangler deploy` with `exports`) and gate-off coverage. The two server-dependent steps (`versions upload` and `versions deploy`) are `it.skip`-marked pending the EWC rollout.
…th actionable message EWC now rejects multi-version (percentage-split) deploys whose versions declare divergent declarative DO `exports`. Map the new 100405 error code in `wrangler versions deploy` to a friendly message that preserves the server's explanation and tells the user exactly what to do next: deploy the version that changes `exports` at 100% first, then re-run the percentage-split deploy. - Adds `INCONSISTENT_EXPORTS_ACROSS_VERSIONS_CODE = 100405` constant alongside the existing `EXPORTS_RECONCILIATION_ERROR_CODE = 100402`. - Adds `renderInconsistentExportsAcrossVersionsError()` renderer that surfaces the server message verbatim plus actionable next-steps and a docs link. - Wraps the `createDeployment` call in `wrangler versions deploy` with a try/catch matching the established rollback pattern. Calls `preventReport()` to suppress Sentry on this user-fixable error. - Unrelated EWC error codes pass through unchanged. - Rolls into the existing unreleased DO exports changeset.
vitest-pool-workers already inherits Durable Object `exports` config via `unstable_getMiniflareWorkerOptions` (SQLite storage + unbound DOs reached through `ctx.exports`). Enforce the `X_DO_EXPORTS` opt-in gate on this path too so tests can't drift from `wrangler dev`/`deploy` semantics, and add a fixture + unit tests covering the flow.
… exports class) with actionable message EWC now translates the confusing 10061 binding error to a clear 100406 (ErrActorBindingDependsOnExport) when a `versions upload` payload combines `exports` with a binding to a not-yet-provisioned class (see edgeworker-config-service!9957). Map it to a UserError that surfaces the server's already-actionable message verbatim. Also unskips the previously-deferred `versions upload` e2e steps to use the realistic flow: stage new classes via `ctx.exports` (no binding) on `versions upload`, then add the binding when running `versions deploy`.
b5a8cc0 to
73b30d8
Compare
Fixes DEVX-2572.
Adds client-side wiring for the EWC declarative Durable Object exports flow.
What this adds
A new declarative
exportsmap inwrangler.json/wrangler.tomlas an alternative to the legacymigrationsarray, gated behind theX_DO_EXPORTSenvironment variable:{ "exports": { "MyDO": { "type": "durable_object", "storage": "sqlite" }, "OldGone": { "type": "durable_object", "state": "deleted" }, "OldName": { "type": "durable_object", "state": "renamed", "renamed_to": "NewName" }, "Movee": { "type": "durable_object", "state": "transferred", "transfer_to_script": "target-worker" }, "Incoming": { "type": "durable_object", "state": "expecting_transfer", "storage": "sqlite", "transfer_from": "source-worker" } } }typecarries the export kind (currently always"durable_object"); the newstatefield carries the lifecycle and defaults to"created"(live) when omitted. Tombstone states (deleted,renamed,transferred) retire / rename / transfer a provisioned namespace;expecting_transferis a live state that names the receiving side of a two-phase cross-script transfer.Both
wrangler deployandwrangler versions uploadsend the new payload whenX_DO_EXPORTS=trueis set:The upload response's
exports_reconciliationenvelope is rendered with the spec's visibility hierarchy:✘per-class detail (rendered from the v4 envelope'smeta.details[]), thrown as aUserErrorso every blocking scenario surfaces in one round tripblocked_bylists for stale tombstonesexports" hintLocal-dev support
Local development (
wrangler devandunstable_startWorker) now reads Durable Object SQLite storage settings from the newexportsfield, so applications using the declarative flow get correct local-dev storage without needing to also declare amigrationsblock. Live entries (state: "created"andstate: "expecting_transfer") contribute the class to the local-dev DO map; tombstones do not.The validator's "no lifecycle declared" warning is now
exports-aware:exportsentries (live or tombstone), the warning suggests extending theexportsmap.X_DO_EXPORTS=trueis set, the warning also suggestsexports.migrationswarning fires.The helper that drives this also got a more accurate rename:
warnIfDurableObjectsHaveNoMigrations→warnIfDurableObjectsHaveNoLifecycleConfig.@cloudflare/configintegrationThe new
@cloudflare/configpackage'sexports.durableObject()is extended with tombstone factories and anexpectingTransfer()factory. The TS-facing API mirrors the wrangler/EWC structural shape with two stylistic transforms:renamedTo,transferToScript,transferFrom)type: "durable-object",state: "expecting-transfer",storage: "legacy-kv")convertExportsperforms the snake_case / underscore conversion at the wrangler boundary.InferDurableNamespaces<T>now correctly filters tombstones from binding-target inference, so adurableObject()typed binding can only reference a live class.Fixture
fixtures/durable-objects-exports-app/— a two-DO fixture that exercises the new shape end-to-end:CounterA,CounterB)deletedtombstone (OldCounter)renamedtombstone (LegacyName→CounterB)ctx.storage.sql.exec("SELECT 1")to prove SQLite storage is honored in local dev via the newexportsmapTested with
unstable_startWorker.Server dependencies
Requires the
exports_reconciliationaccount entitlement on Cloudflare's side. Hidden behind theX_DO_EXPORTSenv var until that gate ships broadly.Server-side phases: DEVX-2563 (1a), DEVX-2598 (1b), DEVX-2564 (2), DEVX-2600 (1d). DEVX-2599 (1c) adds full
transferredsupport — until that lands, thetransferredtombstone returns a "not yet implemented" error from EWC, which is rendered correctly via the samemeta.detailspath.Spec
https://wiki.cfdata.org/spaces/WX/pages/1396640001
A picture of a cute animal (not mandatory, but encouraged)