Overview
Every Brainbox client maintains a complete SQLite database that stores:- Workspace nodes (spaces, folders, pages, databases, etc.)
- Rich text document states (Yjs CRDT)
- User profiles and workspace memberships
- Pending mutations and sync cursors
- File upload/download states
Client Database Implementation
Database Engines
Brainbox uses different SQLite implementations depending on the platform:Web Application
- SQLite WASM: Compiled to WebAssembly, runs in browser
- OPFS storage: Origin Private File System for persistence
- Worker thread: Runs in Web Worker to avoid blocking UI
- Size limit: Browser storage quotas apply (typically several GB)
Desktop Application
- better-sqlite3: Native Node.js binding, fastest performance
- Synchronous API: Simpler programming model
- File system: Standard SQLite file on disk
- No size limit: Limited only by disk space
Database Schema
The client database schema is defined inpackages/client/src/databases/workspace/schema.ts and includes these core tables:
Users Table
Nodes Table
attributes: JSON string of node-specific datalocal_revision: Revision number for local changesserver_revision: Last known server revisionroot_id: Top-level container (workspace or space)
Node States Table
state: Binary Yjs state vectorrevision: Version tracking for sync
Node Updates Table
Mutations Table
pending: Not yet sent to serverin_progress: Currently syncingcompleted: Successfully syncedfailed: Sync failed, may retry
Database Migrations
Migrations are stored inpackages/client/src/databases/workspace/migrations/ and run sequentially:
migrations table to prevent re-running.
Query Layer
Queries are implemented inpackages/client/src/queries/ using Kysely for type safety:
Query Types
nodes.list: Fetch nodes by parent, type, or filtersnodes.get: Get single node by IDnodes.search: Full-text search across nodesusers.list: Fetch workspace memberscollaborations.list: Get workspace memberships
Mutation Layer
Mutations write to the local database first, then queue for server sync:Mutation Flow
- Validation: Check permissions and validate input with Zod
- Local Write: Insert/update/delete in SQLite database
- Queue Mutation: Add to
mutationstable withpendingstatus - Optimistic Update: Return success to UI immediately
- Background Sync: Sync engine processes mutation queue
- Server Response: Update mutation status to
completedorfailed - Conflict Resolution: Apply server version if conflicts detected
Mutation Types
nodes.create: Create new nodenodes.update: Update node attributesnodes.delete: Soft-delete node (tombstone)nodes.move: Change parent or positiondocument.update: Update rich text content
Offline Operation
How It Works
- Immediate Writes: All changes write to local SQLite
- Mutation Queue: Pending changes stored in
mutationstable - Connection Detection: Client detects online/offline status
- Automatic Retry: When reconnected, queue processes automatically
- Conflict Resolution: CRDTs handle concurrent edits
Offline Capabilities
Fully functional offline:- Create, edit, delete pages and databases
- Rich text editing with full formatting
- Search across all local content
- Navigate workspace hierarchy
- View user profiles
- File uploads (queued when offline)
- Workspace invitations
- Real-time collaboration presence
- Server-side search/AI features
Connection Status
Data Persistence
Browser (Web App)
- Storage: Origin Private File System (OPFS)
- Quota: Managed by browser, typically 10% of available disk
- Eviction: Marked as “persistent” to prevent automatic eviction
- Access: Only accessible to same origin (security)
Desktop App
- Storage: Standard SQLite file in user data directory
- Location:
~/Library/Application Support/Brainbox/(macOS) - Quota: No limit (uses available disk space)
- Backup: Users can backup SQLite file directly
Database Size
Typical workspace database sizes:- Small workspace (100 pages): 5-10 MB
- Medium workspace (1,000 pages): 50-100 MB
- Large workspace (10,000 pages): 500 MB - 1 GB
Sync Cursors
The client tracks sync progress for each data stream:nodes.updates:{rootId}: Last synced node revisiondocument.updates:{nodeId}: Last synced document revisionusers:{workspaceId}: Last synced user revision
Data Initialization
When a client first connects to a workspace:- Create Database: Initialize empty SQLite database
- Run Migrations: Execute all migration scripts
- Initial Sync: Request full workspace state from server
- Populate Database: Insert all nodes, users, memberships
- Set Cursors: Record initial sync positions
- Subscribe: Start listening for incremental updates
Performance Optimization
Indexing Strategy
Critical indexes for query performance:Query Caching
TanStack Query caches results in memory:Batch Operations
Mutations can be batched for efficiency:Comparison: Local-First vs Traditional
Traditional (Server-First)
Latency: 100-500ms (network + database)Local-First (Brainbox)
Latency: Under 10ms (local database only)Best Practices
When to Use Local-First
Ideal for:- Note-taking and document editing
- Project management tools
- Collaborative workspaces
- Mobile apps with poor connectivity
- Financial transactions (require server confirmation)
- Real-time trading systems
- Systems requiring strong consistency guarantees
Client Database Maintenance
- Vacuum periodically: Reclaim space from deleted data
- Analyze tables: Update query planner statistics
- Monitor size: Warn users of large databases
- Backup option: Export workspace data
Next Steps
- Sync Engine - How local changes sync to server
- CRDT Implementation - Conflict-free merging with Yjs
- Monorepo Structure - Client package organization