@twin.org/synchronised-storage-service 0.0.1-next.2 → 0.0.1-next.3

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.
@@ -20,9 +20,9 @@ let SyncSnapshotEntry = class SyncSnapshotEntry {
20
20
  */
21
21
  id;
22
22
  /**
23
- * The schema type for the snapshot i.e. which entity is being synchronized.
23
+ * The storage key for the snapshot i.e. which entity is being synchronized.
24
24
  */
25
- schemaType;
25
+ storageKey;
26
26
  /**
27
27
  * The date the snapshot was created.
28
28
  */
@@ -42,7 +42,7 @@ let SyncSnapshotEntry = class SyncSnapshotEntry {
42
42
  /**
43
43
  * The changes that were made in this snapshot, if this is a local snapshot.
44
44
  */
45
- localChanges;
45
+ changes;
46
46
  };
47
47
  __decorate([
48
48
  property({ type: "string", isPrimary: true }),
@@ -51,7 +51,7 @@ __decorate([
51
51
  __decorate([
52
52
  property({ type: "string", isSecondary: true }),
53
53
  __metadata("design:type", String)
54
- ], SyncSnapshotEntry.prototype, "schemaType", void 0);
54
+ ], SyncSnapshotEntry.prototype, "storageKey", void 0);
55
55
  __decorate([
56
56
  property({ type: "string" }),
57
57
  __metadata("design:type", String)
@@ -71,7 +71,7 @@ __decorate([
71
71
  __decorate([
72
72
  property({ type: "array", itemType: "object", optional: true }),
73
73
  __metadata("design:type", Array)
74
- ], SyncSnapshotEntry.prototype, "localChanges", void 0);
74
+ ], SyncSnapshotEntry.prototype, "changes", void 0);
75
75
  SyncSnapshotEntry = __decorate([
76
76
  entity()
77
77
  ], SyncSnapshotEntry);
@@ -229,15 +229,14 @@ class ChangeSetHelper {
229
229
  /**
230
230
  * Apply a sync changeset.
231
231
  * @param changeSetStorageId The id of the sync changeset to apply.
232
- * @returns True if the change was applied.
232
+ * @returns The changeset if it existed.
233
233
  */
234
234
  async getAndApplyChangeset(changeSetStorageId) {
235
235
  const syncChangeset = await this.getAndVerifyChangeset(changeSetStorageId);
236
236
  if (!Is.empty(syncChangeset)) {
237
237
  await this.applyChangeset(syncChangeset);
238
- return true;
239
238
  }
240
- return false;
239
+ return syncChangeset;
241
240
  }
242
241
  /**
243
242
  * Apply a sync changeset.
@@ -264,7 +263,7 @@ class ChangeSetHelper {
264
263
  // so we need to restore it here.
265
264
  change.entity.nodeIdentity = syncChangeset.nodeIdentity;
266
265
  await this._eventBusComponent.publish(SynchronisedStorageTopics.RemoteItemSet, {
267
- schemaType: syncChangeset.schemaType,
266
+ storageKey: syncChangeset.storageKey,
268
267
  entity: change.entity
269
268
  });
270
269
  }
@@ -272,7 +271,7 @@ class ChangeSetHelper {
272
271
  case SyncChangeOperation.Delete:
273
272
  if (!Is.empty(change.id)) {
274
273
  await this._eventBusComponent.publish(SynchronisedStorageTopics.RemoteItemRemove, {
275
- schemaType: syncChangeset.schemaType,
274
+ storageKey: syncChangeset.storageKey,
276
275
  id: change.id
277
276
  });
278
277
  }
@@ -284,9 +283,10 @@ class ChangeSetHelper {
284
283
  /**
285
284
  * Store the changeset.
286
285
  * @param syncChangeSet The sync change set to store.
286
+ * @param nodeIdentity The node identity to use for the changeset.
287
287
  * @returns The id of the change set.
288
288
  */
289
- async storeChangeSet(syncChangeSet) {
289
+ async storeChangeSet(syncChangeSet, nodeIdentity) {
290
290
  await this._logging?.log({
291
291
  level: "info",
292
292
  source: this.CLASS_NAME,
@@ -300,7 +300,7 @@ class ChangeSetHelper {
300
300
  return this._blobStorageComponent.create(Converter.bytesToBase64(ObjectHelper.toBytes(syncChangeSet)), undefined, undefined, undefined, {
301
301
  disableEncryption: true,
302
302
  compress: BlobStorageCompressionType.Gzip
303
- });
303
+ }, undefined, nodeIdentity);
304
304
  }
305
305
  /**
306
306
  * Verify the proof of a sync changeset.
@@ -354,7 +354,7 @@ class ChangeSetHelper {
354
354
  delete changeSetWithoutProof.proof;
355
355
  const proof = await this._identityConnector.createProof(syncChangeset.nodeIdentity, DocumentHelper.joinId(syncChangeset.nodeIdentity, this._decentralisedStorageMethodId), ProofTypes.DataIntegrityProof, changeSetWithoutProof);
356
356
  await this._logging?.log({
357
- level: "error",
357
+ level: "info",
358
358
  source: this.CLASS_NAME,
359
359
  message: "createdChangeSetProof",
360
360
  data: {
@@ -404,33 +404,51 @@ class LocalSyncStateHelper {
404
404
  }
405
405
  /**
406
406
  * Add a new change to the local snapshot.
407
- * @param schemaType The schema type of the snapshot to add the change for.
407
+ * @param storageKey The storage key of the snapshot to add the change for.
408
408
  * @param operation The operation to perform.
409
409
  * @param id The id of the entity to add the change for.
410
410
  * @returns Nothing.
411
411
  */
412
- async addLocalChange(schemaType, operation, id) {
413
- const localChangeSnapshot = await this.getLocalChangeSnapshot(schemaType);
414
- localChangeSnapshot.localChanges ??= [];
412
+ async addLocalChange(storageKey, operation, id) {
413
+ await this._logging?.log({
414
+ level: "info",
415
+ source: this.CLASS_NAME,
416
+ message: "addLocalChange",
417
+ data: {
418
+ storageKey,
419
+ operation,
420
+ id
421
+ }
422
+ });
423
+ const localChangeSnapshot = await this.getLocalChangeSnapshot(storageKey);
424
+ localChangeSnapshot.changes ??= [];
415
425
  // If we already have a change for this id we are
416
426
  // about to supersede it, we remove the previous change
417
427
  // to avoid having multiple changes for the same id
418
- const previousChangeIndex = localChangeSnapshot.localChanges.findIndex(change => change.id === id);
428
+ const previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);
419
429
  if (previousChangeIndex !== -1) {
420
- localChangeSnapshot.localChanges.splice(previousChangeIndex, 1);
430
+ localChangeSnapshot.changes.splice(previousChangeIndex, 1);
421
431
  }
422
- if (localChangeSnapshot.localChanges.length > 0) {
432
+ if (localChangeSnapshot.changes.length > 0) {
423
433
  localChangeSnapshot.dateModified = new Date(Date.now()).toISOString();
424
434
  }
425
- localChangeSnapshot.localChanges.push({ operation, id });
435
+ localChangeSnapshot.changes.push({ operation, id });
426
436
  await this.setLocalChangeSnapshot(localChangeSnapshot);
427
437
  }
428
438
  /**
429
439
  * Get the current local snapshot.
430
- * @param schemaType The schema type of the snapshot to get.
440
+ * @param storageKey The storage key of the snapshot to get.
431
441
  * @returns The local snapshot entry.
432
442
  */
433
- async getLocalChangeSnapshot(schemaType) {
443
+ async getLocalChangeSnapshot(storageKey) {
444
+ await this._logging?.log({
445
+ level: "info",
446
+ source: this.CLASS_NAME,
447
+ message: "getLocalChangeSnapshot",
448
+ data: {
449
+ storageKey
450
+ }
451
+ });
434
452
  const queryResult = await this._localSyncSnapshotEntryEntityStorage.query({
435
453
  conditions: [
436
454
  {
@@ -439,18 +457,34 @@ class LocalSyncStateHelper {
439
457
  comparison: ComparisonOperator.Equals
440
458
  },
441
459
  {
442
- property: "schemaType",
443
- value: schemaType,
460
+ property: "storageKey",
461
+ value: storageKey,
444
462
  comparison: ComparisonOperator.Equals
445
463
  }
446
464
  ]
447
465
  });
448
466
  if (queryResult.entities.length > 0) {
467
+ await this._logging?.log({
468
+ level: "info",
469
+ source: this.CLASS_NAME,
470
+ message: "localChangeSnapshotExists",
471
+ data: {
472
+ storageKey
473
+ }
474
+ });
449
475
  return queryResult.entities[0];
450
476
  }
477
+ await this._logging?.log({
478
+ level: "info",
479
+ source: this.CLASS_NAME,
480
+ message: "localChangeSnapshotDoesNotExist",
481
+ data: {
482
+ storageKey
483
+ }
484
+ });
451
485
  return {
452
486
  id: Converter.bytesToHex(RandomHelper.generate(32)),
453
- schemaType,
487
+ storageKey,
454
488
  dateCreated: new Date(Date.now()).toISOString(),
455
489
  changeSetStorageIds: [],
456
490
  isLocalSnapshot: true
@@ -462,6 +496,14 @@ class LocalSyncStateHelper {
462
496
  * @returns Nothing.
463
497
  */
464
498
  async setLocalChangeSnapshot(localChangeSnapshot) {
499
+ await this._logging?.log({
500
+ level: "info",
501
+ source: this.CLASS_NAME,
502
+ message: "setLocalChangeSnapshot",
503
+ data: {
504
+ storageKey: localChangeSnapshot.storageKey
505
+ }
506
+ });
465
507
  await this._localSyncSnapshotEntryEntityStorage.set(localChangeSnapshot);
466
508
  }
467
509
  /**
@@ -482,11 +524,11 @@ class LocalSyncStateHelper {
482
524
  }
483
525
  /**
484
526
  * Sync local data using a remote sync state.
485
- * @param schemaType The schema type of the snapshot to sync with.
527
+ * @param storageKey The storage key of the snapshot to sync with.
486
528
  * @param remoteSyncState The sync state to sync with.
487
529
  * @returns Nothing.
488
530
  */
489
- async syncFromRemote(schemaType, remoteSyncState) {
531
+ async syncFromRemote(storageKey, remoteSyncState) {
490
532
  await this._logging?.log({
491
533
  level: "info",
492
534
  source: this.CLASS_NAME,
@@ -512,7 +554,7 @@ class LocalSyncStateHelper {
512
554
  const localSnapshot = await this._localSyncSnapshotEntryEntityStorage.get(remoteSnapshot.id);
513
555
  const remoteSnapshotWithContext = {
514
556
  ...remoteSnapshot,
515
- schemaType
557
+ storageKey
516
558
  };
517
559
  if (Is.empty(localSnapshot)) {
518
560
  // We don't have the snapshot locally, so we need to process it
@@ -631,12 +673,12 @@ class RemoteSyncStateHelper {
631
673
  */
632
674
  _changeSetHelper;
633
675
  /**
634
- * The storage ids of the batch responses for each schema type.
676
+ * The storage ids of the batch responses for each storage key.
635
677
  * @internal
636
678
  */
637
679
  _batchResponseStorageIds;
638
680
  /**
639
- * The full changes for each schema type.
681
+ * The full changes for each storage key.
640
682
  * @internal
641
683
  */
642
684
  _populateFullChanges;
@@ -684,52 +726,73 @@ class RemoteSyncStateHelper {
684
726
  }
685
727
  /**
686
728
  * Create and store a change set.
687
- * @param schemaType The schema type of the change set.
729
+ * @param storageKey The storage key of the change set.
688
730
  * @param changes The changes to apply.
689
731
  * @param completeCallback The callback to call when the changeset is created and stored.
690
732
  * @returns The storage id of the change set if created.
691
733
  */
692
- async createAndStoreChangeSet(schemaType, changes, completeCallback) {
693
- if (Is.arrayValue(changes)) {
694
- this._populateFullChanges[schemaType] = {
695
- changes,
696
- entities: {},
697
- requestIds: [],
698
- completeCallback: async () => this.finaliseFullChanges(schemaType, completeCallback)
699
- };
700
- const setChanges = changes.filter(c => c.operation === SyncChangeOperation.Set);
701
- if (setChanges.length === 0) {
702
- // If we don't need to request any full details, we can just call the complete callback
703
- await this.finaliseFullChanges(schemaType, completeCallback);
704
- }
705
- else {
706
- // Otherwise we need to request the full details for each change
707
- this._populateFullChanges[schemaType].requestIds = setChanges.map(change => change.id);
708
- // Once all the requests are handled the callback will be called
709
- for (const change of setChanges) {
710
- // Create a request for each change to populate the full details
711
- this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemRequest, {
712
- schemaType,
713
- id: change.id
714
- });
715
- }
734
+ async createAndStoreChangeSet(storageKey, changes, completeCallback) {
735
+ await this._logging?.log({
736
+ level: "info",
737
+ source: this.CLASS_NAME,
738
+ message: "createAndStoreChangeSet",
739
+ data: {
740
+ storageKey,
741
+ changeCount: changes.length
716
742
  }
743
+ });
744
+ this._populateFullChanges[storageKey] = {
745
+ changes,
746
+ entities: {},
747
+ requestIds: [],
748
+ completeCallback: async () => this.finaliseFullChanges(storageKey, completeCallback)
749
+ };
750
+ const setChanges = changes.filter(c => c.operation === SyncChangeOperation.Set);
751
+ if (setChanges.length === 0) {
752
+ // If we don't need to request any full details, we can just call the complete callback
753
+ await this.finaliseFullChanges(storageKey, completeCallback);
717
754
  }
718
755
  else {
719
- await completeCallback();
756
+ // Otherwise we need to request the full details for each change
757
+ this._populateFullChanges[storageKey].requestIds = setChanges.map(change => change.id);
758
+ // Once all the requests are handled the callback will be called
759
+ for (const change of setChanges) {
760
+ // Create a request for each change to populate the full details
761
+ await this._logging?.log({
762
+ level: "info",
763
+ source: this.CLASS_NAME,
764
+ message: "createChangeSetRequestingItem",
765
+ data: {
766
+ storageKey,
767
+ id: change.id
768
+ }
769
+ });
770
+ this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemRequest, {
771
+ storageKey,
772
+ id: change.id
773
+ });
774
+ }
720
775
  }
721
776
  }
722
777
  /**
723
778
  * Finalise the full details for the sync change set.
724
- * @param schemaType The schema type of the change set.
779
+ * @param storageKey The storage key of the change set.
725
780
  * @param completeCallback The callback to call when the changeset is populated.
726
781
  * @returns Nothing.
727
782
  */
728
- async finaliseFullChanges(schemaType, completeCallback) {
783
+ async finaliseFullChanges(storageKey, completeCallback) {
784
+ await this._logging?.log({
785
+ level: "info",
786
+ source: this.CLASS_NAME,
787
+ message: "finalisingSyncChanges",
788
+ data: {
789
+ storageKey
790
+ }
791
+ });
729
792
  if (Is.stringValue(this._nodeIdentity)) {
730
- const changes = this._populateFullChanges[schemaType].changes;
793
+ const changes = this._populateFullChanges[storageKey].changes;
731
794
  for (const change of changes) {
732
- change.entity = this._populateFullChanges[schemaType].entities[change.id] ?? change.entity;
795
+ change.entity = this._populateFullChanges[storageKey].entities[change.id] ?? change.entity;
733
796
  if (change.operation === SyncChangeOperation.Set && Is.objectValue(change.entity)) {
734
797
  // Remove the node identity as the changeset has this stored at the top level
735
798
  // and we do not want to store it in the change itself to reduce redundancy
@@ -740,15 +803,29 @@ class RemoteSyncStateHelper {
740
803
  const syncChangeSet = {
741
804
  id: Converter.bytesToHex(RandomHelper.generate(32)),
742
805
  dateCreated: new Date(Date.now()).toISOString(),
743
- schemaType,
806
+ storageKey,
744
807
  changes,
745
808
  nodeIdentity: this._nodeIdentity
746
809
  };
747
- // And sign it with the node identity
748
- syncChangeSet.proof = await this._changeSetHelper.createChangeSetProof(syncChangeSet);
749
- // Store the changeset in the blob storage
750
- const changeSetStorageId = await this._changeSetHelper.storeChangeSet(syncChangeSet);
751
- await completeCallback(changeSetStorageId);
810
+ try {
811
+ // And sign it with the node identity
812
+ syncChangeSet.proof = await this._changeSetHelper.createChangeSetProof(syncChangeSet);
813
+ // Store the changeset in the blob storage
814
+ const changeSetStorageId = await this._changeSetHelper.storeChangeSet(syncChangeSet, this._nodeIdentity);
815
+ await completeCallback(changeSetStorageId);
816
+ }
817
+ catch (err) {
818
+ await this._logging?.log({
819
+ level: "error",
820
+ source: this.CLASS_NAME,
821
+ message: "finalisingSyncChangesFailed",
822
+ data: {
823
+ storageKey
824
+ },
825
+ error: BaseError.fromError(err)
826
+ });
827
+ await completeCallback();
828
+ }
752
829
  }
753
830
  else {
754
831
  await completeCallback();
@@ -756,15 +833,25 @@ class RemoteSyncStateHelper {
756
833
  }
757
834
  /**
758
835
  * Add a new changeset into the sync state.
836
+ * @param storageKey The storage key of the change set to add.
759
837
  * @param changeSetStorageId The id of the change set to add the the current state
760
838
  * @returns Nothing.
761
839
  */
762
- async addChangeSetToSyncState(changeSetStorageId) {
763
- // First load the current sync state if there is one
764
- const syncStatePointer = await this.getVerifiableSyncPointer();
840
+ async addChangeSetToSyncState(storageKey, changeSetStorageId) {
841
+ await this._logging?.log({
842
+ level: "info",
843
+ source: this.CLASS_NAME,
844
+ message: "addChangeSetToSyncState",
845
+ data: {
846
+ storageKey,
847
+ changeSetStorageId
848
+ }
849
+ });
850
+ // First load the sync pointer store to get the current sync pointer for the storage key
851
+ const syncPointerStore = await this.getVerifiableSyncPointerStore();
765
852
  let syncState;
766
- if (!Is.empty(syncStatePointer?.syncPointerId)) {
767
- syncState = await this.getRemoteSyncState(syncStatePointer.syncPointerId);
853
+ if (!Is.empty(syncPointerStore.syncPointers[storageKey])) {
854
+ syncState = await this.getRemoteSyncState(syncPointerStore.syncPointers[storageKey]);
768
855
  }
769
856
  // No current sync state, so we create a new one
770
857
  if (Is.empty(syncState)) {
@@ -789,34 +876,34 @@ class RemoteSyncStateHelper {
789
876
  // Add the changeset storage id to the current snapshot
790
877
  currentSnapshot.changeSetStorageIds.push(changeSetStorageId);
791
878
  // Store the sync state in the blob storage
792
- const syncStateId = await this.storeRemoteSyncState(syncState);
793
- // Store the verifiable sync pointer in the verifiable storage
794
- await this.storeVerifiableSyncPointer(syncStateId);
879
+ syncPointerStore.syncPointers[storageKey] = await this.storeRemoteSyncState(syncState);
880
+ // Store the verifiable sync pointer store in the verifiable storage
881
+ await this.storeVerifiableSyncPointerStore(syncPointerStore);
795
882
  }
796
883
  /**
797
884
  * Create a consolidated snapshot for the entire storage.
798
- * @param schemaType The schema type of the snapshot to create.
885
+ * @param storageKey The storage key of the snapshot to create.
799
886
  * @param batchSize The batch size to use for consolidation.
800
887
  * @returns Nothing.
801
888
  */
802
- async consolidateFromLocal(schemaType, batchSize) {
889
+ async consolidateFromLocal(storageKey, batchSize) {
803
890
  await this._logging?.log({
804
891
  level: "info",
805
892
  source: this.CLASS_NAME,
806
893
  message: "consolidationStarting"
807
894
  });
808
- await this._eventBusComponent.publish(SynchronisedStorageTopics.BatchRequest, { schemaType, batchSize });
895
+ await this._eventBusComponent.publish(SynchronisedStorageTopics.BatchRequest, { storageKey, batchSize });
809
896
  }
810
897
  /**
811
- * Get the sync pointer.
812
- * @returns The sync pointer.
898
+ * Get the sync pointer store.
899
+ * @returns The sync pointer store.
813
900
  */
814
- async getVerifiableSyncPointer() {
901
+ async getVerifiableSyncPointerStore() {
815
902
  try {
816
903
  await this._logging?.log({
817
904
  level: "info",
818
905
  source: this.CLASS_NAME,
819
- message: "verifiableSyncPointerRetrieving",
906
+ message: "verifiableSyncPointerStoreRetrieving",
820
907
  data: {
821
908
  key: this._synchronisedStorageKey
822
909
  }
@@ -827,10 +914,9 @@ class RemoteSyncStateHelper {
827
914
  await this._logging?.log({
828
915
  level: "info",
829
916
  source: this.CLASS_NAME,
830
- message: "verifiableSyncPointerRetrieved",
917
+ message: "verifiableSyncPointerStoreRetrieved",
831
918
  data: {
832
- key: this._synchronisedStorageKey,
833
- syncPointerId: syncPointer.syncPointerId
919
+ key: this._synchronisedStorageKey
834
920
  }
835
921
  });
836
922
  return syncPointer;
@@ -844,34 +930,34 @@ class RemoteSyncStateHelper {
844
930
  await this._logging?.log({
845
931
  level: "info",
846
932
  source: this.CLASS_NAME,
847
- message: "verifiableSyncPointerNotFound",
933
+ message: "verifiableSyncPointerStoreNotFound",
848
934
  data: {
849
935
  key: this._synchronisedStorageKey
850
936
  }
851
937
  });
938
+ // If no sync pointer store exists, we return an empty one
939
+ return {
940
+ syncPointers: {}
941
+ };
852
942
  }
853
943
  /**
854
944
  * Store the verifiable sync pointer in the verifiable storage.
855
- * @param syncStateId The id of the sync state to store.
945
+ * @param syncPointerStore The sync pointer store to store.
856
946
  * @returns Nothing.
857
947
  */
858
- async storeVerifiableSyncPointer(syncStateId) {
859
- // Create a new verifiable sync pointer object pointing to the sync state
860
- const verifiableSyncPointer = {
861
- syncPointerId: syncStateId
862
- };
863
- await this._logging?.log({
864
- level: "info",
865
- source: this.CLASS_NAME,
866
- message: "verifiableSyncPointerStoring",
867
- data: {
868
- key: this._synchronisedStorageKey,
869
- syncPointerId: verifiableSyncPointer.syncPointerId
870
- }
871
- });
872
- // Store the verifiable sync pointer in the verifiable storage
873
- await this._verifiableSyncPointerStorageConnector.create(this._synchronisedStorageKey, ObjectHelper.toBytes(verifiableSyncPointer));
874
- return verifiableSyncPointer;
948
+ async storeVerifiableSyncPointerStore(syncPointerStore) {
949
+ if (this._nodeIdentity) {
950
+ await this._logging?.log({
951
+ level: "info",
952
+ source: this.CLASS_NAME,
953
+ message: "verifiableSyncPointerStoreStoring",
954
+ data: {
955
+ key: this._synchronisedStorageKey
956
+ }
957
+ });
958
+ // Store the verifiable sync pointer in the verifiable storage
959
+ await this._verifiableSyncPointerStorageConnector.update(this._nodeIdentity, this._synchronisedStorageKey, ObjectHelper.toBytes(syncPointerStore));
960
+ }
875
961
  }
876
962
  /**
877
963
  * Store the remote sync state.
@@ -889,7 +975,7 @@ class RemoteSyncStateHelper {
889
975
  });
890
976
  // We don't want to encrypt the sync state as no other nodes would be able to read it
891
977
  // the blob storage also needs to be publicly accessible so that other nodes can retrieve it
892
- return this._blobStorageComponent.create(Converter.bytesToBase64(ObjectHelper.toBytes(syncState)), undefined, undefined, undefined, { disableEncryption: true, compress: BlobStorageCompressionType.Gzip });
978
+ return this._blobStorageComponent.create(Converter.bytesToBase64(ObjectHelper.toBytes(syncState)), undefined, undefined, undefined, { disableEncryption: true, compress: BlobStorageCompressionType.Gzip }, undefined, this._nodeIdentity);
893
979
  }
894
980
  /**
895
981
  * Get the remote sync state.
@@ -908,7 +994,7 @@ class RemoteSyncStateHelper {
908
994
  });
909
995
  const blobEntry = await this._blobStorageComponent.get(syncPointerId, {
910
996
  includeContent: true
911
- });
997
+ }, undefined, this._nodeIdentity);
912
998
  if (Is.stringBase64(blobEntry.blob)) {
913
999
  const syncState = ObjectHelper.fromBytes(Converter.base64ToBytes(blobEntry.blob));
914
1000
  await this._logging?.log({
@@ -951,28 +1037,31 @@ class RemoteSyncStateHelper {
951
1037
  operation: SyncChangeOperation.Set,
952
1038
  id: change[response.primaryKey]
953
1039
  })),
954
- schemaType: response.schemaType,
1040
+ storageKey: response.storageKey,
955
1041
  nodeIdentity: this._nodeIdentity
956
1042
  };
957
1043
  // And sign it with the node identity
958
1044
  syncChangeSet.proof = await this._changeSetHelper.createChangeSetProof(syncChangeSet);
959
1045
  // Store the changeset in the blob storage
960
- const changeSetStorageId = await this._changeSetHelper.storeChangeSet(syncChangeSet);
1046
+ const changeSetStorageId = await this._changeSetHelper.storeChangeSet(syncChangeSet, this._nodeIdentity);
961
1047
  // Add the changeset storage id to the snapshot ids
962
- this._batchResponseStorageIds[response.schemaType] ??= [];
963
- this._batchResponseStorageIds[response.schemaType].push(changeSetStorageId);
1048
+ this._batchResponseStorageIds[response.storageKey] ??= [];
1049
+ this._batchResponseStorageIds[response.storageKey].push(changeSetStorageId);
964
1050
  if (response.lastEntry) {
965
1051
  const syncState = { snapshots: [] };
966
1052
  const batchSnapshot = {
967
1053
  id: Converter.bytesToHex(RandomHelper.generate(32)),
968
1054
  dateCreated: new Date(Date.now()).toISOString(),
969
- changeSetStorageIds: this._batchResponseStorageIds[response.schemaType]
1055
+ changeSetStorageIds: this._batchResponseStorageIds[response.storageKey]
970
1056
  };
971
1057
  syncState.snapshots.push(batchSnapshot);
972
1058
  // Store the sync state in the blob storage
973
1059
  const syncStateId = await this.storeRemoteSyncState(syncState);
1060
+ // Get the current sync pointer store
1061
+ const syncPointerStore = await this.getVerifiableSyncPointerStore();
1062
+ syncPointerStore.syncPointers[response.storageKey] = syncStateId;
974
1063
  // Store the verifiable sync pointer in the verifiable storage
975
- await this.storeVerifiableSyncPointer(syncStateId);
1064
+ await this.storeVerifiableSyncPointerStore(syncPointerStore);
976
1065
  await this._logging?.log({
977
1066
  level: "info",
978
1067
  source: this.CLASS_NAME,
@@ -986,13 +1075,22 @@ class RemoteSyncStateHelper {
986
1075
  * @param response The item response to handle.
987
1076
  */
988
1077
  async handleLocalItemResponse(response) {
989
- if (!Is.empty(this._populateFullChanges[response.schemaType])) {
990
- const idx = this._populateFullChanges[response.schemaType].requestIds.indexOf(response.id);
1078
+ await this._logging?.log({
1079
+ level: "info",
1080
+ source: this.CLASS_NAME,
1081
+ message: "createChangeSetRespondingItem",
1082
+ data: {
1083
+ storageKey: response.storageKey,
1084
+ id: response.id
1085
+ }
1086
+ });
1087
+ if (!Is.empty(this._populateFullChanges[response.storageKey])) {
1088
+ const idx = this._populateFullChanges[response.storageKey].requestIds.indexOf(response.id);
991
1089
  if (idx !== -1) {
992
- this._populateFullChanges[response.schemaType].requestIds.splice(idx, 1);
993
- this._populateFullChanges[response.schemaType].entities[response.id] = response.entity;
994
- if (this._populateFullChanges[response.schemaType].requestIds.length === 0) {
995
- await this._populateFullChanges[response.schemaType].completeCallback();
1090
+ this._populateFullChanges[response.storageKey].requestIds.splice(idx, 1);
1091
+ this._populateFullChanges[response.storageKey].entities[response.id] = response.entity;
1092
+ if (this._populateFullChanges[response.storageKey].requestIds.length === 0) {
1093
+ await this._populateFullChanges[response.storageKey].completeCallback();
996
1094
  }
997
1095
  }
998
1096
  }
@@ -1004,20 +1102,20 @@ class RemoteSyncStateHelper {
1004
1102
  */
1005
1103
  class SynchronisedStorageService {
1006
1104
  /**
1007
- * The default interval to check for entity updates, defaults to 5 mins.
1105
+ * The default interval to check for entity updates.
1008
1106
  * @internal
1009
1107
  */
1010
- static _DEFAULT_ENTITY_UPDATE_INTERVAL_MS = 300000;
1108
+ static _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES = 5;
1011
1109
  /**
1012
- * The default interval to perform consolidation, defaults to 60 mins.
1110
+ * The default interval to perform consolidation.
1013
1111
  * @internal
1014
1112
  */
1015
- static _DEFAULT_CONSOLIDATION_INTERVAL_MS = 3600000;
1113
+ static _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES = 60;
1016
1114
  /**
1017
1115
  * The default size of a consolidation batch.
1018
1116
  * @internal
1019
1117
  */
1020
- static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 1000;
1118
+ static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
1021
1119
  /**
1022
1120
  * Runtime name for the class.
1023
1121
  */
@@ -1052,6 +1150,11 @@ class SynchronisedStorageService {
1052
1150
  * @internal
1053
1151
  */
1054
1152
  _identityConnector;
1153
+ /**
1154
+ * The task scheduler component.
1155
+ * @internal
1156
+ */
1157
+ _taskSchedulerComponent;
1055
1158
  /**
1056
1159
  * The synchronised storage service to use when this is not a trusted node.
1057
1160
  * @internal
@@ -1078,15 +1181,15 @@ class SynchronisedStorageService {
1078
1181
  */
1079
1182
  _config;
1080
1183
  /**
1081
- * The timer ids for checking for entity updates.
1184
+ * The flag to determine if the service has been started.
1082
1185
  * @internal
1083
1186
  */
1084
- _entityUpdateTimers;
1187
+ _serviceStarted;
1085
1188
  /**
1086
- * The timer ids for consolidation.
1189
+ * The active storage keys for the synchronised storage service.
1087
1190
  * @internal
1088
1191
  */
1089
- _consolidationTimers;
1192
+ _activeStorageKeys;
1090
1193
  /**
1091
1194
  * The identity of the node this connector is running on.
1092
1195
  * @internal
@@ -1105,14 +1208,15 @@ class SynchronisedStorageService {
1105
1208
  this._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(options.verifiableStorageConnectorType ?? "verifiable-storage");
1106
1209
  this._blobStorageComponent = ComponentFactory.get(options.blobStorageComponentType ?? "blob-storage");
1107
1210
  this._identityConnector = IdentityConnectorFactory.get(options.identityConnectorType ?? "identity");
1211
+ this._taskSchedulerComponent = ComponentFactory.get(options.taskSchedulerComponentType ?? "task-scheduler");
1108
1212
  this._config = {
1109
1213
  synchronisedStorageKey: options.config.synchronisedStorageKey,
1110
1214
  synchronisedStorageMethodId: options.config.synchronisedStorageMethodId ?? "synchronised-storage-assertion",
1111
- entityUpdateIntervalMs: options.config.entityUpdateIntervalMs ??
1112
- SynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MS,
1215
+ entityUpdateIntervalMinutes: options.config.entityUpdateIntervalMinutes ??
1216
+ SynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,
1113
1217
  isTrustedNode: options.config.isTrustedNode ?? false,
1114
- consolidationIntervalMs: options.config.consolidationIntervalMs ??
1115
- SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MS,
1218
+ consolidationIntervalMinutes: options.config.consolidationIntervalMinutes ??
1219
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
1116
1220
  consolidationBatchSize: options.config.consolidationBatchSize ??
1117
1221
  SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE
1118
1222
  };
@@ -1126,10 +1230,10 @@ class SynchronisedStorageService {
1126
1230
  this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._blobStorageComponent, this._identityConnector, this._config.synchronisedStorageMethodId);
1127
1231
  this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
1128
1232
  this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._blobStorageComponent, this._verifiableSyncPointerStorageConnector, this._changeSetHelper, this._config.synchronisedStorageKey);
1129
- this._consolidationTimers = {};
1130
- this._entityUpdateTimers = {};
1131
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterSchemaType, async (event) => this.registerType(event.data));
1132
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => this._localSyncStateHelper.addLocalChange(event.data.schemaType, event.data.operation, event.data.id));
1233
+ this._serviceStarted = false;
1234
+ this._activeStorageKeys = {};
1235
+ this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerType(event.data));
1236
+ this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id));
1133
1237
  }
1134
1238
  /**
1135
1239
  * The component needs to be started when the node is initialized.
@@ -1141,6 +1245,11 @@ class SynchronisedStorageService {
1141
1245
  async start(nodeIdentity, nodeLoggingConnectorType, componentState) {
1142
1246
  this._nodeIdentity = nodeIdentity;
1143
1247
  this._remoteSyncStateHelper.setNodeIdentity(nodeIdentity);
1248
+ this._serviceStarted = true;
1249
+ // If there are already storage keys registered, we need to activate them
1250
+ for (const storageKey in this._activeStorageKeys) {
1251
+ await this.activateStorageKey(storageKey);
1252
+ }
1144
1253
  }
1145
1254
  /**
1146
1255
  * The component needs to be stopped when the node is closed.
@@ -1150,13 +1259,10 @@ class SynchronisedStorageService {
1150
1259
  * @returns Nothing.
1151
1260
  */
1152
1261
  async stop(nodeIdentity, nodeLoggingConnectorType, componentState) {
1153
- for (const schemaType in this._entityUpdateTimers) {
1154
- clearTimeout(this._entityUpdateTimers[schemaType]);
1155
- delete this._entityUpdateTimers[schemaType];
1156
- }
1157
- for (const schemaType in this._consolidationTimers) {
1158
- clearTimeout(this._consolidationTimers[schemaType]);
1159
- delete this._consolidationTimers[schemaType];
1262
+ for (const storageKey in this._activeStorageKeys) {
1263
+ this._activeStorageKeys[storageKey] = false;
1264
+ this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);
1265
+ this._taskSchedulerComponent.removeTask(`synchronised-storage-consolidation-${storageKey}`);
1160
1266
  }
1161
1267
  }
1162
1268
  /**
@@ -1177,21 +1283,29 @@ class SynchronisedStorageService {
1177
1283
  // This will be performed using rights-management
1178
1284
  const changeSet = await this._changeSetHelper.getAndApplyChangeset(changeSetStorageId);
1179
1285
  if (!Is.empty(changeSet)) {
1180
- await this._remoteSyncStateHelper.addChangeSetToSyncState(changeSetStorageId);
1286
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(changeSet.storageKey, changeSetStorageId);
1181
1287
  }
1182
1288
  }
1183
1289
  /**
1184
1290
  * Start the sync with further updates after an interval.
1185
- * @param schemaType The schema type to sync.
1291
+ * @param storageKey The storage key to sync.
1186
1292
  * @returns Nothing.
1187
1293
  * @internal
1188
1294
  */
1189
- async startEntitySync(schemaType) {
1295
+ async startEntitySync(storageKey) {
1190
1296
  try {
1297
+ await this._logging?.log({
1298
+ level: "info",
1299
+ source: this.CLASS_NAME,
1300
+ message: "startEntitySync",
1301
+ data: {
1302
+ storageKey
1303
+ }
1304
+ });
1191
1305
  // First we check for remote changes
1192
- await this.updateFromRemoteSyncState(schemaType);
1306
+ await this.updateFromRemoteSyncState(storageKey);
1193
1307
  // Now send any updates we have to the remote storage
1194
- await this.updateFromLocalSyncState(schemaType);
1308
+ await this.updateFromLocalSyncState(storageKey);
1195
1309
  }
1196
1310
  catch (error) {
1197
1311
  await this._logging?.log({
@@ -1201,27 +1315,31 @@ class SynchronisedStorageService {
1201
1315
  error: BaseError.fromError(error)
1202
1316
  });
1203
1317
  }
1204
- finally {
1205
- // Set a timer to check for updates again
1206
- this._entityUpdateTimers[schemaType] = setTimeout(async () => this.startEntitySync(schemaType), this._config.entityUpdateIntervalMs);
1207
- }
1208
1318
  }
1209
1319
  /**
1210
1320
  * Check for updates in the remote storage.
1211
- * @param schemaType The schema type to check for updates.
1321
+ * @param storageKey The storage key to check for updates.
1212
1322
  * @returns Nothing.
1213
1323
  * @internal
1214
1324
  */
1215
- async updateFromRemoteSyncState(schemaType) {
1216
- // Get the verifiable sync pointer from the verifiable storage
1217
- const verifiableSyncPointer = await this._remoteSyncStateHelper.getVerifiableSyncPointer();
1218
- if (!Is.empty(verifiableSyncPointer)) {
1325
+ async updateFromRemoteSyncState(storageKey) {
1326
+ await this._logging?.log({
1327
+ level: "info",
1328
+ source: this.CLASS_NAME,
1329
+ message: "updateFromRemoteSyncState",
1330
+ data: {
1331
+ storageKey
1332
+ }
1333
+ });
1334
+ // Get the verifiable sync pointer store from the verifiable storage
1335
+ const verifiableSyncPointerStore = await this._remoteSyncStateHelper.getVerifiableSyncPointerStore();
1336
+ if (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
1219
1337
  // Load the sync state from the remote blob storage using the sync pointer
1220
1338
  // to load the sync state
1221
- const remoteSyncState = await this._remoteSyncStateHelper.getRemoteSyncState(verifiableSyncPointer.syncPointerId);
1339
+ const remoteSyncState = await this._remoteSyncStateHelper.getRemoteSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
1222
1340
  // If we got the sync state we can try and sync from it
1223
1341
  if (!Is.undefined(remoteSyncState)) {
1224
- await this._localSyncStateHelper.syncFromRemote(schemaType, remoteSyncState);
1342
+ await this._localSyncStateHelper.syncFromRemote(storageKey, remoteSyncState);
1225
1343
  }
1226
1344
  }
1227
1345
  }
@@ -1230,43 +1348,78 @@ class SynchronisedStorageService {
1230
1348
  * @returns Nothing.
1231
1349
  * @internal
1232
1350
  */
1233
- async updateFromLocalSyncState(schemaType) {
1234
- if (Is.stringValue(this._nodeIdentity)) {
1235
- // Ge the current local change snapshot
1236
- const localChangeSnapshot = await this._localSyncStateHelper.getLocalChangeSnapshot(schemaType);
1237
- if (!Is.empty(localChangeSnapshot)) {
1238
- await this._remoteSyncStateHelper.createAndStoreChangeSet(schemaType, localChangeSnapshot.localChanges, async (changeSetStorageId) => {
1239
- if (Is.stringValue(changeSetStorageId)) {
1240
- // Send the local changes to the remote storage if we are a trusted node
1241
- if (this._config.isTrustedNode) {
1242
- await this._remoteSyncStateHelper.addChangeSetToSyncState(changeSetStorageId);
1243
- }
1244
- else if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
1245
- // If we are not a trusted node, we need to send the changes to the trusted node
1246
- await this._trustedSynchronisedStorageComponent.syncChangeSet(changeSetStorageId);
1351
+ async updateFromLocalSyncState(storageKey) {
1352
+ await this._logging?.log({
1353
+ level: "info",
1354
+ source: this.CLASS_NAME,
1355
+ message: "updateFromLocalSyncState",
1356
+ data: {
1357
+ storageKey
1358
+ }
1359
+ });
1360
+ const localChangeSnapshot = await this._localSyncStateHelper.getLocalChangeSnapshot(storageKey);
1361
+ if (Is.arrayValue(localChangeSnapshot.changes)) {
1362
+ await this._remoteSyncStateHelper.createAndStoreChangeSet(storageKey, localChangeSnapshot.changes, async (changeSetStorageId) => {
1363
+ if (Is.stringValue(changeSetStorageId)) {
1364
+ await this._logging?.log({
1365
+ level: "info",
1366
+ source: this.CLASS_NAME,
1367
+ message: "createdStorageChangeSet",
1368
+ data: {
1369
+ storageKey,
1370
+ changeSetStorageId
1247
1371
  }
1372
+ });
1373
+ // Send the local changes to the remote storage if we are a trusted node
1374
+ if (this._config.isTrustedNode) {
1375
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
1248
1376
  await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
1249
1377
  }
1250
- });
1251
- }
1378
+ else if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
1379
+ // If we are not a trusted node, we need to send the changes to the trusted node
1380
+ await this._trustedSynchronisedStorageComponent.syncChangeSet(changeSetStorageId);
1381
+ await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
1382
+ }
1383
+ }
1384
+ else {
1385
+ await this._logging?.log({
1386
+ level: "info",
1387
+ source: this.CLASS_NAME,
1388
+ message: "createdStorageChangeSetNone",
1389
+ data: {
1390
+ storageKey
1391
+ }
1392
+ });
1393
+ }
1394
+ });
1395
+ }
1396
+ else {
1397
+ await this._logging?.log({
1398
+ level: "info",
1399
+ source: this.CLASS_NAME,
1400
+ message: "updateFromLocalSyncStateNoChanges",
1401
+ data: {
1402
+ storageKey
1403
+ }
1404
+ });
1252
1405
  }
1253
1406
  }
1254
1407
  /**
1255
1408
  * Start the consolidation sync.
1256
- * @param schemaType The schema type to consolidate.
1409
+ * @param storageKey The storage key to consolidate.
1257
1410
  * @returns Nothing.
1258
1411
  * @internal
1259
1412
  */
1260
- async startConsolidationSync(schemaType) {
1413
+ async startConsolidationSync(storageKey) {
1261
1414
  let localChangeSnapshot;
1262
1415
  try {
1263
1416
  // If we are performing a consolidation, we can remove the local changes
1264
- await this._localSyncStateHelper.getLocalChangeSnapshot(schemaType);
1417
+ await this._localSyncStateHelper.getLocalChangeSnapshot(storageKey);
1265
1418
  if (!Is.empty(localChangeSnapshot)) {
1266
1419
  await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
1267
1420
  }
1268
1421
  if (Is.stringValue(this._nodeIdentity)) {
1269
- await this._remoteSyncStateHelper.consolidateFromLocal(schemaType, this._config.consolidationBatchSize ??
1422
+ await this._remoteSyncStateHelper.consolidateFromLocal(storageKey, this._config.consolidationBatchSize ??
1270
1423
  SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
1271
1424
  // The consolidation was successful, so we can remove the local change snapshot permanently
1272
1425
  localChangeSnapshot = undefined;
@@ -1284,10 +1437,6 @@ class SynchronisedStorageService {
1284
1437
  error: BaseError.fromError(error)
1285
1438
  });
1286
1439
  }
1287
- finally {
1288
- // Set a timer to perform the consolidation again
1289
- this._consolidationTimers[schemaType] = setTimeout(async () => this.startConsolidationSync(schemaType), this._config.consolidationIntervalMs);
1290
- }
1291
1440
  }
1292
1441
  /**
1293
1442
  * Register a new sync type.
@@ -1300,14 +1449,48 @@ class SynchronisedStorageService {
1300
1449
  source: this.CLASS_NAME,
1301
1450
  message: "registerType",
1302
1451
  data: {
1303
- schemaType: syncRegisterType.schemaType
1452
+ storageKey: syncRegisterType.storageKey
1304
1453
  }
1305
1454
  });
1306
- if (this._config.entityUpdateIntervalMs > 0) {
1307
- await this.startEntitySync(syncRegisterType.schemaType);
1455
+ if (Is.empty(this._activeStorageKeys[syncRegisterType.storageKey])) {
1456
+ this._activeStorageKeys[syncRegisterType.storageKey] = false;
1457
+ if (this._serviceStarted) {
1458
+ await this.activateStorageKey(syncRegisterType.storageKey);
1459
+ }
1308
1460
  }
1309
- if (this._config.isTrustedNode && this._config.consolidationIntervalMs > 0) {
1310
- await this.startConsolidationSync(syncRegisterType.schemaType);
1461
+ }
1462
+ /**
1463
+ * Activate a storage key.
1464
+ * @param storageKey The storage key to activate.
1465
+ * @internal
1466
+ */
1467
+ async activateStorageKey(storageKey) {
1468
+ if (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {
1469
+ await this._logging?.log({
1470
+ level: "info",
1471
+ source: this.CLASS_NAME,
1472
+ message: "activateType",
1473
+ data: {
1474
+ storageKey
1475
+ }
1476
+ });
1477
+ this._activeStorageKeys[storageKey] = true;
1478
+ if (this._config.entityUpdateIntervalMinutes > 0) {
1479
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-update-${storageKey}`, [
1480
+ {
1481
+ nextTriggerTime: Date.now(),
1482
+ intervalMinutes: this._config.entityUpdateIntervalMinutes
1483
+ }
1484
+ ], async () => this.startEntitySync(storageKey));
1485
+ }
1486
+ if (this._config.isTrustedNode && this._config.consolidationIntervalMinutes > 0) {
1487
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-consolidation-${storageKey}`, [
1488
+ {
1489
+ nextTriggerTime: Date.now(),
1490
+ intervalMinutes: this._config.consolidationIntervalMinutes
1491
+ }
1492
+ ], async () => this.startConsolidationSync(storageKey));
1493
+ }
1311
1494
  }
1312
1495
  }
1313
1496
  }