import { ICalendarEvent } from '../types/CalendarTypes'; import { IEventRepository, UpdateSource } from './IEventRepository'; import { IndexedDBService } from '../storage/IndexedDBService'; import { EventService } from '../storage/events/EventService'; import { OperationQueue } from '../storage/OperationQueue'; /** * IndexedDBEventRepository * Offline-first repository using IndexedDB as single source of truth * * All CRUD operations: * - Save to IndexedDB immediately via EventService (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 eventService: EventService; private queue: OperationQueue; constructor(indexedDB: IndexedDBService, queue: OperationQueue) { this.indexedDB = indexedDB; this.queue = queue; // EventService will be initialized after IndexedDB is ready this.eventService = null as any; } /** * Ensure EventService is initialized with database connection */ private ensureEventService(): void { if (!this.eventService && this.indexedDB.isInitialized()) { const db = (this.indexedDB as any).db; // Access private db property this.eventService = new EventService(db); } } /** * Load all events from IndexedDB * Ensures IndexedDB is initialized on first call */ async loadEvents(): Promise { // Lazy initialization on first data load if (!this.indexedDB.isInitialized()) { await this.indexedDB.initialize(); // TODO: Seeding should be done at application level, not here } this.ensureEventService(); return await this.eventService.getAll(); } /** * 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 via EventService this.ensureEventService(); await this.eventService.save(newEvent); // If local change, add to sync queue if (source === 'local') { await this.queue.enqueue({ type: 'create', entityId: id, dataEntity: { typename: 'Event', 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 via EventService this.ensureEventService(); const existingEvent = await this.eventService.get(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 via EventService await this.eventService.save(updatedEvent); // If local change, add to sync queue if (source === 'local') { await this.queue.enqueue({ type: 'update', entityId: id, dataEntity: { typename: 'Event', 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 via EventService this.ensureEventService(); const existingEvent = await this.eventService.get(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', entityId: id, dataEntity: { typename: 'Event', data: { id } // Minimal data for delete - just ID }, timestamp: Date.now(), retryCount: 0 }); } // Delete from IndexedDB via EventService await this.eventService.delete(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}`; } }