Calendar/src/repositories/IndexedDBEventRepository.ts

146 lines
3.9 KiB
TypeScript
Raw Normal View History

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<ICalendarEvent[]> {
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<ICalendarEvent, 'id'>, source: UpdateSource = 'local'): Promise<ICalendarEvent> {
// 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<ICalendarEvent>, source: UpdateSource = 'local'): Promise<ICalendarEvent> {
// 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<void> {
// 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}`;
}
}