@twin.org/entity-storage-connector-file 0.0.2-next.9 → 0.0.3-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{esm/index.mjs → es/fileEntityStorageConnector.js} +105 -36
- package/dist/es/fileEntityStorageConnector.js.map +1 -0
- package/dist/es/index.js +6 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/IFileEntityStorageConnectorConfig.js +4 -0
- package/dist/es/models/IFileEntityStorageConnectorConfig.js.map +1 -0
- package/dist/es/models/IFileEntityStorageConnectorConstructorOptions.js +2 -0
- package/dist/es/models/IFileEntityStorageConnectorConstructorOptions.js.map +1 -0
- package/dist/types/fileEntityStorageConnector.d.ts +10 -5
- package/dist/types/index.d.ts +3 -3
- package/dist/types/models/IFileEntityStorageConnectorConstructorOptions.d.ts +5 -1
- package/docs/changelog.md +60 -0
- package/docs/reference/classes/FileEntityStorageConnector.md +24 -10
- package/docs/reference/interfaces/IFileEntityStorageConnectorConstructorOptions.md +8 -0
- package/package.json +12 -9
- package/dist/cjs/index.cjs +0 -283
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile, access } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Guards, ComponentFactory, BaseError, Coerce, ObjectHelper, Is } from '@twin.org/core';
|
|
4
|
-
import { EntitySchemaFactory, EntitySchemaHelper, EntitySorter, EntityConditions, ComparisonOperator } from '@twin.org/entity';
|
|
5
|
-
|
|
6
1
|
// Copyright 2024 IOTA Stiftung.
|
|
7
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
|
|
6
|
+
import { BaseError, Coerce, ComponentFactory, Guards, Is, ObjectHelper } from "@twin.org/core";
|
|
7
|
+
import { ComparisonOperator, EntityConditions, EntitySchemaFactory, EntitySchemaHelper, EntitySorter, LogicalOperator } from "@twin.org/entity";
|
|
8
8
|
/**
|
|
9
9
|
* Class for performing entity storage operations in file.
|
|
10
10
|
*/
|
|
11
|
-
class FileEntityStorageConnector {
|
|
11
|
+
export class FileEntityStorageConnector {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Runtime name for the class.
|
|
14
|
+
*/
|
|
15
|
+
static CLASS_NAME = "FileEntityStorageConnector";
|
|
16
|
+
/**
|
|
17
|
+
* Default limit for number of items to return.
|
|
14
18
|
* @internal
|
|
15
19
|
*/
|
|
16
|
-
static
|
|
20
|
+
static _DEFAULT_LIMIT = 20;
|
|
17
21
|
/**
|
|
18
|
-
*
|
|
22
|
+
* Partition key for the operation.
|
|
23
|
+
* @internal
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
static _PARTITION_KEY = "partitionId";
|
|
21
26
|
/**
|
|
22
27
|
* The schema for the entity.
|
|
23
28
|
* @internal
|
|
24
29
|
*/
|
|
25
30
|
_entitySchema;
|
|
31
|
+
/**
|
|
32
|
+
* The keys to use from the context ids to create partitions.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
_partitionContextIds;
|
|
26
36
|
/**
|
|
27
37
|
* The primary key.
|
|
28
38
|
* @internal
|
|
@@ -38,11 +48,12 @@ class FileEntityStorageConnector {
|
|
|
38
48
|
* @param options The options for the connector.
|
|
39
49
|
*/
|
|
40
50
|
constructor(options) {
|
|
41
|
-
Guards.object(
|
|
42
|
-
Guards.stringValue(
|
|
43
|
-
Guards.object(
|
|
44
|
-
Guards.stringValue(
|
|
51
|
+
Guards.object(FileEntityStorageConnector.CLASS_NAME, "options", options);
|
|
52
|
+
Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
|
|
53
|
+
Guards.object(FileEntityStorageConnector.CLASS_NAME, "options.config", options.config);
|
|
54
|
+
Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "options.config.directory", options.config.directory);
|
|
45
55
|
this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
|
|
56
|
+
this._partitionContextIds = options.partitionContextIds;
|
|
46
57
|
this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
47
58
|
this._directory = path.resolve(options.config.directory);
|
|
48
59
|
}
|
|
@@ -56,7 +67,7 @@ class FileEntityStorageConnector {
|
|
|
56
67
|
if (!(await this.dirExists(this._directory))) {
|
|
57
68
|
await nodeLogging?.log({
|
|
58
69
|
level: "info",
|
|
59
|
-
source:
|
|
70
|
+
source: FileEntityStorageConnector.CLASS_NAME,
|
|
60
71
|
message: "directoryCreating",
|
|
61
72
|
data: {
|
|
62
73
|
directory: this._directory
|
|
@@ -66,7 +77,7 @@ class FileEntityStorageConnector {
|
|
|
66
77
|
await mkdir(this._directory, { recursive: true });
|
|
67
78
|
await nodeLogging?.log({
|
|
68
79
|
level: "info",
|
|
69
|
-
source:
|
|
80
|
+
source: FileEntityStorageConnector.CLASS_NAME,
|
|
70
81
|
message: "directoryCreated",
|
|
71
82
|
data: {
|
|
72
83
|
directory: this._directory
|
|
@@ -76,7 +87,7 @@ class FileEntityStorageConnector {
|
|
|
76
87
|
catch (err) {
|
|
77
88
|
await nodeLogging?.log({
|
|
78
89
|
level: "error",
|
|
79
|
-
source:
|
|
90
|
+
source: FileEntityStorageConnector.CLASS_NAME,
|
|
80
91
|
message: "directoryCreateFailed",
|
|
81
92
|
data: {
|
|
82
93
|
directory: this._directory
|
|
@@ -89,7 +100,7 @@ class FileEntityStorageConnector {
|
|
|
89
100
|
else {
|
|
90
101
|
await nodeLogging?.log({
|
|
91
102
|
level: "info",
|
|
92
|
-
source:
|
|
103
|
+
source: FileEntityStorageConnector.CLASS_NAME,
|
|
93
104
|
message: "directoryExists",
|
|
94
105
|
data: {
|
|
95
106
|
directory: this._directory
|
|
@@ -98,6 +109,13 @@ class FileEntityStorageConnector {
|
|
|
98
109
|
}
|
|
99
110
|
return true;
|
|
100
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Returns the class name of the component.
|
|
114
|
+
* @returns The class name of the component.
|
|
115
|
+
*/
|
|
116
|
+
className() {
|
|
117
|
+
return FileEntityStorageConnector.CLASS_NAME;
|
|
118
|
+
}
|
|
101
119
|
/**
|
|
102
120
|
* Get the schema for the entities.
|
|
103
121
|
* @returns The schema for the entities.
|
|
@@ -113,10 +131,23 @@ class FileEntityStorageConnector {
|
|
|
113
131
|
* @returns The object if it can be found or undefined.
|
|
114
132
|
*/
|
|
115
133
|
async get(id, secondaryIndex, conditions) {
|
|
116
|
-
Guards.stringValue(
|
|
134
|
+
Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "id", id);
|
|
135
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
136
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
117
137
|
const store = await this.readStore();
|
|
118
|
-
const
|
|
119
|
-
|
|
138
|
+
const finalConditions = conditions ?? [];
|
|
139
|
+
if (Is.stringValue(partitionKey)) {
|
|
140
|
+
finalConditions.push({
|
|
141
|
+
property: FileEntityStorageConnector._PARTITION_KEY,
|
|
142
|
+
value: partitionKey
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const index = this.findItem(store, id, secondaryIndex, finalConditions);
|
|
146
|
+
const item = index >= 0 ? store[index] : undefined;
|
|
147
|
+
if (Is.objectValue(item)) {
|
|
148
|
+
ObjectHelper.propertyDelete(item, FileEntityStorageConnector._PARTITION_KEY);
|
|
149
|
+
}
|
|
150
|
+
return item;
|
|
120
151
|
}
|
|
121
152
|
/**
|
|
122
153
|
* Set an entity.
|
|
@@ -125,15 +156,26 @@ class FileEntityStorageConnector {
|
|
|
125
156
|
* @returns The id of the entity.
|
|
126
157
|
*/
|
|
127
158
|
async set(entity, conditions) {
|
|
128
|
-
Guards.object(
|
|
159
|
+
Guards.object(FileEntityStorageConnector.CLASS_NAME, "entity", entity);
|
|
160
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
161
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
129
162
|
EntitySchemaHelper.validateEntity(entity, this.getSchema());
|
|
130
163
|
const store = await this.readStore();
|
|
131
|
-
const
|
|
164
|
+
const finalEntity = ObjectHelper.clone(entity);
|
|
165
|
+
const finalConditions = conditions ?? [];
|
|
166
|
+
if (Is.stringValue(partitionKey)) {
|
|
167
|
+
finalConditions.push({
|
|
168
|
+
property: FileEntityStorageConnector._PARTITION_KEY,
|
|
169
|
+
value: partitionKey
|
|
170
|
+
});
|
|
171
|
+
ObjectHelper.propertySet(finalEntity, FileEntityStorageConnector._PARTITION_KEY, partitionKey);
|
|
172
|
+
}
|
|
173
|
+
const existingIndex = this.findItem(store, finalEntity[this._primaryKey.property], undefined, finalConditions);
|
|
132
174
|
if (existingIndex >= 0) {
|
|
133
|
-
store[existingIndex] =
|
|
175
|
+
store[existingIndex] = finalEntity;
|
|
134
176
|
}
|
|
135
177
|
else {
|
|
136
|
-
store.push(
|
|
178
|
+
store.push(finalEntity);
|
|
137
179
|
}
|
|
138
180
|
await this.writeStore(store);
|
|
139
181
|
}
|
|
@@ -144,9 +186,18 @@ class FileEntityStorageConnector {
|
|
|
144
186
|
* @returns Nothing.
|
|
145
187
|
*/
|
|
146
188
|
async remove(id, conditions) {
|
|
147
|
-
Guards.stringValue(
|
|
189
|
+
Guards.stringValue(FileEntityStorageConnector.CLASS_NAME, "id", id);
|
|
190
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
191
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
148
192
|
const store = await this.readStore();
|
|
149
|
-
const
|
|
193
|
+
const finalConditions = conditions ?? [];
|
|
194
|
+
if (Is.stringValue(partitionKey)) {
|
|
195
|
+
finalConditions.push({
|
|
196
|
+
property: FileEntityStorageConnector._PARTITION_KEY,
|
|
197
|
+
value: partitionKey
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const index = this.findItem(store, id, undefined, finalConditions);
|
|
150
201
|
if (index >= 0) {
|
|
151
202
|
store.splice(index, 1);
|
|
152
203
|
await this.writeStore(store);
|
|
@@ -157,24 +208,43 @@ class FileEntityStorageConnector {
|
|
|
157
208
|
* @param conditions The conditions to match for the entities.
|
|
158
209
|
* @param sortProperties The optional sort order.
|
|
159
210
|
* @param properties The optional properties to return, defaults to all.
|
|
160
|
-
* @param cursor The cursor to request the next
|
|
161
|
-
* @param
|
|
211
|
+
* @param cursor The cursor to request the next chunk of entities.
|
|
212
|
+
* @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
162
213
|
* @returns All the entities for the storage matching the conditions,
|
|
163
214
|
* and a cursor which can be used to request more entities.
|
|
164
215
|
*/
|
|
165
|
-
async query(conditions, sortProperties, properties, cursor,
|
|
216
|
+
async query(conditions, sortProperties, properties, cursor, limit) {
|
|
217
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
218
|
+
const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
|
|
166
219
|
let allEntities = await this.readStore();
|
|
220
|
+
const finalConditions = {
|
|
221
|
+
conditions: [],
|
|
222
|
+
logicalOperator: LogicalOperator.And
|
|
223
|
+
};
|
|
224
|
+
if (Is.stringValue(partitionKey)) {
|
|
225
|
+
finalConditions.conditions.push({
|
|
226
|
+
property: FileEntityStorageConnector._PARTITION_KEY,
|
|
227
|
+
comparison: ComparisonOperator.Equals,
|
|
228
|
+
value: partitionKey
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (!Is.empty(conditions)) {
|
|
232
|
+
finalConditions.conditions.push(conditions);
|
|
233
|
+
}
|
|
167
234
|
const entities = [];
|
|
168
|
-
const
|
|
235
|
+
const finalLimit = limit ?? FileEntityStorageConnector._DEFAULT_LIMIT;
|
|
169
236
|
let nextCursor;
|
|
170
237
|
if (allEntities.length > 0) {
|
|
171
238
|
const finalSortKeys = EntitySchemaHelper.buildSortProperties(this._entitySchema, sortProperties);
|
|
172
239
|
allEntities = EntitySorter.sort(allEntities, finalSortKeys);
|
|
173
240
|
const startIndex = Coerce.number(cursor) ?? 0;
|
|
174
241
|
for (let i = startIndex; i < allEntities.length; i++) {
|
|
175
|
-
if (EntityConditions.check(allEntities[i],
|
|
176
|
-
entities.
|
|
177
|
-
|
|
242
|
+
if (EntityConditions.check(allEntities[i], finalConditions) &&
|
|
243
|
+
entities.length < finalLimit) {
|
|
244
|
+
const entity = ObjectHelper.pick(allEntities[i], properties);
|
|
245
|
+
ObjectHelper.propertyDelete(entity, FileEntityStorageConnector._PARTITION_KEY);
|
|
246
|
+
entities.push(entity);
|
|
247
|
+
if (entities.length >= finalLimit) {
|
|
178
248
|
if (i < allEntities.length - 1) {
|
|
179
249
|
nextCursor = (i + 1).toString();
|
|
180
250
|
}
|
|
@@ -277,5 +347,4 @@ class FileEntityStorageConnector {
|
|
|
277
347
|
return -1;
|
|
278
348
|
}
|
|
279
349
|
}
|
|
280
|
-
|
|
281
|
-
export { FileEntityStorageConnector };
|
|
350
|
+
//# sourceMappingURL=fileEntityStorageConnector.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
export * from "./fileEntityStorageConnector.js";
|
|
4
|
+
export * from "./models/IFileEntityStorageConnectorConfig.js";
|
|
5
|
+
export * from "./models/IFileEntityStorageConnectorConstructorOptions.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,2DAA2D,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./fileEntityStorageConnector.js\";\nexport * from \"./models/IFileEntityStorageConnectorConfig.js\";\nexport * from \"./models/IFileEntityStorageConnectorConstructorOptions.js\";\n"]}
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IFileEntityStorageConnectorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/IFileEntityStorageConnectorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IFileEntityStorageConnectorConfig } from \"./IFileEntityStorageConnectorConfig.js\";\n\n/**\n * Options for the File Entity Storage Connector constructor.\n */\nexport interface IFileEntityStorageConnectorConstructorOptions {\n\t/**\n\t * The name of the entity schema.\n\t */\n\tentitySchema: string;\n\n\t/**\n\t * The keys to use from the context ids to create partitions.\n\t */\n\tpartitionContextIds?: string[];\n\n\t/**\n\t * The configuration for the connector.\n\t */\n\tconfig: IFileEntityStorageConnectorConfig;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type EntityCondition, type IEntitySchema, type SortDirection } from "@twin.org/entity";
|
|
2
2
|
import type { IEntityStorageConnector } from "@twin.org/entity-storage-models";
|
|
3
|
-
import type { IFileEntityStorageConnectorConstructorOptions } from "./models/IFileEntityStorageConnectorConstructorOptions";
|
|
3
|
+
import type { IFileEntityStorageConnectorConstructorOptions } from "./models/IFileEntityStorageConnectorConstructorOptions.js";
|
|
4
4
|
/**
|
|
5
5
|
* Class for performing entity storage operations in file.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ export declare class FileEntityStorageConnector<T = unknown> implements IEntityS
|
|
|
8
8
|
/**
|
|
9
9
|
* Runtime name for the class.
|
|
10
10
|
*/
|
|
11
|
-
readonly CLASS_NAME: string;
|
|
11
|
+
static readonly CLASS_NAME: string;
|
|
12
12
|
/**
|
|
13
13
|
* Create a new instance of FileEntityStorageConnector.
|
|
14
14
|
* @param options The options for the connector.
|
|
@@ -20,6 +20,11 @@ export declare class FileEntityStorageConnector<T = unknown> implements IEntityS
|
|
|
20
20
|
* @returns True if the bootstrapping process was successful.
|
|
21
21
|
*/
|
|
22
22
|
bootstrap(nodeLoggingComponentType?: string): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the class name of the component.
|
|
25
|
+
* @returns The class name of the component.
|
|
26
|
+
*/
|
|
27
|
+
className(): string;
|
|
23
28
|
/**
|
|
24
29
|
* Get the schema for the entities.
|
|
25
30
|
* @returns The schema for the entities.
|
|
@@ -61,15 +66,15 @@ export declare class FileEntityStorageConnector<T = unknown> implements IEntityS
|
|
|
61
66
|
* @param conditions The conditions to match for the entities.
|
|
62
67
|
* @param sortProperties The optional sort order.
|
|
63
68
|
* @param properties The optional properties to return, defaults to all.
|
|
64
|
-
* @param cursor The cursor to request the next
|
|
65
|
-
* @param
|
|
69
|
+
* @param cursor The cursor to request the next chunk of entities.
|
|
70
|
+
* @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
66
71
|
* @returns All the entities for the storage matching the conditions,
|
|
67
72
|
* and a cursor which can be used to request more entities.
|
|
68
73
|
*/
|
|
69
74
|
query(conditions?: EntityCondition<T>, sortProperties?: {
|
|
70
75
|
property: keyof T;
|
|
71
76
|
sortDirection: SortDirection;
|
|
72
|
-
}[], properties?: (keyof T)[], cursor?: string,
|
|
77
|
+
}[], properties?: (keyof T)[], cursor?: string, limit?: number): Promise<{
|
|
73
78
|
/**
|
|
74
79
|
* The entities, which can be partial if a limited keys list was provided.
|
|
75
80
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "./fileEntityStorageConnector";
|
|
2
|
-
export * from "./models/IFileEntityStorageConnectorConfig";
|
|
3
|
-
export * from "./models/IFileEntityStorageConnectorConstructorOptions";
|
|
1
|
+
export * from "./fileEntityStorageConnector.js";
|
|
2
|
+
export * from "./models/IFileEntityStorageConnectorConfig.js";
|
|
3
|
+
export * from "./models/IFileEntityStorageConnectorConstructorOptions.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IFileEntityStorageConnectorConfig } from "./IFileEntityStorageConnectorConfig";
|
|
1
|
+
import type { IFileEntityStorageConnectorConfig } from "./IFileEntityStorageConnectorConfig.js";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the File Entity Storage Connector constructor.
|
|
4
4
|
*/
|
|
@@ -7,6 +7,10 @@ export interface IFileEntityStorageConnectorConstructorOptions {
|
|
|
7
7
|
* The name of the entity schema.
|
|
8
8
|
*/
|
|
9
9
|
entitySchema: string;
|
|
10
|
+
/**
|
|
11
|
+
* The keys to use from the context ids to create partitions.
|
|
12
|
+
*/
|
|
13
|
+
partitionContextIds?: string[];
|
|
10
14
|
/**
|
|
11
15
|
* The configuration for the connector.
|
|
12
16
|
*/
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# @twin.org/entity-storage-connector-file - Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-next.2](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.3-next.1...entity-storage-connector-file-v0.0.3-next.2) (2025-11-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **entity-storage-connector-file:** Synchronize repo versions
|
|
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.1 to 0.0.3-next.2
|
|
16
|
+
* devDependencies
|
|
17
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.1 to 0.0.3-next.2
|
|
18
|
+
|
|
19
|
+
## [0.0.3-next.1](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.3-next.0...entity-storage-connector-file-v0.0.3-next.1) (2025-11-10)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* add context id features ([#55](https://github.com/twinfoundation/entity-storage/issues/55)) ([99c15a2](https://github.com/twinfoundation/entity-storage/commit/99c15a257539b61d9da63649ce573ebf47699fc9))
|
|
25
|
+
* add production release automation ([1eb4c8e](https://github.com/twinfoundation/entity-storage/commit/1eb4c8ee3eb099defdfc2d063ae44935276dcae8))
|
|
26
|
+
* add validate-locales ([e66ef0d](https://github.com/twinfoundation/entity-storage/commit/e66ef0de26ca2f82b3fe89bb5c7a15a0978a9644))
|
|
27
|
+
* eslint migration to flat config ([f033b64](https://github.com/twinfoundation/entity-storage/commit/f033b64984c0e6a8129d929c9dd816dcc1b8dab0))
|
|
28
|
+
* logging naming consistency ([f99d12d](https://github.com/twinfoundation/entity-storage/commit/f99d12dea04b6d4f2b5632ff5473e9ec7d5f9055))
|
|
29
|
+
* update dependencies ([7ccc0c4](https://github.com/twinfoundation/entity-storage/commit/7ccc0c429125d073dc60b3de6cf101abc8cc6cba))
|
|
30
|
+
* update framework core ([b59a380](https://github.com/twinfoundation/entity-storage/commit/b59a380bb7fba2b43610f69074dcdee24a4737da))
|
|
31
|
+
* use shared store mechanism ([#34](https://github.com/twinfoundation/entity-storage/issues/34)) ([68b6b71](https://github.com/twinfoundation/entity-storage/commit/68b6b71e7a96d7d016cd57bfff36775b56bf3f93))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
* query params force coercion ([dd6aa87](https://github.com/twinfoundation/entity-storage/commit/dd6aa87efdfb60bab7d6756a86888863c45c51a7))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Dependencies
|
|
40
|
+
|
|
41
|
+
* The following workspace dependencies were updated
|
|
42
|
+
* dependencies
|
|
43
|
+
* @twin.org/entity-storage-models bumped from 0.0.3-next.0 to 0.0.3-next.1
|
|
44
|
+
* devDependencies
|
|
45
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.3-next.0 to 0.0.3-next.1
|
|
46
|
+
|
|
47
|
+
## [0.0.2-next.10](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.2-next.9...entity-storage-connector-file-v0.0.2-next.10) (2025-10-09)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Features
|
|
51
|
+
|
|
52
|
+
* add validate-locales ([e66ef0d](https://github.com/twinfoundation/entity-storage/commit/e66ef0de26ca2f82b3fe89bb5c7a15a0978a9644))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Dependencies
|
|
56
|
+
|
|
57
|
+
* The following workspace dependencies were updated
|
|
58
|
+
* dependencies
|
|
59
|
+
* @twin.org/entity-storage-models bumped from 0.0.2-next.9 to 0.0.2-next.10
|
|
60
|
+
* devDependencies
|
|
61
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.2-next.9 to 0.0.2-next.10
|
|
62
|
+
|
|
3
63
|
## [0.0.2-next.9](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-file-v0.0.2-next.8...entity-storage-connector-file-v0.0.2-next.9) (2025-10-02)
|
|
4
64
|
|
|
5
65
|
|
|
@@ -36,14 +36,10 @@ The options for the connector.
|
|
|
36
36
|
|
|
37
37
|
### CLASS\_NAME
|
|
38
38
|
|
|
39
|
-
> `readonly` **CLASS\_NAME**: `string`
|
|
39
|
+
> `readonly` `static` **CLASS\_NAME**: `string`
|
|
40
40
|
|
|
41
41
|
Runtime name for the class.
|
|
42
42
|
|
|
43
|
-
#### Implementation of
|
|
44
|
-
|
|
45
|
-
`IEntityStorageConnector.CLASS_NAME`
|
|
46
|
-
|
|
47
43
|
## Methods
|
|
48
44
|
|
|
49
45
|
### bootstrap()
|
|
@@ -72,6 +68,24 @@ True if the bootstrapping process was successful.
|
|
|
72
68
|
|
|
73
69
|
***
|
|
74
70
|
|
|
71
|
+
### className()
|
|
72
|
+
|
|
73
|
+
> **className**(): `string`
|
|
74
|
+
|
|
75
|
+
Returns the class name of the component.
|
|
76
|
+
|
|
77
|
+
#### Returns
|
|
78
|
+
|
|
79
|
+
`string`
|
|
80
|
+
|
|
81
|
+
The class name of the component.
|
|
82
|
+
|
|
83
|
+
#### Implementation of
|
|
84
|
+
|
|
85
|
+
`IEntityStorageConnector.className`
|
|
86
|
+
|
|
87
|
+
***
|
|
88
|
+
|
|
75
89
|
### getSchema()
|
|
76
90
|
|
|
77
91
|
> **getSchema**(): `IEntitySchema`
|
|
@@ -92,7 +106,7 @@ The schema for the entities.
|
|
|
92
106
|
|
|
93
107
|
### get()
|
|
94
108
|
|
|
95
|
-
> **get**(`id`, `secondaryIndex?`, `conditions?`): `Promise`\<`
|
|
109
|
+
> **get**(`id`, `secondaryIndex?`, `conditions?`): `Promise`\<`T` \| `undefined`\>
|
|
96
110
|
|
|
97
111
|
Get an entity.
|
|
98
112
|
|
|
@@ -118,7 +132,7 @@ The optional conditions to match for the entities.
|
|
|
118
132
|
|
|
119
133
|
#### Returns
|
|
120
134
|
|
|
121
|
-
`Promise`\<`
|
|
135
|
+
`Promise`\<`T` \| `undefined`\>
|
|
122
136
|
|
|
123
137
|
The object if it can be found or undefined.
|
|
124
138
|
|
|
@@ -194,7 +208,7 @@ Nothing.
|
|
|
194
208
|
|
|
195
209
|
### query()
|
|
196
210
|
|
|
197
|
-
> **query**(`conditions?`, `sortProperties?`, `properties?`, `cursor?`, `
|
|
211
|
+
> **query**(`conditions?`, `sortProperties?`, `properties?`, `cursor?`, `limit?`): `Promise`\<\{ `entities`: `Partial`\<`T`\>[]; `cursor?`: `string`; \}\>
|
|
198
212
|
|
|
199
213
|
Find all the entities which match the conditions.
|
|
200
214
|
|
|
@@ -222,9 +236,9 @@ The optional properties to return, defaults to all.
|
|
|
222
236
|
|
|
223
237
|
`string`
|
|
224
238
|
|
|
225
|
-
The cursor to request the next
|
|
239
|
+
The cursor to request the next chunk of entities.
|
|
226
240
|
|
|
227
|
-
#####
|
|
241
|
+
##### limit?
|
|
228
242
|
|
|
229
243
|
`number`
|
|
230
244
|
|
|
@@ -12,6 +12,14 @@ The name of the entity schema.
|
|
|
12
12
|
|
|
13
13
|
***
|
|
14
14
|
|
|
15
|
+
### partitionContextIds?
|
|
16
|
+
|
|
17
|
+
> `optional` **partitionContextIds**: `string`[]
|
|
18
|
+
|
|
19
|
+
The keys to use from the context ids to create partitions.
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
15
23
|
### config
|
|
16
24
|
|
|
17
25
|
> **config**: [`IFileEntityStorageConnectorConfig`](IFileEntityStorageConnectorConfig.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity-storage-connector-file",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3-next.2",
|
|
4
4
|
"description": "Entity Storage connector implementation using file storage",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,26 +14,25 @@
|
|
|
14
14
|
"node": ">=20.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@twin.org/context": "next",
|
|
17
18
|
"@twin.org/core": "next",
|
|
18
19
|
"@twin.org/entity": "next",
|
|
19
|
-
"@twin.org/entity-storage-models": "0.0.
|
|
20
|
+
"@twin.org/entity-storage-models": "0.0.3-next.2",
|
|
20
21
|
"@twin.org/logging-models": "next",
|
|
21
22
|
"@twin.org/nameof": "next"
|
|
22
23
|
},
|
|
23
|
-
"main": "./dist/
|
|
24
|
-
"module": "./dist/esm/index.mjs",
|
|
24
|
+
"main": "./dist/es/index.js",
|
|
25
25
|
"types": "./dist/types/index.d.ts",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
28
|
"types": "./dist/types/index.d.ts",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"import": "./dist/es/index.js",
|
|
30
|
+
"default": "./dist/es/index.js"
|
|
31
31
|
},
|
|
32
32
|
"./locales/*.json": "./locales/*.json"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist/
|
|
36
|
-
"dist/esm",
|
|
35
|
+
"dist/es",
|
|
37
36
|
"dist/types",
|
|
38
37
|
"locales",
|
|
39
38
|
"docs"
|
|
@@ -52,5 +51,9 @@
|
|
|
52
51
|
"connector",
|
|
53
52
|
"adapter",
|
|
54
53
|
"integration"
|
|
55
|
-
]
|
|
54
|
+
],
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "git+https://github.com/twinfoundation/entity-storage/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://twindev.org"
|
|
56
59
|
}
|
package/dist/cjs/index.cjs
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var promises = require('node:fs/promises');
|
|
4
|
-
var path = require('node:path');
|
|
5
|
-
var core = require('@twin.org/core');
|
|
6
|
-
var entity = require('@twin.org/entity');
|
|
7
|
-
|
|
8
|
-
// Copyright 2024 IOTA Stiftung.
|
|
9
|
-
// SPDX-License-Identifier: Apache-2.0.
|
|
10
|
-
/**
|
|
11
|
-
* Class for performing entity storage operations in file.
|
|
12
|
-
*/
|
|
13
|
-
class FileEntityStorageConnector {
|
|
14
|
-
/**
|
|
15
|
-
* Default Page Size for cursor.
|
|
16
|
-
* @internal
|
|
17
|
-
*/
|
|
18
|
-
static _DEFAULT_PAGE_SIZE = 20;
|
|
19
|
-
/**
|
|
20
|
-
* Runtime name for the class.
|
|
21
|
-
*/
|
|
22
|
-
CLASS_NAME = "FileEntityStorageConnector";
|
|
23
|
-
/**
|
|
24
|
-
* The schema for the entity.
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
_entitySchema;
|
|
28
|
-
/**
|
|
29
|
-
* The primary key.
|
|
30
|
-
* @internal
|
|
31
|
-
*/
|
|
32
|
-
_primaryKey;
|
|
33
|
-
/**
|
|
34
|
-
* The directory to use for storage.
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
_directory;
|
|
38
|
-
/**
|
|
39
|
-
* Create a new instance of FileEntityStorageConnector.
|
|
40
|
-
* @param options The options for the connector.
|
|
41
|
-
*/
|
|
42
|
-
constructor(options) {
|
|
43
|
-
core.Guards.object(this.CLASS_NAME, "options", options);
|
|
44
|
-
core.Guards.stringValue(this.CLASS_NAME, "options.entitySchema", options.entitySchema);
|
|
45
|
-
core.Guards.object(this.CLASS_NAME, "options.config", options.config);
|
|
46
|
-
core.Guards.stringValue(this.CLASS_NAME, "options.config.directory", options.config.directory);
|
|
47
|
-
this._entitySchema = entity.EntitySchemaFactory.get(options.entitySchema);
|
|
48
|
-
this._primaryKey = entity.EntitySchemaHelper.getPrimaryKey(this._entitySchema);
|
|
49
|
-
this._directory = path.resolve(options.config.directory);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Bootstrap the connector by creating and initializing any resources it needs.
|
|
53
|
-
* @param nodeLoggingComponentType The node logging component type.
|
|
54
|
-
* @returns True if the bootstrapping process was successful.
|
|
55
|
-
*/
|
|
56
|
-
async bootstrap(nodeLoggingComponentType) {
|
|
57
|
-
const nodeLogging = core.ComponentFactory.getIfExists(nodeLoggingComponentType);
|
|
58
|
-
if (!(await this.dirExists(this._directory))) {
|
|
59
|
-
await nodeLogging?.log({
|
|
60
|
-
level: "info",
|
|
61
|
-
source: this.CLASS_NAME,
|
|
62
|
-
message: "directoryCreating",
|
|
63
|
-
data: {
|
|
64
|
-
directory: this._directory
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
try {
|
|
68
|
-
await promises.mkdir(this._directory, { recursive: true });
|
|
69
|
-
await nodeLogging?.log({
|
|
70
|
-
level: "info",
|
|
71
|
-
source: this.CLASS_NAME,
|
|
72
|
-
message: "directoryCreated",
|
|
73
|
-
data: {
|
|
74
|
-
directory: this._directory
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
await nodeLogging?.log({
|
|
80
|
-
level: "error",
|
|
81
|
-
source: this.CLASS_NAME,
|
|
82
|
-
message: "directoryCreateFailed",
|
|
83
|
-
data: {
|
|
84
|
-
directory: this._directory
|
|
85
|
-
},
|
|
86
|
-
error: core.BaseError.fromError(err)
|
|
87
|
-
});
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
await nodeLogging?.log({
|
|
93
|
-
level: "info",
|
|
94
|
-
source: this.CLASS_NAME,
|
|
95
|
-
message: "directoryExists",
|
|
96
|
-
data: {
|
|
97
|
-
directory: this._directory
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Get the schema for the entities.
|
|
105
|
-
* @returns The schema for the entities.
|
|
106
|
-
*/
|
|
107
|
-
getSchema() {
|
|
108
|
-
return this._entitySchema;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Get an entity.
|
|
112
|
-
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
113
|
-
* @param secondaryIndex Get the item using a secondary index.
|
|
114
|
-
* @param conditions The optional conditions to match for the entities.
|
|
115
|
-
* @returns The object if it can be found or undefined.
|
|
116
|
-
*/
|
|
117
|
-
async get(id, secondaryIndex, conditions) {
|
|
118
|
-
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
119
|
-
const store = await this.readStore();
|
|
120
|
-
const foundIndex = this.findItem(store, id, secondaryIndex, conditions);
|
|
121
|
-
return foundIndex === -1 ? undefined : store[foundIndex];
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Set an entity.
|
|
125
|
-
* @param entity The entity to set.
|
|
126
|
-
* @param conditions The optional conditions to match for the entities.
|
|
127
|
-
* @returns The id of the entity.
|
|
128
|
-
*/
|
|
129
|
-
async set(entity$1, conditions) {
|
|
130
|
-
core.Guards.object(this.CLASS_NAME, "entity", entity$1);
|
|
131
|
-
entity.EntitySchemaHelper.validateEntity(entity$1, this.getSchema());
|
|
132
|
-
const store = await this.readStore();
|
|
133
|
-
const existingIndex = this.findItem(store, entity$1[this._primaryKey.property], undefined, conditions);
|
|
134
|
-
if (existingIndex >= 0) {
|
|
135
|
-
store[existingIndex] = entity$1;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
store.push(entity$1);
|
|
139
|
-
}
|
|
140
|
-
await this.writeStore(store);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Remove the entity.
|
|
144
|
-
* @param id The id of the entity to remove.
|
|
145
|
-
* @param conditions The optional conditions to match for the entities.
|
|
146
|
-
* @returns Nothing.
|
|
147
|
-
*/
|
|
148
|
-
async remove(id, conditions) {
|
|
149
|
-
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
150
|
-
const store = await this.readStore();
|
|
151
|
-
const index = this.findItem(store, id, undefined, conditions);
|
|
152
|
-
if (index >= 0) {
|
|
153
|
-
store.splice(index, 1);
|
|
154
|
-
await this.writeStore(store);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Find all the entities which match the conditions.
|
|
159
|
-
* @param conditions The conditions to match for the entities.
|
|
160
|
-
* @param sortProperties The optional sort order.
|
|
161
|
-
* @param properties The optional properties to return, defaults to all.
|
|
162
|
-
* @param cursor The cursor to request the next page of entities.
|
|
163
|
-
* @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
164
|
-
* @returns All the entities for the storage matching the conditions,
|
|
165
|
-
* and a cursor which can be used to request more entities.
|
|
166
|
-
*/
|
|
167
|
-
async query(conditions, sortProperties, properties, cursor, pageSize) {
|
|
168
|
-
let allEntities = await this.readStore();
|
|
169
|
-
const entities = [];
|
|
170
|
-
const finalPageSize = pageSize ?? FileEntityStorageConnector._DEFAULT_PAGE_SIZE;
|
|
171
|
-
let nextCursor;
|
|
172
|
-
if (allEntities.length > 0) {
|
|
173
|
-
const finalSortKeys = entity.EntitySchemaHelper.buildSortProperties(this._entitySchema, sortProperties);
|
|
174
|
-
allEntities = entity.EntitySorter.sort(allEntities, finalSortKeys);
|
|
175
|
-
const startIndex = core.Coerce.number(cursor) ?? 0;
|
|
176
|
-
for (let i = startIndex; i < allEntities.length; i++) {
|
|
177
|
-
if (entity.EntityConditions.check(allEntities[i], conditions) && entities.length < finalPageSize) {
|
|
178
|
-
entities.push(core.ObjectHelper.pick(allEntities[i], properties));
|
|
179
|
-
if (entities.length >= finalPageSize) {
|
|
180
|
-
if (i < allEntities.length - 1) {
|
|
181
|
-
nextCursor = (i + 1).toString();
|
|
182
|
-
}
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
entities,
|
|
190
|
-
cursor: nextCursor
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Read the store from file.
|
|
195
|
-
* @returns The store.
|
|
196
|
-
* @internal
|
|
197
|
-
*/
|
|
198
|
-
async readStore() {
|
|
199
|
-
try {
|
|
200
|
-
const filename = path.join(this._directory, "store.json");
|
|
201
|
-
const store = await promises.readFile(filename, "utf8");
|
|
202
|
-
return JSON.parse(store);
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Write the store to the file.
|
|
210
|
-
* @param store The store to write.
|
|
211
|
-
* @returns Nothing.
|
|
212
|
-
* @internal
|
|
213
|
-
*/
|
|
214
|
-
async writeStore(store) {
|
|
215
|
-
try {
|
|
216
|
-
const filename = path.join(this._directory, "store.json");
|
|
217
|
-
await promises.writeFile(filename, JSON.stringify(store, undefined, "\t"), "utf8");
|
|
218
|
-
}
|
|
219
|
-
catch { }
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Check if the dir exists.
|
|
223
|
-
* @param dir The directory to check.
|
|
224
|
-
* @returns True if the dir exists.
|
|
225
|
-
* @internal
|
|
226
|
-
*/
|
|
227
|
-
async dirExists(dir) {
|
|
228
|
-
try {
|
|
229
|
-
await promises.access(dir);
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Find the item in the store.
|
|
238
|
-
* @param store The store to search.
|
|
239
|
-
* @param id The id to search for.
|
|
240
|
-
* @param secondaryIndex The secondary index to search for.
|
|
241
|
-
* @param conditions The optional conditions to match for the entities.
|
|
242
|
-
* @returns The index of the item if found or -1.
|
|
243
|
-
* @internal
|
|
244
|
-
*/
|
|
245
|
-
findItem(store, id, secondaryIndex, conditions) {
|
|
246
|
-
const finalConditions = [];
|
|
247
|
-
if (!core.Is.empty(secondaryIndex)) {
|
|
248
|
-
finalConditions.push({
|
|
249
|
-
property: secondaryIndex,
|
|
250
|
-
comparison: entity.ComparisonOperator.Equals,
|
|
251
|
-
value: id
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
if (core.Is.arrayValue(conditions)) {
|
|
255
|
-
// If we haven't added a secondary index condition we need to add the primary key condition.
|
|
256
|
-
if (finalConditions.length === 0) {
|
|
257
|
-
finalConditions.push({
|
|
258
|
-
property: this._primaryKey.property,
|
|
259
|
-
comparison: entity.ComparisonOperator.Equals,
|
|
260
|
-
value: id
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
finalConditions.push(...conditions.map(c => ({
|
|
264
|
-
property: c.property,
|
|
265
|
-
comparison: entity.ComparisonOperator.Equals,
|
|
266
|
-
value: c.value
|
|
267
|
-
})));
|
|
268
|
-
}
|
|
269
|
-
if (finalConditions.length > 0) {
|
|
270
|
-
for (let i = 0; i < store.length; i++) {
|
|
271
|
-
if (entity.EntityConditions.check(store[i], { conditions: finalConditions })) {
|
|
272
|
-
return i;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
return store.findIndex(e => e[this._primaryKey.property] === id);
|
|
278
|
-
}
|
|
279
|
-
return -1;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
exports.FileEntityStorageConnector = FileEntityStorageConnector;
|