@twin.org/synchronised-storage-service 0.0.1-next.5 → 0.0.1-next.7

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.
@@ -4,7 +4,6 @@ import { HttpStatusCode } from '@twin.org/web';
4
4
  import { BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
5
5
  import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
6
6
  import { DocumentHelper, IdentityConnectorFactory } from '@twin.org/identity-models';
7
- import { LoggingConnectorFactory } from '@twin.org/logging-models';
8
7
  import { ProofTypes } from '@twin.org/standards-w3c-did';
9
8
  import { SyncChangeOperation, SynchronisedStorageTopics, SyncNodeIdentityMode } from '@twin.org/synchronised-storage-models';
10
9
  import { VaultEncryptionType, VaultConnectorFactory } from '@twin.org/vault-models';
@@ -45,6 +44,10 @@ let SyncSnapshotEntry = class SyncSnapshotEntry {
45
44
  * The flag to determine if this is a consolidated snapshot.
46
45
  */
47
46
  isConsolidated;
47
+ /**
48
+ * The epoch for the changeset.
49
+ */
50
+ epoch;
48
51
  /**
49
52
  * The ids of the storage for the change sets in the snapshot, if this is not a local snapshot.
50
53
  */
@@ -82,6 +85,10 @@ __decorate([
82
85
  property({ type: "boolean" }),
83
86
  __metadata("design:type", Boolean)
84
87
  ], SyncSnapshotEntry.prototype, "isConsolidated", void 0);
88
+ __decorate([
89
+ property({ type: "number" }),
90
+ __metadata("design:type", Number)
91
+ ], SyncSnapshotEntry.prototype, "epoch", void 0);
85
92
  __decorate([
86
93
  property({ type: "array", itemType: "string", optional: true }),
87
94
  __metadata("design:type", Array)
@@ -287,10 +294,10 @@ class BlobStorageHelper {
287
294
  */
288
295
  CLASS_NAME = "BlobStorageHelper";
289
296
  /**
290
- * The logging connector to use for logging.
297
+ * The logging component to use for logging.
291
298
  * @internal
292
299
  */
293
- _logging;
300
+ _loggingComponent;
294
301
  /**
295
302
  * The vault connector.
296
303
  * @internal
@@ -313,14 +320,14 @@ class BlobStorageHelper {
313
320
  _isTrustedNode;
314
321
  /**
315
322
  * Create a new instance of BlobStorageHelper.
316
- * @param logging The logging connector to use for logging.
323
+ * @param loggingComponent The logging connector to use for logging.
317
324
  * @param vaultConnector The vault connector to use for for the encryption key.
318
325
  * @param blobStorageConnector The blob storage component to use.
319
326
  * @param blobStorageEncryptionKeyId The id of the vault key to use for encrypting/decrypting blobs.
320
327
  * @param isTrustedNode Is this a trusted node.
321
328
  */
322
- constructor(logging, vaultConnector, blobStorageConnector, blobStorageEncryptionKeyId, isTrustedNode) {
323
- this._logging = logging;
329
+ constructor(loggingComponent, vaultConnector, blobStorageConnector, blobStorageEncryptionKeyId, isTrustedNode) {
330
+ this._loggingComponent = loggingComponent;
324
331
  this._vaultConnector = vaultConnector;
325
332
  this._blobStorageConnector = blobStorageConnector;
326
333
  this._blobStorageEncryptionKeyId = blobStorageEncryptionKeyId;
@@ -332,7 +339,7 @@ class BlobStorageHelper {
332
339
  * @returns The blob.
333
340
  */
334
341
  async loadBlob(blobId) {
335
- await this._logging?.log({
342
+ await this._loggingComponent?.log({
336
343
  level: "info",
337
344
  source: this.CLASS_NAME,
338
345
  message: "loadBlob",
@@ -355,7 +362,7 @@ class BlobStorageHelper {
355
362
  compressedBlob = rsa.decrypt(encryptedBlob);
356
363
  }
357
364
  const decompressedBlob = await Compression.decompress(compressedBlob, CompressionType.Gzip);
358
- await this._logging?.log({
365
+ await this._loggingComponent?.log({
359
366
  level: "info",
360
367
  source: this.CLASS_NAME,
361
368
  message: "loadedBlob",
@@ -367,7 +374,7 @@ class BlobStorageHelper {
367
374
  }
368
375
  }
369
376
  catch (error) {
370
- await this._logging?.log({
377
+ await this._loggingComponent?.log({
371
378
  level: "error",
372
379
  source: this.CLASS_NAME,
373
380
  message: "loadBlobFailed",
@@ -377,7 +384,7 @@ class BlobStorageHelper {
377
384
  error: BaseError.fromError(error)
378
385
  });
379
386
  }
380
- await this._logging?.log({
387
+ await this._loggingComponent?.log({
381
388
  level: "info",
382
389
  source: this.CLASS_NAME,
383
390
  message: "loadBlobEmpty",
@@ -392,7 +399,7 @@ class BlobStorageHelper {
392
399
  * @returns The id of the blob.
393
400
  */
394
401
  async saveBlob(blob) {
395
- await this._logging?.log({
402
+ await this._loggingComponent?.log({
396
403
  level: "info",
397
404
  source: this.CLASS_NAME,
398
405
  message: "saveBlob"
@@ -404,7 +411,7 @@ class BlobStorageHelper {
404
411
  const encryptedBlob = await this._vaultConnector.encrypt(this._blobStorageEncryptionKeyId, VaultEncryptionType.Rsa2048, compressedBlob);
405
412
  try {
406
413
  const blobId = await this._blobStorageConnector.set(encryptedBlob);
407
- await this._logging?.log({
414
+ await this._loggingComponent?.log({
408
415
  level: "info",
409
416
  source: this.CLASS_NAME,
410
417
  message: "savedBlob",
@@ -415,7 +422,7 @@ class BlobStorageHelper {
415
422
  return blobId;
416
423
  }
417
424
  catch (error) {
418
- await this._logging?.log({
425
+ await this._loggingComponent?.log({
419
426
  level: "error",
420
427
  source: this.CLASS_NAME,
421
428
  message: "saveBlobFailed",
@@ -430,7 +437,7 @@ class BlobStorageHelper {
430
437
  * @returns Nothing.
431
438
  */
432
439
  async removeBlob(blobId) {
433
- await this._logging?.log({
440
+ await this._loggingComponent?.log({
434
441
  level: "info",
435
442
  source: this.CLASS_NAME,
436
443
  message: "removeBlob",
@@ -440,7 +447,7 @@ class BlobStorageHelper {
440
447
  });
441
448
  try {
442
449
  await this._blobStorageConnector.remove(blobId);
443
- await this._logging?.log({
450
+ await this._loggingComponent?.log({
444
451
  level: "info",
445
452
  source: this.CLASS_NAME,
446
453
  message: "removedBlob",
@@ -450,7 +457,7 @@ class BlobStorageHelper {
450
457
  });
451
458
  }
452
459
  catch (error) {
453
- await this._logging?.log({
460
+ await this._loggingComponent?.log({
454
461
  level: "error",
455
462
  source: this.CLASS_NAME,
456
463
  message: "removeBlobFailed",
@@ -460,7 +467,7 @@ class BlobStorageHelper {
460
467
  error: BaseError.fromError(error)
461
468
  });
462
469
  }
463
- await this._logging?.log({
470
+ await this._loggingComponent?.log({
464
471
  level: "info",
465
472
  source: this.CLASS_NAME,
466
473
  message: "removeBlobEmpty",
@@ -482,10 +489,10 @@ class ChangeSetHelper {
482
489
  */
483
490
  CLASS_NAME = "ChangeSetHelper";
484
491
  /**
485
- * The logging connector to use for logging.
492
+ * The logging component to use for logging.
486
493
  * @internal
487
494
  */
488
- _logging;
495
+ _loggingComponent;
489
496
  /**
490
497
  * The event bus component.
491
498
  * @internal
@@ -513,14 +520,14 @@ class ChangeSetHelper {
513
520
  _nodeIdentity;
514
521
  /**
515
522
  * Create a new instance of ChangeSetHelper.
516
- * @param logging The logging connector to use for logging.
523
+ * @param loggingComponent The logging connector to use for logging.
517
524
  * @param eventBusComponent The event bus component to use for events.
518
525
  * @param identityConnector The identity connector to use for signing/verifying changesets.
519
526
  * @param blobStorageHelper The blob storage component to use for remote sync states.
520
527
  * @param decentralisedStorageMethodId The id of the identity method to use when signing/verifying changesets.
521
528
  */
522
- constructor(logging, eventBusComponent, identityConnector, blobStorageHelper, decentralisedStorageMethodId) {
523
- this._logging = logging;
529
+ constructor(loggingComponent, eventBusComponent, identityConnector, blobStorageHelper, decentralisedStorageMethodId) {
530
+ this._loggingComponent = loggingComponent;
524
531
  this._eventBusComponent = eventBusComponent;
525
532
  this._decentralisedStorageMethodId = decentralisedStorageMethodId;
526
533
  this._blobStorageHelper = blobStorageHelper;
@@ -539,7 +546,7 @@ class ChangeSetHelper {
539
546
  * @returns The changeset if it was verified.
540
547
  */
541
548
  async getAndVerifyChangeset(changeSetStorageId) {
542
- await this._logging?.log({
549
+ await this._loggingComponent?.log({
543
550
  level: "info",
544
551
  source: this.CLASS_NAME,
545
552
  message: "getChangeSet",
@@ -555,7 +562,7 @@ class ChangeSetHelper {
555
562
  }
556
563
  }
557
564
  catch (error) {
558
- await this._logging?.log({
565
+ await this._loggingComponent?.log({
559
566
  level: "warn",
560
567
  source: this.CLASS_NAME,
561
568
  message: "getChangeSetError",
@@ -565,7 +572,7 @@ class ChangeSetHelper {
565
572
  error: BaseError.fromError(error)
566
573
  });
567
574
  }
568
- await this._logging?.log({
575
+ await this._loggingComponent?.log({
569
576
  level: "info",
570
577
  source: this.CLASS_NAME,
571
578
  message: "getChangeSetEmpty",
@@ -596,7 +603,7 @@ class ChangeSetHelper {
596
603
  async applyChangeset(syncChangeset) {
597
604
  if (Is.arrayValue(syncChangeset.changes)) {
598
605
  for (const change of syncChangeset.changes) {
599
- await this._logging?.log({
606
+ await this._loggingComponent?.log({
600
607
  level: "info",
601
608
  source: this.CLASS_NAME,
602
609
  message: "changeSetApplyingChange",
@@ -642,7 +649,7 @@ class ChangeSetHelper {
642
649
  * @returns The id of the change set.
643
650
  */
644
651
  async storeChangeSet(syncChangeSet) {
645
- await this._logging?.log({
652
+ await this._loggingComponent?.log({
646
653
  level: "info",
647
654
  source: this.CLASS_NAME,
648
655
  message: "changeSetStoring",
@@ -659,7 +666,7 @@ class ChangeSetHelper {
659
666
  */
660
667
  async verifyChangesetProof(syncChangeset) {
661
668
  if (Is.empty(syncChangeset.proof)) {
662
- await this._logging?.log({
669
+ await this._loggingComponent?.log({
663
670
  level: "info",
664
671
  source: this.CLASS_NAME,
665
672
  message: "verifyChangeSetProofMissing",
@@ -672,7 +679,7 @@ class ChangeSetHelper {
672
679
  // If the proof or verification method is missing, the proof is invalid
673
680
  const verificationMethod = syncChangeset.proof?.verificationMethod;
674
681
  if (!Is.stringValue(verificationMethod)) {
675
- await this._logging?.log({
682
+ await this._loggingComponent?.log({
676
683
  level: "error",
677
684
  source: this.CLASS_NAME,
678
685
  message: "verifyChangeSetProofMissing",
@@ -686,7 +693,7 @@ class ChangeSetHelper {
686
693
  // otherwise you could sign a changeset for another node
687
694
  const changeSetNodeIdentity = DocumentHelper.parseId(verificationMethod ?? "");
688
695
  if (changeSetNodeIdentity.id !== syncChangeset.nodeIdentity) {
689
- await this._logging?.log({
696
+ await this._loggingComponent?.log({
690
697
  level: "error",
691
698
  source: this.CLASS_NAME,
692
699
  message: "verifyChangeSetProofNodeIdentityMismatch",
@@ -699,7 +706,7 @@ class ChangeSetHelper {
699
706
  delete changeSetWithoutProof.proof;
700
707
  const isValid = await this._identityConnector.verifyProof(changeSetWithoutProof, syncChangeset.proof);
701
708
  if (!isValid) {
702
- await this._logging?.log({
709
+ await this._loggingComponent?.log({
703
710
  level: "error",
704
711
  source: this.CLASS_NAME,
705
712
  message: "verifyChangeSetProofInvalid",
@@ -709,7 +716,7 @@ class ChangeSetHelper {
709
716
  });
710
717
  }
711
718
  else {
712
- await this._logging?.log({
719
+ await this._loggingComponent?.log({
713
720
  level: "error",
714
721
  source: this.CLASS_NAME,
715
722
  message: "verifyChangeSetProofValid",
@@ -730,7 +737,7 @@ class ChangeSetHelper {
730
737
  const changeSetWithoutProof = ObjectHelper.clone(syncChangeset);
731
738
  delete changeSetWithoutProof.proof;
732
739
  const proof = await this._identityConnector.createProof(this._nodeIdentity, DocumentHelper.joinId(this._nodeIdentity, this._decentralisedStorageMethodId), ProofTypes.DataIntegrityProof, changeSetWithoutProof);
733
- await this._logging?.log({
740
+ await this._loggingComponent?.log({
734
741
  level: "info",
735
742
  source: this.CLASS_NAME,
736
743
  message: "createdChangeSetProof",
@@ -750,7 +757,7 @@ class ChangeSetHelper {
750
757
  if (Is.stringValue(this._nodeIdentity)) {
751
758
  const verified = await this.verifyChangesetProof(syncChangeSet);
752
759
  if (verified) {
753
- await this._logging?.log({
760
+ await this._loggingComponent?.log({
754
761
  level: "info",
755
762
  source: this.CLASS_NAME,
756
763
  message: "copyChangeSet",
@@ -779,7 +786,7 @@ class ChangeSetHelper {
779
786
  async reset(storageKey, resetMode) {
780
787
  // If we are applying a consolidation we need to reset the local db
781
788
  // but keep any entries from the local node, as they might have been updated
782
- await this._logging?.log({
789
+ await this._loggingComponent?.log({
783
790
  level: "info",
784
791
  source: this.CLASS_NAME,
785
792
  message: "storageReset",
@@ -811,10 +818,10 @@ class LocalSyncStateHelper {
811
818
  */
812
819
  CLASS_NAME = "LocalSyncStateHelper";
813
820
  /**
814
- * The logging connector to use for logging.
821
+ * The logging component to use for logging.
815
822
  * @internal
816
823
  */
817
- _logging;
824
+ _loggingComponent;
818
825
  /**
819
826
  * The storage connector for the sync snapshot entries.
820
827
  * @internal
@@ -827,12 +834,12 @@ class LocalSyncStateHelper {
827
834
  _changeSetHelper;
828
835
  /**
829
836
  * Create a new instance of LocalSyncStateHelper.
830
- * @param logging The logging connector to use for logging.
837
+ * @param loggingComponent The logging connector to use for logging.
831
838
  * @param snapshotEntryEntityStorage The storage connector for the sync snapshot entries.
832
839
  * @param changeSetHelper The change set helper to use for applying changesets.
833
840
  */
834
- constructor(logging, snapshotEntryEntityStorage, changeSetHelper) {
835
- this._logging = logging;
841
+ constructor(loggingComponent, snapshotEntryEntityStorage, changeSetHelper) {
842
+ this._loggingComponent = loggingComponent;
836
843
  this._snapshotEntryEntityStorage = snapshotEntryEntityStorage;
837
844
  this._changeSetHelper = changeSetHelper;
838
845
  }
@@ -844,7 +851,7 @@ class LocalSyncStateHelper {
844
851
  * @returns Nothing.
845
852
  */
846
853
  async addLocalChange(storageKey, operation, id) {
847
- await this._logging?.log({
854
+ await this._loggingComponent?.log({
848
855
  level: "info",
849
856
  source: this.CLASS_NAME,
850
857
  message: "addLocalChange",
@@ -865,6 +872,9 @@ class LocalSyncStateHelper {
865
872
  if (previousChangeIndex !== -1) {
866
873
  localChangeSnapshot.changes.splice(previousChangeIndex, 1);
867
874
  }
875
+ // If we already have changes from previous updates
876
+ // then make sure we update the dateModified, otherwise
877
+ // we assume this is the first change and setting modified is not necessary
868
878
  if (localChangeSnapshot.changes.length > 0) {
869
879
  localChangeSnapshot.dateModified = new Date(Date.now()).toISOString();
870
880
  }
@@ -879,7 +889,7 @@ class LocalSyncStateHelper {
879
889
  * @returns The local snapshot entry.
880
890
  */
881
891
  async getSnapshots(storageKey, isLocal) {
882
- await this._logging?.log({
892
+ await this._loggingComponent?.log({
883
893
  level: "info",
884
894
  source: this.CLASS_NAME,
885
895
  message: "getSnapshots",
@@ -902,7 +912,7 @@ class LocalSyncStateHelper {
902
912
  ]
903
913
  });
904
914
  if (queryResult.entities.length > 0) {
905
- await this._logging?.log({
915
+ await this._loggingComponent?.log({
906
916
  level: "info",
907
917
  source: this.CLASS_NAME,
908
918
  message: "getSnapshotsExists",
@@ -912,7 +922,7 @@ class LocalSyncStateHelper {
912
922
  });
913
923
  return queryResult.entities;
914
924
  }
915
- await this._logging?.log({
925
+ await this._loggingComponent?.log({
916
926
  level: "info",
917
927
  source: this.CLASS_NAME,
918
928
  message: "getSnapshotsDoesNotExist",
@@ -930,7 +940,8 @@ class LocalSyncStateHelper {
930
940
  dateModified: now,
931
941
  changeSetStorageIds: [],
932
942
  isLocal,
933
- isConsolidated: false
943
+ isConsolidated: false,
944
+ epoch: 0
934
945
  }
935
946
  ];
936
947
  }
@@ -940,7 +951,7 @@ class LocalSyncStateHelper {
940
951
  * @returns Nothing.
941
952
  */
942
953
  async setLocalChangeSnapshot(localChangeSnapshot) {
943
- await this._logging?.log({
954
+ await this._loggingComponent?.log({
944
955
  level: "info",
945
956
  source: this.CLASS_NAME,
946
957
  message: "setLocalChangeSnapshot",
@@ -956,7 +967,7 @@ class LocalSyncStateHelper {
956
967
  * @returns Nothing.
957
968
  */
958
969
  async removeLocalChangeSnapshot(localChangeSnapshot) {
959
- await this._logging?.log({
970
+ await this._loggingComponent?.log({
960
971
  level: "info",
961
972
  source: this.CLASS_NAME,
962
973
  message: "removeLocalChangeSnapshot",
@@ -973,7 +984,7 @@ class LocalSyncStateHelper {
973
984
  * @returns Nothing.
974
985
  */
975
986
  async applySyncState(storageKey, syncState) {
976
- await this._logging?.log({
987
+ await this._loggingComponent?.log({
977
988
  level: "info",
978
989
  source: this.CLASS_NAME,
979
990
  message: "applySyncState",
@@ -982,14 +993,23 @@ class LocalSyncStateHelper {
982
993
  }
983
994
  });
984
995
  // Get all the existing snapshots that we have processed previously
985
- const existingRemoteSnapshots = await this.getSnapshots(storageKey, false);
996
+ let existingSnapshots = await this.getSnapshots(storageKey, false);
986
997
  // Sort from newest to oldest
987
- const sortedSnapshots = syncState.snapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
988
- // If we have no existing snapshots we can't have yet synced
989
- // in this case we need to find the most recent consolidation
990
- // and use that to build a complete DB table
991
- if (existingRemoteSnapshots.length === 0) {
992
- await this._logging?.log({
998
+ existingSnapshots = existingSnapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
999
+ // Sort from newest to oldest
1000
+ const syncStateSnapshots = syncState.snapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
1001
+ // Get the newest epoch from the local storage
1002
+ const newestExistingEpoch = existingSnapshots[0]?.epoch ?? 0;
1003
+ // Get the oldest epoch from the remote storage
1004
+ const oldestSyncStateEpoch = syncStateSnapshots[syncStateSnapshots.length - 1]?.epoch ?? 0;
1005
+ // If there is a gap between the largest epoch we have locally
1006
+ // and the smallest epoch we have remotely then we have missed
1007
+ // data so we need to perform a full sync
1008
+ const hasEpochGap = newestExistingEpoch + 1 < oldestSyncStateEpoch;
1009
+ // If we have an epoch gap or no existing snapshots then we need to apply
1010
+ // a full sync from a consolidation
1011
+ if (!existingSnapshots.some(s => s.isConsolidated) || hasEpochGap) {
1012
+ await this._loggingComponent?.log({
993
1013
  level: "info",
994
1014
  source: this.CLASS_NAME,
995
1015
  message: "applySnapshotNoExisting",
@@ -997,31 +1017,38 @@ class LocalSyncStateHelper {
997
1017
  storageKey
998
1018
  }
999
1019
  });
1000
- const firstConsolidated = sortedSnapshots.find(snapshot => snapshot.isConsolidated);
1001
- if (firstConsolidated) {
1002
- // We found a consolidated snapshot, we can use it
1003
- await this._logging?.log({
1020
+ const mostRecentConsolidation = syncStateSnapshots.findIndex(snapshot => snapshot.isConsolidated);
1021
+ if (mostRecentConsolidation !== -1) {
1022
+ // We found the most recent consolidated snapshot, we can use it
1023
+ await this._loggingComponent?.log({
1004
1024
  level: "info",
1005
1025
  source: this.CLASS_NAME,
1006
1026
  message: "applySnapshotFoundConsolidated",
1007
1027
  data: {
1008
1028
  storageKey,
1009
- snapshotId: firstConsolidated.id
1029
+ snapshotId: syncStateSnapshots[mostRecentConsolidation].id
1010
1030
  }
1011
1031
  });
1012
1032
  // We need to reset the entity storage and remove all the remote items
1013
- // so that we use just the ones from the consolidation
1033
+ // so that we use just the ones from the consolidation, since
1034
+ // we don't have any existing there shouldn't be any remote entries
1035
+ // but we reset nonetheless
1014
1036
  await this._changeSetHelper.reset(storageKey, SyncNodeIdentityMode.Remote);
1015
- await this.processNewSnapshots([
1016
- {
1017
- ...firstConsolidated,
1018
- storageKey,
1019
- isLocal: false
1020
- }
1021
- ]);
1037
+ // We need to process the most recent consolidation and all changes
1038
+ // that were made since then, from newest to oldest (so newer changes override older ones)
1039
+ // Process snapshots from the consolidation point (most recent) back to the newest
1040
+ for (let i = mostRecentConsolidation; i >= 0; i--) {
1041
+ await this.processNewSnapshots([
1042
+ {
1043
+ ...syncStateSnapshots[i],
1044
+ storageKey,
1045
+ isLocal: false
1046
+ }
1047
+ ]);
1048
+ }
1022
1049
  }
1023
1050
  else {
1024
- await this._logging?.log({
1051
+ await this._loggingComponent?.log({
1025
1052
  level: "info",
1026
1053
  source: this.CLASS_NAME,
1027
1054
  message: "applySnapshotNoConsolidated",
@@ -1032,16 +1059,21 @@ class LocalSyncStateHelper {
1032
1059
  }
1033
1060
  }
1034
1061
  else {
1062
+ // We have existing consolidated remote snapshots, so we can assume that we have
1063
+ // applied at least one consolidation snapshot, in this case we need to look at the changes since
1064
+ // then and apply them if we haven't already
1065
+ // We don't need to apply any additional consolidated snapshots, just the changesets
1035
1066
  // Create a lookup map for the existing snapshots
1036
- const existingSnapshots = {};
1037
- for (const snapshot of existingRemoteSnapshots) {
1038
- existingSnapshots[snapshot.id] = snapshot;
1067
+ const existingSnapshotsMap = {};
1068
+ for (const snapshot of existingSnapshots) {
1069
+ existingSnapshotsMap[snapshot.id] = snapshot;
1039
1070
  }
1040
1071
  const newSnapshots = [];
1041
1072
  const modifiedSnapshots = [];
1042
- const referencedExistingSnapshots = Object.keys(existingSnapshots);
1043
- for (const snapshot of sortedSnapshots) {
1044
- await this._logging?.log({
1073
+ const referencedExistingSnapshots = Object.keys(existingSnapshotsMap);
1074
+ let completedProcessing = false;
1075
+ for (const snapshot of syncStateSnapshots) {
1076
+ await this._loggingComponent?.log({
1045
1077
  level: "info",
1046
1078
  source: this.CLASS_NAME,
1047
1079
  message: "applySnapshot",
@@ -1050,34 +1082,37 @@ class LocalSyncStateHelper {
1050
1082
  dateCreated: new Date(snapshot.dateCreated).toISOString()
1051
1083
  }
1052
1084
  });
1053
- // See if we have the local snapshot
1054
- const currentSnapshot = existingSnapshots[snapshot.id];
1085
+ // See if we have the snapshot stored locally
1086
+ const currentSnapshot = existingSnapshotsMap[snapshot.id];
1055
1087
  // As we are referencing an existing snapshot, we need to remove it from the list
1056
1088
  // to allow us to cleanup any unreferenced snapshots later
1057
1089
  const idx = referencedExistingSnapshots.indexOf(snapshot.id);
1058
1090
  if (idx !== -1) {
1059
1091
  referencedExistingSnapshots.splice(idx, 1);
1060
1092
  }
1061
- const updatedSnapshot = {
1062
- ...snapshot,
1063
- storageKey,
1064
- isLocal: false
1065
- };
1066
- if (Is.empty(currentSnapshot)) {
1067
- // We don't have the snapshot locally, so we need to process it
1068
- newSnapshots.push(updatedSnapshot);
1069
- }
1070
- else if (currentSnapshot.dateModified !== snapshot.dateModified) {
1071
- // If the local snapshot has a different dateModified, we need to update it
1072
- modifiedSnapshots.push({
1073
- currentSnapshot,
1074
- updatedSnapshot
1075
- });
1076
- }
1077
- else {
1078
- // we sorted the snapshots from newest to oldest, so if we found a local snapshot
1079
- // with the same dateModified as the remote snapshot, we can stop processing further
1080
- break;
1093
+ // No need to apply consolidated snapshots
1094
+ if (!snapshot.isConsolidated && !completedProcessing) {
1095
+ const updatedSnapshot = {
1096
+ ...snapshot,
1097
+ storageKey,
1098
+ isLocal: false
1099
+ };
1100
+ if (Is.empty(currentSnapshot)) {
1101
+ // We don't have the snapshot locally, so we need to process all of it
1102
+ newSnapshots.push(updatedSnapshot);
1103
+ }
1104
+ else if (currentSnapshot.dateModified !== snapshot.dateModified) {
1105
+ // If the local snapshot has a different dateModified, we need to update it
1106
+ modifiedSnapshots.push({
1107
+ currentSnapshot,
1108
+ updatedSnapshot
1109
+ });
1110
+ }
1111
+ else {
1112
+ // we sorted the snapshots from newest to oldest, so if we found a local snapshot
1113
+ // with the same dateModified as the remote snapshot, we can stop processing further
1114
+ completedProcessing = true;
1115
+ }
1081
1116
  }
1082
1117
  }
1083
1118
  // We reverse the order of the snapshots to process them from oldest to newest
@@ -1099,7 +1134,7 @@ class LocalSyncStateHelper {
1099
1134
  */
1100
1135
  async processModifiedSnapshots(modifiedSnapshots) {
1101
1136
  for (const modifiedSnapshot of modifiedSnapshots) {
1102
- await this._logging?.log({
1137
+ await this._loggingComponent?.log({
1103
1138
  level: "info",
1104
1139
  source: this.CLASS_NAME,
1105
1140
  message: "processModifiedSnapshot",
@@ -1132,7 +1167,7 @@ class LocalSyncStateHelper {
1132
1167
  */
1133
1168
  async processNewSnapshots(newSnapshots) {
1134
1169
  for (const newSnapshot of newSnapshots) {
1135
- await this._logging?.log({
1170
+ await this._loggingComponent?.log({
1136
1171
  level: "info",
1137
1172
  source: this.CLASS_NAME,
1138
1173
  message: "processNewSnapshot",
@@ -1163,10 +1198,10 @@ class RemoteSyncStateHelper {
1163
1198
  */
1164
1199
  CLASS_NAME = "RemoteSyncStateHelper";
1165
1200
  /**
1166
- * The logging connector to use for logging.
1201
+ * The logging component to use for logging.
1167
1202
  * @internal
1168
1203
  */
1169
- _logging;
1204
+ _loggingComponent;
1170
1205
  /**
1171
1206
  * The event bus component.
1172
1207
  * @internal
@@ -1219,7 +1254,7 @@ class RemoteSyncStateHelper {
1219
1254
  _maxConsolidations;
1220
1255
  /**
1221
1256
  * Create a new instance of DecentralisedEntityStorageConnector.
1222
- * @param logging The logging connector to use for logging.
1257
+ * @param loggingComponent The logging component to use for logging.
1223
1258
  * @param eventBusComponent The event bus component to use for events.
1224
1259
  * @param verifiableSyncPointerStorageConnector The verifiable storage connector to use for storing sync pointers.
1225
1260
  * @param blobStorageHelper The blob storage helper to use for remote sync states.
@@ -1227,8 +1262,8 @@ class RemoteSyncStateHelper {
1227
1262
  * @param isTrustedNode Whether the node is trusted or not.
1228
1263
  * @param maxConsolidations The maximum number of consolidations to keep in storage.
1229
1264
  */
1230
- constructor(logging, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode, maxConsolidations) {
1231
- this._logging = logging;
1265
+ constructor(loggingComponent, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode, maxConsolidations) {
1266
+ this._loggingComponent = loggingComponent;
1232
1267
  this._eventBusComponent = eventBusComponent;
1233
1268
  this._verifiableSyncPointerStorageConnector = verifiableSyncPointerStorageConnector;
1234
1269
  this._changeSetHelper = changeSetHelper;
@@ -1266,7 +1301,7 @@ class RemoteSyncStateHelper {
1266
1301
  * @returns The storage id of the change set if created.
1267
1302
  */
1268
1303
  async buildChangeSet(storageKey, changes, completeCallback) {
1269
- await this._logging?.log({
1304
+ await this._loggingComponent?.log({
1270
1305
  level: "info",
1271
1306
  source: this.CLASS_NAME,
1272
1307
  message: "buildingChangeSet",
@@ -1292,7 +1327,7 @@ class RemoteSyncStateHelper {
1292
1327
  // Once all the requests are handled the callback will be called
1293
1328
  for (const change of setChanges) {
1294
1329
  // Create a request for each change to populate the full details
1295
- await this._logging?.log({
1330
+ await this._loggingComponent?.log({
1296
1331
  level: "info",
1297
1332
  source: this.CLASS_NAME,
1298
1333
  message: "createChangeSetRequestingItem",
@@ -1315,7 +1350,7 @@ class RemoteSyncStateHelper {
1315
1350
  * @returns Nothing.
1316
1351
  */
1317
1352
  async finaliseFullChanges(storageKey, completeCallback) {
1318
- await this._logging?.log({
1353
+ await this._loggingComponent?.log({
1319
1354
  level: "info",
1320
1355
  source: this.CLASS_NAME,
1321
1356
  message: "finalisingSyncChanges",
@@ -1356,7 +1391,7 @@ class RemoteSyncStateHelper {
1356
1391
  await completeCallback(syncChangeSet, changeSetStorageId);
1357
1392
  }
1358
1393
  catch (err) {
1359
- await this._logging?.log({
1394
+ await this._loggingComponent?.log({
1360
1395
  level: "error",
1361
1396
  source: this.CLASS_NAME,
1362
1397
  message: "finalisingSyncChangesFailed",
@@ -1379,7 +1414,7 @@ class RemoteSyncStateHelper {
1379
1414
  * @returns Nothing.
1380
1415
  */
1381
1416
  async addChangeSetToSyncState(storageKey, changeSetStorageId) {
1382
- await this._logging?.log({
1417
+ await this._loggingComponent?.log({
1383
1418
  level: "info",
1384
1419
  source: this.CLASS_NAME,
1385
1420
  message: "addChangeSetToSyncState",
@@ -1402,6 +1437,7 @@ class RemoteSyncStateHelper {
1402
1437
  const sortedSnapshots = syncState.snapshots.sort((a, b) => a.dateCreated.localeCompare(b.dateCreated));
1403
1438
  // Get the current snapshot, if it does not exist we create a new one
1404
1439
  let currentSnapshot = sortedSnapshots[sortedSnapshots.length - 1];
1440
+ const currentEpoch = currentSnapshot?.epoch ?? 0;
1405
1441
  const now = new Date(Date.now()).toISOString();
1406
1442
  // If there is no snapshot or the current one is a consolidation
1407
1443
  // we start a new snapshot
@@ -1412,6 +1448,7 @@ class RemoteSyncStateHelper {
1412
1448
  dateCreated: now,
1413
1449
  dateModified: now,
1414
1450
  isConsolidated: false,
1451
+ epoch: currentEpoch + 1,
1415
1452
  changeSetStorageIds: []
1416
1453
  };
1417
1454
  syncState.snapshots.push(currentSnapshot);
@@ -1434,7 +1471,7 @@ class RemoteSyncStateHelper {
1434
1471
  * @returns Nothing.
1435
1472
  */
1436
1473
  async consolidationStart(storageKey, batchSize) {
1437
- await this._logging?.log({
1474
+ await this._loggingComponent?.log({
1438
1475
  level: "info",
1439
1476
  source: this.CLASS_NAME,
1440
1477
  message: "consolidationStarting"
@@ -1449,7 +1486,7 @@ class RemoteSyncStateHelper {
1449
1486
  async getVerifiableSyncPointerStore() {
1450
1487
  if (Is.stringValue(this._synchronisedStorageKey)) {
1451
1488
  try {
1452
- await this._logging?.log({
1489
+ await this._loggingComponent?.log({
1453
1490
  level: "info",
1454
1491
  source: this.CLASS_NAME,
1455
1492
  message: "verifiableSyncPointerStoreRetrieving",
@@ -1460,7 +1497,7 @@ class RemoteSyncStateHelper {
1460
1497
  const syncPointerStore = await this._verifiableSyncPointerStorageConnector.get(this._synchronisedStorageKey, { includeData: true });
1461
1498
  if (Is.uint8Array(syncPointerStore.data)) {
1462
1499
  const syncPointer = ObjectHelper.fromBytes(syncPointerStore.data);
1463
- await this._logging?.log({
1500
+ await this._loggingComponent?.log({
1464
1501
  level: "info",
1465
1502
  source: this.CLASS_NAME,
1466
1503
  message: "verifiableSyncPointerStoreRetrieved",
@@ -1476,7 +1513,7 @@ class RemoteSyncStateHelper {
1476
1513
  throw err;
1477
1514
  }
1478
1515
  }
1479
- await this._logging?.log({
1516
+ await this._loggingComponent?.log({
1480
1517
  level: "info",
1481
1518
  source: this.CLASS_NAME,
1482
1519
  message: "verifiableSyncPointerStoreNotFound",
@@ -1498,7 +1535,7 @@ class RemoteSyncStateHelper {
1498
1535
  */
1499
1536
  async storeVerifiableSyncPointerStore(syncPointerStore) {
1500
1537
  if (Is.stringValue(this._nodeIdentity) && Is.stringValue(this._synchronisedStorageKey)) {
1501
- await this._logging?.log({
1538
+ await this._loggingComponent?.log({
1502
1539
  level: "info",
1503
1540
  source: this.CLASS_NAME,
1504
1541
  message: "verifiableSyncPointerStoreStoring",
@@ -1516,7 +1553,7 @@ class RemoteSyncStateHelper {
1516
1553
  * @returns The id of the sync state.
1517
1554
  */
1518
1555
  async storeRemoteSyncState(syncState) {
1519
- await this._logging?.log({
1556
+ await this._loggingComponent?.log({
1520
1557
  level: "info",
1521
1558
  source: this.CLASS_NAME,
1522
1559
  message: "syncStateStoring",
@@ -1542,7 +1579,12 @@ class RemoteSyncStateHelper {
1542
1579
  const toRemove = snapshots.slice(consolidationIndexes[this._maxConsolidations - 1] + 1);
1543
1580
  syncState.snapshots = snapshots.slice(0, consolidationIndexes[this._maxConsolidations - 1] + 1);
1544
1581
  for (const snapshot of toRemove) {
1545
- await this._blobStorageHelper.removeBlob(snapshot.id);
1582
+ // We need to remove all the storage ids associated with the snapshot
1583
+ if (Is.arrayValue(snapshot.changeSetStorageIds)) {
1584
+ for (const storageId of snapshot.changeSetStorageIds) {
1585
+ await this._blobStorageHelper.removeBlob(storageId);
1586
+ }
1587
+ }
1546
1588
  }
1547
1589
  }
1548
1590
  return this._blobStorageHelper.saveBlob(syncState);
@@ -1554,7 +1596,7 @@ class RemoteSyncStateHelper {
1554
1596
  */
1555
1597
  async getSyncState(syncPointerId) {
1556
1598
  try {
1557
- await this._logging?.log({
1599
+ await this._loggingComponent?.log({
1558
1600
  level: "info",
1559
1601
  source: this.CLASS_NAME,
1560
1602
  message: "syncStateRetrieving",
@@ -1564,7 +1606,7 @@ class RemoteSyncStateHelper {
1564
1606
  });
1565
1607
  const syncState = await this._blobStorageHelper.loadBlob(syncPointerId);
1566
1608
  if (Is.object(syncState)) {
1567
- await this._logging?.log({
1609
+ await this._loggingComponent?.log({
1568
1610
  level: "info",
1569
1611
  source: this.CLASS_NAME,
1570
1612
  message: "syncStateRetrieved",
@@ -1577,7 +1619,7 @@ class RemoteSyncStateHelper {
1577
1619
  }
1578
1620
  }
1579
1621
  catch (error) {
1580
- await this._logging?.log({
1622
+ await this._loggingComponent?.log({
1581
1623
  level: "warn",
1582
1624
  source: this.CLASS_NAME,
1583
1625
  message: "getSyncStateError",
@@ -1587,7 +1629,7 @@ class RemoteSyncStateHelper {
1587
1629
  error: BaseError.fromError(error)
1588
1630
  });
1589
1631
  }
1590
- await this._logging?.log({
1632
+ await this._loggingComponent?.log({
1591
1633
  level: "info",
1592
1634
  source: this.CLASS_NAME,
1593
1635
  message: "syncStateNotFound",
@@ -1637,12 +1679,17 @@ class RemoteSyncStateHelper {
1637
1679
  storageKey: response.storageKey,
1638
1680
  snapshots: []
1639
1681
  };
1682
+ // Sort the snapshots so the newest snapshot is last in the array
1683
+ const sortedSnapshots = syncState.snapshots.sort((a, b) => a.dateCreated.localeCompare(b.dateCreated));
1684
+ const currentSnapshot = sortedSnapshots[sortedSnapshots.length - 1];
1685
+ const currentEpoch = currentSnapshot?.epoch ?? 0;
1640
1686
  const batchSnapshot = {
1641
1687
  version: SYNC_SNAPSHOT_VERSION,
1642
1688
  id: Converter.bytesToHex(RandomHelper.generate(32)),
1643
1689
  dateCreated: now,
1644
1690
  dateModified: now,
1645
1691
  isConsolidated: true,
1692
+ epoch: currentEpoch + 1,
1646
1693
  changeSetStorageIds: this._batchResponseStorageIds[response.storageKey]
1647
1694
  };
1648
1695
  syncState.snapshots.push(batchSnapshot);
@@ -1654,7 +1701,7 @@ class RemoteSyncStateHelper {
1654
1701
  // Remove the batch response storage ids for the storage key
1655
1702
  // as we have consolidated the changes
1656
1703
  delete this._batchResponseStorageIds[response.storageKey];
1657
- await this._logging?.log({
1704
+ await this._loggingComponent?.log({
1658
1705
  level: "info",
1659
1706
  source: this.CLASS_NAME,
1660
1707
  message: "consolidationCompleted"
@@ -1667,7 +1714,7 @@ class RemoteSyncStateHelper {
1667
1714
  * @param response The item response to handle.
1668
1715
  */
1669
1716
  async handleLocalItemResponse(response) {
1670
- await this._logging?.log({
1717
+ await this._loggingComponent?.log({
1671
1718
  level: "info",
1672
1719
  source: this.CLASS_NAME,
1673
1720
  message: "createChangeSetRespondingItem",
@@ -1721,10 +1768,10 @@ class SynchronisedStorageService {
1721
1768
  */
1722
1769
  CLASS_NAME = "SynchronisedStorageService";
1723
1770
  /**
1724
- * The logging connector to use for logging.
1771
+ * The logging component to use for logging.
1725
1772
  * @internal
1726
1773
  */
1727
- _logging;
1774
+ _loggingComponent;
1728
1775
  /**
1729
1776
  * The event bus component.
1730
1777
  * @internal
@@ -1818,7 +1865,7 @@ class SynchronisedStorageService {
1818
1865
  Guards.object(this.CLASS_NAME, "options", options);
1819
1866
  Guards.object(this.CLASS_NAME, "options.config", options.config);
1820
1867
  this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? "event-bus");
1821
- this._logging = LoggingConnectorFactory.getIfExists(options.loggingConnectorType ?? "logging");
1868
+ this._loggingComponent = ComponentFactory.getIfExists(options.loggingComponentType ?? "logging");
1822
1869
  this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? "vault");
1823
1870
  this._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get(options.syncSnapshotStorageConnectorType ?? "sync-snapshot-entry");
1824
1871
  this._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(options.verifiableStorageConnectorType ?? "verifiable-storage");
@@ -1847,10 +1894,10 @@ class SynchronisedStorageService {
1847
1894
  this._trustedSynchronisedStorageComponent =
1848
1895
  ComponentFactory.get(options.trustedSynchronisedStorageComponentType);
1849
1896
  }
1850
- this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, this._config.isTrustedNode);
1851
- this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._identityConnector, this._blobStorageHelper, this._config.synchronisedStorageMethodId);
1852
- this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
1853
- this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode, this._config.maxConsolidations);
1897
+ this._blobStorageHelper = new BlobStorageHelper(this._loggingComponent, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, this._config.isTrustedNode);
1898
+ this._changeSetHelper = new ChangeSetHelper(this._loggingComponent, this._eventBusComponent, this._identityConnector, this._blobStorageHelper, this._config.synchronisedStorageMethodId);
1899
+ this._localSyncStateHelper = new LocalSyncStateHelper(this._loggingComponent, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
1900
+ this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._loggingComponent, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode, this._config.maxConsolidations);
1854
1901
  this._serviceStarted = false;
1855
1902
  this._activeStorageKeys = {};
1856
1903
  this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
@@ -1935,7 +1982,7 @@ class SynchronisedStorageService {
1935
1982
  throw new GeneralError(this.CLASS_NAME, "notTrustedNode");
1936
1983
  }
1937
1984
  Guards.object(this.CLASS_NAME, "syncChangeSet", syncChangeSet);
1938
- await this._logging?.log({
1985
+ await this._loggingComponent?.log({
1939
1986
  level: "info",
1940
1987
  source: this.CLASS_NAME,
1941
1988
  message: "syncChangeSetForRemoteNode",
@@ -1964,7 +2011,7 @@ class SynchronisedStorageService {
1964
2011
  */
1965
2012
  async startEntitySync(storageKey) {
1966
2013
  try {
1967
- await this._logging?.log({
2014
+ await this._loggingComponent?.log({
1968
2015
  level: "info",
1969
2016
  source: this.CLASS_NAME,
1970
2017
  message: "startEntitySync",
@@ -1978,7 +2025,7 @@ class SynchronisedStorageService {
1978
2025
  await this.updateFromLocalSyncState(storageKey);
1979
2026
  }
1980
2027
  catch (error) {
1981
- await this._logging?.log({
2028
+ await this._loggingComponent?.log({
1982
2029
  level: "error",
1983
2030
  source: this.CLASS_NAME,
1984
2031
  message: "entitySyncFailed",
@@ -1993,7 +2040,7 @@ class SynchronisedStorageService {
1993
2040
  * @internal
1994
2041
  */
1995
2042
  async updateFromRemoteSyncState(storageKey) {
1996
- await this._logging?.log({
2043
+ await this._loggingComponent?.log({
1997
2044
  level: "info",
1998
2045
  source: this.CLASS_NAME,
1999
2046
  message: "updateFromRemoteSyncState",
@@ -2019,7 +2066,7 @@ class SynchronisedStorageService {
2019
2066
  * @internal
2020
2067
  */
2021
2068
  async updateFromLocalSyncState(storageKey) {
2022
- await this._logging?.log({
2069
+ await this._loggingComponent?.log({
2023
2070
  level: "info",
2024
2071
  source: this.CLASS_NAME,
2025
2072
  message: "updateFromLocalSyncState",
@@ -2033,7 +2080,7 @@ class SynchronisedStorageService {
2033
2080
  if (Is.arrayValue(localChangeSnapshot.changes)) {
2034
2081
  await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
2035
2082
  if (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {
2036
- await this._logging?.log({
2083
+ await this._loggingComponent?.log({
2037
2084
  level: "info",
2038
2085
  source: this.CLASS_NAME,
2039
2086
  message: "builtStorageChangeSetNone",
@@ -2043,7 +2090,7 @@ class SynchronisedStorageService {
2043
2090
  });
2044
2091
  }
2045
2092
  else {
2046
- await this._logging?.log({
2093
+ await this._loggingComponent?.log({
2047
2094
  level: "info",
2048
2095
  source: this.CLASS_NAME,
2049
2096
  message: "builtStorageChangeSet",
@@ -2063,7 +2110,7 @@ class SynchronisedStorageService {
2063
2110
  Is.object(syncChangeSet)) {
2064
2111
  // If we are not a trusted node, we need to send the changes to the trusted node
2065
2112
  // and then remove the local change snapshot
2066
- await this._logging?.log({
2113
+ await this._loggingComponent?.log({
2067
2114
  level: "info",
2068
2115
  source: this.CLASS_NAME,
2069
2116
  message: "sendingChangeSetToTrustedNode",
@@ -2079,7 +2126,7 @@ class SynchronisedStorageService {
2079
2126
  });
2080
2127
  }
2081
2128
  else {
2082
- await this._logging?.log({
2129
+ await this._loggingComponent?.log({
2083
2130
  level: "info",
2084
2131
  source: this.CLASS_NAME,
2085
2132
  message: "updateFromLocalSyncStateNoChanges",
@@ -2097,26 +2144,18 @@ class SynchronisedStorageService {
2097
2144
  * @internal
2098
2145
  */
2099
2146
  async startConsolidationSync(storageKey) {
2100
- let localChangeSnapshot;
2101
2147
  try {
2102
- // If we are performing a consolidation, we can remove the local change snapshot
2103
- // as we are going to create a complete changeset from the DB
2104
- const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
2105
- localChangeSnapshot = localChangeSnapshots[0];
2106
- if (!Is.empty(localChangeSnapshot)) {
2107
- await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
2108
- }
2148
+ // If we are going to perform a consolidation first take any local updates
2149
+ // we have and create a changeset from them, so that anybody applying
2150
+ // just changes since a consolidation can use the changeset
2151
+ // and skip the consolidation
2152
+ await this.updateFromLocalSyncState(storageKey);
2153
+ // Now start the consolidation
2109
2154
  await this._remoteSyncStateHelper.consolidationStart(storageKey, this._config.consolidationBatchSize ??
2110
2155
  SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
2111
- // The consolidation was successful, so we can remove the local change snapshot permanently
2112
- localChangeSnapshot = undefined;
2113
2156
  }
2114
2157
  catch (error) {
2115
- if (localChangeSnapshot) {
2116
- // If the consolidation failed, we can keep the local change snapshot
2117
- await this._localSyncStateHelper.setLocalChangeSnapshot(localChangeSnapshot);
2118
- }
2119
- await this._logging?.log({
2158
+ await this._loggingComponent?.log({
2120
2159
  level: "error",
2121
2160
  source: this.CLASS_NAME,
2122
2161
  message: "consolidationSyncFailed",
@@ -2130,7 +2169,7 @@ class SynchronisedStorageService {
2130
2169
  * @internal
2131
2170
  */
2132
2171
  async registerStorageKey(syncRegisterStorageKey) {
2133
- await this._logging?.log({
2172
+ await this._loggingComponent?.log({
2134
2173
  level: "info",
2135
2174
  source: this.CLASS_NAME,
2136
2175
  message: "registerStorageKey",
@@ -2152,7 +2191,7 @@ class SynchronisedStorageService {
2152
2191
  */
2153
2192
  async activateStorageKey(storageKey) {
2154
2193
  if (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {
2155
- await this._logging?.log({
2194
+ await this._loggingComponent?.log({
2156
2195
  level: "info",
2157
2196
  source: this.CLASS_NAME,
2158
2197
  message: "activateStorageKey",