Skip to content

✨ feat: migrate KlipCode backend and GitHub sessions to D1 - #37

Merged
martinezharo merged 2 commits into
mainfrom
t3code/migrate-to-d1
Oct 1, 2026
Merged

martinezharo merged 2 commits into
mainfrom
t3code/migrate-to-d1

Conversation

@martinezharo

@martinezharo martinezharo commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

KlipCode workspace sync and GitHub authentication now run on Cloudflare Workers and D1, removing the Convex request path and VPS dependency. Existing user ids, provider identities, wrapped encryption keys, encrypted fields, local ownership and R2 image paths are preserved.

Workspace writes use owner-scoped primary keys, atomic batches, server-clock deltas and bounded payloads. Cookie sessions are HTTP-only and stored as hashes; account headers and same-origin checks protect sync races. Missing encryption keys fail closed. The archived Convex backend remains frozen for recovery, with protected exports and a documented rollback procedure.

Includes the production regression fix for imported ISO timestamps ending in +00:00: they remain valid after editing, and stale writes are compared by instant across offsets.

Validation:

  • 260 application tests, including real D1 ownership, rollback, deletion, session and legacy timestamp coverage.
  • Four migration importer tests; typecheck, lint and OpenNext production build pass.
  • 23 browser flows passed before the backend-only timestamp fix; CI repeats the full suite.
  • Frozen export and post-cutover backup verified byte for byte. Live audit retains all 14 original users, 14 GitHub identities, 13 wrapped keys, 43 folders and 302 original snippets; new production records are preserved.
  • Live GitHub login, 59 encrypted field decryptions and encrypted create/edit/delete checked. The legacy timestamp reproduction now returns 200 in production and its temporary record was removed.

Production uses the tested D1 Worker already. Automatic production builds remain upload-only until this change is merged, preventing the previous Convex application from being activated. No existing production data or master key is replaced by deployment.

Preserve account ids, wrapped keys and ciphertext while moving workspace sync and authentication to Cloudflare. Keep the frozen Convex source for verified recovery, use indexed deltas and atomic batches, and fail closed on encryption errors.

Accept legacy ISO offsets to prevent imported records from failing subsequent saves. Cover ownership, stale writes, cascades, encryption boundaries and migration safety in regression tests.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
klipcode 1c07a12 Commit Preview URL

Branch Preview URL
Oct 01 2026, 08:03 AM

@oli-review oli-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review

Moves KlipCode's backend off Convex onto Cloudflare D1 and Auth.js database sessions. It adds D1 migrations (workspace tables, owner-scoped keys, deletion/analytics triggers), a Worker workspace data layer plus /api/workspace/[operation] and /api/auth/[...auth] routes, a cookie-session CloudClientProvider, a Convex→D1 importer with tests, and archives the old backend under legacy/convex. Server-side last-write-wins now compares timestamps by instant so imported +00:00 records remain editable.

File Note
migrations/0001_d1_backend.sql ✅ Owner-scoped tables, checks, cycle guards and metadata triggers look coherent.
src/server/workspace.ts ✅ Parameterized SQL, per-owner scoping, instant-based LWW, atomic batches.
src/server/auth.ts ✅ Hashed cookie tokens, expiry check, origin guard, fail-closed config.
src/app/api/workspace/[operation]/route.ts ✅ Bounded body, same-origin + account-header race guard, input errors mapped to 400.
scripts/migrate-convex-to-d1.py 🟡 Destructive reconciliation assumes every table file is present.
src/lib/sync.ts ✅ Combined folder+snippet payload is now bounded and split when oversized.

Risk: medium

Notes

Good coverage of D1 ownership, rollback, deletion and legacy timestamps. Worth adding: route-level tests for /api/workspace (account-header mismatch, cross-origin, oversized body) and a syncTombstones case above 500 tombstones, since those paths aren't exercised. Consider validating that all expected tables exist before generating the import SQL so a truncated export can't pass --verify.

1 file(s) not reviewed
  • pnpm-lock.yaml — generated or binary

Automated review — opencode-go/deepseek-v4.1-flash · 1 comment(s)

Comment thread scripts/migrate-convex-to-d1.py Outdated
Comment on lines +32 to +34
def table(name):
try: return [json.loads(line) for line in archive.read(name+'/documents.jsonl').splitlines() if line]
except KeyError: return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 major — Missing export table silently wipes all rows

table() returns [] for any absent <name>/documents.jsonl, and the loop then emits an unconditional DELETE FROM {name} WHERE analytics_id NOT IN (json_each('[]')), which deletes every row. A truncated export (or a path/format change) therefore empties folders/snippets/accounts, and --verify still passes because it compares against the same empty expectation. Fail loudly on a missing table instead of treating it as authoritative.

Suggested change
def table(name):
try: return [json.loads(line) for line in archive.read(name+'/documents.jsonl').splitlines() if line]
except KeyError: return []
def table(name):
try: return [json.loads(line) for line in archive.read(name+'/documents.jsonl').splitlines() if line]
except KeyError: raise SystemExit(f'missing {name}/documents.jsonl in export; refusing to import')

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 1c07a12. Missing required export tables now raise before any SQL file or REST mutation is applied. The new regression test removes each required table in turn and verifies zero writes; all four importer tests pass.

Fail on missing required tables instead of interpreting them as empty. Regression coverage checks every required table and verifies that no mutation occurs.

@oli-review oli-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review

A small follow-up to the Convex→D1 importer: table() now raises ValueError when a table's documents.jsonl is absent instead of silently returning [], so a truncated export can no longer drive the deletion-reconciliation DELETE to wipe rows or pass --verify. A new test removes each expected table in turn and asserts the import aborts with no mutations.

File Note
scripts/migrate-convex-to-d1.py ✅ Fail-closed on missing export table; all table reads precede any remote mutation or SQL write.
scripts/test-migrate-convex-to-d1.py ✅ Table-by-table missing-file test; shared writes counter stays 0 because reads precede execution.

Risk: low

Notes

The fix is correct: every table() call (lines 42, 51, 66) runs before any SQL is written or batch applied, so raising on a missing file leaves D1 untouched, and the new test verifies that invariant for each expected table. No further changes needed. Optional follow-up: assert that an empty-but-present file is still accepted (already exercised by the empty folders/snippets/authSessions fixtures).

1 file(s) not reviewed
  • pnpm-lock.yaml — generated or binary

Automated review — opencode-go/deepseek-v4.1-flash · 0 comment(s) · incremental

@martinezharo
martinezharo merged commit 190e66f into main Oct 1, 2026
2 checks passed
@martinezharo
martinezharo deleted the t3code/migrate-to-d1 branch October 1, 2026 08:08
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