@framers/sql-storage-adapter 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/adapters/electron/preload.cjs +289 -0
- package/dist/adapters/electron/renderer.d.ts +9 -0
- package/dist/adapters/electron/renderer.d.ts.map +1 -0
- package/dist/adapters/electron/renderer.js +8 -0
- package/dist/adapters/electron/renderer.js.map +1 -0
- package/dist/cjs/adapters/__tests__/indexedDbAdapter.spec.d.ts +2 -0
- package/dist/cjs/adapters/__tests__/indexedDbAdapter.spec.js +133 -0
- package/dist/cjs/adapters/baseStorageAdapter.d.ts +368 -0
- package/dist/cjs/adapters/baseStorageAdapter.js +744 -0
- package/dist/cjs/adapters/betterSqliteAdapter.d.ts +124 -0
- package/dist/cjs/adapters/betterSqliteAdapter.js +334 -0
- package/dist/cjs/adapters/capacitorSqliteAdapter.d.ts +83 -0
- package/dist/cjs/adapters/capacitorSqliteAdapter.js +250 -0
- package/dist/cjs/adapters/electron/electronMainAdapter.d.ts +251 -0
- package/dist/cjs/adapters/electron/electronMainAdapter.js +392 -0
- package/dist/cjs/adapters/electron/electronRendererAdapter.d.ts +177 -0
- package/dist/cjs/adapters/electron/electronRendererAdapter.js +283 -0
- package/dist/cjs/adapters/electron/index.d.ts +74 -0
- package/dist/cjs/adapters/electron/index.js +86 -0
- package/dist/cjs/adapters/electron/ipc/channels.d.ts +196 -0
- package/dist/cjs/adapters/electron/ipc/channels.js +127 -0
- package/dist/cjs/adapters/electron/ipc/index.d.ts +11 -0
- package/dist/cjs/adapters/electron/ipc/index.js +38 -0
- package/dist/cjs/adapters/electron/ipc/protocol.d.ts +78 -0
- package/dist/cjs/adapters/electron/ipc/protocol.js +341 -0
- package/dist/cjs/adapters/electron/ipc/types.d.ts +248 -0
- package/dist/cjs/adapters/electron/ipc/types.js +15 -0
- package/dist/cjs/adapters/electron/migration/autoMigrator.d.ts +184 -0
- package/dist/cjs/adapters/electron/migration/autoMigrator.js +441 -0
- package/dist/cjs/adapters/electron/migration/index.d.ts +9 -0
- package/dist/cjs/adapters/electron/migration/index.js +29 -0
- package/dist/cjs/adapters/electron/preload.d.ts +126 -0
- package/dist/cjs/adapters/electron/preload.js +233 -0
- package/dist/cjs/adapters/electron/recovery/corruptionDetector.d.ts +214 -0
- package/dist/cjs/adapters/electron/recovery/corruptionDetector.js +359 -0
- package/dist/cjs/adapters/electron/recovery/index.d.ts +11 -0
- package/dist/cjs/adapters/electron/recovery/index.js +34 -0
- package/dist/cjs/adapters/electron/recovery/walCheckpoint.d.ts +186 -0
- package/dist/cjs/adapters/electron/recovery/walCheckpoint.js +263 -0
- package/dist/cjs/adapters/electron/renderer.d.ts +9 -0
- package/dist/cjs/adapters/electron/renderer.js +33 -0
- package/dist/cjs/adapters/electron/window/index.d.ts +9 -0
- package/dist/cjs/adapters/electron/window/index.js +29 -0
- package/dist/cjs/adapters/electron/window/windowManager.d.ts +190 -0
- package/dist/cjs/adapters/electron/window/windowManager.js +321 -0
- package/dist/cjs/adapters/indexedDbAdapter.d.ts +490 -0
- package/dist/cjs/adapters/indexedDbAdapter.js +993 -0
- package/dist/cjs/adapters/postgresAdapter.d.ts +424 -0
- package/dist/cjs/adapters/postgresAdapter.js +889 -0
- package/dist/cjs/adapters/sqlJsAdapter.d.ts +64 -0
- package/dist/cjs/adapters/sqlJsAdapter.js +268 -0
- package/dist/cjs/adapters/supabase.d.ts +58 -0
- package/dist/cjs/adapters/supabase.js +401 -0
- package/dist/cjs/codecs/BrowserBlobCodec.d.ts +14 -0
- package/dist/cjs/codecs/BrowserBlobCodec.js +57 -0
- package/dist/cjs/codecs/NodeBlobCodec.d.ts +14 -0
- package/dist/cjs/codecs/NodeBlobCodec.js +59 -0
- package/dist/cjs/core/contracts/blobCodec.d.ts +15 -0
- package/dist/cjs/core/contracts/blobCodec.js +15 -0
- package/dist/cjs/core/contracts/context.d.ts +221 -0
- package/dist/cjs/core/contracts/context.js +15 -0
- package/dist/cjs/core/contracts/dialect.d.ts +154 -0
- package/dist/cjs/core/contracts/dialect.js +15 -0
- package/dist/cjs/core/contracts/events.d.ts +225 -0
- package/dist/cjs/core/contracts/events.js +15 -0
- package/dist/cjs/core/contracts/exporter.d.ts +13 -0
- package/dist/cjs/core/contracts/exporter.js +15 -0
- package/dist/cjs/core/contracts/extensions.d.ts +73 -0
- package/dist/cjs/core/contracts/extensions.js +15 -0
- package/dist/cjs/core/contracts/features.d.ts +25 -0
- package/dist/cjs/core/contracts/features.js +61 -0
- package/dist/cjs/core/contracts/fts.d.ts +66 -0
- package/dist/cjs/core/contracts/fts.js +15 -0
- package/dist/cjs/core/contracts/hooks.d.ts +481 -0
- package/dist/cjs/core/contracts/hooks.js +140 -0
- package/dist/cjs/core/contracts/index.d.ts +260 -0
- package/dist/cjs/core/contracts/index.js +41 -0
- package/dist/cjs/core/contracts/limitations.d.ts +46 -0
- package/dist/cjs/core/contracts/limitations.js +218 -0
- package/dist/cjs/core/contracts/performance.d.ts +308 -0
- package/dist/cjs/core/contracts/performance.js +144 -0
- package/dist/cjs/core/database.d.ts +220 -0
- package/dist/cjs/core/database.js +136 -0
- package/dist/cjs/core/portable.d.ts +23 -0
- package/dist/cjs/core/portable.js +66 -0
- package/dist/cjs/core/resolver.d.ts +77 -0
- package/dist/cjs/core/resolver.js +235 -0
- package/dist/cjs/dialects/PostgresDialect.d.ts +35 -0
- package/dist/cjs/dialects/PostgresDialect.js +143 -0
- package/dist/cjs/dialects/SqliteDialect.d.ts +35 -0
- package/dist/cjs/dialects/SqliteDialect.js +142 -0
- package/dist/cjs/dialects/shared.d.ts +17 -0
- package/dist/cjs/dialects/shared.js +90 -0
- package/dist/cjs/exporters/PostgresExporter.d.ts +14 -0
- package/dist/cjs/exporters/PostgresExporter.js +61 -0
- package/dist/cjs/exporters/SqliteFileExporter.d.ts +16 -0
- package/dist/cjs/exporters/SqliteFileExporter.js +43 -0
- package/dist/cjs/features/backup/cloudBackup.d.ts +219 -0
- package/dist/cjs/features/backup/cloudBackup.js +209 -0
- package/dist/cjs/features/kv/idb.d.ts +79 -0
- package/dist/cjs/features/kv/idb.js +32 -0
- package/dist/cjs/features/kv/index.d.ts +14 -0
- package/dist/cjs/features/kv/index.js +40 -0
- package/dist/cjs/features/kv/indexedDbKeyValueStore.d.ts +77 -0
- package/dist/cjs/features/kv/indexedDbKeyValueStore.js +116 -0
- package/dist/cjs/features/kv/keyValueStore.d.ts +48 -0
- package/dist/cjs/features/kv/keyValueStore.js +67 -0
- package/dist/cjs/features/kv/sqlKeyValueStore.d.ts +34 -0
- package/dist/cjs/features/kv/sqlKeyValueStore.js +81 -0
- package/dist/cjs/features/kv/valueCodec.d.ts +35 -0
- package/dist/cjs/features/kv/valueCodec.js +426 -0
- package/dist/cjs/features/migrations/dataExport.d.ts +77 -0
- package/dist/cjs/features/migrations/dataExport.js +194 -0
- package/dist/cjs/features/migrations/dataImport.d.ts +54 -0
- package/dist/cjs/features/migrations/dataImport.js +300 -0
- package/dist/cjs/features/migrations/migration.d.ts +89 -0
- package/dist/cjs/features/migrations/migration.js +187 -0
- package/dist/cjs/features/migrations/schemaMigrations.d.ts +93 -0
- package/dist/cjs/features/migrations/schemaMigrations.js +220 -0
- package/dist/cjs/features/sync/conflicts/conflictResolver.d.ts +222 -0
- package/dist/cjs/features/sync/conflicts/conflictResolver.js +366 -0
- package/dist/cjs/features/sync/conflicts/index.d.ts +9 -0
- package/dist/cjs/features/sync/conflicts/index.js +21 -0
- package/dist/cjs/features/sync/crossPlatformSync.d.ts +281 -0
- package/dist/cjs/features/sync/crossPlatformSync.js +551 -0
- package/dist/cjs/features/sync/devices/deviceManager.d.ts +243 -0
- package/dist/cjs/features/sync/devices/deviceManager.js +450 -0
- package/dist/cjs/features/sync/devices/index.d.ts +10 -0
- package/dist/cjs/features/sync/devices/index.js +21 -0
- package/dist/cjs/features/sync/index.d.ts +37 -0
- package/dist/cjs/features/sync/index.js +111 -0
- package/dist/cjs/features/sync/protocol/index.d.ts +11 -0
- package/dist/cjs/features/sync/protocol/index.js +23 -0
- package/dist/cjs/features/sync/protocol/messages.d.ts +348 -0
- package/dist/cjs/features/sync/protocol/messages.js +215 -0
- package/dist/cjs/features/sync/protocol/vectorClock.d.ts +164 -0
- package/dist/cjs/features/sync/protocol/vectorClock.js +256 -0
- package/dist/cjs/features/sync/syncManager.d.ts +348 -0
- package/dist/cjs/features/sync/syncManager.js +455 -0
- package/dist/cjs/features/sync/tables/index.d.ts +10 -0
- package/dist/cjs/features/sync/tables/index.js +21 -0
- package/dist/cjs/features/sync/tables/syncLogManager.d.ts +216 -0
- package/dist/cjs/features/sync/tables/syncLogManager.js +423 -0
- package/dist/cjs/features/sync/transport/httpTransport.d.ts +123 -0
- package/dist/cjs/features/sync/transport/httpTransport.js +346 -0
- package/dist/cjs/features/sync/transport/index.d.ts +12 -0
- package/dist/cjs/features/sync/transport/index.js +25 -0
- package/dist/cjs/features/sync/transport/transport.d.ts +259 -0
- package/dist/cjs/features/sync/transport/transport.js +163 -0
- package/dist/cjs/features/sync/transport/websocketTransport.d.ts +126 -0
- package/dist/cjs/features/sync/transport/websocketTransport.js +342 -0
- package/dist/cjs/fts/PostgresFts.d.ts +29 -0
- package/dist/cjs/fts/PostgresFts.js +77 -0
- package/dist/cjs/fts/SqliteFts5.d.ts +22 -0
- package/dist/cjs/fts/SqliteFts5.js +58 -0
- package/dist/cjs/index.d.ts +30 -0
- package/dist/cjs/index.js +101 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/shared/connectionGate.d.ts +94 -0
- package/dist/cjs/shared/connectionGate.js +77 -0
- package/dist/cjs/shared/errors.d.ts +23 -0
- package/dist/cjs/shared/errors.js +45 -0
- package/dist/cjs/shared/logger.d.ts +26 -0
- package/dist/cjs/shared/logger.js +40 -0
- package/dist/cjs/shared/parameterUtils.d.ts +16 -0
- package/dist/cjs/shared/parameterUtils.js +59 -0
- package/dist/cjs/shared/postgresTarget.d.ts +11 -0
- package/dist/cjs/shared/postgresTarget.js +30 -0
- package/dist/cjs/shared/segment.d.ts +43 -0
- package/dist/cjs/shared/segment.js +63 -0
- package/dist/cjs/shared/serializedTransaction.d.ts +14 -0
- package/dist/cjs/shared/serializedTransaction.js +74 -0
- package/dist/cjs/shared/splitSqlStatements.d.ts +27 -0
- package/dist/cjs/shared/splitSqlStatements.js +163 -0
- package/dist/cjs/shared/sqlPlaceholders.d.ts +48 -0
- package/dist/cjs/shared/sqlPlaceholders.js +263 -0
- package/dist/cjs/types/index.d.ts +29 -0
- package/dist/cjs/types/index.js +33 -0
- package/dist/core/portable.d.ts +23 -0
- package/dist/core/portable.d.ts.map +1 -0
- package/dist/core/portable.js +23 -0
- package/dist/core/portable.js.map +1 -0
- package/package.json +139 -24
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var syncManager_exports = {};
|
|
19
|
+
__export(syncManager_exports, {
|
|
20
|
+
SyncManager: () => SyncManager,
|
|
21
|
+
createSyncManager: () => createSyncManager
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(syncManager_exports);
|
|
24
|
+
var import_database = require("../../core/database.js");
|
|
25
|
+
var import_dataExport = require("../migrations/dataExport.js");
|
|
26
|
+
var import_dataImport = require("../migrations/dataImport.js");
|
|
27
|
+
class SyncManager {
|
|
28
|
+
constructor(primary, remote, callbacks, syncConfig) {
|
|
29
|
+
this.callbacks = callbacks;
|
|
30
|
+
this.remoteDb = null;
|
|
31
|
+
this.syncTimer = null;
|
|
32
|
+
this.debounceTimer = null;
|
|
33
|
+
this.syncQueue = [];
|
|
34
|
+
this.isSyncing = false;
|
|
35
|
+
this.isOnline = true;
|
|
36
|
+
this.lastSyncTime = null;
|
|
37
|
+
this.pendingWrites = /* @__PURE__ */ new Set();
|
|
38
|
+
this.primaryDb = primary;
|
|
39
|
+
this.remoteDb = remote;
|
|
40
|
+
this.config = {
|
|
41
|
+
mode: syncConfig.mode ?? "manual",
|
|
42
|
+
direction: syncConfig.direction ?? "bidirectional",
|
|
43
|
+
conflictStrategy: syncConfig.conflictStrategy ?? "last-write-wins",
|
|
44
|
+
interval: syncConfig.interval ?? 3e4,
|
|
45
|
+
debounce: syncConfig.debounce ?? 500,
|
|
46
|
+
batchSize: syncConfig.batchSize ?? 100,
|
|
47
|
+
retryOnError: syncConfig.retryOnError ?? true,
|
|
48
|
+
maxRetries: syncConfig.maxRetries ?? 3,
|
|
49
|
+
retryDelay: syncConfig.retryDelay ?? 1e3,
|
|
50
|
+
tables: syncConfig.tables ?? {},
|
|
51
|
+
mobileStorageLimit: syncConfig.mobileStorageLimit ?? 50,
|
|
52
|
+
storageLimitAction: syncConfig.storageLimitAction ?? "warn",
|
|
53
|
+
includeTables: syncConfig.includeTables,
|
|
54
|
+
excludeTables: syncConfig.excludeTables
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the primary database adapter.
|
|
59
|
+
* Use this for all database operations.
|
|
60
|
+
*/
|
|
61
|
+
get db() {
|
|
62
|
+
return this.primaryDb;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if manager is currently syncing.
|
|
66
|
+
*/
|
|
67
|
+
get syncing() {
|
|
68
|
+
return this.isSyncing;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if currently online.
|
|
72
|
+
*/
|
|
73
|
+
get online() {
|
|
74
|
+
return this.isOnline;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get last successful sync time.
|
|
78
|
+
*/
|
|
79
|
+
get lastSync() {
|
|
80
|
+
return this.lastSyncTime;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Trigger manual sync.
|
|
84
|
+
* Safe to call multiple times - queues if already syncing.
|
|
85
|
+
*/
|
|
86
|
+
async sync() {
|
|
87
|
+
if (!this.remoteDb) {
|
|
88
|
+
throw new Error("No remote database configured for sync");
|
|
89
|
+
}
|
|
90
|
+
if (this.isSyncing) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
this.syncQueue.push(async () => {
|
|
93
|
+
try {
|
|
94
|
+
const result = await this.performSync();
|
|
95
|
+
resolve(result);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
reject(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return this.performSync();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Perform the actual sync operation.
|
|
106
|
+
*/
|
|
107
|
+
async performSync() {
|
|
108
|
+
if (!this.remoteDb) {
|
|
109
|
+
throw new Error("No remote database configured");
|
|
110
|
+
}
|
|
111
|
+
this.isSyncing = true;
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
let recordsSynced = 0;
|
|
114
|
+
let conflicts = 0;
|
|
115
|
+
const tablesSynced = [];
|
|
116
|
+
const errors = [];
|
|
117
|
+
const details = {};
|
|
118
|
+
try {
|
|
119
|
+
await this.checkConnection();
|
|
120
|
+
const tables = await this.getTablesToSync();
|
|
121
|
+
const totalTables = tables.length;
|
|
122
|
+
const sortedTables = this.sortTablesByPriority(tables);
|
|
123
|
+
for (let i = 0; i < sortedTables.length; i++) {
|
|
124
|
+
const table = sortedTables[i];
|
|
125
|
+
this.callbacks.onProgress?.({
|
|
126
|
+
phase: "pulling",
|
|
127
|
+
percent: Math.round(i / totalTables * 50),
|
|
128
|
+
// 0-50% for pull
|
|
129
|
+
currentTable: table,
|
|
130
|
+
recordsProcessed: recordsSynced,
|
|
131
|
+
totalRecords: 0
|
|
132
|
+
// Unknown until we query
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
const tableResult = await this.syncTable(table);
|
|
136
|
+
details[table] = tableResult;
|
|
137
|
+
recordsSynced += tableResult.pushed + tableResult.pulled;
|
|
138
|
+
conflicts += tableResult.conflicts;
|
|
139
|
+
tablesSynced.push(table);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
errors.push(error);
|
|
142
|
+
this.callbacks.onError?.(error);
|
|
143
|
+
if (!this.config.retryOnError) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.lastSyncTime = /* @__PURE__ */ new Date();
|
|
149
|
+
const duration = Date.now() - startTime;
|
|
150
|
+
const result = {
|
|
151
|
+
success: errors.length === 0,
|
|
152
|
+
direction: this.config.direction,
|
|
153
|
+
recordsSynced,
|
|
154
|
+
conflicts,
|
|
155
|
+
duration,
|
|
156
|
+
timestamp: this.lastSyncTime.toISOString(),
|
|
157
|
+
tables: tablesSynced,
|
|
158
|
+
errors: errors.length > 0 ? errors : void 0,
|
|
159
|
+
details
|
|
160
|
+
};
|
|
161
|
+
this.callbacks.onSync?.(result);
|
|
162
|
+
return result;
|
|
163
|
+
} finally {
|
|
164
|
+
this.isSyncing = false;
|
|
165
|
+
this.pendingWrites.clear();
|
|
166
|
+
if (this.syncQueue.length > 0) {
|
|
167
|
+
const nextSync = this.syncQueue.shift();
|
|
168
|
+
nextSync?.();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Sync a single table.
|
|
174
|
+
*/
|
|
175
|
+
async syncTable(table) {
|
|
176
|
+
const direction = this.config.direction;
|
|
177
|
+
let pushed = 0;
|
|
178
|
+
let pulled = 0;
|
|
179
|
+
let conflicts = 0;
|
|
180
|
+
if (direction === "bidirectional" || direction === "pull-only") {
|
|
181
|
+
const pullResult = await this.pullTable(table);
|
|
182
|
+
pulled = pullResult.records;
|
|
183
|
+
conflicts += pullResult.conflicts;
|
|
184
|
+
}
|
|
185
|
+
if (direction === "bidirectional" || direction === "push-only") {
|
|
186
|
+
const pushResult = await this.pushTable(table);
|
|
187
|
+
pushed = pushResult.records;
|
|
188
|
+
conflicts += pushResult.conflicts;
|
|
189
|
+
}
|
|
190
|
+
return { pushed, pulled, conflicts };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Pull table data from remote to local.
|
|
194
|
+
*/
|
|
195
|
+
async pullTable(table) {
|
|
196
|
+
if (!this.remoteDb)
|
|
197
|
+
return { records: 0, conflicts: 0 };
|
|
198
|
+
const remoteData = await (0, import_dataExport.exportData)(this.remoteDb, {
|
|
199
|
+
tables: [table],
|
|
200
|
+
includeSchema: false
|
|
201
|
+
});
|
|
202
|
+
if (!remoteData.data[table]?.length) {
|
|
203
|
+
return { records: 0, conflicts: 0 };
|
|
204
|
+
}
|
|
205
|
+
const localData = await (0, import_dataExport.exportData)(this.primaryDb, {
|
|
206
|
+
tables: [table],
|
|
207
|
+
includeSchema: false
|
|
208
|
+
});
|
|
209
|
+
const localRecords = this.sanitiseRecords(localData.data[table]);
|
|
210
|
+
const remoteRecords = this.sanitiseRecords(remoteData.data[table]);
|
|
211
|
+
const conflicts = await this.resolveConflicts(table, localRecords, remoteRecords);
|
|
212
|
+
await (0, import_dataImport.importData)(this.primaryDb, remoteData, {
|
|
213
|
+
onConflict: "replace",
|
|
214
|
+
// We already resolved conflicts
|
|
215
|
+
skipSchema: true,
|
|
216
|
+
tables: [table]
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
records: remoteRecords.length,
|
|
220
|
+
conflicts
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Push table data from local to remote.
|
|
225
|
+
*/
|
|
226
|
+
async pushTable(table) {
|
|
227
|
+
if (!this.remoteDb)
|
|
228
|
+
return { records: 0, conflicts: 0 };
|
|
229
|
+
const localData = await (0, import_dataExport.exportData)(this.primaryDb, {
|
|
230
|
+
tables: [table],
|
|
231
|
+
includeSchema: false
|
|
232
|
+
});
|
|
233
|
+
if (!localData.data[table]?.length) {
|
|
234
|
+
return { records: 0, conflicts: 0 };
|
|
235
|
+
}
|
|
236
|
+
await (0, import_dataImport.importData)(this.remoteDb, localData, {
|
|
237
|
+
onConflict: "replace",
|
|
238
|
+
skipSchema: true,
|
|
239
|
+
tables: [table]
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
records: localData.data[table].length,
|
|
243
|
+
conflicts: 0
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
sanitiseRecords(records) {
|
|
247
|
+
if (!records) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
return records.filter((record) => typeof record === "object" && record !== null).map((record) => record);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Resolve conflicts between local and remote data.
|
|
254
|
+
*/
|
|
255
|
+
async resolveConflicts(table, local, remote) {
|
|
256
|
+
const tableConfig = this.config.tables[table];
|
|
257
|
+
const strategy = tableConfig?.conflictStrategy ?? this.config.conflictStrategy;
|
|
258
|
+
let conflictCount = 0;
|
|
259
|
+
const localMap = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const record of local) {
|
|
261
|
+
if (record.id !== void 0 && record.id !== null) {
|
|
262
|
+
const identifier = record.id;
|
|
263
|
+
localMap.set(identifier, record);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const remoteRecord of remote) {
|
|
267
|
+
if (remoteRecord.id === void 0 || remoteRecord.id === null) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const identifier = remoteRecord.id;
|
|
271
|
+
const localRecord = localMap.get(identifier);
|
|
272
|
+
if (!localRecord)
|
|
273
|
+
continue;
|
|
274
|
+
const localTimestampValue = typeof localRecord.updated_at === "string" ? localRecord.updated_at : typeof localRecord.created_at === "string" ? localRecord.created_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
275
|
+
const remoteTimestampValue = typeof remoteRecord.updated_at === "string" ? remoteRecord.updated_at : typeof remoteRecord.created_at === "string" ? remoteRecord.created_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
276
|
+
const localTime = new Date(localTimestampValue);
|
|
277
|
+
const remoteTime = new Date(remoteTimestampValue);
|
|
278
|
+
if (localRecord.updated_at || remoteRecord.updated_at) {
|
|
279
|
+
conflictCount++;
|
|
280
|
+
const conflict = {
|
|
281
|
+
table,
|
|
282
|
+
id: identifier,
|
|
283
|
+
local: localRecord,
|
|
284
|
+
remote: remoteRecord,
|
|
285
|
+
localTimestamp: localTime.toISOString(),
|
|
286
|
+
remoteTimestamp: remoteTime.toISOString()
|
|
287
|
+
};
|
|
288
|
+
this.callbacks.onConflict?.(conflict);
|
|
289
|
+
switch (strategy) {
|
|
290
|
+
case "last-write-wins":
|
|
291
|
+
break;
|
|
292
|
+
case "local-wins":
|
|
293
|
+
break;
|
|
294
|
+
case "remote-wins":
|
|
295
|
+
break;
|
|
296
|
+
case "merge":
|
|
297
|
+
if (tableConfig?.mergeFn) {
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
case "keep-both":
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return conflictCount;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get list of tables to sync.
|
|
309
|
+
*/
|
|
310
|
+
async getTablesToSync() {
|
|
311
|
+
const schema = await (0, import_dataExport.exportData)(this.primaryDb, {
|
|
312
|
+
includeSchema: true
|
|
313
|
+
});
|
|
314
|
+
const allTables = schema.schema?.map((s) => s.name) ?? [];
|
|
315
|
+
let tables = allTables;
|
|
316
|
+
if (this.config.includeTables) {
|
|
317
|
+
tables = tables.filter((t) => this.config.includeTables.includes(t));
|
|
318
|
+
}
|
|
319
|
+
if (this.config.excludeTables) {
|
|
320
|
+
tables = tables.filter((t) => !this.config.excludeTables.includes(t));
|
|
321
|
+
}
|
|
322
|
+
tables = tables.filter((t) => !this.config.tables[t]?.skip);
|
|
323
|
+
return tables;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Sort tables by priority.
|
|
327
|
+
*/
|
|
328
|
+
sortTablesByPriority(tables) {
|
|
329
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
330
|
+
return tables.sort((a, b) => {
|
|
331
|
+
const aPriority = this.config.tables[a]?.priority ?? "medium";
|
|
332
|
+
const bPriority = this.config.tables[b]?.priority ?? "medium";
|
|
333
|
+
return priorityOrder[aPriority] - priorityOrder[bPriority];
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Check network connection.
|
|
338
|
+
*/
|
|
339
|
+
async checkConnection() {
|
|
340
|
+
if (!this.remoteDb)
|
|
341
|
+
return;
|
|
342
|
+
try {
|
|
343
|
+
await this.remoteDb.get("SELECT 1 as ok");
|
|
344
|
+
if (!this.isOnline) {
|
|
345
|
+
this.isOnline = true;
|
|
346
|
+
this.callbacks.onOnline?.();
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
if (this.isOnline) {
|
|
350
|
+
this.isOnline = false;
|
|
351
|
+
this.callbacks.onOffline?.();
|
|
352
|
+
}
|
|
353
|
+
throw new Error("Remote database not accessible");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Start automatic sync based on mode.
|
|
358
|
+
*/
|
|
359
|
+
startAutoSync() {
|
|
360
|
+
if (this.config.mode === "periodic") {
|
|
361
|
+
this.syncTimer = setInterval(() => {
|
|
362
|
+
this.sync().catch((error) => {
|
|
363
|
+
this.callbacks.onError?.(error);
|
|
364
|
+
});
|
|
365
|
+
}, this.config.interval);
|
|
366
|
+
} else if (this.config.mode === "on-reconnect") {
|
|
367
|
+
if (typeof window !== "undefined") {
|
|
368
|
+
window.addEventListener("online", () => {
|
|
369
|
+
this.sync().catch((error) => {
|
|
370
|
+
this.callbacks.onError?.(error);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Stop automatic sync.
|
|
378
|
+
*/
|
|
379
|
+
stop() {
|
|
380
|
+
if (this.syncTimer) {
|
|
381
|
+
clearInterval(this.syncTimer);
|
|
382
|
+
this.syncTimer = null;
|
|
383
|
+
}
|
|
384
|
+
if (this.debounceTimer) {
|
|
385
|
+
clearTimeout(this.debounceTimer);
|
|
386
|
+
this.debounceTimer = null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Close all database connections.
|
|
391
|
+
*/
|
|
392
|
+
async close() {
|
|
393
|
+
this.stop();
|
|
394
|
+
await this.primaryDb.close();
|
|
395
|
+
if (this.remoteDb) {
|
|
396
|
+
await this.remoteDb.close();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Create a sync manager instance.
|
|
401
|
+
*/
|
|
402
|
+
static async create(config) {
|
|
403
|
+
let primaryDb;
|
|
404
|
+
const primaryConfig = typeof config.primary === "string" ? { file: config.primary } : config.primary;
|
|
405
|
+
try {
|
|
406
|
+
if (primaryConfig.url) {
|
|
407
|
+
primaryDb = await (0, import_database.createDatabase)({ url: primaryConfig.url });
|
|
408
|
+
} else if (primaryConfig.file) {
|
|
409
|
+
primaryDb = await (0, import_database.openDatabase)(primaryConfig.file);
|
|
410
|
+
} else {
|
|
411
|
+
primaryDb = await (0, import_database.createDatabase)();
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (primaryConfig.fallback) {
|
|
415
|
+
console.warn("[SyncManager] Primary database failed, using fallback:", error);
|
|
416
|
+
primaryDb = await (0, import_database.openDatabase)(primaryConfig.fallback);
|
|
417
|
+
} else {
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
await primaryDb.open();
|
|
422
|
+
let remoteDb = null;
|
|
423
|
+
if (config.remote) {
|
|
424
|
+
const remoteConfig = typeof config.remote === "string" ? { url: config.remote } : config.remote;
|
|
425
|
+
try {
|
|
426
|
+
if (remoteConfig.url) {
|
|
427
|
+
remoteDb = await (0, import_database.connectDatabase)(remoteConfig.url);
|
|
428
|
+
await remoteDb.open();
|
|
429
|
+
}
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.warn("[SyncManager] Remote database not available:", error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const manager = new SyncManager(primaryDb, remoteDb, {
|
|
435
|
+
onSync: config.onSync,
|
|
436
|
+
onConflict: config.onConflict,
|
|
437
|
+
onOffline: config.onOffline,
|
|
438
|
+
onOnline: config.onOnline,
|
|
439
|
+
onError: config.onError,
|
|
440
|
+
onProgress: config.onProgress
|
|
441
|
+
}, config.sync ?? {});
|
|
442
|
+
if (config.sync?.mode && config.sync.mode !== "manual") {
|
|
443
|
+
manager.startAutoSync();
|
|
444
|
+
}
|
|
445
|
+
return manager;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function createSyncManager(config) {
|
|
449
|
+
return SyncManager.create(config);
|
|
450
|
+
}
|
|
451
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
452
|
+
0 && (module.exports = {
|
|
453
|
+
SyncManager,
|
|
454
|
+
createSyncManager
|
|
455
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var tables_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(tables_exports);
|
|
17
|
+
__reExport(tables_exports, require("./syncLogManager.js"), module.exports);
|
|
18
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
19
|
+
0 && (module.exports = {
|
|
20
|
+
...require("./syncLogManager.js")
|
|
21
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Log Manager - Tracks database changes for delta synchronization.
|
|
3
|
+
*
|
|
4
|
+
* Maintains a change log with vector clocks for each modification,
|
|
5
|
+
* enabling efficient delta sync between devices.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const syncLog = new SyncLogManager(adapter, 'device-123');
|
|
10
|
+
* await syncLog.initialize();
|
|
11
|
+
*
|
|
12
|
+
* // Log a change
|
|
13
|
+
* await syncLog.logChange({
|
|
14
|
+
* table: 'users',
|
|
15
|
+
* recordId: 'user-1',
|
|
16
|
+
* operation: 'UPDATE',
|
|
17
|
+
* oldData: { name: 'Old' },
|
|
18
|
+
* newData: { name: 'New' },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Get changes since a clock
|
|
22
|
+
* const changes = await syncLog.getChangesSince(remoteVectorClock);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type { StorageAdapter } from '../../../core/contracts/index.js';
|
|
26
|
+
import type { VectorClockData } from '../protocol/vectorClock.js';
|
|
27
|
+
import { VectorClock } from '../protocol/vectorClock.js';
|
|
28
|
+
import type { ChangeRecord, ChangeOperation, DeviceInfo, DeviceType } from '../protocol/messages.js';
|
|
29
|
+
/**
|
|
30
|
+
* Change log entry as stored in database.
|
|
31
|
+
*/
|
|
32
|
+
export interface ChangeLogEntry {
|
|
33
|
+
logId: number;
|
|
34
|
+
changeId: string;
|
|
35
|
+
tableName: string;
|
|
36
|
+
recordId: string;
|
|
37
|
+
operation: ChangeOperation;
|
|
38
|
+
vectorClock: string;
|
|
39
|
+
deviceId: string;
|
|
40
|
+
oldData: string | null;
|
|
41
|
+
newData: string | null;
|
|
42
|
+
createdAt: number;
|
|
43
|
+
syncedAt: number | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Device registry entry.
|
|
47
|
+
*/
|
|
48
|
+
export interface DeviceEntry {
|
|
49
|
+
deviceId: string;
|
|
50
|
+
deviceType: DeviceType;
|
|
51
|
+
deviceName: string | null;
|
|
52
|
+
vectorClock: string;
|
|
53
|
+
lastSeenAt: number;
|
|
54
|
+
createdAt: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Conflict entry.
|
|
58
|
+
*/
|
|
59
|
+
export interface ConflictEntry {
|
|
60
|
+
conflictId: string;
|
|
61
|
+
tableName: string;
|
|
62
|
+
recordId: string;
|
|
63
|
+
localData: string;
|
|
64
|
+
remoteData: string;
|
|
65
|
+
localClock: string;
|
|
66
|
+
remoteClock: string;
|
|
67
|
+
status: 'pending' | 'resolved' | 'deferred';
|
|
68
|
+
resolution: string | null;
|
|
69
|
+
createdAt: number;
|
|
70
|
+
resolvedAt: number | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sync log manager configuration.
|
|
74
|
+
*/
|
|
75
|
+
export interface SyncLogManagerConfig {
|
|
76
|
+
/** Maximum log entries to keep (auto-prune) */
|
|
77
|
+
maxLogEntries?: number;
|
|
78
|
+
/** Auto-prune synced entries after N days */
|
|
79
|
+
pruneSyncedAfterDays?: number;
|
|
80
|
+
/** Tables to track (undefined = all) */
|
|
81
|
+
includeTables?: string[];
|
|
82
|
+
/** Tables to exclude from tracking */
|
|
83
|
+
excludeTables?: string[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Manages the sync change log and device registry.
|
|
87
|
+
*/
|
|
88
|
+
export declare class SyncLogManager {
|
|
89
|
+
private readonly adapter;
|
|
90
|
+
private readonly deviceId;
|
|
91
|
+
private readonly config;
|
|
92
|
+
private vectorClock;
|
|
93
|
+
private isInitialized;
|
|
94
|
+
constructor(adapter: StorageAdapter, deviceId?: string, config?: SyncLogManagerConfig);
|
|
95
|
+
/**
|
|
96
|
+
* Initialize sync tables.
|
|
97
|
+
*/
|
|
98
|
+
initialize(): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Create sync tracking tables.
|
|
101
|
+
*/
|
|
102
|
+
private createSyncTables;
|
|
103
|
+
/**
|
|
104
|
+
* Load vector clock from database or create new one.
|
|
105
|
+
*/
|
|
106
|
+
private loadVectorClock;
|
|
107
|
+
/**
|
|
108
|
+
* Log a database change.
|
|
109
|
+
*/
|
|
110
|
+
logChange(change: {
|
|
111
|
+
table: string;
|
|
112
|
+
recordId: string;
|
|
113
|
+
operation: ChangeOperation;
|
|
114
|
+
oldData?: Record<string, unknown>;
|
|
115
|
+
newData?: Record<string, unknown>;
|
|
116
|
+
}): Promise<ChangeRecord>;
|
|
117
|
+
/**
|
|
118
|
+
* Get changes since a vector clock.
|
|
119
|
+
*/
|
|
120
|
+
getChangesSince(sinceClock: VectorClockData, options?: {
|
|
121
|
+
tables?: string[];
|
|
122
|
+
limit?: number;
|
|
123
|
+
}): Promise<ChangeRecord[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Mark changes as synced.
|
|
126
|
+
*/
|
|
127
|
+
markSynced(changeIds: string[]): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Get unsynced change count.
|
|
130
|
+
*/
|
|
131
|
+
getUnsyncedCount(): Promise<number>;
|
|
132
|
+
/**
|
|
133
|
+
* Register a device.
|
|
134
|
+
*/
|
|
135
|
+
registerDevice(device: DeviceInfo): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Get all registered devices.
|
|
138
|
+
*/
|
|
139
|
+
getDevices(): Promise<DeviceInfo[]>;
|
|
140
|
+
/**
|
|
141
|
+
* Update a device's vector clock.
|
|
142
|
+
*/
|
|
143
|
+
updateDeviceClockFor(deviceId: string, clock: VectorClockData): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Record a conflict.
|
|
146
|
+
*/
|
|
147
|
+
recordConflict(conflict: {
|
|
148
|
+
table: string;
|
|
149
|
+
recordId: string;
|
|
150
|
+
localData: Record<string, unknown>;
|
|
151
|
+
remoteData: Record<string, unknown>;
|
|
152
|
+
localClock: VectorClockData;
|
|
153
|
+
remoteClock: VectorClockData;
|
|
154
|
+
}): Promise<string>;
|
|
155
|
+
/**
|
|
156
|
+
* Get pending conflicts.
|
|
157
|
+
*/
|
|
158
|
+
getPendingConflicts(): Promise<ConflictEntry[]>;
|
|
159
|
+
/**
|
|
160
|
+
* Resolve a conflict.
|
|
161
|
+
*/
|
|
162
|
+
resolveConflict(conflictId: string, resolution: string): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Get current vector clock.
|
|
165
|
+
*/
|
|
166
|
+
getVectorClock(): VectorClock;
|
|
167
|
+
/**
|
|
168
|
+
* Get vector clock data.
|
|
169
|
+
*/
|
|
170
|
+
getVectorClockData(): VectorClockData;
|
|
171
|
+
/**
|
|
172
|
+
* Merge a remote clock into ours.
|
|
173
|
+
*/
|
|
174
|
+
mergeVectorClock(remoteClock: VectorClockData): void;
|
|
175
|
+
/**
|
|
176
|
+
* Get device ID.
|
|
177
|
+
*/
|
|
178
|
+
getDeviceId(): string;
|
|
179
|
+
/**
|
|
180
|
+
* Update this device's clock in database.
|
|
181
|
+
*/
|
|
182
|
+
private updateDeviceClock;
|
|
183
|
+
/**
|
|
184
|
+
* Check if table should be tracked.
|
|
185
|
+
*/
|
|
186
|
+
private shouldTrackTable;
|
|
187
|
+
/**
|
|
188
|
+
* Check if clock A is after clock B.
|
|
189
|
+
*/
|
|
190
|
+
private isClockAfter;
|
|
191
|
+
/**
|
|
192
|
+
* Generate unique change ID.
|
|
193
|
+
*/
|
|
194
|
+
private generateChangeId;
|
|
195
|
+
/**
|
|
196
|
+
* Generate unique conflict ID.
|
|
197
|
+
*/
|
|
198
|
+
private generateConflictId;
|
|
199
|
+
/**
|
|
200
|
+
* Detect device type from environment.
|
|
201
|
+
*/
|
|
202
|
+
private detectDeviceType;
|
|
203
|
+
/**
|
|
204
|
+
* Get device name.
|
|
205
|
+
*/
|
|
206
|
+
private getDeviceName;
|
|
207
|
+
/**
|
|
208
|
+
* Prune old synced entries if over limit.
|
|
209
|
+
*/
|
|
210
|
+
private pruneIfNeeded;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Create a sync log manager.
|
|
214
|
+
*/
|
|
215
|
+
export declare function createSyncLogManager(adapter: StorageAdapter, deviceId?: string, config?: SyncLogManagerConfig): SyncLogManager;
|
|
216
|
+
//# sourceMappingURL=syncLogManager.d.ts.map
|