@framers/sql-storage-adapter 0.7.1 → 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 +43 -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/dist/features/migrations/schemaMigrations.d.ts +93 -0
- package/dist/features/migrations/schemaMigrations.d.ts.map +1 -0
- package/dist/features/migrations/schemaMigrations.js +216 -0
- package/dist/features/migrations/schemaMigrations.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +139 -24
|
@@ -0,0 +1,366 @@
|
|
|
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 conflictResolver_exports = {};
|
|
19
|
+
__export(conflictResolver_exports, {
|
|
20
|
+
ConflictResolver: () => ConflictResolver,
|
|
21
|
+
FieldMergers: () => FieldMergers,
|
|
22
|
+
createConflictResolver: () => createConflictResolver
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(conflictResolver_exports);
|
|
25
|
+
var import_vectorClock = require("../protocol/vectorClock.js");
|
|
26
|
+
const FieldMergers = {
|
|
27
|
+
/**
|
|
28
|
+
* Use the most recent value based on timestamp.
|
|
29
|
+
*/
|
|
30
|
+
lastWriteWins: ((_fieldName, localValue, remoteValue, localTimestamp, remoteTimestamp) => {
|
|
31
|
+
return localTimestamp >= remoteTimestamp ? localValue : remoteValue;
|
|
32
|
+
}),
|
|
33
|
+
/**
|
|
34
|
+
* For numeric fields, take the maximum value.
|
|
35
|
+
*/
|
|
36
|
+
max: ((_fieldName, localValue, remoteValue) => {
|
|
37
|
+
const local = Number(localValue) || 0;
|
|
38
|
+
const remote = Number(remoteValue) || 0;
|
|
39
|
+
return Math.max(local, remote);
|
|
40
|
+
}),
|
|
41
|
+
/**
|
|
42
|
+
* For numeric fields, sum the values.
|
|
43
|
+
*/
|
|
44
|
+
sum: ((_fieldName, localValue, remoteValue) => {
|
|
45
|
+
const local = Number(localValue) || 0;
|
|
46
|
+
const remote = Number(remoteValue) || 0;
|
|
47
|
+
return local + remote;
|
|
48
|
+
}),
|
|
49
|
+
/**
|
|
50
|
+
* For arrays, union the values.
|
|
51
|
+
*/
|
|
52
|
+
union: ((_fieldName, localValue, remoteValue) => {
|
|
53
|
+
const local = Array.isArray(localValue) ? localValue : [];
|
|
54
|
+
const remote = Array.isArray(remoteValue) ? remoteValue : [];
|
|
55
|
+
return [.../* @__PURE__ */ new Set([...local, ...remote])];
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* For strings, concatenate with separator.
|
|
59
|
+
*/
|
|
60
|
+
concat: ((_fieldName, localValue, remoteValue) => {
|
|
61
|
+
const local = String(localValue ?? "");
|
|
62
|
+
const remote = String(remoteValue ?? "");
|
|
63
|
+
if (!local)
|
|
64
|
+
return remote;
|
|
65
|
+
if (!remote)
|
|
66
|
+
return local;
|
|
67
|
+
if (local === remote)
|
|
68
|
+
return local;
|
|
69
|
+
return `${local}
|
|
70
|
+
---
|
|
71
|
+
${remote}`;
|
|
72
|
+
})
|
|
73
|
+
};
|
|
74
|
+
function mergeVectorClocks(a, b) {
|
|
75
|
+
const merged = { ...a };
|
|
76
|
+
for (const [deviceId, value] of Object.entries(b)) {
|
|
77
|
+
merged[deviceId] = Math.max(merged[deviceId] ?? 0, value);
|
|
78
|
+
}
|
|
79
|
+
return merged;
|
|
80
|
+
}
|
|
81
|
+
function getTotalTicks(clock) {
|
|
82
|
+
return Object.values(clock).reduce((sum, val) => sum + val, 0);
|
|
83
|
+
}
|
|
84
|
+
class ConflictResolver {
|
|
85
|
+
constructor(options) {
|
|
86
|
+
this._pendingConflicts = /* @__PURE__ */ new Map();
|
|
87
|
+
this._resolvedConflicts = /* @__PURE__ */ new Map();
|
|
88
|
+
this._options = {
|
|
89
|
+
defaultStrategy: options.defaultStrategy,
|
|
90
|
+
tableStrategies: options.tableStrategies ?? {},
|
|
91
|
+
fieldMergers: options.fieldMergers ?? {},
|
|
92
|
+
hooks: options.hooks ?? {},
|
|
93
|
+
manualResolutionTimeout: options.manualResolutionTimeout ?? 3e5,
|
|
94
|
+
// 5 minutes
|
|
95
|
+
autoResolveAfter: options.autoResolveAfter ?? 0
|
|
96
|
+
// Disabled by default
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Detect if there is a conflict between local and remote data.
|
|
101
|
+
*/
|
|
102
|
+
detectConflict(tableName, recordId, localData, remoteData, localClock, remoteClock, localDeviceId, remoteDeviceId) {
|
|
103
|
+
const comparison = (0, import_vectorClock.compareClocks)(localClock, remoteClock);
|
|
104
|
+
if (comparison === "before" || comparison === "after") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const conflictingFields = this._findConflictingFields(localData, remoteData);
|
|
108
|
+
if (conflictingFields.length === 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const conflict = {
|
|
112
|
+
conflictId: `${tableName}:${recordId}:${Date.now()}`,
|
|
113
|
+
tableName,
|
|
114
|
+
recordId,
|
|
115
|
+
localData,
|
|
116
|
+
remoteData,
|
|
117
|
+
localClock,
|
|
118
|
+
remoteClock,
|
|
119
|
+
localDeviceId,
|
|
120
|
+
remoteDeviceId,
|
|
121
|
+
detectedAt: Date.now(),
|
|
122
|
+
conflictingFields,
|
|
123
|
+
status: "pending"
|
|
124
|
+
};
|
|
125
|
+
this._pendingConflicts.set(conflict.conflictId, conflict);
|
|
126
|
+
this._options.hooks.onConflictDetected?.(conflict);
|
|
127
|
+
return conflict;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve a conflict using the configured strategy.
|
|
131
|
+
*/
|
|
132
|
+
async resolve(conflict) {
|
|
133
|
+
const strategy = this._getStrategy(conflict.tableName);
|
|
134
|
+
let resolution;
|
|
135
|
+
switch (strategy) {
|
|
136
|
+
case "last-write-wins":
|
|
137
|
+
resolution = this._resolveLastWriteWins(conflict);
|
|
138
|
+
break;
|
|
139
|
+
case "local-wins":
|
|
140
|
+
resolution = this._resolveLocalWins(conflict);
|
|
141
|
+
break;
|
|
142
|
+
case "remote-wins":
|
|
143
|
+
resolution = this._resolveRemoteWins(conflict);
|
|
144
|
+
break;
|
|
145
|
+
case "merge":
|
|
146
|
+
resolution = this._resolveMerge(conflict);
|
|
147
|
+
break;
|
|
148
|
+
case "manual":
|
|
149
|
+
resolution = await this._resolveManual(conflict);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
resolution = this._resolveLastWriteWins(conflict);
|
|
153
|
+
}
|
|
154
|
+
conflict.status = resolution.decision === "defer" ? "deferred" : "resolved";
|
|
155
|
+
this._resolvedConflicts.set(conflict.conflictId, resolution);
|
|
156
|
+
this._pendingConflicts.delete(conflict.conflictId);
|
|
157
|
+
this._options.hooks.onConflictResolved?.(conflict, resolution);
|
|
158
|
+
return resolution;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get pending conflicts.
|
|
162
|
+
*/
|
|
163
|
+
getPendingConflicts() {
|
|
164
|
+
return Array.from(this._pendingConflicts.values());
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get a specific pending conflict.
|
|
168
|
+
*/
|
|
169
|
+
getConflict(conflictId) {
|
|
170
|
+
return this._pendingConflicts.get(conflictId);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Manually resolve a conflict with a user decision.
|
|
174
|
+
*/
|
|
175
|
+
async resolveManually(conflictId, decision, mergedData) {
|
|
176
|
+
const conflict = this._pendingConflicts.get(conflictId);
|
|
177
|
+
if (!conflict) {
|
|
178
|
+
throw new Error(`Conflict not found: ${conflictId}`);
|
|
179
|
+
}
|
|
180
|
+
const resolution = {
|
|
181
|
+
decision,
|
|
182
|
+
mergedData,
|
|
183
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
184
|
+
resolvedBy: "user",
|
|
185
|
+
resolvedAt: Date.now()
|
|
186
|
+
};
|
|
187
|
+
conflict.status = "resolved";
|
|
188
|
+
this._resolvedConflicts.set(conflictId, resolution);
|
|
189
|
+
this._pendingConflicts.delete(conflictId);
|
|
190
|
+
this._options.hooks.onConflictResolved?.(conflict, resolution);
|
|
191
|
+
return resolution;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Auto-resolve old conflicts that have been pending too long.
|
|
195
|
+
*/
|
|
196
|
+
autoResolveStale() {
|
|
197
|
+
if (this._options.autoResolveAfter <= 0) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
const staleThreshold = now - this._options.autoResolveAfter;
|
|
202
|
+
const resolutions = [];
|
|
203
|
+
for (const conflict of this._pendingConflicts.values()) {
|
|
204
|
+
if (conflict.detectedAt < staleThreshold) {
|
|
205
|
+
const resolution = this._resolveLastWriteWins(conflict);
|
|
206
|
+
resolution.reason = "Auto-resolved due to timeout";
|
|
207
|
+
conflict.status = "resolved";
|
|
208
|
+
this._resolvedConflicts.set(conflict.conflictId, resolution);
|
|
209
|
+
this._pendingConflicts.delete(conflict.conflictId);
|
|
210
|
+
resolutions.push(resolution);
|
|
211
|
+
this._options.hooks.onConflictResolved?.(conflict, resolution);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return resolutions;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get the resolution strategy for a table.
|
|
218
|
+
*/
|
|
219
|
+
_getStrategy(tableName) {
|
|
220
|
+
return this._options.tableStrategies[tableName] ?? this._options.defaultStrategy;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Find fields that differ between local and remote data.
|
|
224
|
+
*/
|
|
225
|
+
_findConflictingFields(localData, remoteData) {
|
|
226
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
227
|
+
...Object.keys(localData),
|
|
228
|
+
...Object.keys(remoteData)
|
|
229
|
+
]);
|
|
230
|
+
const conflicting = [];
|
|
231
|
+
for (const key of allKeys) {
|
|
232
|
+
if (key.startsWith("_"))
|
|
233
|
+
continue;
|
|
234
|
+
const localValue = localData[key];
|
|
235
|
+
const remoteValue = remoteData[key];
|
|
236
|
+
if (!this._valuesEqual(localValue, remoteValue)) {
|
|
237
|
+
conflicting.push(key);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return conflicting;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if two values are equal (deep comparison for objects/arrays).
|
|
244
|
+
*/
|
|
245
|
+
_valuesEqual(a, b) {
|
|
246
|
+
if (a === b)
|
|
247
|
+
return true;
|
|
248
|
+
if (a === null || b === null)
|
|
249
|
+
return false;
|
|
250
|
+
if (typeof a !== typeof b)
|
|
251
|
+
return false;
|
|
252
|
+
if (typeof a === "object") {
|
|
253
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve using last-write-wins strategy.
|
|
259
|
+
*/
|
|
260
|
+
_resolveLastWriteWins(conflict) {
|
|
261
|
+
const localTicks = getTotalTicks(conflict.localClock);
|
|
262
|
+
const remoteTicks = getTotalTicks(conflict.remoteClock);
|
|
263
|
+
const useLocal = localTicks > remoteTicks;
|
|
264
|
+
return {
|
|
265
|
+
decision: useLocal ? "use_local" : "use_remote",
|
|
266
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
267
|
+
resolvedBy: "strategy",
|
|
268
|
+
resolvedAt: Date.now(),
|
|
269
|
+
reason: `Last-write-wins: ${useLocal ? "local" : "remote"} had higher clock (${useLocal ? localTicks : remoteTicks} ticks)`
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Resolve using local-wins strategy.
|
|
274
|
+
*/
|
|
275
|
+
_resolveLocalWins(conflict) {
|
|
276
|
+
return {
|
|
277
|
+
decision: "use_local",
|
|
278
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
279
|
+
resolvedBy: "strategy",
|
|
280
|
+
resolvedAt: Date.now(),
|
|
281
|
+
reason: "Local-wins strategy"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Resolve using remote-wins strategy.
|
|
286
|
+
*/
|
|
287
|
+
_resolveRemoteWins(conflict) {
|
|
288
|
+
return {
|
|
289
|
+
decision: "use_remote",
|
|
290
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
291
|
+
resolvedBy: "strategy",
|
|
292
|
+
resolvedAt: Date.now(),
|
|
293
|
+
reason: "Remote-wins strategy"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Resolve using field-level merge strategy.
|
|
298
|
+
*/
|
|
299
|
+
_resolveMerge(conflict) {
|
|
300
|
+
try {
|
|
301
|
+
const mergedData = { ...conflict.localData };
|
|
302
|
+
const now = Date.now();
|
|
303
|
+
for (const field of conflict.conflictingFields) {
|
|
304
|
+
const merger = this._options.fieldMergers[field] ?? FieldMergers.lastWriteWins;
|
|
305
|
+
mergedData[field] = merger(field, conflict.localData[field], conflict.remoteData[field], now, now);
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
decision: "use_merged",
|
|
309
|
+
mergedData,
|
|
310
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
311
|
+
resolvedBy: "strategy",
|
|
312
|
+
resolvedAt: Date.now(),
|
|
313
|
+
reason: `Merged ${conflict.conflictingFields.length} conflicting fields`
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this._options.hooks.onMergeFailed?.(conflict, error);
|
|
317
|
+
return this._resolveLastWriteWins(conflict);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Resolve using manual/UI strategy.
|
|
322
|
+
*/
|
|
323
|
+
async _resolveManual(conflict) {
|
|
324
|
+
const hook = this._options.hooks.onConflictNeedsResolution;
|
|
325
|
+
if (!hook) {
|
|
326
|
+
console.warn("[ConflictResolver] No UI hook for manual resolution, falling back to LWW");
|
|
327
|
+
return this._resolveLastWriteWins(conflict);
|
|
328
|
+
}
|
|
329
|
+
const timeout = new Promise((_, reject) => {
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
reject(new Error("Manual resolution timed out"));
|
|
332
|
+
}, this._options.manualResolutionTimeout);
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
const resolution = await Promise.race([
|
|
336
|
+
hook(conflict),
|
|
337
|
+
timeout
|
|
338
|
+
]);
|
|
339
|
+
return {
|
|
340
|
+
...resolution,
|
|
341
|
+
mergedClock: mergeVectorClocks(conflict.localClock, conflict.remoteClock),
|
|
342
|
+
resolvedBy: "user",
|
|
343
|
+
resolvedAt: Date.now()
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.warn("[ConflictResolver] Manual resolution failed:", error);
|
|
347
|
+
return {
|
|
348
|
+
decision: "defer",
|
|
349
|
+
mergedClock: conflict.localClock,
|
|
350
|
+
// Keep local for now
|
|
351
|
+
resolvedBy: "auto",
|
|
352
|
+
resolvedAt: Date.now(),
|
|
353
|
+
reason: `Manual resolution failed: ${error.message}`
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const createConflictResolver = (options) => {
|
|
359
|
+
return new ConflictResolver(options);
|
|
360
|
+
};
|
|
361
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
362
|
+
0 && (module.exports = {
|
|
363
|
+
ConflictResolver,
|
|
364
|
+
FieldMergers,
|
|
365
|
+
createConflictResolver
|
|
366
|
+
});
|
|
@@ -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 conflicts_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(conflicts_exports);
|
|
17
|
+
__reExport(conflicts_exports, require("./conflictResolver.js"), module.exports);
|
|
18
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
19
|
+
0 && (module.exports = {
|
|
20
|
+
...require("./conflictResolver.js")
|
|
21
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Platform Sync Entry Point.
|
|
3
|
+
*
|
|
4
|
+
* Main API for synchronizing data across Electron, Capacitor,
|
|
5
|
+
* browser, and server platforms.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageAdapter } from '../../core/contracts/index.js';
|
|
10
|
+
import type { VectorClockData } from './protocol/vectorClock.js';
|
|
11
|
+
import type { ChangeRecord } from './protocol/messages.js';
|
|
12
|
+
import type { TransportOptions } from './transport/transport.js';
|
|
13
|
+
import type { SyncConflict, ConflictResolution, ConflictStrategy, ConflictUIHooks, FieldMerger } from './conflicts/conflictResolver.js';
|
|
14
|
+
import type { DeviceInfo, DeviceType } from './devices/deviceManager.js';
|
|
15
|
+
/**
|
|
16
|
+
* Table sync configuration.
|
|
17
|
+
*/
|
|
18
|
+
export interface TableSyncConfig {
|
|
19
|
+
/** Sync priority (higher = synced first) */
|
|
20
|
+
priority?: 'critical' | 'high' | 'normal' | 'low';
|
|
21
|
+
/** Conflict resolution strategy for this table */
|
|
22
|
+
conflictStrategy?: ConflictStrategy;
|
|
23
|
+
/** Maximum records to sync per batch */
|
|
24
|
+
maxRecords?: number;
|
|
25
|
+
/** Custom field mergers for merge strategy */
|
|
26
|
+
fieldMergers?: Record<string, FieldMerger>;
|
|
27
|
+
/** Whether to track deletes */
|
|
28
|
+
trackDeletes?: boolean;
|
|
29
|
+
/** Primary key column (default: 'id') */
|
|
30
|
+
primaryKey?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Cross-platform sync options.
|
|
34
|
+
*/
|
|
35
|
+
export interface CrossPlatformSyncOptions {
|
|
36
|
+
/** Local storage adapter */
|
|
37
|
+
localAdapter: StorageAdapter;
|
|
38
|
+
/** Sync server endpoint */
|
|
39
|
+
endpoint: string;
|
|
40
|
+
/** Authentication token */
|
|
41
|
+
authToken?: string;
|
|
42
|
+
/** Device information */
|
|
43
|
+
device?: {
|
|
44
|
+
name?: string;
|
|
45
|
+
type?: DeviceType;
|
|
46
|
+
};
|
|
47
|
+
/** Tables to sync with their configuration */
|
|
48
|
+
tables: Record<string, TableSyncConfig>;
|
|
49
|
+
/** Transport type preference */
|
|
50
|
+
transport?: 'websocket' | 'http' | 'auto';
|
|
51
|
+
/** Transport options */
|
|
52
|
+
transportOptions?: Partial<TransportOptions>;
|
|
53
|
+
/** Default conflict strategy */
|
|
54
|
+
defaultConflictStrategy?: ConflictStrategy;
|
|
55
|
+
/** Conflict resolution UI hooks */
|
|
56
|
+
hooks?: CrossPlatformSyncHooks;
|
|
57
|
+
/** Sync interval in milliseconds (0 = manual only) */
|
|
58
|
+
syncInterval?: number;
|
|
59
|
+
/** Enable real-time sync (push changes immediately) */
|
|
60
|
+
realTime?: boolean;
|
|
61
|
+
/** Batch size for sync operations */
|
|
62
|
+
batchSize?: number;
|
|
63
|
+
/** Enable compression for sync payloads */
|
|
64
|
+
compression?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Hooks for sync events and conflict resolution.
|
|
68
|
+
*/
|
|
69
|
+
export interface CrossPlatformSyncHooks extends ConflictUIHooks {
|
|
70
|
+
/** Called before sync starts */
|
|
71
|
+
onSyncStart?: () => void;
|
|
72
|
+
/** Called after sync completes */
|
|
73
|
+
onSyncComplete?: (result: SyncResult) => void;
|
|
74
|
+
/** Called when sync fails */
|
|
75
|
+
onSyncError?: (error: Error) => void;
|
|
76
|
+
/** Called when a change is pushed */
|
|
77
|
+
onChangePushed?: (change: ChangeRecord) => void;
|
|
78
|
+
/** Called when a change is pulled */
|
|
79
|
+
onChangePulled?: (change: ChangeRecord) => void;
|
|
80
|
+
/** Called when connection state changes */
|
|
81
|
+
onConnectionStateChange?: (connected: boolean) => void;
|
|
82
|
+
/** Called when a peer device is discovered */
|
|
83
|
+
onPeerDiscovered?: (peer: DeviceInfo) => void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Sync operation result.
|
|
87
|
+
*/
|
|
88
|
+
export interface SyncResult {
|
|
89
|
+
/** Whether sync was successful */
|
|
90
|
+
success: boolean;
|
|
91
|
+
/** Number of records pushed */
|
|
92
|
+
pushed: number;
|
|
93
|
+
/** Number of records pulled */
|
|
94
|
+
pulled: number;
|
|
95
|
+
/** Number of conflicts detected */
|
|
96
|
+
conflicts: number;
|
|
97
|
+
/** Number of conflicts resolved */
|
|
98
|
+
conflictsResolved: number;
|
|
99
|
+
/** Duration in milliseconds */
|
|
100
|
+
duration: number;
|
|
101
|
+
/** Errors encountered */
|
|
102
|
+
errors: Error[];
|
|
103
|
+
/** Resulting vector clock */
|
|
104
|
+
vectorClock: VectorClockData;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Sync status information.
|
|
108
|
+
*/
|
|
109
|
+
export interface SyncStatus {
|
|
110
|
+
/** Current sync state */
|
|
111
|
+
state: 'idle' | 'syncing' | 'error' | 'offline';
|
|
112
|
+
/** Whether connected to sync server */
|
|
113
|
+
connected: boolean;
|
|
114
|
+
/** Last successful sync time */
|
|
115
|
+
lastSyncAt?: number;
|
|
116
|
+
/** Last sync result */
|
|
117
|
+
lastResult?: SyncResult;
|
|
118
|
+
/** Pending changes count */
|
|
119
|
+
pendingChanges: number;
|
|
120
|
+
/** Pending conflicts count */
|
|
121
|
+
pendingConflicts: number;
|
|
122
|
+
/** Current device info */
|
|
123
|
+
device?: DeviceInfo;
|
|
124
|
+
/** Known peer devices */
|
|
125
|
+
peers: DeviceInfo[];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Cross-platform sync manager.
|
|
129
|
+
*
|
|
130
|
+
* Orchestrates synchronization between local database and remote
|
|
131
|
+
* sync server across all platforms.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const sync = await createCrossPlatformSync({
|
|
136
|
+
* localAdapter: db,
|
|
137
|
+
* endpoint: 'wss://sync.example.com',
|
|
138
|
+
* authToken: 'token',
|
|
139
|
+
* device: { name: 'MacBook Pro', type: 'electron' },
|
|
140
|
+
* tables: {
|
|
141
|
+
* notes: { priority: 'high', conflictStrategy: 'merge' },
|
|
142
|
+
* settings: { priority: 'critical', conflictStrategy: 'local-wins' },
|
|
143
|
+
* },
|
|
144
|
+
* hooks: {
|
|
145
|
+
* onConflictNeedsResolution: (conflict) => showConflictDialog(conflict),
|
|
146
|
+
* onSyncComplete: (result) => console.log('Synced:', result),
|
|
147
|
+
* },
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* await sync.sync();
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare class CrossPlatformSync {
|
|
154
|
+
private _adapter;
|
|
155
|
+
private _transport;
|
|
156
|
+
private _conflictResolver;
|
|
157
|
+
private _deviceManager;
|
|
158
|
+
private _syncLogManager;
|
|
159
|
+
private _options;
|
|
160
|
+
private _syncTimer;
|
|
161
|
+
private _status;
|
|
162
|
+
private _isSyncing;
|
|
163
|
+
private _disposed;
|
|
164
|
+
private _deviceId;
|
|
165
|
+
constructor(options: CrossPlatformSyncOptions);
|
|
166
|
+
/**
|
|
167
|
+
* Initialize the sync manager.
|
|
168
|
+
*/
|
|
169
|
+
initialize(): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Perform a sync operation.
|
|
172
|
+
*/
|
|
173
|
+
sync(): Promise<SyncResult>;
|
|
174
|
+
/**
|
|
175
|
+
* Push a single change immediately (for real-time sync).
|
|
176
|
+
*/
|
|
177
|
+
pushChange(tableName: string, recordId: string, operation: 'INSERT' | 'UPDATE' | 'DELETE', data: Record<string, unknown>): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Get current sync status.
|
|
180
|
+
*/
|
|
181
|
+
getStatus(): SyncStatus;
|
|
182
|
+
/**
|
|
183
|
+
* Get pending conflicts.
|
|
184
|
+
*/
|
|
185
|
+
getPendingConflicts(): SyncConflict[];
|
|
186
|
+
/**
|
|
187
|
+
* Resolve a conflict manually.
|
|
188
|
+
*/
|
|
189
|
+
resolveConflict(conflictId: string, decision: 'use_local' | 'use_remote' | 'use_merged' | 'keep_both' | 'defer', mergedData?: Record<string, unknown>): Promise<ConflictResolution>;
|
|
190
|
+
/**
|
|
191
|
+
* Connect to sync server.
|
|
192
|
+
*/
|
|
193
|
+
connect(): Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Disconnect from sync server.
|
|
196
|
+
*/
|
|
197
|
+
disconnect(): Promise<void>;
|
|
198
|
+
/**
|
|
199
|
+
* Dispose of the sync manager.
|
|
200
|
+
*/
|
|
201
|
+
dispose(): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Initialize the transport layer.
|
|
204
|
+
*/
|
|
205
|
+
private _initializeTransport;
|
|
206
|
+
/**
|
|
207
|
+
* Set up transport event listeners.
|
|
208
|
+
*/
|
|
209
|
+
private _setupTransportListeners;
|
|
210
|
+
/**
|
|
211
|
+
* Set up device manager listeners.
|
|
212
|
+
*/
|
|
213
|
+
private _setupDeviceManagerListeners;
|
|
214
|
+
/**
|
|
215
|
+
* Send handshake to server.
|
|
216
|
+
*/
|
|
217
|
+
private _sendHandshake;
|
|
218
|
+
/**
|
|
219
|
+
* Handle incoming sync message.
|
|
220
|
+
*/
|
|
221
|
+
private _handleIncomingMessage;
|
|
222
|
+
/**
|
|
223
|
+
* Push local changes to server.
|
|
224
|
+
*/
|
|
225
|
+
private _pushChanges;
|
|
226
|
+
/**
|
|
227
|
+
* Pull remote changes from server.
|
|
228
|
+
*/
|
|
229
|
+
private _pullChanges;
|
|
230
|
+
/**
|
|
231
|
+
* Apply remote changes to local database.
|
|
232
|
+
*/
|
|
233
|
+
private _applyRemoteChanges;
|
|
234
|
+
/**
|
|
235
|
+
* Apply a single change to local database.
|
|
236
|
+
*/
|
|
237
|
+
private _applyChange;
|
|
238
|
+
/**
|
|
239
|
+
* Get a local record by table and ID.
|
|
240
|
+
*/
|
|
241
|
+
private _getLocalRecord;
|
|
242
|
+
/**
|
|
243
|
+
* Resolve pending conflicts.
|
|
244
|
+
*/
|
|
245
|
+
private _resolveConflicts;
|
|
246
|
+
/**
|
|
247
|
+
* Get count of pending changes.
|
|
248
|
+
*/
|
|
249
|
+
private _getPendingChangesCount;
|
|
250
|
+
/**
|
|
251
|
+
* Start sync timer.
|
|
252
|
+
*/
|
|
253
|
+
private _startSyncTimer;
|
|
254
|
+
/**
|
|
255
|
+
* Build table strategies map.
|
|
256
|
+
*/
|
|
257
|
+
private _buildTableStrategies;
|
|
258
|
+
/**
|
|
259
|
+
* Build field mergers map.
|
|
260
|
+
*/
|
|
261
|
+
private _buildFieldMergers;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a cross-platform sync instance.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* const sync = await createCrossPlatformSync({
|
|
269
|
+
* localAdapter: db,
|
|
270
|
+
* endpoint: 'wss://sync.example.com',
|
|
271
|
+
* tables: {
|
|
272
|
+
* notes: { priority: 'high', conflictStrategy: 'merge' },
|
|
273
|
+
* settings: { priority: 'critical', conflictStrategy: 'local-wins' },
|
|
274
|
+
* },
|
|
275
|
+
* });
|
|
276
|
+
*
|
|
277
|
+
* await sync.sync();
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export declare const createCrossPlatformSync: (options: CrossPlatformSyncOptions) => Promise<CrossPlatformSync>;
|
|
281
|
+
//# sourceMappingURL=crossPlatformSync.d.ts.map
|