UNPKG

delta-sync

Version:

A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.

323 lines (322 loc) 12 kB
// core/SyncClient.ts // Provides a simple and easy-to-use synchronization client API, encapsulating internal synchronization complexity import { DEFAULT_QUERY_OPTIONS } from './types'; import { LocalCoordinator } from './LocalCoordinator'; import { CloudCoordinator } from './CloudCoordinator'; import { SyncManager } from './SyncManager'; // Synchronization status enumeration export var SyncStatus; (function (SyncStatus) { SyncStatus[SyncStatus["Error"] = -2] = "Error"; SyncStatus[SyncStatus["Offline"] = -1] = "Offline"; SyncStatus[SyncStatus["Idle"] = 0] = "Idle"; SyncStatus[SyncStatus["Uploading"] = 1] = "Uploading"; SyncStatus[SyncStatus["Downloading"] = 2] = "Downloading"; SyncStatus[SyncStatus["Operating"] = 3] = "Operating"; SyncStatus[SyncStatus["Maintaining"] = 4] = "Maintaining"; })(SyncStatus || (SyncStatus = {})); // Sync client, providing a simple and easy-to-use API to manage local data and synchronization operations export class SyncClient { // Create sync client constructor(options) { this.syncOptions = { autoSync: { enabled: false, interval: 30000, retryDelay: 5000 }, onStatusUpdate: undefined, onVersionUpdate: undefined, onChangePulled: undefined, onChangePushed: undefined, encryption: undefined, maxRetries: 3, timeout: 10000, maxFileSize: 10000000, batchSize: 100, payloadSize: 100000, fileChunkSize: 1000000 // 1MB }; this.syncStatus = SyncStatus.Offline; this.currentVersion = 0; this.cloudConfigured = false; this.localAdapter = options.localAdapter; this.localCoordinator = new LocalCoordinator(this.localAdapter, options.encryptionConfig); // 如果提供了同步选项,则初始化 if (options.syncOption) { this.updateSyncOptions(options.syncOption); } this.initialize(); } // Initialize local coordinator async initialize() { try { this.updateSyncStatus(SyncStatus.Operating); // 初始化时获取当前版本 const version = await this.localCoordinator.getCurrentVersion(); this.currentVersion = version; this.updateSyncStatus(SyncStatus.Idle); } catch (error) { this.updateSyncStatus(SyncStatus.Error); console.error("Local storage initialization failed:", error); throw error; } } // 启用自动同步 enableAutoSync(interval) { if (interval) { this.syncOptions.autoSync.interval = interval; } if (this.syncOptions.autoSync?.enabled) { return; } this.syncOptions.autoSync.enabled = true; this.scheduleNextSync(); console.log(`自动同步已启用,间隔: ${this.syncOptions.autoSync?.interval}ms`); } // 禁用自动同步 disableAutoSync() { if (this.syncOptions.autoSync) { this.syncOptions.autoSync.enabled = false; } if (this.autoSyncTimer) { clearTimeout(this.autoSyncTimer); this.autoSyncTimer = undefined; } console.log('自动同步已禁用'); } // 安排下一次同步 async scheduleNextSync() { if (!this.syncOptions.autoSync?.enabled) { return; } try { const syncResult = await this.sync(); if (syncResult) { // 成功同步,安排下一次同步 this.autoSyncTimer = setTimeout(() => this.scheduleNextSync(), this.syncOptions.autoSync.interval); } else { // 同步失败,使用较短的重试间隔 this.autoSyncTimer = setTimeout(() => this.scheduleNextSync(), this.syncOptions.autoSync.retryDelay); } } catch (error) { console.error('自动同步执行失败:', error); // 发生错误时使用重试延迟 this.autoSyncTimer = setTimeout(() => this.scheduleNextSync(), this.syncOptions.autoSync.retryDelay); } } // Update synchronization options updateSyncOptions(options) { // 深度合并选项 this.syncOptions = { ...this.syncOptions, ...options, autoSync: options.autoSync ? { ...this.syncOptions.autoSync, ...options.autoSync } : this.syncOptions.autoSync }; // 如果启用了自动同步,则开始同步 if (this.syncOptions.autoSync?.enabled) { this.enableAutoSync(); } else if (options.autoSync?.enabled === false) { this.disableAutoSync(); } } // Set cloud adapter, enable synchronization functionality async setCloudAdapter(cloudAdapter) { this.cloudCoordinator = new CloudCoordinator(cloudAdapter); try { this.updateSyncStatus(SyncStatus.Operating); this.syncManager = new SyncManager(this.localCoordinator, this.cloudCoordinator); this.cloudConfigured = true; this.updateSyncStatus(SyncStatus.Idle); } catch (error) { this.cloudConfigured = false; this.updateSyncStatus(SyncStatus.Error); console.error("Cloud adapter initialization failed:", error); this.cloudCoordinator = undefined; throw error; } } updateSyncStatus(status) { this.syncStatus = status; if (this.syncOptions.onStatusUpdate) { this.syncOptions.onStatusUpdate(status); } } // Query data async query(storeName, options) { try { const result = await this.localAdapter.readByVersion(storeName, options || DEFAULT_QUERY_OPTIONS); return result.items; } catch (error) { throw new Error; } } // Save data to specified storage async save(storeName, data) { const items = Array.isArray(data) ? data : [data]; return await this.localCoordinator.putBulk(storeName, items); } // Delete data from specified storage async delete(storeName, ids) { const itemIds = Array.isArray(ids) ? ids : [ids]; await this.localCoordinator.deleteBulk(storeName, itemIds); } // readSingleFile async readFile(fileId) { if (!fileId) { throw new Error('File ID is required'); } try { const filesMap = await this.localCoordinator.localAdapter.readFiles([fileId]); return filesMap.get(fileId) || null; } catch (error) { console.error(`Error reading file ${fileId}:`, error); throw error; } } // Attach file to specified model async attach(storeId, modelId, file, filename, mimeType, metadata = {}) { if (!storeId) { throw new Error('Store name is required'); } if (!modelId) { throw new Error('Model ID is required'); } return this.localCoordinator.attachFile(modelId, storeId, file, filename, mimeType, metadata); } // Detach file from specified model async detach(storeName, modelId, attachmentId) { if (!storeName) { throw new Error('Store name is required'); } if (!modelId) { throw new Error('Model ID is required'); } if (!attachmentId) { throw new Error('Attachment ID is required'); } return this.localCoordinator.detachFile(storeName, modelId, attachmentId); } async sync() { if (!this.syncManager) { console.error("云同步源未配置,请先调用 setCloudAdapter"); return false; } try { // 记录初始状态 this.updateSyncStatus(SyncStatus.Downloading); const pullSuccess = await this.pull(); if (!pullSuccess) { console.error("同步操作中拉取云端数据失败"); return false; } // 2. 再将本地更改推送到云端 this.updateSyncStatus(SyncStatus.Uploading); const pushSuccess = await this.push(); // 3. 根据结果更新状态 this.updateSyncStatus(pushSuccess ? SyncStatus.Idle : SyncStatus.Error); // 4. 返回综合结果 return pushSuccess; } catch (error) { this.updateSyncStatus(SyncStatus.Error); console.error("同步操作失败:", error); return false; } } // Only push local changes to cloud async push() { if (!this.syncManager) { console.error("Cloud sync source not configured, please call setCloudAdapter first"); return false; } try { this.updateSyncStatus(SyncStatus.Uploading); const result = await this.syncManager.pushChanges(); if (result.success) { // 更新当前版本 if (result.version) { this.currentVersion = result.version; // 触发版本更新回调 if (this.syncOptions.onVersionUpdate) { this.syncOptions.onVersionUpdate(result.version); } } // 触发推送完成回调 if (this.syncOptions.onChangePushed && result.changes) { this.syncOptions.onChangePushed(result.changes); } } this.updateSyncStatus(result.success ? SyncStatus.Idle : SyncStatus.Error); return result.success; } catch (error) { this.updateSyncStatus(SyncStatus.Error); console.error("Push operation failed:", error); return false; } } // Only pull changes from cloud async pull() { if (!this.syncManager) { console.error("Cloud sync source not configured, please call setCloudAdapter first"); return false; } try { this.updateSyncStatus(SyncStatus.Downloading); const result = await this.syncManager.pullChanges(); if (result.success) { // 更新当前版本 if (result.version) { this.currentVersion = result.version; // 触发版本更新回调 if (this.syncOptions.onVersionUpdate) { this.syncOptions.onVersionUpdate(result.version); } } // 触发数据拉取回调 if (this.syncOptions.onChangePulled && result.changes) { this.syncOptions.onChangePulled(result.changes); } } this.updateSyncStatus(result.success ? SyncStatus.Idle : SyncStatus.Error); return result.success; } catch (error) { this.updateSyncStatus(SyncStatus.Error); console.error("Pull operation failed:", error); return false; } } // Access underlying coordination layer getlocalCoordinator() { return this.localCoordinator; } // Access underlying local storage adapter getlocalAdapter() { return this.localAdapter; } getSyncOptions() { return { ...this.syncOptions }; } dispose() { this.disableAutoSync(); } // Disconnect cloud connection, return to local mode disconnectCloud() { this.cloudCoordinator = undefined; this.syncManager = undefined; this.updateSyncStatus(SyncStatus.Offline); console.log("Cloud connection disconnected, now in local mode"); } }