mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Generated by Spark: I am having issues with this. Remember, we should be defaulting to IndexedDB, not the Flask API. Flask API is obtained by explicitly setting it in the UI or setting a env variable. export default function applyFetchPatch() {
if (ssrSafeWindow) {
ssrSafeWindow.fetch = async (input: string | URL | Request, init?: RequestInit) => {
try {
const response = await globalFetch(input, init)
sendFetchStats({input, error: !response.ok, status: response.status})
return response
} catch (error) {
sendFetchStats({input, error: true, status: 'unknown'})
throw error
}
}
}
}
This commit is contained in:
39
STORAGE.md
39
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
|
||||
|
||||
174
STORAGE_DEFAULT_INDEXEDDB.md
Normal file
174
STORAGE_DEFAULT_INDEXEDDB.md
Normal file
@@ -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
|
||||
@@ -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 <CloudArrowUp className="w-5 h-5" />
|
||||
case 'sqlite':
|
||||
return <HardDrive className="w-5 h-5" />
|
||||
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() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium">Switch Storage Backend</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="flask-url" className="text-sm">Flask Backend URL (Optional)</Label>
|
||||
<Input
|
||||
id="flask-url"
|
||||
type="text"
|
||||
value={flaskUrl}
|
||||
onChange={(e) => setFlaskUrl(e.target.value)}
|
||||
placeholder="http://localhost:5001"
|
||||
className="mt-1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Enter Flask backend URL or set VITE_FLASK_BACKEND_URL environment variable
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
onClick={handleSwitchToIndexedDB}
|
||||
disabled={backend === 'indexeddb' || isSwitching}
|
||||
variant={backend === 'indexeddb' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
{isSwitching ? (
|
||||
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Database className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
IndexedDB (Default)
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSwitchToFlask}
|
||||
disabled={backend === 'flask' || isSwitching}
|
||||
variant={backend === 'flask' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
{isSwitching ? (
|
||||
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<CloudArrowUp className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Flask Backend
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSwitchToSQLite}
|
||||
disabled={backend === 'sqlite' || isSwitching}
|
||||
@@ -198,22 +274,9 @@ export function StorageSettingsPanel() {
|
||||
)}
|
||||
SQLite
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSwitchToIndexedDB}
|
||||
disabled={backend === 'indexeddb' || isSwitching}
|
||||
variant={backend === 'indexeddb' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
>
|
||||
{isSwitching ? (
|
||||
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Database className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
IndexedDB
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Switching storage backends will migrate all existing data
|
||||
IndexedDB is the default and works offline. Flask backend enables cross-device sync.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user