@framers/sql-storage-adapter 0.4.2 → 0.5.1
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 +94 -2
- package/dist/adapters/electron/electronMainAdapter.d.ts +241 -0
- package/dist/adapters/electron/electronMainAdapter.d.ts.map +1 -0
- package/dist/adapters/electron/electronMainAdapter.js +442 -0
- package/dist/adapters/electron/electronMainAdapter.js.map +1 -0
- package/dist/adapters/electron/electronRendererAdapter.d.ts +177 -0
- package/dist/adapters/electron/electronRendererAdapter.d.ts.map +1 -0
- package/dist/adapters/electron/electronRendererAdapter.js +339 -0
- package/dist/adapters/electron/electronRendererAdapter.js.map +1 -0
- package/dist/adapters/electron/index.d.ts +74 -0
- package/dist/adapters/electron/index.d.ts.map +1 -0
- package/dist/adapters/electron/index.js +96 -0
- package/dist/adapters/electron/index.js.map +1 -0
- package/dist/adapters/electron/ipc/channels.d.ts +196 -0
- package/dist/adapters/electron/ipc/channels.d.ts.map +1 -0
- package/dist/adapters/electron/ipc/channels.js +121 -0
- package/dist/adapters/electron/ipc/channels.js.map +1 -0
- package/dist/adapters/electron/ipc/index.d.ts +11 -0
- package/dist/adapters/electron/ipc/index.d.ts.map +1 -0
- package/dist/adapters/electron/ipc/index.js +11 -0
- package/dist/adapters/electron/ipc/index.js.map +1 -0
- package/dist/adapters/electron/ipc/protocol.d.ts +78 -0
- package/dist/adapters/electron/ipc/protocol.d.ts.map +1 -0
- package/dist/adapters/electron/ipc/protocol.js +347 -0
- package/dist/adapters/electron/ipc/protocol.js.map +1 -0
- package/dist/adapters/electron/ipc/types.d.ts +248 -0
- package/dist/adapters/electron/ipc/types.d.ts.map +1 -0
- package/dist/adapters/electron/ipc/types.js +8 -0
- package/dist/adapters/electron/ipc/types.js.map +1 -0
- package/dist/adapters/electron/migration/autoMigrator.d.ts +184 -0
- package/dist/adapters/electron/migration/autoMigrator.d.ts.map +1 -0
- package/dist/adapters/electron/migration/autoMigrator.js +478 -0
- package/dist/adapters/electron/migration/autoMigrator.js.map +1 -0
- package/dist/adapters/electron/migration/index.d.ts +9 -0
- package/dist/adapters/electron/migration/index.d.ts.map +1 -0
- package/dist/adapters/electron/migration/index.js +9 -0
- package/dist/adapters/electron/migration/index.js.map +1 -0
- package/dist/adapters/electron/preload.d.ts +126 -0
- package/dist/adapters/electron/preload.d.ts.map +1 -0
- package/dist/adapters/electron/preload.js +254 -0
- package/dist/adapters/electron/preload.js.map +1 -0
- package/dist/adapters/electron/recovery/corruptionDetector.d.ts +214 -0
- package/dist/adapters/electron/recovery/corruptionDetector.d.ts.map +1 -0
- package/dist/adapters/electron/recovery/corruptionDetector.js +410 -0
- package/dist/adapters/electron/recovery/corruptionDetector.js.map +1 -0
- package/dist/adapters/electron/recovery/index.d.ts +11 -0
- package/dist/adapters/electron/recovery/index.d.ts.map +1 -0
- package/dist/adapters/electron/recovery/index.js +11 -0
- package/dist/adapters/electron/recovery/index.js.map +1 -0
- package/dist/adapters/electron/recovery/walCheckpoint.d.ts +186 -0
- package/dist/adapters/electron/recovery/walCheckpoint.d.ts.map +1 -0
- package/dist/adapters/electron/recovery/walCheckpoint.js +302 -0
- package/dist/adapters/electron/recovery/walCheckpoint.js.map +1 -0
- package/dist/adapters/electron/window/index.d.ts +9 -0
- package/dist/adapters/electron/window/index.d.ts.map +1 -0
- package/dist/adapters/electron/window/index.js +9 -0
- package/dist/adapters/electron/window/index.js.map +1 -0
- package/dist/adapters/electron/window/windowManager.d.ts +190 -0
- package/dist/adapters/electron/window/windowManager.d.ts.map +1 -0
- package/dist/adapters/electron/window/windowManager.js +358 -0
- package/dist/adapters/electron/window/windowManager.js.map +1 -0
- package/dist/core/contracts/context.d.ts +2 -2
- package/dist/core/contracts/context.d.ts.map +1 -1
- package/dist/core/database.d.ts +19 -0
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +4 -0
- package/dist/core/database.js.map +1 -1
- package/dist/core/resolver.d.ts +3 -0
- package/dist/core/resolver.d.ts.map +1 -1
- package/dist/core/resolver.js +39 -3
- package/dist/core/resolver.js.map +1 -1
- package/dist/features/sync/conflicts/conflictResolver.d.ts +222 -0
- package/dist/features/sync/conflicts/conflictResolver.d.ts.map +1 -0
- package/dist/features/sync/conflicts/conflictResolver.js +396 -0
- package/dist/features/sync/conflicts/conflictResolver.js.map +1 -0
- package/dist/features/sync/conflicts/index.d.ts +9 -0
- package/dist/features/sync/conflicts/index.d.ts.map +1 -0
- package/dist/features/sync/conflicts/index.js +9 -0
- package/dist/features/sync/conflicts/index.js.map +1 -0
- package/dist/features/sync/crossPlatformSync.d.ts +281 -0
- package/dist/features/sync/crossPlatformSync.d.ts.map +1 -0
- package/dist/features/sync/crossPlatformSync.js +623 -0
- package/dist/features/sync/crossPlatformSync.js.map +1 -0
- package/dist/features/sync/devices/deviceManager.d.ts +243 -0
- package/dist/features/sync/devices/deviceManager.d.ts.map +1 -0
- package/dist/features/sync/devices/deviceManager.js +494 -0
- package/dist/features/sync/devices/deviceManager.js.map +1 -0
- package/dist/features/sync/devices/index.d.ts +10 -0
- package/dist/features/sync/devices/index.d.ts.map +1 -0
- package/dist/features/sync/devices/index.js +10 -0
- package/dist/features/sync/devices/index.js.map +1 -0
- package/dist/features/sync/index.d.ts +37 -0
- package/dist/features/sync/index.d.ts.map +1 -0
- package/dist/features/sync/index.js +47 -0
- package/dist/features/sync/index.js.map +1 -0
- package/dist/features/sync/protocol/index.d.ts +11 -0
- package/dist/features/sync/protocol/index.d.ts.map +1 -0
- package/dist/features/sync/protocol/index.js +11 -0
- package/dist/features/sync/protocol/index.js.map +1 -0
- package/dist/features/sync/protocol/messages.d.ts +348 -0
- package/dist/features/sync/protocol/messages.d.ts.map +1 -0
- package/dist/features/sync/protocol/messages.js +216 -0
- package/dist/features/sync/protocol/messages.js.map +1 -0
- package/dist/features/sync/protocol/vectorClock.d.ts +164 -0
- package/dist/features/sync/protocol/vectorClock.d.ts.map +1 -0
- package/dist/features/sync/protocol/vectorClock.js +286 -0
- package/dist/features/sync/protocol/vectorClock.js.map +1 -0
- package/dist/features/sync/tables/index.d.ts +10 -0
- package/dist/features/sync/tables/index.d.ts.map +1 -0
- package/dist/features/sync/tables/index.js +10 -0
- package/dist/features/sync/tables/index.js.map +1 -0
- package/dist/features/sync/tables/syncLogManager.d.ts +216 -0
- package/dist/features/sync/tables/syncLogManager.d.ts.map +1 -0
- package/dist/features/sync/tables/syncLogManager.js +456 -0
- package/dist/features/sync/tables/syncLogManager.js.map +1 -0
- package/dist/features/sync/transport/httpTransport.d.ts +123 -0
- package/dist/features/sync/transport/httpTransport.d.ts.map +1 -0
- package/dist/features/sync/transport/httpTransport.js +380 -0
- package/dist/features/sync/transport/httpTransport.js.map +1 -0
- package/dist/features/sync/transport/index.d.ts +12 -0
- package/dist/features/sync/transport/index.d.ts.map +1 -0
- package/dist/features/sync/transport/index.js +12 -0
- package/dist/features/sync/transport/index.js.map +1 -0
- package/dist/features/sync/transport/transport.d.ts +259 -0
- package/dist/features/sync/transport/transport.d.ts.map +1 -0
- package/dist/features/sync/transport/transport.js +153 -0
- package/dist/features/sync/transport/transport.js.map +1 -0
- package/dist/features/sync/transport/websocketTransport.d.ts +126 -0
- package/dist/features/sync/transport/websocketTransport.d.ts.map +1 -0
- package/dist/features/sync/transport/websocketTransport.js +374 -0
- package/dist/features/sync/transport/websocketTransport.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +21 -1
|
@@ -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';
|
|
26
|
+
import type { VectorClockData } from '../protocol/vectorClock';
|
|
27
|
+
import { VectorClock } from '../protocol/vectorClock';
|
|
28
|
+
import type { ChangeRecord, ChangeOperation, DeviceInfo, DeviceType } from '../protocol/messages';
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncLogManager.d.ts","sourceRoot":"","sources":["../../../../src/features/sync/tables/syncLogManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,456 @@
|
|
|
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 { VectorClock, generateDeviceId } from '../protocol/vectorClock.js';
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Default Configuration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
const DEFAULT_CONFIG = {
|
|
30
|
+
maxLogEntries: 10000,
|
|
31
|
+
pruneSyncedAfterDays: 30,
|
|
32
|
+
includeTables: [],
|
|
33
|
+
excludeTables: ['_sync_log', '_sync_devices', '_sync_conflicts'],
|
|
34
|
+
};
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Sync Log Manager
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Manages the sync change log and device registry.
|
|
40
|
+
*/
|
|
41
|
+
export class SyncLogManager {
|
|
42
|
+
constructor(adapter, deviceId = generateDeviceId(), config = {}) {
|
|
43
|
+
this.adapter = adapter;
|
|
44
|
+
this.deviceId = deviceId;
|
|
45
|
+
this.isInitialized = false;
|
|
46
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
47
|
+
this.vectorClock = new VectorClock(deviceId);
|
|
48
|
+
}
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Initialization
|
|
51
|
+
// ============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Initialize sync tables.
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
if (this.isInitialized)
|
|
57
|
+
return;
|
|
58
|
+
await this.createSyncTables();
|
|
59
|
+
await this.loadVectorClock();
|
|
60
|
+
this.isInitialized = true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create sync tracking tables.
|
|
64
|
+
*/
|
|
65
|
+
async createSyncTables() {
|
|
66
|
+
// Change log table
|
|
67
|
+
await this.adapter.exec(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS _sync_log (
|
|
69
|
+
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
+
change_id TEXT NOT NULL UNIQUE,
|
|
71
|
+
table_name TEXT NOT NULL,
|
|
72
|
+
record_id TEXT NOT NULL,
|
|
73
|
+
operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
|
|
74
|
+
vector_clock TEXT NOT NULL,
|
|
75
|
+
device_id TEXT NOT NULL,
|
|
76
|
+
old_data TEXT,
|
|
77
|
+
new_data TEXT,
|
|
78
|
+
created_at INTEGER NOT NULL,
|
|
79
|
+
synced_at INTEGER
|
|
80
|
+
)
|
|
81
|
+
`);
|
|
82
|
+
// Indexes for efficient queries
|
|
83
|
+
await this.adapter.exec(`
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_table ON _sync_log(table_name)
|
|
85
|
+
`);
|
|
86
|
+
await this.adapter.exec(`
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_synced ON _sync_log(synced_at)
|
|
88
|
+
`);
|
|
89
|
+
await this.adapter.exec(`
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_device ON _sync_log(device_id)
|
|
91
|
+
`);
|
|
92
|
+
// Device registry table
|
|
93
|
+
await this.adapter.exec(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS _sync_devices (
|
|
95
|
+
device_id TEXT PRIMARY KEY,
|
|
96
|
+
device_type TEXT NOT NULL,
|
|
97
|
+
device_name TEXT,
|
|
98
|
+
vector_clock TEXT NOT NULL,
|
|
99
|
+
last_seen_at INTEGER NOT NULL,
|
|
100
|
+
created_at INTEGER NOT NULL
|
|
101
|
+
)
|
|
102
|
+
`);
|
|
103
|
+
// Conflicts table
|
|
104
|
+
await this.adapter.exec(`
|
|
105
|
+
CREATE TABLE IF NOT EXISTS _sync_conflicts (
|
|
106
|
+
conflict_id TEXT PRIMARY KEY,
|
|
107
|
+
table_name TEXT NOT NULL,
|
|
108
|
+
record_id TEXT NOT NULL,
|
|
109
|
+
local_data TEXT NOT NULL,
|
|
110
|
+
remote_data TEXT NOT NULL,
|
|
111
|
+
local_clock TEXT NOT NULL,
|
|
112
|
+
remote_clock TEXT NOT NULL,
|
|
113
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'resolved', 'deferred')),
|
|
114
|
+
resolution TEXT,
|
|
115
|
+
created_at INTEGER NOT NULL,
|
|
116
|
+
resolved_at INTEGER
|
|
117
|
+
)
|
|
118
|
+
`);
|
|
119
|
+
await this.adapter.exec(`
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_status ON _sync_conflicts(status)
|
|
121
|
+
`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load vector clock from database or create new one.
|
|
125
|
+
*/
|
|
126
|
+
async loadVectorClock() {
|
|
127
|
+
const device = await this.adapter.get('SELECT * FROM _sync_devices WHERE device_id = ?', [this.deviceId]);
|
|
128
|
+
if (device) {
|
|
129
|
+
const clockData = JSON.parse(device.vectorClock);
|
|
130
|
+
this.vectorClock = new VectorClock(this.deviceId);
|
|
131
|
+
this.vectorClock.observe(clockData);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Register this device
|
|
135
|
+
await this.registerDevice({
|
|
136
|
+
deviceId: this.deviceId,
|
|
137
|
+
deviceType: this.detectDeviceType(),
|
|
138
|
+
deviceName: this.getDeviceName(),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Change Logging
|
|
144
|
+
// ============================================================================
|
|
145
|
+
/**
|
|
146
|
+
* Log a database change.
|
|
147
|
+
*/
|
|
148
|
+
async logChange(change) {
|
|
149
|
+
// Check if table should be tracked
|
|
150
|
+
if (!this.shouldTrackTable(change.table)) {
|
|
151
|
+
throw new Error(`Table ${change.table} is not tracked`);
|
|
152
|
+
}
|
|
153
|
+
// Increment vector clock
|
|
154
|
+
this.vectorClock.tick();
|
|
155
|
+
const changeId = this.generateChangeId();
|
|
156
|
+
const clockData = this.vectorClock.getValues();
|
|
157
|
+
const record = {
|
|
158
|
+
changeId,
|
|
159
|
+
table: change.table,
|
|
160
|
+
recordId: change.recordId,
|
|
161
|
+
operation: change.operation,
|
|
162
|
+
vectorClock: clockData,
|
|
163
|
+
deviceId: this.deviceId,
|
|
164
|
+
oldData: change.oldData,
|
|
165
|
+
newData: change.newData,
|
|
166
|
+
timestamp: Date.now(),
|
|
167
|
+
};
|
|
168
|
+
// Insert into log
|
|
169
|
+
await this.adapter.run(`INSERT INTO _sync_log
|
|
170
|
+
(change_id, table_name, record_id, operation, vector_clock, device_id, old_data, new_data, created_at)
|
|
171
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
172
|
+
changeId,
|
|
173
|
+
change.table,
|
|
174
|
+
change.recordId,
|
|
175
|
+
change.operation,
|
|
176
|
+
JSON.stringify(clockData),
|
|
177
|
+
this.deviceId,
|
|
178
|
+
change.oldData ? JSON.stringify(change.oldData) : null,
|
|
179
|
+
change.newData ? JSON.stringify(change.newData) : null,
|
|
180
|
+
Date.now(),
|
|
181
|
+
]);
|
|
182
|
+
// Update device's vector clock
|
|
183
|
+
await this.updateDeviceClock();
|
|
184
|
+
// Auto-prune if needed
|
|
185
|
+
await this.pruneIfNeeded();
|
|
186
|
+
return record;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get changes since a vector clock.
|
|
190
|
+
*/
|
|
191
|
+
async getChangesSince(sinceClock, options = {}) {
|
|
192
|
+
const { tables, limit = 1000 } = options;
|
|
193
|
+
let query = `
|
|
194
|
+
SELECT * FROM _sync_log
|
|
195
|
+
WHERE synced_at IS NULL
|
|
196
|
+
`;
|
|
197
|
+
const params = [];
|
|
198
|
+
if (tables && tables.length > 0) {
|
|
199
|
+
query += ` AND table_name IN (${tables.map(() => '?').join(', ')})`;
|
|
200
|
+
params.push(...tables);
|
|
201
|
+
}
|
|
202
|
+
query += ` ORDER BY log_id ASC LIMIT ?`;
|
|
203
|
+
params.push(limit);
|
|
204
|
+
const entries = await this.adapter.all(query, params);
|
|
205
|
+
// Filter by vector clock comparison
|
|
206
|
+
const changes = [];
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
const entryClock = JSON.parse(entry.vectorClock);
|
|
209
|
+
// Include if entry's clock is after the since clock
|
|
210
|
+
if (this.isClockAfter(entryClock, sinceClock)) {
|
|
211
|
+
changes.push({
|
|
212
|
+
changeId: entry.changeId,
|
|
213
|
+
table: entry.tableName,
|
|
214
|
+
recordId: entry.recordId,
|
|
215
|
+
operation: entry.operation,
|
|
216
|
+
vectorClock: entryClock,
|
|
217
|
+
deviceId: entry.deviceId,
|
|
218
|
+
oldData: entry.oldData ? JSON.parse(entry.oldData) : undefined,
|
|
219
|
+
newData: entry.newData ? JSON.parse(entry.newData) : undefined,
|
|
220
|
+
timestamp: entry.createdAt,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return changes;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Mark changes as synced.
|
|
228
|
+
*/
|
|
229
|
+
async markSynced(changeIds) {
|
|
230
|
+
if (changeIds.length === 0)
|
|
231
|
+
return;
|
|
232
|
+
const placeholders = changeIds.map(() => '?').join(', ');
|
|
233
|
+
await this.adapter.run(`UPDATE _sync_log SET synced_at = ? WHERE change_id IN (${placeholders})`, [Date.now(), ...changeIds]);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get unsynced change count.
|
|
237
|
+
*/
|
|
238
|
+
async getUnsyncedCount() {
|
|
239
|
+
const result = await this.adapter.get('SELECT COUNT(*) as count FROM _sync_log WHERE synced_at IS NULL');
|
|
240
|
+
return result?.count ?? 0;
|
|
241
|
+
}
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Device Registry
|
|
244
|
+
// ============================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Register a device.
|
|
247
|
+
*/
|
|
248
|
+
async registerDevice(device) {
|
|
249
|
+
const existing = await this.adapter.get('SELECT * FROM _sync_devices WHERE device_id = ?', [device.deviceId]);
|
|
250
|
+
const clockData = this.vectorClock.getValues();
|
|
251
|
+
const now = Date.now();
|
|
252
|
+
if (existing) {
|
|
253
|
+
await this.adapter.run(`UPDATE _sync_devices
|
|
254
|
+
SET device_type = ?, device_name = ?, vector_clock = ?, last_seen_at = ?
|
|
255
|
+
WHERE device_id = ?`, [device.deviceType, device.deviceName ?? null, JSON.stringify(clockData), now, device.deviceId]);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
await this.adapter.run(`INSERT INTO _sync_devices (device_id, device_type, device_name, vector_clock, last_seen_at, created_at)
|
|
259
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [device.deviceId, device.deviceType, device.deviceName ?? null, JSON.stringify(clockData), now, now]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get all registered devices.
|
|
264
|
+
*/
|
|
265
|
+
async getDevices() {
|
|
266
|
+
const entries = await this.adapter.all('SELECT * FROM _sync_devices ORDER BY last_seen_at DESC');
|
|
267
|
+
return entries.map(entry => ({
|
|
268
|
+
deviceId: entry.deviceId,
|
|
269
|
+
deviceType: entry.deviceType,
|
|
270
|
+
deviceName: entry.deviceName ?? undefined,
|
|
271
|
+
lastSeen: entry.lastSeenAt,
|
|
272
|
+
firstSeen: entry.createdAt,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Update a device's vector clock.
|
|
277
|
+
*/
|
|
278
|
+
async updateDeviceClockFor(deviceId, clock) {
|
|
279
|
+
await this.adapter.run('UPDATE _sync_devices SET vector_clock = ?, last_seen_at = ? WHERE device_id = ?', [JSON.stringify(clock), Date.now(), deviceId]);
|
|
280
|
+
// Merge into our clock
|
|
281
|
+
this.vectorClock.observe(clock);
|
|
282
|
+
}
|
|
283
|
+
// ============================================================================
|
|
284
|
+
// Conflicts
|
|
285
|
+
// ============================================================================
|
|
286
|
+
/**
|
|
287
|
+
* Record a conflict.
|
|
288
|
+
*/
|
|
289
|
+
async recordConflict(conflict) {
|
|
290
|
+
const conflictId = this.generateConflictId();
|
|
291
|
+
await this.adapter.run(`INSERT INTO _sync_conflicts
|
|
292
|
+
(conflict_id, table_name, record_id, local_data, remote_data, local_clock, remote_clock, created_at)
|
|
293
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
294
|
+
conflictId,
|
|
295
|
+
conflict.table,
|
|
296
|
+
conflict.recordId,
|
|
297
|
+
JSON.stringify(conflict.localData),
|
|
298
|
+
JSON.stringify(conflict.remoteData),
|
|
299
|
+
JSON.stringify(conflict.localClock),
|
|
300
|
+
JSON.stringify(conflict.remoteClock),
|
|
301
|
+
Date.now(),
|
|
302
|
+
]);
|
|
303
|
+
return conflictId;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get pending conflicts.
|
|
307
|
+
*/
|
|
308
|
+
async getPendingConflicts() {
|
|
309
|
+
return this.adapter.all("SELECT * FROM _sync_conflicts WHERE status = 'pending' ORDER BY created_at ASC");
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Resolve a conflict.
|
|
313
|
+
*/
|
|
314
|
+
async resolveConflict(conflictId, resolution) {
|
|
315
|
+
await this.adapter.run("UPDATE _sync_conflicts SET status = 'resolved', resolution = ?, resolved_at = ? WHERE conflict_id = ?", [resolution, Date.now(), conflictId]);
|
|
316
|
+
}
|
|
317
|
+
// ============================================================================
|
|
318
|
+
// Vector Clock
|
|
319
|
+
// ============================================================================
|
|
320
|
+
/**
|
|
321
|
+
* Get current vector clock.
|
|
322
|
+
*/
|
|
323
|
+
getVectorClock() {
|
|
324
|
+
return this.vectorClock;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get vector clock data.
|
|
328
|
+
*/
|
|
329
|
+
getVectorClockData() {
|
|
330
|
+
return this.vectorClock.getValues();
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Merge a remote clock into ours.
|
|
334
|
+
*/
|
|
335
|
+
mergeVectorClock(remoteClock) {
|
|
336
|
+
this.vectorClock.merge(remoteClock);
|
|
337
|
+
}
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// Utilities
|
|
340
|
+
// ============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Get device ID.
|
|
343
|
+
*/
|
|
344
|
+
getDeviceId() {
|
|
345
|
+
return this.deviceId;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Update this device's clock in database.
|
|
349
|
+
*/
|
|
350
|
+
async updateDeviceClock() {
|
|
351
|
+
await this.adapter.run('UPDATE _sync_devices SET vector_clock = ?, last_seen_at = ? WHERE device_id = ?', [JSON.stringify(this.vectorClock.getValues()), Date.now(), this.deviceId]);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Check if table should be tracked.
|
|
355
|
+
*/
|
|
356
|
+
shouldTrackTable(table) {
|
|
357
|
+
// Exclude sync tables
|
|
358
|
+
if (this.config.excludeTables.includes(table)) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
// If include list is specified, only track those
|
|
362
|
+
if (this.config.includeTables.length > 0) {
|
|
363
|
+
return this.config.includeTables.includes(table);
|
|
364
|
+
}
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Check if clock A is after clock B.
|
|
369
|
+
*/
|
|
370
|
+
isClockAfter(a, b) {
|
|
371
|
+
let aGreater = false;
|
|
372
|
+
let bGreater = false;
|
|
373
|
+
const allDevices = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
374
|
+
for (const deviceId of allDevices) {
|
|
375
|
+
const aValue = a[deviceId] ?? 0;
|
|
376
|
+
const bValue = b[deviceId] ?? 0;
|
|
377
|
+
if (aValue > bValue)
|
|
378
|
+
aGreater = true;
|
|
379
|
+
if (bValue > aValue)
|
|
380
|
+
bGreater = true;
|
|
381
|
+
}
|
|
382
|
+
return aGreater && !bGreater;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Generate unique change ID.
|
|
386
|
+
*/
|
|
387
|
+
generateChangeId() {
|
|
388
|
+
const timestamp = Date.now().toString(36);
|
|
389
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
390
|
+
return `chg_${timestamp}_${random}`;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Generate unique conflict ID.
|
|
394
|
+
*/
|
|
395
|
+
generateConflictId() {
|
|
396
|
+
const timestamp = Date.now().toString(36);
|
|
397
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
398
|
+
return `cnf_${timestamp}_${random}`;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Detect device type from environment.
|
|
402
|
+
*/
|
|
403
|
+
detectDeviceType() {
|
|
404
|
+
// Check for Electron
|
|
405
|
+
if (typeof process !== 'undefined' && process.versions?.electron) {
|
|
406
|
+
return 'electron';
|
|
407
|
+
}
|
|
408
|
+
// Check for Capacitor
|
|
409
|
+
if (typeof window !== 'undefined' && window.Capacitor) {
|
|
410
|
+
return 'capacitor';
|
|
411
|
+
}
|
|
412
|
+
// Check for browser
|
|
413
|
+
if (typeof window !== 'undefined') {
|
|
414
|
+
return 'browser';
|
|
415
|
+
}
|
|
416
|
+
return 'server';
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get device name.
|
|
420
|
+
*/
|
|
421
|
+
getDeviceName() {
|
|
422
|
+
if (typeof navigator !== 'undefined') {
|
|
423
|
+
return navigator.userAgent.split('/')[0];
|
|
424
|
+
}
|
|
425
|
+
if (typeof process !== 'undefined') {
|
|
426
|
+
return process.platform;
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Prune old synced entries if over limit.
|
|
432
|
+
*/
|
|
433
|
+
async pruneIfNeeded() {
|
|
434
|
+
const count = await this.adapter.get('SELECT COUNT(*) as count FROM _sync_log');
|
|
435
|
+
if ((count?.count ?? 0) > this.config.maxLogEntries) {
|
|
436
|
+
// Delete oldest synced entries
|
|
437
|
+
const deleteCount = (count?.count ?? 0) - this.config.maxLogEntries + 100;
|
|
438
|
+
await this.adapter.run(`DELETE FROM _sync_log WHERE log_id IN (
|
|
439
|
+
SELECT log_id FROM _sync_log
|
|
440
|
+
WHERE synced_at IS NOT NULL
|
|
441
|
+
ORDER BY log_id ASC
|
|
442
|
+
LIMIT ?
|
|
443
|
+
)`, [deleteCount]);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// ============================================================================
|
|
448
|
+
// Factory Function
|
|
449
|
+
// ============================================================================
|
|
450
|
+
/**
|
|
451
|
+
* Create a sync log manager.
|
|
452
|
+
*/
|
|
453
|
+
export function createSyncLogManager(adapter, deviceId, config) {
|
|
454
|
+
return new SyncLogManager(adapter, deviceId, config);
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=syncLogManager.js.map
|