91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
|
|
import { ISync, SyncStatus, EntityType } from '../types/CalendarTypes';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* SyncPlugin<T extends ISync> - Pluggable sync functionality for entity services
|
||
|
|
*
|
||
|
|
* COMPOSITION PATTERN:
|
||
|
|
* - Encapsulates all sync-related logic in separate class
|
||
|
|
* - Composed into BaseEntityService (not inheritance)
|
||
|
|
* - Allows sync functionality to be swapped/mocked for testing
|
||
|
|
* - Single Responsibility: Only handles sync status management
|
||
|
|
*
|
||
|
|
* DESIGN:
|
||
|
|
* - Takes reference to BaseEntityService for calling get/save
|
||
|
|
* - Implements sync methods that delegate to service's CRUD
|
||
|
|
* - Uses IndexedDB syncStatus index for efficient queries
|
||
|
|
*/
|
||
|
|
export class SyncPlugin<T extends ISync> {
|
||
|
|
/**
|
||
|
|
* @param service - Reference to BaseEntityService for CRUD operations
|
||
|
|
*/
|
||
|
|
constructor(private service: any) {
|
||
|
|
// Type is 'any' to avoid circular dependency at compile time
|
||
|
|
// Runtime: service is BaseEntityService<T>
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mark entity as successfully synced
|
||
|
|
* Sets syncStatus = 'synced' and persists to IndexedDB
|
||
|
|
*
|
||
|
|
* @param id - Entity ID
|
||
|
|
*/
|
||
|
|
async markAsSynced(id: string): Promise<void> {
|
||
|
|
const entity = await this.service.get(id);
|
||
|
|
if (entity) {
|
||
|
|
entity.syncStatus = 'synced';
|
||
|
|
await this.service.save(entity);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mark entity as sync error (max retries exceeded)
|
||
|
|
* Sets syncStatus = 'error' and persists to IndexedDB
|
||
|
|
*
|
||
|
|
* @param id - Entity ID
|
||
|
|
*/
|
||
|
|
async markAsError(id: string): Promise<void> {
|
||
|
|
const entity = await this.service.get(id);
|
||
|
|
if (entity) {
|
||
|
|
entity.syncStatus = 'error';
|
||
|
|
await this.service.save(entity);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get current sync status for an entity
|
||
|
|
*
|
||
|
|
* @param id - Entity ID
|
||
|
|
* @returns SyncStatus or null if entity not found
|
||
|
|
*/
|
||
|
|
async getSyncStatus(id: string): Promise<SyncStatus | null> {
|
||
|
|
const entity = await this.service.get(id);
|
||
|
|
return entity ? entity.syncStatus : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get entities by sync status
|
||
|
|
* Uses IndexedDB syncStatus index for efficient querying
|
||
|
|
*
|
||
|
|
* @param syncStatus - Sync status ('synced', 'pending', 'error')
|
||
|
|
* @returns Array of entities with this sync status
|
||
|
|
*/
|
||
|
|
async getBySyncStatus(syncStatus: string): Promise<T[]> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const transaction = this.service.db.transaction([this.service.storeName], 'readonly');
|
||
|
|
const store = transaction.objectStore(this.service.storeName);
|
||
|
|
const index = store.index('syncStatus');
|
||
|
|
const request = index.getAll(syncStatus);
|
||
|
|
|
||
|
|
request.onsuccess = () => {
|
||
|
|
const data = request.result as any[];
|
||
|
|
const entities = data.map(item => this.service.deserialize(item));
|
||
|
|
resolve(entities);
|
||
|
|
};
|
||
|
|
|
||
|
|
request.onerror = () => {
|
||
|
|
reject(new Error(`Failed to get ${this.service.entityType}s by sync status ${syncStatus}: ${request.error}`));
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|