UNPKG

delta-sync

Version:

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

268 lines (267 loc) 8.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SyncEngine = exports.SyncStatus = void 0; // core/SyncEngine.ts const Queue_1 = require("../types/Queue"); var SyncStatus; (function (SyncStatus) { SyncStatus["Idle"] = "idle"; SyncStatus["Uploading"] = "uploading"; SyncStatus["Downloading"] = "downloading"; SyncStatus["Error"] = "error"; SyncStatus["Offline"] = "offline"; })(SyncStatus || (exports.SyncStatus = SyncStatus = {})); class SyncEngine { constructor(localAdapter, cloudAdapter, options = {}) { this.pendingQueue = (0, Queue_1.createEmptyQueue)(); this.status = SyncStatus.Idle; this.syncVersion = 0; this.isSyncing = false; this.syncTimer = null; this.errorMessage = ''; // 防抖自动同步 this.debounceTimer = null; this.localAdapter = localAdapter; this.cloudAdapter = cloudAdapter; this.options = { batchSize: 100, maxPayloadSize: 4 * 1024 * 1024, // 4MB autoSync: false, syncInterval: 30000, // 30秒 retryCount: 3, ...options }; // 初始化自动同步 if (this.options.autoSync) { this.startAutoSync(); } } // 获取当前同步状态 getStatus() { const stats = (0, Queue_1.getQueueStats)(this.pendingQueue); return { status: this.status, errorMessage: this.errorMessage, pendingChanges: stats.put + stats.hardDelete }; } // 批量添加变更到同步队列 enqueueChanges(changes) { for (const change of changes) { this.pendingQueue = (0, Queue_1.mergeChange)(this.pendingQueue, change); } if (this.options.autoSync && this.status === SyncStatus.Idle) { this.debounceSync(); } } debounceSync() { if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(() => { this.pushChanges().catch(err => { if (this.options.onError) { this.options.onError(err); } }); }, 1000); // 1秒防抖 } // 启动自动同步 startAutoSync() { if (this.syncTimer) { clearInterval(this.syncTimer); } this.syncTimer = setInterval(() => { if (this.status === SyncStatus.Idle) { this.syncAll().catch(err => { if (this.options.onError) { this.options.onError(err); } }); } }, this.options.syncInterval); } // 停止自动同步 stopAutoSync() { if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = null; } } // 初始化同步 async initSync() { try { this.status = SyncStatus.Downloading; // 从云端拉取初始数据 await this.pullChanges(); this.status = SyncStatus.Idle; this.errorMessage = ''; } catch (error) { this.status = SyncStatus.Error; throw error; } } // 推送变更到云端 async pushChanges() { if (this.isSyncing || this.status === SyncStatus.Uploading) { return; } const stats = (0, Queue_1.getQueueStats)(this.pendingQueue); if (stats.put + stats.hardDelete === 0) { return; // 没有变更需要推送 } try { this.isSyncing = true; this.status = SyncStatus.Uploading; // 处理变更批次 while (this.pendingQueue.changes.size > 0) { const batch = this.prepareBatch(); if (batch.length === 0) break; const response = await this.cloudAdapter.processSyncQueue(batch); if (!response.success) { throw new Error(response.error || '同步失败'); } // 从队列中移除已处理的变更 for (const change of batch) { const key = `${change.store}:${change._sync_id}`; this.pendingQueue.changes.delete(key); } // 报告进度 if (this.options.onProgress) { this.options.onProgress({ phase: 'push', processed: batch.length, total: batch.length + this.pendingQueue.changes.size }); } } this.status = SyncStatus.Idle; this.errorMessage = ''; } catch (error) { this.status = SyncStatus.Error; throw error; } finally { this.isSyncing = false; } } // 从云端拉取变更 async pullChanges() { if (this.isSyncing || this.status === SyncStatus.Downloading) { return; } try { this.isSyncing = true; this.status = SyncStatus.Downloading; const response = await this.cloudAdapter.fetchChanges({ since: this.syncVersion }); if (!response.success) { throw new Error(response.error || '拉取变更失败'); } if (response.changes && response.changes.length > 0) { // 处理拉取的变更 for (const change of response.changes) { await this.processRemoteChange(change); } // 更新同步版本 if (response.timestamp) { this.syncVersion = response.timestamp; } // 报告进度 if (this.options.onProgress) { this.options.onProgress({ phase: 'pull', processed: response.changes.length, total: response.changes.length }); } } this.status = SyncStatus.Idle; this.errorMessage = ''; } catch (error) { this.status = SyncStatus.Error; throw error; } finally { this.isSyncing = false; } } // 完整同步流程 async syncAll() { if (this.isSyncing) { return; } try { // 先推送本地变更 await this.pushChanges(); // 再拉取远程变更 await this.pullChanges(); // 报告完成 if (this.options.onProgress) { this.options.onProgress({ phase: 'complete', processed: 1, total: 1 }); } } catch (error) { throw error; } } // 准备一个批次的变更 prepareBatch() { const batchSize = this.options.batchSize || 100; const maxPayloadSize = this.options.maxPayloadSize || 4 * 1024 * 1024; const batch = []; let currentSize = 0; const processedKeys = new Set(); for (const [key, change] of this.pendingQueue.changes.entries()) { if (processedKeys.has(key)) continue; const changeSize = this.estimateSize(change); if (currentSize + changeSize > maxPayloadSize || batch.length >= batchSize) { break; } batch.push(change); processedKeys.add(key); currentSize += changeSize; } return batch; } // 处理从服务器接收的变更 async processRemoteChange(change) { try { if (change.type === 'put') { await this.localAdapter.putBulk(change.store, [change.data]); } else if (change.type === 'hardDelete') { await this.localAdapter.hardDeleteBulk(change.store, [change._sync_id]); // 使用 _sync_id } } catch (error) { console.error('处理远程变更失败:', error, change); throw error; } } // 估算对象大小 estimateSize(obj) { const str = JSON.stringify(obj); return new TextEncoder().encode(str).length; } // 清空同步队列 clearPendingQueue() { this.pendingQueue = (0, Queue_1.createEmptyQueue)(); } // 销毁同步引擎 destroy() { this.stopAutoSync(); if (this.debounceTimer) { clearTimeout(this.debounceTimer); } } } exports.SyncEngine = SyncEngine;