diff --git a/STORAGE.md b/STORAGE.md index 1bd14e2..cb808b0 100644 --- a/STORAGE.md +++ b/STORAGE.md @@ -1,14 +1,30 @@ # Unified Storage System -CodeForge now features a unified storage system that automatically selects the best available storage backend for your data persistence needs. +CodeForge features a unified storage system that automatically selects the best available storage backend for your data persistence needs. ## Storage Backends -The system supports four storage backends in order of preference: +The system supports four storage backends: -### 1. **Flask Backend (Optional)** +### 1. **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 + - **Default behavior** - works out of the box +- **Cons**: + - No SQL query support + - More complex API + - Asynchronous only + +### 2. **Flask Backend (Optional)** - **Type**: Remote HTTP API with SQLite database - **Persistence**: Data stored on Flask server with SQLite +- **When Used**: Only when explicitly configured via UI settings or environment variable - **Pros**: - Cross-device synchronization - Centralized data management @@ -20,20 +36,9 @@ The system supports four storage backends in order of preference: - Network latency - Requires configuration - **Setup**: See backend/README.md for installation - -### 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 +- **Configuration**: + - **UI**: Go to Settings → Storage and enable Flask backend + - **Environment Variable**: Set `VITE_FLASK_BACKEND_URL=http://your-backend-url:5001` in `.env` file ### 3. **SQLite (Optional)** - **Type**: On-disk database via WASM diff --git a/STORAGE_DEFAULT_INDEXEDDB.md b/STORAGE_DEFAULT_INDEXEDDB.md new file mode 100644 index 0000000..bc98eb2 --- /dev/null +++ b/STORAGE_DEFAULT_INDEXEDDB.md @@ -0,0 +1,174 @@ +# Storage Backend Default: IndexedDB + +## Summary + +The storage system has been updated to **default to IndexedDB** instead of Flask backend. Flask backend is now opt-in only, requiring explicit configuration through UI settings or environment variables. + +## Changes Made + +### 1. **Updated Storage Initialization Logic** (`src/lib/unified-storage.ts`) + +**Previous Behavior:** +- Checked for Flask backend preference first +- If `codeforge-prefer-flask` was set in localStorage, attempted Flask connection +- Fell back to IndexedDB only if Flask failed + +**New Behavior:** +- **IndexedDB is now the default** - initialized first unless Flask is explicitly configured +- Flask backend is only used when: + - `VITE_FLASK_BACKEND_URL` environment variable is set, OR + - User explicitly enables Flask backend in UI settings +- Priority order: + 1. **Flask** (only if explicitly configured) + 2. **IndexedDB** (default) + 3. **SQLite** (if preferred) + 4. **Spark KV** (fallback) + +### 2. **Environment Variable Support** + +The Flask backend URL can be configured via environment variable: + +```bash +# .env file +VITE_FLASK_BACKEND_URL=http://localhost:5001 +``` + +Or for production: +```bash +VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +### 3. **Updated UI Components** (`src/components/StorageSettingsPanel.tsx`) + +Added Flask backend switching functionality: +- Input field for Flask backend URL +- Button to switch to Flask backend +- Clear indication that IndexedDB is the default +- Improved descriptions explaining when to use each backend + +### 4. **Updated Documentation** (`STORAGE.md`) + +- Reorganized backend priority to show IndexedDB first as default +- Added clear "Default" label for IndexedDB +- Documented Flask backend configuration methods +- Clarified that Flask requires explicit configuration + +## Usage + +### Default Behavior (No Configuration) + +By default, the app will use **IndexedDB** automatically: + +```typescript +import { unifiedStorage } from '@/lib/unified-storage' + +// Uses IndexedDB by default +await unifiedStorage.set('my-key', myData) +const data = await unifiedStorage.get('my-key') +``` + +Console output: +``` +[Storage] Initializing default IndexedDB backend... +[Storage] ✓ Using IndexedDB (default) +``` + +### Enabling Flask Backend + +#### Method 1: Environment Variable (Recommended for Production) + +Create a `.env` file: +```bash +VITE_FLASK_BACKEND_URL=http://localhost:5001 +``` + +Or set in Docker/Caprover: +```bash +docker run -e VITE_FLASK_BACKEND_URL=https://backend.example.com myapp +``` + +#### Method 2: UI Settings (User Preference) + +1. Navigate to **Settings → Storage** +2. Enter Flask backend URL in the input field +3. Click **"Flask Backend"** button +4. System will migrate all data from IndexedDB to Flask + +### Switching Back to IndexedDB + +In UI Settings: +1. Navigate to **Settings → Storage** +2. Click **"IndexedDB (Default)"** button +3. System will migrate all data back to IndexedDB + +## Benefits of IndexedDB Default + +1. **No Setup Required** - Works immediately without backend server +2. **Offline First** - Full functionality without network connection +3. **Fast Performance** - Local storage is faster than network requests +4. **Privacy** - Data stays on user's device by default +5. **Simpler Deployment** - No backend infrastructure needed for basic usage + +## When to Use Flask Backend + +Enable Flask backend when you need: +- **Cross-device synchronization** - Share data across multiple devices +- **Centralized data management** - Admin access to all user data +- **Server-side processing** - Complex queries, analytics, backups +- **Team collaboration** - Multiple users sharing the same data +- **Production deployments** - Centralized data storage for SaaS applications + +## Migration Path + +The system automatically migrates data when switching backends: + +```typescript +// Switch from IndexedDB to Flask +await switchToFlask('http://localhost:5001') + +// Switch from Flask back to IndexedDB +await switchToIndexedDB() +``` + +All existing data is preserved during migration. + +## Backwards Compatibility + +Existing projects with Flask backend configured will continue to use Flask backend: +- If `codeforge-prefer-flask` is set in localStorage +- If `VITE_FLASK_BACKEND_URL` is configured +- Data will not be lost or modified + +## Testing + +To verify the default behavior: + +1. **Clear localStorage** (to remove any preferences): + ```javascript + localStorage.clear() + ``` + +2. **Reload the app** + +3. **Check console output**: + ``` + [Storage] Initializing default IndexedDB backend... + [Storage] ✓ Using IndexedDB (default) + ``` + +4. **Open DevTools → Application → IndexedDB** + - Should see `CodeForgeDB` database + - Should see `keyvalue` object store + +## Files Changed + +1. `src/lib/unified-storage.ts` - Updated initialization logic +2. `src/components/StorageSettingsPanel.tsx` - Added Flask backend UI +3. `STORAGE.md` - Updated documentation +4. `.env.example` - Already documented VITE_FLASK_BACKEND_URL + +## Related Documentation + +- [STORAGE.md](./STORAGE.md) - Complete storage system documentation +- [FLASK_BACKEND_SETUP.md](./FLASK_BACKEND_SETUP.md) - Flask backend installation guide +- [.env.example](./.env.example) - Environment variable configuration diff --git a/src/components/StorageSettingsPanel.tsx b/src/components/StorageSettingsPanel.tsx index 0836c96..41e7a48 100644 --- a/src/components/StorageSettingsPanel.tsx +++ b/src/components/StorageSettingsPanel.tsx @@ -1,8 +1,10 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' import { useStorageBackend } from '@/hooks/use-unified-storage' -import { Database, HardDrive, Cloud, Download, Upload, CircleNotch } from '@phosphor-icons/react' +import { Database, HardDrive, Cloud, Download, Upload, CircleNotch, CloudArrowUp } from '@phosphor-icons/react' import { toast } from 'sonner' import { useState } from 'react' @@ -10,6 +12,7 @@ export function StorageSettingsPanel() { const { backend, isLoading, + switchToFlask, switchToSQLite, switchToIndexedDB, exportData, @@ -19,6 +22,29 @@ export function StorageSettingsPanel() { const [isSwitching, setIsSwitching] = useState(false) const [isExporting, setIsExporting] = useState(false) const [isImporting, setIsImporting] = useState(false) + const [flaskUrl, setFlaskUrl] = useState(localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001') + + const handleSwitchToFlask = async () => { + if (backend === 'flask') { + toast.info('Already using Flask backend') + return + } + + if (!flaskUrl) { + toast.error('Please enter a Flask backend URL') + return + } + + setIsSwitching(true) + try { + await switchToFlask(flaskUrl) + toast.success('Switched to Flask backend') + } catch (error) { + toast.error(`Failed to switch to Flask: ${error instanceof Error ? error.message : 'Unknown error'}`) + } finally { + setIsSwitching(false) + } + } const handleSwitchToSQLite = async () => { if (backend === 'sqlite') { @@ -46,7 +72,7 @@ export function StorageSettingsPanel() { setIsSwitching(true) try { await switchToIndexedDB() - toast.success('Switched to IndexedDB storage') + toast.success('Switched to IndexedDB storage (default)') } catch (error) { toast.error(`Failed to switch to IndexedDB: ${error instanceof Error ? error.message : 'Unknown error'}`) } finally { @@ -102,6 +128,8 @@ export function StorageSettingsPanel() { const getBackendIcon = () => { switch (backend) { + case 'flask': + return case 'sqlite': return case 'indexeddb': @@ -115,10 +143,12 @@ export function StorageSettingsPanel() { const getBackendLabel = () => { switch (backend) { + case 'flask': + return 'Flask Backend (Remote)' case 'sqlite': return 'SQLite (On-disk)' case 'indexeddb': - return 'IndexedDB (Browser)' + return 'IndexedDB (Browser) - Default' case 'sparkkv': return 'Spark KV (Cloud)' default: @@ -128,10 +158,12 @@ export function StorageSettingsPanel() { const getBackendDescription = () => { switch (backend) { + case 'flask': + return 'Data stored on Flask server with SQLite (cross-device sync)' case 'sqlite': return 'Data stored in SQLite database persisted to localStorage' case 'indexeddb': - return 'Data stored in browser IndexedDB (recommended for most users)' + return 'Data stored in browser IndexedDB (default, recommended for most users)' case 'sparkkv': return 'Data stored in Spark cloud key-value store' default: @@ -184,7 +216,51 @@ export function StorageSettingsPanel() {

Switch Storage Backend

+ +
+
+ + setFlaskUrl(e.target.value)} + placeholder="http://localhost:5001" + className="mt-1" + /> +

+ Enter Flask backend URL or set VITE_FLASK_BACKEND_URL environment variable +

+
+
+
+ +

- Switching storage backends will migrate all existing data + IndexedDB is the default and works offline. Flask backend enables cross-device sync.

diff --git a/src/lib/unified-storage.ts b/src/lib/unified-storage.ts index 3a27524..ff6d103 100644 --- a/src/lib/unified-storage.ts +++ b/src/lib/unified-storage.ts @@ -13,7 +13,7 @@ class FlaskBackendAdapter implements StorageAdapter { private baseUrl: string constructor(baseUrl?: string) { - this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001' + this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || import.meta.env.VITE_FLASK_BACKEND_URL || 'http://localhost:5001' } private async request(endpoint: string, options?: RequestInit): Promise { @@ -340,30 +340,31 @@ class UnifiedStorage { this.initPromise = (async () => { const preferFlask = localStorage.getItem('codeforge-prefer-flask') === 'true' + const flaskEnvUrl = import.meta.env.VITE_FLASK_BACKEND_URL const preferSQLite = localStorage.getItem('codeforge-prefer-sqlite') === 'true' - if (preferFlask) { + if (preferFlask || flaskEnvUrl) { try { - console.log('[Storage] Attempting to initialize Flask backend...') - const flaskAdapter = new FlaskBackendAdapter() + console.log('[Storage] Flask backend explicitly configured, attempting to initialize...') + const flaskAdapter = new FlaskBackendAdapter(flaskEnvUrl) await flaskAdapter.get('_health_check') this.adapter = flaskAdapter this.backend = 'flask' console.log('[Storage] ✓ Using Flask backend') return } catch (error) { - console.warn('[Storage] Flask backend not available:', error) + console.warn('[Storage] Flask backend not available, falling back to IndexedDB:', error) } } if (typeof indexedDB !== 'undefined') { try { - console.log('[Storage] Attempting to initialize IndexedDB...') + console.log('[Storage] Initializing default IndexedDB backend...') const idbAdapter = new IndexedDBAdapter() await idbAdapter.get('_health_check') this.adapter = idbAdapter this.backend = 'indexeddb' - console.log('[Storage] ✓ Using IndexedDB') + console.log('[Storage] ✓ Using IndexedDB (default)') return } catch (error) { console.warn('[Storage] IndexedDB not available:', error) @@ -372,7 +373,7 @@ class UnifiedStorage { if (preferSQLite) { try { - console.log('[Storage] Attempting to initialize SQLite...') + console.log('[Storage] SQLite fallback, attempting to initialize...') const sqliteAdapter = new SQLiteAdapter() await sqliteAdapter.get('_health_check') this.adapter = sqliteAdapter @@ -386,7 +387,7 @@ class UnifiedStorage { if (window.spark?.kv) { try { - console.log('[Storage] Attempting to initialize Spark KV...') + console.log('[Storage] Spark KV fallback, attempting to initialize...') const sparkAdapter = new SparkKVAdapter() await sparkAdapter.get('_health_check') this.adapter = sparkAdapter