@twin.org/entity-storage-connector-memory 0.0.3-next.3 → 0.0.3-next.31

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 Memory
1
+ # Entity Storage Connector Memory
2
2
 
3
- Entity Storage connector implementation using in-memory storage.
3
+ This package provides an in-memory backend suited to local development, automated testing and short-lived workloads. It is designed to work with the wider storage ecosystem so applications can keep behaviour consistent across connectors and environments.
4
4
 
5
5
  ## Installation
6
6
 
package/dist/es/index.js CHANGED
@@ -1,5 +1,6 @@
1
- // Copyright 2024 IOTA Stiftung.
1
+ // Copyright 2026 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  export * from "./memoryEntityStorageConnector.js";
4
+ export * from "./models/IMemoryEntityStorageConnectorConfig.js";
4
5
  export * from "./models/IMemoryEntityStorageConnectorConstructorOptions.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,mCAAmC,CAAC;AAClD,cAAc,6DAA6D,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./memoryEntityStorageConnector.js\";\nexport * from \"./models/IMemoryEntityStorageConnectorConstructorOptions.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,mCAAmC,CAAC;AAClD,cAAc,iDAAiD,CAAC;AAChE,cAAc,6DAA6D,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./memoryEntityStorageConnector.js\";\nexport * from \"./models/IMemoryEntityStorageConnectorConfig.js\";\nexport * from \"./models/IMemoryEntityStorageConnectorConstructorOptions.js\";\n"]}
@@ -1,10 +1,19 @@
1
1
  // Copyright 2024 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
4
- import { Coerce, Guards, Is, ObjectHelper } from "@twin.org/core";
4
+ import { Coerce, ComponentFactory, Guards, HealthStatus, Is, Mutex, ObjectHelper, SharedObjectBuffer, Validation } from "@twin.org/core";
5
5
  import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, EntitySorter, LogicalOperator } from "@twin.org/entity";
6
+ import { EntityStorageHelper } from "@twin.org/entity-storage-models";
6
7
  /**
7
- * Class for performing entity storage operations in-memory.
8
+ * Class for performing entity storage operations in-memory backed by a shared object buffer.
9
+ *
10
+ * All reads and writes are serialised with a per-schema lock so that concurrent async
11
+ * access, including across worker threads, never produces torn or lost updates.
12
+ *
13
+ * All connector instances that share the same entity schema name share the same underlying
14
+ * buffer, making data written in one instance immediately visible in another, including
15
+ * across worker threads when the main thread forwards worker messages to the lock and
16
+ * buffer handlers.
8
17
  */
9
18
  export class MemoryEntityStorageConnector {
10
19
  /**
@@ -37,10 +46,20 @@ export class MemoryEntityStorageConnector {
37
46
  */
38
47
  _primaryKey;
39
48
  /**
40
- * The storage for the in-memory items.
49
+ * The resolved storage key used as the shared buffer and lock key.
41
50
  * @internal
42
51
  */
43
- _store;
52
+ _storageKey;
53
+ /**
54
+ * Initial capacity hint in bytes for the shared entity buffer.
55
+ * @internal
56
+ */
57
+ _initialCapacityBytes;
58
+ /**
59
+ * Maximum capacity in bytes for the shared entity buffer.
60
+ * @internal
61
+ */
62
+ _maxCapacityBytes;
44
63
  /**
45
64
  * Create a new instance of MemoryEntityStorageConnector.
46
65
  * @param options The options for the connector.
@@ -48,10 +67,14 @@ export class MemoryEntityStorageConnector {
48
67
  constructor(options) {
49
68
  Guards.object(MemoryEntityStorageConnector.CLASS_NAME, "options", options);
50
69
  Guards.stringValue(MemoryEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
70
+ Guards.object(MemoryEntityStorageConnector.CLASS_NAME, "options.config", options.config);
71
+ Guards.stringValue(MemoryEntityStorageConnector.CLASS_NAME, "options.config.storageKey", options.config.storageKey);
51
72
  this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
73
+ this._storageKey = options.config.storageKey;
52
74
  this._partitionContextIds = options.partitionContextIds;
53
75
  this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
54
- this._store = [];
76
+ this._initialCapacityBytes = options.config?.initialCapacityBytes;
77
+ this._maxCapacityBytes = options.config?.maxCapacityBytes;
55
78
  }
56
79
  /**
57
80
  * Returns the class name of the component.
@@ -60,6 +83,20 @@ export class MemoryEntityStorageConnector {
60
83
  className() {
61
84
  return MemoryEntityStorageConnector.CLASS_NAME;
62
85
  }
86
+ /**
87
+ * Returns the health status of the component.
88
+ * @returns The health status of the component.
89
+ */
90
+ async health() {
91
+ return [
92
+ {
93
+ source: MemoryEntityStorageConnector.CLASS_NAME,
94
+ status: HealthStatus.Ok,
95
+ description: "healthDescription",
96
+ data: { entityType: this._storageKey }
97
+ }
98
+ ];
99
+ }
63
100
  /**
64
101
  * Get the schema for the entities.
65
102
  * @returns The schema for the entities.
@@ -67,6 +104,18 @@ export class MemoryEntityStorageConnector {
67
104
  getSchema() {
68
105
  return this._entitySchema;
69
106
  }
107
+ /**
108
+ * Bootstrap the component by creating and initializing any resources it needs.
109
+ * @param nodeLoggingComponentType The node logging component type.
110
+ * @returns True if the bootstrapping process was successful.
111
+ */
112
+ async bootstrap(nodeLoggingComponentType) {
113
+ await SharedObjectBuffer.create(this._storageKey, {
114
+ initialCapacityBytes: this._initialCapacityBytes,
115
+ maxCapacityBytes: this._maxCapacityBytes
116
+ });
117
+ return true;
118
+ }
70
119
  /**
71
120
  * Get an entity.
72
121
  * @param id The id of the entity to get, or the index value if secondaryIndex is set.
@@ -85,40 +134,84 @@ export class MemoryEntityStorageConnector {
85
134
  value: partitionKey
86
135
  });
87
136
  }
88
- const index = this.findItem(id, secondaryIndex, finalConditions);
89
- const item = index >= 0 ? ObjectHelper.clone(this._store[index]) : undefined;
90
- if (Is.objectValue(item)) {
91
- ObjectHelper.propertyDelete(item, MemoryEntityStorageConnector._PARTITION_KEY);
92
- }
93
- return item;
137
+ return this.withLock(entities => {
138
+ const index = this.findItem(entities, id, secondaryIndex, finalConditions);
139
+ const item = index >= 0 ? entities[index] : undefined;
140
+ if (Is.objectValue(item)) {
141
+ return {
142
+ result: EntityStorageHelper.unPrepareEntity(item, [
143
+ MemoryEntityStorageConnector._PARTITION_KEY
144
+ ])
145
+ };
146
+ }
147
+ return { result: undefined };
148
+ });
94
149
  }
95
150
  /**
96
151
  * Set an entity.
97
152
  * @param entity The entity to set.
98
153
  * @param conditions The optional conditions to match for the entities.
99
- * @returns The id of the entity.
154
+ * @returns Resolves when the entity has been stored.
100
155
  */
101
156
  async set(entity, conditions) {
102
157
  Guards.object(MemoryEntityStorageConnector.CLASS_NAME, "entity", entity);
103
158
  const contextIds = await ContextIdStore.getContextIds();
104
159
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
105
- EntitySchemaHelper.validateEntity(entity, this.getSchema());
106
160
  const finalConditions = conditions ?? [];
107
- const finalEntity = ObjectHelper.clone(entity);
161
+ const prepared = EntityStorageHelper.prepareEntity(entity, this._entitySchema, Is.stringValue(partitionKey)
162
+ ? [{ property: MemoryEntityStorageConnector._PARTITION_KEY, value: partitionKey }]
163
+ : undefined, { nullBehavior: "omit" });
108
164
  if (Is.stringValue(partitionKey)) {
109
165
  finalConditions.push({
110
166
  property: MemoryEntityStorageConnector._PARTITION_KEY,
111
167
  value: partitionKey
112
168
  });
113
- ObjectHelper.propertySet(finalEntity, MemoryEntityStorageConnector._PARTITION_KEY, partitionKey);
114
- }
115
- const existingIndex = this.findItem(finalEntity[this._primaryKey.property], undefined, finalConditions);
116
- if (existingIndex >= 0) {
117
- this._store[existingIndex] = finalEntity;
118
- }
119
- else {
120
- this._store.push(finalEntity);
121
169
  }
170
+ return this.withLock(entities => {
171
+ const existingIndex = this.findItem(entities, prepared[this._primaryKey.property], undefined, finalConditions);
172
+ if (existingIndex >= 0) {
173
+ entities[existingIndex] = prepared;
174
+ }
175
+ else {
176
+ entities.push(prepared);
177
+ }
178
+ return { updated: entities, result: undefined };
179
+ });
180
+ }
181
+ /**
182
+ * Set multiple entities in a batch.
183
+ * @param entities The entities to set.
184
+ * @returns Nothing.
185
+ */
186
+ async setBatch(entities) {
187
+ Guards.arrayValue(MemoryEntityStorageConnector.CLASS_NAME, "entities", entities);
188
+ const contextIds = await ContextIdStore.getContextIds();
189
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
190
+ const preparedItems = entities.map(entity => EntityStorageHelper.prepareEntity(entity, this._entitySchema, Is.stringValue(partitionKey)
191
+ ? [{ property: MemoryEntityStorageConnector._PARTITION_KEY, value: partitionKey }]
192
+ : undefined, { nullBehavior: "omit" }));
193
+ return this.withLock(store => {
194
+ const indexMap = new Map();
195
+ for (let i = 0; i < store.length; i++) {
196
+ const stored = store[i];
197
+ const storedPartition = ObjectHelper.propertyGet(stored, MemoryEntityStorageConnector._PARTITION_KEY);
198
+ if (!Is.stringValue(partitionKey) || storedPartition === partitionKey) {
199
+ indexMap.set(stored[this._primaryKey.property], i);
200
+ }
201
+ }
202
+ for (const prepared of preparedItems) {
203
+ const id = prepared[this._primaryKey.property];
204
+ const existingIndex = indexMap.get(id);
205
+ if (existingIndex !== undefined) {
206
+ store[existingIndex] = prepared;
207
+ }
208
+ else {
209
+ const newIndex = store.push(prepared) - 1;
210
+ indexMap.set(id, newIndex);
211
+ }
212
+ }
213
+ return { updated: store, result: undefined };
214
+ });
122
215
  }
123
216
  /**
124
217
  * Remove the entity.
@@ -137,10 +230,13 @@ export class MemoryEntityStorageConnector {
137
230
  value: partitionKey
138
231
  });
139
232
  }
140
- const index = this.findItem(id, undefined, finalConditions);
141
- if (index >= 0) {
142
- this._store.splice(index, 1);
143
- }
233
+ return this.withLock(entities => {
234
+ const index = this.findItem(entities, id, undefined, finalConditions);
235
+ if (index >= 0) {
236
+ entities.splice(index, 1);
237
+ }
238
+ return { updated: entities, result: undefined };
239
+ });
144
240
  }
145
241
  /**
146
242
  * Find all the entities which match the conditions.
@@ -155,7 +251,135 @@ export class MemoryEntityStorageConnector {
155
251
  async query(conditions, sortProperties, properties, cursor, limit) {
156
252
  const contextIds = await ContextIdStore.getContextIds();
157
253
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
158
- let allEntities = this._store.slice();
254
+ EntityStorageHelper.validateSortProperties(this._entitySchema, sortProperties);
255
+ EntityStorageHelper.validateProperties(this._entitySchema, properties);
256
+ if (!Is.empty(limit)) {
257
+ const validationFailures = [];
258
+ Validation.integer("limit", limit, validationFailures, undefined, { minValue: 1 });
259
+ Validation.asValidationError(MemoryEntityStorageConnector.CLASS_NAME, "query", validationFailures);
260
+ }
261
+ return this.withLock(store => {
262
+ let allEntities = store.slice();
263
+ const finalConditions = {
264
+ conditions: [],
265
+ logicalOperator: LogicalOperator.And
266
+ };
267
+ if (Is.stringValue(partitionKey)) {
268
+ finalConditions.conditions.push({
269
+ property: MemoryEntityStorageConnector._PARTITION_KEY,
270
+ comparison: ComparisonOperator.Equals,
271
+ value: partitionKey
272
+ });
273
+ }
274
+ if (!Is.empty(conditions)) {
275
+ finalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));
276
+ }
277
+ const resultEntities = [];
278
+ const finalLimit = limit ?? MemoryEntityStorageConnector._DEFAULT_LIMIT;
279
+ let nextCursor;
280
+ if (allEntities.length > 0) {
281
+ const finalSortKeys = EntitySchemaHelper.buildSortProperties(this._entitySchema, sortProperties);
282
+ allEntities = EntitySorter.sort(allEntities, finalSortKeys);
283
+ const startIndex = Coerce.number(cursor) ?? 0;
284
+ for (let i = startIndex; i < allEntities.length; i++) {
285
+ if (EntityConditions.check(allEntities[i], finalConditions) &&
286
+ resultEntities.length < finalLimit) {
287
+ const entity = Is.arrayValue(properties)
288
+ ? ObjectHelper.pick(allEntities[i], properties)
289
+ : allEntities[i];
290
+ resultEntities.push(EntityStorageHelper.unPrepareEntity(entity, [
291
+ MemoryEntityStorageConnector._PARTITION_KEY
292
+ ]));
293
+ if (resultEntities.length >= finalLimit) {
294
+ if (i < allEntities.length - 1) {
295
+ nextCursor = (i + 1).toString();
296
+ }
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ return { result: { entities: resultEntities, cursor: nextCursor } };
303
+ });
304
+ }
305
+ /**
306
+ * Remove all entities from the storage.
307
+ * @returns Nothing.
308
+ */
309
+ async empty() {
310
+ const contextIds = await ContextIdStore.getContextIds();
311
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
312
+ return this.withLock(entities => {
313
+ if (Is.stringValue(partitionKey)) {
314
+ const filtered = entities.filter(item => ObjectHelper.propertyGet(item, MemoryEntityStorageConnector._PARTITION_KEY) !==
315
+ partitionKey);
316
+ return { updated: filtered, result: undefined };
317
+ }
318
+ return { updated: [], result: undefined };
319
+ });
320
+ }
321
+ /**
322
+ * Remove multiple entities by id.
323
+ * @param ids The ids of the entities to remove.
324
+ * @returns Nothing.
325
+ */
326
+ async removeBatch(ids) {
327
+ Guards.arrayValue(MemoryEntityStorageConnector.CLASS_NAME, "ids", ids);
328
+ const contextIds = await ContextIdStore.getContextIds();
329
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
330
+ const finalConditions = [];
331
+ if (Is.stringValue(partitionKey)) {
332
+ finalConditions.push({
333
+ property: MemoryEntityStorageConnector._PARTITION_KEY,
334
+ value: partitionKey
335
+ });
336
+ }
337
+ return this.withLock(entities => {
338
+ for (const id of ids) {
339
+ const index = this.findItem(entities, id, undefined, finalConditions);
340
+ if (index >= 0) {
341
+ entities.splice(index, 1);
342
+ }
343
+ }
344
+ return { updated: entities, result: undefined };
345
+ });
346
+ }
347
+ /**
348
+ * Teardown the storage by clearing the underlying shared buffer for this schema.
349
+ * @param nodeLoggingComponentType The node logging component type.
350
+ * @returns True if the teardown process was successful.
351
+ */
352
+ async teardown(nodeLoggingComponentType) {
353
+ const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
354
+ await nodeLogging?.log({
355
+ level: "info",
356
+ source: MemoryEntityStorageConnector.CLASS_NAME,
357
+ ts: Date.now(),
358
+ message: "storeTearingDown"
359
+ });
360
+ await Mutex.lock(this._storageKey, { throwOnTimeout: true });
361
+ try {
362
+ SharedObjectBuffer.remove(this._storageKey);
363
+ }
364
+ finally {
365
+ Mutex.unlock(this._storageKey);
366
+ }
367
+ await nodeLogging?.log({
368
+ level: "info",
369
+ source: MemoryEntityStorageConnector.CLASS_NAME,
370
+ ts: Date.now(),
371
+ message: "storeTornDown"
372
+ });
373
+ return true;
374
+ }
375
+ /**
376
+ * Count all the entities which match the conditions.
377
+ * @param conditions The optional conditions to match for the entities.
378
+ * @returns The total count of entities in the storage.
379
+ */
380
+ async count(conditions) {
381
+ const contextIds = await ContextIdStore.getContextIds();
382
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
159
383
  const finalConditions = {
160
384
  conditions: [],
161
385
  logicalOperator: LogicalOperator.And
@@ -168,51 +392,127 @@ export class MemoryEntityStorageConnector {
168
392
  });
169
393
  }
170
394
  if (!Is.empty(conditions)) {
171
- finalConditions.conditions.push(conditions);
172
- }
173
- const entities = [];
174
- const finalLimit = limit ?? MemoryEntityStorageConnector._DEFAULT_LIMIT;
175
- let nextCursor;
176
- if (allEntities.length > 0) {
177
- const finalSortKeys = EntitySchemaHelper.buildSortProperties(this._entitySchema, sortProperties);
178
- allEntities = EntitySorter.sort(allEntities, finalSortKeys);
179
- const startIndex = Coerce.number(cursor) ?? 0;
180
- for (let i = startIndex; i < allEntities.length; i++) {
181
- if (EntityConditions.check(allEntities[i], finalConditions) &&
182
- entities.length < finalLimit) {
183
- const entity = ObjectHelper.clone(ObjectHelper.pick(allEntities[i], properties));
184
- ObjectHelper.propertyDelete(entity, MemoryEntityStorageConnector._PARTITION_KEY);
185
- entities.push(entity);
186
- if (entities.length >= finalLimit) {
187
- if (i < allEntities.length - 1) {
188
- nextCursor = (i + 1).toString();
189
- }
190
- break;
191
- }
395
+ finalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));
396
+ }
397
+ return this.withLock(entities => {
398
+ if (finalConditions.conditions.length === 0) {
399
+ return { result: entities.length };
400
+ }
401
+ return {
402
+ result: entities.filter(item => EntityConditions.check(item, finalConditions)).length
403
+ };
404
+ });
405
+ }
406
+ /**
407
+ * Get all entities in the memory store.
408
+ * @returns All stored entities with partition keys removed.
409
+ */
410
+ async getStore() {
411
+ return this.withLock(entities => ({
412
+ result: entities.map(item => EntityStorageHelper.unPrepareEntity(item, [MemoryEntityStorageConnector._PARTITION_KEY]))
413
+ }));
414
+ }
415
+ /**
416
+ * Get a unique list of all the context ids from the storage.
417
+ * @returns The list of unique context ids.
418
+ */
419
+ async getPartitionContextIds() {
420
+ return this.withLock(entities => {
421
+ const contextIds = {};
422
+ for (const entity of entities) {
423
+ const partitionId = ObjectHelper.propertyGet(entity, MemoryEntityStorageConnector._PARTITION_KEY);
424
+ if (Is.stringValue(partitionId)) {
425
+ contextIds[partitionId] = ContextIdHelper.shortSplit(this._partitionContextIds ?? [], partitionId);
192
426
  }
193
427
  }
428
+ return { result: Object.values(contextIds) };
429
+ });
430
+ }
431
+ /**
432
+ * Create the target connector for performing the migration it will use a temporary storage location.
433
+ * @param newEntitySchema The name of the new entity schema to create the connector for.
434
+ * @returns Connector for performing the migration.
435
+ */
436
+ async createTargetConnector(newEntitySchema) {
437
+ // Resolve the target schema name the same way _storageKey is resolved in the constructor.
438
+ const targetSchemaEntry = EntitySchemaFactory.get(newEntitySchema);
439
+ const targetSchemaName = targetSchemaEntry.type ?? newEntitySchema;
440
+ // When migrating to a different schema, wipe the target buffer so that every
441
+ // migration starts from an empty store regardless of any previous connector
442
+ // instances that shared the same schema name.
443
+ if (targetSchemaName !== this._storageKey) {
444
+ await Mutex.lock(targetSchemaName, { throwOnTimeout: true });
445
+ try {
446
+ SharedObjectBuffer.remove(targetSchemaName);
447
+ }
448
+ finally {
449
+ Mutex.unlock(targetSchemaName);
450
+ }
194
451
  }
195
- return {
196
- entities,
197
- cursor: nextCursor
198
- };
452
+ return new MemoryEntityStorageConnector({
453
+ entitySchema: newEntitySchema,
454
+ partitionContextIds: this._partitionContextIds,
455
+ config: {
456
+ storageKey: this._storageKey,
457
+ initialCapacityBytes: this._initialCapacityBytes,
458
+ maxCapacityBytes: this._maxCapacityBytes
459
+ }
460
+ });
461
+ }
462
+ /**
463
+ * Finalize the migration by tearing down the old connector and replacing it with the target connector.
464
+ * @param targetConnector The target connector to finalize the migration with.
465
+ * @param options The options to control how the migration is finalized.
466
+ * @param loggingComponentType The optional component type to use for logging the migration progress.
467
+ * @returns A promise that resolves when the migration is finalized.
468
+ */
469
+ async finalizeMigration(targetConnector, options, loggingComponentType) {
470
+ return targetConnector;
199
471
  }
200
472
  /**
201
- * Get the memory store.
202
- * @returns The store.
473
+ * Cleanup the migration if a migration fails or needs to be aborted.
474
+ * @param targetConnector The target connector to cleanup the migration with.
475
+ * @param options The options to control how the migration is cleaned up.
476
+ * @param loggingComponentType The optional component type to use for logging the migration progress.
477
+ * @returns A promise that resolves when the migration is cleaned up.
478
+ */
479
+ async cleanupMigration(targetConnector, options, loggingComponentType) { }
480
+ /**
481
+ * Acquires the schema-keyed lock, runs fn with the current entity array, optionally
482
+ * writes back a modified array, then releases the lock.
483
+ * @param fn The synchronous function to run while the lock is held.
484
+ * @returns The result produced by fn.
485
+ * @internal
203
486
  */
204
- getStore() {
205
- return this._store;
487
+ async withLock(fn) {
488
+ const key = this._storageKey;
489
+ await Mutex.lock(key, { throwOnTimeout: true });
490
+ try {
491
+ await SharedObjectBuffer.create(key, {
492
+ initialCapacityBytes: this._initialCapacityBytes,
493
+ maxCapacityBytes: this._maxCapacityBytes
494
+ });
495
+ const entities = (await SharedObjectBuffer.read(key)) ?? [];
496
+ const outcome = fn(entities);
497
+ if (outcome.updated !== undefined) {
498
+ await SharedObjectBuffer.write(key, outcome.updated);
499
+ }
500
+ return outcome.result;
501
+ }
502
+ finally {
503
+ Mutex.unlock(key);
504
+ }
206
505
  }
207
506
  /**
208
- * Find the item in the store.
507
+ * Find the item in the provided entity array.
508
+ * @param entities The current entity array.
209
509
  * @param id The id to search for.
210
510
  * @param secondaryIndex The secondary index to search for.
211
511
  * @param conditions The optional conditions to match for the entities.
212
512
  * @returns The index of the item if found or -1.
213
513
  * @internal
214
514
  */
215
- findItem(id, secondaryIndex, conditions) {
515
+ findItem(entities, id, secondaryIndex, conditions) {
216
516
  const finalConditions = [];
217
517
  if (!Is.empty(secondaryIndex)) {
218
518
  finalConditions.push({
@@ -222,7 +522,6 @@ export class MemoryEntityStorageConnector {
222
522
  });
223
523
  }
224
524
  if (Is.arrayValue(conditions)) {
225
- // If we haven't added a secondary index condition we need to add the primary key condition.
226
525
  if (finalConditions.length === 0) {
227
526
  finalConditions.push({
228
527
  property: this._primaryKey.property,
@@ -237,14 +536,14 @@ export class MemoryEntityStorageConnector {
237
536
  })));
238
537
  }
239
538
  if (finalConditions.length > 0) {
240
- for (let i = 0; i < this._store.length; i++) {
241
- if (EntityConditions.check(this._store[i], { conditions: finalConditions })) {
539
+ for (let i = 0; i < entities.length; i++) {
540
+ if (EntityConditions.check(entities[i], { conditions: finalConditions })) {
242
541
  return i;
243
542
  }
244
543
  }
245
544
  }
246
545
  else {
247
- return this._store.findIndex(e => e[this._primaryKey.property] === id);
546
+ return entities.findIndex(e => e[this._primaryKey.property] === id);
248
547
  }
249
548
  return -1;
250
549
  }