Skip to content

feat(migrate): Cloudflare D1 runner#30

Merged
khaliqgant merged 2 commits intomainfrom
feat/relayauth-migrate-d1-runner
Apr 23, 2026
Merged

feat(migrate): Cloudflare D1 runner#30
khaliqgant merged 2 commits intomainfrom
feat/relayauth-migrate-d1-runner

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 23, 2026

Summary

Adds `createD1Runner(db)` to `@relayauth/migrate` — the D1 backend for the `MigrationRunner` contract introduced in #26.

Stacks on #26. Base branch is `feat/relayauth-migrate-package`; this will want a rebase-merge once #26 lands on main.

Why now

Cloud (AgentWorkforce/cloud) is currently maintaining its own snapshot of `packages/server/src/db/migrations/` because there was no shared async migrator. That's how we ended up with a `POST /v1/tokens` outage today — cloud's snapshot was missing `session_id`, `issued_at`, and `expires_at` from the `tokens` table even though the server's route handlers had already started referencing them. Details in AgentWorkforce/cloud#312.

With this runner + the source-of-truth migrations dir #26 introduces, cloud can delete its snapshot and run the server's bundled migrations directly — follow-up PR in cloud.

Design

  • Structural types (`D1DatabaseLike`, `D1PreparedStatementLike`, `D1ResultLike`) instead of depending on `@cloudflare/workers-types`. That lets a Node CLI wrap Cloudflare's REST API into the same shape and run migrations from CI without spinning up a Worker.
  • Multi-statement migrations go through `db.batch(...)`, which D1 runs inside a single transaction — a failing statement rolls back all prior ones in the same batch.
  • Statement splitter strips `--` line comments and splits on `;`. No tokenizer — string literals and `BEGIN/END` blocks with embedded semicolons are out of scope until a consumer needs them. Documented in the JSDoc so future authors don't trip.

Tests

6 tests covering the runner against `node:sqlite` wrapped in an async D1-like facade:

  • fresh DB applies all + populates journal with correct checksums
  • rerun: no-ops, `skipped` equals all
  • checksum drift raises `MigrationChecksumMismatchError`
  • multi-statement failure rolls back prior statements (no table survives, journal unchanged)
  • `recordApplied` is idempotent on duplicate ids
  • `splitSqlStatements` strips `--` comments and splits on `;`

All pass: `node --test --import tsx src/tests/d1-runner.test.ts` → `# pass 6`.
Full `npx turbo typecheck` across the workspace — `11 successful, 11 total`.

Follow-up

  • AgentWorkforce/cloud — delete cloud's copy of `packages/relayauth/src/db/migrations/`; add a CLI script that uses this runner + `createFsMigrationSource` pointed at `node_modules/@relayauth/server/dist/db/migrations/`; replace the relayauth section of `.github/actions/run-cloudflare-d1-migrations/run.sh` with it. Handles the wrangler→`@relayauth/migrate` journal transition.

🤖 Generated with Claude Code


Open in Devin Review

Builds on the MigrationRunner contract from the migrate package refactor
(this PR stacks on feat/relayauth-migrate-package). Provides
createD1Runner(db) for any async driver that matches the D1 prepared-
statement surface — Workers D1 binding or an HTTP client wrapper in
build-time/CI contexts.

Atomicity: each migration is split into statements and submitted via
db.batch(...), which the Cloudflare side runs inside a transaction —
partially-applied DDL is impossible. The splitter strips -- line comments
and splits on ; with no tokenizer; sufficient for pure DDL files that
follow the existing relayauth convention. Documented caveats for strings
and BEGIN/END blocks so future consumers don't trip over it.

Structural typing (D1DatabaseLike, D1PreparedStatementLike,
D1ResultLike) instead of @cloudflare/workers-types so Node consumers
can wrap the Cloudflare REST API without pulling Workers types into
their build.

Tests exercise the runner against node:sqlite wrapped in an async D1-
like facade — fresh DB, rerun, checksum drift, multi-statement rollback,
idempotent recordApplied, splitter correctness.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0453568da1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/migrate/src/runners/d1.ts Outdated
Comment on lines +97 to +98
const idx = line.indexOf("--");
return idx >= 0 ? line.slice(0, idx) : line;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve quoted -- text when stripping SQL comments

splitSqlStatements removes everything after the first -- on each line, even when the dashes appear inside a quoted string literal (for example INSERT ... VALUES('foo--bar')). In that case the migration SQL is truncated before execution, which can either produce syntax errors or alter data and block D1 migrations in production. Because this runner is meant to execute shared .sql migrations, comment stripping needs to be quote-aware (or delegated to the database parser) rather than a raw indexOf("--") cut.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Base automatically changed from feat/relayauth-migrate-package to main April 23, 2026 18:47
Codex flagged (relayauth#30): the prior indexOf("--") + split(";")
splitter corrupted any migration with -- or ; inside a quoted string
literal (e.g. INSERT INTO t VALUES ('foo--bar') or ('hello; world')).
Replaced with a char-by-char scanner that tracks single-quoted strings
(with '' escape), double-quoted identifiers (with "" escape), and --
line comments; semicolons are only statement terminators outside those
regions.

Not supported with a documented caveat so a bad migration fails at
review rather than silently at runtime:
  - block comments /* ... */
  - backslash escapes (SQLite doesn't honor them anyway)

Tests added: -- inside strings preserved, ; inside strings preserved,
'' escape inside strings, "" in identifiers — all covering the exact
patterns Codex pointed at.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khaliqgant
Copy link
Copy Markdown
Member Author

Addressed the Codex P1 in 2016517.

splitSqlStatements is now a char-by-char scanner that tracks single-quoted strings (with '' escape), double-quoted identifiers (with "" escape), and -- line comments. Semicolons and -- are only treated as separators/comments when outside a quoted region. Block comments /* */ and backslash escapes are still not supported — documented in the JSDoc so a bad migration fails at review.

New tests cover the exact patterns Codex pointed at:

  • -- inside single-quoted strings preserved
  • ; inside single-quoted strings preserved
  • '' escape inside single-quoted strings
  • ; and -- inside double-quoted identifiers preserved

10/10 node --test pass; npx turbo typecheck clean across the workspace.

@khaliqgant khaliqgant merged commit 41ff831 into main Apr 23, 2026
2 checks passed
@khaliqgant khaliqgant deleted the feat/relayauth-migrate-d1-runner branch April 23, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant