@acodeninja/persist 3.0.0-next.9 → 3.0.0

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.
Files changed (41) hide show
  1. package/README.md +60 -4
  2. package/docs/code-quirks.md +14 -14
  3. package/docs/defining-models.md +61 -0
  4. package/docs/http.openapi.yml +138 -0
  5. package/docs/{model-property-types.md → model-properties.md} +76 -43
  6. package/docs/models-as-properties.md +46 -46
  7. package/docs/search-queries.md +11 -13
  8. package/docs/storage-engines.md +19 -35
  9. package/docs/structured-queries.md +59 -48
  10. package/docs/transactions.md +6 -7
  11. package/exports/storage/http.js +3 -0
  12. package/exports/storage/s3.js +3 -0
  13. package/jest.config.cjs +8 -12
  14. package/package.json +2 -2
  15. package/src/Connection.js +750 -0
  16. package/src/Persist.js +29 -30
  17. package/src/Schema.js +175 -0
  18. package/src/{Query.js → data/FindIndex.js} +40 -24
  19. package/src/{type → data}/Model.js +95 -55
  20. package/src/data/Property.js +21 -0
  21. package/src/data/SearchIndex.js +106 -0
  22. package/src/{type/complex → data/properties}/ArrayType.js +5 -3
  23. package/src/{type/simple → data/properties}/BooleanType.js +3 -3
  24. package/src/{type/complex → data/properties}/CustomType.js +5 -5
  25. package/src/{type/simple → data/properties}/DateType.js +4 -4
  26. package/src/{type/simple → data/properties}/NumberType.js +3 -3
  27. package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
  28. package/src/{type/resolved → data/properties}/SlugType.js +1 -1
  29. package/src/{type/simple → data/properties}/StringType.js +3 -3
  30. package/src/{type → data/properties}/Type.js +13 -3
  31. package/src/engine/storage/HTTPStorageEngine.js +149 -253
  32. package/src/engine/storage/S3StorageEngine.js +108 -195
  33. package/src/engine/storage/StorageEngine.js +131 -549
  34. package/exports/engine/storage/file.js +0 -3
  35. package/exports/engine/storage/http.js +0 -3
  36. package/exports/engine/storage/s3.js +0 -3
  37. package/src/SchemaCompiler.js +0 -196
  38. package/src/Transactions.js +0 -145
  39. package/src/engine/StorageEngine.js +0 -472
  40. package/src/engine/storage/FileStorageEngine.js +0 -213
  41. package/src/type/index.js +0 -32
@@ -1,472 +0,0 @@
1
- import Type from '../type/index.js';
2
- import _ from 'lodash';
3
-
4
- export default class StorageEngine {
5
- /**
6
- * @param {Object} configuration
7
- * @param {Array<Type.Model.constructor>?} models
8
- */
9
- constructor(configuration = {}, models = null) {
10
- this.configuration = configuration;
11
- this.models = Object.fromEntries((models ?? []).map(model => [model.name, model]));
12
- }
13
-
14
- /**
15
- * Persists a model if it has changed, and updates all related models and their indexes
16
- * @param {Type.Model} model
17
- * @return {Promise<void>}
18
- */
19
- async put(model) {
20
- const processedModels = [];
21
- const modelsToPut = [];
22
- const modelsToReindex = {};
23
- const modelsToReindexSearch = {};
24
-
25
- /**
26
- * @param {Type.Model} modelToProcess
27
- * @return {Promise<void>}
28
- */
29
- const processModel = async (modelToProcess) => {
30
- if (processedModels.includes(modelToProcess.id))
31
- return;
32
-
33
- processedModels.push(modelToProcess.id);
34
-
35
- if (!Object.keys(this.models).includes(modelToProcess.constructor.name))
36
- throw new ModelNotRegisteredStorageEngineError(modelToProcess, this);
37
-
38
- modelToProcess.validate();
39
- const currentModel = await this.get(modelToProcess.id).catch(() => null);
40
-
41
- const modelToProcessHasChanged = JSON.stringify(currentModel?.toData() || {}) !== JSON.stringify(modelToProcess.toData());
42
-
43
- if (modelToProcessHasChanged) modelsToPut.push(modelToProcess);
44
-
45
- if (
46
- Boolean(modelToProcess.constructor.indexedProperties().length) &&
47
- indexedFieldsHaveChanged(currentModel, modelToProcess)
48
- ) {
49
- const modelToProcessConstructor = this.getModelConstructorFromId(modelToProcess.id);
50
- modelsToReindex[modelToProcessConstructor] = modelsToReindex[modelToProcessConstructor] || [];
51
- modelsToReindex[modelToProcessConstructor].push(modelToProcess);
52
- }
53
-
54
- if (
55
- Boolean(modelToProcess.constructor.searchProperties().length) &&
56
- searchableFieldsHaveChanged(currentModel, modelToProcess)
57
- ) {
58
- const modelToProcessConstructor = this.getModelConstructorFromId(modelToProcess.id);
59
- modelsToReindexSearch[modelToProcessConstructor] = modelsToReindexSearch[modelToProcessConstructor] || [];
60
- modelsToReindexSearch[modelToProcessConstructor].push(modelToProcess);
61
- }
62
-
63
- for (const [field, value] of Object.entries(modelToProcess)) {
64
- if (Type.Model.isModel(value)) {
65
- await processModel(modelToProcess[field]);
66
- }
67
- }
68
- };
69
-
70
- await processModel(model);
71
-
72
- await Promise.all([
73
- Promise.all(modelsToPut.map(m => this._putModel(m.toData()))),
74
- Promise.all(Object.entries(modelsToReindex).map(async ([constructorName, models]) => {
75
- const modelConstructor = this.models[constructorName];
76
- const index = await this._getIndex(modelConstructor);
77
-
78
- await this._putIndex(modelConstructor, {
79
- ...index || {},
80
- ...Object.fromEntries(models.map(m => [m.id, m.toIndexData()])),
81
- });
82
- })),
83
- Promise.all(Object.entries(modelsToReindexSearch).map(async ([constructorName, models]) => {
84
- const modelConstructor = this.models[constructorName];
85
- const index = await this._getSearchIndex(modelConstructor);
86
-
87
- await this._putSearchIndex(modelConstructor, {
88
- ...index || {},
89
- ...Object.fromEntries(models.map(m => [m.id, m.toSearchData()])),
90
- });
91
- })),
92
- ]);
93
- }
94
-
95
- /**
96
- * Get a model by its id
97
- * @param {string} modelId
98
- * @throws {ModelNotFoundStorageEngineError}
99
- * @return {Promise<Type.Model>}
100
- */
101
- get(modelId) {
102
- try {
103
- this.getModelConstructorFromId(modelId);
104
- } catch (e) {
105
- return Promise.reject(e);
106
- }
107
- return this._getModel(modelId);
108
- }
109
-
110
- /**
111
- * Delete a model and update indexes that reference it
112
- * @param {Type.Model} model
113
- * @throws {ModelNotRegisteredStorageEngineError}
114
- * @throws {ModelNotFoundStorageEngineError}
115
- */
116
- async delete(model) {
117
- const modelsToDelete = [];
118
- const modelsToPut = [];
119
- const indexCache = {};
120
- const indexActions = {};
121
- const searchIndexCache = {};
122
- const searchIndexActions = {};
123
-
124
- const processModel = async (modelToProcess) => {
125
- if (!Object.keys(this.models).includes(modelToProcess.constructor.name))
126
- throw new ModelNotRegisteredStorageEngineError(modelToProcess, this);
127
-
128
- const currentModel = await this.get(model.id);
129
-
130
- modelsToDelete.push(currentModel.id);
131
-
132
- const modelToProcessConstructor = this.getModelConstructorFromId(modelToProcess.id);
133
- indexActions[modelToProcessConstructor] = indexActions[modelToProcessConstructor] ?? [];
134
- searchIndexActions[modelToProcessConstructor] = searchIndexActions[modelToProcessConstructor] ?? [];
135
-
136
- if (currentModel.constructor.indexedPropertiesResolved().length) {
137
- indexActions[modelToProcessConstructor].push(['delete', modelToProcess]);
138
- }
139
-
140
- if (currentModel.constructor.searchProperties().length) {
141
- searchIndexActions[modelToProcessConstructor].push(['delete', modelToProcess]);
142
- }
143
-
144
- const linkedModels = await this.getInstancesLinkedTo(modelToProcess, indexCache);
145
- const links = this.getLinksFor(modelToProcess.constructor);
146
- Object.values(Object.fromEntries(await Promise.all(
147
- Object.entries(linkedModels)
148
- .map(async ([constructor, updatableModels]) => [
149
- constructor,
150
- await Promise.all(updatableModels.map(m => this.get(m.id))),
151
- ]),
152
- ))).flat(1)
153
- .forEach(m =>
154
- Object.entries(links[m.constructor.name])
155
- .forEach(([linkName, _]) => {
156
- m[linkName] = undefined;
157
- modelsToPut.push(m);
158
-
159
- const modelToProcessConstructor = this.getModelConstructorFromId(m.id);
160
- indexActions[modelToProcessConstructor].push(['reindex', m]);
161
-
162
- if (m.constructor.searchProperties().length) {
163
- const modelToProcessConstructor = this.getModelConstructorFromId(m.id);
164
- searchIndexActions[modelToProcessConstructor].push(['reindex', m]);
165
- }
166
- }),
167
- );
168
- };
169
-
170
- await processModel(model);
171
-
172
- await Promise.all([
173
- Promise.all(Object.entries(indexActions).map(async ([constructorName, actions]) => {
174
- const modelConstructor = this.models[constructorName];
175
- indexCache[constructorName] = indexCache[constructorName] ?? await this._getIndex(modelConstructor);
176
-
177
- actions.forEach(([action, model]) => {
178
- switch (action) {
179
- case 'delete':
180
- indexCache[constructorName] = _.omit(indexCache[constructorName], [model.id]);
181
- break;
182
- case 'reindex':
183
- indexCache[constructorName] = {
184
- ...indexCache[constructorName],
185
- [model.id]: model.toIndexData(),
186
- };
187
- break;
188
- }
189
- });
190
- })),
191
- Promise.all(Object.entries(searchIndexActions).map(async ([constructorName, actions]) => {
192
- const modelConstructor = this.models[constructorName];
193
- searchIndexCache[constructorName] = searchIndexCache[constructorName] ?? await this._getSearchIndex(modelConstructor);
194
-
195
- actions.forEach(([action, model]) => {
196
- switch (action) {
197
- case 'delete':
198
- searchIndexCache[constructorName] = _.omit(searchIndexCache[constructorName], [model.id]);
199
- break;
200
- case 'reindex':
201
- searchIndexCache[constructorName] = {
202
- ...searchIndexCache[constructorName],
203
- [model.id]: model.toSearchData(),
204
- };
205
- break;
206
- }
207
- });
208
- })),
209
- ]);
210
-
211
- await Promise.all([
212
- Promise.all(modelsToDelete.map(m => this._deleteModel(m))),
213
- Promise.all(modelsToPut.map(m => this._putModel(m))),
214
- Promise.all(
215
- Object.entries(indexCache)
216
- .map(([constructorName, _]) => this._putIndex(this.models[constructorName], indexCache[constructorName])),
217
- ),
218
- Promise.all(
219
- Object.entries(searchIndexCache)
220
- .map(([constructorName, _]) => this._putSearchIndex(this.models[constructorName], searchIndexCache[constructorName])),
221
- ),
222
- ]);
223
- }
224
-
225
- /**
226
- * Get the model constructor from a model id
227
- * @param {string} modelId
228
- * @return {Model.constructor}
229
- */
230
- getModelConstructorFromId(modelId) {
231
- const modelName = modelId.split('/')[0];
232
- const constructor = this.models[modelName];
233
-
234
- if (!constructor) throw new ModelNotRegisteredStorageEngineError(modelName, this);
235
-
236
- return constructor;
237
- }
238
-
239
- /**
240
- * Get model instance that are directly linked to the given model in either direction
241
- * @param {Type.Model} model
242
- * @param {object} cache
243
- * @return {Record<string, Record<string, Type.Model>>}
244
- */
245
- async getInstancesLinkedTo(model, cache = {}) {
246
- return Object.fromEntries(
247
- Object.entries(
248
- await Promise.all(
249
- Object.entries(this.getLinksFor(model.constructor))
250
- .map(([name, _]) =>
251
- cache[name] ? Promise.resolve([name, Object.values(cache[name])]) :
252
- this._getIndex(this.models[name])
253
- .then(i => {
254
- cache[name] = i;
255
- return [name, Object.values(i)];
256
- }),
257
- ),
258
- ).then(Object.fromEntries),
259
- ).map(([name, i]) => [
260
- name,
261
- i.map(i => Object.fromEntries(
262
- Object.entries(i)
263
- .filter(([name, property]) => name === 'id' || property?.id === model.id),
264
- )).filter(i => Object.keys(i).length > 1),
265
- ]),
266
- );
267
- }
268
-
269
- /**
270
- * Get model classes that are directly linked to the given model in either direction
271
- * @param {Type.Model.constructor} model
272
- * @return {Record<string, Record<string, Type.Model.constructor>>}
273
- */
274
- getLinksFor(model) {
275
- return Object.fromEntries(
276
- Object.entries(this.getAllModelLinks())
277
- .filter(([modelName, links]) =>
278
- model.name === modelName ||
279
- Object.values(links).some((link) => link.name === model.name),
280
- ),
281
- );
282
- }
283
-
284
- /**
285
- * Get all model links
286
- * @return {Record<string, Record<string, Type.Model.constructor>>}
287
- */
288
- getAllModelLinks() {
289
- return Object.entries(this.models)
290
- .map(([registeredModelName, registeredModelClass]) =>
291
- Object.entries(registeredModelClass)
292
- .map(([propertyName, propertyType]) => [
293
- registeredModelName,
294
- propertyName,
295
- typeof propertyType === 'function' &&
296
- !/^class/.test(Function.prototype.toString.call(propertyType)) &&
297
- !Type.Model.isModel(propertyType) ?
298
- propertyType() : propertyType,
299
- ])
300
- .filter(([_m, _p, type]) => Type.Model.isModel(type))
301
- .map(([containingModel, propertyName, propertyType]) => ({
302
- containingModel,
303
- propertyName,
304
- propertyType,
305
- })),
306
- )
307
- .flat()
308
- .reduce((accumulator, {containingModel, propertyName, propertyType}) => ({
309
- ...accumulator,
310
- [containingModel]: {
311
- ...accumulator[containingModel] || {},
312
- [propertyName]: propertyType,
313
- },
314
- }), {});
315
- }
316
-
317
- /**
318
- * Update a model
319
- * @param {Model} _model
320
- * @throws MethodNotImplementedStorageEngineError
321
- * @return Promise<void>
322
- */
323
- _putModel(_model) {
324
- return Promise.reject(new MethodNotImplementedStorageEngineError('_putModel', this));
325
- }
326
-
327
- /**
328
- * Get a model
329
- * @param {string} _id
330
- * @throws MethodNotImplementedStorageEngineError
331
- * @throws ModelNotFoundStorageEngineError
332
- * @return Promise<Model>
333
- */
334
- _getModel(_id) {
335
- return Promise.reject(new MethodNotImplementedStorageEngineError('_getModel', this));
336
- }
337
-
338
- /**
339
- * Delete a model
340
- * @param {string} _id
341
- * @throws MethodNotImplementedStorageEngineError
342
- * @throws ModelNotFoundStorageEngineError
343
- * @return Promise<void>
344
- */
345
- _deleteModel(_id) {
346
- return Promise.reject(new MethodNotImplementedStorageEngineError('_deleteModel', this));
347
- }
348
-
349
- /**
350
- * Get a model's index data
351
- * @param {Model.constructor} _modelConstructor
352
- * @throws MethodNotImplementedStorageEngineError
353
- * @return Promise<void>
354
- */
355
- _getIndex(_modelConstructor) {
356
- return Promise.reject(new MethodNotImplementedStorageEngineError('_getIndex', this));
357
- }
358
-
359
- /**
360
- * Put a model's index data
361
- * @param {Model.constructor} _modelConstructor
362
- * @param {object} _data
363
- * @throws MethodNotImplementedStorageEngineError
364
- * @return Promise<void>
365
- */
366
- _putIndex(_modelConstructor, _data) {
367
- return Promise.reject(new MethodNotImplementedStorageEngineError('_putIndex', this));
368
- }
369
-
370
- /**
371
- * Get a model's raw search index data
372
- * @param {Model.constructor} _modelConstructor
373
- * @throws MethodNotImplementedStorageEngineError
374
- * @return Promise<void>
375
- */
376
- _getSearchIndex(_modelConstructor) {
377
- return Promise.reject(new MethodNotImplementedStorageEngineError('_getSearchIndex', this));
378
- }
379
-
380
- /**
381
- * Get a model's raw search index data
382
- * @param {Model.constructor} _modelConstructor
383
- * @throws MethodNotImplementedStorageEngineError
384
- * @return Promise<void>
385
- */
386
- _getSearchIndexCompiled(_modelConstructor) {
387
- return Promise.reject(new MethodNotImplementedStorageEngineError('_getSearchIndexCompiled', this));
388
- }
389
-
390
- /**
391
- * Put a model's raw and compiled search index data
392
- * @param {Model.constructor} _modelConstructor
393
- * @param {object} _data
394
- * @throws MethodNotImplementedStorageEngineError
395
- * @return Promise<void>
396
- */
397
- _putSearchIndex(_modelConstructor, _data) {
398
- return Promise.reject(new MethodNotImplementedStorageEngineError('_putSearchIndex', this));
399
- }
400
- }
401
-
402
-
403
- /**
404
- * Decide if two models indexable fields are different
405
- * @param {Type.Model} currentModel
406
- * @param {Type.Model} modelToProcess
407
- * @return {boolean}
408
- * @private
409
- */
410
- function indexedFieldsHaveChanged(currentModel, modelToProcess) {
411
- return !currentModel || JSON.stringify(currentModel.toIndexData()) !== JSON.stringify(modelToProcess.toIndexData());
412
- }
413
-
414
- /**
415
- * Decide if two models searchable fields have changed
416
- * @param {Type.Model} currentModel
417
- * @param {Type.Model} modelToProcess
418
- * @return {boolean}
419
- * @private
420
- */
421
- function searchableFieldsHaveChanged(currentModel, modelToProcess) {
422
- return !currentModel || JSON.stringify(currentModel.toSearchData()) !== JSON.stringify(modelToProcess.toSearchData());
423
- }
424
-
425
- /**
426
- * @class StorageEngineError
427
- * @extends Error
428
- */
429
- export class StorageEngineError extends Error {
430
- }
431
-
432
- /**
433
- * @class ModelNotRegisteredStorageEngineError
434
- * @extends StorageEngineError
435
- */
436
- export class ModelNotRegisteredStorageEngineError extends StorageEngineError {
437
- /**
438
- * @param {Type.Model} model
439
- * @param {StorageEngine} storageEngine
440
- */
441
- constructor(model, storageEngine) {
442
- const modelName = typeof model === 'string' ? model : model.constructor.name;
443
- super(`The model ${modelName} is not registered in the storage engine ${storageEngine.constructor.name}`);
444
- }
445
- }
446
-
447
- /**
448
- * @class MethodNotImplementedStorageEngineError
449
- * @extends StorageEngineError
450
- */
451
- export class MethodNotImplementedStorageEngineError extends StorageEngineError {
452
- /**
453
- * @param {string} method
454
- * @param {StorageEngine} storageEngine
455
- */
456
- constructor(method, storageEngine) {
457
- super(`The method ${method} is not implemented in the storage engine ${storageEngine.constructor.name}`);
458
- }
459
- }
460
-
461
- /**
462
- * @class ModelNotFoundStorageEngineError
463
- * @extends StorageEngineError
464
- */
465
- export class ModelNotFoundStorageEngineError extends StorageEngineError {
466
- /**
467
- * @param {string} modelId
468
- */
469
- constructor(modelId) {
470
- super(`The model ${modelId} was not found`);
471
- }
472
- }
@@ -1,213 +0,0 @@
1
- import StorageEngine, { EngineError, MissConfiguredError } from './StorageEngine.js';
2
- import { dirname, join } from 'node:path';
3
- import fs from 'node:fs/promises';
4
-
5
- /**
6
- * Custom error class for FileStorageEngine-related errors.
7
- * Extends the base `EngineError` class.
8
- */
9
- class FileStorageEngineError extends EngineError {}
10
-
11
- /**
12
- * Error thrown when writing to a file fails in `FileStorageEngine`.
13
- * Extends the `FileStorageEngineError` class.
14
- */
15
- class FailedWriteFileStorageEngineError extends FileStorageEngineError {}
16
-
17
- /**
18
- * `FileStorageEngine` class extends the base `StorageEngine` class to implement
19
- * file system-based storage and retrieval of model data.
20
- *
21
- * @class FileStorageEngine
22
- * @extends StorageEngine
23
- */
24
- class FileStorageEngine extends StorageEngine {
25
- /**
26
- * Configures the FileStorageEngine with a given configuration object.
27
- * Adds default `filesystem` configuration if not provided.
28
- *
29
- * @param {Object} configuration - Configuration settings for FileStorageEngine.
30
- * @param {Object} [configuration.filesystem] - Custom filesystem module (default: Node.js fs/promises).
31
- * @param {Object} [configuration.path] - The absolute path on the filesystem to write models to.
32
- * @returns {FileStorageEngine} A configured instance of FileStorageEngine.
33
- */
34
- static configure(configuration) {
35
- if (!configuration.filesystem) {
36
- configuration.filesystem = fs;
37
- }
38
- return super.configure(configuration);
39
- }
40
-
41
- /**
42
- * Checks if the FileStorageEngine has been configured correctly.
43
- * Ensures that `path` and `filesystem` settings are present.
44
- *
45
- * @throws {MissConfiguredError} Throws if required configuration is missing.
46
- */
47
- static checkConfiguration() {
48
- if (!this.configuration?.path || !this.configuration?.filesystem) {
49
- throw new MissConfiguredError(this.configuration);
50
- }
51
- }
52
-
53
- /**
54
- * Retrieves a model by its ID from the file system.
55
- *
56
- * @param {string} id - The ID of the model to retrieve.
57
- * @returns {Promise<Object>} The parsed model data.
58
- * @throws {Error} Throws if the file cannot be read or parsed.
59
- */
60
- static getById(id) {
61
- return this.configuration.filesystem
62
- .readFile(join(this.configuration.path, `${id}.json`))
63
- .then((b) => b.toString())
64
- .then(JSON.parse);
65
- }
66
-
67
- /**
68
- * Deletes a model by its ID from the file system.
69
- *
70
- * @param {string} id - The ID of the model to delete.
71
- * @returns {Promise<void>} Resolves when the model has been deleted.
72
- * @throws {Error} Throws if the file cannot be deleted.
73
- */
74
- static deleteById(id) {
75
- return this.configuration.filesystem.rm(join(this.configuration.path, `${id}.json`));
76
- }
77
-
78
- /**
79
- * Retrieves the index for a given model from the file system.
80
- *
81
- * @param {Model.constructor?} model - The model for which the index is retrieved.
82
- * @returns {Promise<Object>} The parsed index data.
83
- * @throws {Error} Throws if the file cannot be read.
84
- */
85
- static getIndex(model) {
86
- return this.configuration.filesystem
87
- .readFile(join(this.configuration.path, model.toString(), '_index.json'))
88
- .then((b) => b.toString())
89
- .catch(() => '{}')
90
- .then(JSON.parse);
91
- }
92
-
93
- /**
94
- * Saves a model to the file system.
95
- *
96
- * @param {Model} model - The model to save.
97
- * @throws {FailedWriteFileStorageEngineError} Throws if the model cannot be written to the file system.
98
- */
99
- static async putModel(model) {
100
- const filePath = join(this.configuration.path, `${model.id}.json`);
101
- try {
102
- await this.configuration.filesystem.mkdir(dirname(filePath), { recursive: true });
103
- await this.configuration.filesystem.writeFile(filePath, JSON.stringify(model.toData()));
104
- } catch (error) {
105
- throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
106
- }
107
- }
108
-
109
- /**
110
- * Saves the index for multiple models to the file system.
111
- *
112
- * @param {Object} index - An object where keys are locations and values are key value pairs of models and their ids.
113
- * @throws {FailedWriteFileStorageEngineError} Throws if the index cannot be written to the file system.
114
- */
115
- static async putIndex(index) {
116
- /**
117
- * Process an index of models
118
- * @param {string} location
119
- * @param {Array<Model>} models
120
- * @throws FailedWriteFileStorageEngineError
121
- * @return {Promise<void>}
122
- */
123
- const processIndex = async (location, models) => {
124
- const filePath = join(this.configuration.path, location, '_index.json');
125
- const currentIndex = JSON.parse((await this.configuration.filesystem.readFile(filePath).catch(() => '{}')).toString());
126
-
127
- try {
128
- await this.configuration.filesystem.writeFile(filePath, JSON.stringify({
129
- ...currentIndex,
130
- ...Object.fromEntries(
131
- Object.entries(models).map(([k, v]) => [k, v?.toIndexData?.() || v]),
132
- ),
133
- }));
134
- } catch (error) {
135
- throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
136
- }
137
- };
138
-
139
- for (const [location, models] of Object.entries(index)) {
140
- await processIndex(location, models);
141
- }
142
-
143
- await processIndex('', Object.values(index).reduce((accumulator, currentValue) => {
144
- Object.keys(currentValue).forEach(key => {
145
- accumulator[key] = currentValue[key];
146
- });
147
- return accumulator;
148
- }, {}));
149
- }
150
-
151
- /**
152
- * Retrieves the compiled search index for a model from the file system.
153
- *
154
- * @param {Model.constructor} model - The model for which the search index is retrieved.
155
- * @returns {Promise<Object>} The parsed compiled search index.
156
- * @throws {Error} Throws if the file cannot be read.
157
- */
158
- static getSearchIndexCompiled(model) {
159
- return this.configuration.filesystem
160
- .readFile(join(this.configuration.path, model.toString(), '_search_index.json'))
161
- .then((b) => b.toString())
162
- .then(JSON.parse);
163
- }
164
-
165
- /**
166
- * Retrieves the raw search index for a model from the file system.
167
- *
168
- * @param {Model.constructor} model - The model for which the raw search index is retrieved.
169
- * @returns {Promise<Object>} The parsed raw search index.
170
- * @throws {Error} Throws if the file cannot be read.
171
- */
172
- static getSearchIndexRaw(model) {
173
- return this.configuration.filesystem
174
- .readFile(join(this.configuration.path, model.toString(), '_search_index_raw.json'))
175
- .then((b) => b.toString())
176
- .then(JSON.parse)
177
- .catch(() => ({}));
178
- }
179
-
180
- /**
181
- * Saves the compiled search index for a model to the file system.
182
- *
183
- * @param {Model.constructor} model - The model for which the compiled search index is saved.
184
- * @param {Object} compiledIndex - The compiled search index to save.
185
- * @throws {FailedWriteFileStorageEngineError} Throws if the compiled index cannot be written to the file system.
186
- */
187
- static async putSearchIndexCompiled(model, compiledIndex) {
188
- const filePath = join(this.configuration.path, model.toString(), '_search_index.json');
189
- try {
190
- await this.configuration.filesystem.writeFile(filePath, JSON.stringify(compiledIndex));
191
- } catch (error) {
192
- throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
193
- }
194
- }
195
-
196
- /**
197
- * Saves the raw search index for a model to the file system.
198
- *
199
- * @param {Model.constructor} model - The model for which the raw search index is saved.
200
- * @param {Object} rawIndex - The raw search index to save.
201
- * @throws {FailedWriteFileStorageEngineError} Throws if the raw index cannot be written to the file system.
202
- */
203
- static async putSearchIndexRaw(model, rawIndex) {
204
- const filePath = join(this.configuration.path, model.toString(), '_search_index_raw.json');
205
- try {
206
- await this.configuration.filesystem.writeFile(filePath, JSON.stringify(rawIndex));
207
- } catch (error) {
208
- throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
209
- }
210
- }
211
- }
212
-
213
- export default FileStorageEngine;