@twin.org/entity-storage-connector-file 0.0.3-next.28 → 0.0.3-next.29

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.
@@ -3,7 +3,7 @@
3
3
  import { access, mkdir, readFile, rename, rm, statfs, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
6
- import { BaseError, Coerce, ComponentFactory, GeneralError, Guards, HealthStatus, Is, ObjectHelper, Validation } from "@twin.org/core";
6
+ import { BaseError, Coerce, ComponentFactory, GeneralError, Guards, HealthStatus, Is, Mutex, ObjectHelper, Validation } from "@twin.org/core";
7
7
  import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, EntitySorter, LogicalOperator } from "@twin.org/entity";
8
8
  import { EntityStorageHelper } from "@twin.org/entity-storage-models";
9
9
  /**
@@ -34,6 +34,11 @@ export class FileEntityStorageConnector {
34
34
  * @internal
35
35
  */
36
36
  static _DEFAULT_DISK_ERROR_THRESHOLD_BYTES = 100 * 1024 * 1024;
37
+ /**
38
+ * Maximum time to wait for the store update lock.
39
+ * @internal
40
+ */
41
+ static _UPDATE_LOCK_TIMEOUT_MS = 60000;
37
42
  /**
38
43
  * The name for the schema.
39
44
  * @internal
@@ -69,6 +74,11 @@ export class FileEntityStorageConnector {
69
74
  * @internal
70
75
  */
71
76
  _diskWarningThresholdBytes;
77
+ /**
78
+ * Milliseconds to wait for the directory lock before throwing.
79
+ * @internal
80
+ */
81
+ _lockTimeoutMs;
72
82
  /**
73
83
  * Create a new instance of FileEntityStorageConnector.
74
84
  * @param options The options for the connector.
@@ -89,6 +99,8 @@ export class FileEntityStorageConnector {
89
99
  this._diskWarningThresholdBytes =
90
100
  options.config.diskWarningThresholdBytes ??
91
101
  FileEntityStorageConnector._DEFAULT_DISK_WARNING_THRESHOLD_BYTES;
102
+ this._lockTimeoutMs =
103
+ options.config.lockTimeoutMs ?? FileEntityStorageConnector._UPDATE_LOCK_TIMEOUT_MS;
92
104
  }
93
105
  /**
94
106
  * Bootstrap the connector by creating and initializing any resources it needs.
@@ -226,7 +238,7 @@ export class FileEntityStorageConnector {
226
238
  Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "id", id);
227
239
  const contextIds = await ContextIdStore.getContextIds();
228
240
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
229
- const store = await this.readStore();
241
+ const store = await this.readStoreWithLock();
230
242
  const finalConditions = conditions ?? [];
231
243
  if (Is.stringValue(partitionKey)) {
232
244
  finalConditions.push({
@@ -256,22 +268,24 @@ export class FileEntityStorageConnector {
256
268
  const prepared = EntityStorageHelper.prepareEntity(entity, this._entitySchema, Is.stringValue(partitionKey)
257
269
  ? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]
258
270
  : undefined, { nullBehavior: "omit" });
259
- const store = await this.readStore();
260
- const finalConditions = conditions ?? [];
261
- if (Is.stringValue(partitionKey)) {
262
- finalConditions.push({
263
- property: FileEntityStorageConnector._PARTITION_KEY,
264
- value: partitionKey
265
- });
266
- }
267
- const existingIndex = this.findItem(store, prepared[this._primaryKey.property], undefined, finalConditions);
268
- if (existingIndex >= 0) {
269
- store[existingIndex] = prepared;
270
- }
271
- else {
272
- store.push(prepared);
273
- }
274
- await this.writeStore(store);
271
+ await this.withLock(async () => {
272
+ const store = await this.readStore();
273
+ const finalConditions = conditions ?? [];
274
+ if (Is.stringValue(partitionKey)) {
275
+ finalConditions.push({
276
+ property: FileEntityStorageConnector._PARTITION_KEY,
277
+ value: partitionKey
278
+ });
279
+ }
280
+ const existingIndex = this.findItem(store, prepared[this._primaryKey.property], undefined, finalConditions);
281
+ if (existingIndex >= 0) {
282
+ store[existingIndex] = prepared;
283
+ }
284
+ else {
285
+ store.push(prepared);
286
+ }
287
+ await this.writeStore(store);
288
+ });
275
289
  }
276
290
  /**
277
291
  * Set multiple entities in a batch.
@@ -282,28 +296,30 @@ export class FileEntityStorageConnector {
282
296
  Guards.arrayValue(FileEntityStorageConnector.CLASS_NAME, "entities", entities);
283
297
  const contextIds = await ContextIdStore.getContextIds();
284
298
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
285
- const store = await this.readStore();
286
- for (const entity of entities) {
287
- Guards.object(FileEntityStorageConnector.CLASS_NAME, "entity", entity);
288
- const prepared = EntityStorageHelper.prepareEntity(entity, this._entitySchema, Is.stringValue(partitionKey)
289
- ? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]
290
- : undefined, { nullBehavior: "omit" });
291
- const existingIndex = this.findItem(store, prepared[this._primaryKey.property], undefined, Is.stringValue(partitionKey)
292
- ? [
293
- {
294
- property: FileEntityStorageConnector._PARTITION_KEY,
295
- value: partitionKey
296
- }
297
- ]
298
- : []);
299
- if (existingIndex >= 0) {
300
- store[existingIndex] = prepared;
301
- }
302
- else {
303
- store.push(prepared);
299
+ await this.withLock(async () => {
300
+ const store = await this.readStore();
301
+ for (const entity of entities) {
302
+ Guards.object(FileEntityStorageConnector.CLASS_NAME, "entity", entity);
303
+ const prepared = EntityStorageHelper.prepareEntity(entity, this._entitySchema, Is.stringValue(partitionKey)
304
+ ? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]
305
+ : undefined, { nullBehavior: "omit" });
306
+ const existingIndex = this.findItem(store, prepared[this._primaryKey.property], undefined, Is.stringValue(partitionKey)
307
+ ? [
308
+ {
309
+ property: FileEntityStorageConnector._PARTITION_KEY,
310
+ value: partitionKey
311
+ }
312
+ ]
313
+ : []);
314
+ if (existingIndex >= 0) {
315
+ store[existingIndex] = prepared;
316
+ }
317
+ else {
318
+ store.push(prepared);
319
+ }
304
320
  }
305
- }
306
- await this.writeStore(store);
321
+ await this.writeStore(store);
322
+ });
307
323
  }
308
324
  /**
309
325
  * Remove all entities from the storage.
@@ -313,11 +329,13 @@ export class FileEntityStorageConnector {
313
329
  const contextIds = await ContextIdStore.getContextIds();
314
330
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
315
331
  try {
316
- const store = await this.readStore();
317
- const remaining = Is.stringValue(partitionKey)
318
- ? store.filter(item => ObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !== partitionKey)
319
- : [];
320
- await this.writeStore(remaining);
332
+ await this.withLock(async () => {
333
+ const store = await this.readStore();
334
+ const remaining = Is.stringValue(partitionKey)
335
+ ? store.filter(item => ObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !== partitionKey)
336
+ : [];
337
+ await this.writeStore(remaining);
338
+ });
321
339
  }
322
340
  catch (err) {
323
341
  throw new GeneralError(FileEntityStorageConnector.CLASS_NAME, "emptyFailed", undefined, err);
@@ -333,16 +351,19 @@ export class FileEntityStorageConnector {
333
351
  const contextIds = await ContextIdStore.getContextIds();
334
352
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
335
353
  try {
336
- const store = await this.readStore();
337
- const idSet = new Set(ids);
338
- const remaining = store.filter(item => {
339
- if (Is.stringValue(partitionKey) &&
340
- ObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !== partitionKey) {
341
- return true;
342
- }
343
- return !idSet.has(item[this._primaryKey.property]);
354
+ await this.withLock(async () => {
355
+ const store = await this.readStore();
356
+ const idSet = new Set(ids);
357
+ const remaining = store.filter(item => {
358
+ if (Is.stringValue(partitionKey) &&
359
+ ObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !==
360
+ partitionKey) {
361
+ return true;
362
+ }
363
+ return !idSet.has(item[this._primaryKey.property]);
364
+ });
365
+ await this.writeStore(remaining);
344
366
  });
345
- await this.writeStore(remaining);
346
367
  }
347
368
  catch (err) {
348
369
  throw new GeneralError(FileEntityStorageConnector.CLASS_NAME, "removeBatchFailed", undefined, err);
@@ -392,19 +413,21 @@ export class FileEntityStorageConnector {
392
413
  Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "id", id);
393
414
  const contextIds = await ContextIdStore.getContextIds();
394
415
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
395
- const store = await this.readStore();
396
- const finalConditions = conditions ?? [];
397
- if (Is.stringValue(partitionKey)) {
398
- finalConditions.push({
399
- property: FileEntityStorageConnector._PARTITION_KEY,
400
- value: partitionKey
401
- });
402
- }
403
- const index = this.findItem(store, id, undefined, finalConditions);
404
- if (index >= 0) {
405
- store.splice(index, 1);
406
- await this.writeStore(store);
407
- }
416
+ await this.withLock(async () => {
417
+ const store = await this.readStore();
418
+ const finalConditions = conditions ?? [];
419
+ if (Is.stringValue(partitionKey)) {
420
+ finalConditions.push({
421
+ property: FileEntityStorageConnector._PARTITION_KEY,
422
+ value: partitionKey
423
+ });
424
+ }
425
+ const index = this.findItem(store, id, undefined, finalConditions);
426
+ if (index >= 0) {
427
+ store.splice(index, 1);
428
+ await this.writeStore(store);
429
+ }
430
+ });
408
431
  }
409
432
  /**
410
433
  * Find all the entities which match the conditions.
@@ -426,7 +449,7 @@ export class FileEntityStorageConnector {
426
449
  Validation.integer("limit", limit, validationFailures, undefined, { minValue: 1 });
427
450
  Validation.asValidationError(FileEntityStorageConnector.CLASS_NAME, "query", validationFailures);
428
451
  }
429
- let allEntities = await this.readStore();
452
+ let allEntities = await this.readStoreWithLock();
430
453
  const finalConditions = {
431
454
  conditions: [],
432
455
  logicalOperator: LogicalOperator.And
@@ -477,7 +500,7 @@ export class FileEntityStorageConnector {
477
500
  * @returns The total count of entities in the storage.
478
501
  */
479
502
  async count(conditions) {
480
- const store = await this.readStore();
503
+ const store = await this.readStoreWithLock();
481
504
  const contextIds = await ContextIdStore.getContextIds();
482
505
  const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
483
506
  const finalConditions = {
@@ -505,7 +528,7 @@ export class FileEntityStorageConnector {
505
528
  */
506
529
  async getPartitionContextIds() {
507
530
  const contextIds = {};
508
- const store = await this.readStore();
531
+ const store = await this.readStoreWithLock();
509
532
  for (const entity of store) {
510
533
  const partitionId = ObjectHelper.propertyGet(entity, FileEntityStorageConnector._PARTITION_KEY);
511
534
  if (Is.stringValue(partitionId)) {
@@ -568,22 +591,48 @@ export class FileEntityStorageConnector {
568
591
  await targetConnector?.teardown?.(loggingComponentType);
569
592
  }
570
593
  /**
571
- * Read the store from file.
594
+ * Read the store from file while holding the directory mutex.
595
+ * Use this for standalone reads (get, query, count, getPartitionContextIds) where
596
+ * no outer lock is held. Do not call from inside a withLock callback —
597
+ * use readStore instead to avoid a re-entrant deadlock.
598
+ * @returns The store.
599
+ * @internal
600
+ */
601
+ async readStoreWithLock() {
602
+ return this.withLock(async () => this.readStore());
603
+ }
604
+ /**
605
+ * Read the store from file without acquiring the directory mutex.
606
+ * Must only be called from inside a withLock callback, where the mutex
607
+ * is already held. Performing the read under the held lock prevents a race with
608
+ * the atomic rename(tmp→store.json) window on Windows that would otherwise
609
+ * return ENOENT and be misinterpreted as an empty store.
572
610
  * @returns The store.
573
611
  * @internal
574
612
  */
575
613
  async readStore() {
614
+ const filename = path.join(this._directory, "store.json");
615
+ let store;
616
+ try {
617
+ store = await readFile(filename, "utf8");
618
+ }
619
+ catch (err) {
620
+ if (ObjectHelper.propertyGet(err, "code") === "ENOENT") {
621
+ // The store has not been written yet, which is valid for a new store.
622
+ return [];
623
+ }
624
+ throw new GeneralError(FileEntityStorageConnector.CLASS_NAME, "readStoreFailed", { directory: this._directory }, err);
625
+ }
576
626
  try {
577
- const filename = path.join(this._directory, "store.json");
578
- const store = await readFile(filename, "utf8");
579
627
  return JSON.parse(store);
580
628
  }
581
- catch {
582
- return [];
629
+ catch (err) {
630
+ throw new GeneralError(FileEntityStorageConnector.CLASS_NAME, "readStoreCorrupt", { directory: this._directory }, err);
583
631
  }
584
632
  }
585
633
  /**
586
- * Write the store to the file.
634
+ * Write the store to the file, atomically replacing the previous version so
635
+ * a reader can never observe a partially written store.
587
636
  * @param store The store to write.
588
637
  * @returns Nothing.
589
638
  * @internal
@@ -591,9 +640,34 @@ export class FileEntityStorageConnector {
591
640
  async writeStore(store) {
592
641
  try {
593
642
  const filename = path.join(this._directory, "store.json");
594
- await writeFile(filename, JSON.stringify(store, undefined, "\t"), "utf8");
643
+ const tempFilename = `${filename}.tmp`;
644
+ await writeFile(tempFilename, JSON.stringify(store, undefined, "\t"), "utf8");
645
+ await rename(tempFilename, filename);
646
+ }
647
+ catch (err) {
648
+ throw new GeneralError(FileEntityStorageConnector.CLASS_NAME, "writeStoreFailed", { directory: this._directory }, err);
649
+ }
650
+ }
651
+ /**
652
+ * Serialize an update so that concurrent modifications cannot interleave
653
+ * their read-modify-write cycles, which would lose updates or tear the
654
+ * store file. The mutex is keyed on the storage directory, so every
655
+ * connector or worker using the same store serializes with the others.
656
+ * @param update The update operation to perform.
657
+ * @returns The result of the update.
658
+ * @internal
659
+ */
660
+ async withLock(update) {
661
+ await Mutex.lock(this._directory, {
662
+ timeoutMs: this._lockTimeoutMs,
663
+ throwOnTimeout: true
664
+ });
665
+ try {
666
+ return await update();
667
+ }
668
+ finally {
669
+ Mutex.unlock(this._directory);
595
670
  }
596
- catch { }
597
671
  }
598
672
  /**
599
673
  * Check if the dir exists.
@@ -1 +1 @@
1
- {"version":3,"file":"fileEntityStorageConnector.js","sourceRoot":"","sources":["../../src/fileEntityStorageConnector.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { access, mkdir, readFile, rename, rm, statfs, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ContextIdHelper, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tGeneralError,\n\tGuards,\n\tHealthStatus,\n\ttype IHealth,\n\tIs,\n\ttype IValidationFailure,\n\tObjectHelper,\n\tValidation\n} from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\ttype EntityCondition,\n\tEntityConditions,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\tEntitySorter,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\tLogicalOperator,\n\ttype SortDirection\n} from \"@twin.org/entity\";\nimport {\n\tEntityStorageHelper,\n\ttype IEntityStorageConnector,\n\ttype IEntityStorageMigrationConnector,\n\ttype IMigrationOptions\n} from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IFileEntityStorageConnectorConstructorOptions } from \"./models/IFileEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations in file.\n */\nexport class FileEntityStorageConnector<\n\tT = unknown\n> implements IEntityStorageMigrationConnector<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<FileEntityStorageConnector>();\n\n\t/**\n\t * Default limit for number of items to return.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LIMIT: number = 20;\n\n\t/**\n\t * Partition key for the operation.\n\t * @internal\n\t */\n\tprivate static readonly _PARTITION_KEY: string = \"partitionId\";\n\n\t/**\n\t * Default disk space warning threshold: 500 MB.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_DISK_WARNING_THRESHOLD_BYTES: number = 500 * 1024 * 1024;\n\n\t/**\n\t * Default disk space error threshold: 100 MB.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_DISK_ERROR_THRESHOLD_BYTES: number = 100 * 1024 * 1024;\n\n\t/**\n\t * The name for the schema.\n\t * @internal\n\t */\n\tprivate readonly _entitySchemaName: string;\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 keys to use from the context ids to create partitions.\n\t * @internal\n\t */\n\tprivate readonly _partitionContextIds?: string[];\n\n\t/**\n\t * The primary key.\n\t * @internal\n\t */\n\tprivate readonly _primaryKey: IEntitySchemaProperty<T>;\n\n\t/**\n\t * The directory to use for storage.\n\t * @internal\n\t */\n\tprivate readonly _directory: string;\n\n\t/**\n\t * Free bytes below which health reports an error.\n\t * @internal\n\t */\n\tprivate readonly _diskErrorThresholdBytes: number;\n\n\t/**\n\t * Free bytes below which health reports a warning.\n\t * @internal\n\t */\n\tprivate readonly _diskWarningThresholdBytes: number;\n\n\t/**\n\t * Create a new instance of FileEntityStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: IFileEntityStorageConnectorConstructorOptions) {\n\t\tGuards.object(FileEntityStorageConnector.CLASS_NAME, nameof(options), options);\n\t\tGuards.stringValue(\n\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.entitySchema),\n\t\t\toptions.entitySchema\n\t\t);\n\t\tGuards.object(FileEntityStorageConnector.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.stringValue(\n\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.directory),\n\t\t\toptions.config.directory\n\t\t);\n\t\tthis._entitySchemaName = options.entitySchema;\n\t\tthis._entitySchema = EntitySchemaFactory.get(options.entitySchema);\n\t\tthis._partitionContextIds = options.partitionContextIds;\n\t\tthis._primaryKey = EntitySchemaHelper.getPrimaryKey<T>(this._entitySchema);\n\t\tthis._directory = path.resolve(options.config.directory);\n\t\tthis._diskErrorThresholdBytes =\n\t\t\toptions.config.diskErrorThresholdBytes ??\n\t\t\tFileEntityStorageConnector._DEFAULT_DISK_ERROR_THRESHOLD_BYTES;\n\t\tthis._diskWarningThresholdBytes =\n\t\t\toptions.config.diskWarningThresholdBytes ??\n\t\t\tFileEntityStorageConnector._DEFAULT_DISK_WARNING_THRESHOLD_BYTES;\n\t}\n\n\t/**\n\t * Bootstrap the connector by creating and initializing any resources it needs.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the bootstrapping process was successful.\n\t */\n\tpublic async bootstrap(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tif (!(await this.dirExists(this._directory))) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"directoryCreating\",\n\t\t\t\tdata: {\n\t\t\t\t\tdirectory: this._directory\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait mkdir(this._directory, { recursive: true });\n\n\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tmessage: \"directoryCreated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdirectory: this._directory\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tmessage: \"directoryCreateFailed\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdirectory: this._directory\n\t\t\t\t\t},\n\t\t\t\t\terror: BaseError.fromError(err)\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"directoryExists\",\n\t\t\t\tdata: {\n\t\t\t\t\tdirectory: this._directory\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\treturn true;\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 FileEntityStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Returns the health status of the component.\n\t * @returns The health status of the component, can return multiple entries for elements within the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tconst stats = await statfs(this._directory);\n\t\t\tconst freeBytes = stats.bavail * stats.bsize;\n\n\t\t\tif (freeBytes < this._diskErrorThresholdBytes) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\t\tmessage: \"diskSpaceError\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tdirectory: this._directory,\n\t\t\t\t\t\t\tfreeBytes,\n\t\t\t\t\t\t\tthresholdBytes: this._diskErrorThresholdBytes\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t} else if (freeBytes < this._diskWarningThresholdBytes) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\t\tmessage: \"diskSpaceWarning\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tdirectory: this._directory,\n\t\t\t\t\t\t\tfreeBytes,\n\t\t\t\t\t\t\tthresholdBytes: this._diskWarningThresholdBytes\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tdata: { directory: this._directory, freeBytes }\n\t\t\t\t}\n\t\t\t];\n\t\t} catch {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tmessage: \"diskSpaceCheckFailed\",\n\t\t\t\t\tdata: { directory: this._directory }\n\t\t\t\t}\n\t\t\t];\n\t\t}\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 * 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(FileEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst store = await this.readStore();\n\n\t\tconst finalConditions = conditions ?? [];\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tconst index = this.findItem(store, id, secondaryIndex, finalConditions);\n\t\tconst item = index >= 0 ? store[index] : undefined;\n\n\t\tif (Is.objectValue(item)) {\n\t\t\treturn EntityStorageHelper.unPrepareEntity<T>(item, [\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t]);\n\t\t}\n\n\t\treturn undefined;\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>(FileEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst prepared = EntityStorageHelper.prepareEntity(\n\t\t\tentity,\n\t\t\tthis._entitySchema,\n\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]\n\t\t\t\t: undefined,\n\t\t\t{ nullBehavior: \"omit\" }\n\t\t);\n\n\t\tconst store = await this.readStore();\n\n\t\tconst finalConditions = conditions ?? [];\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tconst existingIndex = this.findItem(\n\t\t\tstore,\n\t\t\tprepared[this._primaryKey.property] as string,\n\t\t\tundefined,\n\t\t\tfinalConditions\n\t\t);\n\t\tif (existingIndex >= 0) {\n\t\t\tstore[existingIndex] = prepared;\n\t\t} else {\n\t\t\tstore.push(prepared);\n\t\t}\n\n\t\tawait this.writeStore(store);\n\t}\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tpublic async setBatch(entities: T[]): Promise<void> {\n\t\tGuards.arrayValue(FileEntityStorageConnector.CLASS_NAME, nameof(entities), entities);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\t\tconst store = await this.readStore();\n\n\t\tfor (const entity of entities) {\n\t\t\tGuards.object<T>(FileEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\t\tconst prepared = EntityStorageHelper.prepareEntity(\n\t\t\t\tentity,\n\t\t\t\tthis._entitySchema,\n\t\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t\t? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]\n\t\t\t\t\t: undefined,\n\t\t\t\t{ nullBehavior: \"omit\" }\n\t\t\t);\n\n\t\t\tconst existingIndex = this.findItem(\n\t\t\t\tstore,\n\t\t\t\tprepared[this._primaryKey.property] as string,\n\t\t\t\tundefined,\n\t\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t\t? [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\t\t\t\t\tvalue: partitionKey\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t: []\n\t\t\t);\n\t\t\tif (existingIndex >= 0) {\n\t\t\t\tstore[existingIndex] = prepared;\n\t\t\t} else {\n\t\t\t\tstore.push(prepared);\n\t\t\t}\n\t\t}\n\n\t\tawait this.writeStore(store);\n\t}\n\n\t/**\n\t * Remove all entities from the storage.\n\t * @returns Nothing.\n\t */\n\tpublic async empty(): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tconst store = await this.readStore();\n\t\t\tconst remaining = Is.stringValue(partitionKey)\n\t\t\t\t? store.filter(\n\t\t\t\t\t\titem =>\n\t\t\t\t\t\t\tObjectHelper.propertyGet(\n\t\t\t\t\t\t\t\titem as object,\n\t\t\t\t\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t\t\t\t\t) !== partitionKey\n\t\t\t\t\t)\n\t\t\t\t: [];\n\t\t\tawait this.writeStore(remaining);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(FileEntityStorageConnector.CLASS_NAME, \"emptyFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Remove multiple entities by id.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeBatch(ids: string[]): Promise<void> {\n\t\tGuards.arrayValue(FileEntityStorageConnector.CLASS_NAME, nameof(ids), ids);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tconst store = await this.readStore();\n\t\t\tconst idSet = new Set(ids);\n\t\t\tconst remaining = store.filter(item => {\n\t\t\t\tif (\n\t\t\t\t\tIs.stringValue(partitionKey) &&\n\t\t\t\t\tObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !== partitionKey\n\t\t\t\t) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\treturn !idSet.has(item[this._primaryKey.property] as string);\n\t\t\t});\n\t\t\tawait this.writeStore(remaining);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"removeBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Teardown the storage by deleting the underlying store file.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the teardown process was successful.\n\t */\n\tpublic async teardown(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tawait nodeLogging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"storeTearingDown\"\n\t\t});\n\n\t\ttry {\n\t\t\tawait rm(this._directory, { recursive: true, force: true });\n\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"storeTornDown\"\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"teardownFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t\treturn false;\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(FileEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst store = await this.readStore();\n\n\t\tconst finalConditions = conditions ?? [];\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tconst index = this.findItem(store, id, undefined, finalConditions);\n\n\t\tif (index >= 0) {\n\t\t\tstore.splice(index, 1);\n\t\t\tawait this.writeStore(store);\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\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tEntityStorageHelper.validateSortProperties(this._entitySchema, sortProperties);\n\t\tEntityStorageHelper.validateProperties(this._entitySchema, properties);\n\n\t\tif (!Is.empty(limit)) {\n\t\t\tconst validationFailures: IValidationFailure[] = [];\n\t\t\tValidation.integer(nameof(limit), limit, validationFailures, undefined, { minValue: 1 });\n\t\t\tValidation.asValidationError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"query\",\n\t\t\t\tvalidationFailures\n\t\t\t);\n\t\t}\n\n\t\tlet allEntities = await this.readStore();\n\n\t\tconst finalConditions: EntityCondition<T> = {\n\t\t\tconditions: [],\n\t\t\tlogicalOperator: LogicalOperator.And\n\t\t};\n\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.conditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tif (!Is.empty(conditions)) {\n\t\t\tfinalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));\n\t\t}\n\n\t\tconst entities = [];\n\t\tconst finalLimit = limit ?? FileEntityStorageConnector._DEFAULT_LIMIT;\n\t\tlet nextCursor: string | undefined;\n\n\t\tif (allEntities.length > 0) {\n\t\t\tconst finalSortKeys = EntitySchemaHelper.buildSortProperties<T>(\n\t\t\t\tthis._entitySchema,\n\t\t\t\tsortProperties\n\t\t\t);\n\t\t\tallEntities = EntitySorter.sort(allEntities, finalSortKeys);\n\n\t\t\tconst startIndex = Coerce.number(cursor) ?? 0;\n\n\t\t\tfor (let i = startIndex; i < allEntities.length; i++) {\n\t\t\t\tif (\n\t\t\t\t\tEntityConditions.check(allEntities[i], finalConditions) &&\n\t\t\t\t\tentities.length < finalLimit\n\t\t\t\t) {\n\t\t\t\t\tconst entity = Is.arrayValue(properties)\n\t\t\t\t\t\t? ObjectHelper.pick(allEntities[i], properties)\n\t\t\t\t\t\t: allEntities[i];\n\t\t\t\t\tentities.push(\n\t\t\t\t\t\tEntityStorageHelper.unPrepareEntity<T>(entity, [\n\t\t\t\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t\tif (entities.length >= finalLimit) {\n\t\t\t\t\t\tif (i < allEntities.length - 1) {\n\t\t\t\t\t\t\tnextCursor = (i + 1).toString();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tentities,\n\t\t\tcursor: nextCursor\n\t\t};\n\t}\n\n\t/**\n\t * Count all the entities which match the conditions.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The total count of entities in the storage.\n\t */\n\tpublic async count(conditions?: EntityCondition<T>): Promise<number> {\n\t\tconst store = await this.readStore();\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst finalConditions: EntityCondition<T> = {\n\t\t\tconditions: [],\n\t\t\tlogicalOperator: LogicalOperator.And\n\t\t};\n\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.conditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tif (!Is.empty(conditions)) {\n\t\t\tfinalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));\n\t\t}\n\n\t\tif (finalConditions.conditions.length === 0) {\n\t\t\treturn store.length;\n\t\t}\n\n\t\treturn store.filter(item => EntityConditions.check(item, finalConditions)).length;\n\t}\n\n\t/**\n\t * Get a unique list of all the context ids from the storage.\n\t * @returns The list of unique context ids.\n\t */\n\tpublic async getPartitionContextIds(): Promise<IContextIds[]> {\n\t\tconst contextIds: { [id: string]: IContextIds } = {};\n\n\t\tconst store = await this.readStore();\n\n\t\tfor (const entity of store) {\n\t\t\tconst partitionId = ObjectHelper.propertyGet(\n\t\t\t\tentity,\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t);\n\t\t\tif (Is.stringValue(partitionId)) {\n\t\t\t\tcontextIds[partitionId] = ContextIdHelper.shortSplit(\n\t\t\t\t\tthis._partitionContextIds ?? [],\n\t\t\t\t\tpartitionId\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn Object.values(contextIds);\n\t}\n\n\t/**\n\t * Create the target connector for performing the migration it will use a temporary storage location.\n\t * @param newEntitySchema The name of the new entity schema to create the connector for.\n\t * @returns Connector for performing the migration.\n\t */\n\tpublic async createTargetConnector<U>(\n\t\tnewEntitySchema: string\n\t): Promise<IEntityStorageConnector<U>> {\n\t\tconst baseName = path.basename(this._directory);\n\t\tconst parentDir = path.resolve(this._directory, \"..\");\n\t\tconst migrationDir = path.join(parentDir, `${baseName}_migration_${Date.now()}`);\n\n\t\treturn new FileEntityStorageConnector<U>({\n\t\t\tentitySchema: newEntitySchema,\n\t\t\tpartitionContextIds: this._partitionContextIds,\n\t\t\tconfig: {\n\t\t\t\tdirectory: migrationDir,\n\t\t\t\tdiskErrorThresholdBytes: this._diskErrorThresholdBytes,\n\t\t\t\tdiskWarningThresholdBytes: this._diskWarningThresholdBytes\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Finalize the migration by tearing down the old connector and replacing it with the target connector.\n\t * @param targetConnector The target connector to finalize the migration with.\n\t * @param options The options to control how the migration is finalized.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is finalized.\n\t */\n\tpublic async finalizeMigration<U>(\n\t\ttargetConnector: FileEntityStorageConnector<U>,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<IEntityStorageConnector<U>> {\n\t\tconst originalDir = this._directory;\n\t\tconst migrationDir = targetConnector._directory;\n\n\t\t// Teardown the original connector, removing the entire source directory.\n\t\tawait this.teardown(loggingComponentType);\n\n\t\t// Rename the migration directory into the original location.\n\t\tawait rename(migrationDir, originalDir);\n\n\t\treturn new FileEntityStorageConnector<U>({\n\t\t\tentitySchema: targetConnector._entitySchemaName,\n\t\t\tpartitionContextIds: targetConnector._partitionContextIds,\n\t\t\tconfig: {\n\t\t\t\tdirectory: this._directory,\n\t\t\t\tdiskErrorThresholdBytes: targetConnector._diskErrorThresholdBytes,\n\t\t\t\tdiskWarningThresholdBytes: targetConnector._diskWarningThresholdBytes\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Cleanup the migration if a migration fails or needs to be aborted.\n\t * @param targetConnector The target connector to cleanup the migration with.\n\t * @param options The options to control how the migration is cleaned up.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is cleaned up.\n\t */\n\tpublic async cleanupMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U> | undefined,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<void> {\n\t\tawait targetConnector?.teardown?.(loggingComponentType);\n\t}\n\n\t/**\n\t * Read the store from file.\n\t * @returns The store.\n\t * @internal\n\t */\n\tprivate async readStore(): Promise<T[]> {\n\t\ttry {\n\t\t\tconst filename = path.join(this._directory, \"store.json\");\n\t\t\tconst store = await readFile(filename, \"utf8\");\n\t\t\treturn JSON.parse(store) as T[];\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Write the store to the file.\n\t * @param store The store to write.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async writeStore(store: T[]): Promise<void> {\n\t\ttry {\n\t\t\tconst filename = path.join(this._directory, \"store.json\");\n\t\t\tawait writeFile(filename, JSON.stringify(store, undefined, \"\\t\"), \"utf8\");\n\t\t} catch {}\n\t}\n\n\t/**\n\t * Check if the dir exists.\n\t * @param dir The directory to check.\n\t * @returns True if the dir exists.\n\t * @internal\n\t */\n\tprivate async dirExists(dir: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(dir);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Find the item in the store.\n\t * @param store The store to search.\n\t * @param id The id to search for.\n\t * @param secondaryIndex The secondary index to search for.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The index of the item if found or -1.\n\t * @internal\n\t */\n\tprivate findItem(\n\t\tstore: T[],\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): number {\n\t\tconst finalConditions: EntityCondition<T>[] = [];\n\n\t\tif (!Is.empty(secondaryIndex)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: secondaryIndex as string,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: id\n\t\t\t});\n\t\t}\n\n\t\tif (Is.arrayValue(conditions)) {\n\t\t\t// If we haven't added a secondary index condition we need to add the primary key condition.\n\t\t\tif (finalConditions.length === 0) {\n\t\t\t\tfinalConditions.push({\n\t\t\t\t\tproperty: this._primaryKey.property as string,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\tvalue: id\n\t\t\t\t});\n\t\t\t}\n\t\t\tfinalConditions.push(\n\t\t\t\t...conditions.map(c => ({\n\t\t\t\t\tproperty: c.property as string,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\tvalue: c.value\n\t\t\t\t}))\n\t\t\t);\n\t\t}\n\n\t\tif (finalConditions.length > 0) {\n\t\t\tfor (let i = 0; i < store.length; i++) {\n\t\t\t\tif (EntityConditions.check(store[i], { conditions: finalConditions })) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn store.findIndex(e => e[this._primaryKey.property] === id);\n\t\t}\n\n\t\treturn -1;\n\t}\n}\n"]}
1
+ {"version":3,"file":"fileEntityStorageConnector.js","sourceRoot":"","sources":["../../src/fileEntityStorageConnector.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAoB,MAAM,mBAAmB,CAAC;AACtF,OAAO,EACN,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,YAAY,EACZ,MAAM,EACN,YAAY,EAEZ,EAAE,EAEF,KAAK,EACL,YAAY,EACZ,UAAU,EACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,kBAAkB,EAElB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EAGZ,eAAe,EAEf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,mBAAmB,EAInB,MAAM,iCAAiC,CAAC;AAKzC;;GAEG;AACH,MAAM,OAAO,0BAA0B;IAGtC;;OAEG;IACI,MAAM,CAAU,UAAU,gCAAgD;IAEjF;;;OAGG;IACK,MAAM,CAAU,cAAc,GAAW,EAAE,CAAC;IAEpD;;;OAGG;IACK,MAAM,CAAU,cAAc,GAAW,aAAa,CAAC;IAE/D;;;OAGG;IACK,MAAM,CAAU,qCAAqC,GAAW,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;IAE1F;;;OAGG;IACK,MAAM,CAAU,mCAAmC,GAAW,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;IAExF;;;OAGG;IACK,MAAM,CAAU,uBAAuB,GAAW,KAAK,CAAC;IAEhE;;;OAGG;IACc,iBAAiB,CAAS;IAE3C;;;OAGG;IACc,aAAa,CAAmB;IAEjD;;;OAGG;IACc,oBAAoB,CAAY;IAEjD;;;OAGG;IACc,WAAW,CAA2B;IAEvD;;;OAGG;IACc,UAAU,CAAS;IAEpC;;;OAGG;IACc,wBAAwB,CAAS;IAElD;;;OAGG;IACc,0BAA0B,CAAS;IAEpD;;;OAGG;IACc,cAAc,CAAS;IAExC;;;OAGG;IACH,YAAY,OAAsD;QACjE,MAAM,CAAC,MAAM,CAAC,0BAA0B,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC/E,MAAM,CAAC,WAAW,CACjB,0BAA0B,CAAC,UAAU,0BAErC,OAAO,CAAC,YAAY,CACpB,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,0BAA0B,CAAC,UAAU,oBAA0B,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7F,MAAM,CAAC,WAAW,CACjB,0BAA0B,CAAC,UAAU,8BAErC,OAAO,CAAC,MAAM,CAAC,SAAS,CACxB,CAAC;QACF,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;QAC9C,IAAI,CAAC,aAAa,GAAG,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnE,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACxD,IAAI,CAAC,WAAW,GAAG,kBAAkB,CAAC,aAAa,CAAI,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3E,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzD,IAAI,CAAC,wBAAwB;YAC5B,OAAO,CAAC,MAAM,CAAC,uBAAuB;gBACtC,0BAA0B,CAAC,mCAAmC,CAAC;QAChE,IAAI,CAAC,0BAA0B;YAC9B,OAAO,CAAC,MAAM,CAAC,yBAAyB;gBACxC,0BAA0B,CAAC,qCAAqC,CAAC;QAClE,IAAI,CAAC,cAAc;YAClB,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,0BAA0B,CAAC,uBAAuB,CAAC;IACrF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,SAAS,CAAC,wBAAiC;QACvD,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;QAE9F,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,mBAAmB;gBAC5B,IAAI,EAAE;oBACL,SAAS,EAAE,IAAI,CAAC,UAAU;iBAC1B;aACD,CAAC,CAAC;YAEH,IAAI,CAAC;gBACJ,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAElD,MAAM,WAAW,EAAE,GAAG,CAAC;oBACtB,KAAK,EAAE,MAAM;oBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;oBAC7C,OAAO,EAAE,kBAAkB;oBAC3B,IAAI,EAAE;wBACL,SAAS,EAAE,IAAI,CAAC,UAAU;qBAC1B;iBACD,CAAC,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,WAAW,EAAE,GAAG,CAAC;oBACtB,KAAK,EAAE,OAAO;oBACd,MAAM,EAAE,0BAA0B,CAAC,UAAU;oBAC7C,OAAO,EAAE,uBAAuB;oBAChC,IAAI,EAAE;wBACL,SAAS,EAAE,IAAI,CAAC,UAAU;qBAC1B;oBACD,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC;iBAC/B,CAAC,CAAC;gBACH,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC;aAAM,CAAC;YACP,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,OAAO,EAAE,iBAAiB;gBAC1B,IAAI,EAAE;oBACL,SAAS,EAAE,IAAI,CAAC,UAAU;iBAC1B;aACD,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,0BAA0B,CAAC,UAAU,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM;QAClB,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;YAE7C,IAAI,SAAS,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;gBAC/C,OAAO;oBACN;wBACC,MAAM,EAAE,0BAA0B,CAAC,UAAU;wBAC7C,MAAM,EAAE,YAAY,CAAC,KAAK;wBAC1B,WAAW,EAAE,mBAAmB;wBAChC,OAAO,EAAE,gBAAgB;wBACzB,IAAI,EAAE;4BACL,SAAS,EAAE,IAAI,CAAC,UAAU;4BAC1B,SAAS;4BACT,cAAc,EAAE,IAAI,CAAC,wBAAwB;yBAC7C;qBACD;iBACD,CAAC;YACH,CAAC;iBAAM,IAAI,SAAS,GAAG,IAAI,CAAC,0BAA0B,EAAE,CAAC;gBACxD,OAAO;oBACN;wBACC,MAAM,EAAE,0BAA0B,CAAC,UAAU;wBAC7C,MAAM,EAAE,YAAY,CAAC,OAAO;wBAC5B,WAAW,EAAE,mBAAmB;wBAChC,OAAO,EAAE,kBAAkB;wBAC3B,IAAI,EAAE;4BACL,SAAS,EAAE,IAAI,CAAC,UAAU;4BAC1B,SAAS;4BACT,cAAc,EAAE,IAAI,CAAC,0BAA0B;yBAC/C;qBACD;iBACD,CAAC;YACH,CAAC;YACD,OAAO;gBACN;oBACC,MAAM,EAAE,0BAA0B,CAAC,UAAU;oBAC7C,MAAM,EAAE,YAAY,CAAC,EAAE;oBACvB,WAAW,EAAE,mBAAmB;oBAChC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE;iBAC/C;aACD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACR,OAAO;gBACN;oBACC,MAAM,EAAE,0BAA0B,CAAC,UAAU;oBAC7C,MAAM,EAAE,YAAY,CAAC,KAAK;oBAC1B,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,sBAAsB;oBAC/B,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE;iBACpC;aACD,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,IAAI,CAAC,aAA8B,CAAC;IAC5C,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,GAAG,CACf,EAAU,EACV,cAAwB,EACxB,UAAoD;QAEpD,MAAM,CAAC,WAAW,CAAC,0BAA0B,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAE1E,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE7C,MAAM,eAAe,GAAG,UAAU,IAAI,EAAE,CAAC;QACzC,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;YAClC,eAAe,CAAC,IAAI,CAAC;gBACpB,QAAQ,EAAE,0BAA0B,CAAC,cAAyB;gBAC9D,KAAK,EAAE,YAAY;aACnB,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEnD,IAAI,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,mBAAmB,CAAC,eAAe,CAAI,IAAI,EAAE;gBACnD,0BAA0B,CAAC,cAAc;aACzC,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,SAAS,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,GAAG,CAAC,MAAS,EAAE,UAAoD;QAC/E,MAAM,CAAC,MAAM,CAAI,0BAA0B,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QAEhF,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,QAAQ,GAAG,mBAAmB,CAAC,aAAa,CACjD,MAAM,EACN,IAAI,CAAC,aAAa,EAClB,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;YAC3B,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,0BAA0B,CAAC,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;YAChF,CAAC,CAAC,SAAS,EACZ,EAAE,YAAY,EAAE,MAAM,EAAE,CACxB,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YAErC,MAAM,eAAe,GAAG,UAAU,IAAI,EAAE,CAAC;YACzC,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClC,eAAe,CAAC,IAAI,CAAC;oBACpB,QAAQ,EAAE,0BAA0B,CAAC,cAAyB;oBAC9D,KAAK,EAAE,YAAY;iBACnB,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAClC,KAAK,EACL,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAW,EAC7C,SAAS,EACT,eAAe,CACf,CAAC;YACF,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACP,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtB,CAAC;YAED,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAC,QAAa;QAClC,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,UAAU,cAAoB,QAAQ,CAAC,CAAC;QAErF,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YAErC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,CAAI,0BAA0B,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;gBAEhF,MAAM,QAAQ,GAAG,mBAAmB,CAAC,aAAa,CACjD,MAAM,EACN,IAAI,CAAC,aAAa,EAClB,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;oBAC3B,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,0BAA0B,CAAC,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;oBAChF,CAAC,CAAC,SAAS,EACZ,EAAE,YAAY,EAAE,MAAM,EAAE,CACxB,CAAC;gBAEF,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAClC,KAAK,EACL,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAW,EAC7C,SAAS,EACT,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;oBAC3B,CAAC,CAAC;wBACA;4BACC,QAAQ,EAAE,0BAA0B,CAAC,cAAyB;4BAC9D,KAAK,EAAE,YAAY;yBACnB;qBACD;oBACF,CAAC,CAAC,EAAE,CACL,CAAC;gBACF,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;oBACxB,KAAK,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACP,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACtB,CAAC;YACF,CAAC;YAED,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK;QACjB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;gBAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;gBACrC,MAAM,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;oBAC7C,CAAC,CAAC,KAAK,CAAC,MAAM,CACZ,IAAI,CAAC,EAAE,CACN,YAAY,CAAC,WAAW,CACvB,IAAc,EACd,0BAA0B,CAAC,cAAc,CACzC,KAAK,YAAY,CACnB;oBACF,CAAC,CAAC,EAAE,CAAC;gBACN,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CAAC,0BAA0B,CAAC,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9F,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,WAAW,CAAC,GAAa;QACrC,MAAM,CAAC,UAAU,CAAC,0BAA0B,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAE3E,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;gBAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;oBACrC,IACC,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC;wBAC5B,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,0BAA0B,CAAC,cAAc,CAAC;4BACxE,YAAY,EACZ,CAAC;wBACF,OAAO,IAAI,CAAC;oBACb,CAAC;oBACD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAW,CAAC,CAAC;gBAC9D,CAAC,CAAC,CAAC;gBACH,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,0BAA0B,CAAC,UAAU,EACrC,mBAAmB,EACnB,SAAS,EACT,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,QAAQ,CAAC,wBAAiC;QACtD,MAAM,WAAW,GAAG,gBAAgB,CAAC,WAAW,CAAoB,wBAAwB,CAAC,CAAC;QAE9F,MAAM,WAAW,EAAE,GAAG,CAAC;YACtB,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;YAC7C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,OAAO,EAAE,kBAAkB;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAE5D,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,MAAM;gBACb,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,eAAe;aACxB,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,WAAW,EAAE,GAAG,CAAC;gBACtB,KAAK,EAAE,OAAO;gBACd,MAAM,EAAE,0BAA0B,CAAC,UAAU;gBAC7C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,OAAO,EAAE,gBAAgB;gBACzB,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC;aAC/B,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CAClB,EAAU,EACV,UAAoD;QAEpD,MAAM,CAAC,WAAW,CAAC,0BAA0B,CAAC,UAAU,QAAc,EAAE,CAAC,CAAC;QAE1E,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YAC9B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YAErC,MAAM,eAAe,GAAG,UAAU,IAAI,EAAE,CAAC;YACzC,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClC,eAAe,CAAC,IAAI,CAAC;oBACpB,QAAQ,EAAE,0BAA0B,CAAC,cAAyB;oBAC9D,KAAK,EAAE,YAAY;iBACnB,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;YAEnE,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBAChB,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACvB,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACI,KAAK,CAAC,KAAK,CACjB,UAA+B,EAC/B,cAGG,EACH,UAAwB,EACxB,MAAe,EACf,KAAc;QAWd,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,mBAAmB,CAAC,sBAAsB,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC/E,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QAEvE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,kBAAkB,GAAyB,EAAE,CAAC;YACpD,UAAU,CAAC,OAAO,UAAgB,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;YACzF,UAAU,CAAC,iBAAiB,CAC3B,0BAA0B,CAAC,UAAU,EACrC,OAAO,EACP,kBAAkB,CAClB,CAAC;QACH,CAAC;QAED,IAAI,WAAW,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAEjD,MAAM,eAAe,GAAuB;YAC3C,UAAU,EAAE,EAAE;YACd,eAAe,EAAE,eAAe,CAAC,GAAG;SACpC,CAAC;QAEF,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;YAClC,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC;gBAC/B,QAAQ,EAAE,0BAA0B,CAAC,cAAc;gBACnD,UAAU,EAAE,kBAAkB,CAAC,MAAM;gBACrC,KAAK,EAAE,YAAY;aACnB,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3F,CAAC;QAED,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,KAAK,IAAI,0BAA0B,CAAC,cAAc,CAAC;QACtE,IAAI,UAA8B,CAAC;QAEnC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,kBAAkB,CAAC,mBAAmB,CAC3D,IAAI,CAAC,aAAa,EAClB,cAAc,CACd,CAAC;YACF,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;YAE5D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE9C,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtD,IACC,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;oBACvD,QAAQ,CAAC,MAAM,GAAG,UAAU,EAC3B,CAAC;oBACF,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;wBACvC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;wBAC/C,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;oBAClB,QAAQ,CAAC,IAAI,CACZ,mBAAmB,CAAC,eAAe,CAAI,MAAM,EAAE;wBAC9C,0BAA0B,CAAC,cAAc;qBACzC,CAAC,CACF,CAAC;oBACF,IAAI,QAAQ,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;wBACnC,IAAI,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;4BAChC,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;wBACjC,CAAC;wBACD,MAAM;oBACP,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO;YACN,QAAQ;YACR,MAAM,EAAE,UAAU;SAClB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,KAAK,CAAC,UAA+B;QACjD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE7C,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,eAAe,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAE/F,MAAM,eAAe,GAAuB;YAC3C,UAAU,EAAE,EAAE;YACd,eAAe,EAAE,eAAe,CAAC,GAAG;SACpC,CAAC;QAEF,IAAI,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;YAClC,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC;gBAC/B,QAAQ,EAAE,0BAA0B,CAAC,cAAc;gBACnD,UAAU,EAAE,kBAAkB,CAAC,MAAM;gBACrC,KAAK,EAAE,YAAY;aACnB,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3F,CAAC;QAED,IAAI,eAAe,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC;IACnF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,sBAAsB;QAClC,MAAM,UAAU,GAAkC,EAAE,CAAC;QAErD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAE7C,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,YAAY,CAAC,WAAW,CAC3C,MAAM,EACN,0BAA0B,CAAC,cAAc,CACzC,CAAC;YACF,IAAI,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;gBACjC,UAAU,CAAC,WAAW,CAAC,GAAG,eAAe,CAAC,UAAU,CACnD,IAAI,CAAC,oBAAoB,IAAI,EAAE,EAC/B,WAAW,CACX,CAAC;YACH,CAAC;QACF,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,qBAAqB,CACjC,eAAuB;QAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,cAAc,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAEjF,OAAO,IAAI,0BAA0B,CAAI;YACxC,YAAY,EAAE,eAAe;YAC7B,mBAAmB,EAAE,IAAI,CAAC,oBAAoB;YAC9C,MAAM,EAAE;gBACP,SAAS,EAAE,YAAY;gBACvB,uBAAuB,EAAE,IAAI,CAAC,wBAAwB;gBACtD,yBAAyB,EAAE,IAAI,CAAC,0BAA0B;aAC1D;SACD,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,iBAAiB,CAC7B,eAA8C,EAC9C,OAA2B,EAC3B,oBAA6B;QAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC;QACpC,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC;QAEhD,yEAAyE;QACzE,MAAM,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;QAE1C,6DAA6D;QAC7D,MAAM,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAExC,OAAO,IAAI,0BAA0B,CAAI;YACxC,YAAY,EAAE,eAAe,CAAC,iBAAiB;YAC/C,mBAAmB,EAAE,eAAe,CAAC,oBAAoB;YACzD,MAAM,EAAE;gBACP,SAAS,EAAE,IAAI,CAAC,UAAU;gBAC1B,uBAAuB,EAAE,eAAe,CAAC,wBAAwB;gBACjE,yBAAyB,EAAE,eAAe,CAAC,0BAA0B;aACrE;SACD,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,gBAAgB,CAC5B,eAAuD,EACvD,OAA2B,EAC3B,oBAA6B;QAE7B,MAAM,eAAe,EAAE,QAAQ,EAAE,CAAC,oBAAoB,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,iBAAiB;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,SAAS;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAE1D,IAAI,KAAK,CAAC;QACV,IAAI,CAAC;YACJ,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,YAAY,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxD,sEAAsE;gBACtE,OAAO,EAAE,CAAC;YACX,CAAC;YACD,MAAM,IAAI,YAAY,CACrB,0BAA0B,CAAC,UAAU,EACrC,iBAAiB,EACjB,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAC9B,GAAG,CACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACJ,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAQ,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,0BAA0B,CAAC,UAAU,EACrC,kBAAkB,EAClB,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAC9B,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,UAAU,CAAC,KAAU;QAClC,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;YAC1D,MAAM,YAAY,GAAG,GAAG,QAAQ,MAAM,CAAC;YACvC,MAAM,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;YAC9E,MAAM,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CACrB,0BAA0B,CAAC,UAAU,EACrC,kBAAkB,EAClB,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAC9B,GAAG,CACH,CAAC;QACH,CAAC;IACF,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,QAAQ,CAAI,MAAwB;QACjD,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACjC,SAAS,EAAE,IAAI,CAAC,cAAc;YAC9B,cAAc,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,OAAO,MAAM,MAAM,EAAE,CAAC;QACvB,CAAC;gBAAS,CAAC;YACV,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,SAAS,CAAC,GAAW;QAClC,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAED;;;;;;;;OAQG;IACK,QAAQ,CACf,KAAU,EACV,EAAU,EACV,cAAwB,EACxB,UAAoD;QAEpD,MAAM,eAAe,GAAyB,EAAE,CAAC;QAEjD,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/B,eAAe,CAAC,IAAI,CAAC;gBACpB,QAAQ,EAAE,cAAwB;gBAClC,UAAU,EAAE,kBAAkB,CAAC,MAAM;gBACrC,KAAK,EAAE,EAAE;aACT,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,4FAA4F;YAC5F,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,eAAe,CAAC,IAAI,CAAC;oBACpB,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,QAAkB;oBAC7C,UAAU,EAAE,kBAAkB,CAAC,MAAM;oBACrC,KAAK,EAAE,EAAE;iBACT,CAAC,CAAC;YACJ,CAAC;YACD,eAAe,CAAC,IAAI,CACnB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvB,QAAQ,EAAE,CAAC,CAAC,QAAkB;gBAC9B,UAAU,EAAE,kBAAkB,CAAC,MAAM;gBACrC,KAAK,EAAE,CAAC,CAAC,KAAK;aACd,CAAC,CAAC,CACH,CAAC;QACH,CAAC;QAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACvC,IAAI,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;oBACvE,OAAO,CAAC,CAAC;gBACV,CAAC;YACF,CAAC;QACF,CAAC;aAAM,CAAC;YACP,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;IACX,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { access, mkdir, readFile, rename, rm, statfs, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ContextIdHelper, ContextIdStore, type IContextIds } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tGeneralError,\n\tGuards,\n\tHealthStatus,\n\ttype IHealth,\n\tIs,\n\ttype IValidationFailure,\n\tMutex,\n\tObjectHelper,\n\tValidation\n} from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\ttype EntityCondition,\n\tEntityConditions,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\tEntitySorter,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\tLogicalOperator,\n\ttype SortDirection\n} from \"@twin.org/entity\";\nimport {\n\tEntityStorageHelper,\n\ttype IEntityStorageConnector,\n\ttype IEntityStorageMigrationConnector,\n\ttype IMigrationOptions\n} from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IFileEntityStorageConnectorConstructorOptions } from \"./models/IFileEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations in file.\n */\nexport class FileEntityStorageConnector<\n\tT = unknown\n> implements IEntityStorageMigrationConnector<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<FileEntityStorageConnector>();\n\n\t/**\n\t * Default limit for number of items to return.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_LIMIT: number = 20;\n\n\t/**\n\t * Partition key for the operation.\n\t * @internal\n\t */\n\tprivate static readonly _PARTITION_KEY: string = \"partitionId\";\n\n\t/**\n\t * Default disk space warning threshold: 500 MB.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_DISK_WARNING_THRESHOLD_BYTES: number = 500 * 1024 * 1024;\n\n\t/**\n\t * Default disk space error threshold: 100 MB.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_DISK_ERROR_THRESHOLD_BYTES: number = 100 * 1024 * 1024;\n\n\t/**\n\t * Maximum time to wait for the store update lock.\n\t * @internal\n\t */\n\tprivate static readonly _UPDATE_LOCK_TIMEOUT_MS: number = 60000;\n\n\t/**\n\t * The name for the schema.\n\t * @internal\n\t */\n\tprivate readonly _entitySchemaName: string;\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 keys to use from the context ids to create partitions.\n\t * @internal\n\t */\n\tprivate readonly _partitionContextIds?: string[];\n\n\t/**\n\t * The primary key.\n\t * @internal\n\t */\n\tprivate readonly _primaryKey: IEntitySchemaProperty<T>;\n\n\t/**\n\t * The directory to use for storage.\n\t * @internal\n\t */\n\tprivate readonly _directory: string;\n\n\t/**\n\t * Free bytes below which health reports an error.\n\t * @internal\n\t */\n\tprivate readonly _diskErrorThresholdBytes: number;\n\n\t/**\n\t * Free bytes below which health reports a warning.\n\t * @internal\n\t */\n\tprivate readonly _diskWarningThresholdBytes: number;\n\n\t/**\n\t * Milliseconds to wait for the directory lock before throwing.\n\t * @internal\n\t */\n\tprivate readonly _lockTimeoutMs: number;\n\n\t/**\n\t * Create a new instance of FileEntityStorageConnector.\n\t * @param options The options for the connector.\n\t */\n\tconstructor(options: IFileEntityStorageConnectorConstructorOptions) {\n\t\tGuards.object(FileEntityStorageConnector.CLASS_NAME, nameof(options), options);\n\t\tGuards.stringValue(\n\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.entitySchema),\n\t\t\toptions.entitySchema\n\t\t);\n\t\tGuards.object(FileEntityStorageConnector.CLASS_NAME, nameof(options.config), options.config);\n\t\tGuards.stringValue(\n\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\tnameof(options.config.directory),\n\t\t\toptions.config.directory\n\t\t);\n\t\tthis._entitySchemaName = options.entitySchema;\n\t\tthis._entitySchema = EntitySchemaFactory.get(options.entitySchema);\n\t\tthis._partitionContextIds = options.partitionContextIds;\n\t\tthis._primaryKey = EntitySchemaHelper.getPrimaryKey<T>(this._entitySchema);\n\t\tthis._directory = path.resolve(options.config.directory);\n\t\tthis._diskErrorThresholdBytes =\n\t\t\toptions.config.diskErrorThresholdBytes ??\n\t\t\tFileEntityStorageConnector._DEFAULT_DISK_ERROR_THRESHOLD_BYTES;\n\t\tthis._diskWarningThresholdBytes =\n\t\t\toptions.config.diskWarningThresholdBytes ??\n\t\t\tFileEntityStorageConnector._DEFAULT_DISK_WARNING_THRESHOLD_BYTES;\n\t\tthis._lockTimeoutMs =\n\t\t\toptions.config.lockTimeoutMs ?? FileEntityStorageConnector._UPDATE_LOCK_TIMEOUT_MS;\n\t}\n\n\t/**\n\t * Bootstrap the connector by creating and initializing any resources it needs.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the bootstrapping process was successful.\n\t */\n\tpublic async bootstrap(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tif (!(await this.dirExists(this._directory))) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"directoryCreating\",\n\t\t\t\tdata: {\n\t\t\t\t\tdirectory: this._directory\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait mkdir(this._directory, { recursive: true });\n\n\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tmessage: \"directoryCreated\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdirectory: this._directory\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tawait nodeLogging?.log({\n\t\t\t\t\tlevel: \"error\",\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tmessage: \"directoryCreateFailed\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tdirectory: this._directory\n\t\t\t\t\t},\n\t\t\t\t\terror: BaseError.fromError(err)\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tmessage: \"directoryExists\",\n\t\t\t\tdata: {\n\t\t\t\t\tdirectory: this._directory\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\treturn true;\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 FileEntityStorageConnector.CLASS_NAME;\n\t}\n\n\t/**\n\t * Returns the health status of the component.\n\t * @returns The health status of the component, can return multiple entries for elements within the component.\n\t */\n\tpublic async health(): Promise<IHealth[]> {\n\t\ttry {\n\t\t\tconst stats = await statfs(this._directory);\n\t\t\tconst freeBytes = stats.bavail * stats.bsize;\n\n\t\t\tif (freeBytes < this._diskErrorThresholdBytes) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\t\tmessage: \"diskSpaceError\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tdirectory: this._directory,\n\t\t\t\t\t\t\tfreeBytes,\n\t\t\t\t\t\t\tthresholdBytes: this._diskErrorThresholdBytes\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t} else if (freeBytes < this._diskWarningThresholdBytes) {\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\t\tstatus: HealthStatus.Warning,\n\t\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\t\tmessage: \"diskSpaceWarning\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tdirectory: this._directory,\n\t\t\t\t\t\t\tfreeBytes,\n\t\t\t\t\t\t\tthresholdBytes: this._diskWarningThresholdBytes\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Ok,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tdata: { directory: this._directory, freeBytes }\n\t\t\t\t}\n\t\t\t];\n\t\t} catch {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\tstatus: HealthStatus.Error,\n\t\t\t\t\tdescription: \"healthDescription\",\n\t\t\t\t\tmessage: \"diskSpaceCheckFailed\",\n\t\t\t\t\tdata: { directory: this._directory }\n\t\t\t\t}\n\t\t\t];\n\t\t}\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 * 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(FileEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst store = await this.readStoreWithLock();\n\n\t\tconst finalConditions = conditions ?? [];\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tconst index = this.findItem(store, id, secondaryIndex, finalConditions);\n\t\tconst item = index >= 0 ? store[index] : undefined;\n\n\t\tif (Is.objectValue(item)) {\n\t\t\treturn EntityStorageHelper.unPrepareEntity<T>(item, [\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t]);\n\t\t}\n\n\t\treturn undefined;\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>(FileEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst prepared = EntityStorageHelper.prepareEntity(\n\t\t\tentity,\n\t\t\tthis._entitySchema,\n\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]\n\t\t\t\t: undefined,\n\t\t\t{ nullBehavior: \"omit\" }\n\t\t);\n\n\t\tawait this.withLock(async () => {\n\t\t\tconst store = await this.readStore();\n\n\t\t\tconst finalConditions = conditions ?? [];\n\t\t\tif (Is.stringValue(partitionKey)) {\n\t\t\t\tfinalConditions.push({\n\t\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\t\tvalue: partitionKey\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst existingIndex = this.findItem(\n\t\t\t\tstore,\n\t\t\t\tprepared[this._primaryKey.property] as string,\n\t\t\t\tundefined,\n\t\t\t\tfinalConditions\n\t\t\t);\n\t\t\tif (existingIndex >= 0) {\n\t\t\t\tstore[existingIndex] = prepared;\n\t\t\t} else {\n\t\t\t\tstore.push(prepared);\n\t\t\t}\n\n\t\t\tawait this.writeStore(store);\n\t\t});\n\t}\n\n\t/**\n\t * Set multiple entities in a batch.\n\t * @param entities The entities to set.\n\t * @returns Nothing.\n\t */\n\tpublic async setBatch(entities: T[]): Promise<void> {\n\t\tGuards.arrayValue(FileEntityStorageConnector.CLASS_NAME, nameof(entities), entities);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tawait this.withLock(async () => {\n\t\t\tconst store = await this.readStore();\n\n\t\t\tfor (const entity of entities) {\n\t\t\t\tGuards.object<T>(FileEntityStorageConnector.CLASS_NAME, nameof(entity), entity);\n\n\t\t\t\tconst prepared = EntityStorageHelper.prepareEntity(\n\t\t\t\t\tentity,\n\t\t\t\t\tthis._entitySchema,\n\t\t\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t\t\t? [{ property: FileEntityStorageConnector._PARTITION_KEY, value: partitionKey }]\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\t{ nullBehavior: \"omit\" }\n\t\t\t\t);\n\n\t\t\t\tconst existingIndex = this.findItem(\n\t\t\t\t\tstore,\n\t\t\t\t\tprepared[this._primaryKey.property] as string,\n\t\t\t\t\tundefined,\n\t\t\t\t\tIs.stringValue(partitionKey)\n\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\t\t\t\t\t\tvalue: partitionKey\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t: []\n\t\t\t\t);\n\t\t\t\tif (existingIndex >= 0) {\n\t\t\t\t\tstore[existingIndex] = prepared;\n\t\t\t\t} else {\n\t\t\t\t\tstore.push(prepared);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this.writeStore(store);\n\t\t});\n\t}\n\n\t/**\n\t * Remove all entities from the storage.\n\t * @returns Nothing.\n\t */\n\tpublic async empty(): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tawait this.withLock(async () => {\n\t\t\t\tconst store = await this.readStore();\n\t\t\t\tconst remaining = Is.stringValue(partitionKey)\n\t\t\t\t\t? store.filter(\n\t\t\t\t\t\t\titem =>\n\t\t\t\t\t\t\t\tObjectHelper.propertyGet(\n\t\t\t\t\t\t\t\t\titem as object,\n\t\t\t\t\t\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t\t\t\t\t\t) !== partitionKey\n\t\t\t\t\t\t)\n\t\t\t\t\t: [];\n\t\t\t\tawait this.writeStore(remaining);\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(FileEntityStorageConnector.CLASS_NAME, \"emptyFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Remove multiple entities by id.\n\t * @param ids The ids of the entities to remove.\n\t * @returns Nothing.\n\t */\n\tpublic async removeBatch(ids: string[]): Promise<void> {\n\t\tGuards.arrayValue(FileEntityStorageConnector.CLASS_NAME, nameof(ids), ids);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\ttry {\n\t\t\tawait this.withLock(async () => {\n\t\t\t\tconst store = await this.readStore();\n\t\t\t\tconst idSet = new Set(ids);\n\t\t\t\tconst remaining = store.filter(item => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tIs.stringValue(partitionKey) &&\n\t\t\t\t\t\tObjectHelper.propertyGet(item, FileEntityStorageConnector._PARTITION_KEY) !==\n\t\t\t\t\t\t\tpartitionKey\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\treturn !idSet.has(item[this._primaryKey.property] as string);\n\t\t\t\t});\n\t\t\t\tawait this.writeStore(remaining);\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"removeBatchFailed\",\n\t\t\t\tundefined,\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Teardown the storage by deleting the underlying store file.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns True if the teardown process was successful.\n\t */\n\tpublic async teardown(nodeLoggingComponentType?: string): Promise<boolean> {\n\t\tconst nodeLogging = ComponentFactory.getIfExists<ILoggingComponent>(nodeLoggingComponentType);\n\n\t\tawait nodeLogging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\tts: Date.now(),\n\t\t\tmessage: \"storeTearingDown\"\n\t\t});\n\n\t\ttry {\n\t\t\tawait rm(this._directory, { recursive: true, force: true });\n\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"storeTornDown\"\n\t\t\t});\n\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tawait nodeLogging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: FileEntityStorageConnector.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"teardownFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t\treturn false;\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(FileEntityStorageConnector.CLASS_NAME, nameof(id), id);\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tawait this.withLock(async () => {\n\t\t\tconst store = await this.readStore();\n\n\t\t\tconst finalConditions = conditions ?? [];\n\t\t\tif (Is.stringValue(partitionKey)) {\n\t\t\t\tfinalConditions.push({\n\t\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY as keyof T,\n\t\t\t\t\tvalue: partitionKey\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst index = this.findItem(store, id, undefined, finalConditions);\n\n\t\t\tif (index >= 0) {\n\t\t\t\tstore.splice(index, 1);\n\t\t\t\tawait this.writeStore(store);\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\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tEntityStorageHelper.validateSortProperties(this._entitySchema, sortProperties);\n\t\tEntityStorageHelper.validateProperties(this._entitySchema, properties);\n\n\t\tif (!Is.empty(limit)) {\n\t\t\tconst validationFailures: IValidationFailure[] = [];\n\t\t\tValidation.integer(nameof(limit), limit, validationFailures, undefined, { minValue: 1 });\n\t\t\tValidation.asValidationError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"query\",\n\t\t\t\tvalidationFailures\n\t\t\t);\n\t\t}\n\n\t\tlet allEntities = await this.readStoreWithLock();\n\n\t\tconst finalConditions: EntityCondition<T> = {\n\t\t\tconditions: [],\n\t\t\tlogicalOperator: LogicalOperator.And\n\t\t};\n\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.conditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tif (!Is.empty(conditions)) {\n\t\t\tfinalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));\n\t\t}\n\n\t\tconst entities = [];\n\t\tconst finalLimit = limit ?? FileEntityStorageConnector._DEFAULT_LIMIT;\n\t\tlet nextCursor: string | undefined;\n\n\t\tif (allEntities.length > 0) {\n\t\t\tconst finalSortKeys = EntitySchemaHelper.buildSortProperties<T>(\n\t\t\t\tthis._entitySchema,\n\t\t\t\tsortProperties\n\t\t\t);\n\t\t\tallEntities = EntitySorter.sort(allEntities, finalSortKeys);\n\n\t\t\tconst startIndex = Coerce.number(cursor) ?? 0;\n\n\t\t\tfor (let i = startIndex; i < allEntities.length; i++) {\n\t\t\t\tif (\n\t\t\t\t\tEntityConditions.check(allEntities[i], finalConditions) &&\n\t\t\t\t\tentities.length < finalLimit\n\t\t\t\t) {\n\t\t\t\t\tconst entity = Is.arrayValue(properties)\n\t\t\t\t\t\t? ObjectHelper.pick(allEntities[i], properties)\n\t\t\t\t\t\t: allEntities[i];\n\t\t\t\t\tentities.push(\n\t\t\t\t\t\tEntityStorageHelper.unPrepareEntity<T>(entity, [\n\t\t\t\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t\tif (entities.length >= finalLimit) {\n\t\t\t\t\t\tif (i < allEntities.length - 1) {\n\t\t\t\t\t\t\tnextCursor = (i + 1).toString();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tentities,\n\t\t\tcursor: nextCursor\n\t\t};\n\t}\n\n\t/**\n\t * Count all the entities which match the conditions.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The total count of entities in the storage.\n\t */\n\tpublic async count(conditions?: EntityCondition<T>): Promise<number> {\n\t\tconst store = await this.readStoreWithLock();\n\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tconst partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);\n\n\t\tconst finalConditions: EntityCondition<T> = {\n\t\t\tconditions: [],\n\t\t\tlogicalOperator: LogicalOperator.And\n\t\t};\n\n\t\tif (Is.stringValue(partitionKey)) {\n\t\t\tfinalConditions.conditions.push({\n\t\t\t\tproperty: FileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: partitionKey\n\t\t\t});\n\t\t}\n\n\t\tif (!Is.empty(conditions)) {\n\t\t\tfinalConditions.conditions.push(EntityStorageHelper.normalizeConditionValues(conditions));\n\t\t}\n\n\t\tif (finalConditions.conditions.length === 0) {\n\t\t\treturn store.length;\n\t\t}\n\n\t\treturn store.filter(item => EntityConditions.check(item, finalConditions)).length;\n\t}\n\n\t/**\n\t * Get a unique list of all the context ids from the storage.\n\t * @returns The list of unique context ids.\n\t */\n\tpublic async getPartitionContextIds(): Promise<IContextIds[]> {\n\t\tconst contextIds: { [id: string]: IContextIds } = {};\n\n\t\tconst store = await this.readStoreWithLock();\n\n\t\tfor (const entity of store) {\n\t\t\tconst partitionId = ObjectHelper.propertyGet(\n\t\t\t\tentity,\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY\n\t\t\t);\n\t\t\tif (Is.stringValue(partitionId)) {\n\t\t\t\tcontextIds[partitionId] = ContextIdHelper.shortSplit(\n\t\t\t\t\tthis._partitionContextIds ?? [],\n\t\t\t\t\tpartitionId\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn Object.values(contextIds);\n\t}\n\n\t/**\n\t * Create the target connector for performing the migration it will use a temporary storage location.\n\t * @param newEntitySchema The name of the new entity schema to create the connector for.\n\t * @returns Connector for performing the migration.\n\t */\n\tpublic async createTargetConnector<U>(\n\t\tnewEntitySchema: string\n\t): Promise<IEntityStorageConnector<U>> {\n\t\tconst baseName = path.basename(this._directory);\n\t\tconst parentDir = path.resolve(this._directory, \"..\");\n\t\tconst migrationDir = path.join(parentDir, `${baseName}_migration_${Date.now()}`);\n\n\t\treturn new FileEntityStorageConnector<U>({\n\t\t\tentitySchema: newEntitySchema,\n\t\t\tpartitionContextIds: this._partitionContextIds,\n\t\t\tconfig: {\n\t\t\t\tdirectory: migrationDir,\n\t\t\t\tdiskErrorThresholdBytes: this._diskErrorThresholdBytes,\n\t\t\t\tdiskWarningThresholdBytes: this._diskWarningThresholdBytes\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Finalize the migration by tearing down the old connector and replacing it with the target connector.\n\t * @param targetConnector The target connector to finalize the migration with.\n\t * @param options The options to control how the migration is finalized.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is finalized.\n\t */\n\tpublic async finalizeMigration<U>(\n\t\ttargetConnector: FileEntityStorageConnector<U>,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<IEntityStorageConnector<U>> {\n\t\tconst originalDir = this._directory;\n\t\tconst migrationDir = targetConnector._directory;\n\n\t\t// Teardown the original connector, removing the entire source directory.\n\t\tawait this.teardown(loggingComponentType);\n\n\t\t// Rename the migration directory into the original location.\n\t\tawait rename(migrationDir, originalDir);\n\n\t\treturn new FileEntityStorageConnector<U>({\n\t\t\tentitySchema: targetConnector._entitySchemaName,\n\t\t\tpartitionContextIds: targetConnector._partitionContextIds,\n\t\t\tconfig: {\n\t\t\t\tdirectory: this._directory,\n\t\t\t\tdiskErrorThresholdBytes: targetConnector._diskErrorThresholdBytes,\n\t\t\t\tdiskWarningThresholdBytes: targetConnector._diskWarningThresholdBytes\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Cleanup the migration if a migration fails or needs to be aborted.\n\t * @param targetConnector The target connector to cleanup the migration with.\n\t * @param options The options to control how the migration is cleaned up.\n\t * @param loggingComponentType The optional component type to use for logging the migration progress.\n\t * @returns A promise that resolves when the migration is cleaned up.\n\t */\n\tpublic async cleanupMigration<U>(\n\t\ttargetConnector: IEntityStorageConnector<U> | undefined,\n\t\toptions?: IMigrationOptions,\n\t\tloggingComponentType?: string\n\t): Promise<void> {\n\t\tawait targetConnector?.teardown?.(loggingComponentType);\n\t}\n\n\t/**\n\t * Read the store from file while holding the directory mutex.\n\t * Use this for standalone reads (get, query, count, getPartitionContextIds) where\n\t * no outer lock is held. Do not call from inside a withLock callback —\n\t * use readStore instead to avoid a re-entrant deadlock.\n\t * @returns The store.\n\t * @internal\n\t */\n\tprivate async readStoreWithLock(): Promise<T[]> {\n\t\treturn this.withLock(async () => this.readStore());\n\t}\n\n\t/**\n\t * Read the store from file without acquiring the directory mutex.\n\t * Must only be called from inside a withLock callback, where the mutex\n\t * is already held. Performing the read under the held lock prevents a race with\n\t * the atomic rename(tmp→store.json) window on Windows that would otherwise\n\t * return ENOENT and be misinterpreted as an empty store.\n\t * @returns The store.\n\t * @internal\n\t */\n\tprivate async readStore(): Promise<T[]> {\n\t\tconst filename = path.join(this._directory, \"store.json\");\n\n\t\tlet store;\n\t\ttry {\n\t\t\tstore = await readFile(filename, \"utf8\");\n\t\t} catch (err) {\n\t\t\tif (ObjectHelper.propertyGet(err, \"code\") === \"ENOENT\") {\n\t\t\t\t// The store has not been written yet, which is valid for a new store.\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tthrow new GeneralError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"readStoreFailed\",\n\t\t\t\t{ directory: this._directory },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(store) as T[];\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"readStoreCorrupt\",\n\t\t\t\t{ directory: this._directory },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Write the store to the file, atomically replacing the previous version so\n\t * a reader can never observe a partially written store.\n\t * @param store The store to write.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async writeStore(store: T[]): Promise<void> {\n\t\ttry {\n\t\t\tconst filename = path.join(this._directory, \"store.json\");\n\t\t\tconst tempFilename = `${filename}.tmp`;\n\t\t\tawait writeFile(tempFilename, JSON.stringify(store, undefined, \"\\t\"), \"utf8\");\n\t\t\tawait rename(tempFilename, filename);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(\n\t\t\t\tFileEntityStorageConnector.CLASS_NAME,\n\t\t\t\t\"writeStoreFailed\",\n\t\t\t\t{ directory: this._directory },\n\t\t\t\terr\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Serialize an update so that concurrent modifications cannot interleave\n\t * their read-modify-write cycles, which would lose updates or tear the\n\t * store file. The mutex is keyed on the storage directory, so every\n\t * connector or worker using the same store serializes with the others.\n\t * @param update The update operation to perform.\n\t * @returns The result of the update.\n\t * @internal\n\t */\n\tprivate async withLock<U>(update: () => Promise<U>): Promise<U> {\n\t\tawait Mutex.lock(this._directory, {\n\t\t\ttimeoutMs: this._lockTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\t\ttry {\n\t\t\treturn await update();\n\t\t} finally {\n\t\t\tMutex.unlock(this._directory);\n\t\t}\n\t}\n\n\t/**\n\t * Check if the dir exists.\n\t * @param dir The directory to check.\n\t * @returns True if the dir exists.\n\t * @internal\n\t */\n\tprivate async dirExists(dir: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(dir);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Find the item in the store.\n\t * @param store The store to search.\n\t * @param id The id to search for.\n\t * @param secondaryIndex The secondary index to search for.\n\t * @param conditions The optional conditions to match for the entities.\n\t * @returns The index of the item if found or -1.\n\t * @internal\n\t */\n\tprivate findItem(\n\t\tstore: T[],\n\t\tid: string,\n\t\tsecondaryIndex?: keyof T,\n\t\tconditions?: { property: keyof T; value: unknown }[]\n\t): number {\n\t\tconst finalConditions: EntityCondition<T>[] = [];\n\n\t\tif (!Is.empty(secondaryIndex)) {\n\t\t\tfinalConditions.push({\n\t\t\t\tproperty: secondaryIndex as string,\n\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\tvalue: id\n\t\t\t});\n\t\t}\n\n\t\tif (Is.arrayValue(conditions)) {\n\t\t\t// If we haven't added a secondary index condition we need to add the primary key condition.\n\t\t\tif (finalConditions.length === 0) {\n\t\t\t\tfinalConditions.push({\n\t\t\t\t\tproperty: this._primaryKey.property as string,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\tvalue: id\n\t\t\t\t});\n\t\t\t}\n\t\t\tfinalConditions.push(\n\t\t\t\t...conditions.map(c => ({\n\t\t\t\t\tproperty: c.property as string,\n\t\t\t\t\tcomparison: ComparisonOperator.Equals,\n\t\t\t\t\tvalue: c.value\n\t\t\t\t}))\n\t\t\t);\n\t\t}\n\n\t\tif (finalConditions.length > 0) {\n\t\t\tfor (let i = 0; i < store.length; i++) {\n\t\t\t\tif (EntityConditions.check(store[i], { conditions: finalConditions })) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn store.findIndex(e => e[this._primaryKey.property] === id);\n\t\t}\n\n\t\treturn -1;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"IFileEntityStorageConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IFileEntityStorageConnectorConfig.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 File Entity Storage Connector.\n */\nexport interface IFileEntityStorageConnectorConfig {\n\t/**\n\t * The directory to use for storage.\n\t */\n\tdirectory: string;\n\n\t/**\n\t * The number of free bytes below which the health check reports an error.\n\t * Defaults to 100 MB.\n\t */\n\tdiskErrorThresholdBytes?: number;\n\n\t/**\n\t * The number of free bytes below which the health check reports a warning.\n\t * Defaults to 500 MB.\n\t */\n\tdiskWarningThresholdBytes?: number;\n}\n"]}
1
+ {"version":3,"file":"IFileEntityStorageConnectorConfig.js","sourceRoot":"","sources":["../../../src/models/IFileEntityStorageConnectorConfig.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 File Entity Storage Connector.\n */\nexport interface IFileEntityStorageConnectorConfig {\n\t/**\n\t * The directory to use for storage.\n\t */\n\tdirectory: string;\n\n\t/**\n\t * The number of free bytes below which the health check reports an error.\n\t * Defaults to 100 MB.\n\t */\n\tdiskErrorThresholdBytes?: number;\n\n\t/**\n\t * The number of free bytes below which the health check reports a warning.\n\t * Defaults to 500 MB.\n\t */\n\tdiskWarningThresholdBytes?: number;\n\n\t/**\n\t * Maximum milliseconds to wait for the per-directory write lock before throwing.\n\t * Defaults to 60 000 ms (1 minute).\n\t */\n\tlockTimeoutMs?: number;\n}\n"]}
@@ -16,4 +16,9 @@ export interface IFileEntityStorageConnectorConfig {
16
16
  * Defaults to 500 MB.
17
17
  */
18
18
  diskWarningThresholdBytes?: number;
19
+ /**
20
+ * Maximum milliseconds to wait for the per-directory write lock before throwing.
21
+ * Defaults to 60 000 ms (1 minute).
22
+ */
23
+ lockTimeoutMs?: number;
19
24
  }
package/docs/changelog.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.3-next.29](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-connector-file-v0.0.3-next.28...entity-storage-connector-file-v0.0.3-next.29) (2026-06-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * serialize and atomically write file entity storage stores ([#132](https://github.com/iotaledger/twin-entity-storage/issues/132)) ([4b65edb](https://github.com/iotaledger/twin-entity-storage/commit/4b65edbceeff1b3d9bee29e5451a2cc78f46f577))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/entity-storage-models bumped from 0.0.3-next.28 to 0.0.3-next.29
16
+ * devDependencies
17
+ * @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.28 to 0.0.3-next.29
18
+
3
19
  ## [0.0.3-next.28](https://github.com/iotaledger/twin-entity-storage/compare/entity-storage-connector-file-v0.0.3-next.27...entity-storage-connector-file-v0.0.3-next.28) (2026-06-12)
4
20
 
5
21
 
@@ -27,3 +27,12 @@ Defaults to 100 MB.
27
27
 
28
28
  The number of free bytes below which the health check reports a warning.
29
29
  Defaults to 500 MB.
30
+
31
+ ***
32
+
33
+ ### lockTimeoutMs? {#locktimeoutms}
34
+
35
+ > `optional` **lockTimeoutMs?**: `number`
36
+
37
+ Maximum milliseconds to wait for the per-directory write lock before throwing.
38
+ Defaults to 60 000 ms (1 minute).
package/locales/en.json CHANGED
@@ -12,8 +12,11 @@
12
12
  "fileEntityStorageConnector": {
13
13
  "directoryCreateFailed": "Creating directory \"{directory}\" failed",
14
14
  "emptyFailed": "Unable to empty entity storage",
15
+ "readStoreCorrupt": "The entity storage store file in directory \"{directory}\" contains corrupt JSON",
16
+ "readStoreFailed": "Unable to read the entity storage store file in directory \"{directory}\"",
15
17
  "removeBatchFailed": "Unable to remove batch of entities",
16
- "teardownFailed": "Unable to teardown entity storage"
18
+ "teardownFailed": "Unable to teardown entity storage",
19
+ "writeStoreFailed": "Unable to write the entity storage store file in directory \"{directory}\""
17
20
  }
18
21
  },
19
22
  "health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/entity-storage-connector-file",
3
- "version": "0.0.3-next.28",
3
+ "version": "0.0.3-next.29",
4
4
  "description": "File-based connector that stores entities on disk for straightforward deployments.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "@twin.org/context": "next",
18
18
  "@twin.org/core": "next",
19
19
  "@twin.org/entity": "next",
20
- "@twin.org/entity-storage-models": "0.0.3-next.28",
20
+ "@twin.org/entity-storage-models": "0.0.3-next.29",
21
21
  "@twin.org/logging-models": "next",
22
22
  "@twin.org/nameof": "next"
23
23
  },