delta-sync
Version:
A lightweight framework for bi-directional database synchronization with automatic version tracking and conflict resolution.
127 lines (126 loc) • 4.2 kB
JavaScript
// core/types.ts
// 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 = {}));
export class SyncView {
constructor() {
this.items = new Map();
this.storeIndex = new Map();
}
// Add or update record
upsert(item) {
const key = this.getKey(item.store, item.id);
this.items.set(key, item);
if (!this.storeIndex.has(item.store)) {
this.storeIndex.set(item.store, new Set());
}
this.storeIndex.get(item.store).add(item.id);
}
// Batch update records
upsertBatch(items) {
for (const item of items) {
this.upsert(item);
}
}
// Get specific record
get(store, id) {
return this.items.get(this.getKey(store, id));
}
// Get paginated records for specified store
getByStore(store, offset = 0, limit = 100) {
const storeItems = this.storeIndex.get(store);
if (!storeItems)
return [];
return Array.from(storeItems)
.slice(offset, offset + limit)
.map(id => this.items.get(this.getKey(store, id)))
.filter(item => item !== undefined);
}
// Get all store names
getStores() {
return Array.from(this.storeIndex.keys());
}
// Compare differences between two views
static diffViews(local, remote) {
const toDownload = [];
const toUpload = [];
const allKeys = new Set([...local.items.keys(), ...remote.items.keys()]);
for (const key of allKeys) {
const localItem = local.items.get(key);
const remoteItem = remote.items.get(key);
if (!localItem && remoteItem) {
// 本地没有,远程有
toDownload.push(remoteItem);
}
else if (localItem && !remoteItem) {
// 本地有,远程没有
toUpload.push(localItem);
}
else if (localItem && remoteItem) {
// 两边都有,需要比较版本和删除状态
if (localItem._ver !== remoteItem._ver || localItem.deleted !== remoteItem.deleted) {
if (localItem._ver > remoteItem._ver) {
toUpload.push(localItem);
}
else if (localItem._ver < remoteItem._ver) {
toDownload.push(remoteItem);
}
else {
// 版本相同但删除状态不同时,以远程为准
toDownload.push(remoteItem);
}
}
}
}
return { toDownload, toUpload };
}
// Generate composite key
getKey(store, id) {
return `${store}:${id}`;
}
// Delete record
delete(store, id) {
const key = this.getKey(store, id);
this.items.delete(key);
this.storeIndex.get(store)?.delete(id);
}
// Get total storage size
size() {
return this.items.size;
}
// Get record count for specific store
storeSize(store) {
return this.storeIndex.get(store)?.size || 0;
}
// Clear all data
clear() {
this.items.clear();
this.storeIndex.clear();
}
countByStore(store, includeDeleted = false) {
const storeItems = this.storeIndex.get(store);
if (!storeItems) {
return 0;
}
if (includeDeleted) {
return storeItems.size;
}
else {
let activeCount = 0;
for (const id of storeItems) {
const item = this.get(store, id);
if (item && !item.deleted) {
activeCount++;
}
}
return activeCount;
}
}
}