cry-synced-db-client 0.1.26 → 0.1.28

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.
@@ -18,6 +18,7 @@ export declare class SyncedDb implements I_SyncedDb {
18
18
  private debounceRestWritesMs;
19
19
  private onForcedOffline?;
20
20
  private onSync?;
21
+ private onConflictResolved?;
21
22
  private onServerWriteRequest?;
22
23
  private onServerWriteResult?;
23
24
  private onFindNewerManyCall?;
package/dist/index.js CHANGED
@@ -271,6 +271,7 @@ class SyncedDb {
271
271
  debounceRestWritesMs;
272
272
  onForcedOffline;
273
273
  onSync;
274
+ onConflictResolved;
274
275
  onServerWriteRequest;
275
276
  onServerWriteResult;
276
277
  onFindNewerManyCall;
@@ -320,6 +321,7 @@ class SyncedDb {
320
321
  this.debounceRestWritesMs = config.debounceRestWritesMs ?? DEFAULT_REST_DEBOUNCE_MS;
321
322
  this.onForcedOffline = config.onForcedOffline;
322
323
  this.onSync = config.onSync;
324
+ this.onConflictResolved = config.onConflictResolved;
323
325
  this.onServerWriteRequest = config.onServerWriteRequest;
324
326
  this.onServerWriteResult = config.onServerWriteResult;
325
327
  this.onFindNewerManyCall = config.onFindNewerManyCall;
@@ -1312,7 +1314,7 @@ class SyncedDb {
1312
1314
  if (localItem) {
1313
1315
  if (localItem._dirty) {
1314
1316
  conflictsResolved++;
1315
- const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem);
1317
+ const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem, "sync");
1316
1318
  dexieBatch.push({
1317
1319
  ...resolved,
1318
1320
  _dirty: true,
@@ -1467,11 +1469,26 @@ class SyncedDb {
1467
1469
  }
1468
1470
  return { sentCount };
1469
1471
  }
1470
- resolveCollectionConflict(collectionName, config, local, external) {
1471
- if (config.resolveSyncConflict) {
1472
- return config.resolveSyncConflict(local, external);
1472
+ resolveCollectionConflict(collectionName, config, local, external, source) {
1473
+ const usedCustomResolver = !!config.resolveSyncConflict;
1474
+ const resolved = config.resolveSyncConflict ? config.resolveSyncConflict(local, external) : resolveConflict(local, external);
1475
+ if (this.onConflictResolved) {
1476
+ try {
1477
+ this.onConflictResolved({
1478
+ collection: collectionName,
1479
+ id: local._id,
1480
+ source,
1481
+ localItem: local,
1482
+ serverItem: external,
1483
+ resolvedItem: resolved,
1484
+ usedCustomResolver,
1485
+ timestamp: new Date
1486
+ });
1487
+ } catch (err) {
1488
+ console.error("onConflictResolved callback failed:", err);
1489
+ }
1473
1490
  }
1474
- return resolveConflict(local, external);
1491
+ return resolved;
1475
1492
  }
1476
1493
  compareTimestamps(a, b) {
1477
1494
  const aT = typeof a === "object" && "t" in a ? a.t : 0;
@@ -1598,7 +1615,7 @@ class SyncedDb {
1598
1615
  const currentMemItem = this.inMemDb.getById(collectionName, serverItem._id);
1599
1616
  if (currentMemItem) {
1600
1617
  const config = this.collections.get(collectionName);
1601
- const resolved = this.resolveCollectionConflict(collectionName, config, currentMemItem, serverItem);
1618
+ const resolved = this.resolveCollectionConflict(collectionName, config, currentMemItem, serverItem, "serverUpdate_pendingChanges");
1602
1619
  if (!resolved._deleted) {
1603
1620
  this.inMemDb.save(collectionName, serverItem._id, this.stripLocalFields(resolved));
1604
1621
  }
@@ -1620,7 +1637,7 @@ class SyncedDb {
1620
1637
  }
1621
1638
  if (localItem._dirty) {
1622
1639
  const config = this.collections.get(collectionName);
1623
- const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem);
1640
+ const resolved = this.resolveCollectionConflict(collectionName, config, localItem, serverItem, "serverUpdate_dirtyLocal");
1624
1641
  await this.dexieDb.save(collectionName, serverItem._id, {
1625
1642
  ...resolved,
1626
1643
  _dirty: true
@@ -1695,7 +1712,7 @@ class DexieDb extends Dexie {
1695
1712
  schema[SYNC_META_TABLE] = "[tenant+collection]";
1696
1713
  for (const config of collectionConfigs) {
1697
1714
  const additionalIndexes = config.indexes || [];
1698
- const indexes = ["_id", "_dirty", "_updatedAt", ...additionalIndexes.map(String)];
1715
+ const indexes = ["_id", "_dirty", "_ts", ...additionalIndexes.map(String)];
1699
1716
  schema[config.name] = indexes.join(", ");
1700
1717
  }
1701
1718
  this.version(1).stores(schema);
@@ -1860,7 +1877,7 @@ class DexieDb extends Dexie {
1860
1877
  async getNewerThan(collection, timestamp) {
1861
1878
  const table = this.getTable(collection);
1862
1879
  const normalizedTs = this.normalizeTimestamp(timestamp);
1863
- return await table.where("_updatedAt").above(normalizedTs).toArray();
1880
+ return await table.where("_ts").above(normalizedTs).toArray();
1864
1881
  }
1865
1882
  onMetaUpdated(callback) {
1866
1883
  this.metaUpdateCallbacks.add(callback);
@@ -61,7 +61,7 @@ export interface I_DexieDb {
61
61
  deleteSyncMeta(collection: string): Promise<void>;
62
62
  /** Vrne tenant */
63
63
  getTenant(): string;
64
- /** Get records newer than timestamp (uses _updatedAt index) */
64
+ /** Get records newer than timestamp (uses _ts index - server timestamp) */
65
65
  getNewerThan<T extends LocalDbEntity>(collection: string, timestamp: any): Promise<T[]>;
66
66
  /** Subscribe to metadata updates from other tabs. Returns unsubscribe function. */
67
67
  onMetaUpdated(callback: (payload: MetaUpdateBroadcast) => void): () => void;
@@ -144,6 +144,37 @@ export interface WsNotificationInfo {
144
144
  /** Timestamp when notification was received */
145
145
  timestamp: Date;
146
146
  }
147
+ /**
148
+ * Source of the conflict - where the conflict was detected
149
+ */
150
+ export type ConflictSource =
151
+ /** Conflict during batch sync (syncSingleCollection) */
152
+ "sync"
153
+ /** Conflict when processing server update with pending local changes not yet in Dexie */
154
+ | "serverUpdate_pendingChanges"
155
+ /** Conflict when processing server update with dirty local item in Dexie */
156
+ | "serverUpdate_dirtyLocal";
157
+ /**
158
+ * Callback payload for conflict resolution
159
+ */
160
+ export interface ConflictResolutionReport {
161
+ /** Collection where conflict occurred */
162
+ collection: string;
163
+ /** ID of the conflicting item */
164
+ id: Id;
165
+ /** Source/context of the conflict */
166
+ source: ConflictSource;
167
+ /** Local item before resolution */
168
+ localItem: LocalDbEntity;
169
+ /** Server/external item */
170
+ serverItem: LocalDbEntity;
171
+ /** Resolved item (result of conflict resolution) */
172
+ resolvedItem: LocalDbEntity;
173
+ /** Whether a custom resolver was used (from CollectionConfig.resolveSyncConflict) */
174
+ usedCustomResolver: boolean;
175
+ /** Timestamp when conflict was resolved */
176
+ timestamp: Date;
177
+ }
147
178
  /**
148
179
  * Informacije o sinhronizaciji za debugging/logging
149
180
  */
@@ -204,6 +235,8 @@ export interface SyncedDbConfig {
204
235
  onForcedOffline?: (reason: string) => void;
205
236
  /** Callback za debugging/logging - pokliče se po vsaki sinhronizaciji */
206
237
  onSync?: (info: SyncInfo) => void;
238
+ /** Callback when a sync conflict is resolved (local vs server data) */
239
+ onConflictResolved?: (report: ConflictResolutionReport) => void;
207
240
  /** Callback before sending data to server (updateCollections) */
208
241
  onServerWriteRequest?: (info: ServerWriteRequestInfo) => void;
209
242
  /** Callback after receiving result from server (updateCollections) */
@@ -4,5 +4,5 @@ export type { Obj, QuerySpec, Projection, QueryOpts, KeyOf, InsertKeyOf, InsertS
4
4
  export type { I_InMemDb as InMemDb } from "./I_InMemDb";
5
5
  export type { I_DexieDb as DexieDb, SyncMeta } from "./I_DexieDb";
6
6
  export type { I_ServerUpdateNotifier as ServerUpdateNotifier, ServerUpdateCallback, ServerUpdateNotifierCallbacks } from "./I_ServerUpdateNotifier";
7
- export type { I_SyncedDb as SyncedDb, SyncedDbConfig, CollectionConfig, SyncInfo, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerCallInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, InfrastructureErrorType, InfrastructureErrorInfo, } from "./I_SyncedDb";
7
+ export type { I_SyncedDb as SyncedDb, SyncedDbConfig, CollectionConfig, SyncInfo, ServerWriteRequestInfo, ServerWriteResultInfo, FindNewerManyCallInfo, FindNewerCallInfo, DexieWriteRequestInfo, DexieWriteResultInfo, LocalstorageWriteResultInfo, WsNotificationInfo, InfrastructureErrorType, InfrastructureErrorInfo, ConflictSource, ConflictResolutionReport, } from "./I_SyncedDb";
8
8
  export type { CollectionConfig as CollectionConfigFull } from "./CollectionConfig";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",