@twin.org/synchronised-storage-service 0.0.3-next.8 → 0.9.0-next.1

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.
Files changed (36) hide show
  1. package/README.md +2 -2
  2. package/dist/es/helpers/blobStorageHelper.js +15 -3
  3. package/dist/es/helpers/blobStorageHelper.js.map +1 -1
  4. package/dist/es/helpers/changeSetHelper.js +3 -3
  5. package/dist/es/helpers/changeSetHelper.js.map +1 -1
  6. package/dist/es/helpers/localSyncStateHelper.js +7 -7
  7. package/dist/es/helpers/localSyncStateHelper.js.map +1 -1
  8. package/dist/es/helpers/remoteSyncStateHelper.js +14 -9
  9. package/dist/es/helpers/remoteSyncStateHelper.js.map +1 -1
  10. package/dist/es/helpers/versions.js +9 -0
  11. package/dist/es/helpers/versions.js.map +1 -1
  12. package/dist/es/restEntryPoints.js +3 -0
  13. package/dist/es/restEntryPoints.js.map +1 -1
  14. package/dist/es/synchronisedStorageRoutes.js +4 -2
  15. package/dist/es/synchronisedStorageRoutes.js.map +1 -1
  16. package/dist/es/synchronisedStorageService.js +21 -15
  17. package/dist/es/synchronisedStorageService.js.map +1 -1
  18. package/dist/types/helpers/blobStorageHelper.d.ts +6 -1
  19. package/dist/types/helpers/changeSetHelper.d.ts +3 -3
  20. package/dist/types/helpers/localSyncStateHelper.d.ts +5 -5
  21. package/dist/types/helpers/remoteSyncStateHelper.d.ts +10 -19
  22. package/dist/types/helpers/versions.d.ts +9 -0
  23. package/dist/types/restEntryPoints.d.ts +3 -0
  24. package/dist/types/synchronisedStorageService.d.ts +3 -3
  25. package/docs/changelog.md +233 -74
  26. package/docs/examples.md +80 -1
  27. package/docs/open-api/spec.json +17 -21
  28. package/docs/reference/classes/SyncSnapshotEntry.md +12 -12
  29. package/docs/reference/classes/SynchronisedStorageService.md +9 -9
  30. package/docs/reference/interfaces/ISyncPointerStore.md +2 -2
  31. package/docs/reference/interfaces/ISyncSnapshot.md +7 -7
  32. package/docs/reference/interfaces/ISyncState.md +3 -3
  33. package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +13 -13
  34. package/docs/reference/interfaces/ISynchronisedStorageServiceConstructorOptions.md +19 -19
  35. package/docs/reference/variables/restEntryPoints.md +2 -0
  36. package/package.json +20 -22
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # TWIN Synchronised Storage Service
1
+ # Synchronised Storage Service
2
2
 
3
- Synchronised storage contract implementation and REST endpoint definitions.
3
+ This package provides the synchronisation service layer responsible for tracking local and remote change state, scheduling update cycles, and handling trusted interactions around key and change propagation.
4
4
 
5
5
  ## Installation
6
6
 
@@ -33,6 +33,11 @@ export class BlobStorageHelper {
33
33
  * @internal
34
34
  */
35
35
  _isTrustedNode;
36
+ /**
37
+ * The node id of this node.
38
+ * @internal
39
+ */
40
+ _nodeId;
36
41
  /**
37
42
  * Create a new instance of BlobStorageHelper.
38
43
  * @param logging The logging component to use for logging.
@@ -48,6 +53,13 @@ export class BlobStorageHelper {
48
53
  this._blobStorageEncryptionKeyId = blobStorageEncryptionKeyId;
49
54
  this._isTrustedNode = isTrustedNode;
50
55
  }
56
+ /**
57
+ * Set the node id of this node.
58
+ * @param nodeId The node id to set.
59
+ */
60
+ setNodeId(nodeId) {
61
+ this._nodeId = nodeId;
62
+ }
51
63
  /**
52
64
  * Load a blob from storage.
53
65
  * @param blobId The id of the blob to apply.
@@ -65,7 +77,7 @@ export class BlobStorageHelper {
65
77
  try {
66
78
  const encryptedBlob = await this._blobStorageConnector.get(blobId);
67
79
  if (Is.uint8Array(encryptedBlob)) {
68
- const compressedBlob = await this._vaultConnector.decrypt(this._blobStorageEncryptionKeyId, VaultEncryptionType.ChaCha20Poly1305, encryptedBlob);
80
+ const compressedBlob = await this._vaultConnector.decrypt(`${this._nodeId}/${this._blobStorageEncryptionKeyId}`, VaultEncryptionType.ChaCha20Poly1305, encryptedBlob);
69
81
  const decompressedBlob = await Compression.decompress(compressedBlob, CompressionType.Gzip);
70
82
  await this._logging?.log({
71
83
  level: "info",
@@ -113,7 +125,7 @@ export class BlobStorageHelper {
113
125
  throw new GeneralError(BlobStorageHelper.CLASS_NAME, "notTrustedNode");
114
126
  }
115
127
  const compressedBlob = await Compression.compress(ObjectHelper.toBytes(blob), CompressionType.Gzip);
116
- const encryptedBlob = await this._vaultConnector.encrypt(this._blobStorageEncryptionKeyId, VaultEncryptionType.ChaCha20Poly1305, compressedBlob);
128
+ const encryptedBlob = await this._vaultConnector.encrypt(`${this._nodeId}/${this._blobStorageEncryptionKeyId}`, VaultEncryptionType.ChaCha20Poly1305, compressedBlob);
117
129
  try {
118
130
  const blobId = await this._blobStorageConnector.set(encryptedBlob);
119
131
  await this._logging?.log({
@@ -139,7 +151,7 @@ export class BlobStorageHelper {
139
151
  /**
140
152
  * Remove a blob from storage.
141
153
  * @param blobId The id of the blob to remove.
142
- * @returns Nothing.
154
+ * @returns A promise that resolves when the blob is removed.
143
155
  */
144
156
  async removeBlob(blobId) {
145
157
  await this._logging?.log({
@@ -1 +1 @@
1
- {"version":3,"file":"blobStorageHelper.js","sourceRoot":"","sources":["../../../src/helpers/blobStorageHelper.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,SAAS,EACT,WAAW,EACX,eAAe,EACf,YAAY,EACZ,EAAE,EACF,YAAY,EACZ,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAwB,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAEnF;;GAEG;AACH,MAAM,OAAO,iBAAiB;IAC7B;;OAEG;IACI,MAAM,CAAU,UAAU,uBAAuC;IAExE;;;OAGG;IACc,QAAQ,CAAqB;IAE9C;;;OAGG;IACc,eAAe,CAAkB;IAElD;;;OAGG;IACc,qBAAqB,CAAwB;IAE9D;;;OAGG;IACc,2BAA2B,CAAS;IAErD;;;OAGG;IACc,cAAc,CAAU;IAEzC;;;;;;;OAOG;IACH,YACC,OAAsC,EACtC,cAA+B,EAC/B,oBAA2C,EAC3C,0BAAkC,EAClC,aAAsB;QAEtB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,IAAI,CAAC,qBAAqB,GAAG,oBAAoB,CAAC;QAClD,IAAI,CAAC,2BAA2B,GAAG,0BAA0B,CAAC;QAC9D,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAI,MAAc;QACtC,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;YACpC,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE;gBACL,MAAM;aACN;SACD,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAEnE,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CACxD,IAAI,CAAC,2BAA2B,EAChC,mBAAmB,CAAC,gBAAgB,EACpC,aAAa,CACb,CAAC;gBAEF,MAAM,gBAAgB,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,cAAc,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;gBAC5F,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;oBACxB,KAAK,EAAE,MAAM;oBACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;oBACpC,OAAO,EAAE,YAAY;oBACrB,IAAI,EAAE;wBACL,MAAM;qBACN;iBACD,CAAC,CAAC;gBAEH,OAAO,YAAY,CAAC,SAAS,CAAI,gBAAgB,CAAC,CAAC;YACpD,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,iBAAiB,CAAC,UAAU;gBACpC,OAAO,EAAE,gBAAgB;gBACzB,IAAI,EAAE;oBACL,MAAM;iBACN;gBACD,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aACjC,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;YACpC,OAAO,EAAE,eAAe;YACxB,IAAI,EAAE;gBACL,MAAM;aACN;SACD,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAI,IAAO;QAC/B,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;YACpC,OAAO,EAAE,UAAU;SACnB,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1B,MAAM,IAAI,YAAY,CAAC,iBAAiB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,cAAc,GAAG,MAAM,WAAW,CAAC,QAAQ,CAChD,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,EAC1B,eAAe,CAAC,IAAI,CACpB,CAAC;QAEF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CACvD,IAAI,CAAC,2BAA2B,EAChC,mBAAmB,CAAC,gBAAgB,EACpC,cAAc,CACd,CAAC;QAEF,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAEnE,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;gBACpC,OAAO,EAAE,WAAW;gBACpB,IAAI,EAAE;oBACL,MAAM;iBACN;aACD,CAAC,CAAC;YACH,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,iBAAiB,CAAC,UAAU;gBACpC,OAAO,EAAE,gBAAgB;gBACzB,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aACjC,CAAC,CAAC;YACH,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,UAAU,CAAC,MAAc;QACrC,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;YACpC,OAAO,EAAE,YAAY;YACrB,IAAI,EAAE;gBACL,MAAM;aACN;SACD,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEhD,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;gBACpC,OAAO,EAAE,aAAa;gBACtB,IAAI,EAAE;oBACL,MAAM;iBACN;aACD,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,iBAAiB,CAAC,UAAU;gBACpC,OAAO,EAAE,kBAAkB;gBAC3B,IAAI,EAAE;oBACL,MAAM;iBACN;gBACD,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aACjC,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,CAAC,UAAU;YACpC,OAAO,EAAE,iBAAiB;YAC1B,IAAI,EAAE;gBACL,MAAM;aACN;SACD,CAAC,CAAC;IACJ,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IBlobStorageConnector } from \"@twin.org/blob-storage-models\";\nimport {\n\tBaseError,\n\tCompression,\n\tCompressionType,\n\tGeneralError,\n\tIs,\n\tObjectHelper\n} from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultEncryptionType } from \"@twin.org/vault-models\";\n\n/**\n * Class for performing blob storage operations.\n */\nexport class BlobStorageHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<BlobStorageHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The blob storage connector to use.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageConnector: IBlobStorageConnector;\n\n\t/**\n\t * The id of the vault key to use for encrypting/decrypting blobs.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageEncryptionKeyId: string;\n\n\t/**\n\t * Is this a trusted node.\n\t * @internal\n\t */\n\tprivate readonly _isTrustedNode: boolean;\n\n\t/**\n\t * Create a new instance of BlobStorageHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param vaultConnector The vault connector to use for for the encryption key.\n\t * @param blobStorageConnector The blob storage component to use.\n\t * @param blobStorageEncryptionKeyId The id of the vault key to use for encrypting/decrypting blobs.\n\t * @param isTrustedNode Is this a trusted node.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\tvaultConnector: IVaultConnector,\n\t\tblobStorageConnector: IBlobStorageConnector,\n\t\tblobStorageEncryptionKeyId: string,\n\t\tisTrustedNode: boolean\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._vaultConnector = vaultConnector;\n\t\tthis._blobStorageConnector = blobStorageConnector;\n\t\tthis._blobStorageEncryptionKeyId = blobStorageEncryptionKeyId;\n\t\tthis._isTrustedNode = isTrustedNode;\n\t}\n\n\t/**\n\t * Load a blob from storage.\n\t * @param blobId The id of the blob to apply.\n\t * @returns The blob.\n\t */\n\tpublic async loadBlob<T>(blobId: string): Promise<T | undefined> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"loadBlob\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tconst encryptedBlob = await this._blobStorageConnector.get(blobId);\n\n\t\t\tif (Is.uint8Array(encryptedBlob)) {\n\t\t\t\tconst compressedBlob = await this._vaultConnector.decrypt(\n\t\t\t\t\tthis._blobStorageEncryptionKeyId,\n\t\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\t\tencryptedBlob\n\t\t\t\t);\n\n\t\t\t\tconst decompressedBlob = await Compression.decompress(compressedBlob, CompressionType.Gzip);\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"loadedBlob\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tblobId\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn ObjectHelper.fromBytes<T>(decompressedBlob);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"loadBlobFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"loadBlobEmpty\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Save a blob.\n\t * @param blob The blob to save.\n\t * @returns The id of the blob.\n\t */\n\tpublic async saveBlob<T>(blob: T): Promise<string> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"saveBlob\"\n\t\t});\n\n\t\tif (!this._isTrustedNode) {\n\t\t\tthrow new GeneralError(BlobStorageHelper.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tconst compressedBlob = await Compression.compress(\n\t\t\tObjectHelper.toBytes(blob),\n\t\t\tCompressionType.Gzip\n\t\t);\n\n\t\tconst encryptedBlob = await this._vaultConnector.encrypt(\n\t\t\tthis._blobStorageEncryptionKeyId,\n\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\tcompressedBlob\n\t\t);\n\n\t\ttry {\n\t\t\tconst blobId = await this._blobStorageConnector.set(encryptedBlob);\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"savedBlob\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn blobId;\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"saveBlobFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Remove a blob from storage.\n\t * @param blobId The id of the blob to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeBlob(blobId: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"removeBlob\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tawait this._blobStorageConnector.remove(blobId);\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"removedBlob\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"removeBlobFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"removeBlobEmpty\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
1
+ {"version":3,"file":"blobStorageHelper.js","sourceRoot":"","sources":["../../../src/helpers/blobStorageHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IBlobStorageConnector } from \"@twin.org/blob-storage-models\";\nimport {\n\tBaseError,\n\tCompression,\n\tCompressionType,\n\tGeneralError,\n\tIs,\n\tObjectHelper\n} from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultEncryptionType } from \"@twin.org/vault-models\";\n\n/**\n * Class for performing blob storage operations.\n */\nexport class BlobStorageHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<BlobStorageHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The blob storage connector to use.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageConnector: IBlobStorageConnector;\n\n\t/**\n\t * The id of the vault key to use for encrypting/decrypting blobs.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageEncryptionKeyId: string;\n\n\t/**\n\t * Is this a trusted node.\n\t * @internal\n\t */\n\tprivate readonly _isTrustedNode: boolean;\n\n\t/**\n\t * The node id of this node.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of BlobStorageHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param vaultConnector The vault connector to use for for the encryption key.\n\t * @param blobStorageConnector The blob storage component to use.\n\t * @param blobStorageEncryptionKeyId The id of the vault key to use for encrypting/decrypting blobs.\n\t * @param isTrustedNode Is this a trusted node.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\tvaultConnector: IVaultConnector,\n\t\tblobStorageConnector: IBlobStorageConnector,\n\t\tblobStorageEncryptionKeyId: string,\n\t\tisTrustedNode: boolean\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._vaultConnector = vaultConnector;\n\t\tthis._blobStorageConnector = blobStorageConnector;\n\t\tthis._blobStorageEncryptionKeyId = blobStorageEncryptionKeyId;\n\t\tthis._isTrustedNode = isTrustedNode;\n\t}\n\n\t/**\n\t * Set the node id of this node.\n\t * @param nodeId The node id to set.\n\t */\n\tpublic setNodeId(nodeId: string): void {\n\t\tthis._nodeId = nodeId;\n\t}\n\n\t/**\n\t * Load a blob from storage.\n\t * @param blobId The id of the blob to apply.\n\t * @returns The blob.\n\t */\n\tpublic async loadBlob<T>(blobId: string): Promise<T | undefined> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"loadBlob\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tconst encryptedBlob = await this._blobStorageConnector.get(blobId);\n\n\t\t\tif (Is.uint8Array(encryptedBlob)) {\n\t\t\t\tconst compressedBlob = await this._vaultConnector.decrypt(\n\t\t\t\t\t`${this._nodeId}/${this._blobStorageEncryptionKeyId}`,\n\t\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\t\tencryptedBlob\n\t\t\t\t);\n\n\t\t\t\tconst decompressedBlob = await Compression.decompress(compressedBlob, CompressionType.Gzip);\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"loadedBlob\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tblobId\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn ObjectHelper.fromBytes<T>(decompressedBlob);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"loadBlobFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"loadBlobEmpty\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Save a blob.\n\t * @param blob The blob to save.\n\t * @returns The id of the blob.\n\t */\n\tpublic async saveBlob<T>(blob: T): Promise<string> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"saveBlob\"\n\t\t});\n\n\t\tif (!this._isTrustedNode) {\n\t\t\tthrow new GeneralError(BlobStorageHelper.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tconst compressedBlob = await Compression.compress(\n\t\t\tObjectHelper.toBytes(blob),\n\t\t\tCompressionType.Gzip\n\t\t);\n\n\t\tconst encryptedBlob = await this._vaultConnector.encrypt(\n\t\t\t`${this._nodeId}/${this._blobStorageEncryptionKeyId}`,\n\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\tcompressedBlob\n\t\t);\n\n\t\ttry {\n\t\t\tconst blobId = await this._blobStorageConnector.set(encryptedBlob);\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"savedBlob\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn blobId;\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"saveBlobFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Remove a blob from storage.\n\t * @param blobId The id of the blob to remove.\n\t * @returns A promise that resolves when the blob is removed.\n\t */\n\tpublic async removeBlob(blobId: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"removeBlob\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tawait this._blobStorageConnector.remove(blobId);\n\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"removedBlob\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\t\tmessage: \"removeBlobFailed\",\n\t\t\t\tdata: {\n\t\t\t\t\tblobId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: BlobStorageHelper.CLASS_NAME,\n\t\t\tmessage: \"removeBlobEmpty\",\n\t\t\tdata: {\n\t\t\t\tblobId\n\t\t\t}\n\t\t});\n\t}\n}\n"]}
@@ -103,7 +103,7 @@ export class ChangeSetHelper {
103
103
  /**
104
104
  * Apply a sync changeset.
105
105
  * @param syncChangeset The sync changeset to apply.
106
- * @returns Nothing.
106
+ * @returns A promise that resolves when all changes in the set have been published to the event bus.
107
107
  */
108
108
  async applyChangeset(syncChangeset) {
109
109
  if (Is.arrayValue(syncChangeset.changes)) {
@@ -192,8 +192,8 @@ export class ChangeSetHelper {
192
192
  /**
193
193
  * Reset the storage for a given storage key.
194
194
  * @param storageKey The key of the storage to reset.
195
- * @param resetMode The reset mode, this will use the nodeId in the entities to determine which are local/remote.
196
- * @returns Nothing.
195
+ * @param resetMode The reset mode, which uses the nodeId in the entities to determine which are local or remote.
196
+ * @returns A promise that resolves when the reset event is published to the event bus.
197
197
  */
198
198
  async reset(storageKey, resetMode) {
199
199
  // If we are applying a consolidation we need to reset the local db
@@ -1 +1 @@
1
- {"version":3,"file":"changeSetHelper.js","sourceRoot":"","sources":["../../../src/helpers/changeSetHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { BaseError, Converter, Is, ObjectHelper, RandomHelper } from \"@twin.org/core\";\nimport type { IEventBusComponent } from \"@twin.org/event-bus-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\ttype ISyncChangeSet,\n\ttype ISyncItemRemove,\n\ttype ISyncItemSet,\n\ttype ISyncReset,\n\tSyncChangeOperation,\n\tSynchronisedStorageTopics,\n\ttype SyncNodeIdMode\n} from \"@twin.org/synchronised-storage-models\";\nimport type { BlobStorageHelper } from \"./blobStorageHelper.js\";\n\n/**\n * Class for performing change set operations.\n */\nexport class ChangeSetHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<ChangeSetHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The event bus component.\n\t * @internal\n\t */\n\tprivate readonly _eventBusComponent: IEventBusComponent;\n\n\t/**\n\t * The blob storage helper to use for remote sync states.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageHelper: BlobStorageHelper;\n\n\t/**\n\t * The identity of the node that is performing the update.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of ChangeSetHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param eventBusComponent The event bus component to use for events.\n\t * @param blobStorageHelper The blob storage component to use for remote sync states.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\teventBusComponent: IEventBusComponent,\n\t\tblobStorageHelper: BlobStorageHelper\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._eventBusComponent = eventBusComponent;\n\t\tthis._blobStorageHelper = blobStorageHelper;\n\t}\n\n\t/**\n\t * Set the node identity to use for signing changesets.\n\t * @param nodeId The identity of the node that is performing the update.\n\t */\n\tpublic setNodeId(nodeId: string): void {\n\t\tthis._nodeId = nodeId;\n\t}\n\n\t/**\n\t * Get a changeset.\n\t * @param changeSetStorageId The id of the sync changeset to apply.\n\t * @returns The changeset if it was verified.\n\t */\n\tpublic async getChangeset(changeSetStorageId: string): Promise<ISyncChangeSet | undefined> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"getChangeSet\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tconst syncChangeSet =\n\t\t\t\tawait this._blobStorageHelper.loadBlob<ISyncChangeSet>(changeSetStorageId);\n\n\t\t\treturn syncChangeSet;\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"warn\",\n\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\tmessage: \"getChangeSetError\",\n\t\t\t\tdata: {\n\t\t\t\t\tchangeSetStorageId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"getChangeSetEmpty\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Apply a sync changeset.\n\t * @param changeSetStorageId The id of the sync changeset to apply.\n\t * @returns The changeset if it existed.\n\t */\n\tpublic async getAndApplyChangeset(\n\t\tchangeSetStorageId: string\n\t): Promise<ISyncChangeSet | undefined> {\n\t\tconst syncChangeset = await this.getChangeset(changeSetStorageId);\n\n\t\t// Only apply changesets from other nodes, we don't want to overwrite\n\t\t// any changes we have made to local entity storage\n\t\tif (!Is.empty(syncChangeset) && syncChangeset.nodeIdentity !== this._nodeId) {\n\t\t\tawait this.applyChangeset(syncChangeset);\n\t\t}\n\n\t\treturn syncChangeset;\n\t}\n\n\t/**\n\t * Apply a sync changeset.\n\t * @param syncChangeset The sync changeset to apply.\n\t * @returns Nothing.\n\t */\n\tpublic async applyChangeset(syncChangeset: ISyncChangeSet): Promise<void> {\n\t\tif (Is.arrayValue(syncChangeset.changes)) {\n\t\t\tfor (const change of syncChangeset.changes) {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"changeSetApplyingChange\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\toperation: change.operation,\n\t\t\t\t\t\tid: change.id\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tswitch (change.operation) {\n\t\t\t\t\tcase SyncChangeOperation.Set:\n\t\t\t\t\t\tif (!Is.empty(change.entity)) {\n\t\t\t\t\t\t\t// The id was stripped from the entity as it is part of the operation\n\t\t\t\t\t\t\t// we make sure we reinstate it in the publish\n\t\t\t\t\t\t\t// Also the node identity was stripped when stored in the changeset\n\t\t\t\t\t\t\t// as the changeset is signed with the node identity.\n\t\t\t\t\t\t\t// so we need to restore it here.\n\t\t\t\t\t\t\tawait this._eventBusComponent.publish<ISyncItemSet>(\n\t\t\t\t\t\t\t\tSynchronisedStorageTopics.RemoteItemSet,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstorageKey: syncChangeset.storageKey,\n\t\t\t\t\t\t\t\t\tentity: {\n\t\t\t\t\t\t\t\t\t\t...change.entity,\n\t\t\t\t\t\t\t\t\t\tid: change.id,\n\t\t\t\t\t\t\t\t\t\tnodeIdentity: syncChangeset.nodeIdentity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase SyncChangeOperation.Delete:\n\t\t\t\t\t\tif (!Is.empty(change.id)) {\n\t\t\t\t\t\t\tawait this._eventBusComponent.publish<ISyncItemRemove>(\n\t\t\t\t\t\t\t\tSynchronisedStorageTopics.RemoteItemRemove,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstorageKey: syncChangeset.storageKey,\n\t\t\t\t\t\t\t\t\tid: change.id,\n\t\t\t\t\t\t\t\t\tnodeId: syncChangeset.nodeIdentity\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Store the changeset.\n\t * @param syncChangeSet The sync change set to store.\n\t * @returns The id of the change set.\n\t */\n\tpublic async storeChangeSet(syncChangeSet: ISyncChangeSet): Promise<string> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"changeSetStoring\",\n\t\t\tdata: {\n\t\t\t\tid: syncChangeSet.id\n\t\t\t}\n\t\t});\n\n\t\treturn this._blobStorageHelper.saveBlob(syncChangeSet);\n\t}\n\n\t/**\n\t * Copy a change set.\n\t * @param syncChangeSet The sync changeset to copy.\n\t * @returns The id of the updated change set.\n\t */\n\tpublic async copyChangeset(syncChangeSet: ISyncChangeSet): Promise<\n\t\t| {\n\t\t\t\tsyncChangeSet: ISyncChangeSet;\n\t\t\t\tchangeSetStorageId: string;\n\t\t }\n\t\t| undefined\n\t> {\n\t\tif (Is.stringValue(this._nodeId)) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\tmessage: \"copyChangeSet\",\n\t\t\t\tdata: {\n\t\t\t\t\tchangeSetStorageId: syncChangeSet.id\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Allocate a new id to the changeset copy\n\t\t\tconst copy = ObjectHelper.clone(syncChangeSet);\n\t\t\tcopy.id = Converter.bytesToHex(RandomHelper.generate(32));\n\n\t\t\t// Store the copy\n\t\t\treturn {\n\t\t\t\tsyncChangeSet: copy,\n\t\t\t\tchangeSetStorageId: await this.storeChangeSet(copy)\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Reset the storage for a given storage key.\n\t * @param storageKey The key of the storage to reset.\n\t * @param resetMode The reset mode, this will use the nodeId in the entities to determine which are local/remote.\n\t * @returns Nothing.\n\t */\n\tpublic async reset(storageKey: string, resetMode: SyncNodeIdMode): Promise<void> {\n\t\t// If we are applying a consolidation we need to reset the local db\n\t\t// but keep any entries from the local node, as they might have been updated\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"storageReset\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\t\tawait this._eventBusComponent.publish<ISyncReset>(SynchronisedStorageTopics.Reset, {\n\t\t\tstorageKey,\n\t\t\tresetMode\n\t\t});\n\t}\n}\n"]}
1
+ {"version":3,"file":"changeSetHelper.js","sourceRoot":"","sources":["../../../src/helpers/changeSetHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { BaseError, Converter, Is, ObjectHelper, RandomHelper } from \"@twin.org/core\";\nimport type { IEventBusComponent } from \"@twin.org/event-bus-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\ttype ISyncChangeSet,\n\ttype ISyncItemRemove,\n\ttype ISyncItemSet,\n\ttype ISyncReset,\n\tSyncChangeOperation,\n\tSynchronisedStorageTopics,\n\ttype SyncNodeIdMode\n} from \"@twin.org/synchronised-storage-models\";\nimport type { BlobStorageHelper } from \"./blobStorageHelper.js\";\n\n/**\n * Class for performing change set operations.\n */\nexport class ChangeSetHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<ChangeSetHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The event bus component.\n\t * @internal\n\t */\n\tprivate readonly _eventBusComponent: IEventBusComponent;\n\n\t/**\n\t * The blob storage helper to use for remote sync states.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageHelper: BlobStorageHelper;\n\n\t/**\n\t * The identity of the node that is performing the update.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of ChangeSetHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param eventBusComponent The event bus component to use for events.\n\t * @param blobStorageHelper The blob storage component to use for remote sync states.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\teventBusComponent: IEventBusComponent,\n\t\tblobStorageHelper: BlobStorageHelper\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._eventBusComponent = eventBusComponent;\n\t\tthis._blobStorageHelper = blobStorageHelper;\n\t}\n\n\t/**\n\t * Set the node identity to use for signing changesets.\n\t * @param nodeId The identity of the node that is performing the update.\n\t */\n\tpublic setNodeId(nodeId: string): void {\n\t\tthis._nodeId = nodeId;\n\t}\n\n\t/**\n\t * Get a changeset.\n\t * @param changeSetStorageId The id of the sync changeset to apply.\n\t * @returns The changeset if it was verified.\n\t */\n\tpublic async getChangeset(changeSetStorageId: string): Promise<ISyncChangeSet | undefined> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"getChangeSet\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId\n\t\t\t}\n\t\t});\n\n\t\ttry {\n\t\t\tconst syncChangeSet =\n\t\t\t\tawait this._blobStorageHelper.loadBlob<ISyncChangeSet>(changeSetStorageId);\n\n\t\t\treturn syncChangeSet;\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"warn\",\n\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\tmessage: \"getChangeSetError\",\n\t\t\t\tdata: {\n\t\t\t\t\tchangeSetStorageId\n\t\t\t\t},\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"getChangeSetEmpty\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Apply a sync changeset.\n\t * @param changeSetStorageId The id of the sync changeset to apply.\n\t * @returns The changeset if it existed.\n\t */\n\tpublic async getAndApplyChangeset(\n\t\tchangeSetStorageId: string\n\t): Promise<ISyncChangeSet | undefined> {\n\t\tconst syncChangeset = await this.getChangeset(changeSetStorageId);\n\n\t\t// Only apply changesets from other nodes, we don't want to overwrite\n\t\t// any changes we have made to local entity storage\n\t\tif (!Is.empty(syncChangeset) && syncChangeset.nodeIdentity !== this._nodeId) {\n\t\t\tawait this.applyChangeset(syncChangeset);\n\t\t}\n\n\t\treturn syncChangeset;\n\t}\n\n\t/**\n\t * Apply a sync changeset.\n\t * @param syncChangeset The sync changeset to apply.\n\t * @returns A promise that resolves when all changes in the set have been published to the event bus.\n\t */\n\tpublic async applyChangeset(syncChangeset: ISyncChangeSet): Promise<void> {\n\t\tif (Is.arrayValue(syncChangeset.changes)) {\n\t\t\tfor (const change of syncChangeset.changes) {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"changeSetApplyingChange\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\toperation: change.operation,\n\t\t\t\t\t\tid: change.id\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tswitch (change.operation) {\n\t\t\t\t\tcase SyncChangeOperation.Set:\n\t\t\t\t\t\tif (!Is.empty(change.entity)) {\n\t\t\t\t\t\t\t// The id was stripped from the entity as it is part of the operation\n\t\t\t\t\t\t\t// we make sure we reinstate it in the publish\n\t\t\t\t\t\t\t// Also the node identity was stripped when stored in the changeset\n\t\t\t\t\t\t\t// as the changeset is signed with the node identity.\n\t\t\t\t\t\t\t// so we need to restore it here.\n\t\t\t\t\t\t\tawait this._eventBusComponent.publish<ISyncItemSet>(\n\t\t\t\t\t\t\t\tSynchronisedStorageTopics.RemoteItemSet,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstorageKey: syncChangeset.storageKey,\n\t\t\t\t\t\t\t\t\tentity: {\n\t\t\t\t\t\t\t\t\t\t...change.entity,\n\t\t\t\t\t\t\t\t\t\tid: change.id,\n\t\t\t\t\t\t\t\t\t\tnodeIdentity: syncChangeset.nodeIdentity\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase SyncChangeOperation.Delete:\n\t\t\t\t\t\tif (!Is.empty(change.id)) {\n\t\t\t\t\t\t\tawait this._eventBusComponent.publish<ISyncItemRemove>(\n\t\t\t\t\t\t\t\tSynchronisedStorageTopics.RemoteItemRemove,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstorageKey: syncChangeset.storageKey,\n\t\t\t\t\t\t\t\t\tid: change.id,\n\t\t\t\t\t\t\t\t\tnodeId: syncChangeset.nodeIdentity\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Store the changeset.\n\t * @param syncChangeSet The sync change set to store.\n\t * @returns The id of the change set.\n\t */\n\tpublic async storeChangeSet(syncChangeSet: ISyncChangeSet): Promise<string> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"changeSetStoring\",\n\t\t\tdata: {\n\t\t\t\tid: syncChangeSet.id\n\t\t\t}\n\t\t});\n\n\t\treturn this._blobStorageHelper.saveBlob(syncChangeSet);\n\t}\n\n\t/**\n\t * Copy a change set.\n\t * @param syncChangeSet The sync changeset to copy.\n\t * @returns The id of the updated change set.\n\t */\n\tpublic async copyChangeset(syncChangeSet: ISyncChangeSet): Promise<\n\t\t| {\n\t\t\t\tsyncChangeSet: ISyncChangeSet;\n\t\t\t\tchangeSetStorageId: string;\n\t\t }\n\t\t| undefined\n\t> {\n\t\tif (Is.stringValue(this._nodeId)) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\t\tmessage: \"copyChangeSet\",\n\t\t\t\tdata: {\n\t\t\t\t\tchangeSetStorageId: syncChangeSet.id\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Allocate a new id to the changeset copy\n\t\t\tconst copy = ObjectHelper.clone(syncChangeSet);\n\t\t\tcopy.id = Converter.bytesToHex(RandomHelper.generate(32));\n\n\t\t\t// Store the copy\n\t\t\treturn {\n\t\t\t\tsyncChangeSet: copy,\n\t\t\t\tchangeSetStorageId: await this.storeChangeSet(copy)\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Reset the storage for a given storage key.\n\t * @param storageKey The key of the storage to reset.\n\t * @param resetMode The reset mode, which uses the nodeId in the entities to determine which are local or remote.\n\t * @returns A promise that resolves when the reset event is published to the event bus.\n\t */\n\tpublic async reset(storageKey: string, resetMode: SyncNodeIdMode): Promise<void> {\n\t\t// If we are applying a consolidation we need to reset the local db\n\t\t// but keep any entries from the local node, as they might have been updated\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: ChangeSetHelper.CLASS_NAME,\n\t\t\tmessage: \"storageReset\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\t\tawait this._eventBusComponent.publish<ISyncReset>(SynchronisedStorageTopics.Reset, {\n\t\t\tstorageKey,\n\t\t\tresetMode\n\t\t});\n\t}\n}\n"]}
@@ -43,7 +43,7 @@ export class LocalSyncStateHelper {
43
43
  * @param storageKey The storage key of the snapshot to add the change for.
44
44
  * @param operation The operation to perform.
45
45
  * @param id The id of the entity to add the change for.
46
- * @returns Nothing.
46
+ * @returns A promise that resolves when the change is recorded in the local snapshot.
47
47
  */
48
48
  async addLocalChange(storageKey, operation, id) {
49
49
  await this._logging?.log({
@@ -143,7 +143,7 @@ export class LocalSyncStateHelper {
143
143
  /**
144
144
  * Set the current local snapshot with changes for this node.
145
145
  * @param localChangeSnapshot The local change snapshot to set.
146
- * @returns Nothing.
146
+ * @returns A promise that resolves when the snapshot is persisted to entity storage.
147
147
  */
148
148
  async setLocalChangeSnapshot(localChangeSnapshot) {
149
149
  await this._logging?.log({
@@ -157,9 +157,9 @@ export class LocalSyncStateHelper {
157
157
  await this._snapshotEntryEntityStorage.set(localChangeSnapshot);
158
158
  }
159
159
  /**
160
- * Get the current local snapshot with the changes for this node.
160
+ * Remove the local snapshot entry from storage.
161
161
  * @param localChangeSnapshot The local change snapshot to remove.
162
- * @returns Nothing.
162
+ * @returns A promise that resolves when the snapshot is removed from entity storage.
163
163
  */
164
164
  async removeLocalChangeSnapshot(localChangeSnapshot) {
165
165
  await this._logging?.log({
@@ -176,7 +176,7 @@ export class LocalSyncStateHelper {
176
176
  * Apply a sync state to the local node.
177
177
  * @param storageKey The storage key of the snapshot to sync with.
178
178
  * @param syncState The sync state to sync with.
179
- * @returns Nothing.
179
+ * @returns A promise that resolves when all new and modified snapshots have been processed.
180
180
  */
181
181
  async applySyncState(storageKey, syncState) {
182
182
  await this._logging?.log({
@@ -324,7 +324,7 @@ export class LocalSyncStateHelper {
324
324
  /**
325
325
  * Process the modified snapshots and store them in the local storage.
326
326
  * @param modifiedSnapshots The modified snapshots to process.
327
- * @returns Nothing.
327
+ * @returns A promise that resolves when all new changesets in each snapshot have been applied and the snapshots saved.
328
328
  * @internal
329
329
  */
330
330
  async processModifiedSnapshots(modifiedSnapshots) {
@@ -357,7 +357,7 @@ export class LocalSyncStateHelper {
357
357
  /**
358
358
  * Process the new snapshots and store them in the local storage.
359
359
  * @param newSnapshots The new snapshots to process.
360
- * @returns Nothing.
360
+ * @returns A promise that resolves when all changesets for each snapshot have been applied and the snapshots saved.
361
361
  * @internal
362
362
  */
363
363
  async processNewSnapshots(newSnapshots) {
@@ -1 +1 @@
1
- {"version":3,"file":"localSyncStateHelper.js","sourceRoot":"","sources":["../../../src/helpers/localSyncStateHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Converter, Is, RandomHelper } from \"@twin.org/core\";\nimport { ComparisonOperator } from \"@twin.org/entity\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type SyncChangeOperation, SyncNodeIdMode } from \"@twin.org/synchronised-storage-models\";\nimport type { ChangeSetHelper } from \"./changeSetHelper.js\";\nimport { SYNC_SNAPSHOT_VERSION } from \"./versions.js\";\nimport type { SyncSnapshotEntry } from \"../entities/syncSnapshotEntry.js\";\nimport type { ISyncState } from \"../models/ISyncState.js\";\n\n/**\n * Class for performing entity storage operations in decentralised storage.\n */\nexport class LocalSyncStateHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LocalSyncStateHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The storage connector for the sync snapshot entries.\n\t * @internal\n\t */\n\tprivate readonly _snapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>;\n\n\t/**\n\t * The change set helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _changeSetHelper: ChangeSetHelper;\n\n\t/**\n\t * Create a new instance of LocalSyncStateHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param snapshotEntryEntityStorage The storage connector for the sync snapshot entries.\n\t * @param changeSetHelper The change set helper to use for applying changesets.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\tsnapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>,\n\t\tchangeSetHelper: ChangeSetHelper\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._snapshotEntryEntityStorage = snapshotEntryEntityStorage;\n\t\tthis._changeSetHelper = changeSetHelper;\n\t}\n\n\t/**\n\t * Add a new change to the local snapshot.\n\t * @param storageKey The storage key of the snapshot to add the change for.\n\t * @param operation The operation to perform.\n\t * @param id The id of the entity to add the change for.\n\t * @returns Nothing.\n\t */\n\tpublic async addLocalChange(\n\t\tstorageKey: string,\n\t\toperation: SyncChangeOperation,\n\t\tid: string\n\t): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"addLocalChange\",\n\t\t\tdata: {\n\t\t\t\tstorageKey,\n\t\t\t\toperation,\n\t\t\t\tid\n\t\t\t}\n\t\t});\n\n\t\tconst localChangeSnapshots = await this.getSnapshots(storageKey, true);\n\n\t\tif (localChangeSnapshots.length > 0) {\n\t\t\tconst localChangeSnapshot = localChangeSnapshots[0];\n\n\t\t\tlocalChangeSnapshot.changes ??= [];\n\n\t\t\t// If we already have a change for this id we are\n\t\t\t// about to supersede it, we remove the previous change\n\t\t\t// to avoid having multiple changes for the same id\n\t\t\tconst previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);\n\t\t\tif (previousChangeIndex !== -1) {\n\t\t\t\tlocalChangeSnapshot.changes.splice(previousChangeIndex, 1);\n\t\t\t}\n\n\t\t\t// If we already have changes from previous updates\n\t\t\t// then make sure we update the dateModified, otherwise\n\t\t\t// we assume this is the first change and setting modified is not necessary\n\t\t\tif (localChangeSnapshot.changes.length > 0) {\n\t\t\t\tlocalChangeSnapshot.dateModified = new Date(Date.now()).toISOString();\n\t\t\t}\n\n\t\t\tlocalChangeSnapshot.changes.push({ operation, id });\n\n\t\t\tawait this.setLocalChangeSnapshot(localChangeSnapshot);\n\t\t}\n\t}\n\n\t/**\n\t * Get the snapshot which contains just the changes for this node.\n\t * @param storageKey The storage key of the snapshot to get.\n\t * @param isLocal Whether to get the local snapshot or not.\n\t * @returns The local snapshot entry.\n\t */\n\tpublic async getSnapshots(storageKey: string, isLocal: boolean): Promise<SyncSnapshotEntry[]> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"getSnapshots\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\tconst queryResult = await this._snapshotEntryEntityStorage.query({\n\t\t\tconditions: [\n\t\t\t\t{\n\t\t\t\t\tproperty: \"isLocal\",\n\t\t\t\t\tvalue: isLocal,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: \"storageKey\",\n\t\t\t\t\tvalue: storageKey,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals\n\t\t\t\t}\n\t\t\t]\n\t\t});\n\n\t\tif (queryResult.entities.length > 0) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"getSnapshotsExists\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn queryResult.entities as SyncSnapshotEntry[];\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"getSnapshotsDoesNotExist\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\t\tconst now = new Date(Date.now()).toISOString();\n\t\treturn [\n\t\t\t{\n\t\t\t\tversion: SYNC_SNAPSHOT_VERSION,\n\t\t\t\tid: Converter.bytesToHex(RandomHelper.generate(32)),\n\t\t\t\tstorageKey,\n\t\t\t\tdateCreated: now,\n\t\t\t\tdateModified: now,\n\t\t\t\tchangeSetStorageIds: [],\n\t\t\t\tisLocal,\n\t\t\t\tisConsolidated: false,\n\t\t\t\tepoch: 0\n\t\t\t}\n\t\t];\n\t}\n\n\t/**\n\t * Set the current local snapshot with changes for this node.\n\t * @param localChangeSnapshot The local change snapshot to set.\n\t * @returns Nothing.\n\t */\n\tpublic async setLocalChangeSnapshot(localChangeSnapshot: SyncSnapshotEntry): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"setLocalChangeSnapshot\",\n\t\t\tdata: {\n\t\t\t\tstorageKey: localChangeSnapshot.storageKey\n\t\t\t}\n\t\t});\n\t\tawait this._snapshotEntryEntityStorage.set(localChangeSnapshot);\n\t}\n\n\t/**\n\t * Get the current local snapshot with the changes for this node.\n\t * @param localChangeSnapshot The local change snapshot to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeLocalChangeSnapshot(localChangeSnapshot: SyncSnapshotEntry): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"removeLocalChangeSnapshot\",\n\t\t\tdata: {\n\t\t\t\tsnapshotId: localChangeSnapshot.id\n\t\t\t}\n\t\t});\n\t\tawait this._snapshotEntryEntityStorage.remove(localChangeSnapshot.id);\n\t}\n\n\t/**\n\t * Apply a sync state to the local node.\n\t * @param storageKey The storage key of the snapshot to sync with.\n\t * @param syncState The sync state to sync with.\n\t * @returns Nothing.\n\t */\n\tpublic async applySyncState(storageKey: string, syncState: ISyncState): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"applySyncState\",\n\t\t\tdata: {\n\t\t\t\tsnapshotCount: syncState.snapshots.length\n\t\t\t}\n\t\t});\n\n\t\t// Get all the existing snapshots that we have processed previously\n\t\tlet existingSnapshots = await this.getSnapshots(storageKey, false);\n\n\t\t// Sort from newest to oldest\n\t\texistingSnapshots = existingSnapshots.sort(\n\t\t\t(a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime()\n\t\t);\n\n\t\t// Sort from newest to oldest\n\t\tconst syncStateSnapshots = syncState.snapshots.sort(\n\t\t\t(a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime()\n\t\t);\n\n\t\t// Get the newest epoch from the local storage\n\t\tconst newestExistingEpoch = existingSnapshots[0]?.epoch ?? 0;\n\n\t\t// Get the oldest epoch from the remote storage\n\t\tconst oldestSyncStateEpoch = syncStateSnapshots[syncStateSnapshots.length - 1]?.epoch ?? 0;\n\n\t\t// If there is a gap between the largest epoch we have locally\n\t\t// and the smallest epoch we have remotely then we have missed\n\t\t// data so we need to perform a full sync\n\t\tconst hasEpochGap = newestExistingEpoch + 1 < oldestSyncStateEpoch;\n\n\t\t// If we have an epoch gap or no existing snapshots then we need to apply\n\t\t// a full sync from a consolidation\n\t\tif (!existingSnapshots.some(s => s.isConsolidated) || hasEpochGap) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"applySnapshotNoExisting\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst mostRecentConsolidation = syncStateSnapshots.findIndex(\n\t\t\t\tsnapshot => snapshot.isConsolidated\n\t\t\t);\n\t\t\tif (mostRecentConsolidation !== -1) {\n\t\t\t\t// We found the most recent consolidated snapshot, we can use it\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshotFoundConsolidated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\tsnapshotId: syncStateSnapshots[mostRecentConsolidation].id\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// We need to reset the entity storage and remove all the remote items\n\t\t\t\t// so that we use just the ones from the consolidation, since\n\t\t\t\t// we don't have any existing there shouldn't be any remote entries\n\t\t\t\t// but we reset nonetheless\n\t\t\t\tawait this._changeSetHelper.reset(storageKey, SyncNodeIdMode.Remote);\n\n\t\t\t\t// We need to process the most recent consolidation and all changes\n\t\t\t\t// that were made since then, from newest to oldest (so newer changes override older ones)\n\t\t\t\t// Process snapshots from the consolidation point (most recent) back to the newest\n\t\t\t\tfor (let i = mostRecentConsolidation; i >= 0; i--) {\n\t\t\t\t\tawait this.processNewSnapshots([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...syncStateSnapshots[i],\n\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\tisLocal: false\n\t\t\t\t\t\t}\n\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshotNoConsolidated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\t// We have existing consolidated remote snapshots, so we can assume that we have\n\t\t\t// applied at least one consolidation snapshot, in this case we need to look at the changes since\n\t\t\t// then and apply them if we haven't already\n\t\t\t// We don't need to apply any additional consolidated snapshots, just the changesets\n\n\t\t\t// Create a lookup map for the existing snapshots\n\t\t\tconst existingSnapshotsMap: { [id: string]: SyncSnapshotEntry } = {};\n\t\t\tfor (const snapshot of existingSnapshots) {\n\t\t\t\texistingSnapshotsMap[snapshot.id] = snapshot;\n\t\t\t}\n\n\t\t\tconst newSnapshots: SyncSnapshotEntry[] = [];\n\t\t\tconst modifiedSnapshots: {\n\t\t\t\tcurrentSnapshot: SyncSnapshotEntry;\n\t\t\t\tupdatedSnapshot: SyncSnapshotEntry;\n\t\t\t}[] = [];\n\t\t\tconst referencedExistingSnapshots: string[] = Object.keys(existingSnapshotsMap);\n\n\t\t\tlet completedProcessing = false;\n\t\t\tfor (const snapshot of syncStateSnapshots) {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshot\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsnapshotId: snapshot.id,\n\t\t\t\t\t\tdateCreated: new Date(snapshot.dateCreated).toISOString()\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// See if we have the snapshot stored locally\n\t\t\t\tconst currentSnapshot = existingSnapshotsMap[snapshot.id];\n\n\t\t\t\t// As we are referencing an existing snapshot, we need to remove it from the list\n\t\t\t\t// to allow us to cleanup any unreferenced snapshots later\n\t\t\t\tconst idx = referencedExistingSnapshots.indexOf(snapshot.id);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\treferencedExistingSnapshots.splice(idx, 1);\n\t\t\t\t}\n\n\t\t\t\t// No need to apply consolidated snapshots\n\t\t\t\tif (!snapshot.isConsolidated && !completedProcessing) {\n\t\t\t\t\tconst updatedSnapshot: SyncSnapshotEntry = {\n\t\t\t\t\t\t...snapshot,\n\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\tisLocal: false\n\t\t\t\t\t};\n\n\t\t\t\t\tif (Is.empty(currentSnapshot)) {\n\t\t\t\t\t\t// We don't have the snapshot locally, so we need to process all of it\n\t\t\t\t\t\tnewSnapshots.push(updatedSnapshot);\n\t\t\t\t\t} else if (currentSnapshot.dateModified !== snapshot.dateModified) {\n\t\t\t\t\t\t// If the local snapshot has a different dateModified, we need to update it\n\t\t\t\t\t\tmodifiedSnapshots.push({\n\t\t\t\t\t\t\tcurrentSnapshot,\n\t\t\t\t\t\t\tupdatedSnapshot\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// we sorted the snapshots from newest to oldest, so if we found a local snapshot\n\t\t\t\t\t\t// with the same dateModified as the remote snapshot, we can stop processing further\n\t\t\t\t\t\tcompletedProcessing = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We reverse the order of the snapshots to process them from oldest to newest\n\t\t\t// because we want to apply the changes in the order they were created\n\t\t\tawait this.processModifiedSnapshots(modifiedSnapshots.reverse());\n\t\t\tawait this.processNewSnapshots(newSnapshots.reverse());\n\n\t\t\t// Any ids remaining in this list are no longer referenced in the global state\n\t\t\t// so we should remove them from the local storage as they will never be updated again\n\t\t\tfor (const referencedSnapshotId of referencedExistingSnapshots) {\n\t\t\t\tawait this._snapshotEntryEntityStorage.remove(referencedSnapshotId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Process the modified snapshots and store them in the local storage.\n\t * @param modifiedSnapshots The modified snapshots to process.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async processModifiedSnapshots(\n\t\tmodifiedSnapshots: {\n\t\t\tcurrentSnapshot: SyncSnapshotEntry;\n\t\t\tupdatedSnapshot: SyncSnapshotEntry;\n\t\t}[]\n\t): Promise<void> {\n\t\tfor (const modifiedSnapshot of modifiedSnapshots) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"processModifiedSnapshot\",\n\t\t\t\tdata: {\n\t\t\t\t\tsnapshotId: modifiedSnapshot.updatedSnapshot.id,\n\t\t\t\t\tlocalModified: new Date(\n\t\t\t\t\t\tmodifiedSnapshot.currentSnapshot.dateModified ??\n\t\t\t\t\t\t\tmodifiedSnapshot.currentSnapshot.dateCreated\n\t\t\t\t\t).toISOString(),\n\t\t\t\t\tremoteModified: new Date(\n\t\t\t\t\t\tmodifiedSnapshot.updatedSnapshot.dateModified ??\n\t\t\t\t\t\t\tmodifiedSnapshot.updatedSnapshot.dateCreated\n\t\t\t\t\t).toISOString()\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tconst remoteChangeSetStorageIds = modifiedSnapshot.updatedSnapshot.changeSetStorageIds;\n\t\t\tconst localChangeSetStorageIds = modifiedSnapshot.currentSnapshot.changeSetStorageIds ?? [];\n\t\t\tif (Is.arrayValue(remoteChangeSetStorageIds)) {\n\t\t\t\tfor (const storageId of remoteChangeSetStorageIds) {\n\t\t\t\t\t// Check if the local snapshot does not have the storageId\n\t\t\t\t\tif (!localChangeSetStorageIds.includes(storageId)) {\n\t\t\t\t\t\tawait this._changeSetHelper.getAndApplyChangeset(storageId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this._snapshotEntryEntityStorage.set(modifiedSnapshot.updatedSnapshot);\n\t\t}\n\t}\n\n\t/**\n\t * Process the new snapshots and store them in the local storage.\n\t * @param newSnapshots The new snapshots to process.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async processNewSnapshots(newSnapshots: SyncSnapshotEntry[]): Promise<void> {\n\t\tfor (const newSnapshot of newSnapshots) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"processNewSnapshot\",\n\t\t\t\tdata: {\n\t\t\t\t\tsnapshotId: newSnapshot.id,\n\t\t\t\t\tdateCreated: newSnapshot.dateCreated\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tconst newSnapshotChangeSetStorageIds = newSnapshot.changeSetStorageIds ?? [];\n\t\t\tif (Is.arrayValue(newSnapshotChangeSetStorageIds)) {\n\t\t\t\tfor (const storageId of newSnapshotChangeSetStorageIds) {\n\t\t\t\t\tawait this._changeSetHelper.getAndApplyChangeset(storageId);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this._snapshotEntryEntityStorage.set(newSnapshot);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"localSyncStateHelper.js","sourceRoot":"","sources":["../../../src/helpers/localSyncStateHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Converter, Is, RandomHelper } from \"@twin.org/core\";\nimport { ComparisonOperator } from \"@twin.org/entity\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type SyncChangeOperation, SyncNodeIdMode } from \"@twin.org/synchronised-storage-models\";\nimport type { ChangeSetHelper } from \"./changeSetHelper.js\";\nimport { SYNC_SNAPSHOT_VERSION } from \"./versions.js\";\nimport type { SyncSnapshotEntry } from \"../entities/syncSnapshotEntry.js\";\nimport type { ISyncState } from \"../models/ISyncState.js\";\n\n/**\n * Class for performing entity storage operations in decentralised storage.\n */\nexport class LocalSyncStateHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LocalSyncStateHelper>();\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The storage connector for the sync snapshot entries.\n\t * @internal\n\t */\n\tprivate readonly _snapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>;\n\n\t/**\n\t * The change set helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _changeSetHelper: ChangeSetHelper;\n\n\t/**\n\t * Create a new instance of LocalSyncStateHelper.\n\t * @param logging The logging component to use for logging.\n\t * @param snapshotEntryEntityStorage The storage connector for the sync snapshot entries.\n\t * @param changeSetHelper The change set helper to use for applying changesets.\n\t */\n\tconstructor(\n\t\tlogging: ILoggingComponent | undefined,\n\t\tsnapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>,\n\t\tchangeSetHelper: ChangeSetHelper\n\t) {\n\t\tthis._logging = logging;\n\t\tthis._snapshotEntryEntityStorage = snapshotEntryEntityStorage;\n\t\tthis._changeSetHelper = changeSetHelper;\n\t}\n\n\t/**\n\t * Add a new change to the local snapshot.\n\t * @param storageKey The storage key of the snapshot to add the change for.\n\t * @param operation The operation to perform.\n\t * @param id The id of the entity to add the change for.\n\t * @returns A promise that resolves when the change is recorded in the local snapshot.\n\t */\n\tpublic async addLocalChange(\n\t\tstorageKey: string,\n\t\toperation: SyncChangeOperation,\n\t\tid: string\n\t): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"addLocalChange\",\n\t\t\tdata: {\n\t\t\t\tstorageKey,\n\t\t\t\toperation,\n\t\t\t\tid\n\t\t\t}\n\t\t});\n\n\t\tconst localChangeSnapshots = await this.getSnapshots(storageKey, true);\n\n\t\tif (localChangeSnapshots.length > 0) {\n\t\t\tconst localChangeSnapshot = localChangeSnapshots[0];\n\n\t\t\tlocalChangeSnapshot.changes ??= [];\n\n\t\t\t// If we already have a change for this id we are\n\t\t\t// about to supersede it, we remove the previous change\n\t\t\t// to avoid having multiple changes for the same id\n\t\t\tconst previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);\n\t\t\tif (previousChangeIndex !== -1) {\n\t\t\t\tlocalChangeSnapshot.changes.splice(previousChangeIndex, 1);\n\t\t\t}\n\n\t\t\t// If we already have changes from previous updates\n\t\t\t// then make sure we update the dateModified, otherwise\n\t\t\t// we assume this is the first change and setting modified is not necessary\n\t\t\tif (localChangeSnapshot.changes.length > 0) {\n\t\t\t\tlocalChangeSnapshot.dateModified = new Date(Date.now()).toISOString();\n\t\t\t}\n\n\t\t\tlocalChangeSnapshot.changes.push({ operation, id });\n\n\t\t\tawait this.setLocalChangeSnapshot(localChangeSnapshot);\n\t\t}\n\t}\n\n\t/**\n\t * Get the snapshot which contains just the changes for this node.\n\t * @param storageKey The storage key of the snapshot to get.\n\t * @param isLocal Whether to get the local snapshot or not.\n\t * @returns The local snapshot entry.\n\t */\n\tpublic async getSnapshots(storageKey: string, isLocal: boolean): Promise<SyncSnapshotEntry[]> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"getSnapshots\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\tconst queryResult = await this._snapshotEntryEntityStorage.query({\n\t\t\tconditions: [\n\t\t\t\t{\n\t\t\t\t\tproperty: \"isLocal\",\n\t\t\t\t\tvalue: isLocal,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: \"storageKey\",\n\t\t\t\t\tvalue: storageKey,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals\n\t\t\t\t}\n\t\t\t]\n\t\t});\n\n\t\tif (queryResult.entities.length > 0) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"getSnapshotsExists\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn queryResult.entities as SyncSnapshotEntry[];\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"getSnapshotsDoesNotExist\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\t\tconst now = new Date(Date.now()).toISOString();\n\t\treturn [\n\t\t\t{\n\t\t\t\tversion: SYNC_SNAPSHOT_VERSION,\n\t\t\t\tid: Converter.bytesToHex(RandomHelper.generate(32)),\n\t\t\t\tstorageKey,\n\t\t\t\tdateCreated: now,\n\t\t\t\tdateModified: now,\n\t\t\t\tchangeSetStorageIds: [],\n\t\t\t\tisLocal,\n\t\t\t\tisConsolidated: false,\n\t\t\t\tepoch: 0\n\t\t\t}\n\t\t];\n\t}\n\n\t/**\n\t * Set the current local snapshot with changes for this node.\n\t * @param localChangeSnapshot The local change snapshot to set.\n\t * @returns A promise that resolves when the snapshot is persisted to entity storage.\n\t */\n\tpublic async setLocalChangeSnapshot(localChangeSnapshot: SyncSnapshotEntry): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"setLocalChangeSnapshot\",\n\t\t\tdata: {\n\t\t\t\tstorageKey: localChangeSnapshot.storageKey\n\t\t\t}\n\t\t});\n\t\tawait this._snapshotEntryEntityStorage.set(localChangeSnapshot);\n\t}\n\n\t/**\n\t * Remove the local snapshot entry from storage.\n\t * @param localChangeSnapshot The local change snapshot to remove.\n\t * @returns A promise that resolves when the snapshot is removed from entity storage.\n\t */\n\tpublic async removeLocalChangeSnapshot(localChangeSnapshot: SyncSnapshotEntry): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"removeLocalChangeSnapshot\",\n\t\t\tdata: {\n\t\t\t\tsnapshotId: localChangeSnapshot.id\n\t\t\t}\n\t\t});\n\t\tawait this._snapshotEntryEntityStorage.remove(localChangeSnapshot.id);\n\t}\n\n\t/**\n\t * Apply a sync state to the local node.\n\t * @param storageKey The storage key of the snapshot to sync with.\n\t * @param syncState The sync state to sync with.\n\t * @returns A promise that resolves when all new and modified snapshots have been processed.\n\t */\n\tpublic async applySyncState(storageKey: string, syncState: ISyncState): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\tmessage: \"applySyncState\",\n\t\t\tdata: {\n\t\t\t\tsnapshotCount: syncState.snapshots.length\n\t\t\t}\n\t\t});\n\n\t\t// Get all the existing snapshots that we have processed previously\n\t\tlet existingSnapshots = await this.getSnapshots(storageKey, false);\n\n\t\t// Sort from newest to oldest\n\t\texistingSnapshots = existingSnapshots.sort(\n\t\t\t(a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime()\n\t\t);\n\n\t\t// Sort from newest to oldest\n\t\tconst syncStateSnapshots = syncState.snapshots.sort(\n\t\t\t(a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime()\n\t\t);\n\n\t\t// Get the newest epoch from the local storage\n\t\tconst newestExistingEpoch = existingSnapshots[0]?.epoch ?? 0;\n\n\t\t// Get the oldest epoch from the remote storage\n\t\tconst oldestSyncStateEpoch = syncStateSnapshots[syncStateSnapshots.length - 1]?.epoch ?? 0;\n\n\t\t// If there is a gap between the largest epoch we have locally\n\t\t// and the smallest epoch we have remotely then we have missed\n\t\t// data so we need to perform a full sync\n\t\tconst hasEpochGap = newestExistingEpoch + 1 < oldestSyncStateEpoch;\n\n\t\t// If we have an epoch gap or no existing snapshots then we need to apply\n\t\t// a full sync from a consolidation\n\t\tif (!existingSnapshots.some(s => s.isConsolidated) || hasEpochGap) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"applySnapshotNoExisting\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst mostRecentConsolidation = syncStateSnapshots.findIndex(\n\t\t\t\tsnapshot => snapshot.isConsolidated\n\t\t\t);\n\t\t\tif (mostRecentConsolidation !== -1) {\n\t\t\t\t// We found the most recent consolidated snapshot, we can use it\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshotFoundConsolidated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\tsnapshotId: syncStateSnapshots[mostRecentConsolidation].id\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// We need to reset the entity storage and remove all the remote items\n\t\t\t\t// so that we use just the ones from the consolidation, since\n\t\t\t\t// we don't have any existing there shouldn't be any remote entries\n\t\t\t\t// but we reset nonetheless\n\t\t\t\tawait this._changeSetHelper.reset(storageKey, SyncNodeIdMode.Remote);\n\n\t\t\t\t// We need to process the most recent consolidation and all changes\n\t\t\t\t// that were made since then, from newest to oldest (so newer changes override older ones)\n\t\t\t\t// Process snapshots from the consolidation point (most recent) back to the newest\n\t\t\t\tfor (let i = mostRecentConsolidation; i >= 0; i--) {\n\t\t\t\t\tawait this.processNewSnapshots([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...syncStateSnapshots[i],\n\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\tisLocal: false\n\t\t\t\t\t\t}\n\t\t\t\t\t]);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshotNoConsolidated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\t// We have existing consolidated remote snapshots, so we can assume that we have\n\t\t\t// applied at least one consolidation snapshot, in this case we need to look at the changes since\n\t\t\t// then and apply them if we haven't already\n\t\t\t// We don't need to apply any additional consolidated snapshots, just the changesets\n\n\t\t\t// Create a lookup map for the existing snapshots\n\t\t\tconst existingSnapshotsMap: { [id: string]: SyncSnapshotEntry } = {};\n\t\t\tfor (const snapshot of existingSnapshots) {\n\t\t\t\texistingSnapshotsMap[snapshot.id] = snapshot;\n\t\t\t}\n\n\t\t\tconst newSnapshots: SyncSnapshotEntry[] = [];\n\t\t\tconst modifiedSnapshots: {\n\t\t\t\tcurrentSnapshot: SyncSnapshotEntry;\n\t\t\t\tupdatedSnapshot: SyncSnapshotEntry;\n\t\t\t}[] = [];\n\t\t\tconst referencedExistingSnapshots: string[] = Object.keys(existingSnapshotsMap);\n\n\t\t\tlet completedProcessing = false;\n\t\t\tfor (const snapshot of syncStateSnapshots) {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\t\tmessage: \"applySnapshot\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsnapshotId: snapshot.id,\n\t\t\t\t\t\tdateCreated: new Date(snapshot.dateCreated).toISOString()\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// See if we have the snapshot stored locally\n\t\t\t\tconst currentSnapshot = existingSnapshotsMap[snapshot.id];\n\n\t\t\t\t// As we are referencing an existing snapshot, we need to remove it from the list\n\t\t\t\t// to allow us to cleanup any unreferenced snapshots later\n\t\t\t\tconst idx = referencedExistingSnapshots.indexOf(snapshot.id);\n\t\t\t\tif (idx !== -1) {\n\t\t\t\t\treferencedExistingSnapshots.splice(idx, 1);\n\t\t\t\t}\n\n\t\t\t\t// No need to apply consolidated snapshots\n\t\t\t\tif (!snapshot.isConsolidated && !completedProcessing) {\n\t\t\t\t\tconst updatedSnapshot: SyncSnapshotEntry = {\n\t\t\t\t\t\t...snapshot,\n\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\tisLocal: false\n\t\t\t\t\t};\n\n\t\t\t\t\tif (Is.empty(currentSnapshot)) {\n\t\t\t\t\t\t// We don't have the snapshot locally, so we need to process all of it\n\t\t\t\t\t\tnewSnapshots.push(updatedSnapshot);\n\t\t\t\t\t} else if (currentSnapshot.dateModified !== snapshot.dateModified) {\n\t\t\t\t\t\t// If the local snapshot has a different dateModified, we need to update it\n\t\t\t\t\t\tmodifiedSnapshots.push({\n\t\t\t\t\t\t\tcurrentSnapshot,\n\t\t\t\t\t\t\tupdatedSnapshot\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// we sorted the snapshots from newest to oldest, so if we found a local snapshot\n\t\t\t\t\t\t// with the same dateModified as the remote snapshot, we can stop processing further\n\t\t\t\t\t\tcompletedProcessing = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We reverse the order of the snapshots to process them from oldest to newest\n\t\t\t// because we want to apply the changes in the order they were created\n\t\t\tawait this.processModifiedSnapshots(modifiedSnapshots.reverse());\n\t\t\tawait this.processNewSnapshots(newSnapshots.reverse());\n\n\t\t\t// Any ids remaining in this list are no longer referenced in the global state\n\t\t\t// so we should remove them from the local storage as they will never be updated again\n\t\t\tfor (const referencedSnapshotId of referencedExistingSnapshots) {\n\t\t\t\tawait this._snapshotEntryEntityStorage.remove(referencedSnapshotId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Process the modified snapshots and store them in the local storage.\n\t * @param modifiedSnapshots The modified snapshots to process.\n\t * @returns A promise that resolves when all new changesets in each snapshot have been applied and the snapshots saved.\n\t * @internal\n\t */\n\tprivate async processModifiedSnapshots(\n\t\tmodifiedSnapshots: {\n\t\t\tcurrentSnapshot: SyncSnapshotEntry;\n\t\t\tupdatedSnapshot: SyncSnapshotEntry;\n\t\t}[]\n\t): Promise<void> {\n\t\tfor (const modifiedSnapshot of modifiedSnapshots) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"processModifiedSnapshot\",\n\t\t\t\tdata: {\n\t\t\t\t\tsnapshotId: modifiedSnapshot.updatedSnapshot.id,\n\t\t\t\t\tlocalModified: new Date(\n\t\t\t\t\t\tmodifiedSnapshot.currentSnapshot.dateModified ??\n\t\t\t\t\t\t\tmodifiedSnapshot.currentSnapshot.dateCreated\n\t\t\t\t\t).toISOString(),\n\t\t\t\t\tremoteModified: new Date(\n\t\t\t\t\t\tmodifiedSnapshot.updatedSnapshot.dateModified ??\n\t\t\t\t\t\t\tmodifiedSnapshot.updatedSnapshot.dateCreated\n\t\t\t\t\t).toISOString()\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tconst remoteChangeSetStorageIds = modifiedSnapshot.updatedSnapshot.changeSetStorageIds;\n\t\t\tconst localChangeSetStorageIds = modifiedSnapshot.currentSnapshot.changeSetStorageIds ?? [];\n\t\t\tif (Is.arrayValue(remoteChangeSetStorageIds)) {\n\t\t\t\tfor (const storageId of remoteChangeSetStorageIds) {\n\t\t\t\t\t// Check if the local snapshot does not have the storageId\n\t\t\t\t\tif (!localChangeSetStorageIds.includes(storageId)) {\n\t\t\t\t\t\tawait this._changeSetHelper.getAndApplyChangeset(storageId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this._snapshotEntryEntityStorage.set(modifiedSnapshot.updatedSnapshot);\n\t\t}\n\t}\n\n\t/**\n\t * Process the new snapshots and store them in the local storage.\n\t * @param newSnapshots The new snapshots to process.\n\t * @returns A promise that resolves when all changesets for each snapshot have been applied and the snapshots saved.\n\t * @internal\n\t */\n\tprivate async processNewSnapshots(newSnapshots: SyncSnapshotEntry[]): Promise<void> {\n\t\tfor (const newSnapshot of newSnapshots) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: LocalSyncStateHelper.CLASS_NAME,\n\t\t\t\tmessage: \"processNewSnapshot\",\n\t\t\t\tdata: {\n\t\t\t\t\tsnapshotId: newSnapshot.id,\n\t\t\t\t\tdateCreated: newSnapshot.dateCreated\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tconst newSnapshotChangeSetStorageIds = newSnapshot.changeSetStorageIds ?? [];\n\t\t\tif (Is.arrayValue(newSnapshotChangeSetStorageIds)) {\n\t\t\t\tfor (const storageId of newSnapshotChangeSetStorageIds) {\n\t\t\t\t\tawait this._changeSetHelper.getAndApplyChangeset(storageId);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this._snapshotEntryEntityStorage.set(newSnapshot);\n\t\t}\n\t}\n}\n"]}
@@ -96,6 +96,7 @@ export class RemoteSyncStateHelper {
96
96
  }
97
97
  /**
98
98
  * Start the remote sync state helper.
99
+ * @returns A promise that resolves when all event bus subscriptions are registered.
99
100
  */
100
101
  async start() {
101
102
  await this._eventBusComponent.subscribe(SynchronisedStorageTopics.BatchResponse, async (response) => {
@@ -113,11 +114,11 @@ export class RemoteSyncStateHelper {
113
114
  this._synchronisedStorageKey = synchronisedStorageKey;
114
115
  }
115
116
  /**
116
- * Build a changeset.
117
+ * Build a changeset and invoke the callback when complete.
117
118
  * @param storageKey The storage key of the change set.
118
119
  * @param changes The changes to apply.
119
- * @param completeCallback The callback to call when the changeset is created and stored.
120
- * @returns The storage id of the change set if created.
120
+ * @param completeCallback The callback invoked when the changeset is created and stored.
121
+ * @returns A promise that resolves when item requests are dispatched or the changeset is finalised immediately.
121
122
  */
122
123
  async buildChangeSet(storageKey, changes, completeCallback) {
123
124
  await this._logging?.log({
@@ -165,8 +166,8 @@ export class RemoteSyncStateHelper {
165
166
  /**
166
167
  * Finalise the full details for the sync change set.
167
168
  * @param storageKey The storage key of the change set.
168
- * @param completeCallback The callback to call when the changeset is populated.
169
- * @returns Nothing.
169
+ * @param completeCallback The callback invoked when the changeset is populated and optionally stored.
170
+ * @returns A promise that resolves when the changeset is built and the callback is invoked.
170
171
  */
171
172
  async finaliseFullChanges(storageKey, completeCallback) {
172
173
  await this._logging?.log({
@@ -229,8 +230,8 @@ export class RemoteSyncStateHelper {
229
230
  /**
230
231
  * Add a new changeset into the sync state.
231
232
  * @param storageKey The storage key of the change set to add.
232
- * @param changeSetStorageId The id of the change set to add the current state
233
- * @returns Nothing.
233
+ * @param changeSetStorageId The id of the change set to add.
234
+ * @returns A promise that resolves when the sync state and verifiable sync pointer are updated.
234
235
  */
235
236
  async addChangeSetToSyncState(storageKey, changeSetStorageId) {
236
237
  await this._logging?.log({
@@ -287,7 +288,7 @@ export class RemoteSyncStateHelper {
287
288
  * Create a consolidated snapshot for the entire storage.
288
289
  * @param storageKey The storage key of the snapshot to create.
289
290
  * @param batchSize The batch size to use for consolidation.
290
- * @returns Nothing.
291
+ * @returns A promise that resolves when the batch request is published to the event bus.
291
292
  */
292
293
  async consolidationStart(storageKey, batchSize) {
293
294
  await this._logging?.log({
@@ -350,7 +351,7 @@ export class RemoteSyncStateHelper {
350
351
  /**
351
352
  * Store the verifiable sync pointer in the verifiable storage.
352
353
  * @param syncPointerStore The sync pointer store to store.
353
- * @returns Nothing.
354
+ * @returns A promise that resolves when the pointer store is persisted to verifiable storage.
354
355
  */
355
356
  async storeVerifiableSyncPointerStore(syncPointerStore) {
356
357
  if (Is.stringValue(this._nodeId) && Is.stringValue(this._synchronisedStorageKey)) {
@@ -460,6 +461,8 @@ export class RemoteSyncStateHelper {
460
461
  /**
461
462
  * Handle the batch response which is triggered from a consolidation request.
462
463
  * @param response The batch response to handle.
464
+ * @returns A promise that resolves when the batch is processed and, if it is the last entry, the consolidated snapshot is stored.
465
+ * @internal
463
466
  */
464
467
  async handleBatchResponse(response) {
465
468
  if (Is.stringValue(this._nodeId)) {
@@ -531,6 +534,8 @@ export class RemoteSyncStateHelper {
531
534
  /**
532
535
  * Handle the item response.
533
536
  * @param response The item response to handle.
537
+ * @returns A promise that resolves when the entity is recorded and the complete callback is invoked if all requests are fulfilled.
538
+ * @internal
534
539
  */
535
540
  async handleLocalItemResponse(response) {
536
541
  await this._logging?.log({