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