@twin.org/synchronised-storage-service 0.0.1-next.8 → 0.0.3-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.
- package/dist/es/data/verifiableStorageKeys.json +5 -0
- package/dist/es/entities/syncSnapshotEntry.js +93 -0
- package/dist/es/entities/syncSnapshotEntry.js.map +1 -0
- package/dist/es/helpers/blobStorageHelper.js +185 -0
- package/dist/es/helpers/blobStorageHelper.js.map +1 -0
- package/dist/es/helpers/changeSetHelper.js +215 -0
- package/dist/es/helpers/changeSetHelper.js.map +1 -0
- package/dist/es/helpers/localSyncStateHelper.js +384 -0
- package/dist/es/helpers/localSyncStateHelper.js.map +1 -0
- package/dist/es/helpers/remoteSyncStateHelper.js +560 -0
- package/dist/es/helpers/remoteSyncStateHelper.js.map +1 -0
- package/dist/es/helpers/versions.js +6 -0
- package/dist/es/helpers/versions.js.map +1 -0
- package/dist/es/index.js +13 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/ISyncPointerStore.js +4 -0
- package/dist/es/models/ISyncPointerStore.js.map +1 -0
- package/dist/es/models/ISyncSnapshot.js +4 -0
- package/dist/es/models/ISyncSnapshot.js.map +1 -0
- package/dist/es/models/ISyncState.js +2 -0
- package/dist/es/models/ISyncState.js.map +1 -0
- package/dist/es/models/ISynchronisedStorageServiceConfig.js +4 -0
- package/dist/es/models/ISynchronisedStorageServiceConfig.js.map +1 -0
- package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js +2 -0
- package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js.map +1 -0
- package/dist/es/restEntryPoints.js +10 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/es/schema.js +11 -0
- package/dist/es/schema.js.map +1 -0
- package/dist/es/synchronisedStorageRoutes.js +153 -0
- package/dist/es/synchronisedStorageRoutes.js.map +1 -0
- package/dist/es/synchronisedStorageService.js +554 -0
- package/dist/es/synchronisedStorageService.js.map +1 -0
- package/dist/types/entities/syncSnapshotEntry.d.ts +3 -3
- package/dist/types/helpers/blobStorageHelper.d.ts +3 -3
- package/dist/types/helpers/changeSetHelper.d.ts +16 -32
- package/dist/types/helpers/localSyncStateHelper.d.ts +11 -11
- package/dist/types/helpers/remoteSyncStateHelper.d.ts +18 -14
- package/dist/types/index.d.ts +10 -10
- package/dist/types/models/ISyncState.d.ts +1 -1
- package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +3 -8
- package/dist/types/models/ISynchronisedStorageServiceConstructorOptions.d.ts +7 -6
- package/dist/types/synchronisedStorageRoutes.d.ts +1 -1
- package/dist/types/synchronisedStorageService.d.ts +18 -21
- package/docs/architecture.md +168 -12
- package/docs/changelog.md +149 -0
- package/docs/open-api/spec.json +62 -57
- package/docs/reference/classes/SyncSnapshotEntry.md +4 -10
- package/docs/reference/classes/SynchronisedStorageService.md +38 -50
- package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +3 -17
- package/docs/reference/interfaces/ISynchronisedStorageServiceConstructorOptions.md +9 -8
- package/locales/en.json +11 -16
- package/package.json +26 -9
- package/dist/cjs/index.cjs +0 -2233
- package/dist/esm/index.mjs +0 -2225
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { Converter, Is, RandomHelper } from "@twin.org/core";
|
|
4
|
+
import { ComparisonOperator } from "@twin.org/entity";
|
|
5
|
+
import { SyncNodeIdMode } from "@twin.org/synchronised-storage-models";
|
|
6
|
+
import { SYNC_SNAPSHOT_VERSION } from "./versions.js";
|
|
7
|
+
/**
|
|
8
|
+
* Class for performing entity storage operations in decentralised storage.
|
|
9
|
+
*/
|
|
10
|
+
export class LocalSyncStateHelper {
|
|
11
|
+
/**
|
|
12
|
+
* Runtime name for the class.
|
|
13
|
+
*/
|
|
14
|
+
static CLASS_NAME = "LocalSyncStateHelper";
|
|
15
|
+
/**
|
|
16
|
+
* The logging component to use for logging.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
_logging;
|
|
20
|
+
/**
|
|
21
|
+
* The storage connector for the sync snapshot entries.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
_snapshotEntryEntityStorage;
|
|
25
|
+
/**
|
|
26
|
+
* The change set helper to use for applying changesets.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
_changeSetHelper;
|
|
30
|
+
/**
|
|
31
|
+
* Create a new instance of LocalSyncStateHelper.
|
|
32
|
+
* @param logging The logging component to use for logging.
|
|
33
|
+
* @param snapshotEntryEntityStorage The storage connector for the sync snapshot entries.
|
|
34
|
+
* @param changeSetHelper The change set helper to use for applying changesets.
|
|
35
|
+
*/
|
|
36
|
+
constructor(logging, snapshotEntryEntityStorage, changeSetHelper) {
|
|
37
|
+
this._logging = logging;
|
|
38
|
+
this._snapshotEntryEntityStorage = snapshotEntryEntityStorage;
|
|
39
|
+
this._changeSetHelper = changeSetHelper;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Add a new change to the local snapshot.
|
|
43
|
+
* @param storageKey The storage key of the snapshot to add the change for.
|
|
44
|
+
* @param operation The operation to perform.
|
|
45
|
+
* @param id The id of the entity to add the change for.
|
|
46
|
+
* @returns Nothing.
|
|
47
|
+
*/
|
|
48
|
+
async addLocalChange(storageKey, operation, id) {
|
|
49
|
+
await this._logging?.log({
|
|
50
|
+
level: "info",
|
|
51
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
52
|
+
message: "addLocalChange",
|
|
53
|
+
data: {
|
|
54
|
+
storageKey,
|
|
55
|
+
operation,
|
|
56
|
+
id
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const localChangeSnapshots = await this.getSnapshots(storageKey, true);
|
|
60
|
+
if (localChangeSnapshots.length > 0) {
|
|
61
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
62
|
+
localChangeSnapshot.changes ??= [];
|
|
63
|
+
// If we already have a change for this id we are
|
|
64
|
+
// about to supersede it, we remove the previous change
|
|
65
|
+
// to avoid having multiple changes for the same id
|
|
66
|
+
const previousChangeIndex = localChangeSnapshot.changes.findIndex(change => change.id === id);
|
|
67
|
+
if (previousChangeIndex !== -1) {
|
|
68
|
+
localChangeSnapshot.changes.splice(previousChangeIndex, 1);
|
|
69
|
+
}
|
|
70
|
+
// If we already have changes from previous updates
|
|
71
|
+
// then make sure we update the dateModified, otherwise
|
|
72
|
+
// we assume this is the first change and setting modified is not necessary
|
|
73
|
+
if (localChangeSnapshot.changes.length > 0) {
|
|
74
|
+
localChangeSnapshot.dateModified = new Date(Date.now()).toISOString();
|
|
75
|
+
}
|
|
76
|
+
localChangeSnapshot.changes.push({ operation, id });
|
|
77
|
+
await this.setLocalChangeSnapshot(localChangeSnapshot);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the snapshot which contains just the changes for this node.
|
|
82
|
+
* @param storageKey The storage key of the snapshot to get.
|
|
83
|
+
* @param isLocal Whether to get the local snapshot or not.
|
|
84
|
+
* @returns The local snapshot entry.
|
|
85
|
+
*/
|
|
86
|
+
async getSnapshots(storageKey, isLocal) {
|
|
87
|
+
await this._logging?.log({
|
|
88
|
+
level: "info",
|
|
89
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
90
|
+
message: "getSnapshots",
|
|
91
|
+
data: {
|
|
92
|
+
storageKey
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const queryResult = await this._snapshotEntryEntityStorage.query({
|
|
96
|
+
conditions: [
|
|
97
|
+
{
|
|
98
|
+
property: "isLocal",
|
|
99
|
+
value: isLocal,
|
|
100
|
+
comparison: ComparisonOperator.Equals
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
property: "storageKey",
|
|
104
|
+
value: storageKey,
|
|
105
|
+
comparison: ComparisonOperator.Equals
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
if (queryResult.entities.length > 0) {
|
|
110
|
+
await this._logging?.log({
|
|
111
|
+
level: "info",
|
|
112
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
113
|
+
message: "getSnapshotsExists",
|
|
114
|
+
data: {
|
|
115
|
+
storageKey
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return queryResult.entities;
|
|
119
|
+
}
|
|
120
|
+
await this._logging?.log({
|
|
121
|
+
level: "info",
|
|
122
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
123
|
+
message: "getSnapshotsDoesNotExist",
|
|
124
|
+
data: {
|
|
125
|
+
storageKey
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const now = new Date(Date.now()).toISOString();
|
|
129
|
+
return [
|
|
130
|
+
{
|
|
131
|
+
version: SYNC_SNAPSHOT_VERSION,
|
|
132
|
+
id: Converter.bytesToHex(RandomHelper.generate(32)),
|
|
133
|
+
storageKey,
|
|
134
|
+
dateCreated: now,
|
|
135
|
+
dateModified: now,
|
|
136
|
+
changeSetStorageIds: [],
|
|
137
|
+
isLocal,
|
|
138
|
+
isConsolidated: false,
|
|
139
|
+
epoch: 0
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Set the current local snapshot with changes for this node.
|
|
145
|
+
* @param localChangeSnapshot The local change snapshot to set.
|
|
146
|
+
* @returns Nothing.
|
|
147
|
+
*/
|
|
148
|
+
async setLocalChangeSnapshot(localChangeSnapshot) {
|
|
149
|
+
await this._logging?.log({
|
|
150
|
+
level: "info",
|
|
151
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
152
|
+
message: "setLocalChangeSnapshot",
|
|
153
|
+
data: {
|
|
154
|
+
storageKey: localChangeSnapshot.storageKey
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
await this._snapshotEntryEntityStorage.set(localChangeSnapshot);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the current local snapshot with the changes for this node.
|
|
161
|
+
* @param localChangeSnapshot The local change snapshot to remove.
|
|
162
|
+
* @returns Nothing.
|
|
163
|
+
*/
|
|
164
|
+
async removeLocalChangeSnapshot(localChangeSnapshot) {
|
|
165
|
+
await this._logging?.log({
|
|
166
|
+
level: "info",
|
|
167
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
168
|
+
message: "removeLocalChangeSnapshot",
|
|
169
|
+
data: {
|
|
170
|
+
snapshotId: localChangeSnapshot.id
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
await this._snapshotEntryEntityStorage.remove(localChangeSnapshot.id);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Apply a sync state to the local node.
|
|
177
|
+
* @param storageKey The storage key of the snapshot to sync with.
|
|
178
|
+
* @param syncState The sync state to sync with.
|
|
179
|
+
* @returns Nothing.
|
|
180
|
+
*/
|
|
181
|
+
async applySyncState(storageKey, syncState) {
|
|
182
|
+
await this._logging?.log({
|
|
183
|
+
level: "info",
|
|
184
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
185
|
+
message: "applySyncState",
|
|
186
|
+
data: {
|
|
187
|
+
snapshotCount: syncState.snapshots.length
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// Get all the existing snapshots that we have processed previously
|
|
191
|
+
let existingSnapshots = await this.getSnapshots(storageKey, false);
|
|
192
|
+
// Sort from newest to oldest
|
|
193
|
+
existingSnapshots = existingSnapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
|
|
194
|
+
// Sort from newest to oldest
|
|
195
|
+
const syncStateSnapshots = syncState.snapshots.sort((a, b) => new Date(b.dateCreated).getTime() - new Date(a.dateCreated).getTime());
|
|
196
|
+
// Get the newest epoch from the local storage
|
|
197
|
+
const newestExistingEpoch = existingSnapshots[0]?.epoch ?? 0;
|
|
198
|
+
// Get the oldest epoch from the remote storage
|
|
199
|
+
const oldestSyncStateEpoch = syncStateSnapshots[syncStateSnapshots.length - 1]?.epoch ?? 0;
|
|
200
|
+
// If there is a gap between the largest epoch we have locally
|
|
201
|
+
// and the smallest epoch we have remotely then we have missed
|
|
202
|
+
// data so we need to perform a full sync
|
|
203
|
+
const hasEpochGap = newestExistingEpoch + 1 < oldestSyncStateEpoch;
|
|
204
|
+
// If we have an epoch gap or no existing snapshots then we need to apply
|
|
205
|
+
// a full sync from a consolidation
|
|
206
|
+
if (!existingSnapshots.some(s => s.isConsolidated) || hasEpochGap) {
|
|
207
|
+
await this._logging?.log({
|
|
208
|
+
level: "info",
|
|
209
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
210
|
+
message: "applySnapshotNoExisting",
|
|
211
|
+
data: {
|
|
212
|
+
storageKey
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const mostRecentConsolidation = syncStateSnapshots.findIndex(snapshot => snapshot.isConsolidated);
|
|
216
|
+
if (mostRecentConsolidation !== -1) {
|
|
217
|
+
// We found the most recent consolidated snapshot, we can use it
|
|
218
|
+
await this._logging?.log({
|
|
219
|
+
level: "info",
|
|
220
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
221
|
+
message: "applySnapshotFoundConsolidated",
|
|
222
|
+
data: {
|
|
223
|
+
storageKey,
|
|
224
|
+
snapshotId: syncStateSnapshots[mostRecentConsolidation].id
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// We need to reset the entity storage and remove all the remote items
|
|
228
|
+
// so that we use just the ones from the consolidation, since
|
|
229
|
+
// we don't have any existing there shouldn't be any remote entries
|
|
230
|
+
// but we reset nonetheless
|
|
231
|
+
await this._changeSetHelper.reset(storageKey, SyncNodeIdMode.Remote);
|
|
232
|
+
// We need to process the most recent consolidation and all changes
|
|
233
|
+
// that were made since then, from newest to oldest (so newer changes override older ones)
|
|
234
|
+
// Process snapshots from the consolidation point (most recent) back to the newest
|
|
235
|
+
for (let i = mostRecentConsolidation; i >= 0; i--) {
|
|
236
|
+
await this.processNewSnapshots([
|
|
237
|
+
{
|
|
238
|
+
...syncStateSnapshots[i],
|
|
239
|
+
storageKey,
|
|
240
|
+
isLocal: false
|
|
241
|
+
}
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
await this._logging?.log({
|
|
247
|
+
level: "info",
|
|
248
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
249
|
+
message: "applySnapshotNoConsolidated",
|
|
250
|
+
data: {
|
|
251
|
+
storageKey
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// We have existing consolidated remote snapshots, so we can assume that we have
|
|
258
|
+
// applied at least one consolidation snapshot, in this case we need to look at the changes since
|
|
259
|
+
// then and apply them if we haven't already
|
|
260
|
+
// We don't need to apply any additional consolidated snapshots, just the changesets
|
|
261
|
+
// Create a lookup map for the existing snapshots
|
|
262
|
+
const existingSnapshotsMap = {};
|
|
263
|
+
for (const snapshot of existingSnapshots) {
|
|
264
|
+
existingSnapshotsMap[snapshot.id] = snapshot;
|
|
265
|
+
}
|
|
266
|
+
const newSnapshots = [];
|
|
267
|
+
const modifiedSnapshots = [];
|
|
268
|
+
const referencedExistingSnapshots = Object.keys(existingSnapshotsMap);
|
|
269
|
+
let completedProcessing = false;
|
|
270
|
+
for (const snapshot of syncStateSnapshots) {
|
|
271
|
+
await this._logging?.log({
|
|
272
|
+
level: "info",
|
|
273
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
274
|
+
message: "applySnapshot",
|
|
275
|
+
data: {
|
|
276
|
+
snapshotId: snapshot.id,
|
|
277
|
+
dateCreated: new Date(snapshot.dateCreated).toISOString()
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
// See if we have the snapshot stored locally
|
|
281
|
+
const currentSnapshot = existingSnapshotsMap[snapshot.id];
|
|
282
|
+
// As we are referencing an existing snapshot, we need to remove it from the list
|
|
283
|
+
// to allow us to cleanup any unreferenced snapshots later
|
|
284
|
+
const idx = referencedExistingSnapshots.indexOf(snapshot.id);
|
|
285
|
+
if (idx !== -1) {
|
|
286
|
+
referencedExistingSnapshots.splice(idx, 1);
|
|
287
|
+
}
|
|
288
|
+
// No need to apply consolidated snapshots
|
|
289
|
+
if (!snapshot.isConsolidated && !completedProcessing) {
|
|
290
|
+
const updatedSnapshot = {
|
|
291
|
+
...snapshot,
|
|
292
|
+
storageKey,
|
|
293
|
+
isLocal: false
|
|
294
|
+
};
|
|
295
|
+
if (Is.empty(currentSnapshot)) {
|
|
296
|
+
// We don't have the snapshot locally, so we need to process all of it
|
|
297
|
+
newSnapshots.push(updatedSnapshot);
|
|
298
|
+
}
|
|
299
|
+
else if (currentSnapshot.dateModified !== snapshot.dateModified) {
|
|
300
|
+
// If the local snapshot has a different dateModified, we need to update it
|
|
301
|
+
modifiedSnapshots.push({
|
|
302
|
+
currentSnapshot,
|
|
303
|
+
updatedSnapshot
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// we sorted the snapshots from newest to oldest, so if we found a local snapshot
|
|
308
|
+
// with the same dateModified as the remote snapshot, we can stop processing further
|
|
309
|
+
completedProcessing = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// We reverse the order of the snapshots to process them from oldest to newest
|
|
314
|
+
// because we want to apply the changes in the order they were created
|
|
315
|
+
await this.processModifiedSnapshots(modifiedSnapshots.reverse());
|
|
316
|
+
await this.processNewSnapshots(newSnapshots.reverse());
|
|
317
|
+
// Any ids remaining in this list are no longer referenced in the global state
|
|
318
|
+
// so we should remove them from the local storage as they will never be updated again
|
|
319
|
+
for (const referencedSnapshotId of referencedExistingSnapshots) {
|
|
320
|
+
await this._snapshotEntryEntityStorage.remove(referencedSnapshotId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Process the modified snapshots and store them in the local storage.
|
|
326
|
+
* @param modifiedSnapshots The modified snapshots to process.
|
|
327
|
+
* @returns Nothing.
|
|
328
|
+
* @internal
|
|
329
|
+
*/
|
|
330
|
+
async processModifiedSnapshots(modifiedSnapshots) {
|
|
331
|
+
for (const modifiedSnapshot of modifiedSnapshots) {
|
|
332
|
+
await this._logging?.log({
|
|
333
|
+
level: "info",
|
|
334
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
335
|
+
message: "processModifiedSnapshot",
|
|
336
|
+
data: {
|
|
337
|
+
snapshotId: modifiedSnapshot.updatedSnapshot.id,
|
|
338
|
+
localModified: new Date(modifiedSnapshot.currentSnapshot.dateModified ??
|
|
339
|
+
modifiedSnapshot.currentSnapshot.dateCreated).toISOString(),
|
|
340
|
+
remoteModified: new Date(modifiedSnapshot.updatedSnapshot.dateModified ??
|
|
341
|
+
modifiedSnapshot.updatedSnapshot.dateCreated).toISOString()
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
const remoteChangeSetStorageIds = modifiedSnapshot.updatedSnapshot.changeSetStorageIds;
|
|
345
|
+
const localChangeSetStorageIds = modifiedSnapshot.currentSnapshot.changeSetStorageIds ?? [];
|
|
346
|
+
if (Is.arrayValue(remoteChangeSetStorageIds)) {
|
|
347
|
+
for (const storageId of remoteChangeSetStorageIds) {
|
|
348
|
+
// Check if the local snapshot does not have the storageId
|
|
349
|
+
if (!localChangeSetStorageIds.includes(storageId)) {
|
|
350
|
+
await this._changeSetHelper.getAndApplyChangeset(storageId);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
await this._snapshotEntryEntityStorage.set(modifiedSnapshot.updatedSnapshot);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Process the new snapshots and store them in the local storage.
|
|
359
|
+
* @param newSnapshots The new snapshots to process.
|
|
360
|
+
* @returns Nothing.
|
|
361
|
+
* @internal
|
|
362
|
+
*/
|
|
363
|
+
async processNewSnapshots(newSnapshots) {
|
|
364
|
+
for (const newSnapshot of newSnapshots) {
|
|
365
|
+
await this._logging?.log({
|
|
366
|
+
level: "info",
|
|
367
|
+
source: LocalSyncStateHelper.CLASS_NAME,
|
|
368
|
+
message: "processNewSnapshot",
|
|
369
|
+
data: {
|
|
370
|
+
snapshotId: newSnapshot.id,
|
|
371
|
+
dateCreated: newSnapshot.dateCreated
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
const newSnapshotChangeSetStorageIds = newSnapshot.changeSetStorageIds ?? [];
|
|
375
|
+
if (Is.arrayValue(newSnapshotChangeSetStorageIds)) {
|
|
376
|
+
for (const storageId of newSnapshotChangeSetStorageIds) {
|
|
377
|
+
await this._changeSetHelper.getAndApplyChangeset(storageId);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
await this._snapshotEntryEntityStorage.set(newSnapshot);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=localSyncStateHelper.js.map
|
|
@@ -0,0 +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"]}
|