Add Cloudflare deployment migration executors - #2439
Conversation
🦋 Changeset detectedLatest commit: b724193 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 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 |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 017e863 | Aug 13 2026, 08:53 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | b724193 | Aug 18 2026, 02:26 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | b724193 | Aug 18 2026, 02:31 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | b724193 | Aug 18 2026, 02:30 PM |
Scope checkThis PR changes 1,758 lines across 18 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
There was a problem hiding this comment.
Pull request overview
Adds Cloudflare-focused deployment migration executors to support running EmDash migrations during deployment (rather than during Worker runtime init), including a D1 REST-based Kysely dialect/target resolution and a Hyperdrive direct-origin executor.
Changes:
- Exposes new Cloudflare package entrypoints for D1 and Hyperdrive migration executors (and wires them into build config).
- Implements D1 deployment migration support via Cloudflare’s authenticated REST API (dialect + target resolution helpers).
- Adds unit/live tests and changesets for the new Cloudflare migration functionality.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cloudflare/tsdown.config.ts | Adds tsdown build entries for new migration executor entrypoints. |
| packages/cloudflare/package.json | Exports new ./db/*-migrations entrypoints. |
| packages/cloudflare/src/index.ts | Extends D1/Hyperdrive descriptors with migrations manifest config and adds Hyperdrive env-var option/validation. |
| packages/cloudflare/src/db/hyperdrive.ts | Updates docs/comments to clarify “runtime migrations” binding behavior. |
| packages/cloudflare/src/db/hyperdrive-migrations.ts | Adds Hyperdrive migration executor factory that delegates to core Postgres migrations. |
| packages/cloudflare/src/db/d1-rest-dialect.ts | Introduces a REST-backed Kysely dialect for administrative D1 queries. |
| packages/cloudflare/src/db/d1-migrations.ts | Implements D1 migration executor factory using the REST dialect + resolved migration target. |
| packages/cloudflare/src/db/d1-migration-target.ts | Resolves/validates D1 migration targets (wrangler config + control-plane metadata), and fingerprints targets. |
| packages/cloudflare/tests/hyperdrive-config.test.ts | Adds tests for Hyperdrive migration manifest config behavior and credential isolation. |
| packages/cloudflare/tests/do-config.test.ts | Updates D1 descriptor test expectations to include migrations entrypoint/config. |
| packages/cloudflare/tests/fixtures/d1-wrangler.jsonc | Adds wrangler config fixture for D1 target resolution tests. |
| packages/cloudflare/tests/db/hyperdrive-migrations.test.ts | Adds tests for Hyperdrive migration executor target identity and env overrides. |
| packages/cloudflare/tests/db/d1-rest-dialect.test.ts | Adds comprehensive unit tests for the D1 REST dialect behavior and error handling. |
| packages/cloudflare/tests/db/d1-migrations.test.ts | Adds unit tests ensuring D1 executor construction and token-gating behavior. |
| packages/cloudflare/tests/db/d1-migrations.live.test.ts | Adds credential-gated live D1 contract tests for apply/idempotency and query semantics. |
| packages/cloudflare/tests/db/d1-migration-target.test.ts | Adds tests for D1 target resolution rules, pagination, and wrangler env selection. |
| .changeset/add-hyperdrive-direct-migrations.md | Changeset for Hyperdrive direct-origin deployment migrations. |
| .changeset/add-cloudflare-d1-migrations.md | Changeset for deployment-managed D1 migrations via REST. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
The PR adds a sensible slice of the deployment-migration story: a D1 REST dialect, resolver for D1 target metadata, package entrypoints, and a Hyperdrive wrapper around the existing PostgreSQL migration executor. The changes are additive, carry changesets, keep migration credentials out of the Worker runtime config, and include targeted tests. I checked the diff, migration runner, direct executor, and the existing SQLite/PostgreSQL migration providers.
The big gap is that the new D1 REST dialect rejects beginTransaction/commitTransaction/rollbackTransaction. EmDash's migration runner uses Kysely's Migrator.migrateToLatest(), which wraps each migration in a transaction by default. The other migration providers (SQLite, libSQL, PostgreSQL) all use transaction-capable Kysely dialects, so an action: "apply" against the new D1 REST executor will throw at the first pending migration. The credential-gated live apply test is skipped in CI, so this path is not currently exercised and the bug is hidden. That needs to be fixed before the feature can be considered working.
Aside from that, the Hyperdrive side looks correct, tests are behavior-oriented rather than tautological, and the exports/build config line up.
ad5242b to
beb551b
Compare
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
beb551b to
017e863
Compare
There was a problem hiding this comment.
This is a coherent, additive slice of the deployment-migration story: a D1 REST dialect, a Wrangler-aware target resolver, package entrypoints/build config, and a Hyperdrive wrapper that keeps migration credentials out of the Worker runtime config. The Hyperdrive side is correct and the tests are behavior-oriented where they run.
However, the re-review confirms the hard blocker from my previous review is still open: D1RestDriver throws on beginTransaction, commitTransaction, and rollbackTransaction. EmDash's migration runner uses Kysely.Migrator.migrateToLatest(), which wraps each migration in a transaction by default. The other migration providers (SQLite, PostgreSQL) all use transaction-capable dialects, so an action: "apply" against the new D1 REST executor will throw "Transactions are not supported yet" at the first pending migration. The credential-gated live apply test is skipped in CI, and the unit tests only exercise action: "check", so the bug remains hidden.
That needs to be fixed before this feature can be considered working — either by implementing D1 REST batch transactions in the driver (the correct fix, since /query accepts a statement array) or by another transaction strategy that interoperates with Kysely's migrator. Once the dialect can survive migrateToLatest(), the live test should run when credentials are present, and a unit-level test for the apply path would prevent regression in CI.
017e863 to
64c4489
Compare
1f8fa12 to
edd028d
Compare
There was a problem hiding this comment.
Re-review: the branch is unchanged since the last pass, and the unresolved D1 transaction blocker is still present.
The change remains a coherent additive slice: Wrangler-aware D1 target resolution, a D1 REST dialect, a Hyperdrive direct-origin migration entrypoint, package exports/build config, and credential-gated live coverage. The Hyperdrive path keeps migration credentials out of the runtime Worker config, the D1 REST request/response validation and error redaction are well-tested, and the AGENTS.md conventions I checked (changesets, SQL safety through parameterized queries, secret-free manifest config, no new logged-out-route queries, no offending reviewer-facing comments) are otherwise satisfied.
The one issue that makes the D1 executor non-functional is that D1RestDriver throws from beginTransaction, commitTransaction, and rollbackTransaction, while D1RestDialect.createAdapter() returns a plain D1Adapter that inherits supportsTransactionalDdl() === true from Kysely's SqliteAdapter. createDirectMigrationExecutor constructs a Kysely Migrator without allowMigrationStatementRollback, so migrateToLatest() will attempt to wrap each migration in a transaction before any migration SQL reaches Cloudflare. That means action: "apply" fails against every D1 REST target, including a fresh database. A unit-level regression test for the apply path is also missing: packages/cloudflare/tests/db/d1-migrations.test.ts only tests "check", and the live "apply" test is skipped in CI.
No new blockers or convention violations were found in this pass.
edd028d to
44a6a74
Compare
There was a problem hiding this comment.
This is a coherent additive slice that wires Cloudflare D1 and Hyperdrive into the new deployment migration framework: a Wrangler-aware D1 target resolver, a D1 REST dialect for running migrations over the Cloudflare API, a Hyperdrive direct-origin migration entrypoint, package exports/build config, and credential-gated live tests. The Hyperdrive path keeps migration credentials out of runtime Worker config, the D1 REST request/response validation and error redaction are well-tested, and the AGENTS.md conventions I checked (changesets, SQL safety through parameterized queries, secret-free manifest config, no new logged-out-route queries, no reviewer-facing comments) are otherwise satisfied.
However, the branch is unchanged since the last review and the D1 transaction blocker is still present: D1RestDialect.createAdapter() returns a plain D1Adapter that inherits supportsTransactionalDdl() === true from Kysely's SqliteAdapter, while D1RestDriver throws from beginTransaction, commitTransaction, and rollbackTransaction. The shared createDirectMigrationExecutor constructs a Kysely Migrator without disabling transactional DDL wrapping, so migrateToLatest() will attempt to start a transaction before any migration SQL reaches Cloudflare. That makes the D1 apply action fail against every D1 REST target, including a fresh database.
A unit-level regression test for the apply path is also missing: packages/cloudflare/tests/db/d1-migrations.test.ts only exercises "check", and the one live "apply" test in d1-migrations.live.test.ts is skipped in CI when credentials are unavailable. I would expect the fix plus a mocked apply test before this merges.
No new blockers or convention violations were found in this pass.
44a6a74 to
27359a8
Compare
27359a8 to
b724193
Compare
What does this PR do?
Adds deployment migration executors for Cloudflare D1 and Hyperdrive, including the D1 REST dialect and migration target, package entrypoints, build configuration, and credential-gated live coverage.
Part of #2276
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.Approved design discussion: #2418. Package typechecking, quick lint, and targeted formatting pass.
AI-generated code disclosure
Screenshots / test output
Non-visual change. Cloudflare build and typecheck pass; 346 tests pass and two live credential-gated tests are skipped because D1/PostgreSQL credentials are unavailable. Built executor entrypoints contain no test credentials.