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
JavaScript
"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;