๐๏ธ DATABASE LAYER โ
Overview โ
The database layer uses Cloudflare D1 (SQLite) with Drizzle ORM for type-safe queries. All database operations are abstracted through service classes that extend BaseService.
Key Technologies:
- D1 Database: Serverless SQLite on Cloudflare's edge
- Drizzle ORM: Type-safe SQL query builder
- D1 Sessions API: Read replicas for lower latency
- Migrations: SQL-based schema migrations
Database Architecture โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ API Controllers โ
โ (Handle HTTP requests, validate input) โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Domain Services โ
โ AppService โ UserService โ AuthService โ etc. โ
โ (Business logic, transaction management) โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ extends
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BaseService โ
โ - Database connection (DatabaseService) โ
โ - Read replicas (D1 Sessions API) โ
โ - Common utilities (buildWhereConditions) โ
โ - Error handling โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DatabaseService โ
โ - Primary database connection (writes) โ
โ - Read replica connections (reads) โ
โ - Drizzle ORM instance โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Cloudflare D1 Database โ
โ Primary + Read Replicas (Global Distribution) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโBaseService Pattern โ
Location: /worker/database/services/BaseService.ts
Purpose โ
Provides common database functionality to all domain services:
- Database connection management
- Read replica access (D1 Sessions API)
- Type-safe where condition building
- Error handling patterns
- Logging
Implementation โ
abstract class BaseService {
protected logger = createLogger(this.constructor.name);
protected db: DatabaseService;
protected env: Env;
constructor(env: Env) {
this.db = createDatabaseService(env);
this.env = env;
}
// Direct database access (primary)
protected get database() {
return this.db.db;
}
// Read replica access (optimized latency)
protected getReadDb(strategy: 'fast' | 'fresh' = 'fast') {
return this.db.getReadDb(strategy);
}
// Build type-safe WHERE conditions
protected buildWhereConditions(
conditions: (SQL<unknown> | undefined)[]
): SQL<unknown> | undefined {
const validConditions = conditions.filter(
(c): c is SQL<unknown> => c !== undefined
);
if (validConditions.length === 0) return undefined;
if (validConditions.length === 1) return validConditions[0];
return and(...validConditions);
}
// Standard error handling
protected handleDatabaseError(
error: unknown,
operation: string,
context?: Record<string, unknown>
): never {
this.logger.error(`Database error in ${operation}`, { error, context });
throw error;
}
}D1 Sessions API - Read Replicas โ
What is D1 Sessions? โ
Cloudflare D1 Sessions API provides read replicas for D1 databases distributed globally. This dramatically reduces latency for read queries by serving them from the nearest replica.
Strategies โ
Location: /worker/database/database.ts
class DatabaseService {
getReadDb(strategy: 'fast' | 'fresh' = 'fast') {
if (strategy === 'fast') {
// Lowest latency - may be slightly stale
return drizzle(this.env.DB, { ... });
} else {
// Latest data - may have higher latency
return drizzle(this.env.DB.withSession({ strategy: 'fresh' }), { ... });
}
}
}When to Use Each Strategy โ
'fast' Strategy (Default) โ
Use for:
- Public app listings
- Public app details
- Analytics and stats
- Search results
- Any read-only public data
Benefits:
- Lowest latency (served from nearest replica)
- Suitable for data that can tolerate slight staleness (few seconds)
- Most queries should use this
Example:
// Public apps - use fast replicas
async getPublicApps(options: PublicAppQueryOptions) {
const readDb = this.getReadDb('fast'); // โ Use fast strategy
const apps = await readDb
.select()
.from(schema.apps)
.where(eq(schema.apps.visibility, 'public'));
}'fresh' Strategy โ
Use for:
- User's own data (own apps, favorites)
- Immediately after writes (read-after-write)
- Auth/session validation
- Account settings
- Any data where staleness is unacceptable
Benefits:
- Latest data from primary or recent replica
- Ensures user sees their own changes immediately
Example:
// User's own apps - use fresh data
async getUserApps(userId: string) {
const readDb = this.getReadDb('fresh'); // โ Use fresh strategy
const apps = await readDb
.select()
.from(schema.apps)
.where(eq(schema.apps.userId, userId));
}NEVER Use Read Replicas For: โ
โ Write operations - Always use primary (this.database) โ Auth validation - Use primary to avoid security issues โ Immediately after INSERT/UPDATE - Read from primary โ Critical consistency - Password changes, payments, etc.
Drizzle ORM Patterns โ
Basic Queries โ
SELECT โ
// Simple select
const users = await db
.select()
.from(schema.users)
.where(eq(schema.users.email, email));
// Select specific columns
const users = await db
.select({
id: schema.users.id,
email: schema.users.email
})
.from(schema.users);
// With JOIN
const apps = await db
.select({
app: schema.apps,
userName: schema.users.displayName
})
.from(schema.apps)
.leftJoin(schema.users, eq(schema.apps.userId, schema.users.id));INSERT โ
// Insert one
const [user] = await db
.insert(schema.users)
.values({
id: generateId(),
email: 'user@example.com',
displayName: 'User',
createdAt: new Date()
})
.returning();
// Insert many
await db
.insert(schema.apps)
.values([
{ id: id1, title: 'App 1', ... },
{ id: id2, title: 'App 2', ... }
]);UPDATE โ
await db
.update(schema.users)
.set({
displayName: 'New Name',
updatedAt: new Date()
})
.where(eq(schema.users.id, userId));DELETE โ
// Hard delete
await db
.delete(schema.sessions)
.where(eq(schema.sessions.id, sessionId));
// Soft delete (preferred)
await db
.update(schema.users)
.set({ deletedAt: new Date() })
.where(eq(schema.users.id, userId));Complex Queries โ
Aggregations โ
// COUNT
const result = await db
.select({ count: sql<number>`COUNT(*)` })
.from(schema.apps)
.where(eq(schema.apps.visibility, 'public'));
const total = result[0].count;
// SUM, AVG
const stats = await db
.select({
totalViews: sql<number>`SUM(${schema.appViews.id})`,
avgViews: sql<number>`AVG(view_count)`
})
.from(schema.apps);Subqueries โ
// Subquery in WHERE
const apps = await db
.select()
.from(schema.apps)
.where(
inArray(
schema.apps.id,
db.select({ id: schema.favorites.appId })
.from(schema.favorites)
.where(eq(schema.favorites.userId, userId))
)
);Conditional WHERE Clauses โ
// Use BaseService.buildWhereConditions()
const conditions: WhereCondition[] = [];
if (framework) {
conditions.push(eq(schema.apps.framework, framework));
}
if (search) {
conditions.push(
or(
sql`LOWER(${schema.apps.title}) LIKE ${`%${search}%`}`,
sql`LOWER(${schema.apps.description}) LIKE ${`%${search}%`}`
)
);
}
const whereClause = this.buildWhereConditions(conditions);
const apps = await db
.select()
.from(schema.apps)
.where(whereClause);Domain Services โ
Available Services โ
Location: /worker/database/services/
- AuthService - Authentication, login, OAuth
- SessionService - JWT sessions, token management
- UserService - User CRUD, profiles
- AppService - App CRUD, public listings, search, ranking
- AnalyticsService - Views, stars, activity tracking
- SecretsService - Encrypted secrets storage
- ModelConfigService - User model overrides
- ApiKeyService - API key generation, validation
Each extends BaseService, uses Drizzle ORM, follows standard CRUD patterns.
AppService - Public App Ranking โ
Key methods: createApp, getPublicApps (paginated with filters), getUserAppsWithFavorites, toggleAppStar, updateDeploymentId, updateGitHubRepository, updateAppScreenshot
Ranking algorithms:
- Popular: (views ร 1 + stars ร 3) DESC
- Trending: (recent_activity ร 1000000 + recency_bonus) DESC
- Recent: updatedAt DESC
- Starred: COUNT(stars) DESC
Read replica usage: Public queries use 'fast', user's own data uses 'fresh'
Database Migrations โ
Location: /migrations/ (SQL files + meta snapshots)
Commands:
npm run db:generate- Generate migration from schema changesnpm run db:migrate:local- Apply to local D1npm run db:migrate:remote- Apply to production D1npm run db:push:local- Direct push (dev only)
Tool: Drizzle Kit with d1-http driver
๐ Key Files Reference โ
Frontend Core Files โ
/src/api-types.ts- ALL shared API types (single source of truth)/src/lib/api-client.ts- ALL API calls defined here/src/routes/chat/chat.tsx- Main chat interface (1208 lines)/src/routes/chat/hooks/use-chat.ts- Chat state management (BRAIN)/src/routes/chat/utils/handle-websocket-message.ts- WebSocket handler (831 lines)/src/routes/chat/utils/deduplicate-messages.ts- Message deduplication utilities/src/routes/chat/components/phase-timeline.tsx- Phase progress UI/src/routes/chat/components/messages.tsx- User/AI message rendering/src/hooks/useAuthGuard.ts- Authentication guards/src/hooks/use-image-upload.ts- Image upload handling
Backend Core Files โ
/worker/agents/core/simpleGeneratorAgent.ts- Base agent DO class/worker/agents/core/state.ts- CodeGenState interface/worker/agents/core/websocket.ts- WebSocket message handler (250 lines)/worker/agents/constants.ts- WebSocket message type constants/worker/agents/inferutils/core.ts- LLM inference engine/worker/agents/inferutils/infer.ts- Inference execution wrapper/worker/agents/inferutils/config.ts- Model configurations/worker/agents/assistants/codeDebugger.ts- Deep debugger assistant/worker/agents/operations/UserConversationProcessor.ts- Orange AI (818 lines)/worker/agents/tools/customTools.ts- Tool registration/worker/api/routes/index.ts- Main API router/worker/database/schema.ts- Database schema (618 lines)
Configuration Files โ
/worker/agents/inferutils/config.ts- LLM model configs/wrangler.jsonc- Cloudflare Workers config/vite.config.ts- Frontend build config/tsconfig.json- TypeScript config/drizzle.config.local.ts- Local database config/drizzle.config.remote.ts- Remote database config
โ Checklist for Changes โ
Before submitting any change, verify:
- [ ] Types are properly defined (no
any) - [ ] Existing patterns are followed
- [ ] Code is DRY (no duplication)
- [ ] Comments are clear and concise
- [ ] File naming matches conventions
- [ ] API calls use
api-client.ts - [ ] Database operations use service classes
- [ ] Error handling is comprehensive
- [ ] AbortController lifecycle is correct (if applicable)
- [ ] WebSocket messages are handled (if applicable)
- [ ] This document is updated (if needed)
๐ง Troubleshooting Common Issues โ
Issue: "Cannot find module" errors โ
Cause: Import path incorrect or module not installed
Fix:
- Check import path matches file location
- For workspace imports, use
worker/...not../../../... - Run
npm installif package missing - Check
tsconfig.jsonpath mappings
Issue: Durable Object not receiving WebSocket messages โ
Check:
- Message type in constants:
/worker/agents/constants.ts - Handler in
/worker/agents/core/websocket.tsโhandleWebSocketMessage() - Frontend sending correct type (check browser console)
- WebSocket connection established (check
agent_connectedreceived)
Debug:
// Add to websocket.ts handleWebSocketMessage()
logger.info('Received WebSocket message', { type: message.type, data: message });Issue: LLM not calling tools โ
Common causes:
- Tool description unclear โ LLM doesn't know when to use it
- Tool not registered in
buildTools()orbuildDebugTools() - Parameter schema too complex โ simplify
- System prompt doesn't mention tool
Fix:
- Keep tool description to 2-3 clear lines
- Make parameters simple (prefer strings over complex objects)
- Add tool to relevant system prompt
Issue: Database query returning stale data โ
Cause: Using read replica for data that needs to be fresh
Fix:
// WRONG - uses fast replica
const readDb = this.getReadDb('fast');
// RIGHT - uses fresh data
const readDb = this.getReadDb('fresh');
// OR use primary for critical consistency
const result = await this.database.select()...Issue: "Rate limit exceeded" during development โ
Quick fix:
// In UserConversationProcessor.ts or codeDebugger.ts
// Temporarily increase max_tokens or reduce frequencyBetter fix: Use cheaper model for testing
// In config.ts
conversationalResponse: {
name: GEMINI_2_5_FLASH, // Fast & cheap
max_tokens: 4000,
}Issue: Type errors after schema change โ
Steps:
- Regenerate Drizzle types:
npm run db:generate - Restart TypeScript server in IDE
- Check migration applied:
npm run db:migrate:local
Issue: Sandbox deployment failing โ
Check logs:
// In DeploymentManager.ts, enable verbose logging
this.logger.info('Deployment attempt', {
sessionId: this.getSessionId(),
filesCount: files.length
});Common causes:
- Sandbox service unreachable
- Invalid template name
- sessionId mismatch (check
agent.state.sessionId) - npm install timeout โ increase timeout or split commands
Issue: Agent state not persisting โ
Verify:
- Check DO storage: Cloudflare dashboard โ Durable Objects
- Ensure
setState()called after changes - Check for exceptions in state serialization
Test:
const currentState = this.getState();
this.logger().info('State before save', { currentState });
this.setState(newState);
this.logger().info('State after save', { newState });Where to Look for Logs โ
Local development:
- Frontend: Browser console
- Worker: Terminal where
npm run dev:workeris running - Durable Objects: Same terminal, prefixed with DO ID
Production:
- Cloudflare dashboard โ Workers & Pages โ Logs
- Real-time logs via
wrangler tail - Sentry (if configured)
Useful Debug Snippets โ
Log all WebSocket messages:
// In websocket.ts
logger.info('[WS_IN]', { type: message.type, keys: Object.keys(message) });Log all tool calls:
// In customTools.ts executeToolWithDefinition()
logger.info('[TOOL_CALL]', { name: toolDef.function.name, args });Log state transitions:
// In simpleGeneratorAgent.ts launchStateMachine()
logger.info('[STATE_TRANSITION]', {
from: currentDevState,
to: executionResults.currentDevState
});