Repository navigation
✨ feat: migrate KlipCode backend and GitHub sessions to D1 - #37
Conversation
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.
Deploying with
|
| 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 |
There was a problem hiding this comment.
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)
| def table(name): | ||
| try: return [json.loads(line) for line in archive.read(name+'/documents.jsonl').splitlines() if line] | ||
| except KeyError: return [] |
There was a problem hiding this comment.
🟠 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.
| 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') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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:
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.