delta-sync
Version:
A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.
323 lines (322 loc) • 12 kB
JavaScript
// 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");
}
}