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

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 (55) hide show
  1. package/dist/es/data/verifiableStorageKeys.json +5 -0
  2. package/dist/es/entities/syncSnapshotEntry.js +93 -0
  3. package/dist/es/entities/syncSnapshotEntry.js.map +1 -0
  4. package/dist/es/helpers/blobStorageHelper.js +185 -0
  5. package/dist/es/helpers/blobStorageHelper.js.map +1 -0
  6. package/dist/es/helpers/changeSetHelper.js +215 -0
  7. package/dist/es/helpers/changeSetHelper.js.map +1 -0
  8. package/dist/es/helpers/localSyncStateHelper.js +384 -0
  9. package/dist/es/helpers/localSyncStateHelper.js.map +1 -0
  10. package/dist/es/helpers/remoteSyncStateHelper.js +560 -0
  11. package/dist/es/helpers/remoteSyncStateHelper.js.map +1 -0
  12. package/dist/es/helpers/versions.js +6 -0
  13. package/dist/es/helpers/versions.js.map +1 -0
  14. package/dist/es/index.js +13 -0
  15. package/dist/es/index.js.map +1 -0
  16. package/dist/es/models/ISyncPointerStore.js +4 -0
  17. package/dist/es/models/ISyncPointerStore.js.map +1 -0
  18. package/dist/es/models/ISyncSnapshot.js +4 -0
  19. package/dist/es/models/ISyncSnapshot.js.map +1 -0
  20. package/dist/es/models/ISyncState.js +2 -0
  21. package/dist/es/models/ISyncState.js.map +1 -0
  22. package/dist/es/models/ISynchronisedStorageServiceConfig.js +4 -0
  23. package/dist/es/models/ISynchronisedStorageServiceConfig.js.map +1 -0
  24. package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js +2 -0
  25. package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js.map +1 -0
  26. package/dist/es/restEntryPoints.js +10 -0
  27. package/dist/es/restEntryPoints.js.map +1 -0
  28. package/dist/es/schema.js +11 -0
  29. package/dist/es/schema.js.map +1 -0
  30. package/dist/es/synchronisedStorageRoutes.js +142 -0
  31. package/dist/es/synchronisedStorageRoutes.js.map +1 -0
  32. package/dist/es/synchronisedStorageService.js +512 -0
  33. package/dist/es/synchronisedStorageService.js.map +1 -0
  34. package/dist/types/entities/syncSnapshotEntry.d.ts +3 -3
  35. package/dist/types/helpers/blobStorageHelper.d.ts +3 -3
  36. package/dist/types/helpers/changeSetHelper.d.ts +16 -32
  37. package/dist/types/helpers/localSyncStateHelper.d.ts +11 -11
  38. package/dist/types/helpers/remoteSyncStateHelper.d.ts +18 -14
  39. package/dist/types/index.d.ts +10 -10
  40. package/dist/types/models/ISyncState.d.ts +1 -1
  41. package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +7 -8
  42. package/dist/types/models/ISynchronisedStorageServiceConstructorOptions.d.ts +6 -6
  43. package/dist/types/synchronisedStorageRoutes.d.ts +1 -1
  44. package/dist/types/synchronisedStorageService.d.ts +17 -21
  45. package/docs/architecture.md +168 -12
  46. package/docs/changelog.md +149 -0
  47. package/docs/open-api/spec.json +62 -57
  48. package/docs/reference/classes/SyncSnapshotEntry.md +4 -10
  49. package/docs/reference/classes/SynchronisedStorageService.md +38 -50
  50. package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +11 -17
  51. package/docs/reference/interfaces/ISynchronisedStorageServiceConstructorOptions.md +8 -8
  52. package/locales/en.json +7 -15
  53. package/package.json +26 -9
  54. package/dist/cjs/index.cjs +0 -2235
  55. package/dist/esm/index.mjs +0 -2227
@@ -0,0 +1,512 @@
1
+ import { BlobStorageConnectorFactory } from "@twin.org/blob-storage-models";
2
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
3
+ import { BaseError, ComponentFactory, Converter, GeneralError, Guards, Is, UnauthorizedError } from "@twin.org/core";
4
+ import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
5
+ import { SynchronisedStorageTopics } from "@twin.org/synchronised-storage-models";
6
+ import { TrustHelper } from "@twin.org/trust-models";
7
+ import { VaultConnectorFactory, VaultKeyType } from "@twin.org/vault-models";
8
+ import { VerifiableStorageConnectorFactory } from "@twin.org/verifiable-storage-models";
9
+ import verifiableStorageKeys from "./data/verifiableStorageKeys.json" with { type: "json" };
10
+ import { BlobStorageHelper } from "./helpers/blobStorageHelper.js";
11
+ import { ChangeSetHelper } from "./helpers/changeSetHelper.js";
12
+ import { LocalSyncStateHelper } from "./helpers/localSyncStateHelper.js";
13
+ import { RemoteSyncStateHelper } from "./helpers/remoteSyncStateHelper.js";
14
+ /**
15
+ * Class for performing synchronised storage operations.
16
+ */
17
+ export class SynchronisedStorageService {
18
+ /**
19
+ * Runtime name for the class.
20
+ */
21
+ static CLASS_NAME = "SynchronisedStorageService";
22
+ /**
23
+ * The default interval to check for entity updates.
24
+ * @internal
25
+ */
26
+ static _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES = 5;
27
+ /**
28
+ * The default interval to perform consolidation.
29
+ * @internal
30
+ */
31
+ static _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES = 60;
32
+ /**
33
+ * The default size of a consolidation batch.
34
+ * @internal
35
+ */
36
+ static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
37
+ /**
38
+ * The default max number of consolidations to keep in storage.
39
+ * @internal
40
+ */
41
+ static _DEFAULT_MAX_CONSOLIDATIONS = 5;
42
+ /**
43
+ * The logging component to use for logging.
44
+ * @internal
45
+ */
46
+ _logging;
47
+ /**
48
+ * The event bus component.
49
+ * @internal
50
+ */
51
+ _eventBusComponent;
52
+ /**
53
+ * The vault connector.
54
+ * @internal
55
+ */
56
+ _vaultConnector;
57
+ /**
58
+ * The storage connector for the sync snapshot entries.
59
+ * @internal
60
+ */
61
+ _localSyncSnapshotEntryEntityStorage;
62
+ /**
63
+ * The blob storage connector to use for remote sync states.
64
+ * @internal
65
+ */
66
+ _blobStorageConnector;
67
+ /**
68
+ * The verifiable storage connector to use for storing sync pointers.
69
+ * @internal
70
+ */
71
+ _verifiableSyncPointerStorageConnector;
72
+ /**
73
+ * The task scheduler component.
74
+ * @internal
75
+ */
76
+ _taskSchedulerComponent;
77
+ /**
78
+ * The synchronised storage service to use when this is not a trusted node.
79
+ * @internal
80
+ */
81
+ _trustedSynchronisedStorageComponent;
82
+ /**
83
+ * The blob storage helper.
84
+ * @internal
85
+ */
86
+ _blobStorageHelper;
87
+ /**
88
+ * The trust component.
89
+ * @internal
90
+ */
91
+ _trustComponent;
92
+ /**
93
+ * The change set helper.
94
+ * @internal
95
+ */
96
+ _changeSetHelper;
97
+ /**
98
+ * The local sync state helper to use for applying changesets.
99
+ * @internal
100
+ */
101
+ _localSyncStateHelper;
102
+ /**
103
+ * The remote sync state helper to use for applying changesets.
104
+ * @internal
105
+ */
106
+ _remoteSyncStateHelper;
107
+ /**
108
+ * The options for the connector.
109
+ * @internal
110
+ */
111
+ _config;
112
+ /**
113
+ * The synchronised storage key to use for the remote synchronised storage.
114
+ * @internal
115
+ */
116
+ _synchronisedStorageKey;
117
+ /**
118
+ * The flag to determine if the service has been started.
119
+ * @internal
120
+ */
121
+ _serviceStarted;
122
+ /**
123
+ * The active storage keys for the synchronised storage service.
124
+ * @internal
125
+ */
126
+ _activeStorageKeys;
127
+ /**
128
+ * The identity of the node this connector is running on.
129
+ * @internal
130
+ */
131
+ _nodeId;
132
+ /**
133
+ * Create a new instance of SynchronisedStorageService.
134
+ * @param options The options for the service.
135
+ */
136
+ constructor(options) {
137
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "options", options);
138
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "options.config", options.config);
139
+ Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "options.config.verifiableStorageKeyId", options.config.verifiableStorageKeyId);
140
+ this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? "event-bus");
141
+ this._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? "logging");
142
+ this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? "vault");
143
+ this._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get(options.syncSnapshotStorageConnectorType ?? "sync-snapshot-entry");
144
+ this._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(options.verifiableStorageConnectorType ?? "verifiable-storage");
145
+ this._blobStorageConnector = BlobStorageConnectorFactory.get(options.blobStorageConnectorType ?? "blob-storage");
146
+ this._taskSchedulerComponent = ComponentFactory.get(options.taskSchedulerComponentType ?? "task-scheduler");
147
+ this._trustComponent = ComponentFactory.get(options?.trustComponentType ?? "trust");
148
+ // If this is empty we assume the local node has the rights to write to the verifiable storage.
149
+ let isTrustedNode = true;
150
+ if (!Is.empty(options.trustedSynchronisedStorageComponentType)) {
151
+ isTrustedNode = false;
152
+ // If it is set then we used the trusted component to send changesets to
153
+ this._trustedSynchronisedStorageComponent =
154
+ ComponentFactory.get(options.trustedSynchronisedStorageComponentType);
155
+ }
156
+ this._config = {
157
+ entityUpdateIntervalMinutes: options.config.entityUpdateIntervalMinutes ??
158
+ SynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,
159
+ consolidationIntervalMinutes: options.config.consolidationIntervalMinutes ??
160
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
161
+ consolidationBatchSize: options.config.consolidationBatchSize ??
162
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,
163
+ maxConsolidations: options.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,
164
+ blobStorageEncryptionKeyId: options.config.blobStorageEncryptionKeyId ?? "synchronised-storage-blob-encryption-key",
165
+ verifiableStorageKeyId: options.config.verifiableStorageKeyId,
166
+ overrideTrustGeneratorType: options.config.overrideTrustGeneratorType ?? ""
167
+ };
168
+ this._synchronisedStorageKey =
169
+ verifiableStorageKeys[options.config.verifiableStorageKeyId] ?? options.config.verifiableStorageKeyId;
170
+ Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "synchronisedStorageKey", this._synchronisedStorageKey);
171
+ this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, isTrustedNode);
172
+ this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._blobStorageHelper);
173
+ this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
174
+ this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, isTrustedNode, this._config.maxConsolidations);
175
+ this._serviceStarted = false;
176
+ this._activeStorageKeys = {};
177
+ }
178
+ /**
179
+ * Returns the class name of the component.
180
+ * @returns The class name of the component.
181
+ */
182
+ className() {
183
+ return SynchronisedStorageService.CLASS_NAME;
184
+ }
185
+ /**
186
+ * The component needs to be started when the node is initialized.
187
+ * @param nodeLoggingComponentType The node logging component type.
188
+ * @returns Nothing.
189
+ */
190
+ async start(nodeLoggingComponentType) {
191
+ const contextIds = await ContextIdStore.getContextIds();
192
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
193
+ this._nodeId = contextIds[ContextIdKeys.Node];
194
+ this._remoteSyncStateHelper.setNodeId(this._nodeId);
195
+ this._changeSetHelper.setNodeId(this._nodeId);
196
+ this._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);
197
+ this._serviceStarted = true;
198
+ // If this is not a trusted node we need to request the decryption key from a trusted node
199
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
200
+ const trustPayload = await this._trustComponent.generate(this._nodeId, this._config.overrideTrustGeneratorType.length > 0
201
+ ? this._config.overrideTrustGeneratorType
202
+ : undefined);
203
+ const decryptionKey = await this._trustedSynchronisedStorageComponent.getDecryptionKey(trustPayload);
204
+ // If the key exists remove it and get a new one, in case the key has been rotated
205
+ const existingKey = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
206
+ if (!Is.empty(existingKey)) {
207
+ await this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);
208
+ }
209
+ await this._vaultConnector.addKey(this._config.blobStorageEncryptionKeyId, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(decryptionKey));
210
+ }
211
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
212
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => {
213
+ // Make sure the change event is from this node
214
+ if (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {
215
+ await this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id);
216
+ }
217
+ });
218
+ await this._remoteSyncStateHelper.start();
219
+ // If there are already storage keys registered, we need to activate them
220
+ for (const storageKey in this._activeStorageKeys) {
221
+ await this.activateStorageKey(storageKey);
222
+ }
223
+ }
224
+ /**
225
+ * The component needs to be stopped when the node is closed.
226
+ * @param nodeLoggingComponentType The node logging component type.
227
+ * @returns Nothing.
228
+ */
229
+ async stop(nodeLoggingComponentType) {
230
+ for (const storageKey in this._activeStorageKeys) {
231
+ this._activeStorageKeys[storageKey] = false;
232
+ await this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);
233
+ await this._taskSchedulerComponent.removeTask(`synchronised-storage-consolidation-${storageKey}`);
234
+ }
235
+ }
236
+ /**
237
+ * Get the decryption key for the synchronised storage.
238
+ * This is used to decrypt the data stored in the synchronised storage.
239
+ * @param trustPayload Trust payload to verify the requesters identity.
240
+ * @returns The decryption key.
241
+ */
242
+ async getDecryptionKey(trustPayload) {
243
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
244
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
245
+ }
246
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "getDecryptionKey");
247
+ await this._logging?.log({
248
+ level: "info",
249
+ source: SynchronisedStorageService.CLASS_NAME,
250
+ message: "decryptionKeyRequest",
251
+ data: {
252
+ nodeId: trustInfo.identity
253
+ }
254
+ });
255
+ const key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
256
+ if (Is.undefined(key.privateKey)) {
257
+ throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "decryptionKeyNotFound");
258
+ }
259
+ return Converter.bytesToBase64(key.privateKey);
260
+ }
261
+ /**
262
+ * Synchronise a set of changes from an untrusted node, assumes this is a trusted node.
263
+ * @param syncChangeSet The change set to synchronise.
264
+ * @param trustPayload Trust payload to verify the requesters identity.
265
+ * @returns Nothing.
266
+ */
267
+ async syncChangeSet(syncChangeSet, trustPayload) {
268
+ if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
269
+ throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
270
+ }
271
+ Guards.object(SynchronisedStorageService.CLASS_NAME, "syncChangeSet", syncChangeSet);
272
+ const trustInfo = await TrustHelper.verifyTrust(this._trustComponent, trustPayload, "syncChangeSet");
273
+ await this._logging?.log({
274
+ level: "info",
275
+ source: SynchronisedStorageService.CLASS_NAME,
276
+ message: "syncChangeSetForRemoteNode",
277
+ data: {
278
+ changeSetStorageId: syncChangeSet.id,
279
+ nodeId: trustInfo.identity
280
+ }
281
+ });
282
+ const copy = await this._changeSetHelper.copyChangeset(syncChangeSet);
283
+ if (!Is.empty(copy)) {
284
+ // Apply the changes to this node
285
+ await this._changeSetHelper.applyChangeset(copy.syncChangeSet);
286
+ // And update the sync state with the latest changes
287
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(copy.syncChangeSet.storageKey, copy.changeSetStorageId);
288
+ }
289
+ }
290
+ /**
291
+ * Start the sync with further updates after an interval.
292
+ * @param storageKey The storage key to sync.
293
+ * @returns Nothing.
294
+ * @internal
295
+ */
296
+ async startEntitySync(storageKey) {
297
+ try {
298
+ await this._logging?.log({
299
+ level: "info",
300
+ source: SynchronisedStorageService.CLASS_NAME,
301
+ message: "startEntitySync",
302
+ data: {
303
+ storageKey
304
+ }
305
+ });
306
+ // First we check for remote changes
307
+ await this.updateFromRemoteSyncState(storageKey);
308
+ // Now send any updates we have to the remote storage
309
+ await this.updateFromLocalSyncState(storageKey);
310
+ }
311
+ catch (error) {
312
+ await this._logging?.log({
313
+ level: "error",
314
+ source: SynchronisedStorageService.CLASS_NAME,
315
+ message: "entitySyncFailed",
316
+ error: BaseError.fromError(error)
317
+ });
318
+ }
319
+ }
320
+ /**
321
+ * Check for updates in the remote storage.
322
+ * @param storageKey The storage key to check for updates.
323
+ * @returns Nothing.
324
+ * @internal
325
+ */
326
+ async updateFromRemoteSyncState(storageKey) {
327
+ await this._logging?.log({
328
+ level: "info",
329
+ source: SynchronisedStorageService.CLASS_NAME,
330
+ message: "updateFromRemoteSyncState",
331
+ data: {
332
+ storageKey
333
+ }
334
+ });
335
+ // Get the verifiable sync pointer store from the verifiable storage
336
+ const verifiableSyncPointerStore = await this._remoteSyncStateHelper.getVerifiableSyncPointerStore();
337
+ if (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
338
+ // Load the sync state from the remote blob storage using the sync pointer
339
+ // to load the sync state
340
+ const remoteSyncState = await this._remoteSyncStateHelper.getSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
341
+ // If we got the sync state we can try and sync from it
342
+ if (!Is.undefined(remoteSyncState)) {
343
+ await this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);
344
+ }
345
+ }
346
+ }
347
+ /**
348
+ * Find any local updates and send them to the remote storage.
349
+ * @returns Nothing.
350
+ * @internal
351
+ */
352
+ async updateFromLocalSyncState(storageKey) {
353
+ await this._logging?.log({
354
+ level: "info",
355
+ source: SynchronisedStorageService.CLASS_NAME,
356
+ message: "updateFromLocalSyncState",
357
+ data: {
358
+ storageKey
359
+ }
360
+ });
361
+ const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
362
+ if (localChangeSnapshots.length > 0) {
363
+ const localChangeSnapshot = localChangeSnapshots[0];
364
+ if (Is.arrayValue(localChangeSnapshot.changes)) {
365
+ await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
366
+ if (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {
367
+ await this._logging?.log({
368
+ level: "info",
369
+ source: SynchronisedStorageService.CLASS_NAME,
370
+ message: "builtStorageChangeSetNone",
371
+ data: {
372
+ storageKey
373
+ }
374
+ });
375
+ }
376
+ else {
377
+ await this._logging?.log({
378
+ level: "info",
379
+ source: SynchronisedStorageService.CLASS_NAME,
380
+ message: "builtStorageChangeSet",
381
+ data: {
382
+ storageKey,
383
+ changeSetStorageId
384
+ }
385
+ });
386
+ // Send the local changes to the remote storage if we are a trusted node
387
+ if (Is.empty(this._trustedSynchronisedStorageComponent) &&
388
+ Is.stringValue(changeSetStorageId)) {
389
+ // If we are a trusted node, we can add the change set to the sync state
390
+ // and remove the local change snapshot
391
+ await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
392
+ await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
393
+ }
394
+ else if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
395
+ Is.object(syncChangeSet) &&
396
+ Is.stringValue(this._nodeId)) {
397
+ // If we are not a trusted node, we need to send the changes to the trusted node
398
+ // and then remove the local change snapshot
399
+ await this._logging?.log({
400
+ level: "info",
401
+ source: SynchronisedStorageService.CLASS_NAME,
402
+ message: "sendingChangeSetToTrustedNode",
403
+ data: {
404
+ storageKey,
405
+ changeSetStorageId
406
+ }
407
+ });
408
+ const trustPayload = await this._trustComponent.generate(this._nodeId, this._config.overrideTrustGeneratorType.length > 0
409
+ ? this._config.overrideTrustGeneratorType
410
+ : undefined);
411
+ await this._trustedSynchronisedStorageComponent.syncChangeSet(syncChangeSet, trustPayload);
412
+ await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
413
+ }
414
+ }
415
+ });
416
+ }
417
+ else {
418
+ await this._logging?.log({
419
+ level: "info",
420
+ source: SynchronisedStorageService.CLASS_NAME,
421
+ message: "updateFromLocalSyncStateNoChanges",
422
+ data: {
423
+ storageKey
424
+ }
425
+ });
426
+ }
427
+ }
428
+ }
429
+ /**
430
+ * Start the consolidation sync.
431
+ * @param storageKey The storage key to consolidate.
432
+ * @returns Nothing.
433
+ * @internal
434
+ */
435
+ async startConsolidationSync(storageKey) {
436
+ try {
437
+ // If we are going to perform a consolidation first take any local updates
438
+ // we have and create a changeset from them, so that anybody applying
439
+ // just changes since a consolidation can use the changeset
440
+ // and skip the consolidation
441
+ await this.updateFromLocalSyncState(storageKey);
442
+ // Now start the consolidation
443
+ await this._remoteSyncStateHelper.consolidationStart(storageKey, this._config.consolidationBatchSize ??
444
+ SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
445
+ }
446
+ catch (error) {
447
+ await this._logging?.log({
448
+ level: "error",
449
+ source: SynchronisedStorageService.CLASS_NAME,
450
+ message: "consolidationSyncFailed",
451
+ error: BaseError.fromError(error)
452
+ });
453
+ }
454
+ }
455
+ /**
456
+ * Register a new sync type.
457
+ * @param syncRegisterStorageKey The sync register type to register.
458
+ * @internal
459
+ */
460
+ async registerStorageKey(syncRegisterStorageKey) {
461
+ await this._logging?.log({
462
+ level: "info",
463
+ source: SynchronisedStorageService.CLASS_NAME,
464
+ message: "registerStorageKey",
465
+ data: {
466
+ storageKey: syncRegisterStorageKey.storageKey
467
+ }
468
+ });
469
+ if (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {
470
+ this._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;
471
+ if (this._serviceStarted) {
472
+ await this.activateStorageKey(syncRegisterStorageKey.storageKey);
473
+ }
474
+ }
475
+ }
476
+ /**
477
+ * Activate a storage key.
478
+ * @param storageKey The storage key to activate.
479
+ * @internal
480
+ */
481
+ async activateStorageKey(storageKey) {
482
+ if (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {
483
+ await this._logging?.log({
484
+ level: "info",
485
+ source: SynchronisedStorageService.CLASS_NAME,
486
+ message: "activateStorageKey",
487
+ data: {
488
+ storageKey
489
+ }
490
+ });
491
+ this._activeStorageKeys[storageKey] = true;
492
+ if (this._config.entityUpdateIntervalMinutes > 0) {
493
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-update-${storageKey}`, [
494
+ {
495
+ nextTriggerTime: Date.now(),
496
+ intervalMinutes: this._config.entityUpdateIntervalMinutes
497
+ }
498
+ ], async () => this.startEntitySync(storageKey));
499
+ }
500
+ if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
501
+ this._config.consolidationIntervalMinutes > 0) {
502
+ await this._taskSchedulerComponent.addTask(`synchronised-storage-consolidation-${storageKey}`, [
503
+ {
504
+ nextTriggerTime: Date.now(),
505
+ intervalMinutes: this._config.consolidationIntervalMinutes
506
+ }
507
+ ], async () => this.startConsolidationSync(storageKey));
508
+ }
509
+ }
510
+ }
511
+ }
512
+ //# sourceMappingURL=synchronisedStorageService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronisedStorageService.js","sourceRoot":"","sources":["../../src/synchronisedStorageService.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,2BAA2B,EAE3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EACN,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,MAAM,EACN,EAAE,EACF,iBAAiB,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,6BAA6B,EAE7B,MAAM,iCAAiC,CAAC;AAIzC,OAAO,EAKN,yBAAyB,EACzB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAwB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAwB,qBAAqB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACnG,OAAO,EAEN,iCAAiC,EACjC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,qBAAqB,MAAM,mCAAmC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAE5F,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAI3E;;GAEG;AACH,MAAM,OAAO,0BAA0B;IACtC;;OAEG;IACI,MAAM,CAAU,UAAU,gCAAgD;IAEjF;;;OAGG;IACK,MAAM,CAAU,uCAAuC,GAAW,CAAC,CAAC;IAE5E;;;OAGG;IACK,MAAM,CAAU,uCAAuC,GAAW,EAAE,CAAC;IAE7E;;;OAGG;IACK,MAAM,CAAU,iCAAiC,GAAW,GAAG,CAAC;IAExE;;;OAGG;IACK,MAAM,CAAU,2BAA2B,GAAW,CAAC,CAAC;IAEhE;;;OAGG;IACc,QAAQ,CAAqB;IAE9C;;;OAGG;IACc,kBAAkB,CAAqB;IAExD;;;OAGG;IACc,eAAe,CAAkB;IAElD;;;OAGG;IACc,oCAAoC,CAA6C;IAElG;;;OAGG;IACc,qBAAqB,CAAwB;IAE9D;;;OAGG;IACc,sCAAsC,CAA8B;IAErF;;;OAGG;IACc,uBAAuB,CAA0B;IAElE;;;OAGG;IACc,oCAAoC,CAAiC;IAEtF;;;OAGG;IACc,kBAAkB,CAAoB;IAEvD;;;OAGG;IACc,eAAe,CAAkB;IAElD;;;OAGG;IACc,gBAAgB,CAAkB;IAEnD;;;OAGG;IACc,qBAAqB,CAAuB;IAE7D;;;OAGG;IACc,sBAAsB,CAAwB;IAE/D;;;OAGG;IACc,OAAO,CAA8C;IAEtE;;;OAGG;IACc,uBAAuB,CAAS;IAEjD;;;OAGG;IACK,eAAe,CAAU;IAEjC;;;OAGG;IACc,kBAAkB,CAAoC;IAEvE;;;OAGG;IACK,OAAO,CAAU;IAEzB;;;OAGG;IACH,YAAY,OAAsD;QACjE,MAAM,CAAC,MAAM,CACZ,0BAA0B,CAAC,UAAU,aAErC,OAAO,CACP,CAAC;QACF,MAAM,CAAC,MAAM,CACZ,0BAA0B,CAAC,UAAU,oBAErC,OAAO,CAAC,MAAM,CACd,CAAC;QACF,MAAM,CAAC,WAAW,CACjB,0BAA0B,CAAC,UAAU,2CAErC,OAAO,CAAC,MAAM,CAAC,sBAAsB,CACrC,CAAC;QAEF,IAAI,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,qBAAqB,IAAI,WAAW,CAAC,CAAC;QAC7F,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC,WAAW,CAAC,OAAO,CAAC,oBAAoB,IAAI,SAAS,CAAC,CAAC;QACxF,IAAI,CAAC,eAAe,GAAG,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC,CAAC;QAExF,IAAI,CAAC,oCAAoC,GAAG,6BAA6B,CAAC,GAAG,CAE3E,OAAO,CAAC,gCAAgC,IAAI,qBAAqB,CAAC,CAAC;QAErE,IAAI,CAAC,sCAAsC,GAAG,iCAAiC,CAAC,GAAG,CAClF,OAAO,CAAC,8BAA8B,IAAI,oBAAoB,CAC9D,CAAC;QAEF,IAAI,CAAC,qBAAqB,GAAG,2BAA2B,CAAC,GAAG,CAC3D,OAAO,CAAC,wBAAwB,IAAI,cAAc,CAClD,CAAC;QAEF,IAAI,CAAC,uBAAuB,GAAG,gBAAgB,CAAC,GAAG,CAClD,OAAO,CAAC,0BAA0B,IAAI,gBAAgB,CACtD,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,gBAAgB,CAAC,GAAG,CAC1C,OAAO,EAAE,kBAAkB,IAAI,OAAO,CACtC,CAAC;QAEF,+FAA+F;QAC/F,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,uCAAuC,CAAC,EAAE,CAAC;YAChE,aAAa,GAAG,KAAK,CAAC;YAEtB,wEAAwE;YACxE,IAAI,CAAC,oCAAoC;gBACxC,gBAAgB,CAAC,GAAG,CACnB,OAAO,CAAC,uCAAuC,CAC/C,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,GAAG;YACd,2BAA2B,EAC1B,OAAO,CAAC,MAAM,CAAC,2BAA2B;gBAC1C,0BAA0B,CAAC,uCAAuC;YACnE,4BAA4B,EAC3B,OAAO,CAAC,MAAM,CAAC,4BAA4B;gBAC3C,0BAA0B,CAAC,uCAAuC;YACnE,sBAAsB,EACrB,OAAO,CAAC,MAAM,CAAC,sBAAsB;gBACrC,0BAA0B,CAAC,iCAAiC;YAC7D,iBAAiB,EAChB,OAAO,CAAC,MAAM,CAAC,iBAAiB,IAAI,0BAA0B,CAAC,2BAA2B;YAC3F,0BAA0B,EACzB,OAAO,CAAC,MAAM,CAAC,0BAA0B,IAAI,0CAA0C;YACxF,sBAAsB,EAAE,OAAO,CAAC,MAAM,CAAC,sBAAsB;YAC7D,0BAA0B,EAAE,OAAO,CAAC,MAAM,CAAC,0BAA0B,IAAI,EAAE;SAC3E,CAAC;QAEF,IAAI,CAAC,uBAAuB;YAC3B,qBAAqB,CACpB,OAAO,CAAC,MAAM,CAAC,sBAA4D,CAC3E,IAAI,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAAC;QAE5C,MAAM,CAAC,WAAW,CACjB,0BAA0B,CAAC,UAAU,EACrC,wBAAwB,EACxB,IAAI,CAAC,uBAAuB,CAC5B,CAAC;QAEF,IAAI,CAAC,kBAAkB,GAAG,IAAI,iBAAiB,CAC9C,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,eAAe,EACpB,IAAI,CAAC,qBAAqB,EAC1B,IAAI,CAAC,OAAO,CAAC,0BAA0B,EACvC,aAAa,CACb,CAAC;QAEF,IAAI,CAAC,gBAAgB,GAAG,IAAI,eAAe,CAC1C,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,kBAAkB,EACvB,IAAI,CAAC,kBAAkB,CACvB,CAAC;QAEF,IAAI,CAAC,qBAAqB,GAAG,IAAI,oBAAoB,CACpD,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,oCAAoC,EACzC,IAAI,CAAC,gBAAgB,CACrB,CAAC;QAEF,IAAI,CAAC,sBAAsB,GAAG,IAAI,qBAAqB,CACtD,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,kBAAkB,EACvB,IAAI,CAAC,sCAAsC,EAC3C,IAAI,CAAC,kBAAkB,EACvB,IAAI,CAAC,gBAAgB,EACrB,aAAa,EACb,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAC9B,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,0BAA0B,CAAC,UAAU,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,KAAK,CAAC,wBAAiC;QACnD,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,eAAe,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAE9C,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE9C,IAAI,CAAC,sBAAsB,CAAC,yBAAyB,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACpF,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAE5B,0FAA0F;QAC1F,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,EAAE,CAAC;YAC1D,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CACvD,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,MAAM,GAAG,CAAC;gBACjD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,0BAA0B;gBACzC,CAAC,CAAC,SAAS,CACZ,CAAC;YAEF,MAAM,aAAa,GAClB,MAAM,IAAI,CAAC,oCAAoC,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;YAEhF,kFAAkF;YAClF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CACpD,IAAI,CAAC,OAAO,CAAC,0BAA0B,CACvC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;YAC/E,CAAC;YAED,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAChC,IAAI,CAAC,OAAO,CAAC,0BAA0B,EACvC,YAAY,CAAC,gBAAgB,EAC7B,SAAS,CAAC,aAAa,CAAC,aAAa,CAAC,CACtC,CAAC;QACH,CAAC;QAED,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,CACtC,yBAAyB,CAAC,kBAAkB,EAC5C,KAAK,EAAC,KAAK,EAAC,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAClD,CAAC;QAEF,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,CACtC,yBAAyB,CAAC,eAAe,EACzC,KAAK,EAAC,KAAK,EAAC,EAAE;YACb,+CAA+C;YAC/C,IAAI,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxE,MAAM,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAC9C,KAAK,CAAC,IAAI,CAAC,UAAU,EACrB,KAAK,CAAC,IAAI,CAAC,SAAS,EACpB,KAAK,CAAC,IAAI,CAAC,EAAE,CACb,CAAC;YACH,CAAC;QACF,CAAC,CACD,CAAC;QAEF,MAAM,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,CAAC;QAE1C,yEAAyE;QACzE,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAClD,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,IAAI,CAAC,wBAAiC;QAClD,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAClD,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;YAC5C,MAAM,IAAI,CAAC,uBAAuB,CAAC,UAAU,CAAC,+BAA+B,UAAU,EAAE,CAAC,CAAC;YAC3F,MAAM,IAAI,CAAC,uBAAuB,CAAC,UAAU,CAC5C,sCAAsC,UAAU,EAAE,CAClD,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,gBAAgB,CAAC,YAAqB;QAClD,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,YAAY,CAAC,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,WAAW,CAC9C,IAAI,CAAC,eAAe,EACpB,YAAY,EACZ,kBAAkB,CAClB,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,OAAO,EAAE,sBAAsB;YAC/B,IAAI,EAAE;gBACL,MAAM,EAAE,SAAS,CAAC,QAAQ;aAC1B;SACD,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QACvF,IAAI,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;QAC7F,CAAC;QAED,OAAO,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,aAAa,CAAC,aAA6B,EAAE,YAAqB;QAC9E,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,YAAY,CAAC,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,CAAC,MAAM,CACZ,0BAA0B,CAAC,UAAU,mBAErC,aAAa,CACb,CAAC;QACF,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,WAAW,CAC9C,IAAI,CAAC,eAAe,EACpB,YAAY,EACZ,eAAe,CACf,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,OAAO,EAAE,4BAA4B;YACrC,IAAI,EAAE;gBACL,kBAAkB,EAAE,aAAa,CAAC,EAAE;gBACpC,MAAM,EAAE,SAAS,CAAC,QAAQ;aAC1B;SACD,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;QAEtE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,iCAAiC;YACjC,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAE/D,oDAAoD;YACpD,MAAM,IAAI,CAAC,sBAAsB,CAAC,uBAAuB,CACxD,IAAI,CAAC,aAAa,CAAC,UAAU,EAC7B,IAAI,CAAC,kBAAkB,CACvB,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,eAAe,CAAC,UAAkB;QAC/C,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,iBAAiB;gBAC1B,IAAI,EAAE;oBACL,UAAU;iBACV;aACD,CAAC,CAAC;YAEH,oCAAoC;YACpC,MAAM,IAAI,CAAC,yBAAyB,CAAC,UAAU,CAAC,CAAC;YAEjD,qDAAqD;YACrD,MAAM,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,kBAAkB;gBAC3B,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aACjC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,yBAAyB,CAAC,UAAkB;QACzD,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,OAAO,EAAE,2BAA2B;YACpC,IAAI,EAAE;gBACL,UAAU;aACV;SACD,CAAC,CAAC;QAEH,oEAAoE;QACpE,MAAM,0BAA0B,GAC/B,MAAM,IAAI,CAAC,sBAAsB,CAAC,6BAA6B,EAAE,CAAC;QAEnE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,0BAA0B,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YACpE,0EAA0E;YAC1E,yBAAyB;YACzB,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,YAAY,CACrE,0BAA0B,CAAC,YAAY,CAAC,UAAU,CAAC,CACnD,CAAC;YAEF,uDAAuD;YACvD,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,EAAE,CAAC;gBACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAC9E,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,wBAAwB,CAAC,UAAkB;QACxD,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,OAAO,EAAE,0BAA0B;YACnC,IAAI,EAAE;gBACL,UAAU;aACV;SACD,CAAC,CAAC;QAEH,MAAM,oBAAoB,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAE7F,IAAI,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,mBAAmB,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;YAEpD,IAAI,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,CAAC,sBAAsB,CAAC,cAAc,CAC/C,UAAU,EACV,mBAAmB,CAAC,OAAO,EAC3B,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,EAAE;oBAC3C,IAAI,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC;wBAC7D,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;4BACxB,KAAK,EAAE,MAAM;4BACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;4BAC7C,OAAO,EAAE,2BAA2B;4BACpC,IAAI,EAAE;gCACL,UAAU;6BACV;yBACD,CAAC,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACP,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;4BACxB,KAAK,EAAE,MAAM;4BACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;4BAC7C,OAAO,EAAE,uBAAuB;4BAChC,IAAI,EAAE;gCACL,UAAU;gCACV,kBAAkB;6BAClB;yBACD,CAAC,CAAC;wBACH,wEAAwE;wBACxE,IACC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC;4BACnD,EAAE,CAAC,WAAW,CAAC,kBAAkB,CAAC,EACjC,CAAC;4BACF,wEAAwE;4BACxE,uCAAuC;4BACvC,MAAM,IAAI,CAAC,sBAAsB,CAAC,uBAAuB,CACxD,UAAU,EACV,kBAAkB,CAClB,CAAC;4BACF,MAAM,IAAI,CAAC,qBAAqB,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;wBACjF,CAAC;6BAAM,IACN,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC;4BACpD,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC;4BACxB,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAC3B,CAAC;4BACF,gFAAgF;4BAChF,4CAA4C;4BAC5C,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gCACxB,KAAK,EAAE,MAAM;gCACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gCAC7C,OAAO,EAAE,+BAA+B;gCACxC,IAAI,EAAE;oCACL,UAAU;oCACV,kBAAkB;iCAClB;6BACD,CAAC,CAAC;4BAEH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CACvD,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,MAAM,GAAG,CAAC;gCACjD,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,0BAA0B;gCACzC,CAAC,CAAC,SAAS,CACZ,CAAC;4BAEF,MAAM,IAAI,CAAC,oCAAoC,CAAC,aAAa,CAC5D,aAAa,EACb,YAAY,CACZ,CAAC;4BAEF,MAAM,IAAI,CAAC,qBAAqB,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,CAAC;wBACjF,CAAC;oBACF,CAAC;gBACF,CAAC,CACD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;oBACxB,KAAK,EAAE,MAAM;oBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;oBAC7C,OAAO,EAAE,mCAAmC;oBAC5C,IAAI,EAAE;wBACL,UAAU;qBACV;iBACD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,sBAAsB,CAAC,UAAkB;QACtD,IAAI,CAAC;YACJ,0EAA0E;YAC1E,qEAAqE;YACrE,2DAA2D;YAC3D,6BAA6B;YAC7B,MAAM,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAEhD,8BAA8B;YAC9B,MAAM,IAAI,CAAC,sBAAsB,CAAC,kBAAkB,CACnD,UAAU,EACV,IAAI,CAAC,OAAO,CAAC,sBAAsB;gBAClC,0BAA0B,CAAC,iCAAiC,CAC7D,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,yBAAyB;gBAClC,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;aACjC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,kBAAkB,CAAC,sBAA+C;QAC/E,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,OAAO,EAAE,oBAAoB;YAC7B,IAAI,EAAE;gBACL,UAAU,EAAE,sBAAsB,CAAC,UAAU;aAC7C;SACD,CAAC,CAAC;QAEH,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YAC1E,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC;YAEnE,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC;YAClE,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,kBAAkB,CAAC,UAAkB;QAClD,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5F,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC;gBACxB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,oBAAoB;gBAC7B,IAAI,EAAE;oBACL,UAAU;iBACV;aACD,CAAC,CAAC;YAEH,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;YAE3C,IAAI,IAAI,CAAC,OAAO,CAAC,2BAA2B,GAAG,CAAC,EAAE,CAAC;gBAClD,MAAM,IAAI,CAAC,uBAAuB,CAAC,OAAO,CACzC,+BAA+B,UAAU,EAAE,EAC3C;oBACC;wBACC,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE;wBAC3B,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,2BAA2B;qBACzD;iBACD,EACD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAC5C,CAAC;YACH,CAAC;YAED,IACC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC;gBACpD,IAAI,CAAC,OAAO,CAAC,4BAA4B,GAAG,CAAC,EAC5C,CAAC;gBACF,MAAM,IAAI,CAAC,uBAAuB,CAAC,OAAO,CACzC,sCAAsC,UAAU,EAAE,EAClD;oBACC;wBACC,eAAe,EAAE,IAAI,CAAC,GAAG,EAAE;wBAC3B,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,4BAA4B;qBAC1D;iBACD,EACD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,UAAU,CAAC,CACnD,CAAC;YACH,CAAC;QACF,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITaskSchedulerComponent } from \"@twin.org/background-task-models\";\nimport {\n\tBlobStorageConnectorFactory,\n\ttype IBlobStorageConnector\n} from \"@twin.org/blob-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\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 ISynchronisedStorageComponent,\n\ttype ISyncItemChange,\n\ttype ISyncRegisterStorageKey,\n\tSynchronisedStorageTopics\n} from \"@twin.org/synchronised-storage-models\";\nimport { type ITrustComponent, TrustHelper } from \"@twin.org/trust-models\";\nimport { type IVaultConnector, VaultConnectorFactory, VaultKeyType } from \"@twin.org/vault-models\";\nimport {\n\ttype IVerifiableStorageConnector,\n\tVerifiableStorageConnectorFactory\n} from \"@twin.org/verifiable-storage-models\";\nimport verifiableStorageKeys from \"./data/verifiableStorageKeys.json\" with { type: \"json\" };\nimport type { SyncSnapshotEntry } from \"./entities/syncSnapshotEntry.js\";\nimport { BlobStorageHelper } from \"./helpers/blobStorageHelper.js\";\nimport { ChangeSetHelper } from \"./helpers/changeSetHelper.js\";\nimport { LocalSyncStateHelper } from \"./helpers/localSyncStateHelper.js\";\nimport { RemoteSyncStateHelper } from \"./helpers/remoteSyncStateHelper.js\";\nimport type { ISynchronisedStorageServiceConfig } from \"./models/ISynchronisedStorageServiceConfig.js\";\nimport type { ISynchronisedStorageServiceConstructorOptions } from \"./models/ISynchronisedStorageServiceConstructorOptions.js\";\n\n/**\n * Class for performing synchronised storage operations.\n */\nexport class SynchronisedStorageService implements ISynchronisedStorageComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SynchronisedStorageService>();\n\n\t/**\n\t * The default interval to check for entity updates.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES: number = 5;\n\n\t/**\n\t * The default interval to perform consolidation.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES: number = 60;\n\n\t/**\n\t * The default size of a consolidation batch.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_BATCH_SIZE: number = 100;\n\n\t/**\n\t * The default max number of consolidations to keep in storage.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MAX_CONSOLIDATIONS: number = 5;\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 vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The storage connector for the sync snapshot entries.\n\t * @internal\n\t */\n\tprivate readonly _localSyncSnapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>;\n\n\t/**\n\t * The blob storage connector to use for remote sync states.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageConnector: IBlobStorageConnector;\n\n\t/**\n\t * The verifiable storage connector to use for storing sync pointers.\n\t * @internal\n\t */\n\tprivate readonly _verifiableSyncPointerStorageConnector: IVerifiableStorageConnector;\n\n\t/**\n\t * The task scheduler component.\n\t * @internal\n\t */\n\tprivate readonly _taskSchedulerComponent: ITaskSchedulerComponent;\n\n\t/**\n\t * The synchronised storage service to use when this is not a trusted node.\n\t * @internal\n\t */\n\tprivate readonly _trustedSynchronisedStorageComponent?: ISynchronisedStorageComponent;\n\n\t/**\n\t * The blob storage helper.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageHelper: BlobStorageHelper;\n\n\t/**\n\t * The trust component.\n\t * @internal\n\t */\n\tprivate readonly _trustComponent: ITrustComponent;\n\n\t/**\n\t * The change set helper.\n\t * @internal\n\t */\n\tprivate readonly _changeSetHelper: ChangeSetHelper;\n\n\t/**\n\t * The local sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _localSyncStateHelper: LocalSyncStateHelper;\n\n\t/**\n\t * The remote sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _remoteSyncStateHelper: RemoteSyncStateHelper;\n\n\t/**\n\t * The options for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: Required<ISynchronisedStorageServiceConfig>;\n\n\t/**\n\t * The synchronised storage key to use for the remote synchronised storage.\n\t * @internal\n\t */\n\tprivate readonly _synchronisedStorageKey: string;\n\n\t/**\n\t * The flag to determine if the service has been started.\n\t * @internal\n\t */\n\tprivate _serviceStarted: boolean;\n\n\t/**\n\t * The active storage keys for the synchronised storage service.\n\t * @internal\n\t */\n\tprivate readonly _activeStorageKeys: { [storageKey: string]: boolean };\n\n\t/**\n\t * The identity of the node this connector is running on.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of SynchronisedStorageService.\n\t * @param options The options for the service.\n\t */\n\tconstructor(options: ISynchronisedStorageServiceConstructorOptions) {\n\t\tGuards.object<ISynchronisedStorageServiceConstructorOptions>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options),\n\t\t\toptions\n\t\t);\n\t\tGuards.object<ISynchronisedStorageServiceConfig>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config.verifiableStorageKeyId),\n\t\t\toptions.config.verifiableStorageKeyId\n\t\t);\n\n\t\tthis._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? \"event-bus\");\n\t\tthis._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? \"logging\");\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? \"vault\");\n\n\t\tthis._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get<\n\t\t\tIEntityStorageConnector<SyncSnapshotEntry>\n\t\t>(options.syncSnapshotStorageConnectorType ?? \"sync-snapshot-entry\");\n\n\t\tthis._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(\n\t\t\toptions.verifiableStorageConnectorType ?? \"verifiable-storage\"\n\t\t);\n\n\t\tthis._blobStorageConnector = BlobStorageConnectorFactory.get(\n\t\t\toptions.blobStorageConnectorType ?? \"blob-storage\"\n\t\t);\n\n\t\tthis._taskSchedulerComponent = ComponentFactory.get(\n\t\t\toptions.taskSchedulerComponentType ?? \"task-scheduler\"\n\t\t);\n\n\t\tthis._trustComponent = ComponentFactory.get<ITrustComponent>(\n\t\t\toptions?.trustComponentType ?? \"trust\"\n\t\t);\n\n\t\t// If this is empty we assume the local node has the rights to write to the verifiable storage.\n\t\tlet isTrustedNode = true;\n\t\tif (!Is.empty(options.trustedSynchronisedStorageComponentType)) {\n\t\t\tisTrustedNode = false;\n\n\t\t\t// If it is set then we used the trusted component to send changesets to\n\t\t\tthis._trustedSynchronisedStorageComponent =\n\t\t\t\tComponentFactory.get<ISynchronisedStorageComponent>(\n\t\t\t\t\toptions.trustedSynchronisedStorageComponentType\n\t\t\t\t);\n\t\t}\n\n\t\tthis._config = {\n\t\t\tentityUpdateIntervalMinutes:\n\t\t\t\toptions.config.entityUpdateIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,\n\t\t\tconsolidationIntervalMinutes:\n\t\t\t\toptions.config.consolidationIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,\n\t\t\tconsolidationBatchSize:\n\t\t\t\toptions.config.consolidationBatchSize ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,\n\t\t\tmaxConsolidations:\n\t\t\t\toptions.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,\n\t\t\tblobStorageEncryptionKeyId:\n\t\t\t\toptions.config.blobStorageEncryptionKeyId ?? \"synchronised-storage-blob-encryption-key\",\n\t\t\tverifiableStorageKeyId: options.config.verifiableStorageKeyId,\n\t\t\toverrideTrustGeneratorType: options.config.overrideTrustGeneratorType ?? \"\"\n\t\t};\n\n\t\tthis._synchronisedStorageKey =\n\t\t\tverifiableStorageKeys[\n\t\t\t\toptions.config.verifiableStorageKeyId as keyof typeof verifiableStorageKeys\n\t\t\t] ?? options.config.verifiableStorageKeyId;\n\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\t\"synchronisedStorageKey\",\n\t\t\tthis._synchronisedStorageKey\n\t\t);\n\n\t\tthis._blobStorageHelper = new BlobStorageHelper(\n\t\t\tthis._logging,\n\t\t\tthis._vaultConnector,\n\t\t\tthis._blobStorageConnector,\n\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\tisTrustedNode\n\t\t);\n\n\t\tthis._changeSetHelper = new ChangeSetHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._blobStorageHelper\n\t\t);\n\n\t\tthis._localSyncStateHelper = new LocalSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._localSyncSnapshotEntryEntityStorage,\n\t\t\tthis._changeSetHelper\n\t\t);\n\n\t\tthis._remoteSyncStateHelper = new RemoteSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._verifiableSyncPointerStorageConnector,\n\t\t\tthis._blobStorageHelper,\n\t\t\tthis._changeSetHelper,\n\t\t\tisTrustedNode,\n\t\t\tthis._config.maxConsolidations\n\t\t);\n\n\t\tthis._serviceStarted = false;\n\t\tthis._activeStorageKeys = {};\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn SynchronisedStorageService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\n\t\tthis._remoteSyncStateHelper.setNodeId(this._nodeId);\n\t\tthis._changeSetHelper.setNodeId(this._nodeId);\n\n\t\tthis._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);\n\t\tthis._serviceStarted = true;\n\n\t\t// If this is not a trusted node we need to request the decryption key from a trusted node\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tconst trustPayload = await this._trustComponent.generate(\n\t\t\t\tthis._nodeId,\n\t\t\t\tthis._config.overrideTrustGeneratorType.length > 0\n\t\t\t\t\t? this._config.overrideTrustGeneratorType\n\t\t\t\t\t: undefined\n\t\t\t);\n\n\t\t\tconst decryptionKey =\n\t\t\t\tawait this._trustedSynchronisedStorageComponent.getDecryptionKey(trustPayload);\n\n\t\t\t// If the key exists remove it and get a new one, in case the key has been rotated\n\t\t\tconst existingKey = await this._vaultConnector.getKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId\n\t\t\t);\n\n\t\t\tif (!Is.empty(existingKey)) {\n\t\t\t\tawait this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);\n\t\t\t}\n\n\t\t\tawait this._vaultConnector.addKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\t\tVaultKeyType.ChaCha20Poly1305,\n\t\t\t\tConverter.base64ToBytes(decryptionKey)\n\t\t\t);\n\t\t}\n\n\t\tawait this._eventBusComponent.subscribe<ISyncRegisterStorageKey>(\n\t\t\tSynchronisedStorageTopics.RegisterStorageKey,\n\t\t\tasync event => this.registerStorageKey(event.data)\n\t\t);\n\n\t\tawait this._eventBusComponent.subscribe<ISyncItemChange>(\n\t\t\tSynchronisedStorageTopics.LocalItemChange,\n\t\t\tasync event => {\n\t\t\t\t// Make sure the change event is from this node\n\t\t\t\tif (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {\n\t\t\t\t\tawait this._localSyncStateHelper.addLocalChange(\n\t\t\t\t\t\tevent.data.storageKey,\n\t\t\t\t\t\tevent.data.operation,\n\t\t\t\t\t\tevent.data.id\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\n\t\tawait this._remoteSyncStateHelper.start();\n\n\t\t// If there are already storage keys registered, we need to activate them\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tawait this.activateStorageKey(storageKey);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tthis._activeStorageKeys[storageKey] = false;\n\t\t\tawait this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);\n\t\t\tawait this._taskSchedulerComponent.removeTask(\n\t\t\t\t`synchronised-storage-consolidation-${storageKey}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the decryption key for the synchronised storage.\n\t * This is used to decrypt the data stored in the synchronised storage.\n\t * @param trustPayload Trust payload to verify the requesters identity.\n\t * @returns The decryption key.\n\t */\n\tpublic async getDecryptionKey(trustPayload: unknown): Promise<string> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tconst trustInfo = await TrustHelper.verifyTrust(\n\t\t\tthis._trustComponent,\n\t\t\ttrustPayload,\n\t\t\t\"getDecryptionKey\"\n\t\t);\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"decryptionKeyRequest\",\n\t\t\tdata: {\n\t\t\t\tnodeId: trustInfo.identity\n\t\t\t}\n\t\t});\n\n\t\tconst key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);\n\t\tif (Is.undefined(key.privateKey)) {\n\t\t\tthrow new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, \"decryptionKeyNotFound\");\n\t\t}\n\n\t\treturn Converter.bytesToBase64(key.privateKey);\n\t}\n\n\t/**\n\t * Synchronise a set of changes from an untrusted node, assumes this is a trusted node.\n\t * @param syncChangeSet The change set to synchronise.\n\t * @param trustPayload Trust payload to verify the requesters identity.\n\t * @returns Nothing.\n\t */\n\tpublic async syncChangeSet(syncChangeSet: ISyncChangeSet, trustPayload: unknown): Promise<void> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tGuards.object<ISyncChangeSet>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(syncChangeSet),\n\t\t\tsyncChangeSet\n\t\t);\n\t\tconst trustInfo = await TrustHelper.verifyTrust(\n\t\t\tthis._trustComponent,\n\t\t\ttrustPayload,\n\t\t\t\"syncChangeSet\"\n\t\t);\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"syncChangeSetForRemoteNode\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId: syncChangeSet.id,\n\t\t\t\tnodeId: trustInfo.identity\n\t\t\t}\n\t\t});\n\n\t\tconst copy = await this._changeSetHelper.copyChangeset(syncChangeSet);\n\n\t\tif (!Is.empty(copy)) {\n\t\t\t// Apply the changes to this node\n\t\t\tawait this._changeSetHelper.applyChangeset(copy.syncChangeSet);\n\n\t\t\t// And update the sync state with the latest changes\n\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\tcopy.syncChangeSet.storageKey,\n\t\t\t\tcopy.changeSetStorageId\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the sync with further updates after an interval.\n\t * @param storageKey The storage key to sync.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startEntitySync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"startEntitySync\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// First we check for remote changes\n\t\t\tawait this.updateFromRemoteSyncState(storageKey);\n\n\t\t\t// Now send any updates we have to the remote storage\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"entitySyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Check for updates in the remote storage.\n\t * @param storageKey The storage key to check for updates.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromRemoteSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromRemoteSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\t// Get the verifiable sync pointer store from the verifiable storage\n\t\tconst verifiableSyncPointerStore =\n\t\t\tawait this._remoteSyncStateHelper.getVerifiableSyncPointerStore();\n\n\t\tif (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {\n\t\t\t// Load the sync state from the remote blob storage using the sync pointer\n\t\t\t// to load the sync state\n\t\t\tconst remoteSyncState = await this._remoteSyncStateHelper.getSyncState(\n\t\t\t\tverifiableSyncPointerStore.syncPointers[storageKey]\n\t\t\t);\n\n\t\t\t// If we got the sync state we can try and sync from it\n\t\t\tif (!Is.undefined(remoteSyncState)) {\n\t\t\t\tawait this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Find any local updates and send them to the remote storage.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromLocalSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromLocalSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\tconst localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);\n\n\t\tif (localChangeSnapshots.length > 0) {\n\t\t\tconst localChangeSnapshot = localChangeSnapshots[0];\n\n\t\t\tif (Is.arrayValue(localChangeSnapshot.changes)) {\n\t\t\t\tawait this._remoteSyncStateHelper.buildChangeSet(\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tlocalChangeSnapshot.changes,\n\t\t\t\t\tasync (syncChangeSet, changeSetStorageId) => {\n\t\t\t\t\t\tif (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSetNone\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSet\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Send the local changes to the remote storage if we are a trusted node\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tIs.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.stringValue(changeSetStorageId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are a trusted node, we can add the change set to the sync state\n\t\t\t\t\t\t\t\t// and remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.object(syncChangeSet) &&\n\t\t\t\t\t\t\t\tIs.stringValue(this._nodeId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are not a trusted node, we need to send the changes to the trusted node\n\t\t\t\t\t\t\t\t// and then remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"sendingChangeSetToTrustedNode\",\n\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst trustPayload = await this._trustComponent.generate(\n\t\t\t\t\t\t\t\t\tthis._nodeId,\n\t\t\t\t\t\t\t\t\tthis._config.overrideTrustGeneratorType.length > 0\n\t\t\t\t\t\t\t\t\t\t? this._config.overrideTrustGeneratorType\n\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait this._trustedSynchronisedStorageComponent.syncChangeSet(\n\t\t\t\t\t\t\t\t\tsyncChangeSet,\n\t\t\t\t\t\t\t\t\ttrustPayload\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t}\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: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\tmessage: \"updateFromLocalSyncStateNoChanges\",\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}\n\t}\n\n\t/**\n\t * Start the consolidation sync.\n\t * @param storageKey The storage key to consolidate.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startConsolidationSync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\t// If we are going to perform a consolidation first take any local updates\n\t\t\t// we have and create a changeset from them, so that anybody applying\n\t\t\t// just changes since a consolidation can use the changeset\n\t\t\t// and skip the consolidation\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\n\t\t\t// Now start the consolidation\n\t\t\tawait this._remoteSyncStateHelper.consolidationStart(\n\t\t\t\tstorageKey,\n\t\t\t\tthis._config.consolidationBatchSize ??\n\t\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE\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: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"consolidationSyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Register a new sync type.\n\t * @param syncRegisterStorageKey The sync register type to register.\n\t * @internal\n\t */\n\tprivate async registerStorageKey(syncRegisterStorageKey: ISyncRegisterStorageKey): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"registerStorageKey\",\n\t\t\tdata: {\n\t\t\t\tstorageKey: syncRegisterStorageKey.storageKey\n\t\t\t}\n\t\t});\n\n\t\tif (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {\n\t\t\tthis._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;\n\n\t\t\tif (this._serviceStarted) {\n\t\t\t\tawait this.activateStorageKey(syncRegisterStorageKey.storageKey);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Activate a storage key.\n\t * @param storageKey The storage key to activate.\n\t * @internal\n\t */\n\tprivate async activateStorageKey(storageKey: string): Promise<void> {\n\t\tif (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"activateStorageKey\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis._activeStorageKeys[storageKey] = true;\n\n\t\t\tif (this._config.entityUpdateIntervalMinutes > 0) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-update-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.entityUpdateIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startEntitySync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\tthis._config.consolidationIntervalMinutes > 0\n\t\t\t) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-consolidation-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.consolidationIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startConsolidationSync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -1,8 +1,8 @@
1
- import type { ISyncChange, ISynchronisedEntity } from "@twin.org/synchronised-storage-models";
1
+ import type { ISyncChange } from "@twin.org/synchronised-storage-models";
2
2
  /**
3
3
  * Class representing an entry for the sync snapshot.
4
4
  */
5
- export declare class SyncSnapshotEntry<T extends ISynchronisedEntity = ISynchronisedEntity> {
5
+ export declare class SyncSnapshotEntry {
6
6
  /**
7
7
  * The id for the snapshot.
8
8
  */
@@ -42,5 +42,5 @@ export declare class SyncSnapshotEntry<T extends ISynchronisedEntity = ISynchron
42
42
  /**
43
43
  * The changes that were made in this snapshot, if this is a local snapshot.
44
44
  */
45
- changes?: ISyncChange<T>[];
45
+ changes?: ISyncChange[];
46
46
  }
@@ -8,16 +8,16 @@ export declare class BlobStorageHelper {
8
8
  /**
9
9
  * Runtime name for the class.
10
10
  */
11
- readonly CLASS_NAME: string;
11
+ static readonly CLASS_NAME: string;
12
12
  /**
13
13
  * Create a new instance of BlobStorageHelper.
14
- * @param loggingComponent The logging connector to use for logging.
14
+ * @param logging The logging component to use for logging.
15
15
  * @param vaultConnector The vault connector to use for for the encryption key.
16
16
  * @param blobStorageConnector The blob storage component to use.
17
17
  * @param blobStorageEncryptionKeyId The id of the vault key to use for encrypting/decrypting blobs.
18
18
  * @param isTrustedNode Is this a trusted node.
19
19
  */
20
- constructor(loggingComponent: ILoggingComponent | undefined, vaultConnector: IVaultConnector, blobStorageConnector: IBlobStorageConnector, blobStorageEncryptionKeyId: string, isTrustedNode: boolean);
20
+ constructor(logging: ILoggingComponent | undefined, vaultConnector: IVaultConnector, blobStorageConnector: IBlobStorageConnector, blobStorageEncryptionKeyId: string, isTrustedNode: boolean);
21
21
  /**
22
22
  * Load a blob from storage.
23
23
  * @param blobId The id of the blob to apply.