@twin.org/synchronised-storage-service 0.0.1-next.4 → 0.0.1-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +369 -147
- package/dist/esm/index.mjs +370 -148
- package/dist/types/entities/syncSnapshotEntry.d.ts +11 -3
- package/dist/types/helpers/blobStorageHelper.d.ts +7 -1
- package/dist/types/helpers/changeSetHelper.d.ts +8 -1
- package/dist/types/helpers/localSyncStateHelper.d.ts +4 -3
- package/dist/types/helpers/remoteSyncStateHelper.d.ts +3 -2
- package/dist/types/models/ISyncSnapshot.d.ts +4 -0
- package/dist/types/models/ISyncState.d.ts +4 -0
- package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +5 -0
- package/docs/architecture.md +4 -1
- package/docs/changelog.md +14 -0
- package/docs/open-api/spec.json +2 -0
- package/docs/reference/classes/SyncSnapshotEntry.md +21 -5
- package/docs/reference/interfaces/ISyncSnapshot.md +8 -0
- package/docs/reference/interfaces/ISyncState.md +8 -0
- package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +14 -0
- package/locales/en.json +16 -9
- package/package.json +2 -2
package/dist/esm/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
|
|
|
6
6
|
import { DocumentHelper, IdentityConnectorFactory } from '@twin.org/identity-models';
|
|
7
7
|
import { LoggingConnectorFactory } from '@twin.org/logging-models';
|
|
8
8
|
import { ProofTypes } from '@twin.org/standards-w3c-did';
|
|
9
|
-
import { SyncChangeOperation, SynchronisedStorageTopics } from '@twin.org/synchronised-storage-models';
|
|
9
|
+
import { SyncChangeOperation, SynchronisedStorageTopics, SyncNodeIdentityMode } from '@twin.org/synchronised-storage-models';
|
|
10
10
|
import { VaultEncryptionType, VaultConnectorFactory } from '@twin.org/vault-models';
|
|
11
11
|
import { VerifiableStorageConnectorFactory } from '@twin.org/verifiable-storage-models';
|
|
12
12
|
import { RSA } from '@twin.org/crypto';
|
|
@@ -21,6 +21,10 @@ let SyncSnapshotEntry = class SyncSnapshotEntry {
|
|
|
21
21
|
* The id for the snapshot.
|
|
22
22
|
*/
|
|
23
23
|
id;
|
|
24
|
+
/**
|
|
25
|
+
* The version for the snapshot.
|
|
26
|
+
*/
|
|
27
|
+
version;
|
|
24
28
|
/**
|
|
25
29
|
* The storage key for the snapshot i.e. which entity is being synchronized.
|
|
26
30
|
*/
|
|
@@ -34,9 +38,13 @@ let SyncSnapshotEntry = class SyncSnapshotEntry {
|
|
|
34
38
|
*/
|
|
35
39
|
dateModified;
|
|
36
40
|
/**
|
|
37
|
-
* The flag to determine if this is the
|
|
41
|
+
* The flag to determine if this is the snapshot is the local one containing changes for this node.
|
|
42
|
+
*/
|
|
43
|
+
isLocal;
|
|
44
|
+
/**
|
|
45
|
+
* The flag to determine if this is a consolidated snapshot.
|
|
38
46
|
*/
|
|
39
|
-
|
|
47
|
+
isConsolidated;
|
|
40
48
|
/**
|
|
41
49
|
* The ids of the storage for the change sets in the snapshot, if this is not a local snapshot.
|
|
42
50
|
*/
|
|
@@ -50,6 +58,10 @@ __decorate([
|
|
|
50
58
|
property({ type: "string", isPrimary: true }),
|
|
51
59
|
__metadata("design:type", String)
|
|
52
60
|
], SyncSnapshotEntry.prototype, "id", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
property({ type: "string" }),
|
|
63
|
+
__metadata("design:type", String)
|
|
64
|
+
], SyncSnapshotEntry.prototype, "version", void 0);
|
|
53
65
|
__decorate([
|
|
54
66
|
property({ type: "string", isSecondary: true }),
|
|
55
67
|
__metadata("design:type", String)
|
|
@@ -59,13 +71,17 @@ __decorate([
|
|
|
59
71
|
__metadata("design:type", String)
|
|
60
72
|
], SyncSnapshotEntry.prototype, "dateCreated", void 0);
|
|
61
73
|
__decorate([
|
|
62
|
-
property({ type: "string"
|
|
74
|
+
property({ type: "string" }),
|
|
63
75
|
__metadata("design:type", String)
|
|
64
76
|
], SyncSnapshotEntry.prototype, "dateModified", void 0);
|
|
65
77
|
__decorate([
|
|
66
|
-
property({ type: "boolean"
|
|
78
|
+
property({ type: "boolean" }),
|
|
79
|
+
__metadata("design:type", Boolean)
|
|
80
|
+
], SyncSnapshotEntry.prototype, "isLocal", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
property({ type: "boolean" }),
|
|
67
83
|
__metadata("design:type", Boolean)
|
|
68
|
-
], SyncSnapshotEntry.prototype, "
|
|
84
|
+
], SyncSnapshotEntry.prototype, "isConsolidated", void 0);
|
|
69
85
|
__decorate([
|
|
70
86
|
property({ type: "array", itemType: "string", optional: true }),
|
|
71
87
|
__metadata("design:type", Array)
|
|
@@ -114,6 +130,7 @@ function generateRestRoutesSynchronisedStorage(baseRouteName, componentName) {
|
|
|
114
130
|
body: {
|
|
115
131
|
id: "0909090909090909090909090909090909090909090909090909090909090909",
|
|
116
132
|
dateCreated: "2025-05-29T01:00:00.000Z",
|
|
133
|
+
dateModified: "2025-05-29T01:00:00.000Z",
|
|
117
134
|
nodeIdentity: "did:entity-storage:0xd2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2",
|
|
118
135
|
changes: [
|
|
119
136
|
{
|
|
@@ -314,7 +331,7 @@ class BlobStorageHelper {
|
|
|
314
331
|
* @param blobId The id of the blob to apply.
|
|
315
332
|
* @returns The blob.
|
|
316
333
|
*/
|
|
317
|
-
async
|
|
334
|
+
async loadBlob(blobId) {
|
|
318
335
|
await this._logging?.log({
|
|
319
336
|
level: "info",
|
|
320
337
|
source: this.CLASS_NAME,
|
|
@@ -407,6 +424,51 @@ class BlobStorageHelper {
|
|
|
407
424
|
throw error;
|
|
408
425
|
}
|
|
409
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Remove a blob from storage.
|
|
429
|
+
* @param blobId The id of the blob to remove.
|
|
430
|
+
* @returns Nothing.
|
|
431
|
+
*/
|
|
432
|
+
async removeBlob(blobId) {
|
|
433
|
+
await this._logging?.log({
|
|
434
|
+
level: "info",
|
|
435
|
+
source: this.CLASS_NAME,
|
|
436
|
+
message: "removeBlob",
|
|
437
|
+
data: {
|
|
438
|
+
blobId
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
try {
|
|
442
|
+
await this._blobStorageConnector.remove(blobId);
|
|
443
|
+
await this._logging?.log({
|
|
444
|
+
level: "info",
|
|
445
|
+
source: this.CLASS_NAME,
|
|
446
|
+
message: "removedBlob",
|
|
447
|
+
data: {
|
|
448
|
+
blobId
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
await this._logging?.log({
|
|
454
|
+
level: "error",
|
|
455
|
+
source: this.CLASS_NAME,
|
|
456
|
+
message: "removeBlobFailed",
|
|
457
|
+
data: {
|
|
458
|
+
blobId
|
|
459
|
+
},
|
|
460
|
+
error: BaseError.fromError(error)
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
await this._logging?.log({
|
|
464
|
+
level: "info",
|
|
465
|
+
source: this.CLASS_NAME,
|
|
466
|
+
message: "removeBlobEmpty",
|
|
467
|
+
data: {
|
|
468
|
+
blobId
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
410
472
|
}
|
|
411
473
|
|
|
412
474
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -486,7 +548,7 @@ class ChangeSetHelper {
|
|
|
486
548
|
}
|
|
487
549
|
});
|
|
488
550
|
try {
|
|
489
|
-
const syncChangeSet = await this._blobStorageHelper.
|
|
551
|
+
const syncChangeSet = await this._blobStorageHelper.loadBlob(changeSetStorageId);
|
|
490
552
|
if (Is.object(syncChangeSet)) {
|
|
491
553
|
const verified = await this.verifyChangesetProof(syncChangeSet);
|
|
492
554
|
return verified ? syncChangeSet : undefined;
|
|
@@ -519,7 +581,9 @@ class ChangeSetHelper {
|
|
|
519
581
|
*/
|
|
520
582
|
async getAndApplyChangeset(changeSetStorageId) {
|
|
521
583
|
const syncChangeset = await this.getAndVerifyChangeset(changeSetStorageId);
|
|
522
|
-
|
|
584
|
+
// Only apply changesets from other nodes, we don't want to overwrite
|
|
585
|
+
// any changes we have made to local entity storage
|
|
586
|
+
if (!Is.empty(syncChangeset) && syncChangeset.nodeIdentity !== this._nodeIdentity) {
|
|
523
587
|
await this.applyChangeset(syncChangeset);
|
|
524
588
|
}
|
|
525
589
|
return syncChangeset;
|
|
@@ -563,7 +627,8 @@ class ChangeSetHelper {
|
|
|
563
627
|
if (!Is.empty(change.id)) {
|
|
564
628
|
await this._eventBusComponent.publish(SynchronisedStorageTopics.RemoteItemRemove, {
|
|
565
629
|
storageKey: syncChangeset.storageKey,
|
|
566
|
-
id: change.id
|
|
630
|
+
id: change.id,
|
|
631
|
+
nodeIdentity: syncChangeset.nodeIdentity
|
|
567
632
|
});
|
|
568
633
|
}
|
|
569
634
|
break;
|
|
@@ -705,8 +770,36 @@ class ChangeSetHelper {
|
|
|
705
770
|
}
|
|
706
771
|
}
|
|
707
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Reset the storage for a given storage key.
|
|
775
|
+
* @param storageKey The key of the storage to reset.
|
|
776
|
+
* @param resetMode The reset mode, this will use the nodeIdentity in the entities to determine which are local/remote.
|
|
777
|
+
* @returns Nothing.
|
|
778
|
+
*/
|
|
779
|
+
async reset(storageKey, resetMode) {
|
|
780
|
+
// If we are applying a consolidation we need to reset the local db
|
|
781
|
+
// but keep any entries from the local node, as they might have been updated
|
|
782
|
+
await this._logging?.log({
|
|
783
|
+
level: "info",
|
|
784
|
+
source: this.CLASS_NAME,
|
|
785
|
+
message: "storageReset",
|
|
786
|
+
data: {
|
|
787
|
+
storageKey
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
await this._eventBusComponent.publish(SynchronisedStorageTopics.Reset, {
|
|
791
|
+
storageKey,
|
|
792
|
+
resetMode
|
|
793
|
+
});
|
|
794
|
+
}
|
|
708
795
|
}
|
|
709
796
|
|
|
797
|
+
// Copyright 2024 IOTA Stiftung.
|
|
798
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
799
|
+
const SYNC_STATE_VERSION = "1";
|
|
800
|
+
const SYNC_POINTER_STORE_VERSION = "1";
|
|
801
|
+
const SYNC_SNAPSHOT_VERSION = "1";
|
|
802
|
+
|
|
710
803
|
// Copyright 2024 IOTA Stiftung.
|
|
711
804
|
// SPDX-License-Identifier: Apache-2.0.
|
|
712
805
|
/**
|
|
@@ -761,31 +854,35 @@ class LocalSyncStateHelper {
|
|
|
761
854
|
id
|
|
762
855
|
}
|
|
763
856
|
});
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
localChangeSnapshot.changes.
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
857
|
+
const localChangeSnapshots = await this.getSnapshots(storageKey, true);
|
|
858
|
+
if (localChangeSnapshots.length > 0) {
|
|
859
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
860
|
+
localChangeSnapshot.changes ??= [];
|
|
861
|
+
// If we already have a change for this id we are
|
|
862
|
+
// about to supersede it, we remove the previous change
|
|
863
|
+
// to avoid having multiple changes for the same id
|
|
864
|
+
const previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);
|
|
865
|
+
if (previousChangeIndex !== -1) {
|
|
866
|
+
localChangeSnapshot.changes.splice(previousChangeIndex, 1);
|
|
867
|
+
}
|
|
868
|
+
if (localChangeSnapshot.changes.length > 0) {
|
|
869
|
+
localChangeSnapshot.dateModified = new Date(Date.now()).toISOString();
|
|
870
|
+
}
|
|
871
|
+
localChangeSnapshot.changes.push({ operation, id });
|
|
872
|
+
await this.setLocalChangeSnapshot(localChangeSnapshot);
|
|
775
873
|
}
|
|
776
|
-
localChangeSnapshot.changes.push({ operation, id });
|
|
777
|
-
await this.setLocalChangeSnapshot(localChangeSnapshot);
|
|
778
874
|
}
|
|
779
875
|
/**
|
|
780
|
-
* Get the
|
|
876
|
+
* Get the snapshot which contains just the changes for this node.
|
|
781
877
|
* @param storageKey The storage key of the snapshot to get.
|
|
878
|
+
* @param isLocal Whether to get the local snapshot or not.
|
|
782
879
|
* @returns The local snapshot entry.
|
|
783
880
|
*/
|
|
784
|
-
async
|
|
881
|
+
async getSnapshots(storageKey, isLocal) {
|
|
785
882
|
await this._logging?.log({
|
|
786
883
|
level: "info",
|
|
787
884
|
source: this.CLASS_NAME,
|
|
788
|
-
message: "
|
|
885
|
+
message: "getSnapshots",
|
|
789
886
|
data: {
|
|
790
887
|
storageKey
|
|
791
888
|
}
|
|
@@ -793,8 +890,8 @@ class LocalSyncStateHelper {
|
|
|
793
890
|
const queryResult = await this._snapshotEntryEntityStorage.query({
|
|
794
891
|
conditions: [
|
|
795
892
|
{
|
|
796
|
-
property: "
|
|
797
|
-
value:
|
|
893
|
+
property: "isLocal",
|
|
894
|
+
value: isLocal,
|
|
798
895
|
comparison: ComparisonOperator.Equals
|
|
799
896
|
},
|
|
800
897
|
{
|
|
@@ -808,28 +905,34 @@ class LocalSyncStateHelper {
|
|
|
808
905
|
await this._logging?.log({
|
|
809
906
|
level: "info",
|
|
810
907
|
source: this.CLASS_NAME,
|
|
811
|
-
message: "
|
|
908
|
+
message: "getSnapshotsExists",
|
|
812
909
|
data: {
|
|
813
910
|
storageKey
|
|
814
911
|
}
|
|
815
912
|
});
|
|
816
|
-
return queryResult.entities
|
|
913
|
+
return queryResult.entities;
|
|
817
914
|
}
|
|
818
915
|
await this._logging?.log({
|
|
819
916
|
level: "info",
|
|
820
917
|
source: this.CLASS_NAME,
|
|
821
|
-
message: "
|
|
918
|
+
message: "getSnapshotsDoesNotExist",
|
|
822
919
|
data: {
|
|
823
920
|
storageKey
|
|
824
921
|
}
|
|
825
922
|
});
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
923
|
+
const now = new Date(Date.now()).toISOString();
|
|
924
|
+
return [
|
|
925
|
+
{
|
|
926
|
+
version: SYNC_SNAPSHOT_VERSION,
|
|
927
|
+
id: Converter.bytesToHex(RandomHelper.generate(32)),
|
|
928
|
+
storageKey,
|
|
929
|
+
dateCreated: now,
|
|
930
|
+
dateModified: now,
|
|
931
|
+
changeSetStorageIds: [],
|
|
932
|
+
isLocal,
|
|
933
|
+
isConsolidated: false
|
|
934
|
+
}
|
|
935
|
+
];
|
|
833
936
|
}
|
|
834
937
|
/**
|
|
835
938
|
* Set the current local snapshot with changes for this node.
|
|
@@ -878,46 +981,115 @@ class LocalSyncStateHelper {
|
|
|
878
981
|
snapshotCount: syncState.snapshots.length
|
|
879
982
|
}
|
|
880
983
|
});
|
|
984
|
+
// Get all the existing snapshots that we have processed previously
|
|
985
|
+
const existingRemoteSnapshots = await this.getSnapshots(storageKey, false);
|
|
881
986
|
// Sort from newest to oldest
|
|
882
987
|
const sortedSnapshots = syncState.snapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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) {
|
|
886
992
|
await this._logging?.log({
|
|
887
993
|
level: "info",
|
|
888
994
|
source: this.CLASS_NAME,
|
|
889
|
-
message: "
|
|
995
|
+
message: "applySnapshotNoExisting",
|
|
890
996
|
data: {
|
|
891
|
-
|
|
892
|
-
dateCreated: new Date(snapshot.dateCreated).toISOString()
|
|
997
|
+
storageKey
|
|
893
998
|
}
|
|
894
999
|
});
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
modifiedSnapshots.push({
|
|
907
|
-
localSnapshot,
|
|
908
|
-
remoteSnapshot: remoteSnapshotWithContext
|
|
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({
|
|
1004
|
+
level: "info",
|
|
1005
|
+
source: this.CLASS_NAME,
|
|
1006
|
+
message: "applySnapshotFoundConsolidated",
|
|
1007
|
+
data: {
|
|
1008
|
+
storageKey,
|
|
1009
|
+
snapshotId: firstConsolidated.id
|
|
1010
|
+
}
|
|
909
1011
|
});
|
|
1012
|
+
// We need to reset the entity storage and remove all the remote items
|
|
1013
|
+
// so that we use just the ones from the consolidation
|
|
1014
|
+
await this._changeSetHelper.reset(storageKey, SyncNodeIdentityMode.Remote);
|
|
1015
|
+
await this.processNewSnapshots([
|
|
1016
|
+
{
|
|
1017
|
+
...firstConsolidated,
|
|
1018
|
+
storageKey,
|
|
1019
|
+
isLocal: false
|
|
1020
|
+
}
|
|
1021
|
+
]);
|
|
910
1022
|
}
|
|
911
1023
|
else {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1024
|
+
await this._logging?.log({
|
|
1025
|
+
level: "info",
|
|
1026
|
+
source: this.CLASS_NAME,
|
|
1027
|
+
message: "applySnapshotNoConsolidated",
|
|
1028
|
+
data: {
|
|
1029
|
+
storageKey
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
// Create a lookup map for the existing snapshots
|
|
1036
|
+
const existingSnapshots = {};
|
|
1037
|
+
for (const snapshot of existingRemoteSnapshots) {
|
|
1038
|
+
existingSnapshots[snapshot.id] = snapshot;
|
|
1039
|
+
}
|
|
1040
|
+
const newSnapshots = [];
|
|
1041
|
+
const modifiedSnapshots = [];
|
|
1042
|
+
const referencedExistingSnapshots = Object.keys(existingSnapshots);
|
|
1043
|
+
for (const snapshot of sortedSnapshots) {
|
|
1044
|
+
await this._logging?.log({
|
|
1045
|
+
level: "info",
|
|
1046
|
+
source: this.CLASS_NAME,
|
|
1047
|
+
message: "applySnapshot",
|
|
1048
|
+
data: {
|
|
1049
|
+
snapshotId: snapshot.id,
|
|
1050
|
+
dateCreated: new Date(snapshot.dateCreated).toISOString()
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
// See if we have the local snapshot
|
|
1054
|
+
const currentSnapshot = existingSnapshots[snapshot.id];
|
|
1055
|
+
// As we are referencing an existing snapshot, we need to remove it from the list
|
|
1056
|
+
// to allow us to cleanup any unreferenced snapshots later
|
|
1057
|
+
const idx = referencedExistingSnapshots.indexOf(snapshot.id);
|
|
1058
|
+
if (idx !== -1) {
|
|
1059
|
+
referencedExistingSnapshots.splice(idx, 1);
|
|
1060
|
+
}
|
|
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;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
// We reverse the order of the snapshots to process them from oldest to newest
|
|
1084
|
+
// because we want to apply the changes in the order they were created
|
|
1085
|
+
await this.processModifiedSnapshots(modifiedSnapshots.reverse());
|
|
1086
|
+
await this.processNewSnapshots(newSnapshots.reverse());
|
|
1087
|
+
// Any ids remaining in this list are no longer referenced in the global state
|
|
1088
|
+
// so we should remove them from the local storage as they will never be updated again
|
|
1089
|
+
for (const referencedSnapshotId of referencedExistingSnapshots) {
|
|
1090
|
+
await this._snapshotEntryEntityStorage.remove(referencedSnapshotId);
|
|
915
1091
|
}
|
|
916
1092
|
}
|
|
917
|
-
// We reverse the order of the snapshots to process them from oldest to newest
|
|
918
|
-
// because we want to apply the changes in the order they were created
|
|
919
|
-
await this.processModifiedSnapshots(modifiedSnapshots.reverse());
|
|
920
|
-
await this.processNewSnapshots(newSnapshots.reverse());
|
|
921
1093
|
}
|
|
922
1094
|
/**
|
|
923
1095
|
* Process the modified snapshots and store them in the local storage.
|
|
@@ -932,15 +1104,15 @@ class LocalSyncStateHelper {
|
|
|
932
1104
|
source: this.CLASS_NAME,
|
|
933
1105
|
message: "processModifiedSnapshot",
|
|
934
1106
|
data: {
|
|
935
|
-
snapshotId: modifiedSnapshot.
|
|
936
|
-
localModified: new Date(modifiedSnapshot.
|
|
937
|
-
modifiedSnapshot.
|
|
938
|
-
remoteModified: new Date(modifiedSnapshot.
|
|
939
|
-
modifiedSnapshot.
|
|
1107
|
+
snapshotId: modifiedSnapshot.updatedSnapshot.id,
|
|
1108
|
+
localModified: new Date(modifiedSnapshot.currentSnapshot.dateModified ??
|
|
1109
|
+
modifiedSnapshot.currentSnapshot.dateCreated).toISOString(),
|
|
1110
|
+
remoteModified: new Date(modifiedSnapshot.updatedSnapshot.dateModified ??
|
|
1111
|
+
modifiedSnapshot.updatedSnapshot.dateCreated).toISOString()
|
|
940
1112
|
}
|
|
941
1113
|
});
|
|
942
|
-
const remoteChangeSetStorageIds = modifiedSnapshot.
|
|
943
|
-
const localChangeSetStorageIds = modifiedSnapshot.
|
|
1114
|
+
const remoteChangeSetStorageIds = modifiedSnapshot.updatedSnapshot.changeSetStorageIds;
|
|
1115
|
+
const localChangeSetStorageIds = modifiedSnapshot.currentSnapshot.changeSetStorageIds ?? [];
|
|
944
1116
|
if (Is.arrayValue(remoteChangeSetStorageIds)) {
|
|
945
1117
|
for (const storageId of remoteChangeSetStorageIds) {
|
|
946
1118
|
// Check if the local snapshot does not have the storageId
|
|
@@ -949,7 +1121,7 @@ class LocalSyncStateHelper {
|
|
|
949
1121
|
}
|
|
950
1122
|
}
|
|
951
1123
|
}
|
|
952
|
-
await this._snapshotEntryEntityStorage.set(modifiedSnapshot.
|
|
1124
|
+
await this._snapshotEntryEntityStorage.set(modifiedSnapshot.updatedSnapshot);
|
|
953
1125
|
}
|
|
954
1126
|
}
|
|
955
1127
|
/**
|
|
@@ -966,7 +1138,7 @@ class LocalSyncStateHelper {
|
|
|
966
1138
|
message: "processNewSnapshot",
|
|
967
1139
|
data: {
|
|
968
1140
|
snapshotId: newSnapshot.id,
|
|
969
|
-
|
|
1141
|
+
dateCreated: newSnapshot.dateCreated
|
|
970
1142
|
}
|
|
971
1143
|
});
|
|
972
1144
|
const newSnapshotChangeSetStorageIds = newSnapshot.changeSetStorageIds ?? [];
|
|
@@ -980,12 +1152,6 @@ class LocalSyncStateHelper {
|
|
|
980
1152
|
}
|
|
981
1153
|
}
|
|
982
1154
|
|
|
983
|
-
// Copyright 2024 IOTA Stiftung.
|
|
984
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
985
|
-
const SYNC_STATE_VERSION = "1";
|
|
986
|
-
const SYNC_POINTER_STORE_VERSION = "1";
|
|
987
|
-
const SYNC_SNAPSHOT_VERSION = "1";
|
|
988
|
-
|
|
989
1155
|
// Copyright 2024 IOTA Stiftung.
|
|
990
1156
|
// SPDX-License-Identifier: Apache-2.0.
|
|
991
1157
|
/**
|
|
@@ -1046,6 +1212,11 @@ class RemoteSyncStateHelper {
|
|
|
1046
1212
|
* @internal
|
|
1047
1213
|
*/
|
|
1048
1214
|
_isTrustedNode;
|
|
1215
|
+
/**
|
|
1216
|
+
* Maximum number of consolidations to keep in storage.
|
|
1217
|
+
* @internal
|
|
1218
|
+
*/
|
|
1219
|
+
_maxConsolidations;
|
|
1049
1220
|
/**
|
|
1050
1221
|
* Create a new instance of DecentralisedEntityStorageConnector.
|
|
1051
1222
|
* @param logging The logging connector to use for logging.
|
|
@@ -1054,14 +1225,16 @@ class RemoteSyncStateHelper {
|
|
|
1054
1225
|
* @param blobStorageHelper The blob storage helper to use for remote sync states.
|
|
1055
1226
|
* @param changeSetHelper The change set helper to use for managing changesets.
|
|
1056
1227
|
* @param isTrustedNode Whether the node is trusted or not.
|
|
1228
|
+
* @param maxConsolidations The maximum number of consolidations to keep in storage.
|
|
1057
1229
|
*/
|
|
1058
|
-
constructor(logging, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode) {
|
|
1230
|
+
constructor(logging, eventBusComponent, verifiableSyncPointerStorageConnector, blobStorageHelper, changeSetHelper, isTrustedNode, maxConsolidations) {
|
|
1059
1231
|
this._logging = logging;
|
|
1060
1232
|
this._eventBusComponent = eventBusComponent;
|
|
1061
1233
|
this._verifiableSyncPointerStorageConnector = verifiableSyncPointerStorageConnector;
|
|
1062
1234
|
this._changeSetHelper = changeSetHelper;
|
|
1063
1235
|
this._blobStorageHelper = blobStorageHelper;
|
|
1064
1236
|
this._isTrustedNode = isTrustedNode;
|
|
1237
|
+
this._maxConsolidations = maxConsolidations;
|
|
1065
1238
|
this._batchResponseStorageIds = {};
|
|
1066
1239
|
this._populateFullChanges = {};
|
|
1067
1240
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.BatchResponse, async (response) => {
|
|
@@ -1163,9 +1336,11 @@ class RemoteSyncStateHelper {
|
|
|
1163
1336
|
ObjectHelper.propertyDelete(change.entity, "nodeIdentity");
|
|
1164
1337
|
}
|
|
1165
1338
|
}
|
|
1339
|
+
const now = new Date(Date.now()).toISOString();
|
|
1166
1340
|
const syncChangeSet = {
|
|
1167
1341
|
id: Converter.bytesToHex(RandomHelper.generate(32)),
|
|
1168
|
-
dateCreated:
|
|
1342
|
+
dateCreated: now,
|
|
1343
|
+
dateModified: now,
|
|
1169
1344
|
storageKey,
|
|
1170
1345
|
changes,
|
|
1171
1346
|
nodeIdentity: this._nodeIdentity
|
|
@@ -1217,23 +1392,26 @@ class RemoteSyncStateHelper {
|
|
|
1217
1392
|
const syncPointerStore = await this.getVerifiableSyncPointerStore();
|
|
1218
1393
|
let syncState;
|
|
1219
1394
|
if (!Is.empty(syncPointerStore.syncPointers[storageKey])) {
|
|
1220
|
-
syncState = await this.
|
|
1395
|
+
syncState = await this.getSyncState(syncPointerStore.syncPointers[storageKey]);
|
|
1221
1396
|
}
|
|
1222
1397
|
// No current sync state, so we create a new one
|
|
1223
1398
|
if (Is.empty(syncState)) {
|
|
1224
|
-
syncState = { version: SYNC_STATE_VERSION, snapshots: [] };
|
|
1399
|
+
syncState = { version: SYNC_STATE_VERSION, storageKey, snapshots: [] };
|
|
1225
1400
|
}
|
|
1226
1401
|
// Sort the snapshots so the newest snapshot is last in the array
|
|
1227
1402
|
const sortedSnapshots = syncState.snapshots.sort((a, b) => a.dateCreated.localeCompare(b.dateCreated));
|
|
1228
1403
|
// Get the current snapshot, if it does not exist we create a new one
|
|
1229
1404
|
let currentSnapshot = sortedSnapshots[sortedSnapshots.length - 1];
|
|
1230
1405
|
const now = new Date(Date.now()).toISOString();
|
|
1231
|
-
|
|
1406
|
+
// If there is no snapshot or the current one is a consolidation
|
|
1407
|
+
// we start a new snapshot
|
|
1408
|
+
if (Is.empty(currentSnapshot) || currentSnapshot.isConsolidated) {
|
|
1232
1409
|
currentSnapshot = {
|
|
1233
1410
|
version: SYNC_SNAPSHOT_VERSION,
|
|
1234
1411
|
id: Converter.bytesToHex(RandomHelper.generate(32)),
|
|
1235
1412
|
dateCreated: now,
|
|
1236
1413
|
dateModified: now,
|
|
1414
|
+
isConsolidated: false,
|
|
1237
1415
|
changeSetStorageIds: []
|
|
1238
1416
|
};
|
|
1239
1417
|
syncState.snapshots.push(currentSnapshot);
|
|
@@ -1262,7 +1440,7 @@ class RemoteSyncStateHelper {
|
|
|
1262
1440
|
message: "consolidationStarting"
|
|
1263
1441
|
});
|
|
1264
1442
|
// Perform a batch request to start the consolidation
|
|
1265
|
-
await this._eventBusComponent.publish(SynchronisedStorageTopics.BatchRequest, { storageKey, batchSize });
|
|
1443
|
+
await this._eventBusComponent.publish(SynchronisedStorageTopics.BatchRequest, { storageKey, batchSize, requestMode: SyncNodeIdentityMode.All });
|
|
1266
1444
|
}
|
|
1267
1445
|
/**
|
|
1268
1446
|
* Get the sync pointer store.
|
|
@@ -1341,11 +1519,32 @@ class RemoteSyncStateHelper {
|
|
|
1341
1519
|
await this._logging?.log({
|
|
1342
1520
|
level: "info",
|
|
1343
1521
|
source: this.CLASS_NAME,
|
|
1344
|
-
message: "
|
|
1522
|
+
message: "syncStateStoring",
|
|
1345
1523
|
data: {
|
|
1346
1524
|
snapshotCount: syncState.snapshots.length
|
|
1347
1525
|
}
|
|
1348
1526
|
});
|
|
1527
|
+
// Limits the number of consolidations in the list so that we can shrink decentralised
|
|
1528
|
+
// storage requirements, sort from newest to oldest so that we can easily find the
|
|
1529
|
+
// oldest snapshots to remove.
|
|
1530
|
+
const snapshots = syncState.snapshots.sort((a, b) => new Date(a.dateCreated).getTime() - new Date(b.dateCreated).getTime());
|
|
1531
|
+
// Find all the consolidation indexes
|
|
1532
|
+
const consolidationIndexes = [];
|
|
1533
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
1534
|
+
const snapshot = snapshots[i];
|
|
1535
|
+
if (snapshot.isConsolidated) {
|
|
1536
|
+
consolidationIndexes.push(i);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (consolidationIndexes.length > this._maxConsolidations) {
|
|
1540
|
+
// Once we have reached the max for consolidations we need to remove
|
|
1541
|
+
// all the snapshots, including non consolidated ones, beyond this point
|
|
1542
|
+
const toRemove = snapshots.slice(consolidationIndexes[this._maxConsolidations - 1] + 1);
|
|
1543
|
+
syncState.snapshots = snapshots.slice(0, consolidationIndexes[this._maxConsolidations - 1] + 1);
|
|
1544
|
+
for (const snapshot of toRemove) {
|
|
1545
|
+
await this._blobStorageHelper.removeBlob(snapshot.id);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1349
1548
|
return this._blobStorageHelper.saveBlob(syncState);
|
|
1350
1549
|
}
|
|
1351
1550
|
/**
|
|
@@ -1353,22 +1552,22 @@ class RemoteSyncStateHelper {
|
|
|
1353
1552
|
* @param syncPointerId The id of the sync pointer to retrieve the state for.
|
|
1354
1553
|
* @returns The remote sync state.
|
|
1355
1554
|
*/
|
|
1356
|
-
async
|
|
1555
|
+
async getSyncState(syncPointerId) {
|
|
1357
1556
|
try {
|
|
1358
1557
|
await this._logging?.log({
|
|
1359
1558
|
level: "info",
|
|
1360
1559
|
source: this.CLASS_NAME,
|
|
1361
|
-
message: "
|
|
1560
|
+
message: "syncStateRetrieving",
|
|
1362
1561
|
data: {
|
|
1363
1562
|
syncPointerId
|
|
1364
1563
|
}
|
|
1365
1564
|
});
|
|
1366
|
-
const syncState = await this._blobStorageHelper.
|
|
1565
|
+
const syncState = await this._blobStorageHelper.loadBlob(syncPointerId);
|
|
1367
1566
|
if (Is.object(syncState)) {
|
|
1368
1567
|
await this._logging?.log({
|
|
1369
1568
|
level: "info",
|
|
1370
1569
|
source: this.CLASS_NAME,
|
|
1371
|
-
message: "
|
|
1570
|
+
message: "syncStateRetrieved",
|
|
1372
1571
|
data: {
|
|
1373
1572
|
syncPointerId,
|
|
1374
1573
|
snapshotCount: syncState.snapshots.length
|
|
@@ -1391,7 +1590,7 @@ class RemoteSyncStateHelper {
|
|
|
1391
1590
|
await this._logging?.log({
|
|
1392
1591
|
level: "info",
|
|
1393
1592
|
source: this.CLASS_NAME,
|
|
1394
|
-
message: "
|
|
1593
|
+
message: "syncStateNotFound",
|
|
1395
1594
|
data: {
|
|
1396
1595
|
syncPointerId
|
|
1397
1596
|
}
|
|
@@ -1430,19 +1629,24 @@ class RemoteSyncStateHelper {
|
|
|
1430
1629
|
let syncState;
|
|
1431
1630
|
if (Is.stringValue(syncPointerStore.syncPointers[response.storageKey])) {
|
|
1432
1631
|
// If the sync pointer exists, we load the current sync state
|
|
1433
|
-
syncState = await this.
|
|
1632
|
+
syncState = await this.getSyncState(syncPointerStore.syncPointers[response.storageKey]);
|
|
1434
1633
|
}
|
|
1435
1634
|
// If the sync state does not exist, we create a new one
|
|
1436
|
-
syncState ??= {
|
|
1635
|
+
syncState ??= {
|
|
1636
|
+
version: SYNC_STATE_VERSION,
|
|
1637
|
+
storageKey: response.storageKey,
|
|
1638
|
+
snapshots: []
|
|
1639
|
+
};
|
|
1437
1640
|
const batchSnapshot = {
|
|
1438
1641
|
version: SYNC_SNAPSHOT_VERSION,
|
|
1439
1642
|
id: Converter.bytesToHex(RandomHelper.generate(32)),
|
|
1440
1643
|
dateCreated: now,
|
|
1441
1644
|
dateModified: now,
|
|
1645
|
+
isConsolidated: true,
|
|
1442
1646
|
changeSetStorageIds: this._batchResponseStorageIds[response.storageKey]
|
|
1443
1647
|
};
|
|
1444
1648
|
syncState.snapshots.push(batchSnapshot);
|
|
1445
|
-
// Store the sync state
|
|
1649
|
+
// Store the updated sync state
|
|
1446
1650
|
const syncStateId = await this.storeRemoteSyncState(syncState);
|
|
1447
1651
|
syncPointerStore.syncPointers[response.storageKey] = syncStateId;
|
|
1448
1652
|
// Store the verifiable sync pointer in the verifiable storage
|
|
@@ -1472,11 +1676,14 @@ class RemoteSyncStateHelper {
|
|
|
1472
1676
|
id: response.id
|
|
1473
1677
|
}
|
|
1474
1678
|
});
|
|
1679
|
+
// We have received a response to an item request, find the right storage
|
|
1680
|
+
// for the request id
|
|
1475
1681
|
if (!Is.empty(this._populateFullChanges[response.storageKey])) {
|
|
1476
1682
|
const idx = this._populateFullChanges[response.storageKey].requestIds.indexOf(response.id);
|
|
1477
1683
|
if (idx !== -1) {
|
|
1478
1684
|
this._populateFullChanges[response.storageKey].requestIds.splice(idx, 1);
|
|
1479
1685
|
this._populateFullChanges[response.storageKey].entities[response.id] = response.entity;
|
|
1686
|
+
// If there are no request ids remaining we can complete the population
|
|
1480
1687
|
if (this._populateFullChanges[response.storageKey].requestIds.length === 0) {
|
|
1481
1688
|
await this._populateFullChanges[response.storageKey].completeCallback();
|
|
1482
1689
|
}
|
|
@@ -1504,6 +1711,11 @@ class SynchronisedStorageService {
|
|
|
1504
1711
|
* @internal
|
|
1505
1712
|
*/
|
|
1506
1713
|
static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
|
|
1714
|
+
/**
|
|
1715
|
+
* The default max number of consolidations to keep in storage.
|
|
1716
|
+
* @internal
|
|
1717
|
+
*/
|
|
1718
|
+
static _DEFAULT_MAX_CONSOLIDATIONS = 5;
|
|
1507
1719
|
/**
|
|
1508
1720
|
* Runtime name for the class.
|
|
1509
1721
|
*/
|
|
@@ -1622,6 +1834,7 @@ class SynchronisedStorageService {
|
|
|
1622
1834
|
SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
|
|
1623
1835
|
consolidationBatchSize: options.config.consolidationBatchSize ??
|
|
1624
1836
|
SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,
|
|
1837
|
+
maxConsolidations: options.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,
|
|
1625
1838
|
blobStorageEncryptionKeyId: options.config.blobStorageEncryptionKeyId ?? "synchronised-storage-blob-encryption-key",
|
|
1626
1839
|
verifiableStorageKeyId: options.config.verifiableStorageKeyId
|
|
1627
1840
|
};
|
|
@@ -1637,11 +1850,16 @@ class SynchronisedStorageService {
|
|
|
1637
1850
|
this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, this._config.isTrustedNode);
|
|
1638
1851
|
this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._identityConnector, this._blobStorageHelper, this._config.synchronisedStorageMethodId);
|
|
1639
1852
|
this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
|
|
1640
|
-
this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode);
|
|
1853
|
+
this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, this._config.isTrustedNode, this._config.maxConsolidations);
|
|
1641
1854
|
this._serviceStarted = false;
|
|
1642
1855
|
this._activeStorageKeys = {};
|
|
1643
1856
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
|
|
1644
|
-
this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) =>
|
|
1857
|
+
this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => {
|
|
1858
|
+
// Make sure the change event is from this node
|
|
1859
|
+
if (Is.stringValue(this._nodeIdentity) && this._nodeIdentity === event.data.nodeIdentity) {
|
|
1860
|
+
await this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1645
1863
|
}
|
|
1646
1864
|
/**
|
|
1647
1865
|
* The component needs to be started when the node is initialized.
|
|
@@ -1731,7 +1949,7 @@ class SynchronisedStorageService {
|
|
|
1731
1949
|
// to store the change set in the synchronised storage.
|
|
1732
1950
|
// This will be performed using rights-management
|
|
1733
1951
|
const copy = await this._changeSetHelper.copyChangeset(syncChangeSet);
|
|
1734
|
-
if (!Is.empty(copy)
|
|
1952
|
+
if (!Is.empty(copy)) {
|
|
1735
1953
|
// Apply the changes to this node
|
|
1736
1954
|
await this._changeSetHelper.applyChangeset(copy.syncChangeSet);
|
|
1737
1955
|
// And update the sync state with the latest changes
|
|
@@ -1788,7 +2006,7 @@ class SynchronisedStorageService {
|
|
|
1788
2006
|
if (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
|
|
1789
2007
|
// Load the sync state from the remote blob storage using the sync pointer
|
|
1790
2008
|
// to load the sync state
|
|
1791
|
-
const remoteSyncState = await this._remoteSyncStateHelper.
|
|
2009
|
+
const remoteSyncState = await this._remoteSyncStateHelper.getSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
|
|
1792
2010
|
// If we got the sync state we can try and sync from it
|
|
1793
2011
|
if (!Is.undefined(remoteSyncState)) {
|
|
1794
2012
|
await this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);
|
|
@@ -1809,64 +2027,67 @@ class SynchronisedStorageService {
|
|
|
1809
2027
|
storageKey
|
|
1810
2028
|
}
|
|
1811
2029
|
});
|
|
1812
|
-
const
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
await this._logging?.log({
|
|
1827
|
-
level: "info",
|
|
1828
|
-
source: this.CLASS_NAME,
|
|
1829
|
-
message: "builtStorageChangeSet",
|
|
1830
|
-
data: {
|
|
1831
|
-
storageKey,
|
|
1832
|
-
changeSetStorageId
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1835
|
-
// Send the local changes to the remote storage if we are a trusted node
|
|
1836
|
-
if (this._config.isTrustedNode && Is.stringValue(changeSetStorageId)) {
|
|
1837
|
-
// If we are a trusted node, we can add the change set to the sync state
|
|
1838
|
-
// and remove the local change snapshot
|
|
1839
|
-
await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
|
|
1840
|
-
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2030
|
+
const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
|
|
2031
|
+
if (localChangeSnapshots.length > 0) {
|
|
2032
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
2033
|
+
if (Is.arrayValue(localChangeSnapshot.changes)) {
|
|
2034
|
+
await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
|
|
2035
|
+
if (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {
|
|
2036
|
+
await this._logging?.log({
|
|
2037
|
+
level: "info",
|
|
2038
|
+
source: this.CLASS_NAME,
|
|
2039
|
+
message: "builtStorageChangeSetNone",
|
|
2040
|
+
data: {
|
|
2041
|
+
storageKey
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
1841
2044
|
}
|
|
1842
|
-
else
|
|
1843
|
-
Is.object(syncChangeSet)) {
|
|
1844
|
-
// If we are not a trusted node, we need to send the changes to the trusted node
|
|
1845
|
-
// and then remove the local change snapshot
|
|
2045
|
+
else {
|
|
1846
2046
|
await this._logging?.log({
|
|
1847
2047
|
level: "info",
|
|
1848
2048
|
source: this.CLASS_NAME,
|
|
1849
|
-
message: "
|
|
2049
|
+
message: "builtStorageChangeSet",
|
|
1850
2050
|
data: {
|
|
1851
2051
|
storageKey,
|
|
1852
2052
|
changeSetStorageId
|
|
1853
2053
|
}
|
|
1854
2054
|
});
|
|
1855
|
-
|
|
1856
|
-
|
|
2055
|
+
// Send the local changes to the remote storage if we are a trusted node
|
|
2056
|
+
if (this._config.isTrustedNode && Is.stringValue(changeSetStorageId)) {
|
|
2057
|
+
// If we are a trusted node, we can add the change set to the sync state
|
|
2058
|
+
// and remove the local change snapshot
|
|
2059
|
+
await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
|
|
2060
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2061
|
+
}
|
|
2062
|
+
else if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
|
|
2063
|
+
Is.object(syncChangeSet)) {
|
|
2064
|
+
// If we are not a trusted node, we need to send the changes to the trusted node
|
|
2065
|
+
// and then remove the local change snapshot
|
|
2066
|
+
await this._logging?.log({
|
|
2067
|
+
level: "info",
|
|
2068
|
+
source: this.CLASS_NAME,
|
|
2069
|
+
message: "sendingChangeSetToTrustedNode",
|
|
2070
|
+
data: {
|
|
2071
|
+
storageKey,
|
|
2072
|
+
changeSetStorageId
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
await this._trustedSynchronisedStorageComponent.syncChangeSet(syncChangeSet);
|
|
2076
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
2077
|
+
}
|
|
1857
2078
|
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
await this._logging?.log({
|
|
2083
|
+
level: "info",
|
|
2084
|
+
source: this.CLASS_NAME,
|
|
2085
|
+
message: "updateFromLocalSyncStateNoChanges",
|
|
2086
|
+
data: {
|
|
2087
|
+
storageKey
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
1870
2091
|
}
|
|
1871
2092
|
}
|
|
1872
2093
|
/**
|
|
@@ -1880,7 +2101,8 @@ class SynchronisedStorageService {
|
|
|
1880
2101
|
try {
|
|
1881
2102
|
// If we are performing a consolidation, we can remove the local change snapshot
|
|
1882
2103
|
// as we are going to create a complete changeset from the DB
|
|
1883
|
-
|
|
2104
|
+
const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
|
|
2105
|
+
localChangeSnapshot = localChangeSnapshots[0];
|
|
1884
2106
|
if (!Is.empty(localChangeSnapshot)) {
|
|
1885
2107
|
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
1886
2108
|
}
|