Skip to content

Instantly share code, notes, and snippets.

@caffeinum
Last active February 11, 2026 00:15
Show Gist options
  • Select an option

  • Save caffeinum/98ad579558a41a73bd1976fe0f3dd681 to your computer and use it in GitHub Desktop.

Select an option

Save caffeinum/98ad579558a41a73bd1976fe0f3dd681 to your computer and use it in GitHub Desktop.
varg refactoring plan: render independence

refactoring plan: render independence

current state (post-refactor)

app (next.js)                    render (bun)                     gateway (hono/effect-ts)
├── supabase auth                ├── POST /api/render             ├── POST /v1/video,image,speech,music
├── chat (claude) → renderVideo  ├── tsx-eval (vargai jsx)        ├── provider proxy (fal, 11labs, replicate, higgsfield)
├── projects, documents, workflows├── sqlite-cache OR redis-cache  ├── job queue (forkDaemon)
├── ✅ writes media              ├── r2-storage (StorageProvider)  ├── billing: usage table (cost_cents, billing_type)
├── ✅ writes project_media      ├── zod request validation        ├── auth: api_keys table (sha256 hash)
├── ✅ writes document_media     ├── zero supabase dependency      ├── cache: vercel kv (30d ttl)
└── calls render w/ VARG_SERVICE_KEY│                               └── files: r2 upload + dedup
                                 ├── ✅ returns { url, files }
                                 ├── ✅ content-hash cache (no projectId)
                                 ├── ✅ no project/document knowledge
                                 └── calls gateway w/ VARG_API_KEY

target state

render knows NOTHING about projects, documents, or app's database.

consumers (agent, human, cli)
        │
        ▼
   ┌──────────────────────┐
   │  render               │   input:  { code, userId }
   │                       │   output: { url, files: [{ url, mediaType, metadata }] }
   │  • tsx eval            │
   │  • mediaStorage (r2)   │   ← saves all files to s3, owns them
   │  • cache (key→url)     │   ← prompt dedup, content-hash scoped (NOT project-scoped)
   │  • sqlite or redis     │   ← auto-selects via REDIS_URL env var
   │                       │
   │  zero knowledge of:   │
   │  • projects            │
   │  • documents           │
   │  • billing             │
   │  • app's supabase      │
   └───────┬───────────────┘
           │ VARG_API_KEY
           ▼
   ┌──────────────────────┐
   │  gateway              │
   │  • provider proxy     │   fal, 11labs, replicate, higgsfield
   │  • billing            │   records usage by user_id in own postgres
   │  • job orchestration  │
   │  • own postgres       │   users, api_keys, usage, jobs
   └──────────────────────┘
           ▲
           │ VARG_API_KEY
   ┌──────────────────────┐
   │  app                  │
   │  • supabase auth      │
   │  • projects/documents │   ← app links render output to projects AFTER receiving urls
   │  • billing check      │   ← app checks balance BEFORE calling render (gap)
   │  • saves code:output  │   ← app writes media, project_media, document_media
   └──────────────────────┘

key principle: render returns lazy files (s3 urls). app decides what to do with them — link to project, link to document, show in UI. render doesn't care.


changes

1. render becomes project-blind — ✅ DONE

render request is { code, userId }. no projectId, no documentId, no supabase writes.

what was done:

  • render/src/api/render/route.ts — rewritten: zod validation, accepts { code, userId, verbose? }, returns { url, files }
  • render/src/api/render/types.ts — NEW: RenderRequestSchema (zod), FileInfo + RenderResponse (plain interfaces)
  • render/src/api/render/supabase-cache.ts — DELETED
  • render/src/utils/supabase.ts — DELETED
  • render/src/utils/upload.ts — DELETED
  • render/package.json — removed @supabase/supabase-js, added zod

PR: https://github.com/vargHQ/render/pull/2 (merged to main)

2. split cache vs mediaStorage in render — ✅ DONE

  • cache = CacheStorage interface from vargai/ai sdk. two implementations:
    • render/src/api/render/sqlite-cache.ts — bun:sqlite, content-hash scoped, local dev default
    • render/src/api/render/redis-cache.ts — Bun.redis, 30d TTL, production (auto-selected when REDIS_URL set)
  • mediaStorage = StorageProvider interface from vargai/ai sdk
    • render/src/api/render/r2-storage.ts — imports StorageProvider from sdk, removed uploadBlob
  • shared utils = render/src/api/render/cache-utils.tsextractMediaData, fetchAsUint8Array, reconstructCachedValue (shared between sqlite and redis)

3. render gets its own database — ✅ DONE (cache layer)

render has zero supabase dependency. cache is self-contained:

  • sqlite: local file data/cache.db, auto-creates table
  • redis: connects via REDIS_URL, 30d TTL, shared across instances

what moved to app (✅ done):

  • media inserts → app creates media record after receiving render urls
  • project_media inserts → app links media to project
  • document_media inserts → app links media to document with role/sourceInfo

render_jobs table (✅ done, PR #3):

  • drizzle-orm with dual driver: bun:sqlite local, postgres prod
  • migrations via fly.io release_command (runs before traffic)
  • records every render: status, input code hash, output url, files, duration, errors

infra provisioned:

  • upstash redis on fly (REDIS_URL) — render cache
  • postgres database render on varg-db cluster (DATABASE_URL) — job history

app PR: https://github.com/vargHQ/app/pull/85 (merged)

4. move all billing to gateway + app — ⏳ NOT STARTED

render has no billing code (already clean). gateway records usage when providers are called.

gap: app needs pre-render balance check against gateway usage data.

5. render returns lazy files — ✅ DONE

response contract:

// Request: POST /api/render, Authorization: Bearer <VARG_SERVICE_KEY>
{ code: string, userId: string, verbose?: boolean }

// Response:
{ url: string, files: Array<{ url: string | null, mediaType: string, metadata: { type?, model?, prompt? } }> }

uses RenderResult.files from vargai sdk (not custom assets format).

6. gateway ↔ render auth — ✅ ALREADY DONE

no changes needed. app → render uses VARG_SERVICE_KEY. render → gateway uses VARG_API_KEY.


migration order

phase 1: decouple render internals                              ✅ DONE (PR #2 merged)
  ├── ✅ split supabase-cache.ts → sqlite-cache.ts + redis-cache.ts + cache-utils.ts
  ├── ✅ remove project_media / document_media writes from render
  ├── ✅ remove projectId from render request interface
  ├── ✅ add zod validation for request
  └── ✅ import StorageProvider + CacheStorage from vargai/ai sdk

phase 2: render gets own db                                     ✅ DONE
  ├── ✅ sqlite cache (bun:sqlite, content-hash scoped)
  ├── ✅ redis cache (Bun.redis, 30d TTL, auto-selected via REDIS_URL)
  ├── ✅ remove supabase dependency entirely
  ├── ✅ render_jobs table (drizzle-orm, sqlite local + postgres prod)
  ├── ✅ upstash redis provisioned on fly (REDIS_URL)
  └── ✅ postgres db provisioned on varg-db cluster (DATABASE_URL)

phase 3: app takes ownership of linking                         ✅ DONE (PR #85 merged)
  ├── ✅ app writes media after receiving render response
  ├── ✅ app writes project_media after receiving render response
  ├── ✅ app writes document_media after receiving render response
  └── ⏳ app checks billing before calling render (gap)

phase 4: (optional) gateway wraps render                        ⏳ NOT STARTED
  ├── add POST /v1/render to gateway
  ├── app calls gateway instead of render directly
  └── gateway handles billing + auth + routing

s3 path convention (post-refactor)

renders/{render_id}/output.mp4        # final video
renders/{render_id}/assets/{hash}.ext  # intermediate assets (images, audio)
cache/{content_hash}.ext               # cached generation outputs

no more projectId in s3 paths. render doesn't know about projects.


testing status

render service (localhost:3002)

  • ✅ auth (401 on bad token)
  • ✅ zod validation (400 on missing fields)
  • ✅ tsx eval rejection (no export default)
  • ✅ image generation via fal (flux-schnell) + r2 upload
  • ✅ sqlite cache (write + hit)
  • ✅ redis cache (write + hit)
  • ❌ video stitching — rendi can't reach localhost minio (known dev limitation)

app → render → gateway (production e2e)

  • ✅ app → render connection (auth, correct request shape)
  • ✅ render → fal image generation works
  • ✅ render → kling video generation works
  • ✅ render → rendi video stitching works (3 clips)
  • ✅ render returns { url, files } to app
  • ✅ app links media + project_media + document_media in supabase
  • ✅ video url: su.varg.ai/renders/... (production R2)

open items

  • merge app PR #85
  • full e2e test (3-clip cat video, prod render → prod app, video stitched + returned)
  • add data/ to render .gitignore (already there)
  • sdk issue #115 — improve CacheStorage interface
  • app pre-render billing check (phase 4 prerequisite)
  • phase 4: gateway wraps render (optional)

links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment