8.9 KiB
Unified Storage System
CodeForge now features a unified storage system that automatically selects the best available storage backend for your data persistence needs.
Storage Backends
The system supports three storage backends in order of preference:
1. SQLite (Preferred)
- Type: On-disk database via WASM
- Persistence: Data stored in browser localStorage as serialized SQLite database
- Pros:
- SQL query support
- Better performance for complex queries
- More robust data integrity
- Works offline
- Cons:
- Requires sql.js library (optional dependency)
- Slightly larger bundle size
- localStorage size limits (~5-10MB)
- Installation:
npm install sql.js
2. IndexedDB (Default)
- Type: Browser-native key-value store
- Persistence: Data stored in browser IndexedDB
- Pros:
- No additional dependencies
- Large storage capacity (usually >50MB, can be GBs)
- Fast for simple key-value operations
- Works offline
- Native browser support
- Cons:
- No SQL query support
- More complex API
- Asynchronous only
3. Spark KV (Fallback)
- Type: Cloud key-value store
- Persistence: Data stored in Spark runtime
- Pros:
- No size limits
- Synced across devices
- Persistent beyond browser
- Cons:
- Requires Spark runtime
- Online only
- Slower than local storage
Usage
Basic Usage
import { unifiedStorage } from '@/lib/unified-storage'
// Get data
const value = await unifiedStorage.get<MyType>('my-key')
// Set data
await unifiedStorage.set('my-key', myData)
// Delete data
await unifiedStorage.delete('my-key')
// Get all keys
const keys = await unifiedStorage.keys()
// Clear all data
await unifiedStorage.clear()
// Check current backend
const backend = await unifiedStorage.getBackend()
console.log(`Using: ${backend}`) // 'sqlite', 'indexeddb', or 'sparkkv'
React Hook
import { useUnifiedStorage } from '@/hooks/use-unified-storage'
function MyComponent() {
const [todos, setTodos, deleteTodos] = useUnifiedStorage('todos', [])
const addTodo = async (todo: Todo) => {
// ALWAYS use functional updates to avoid stale data
await setTodos((current) => [...current, todo])
}
const removeTodo = async (id: string) => {
await setTodos((current) => current.filter(t => t.id !== id))
}
return (
<div>
<button onClick={() => addTodo({ id: '1', text: 'New Todo' })}>
Add Todo
</button>
<button onClick={deleteTodos}>Clear All</button>
</div>
)
}
Storage Backend Management
import { useStorageBackend } from '@/hooks/use-unified-storage'
function StorageManager() {
const {
backend,
isLoading,
switchToSQLite,
switchToIndexedDB,
exportData,
importData,
} = useStorageBackend()
return (
<div>
<p>Current backend: {backend}</p>
<button onClick={switchToSQLite}>Switch to SQLite</button>
<button onClick={switchToIndexedDB}>Switch to IndexedDB</button>
<button onClick={async () => {
const data = await exportData()
console.log('Exported:', data)
}}>
Export Data
</button>
</div>
)
}
Migration Between Backends
The system supports seamless migration between storage backends:
// Migrate from IndexedDB to SQLite (preserves all data)
await unifiedStorage.switchToSQLite()
// Migrate from SQLite to IndexedDB (preserves all data)
await unifiedStorage.switchToIndexedDB()
When switching backends:
- All existing data is exported from the current backend
- The new backend is initialized
- All data is imported into the new backend
- The preference is saved for future sessions
Data Export/Import
Export and import data for backup or migration purposes:
// Export all data as JSON
const data = await unifiedStorage.exportData()
const json = JSON.stringify(data, null, 2)
// Save to file
const blob = new Blob([json], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'codeforge-backup.json'
a.click()
// Import data from JSON
const imported = JSON.parse(jsonString)
await unifiedStorage.importData(imported)
Backend Detection
The system automatically detects and selects the best available backend on initialization:
- SQLite is attempted first if
localStorage.getItem('codeforge-prefer-sqlite') === 'true' - IndexedDB is attempted next if available in the browser
- Spark KV is used as a last resort fallback
You can check which backend is in use:
const backend = await unifiedStorage.getBackend()
// Returns: 'sqlite' | 'indexeddb' | 'sparkkv' | null
Performance Considerations
SQLite
- Best for: Complex queries, relational data, large datasets
- Read: Fast (in-memory queries)
- Write: Moderate (requires serialization to localStorage)
- Capacity: Limited by localStorage (~5-10MB)
IndexedDB
- Best for: Simple key-value storage, large data volumes
- Read: Very fast (optimized for key lookups)
- Write: Very fast (optimized browser API)
- Capacity: Large (typically 50MB+, can scale to GBs)
Spark KV
- Best for: Cross-device sync, cloud persistence
- Read: Moderate (network latency)
- Write: Moderate (network latency)
- Capacity: Unlimited
Troubleshooting
SQLite Not Available
If SQLite fails to initialize:
- Check console for errors
- Ensure sql.js is installed:
npm install sql.js - System will automatically fallback to IndexedDB
IndexedDB Quota Exceeded
If IndexedDB storage is full:
- Clear old data:
await unifiedStorage.clear() - Export important data first
- Consider switching to Spark KV for unlimited storage
Data Not Persisting
- Check which backend is active:
await unifiedStorage.getBackend() - Verify browser supports storage (check if in private mode)
- Check browser console for errors
- Try exporting/importing data to refresh storage
Best Practices
-
Use Functional Updates: Always use functional form of setState to avoid stale data:
// ❌ WRONG - can lose data setTodos([...todos, newTodo]) // ✅ CORRECT - always safe setTodos((current) => [...current, newTodo]) -
Handle Errors: Wrap storage operations in try-catch:
try { await unifiedStorage.set('key', value) } catch (error) { console.error('Storage failed:', error) toast.error('Failed to save data') } -
Export Regularly: Create backups of important data:
const backup = await unifiedStorage.exportData() // Save backup somewhere safe -
Use Appropriate Backend: Choose based on your needs:
- Local-only, small data → IndexedDB
- Local-only, needs SQL → SQLite (install sql.js)
- Cloud sync needed → Spark KV
UI Component
The app includes a StorageSettingsPanel component that provides a user-friendly interface for:
- Viewing current storage backend
- Switching between backends
- Exporting/importing data
- Viewing storage statistics
Add it to your settings page:
import { StorageSettingsPanel } from '@/components/StorageSettingsPanel'
function SettingsPage() {
return (
<div>
<h1>Settings</h1>
<StorageSettingsPanel />
</div>
)
}
Architecture
┌─────────────────────────────────────────┐
│ Unified Storage API │
│ (unifiedStorage.get/set/delete/keys) │
└──────────────┬──────────────────────────┘
│
├─ Automatic Backend Detection
│
┌───────┴───────┬─────────────┬────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌────────────┐ ┌─────────┐ ┌────┐
│ SQLite │ │ IndexedDB │ │Spark KV │ │ ? │
│ (optional) │ │ (default) │ │(fallback│ │Next│
└─────────────┘ └────────────┘ └─────────┘ └────┘
│ │ │
└───────┬───────┴─────────────┘
│
▼
Browser Storage
Future Enhancements
- Add compression for large data objects
- Implement automatic backup scheduling
- Add support for native file system API
- Support for WebSQL (legacy browsers)
- Encrypted storage option
- Storage analytics and usage metrics
- Automatic data migration on version changes