@prabhask5/stellar-engine 1.0.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 +295 -0
- package/dist/actions/remoteChange.d.ts +79 -0
- package/dist/actions/remoteChange.d.ts.map +1 -0
- package/dist/actions/remoteChange.js +300 -0
- package/dist/actions/remoteChange.js.map +1 -0
- package/dist/auth/admin.d.ts +12 -0
- package/dist/auth/admin.d.ts.map +1 -0
- package/dist/auth/admin.js +23 -0
- package/dist/auth/admin.js.map +1 -0
- package/dist/auth/offlineCredentials.d.ts +41 -0
- package/dist/auth/offlineCredentials.d.ts.map +1 -0
- package/dist/auth/offlineCredentials.js +121 -0
- package/dist/auth/offlineCredentials.js.map +1 -0
- package/dist/auth/offlineLogin.d.ts +34 -0
- package/dist/auth/offlineLogin.d.ts.map +1 -0
- package/dist/auth/offlineLogin.js +75 -0
- package/dist/auth/offlineLogin.js.map +1 -0
- package/dist/auth/offlineSession.d.ts +22 -0
- package/dist/auth/offlineSession.d.ts.map +1 -0
- package/dist/auth/offlineSession.js +54 -0
- package/dist/auth/offlineSession.js.map +1 -0
- package/dist/auth/resolveAuthState.d.ts +24 -0
- package/dist/auth/resolveAuthState.d.ts.map +1 -0
- package/dist/auth/resolveAuthState.js +69 -0
- package/dist/auth/resolveAuthState.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +55 -0
- package/dist/config.js.map +1 -0
- package/dist/conflicts.d.ts +70 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +321 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/data.d.ts +77 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +360 -0
- package/dist/data.js.map +1 -0
- package/dist/database.d.ts +31 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +51 -0
- package/dist/database.js.map +1 -0
- package/dist/debug.d.ts +11 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +48 -0
- package/dist/debug.js.map +1 -0
- package/dist/deviceId.d.ts +16 -0
- package/dist/deviceId.d.ts.map +1 -0
- package/dist/deviceId.js +48 -0
- package/dist/deviceId.js.map +1 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1903 -0
- package/dist/engine.js.map +1 -0
- package/dist/entries/actions.d.ts +2 -0
- package/dist/entries/actions.d.ts.map +1 -0
- package/dist/entries/actions.js +3 -0
- package/dist/entries/actions.js.map +1 -0
- package/dist/entries/auth.d.ts +7 -0
- package/dist/entries/auth.d.ts.map +1 -0
- package/dist/entries/auth.js +6 -0
- package/dist/entries/auth.js.map +1 -0
- package/dist/entries/config.d.ts +3 -0
- package/dist/entries/config.d.ts.map +1 -0
- package/dist/entries/config.js +3 -0
- package/dist/entries/config.js.map +1 -0
- package/dist/entries/stores.d.ts +9 -0
- package/dist/entries/stores.d.ts.map +1 -0
- package/dist/entries/stores.js +9 -0
- package/dist/entries/stores.js.map +1 -0
- package/dist/entries/types.d.ts +11 -0
- package/dist/entries/types.d.ts.map +1 -0
- package/dist/entries/types.js +2 -0
- package/dist/entries/types.js.map +1 -0
- package/dist/entries/utils.d.ts +3 -0
- package/dist/entries/utils.d.ts.map +1 -0
- package/dist/entries/utils.js +4 -0
- package/dist/entries/utils.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/operations.d.ts +73 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +227 -0
- package/dist/operations.js.map +1 -0
- package/dist/queue.d.ts +32 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +377 -0
- package/dist/queue.js.map +1 -0
- package/dist/realtime.d.ts +57 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +491 -0
- package/dist/realtime.js.map +1 -0
- package/dist/reconnectHandler.d.ts +16 -0
- package/dist/reconnectHandler.d.ts.map +1 -0
- package/dist/reconnectHandler.js +21 -0
- package/dist/reconnectHandler.js.map +1 -0
- package/dist/runtime/runtimeConfig.d.ts +27 -0
- package/dist/runtime/runtimeConfig.d.ts.map +1 -0
- package/dist/runtime/runtimeConfig.js +133 -0
- package/dist/runtime/runtimeConfig.js.map +1 -0
- package/dist/stores/authState.d.ts +57 -0
- package/dist/stores/authState.d.ts.map +1 -0
- package/dist/stores/authState.js +154 -0
- package/dist/stores/authState.js.map +1 -0
- package/dist/stores/network.d.ts +9 -0
- package/dist/stores/network.d.ts.map +1 -0
- package/dist/stores/network.js +97 -0
- package/dist/stores/network.js.map +1 -0
- package/dist/stores/remoteChanges.d.ts +142 -0
- package/dist/stores/remoteChanges.d.ts.map +1 -0
- package/dist/stores/remoteChanges.js +353 -0
- package/dist/stores/remoteChanges.js.map +1 -0
- package/dist/stores/sync.d.ts +35 -0
- package/dist/stores/sync.d.ts.map +1 -0
- package/dist/stores/sync.js +115 -0
- package/dist/stores/sync.js.map +1 -0
- package/dist/supabase/auth.d.ts +60 -0
- package/dist/supabase/auth.d.ts.map +1 -0
- package/dist/supabase/auth.js +298 -0
- package/dist/supabase/auth.js.map +1 -0
- package/dist/supabase/client.d.ts +15 -0
- package/dist/supabase/client.d.ts.map +1 -0
- package/dist/supabase/client.js +149 -0
- package/dist/supabase/client.js.map +1 -0
- package/dist/supabase/validate.d.ts +11 -0
- package/dist/supabase/validate.d.ts.map +1 -0
- package/dist/supabase/validate.js +38 -0
- package/dist/supabase/validate.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +24 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +56 -0
- package/dist/utils.js.map +1 -0
- package/package.json +84 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { _setDebugPrefix } from './debug';
|
|
2
|
+
import { _setDeviceIdPrefix } from './deviceId';
|
|
3
|
+
import { _setClientPrefix } from './supabase/client';
|
|
4
|
+
import { _setConfigPrefix } from './runtime/runtimeConfig';
|
|
5
|
+
import { createDatabase, _setManagedDb } from './database';
|
|
6
|
+
let engineConfig = null;
|
|
7
|
+
export function initEngine(config) {
|
|
8
|
+
engineConfig = config;
|
|
9
|
+
// Propagate prefix to all internal modules
|
|
10
|
+
if (config.prefix) {
|
|
11
|
+
_setDebugPrefix(config.prefix);
|
|
12
|
+
_setDeviceIdPrefix(config.prefix);
|
|
13
|
+
_setClientPrefix(config.prefix);
|
|
14
|
+
_setConfigPrefix(config.prefix);
|
|
15
|
+
}
|
|
16
|
+
// Handle database creation
|
|
17
|
+
if (config.database) {
|
|
18
|
+
const db = createDatabase(config.database);
|
|
19
|
+
// Store on config for backward compat (engine.ts reads config.db)
|
|
20
|
+
config.db = db;
|
|
21
|
+
}
|
|
22
|
+
else if (config.db) {
|
|
23
|
+
// Backward compat: use provided Dexie instance
|
|
24
|
+
_setManagedDb(config.db);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function getEngineConfig() {
|
|
28
|
+
if (!engineConfig) {
|
|
29
|
+
throw new Error('Sync engine not initialized. Call initEngine() first.');
|
|
30
|
+
}
|
|
31
|
+
return engineConfig;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the Supabase-to-Dexie table mapping derived from config.
|
|
35
|
+
*/
|
|
36
|
+
export function getTableMap() {
|
|
37
|
+
const config = getEngineConfig();
|
|
38
|
+
const map = {};
|
|
39
|
+
for (const table of config.tables) {
|
|
40
|
+
map[table.supabaseName] = table.dexieTable;
|
|
41
|
+
}
|
|
42
|
+
return map;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get columns for a specific Supabase table from config.
|
|
46
|
+
*/
|
|
47
|
+
export function getTableColumns(supabaseName) {
|
|
48
|
+
const config = getEngineConfig();
|
|
49
|
+
const table = config.tables.find(t => t.supabaseName === supabaseName);
|
|
50
|
+
if (!table) {
|
|
51
|
+
throw new Error(`Table ${supabaseName} not found in engine config`);
|
|
52
|
+
}
|
|
53
|
+
return table.columns;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAuB,MAAM,YAAY,CAAC;AA8ChF,IAAI,YAAY,GAA4B,IAAI,CAAC;AAEjD,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,YAAY,GAAG,MAAM,CAAC;IAEtB,2CAA2C;IAC3C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/B,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,2BAA2B;IAC3B,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3C,kEAAkE;QACjE,MAAwB,CAAC,EAAE,GAAG,EAAE,CAAC;IACpC,CAAC;SAAM,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACrB,+CAA+C;QAC/C,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAGD;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,SAAS,YAAY,6BAA6B,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict Resolution Engine
|
|
3
|
+
*
|
|
4
|
+
* Implements three-tier conflict resolution for multi-device sync:
|
|
5
|
+
*
|
|
6
|
+
* Tier 1: Non-overlapping (different entities) → AUTO-MERGE
|
|
7
|
+
* Tier 2: Different fields → AUTO-MERGE FIELDS
|
|
8
|
+
* Tier 3: Same field → Apply resolution strategy
|
|
9
|
+
*
|
|
10
|
+
* Resolution strategies for Tier 3:
|
|
11
|
+
* - Numeric fields with pending increments: Merge (sum deltas)
|
|
12
|
+
* - Delete operations: Delete wins over edits
|
|
13
|
+
* - All other cases: Last-write-wins (with deviceId tiebreaker)
|
|
14
|
+
*/
|
|
15
|
+
import type { SyncOperationItem } from './types';
|
|
16
|
+
import type { ConflictHistoryEntry } from './types';
|
|
17
|
+
export type { ConflictHistoryEntry };
|
|
18
|
+
/**
|
|
19
|
+
* Conflict resolution result for a single field
|
|
20
|
+
*/
|
|
21
|
+
export interface FieldConflictResolution {
|
|
22
|
+
field: string;
|
|
23
|
+
localValue: unknown;
|
|
24
|
+
remoteValue: unknown;
|
|
25
|
+
resolvedValue: unknown;
|
|
26
|
+
winner: 'local' | 'remote' | 'merged';
|
|
27
|
+
strategy: 'last_write' | 'numeric_merge' | 'delete_wins' | 'local_pending';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Full conflict resolution result for an entity
|
|
31
|
+
*/
|
|
32
|
+
export interface ConflictResolution {
|
|
33
|
+
entityId: string;
|
|
34
|
+
entityType: string;
|
|
35
|
+
localUpdatedAt: string;
|
|
36
|
+
remoteUpdatedAt: string;
|
|
37
|
+
fieldResolutions: FieldConflictResolution[];
|
|
38
|
+
mergedEntity: Record<string, unknown>;
|
|
39
|
+
hasConflicts: boolean;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve conflicts between local and remote entity states.
|
|
44
|
+
*
|
|
45
|
+
* @param entityType The type of entity (table name)
|
|
46
|
+
* @param entityId The entity's ID
|
|
47
|
+
* @param local The local entity state (may be null if entity doesn't exist locally)
|
|
48
|
+
* @param remote The remote entity state
|
|
49
|
+
* @param pendingOps Pending operations for this entity from the sync queue
|
|
50
|
+
* @returns The merged entity with conflict resolution applied
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveConflicts(entityType: string, entityId: string, local: Record<string, unknown> | null, remote: Record<string, unknown>, pendingOps: SyncOperationItem[]): Promise<ConflictResolution>;
|
|
53
|
+
/**
|
|
54
|
+
* Store conflict resolution history for review/undo.
|
|
55
|
+
*
|
|
56
|
+
* @param resolution The conflict resolution result
|
|
57
|
+
*/
|
|
58
|
+
export declare function storeConflictHistory(resolution: ConflictResolution): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Get pending operations for a specific entity from the sync queue.
|
|
61
|
+
*
|
|
62
|
+
* @param entityId The entity ID to check
|
|
63
|
+
* @returns Array of pending operations for this entity
|
|
64
|
+
*/
|
|
65
|
+
export declare function getPendingOpsForEntity(entityId: string): Promise<SyncOperationItem[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Clean up old conflict history entries (older than 30 days).
|
|
68
|
+
*/
|
|
69
|
+
export declare function cleanupConflictHistory(): Promise<number>;
|
|
70
|
+
//# sourceMappingURL=conflicts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGpD,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACtC,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,aAAa,GAAG,eAAe,CAAC;CAC5E;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,uBAAuB,EAAE,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,iBAAiB,EAAE,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CA4K7B;AA8FD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBxF;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAG3F;AAED;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAiB9D"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict Resolution Engine
|
|
3
|
+
*
|
|
4
|
+
* Implements three-tier conflict resolution for multi-device sync:
|
|
5
|
+
*
|
|
6
|
+
* Tier 1: Non-overlapping (different entities) → AUTO-MERGE
|
|
7
|
+
* Tier 2: Different fields → AUTO-MERGE FIELDS
|
|
8
|
+
* Tier 3: Same field → Apply resolution strategy
|
|
9
|
+
*
|
|
10
|
+
* Resolution strategies for Tier 3:
|
|
11
|
+
* - Numeric fields with pending increments: Merge (sum deltas)
|
|
12
|
+
* - Delete operations: Delete wins over edits
|
|
13
|
+
* - All other cases: Last-write-wins (with deviceId tiebreaker)
|
|
14
|
+
*/
|
|
15
|
+
import { debugLog, debugError } from './debug';
|
|
16
|
+
import { getEngineConfig } from './config';
|
|
17
|
+
import { getDeviceId } from './deviceId';
|
|
18
|
+
/**
|
|
19
|
+
* Get excluded fields for a given entity type.
|
|
20
|
+
* Combines the default excluded fields with any per-table excludeFromConflict config.
|
|
21
|
+
*/
|
|
22
|
+
function getExcludedFields(entityType) {
|
|
23
|
+
const defaultExcluded = new Set(['id', 'user_id', 'created_at', '_version']);
|
|
24
|
+
const tableConfig = getEngineConfig().tables.find(t => t.supabaseName === entityType);
|
|
25
|
+
return new Set([...defaultExcluded, ...(tableConfig?.excludeFromConflict || [])]);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get numeric merge fields for a given entity type from per-table config.
|
|
29
|
+
*/
|
|
30
|
+
function getNumericMergeFields(entityType) {
|
|
31
|
+
const tableConfig = getEngineConfig().tables.find(t => t.supabaseName === entityType);
|
|
32
|
+
return new Set(tableConfig?.numericMergeFields || []);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve conflicts between local and remote entity states.
|
|
36
|
+
*
|
|
37
|
+
* @param entityType The type of entity (table name)
|
|
38
|
+
* @param entityId The entity's ID
|
|
39
|
+
* @param local The local entity state (may be null if entity doesn't exist locally)
|
|
40
|
+
* @param remote The remote entity state
|
|
41
|
+
* @param pendingOps Pending operations for this entity from the sync queue
|
|
42
|
+
* @returns The merged entity with conflict resolution applied
|
|
43
|
+
*/
|
|
44
|
+
export async function resolveConflicts(entityType, entityId, local, remote, pendingOps) {
|
|
45
|
+
const timestamp = new Date().toISOString();
|
|
46
|
+
const fieldResolutions = [];
|
|
47
|
+
const deviceId = getDeviceId();
|
|
48
|
+
// If no local entity, remote wins entirely (no conflict)
|
|
49
|
+
if (!local) {
|
|
50
|
+
return {
|
|
51
|
+
entityId,
|
|
52
|
+
entityType,
|
|
53
|
+
localUpdatedAt: '',
|
|
54
|
+
remoteUpdatedAt: remote.updated_at,
|
|
55
|
+
fieldResolutions: [],
|
|
56
|
+
mergedEntity: { ...remote },
|
|
57
|
+
hasConflicts: false,
|
|
58
|
+
timestamp
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const localUpdatedAt = local.updated_at;
|
|
62
|
+
const remoteUpdatedAt = remote.updated_at;
|
|
63
|
+
// Start with remote as base (since it's newer if we're processing it)
|
|
64
|
+
const mergedEntity = { ...remote };
|
|
65
|
+
// Get all unique fields from both entities
|
|
66
|
+
const allFields = new Set([...Object.keys(local), ...Object.keys(remote)]);
|
|
67
|
+
// Check for pending operations on specific fields
|
|
68
|
+
const pendingFieldOps = new Map();
|
|
69
|
+
for (const op of pendingOps) {
|
|
70
|
+
if (op.field) {
|
|
71
|
+
const existing = pendingFieldOps.get(op.field) || [];
|
|
72
|
+
existing.push(op);
|
|
73
|
+
pendingFieldOps.set(op.field, existing);
|
|
74
|
+
}
|
|
75
|
+
else if (op.operationType === 'set' && typeof op.value === 'object' && op.value !== null) {
|
|
76
|
+
// Multi-field set operation - extract fields
|
|
77
|
+
for (const field of Object.keys(op.value)) {
|
|
78
|
+
const existing = pendingFieldOps.get(field) || [];
|
|
79
|
+
existing.push(op);
|
|
80
|
+
pendingFieldOps.set(field, existing);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Check if there's a pending delete
|
|
85
|
+
const hasPendingDelete = pendingOps.some((op) => op.operationType === 'delete');
|
|
86
|
+
// If there's a pending delete locally, local delete wins
|
|
87
|
+
if (hasPendingDelete && !remote.deleted) {
|
|
88
|
+
mergedEntity.deleted = true;
|
|
89
|
+
fieldResolutions.push({
|
|
90
|
+
field: 'deleted',
|
|
91
|
+
localValue: true,
|
|
92
|
+
remoteValue: remote.deleted,
|
|
93
|
+
resolvedValue: true,
|
|
94
|
+
winner: 'local',
|
|
95
|
+
strategy: 'local_pending'
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// If remote is deleted but local has pending edits, delete still wins
|
|
99
|
+
// (This prevents resurrection of deleted entities)
|
|
100
|
+
if (remote.deleted && !hasPendingDelete) {
|
|
101
|
+
// Remote delete wins - entity should stay deleted
|
|
102
|
+
return {
|
|
103
|
+
entityId,
|
|
104
|
+
entityType,
|
|
105
|
+
localUpdatedAt,
|
|
106
|
+
remoteUpdatedAt,
|
|
107
|
+
fieldResolutions: [
|
|
108
|
+
{
|
|
109
|
+
field: 'deleted',
|
|
110
|
+
localValue: local.deleted,
|
|
111
|
+
remoteValue: true,
|
|
112
|
+
resolvedValue: true,
|
|
113
|
+
winner: 'remote',
|
|
114
|
+
strategy: 'delete_wins'
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
mergedEntity: { ...remote },
|
|
118
|
+
hasConflicts: true,
|
|
119
|
+
timestamp
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Get config-driven field sets for this entity type
|
|
123
|
+
const excludedFields = getExcludedFields(entityType);
|
|
124
|
+
const numericMergeFields = getNumericMergeFields(entityType);
|
|
125
|
+
// Process each field
|
|
126
|
+
for (const field of allFields) {
|
|
127
|
+
if (excludedFields.has(field))
|
|
128
|
+
continue;
|
|
129
|
+
if (field === 'deleted' && hasPendingDelete)
|
|
130
|
+
continue; // Already handled
|
|
131
|
+
const localValue = local[field];
|
|
132
|
+
const remoteValue = remote[field];
|
|
133
|
+
// If values are equal, no conflict
|
|
134
|
+
if (valuesEqual(localValue, remoteValue)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Check for pending operations on this field
|
|
138
|
+
const fieldOps = pendingFieldOps.get(field) || [];
|
|
139
|
+
const hasPendingOps = fieldOps.length > 0;
|
|
140
|
+
// Determine resolution strategy
|
|
141
|
+
let resolution;
|
|
142
|
+
if (hasPendingOps) {
|
|
143
|
+
// Tier 3a: Field has pending local operations - local wins
|
|
144
|
+
resolution = {
|
|
145
|
+
field,
|
|
146
|
+
localValue,
|
|
147
|
+
remoteValue,
|
|
148
|
+
resolvedValue: localValue,
|
|
149
|
+
winner: 'local',
|
|
150
|
+
strategy: 'local_pending'
|
|
151
|
+
};
|
|
152
|
+
mergedEntity[field] = localValue;
|
|
153
|
+
}
|
|
154
|
+
else if (numericMergeFields.has(field) && canNumericMerge(local, remote, field)) {
|
|
155
|
+
// Tier 3b: Numeric field that could theoretically be merged
|
|
156
|
+
// For now, use last-write-wins since we only have final values, not operation deltas
|
|
157
|
+
// True numeric merge (e.g., +50 + +30 = +80) would require an operation inbox system
|
|
158
|
+
resolution = resolveByTimestamp(field, local, remote, localUpdatedAt, remoteUpdatedAt, deviceId);
|
|
159
|
+
mergedEntity[field] = resolution.resolvedValue;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Tier 3c: Last-write-wins with timestamp comparison
|
|
163
|
+
resolution = resolveByTimestamp(field, local, remote, localUpdatedAt, remoteUpdatedAt, deviceId);
|
|
164
|
+
mergedEntity[field] = resolution.resolvedValue;
|
|
165
|
+
}
|
|
166
|
+
fieldResolutions.push(resolution);
|
|
167
|
+
}
|
|
168
|
+
// If there were field-level resolutions, we need to update the version
|
|
169
|
+
if (fieldResolutions.length > 0) {
|
|
170
|
+
const localVersion = typeof local._version === 'number' ? local._version : 1;
|
|
171
|
+
const remoteVersion = typeof remote._version === 'number' ? remote._version : 1;
|
|
172
|
+
mergedEntity._version = Math.max(localVersion, remoteVersion) + 1;
|
|
173
|
+
}
|
|
174
|
+
// Update updated_at to the later of the two
|
|
175
|
+
if (localUpdatedAt > remoteUpdatedAt) {
|
|
176
|
+
mergedEntity.updated_at = localUpdatedAt;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
entityId,
|
|
180
|
+
entityType,
|
|
181
|
+
localUpdatedAt,
|
|
182
|
+
remoteUpdatedAt,
|
|
183
|
+
fieldResolutions,
|
|
184
|
+
mergedEntity,
|
|
185
|
+
hasConflicts: fieldResolutions.length > 0,
|
|
186
|
+
timestamp
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resolve a field conflict using last-write-wins strategy.
|
|
191
|
+
* Uses deviceId as deterministic tiebreaker when timestamps are equal.
|
|
192
|
+
*/
|
|
193
|
+
function resolveByTimestamp(field, local, remote, localUpdatedAt, remoteUpdatedAt, localDeviceId) {
|
|
194
|
+
const localValue = local[field];
|
|
195
|
+
const remoteValue = remote[field];
|
|
196
|
+
// Compare timestamps
|
|
197
|
+
const localTime = new Date(localUpdatedAt).getTime();
|
|
198
|
+
const remoteTime = new Date(remoteUpdatedAt).getTime();
|
|
199
|
+
let winner;
|
|
200
|
+
let resolvedValue;
|
|
201
|
+
if (localTime > remoteTime) {
|
|
202
|
+
winner = 'local';
|
|
203
|
+
resolvedValue = localValue;
|
|
204
|
+
}
|
|
205
|
+
else if (remoteTime > localTime) {
|
|
206
|
+
winner = 'remote';
|
|
207
|
+
resolvedValue = remoteValue;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// Timestamps are equal - use deviceId as deterministic tiebreaker
|
|
211
|
+
// Lower deviceId wins (arbitrary but consistent across all devices)
|
|
212
|
+
const remoteDeviceId = remote.device_id || '';
|
|
213
|
+
if (remoteDeviceId && localDeviceId < remoteDeviceId) {
|
|
214
|
+
winner = 'local';
|
|
215
|
+
resolvedValue = localValue;
|
|
216
|
+
}
|
|
217
|
+
else if (remoteDeviceId && localDeviceId > remoteDeviceId) {
|
|
218
|
+
winner = 'remote';
|
|
219
|
+
resolvedValue = remoteValue;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
// Same device or no remote device_id - local wins (it's the more recent action)
|
|
223
|
+
winner = 'local';
|
|
224
|
+
resolvedValue = localValue;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
field,
|
|
229
|
+
localValue,
|
|
230
|
+
remoteValue,
|
|
231
|
+
resolvedValue,
|
|
232
|
+
winner,
|
|
233
|
+
strategy: 'last_write'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if two values are equal (deep comparison for objects/arrays).
|
|
238
|
+
*/
|
|
239
|
+
function valuesEqual(a, b) {
|
|
240
|
+
if (a === b)
|
|
241
|
+
return true;
|
|
242
|
+
if (a === null || b === null)
|
|
243
|
+
return a === b;
|
|
244
|
+
if (typeof a !== typeof b)
|
|
245
|
+
return false;
|
|
246
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
247
|
+
if (a.length !== b.length)
|
|
248
|
+
return false;
|
|
249
|
+
return a.every((val, i) => valuesEqual(val, b[i]));
|
|
250
|
+
}
|
|
251
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
252
|
+
const aKeys = Object.keys(a);
|
|
253
|
+
const bKeys = Object.keys(b);
|
|
254
|
+
if (aKeys.length !== bKeys.length)
|
|
255
|
+
return false;
|
|
256
|
+
return aKeys.every((key) => valuesEqual(a[key], b[key]));
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Check if a numeric field can be merged (both sides have numeric values).
|
|
262
|
+
*/
|
|
263
|
+
function canNumericMerge(local, remote, field) {
|
|
264
|
+
return typeof local[field] === 'number' && typeof remote[field] === 'number';
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Store conflict resolution history for review/undo.
|
|
268
|
+
*
|
|
269
|
+
* @param resolution The conflict resolution result
|
|
270
|
+
*/
|
|
271
|
+
export async function storeConflictHistory(resolution) {
|
|
272
|
+
if (!resolution.hasConflicts)
|
|
273
|
+
return;
|
|
274
|
+
try {
|
|
275
|
+
const entries = resolution.fieldResolutions.map((fr) => ({
|
|
276
|
+
entityId: resolution.entityId,
|
|
277
|
+
entityType: resolution.entityType,
|
|
278
|
+
field: fr.field,
|
|
279
|
+
localValue: fr.localValue,
|
|
280
|
+
remoteValue: fr.remoteValue,
|
|
281
|
+
resolvedValue: fr.resolvedValue,
|
|
282
|
+
winner: fr.winner,
|
|
283
|
+
strategy: fr.strategy,
|
|
284
|
+
timestamp: resolution.timestamp
|
|
285
|
+
}));
|
|
286
|
+
await getEngineConfig().db.table('conflictHistory').bulkAdd(entries);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
debugError('[Conflict] Failed to store conflict history:', error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get pending operations for a specific entity from the sync queue.
|
|
294
|
+
*
|
|
295
|
+
* @param entityId The entity ID to check
|
|
296
|
+
* @returns Array of pending operations for this entity
|
|
297
|
+
*/
|
|
298
|
+
export async function getPendingOpsForEntity(entityId) {
|
|
299
|
+
const allPending = await getEngineConfig().db.table('syncQueue').where('entityId').equals(entityId).toArray();
|
|
300
|
+
return allPending;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Clean up old conflict history entries (older than 30 days).
|
|
304
|
+
*/
|
|
305
|
+
export async function cleanupConflictHistory() {
|
|
306
|
+
const cutoffDate = new Date();
|
|
307
|
+
cutoffDate.setDate(cutoffDate.getDate() - 30);
|
|
308
|
+
const cutoffStr = cutoffDate.toISOString();
|
|
309
|
+
try {
|
|
310
|
+
const count = await getEngineConfig().db.table('conflictHistory').filter((entry) => entry.timestamp < cutoffStr).delete();
|
|
311
|
+
if (count > 0) {
|
|
312
|
+
debugLog(`[Conflict] Cleaned up ${count} old conflict history entries`);
|
|
313
|
+
}
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
debugError('[Conflict] Failed to cleanup conflict history:', error);
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=conflicts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflicts.js","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"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"}
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic CRUD and Query Operations
|
|
3
|
+
*
|
|
4
|
+
* Replaces repository infrastructure boilerplate.
|
|
5
|
+
* Uses Supabase table names as the API surface, internally resolves to Dexie table names.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create a new entity. Auto: transaction + queue + markModified + schedulePush.
|
|
9
|
+
* Caller provides all fields including id, timestamps, etc.
|
|
10
|
+
*/
|
|
11
|
+
export declare function engineCreate(table: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
12
|
+
/**
|
|
13
|
+
* Update an entity's fields. Auto-sets updated_at, queues sync, marks modified.
|
|
14
|
+
*/
|
|
15
|
+
export declare function engineUpdate(table: string, id: string, fields: Record<string, unknown>): Promise<Record<string, unknown> | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Soft-delete an entity. Sets deleted=true, queues delete op.
|
|
18
|
+
*/
|
|
19
|
+
export declare function engineDelete(table: string, id: string): Promise<void>;
|
|
20
|
+
export type BatchOperation = {
|
|
21
|
+
type: 'create';
|
|
22
|
+
table: string;
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'update';
|
|
26
|
+
table: string;
|
|
27
|
+
id: string;
|
|
28
|
+
fields: Record<string, unknown>;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'delete';
|
|
31
|
+
table: string;
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Execute multiple write operations in a single atomic transaction.
|
|
36
|
+
* All ops succeed or all roll back.
|
|
37
|
+
*/
|
|
38
|
+
export declare function engineBatchWrite(operations: BatchOperation[]): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Increment a numeric field on an entity, preserving increment intent for conflict resolution.
|
|
41
|
+
* Uses increment operationType in the sync queue so multi-device increments are additive.
|
|
42
|
+
* Optionally sets additional fields (e.g., completed, updated_at) alongside the increment.
|
|
43
|
+
*/
|
|
44
|
+
export declare function engineIncrement(table: string, id: string, field: string, amount: number, additionalFields?: Record<string, unknown>): Promise<Record<string, unknown> | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Get a single entity by ID. Optional remote fallback if not found locally.
|
|
47
|
+
*/
|
|
48
|
+
export declare function engineGet(table: string, id: string, opts?: {
|
|
49
|
+
remoteFallback?: boolean;
|
|
50
|
+
}): Promise<Record<string, unknown> | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Get all entities from a table. Optional ordering and remote fallback.
|
|
53
|
+
*/
|
|
54
|
+
export declare function engineGetAll(table: string, opts?: {
|
|
55
|
+
orderBy?: string;
|
|
56
|
+
remoteFallback?: boolean;
|
|
57
|
+
}): Promise<Record<string, unknown>[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Query entities by index value (WHERE index = value).
|
|
60
|
+
*/
|
|
61
|
+
export declare function engineQuery(table: string, index: string, value: unknown, opts?: {
|
|
62
|
+
remoteFallback?: boolean;
|
|
63
|
+
}): Promise<Record<string, unknown>[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Range query (WHERE index BETWEEN lower AND upper).
|
|
66
|
+
*/
|
|
67
|
+
export declare function engineQueryRange(table: string, index: string, lower: unknown, upper: unknown, opts?: {
|
|
68
|
+
remoteFallback?: boolean;
|
|
69
|
+
}): Promise<Record<string, unknown>[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Singleton get-or-create with optional remote check.
|
|
72
|
+
* Used for patterns like focus_settings where one record per user exists.
|
|
73
|
+
*/
|
|
74
|
+
export declare function engineGetOrCreate(table: string, index: string, value: unknown, defaults: Record<string, unknown>, opts?: {
|
|
75
|
+
checkRemote?: boolean;
|
|
76
|
+
}): Promise<Record<string, unknown>>;
|
|
77
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"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"}
|