cry-synced-db-client 0.1.102 → 0.1.105

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/dist/index.js CHANGED
@@ -2348,6 +2348,7 @@ var _SyncEngine = class _SyncEngine {
2348
2348
  let sentCount = 0;
2349
2349
  let conflictsResolved = 0;
2350
2350
  const collectionStats = {};
2351
+ this.callOnSyncStart({ calledFrom, initialSync: false });
2351
2352
  try {
2352
2353
  this.deps.cancelRestUploadTimer();
2353
2354
  await this.deps.awaitRestUpload();
@@ -2373,6 +2374,7 @@ var _SyncEngine = class _SyncEngine {
2373
2374
  configMap.set(collectionName, config);
2374
2375
  }
2375
2376
  this.callOnFindNewerManyCall(syncSpecs, calledFrom);
2377
+ this.callbackSafe(this.callbacks.onServerSyncStart, { calledFrom, collectionCount: syncSpecs.length });
2376
2378
  const findNewerManyStartTime = Date.now();
2377
2379
  const collectionState = /* @__PURE__ */ new Map();
2378
2380
  for (const [name] of configMap) {
@@ -2414,8 +2416,22 @@ var _SyncEngine = class _SyncEngine {
2414
2416
  };
2415
2417
  }
2416
2418
  this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, true, calledFrom);
2419
+ this.callbackSafe(this.callbacks.onServerSyncEnd, {
2420
+ calledFrom,
2421
+ collectionCount: syncSpecs.length,
2422
+ receivedCount,
2423
+ durationMs: Date.now() - findNewerManyStartTime,
2424
+ success: true
2425
+ });
2417
2426
  } catch (err) {
2418
2427
  this.callOnFindNewerManyResult(syncSpecs, {}, findNewerManyStartTime, false, calledFrom, err);
2428
+ this.callbackSafe(this.callbacks.onServerSyncEnd, {
2429
+ calledFrom,
2430
+ collectionCount: syncSpecs.length,
2431
+ receivedCount,
2432
+ durationMs: Date.now() - findNewerManyStartTime,
2433
+ success: false
2434
+ });
2419
2435
  throw err;
2420
2436
  }
2421
2437
  const uploadStats = await this.uploadDirtyItems(calledFrom);
@@ -2430,7 +2446,7 @@ var _SyncEngine = class _SyncEngine {
2430
2446
  };
2431
2447
  }
2432
2448
  }
2433
- this.callOnSync({
2449
+ this.callOnSyncEnd({
2434
2450
  durationMs: Date.now() - startTime,
2435
2451
  receivedCount,
2436
2452
  sentCount,
@@ -2443,7 +2459,7 @@ var _SyncEngine = class _SyncEngine {
2443
2459
  const reason = err instanceof Error ? err.message : String(err);
2444
2460
  console.error("Sync failed, going offline:", err);
2445
2461
  this.deps.goOffline(`Sync failed: ${reason}`);
2446
- this.callOnSync({
2462
+ this.callOnSyncEnd({
2447
2463
  durationMs: Date.now() - startTime,
2448
2464
  receivedCount,
2449
2465
  sentCount,
@@ -2798,12 +2814,31 @@ var _SyncEngine = class _SyncEngine {
2798
2814
  // ============================================================
2799
2815
  // Callback Wrappers
2800
2816
  // ============================================================
2801
- callOnSync(info) {
2802
- if (this.callbacks.onSync) {
2817
+ /** Safe callback invocation — swallows errors */
2818
+ callbackSafe(fn, info) {
2819
+ if (fn) {
2803
2820
  try {
2804
- this.callbacks.onSync(info);
2821
+ fn(info);
2805
2822
  } catch (err) {
2806
- console.error("onSync callback failed:", err);
2823
+ console.error("Callback failed:", err);
2824
+ }
2825
+ }
2826
+ }
2827
+ callOnSyncStart(info) {
2828
+ if (this.callbacks.onSyncStart) {
2829
+ try {
2830
+ this.callbacks.onSyncStart(info);
2831
+ } catch (err) {
2832
+ console.error("onSyncStart callback failed:", err);
2833
+ }
2834
+ }
2835
+ }
2836
+ callOnSyncEnd(info) {
2837
+ if (this.callbacks.onSyncEnd) {
2838
+ try {
2839
+ this.callbacks.onSyncEnd(info);
2840
+ } catch (err) {
2841
+ console.error("onSyncEnd callback failed:", err);
2807
2842
  }
2808
2843
  }
2809
2844
  }
@@ -3321,7 +3356,12 @@ var SyncedDb = class _SyncedDb {
3321
3356
  const windowId = (_a = config._testWindowId) != null ? _a : this.getOrCreateWindowId();
3322
3357
  this.defaultReturnDeleted = (_b = config.returnDeleted) != null ? _b : false;
3323
3358
  this.defaultReturnArchived = (_c = config.returnArchived) != null ? _c : false;
3324
- this.onSync = config.onSync;
3359
+ this.onSyncStart = config.onSyncStart;
3360
+ this.onSyncEnd = config.onSyncEnd;
3361
+ this.onDexieSyncStart = config.onDexieSyncStart;
3362
+ this.onDexieSyncEnd = config.onDexieSyncEnd;
3363
+ this.onServerSyncStart = config.onServerSyncStart;
3364
+ this.onServerSyncEnd = config.onServerSyncEnd;
3325
3365
  this.onConflictResolved = config.onConflictResolved;
3326
3366
  this.onWsNotification = config.onWsNotification;
3327
3367
  this.onCrossTabSync = config.onCrossTabSync;
@@ -3435,7 +3475,10 @@ var SyncedDb = class _SyncedDb {
3435
3475
  dexieDb: this.dexieDb,
3436
3476
  restInterface: this.restInterface,
3437
3477
  callbacks: {
3438
- onSync: config.onSync,
3478
+ onSyncStart: config.onSyncStart ? (info) => config.onSyncStart(__spreadProps(__spreadValues({}, info), { initialSync: !this._lastFullSyncDate })) : void 0,
3479
+ onSyncEnd: config.onSyncEnd,
3480
+ onServerSyncStart: config.onServerSyncStart,
3481
+ onServerSyncEnd: config.onServerSyncEnd,
3439
3482
  onConflictResolved: config.onConflictResolved,
3440
3483
  onServerWriteRequest: config.onServerWriteRequest,
3441
3484
  onServerWriteResult: config.onServerWriteResult,
@@ -3538,8 +3581,14 @@ var SyncedDb = class _SyncedDb {
3538
3581
  newlyAllowed.push(name);
3539
3582
  }
3540
3583
  }
3541
- for (const name of newlyAllowed) {
3542
- await this.loadCollectionToInMem(name);
3584
+ if (newlyAllowed.length > 0) {
3585
+ const dexieStart = Date.now();
3586
+ let totalItems = 0;
3587
+ this.safeCallback(this.onDexieSyncStart, { calledFrom: "setSyncOnlyTheseCollections", collectionCount: newlyAllowed.length });
3588
+ for (const name of newlyAllowed) {
3589
+ totalItems += await this.loadCollectionToInMem(name);
3590
+ }
3591
+ this.safeCallback(this.onDexieSyncEnd, { calledFrom: "setSyncOnlyTheseCollections", collectionCount: newlyAllowed.length, totalItems, durationMs: Date.now() - dexieStart });
3543
3592
  }
3544
3593
  if (newlyAllowed.length > 0 && this.connectionManager.canSync()) {
3545
3594
  this.sync("setSyncOnlyTheseCollections").catch(() => {
@@ -3575,11 +3624,16 @@ var SyncedDb = class _SyncedDb {
3575
3624
  "BroadcastChannel API is not available. Cross-tab synchronization disabled."
3576
3625
  );
3577
3626
  }
3627
+ await this._loadLastFullSync();
3578
3628
  await this.pendingChanges.recoverPendingWrites();
3579
- for (const [name] of this.collections) {
3580
- if (!this.isSyncAllowed(name)) continue;
3581
- await this.loadCollectionToInMem(name);
3582
- }
3629
+ const allowedColls = [...this.collections.keys()].filter((n) => this.isSyncAllowed(n));
3630
+ const dexieStart = Date.now();
3631
+ let totalItems = 0;
3632
+ this.safeCallback(this.onDexieSyncStart, { calledFrom: "init", collectionCount: allowedColls.length });
3633
+ for (const name of allowedColls) {
3634
+ totalItems += await this.loadCollectionToInMem(name);
3635
+ }
3636
+ this.safeCallback(this.onDexieSyncEnd, { calledFrom: "init", collectionCount: allowedColls.length, totalItems, durationMs: Date.now() - dexieStart });
3583
3637
  this.leaderElection.init();
3584
3638
  this.crossTabSync.init();
3585
3639
  (_a = this.wakeSync) == null ? void 0 : _a.init();
@@ -3624,6 +3678,40 @@ var SyncedDb = class _SyncedDb {
3624
3678
  }
3625
3679
  this.initialized = true;
3626
3680
  }
3681
+ /**
3682
+ * Flush all debounced pending writes to Dexie.
3683
+ * Resolves when all data is persisted to IndexedDB.
3684
+ * Does NOT upload to server — call sync() for that.
3685
+ */
3686
+ async flush() {
3687
+ await this.pendingChanges.flushAll();
3688
+ }
3689
+ /**
3690
+ * Returns when all collections were last successfully synced
3691
+ * from the server, or undefined if never.
3692
+ * Value is persisted in Dexie and survives page reload.
3693
+ * Only set when syncOnlyCollections is null (all collections active).
3694
+ * Cleared on dropDatabase.
3695
+ */
3696
+ lastSuccessfulServerSync() {
3697
+ return this._lastFullSyncDate;
3698
+ }
3699
+ /** @internal Update after successful full sync */
3700
+ async _setLastFullSync(date) {
3701
+ this._lastFullSyncDate = date;
3702
+ await this.dexieDb.setSyncMeta("__lastFullSync", date.toISOString());
3703
+ }
3704
+ /** @internal Load cached value from Dexie */
3705
+ async _loadLastFullSync() {
3706
+ const meta = await this.dexieDb.getSyncMeta("__lastFullSync");
3707
+ if (meta == null ? void 0 : meta.lastSyncTs) {
3708
+ this._lastFullSyncDate = new Date(meta.lastSyncTs);
3709
+ }
3710
+ }
3711
+ /** @internal Clear on dropDatabase */
3712
+ _clearLastFullSync() {
3713
+ this._lastFullSyncDate = void 0;
3714
+ }
3627
3715
  async close() {
3628
3716
  var _a, _b;
3629
3717
  this.leaderElection.setClosing(true);
@@ -4125,6 +4213,10 @@ var SyncedDb = class _SyncedDb {
4125
4213
  this.syncing = true;
4126
4214
  try {
4127
4215
  await this.syncEngine.sync(calledFrom);
4216
+ if (!this.syncOnlyCollections) {
4217
+ this._setLastFullSync(/* @__PURE__ */ new Date()).catch(() => {
4218
+ });
4219
+ }
4128
4220
  } finally {
4129
4221
  this.syncing = false;
4130
4222
  this.syncLock = false;
@@ -4266,6 +4358,7 @@ var SyncedDb = class _SyncedDb {
4266
4358
  await this.dexieDb.clearDirtyChanges(collectionName);
4267
4359
  }
4268
4360
  this.syncMetaCache.clear();
4361
+ this._clearLastFullSync();
4269
4362
  }
4270
4363
  // ==================== Object Metadata ====================
4271
4364
  getObjectMetadata(collection, _id) {
@@ -4330,6 +4423,16 @@ var SyncedDb = class _SyncedDb {
4330
4423
  * Accumulates all items first, then does a single initCollection
4331
4424
  * call to minimize reactive update overhead.
4332
4425
  */
4426
+ /** Safe callback invocation — swallows errors */
4427
+ safeCallback(fn, info) {
4428
+ if (fn) {
4429
+ try {
4430
+ fn(info);
4431
+ } catch (err) {
4432
+ console.error("Callback failed:", err);
4433
+ }
4434
+ }
4435
+ }
4333
4436
  async loadCollectionToInMem(name) {
4334
4437
  const allItems = [];
4335
4438
  await this.dexieDb.forEachBatch(name, 2e3, async (chunk) => {
@@ -4345,6 +4448,7 @@ var SyncedDb = class _SyncedDb {
4345
4448
  if (meta) {
4346
4449
  this.syncMetaCache.set(name, meta);
4347
4450
  }
4451
+ return allItems.length;
4348
4452
  }
4349
4453
  assertCollection(name) {
4350
4454
  if (!this.collections.has(name)) {
@@ -37,7 +37,12 @@ export declare class SyncedDb implements I_SyncedDb {
37
37
  private beforeUnloadHandler?;
38
38
  private readonly defaultReturnDeleted;
39
39
  private readonly defaultReturnArchived;
40
- private readonly onSync?;
40
+ private readonly onSyncStart?;
41
+ private readonly onSyncEnd?;
42
+ private readonly onDexieSyncStart?;
43
+ private readonly onDexieSyncEnd?;
44
+ private readonly onServerSyncStart?;
45
+ private readonly onServerSyncEnd?;
41
46
  private readonly onConflictResolved?;
42
47
  private readonly onWsNotification?;
43
48
  private readonly onCrossTabSync?;
@@ -65,6 +70,27 @@ export declare class SyncedDb implements I_SyncedDb {
65
70
  */
66
71
  simulateExternalBroadcast(payload: MetaUpdateBroadcast): void;
67
72
  init(): Promise<void>;
73
+ /**
74
+ * Flush all debounced pending writes to Dexie.
75
+ * Resolves when all data is persisted to IndexedDB.
76
+ * Does NOT upload to server — call sync() for that.
77
+ */
78
+ flush(): Promise<void>;
79
+ private _lastFullSyncDate?;
80
+ /**
81
+ * Returns when all collections were last successfully synced
82
+ * from the server, or undefined if never.
83
+ * Value is persisted in Dexie and survives page reload.
84
+ * Only set when syncOnlyCollections is null (all collections active).
85
+ * Cleared on dropDatabase.
86
+ */
87
+ lastSuccessfulServerSync(): Date | undefined;
88
+ /** @internal Update after successful full sync */
89
+ _setLastFullSync(date: Date): Promise<void>;
90
+ /** @internal Load cached value from Dexie */
91
+ private _loadLastFullSync;
92
+ /** @internal Clear on dropDatabase */
93
+ private _clearLastFullSync;
68
94
  close(): Promise<void>;
69
95
  isOnline(): boolean;
70
96
  forceOffline(forced: boolean): void;
@@ -141,6 +167,8 @@ export declare class SyncedDb implements I_SyncedDb {
141
167
  * Accumulates all items first, then does a single initCollection
142
168
  * call to minimize reactive update overhead.
143
169
  */
170
+ /** Safe callback invocation — swallows errors */
171
+ private safeCallback;
144
172
  private loadCollectionToInMem;
145
173
  private assertCollection;
146
174
  /** Stringify an Id parameter (ObjectId → hex string). */
@@ -43,7 +43,10 @@ export declare class SyncEngine implements I_SyncEngine {
43
43
  private processIncomingServerData;
44
44
  private compareTimestamps;
45
45
  private resolveCollectionConflict;
46
- private callOnSync;
46
+ /** Safe callback invocation — swallows errors */
47
+ private callbackSafe;
48
+ private callOnSyncStart;
49
+ private callOnSyncEnd;
47
50
  private callOnFindNewerManyCall;
48
51
  private callOnFindNewerManyResult;
49
52
  private callOnServerWriteRequest;
@@ -208,7 +208,22 @@ export interface I_InMemManager {
208
208
  deleteObjectsMetadata(collection: string, ids: Id[]): void;
209
209
  }
210
210
  export interface SyncEngineCallbacks {
211
- onSync?: (info: SyncInfo) => void;
211
+ onSyncStart?: (info: {
212
+ calledFrom?: string;
213
+ initialSync: boolean;
214
+ }) => void;
215
+ onSyncEnd?: (info: SyncInfo) => void;
216
+ onServerSyncStart?: (info: {
217
+ calledFrom?: string;
218
+ collectionCount: number;
219
+ }) => void;
220
+ onServerSyncEnd?: (info: {
221
+ calledFrom?: string;
222
+ collectionCount: number;
223
+ receivedCount: number;
224
+ durationMs: number;
225
+ success: boolean;
226
+ }) => void;
212
227
  onConflictResolved?: (report: ConflictResolutionReport) => void;
213
228
  onServerWriteRequest?: (info: ServerWriteRequestInfo) => void;
214
229
  onServerWriteResult?: (info: ServerWriteResultInfo) => void;
@@ -293,8 +293,38 @@ export interface SyncedDbConfig {
293
293
  debounceRestWritesMs?: number;
294
294
  /** Callback ki se pokliče, ko SyncedDb sam preide v offline stanje (npr. ob sync napaki) */
295
295
  onForcedOffline?: (reason: string) => void;
296
- /** Callback za debugging/logging - pokliče se po vsaki sinhronizaciji */
297
- onSync?: (info: SyncInfo) => void;
296
+ /** Callback at the start of each sync cycle. initialSync=true if no full sync has completed yet. */
297
+ onSyncStart?: (info: {
298
+ calledFrom?: string;
299
+ initialSync: boolean;
300
+ }) => void;
301
+ /** Callback at the end of each sync cycle */
302
+ onSyncEnd?: (info: SyncInfo) => void;
303
+ /** Callback when Dexie→inMem loading starts (loadCollectionToInMem) */
304
+ onDexieSyncStart?: (info: {
305
+ calledFrom?: string;
306
+ collectionCount: number;
307
+ }) => void;
308
+ /** Callback when Dexie→inMem loading ends */
309
+ onDexieSyncEnd?: (info: {
310
+ calledFrom?: string;
311
+ collectionCount: number;
312
+ totalItems: number;
313
+ durationMs: number;
314
+ }) => void;
315
+ /** Callback when server download starts (findNewerManyStream) */
316
+ onServerSyncStart?: (info: {
317
+ calledFrom?: string;
318
+ collectionCount: number;
319
+ }) => void;
320
+ /** Callback when server download ends */
321
+ onServerSyncEnd?: (info: {
322
+ calledFrom?: string;
323
+ collectionCount: number;
324
+ receivedCount: number;
325
+ durationMs: number;
326
+ success: boolean;
327
+ }) => void;
298
328
  /** Callback when a sync conflict is resolved (local vs server data) */
299
329
  onConflictResolved?: (report: ConflictResolutionReport) => void;
300
330
  /** Callback before sending data to server (updateCollections) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.102",
3
+ "version": "0.1.105",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",