@prabhask5/stellar-engine 1.1.7 → 1.1.8
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 +4 -1
- package/dist/actions/remoteChange.d.ts +143 -18
- package/dist/actions/remoteChange.d.ts.map +1 -1
- package/dist/actions/remoteChange.js +182 -58
- package/dist/actions/remoteChange.js.map +1 -1
- package/dist/actions/truncateTooltip.d.ts +26 -12
- package/dist/actions/truncateTooltip.d.ts.map +1 -1
- package/dist/actions/truncateTooltip.js +89 -34
- package/dist/actions/truncateTooltip.js.map +1 -1
- package/dist/auth/admin.d.ts +40 -3
- package/dist/auth/admin.d.ts.map +1 -1
- package/dist/auth/admin.js +45 -5
- package/dist/auth/admin.js.map +1 -1
- package/dist/auth/crypto.d.ts +55 -5
- package/dist/auth/crypto.d.ts.map +1 -1
- package/dist/auth/crypto.js +58 -5
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/deviceVerification.d.ts +236 -20
- package/dist/auth/deviceVerification.d.ts.map +1 -1
- package/dist/auth/deviceVerification.js +293 -40
- package/dist/auth/deviceVerification.js.map +1 -1
- package/dist/auth/displayUtils.d.ts +98 -0
- package/dist/auth/displayUtils.d.ts.map +1 -0
- package/dist/auth/displayUtils.js +133 -0
- package/dist/auth/displayUtils.js.map +1 -0
- package/dist/auth/loginGuard.d.ts +108 -14
- package/dist/auth/loginGuard.d.ts.map +1 -1
- package/dist/auth/loginGuard.js +153 -31
- package/dist/auth/loginGuard.js.map +1 -1
- package/dist/auth/offlineCredentials.d.ts +132 -15
- package/dist/auth/offlineCredentials.d.ts.map +1 -1
- package/dist/auth/offlineCredentials.js +167 -23
- package/dist/auth/offlineCredentials.js.map +1 -1
- package/dist/auth/offlineLogin.d.ts +96 -10
- package/dist/auth/offlineLogin.d.ts.map +1 -1
- package/dist/auth/offlineLogin.js +82 -15
- package/dist/auth/offlineLogin.js.map +1 -1
- package/dist/auth/offlineSession.d.ts +83 -9
- package/dist/auth/offlineSession.d.ts.map +1 -1
- package/dist/auth/offlineSession.js +104 -13
- package/dist/auth/offlineSession.js.map +1 -1
- package/dist/auth/resolveAuthState.d.ts +70 -8
- package/dist/auth/resolveAuthState.d.ts.map +1 -1
- package/dist/auth/resolveAuthState.js +142 -46
- package/dist/auth/resolveAuthState.js.map +1 -1
- package/dist/auth/singleUser.d.ts +390 -37
- package/dist/auth/singleUser.d.ts.map +1 -1
- package/dist/auth/singleUser.js +500 -99
- package/dist/auth/singleUser.js.map +1 -1
- package/dist/bin/install-pwa.d.ts +18 -2
- package/dist/bin/install-pwa.d.ts.map +1 -1
- package/dist/bin/install-pwa.js +801 -25
- package/dist/bin/install-pwa.js.map +1 -1
- package/dist/config.d.ts +132 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -9
- package/dist/config.js.map +1 -1
- package/dist/conflicts.d.ts +246 -23
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/conflicts.js +495 -46
- package/dist/conflicts.js.map +1 -1
- package/dist/data.d.ts +338 -18
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +385 -34
- package/dist/data.js.map +1 -1
- package/dist/database.d.ts +72 -14
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +120 -29
- package/dist/database.js.map +1 -1
- package/dist/debug.d.ts +77 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +88 -1
- package/dist/debug.js.map +1 -1
- package/dist/deviceId.d.ts +38 -7
- package/dist/deviceId.d.ts.map +1 -1
- package/dist/deviceId.js +68 -10
- package/dist/deviceId.js.map +1 -1
- package/dist/engine.d.ts +175 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +756 -109
- package/dist/engine.js.map +1 -1
- package/dist/entries/actions.d.ts +13 -0
- package/dist/entries/actions.d.ts.map +1 -1
- package/dist/entries/actions.js +26 -1
- package/dist/entries/actions.js.map +1 -1
- package/dist/entries/auth.d.ts +16 -0
- package/dist/entries/auth.d.ts.map +1 -1
- package/dist/entries/auth.js +73 -1
- package/dist/entries/auth.js.map +1 -1
- package/dist/entries/config.d.ts +12 -0
- package/dist/entries/config.d.ts.map +1 -1
- package/dist/entries/config.js +18 -1
- package/dist/entries/config.js.map +1 -1
- package/dist/entries/kit.d.ts +11 -0
- package/dist/entries/kit.d.ts.map +1 -1
- package/dist/entries/kit.js +52 -2
- package/dist/entries/kit.js.map +1 -1
- package/dist/entries/stores.d.ts +11 -0
- package/dist/entries/stores.d.ts.map +1 -1
- package/dist/entries/stores.js +43 -2
- package/dist/entries/stores.js.map +1 -1
- package/dist/entries/types.d.ts +10 -0
- package/dist/entries/types.d.ts.map +1 -1
- package/dist/entries/types.js +10 -0
- package/dist/entries/types.js.map +1 -1
- package/dist/entries/utils.d.ts +6 -0
- package/dist/entries/utils.d.ts.map +1 -1
- package/dist/entries/utils.js +22 -1
- package/dist/entries/utils.js.map +1 -1
- package/dist/entries/vite.d.ts +17 -0
- package/dist/entries/vite.d.ts.map +1 -1
- package/dist/entries/vite.js +24 -1
- package/dist/entries/vite.js.map +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -20
- package/dist/index.js.map +1 -1
- package/dist/kit/auth.d.ts +60 -5
- package/dist/kit/auth.d.ts.map +1 -1
- package/dist/kit/auth.js +45 -4
- package/dist/kit/auth.js.map +1 -1
- package/dist/kit/confirm.d.ts +93 -12
- package/dist/kit/confirm.d.ts.map +1 -1
- package/dist/kit/confirm.js +103 -16
- package/dist/kit/confirm.js.map +1 -1
- package/dist/kit/loads.d.ts +150 -23
- package/dist/kit/loads.d.ts.map +1 -1
- package/dist/kit/loads.js +140 -24
- package/dist/kit/loads.js.map +1 -1
- package/dist/kit/server.d.ts +142 -10
- package/dist/kit/server.d.ts.map +1 -1
- package/dist/kit/server.js +158 -15
- package/dist/kit/server.js.map +1 -1
- package/dist/kit/sw.d.ts +152 -23
- package/dist/kit/sw.d.ts.map +1 -1
- package/dist/kit/sw.js +182 -26
- package/dist/kit/sw.js.map +1 -1
- package/dist/queue.d.ts +274 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +556 -38
- package/dist/queue.js.map +1 -1
- package/dist/realtime.d.ts +241 -27
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +633 -109
- package/dist/realtime.js.map +1 -1
- package/dist/runtime/runtimeConfig.d.ts +91 -8
- package/dist/runtime/runtimeConfig.d.ts.map +1 -1
- package/dist/runtime/runtimeConfig.js +146 -19
- package/dist/runtime/runtimeConfig.js.map +1 -1
- package/dist/stores/authState.d.ts +150 -11
- package/dist/stores/authState.d.ts.map +1 -1
- package/dist/stores/authState.js +169 -17
- package/dist/stores/authState.js.map +1 -1
- package/dist/stores/network.d.ts +39 -0
- package/dist/stores/network.d.ts.map +1 -1
- package/dist/stores/network.js +169 -16
- package/dist/stores/network.js.map +1 -1
- package/dist/stores/remoteChanges.d.ts +327 -52
- package/dist/stores/remoteChanges.d.ts.map +1 -1
- package/dist/stores/remoteChanges.js +337 -75
- package/dist/stores/remoteChanges.js.map +1 -1
- package/dist/stores/sync.d.ts +130 -0
- package/dist/stores/sync.d.ts.map +1 -1
- package/dist/stores/sync.js +167 -7
- package/dist/stores/sync.js.map +1 -1
- package/dist/supabase/auth.d.ts +325 -18
- package/dist/supabase/auth.d.ts.map +1 -1
- package/dist/supabase/auth.js +374 -26
- package/dist/supabase/auth.js.map +1 -1
- package/dist/supabase/client.d.ts +79 -6
- package/dist/supabase/client.d.ts.map +1 -1
- package/dist/supabase/client.js +158 -15
- package/dist/supabase/client.js.map +1 -1
- package/dist/supabase/validate.d.ts +101 -7
- package/dist/supabase/validate.d.ts.map +1 -1
- package/dist/supabase/validate.js +117 -8
- package/dist/supabase/validate.js.map +1 -1
- package/dist/sw/build/vite-plugin.d.ts +55 -10
- package/dist/sw/build/vite-plugin.d.ts.map +1 -1
- package/dist/sw/build/vite-plugin.js +77 -18
- package/dist/sw/build/vite-plugin.js.map +1 -1
- package/dist/sw/sw.js +99 -44
- package/dist/types.d.ts +150 -26
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -10
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +55 -13
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +83 -22
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/conflicts.d.ts
CHANGED
|
@@ -1,70 +1,293 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Conflict Resolution Engine
|
|
2
|
+
* @fileoverview Three-Tier Conflict Resolution Engine for Multi-Device Sync
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* This module implements a deterministic, three-tier conflict resolution
|
|
5
|
+
* architecture designed for offline-first multi-device synchronization.
|
|
6
|
+
* When a remote entity arrives that diverges from the local state, the engine
|
|
7
|
+
* walks through progressively finer granularity to produce a merged result:
|
|
5
8
|
*
|
|
6
|
-
* Tier 1
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* **Tier 1 -- Non-overlapping entities (AUTO-MERGE)**
|
|
10
|
+
* Different entities changed on different devices never conflict; each side's
|
|
11
|
+
* changes are accepted wholesale. This tier is handled upstream by the sync
|
|
12
|
+
* pull logic before this module is invoked.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* **Tier 2 -- Different fields on the same entity (AUTO-MERGE FIELDS)**
|
|
15
|
+
* When two devices edit *different* fields of the same entity, both changes
|
|
16
|
+
* are preserved automatically. The per-field loop inside {@link resolveConflicts}
|
|
17
|
+
* only emits a {@link FieldConflictResolution} when the local and remote
|
|
18
|
+
* values for a given field actually differ.
|
|
19
|
+
*
|
|
20
|
+
* **Tier 3 -- Same field on the same entity (STRATEGY-BASED)**
|
|
21
|
+
* When the exact same field was modified on both sides, a resolution strategy
|
|
22
|
+
* is selected based on the field's nature and any pending local operations:
|
|
23
|
+
* - **local_pending** -- The field has unsynced local ops; local value wins
|
|
24
|
+
* so user intent is never silently discarded.
|
|
25
|
+
* - **numeric_merge** -- Reserved for fields declared in
|
|
26
|
+
* `numericMergeFields`; currently falls through to last-write-wins
|
|
27
|
+
* because true delta-merge requires an operation-inbox system.
|
|
28
|
+
* - **delete_wins** -- A delete on either side trumps edits to prevent
|
|
29
|
+
* accidental resurrection of soft-deleted entities.
|
|
30
|
+
* - **last_write** -- The default fallback: the later `updated_at` wins,
|
|
31
|
+
* with a deterministic deviceId tiebreaker for simultaneous writes.
|
|
32
|
+
*
|
|
33
|
+
* ## Design Decisions
|
|
34
|
+
*
|
|
35
|
+
* - **Remote as base layer:** The merged entity starts as a copy of the remote
|
|
36
|
+
* entity, with local-winning fields overwritten on top. This bias toward
|
|
37
|
+
* remote is intentional: in the common case the remote version is newer (the
|
|
38
|
+
* conflict arose because a remote change was detected), so starting from
|
|
39
|
+
* remote minimizes the number of field overwrites.
|
|
40
|
+
* - **Version bumping:** After resolution, the merged entity's `_version` is
|
|
41
|
+
* set to `max(local, remote) + 1`. This ensures any device receiving the
|
|
42
|
+
* merged entity recognizes it as strictly newer than either input.
|
|
43
|
+
* - **Timestamp preservation:** The later `updated_at` is preserved so the
|
|
44
|
+
* merged entity sorts correctly in "recently modified" queries.
|
|
45
|
+
*
|
|
46
|
+
* ## Security Considerations
|
|
47
|
+
*
|
|
48
|
+
* - **Device ID as tiebreaker:** The `device_id` field is used only for
|
|
49
|
+
* deterministic tiebreaking when timestamps are identical. It is not used
|
|
50
|
+
* for authorization or access control. Lexicographic comparison ensures
|
|
51
|
+
* every device converges on the same winner without coordination.
|
|
52
|
+
* - **No data exfiltration:** Conflict history is stored locally in IndexedDB
|
|
53
|
+
* (`conflictHistory` table). It never leaves the device and is automatically
|
|
54
|
+
* purged after 30 days by {@link cleanupConflictHistory}.
|
|
55
|
+
*
|
|
56
|
+
* ## Auditability
|
|
57
|
+
*
|
|
58
|
+
* All resolution outcomes are recorded via {@link storeConflictHistory} for
|
|
59
|
+
* auditability and potential future undo support. Each field-level decision
|
|
60
|
+
* is stored as a separate {@link ConflictHistoryEntry}, enabling fine-grained
|
|
61
|
+
* queries like "show me every time `title` was overwritten by a remote value."
|
|
62
|
+
*
|
|
63
|
+
* @see {@link ./types.ts} for {@link SyncOperationItem} and {@link ConflictHistoryEntry}
|
|
64
|
+
* @see {@link ./config.ts} for per-table `excludeFromConflict` and `numericMergeFields`
|
|
65
|
+
* @see {@link ./deviceId.ts} for the stable device identifier used as tiebreaker
|
|
66
|
+
* @see {@link ./realtime.ts} for the primary consumer of this module
|
|
67
|
+
* @see {@link ./queue.ts} for the sync queue that provides pending operations
|
|
14
68
|
*/
|
|
15
69
|
import type { SyncOperationItem } from './types';
|
|
16
70
|
import type { ConflictHistoryEntry } from './types';
|
|
17
71
|
export type { ConflictHistoryEntry };
|
|
18
72
|
/**
|
|
19
|
-
* Conflict resolution result for a single field
|
|
73
|
+
* Conflict resolution result for a single field.
|
|
74
|
+
*
|
|
75
|
+
* Each instance captures the before/after state for one field where the local
|
|
76
|
+
* and remote values diverged, along with metadata about which side won and why.
|
|
77
|
+
* This granular record enables:
|
|
78
|
+
* - **UI display:** Showing the user exactly what changed and why.
|
|
79
|
+
* - **Audit trail:** Persisted via {@link storeConflictHistory} for post-hoc analysis.
|
|
80
|
+
* - **Potential undo:** Retaining the "losing" value so it can be restored.
|
|
81
|
+
*
|
|
82
|
+
* @see {@link ConflictResolution} for the entity-level container
|
|
20
83
|
*/
|
|
21
84
|
export interface FieldConflictResolution {
|
|
85
|
+
/** The name of the conflicting field (e.g., `"title"`, `"target_amount"`). */
|
|
22
86
|
field: string;
|
|
87
|
+
/** The value of this field in the local (device) copy of the entity. */
|
|
23
88
|
localValue: unknown;
|
|
89
|
+
/** The value of this field in the remote (server) copy of the entity. */
|
|
24
90
|
remoteValue: unknown;
|
|
91
|
+
/** The value chosen (or computed) by the resolution strategy. */
|
|
25
92
|
resolvedValue: unknown;
|
|
93
|
+
/**
|
|
94
|
+
* Which side's value was accepted.
|
|
95
|
+
* - `'local'` -- local device value was kept
|
|
96
|
+
* - `'remote'` -- server value was kept
|
|
97
|
+
* - `'merged'` -- a new value was computed from both sides (e.g., numeric merge)
|
|
98
|
+
*/
|
|
26
99
|
winner: 'local' | 'remote' | 'merged';
|
|
100
|
+
/**
|
|
101
|
+
* The strategy that determined the outcome.
|
|
102
|
+
* - `'last_write'` -- timestamp comparison (with deviceId tiebreaker)
|
|
103
|
+
* - `'numeric_merge'` -- reserved for additive delta merge
|
|
104
|
+
* - `'delete_wins'` -- delete operation trumps edits
|
|
105
|
+
* - `'local_pending'` -- unsynced local operation takes priority
|
|
106
|
+
*
|
|
107
|
+
* @see the Tier 3 description in the file-level JSDoc for details
|
|
108
|
+
*/
|
|
27
109
|
strategy: 'last_write' | 'numeric_merge' | 'delete_wins' | 'local_pending';
|
|
28
110
|
}
|
|
29
111
|
/**
|
|
30
|
-
* Full conflict resolution result for
|
|
112
|
+
* Full conflict resolution result for a single entity.
|
|
113
|
+
*
|
|
114
|
+
* Aggregates every field-level decision and provides the final merged entity
|
|
115
|
+
* ready to be written back to the local database.
|
|
116
|
+
*
|
|
117
|
+
* **Invariant:** `hasConflicts === (fieldResolutions.length > 0)`. If no fields
|
|
118
|
+
* diverged, both are false/empty and the merged entity is simply the remote copy.
|
|
119
|
+
*
|
|
120
|
+
* @see {@link resolveConflicts} which produces this structure
|
|
121
|
+
* @see {@link storeConflictHistory} which persists it for auditing
|
|
31
122
|
*/
|
|
32
123
|
export interface ConflictResolution {
|
|
124
|
+
/** UUID of the entity that was resolved. */
|
|
33
125
|
entityId: string;
|
|
126
|
+
/** Supabase table name the entity belongs to (e.g., `"goals"`). */
|
|
34
127
|
entityType: string;
|
|
128
|
+
/** ISO 8601 `updated_at` from the local copy (empty string if no local copy existed). */
|
|
35
129
|
localUpdatedAt: string;
|
|
130
|
+
/** ISO 8601 `updated_at` from the incoming remote copy. */
|
|
36
131
|
remoteUpdatedAt: string;
|
|
132
|
+
/** Per-field resolution details; empty when there were no diverging fields. */
|
|
37
133
|
fieldResolutions: FieldConflictResolution[];
|
|
134
|
+
/** The fully merged entity payload, ready to be persisted locally. */
|
|
38
135
|
mergedEntity: Record<string, unknown>;
|
|
136
|
+
/** `true` when at least one field required a resolution decision. */
|
|
39
137
|
hasConflicts: boolean;
|
|
138
|
+
/** ISO 8601 timestamp of when this resolution was computed. */
|
|
40
139
|
timestamp: string;
|
|
41
140
|
}
|
|
42
141
|
/**
|
|
43
142
|
* Resolve conflicts between local and remote entity states.
|
|
44
143
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
144
|
+
* This is the main entry point for the three-tier conflict resolution engine.
|
|
145
|
+
* It compares every field of the local and remote copies, applies the
|
|
146
|
+
* appropriate resolution strategy for each divergence, and returns a fully
|
|
147
|
+
* merged entity along with an audit trail of every decision.
|
|
148
|
+
*
|
|
149
|
+
* **Resolution flow:**
|
|
150
|
+
* 1. If no local entity exists, the remote is accepted wholesale (no conflict).
|
|
151
|
+
* 2. Pending delete operations are checked first -- deletes always win to
|
|
152
|
+
* prevent entity resurrection.
|
|
153
|
+
* 3. Each field is compared; equal values are skipped (Tier 2 auto-merge).
|
|
154
|
+
* 4. Divergent fields are resolved via the Tier 3 strategy cascade:
|
|
155
|
+
* pending local ops --> numeric merge --> last-write-wins.
|
|
156
|
+
* 5. The merged entity's `_version` is bumped past both sides so downstream
|
|
157
|
+
* sync recognizes the merge as the newest state.
|
|
158
|
+
*
|
|
159
|
+
* **Delete handling rationale:**
|
|
160
|
+
* Deletes are resolved before the per-field loop because a delete affects the
|
|
161
|
+
* entire entity, not a single field. The "delete wins" policy prevents entity
|
|
162
|
+
* resurrection, which is almost always the correct UX: if a user explicitly
|
|
163
|
+
* deleted something on one device, they don't want it reappearing because
|
|
164
|
+
* another device had pending edits.
|
|
165
|
+
*
|
|
166
|
+
* @param entityType - Supabase table name (e.g., `"goals"`)
|
|
167
|
+
* @param entityId - UUID of the entity being resolved
|
|
168
|
+
* @param local - The local entity state, or `null` if the entity does
|
|
169
|
+
* not yet exist on this device
|
|
170
|
+
* @param remote - The incoming remote entity state from the server
|
|
171
|
+
* @param pendingOps - Unsynced operations for this entity from the local
|
|
172
|
+
* sync queue (used to detect local user intent)
|
|
173
|
+
* @returns A {@link ConflictResolution} containing the merged entity and
|
|
174
|
+
* per-field audit trail
|
|
175
|
+
*
|
|
176
|
+
* @throws Never throws directly -- but callers should handle potential Dexie
|
|
177
|
+
* errors when persisting the returned `mergedEntity`.
|
|
178
|
+
*
|
|
179
|
+
* @see {@link resolveByTimestamp} for the last-write-wins tiebreaker logic
|
|
180
|
+
* @see {@link storeConflictHistory} for persisting the resolution outcome
|
|
181
|
+
* @see {@link ./realtime.ts} and {@link ./engine.ts} which call this function
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const result = await resolveConflicts(
|
|
186
|
+
* 'goals',
|
|
187
|
+
* 'abc-123',
|
|
188
|
+
* { id: 'abc-123', title: 'Local Title', target: 100, updated_at: '2026-01-01T00:00:00Z' },
|
|
189
|
+
* { id: 'abc-123', title: 'Remote Title', target: 100, updated_at: '2026-01-02T00:00:00Z' },
|
|
190
|
+
* []
|
|
191
|
+
* );
|
|
192
|
+
* // result.mergedEntity.title === 'Remote Title' (remote is newer)
|
|
193
|
+
* // result.hasConflicts === true
|
|
194
|
+
* ```
|
|
51
195
|
*/
|
|
52
196
|
export declare function resolveConflicts(entityType: string, entityId: string, local: Record<string, unknown> | null, remote: Record<string, unknown>, pendingOps: SyncOperationItem[]): Promise<ConflictResolution>;
|
|
53
197
|
/**
|
|
54
|
-
* Store conflict resolution history for review
|
|
198
|
+
* Store conflict resolution history for review and potential undo.
|
|
55
199
|
*
|
|
56
|
-
* @
|
|
200
|
+
* Each {@link FieldConflictResolution} within the given resolution is persisted
|
|
201
|
+
* as a separate {@link ConflictHistoryEntry} row in the `conflictHistory`
|
|
202
|
+
* IndexedDB table. This enables fine-grained auditing (e.g., "show me every
|
|
203
|
+
* time field X was overwritten by a remote value").
|
|
204
|
+
*
|
|
205
|
+
* No-ops silently when there are no actual conflicts (`hasConflicts === false`).
|
|
206
|
+
*
|
|
207
|
+
* **Storage cost:** Each entry is a small JSON object (~200-500 bytes). With
|
|
208
|
+
* 30-day retention and typical usage patterns, the `conflictHistory` table
|
|
209
|
+
* rarely exceeds a few hundred KB. {@link cleanupConflictHistory} handles
|
|
210
|
+
* periodic pruning.
|
|
211
|
+
*
|
|
212
|
+
* **Error handling:** Failures are caught and logged but do not propagate.
|
|
213
|
+
* Conflict history is a best-effort audit trail; a failure to record it must
|
|
214
|
+
* not block the critical path of applying the merged entity to the local DB.
|
|
215
|
+
*
|
|
216
|
+
* @param resolution - The entity-level resolution result produced by
|
|
217
|
+
* {@link resolveConflicts}
|
|
218
|
+
* @returns Resolves when all entries have been written (or on error, after
|
|
219
|
+
* logging the failure)
|
|
220
|
+
*
|
|
221
|
+
* @throws Never throws -- all errors are caught internally and logged.
|
|
222
|
+
*
|
|
223
|
+
* @see {@link ConflictHistoryEntry} in `./types.ts` for the persisted schema
|
|
224
|
+
* @see {@link cleanupConflictHistory} for expiring old entries
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* const resolution = await resolveConflicts('goals', 'abc', local, remote, []);
|
|
229
|
+
* await storeConflictHistory(resolution);
|
|
230
|
+
* ```
|
|
57
231
|
*/
|
|
58
232
|
export declare function storeConflictHistory(resolution: ConflictResolution): Promise<void>;
|
|
59
233
|
/**
|
|
60
|
-
* Get pending operations for a specific entity from the
|
|
234
|
+
* Get all pending (unsynced) operations for a specific entity from the
|
|
235
|
+
* local sync queue.
|
|
236
|
+
*
|
|
237
|
+
* This is used by {@link resolveConflicts} to detect Tier 3a situations where
|
|
238
|
+
* the user has local changes that have not yet been pushed to the server.
|
|
239
|
+
* Those pending operations take priority so user intent is never silently lost.
|
|
240
|
+
*
|
|
241
|
+
* **Query strategy:** Uses Dexie's `where('entityId').equals(...)` for an
|
|
242
|
+
* indexed lookup rather than scanning the entire queue. This is efficient
|
|
243
|
+
* even for large queues because `entityId` is indexed.
|
|
61
244
|
*
|
|
62
|
-
* @param entityId
|
|
63
|
-
* @returns
|
|
245
|
+
* @param entityId - UUID of the entity to look up
|
|
246
|
+
* @returns An array of {@link SyncOperationItem} entries queued for this entity
|
|
247
|
+
* (may be empty if nothing is pending)
|
|
248
|
+
*
|
|
249
|
+
* @see {@link SyncOperationItem} in `./types.ts` for the operation schema
|
|
250
|
+
* @see {@link resolveConflicts} which consumes these pending ops
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* const ops = await getPendingOpsForEntity('abc-123');
|
|
255
|
+
* // [{ table: 'goals', entityId: 'abc-123', operationType: 'set', ... }]
|
|
256
|
+
* ```
|
|
64
257
|
*/
|
|
65
258
|
export declare function getPendingOpsForEntity(entityId: string): Promise<SyncOperationItem[]>;
|
|
66
259
|
/**
|
|
67
|
-
* Clean up old conflict history entries
|
|
260
|
+
* Clean up old conflict history entries older than 30 days.
|
|
261
|
+
*
|
|
262
|
+
* Should be called periodically (e.g., on app launch or after a successful
|
|
263
|
+
* full sync) to prevent the `conflictHistory` IndexedDB table from growing
|
|
264
|
+
* unboundedly. The 30-day retention window provides enough runway for users
|
|
265
|
+
* to notice and investigate unexpected data changes.
|
|
266
|
+
*
|
|
267
|
+
* **Why 30 days?** This balances storage cost against audit usefulness:
|
|
268
|
+
* - Too short (e.g., 7 days) and users may not notice a conflict before
|
|
269
|
+
* the history is purged.
|
|
270
|
+
* - Too long (e.g., 1 year) and the table could grow to several MB on
|
|
271
|
+
* devices with frequent multi-device conflicts.
|
|
272
|
+
* - 30 days covers typical usage patterns where users check in at least
|
|
273
|
+
* once a month.
|
|
274
|
+
*
|
|
275
|
+
* **Performance note:** Uses Dexie's `.filter()` (client-side scan) rather
|
|
276
|
+
* than an indexed query because the `conflictHistory` table is small and
|
|
277
|
+
* does not have a `timestamp` index. If the table grows significantly,
|
|
278
|
+
* adding an index on `timestamp` and using `.where()` would be faster.
|
|
279
|
+
*
|
|
280
|
+
* @returns The number of entries deleted, or `0` if an error occurred
|
|
281
|
+
*
|
|
282
|
+
* @throws Never throws -- all errors are caught internally and logged.
|
|
283
|
+
*
|
|
284
|
+
* @see {@link storeConflictHistory} which creates the entries being cleaned
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* const deleted = await cleanupConflictHistory();
|
|
289
|
+
* console.log(`Purged ${deleted} stale conflict records`);
|
|
290
|
+
* ```
|
|
68
291
|
*/
|
|
69
292
|
export declare function cleanupConflictHistory(): Promise<number>;
|
|
70
293
|
//# sourceMappingURL=conflicts.d.ts.map
|
package/dist/conflicts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../src/conflicts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAIpD,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAMrC;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IAEd,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IAEpB,yEAAyE;IACzE,WAAW,EAAE,OAAO,CAAC;IAErB,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAEtC;;;;;;;;OAQG;IACH,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,aAAa,GAAG,eAAe,CAAC;CAC5E;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IAEjB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IAEnB,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAC;IAEvB,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;IAExB,+EAA+E;IAC/E,gBAAgB,EAAE,uBAAuB,EAAE,CAAC;IAE5C,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEtC,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IAEtB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACnB;AA4ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;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,CAoQ7B;AAqND;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BxF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAO3F;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAsB9D"}
|