@twin.org/entity-storage-connector-synchronised 0.0.2-next.9 → 0.0.3-next.10

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # TWIN Entity Storage Connector Synchronised
1
+ # Entity Storage Connector Synchronised
2
2
 
3
- Entity Storage connector implementation using synchronised storage.
3
+ This package integrates synchronised storage with an entity storage connector so local writes can be published as sync events and remote updates can be applied to local persistence.
4
4
 
5
5
  ## Installation
6
6
 
@@ -0,0 +1,6 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./synchronisedEntityStorageConnector.js";
4
+ export * from "./models/ISynchronisedEntityStorageConnectorConfig.js";
5
+ export * from "./models/ISynchronisedEntityStorageConnectorConstructorOptions.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,yCAAyC,CAAC;AACxD,cAAc,uDAAuD,CAAC;AACtE,cAAc,mEAAmE,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./synchronisedEntityStorageConnector.js\";\nexport * from \"./models/ISynchronisedEntityStorageConnectorConfig.js\";\nexport * from \"./models/ISynchronisedEntityStorageConnectorConstructorOptions.js\";\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=ISynchronisedEntityStorageConnectorConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISynchronisedEntityStorageConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/ISynchronisedEntityStorageConnectorConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the Synchronised Entity Storage Connector.\n */\nexport interface ISynchronisedEntityStorageConnectorConfig {\n\t/**\n\t * The storage key for the synchronised entity storage connector.\n\t * Will default to kebab cased entity schema name.\n\t */\n\tstorageKey?: string;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ISynchronisedEntityStorageConnectorConstructorOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ISynchronisedEntityStorageConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/ISynchronisedEntityStorageConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ISynchronisedEntityStorageConnectorConfig } from \"./ISynchronisedEntityStorageConnectorConfig.js\";\n\n/**\n * Options for the Synchronised Entity Storage Connector constructor.\n */\nexport interface ISynchronisedEntityStorageConnectorConstructorOptions {\n\t/**\n\t * The name of the entity schema.\n\t */\n\tentitySchema: string;\n\n\t/**\n\t * The entity storage connector type to use for actual data.\n\t */\n\tentityStorageConnectorType: string;\n\n\t/**\n\t * The event bus component type.\n\t * @default event-bus\n\t */\n\teventBusComponentType?: string;\n\n\t/**\n\t * The configuration for the connector.\n\t */\n\tconfig?: ISynchronisedEntityStorageConnectorConfig;\n}\n"]}
@@ -1,18 +1,33 @@
1
- import { Guards, StringHelper, Is, GeneralError, ComponentFactory } from '@twin.org/core';
2
- import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, SortDirection } from '@twin.org/entity';
3
- import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
4
- import { SynchronisedStorageTopics, SyncChangeOperation, SyncNodeIdentityMode } from '@twin.org/synchronised-storage-models';
5
-
6
1
  // Copyright 2024 IOTA Stiftung.
7
2
  // SPDX-License-Identifier: Apache-2.0.
3
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
4
+ import { ComponentFactory, GeneralError, Guards, Is, StringHelper } from "@twin.org/core";
5
+ import { ComparisonOperator, EntitySchemaFactory, EntitySchemaHelper, SortDirection } from "@twin.org/entity";
6
+ import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
7
+ import { SyncChangeOperation, SynchronisedStorageTopics, SyncNodeIdMode } from "@twin.org/synchronised-storage-models";
8
8
  /**
9
9
  * Class for performing entity storage operations in synchronised storage.
10
10
  */
11
- class SynchronisedEntityStorageConnector {
11
+ export class SynchronisedEntityStorageConnector {
12
12
  /**
13
13
  * Runtime name for the class.
14
14
  */
15
- CLASS_NAME = "SynchronisedEntityStorageConnector";
15
+ static CLASS_NAME = "SynchronisedEntityStorageConnector";
16
+ /**
17
+ * Fixed name for node id properties.
18
+ * @internal
19
+ */
20
+ static _PROP_NAME_NODE_IDENTITY = "nodeIdentity";
21
+ /**
22
+ * Fixed name for date modified properties.
23
+ * @internal
24
+ */
25
+ static _PROP_NAME_DATE_MODIFIED = "dateModified";
26
+ /**
27
+ * Fixed name for id properties.
28
+ * @internal
29
+ */
30
+ static _PROP_NAME_ID = "id";
16
31
  /**
17
32
  * The schema for the entity.
18
33
  * @internal
@@ -42,32 +57,32 @@ class SynchronisedEntityStorageConnector {
42
57
  * The node identity.
43
58
  * @internal
44
59
  */
45
- _nodeIdentity;
60
+ _nodeId;
46
61
  /**
47
62
  * Create a new instance of SynchronisedEntityStorageConnector.
48
63
  * @param options The options for the connector.
49
64
  */
50
65
  constructor(options) {
51
- Guards.object(this.CLASS_NAME, "options", options);
52
- Guards.stringValue(this.CLASS_NAME, "options.entitySchema", options.entitySchema);
53
- Guards.stringValue(this.CLASS_NAME, "options.entityStorageConnectorType", options.entityStorageConnectorType);
66
+ Guards.object(SynchronisedEntityStorageConnector.CLASS_NAME, "options", options);
67
+ Guards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
68
+ Guards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, "options.entityStorageConnectorType", options.entityStorageConnectorType);
54
69
  this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
55
70
  this._storageKey = options?.config?.storageKey ?? StringHelper.kebabCase(options.entitySchema);
56
71
  this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
57
72
  const requiredProperties = [
58
- "id",
59
- "nodeIdentity",
60
- "dateModified"
73
+ SynchronisedEntityStorageConnector._PROP_NAME_ID,
74
+ SynchronisedEntityStorageConnector._PROP_NAME_NODE_IDENTITY,
75
+ SynchronisedEntityStorageConnector._PROP_NAME_DATE_MODIFIED
61
76
  ];
62
77
  for (const requiredProperty of requiredProperties) {
63
78
  const foundProperty = this._entitySchema.properties?.find(prop => prop.property === requiredProperty);
64
79
  if (Is.empty(foundProperty)) {
65
- throw new GeneralError(this.CLASS_NAME, "missingRequiredProperty", { requiredProperty });
80
+ throw new GeneralError(SynchronisedEntityStorageConnector.CLASS_NAME, "missingRequiredProperty", { requiredProperty });
66
81
  }
67
82
  else if (Is.empty(foundProperty.isPrimary) &&
68
83
  Is.empty(foundProperty.isSecondary) &&
69
84
  Is.empty(foundProperty.sortDirection)) {
70
- throw new GeneralError(this.CLASS_NAME, "missingRequiredPropertySort", {
85
+ throw new GeneralError(SynchronisedEntityStorageConnector.CLASS_NAME, "missingRequiredPropertySort", {
71
86
  requiredProperty
72
87
  });
73
88
  }
@@ -75,6 +90,13 @@ class SynchronisedEntityStorageConnector {
75
90
  this._entityStorageConnector = EntityStorageConnectorFactory.get(options.entityStorageConnectorType);
76
91
  this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? "event-bus");
77
92
  }
93
+ /**
94
+ * Returns the class name of the component.
95
+ * @returns The class name of the component.
96
+ */
97
+ className() {
98
+ return SynchronisedEntityStorageConnector.CLASS_NAME;
99
+ }
78
100
  /**
79
101
  * Get the schema for the entities.
80
102
  * @returns The schema for the entities.
@@ -84,17 +106,18 @@ class SynchronisedEntityStorageConnector {
84
106
  }
85
107
  /**
86
108
  * The component needs to be started when the node is initialized.
87
- * @param nodeIdentity The identity of the node starting the component.
88
109
  * @param nodeLoggingComponentType The node logging component type.
89
110
  * @returns Nothing.
90
111
  */
91
- async start(nodeIdentity, nodeLoggingComponentType) {
92
- this._nodeIdentity = nodeIdentity;
112
+ async start(nodeLoggingComponentType) {
113
+ const contextIds = await ContextIdStore.getContextIds();
114
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
115
+ this._nodeId = contextIds[ContextIdKeys.Node];
93
116
  // Tell the synchronised storage about this storage key
94
117
  await this._eventBusComponent.publish(SynchronisedStorageTopics.RegisterStorageKey, {
95
118
  storageKey: this._storageKey
96
119
  });
97
- this.handleEventBusMessages();
120
+ await this.handleEventBusMessages();
98
121
  }
99
122
  /**
100
123
  * Get an entity.
@@ -104,7 +127,7 @@ class SynchronisedEntityStorageConnector {
104
127
  * @returns The object if it can be found or undefined.
105
128
  */
106
129
  async get(id, secondaryIndex, conditions) {
107
- Guards.stringValue(this.CLASS_NAME, "id", id);
130
+ Guards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, "id", id);
108
131
  return this._entityStorageConnector.get(id, secondaryIndex, conditions);
109
132
  }
110
133
  /**
@@ -114,17 +137,17 @@ class SynchronisedEntityStorageConnector {
114
137
  * @returns The id of the entity.
115
138
  */
116
139
  async set(entity, conditions) {
117
- Guards.object(this.CLASS_NAME, "entity", entity);
118
- if (Is.stringValue(this._nodeIdentity)) {
119
- // Make sure the entity has the required properties
140
+ Guards.object(SynchronisedEntityStorageConnector.CLASS_NAME, "entity", entity);
141
+ if (Is.stringValue(this._nodeId)) {
142
+ // Make sure the entity has the required properties populated
120
143
  entity.dateModified = new Date(Date.now()).toISOString();
121
- entity.nodeIdentity = this._nodeIdentity;
144
+ entity.nodeIdentity = this._nodeId;
122
145
  await this._entityStorageConnector.set(entity, conditions);
123
146
  // Tell the synchronised storage about the entity changes
124
147
  await this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemChange, {
125
148
  storageKey: this._storageKey,
126
149
  operation: SyncChangeOperation.Set,
127
- nodeIdentity: this._nodeIdentity,
150
+ nodeId: this._nodeId,
128
151
  id: entity[this._primaryKey.property]
129
152
  });
130
153
  }
@@ -136,14 +159,14 @@ class SynchronisedEntityStorageConnector {
136
159
  * @returns Nothing.
137
160
  */
138
161
  async remove(id, conditions) {
139
- Guards.stringValue(this.CLASS_NAME, "id", id);
140
- if (Is.stringValue(this._nodeIdentity)) {
162
+ Guards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, "id", id);
163
+ if (Is.stringValue(this._nodeId)) {
141
164
  await this._entityStorageConnector.remove(id, conditions);
142
165
  // Tell the synchronised storage about the entity removal
143
166
  await this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemChange, {
144
167
  storageKey: this._storageKey,
145
168
  operation: SyncChangeOperation.Delete,
146
- nodeIdentity: this._nodeIdentity,
169
+ nodeId: this._nodeId,
147
170
  id
148
171
  });
149
172
  }
@@ -153,37 +176,38 @@ class SynchronisedEntityStorageConnector {
153
176
  * @param conditions The conditions to match for the entities.
154
177
  * @param sortProperties The optional sort order.
155
178
  * @param properties The optional properties to return, defaults to all.
156
- * @param cursor The cursor to request the next page of entities.
157
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
179
+ * @param cursor The cursor to request the next chunk of entities.
180
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
158
181
  * @returns All the entities for the storage matching the conditions,
159
182
  * and a cursor which can be used to request more entities.
160
183
  */
161
- async query(conditions, sortProperties, properties, cursor, pageSize) {
162
- return this._entityStorageConnector.query(conditions, sortProperties, properties, cursor, pageSize);
184
+ async query(conditions, sortProperties, properties, cursor, limit) {
185
+ // We deliberately skip the partition key as we want to query all partitions in synchronised storage
186
+ return this._entityStorageConnector.query(conditions, sortProperties, properties, cursor, limit);
163
187
  }
164
188
  /**
165
189
  * Handle the event bus messages.
166
190
  * @internal
167
191
  */
168
- handleEventBusMessages() {
192
+ async handleEventBusMessages() {
169
193
  // When the synchronised storage requests an item, we need to provide it
170
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemRequest, async (params) => {
194
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemRequest, async (params) => {
171
195
  await this.handleLocalItemRequest(params);
172
196
  });
173
197
  // When the synchronised storage requests a batch, we need to provide it
174
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.BatchRequest, async (params) => {
198
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.BatchRequest, async (params) => {
175
199
  await this.handleBatchRequest(params);
176
200
  });
177
201
  // Subscribe to remote item set events from the synchronised storage and update the local storage
178
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemSet, async (params) => {
202
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemSet, async (params) => {
179
203
  await this.handleRemoteItemSet(params);
180
204
  });
181
205
  // Subscribe to remote item remove events from the synchronised storage and update the local storage
182
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemRemove, async (params) => {
206
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.RemoteItemRemove, async (params) => {
183
207
  await this.handleRemoteItemRemove(params);
184
208
  });
185
209
  // Subscribe to resets from the synchronised storage and update the local storage
186
- this._eventBusComponent.subscribe(SynchronisedStorageTopics.Reset, async (params) => {
210
+ await this._eventBusComponent.subscribe(SynchronisedStorageTopics.Reset, async (params) => {
187
211
  await this.handleReset(params);
188
212
  });
189
213
  }
@@ -201,7 +225,7 @@ class SynchronisedEntityStorageConnector {
201
225
  }
202
226
  catch { }
203
227
  // Publish the item response with the entity
204
- this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemResponse, {
228
+ await this._eventBusComponent.publish(SynchronisedStorageTopics.LocalItemResponse, {
205
229
  storageKey: this._storageKey,
206
230
  id: event.data.id,
207
231
  entity
@@ -218,7 +242,7 @@ class SynchronisedEntityStorageConnector {
218
242
  // and it is from another node, remote updates can not change data for this node
219
243
  // That must be done via the regular entity storage methods
220
244
  if (event.data.storageKey === this._storageKey &&
221
- event.data.entity.nodeIdentity !== this._nodeIdentity) {
245
+ event.data.entity.nodeIdentity !== this._nodeId) {
222
246
  await this._entityStorageConnector.set(event.data.entity);
223
247
  }
224
248
  }
@@ -231,8 +255,7 @@ class SynchronisedEntityStorageConnector {
231
255
  // Only remove the item if it matches the storage key
232
256
  // and it is from another node, remote updates can not change data for this node
233
257
  // That must be done via the regular entity storage methods
234
- if (params.data.storageKey === this._storageKey &&
235
- params.data.nodeIdentity !== this._nodeIdentity) {
258
+ if (params.data.storageKey === this._storageKey && params.data.nodeId !== this._nodeId) {
236
259
  await this._entityStorageConnector.remove(params.data.id);
237
260
  }
238
261
  }
@@ -249,20 +272,25 @@ class SynchronisedEntityStorageConnector {
249
272
  const condition = {
250
273
  conditions: []
251
274
  };
252
- if (event.data.requestMode === SyncNodeIdentityMode.Local ||
253
- event.data.requestMode === SyncNodeIdentityMode.Remote) {
275
+ if (event.data.requestMode === SyncNodeIdMode.Local ||
276
+ event.data.requestMode === SyncNodeIdMode.Remote) {
254
277
  condition.conditions.push({
255
- property: "nodeIdentity",
256
- value: this._nodeIdentity,
257
- comparison: event.data.requestMode === SyncNodeIdentityMode.Local
278
+ property: SynchronisedEntityStorageConnector._PROP_NAME_NODE_IDENTITY,
279
+ value: this._nodeId,
280
+ comparison: event.data.requestMode === SyncNodeIdMode.Local
258
281
  ? ComparisonOperator.Equals
259
282
  : ComparisonOperator.NotEquals
260
283
  });
261
284
  }
262
- const result = await this._entityStorageConnector.query(condition, [{ property: "dateModified", sortDirection: SortDirection.Ascending }], undefined, cursor, event.data.batchSize);
285
+ const result = await this._entityStorageConnector.query(condition, [
286
+ {
287
+ property: SynchronisedEntityStorageConnector._PROP_NAME_DATE_MODIFIED,
288
+ sortDirection: SortDirection.Ascending
289
+ }
290
+ ], undefined, cursor, event.data.batchSize);
263
291
  cursor = result.cursor;
264
292
  // Publish the batch response with the entities
265
- this._eventBusComponent.publish(SynchronisedStorageTopics.BatchResponse, {
293
+ await this._eventBusComponent.publish(SynchronisedStorageTopics.BatchResponse, {
266
294
  storageKey: this._storageKey,
267
295
  entities: result.entities,
268
296
  lastEntry: !Is.stringValue(cursor)
@@ -286,12 +314,12 @@ class SynchronisedEntityStorageConnector {
286
314
  conditions: []
287
315
  };
288
316
  // Depending on the reset mode we can filter the entities to remove
289
- if (event.data.resetMode === SyncNodeIdentityMode.Local ||
290
- event.data.resetMode === SyncNodeIdentityMode.Remote) {
317
+ if (event.data.resetMode === SyncNodeIdMode.Local ||
318
+ event.data.resetMode === SyncNodeIdMode.Remote) {
291
319
  condition.conditions.push({
292
320
  property: "nodeIdentity",
293
- value: this._nodeIdentity,
294
- comparison: event.data.resetMode === SyncNodeIdentityMode.Local
321
+ value: this._nodeId,
322
+ comparison: event.data.resetMode === SyncNodeIdMode.Local
295
323
  ? ComparisonOperator.Equals
296
324
  : ComparisonOperator.NotEquals
297
325
  });
@@ -307,5 +335,4 @@ class SynchronisedEntityStorageConnector {
307
335
  }
308
336
  }
309
337
  }
310
-
311
- export { SynchronisedEntityStorageConnector };
338
+ //# sourceMappingURL=synchronisedEntityStorageConnector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synchronisedEntityStorageConnector.js","sourceRoot":"","sources":["../../src/synchronisedEntityStorageConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport { ComponentFactory, GeneralError, Guards, Is, StringHelper } from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\ttype EntityCondition,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\tSortDirection\n} from \"@twin.org/entity\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport type { IEvent, IEventBusComponent } from \"@twin.org/event-bus-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\ttype ISyncBatchRequest,\n\ttype ISyncBatchResponse,\n\ttype ISynchronisedEntity,\n\ttype ISyncItemChange,\n\ttype ISyncItemRemove,\n\ttype ISyncItemRequest,\n\ttype ISyncItemResponse,\n\ttype ISyncItemSet,\n\ttype ISyncRegisterStorageKey,\n\ttype ISyncReset,\n\tSyncChangeOperation,\n\tSynchronisedStorageTopics,\n\tSyncNodeIdMode\n} from \"@twin.org/synchronised-storage-models\";\nimport type { ISynchronisedEntityStorageConnectorConstructorOptions } from \"./models/ISynchronisedEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations in synchronised storage.\n */\nexport class SynchronisedEntityStorageConnector<\n\tT extends ISynchronisedEntity = ISynchronisedEntity\n> implements IEntityStorageConnector<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SynchronisedEntityStorageConnector>();\n\n\t/**\n\t * Fixed name for node id properties.\n\t * @internal\n\t */\n\tprivate static readonly _PROP_NAME_NODE_IDENTITY = \"nodeIdentity\";\n\n\t/**\n\t * Fixed name for date modified properties.\n\t * @internal\n\t */\n\tprivate static readonly _PROP_NAME_DATE_MODIFIED = \"dateModified\";\n\n\t/**\n\t * Fixed name for id properties.\n\t * @internal\n\t */\n\tprivate static readonly _PROP_NAME_ID = \"id\";\n\n\t/**\n\t * The schema for the entity.\n\t * @internal\n\t */\n\tprivate readonly _entitySchema: IEntitySchema<T>;\n\n\t/**\n\t * The primary key for the entity schema.\n\t * @internal\n\t */\n\tprivate readonly _primaryKey: IEntitySchemaProperty<T>;\n\n\t/**\n\t * The storage key for the entity.\n\t * @internal\n\t */\n\tprivate readonly _storageKey: string;\n\n\t/**\n\t * The entity storage connector to use for actual data.\n\t * @internal\n\t */\n\tprivate readonly _entityStorageConnector: IEntityStorageConnector<T>;\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 node identity.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of SynchronisedEntityStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: ISynchronisedEntityStorageConnectorConstructorOptions) {\n\t\tGuards.object<ISynchronisedEntityStorageConnectorConstructorOptions>(\n\t\t\tSynchronisedEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options),\n\t\t\toptions\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tSynchronisedEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.entitySchema),\n\t\t\toptions.entitySchema\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tSynchronisedEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.entityStorageConnectorType),\n\t\t\toptions.entityStorageConnectorType\n\t\t);\n\n\t\tthis._entitySchema = EntitySchemaFactory.get(options.entitySchema);\n\t\tthis._storageKey = options?.config?.storageKey ?? StringHelper.kebabCase(options.entitySchema);\n\n\t\tthis._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);\n\n\t\tconst requiredProperties: (keyof ISynchronisedEntity)[] = [\n\t\t\tSynchronisedEntityStorageConnector._PROP_NAME_ID,\n\t\t\tSynchronisedEntityStorageConnector._PROP_NAME_NODE_IDENTITY,\n\t\t\tSynchronisedEntityStorageConnector._PROP_NAME_DATE_MODIFIED\n\t\t];\n\n\t\tfor (const requiredProperty of requiredProperties) {\n\t\t\tconst foundProperty = this._entitySchema.properties?.find(\n\t\t\t\tprop => prop.property === requiredProperty\n\t\t\t);\n\t\t\tif (Is.empty(foundProperty)) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tSynchronisedEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\"missingRequiredProperty\",\n\t\t\t\t\t{ requiredProperty }\n\t\t\t\t);\n\t\t\t} else if (\n\t\t\t\tIs.empty(foundProperty.isPrimary) &&\n\t\t\t\tIs.empty(foundProperty.isSecondary) &&\n\t\t\t\tIs.empty(foundProperty.sortDirection)\n\t\t\t) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tSynchronisedEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\"missingRequiredPropertySort\",\n\t\t\t\t\t{\n\t\t\t\t\t\trequiredProperty\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis._entityStorageConnector = EntityStorageConnectorFactory.get(\n\t\t\toptions.entityStorageConnectorType\n\t\t);\n\n\t\tthis._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? \"event-bus\");\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 SynchronisedEntityStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Get the schema for the entities.\n\t * @returns The schema for the entities.\n\t */\n\tpublic getSchema(): IEntitySchema {\n\t\treturn this._entitySchema as IEntitySchema;\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\t// Tell the synchronised storage about this storage key\n\t\tawait this._eventBusComponent.publish<ISyncRegisterStorageKey>(\n\t\t\tSynchronisedStorageTopics.RegisterStorageKey,\n\t\t\t{\n\t\t\t\tstorageKey: this._storageKey\n\t\t\t}\n\t\t);\n\n\t\tawait this.handleEventBusMessages();\n\t}\n\n\t/**\n\t * Get an entity.\n\t * @param id The id of the entity to get, or the index value if secondaryIndex is set.\n\t * @param secondaryIndex Get the item using a secondary index.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The object if it can be found or undefined.\n\t */\n\tpublic async get(\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<T | undefined> {\n\t\tGuards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\treturn this._entityStorageConnector.get(id, secondaryIndex, conditions);\n\t}\n\n\t/**\n\t * Set an entity.\n\t * @param entity The entity to set.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The id of the entity.\n\t */\n\tpublic async set(entity: T, conditions?: { property: keyof T; value: unknown }[]): Promise<void> {\n\t\tGuards.object<T>(SynchronisedEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\tif (Is.stringValue(this._nodeId)) {\n\t\t\t// Make sure the entity has the required properties populated\n\t\t\tentity.dateModified = new Date(Date.now()).toISOString();\n\t\t\tentity.nodeIdentity = this._nodeId;\n\n\t\t\tawait this._entityStorageConnector.set(entity, conditions);\n\n\t\t\t// Tell the synchronised storage about the entity changes\n\t\t\tawait this._eventBusComponent.publish<ISyncItemChange>(\n\t\t\t\tSynchronisedStorageTopics.LocalItemChange,\n\t\t\t\t{\n\t\t\t\t\tstorageKey: this._storageKey,\n\t\t\t\t\toperation: SyncChangeOperation.Set,\n\t\t\t\t\tnodeId: this._nodeId,\n\t\t\t\t\tid: entity[this._primaryKey.property] as string\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Remove the entity.\n\t * @param id The id of the entity to remove.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns Nothing.\n\t */\n\tpublic async remove(\n\t\tid: string,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): Promise<void> {\n\t\tGuards.stringValue(SynchronisedEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tif (Is.stringValue(this._nodeId)) {\n\t\t\tawait this._entityStorageConnector.remove(id, conditions);\n\n\t\t\t// Tell the synchronised storage about the entity removal\n\t\t\tawait this._eventBusComponent.publish<ISyncItemChange>(\n\t\t\t\tSynchronisedStorageTopics.LocalItemChange,\n\t\t\t\t{\n\t\t\t\t\tstorageKey: this._storageKey,\n\t\t\t\t\toperation: SyncChangeOperation.Delete,\n\t\t\t\t\tnodeId: this._nodeId,\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Find all the entities which match the conditions.\n\t * @param conditions The conditions to match for the entities.\n\t * @param sortProperties The optional sort order.\n\t * @param properties The optional properties to return, defaults to all.\n\t * @param cursor The cursor to request the next chunk of entities.\n\t * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.\n\t * @returns All the entities for the storage matching the conditions,\n\t * and a cursor which can be used to request more entities.\n\t */\n\tpublic async query(\n\t\tconditions?: EntityCondition<T>,\n\t\tsortProperties?: {\n\t\t\tproperty: keyof T;\n\t\t\tsortDirection: SortDirection;\n\t\t}[],\n\t\tproperties?: (keyof T)[],\n\t\tcursor?: string,\n\t\tlimit?: number\n\t): Promise<{\n\t\t/**\n\t\t * The entities, which can be partial if a limited keys list was provided.\n\t\t */\n\t\tentities: Partial<T>[];\n\t\t/**\n\t\t * An optional cursor, when defined can be used to call find to get more entities.\n\t\t */\n\t\tcursor?: string;\n\t}> {\n\t\t// We deliberately skip the partition key as we want to query all partitions in synchronised storage\n\t\treturn this._entityStorageConnector.query(\n\t\t\tconditions,\n\t\t\tsortProperties,\n\t\t\tproperties,\n\t\t\tcursor,\n\t\t\tlimit\n\t\t);\n\t}\n\n\t/**\n\t * Handle the event bus messages.\n\t * @internal\n\t */\n\tprivate async handleEventBusMessages(): Promise<void> {\n\t\t// When the synchronised storage requests an item, we need to provide it\n\t\tawait this._eventBusComponent.subscribe<ISyncItemRequest>(\n\t\t\tSynchronisedStorageTopics.LocalItemRequest,\n\t\t\tasync params => {\n\t\t\t\tawait this.handleLocalItemRequest(params);\n\t\t\t}\n\t\t);\n\n\t\t// When the synchronised storage requests a batch, we need to provide it\n\t\tawait this._eventBusComponent.subscribe<ISyncBatchRequest>(\n\t\t\tSynchronisedStorageTopics.BatchRequest,\n\t\t\tasync params => {\n\t\t\t\tawait this.handleBatchRequest(params);\n\t\t\t}\n\t\t);\n\n\t\t// Subscribe to remote item set events from the synchronised storage and update the local storage\n\t\tawait this._eventBusComponent.subscribe<ISyncItemSet>(\n\t\t\tSynchronisedStorageTopics.RemoteItemSet,\n\t\t\tasync params => {\n\t\t\t\tawait this.handleRemoteItemSet(params);\n\t\t\t}\n\t\t);\n\n\t\t// Subscribe to remote item remove events from the synchronised storage and update the local storage\n\t\tawait this._eventBusComponent.subscribe<ISyncItemRemove>(\n\t\t\tSynchronisedStorageTopics.RemoteItemRemove,\n\t\t\tasync params => {\n\t\t\t\tawait this.handleRemoteItemRemove(params);\n\t\t\t}\n\t\t);\n\n\t\t// Subscribe to resets from the synchronised storage and update the local storage\n\t\tawait this._eventBusComponent.subscribe<ISyncReset>(\n\t\t\tSynchronisedStorageTopics.Reset,\n\t\t\tasync params => {\n\t\t\t\tawait this.handleReset(params);\n\t\t\t}\n\t\t);\n\t}\n\n\t/**\n\t * Handle a local item request.\n\t * @param event The request parameters\n\t * @internal\n\t */\n\tprivate async handleLocalItemRequest(event: IEvent<ISyncItemRequest>): Promise<void> {\n\t\t// Only handle the request if it matches the storage key\n\t\tif (event.data.storageKey === this._storageKey) {\n\t\t\tlet entity: T | undefined;\n\t\t\ttry {\n\t\t\t\tentity = await this._entityStorageConnector.get(event.data.id);\n\t\t\t} catch {}\n\n\t\t\t// Publish the item response with the entity\n\t\t\tawait this._eventBusComponent.publish<ISyncItemResponse>(\n\t\t\t\tSynchronisedStorageTopics.LocalItemResponse,\n\t\t\t\t{\n\t\t\t\t\tstorageKey: this._storageKey,\n\t\t\t\t\tid: event.data.id,\n\t\t\t\t\tentity\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Handle a remote item set event.\n\t * @param event The event parameters\n\t * @internal\n\t */\n\tprivate async handleRemoteItemSet(event: IEvent<ISyncItemSet>): Promise<void> {\n\t\t// Only set the item if it matches the storage key\n\t\t// and it is from another node, remote updates can not change data for this node\n\t\t// That must be done via the regular entity storage methods\n\t\tif (\n\t\t\tevent.data.storageKey === this._storageKey &&\n\t\t\tevent.data.entity.nodeIdentity !== this._nodeId\n\t\t) {\n\t\t\tawait this._entityStorageConnector.set(event.data.entity as T);\n\t\t}\n\t}\n\n\t/**\n\t * Handle a remote item remove event.\n\t * @param params The event parameters\n\t * @internal\n\t */\n\tprivate async handleRemoteItemRemove(params: IEvent<ISyncItemRemove>): Promise<void> {\n\t\t// Only remove the item if it matches the storage key\n\t\t// and it is from another node, remote updates can not change data for this node\n\t\t// That must be done via the regular entity storage methods\n\t\tif (params.data.storageKey === this._storageKey && params.data.nodeId !== this._nodeId) {\n\t\t\tawait this._entityStorageConnector.remove(params.data.id);\n\t\t}\n\t}\n\n\t/**\n\t * Handle a batch request.\n\t * @param event The request parameters\n\t * @internal\n\t */\n\tprivate async handleBatchRequest(event: IEvent<ISyncBatchRequest>): Promise<void> {\n\t\t// Only handle the request if it matches the storage key\n\t\tif (event.data.storageKey === this._storageKey) {\n\t\t\tlet cursor;\n\t\t\tdo {\n\t\t\t\tconst condition: EntityCondition<T> = {\n\t\t\t\t\tconditions: []\n\t\t\t\t};\n\t\t\t\tif (\n\t\t\t\t\tevent.data.requestMode === SyncNodeIdMode.Local ||\n\t\t\t\t\tevent.data.requestMode === SyncNodeIdMode.Remote\n\t\t\t\t) {\n\t\t\t\t\tcondition.conditions.push({\n\t\t\t\t\t\tproperty: SynchronisedEntityStorageConnector._PROP_NAME_NODE_IDENTITY,\n\t\t\t\t\t\tvalue: this._nodeId,\n\t\t\t\t\t\tcomparison:\n\t\t\t\t\t\t\tevent.data.requestMode === SyncNodeIdMode.Local\n\t\t\t\t\t\t\t\t? ComparisonOperator.Equals\n\t\t\t\t\t\t\t\t: ComparisonOperator.NotEquals\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst result = await this._entityStorageConnector.query(\n\t\t\t\t\tcondition,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tproperty: SynchronisedEntityStorageConnector._PROP_NAME_DATE_MODIFIED,\n\t\t\t\t\t\t\tsortDirection: SortDirection.Ascending\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tundefined,\n\t\t\t\t\tcursor,\n\t\t\t\t\tevent.data.batchSize\n\t\t\t\t);\n\n\t\t\t\tcursor = result.cursor;\n\n\t\t\t\t// Publish the batch response with the entities\n\t\t\t\tawait this._eventBusComponent.publish<ISyncBatchResponse>(\n\t\t\t\t\tSynchronisedStorageTopics.BatchResponse,\n\t\t\t\t\t{\n\t\t\t\t\t\tstorageKey: this._storageKey,\n\t\t\t\t\t\tentities: result.entities as T[],\n\t\t\t\t\t\tlastEntry: !Is.stringValue(cursor)\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} while (Is.stringValue(cursor));\n\t\t}\n\t}\n\n\t/**\n\t * Handle a reset event.\n\t * @param event The event parameters\n\t * @internal\n\t */\n\tprivate async handleReset(event: IEvent<ISyncReset>): Promise<void> {\n\t\t// Only reset the storage if it matches the storage key\n\t\tif (event.data.storageKey === this._storageKey) {\n\t\t\tlet cursor;\n\t\t\tlet toRemove: string[] = [];\n\n\t\t\t// Build a list of the ids to remove\n\t\t\tdo {\n\t\t\t\tconst condition: EntityCondition<T> = {\n\t\t\t\t\tconditions: []\n\t\t\t\t};\n\n\t\t\t\t// Depending on the reset mode we can filter the entities to remove\n\t\t\t\tif (\n\t\t\t\t\tevent.data.resetMode === SyncNodeIdMode.Local ||\n\t\t\t\t\tevent.data.resetMode === SyncNodeIdMode.Remote\n\t\t\t\t) {\n\t\t\t\t\tcondition.conditions.push({\n\t\t\t\t\t\tproperty: \"nodeIdentity\",\n\t\t\t\t\t\tvalue: this._nodeId,\n\t\t\t\t\t\tcomparison:\n\t\t\t\t\t\t\tevent.data.resetMode === SyncNodeIdMode.Local\n\t\t\t\t\t\t\t\t? ComparisonOperator.Equals\n\t\t\t\t\t\t\t\t: ComparisonOperator.NotEquals\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst result = await this._entityStorageConnector.query(\n\t\t\t\t\tcondition,\n\t\t\t\t\tundefined,\n\t\t\t\t\tundefined,\n\t\t\t\t\tcursor\n\t\t\t\t);\n\n\t\t\t\tcursor = result.cursor;\n\t\t\t\ttoRemove = toRemove.concat(result.entities.map(entity => (entity as T).id));\n\t\t\t} while (Is.stringValue(cursor));\n\n\t\t\t// Remove the entities\n\t\t\tfor (let i = 0; i < toRemove.length; i++) {\n\t\t\t\tawait this.remove(toRemove[i]);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -1,3 +1,3 @@
1
- export * from "./synchronisedEntityStorageConnector";
2
- export * from "./models/ISynchronisedEntityStorageConnectorConfig";
3
- export * from "./models/ISynchronisedEntityStorageConnectorConstructorOptions";
1
+ export * from "./synchronisedEntityStorageConnector.js";
2
+ export * from "./models/ISynchronisedEntityStorageConnectorConfig.js";
3
+ export * from "./models/ISynchronisedEntityStorageConnectorConstructorOptions.js";
@@ -1,4 +1,4 @@
1
- import type { ISynchronisedEntityStorageConnectorConfig } from "./ISynchronisedEntityStorageConnectorConfig";
1
+ import type { ISynchronisedEntityStorageConnectorConfig } from "./ISynchronisedEntityStorageConnectorConfig.js";
2
2
  /**
3
3
  * Options for the Synchronised Entity Storage Connector constructor.
4
4
  */
@@ -1,7 +1,7 @@
1
1
  import { type EntityCondition, type IEntitySchema, SortDirection } from "@twin.org/entity";
2
2
  import { type IEntityStorageConnector } from "@twin.org/entity-storage-models";
3
3
  import { type ISynchronisedEntity } from "@twin.org/synchronised-storage-models";
4
- import type { ISynchronisedEntityStorageConnectorConstructorOptions } from "./models/ISynchronisedEntityStorageConnectorConstructorOptions";
4
+ import type { ISynchronisedEntityStorageConnectorConstructorOptions } from "./models/ISynchronisedEntityStorageConnectorConstructorOptions.js";
5
5
  /**
6
6
  * Class for performing entity storage operations in synchronised storage.
7
7
  */
@@ -9,12 +9,17 @@ export declare class SynchronisedEntityStorageConnector<T extends ISynchronisedE
9
9
  /**
10
10
  * Runtime name for the class.
11
11
  */
12
- readonly CLASS_NAME: string;
12
+ static readonly CLASS_NAME: string;
13
13
  /**
14
14
  * Create a new instance of SynchronisedEntityStorageConnector.
15
15
  * @param options The options for the connector.
16
16
  */
17
17
  constructor(options: ISynchronisedEntityStorageConnectorConstructorOptions);
18
+ /**
19
+ * Returns the class name of the component.
20
+ * @returns The class name of the component.
21
+ */
22
+ className(): string;
18
23
  /**
19
24
  * Get the schema for the entities.
20
25
  * @returns The schema for the entities.
@@ -22,11 +27,10 @@ export declare class SynchronisedEntityStorageConnector<T extends ISynchronisedE
22
27
  getSchema(): IEntitySchema;
23
28
  /**
24
29
  * The component needs to be started when the node is initialized.
25
- * @param nodeIdentity The identity of the node starting the component.
26
30
  * @param nodeLoggingComponentType The node logging component type.
27
31
  * @returns Nothing.
28
32
  */
29
- start(nodeIdentity: string, nodeLoggingComponentType: string | undefined): Promise<void>;
33
+ start(nodeLoggingComponentType?: string): Promise<void>;
30
34
  /**
31
35
  * Get an entity.
32
36
  * @param id The id of the entity to get, or the index value if secondaryIndex is set.
@@ -63,15 +67,15 @@ export declare class SynchronisedEntityStorageConnector<T extends ISynchronisedE
63
67
  * @param conditions The conditions to match for the entities.
64
68
  * @param sortProperties The optional sort order.
65
69
  * @param properties The optional properties to return, defaults to all.
66
- * @param cursor The cursor to request the next page of entities.
67
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
70
+ * @param cursor The cursor to request the next chunk of entities.
71
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
68
72
  * @returns All the entities for the storage matching the conditions,
69
73
  * and a cursor which can be used to request more entities.
70
74
  */
71
75
  query(conditions?: EntityCondition<T>, sortProperties?: {
72
76
  property: keyof T;
73
77
  sortDirection: SortDirection;
74
- }[], properties?: (keyof T)[], cursor?: string, pageSize?: number): Promise<{
78
+ }[], properties?: (keyof T)[], cursor?: string, limit?: number): Promise<{
75
79
  /**
76
80
  * The entities, which can be partial if a limited keys list was provided.
77
81
  */
package/docs/changelog.md CHANGED
@@ -1,5 +1,161 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.3-next.10](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.9...entity-storage-connector-synchronised-v0.0.3-next.10) (2026-03-20)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.9 to 0.0.3-next.10
16
+
17
+ ## [0.0.3-next.9](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.8...entity-storage-connector-synchronised-v0.0.3-next.9) (2026-03-04)
18
+
19
+
20
+ ### Miscellaneous Chores
21
+
22
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.8 to 0.0.3-next.9
30
+
31
+ ## [0.0.3-next.8](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.7...entity-storage-connector-synchronised-v0.0.3-next.8) (2026-02-25)
32
+
33
+
34
+ ### Miscellaneous Chores
35
+
36
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
37
+
38
+
39
+ ### Dependencies
40
+
41
+ * The following workspace dependencies were updated
42
+ * dependencies
43
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.7 to 0.0.3-next.8
44
+
45
+ ## [0.0.3-next.7](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.6...entity-storage-connector-synchronised-v0.0.3-next.7) (2026-01-28)
46
+
47
+
48
+ ### Features
49
+
50
+ * json ld contexts ([#36](https://github.com/twinfoundation/synchronised-storage/issues/36)) ([3a87a9f](https://github.com/twinfoundation/synchronised-storage/commit/3a87a9fb16d21baf672e44b4e8914cf1937b1d6a))
51
+
52
+
53
+ ### Dependencies
54
+
55
+ * The following workspace dependencies were updated
56
+ * dependencies
57
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.6 to 0.0.3-next.7
58
+
59
+ ## [0.0.3-next.6](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.5...entity-storage-connector-synchronised-v0.0.3-next.6) (2026-01-22)
60
+
61
+
62
+ ### Miscellaneous Chores
63
+
64
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
65
+
66
+
67
+ ### Dependencies
68
+
69
+ * The following workspace dependencies were updated
70
+ * dependencies
71
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.5 to 0.0.3-next.6
72
+
73
+ ## [0.0.3-next.5](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.4...entity-storage-connector-synchronised-v0.0.3-next.5) (2026-01-19)
74
+
75
+
76
+ ### Miscellaneous Chores
77
+
78
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
79
+
80
+
81
+ ### Dependencies
82
+
83
+ * The following workspace dependencies were updated
84
+ * dependencies
85
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.4 to 0.0.3-next.5
86
+
87
+ ## [0.0.3-next.4](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.3...entity-storage-connector-synchronised-v0.0.3-next.4) (2026-01-15)
88
+
89
+
90
+ ### Miscellaneous Chores
91
+
92
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
93
+
94
+
95
+ ### Dependencies
96
+
97
+ * The following workspace dependencies were updated
98
+ * dependencies
99
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.3 to 0.0.3-next.4
100
+
101
+ ## [0.0.3-next.3](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.2...entity-storage-connector-synchronised-v0.0.3-next.3) (2026-01-12)
102
+
103
+
104
+ ### Miscellaneous Chores
105
+
106
+ * **entity-storage-connector-synchronised:** Synchronize repo versions
107
+
108
+
109
+ ### Dependencies
110
+
111
+ * The following workspace dependencies were updated
112
+ * dependencies
113
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.2 to 0.0.3-next.3
114
+
115
+ ## [0.0.3-next.2](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.1...entity-storage-connector-synchronised-v0.0.3-next.2) (2025-12-04)
116
+
117
+
118
+ ### Bug Fixes
119
+
120
+ * package.json ([9d3c7d6](https://github.com/twinfoundation/synchronised-storage/commit/9d3c7d65d01b4100f7831b5f057b96fba8211815))
121
+
122
+
123
+ ### Dependencies
124
+
125
+ * The following workspace dependencies were updated
126
+ * dependencies
127
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.1 to 0.0.3-next.2
128
+
129
+ ## [0.0.3-next.1](https://github.com/twinfoundation/synchronised-storage/compare/entity-storage-connector-synchronised-v0.0.3-next.0...entity-storage-connector-synchronised-v0.0.3-next.1) (2025-11-12)
130
+
131
+
132
+ ### Features
133
+
134
+ * add context id features ([#24](https://github.com/twinfoundation/synchronised-storage/issues/24)) ([5266b18](https://github.com/twinfoundation/synchronised-storage/commit/5266b18088317c7dc274a209a79102a6fc88a8e4))
135
+
136
+
137
+ ### Dependencies
138
+
139
+ * The following workspace dependencies were updated
140
+ * dependencies
141
+ * @twin.org/synchronised-storage-models bumped from 0.0.3-next.0 to 0.0.3-next.1
142
+
143
+ ## [0.0.2-next.10](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-synchronised-v0.0.2-next.9...entity-storage-connector-synchronised-v0.0.2-next.10) (2025-10-09)
144
+
145
+
146
+ ### Features
147
+
148
+ * add validate-locales ([e66ef0d](https://github.com/twinfoundation/entity-storage/commit/e66ef0de26ca2f82b3fe89bb5c7a15a0978a9644))
149
+
150
+
151
+ ### Dependencies
152
+
153
+ * The following workspace dependencies were updated
154
+ * dependencies
155
+ * @twin.org/entity-storage-models bumped from 0.0.2-next.9 to 0.0.2-next.10
156
+ * devDependencies
157
+ * @twin.org/entity-storage-connector-memory bumped from 0.0.2-next.9 to 0.0.2-next.10
158
+
3
159
  ## [0.0.2-next.9](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-synchronised-v0.0.2-next.8...entity-storage-connector-synchronised-v0.0.2-next.9) (2025-10-02)
4
160
 
5
161
 
@@ -122,4 +278,4 @@
122
278
  * devDependencies
123
279
  * @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.29 to 0.0.2-next.3
124
280
 
125
- ## @twin.org/entity-storage-connector-synchronised - Changelog
281
+ ## Changelog