6.9 KiB
IndexedDB Migration Complete
Overview
The application has been successfully migrated from Spark KV to IndexedDB for all data persistence needs. This provides better performance, larger storage capacity, and more robust data management capabilities.
What Changed
Core Infrastructure
- IndexedDB Manager (
src/lib/indexed-db.ts): Robust IndexedDB wrapper with full CRUD operations - useIndexedDBState Hook (
src/hooks/use-indexed-db-state.ts): React hook for reactive IndexedDB state management - Database Version: Upgraded to v3 to support the migration
Migrated Hooks
All hooks that previously used useKV from @github/spark/hooks now use useIndexedDBState:
- use-notifications.ts - Notification storage
- use-session-timeout-preferences.ts - Session timeout preferences
- use-translation.ts - Locale preferences
- use-locale-init.ts - Locale initialization
- use-sample-data.ts - Sample data initialization (now uses direct IndexedDB operations)
- use-favorites.ts - User favorites
- use-app-data.ts - All application entity data (timesheets, invoices, payroll, etc.)
Storage Architecture
Entity Stores (Object Stores)
The following object stores are used for structured business entities:
timesheets- Timesheet records with indexes on workerId, status, weekEndinginvoices- Invoice records with indexes on clientId, status, invoiceDatepayrollRuns- Payroll run records with indexes on status, periodEndingworkers- Worker records with indexes on status, emailcomplianceDocs- Compliance documents with indexes on workerId, status, expiryDateexpenses- Expense records with indexes on workerId, status, daterateCards- Rate card records with indexes on clientId, rolesessions- Session management with indexes on userId, lastActivityTimestamp
App State Store
The appState object store is used for key-value pairs:
- User preferences (locale, session timeout settings)
- UI state
- Notifications
- Favorites
- Any other non-entity data
Migration Benefits
Performance
- Indexed Queries: Fast lookups using IndexedDB indexes
- Bulk Operations: Efficient batch creates and updates
- Asynchronous: Non-blocking I/O operations
Storage
- Capacity: Much larger storage limits than Spark KV
- Structured Data: Proper schema with indexes for complex queries
- Transactions: ACID-compliant database operations
Developer Experience
- Type Safety: Full TypeScript support
- React Integration: Seamless integration with React state management
- Error Handling: Comprehensive error handling and recovery
API Reference
useIndexedDBState Hook
import { useIndexedDBState } from '@/hooks/use-indexed-db-state'
// Basic usage (for app state)
const [value, setValue, deleteValue] = useIndexedDBState<string>('my-key', 'default')
// Entity store usage (automatically detected)
const [entities, setEntities] = useIndexedDBState<MyEntity[]>(STORES.MY_STORE, [])
// Functional updates (always use for correct behavior)
setValue(prevValue => prevValue + 1)
setEntities(prevEntities => [...prevEntities, newEntity])
Direct IndexedDB Access
import { indexedDB, STORES } from '@/lib/indexed-db'
// CRUD operations
await indexedDB.create(STORES.TIMESHEETS, timesheet)
await indexedDB.read(STORES.TIMESHEETS, id)
await indexedDB.readAll(STORES.TIMESHEETS)
await indexedDB.update(STORES.TIMESHEETS, updatedTimesheet)
await indexedDB.delete(STORES.TIMESHEETS, id)
// Bulk operations
await indexedDB.bulkCreate(STORES.TIMESHEETS, timesheets)
await indexedDB.bulkUpdate(STORES.TIMESHEETS, timesheets)
// Query with predicate
const results = await indexedDB.query(STORES.TIMESHEETS, t => t.status === 'pending')
// Index-based queries
const byWorker = await indexedDB.readByIndex(STORES.TIMESHEETS, 'workerId', workerId)
// App state
await indexedDB.saveAppState('my-key', value)
const value = await indexedDB.getAppState('my-key')
await indexedDB.deleteAppState('my-key')
Breaking Changes
None!
The migration was designed to be backward compatible. The API surface of useIndexedDBState matches useKV exactly:
// Before (Spark KV)
const [value, setValue, deleteValue] = useKV('key', defaultValue)
// After (IndexedDB)
const [value, setValue, deleteValue] = useIndexedDBState('key', defaultValue)
Data Persistence
Session Data
- Sessions are stored in the
sessionsobject store - Automatic expiry after 24 hours
- Activity tracking for timeout management
Business Data
- All entity data is stored in dedicated object stores
- Indexes enable fast queries and filtering
- Support for complex relationships
User Preferences
- Stored in the
appStatestore - Includes locale, session timeout settings, UI preferences
- Persists across browser sessions
Best Practices
1. Always Use Functional Updates
// ❌ WRONG - Don't reference state from closure
setValue([...value, newItem]) // value might be stale!
// ✅ CORRECT - Use functional update
setValue(currentValue => [...currentValue, newItem])
2. Use Appropriate Storage
// Entity data → Use entity stores
useIndexedDBState(STORES.TIMESHEETS, [])
// Simple preferences → Use app state
useIndexedDBState('user-preference', defaultValue)
3. Handle Errors
try {
await indexedDB.create(STORES.TIMESHEETS, timesheet)
} catch (error) {
console.error('Failed to create timesheet:', error)
// Handle error appropriately
}
Testing
The IndexedDB implementation uses the browser's native IndexedDB API, which is available in all modern browsers and can be tested in:
- Development environment
- Browser DevTools (Application → IndexedDB)
- Automated tests using jsdom (already configured)
Future Enhancements
Potential improvements for future iterations:
- Data Migration Tool: Utility to migrate from Spark KV to IndexedDB (if needed for existing users)
- IndexedDB DevTools: Custom debugging panel for inspecting database state
- Query Builder: Fluent API for complex queries
- Schema Migrations: Automated database schema versioning
- Offline Sync: Sync mechanism for multi-device scenarios
Troubleshooting
Database Not Initializing
Check browser console for errors. IndexedDB may be blocked in private/incognito mode.
Data Not Persisting
Ensure you're using functional updates with useIndexedDBState.
Performance Issues
Use indexes for frequently queried fields. Consider pagination for large datasets.
Storage Quota Exceeded
IndexedDB has generous limits (~50% of available disk space), but consider implementing data archiving for very large datasets.
Support
For issues or questions:
- Check browser compatibility (IndexedDB is supported in all modern browsers)
- Review the IndexedDB manager implementation in
src/lib/indexed-db.ts - Inspect the database using browser DevTools (Application → Storage → IndexedDB → WorkForceProDB)