Skip to content

[wrangler] Add experimental support for declarative Durable Object exports#14382

Open
petebacondarwin wants to merge 26 commits into
mainfrom
pbacondarwin/devx-2572-wrangler-do-exports
Open

[wrangler] Add experimental support for declarative Durable Object exports#14382
petebacondarwin wants to merge 26 commits into
mainfrom
pbacondarwin/devx-2572-wrangler-do-exports

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Fixes DEVX-2572.

Adds client-side wiring for the EWC declarative Durable Object exports flow.

What this adds

A new declarative exports map in wrangler.json / wrangler.toml as an alternative to the legacy migrations array, gated behind the X_DO_EXPORTS environment 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"
		}
	}
}

type carries the export kind (currently always "durable_object"); the new state field carries the lifecycle and defaults to "created" (live) when omitted. Tombstone states (deleted, renamed, transferred) retire / rename / transfer a provisioned namespace; expecting_transfer is a live state that names the receiving side of a two-phase cross-script transfer.

Both wrangler deploy and wrangler versions upload send the new payload when X_DO_EXPORTS=true is set:

X_DO_EXPORTS=true wrangler deploy

The upload response's exports_reconciliation envelope is rendered with the spec's visibility hierarchy:

  • errors → red per-class detail (rendered from the v4 envelope's meta.details[]), thrown as a UserError so every blocking scenario surfaces in one round trip
  • warnings → yellow, prominent (reserved — no current server scenarios emit one)
  • info → dim text, with per-entry blocked_by lists for stale tombstones
  • removable_entries → single-line "you can delete these from exports" hint

Local-dev support

Local development (wrangler dev and unstable_startWorker) now reads Durable Object SQLite storage settings from the new exports field, so applications using the declarative flow get correct local-dev storage without needing to also declare a migrations block. Live entries (state: "created" and state: "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:

  • if the config already declares any exports entries (live or tombstone), the warning suggests extending the exports map.
  • if neither lifecycle is declared but X_DO_EXPORTS=true is set, the warning also suggests exports.
  • otherwise, the existing migrations warning fires.

The helper that drives this also got a more accurate rename: warnIfDurableObjectsHaveNoMigrationswarnIfDurableObjectsHaveNoLifecycleConfig.

@cloudflare/config integration

The new @cloudflare/config package's exports.durableObject() is extended with tombstone factories and an expectingTransfer() factory. The TS-facing API mirrors the wrangler/EWC structural shape with two stylistic transforms:

  • property names are camelCased (renamedTo, transferToScript, transferFrom)
  • enumeration values are kebab-cased (type: "durable-object", state: "expecting-transfer", storage: "legacy-kv")

convertExports performs the snake_case / underscore conversion at the wrangler boundary. InferDurableNamespaces<T> now correctly filters tombstones from binding-target inference, so a durableObject() 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:

  • two live SQLite-backed Durable Objects (CounterA, CounterB)
  • a deleted tombstone (OldCounter)
  • a renamed tombstone (LegacyNameCounterB)
  • a diagnostic endpoint that runs ctx.storage.sql.exec("SELECT 1") to prove SQLite storage is honored in local dev via the new exports map

Tested with unstable_startWorker.

Server dependencies

Requires the exports_reconciliation account entitlement on Cloudflare's side. Hidden behind the X_DO_EXPORTS env 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 transferred support — until that lands, the transferred tombstone returns a "not yet implemented" error from EWC, which is rendered correctly via the same meta.details path.

Spec

https://wiki.cfdata.org/spaces/WX/pages/1396640001


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: customer-facing docs are tracked separately in DEVX-2569.

A picture of a cute animal (not mandatory, but encouraged)


Open in Devin Review

@petebacondarwin petebacondarwin added the ci:run-remote-tests Run remote/E2E tests that require Cloudflare API credentials label Jun 21, 2026
@petebacondarwin petebacondarwin added the ci:run-remote-tests Run remote/E2E tests that require Cloudflare API credentials label Jun 21, 2026
@petebacondarwin petebacondarwin requested review from a team and james-elicx June 21, 2026 16:45
@changeset-bot

changeset-bot Bot commented Jun 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 73b30d8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Minor
@cloudflare/vite-plugin Minor
@cloudflare/vitest-pool-workers Minor

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

@workers-devprod

workers-devprod commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/cloudchamber
  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/devx-2572-wrangler-do-exports.md: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/package.json: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/src/index.ts: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/tests/index.test.ts: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/tests/tsconfig.json: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/tsconfig.json: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/vitest.config.mts: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/worker-configuration.d.ts: [@cloudflare/wrangler]
  • fixtures/durable-objects-exports-app/wrangler.jsonc: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/README.md: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/src/env.d.ts: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/src/index.ts: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/src/tsconfig.json: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/test/exports.test.ts: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/test/tsconfig.json: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/tsconfig.json: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/vitest.config.ts: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/durable-objects-exports/wrangler.jsonc: [@cloudflare/wrangler]
  • fixtures/vitest-pool-workers-examples/package.json: [@cloudflare/wrangler]
  • packages/config/src/tests/convert.test.ts: [@cloudflare/wrangler]
  • packages/config/src/convert.ts: [@cloudflare/wrangler]
  • packages/config/src/exports.ts: [@cloudflare/wrangler]
  • packages/config/src/inference.ts: [@cloudflare/wrangler]
  • packages/config/src/public.ts: [@cloudflare/wrangler]
  • packages/config/src/schema.ts: [@cloudflare/wrangler]
  • packages/config/src/types.ts: [@cloudflare/wrangler]
  • packages/config/src/worker-definition.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/deploy.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/binding-depends-on-export.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/create-worker-upload-form.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/durable.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/error-codes.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/exports-reconciliation.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/helpers/inconsistent-exports.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/deploy/versions-upload.ts: [@cloudflare/wrangler]
  • packages/deploy-helpers/src/index.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/tests/experimental-new-config.spec.ts: [@cloudflare/wrangler]
  • packages/vite-plugin-cloudflare/src/export-types.ts: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/src/pool/config.ts: [@cloudflare/wrangler]
  • packages/vitest-pool-workers/test/do-exports.test.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/cfetch/errors.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/cfetch/index.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/config.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/durable-object-exports.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/environment.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/config/validation.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/environment-variables/factory.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/environment-variables/misc-variables.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/index.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/parse.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/types.ts: [@cloudflare/wrangler]
  • packages/workers-utils/src/worker.ts: [@cloudflare/wrangler]
  • packages/workers-utils/tests/config/validation/normalize-and-validate-config.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/e2e/durable-objects-exports.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/api/startDevWorker/RemoteRuntimeController.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/create-worker-upload-form/helpers.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/deploy/binding-depends-on-export.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/deploy/durable-objects.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/deploy/exports-reconciliation.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/deploy/inconsistent-exports.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/dev.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/helpers/mock-upload-worker.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/type-generation.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/versions/versions.deploy.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/versions/versions.upload.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/integrations/platform/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/pages/create-worker-bundle-contents.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/ConfigController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/LocalRuntimeController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/containers/config.ts: [@cloudflare/cloudchamber @cloudflare/wrangler]
  • packages/wrangler/src/dev/class-names-sqlite.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev/miniflare/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev/remote.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/durable.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/secret/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/type-generation/index.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/versions/deploy.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/versions/secrets/index.ts: [@cloudflare/wrangler]
  • pnpm-lock.yaml: [@cloudflare/wrangler]

@ask-bonk

ask-bonk Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

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:

  1. renderExportsReconciliationSuccess uses logger.warn(bold(" Warnings:")) and logger.warn(yellow(...)) — this writes warnings to stderr during a successful deploy render. This is intentional per the spec's visibility hierarchy. Not a bug.

  2. The deploy.ts fallback (disabling gradual rollout when exports is set) is documented as an intentional trade-off with a TODO. Acceptable.

  3. The validation logic, conversion, inference, and local-dev wiring all look correct and well-tested.

Let me verify one last potential genuine concern: whether expecting-transfer storage legacy-kv is sensible, and whether the created storage requirement properly rejects missing storage. These are covered by validation.

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 migrations path is unchanged when exports is absent, and the feature is gated behind X_DO_EXPORTS).

LGTM

github run

devin-ai-integration[bot]

This comment was marked as resolved.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

✅ All changesets look good

@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14382

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14382

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14382

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14382

miniflare

npm i https://pkg.pr.new/miniflare@14382

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14382

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14382

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14382

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14382

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14382

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14382

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14382

wrangler

npm i https://pkg.pr.new/wrangler@14382

commit: 73b30d8

devin-ai-integration[bot]

This comment was marked as resolved.

petebacondarwin added a commit that referenced this pull request Jun 22, 2026
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.
devin-ai-integration[bot]

This comment was marked as resolved.

@penalosa penalosa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread .changeset/devx-2572-wrangler-do-exports.md
Comment thread packages/config/src/convert.ts Outdated
Comment thread packages/config/src/convert.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
petebacondarwin added a commit that referenced this pull request Jun 23, 2026
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.
@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch from 2f62e37 to 3a45700 Compare June 23, 2026 14:43
@petebacondarwin

petebacondarwin commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in 91d5bc8:

  • Removed internal spec/wiki/ticket references and neutralized server-internal wording in comments/docs.
  • Trimmed comments that mostly restated code or tests.
  • Made unknown @cloudflare/config export types throw a UserError and added conversion test coverage.

devin-ai-integration[bot]

This comment was marked as resolved.

@jamesopstad jamesopstad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting my comments so far and will continue reviewing the rest of the code later on.

Comment thread .changeset/devx-2572-wrangler-do-exports.md Outdated
Comment thread .changeset/devx-2572-wrangler-do-exports.md
Comment thread .changeset/devx-2572-wrangler-do-exports.md Outdated
Comment thread packages/config/src/__tests__/convert.test.ts
Comment thread packages/config/src/convert.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
Comment thread packages/config/src/exports.ts

@jamesopstad jamesopstad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/config/src/types.ts Outdated
Comment thread packages/deploy-helpers/src/deploy/helpers/durable.ts Outdated
Comment thread packages/deploy-helpers/src/deploy/helpers/durable.ts Outdated
Comment thread packages/deploy-helpers/src/deploy/helpers/durable.ts Outdated
Comment thread packages/deploy-helpers/src/deploy/helpers/exports-reconciliation.ts Outdated
Comment thread packages/deploy-helpers/src/deploy/versions-upload.ts Outdated
Comment thread packages/workers-utils/src/config/validation.ts Outdated
Comment thread packages/workers-utils/src/config/validation.ts Outdated
Comment thread packages/wrangler/src/dev/class-names-sqlite.ts Outdated
Comment thread packages/config/src/exports.ts Outdated
petebacondarwin added a commit that referenced this pull request Jun 25, 2026
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.
@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch from 3afeaf6 to 3204dea Compare June 25, 2026 05:13
devin-ai-integration[bot]

This comment was marked as resolved.

petebacondarwin added a commit that referenced this pull request Jun 25, 2026
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.
@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch from 23ac341 to 3cc75dd Compare June 25, 2026 16:28
…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`.
@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch from b5a8cc0 to 73b30d8 Compare June 26, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run-remote-tests Run remote/E2E tests that require Cloudflare API credentials

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

4 participants