You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-Convex is not a thin persistence dependency in this codebase. It is currently acting as:
-- the primary database
-- the client API layer
-- the reactive subscription layer
-- the auth-aware server function runtime
-- the internal job orchestration layer
-- the worker callback surface
-- part of the Stripe integration
-This means there is no low-risk "swap Convex for X" adapter that will let us keep the rest of the app unchanged.
-The correct migration strategy is:
-1. Do not build a generic Convex compatibility layer.
-2. Introduce a proper server/data layer now.
-3. Migrate feature-by-feature behind domain services and hooks.
-4. Preserve the existing Cloudflare worker plane and R2.
-5. Replace Convex reactivity with standard request/response + targeted polling/invalidation.
-## Recommendation
-Recommended target stack:
-- Database: Turso
-- ORM/query layer: Drizzle ORM
-- Auth: keep Clerk
-- App API layer: Next.js Route Handlers + Server Actions
-- Background orchestration: keep the existing Cloudflare workers/queues/crons
-- Client data fetching: TanStack Query plus existing local UI state patterns
-Why this is the best fit for this repo:
-- It is the lowest-friction database option across both Next.js and the existing Cloudflare worker plane.
-- It keeps cost near zero on hobby-scale usage.
-- It avoids Convex-specific lock-in while staying modern and simple.
-- It does not force an immediate hosting migration away from the current Vercel + Cloudflare split.
-- It is a better fit than Cloudflare D1 for this app because the app is not fully on Cloudflare.
-- It is a better fit than Supabase for the first move because we already use Clerk, R2, and Cloudflare workers, so most of Supabase's extra platform surface would go unused.
-Secondary option if you want Postgres instead of SQLite:
-- Supabase Postgres + Drizzle
-I would choose Supabase only if you specifically want:
-- Postgres now
-- built-in realtime as an option
-- a single vendor for database plus operational add-ons
-I would not choose Cloudflare D1 as the first migration target unless you also plan to move the primary app/backend execution plane onto Cloudflare. In the current architecture it adds platform coupling in the wrong place.
-## Current Convex Footprint
-Measured in this repo:
-- 15 tables in convex/schema.ts
-- 29 Convex backend modules with database/function logic
-- 71 public Convex functions
-- 94 internal Convex functions
-- 20 worker HTTP callback routes plus Stripe webhook routing in convex/http.ts
-- 88 files in app/, components/, hooks/, and lib/ that reference Convex surfaces
-- 184 import/call-site references to Convex-generated APIs, IDs, or hooks in the app code
-- 32 useQuery(...) call sites
-- 29 useMutation(...) call sites
-- 8 usePaginatedQuery(...) call sites
-- 3 useAction(...) call sites
-- 6 fetchQuery(...) server call sites
-- 2 fetchMutation(...) server call sites
-This is not "database only". It is full-stack coupling.
-## Where Convex Is Embedded
-### 1. Frontend runtime coupling
-The UI directly imports Convex in many places:
-- components/providers/convex-client-provider.tsx
-- hooks/queries/use-image-history.ts
-- hooks/queries/use-favorites.ts
-- hooks/queries/use-batch-generation.ts
-- hooks/use-prompt-library.ts
-- hooks/use-subscription-status.ts
-- lib/pollen-auth/context.tsx
-- many page/components that import api from @/convex/_generated/api
-Convex-specific frontend semantics currently relied on:
-- useQuery loading state is undefined
-- "skip" sentinel arguments
-- usePaginatedQuery pagination model
-- reactive live updates
-- generated api.* function references
-- branded Id<"table"> types from Convex
-- optimistic mutations in Convex hooks
-- useConvexAuth and useConvex
-- query hooks for feeds/history/favorites
-- some domain hooks for prompt library, batch generation, subscription status, music generation
-- server cache helpers under app/_server/cache
-- Cloudflare worker code already separated from the UI
-Missing seams:
-- many components/pages still call Convex directly instead of via domain hooks/services
-- server routes call Convex directly
-- worker state transitions are hardwired to Convex HTTP endpoints
-- Convex IDs/types are used in UI code
-- auth loading state in some pages depends on useConvexAuth
-### Should we build a proper adapter layer?
-Yes, but only at the domain/service layer.
-Do this:
-- introduce lib/db/* for schema and database access
-- introduce lib/server/* or app/_server/* repositories/services
-- introduce domain APIs like imageService, userService, generationService
-- refactor hooks to call our own API layer, not Convex directly
-Do not do this:
-- do not build convexCompat.useQuery(...)
-- do not try to emulate Convex's generated api.* tree
-- do not attempt to preserve Convex reactivity semantics generically
-That would recreate the lock-in instead of removing it.
-### Adapter conclusion
-The code is too tightly integrated with Convex for a single drop-in backend adapter.
-The right approach is:
-- one-by-one replacements by feature slice
-- but with a real shared server/data layer introduced first so the replacements converge on a clean architecture
-## Recommended Target Architecture
-### Core shape
-- Next.js app remains the user-facing application
-- Clerk remains auth
-- Cloudflare workers remain the async compute/orchestration plane
-- R2 remains media storage
-- Turso becomes the system-of-record database
-- Drizzle becomes the schema/migration/query layer
-- Next Route Handlers / Server Actions replace public Convex functions
-- Cloudflare worker callback routes move from Convex HTTP actions to Next.js API routes or a thin backend worker API
-### Data flow after migration
-1. Client calls Route Handler or Server Action.
-2. Server code authenticates with Clerk.
-3. Server code uses domain service + Drizzle repository.
-4. Background work is dispatched to the existing Cloudflare queue/worker.
-5. Worker reports state changes to backend HTTP endpoints not tied to Convex.
-6. Client refreshes with TanStack Query invalidation or targeted polling.
-### Realtime strategy
-Do not try to preserve Convex-style reactivity everywhere.
-Use three simpler patterns:
-- server-rendered initial fetch for read-heavy pages
-- TanStack Query polling for job progress and short-lived status
-- explicit invalidation/refetch after writes
-Only add true realtime later if a page demonstrably needs it.
-## Why Turso First
-As of 2026-03-13, official materials indicate:
-- Turso positions itself as edge SQLite/libSQL with Drizzle support and a generous free plan.
-- Cloudflare D1 free pricing is row-read/write based and can be punishing if query shape regresses.
-- Supabase free plan is workable, but comes with a larger platform surface than this repo needs.
-Why Turso fits this repo specifically:
-- The app already has a split runtime: Vercel/Next plus Cloudflare workers.
-- Turso's client model is friendly to both environments.
-- The current data model is relational enough to map cleanly to SQL.
-- Current usage is low enough that SQLite write-concurrency is acceptable if we keep transactions short.
-Known tradeoff:
-- If write concurrency grows materially, or if you later want richer SQL features and more headroom, Postgres may become the better long-term store.
-That is acceptable for this migration because the primary goal is to get off Convex quickly with minimal operational cost and minimal platform sprawl.
-## Why Not D1 First
-Cloudflare D1 is attractive on price, but it is not the least-friction move for this repository.
-Reasons:
-- The main app is still built/deployed as a Next.js app on Vercel.
-- The worker plane is on Cloudflare, but the primary application data access is not.
-- Choosing D1 strongly nudges us toward a broader hosting/runtime migration at the same time as the data migration.
-- This app already had bandwidth/query-shape issues; D1's row-based pricing punishes inefficient scans too.
-D1 is a good second-step option only if the broader plan becomes:
-- move Next/backend execution to Cloudflare
-- consolidate app and workers onto one vendor/runtime plane
-That is a larger project than the current emergency migration.
-## Why Not a Generic Supabase Migration First
-Supabase is the best Postgres-based alternative, but it is not the lowest-friction replacement for this codebase right now.
-Reasons:
-- We already use Clerk, so Supabase Auth is unnecessary.
-- We already use R2, so Supabase Storage is unnecessary.
-- We already use Cloudflare workers, so Supabase Edge Functions are unnecessary.
-- The migration would still require replacing Convex functions, hooks, auth bindings, and callbacks one by one.
-Supabase is still a valid backup choice if:
-- you strongly prefer Postgres over SQLite
-- you want optional realtime later
-- you want a broader managed database platform now
-## Migration Principles
-1. Migrate vertical slices, not infrastructure in the abstract.
-2. Preserve the current external compute plane.
-3. Replace Convex with our own domain APIs, not with another vendor-shaped API.
-4. Remove Convex-generated IDs and types from the app boundary.
-5. Accept polling/invalidation instead of trying to recreate full reactivity.
-- polling
-- invalidation
-- local optimistic UI where useful
-## Feature-by-Feature Migration Order
-### Phase 0: Stop the bleeding
-Goal: remove Convex as the hard dependency that keeps the site down.
-Actions:
-- snapshot/export current Convex data while access still exists
-- remove Convex deploy from vercel.json
-- decouple build from Convex codegen/deploy
-- add new DB env plumbing
-- stand up Turso + Drizzle schema and migrations
-Success condition:
-- the app can build and deploy without a Convex deployment
-### Phase 1: Core reads required for a minimally useful site
-- keep Cloudflare queue dispatch
-- replace Convex HTTP callback routes with Next.js API routes or a small backend worker API
-- keep the same state machine semantics but store state in SQL
-Success condition:
-- generation works end-to-end without Convex
-### Phase 4: Moderation and recovery jobs
-Migrate:
-- moderation state tables/columns
-- provider health
-- prompt inference and vision-analysis callbacks
-- recovery scheduling
-- cron jobs
-- store large flexible fields like generationParams as JSON
-- use normal SQL foreign keys where the relationships are stable
-- add explicit indexes for every current hot query path
-- use text IDs generated by the app, not database-implicit integer IDs, so IDs are stable across app and worker boundaries
-Recommended ID strategy:
-- cuid2 or ulid for all top-level entities
-Do not keep Convex-branded ID types in the app.
-## Query/Mutation Migration Strategy
-### Replace public Convex functions with domain endpoints
-Examples:
-- api.generatedImages.getPublicFeed -> GET /api/feed/public
-- api.generatedImages.getMyImages -> GET /api/images/my
-- api.generatedImages.setVisibility -> POST /api/images/:id/visibility
-- api.favorites.toggle -> POST /api/favorites/toggle
-- api.follows.follow -> POST /api/follows
-- api.users.getCurrentUser -> GET /api/me
-### Replace internal Convex actions with service calls and queues
-Examples:
-- internal.cloudflareDispatch.dispatchSingleGeneration -> service method that enqueues the existing Cloudflare message
-- internal.contentAnalysis.analyzeRecentImages -> worker cron or backend cron endpoint
-- ctx.scheduler.runAfter(...) -> Cloudflare Cron/Queues-based follow-up or application-side scheduler table
-## Worker Migration Strategy
-Current worker callbacks are already shaped like a state machine. That is good news.
-- CONVEX_SITE_URL becomes BACKEND_BASE_URL
-- worker callback paths move off convex/http.ts
-- callback handlers call service methods backed by Drizzle
-This lets us preserve most of the queue/worker logic while replacing the state store.
-## Realtime Replacement Strategy
-Convex gave us "always fresh" subscriptions. We do not need to fully replace that.
-By feature:
-- generation status: poll every few seconds while there are active jobs
-- batch status: poll while active
-- feeds/history: server render + manual invalidation or occasional background refresh
-- favorites/follows: optimistic local state + invalidate affected query
-- settings/profile: fetch on mount, refetch after mutation
-This is simpler and cheaper than trying to rebuild live subscriptions on day one.
-## What Can Be Reused
-These areas already provide decent seams and should be reused:
-These should be treated as migration boundaries, not thrown away.
-## What Must Change Directly
-These Convex-specific pieces need explicit replacement:
-- components/providers/convex-client-provider.tsx
-- app/_server/convex/client.ts
-- any use of useConvexAuth
-- any use of useConvex
-- any direct import from @/convex/_generated/api
-- any direct import from @/convex/_generated/dataModel
-- vercel.json build command that runs Convex deploy
-- worker env/config names that point at Convex
-## Risks
-### 1. Generation pipeline regression
-This is the highest-risk slice because it spans:
-- UI
-- backend
-- database state
-- Cloudflare queues/workers
-- moderation
-- media persistence
-Mitigation:
-- migrate single generation first
-- then batch generation
-- keep the current worker message shapes where possible
-### 2. Overbuilding a fake Convex replacement
-This is the main architectural trap.
-Mitigation:
-- build domain services and route handlers
-- do not emulate api.*, useQuery, usePaginatedQuery, or Convex auth semantics
-### 3. ID/type churn
-Convex ID types are embedded in UI code.
-Mitigation:
-- introduce app-owned ID aliases early
-- migrate hooks/components to plain string IDs or branded app types
-### 4. Query regressions
-The repo already showed how expensive bad query shapes can get.
-Mitigation:
-- define indexes up front
-- write repository methods around concrete access patterns
-- test hot paths with realistic pagination and moderation state
-## Concrete First Deliverables
-I would implement these first:
-1. lib/db/client.ts
-2. lib/db/schema/*
-3. lib/server/repositories/users.ts
-4. lib/server/repositories/images.ts
-5. lib/server/services/feed.ts
-6. app/api/feed/public/route.ts
-7. app/api/me/route.ts
-8. replacement for app/_server/cache/feed.ts
-9. replacement for hooks/queries/use-image-history.ts
-10. worker callback API skeleton under app/api/workers/*
-That gets the architecture moving in the right direction without pretending this is a one-file vendor swap.
-## Final Recommendation
-Do this:
-- migrate off Convex to Turso + Drizzle
-- keep Clerk, R2, and Cloudflare workers
-- replace Convex with our own server/data layer
-- migrate by vertical feature slices
-- accept polling/invalidation instead of global reactivity
-Do not do this:
-- do not build a fake generic Convex adapter
-- do not try to preserve Convex API shapes as your internal architecture
-- do not combine this with a full hosting migration unless you intentionally want a larger platform consolidation project