@twin.org/entity-storage-connector-synchronised 0.0.2-next.3 → 0.0.2-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +164 -55
- package/dist/esm/index.mjs +166 -57
- package/docs/changelog.md +32 -0
- package/package.json +2 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -40,6 +40,11 @@ class SynchronisedEntityStorageConnector {
|
|
|
40
40
|
* @internal
|
|
41
41
|
*/
|
|
42
42
|
_eventBusComponent;
|
|
43
|
+
/**
|
|
44
|
+
* The node identity.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
_nodeIdentity;
|
|
43
48
|
/**
|
|
44
49
|
* Create a new instance of SynchronisedEntityStorageConnector.
|
|
45
50
|
* @param options The options for the connector.
|
|
@@ -51,13 +56,19 @@ class SynchronisedEntityStorageConnector {
|
|
|
51
56
|
this._entitySchema = entity.EntitySchemaFactory.get(options.entitySchema);
|
|
52
57
|
this._storageKey = options?.config?.storageKey ?? core.StringHelper.kebabCase(options.entitySchema);
|
|
53
58
|
this._primaryKey = entity.EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
54
|
-
const requiredProperties = [
|
|
59
|
+
const requiredProperties = [
|
|
60
|
+
"id",
|
|
61
|
+
"nodeIdentity",
|
|
62
|
+
"dateModified"
|
|
63
|
+
];
|
|
55
64
|
for (const requiredProperty of requiredProperties) {
|
|
56
65
|
const foundProperty = this._entitySchema.properties?.find(prop => prop.property === requiredProperty);
|
|
57
66
|
if (core.Is.empty(foundProperty)) {
|
|
58
67
|
throw new core.GeneralError(this.CLASS_NAME, "missingRequiredProperty", { requiredProperty });
|
|
59
68
|
}
|
|
60
|
-
else if (core.Is.empty(foundProperty.
|
|
69
|
+
else if (core.Is.empty(foundProperty.isPrimary) &&
|
|
70
|
+
core.Is.empty(foundProperty.isSecondary) &&
|
|
71
|
+
core.Is.empty(foundProperty.sortDirection)) {
|
|
61
72
|
throw new core.GeneralError(this.CLASS_NAME, "missingRequiredPropertySort", {
|
|
62
73
|
requiredProperty
|
|
63
74
|
});
|
|
@@ -81,6 +92,7 @@ class SynchronisedEntityStorageConnector {
|
|
|
81
92
|
* @returns Nothing.
|
|
82
93
|
*/
|
|
83
94
|
async start(nodeIdentity, nodeLoggingConnectorType, componentState) {
|
|
95
|
+
this._nodeIdentity = nodeIdentity;
|
|
84
96
|
// Tell the synchronised storage about this storage key
|
|
85
97
|
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.RegisterStorageKey, {
|
|
86
98
|
storageKey: this._storageKey
|
|
@@ -106,15 +118,19 @@ class SynchronisedEntityStorageConnector {
|
|
|
106
118
|
*/
|
|
107
119
|
async set(entity, conditions) {
|
|
108
120
|
core.Guards.object(this.CLASS_NAME, "entity", entity);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
if (core.Is.stringValue(this._nodeIdentity)) {
|
|
122
|
+
// Make sure the entity has the required properties
|
|
123
|
+
entity.dateModified = new Date(Date.now()).toISOString();
|
|
124
|
+
entity.nodeIdentity = this._nodeIdentity;
|
|
125
|
+
await this._entityStorageConnector.set(entity, conditions);
|
|
126
|
+
// Tell the synchronised storage about the entity changes
|
|
127
|
+
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemChange, {
|
|
128
|
+
storageKey: this._storageKey,
|
|
129
|
+
operation: synchronisedStorageModels.SyncChangeOperation.Set,
|
|
130
|
+
nodeIdentity: this._nodeIdentity,
|
|
131
|
+
id: entity[this._primaryKey.property]
|
|
132
|
+
});
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
/**
|
|
120
136
|
* Remove the entity.
|
|
@@ -124,13 +140,16 @@ class SynchronisedEntityStorageConnector {
|
|
|
124
140
|
*/
|
|
125
141
|
async remove(id, conditions) {
|
|
126
142
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
if (core.Is.stringValue(this._nodeIdentity)) {
|
|
144
|
+
await this._entityStorageConnector.remove(id, conditions);
|
|
145
|
+
// Tell the synchronised storage about the entity removal
|
|
146
|
+
await this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemChange, {
|
|
147
|
+
storageKey: this._storageKey,
|
|
148
|
+
operation: synchronisedStorageModels.SyncChangeOperation.Delete,
|
|
149
|
+
nodeIdentity: this._nodeIdentity,
|
|
150
|
+
id
|
|
151
|
+
});
|
|
152
|
+
}
|
|
134
153
|
}
|
|
135
154
|
/**
|
|
136
155
|
* Find all the entities which match the conditions.
|
|
@@ -152,54 +171,144 @@ class SynchronisedEntityStorageConnector {
|
|
|
152
171
|
handleEventBusMessages() {
|
|
153
172
|
// When the synchronised storage requests an item, we need to provide it
|
|
154
173
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemRequest, async (params) => {
|
|
155
|
-
|
|
156
|
-
if (params.data.storageKey === this._storageKey) {
|
|
157
|
-
let entity;
|
|
158
|
-
try {
|
|
159
|
-
entity = await this._entityStorageConnector.get(params.data.id);
|
|
160
|
-
}
|
|
161
|
-
catch { }
|
|
162
|
-
// Publish the item response with the entity
|
|
163
|
-
this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemResponse, {
|
|
164
|
-
storageKey: this._storageKey,
|
|
165
|
-
id: params.data.id,
|
|
166
|
-
entity
|
|
167
|
-
});
|
|
168
|
-
}
|
|
174
|
+
await this.handleLocalItemRequest(params);
|
|
169
175
|
});
|
|
170
176
|
// When the synchronised storage requests a batch, we need to provide it
|
|
171
177
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.BatchRequest, async (params) => {
|
|
172
|
-
|
|
173
|
-
if (params.data.storageKey === this._storageKey) {
|
|
174
|
-
let cursor;
|
|
175
|
-
do {
|
|
176
|
-
const result = await this._entityStorageConnector.query(undefined, [{ property: "dateModified", sortDirection: entity.SortDirection.Ascending }], undefined, cursor, params.data.batchSize);
|
|
177
|
-
cursor = result.cursor;
|
|
178
|
-
// Publish the batch response with the entities
|
|
179
|
-
this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.BatchResponse, {
|
|
180
|
-
storageKey: this._storageKey,
|
|
181
|
-
primaryKey: this._primaryKey.property,
|
|
182
|
-
entities: result.entities,
|
|
183
|
-
lastEntry: !core.Is.stringValue(cursor)
|
|
184
|
-
});
|
|
185
|
-
} while (core.Is.stringValue(cursor));
|
|
186
|
-
}
|
|
178
|
+
await this.handleBatchRequest(params);
|
|
187
179
|
});
|
|
188
180
|
// Subscribe to remote item set events from the synchronised storage and update the local storage
|
|
189
181
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.RemoteItemSet, async (params) => {
|
|
190
|
-
|
|
191
|
-
if (params.data.storageKey === this._storageKey) {
|
|
192
|
-
await this.set(params.data.entity);
|
|
193
|
-
}
|
|
182
|
+
await this.handleRemoteItemSet(params);
|
|
194
183
|
});
|
|
195
184
|
// Subscribe to remote item remove events from the synchronised storage and update the local storage
|
|
196
185
|
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.RemoteItemRemove, async (params) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
186
|
+
await this.handleRemoteItemRemove(params);
|
|
187
|
+
});
|
|
188
|
+
// Subscribe to resets from the synchronised storage and update the local storage
|
|
189
|
+
this._eventBusComponent.subscribe(synchronisedStorageModels.SynchronisedStorageTopics.Reset, async (params) => {
|
|
190
|
+
await this.handleReset(params);
|
|
201
191
|
});
|
|
202
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Handle a local item request.
|
|
195
|
+
* @param event The request parameters
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
async handleLocalItemRequest(event) {
|
|
199
|
+
// Only handle the request if it matches the storage key
|
|
200
|
+
if (event.data.storageKey === this._storageKey) {
|
|
201
|
+
let entity;
|
|
202
|
+
try {
|
|
203
|
+
entity = await this._entityStorageConnector.get(event.data.id);
|
|
204
|
+
}
|
|
205
|
+
catch { }
|
|
206
|
+
// Publish the item response with the entity
|
|
207
|
+
this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.LocalItemResponse, {
|
|
208
|
+
storageKey: this._storageKey,
|
|
209
|
+
id: event.data.id,
|
|
210
|
+
entity
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Handle a remote item set event.
|
|
216
|
+
* @param event The event parameters
|
|
217
|
+
* @internal
|
|
218
|
+
*/
|
|
219
|
+
async handleRemoteItemSet(event) {
|
|
220
|
+
// Only set the item if it matches the storage key
|
|
221
|
+
// and it is from another node, remote updates can not change data for this node
|
|
222
|
+
// That must be done via the regular entity storage methods
|
|
223
|
+
if (event.data.storageKey === this._storageKey &&
|
|
224
|
+
event.data.entity.nodeIdentity !== this._nodeIdentity) {
|
|
225
|
+
await this._entityStorageConnector.set(event.data.entity);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Handle a remote item remove event.
|
|
230
|
+
* @param params The event parameters
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
async handleRemoteItemRemove(params) {
|
|
234
|
+
// Only remove the item if it matches the storage key
|
|
235
|
+
// and it is from another node, remote updates can not change data for this node
|
|
236
|
+
// That must be done via the regular entity storage methods
|
|
237
|
+
if (params.data.storageKey === this._storageKey &&
|
|
238
|
+
params.data.nodeIdentity !== this._nodeIdentity) {
|
|
239
|
+
await this._entityStorageConnector.remove(params.data.id);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Handle a batch request.
|
|
244
|
+
* @param event The request parameters
|
|
245
|
+
* @internal
|
|
246
|
+
*/
|
|
247
|
+
async handleBatchRequest(event) {
|
|
248
|
+
// Only handle the request if it matches the storage key
|
|
249
|
+
if (event.data.storageKey === this._storageKey) {
|
|
250
|
+
let cursor;
|
|
251
|
+
do {
|
|
252
|
+
const condition = {
|
|
253
|
+
conditions: []
|
|
254
|
+
};
|
|
255
|
+
if (event.data.requestMode === synchronisedStorageModels.SyncNodeIdentityMode.Local ||
|
|
256
|
+
event.data.requestMode === synchronisedStorageModels.SyncNodeIdentityMode.Remote) {
|
|
257
|
+
condition.conditions.push({
|
|
258
|
+
property: "nodeIdentity",
|
|
259
|
+
value: this._nodeIdentity,
|
|
260
|
+
comparison: event.data.requestMode === synchronisedStorageModels.SyncNodeIdentityMode.Local
|
|
261
|
+
? entity.ComparisonOperator.Equals
|
|
262
|
+
: entity.ComparisonOperator.NotEquals
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const result = await this._entityStorageConnector.query(condition, [{ property: "dateModified", sortDirection: entity.SortDirection.Ascending }], undefined, cursor, event.data.batchSize);
|
|
266
|
+
cursor = result.cursor;
|
|
267
|
+
// Publish the batch response with the entities
|
|
268
|
+
this._eventBusComponent.publish(synchronisedStorageModels.SynchronisedStorageTopics.BatchResponse, {
|
|
269
|
+
storageKey: this._storageKey,
|
|
270
|
+
entities: result.entities,
|
|
271
|
+
lastEntry: !core.Is.stringValue(cursor)
|
|
272
|
+
});
|
|
273
|
+
} while (core.Is.stringValue(cursor));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Handle a reset event.
|
|
278
|
+
* @param event The event parameters
|
|
279
|
+
* @internal
|
|
280
|
+
*/
|
|
281
|
+
async handleReset(event) {
|
|
282
|
+
// Only reset the storage if it matches the storage key
|
|
283
|
+
if (event.data.storageKey === this._storageKey) {
|
|
284
|
+
let cursor;
|
|
285
|
+
let toRemove = [];
|
|
286
|
+
// Build a list of the ids to remove
|
|
287
|
+
do {
|
|
288
|
+
const condition = {
|
|
289
|
+
conditions: []
|
|
290
|
+
};
|
|
291
|
+
// Depending on the reset mode we can filter the entities to remove
|
|
292
|
+
if (event.data.resetMode === synchronisedStorageModels.SyncNodeIdentityMode.Local ||
|
|
293
|
+
event.data.resetMode === synchronisedStorageModels.SyncNodeIdentityMode.Remote) {
|
|
294
|
+
condition.conditions.push({
|
|
295
|
+
property: "nodeIdentity",
|
|
296
|
+
value: this._nodeIdentity,
|
|
297
|
+
comparison: event.data.resetMode === synchronisedStorageModels.SyncNodeIdentityMode.Local
|
|
298
|
+
? entity.ComparisonOperator.Equals
|
|
299
|
+
: entity.ComparisonOperator.NotEquals
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const result = await this._entityStorageConnector.query(condition, undefined, undefined, cursor);
|
|
303
|
+
cursor = result.cursor;
|
|
304
|
+
toRemove = toRemove.concat(result.entities.map(entity => entity.id));
|
|
305
|
+
} while (core.Is.stringValue(cursor));
|
|
306
|
+
// Remove the entities
|
|
307
|
+
for (let i = 0; i < toRemove.length; i++) {
|
|
308
|
+
await this.remove(toRemove[i]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
203
312
|
}
|
|
204
313
|
|
|
205
314
|
exports.SynchronisedEntityStorageConnector = SynchronisedEntityStorageConnector;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Guards, StringHelper, Is, GeneralError, ComponentFactory } from '@twin.org/core';
|
|
2
|
-
import { EntitySchemaFactory, EntitySchemaHelper, SortDirection } from '@twin.org/entity';
|
|
2
|
+
import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, SortDirection } from '@twin.org/entity';
|
|
3
3
|
import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
|
|
4
|
-
import { SynchronisedStorageTopics, SyncChangeOperation } from '@twin.org/synchronised-storage-models';
|
|
4
|
+
import { SynchronisedStorageTopics, SyncChangeOperation, SyncNodeIdentityMode } from '@twin.org/synchronised-storage-models';
|
|
5
5
|
|
|
6
6
|
// Copyright 2024 IOTA Stiftung.
|
|
7
7
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -38,6 +38,11 @@ class SynchronisedEntityStorageConnector {
|
|
|
38
38
|
* @internal
|
|
39
39
|
*/
|
|
40
40
|
_eventBusComponent;
|
|
41
|
+
/**
|
|
42
|
+
* The node identity.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
_nodeIdentity;
|
|
41
46
|
/**
|
|
42
47
|
* Create a new instance of SynchronisedEntityStorageConnector.
|
|
43
48
|
* @param options The options for the connector.
|
|
@@ -49,13 +54,19 @@ class SynchronisedEntityStorageConnector {
|
|
|
49
54
|
this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
|
|
50
55
|
this._storageKey = options?.config?.storageKey ?? StringHelper.kebabCase(options.entitySchema);
|
|
51
56
|
this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
52
|
-
const requiredProperties = [
|
|
57
|
+
const requiredProperties = [
|
|
58
|
+
"id",
|
|
59
|
+
"nodeIdentity",
|
|
60
|
+
"dateModified"
|
|
61
|
+
];
|
|
53
62
|
for (const requiredProperty of requiredProperties) {
|
|
54
63
|
const foundProperty = this._entitySchema.properties?.find(prop => prop.property === requiredProperty);
|
|
55
64
|
if (Is.empty(foundProperty)) {
|
|
56
65
|
throw new GeneralError(this.CLASS_NAME, "missingRequiredProperty", { requiredProperty });
|
|
57
66
|
}
|
|
58
|
-
else if (Is.empty(foundProperty.
|
|
67
|
+
else if (Is.empty(foundProperty.isPrimary) &&
|
|
68
|
+
Is.empty(foundProperty.isSecondary) &&
|
|
69
|
+
Is.empty(foundProperty.sortDirection)) {
|
|
59
70
|
throw new GeneralError(this.CLASS_NAME, "missingRequiredPropertySort", {
|
|
60
71
|
requiredProperty
|
|
61
72
|
});
|
|
@@ -79,6 +90,7 @@ class SynchronisedEntityStorageConnector {
|
|
|
79
90
|
* @returns Nothing.
|
|
80
91
|
*/
|
|
81
92
|
async start(nodeIdentity, nodeLoggingConnectorType, componentState) {
|
|
93
|
+
this._nodeIdentity = nodeIdentity;
|
|
82
94
|
// Tell the synchronised storage about this storage key
|
|
83
95
|
await this._eventBusComponent.publish(SynchronisedStorageTopics.RegisterStorageKey, {
|
|
84
96
|
storageKey: this._storageKey
|
|
@@ -104,15 +116,19 @@ class SynchronisedEntityStorageConnector {
|
|
|
104
116
|
*/
|
|
105
117
|
async set(entity, conditions) {
|
|
106
118
|
Guards.object(this.CLASS_NAME, "entity", entity);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
if (Is.stringValue(this._nodeIdentity)) {
|
|
120
|
+
// Make sure the entity has the required properties
|
|
121
|
+
entity.dateModified = new Date(Date.now()).toISOString();
|
|
122
|
+
entity.nodeIdentity = this._nodeIdentity;
|
|
123
|
+
await this._entityStorageConnector.set(entity, conditions);
|
|
124
|
+
// Tell the synchronised storage about the entity changes
|
|
125
|
+
await this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemChange, {
|
|
126
|
+
storageKey: this._storageKey,
|
|
127
|
+
operation: SyncChangeOperation.Set,
|
|
128
|
+
nodeIdentity: this._nodeIdentity,
|
|
129
|
+
id: entity[this._primaryKey.property]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
116
132
|
}
|
|
117
133
|
/**
|
|
118
134
|
* Remove the entity.
|
|
@@ -122,13 +138,16 @@ class SynchronisedEntityStorageConnector {
|
|
|
122
138
|
*/
|
|
123
139
|
async remove(id, conditions) {
|
|
124
140
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
if (Is.stringValue(this._nodeIdentity)) {
|
|
142
|
+
await this._entityStorageConnector.remove(id, conditions);
|
|
143
|
+
// Tell the synchronised storage about the entity removal
|
|
144
|
+
await this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemChange, {
|
|
145
|
+
storageKey: this._storageKey,
|
|
146
|
+
operation: SyncChangeOperation.Delete,
|
|
147
|
+
nodeIdentity: this._nodeIdentity,
|
|
148
|
+
id
|
|
149
|
+
});
|
|
150
|
+
}
|
|
132
151
|
}
|
|
133
152
|
/**
|
|
134
153
|
* Find all the entities which match the conditions.
|
|
@@ -150,54 +169,144 @@ class SynchronisedEntityStorageConnector {
|
|
|
150
169
|
handleEventBusMessages() {
|
|
151
170
|
// When the synchronised storage requests an item, we need to provide it
|
|
152
171
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemRequest, async (params) => {
|
|
153
|
-
|
|
154
|
-
if (params.data.storageKey === this._storageKey) {
|
|
155
|
-
let entity;
|
|
156
|
-
try {
|
|
157
|
-
entity = await this._entityStorageConnector.get(params.data.id);
|
|
158
|
-
}
|
|
159
|
-
catch { }
|
|
160
|
-
// Publish the item response with the entity
|
|
161
|
-
this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemResponse, {
|
|
162
|
-
storageKey: this._storageKey,
|
|
163
|
-
id: params.data.id,
|
|
164
|
-
entity
|
|
165
|
-
});
|
|
166
|
-
}
|
|
172
|
+
await this.handleLocalItemRequest(params);
|
|
167
173
|
});
|
|
168
174
|
// When the synchronised storage requests a batch, we need to provide it
|
|
169
175
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.BatchRequest, async (params) => {
|
|
170
|
-
|
|
171
|
-
if (params.data.storageKey === this._storageKey) {
|
|
172
|
-
let cursor;
|
|
173
|
-
do {
|
|
174
|
-
const result = await this._entityStorageConnector.query(undefined, [{ property: "dateModified", sortDirection: SortDirection.Ascending }], undefined, cursor, params.data.batchSize);
|
|
175
|
-
cursor = result.cursor;
|
|
176
|
-
// Publish the batch response with the entities
|
|
177
|
-
this._eventBusComponent.publish(SynchronisedStorageTopics.BatchResponse, {
|
|
178
|
-
storageKey: this._storageKey,
|
|
179
|
-
primaryKey: this._primaryKey.property,
|
|
180
|
-
entities: result.entities,
|
|
181
|
-
lastEntry: !Is.stringValue(cursor)
|
|
182
|
-
});
|
|
183
|
-
} while (Is.stringValue(cursor));
|
|
184
|
-
}
|
|
176
|
+
await this.handleBatchRequest(params);
|
|
185
177
|
});
|
|
186
178
|
// Subscribe to remote item set events from the synchronised storage and update the local storage
|
|
187
179
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemSet, async (params) => {
|
|
188
|
-
|
|
189
|
-
if (params.data.storageKey === this._storageKey) {
|
|
190
|
-
await this.set(params.data.entity);
|
|
191
|
-
}
|
|
180
|
+
await this.handleRemoteItemSet(params);
|
|
192
181
|
});
|
|
193
182
|
// Subscribe to remote item remove events from the synchronised storage and update the local storage
|
|
194
183
|
this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemRemove, async (params) => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
await this.handleRemoteItemRemove(params);
|
|
185
|
+
});
|
|
186
|
+
// Subscribe to resets from the synchronised storage and update the local storage
|
|
187
|
+
this._eventBusComponent.subscribe(SynchronisedStorageTopics.Reset, async (params) => {
|
|
188
|
+
await this.handleReset(params);
|
|
199
189
|
});
|
|
200
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Handle a local item request.
|
|
193
|
+
* @param event The request parameters
|
|
194
|
+
* @internal
|
|
195
|
+
*/
|
|
196
|
+
async handleLocalItemRequest(event) {
|
|
197
|
+
// Only handle the request if it matches the storage key
|
|
198
|
+
if (event.data.storageKey === this._storageKey) {
|
|
199
|
+
let entity;
|
|
200
|
+
try {
|
|
201
|
+
entity = await this._entityStorageConnector.get(event.data.id);
|
|
202
|
+
}
|
|
203
|
+
catch { }
|
|
204
|
+
// Publish the item response with the entity
|
|
205
|
+
this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemResponse, {
|
|
206
|
+
storageKey: this._storageKey,
|
|
207
|
+
id: event.data.id,
|
|
208
|
+
entity
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Handle a remote item set event.
|
|
214
|
+
* @param event The event parameters
|
|
215
|
+
* @internal
|
|
216
|
+
*/
|
|
217
|
+
async handleRemoteItemSet(event) {
|
|
218
|
+
// Only set the item if it matches the storage key
|
|
219
|
+
// and it is from another node, remote updates can not change data for this node
|
|
220
|
+
// That must be done via the regular entity storage methods
|
|
221
|
+
if (event.data.storageKey === this._storageKey &&
|
|
222
|
+
event.data.entity.nodeIdentity !== this._nodeIdentity) {
|
|
223
|
+
await this._entityStorageConnector.set(event.data.entity);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Handle a remote item remove event.
|
|
228
|
+
* @param params The event parameters
|
|
229
|
+
* @internal
|
|
230
|
+
*/
|
|
231
|
+
async handleRemoteItemRemove(params) {
|
|
232
|
+
// Only remove the item if it matches the storage key
|
|
233
|
+
// and it is from another node, remote updates can not change data for this node
|
|
234
|
+
// That must be done via the regular entity storage methods
|
|
235
|
+
if (params.data.storageKey === this._storageKey &&
|
|
236
|
+
params.data.nodeIdentity !== this._nodeIdentity) {
|
|
237
|
+
await this._entityStorageConnector.remove(params.data.id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Handle a batch request.
|
|
242
|
+
* @param event The request parameters
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
async handleBatchRequest(event) {
|
|
246
|
+
// Only handle the request if it matches the storage key
|
|
247
|
+
if (event.data.storageKey === this._storageKey) {
|
|
248
|
+
let cursor;
|
|
249
|
+
do {
|
|
250
|
+
const condition = {
|
|
251
|
+
conditions: []
|
|
252
|
+
};
|
|
253
|
+
if (event.data.requestMode === SyncNodeIdentityMode.Local ||
|
|
254
|
+
event.data.requestMode === SyncNodeIdentityMode.Remote) {
|
|
255
|
+
condition.conditions.push({
|
|
256
|
+
property: "nodeIdentity",
|
|
257
|
+
value: this._nodeIdentity,
|
|
258
|
+
comparison: event.data.requestMode === SyncNodeIdentityMode.Local
|
|
259
|
+
? ComparisonOperator.Equals
|
|
260
|
+
: ComparisonOperator.NotEquals
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const result = await this._entityStorageConnector.query(condition, [{ property: "dateModified", sortDirection: SortDirection.Ascending }], undefined, cursor, event.data.batchSize);
|
|
264
|
+
cursor = result.cursor;
|
|
265
|
+
// Publish the batch response with the entities
|
|
266
|
+
this._eventBusComponent.publish(SynchronisedStorageTopics.BatchResponse, {
|
|
267
|
+
storageKey: this._storageKey,
|
|
268
|
+
entities: result.entities,
|
|
269
|
+
lastEntry: !Is.stringValue(cursor)
|
|
270
|
+
});
|
|
271
|
+
} while (Is.stringValue(cursor));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Handle a reset event.
|
|
276
|
+
* @param event The event parameters
|
|
277
|
+
* @internal
|
|
278
|
+
*/
|
|
279
|
+
async handleReset(event) {
|
|
280
|
+
// Only reset the storage if it matches the storage key
|
|
281
|
+
if (event.data.storageKey === this._storageKey) {
|
|
282
|
+
let cursor;
|
|
283
|
+
let toRemove = [];
|
|
284
|
+
// Build a list of the ids to remove
|
|
285
|
+
do {
|
|
286
|
+
const condition = {
|
|
287
|
+
conditions: []
|
|
288
|
+
};
|
|
289
|
+
// Depending on the reset mode we can filter the entities to remove
|
|
290
|
+
if (event.data.resetMode === SyncNodeIdentityMode.Local ||
|
|
291
|
+
event.data.resetMode === SyncNodeIdentityMode.Remote) {
|
|
292
|
+
condition.conditions.push({
|
|
293
|
+
property: "nodeIdentity",
|
|
294
|
+
value: this._nodeIdentity,
|
|
295
|
+
comparison: event.data.resetMode === SyncNodeIdentityMode.Local
|
|
296
|
+
? ComparisonOperator.Equals
|
|
297
|
+
: ComparisonOperator.NotEquals
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
const result = await this._entityStorageConnector.query(condition, undefined, undefined, cursor);
|
|
301
|
+
cursor = result.cursor;
|
|
302
|
+
toRemove = toRemove.concat(result.entities.map(entity => entity.id));
|
|
303
|
+
} while (Is.stringValue(cursor));
|
|
304
|
+
// Remove the entities
|
|
305
|
+
for (let i = 0; i < toRemove.length; i++) {
|
|
306
|
+
await this.remove(toRemove[i]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
201
310
|
}
|
|
202
311
|
|
|
203
312
|
export { SynchronisedEntityStorageConnector };
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.2-next.5](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-synchronised-v0.0.2-next.4...entity-storage-connector-synchronised-v0.0.2-next.5) (2025-08-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* support new synchronised storage features ([0d9ebab](https://github.com/twinfoundation/entity-storage/commit/0d9ebab237040892cab8e7db751aa2e40c0c76e0))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/entity-storage-models bumped from 0.0.2-next.4 to 0.0.2-next.5
|
|
16
|
+
* devDependencies
|
|
17
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.2-next.4 to 0.0.2-next.5
|
|
18
|
+
|
|
19
|
+
## [0.0.2-next.4](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-synchronised-v0.0.2-next.3...entity-storage-connector-synchronised-v0.0.2-next.4) (2025-08-08)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* support new synchronised storage features ([a43ee88](https://github.com/twinfoundation/entity-storage/commit/a43ee88431ee3eb8e59e4aeb176009d63ea09d05))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Dependencies
|
|
28
|
+
|
|
29
|
+
* The following workspace dependencies were updated
|
|
30
|
+
* dependencies
|
|
31
|
+
* @twin.org/entity-storage-models bumped from 0.0.2-next.3 to 0.0.2-next.4
|
|
32
|
+
* devDependencies
|
|
33
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.2-next.3 to 0.0.2-next.4
|
|
34
|
+
|
|
3
35
|
## [0.0.2-next.3](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-synchronised-v0.0.2-next.2...entity-storage-connector-synchronised-v0.0.2-next.3) (2025-07-25)
|
|
4
36
|
|
|
5
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity-storage-connector-synchronised",
|
|
3
|
-
"version": "0.0.2-next.
|
|
3
|
+
"version": "0.0.2-next.5",
|
|
4
4
|
"description": "Entity Storage connector implementation using synchronised storage",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@twin.org/core": "next",
|
|
18
18
|
"@twin.org/entity": "next",
|
|
19
|
-
"@twin.org/entity-storage-models": "0.0.2-next.
|
|
19
|
+
"@twin.org/entity-storage-models": "0.0.2-next.5",
|
|
20
20
|
"@twin.org/event-bus-models": "next",
|
|
21
21
|
"@twin.org/logging-models": "next",
|
|
22
22
|
"@twin.org/nameof": "next",
|