@twin.org/entity-storage-connector-file 0.0.3-next.6 → 0.0.3-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # TWIN Entity Storage Connector File
1
+ # Entity Storage Connector File
2
2
 
3
- Entity Storage connector implementation using file storage.
3
+ This package provides a file-backed backend for persisting entities on local or mounted disks in straightforward environments. It is designed to work with the wider storage ecosystem so applications can keep behaviour consistent across connectors and environments.
4
4
 
5
5
  ## Installation
6
6
 
@@ -57,6 +57,28 @@ export class FileEntityStorageConnector {
57
57
  this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
58
58
  this._directory = path.resolve(options.config.directory);
59
59
  }
60
+ /**
61
+ * Deep-clone condition tree and map `null` to `undefined` on Equals/NotEquals leaves
62
+ * so in-memory evaluation matches SQL-style "IS NULL" / "IS NOT NULL" semantics.
63
+ * @param condition The user-supplied condition (not mutated).
64
+ * @returns A clone safe to pass to {@link EntityConditions.check}.
65
+ * @internal
66
+ */
67
+ static normalizeNullToUndefined(condition) {
68
+ if ("conditions" in condition) {
69
+ return {
70
+ ...condition,
71
+ conditions: condition.conditions.map(c => FileEntityStorageConnector.normalizeNullToUndefined(c))
72
+ };
73
+ }
74
+ const leaf = condition;
75
+ if ((leaf.comparison === ComparisonOperator.Equals ||
76
+ leaf.comparison === ComparisonOperator.NotEquals) &&
77
+ leaf.value === null) {
78
+ return { ...leaf, value: undefined };
79
+ }
80
+ return { ...leaf };
81
+ }
60
82
  /**
61
83
  * Bootstrap the connector by creating and initializing any resources it needs.
62
84
  * @param nodeLoggingComponentType The node logging component type.
@@ -229,7 +251,7 @@ export class FileEntityStorageConnector {
229
251
  });
230
252
  }
231
253
  if (!Is.empty(conditions)) {
232
- finalConditions.conditions.push(conditions);
254
+ finalConditions.conditions.push(FileEntityStorageConnector.normalizeNullToUndefined(conditions));
233
255
  }
234
256
  const entities = [];
235
257
  const finalLimit = limit ?? FileEntityStorageConnector._DEFAULT_LIMIT;
@@ -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, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ContextIdHelper, ContextIdStore } from \"@twin.org/context\";\nimport { BaseError, Coerce, ComponentFactory, Guards, Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\tEntityConditions,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\tEntitySorter,\n\tLogicalOperator,\n\ttype EntityCondition,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\ttype SortDirection\n} from \"@twin.org/entity\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IFileEntityStorageConnectorConstructorOptions } from \"./models/IFileEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations in file.\n */\nexport class FileEntityStorageConnector<T = unknown> implements IEntityStorageConnector<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 * 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 * 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._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}\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 * 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\tObjectHelper.propertyDelete(item, FileEntityStorageConnector._PARTITION_KEY);\n\t\t}\n\n\t\treturn item;\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\tEntitySchemaHelper.validateEntity(entity, this.getSchema());\n\n\t\tconst store = await this.readStore();\n\n\t\tconst finalEntity = ObjectHelper.clone(entity);\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\tObjectHelper.propertySet(\n\t\t\t\tfinalEntity,\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tpartitionKey\n\t\t\t);\n\t\t}\n\n\t\tconst existingIndex = this.findItem(\n\t\t\tstore,\n\t\t\tfinalEntity[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] = finalEntity;\n\t\t} else {\n\t\t\tstore.push(finalEntity);\n\t\t}\n\n\t\tawait this.writeStore(store);\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\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(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 = ObjectHelper.pick(allEntities[i], properties);\n\t\t\t\t\tObjectHelper.propertyDelete(entity, FileEntityStorageConnector._PARTITION_KEY);\n\t\t\t\t\tentities.push(entity);\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 * 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":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { ContextIdHelper, ContextIdStore } from \"@twin.org/context\";\nimport { BaseError, Coerce, ComponentFactory, Guards, Is, ObjectHelper } from \"@twin.org/core\";\nimport {\n\tComparisonOperator,\n\tEntityConditions,\n\tEntitySchemaFactory,\n\tEntitySchemaHelper,\n\tEntitySorter,\n\tLogicalOperator,\n\ttype EntityCondition,\n\ttype IEntitySchema,\n\ttype IEntitySchemaProperty,\n\ttype SortDirection\n} from \"@twin.org/entity\";\nimport type { IEntityStorageConnector } from \"@twin.org/entity-storage-models\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IFileEntityStorageConnectorConstructorOptions } from \"./models/IFileEntityStorageConnectorConstructorOptions.js\";\n\n/**\n * Class for performing entity storage operations in file.\n */\nexport class FileEntityStorageConnector<T = unknown> implements IEntityStorageConnector<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 * 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 * 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._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}\n\n\t/**\n\t * Deep-clone condition tree and map `null` to `undefined` on Equals/NotEquals leaves\n\t * so in-memory evaluation matches SQL-style \"IS NULL\" / \"IS NOT NULL\" semantics.\n\t * @param condition The user-supplied condition (not mutated).\n\t * @returns A clone safe to pass to {@link EntityConditions.check}.\n\t * @internal\n\t */\n\tprivate static normalizeNullToUndefined<T>(condition: EntityCondition<T>): EntityCondition<T> {\n\t\tif (\"conditions\" in condition) {\n\t\t\treturn {\n\t\t\t\t...condition,\n\t\t\t\tconditions: condition.conditions.map(c =>\n\t\t\t\t\tFileEntityStorageConnector.normalizeNullToUndefined(c)\n\t\t\t\t)\n\t\t\t};\n\t\t}\n\n\t\tconst leaf = condition;\n\t\tif (\n\t\t\t(leaf.comparison === ComparisonOperator.Equals ||\n\t\t\t\tleaf.comparison === ComparisonOperator.NotEquals) &&\n\t\t\tleaf.value === null\n\t\t) {\n\t\t\treturn { ...leaf, value: undefined };\n\t\t}\n\t\treturn { ...leaf };\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 * 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\tObjectHelper.propertyDelete(item, FileEntityStorageConnector._PARTITION_KEY);\n\t\t}\n\n\t\treturn item;\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\tEntitySchemaHelper.validateEntity(entity, this.getSchema());\n\n\t\tconst store = await this.readStore();\n\n\t\tconst finalEntity = ObjectHelper.clone(entity);\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\tObjectHelper.propertySet(\n\t\t\t\tfinalEntity,\n\t\t\t\tFileEntityStorageConnector._PARTITION_KEY,\n\t\t\t\tpartitionKey\n\t\t\t);\n\t\t}\n\n\t\tconst existingIndex = this.findItem(\n\t\t\tstore,\n\t\t\tfinalEntity[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] = finalEntity;\n\t\t} else {\n\t\t\tstore.push(finalEntity);\n\t\t}\n\n\t\tawait this.writeStore(store);\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\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(\n\t\t\t\tFileEntityStorageConnector.normalizeNullToUndefined(conditions)\n\t\t\t);\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 = ObjectHelper.pick(allEntities[i], properties);\n\t\t\t\t\tObjectHelper.propertyDelete(entity, FileEntityStorageConnector._PARTITION_KEY);\n\t\t\t\t\tentities.push(entity);\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 * 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"]}
package/docs/changelog.md CHANGED
@@ -1,4 +1,36 @@
1
- # @twin.org/entity-storage-connector-file - Changelog
1
+ # Changelog
2
+
3
+ ## [0.0.3-next.8](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.3-next.7...entity-storage-connector-file-v0.0.3-next.8) (2026-03-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * tests and fixes for the comparisons for null and undefined ([#79](https://github.com/twinfoundation/entity-storage/issues/79)) ([e7ffd62](https://github.com/twinfoundation/entity-storage/commit/e7ffd62e9ec40ef31498e6e2350bb25d9c84638a))
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.7 to 0.0.3-next.8
16
+ * devDependencies
17
+ * @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.7 to 0.0.3-next.8
18
+
19
+ ## [0.0.3-next.7](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.3-next.6...entity-storage-connector-file-v0.0.3-next.7) (2026-03-13)
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * adding tests and fixes for dot notation ([#76](https://github.com/twinfoundation/entity-storage/issues/76)) ([3879337](https://github.com/twinfoundation/entity-storage/commit/387933797e33543e4d8b2d49b8beeb792512a4ff))
25
+
26
+
27
+ ### Dependencies
28
+
29
+ * The following workspace dependencies were updated
30
+ * dependencies
31
+ * @twin.org/entity-storage-models bumped from 0.0.3-next.6 to 0.0.3-next.7
32
+ * devDependencies
33
+ * @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.6 to 0.0.3-next.7
2
34
 
3
35
  ## [0.0.3-next.6](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.3-next.5...entity-storage-connector-file-v0.0.3-next.6) (2026-01-21)
4
36
 
package/docs/examples.md CHANGED
@@ -1 +1,69 @@
1
- # @twin.org/entity-storage-connector-file - Examples
1
+ # Entity Storage Connector File Examples
2
+
3
+ Use this page to see local-file storage workflows for bootstrapping, entity lifecycle operations, and filtered querying.
4
+
5
+ ## FileEntityStorageConnector
6
+
7
+ ```typescript
8
+ import {
9
+ FileEntityStorageConnector,
10
+ type IFileEntityStorageConnectorConstructorOptions
11
+ } from '@twin.org/entity-storage-connector-file';
12
+ import {
13
+ ComparisonOperator,
14
+ LogicalOperator,
15
+ SortDirection,
16
+ type EntityCondition
17
+ } from '@twin.org/entity';
18
+
19
+ interface Profile {
20
+ id: string;
21
+ email: string;
22
+ status: 'active' | 'inactive';
23
+ createdAt: string;
24
+ }
25
+
26
+ const options: IFileEntityStorageConnectorConstructorOptions = {
27
+ entitySchema: 'Profile',
28
+ config: {
29
+ directory: './data/profiles'
30
+ }
31
+ };
32
+
33
+ const connector = new FileEntityStorageConnector<Profile>(options);
34
+ await connector.bootstrap();
35
+
36
+ const className = connector.className();
37
+ const schema = connector.getSchema();
38
+
39
+ await connector.set({
40
+ id: 'profile-1',
41
+ email: 'ada@example.com',
42
+ status: 'active',
43
+ createdAt: '2026-03-09T10:30:00.000Z'
44
+ });
45
+
46
+ const byPrimaryKey = await connector.get('profile-1');
47
+ const bySecondaryIndex = await connector.get('ada@example.com', 'email');
48
+
49
+ const activeCondition: EntityCondition<Profile> = {
50
+ logicalOperator: LogicalOperator.And,
51
+ conditions: [
52
+ {
53
+ property: 'status',
54
+ comparison: ComparisonOperator.Equals,
55
+ value: 'active'
56
+ }
57
+ ]
58
+ };
59
+
60
+ const result = await connector.query(
61
+ activeCondition,
62
+ [{ property: 'createdAt', sortDirection: SortDirection.Descending }],
63
+ ['id', 'email', 'status'],
64
+ undefined,
65
+ 25
66
+ );
67
+
68
+ await connector.remove('profile-1');
69
+ ```
@@ -34,7 +34,7 @@ The options for the connector.
34
34
 
35
35
  ## Properties
36
36
 
37
- ### CLASS\_NAME
37
+ ### CLASS\_NAME {#class_name}
38
38
 
39
39
  > `readonly` `static` **CLASS\_NAME**: `string`
40
40
 
@@ -42,7 +42,7 @@ Runtime name for the class.
42
42
 
43
43
  ## Methods
44
44
 
45
- ### bootstrap()
45
+ ### bootstrap() {#bootstrap}
46
46
 
47
47
  > **bootstrap**(`nodeLoggingComponentType?`): `Promise`\<`boolean`\>
48
48
 
@@ -68,7 +68,7 @@ True if the bootstrapping process was successful.
68
68
 
69
69
  ***
70
70
 
71
- ### className()
71
+ ### className() {#classname}
72
72
 
73
73
  > **className**(): `string`
74
74
 
@@ -86,7 +86,7 @@ The class name of the component.
86
86
 
87
87
  ***
88
88
 
89
- ### getSchema()
89
+ ### getSchema() {#getschema}
90
90
 
91
91
  > **getSchema**(): `IEntitySchema`
92
92
 
@@ -104,7 +104,7 @@ The schema for the entities.
104
104
 
105
105
  ***
106
106
 
107
- ### get()
107
+ ### get() {#get}
108
108
 
109
109
  > **get**(`id`, `secondaryIndex?`, `conditions?`): `Promise`\<`T` \| `undefined`\>
110
110
 
@@ -142,7 +142,7 @@ The object if it can be found or undefined.
142
142
 
143
143
  ***
144
144
 
145
- ### set()
145
+ ### set() {#set}
146
146
 
147
147
  > **set**(`entity`, `conditions?`): `Promise`\<`void`\>
148
148
 
@@ -174,7 +174,7 @@ The id of the entity.
174
174
 
175
175
  ***
176
176
 
177
- ### remove()
177
+ ### remove() {#remove}
178
178
 
179
179
  > **remove**(`id`, `conditions?`): `Promise`\<`void`\>
180
180
 
@@ -206,7 +206,7 @@ Nothing.
206
206
 
207
207
  ***
208
208
 
209
- ### query()
209
+ ### query() {#query}
210
210
 
211
211
  > **query**(`conditions?`, `sortProperties?`, `properties?`, `cursor?`, `limit?`): `Promise`\<\{ `entities`: `Partial`\<`T`\>[]; `cursor?`: `string`; \}\>
212
212
 
@@ -4,7 +4,7 @@ Configuration for the File Entity Storage Connector.
4
4
 
5
5
  ## Properties
6
6
 
7
- ### directory
7
+ ### directory {#directory}
8
8
 
9
9
  > **directory**: `string`
10
10
 
@@ -4,7 +4,7 @@ Options for the File Entity Storage Connector constructor.
4
4
 
5
5
  ## Properties
6
6
 
7
- ### entitySchema
7
+ ### entitySchema {#entityschema}
8
8
 
9
9
  > **entitySchema**: `string`
10
10
 
@@ -12,15 +12,15 @@ The name of the entity schema.
12
12
 
13
13
  ***
14
14
 
15
- ### partitionContextIds?
15
+ ### partitionContextIds? {#partitioncontextids}
16
16
 
17
- > `optional` **partitionContextIds**: `string`[]
17
+ > `optional` **partitionContextIds?**: `string`[]
18
18
 
19
19
  The keys to use from the context ids to create partitions.
20
20
 
21
21
  ***
22
22
 
23
- ### config
23
+ ### config {#config}
24
24
 
25
25
  > **config**: [`IFileEntityStorageConnectorConfig`](IFileEntityStorageConnectorConfig.md)
26
26
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@twin.org/entity-storage-connector-file",
3
- "version": "0.0.3-next.6",
4
- "description": "Entity Storage connector implementation using file storage",
3
+ "version": "0.0.3-next.8",
4
+ "description": "File-based connector that stores entities on disk for straightforward deployments.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/twinfoundation/entity-storage.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.6",
20
+ "@twin.org/entity-storage-models": "0.0.3-next.8",
21
21
  "@twin.org/logging-models": "next",
22
22
  "@twin.org/nameof": "next"
23
23
  },