@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,423 @@
|
|
|
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 syncLogManager_exports = {};
|
|
19
|
+
__export(syncLogManager_exports, {
|
|
20
|
+
SyncLogManager: () => SyncLogManager,
|
|
21
|
+
createSyncLogManager: () => createSyncLogManager
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(syncLogManager_exports);
|
|
24
|
+
var import_vectorClock = require("../protocol/vectorClock.js");
|
|
25
|
+
const DEFAULT_CONFIG = {
|
|
26
|
+
maxLogEntries: 1e4,
|
|
27
|
+
pruneSyncedAfterDays: 30,
|
|
28
|
+
includeTables: [],
|
|
29
|
+
excludeTables: ["_sync_log", "_sync_devices", "_sync_conflicts"]
|
|
30
|
+
};
|
|
31
|
+
class SyncLogManager {
|
|
32
|
+
constructor(adapter, deviceId = (0, import_vectorClock.generateDeviceId)(), config = {}) {
|
|
33
|
+
this.adapter = adapter;
|
|
34
|
+
this.deviceId = deviceId;
|
|
35
|
+
this.isInitialized = false;
|
|
36
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
37
|
+
this.vectorClock = new import_vectorClock.VectorClock(deviceId);
|
|
38
|
+
}
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Initialization
|
|
41
|
+
// ============================================================================
|
|
42
|
+
/**
|
|
43
|
+
* Initialize sync tables.
|
|
44
|
+
*/
|
|
45
|
+
async initialize() {
|
|
46
|
+
if (this.isInitialized)
|
|
47
|
+
return;
|
|
48
|
+
await this.createSyncTables();
|
|
49
|
+
await this.loadVectorClock();
|
|
50
|
+
this.isInitialized = true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create sync tracking tables.
|
|
54
|
+
*/
|
|
55
|
+
async createSyncTables() {
|
|
56
|
+
await this.adapter.exec(`
|
|
57
|
+
CREATE TABLE IF NOT EXISTS _sync_log (
|
|
58
|
+
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
change_id TEXT NOT NULL UNIQUE,
|
|
60
|
+
table_name TEXT NOT NULL,
|
|
61
|
+
record_id TEXT NOT NULL,
|
|
62
|
+
operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
|
|
63
|
+
vector_clock TEXT NOT NULL,
|
|
64
|
+
device_id TEXT NOT NULL,
|
|
65
|
+
old_data TEXT,
|
|
66
|
+
new_data TEXT,
|
|
67
|
+
created_at INTEGER NOT NULL,
|
|
68
|
+
synced_at INTEGER
|
|
69
|
+
)
|
|
70
|
+
`);
|
|
71
|
+
await this.adapter.exec(`
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_table ON _sync_log(table_name)
|
|
73
|
+
`);
|
|
74
|
+
await this.adapter.exec(`
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_synced ON _sync_log(synced_at)
|
|
76
|
+
`);
|
|
77
|
+
await this.adapter.exec(`
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_sync_log_device ON _sync_log(device_id)
|
|
79
|
+
`);
|
|
80
|
+
await this.adapter.exec(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS _sync_devices (
|
|
82
|
+
device_id TEXT PRIMARY KEY,
|
|
83
|
+
device_type TEXT NOT NULL,
|
|
84
|
+
device_name TEXT,
|
|
85
|
+
vector_clock TEXT NOT NULL,
|
|
86
|
+
last_seen_at INTEGER NOT NULL,
|
|
87
|
+
created_at INTEGER NOT NULL
|
|
88
|
+
)
|
|
89
|
+
`);
|
|
90
|
+
await this.adapter.exec(`
|
|
91
|
+
CREATE TABLE IF NOT EXISTS _sync_conflicts (
|
|
92
|
+
conflict_id TEXT PRIMARY KEY,
|
|
93
|
+
table_name TEXT NOT NULL,
|
|
94
|
+
record_id TEXT NOT NULL,
|
|
95
|
+
local_data TEXT NOT NULL,
|
|
96
|
+
remote_data TEXT NOT NULL,
|
|
97
|
+
local_clock TEXT NOT NULL,
|
|
98
|
+
remote_clock TEXT NOT NULL,
|
|
99
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'resolved', 'deferred')),
|
|
100
|
+
resolution TEXT,
|
|
101
|
+
created_at INTEGER NOT NULL,
|
|
102
|
+
resolved_at INTEGER
|
|
103
|
+
)
|
|
104
|
+
`);
|
|
105
|
+
await this.adapter.exec(`
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_status ON _sync_conflicts(status)
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Load vector clock from database or create new one.
|
|
111
|
+
*/
|
|
112
|
+
async loadVectorClock() {
|
|
113
|
+
const device = await this.adapter.get("SELECT * FROM _sync_devices WHERE device_id = ?", [this.deviceId]);
|
|
114
|
+
if (device) {
|
|
115
|
+
const clockData = JSON.parse(device.vectorClock);
|
|
116
|
+
this.vectorClock = new import_vectorClock.VectorClock(this.deviceId);
|
|
117
|
+
this.vectorClock.observe(clockData);
|
|
118
|
+
} else {
|
|
119
|
+
await this.registerDevice({
|
|
120
|
+
deviceId: this.deviceId,
|
|
121
|
+
deviceType: this.detectDeviceType(),
|
|
122
|
+
deviceName: this.getDeviceName()
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Change Logging
|
|
128
|
+
// ============================================================================
|
|
129
|
+
/**
|
|
130
|
+
* Log a database change.
|
|
131
|
+
*/
|
|
132
|
+
async logChange(change) {
|
|
133
|
+
if (!this.shouldTrackTable(change.table)) {
|
|
134
|
+
throw new Error(`Table ${change.table} is not tracked`);
|
|
135
|
+
}
|
|
136
|
+
this.vectorClock.tick();
|
|
137
|
+
const changeId = this.generateChangeId();
|
|
138
|
+
const clockData = this.vectorClock.getValues();
|
|
139
|
+
const record = {
|
|
140
|
+
changeId,
|
|
141
|
+
table: change.table,
|
|
142
|
+
recordId: change.recordId,
|
|
143
|
+
operation: change.operation,
|
|
144
|
+
vectorClock: clockData,
|
|
145
|
+
deviceId: this.deviceId,
|
|
146
|
+
oldData: change.oldData,
|
|
147
|
+
newData: change.newData,
|
|
148
|
+
timestamp: Date.now()
|
|
149
|
+
};
|
|
150
|
+
await this.adapter.run(`INSERT INTO _sync_log
|
|
151
|
+
(change_id, table_name, record_id, operation, vector_clock, device_id, old_data, new_data, created_at)
|
|
152
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
153
|
+
changeId,
|
|
154
|
+
change.table,
|
|
155
|
+
change.recordId,
|
|
156
|
+
change.operation,
|
|
157
|
+
JSON.stringify(clockData),
|
|
158
|
+
this.deviceId,
|
|
159
|
+
change.oldData ? JSON.stringify(change.oldData) : null,
|
|
160
|
+
change.newData ? JSON.stringify(change.newData) : null,
|
|
161
|
+
Date.now()
|
|
162
|
+
]);
|
|
163
|
+
await this.updateDeviceClock();
|
|
164
|
+
await this.pruneIfNeeded();
|
|
165
|
+
return record;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get changes since a vector clock.
|
|
169
|
+
*/
|
|
170
|
+
async getChangesSince(sinceClock, options = {}) {
|
|
171
|
+
const { tables, limit = 1e3 } = options;
|
|
172
|
+
let query = `
|
|
173
|
+
SELECT * FROM _sync_log
|
|
174
|
+
WHERE synced_at IS NULL
|
|
175
|
+
`;
|
|
176
|
+
const params = [];
|
|
177
|
+
if (tables && tables.length > 0) {
|
|
178
|
+
query += ` AND table_name IN (${tables.map(() => "?").join(", ")})`;
|
|
179
|
+
params.push(...tables);
|
|
180
|
+
}
|
|
181
|
+
query += ` ORDER BY log_id ASC LIMIT ?`;
|
|
182
|
+
params.push(limit);
|
|
183
|
+
const entries = await this.adapter.all(query, params);
|
|
184
|
+
const changes = [];
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
const entryClock = JSON.parse(entry.vectorClock);
|
|
187
|
+
if (this.isClockAfter(entryClock, sinceClock)) {
|
|
188
|
+
changes.push({
|
|
189
|
+
changeId: entry.changeId,
|
|
190
|
+
table: entry.tableName,
|
|
191
|
+
recordId: entry.recordId,
|
|
192
|
+
operation: entry.operation,
|
|
193
|
+
vectorClock: entryClock,
|
|
194
|
+
deviceId: entry.deviceId,
|
|
195
|
+
oldData: entry.oldData ? JSON.parse(entry.oldData) : void 0,
|
|
196
|
+
newData: entry.newData ? JSON.parse(entry.newData) : void 0,
|
|
197
|
+
timestamp: entry.createdAt
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return changes;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Mark changes as synced.
|
|
205
|
+
*/
|
|
206
|
+
async markSynced(changeIds) {
|
|
207
|
+
if (changeIds.length === 0)
|
|
208
|
+
return;
|
|
209
|
+
const placeholders = changeIds.map(() => "?").join(", ");
|
|
210
|
+
await this.adapter.run(`UPDATE _sync_log SET synced_at = ? WHERE change_id IN (${placeholders})`, [Date.now(), ...changeIds]);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get unsynced change count.
|
|
214
|
+
*/
|
|
215
|
+
async getUnsyncedCount() {
|
|
216
|
+
const result = await this.adapter.get("SELECT COUNT(*) as count FROM _sync_log WHERE synced_at IS NULL");
|
|
217
|
+
return result?.count ?? 0;
|
|
218
|
+
}
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Device Registry
|
|
221
|
+
// ============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* Register a device.
|
|
224
|
+
*/
|
|
225
|
+
async registerDevice(device) {
|
|
226
|
+
const existing = await this.adapter.get("SELECT * FROM _sync_devices WHERE device_id = ?", [device.deviceId]);
|
|
227
|
+
const clockData = this.vectorClock.getValues();
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
if (existing) {
|
|
230
|
+
await this.adapter.run(`UPDATE _sync_devices
|
|
231
|
+
SET device_type = ?, device_name = ?, vector_clock = ?, last_seen_at = ?
|
|
232
|
+
WHERE device_id = ?`, [device.deviceType, device.deviceName ?? null, JSON.stringify(clockData), now, device.deviceId]);
|
|
233
|
+
} else {
|
|
234
|
+
await this.adapter.run(`INSERT INTO _sync_devices (device_id, device_type, device_name, vector_clock, last_seen_at, created_at)
|
|
235
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [device.deviceId, device.deviceType, device.deviceName ?? null, JSON.stringify(clockData), now, now]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get all registered devices.
|
|
240
|
+
*/
|
|
241
|
+
async getDevices() {
|
|
242
|
+
const entries = await this.adapter.all("SELECT * FROM _sync_devices ORDER BY last_seen_at DESC");
|
|
243
|
+
return entries.map((entry) => ({
|
|
244
|
+
deviceId: entry.deviceId,
|
|
245
|
+
deviceType: entry.deviceType,
|
|
246
|
+
deviceName: entry.deviceName ?? void 0,
|
|
247
|
+
lastSeen: entry.lastSeenAt,
|
|
248
|
+
firstSeen: entry.createdAt
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update a device's vector clock.
|
|
253
|
+
*/
|
|
254
|
+
async updateDeviceClockFor(deviceId, clock) {
|
|
255
|
+
await this.adapter.run("UPDATE _sync_devices SET vector_clock = ?, last_seen_at = ? WHERE device_id = ?", [JSON.stringify(clock), Date.now(), deviceId]);
|
|
256
|
+
this.vectorClock.observe(clock);
|
|
257
|
+
}
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// Conflicts
|
|
260
|
+
// ============================================================================
|
|
261
|
+
/**
|
|
262
|
+
* Record a conflict.
|
|
263
|
+
*/
|
|
264
|
+
async recordConflict(conflict) {
|
|
265
|
+
const conflictId = this.generateConflictId();
|
|
266
|
+
await this.adapter.run(`INSERT INTO _sync_conflicts
|
|
267
|
+
(conflict_id, table_name, record_id, local_data, remote_data, local_clock, remote_clock, created_at)
|
|
268
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
269
|
+
conflictId,
|
|
270
|
+
conflict.table,
|
|
271
|
+
conflict.recordId,
|
|
272
|
+
JSON.stringify(conflict.localData),
|
|
273
|
+
JSON.stringify(conflict.remoteData),
|
|
274
|
+
JSON.stringify(conflict.localClock),
|
|
275
|
+
JSON.stringify(conflict.remoteClock),
|
|
276
|
+
Date.now()
|
|
277
|
+
]);
|
|
278
|
+
return conflictId;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get pending conflicts.
|
|
282
|
+
*/
|
|
283
|
+
async getPendingConflicts() {
|
|
284
|
+
return this.adapter.all("SELECT * FROM _sync_conflicts WHERE status = 'pending' ORDER BY created_at ASC");
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Resolve a conflict.
|
|
288
|
+
*/
|
|
289
|
+
async resolveConflict(conflictId, resolution) {
|
|
290
|
+
await this.adapter.run("UPDATE _sync_conflicts SET status = 'resolved', resolution = ?, resolved_at = ? WHERE conflict_id = ?", [resolution, Date.now(), conflictId]);
|
|
291
|
+
}
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Vector Clock
|
|
294
|
+
// ============================================================================
|
|
295
|
+
/**
|
|
296
|
+
* Get current vector clock.
|
|
297
|
+
*/
|
|
298
|
+
getVectorClock() {
|
|
299
|
+
return this.vectorClock;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get vector clock data.
|
|
303
|
+
*/
|
|
304
|
+
getVectorClockData() {
|
|
305
|
+
return this.vectorClock.getValues();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Merge a remote clock into ours.
|
|
309
|
+
*/
|
|
310
|
+
mergeVectorClock(remoteClock) {
|
|
311
|
+
this.vectorClock.merge(remoteClock);
|
|
312
|
+
}
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// Utilities
|
|
315
|
+
// ============================================================================
|
|
316
|
+
/**
|
|
317
|
+
* Get device ID.
|
|
318
|
+
*/
|
|
319
|
+
getDeviceId() {
|
|
320
|
+
return this.deviceId;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Update this device's clock in database.
|
|
324
|
+
*/
|
|
325
|
+
async updateDeviceClock() {
|
|
326
|
+
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]);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Check if table should be tracked.
|
|
330
|
+
*/
|
|
331
|
+
shouldTrackTable(table) {
|
|
332
|
+
if (this.config.excludeTables.includes(table)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (this.config.includeTables.length > 0) {
|
|
336
|
+
return this.config.includeTables.includes(table);
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Check if clock A is after clock B.
|
|
342
|
+
*/
|
|
343
|
+
isClockAfter(a, b) {
|
|
344
|
+
let aGreater = false;
|
|
345
|
+
let bGreater = false;
|
|
346
|
+
const allDevices = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
347
|
+
for (const deviceId of allDevices) {
|
|
348
|
+
const aValue = a[deviceId] ?? 0;
|
|
349
|
+
const bValue = b[deviceId] ?? 0;
|
|
350
|
+
if (aValue > bValue)
|
|
351
|
+
aGreater = true;
|
|
352
|
+
if (bValue > aValue)
|
|
353
|
+
bGreater = true;
|
|
354
|
+
}
|
|
355
|
+
return aGreater && !bGreater;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Generate unique change ID.
|
|
359
|
+
*/
|
|
360
|
+
generateChangeId() {
|
|
361
|
+
const timestamp = Date.now().toString(36);
|
|
362
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
363
|
+
return `chg_${timestamp}_${random}`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Generate unique conflict ID.
|
|
367
|
+
*/
|
|
368
|
+
generateConflictId() {
|
|
369
|
+
const timestamp = Date.now().toString(36);
|
|
370
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
371
|
+
return `cnf_${timestamp}_${random}`;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Detect device type from environment.
|
|
375
|
+
*/
|
|
376
|
+
detectDeviceType() {
|
|
377
|
+
if (typeof process !== "undefined" && process.versions?.electron) {
|
|
378
|
+
return "electron";
|
|
379
|
+
}
|
|
380
|
+
if (typeof window !== "undefined" && window.Capacitor) {
|
|
381
|
+
return "capacitor";
|
|
382
|
+
}
|
|
383
|
+
if (typeof window !== "undefined") {
|
|
384
|
+
return "browser";
|
|
385
|
+
}
|
|
386
|
+
return "server";
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get device name.
|
|
390
|
+
*/
|
|
391
|
+
getDeviceName() {
|
|
392
|
+
if (typeof navigator !== "undefined") {
|
|
393
|
+
return navigator.userAgent.split("/")[0];
|
|
394
|
+
}
|
|
395
|
+
if (typeof process !== "undefined") {
|
|
396
|
+
return process.platform;
|
|
397
|
+
}
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Prune old synced entries if over limit.
|
|
402
|
+
*/
|
|
403
|
+
async pruneIfNeeded() {
|
|
404
|
+
const count = await this.adapter.get("SELECT COUNT(*) as count FROM _sync_log");
|
|
405
|
+
if ((count?.count ?? 0) > this.config.maxLogEntries) {
|
|
406
|
+
const deleteCount = (count?.count ?? 0) - this.config.maxLogEntries + 100;
|
|
407
|
+
await this.adapter.run(`DELETE FROM _sync_log WHERE log_id IN (
|
|
408
|
+
SELECT log_id FROM _sync_log
|
|
409
|
+
WHERE synced_at IS NOT NULL
|
|
410
|
+
ORDER BY log_id ASC
|
|
411
|
+
LIMIT ?
|
|
412
|
+
)`, [deleteCount]);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function createSyncLogManager(adapter, deviceId, config) {
|
|
417
|
+
return new SyncLogManager(adapter, deviceId, config);
|
|
418
|
+
}
|
|
419
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
420
|
+
0 && (module.exports = {
|
|
421
|
+
SyncLogManager,
|
|
422
|
+
createSyncLogManager
|
|
423
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Polling Transport Implementation.
|
|
3
|
+
*
|
|
4
|
+
* Fallback sync transport using HTTP long-polling.
|
|
5
|
+
* Works through firewalls and proxies that block WebSocket.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import { BaseTransport, type TransportOptions } from './transport.js';
|
|
10
|
+
import type { SyncMessage } from '../protocol/messages.js';
|
|
11
|
+
/**
|
|
12
|
+
* HTTP-specific transport options.
|
|
13
|
+
*/
|
|
14
|
+
export interface HttpTransportOptions extends TransportOptions {
|
|
15
|
+
/** Polling interval in milliseconds */
|
|
16
|
+
pollingInterval?: number;
|
|
17
|
+
/** Maximum messages to fetch per poll */
|
|
18
|
+
maxMessagesPerPoll?: number;
|
|
19
|
+
/** Enable long-polling (server holds connection) */
|
|
20
|
+
longPolling?: boolean;
|
|
21
|
+
/** Long-polling timeout in milliseconds */
|
|
22
|
+
longPollTimeout?: number;
|
|
23
|
+
/** Use fetch or XMLHttpRequest */
|
|
24
|
+
fetchImplementation?: typeof fetch;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* HTTP transport for sync with polling.
|
|
28
|
+
*
|
|
29
|
+
* Provides reliable sync over HTTP with:
|
|
30
|
+
* - Regular or long-polling modes
|
|
31
|
+
* - Automatic retry with exponential backoff
|
|
32
|
+
* - Request batching
|
|
33
|
+
* - Configurable polling intervals
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const transport = new HttpTransport({
|
|
38
|
+
* endpoint: 'https://sync.example.com/api/sync',
|
|
39
|
+
* authToken: 'bearer-token',
|
|
40
|
+
* pollingInterval: 5000,
|
|
41
|
+
* longPolling: true,
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* transport.on('message', ({ message }) => {
|
|
45
|
+
* handleSyncMessage(message);
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* await transport.connect();
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare class HttpTransport extends BaseTransport {
|
|
52
|
+
readonly type: "http";
|
|
53
|
+
private _pollTimer;
|
|
54
|
+
private _abortController;
|
|
55
|
+
private _isPolling;
|
|
56
|
+
private _httpOptions;
|
|
57
|
+
private _lastEventId;
|
|
58
|
+
private _messageQueue;
|
|
59
|
+
private _flushTimer;
|
|
60
|
+
private _pendingRequests;
|
|
61
|
+
constructor(options: HttpTransportOptions);
|
|
62
|
+
/**
|
|
63
|
+
* Connect (start polling).
|
|
64
|
+
*/
|
|
65
|
+
connect(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Disconnect (stop polling).
|
|
68
|
+
*/
|
|
69
|
+
disconnect(reason?: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Send a sync message.
|
|
72
|
+
*/
|
|
73
|
+
send(message: SyncMessage): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Send a message and wait for a response.
|
|
76
|
+
*/
|
|
77
|
+
request(message: SyncMessage, timeout?: number): Promise<SyncMessage>;
|
|
78
|
+
/**
|
|
79
|
+
* Perform initial sync check to verify connectivity.
|
|
80
|
+
*/
|
|
81
|
+
private _syncCheck;
|
|
82
|
+
/**
|
|
83
|
+
* Start the polling loop.
|
|
84
|
+
*/
|
|
85
|
+
private _startPolling;
|
|
86
|
+
/**
|
|
87
|
+
* Stop the polling loop.
|
|
88
|
+
*/
|
|
89
|
+
private _stopPolling;
|
|
90
|
+
/**
|
|
91
|
+
* Poll for new messages.
|
|
92
|
+
*/
|
|
93
|
+
private _poll;
|
|
94
|
+
/**
|
|
95
|
+
* Fetch messages from server.
|
|
96
|
+
*/
|
|
97
|
+
private _fetchMessages;
|
|
98
|
+
/**
|
|
99
|
+
* Handle an incoming message.
|
|
100
|
+
*/
|
|
101
|
+
private _handleMessage;
|
|
102
|
+
/**
|
|
103
|
+
* Schedule message queue flush.
|
|
104
|
+
*/
|
|
105
|
+
private _scheduleFlush;
|
|
106
|
+
/**
|
|
107
|
+
* Flush the message queue to server.
|
|
108
|
+
*/
|
|
109
|
+
private _flushQueue;
|
|
110
|
+
/**
|
|
111
|
+
* Make an HTTP request.
|
|
112
|
+
*/
|
|
113
|
+
private _fetch;
|
|
114
|
+
/**
|
|
115
|
+
* Clear all pending requests.
|
|
116
|
+
*/
|
|
117
|
+
private _clearPendingRequests;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create an HTTP transport instance.
|
|
121
|
+
*/
|
|
122
|
+
export declare const createHttpTransport: (options: HttpTransportOptions) => HttpTransport;
|
|
123
|
+
//# sourceMappingURL=httpTransport.d.ts.map
|