Plan 2: entire backend ported to Drizzle, Supabase packages removed

juanjo finishes the mechanical backend migration: 16 commits convert every remaining supabase-js call site to Drizzle ORM across all route files and lib helpers. Two post-migration bug fixes suggest the sweep was large enough to introduce subtle runtime errors.

infrastructure

The migration starts in the lib layer. A new backend/src/lib/users.ts (68d1887) exposes listAllUsers() and emailToUserIdMap() to replace auth.admin.listUsers. The function is used in three routes that need to resolve emails to user IDs for sharing features. The dead Supabase admin client in workflows.ts is removed in a follow-up (cfc84fb).

Then four lib modules: access.ts (36c889c), userSettings.ts (a41622a), documentVersions.ts (3a5c8b6), chatTools.ts (d352d15). The chatTools.ts rewrite is 479 lines changed - it's the largest single file. All four drop the db: Supa parameter that previously threaded a supabase client through every function call; they now import the module-level Drizzle db directly. Call sites in routes no longer need to pass db at all.

Route files follow: downloads.ts (ff63879), projectChat.ts (d2f30d5), chat.ts (318e559), projects.ts (5e802ac, +799/-499), documents.ts (3e8d181, +927/-639), workflows.ts (b691f92), tabular.ts (022434f, +896/-698). The consistent pattern across all of them: a set of *ToWire() mapper functions (e.g., chatToWire, projectToWire, docToWire) translate Drizzle's camelCase property names back to the snake_case JSON keys the frontend already expects. Every handler gets wrapped in try/catch since Drizzle throws on error rather than returning { data, error }.

Two fixes landed after the sweep. ed0cc46 guards the outer catch in projectChat.ts against crashing when the SSE response headers have already been sent - the outer try/catch was calling res.status(500).json() after streaming had begun, which Node rejects. bdb80c4 fixes a snake_case key being read with a camelCase property name in buildTabularContext, introduced when the column name mapping wasn't applied consistently.

So what Worth importing as a unit if you're doing the same Supabase exit. The wire-shape mapper pattern kept the frontend untouched through a giant ORM swap - that's the technique to borrow. Plan to test SSE paths and anywhere DB row keys are destructured; the two post-migration fixes show those are the most likely spots for similar bugs.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

16 commits from juanjo/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
68d18876 feat(backend): replace Supabase auth.admin.listUsers with Drizzle helper Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cfc84fbf chore(backend): remove dead Supabase admin client from workflows.ts Juan Vidal 2026-05-08 ↗ GitHub
36c889ce feat(backend): migrate lib/access.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
a41622ab feat(backend): migrate lib/userSettings.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
3a5c8b6d feat(backend): migrate lib/documentVersions.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
d352d15c feat(backend): migrate lib/chatTools.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
ff63879b feat(backend): migrate downloads.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d2f30d55 feat(backend): migrate projectChat.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
318e5594 feat(backend): migrate chat.ts to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ed0cc463 fix(projectChat): guard outer catch against post-headers-sent SSE crash Juan Vidal 2026-05-08 ↗ GitHub
5e802ac6 feat(backend): migrate projects route to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
commit body
Replace all supabase-js calls in backend/src/routes/projects.ts with
Drizzle ORM equivalents, add projectToWire/docToWire/folderToWire helpers
for snake_case wire shape preservation, and wrap every async handler in
try/catch. Drop the redundant _db shim arg from all checkProjectAccess
call sites. The listAllUsers() usage from P2-1 is preserved intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3e8d1813 feat(backend): migrate documents route to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
commit body
Replace all 30 supabase-js calls in documents.ts with Drizzle ORM queries.
Drop _db shim args from lib calls; preserve snake_case wire shapes via
inline mappers; wrap all handlers in try/catch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b691f92c feat(backend): migrate workflows route to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
commit body
Replace all 18 supabase-js calls in workflows.ts with Drizzle ORM queries.
Add workflowToWire mapper for snake_case wire shape; drop _db shim args;
preserve listAllUsers() from P2-1; wrap all handlers in try/catch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7cf1e82e fix(backend): workflow list ordering + tighten Drizzle update types Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
022434f9 feat(backend): migrate tabular route to Drizzle Juan Vidal 2026-05-08 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bdb80c46 fix(tabular): correct snake_case→camelCase mismatch in buildTabularContext Juan Vidal 2026-05-08 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-185.md from inside the repo you want the changes in.

⬇ Download capture-thread-185.md