import { ICalendarEvent } from '../types/CalendarTypes'; import { IEventRepository, UpdateSource } from './IEventRepository'; import { IndexedDBService } from '../storage/IndexedDBService'; import { OperationQueue } from '../storage/OperationQueue'; /** * IndexedDBEventRepository * Offline-first repository using IndexedDB as single source of truth * * All CRUD operations: * - Save to IndexedDB immediately (always succeeds) * - Add to sync queue if source is 'local' * - Background SyncManager processes queue to sync with API */ export class IndexedDBEventRepository implements IEventRepository { private indexedDB: IndexedDBService; private queue: OperationQueue; constructor(indexedDB: IndexedDBService, queue: OperationQueue) { this.indexedDB = indexedDB; this.queue = queue; } /** * Load all events from IndexedDB */ async loadEvents(): Promise { return await this.indexedDB.getAllEvents(); } /** * Create a new event * - Generates ID * - Saves to IndexedDB * - Adds to queue if local (needs sync) */ async createEvent(event: Omit, source: UpdateSource = 'local'): Promise { // Generate unique ID const id = this.generateEventId(); // Determine sync status based on source const syncStatus = source === 'local' ? 'pending' : 'synced'; // Create full event object const newEvent: ICalendarEvent = { ...event, id, syncStatus } as ICalendarEvent; // Save to IndexedDB await this.indexedDB.saveEvent(newEvent); // If local change, add to sync queue if (source === 'local') { await this.queue.enqueue({ type: 'create', eventId: id, data: newEvent, timestamp: Date.now(), retryCount: 0 }); } return newEvent; } /** * Update an existing event * - Updates in IndexedDB * - Adds to queue if local (needs sync) */ async updateEvent(id: string, updates: Partial, source: UpdateSource = 'local'): Promise { // Get existing event const existingEvent = await this.indexedDB.getEvent(id); if (!existingEvent) { throw new Error(`Event with ID ${id} not found`); } // Determine sync status based on source const syncStatus = source === 'local' ? 'pending' : 'synced'; // Merge updates const updatedEvent: ICalendarEvent = { ...existingEvent, ...updates, id, // Ensure ID doesn't change syncStatus }; // Save to IndexedDB await this.indexedDB.saveEvent(updatedEvent); // If local change, add to sync queue if (source === 'local') { await this.queue.enqueue({ type: 'update', eventId: id, data: updates, timestamp: Date.now(), retryCount: 0 }); } return updatedEvent; } /** * Delete an event * - Removes from IndexedDB * - Adds to queue if local (needs sync) */ async deleteEvent(id: string, source: UpdateSource = 'local'): Promise { // Check if event exists const existingEvent = await this.indexedDB.getEvent(id); if (!existingEvent) { throw new Error(`Event with ID ${id} not found`); } // If local change, add to sync queue BEFORE deleting // (so we can send the delete operation to API later) if (source === 'local') { await this.queue.enqueue({ type: 'delete', eventId: id, data: {}, // No data needed for delete timestamp: Date.now(), retryCount: 0 }); } // Delete from IndexedDB await this.indexedDB.deleteEvent(id); } /** * Generate unique event ID * Format: {timestamp}-{random} */ private generateEventId(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 9); return `${timestamp}-${random}`; } }