@acodeninja/persist 3.0.0-next.2 → 3.0.0-next.21
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 +60 -4
- package/docs/code-quirks.md +6 -6
- package/docs/defining-models.md +61 -0
- package/docs/http.openapi.yml +138 -0
- package/docs/{model-property-types.md → model-properties.md} +37 -35
- package/docs/models-as-properties.md +40 -40
- package/docs/search-queries.md +3 -5
- package/docs/storage-engines.md +19 -35
- package/docs/structured-queries.md +56 -45
- package/docs/transactions.md +6 -7
- package/exports/storage/http.js +3 -0
- package/exports/storage/s3.js +3 -0
- package/jest.config.cjs +8 -12
- package/package.json +2 -2
- package/src/Connection.js +631 -0
- package/src/Persist.js +29 -30
- package/src/Schema.js +175 -0
- package/src/{Query.js → data/FindIndex.js} +40 -24
- package/src/{type → data}/Model.js +41 -26
- package/src/data/Property.js +19 -0
- package/src/data/SearchIndex.js +106 -0
- package/src/{type/complex → data/properties}/ArrayType.js +1 -1
- package/src/{type/simple → data/properties}/BooleanType.js +1 -1
- package/src/{type/complex → data/properties}/CustomType.js +1 -1
- package/src/{type/simple → data/properties}/DateType.js +1 -1
- package/src/{type/simple → data/properties}/NumberType.js +1 -1
- package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
- package/src/{type/simple → data/properties}/StringType.js +1 -1
- package/src/{type → data/properties}/Type.js +8 -0
- package/src/engine/storage/HTTPStorageEngine.js +149 -253
- package/src/engine/storage/S3StorageEngine.js +108 -195
- package/src/engine/storage/StorageEngine.js +114 -550
- package/exports/engine/storage/file.js +0 -3
- package/exports/engine/storage/http.js +0 -3
- package/exports/engine/storage/s3.js +0 -3
- package/src/SchemaCompiler.js +0 -192
- package/src/Transactions.js +0 -145
- package/src/engine/StorageEngine.js +0 -250
- package/src/engine/storage/FileStorageEngine.js +0 -213
- package/src/type/index.js +0 -32
- /package/src/{type/resolved → data/properties}/SlugType.js +0 -0
@@ -1,611 +1,175 @@
|
|
1
|
-
import
|
2
|
-
import Type from '../../type/index.js';
|
3
|
-
import lunr from 'lunr';
|
4
|
-
|
5
|
-
/**
|
6
|
-
* The `StorageEngine` class provides a base interface for implementing data storage and retrieval engines.
|
7
|
-
* It includes methods for handling models, indexes, and search functionality.
|
8
|
-
*
|
9
|
-
* @class StorageEngine
|
10
|
-
*/
|
11
|
-
class StorageEngine {
|
12
|
-
static configuration = undefined;
|
13
|
-
static _searchCache = undefined;
|
14
|
-
|
15
|
-
/**
|
16
|
-
* Retrieves a model by its ID. This method must be implemented by subclasses.
|
17
|
-
*
|
18
|
-
* @param {string} _id - The ID of the model to retrieve.
|
19
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
20
|
-
* @returns {Promise<Object>} - Returns a promise resolving to the raw data of the requested model.
|
21
|
-
* @abstract
|
22
|
-
*/
|
23
|
-
static getById(_id) {
|
24
|
-
return Promise.reject(new NotImplementedError(`${this.name} must implement .getById()`));
|
25
|
-
}
|
26
|
-
|
27
|
-
/**
|
28
|
-
* Deletes a model by its ID. This method must be implemented by subclasses.
|
29
|
-
*
|
30
|
-
* @param {string} _id - The ID of the model to retrieve.
|
31
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
32
|
-
* @returns {Promise<void>} - Returns a promise resolving when the model has been deleted.
|
33
|
-
* @abstract
|
34
|
-
*/
|
35
|
-
static deleteById(_id) {
|
36
|
-
return Promise.reject(new NotImplementedError(`${this.name} must implement .deleteById()`));
|
37
|
-
}
|
38
|
-
|
39
|
-
/**
|
40
|
-
* Saves a model to the data store. This method must be implemented by subclasses.
|
41
|
-
*
|
42
|
-
* @param {Model} _data - The model data to save.
|
43
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
44
|
-
* @returns {Promise<void>}
|
45
|
-
* @abstract
|
46
|
-
*/
|
47
|
-
static putModel(_data) {
|
48
|
-
return Promise.reject(new NotImplementedError(`${this.name} must implement .putModel()`));
|
49
|
-
}
|
1
|
+
import {Transaction} from '../../Connection.js';
|
50
2
|
|
3
|
+
export default class StorageEngine {
|
51
4
|
/**
|
52
|
-
*
|
53
|
-
*
|
54
|
-
* @param {Model.constructor} _model - The model to retrieve the index for.
|
55
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
56
|
-
* @returns {Promise<Object>} - Returns a promise resolving to the model index.
|
57
|
-
* @abstract
|
5
|
+
* @param {Object} configuration
|
58
6
|
*/
|
59
|
-
|
60
|
-
|
7
|
+
constructor(configuration = {}) {
|
8
|
+
this.configuration = configuration;
|
61
9
|
}
|
62
10
|
|
63
11
|
/**
|
64
|
-
*
|
65
|
-
*
|
66
|
-
* @
|
67
|
-
* @throws
|
68
|
-
* @
|
69
|
-
* @abstract
|
12
|
+
* Get a model
|
13
|
+
* @param {string} _id
|
14
|
+
* @throws MethodNotImplementedStorageEngineError
|
15
|
+
* @throws ModelNotFoundStorageEngineError
|
16
|
+
* @return Promise<Model>
|
70
17
|
*/
|
71
|
-
|
72
|
-
return Promise.reject(new
|
18
|
+
getModel(_id) {
|
19
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getModel', this));
|
73
20
|
}
|
74
21
|
|
75
22
|
/**
|
76
|
-
*
|
77
|
-
*
|
78
|
-
* @
|
79
|
-
* @
|
80
|
-
* @returns {Promise<Object>} - Returns a promise resolving to the compiled search index.
|
81
|
-
* @abstract
|
23
|
+
* Update a model
|
24
|
+
* @param {object} _model
|
25
|
+
* @throws MethodNotImplementedStorageEngineError
|
26
|
+
* @return Promise<void>
|
82
27
|
*/
|
83
|
-
|
84
|
-
return Promise.reject(new
|
28
|
+
putModel(_model) {
|
29
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putModel', this));
|
85
30
|
}
|
86
31
|
|
87
32
|
/**
|
88
|
-
*
|
89
|
-
*
|
90
|
-
* @
|
91
|
-
* @throws
|
92
|
-
* @
|
93
|
-
* @abstract
|
33
|
+
* Delete a model
|
34
|
+
* @param {string} _id
|
35
|
+
* @throws MethodNotImplementedStorageEngineError
|
36
|
+
* @throws ModelNotFoundStorageEngineError
|
37
|
+
* @return Promise<void>
|
94
38
|
*/
|
95
|
-
|
96
|
-
return Promise.reject(new
|
39
|
+
deleteModel(_id) {
|
40
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('deleteModel', this));
|
97
41
|
}
|
98
42
|
|
99
43
|
/**
|
100
|
-
*
|
101
|
-
*
|
102
|
-
* @
|
103
|
-
* @
|
104
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
105
|
-
* @returns {Promise<void>}
|
106
|
-
* @abstract
|
44
|
+
* Get a model's index data
|
45
|
+
* @param {Model.constructor} _modelConstructor
|
46
|
+
* @throws MethodNotImplementedStorageEngineError
|
47
|
+
* @return Promise<void>
|
107
48
|
*/
|
108
|
-
|
109
|
-
return Promise.reject(new
|
49
|
+
getIndex(_modelConstructor) {
|
50
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getIndex', this));
|
110
51
|
}
|
111
52
|
|
112
53
|
/**
|
113
|
-
*
|
114
|
-
*
|
115
|
-
* @param {
|
116
|
-
* @
|
117
|
-
* @
|
118
|
-
* @returns {Promise<void>}
|
119
|
-
* @abstract
|
54
|
+
* Put a model's index data
|
55
|
+
* @param {Model.constructor} _modelConstructor
|
56
|
+
* @param {object} _data
|
57
|
+
* @throws MethodNotImplementedStorageEngineError
|
58
|
+
* @return Promise<void>
|
120
59
|
*/
|
121
|
-
|
122
|
-
return Promise.reject(new
|
60
|
+
putIndex(_modelConstructor, _data) {
|
61
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putIndex', this));
|
123
62
|
}
|
124
63
|
|
125
64
|
/**
|
126
|
-
*
|
127
|
-
*
|
128
|
-
* @
|
129
|
-
* @
|
130
|
-
* @returns {Promise<Array<Model>>} An array of models matching the search query.
|
131
|
-
* @throws {EngineError} Throws if the search index is not available for the model.
|
65
|
+
* Get a model's raw search index data
|
66
|
+
* @param {Model.constructor} _modelConstructor
|
67
|
+
* @throws MethodNotImplementedStorageEngineError
|
68
|
+
* @return Promise<object>
|
132
69
|
*/
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
const index =
|
137
|
-
(this._searchCache && this.configuration?.cache?.search && Date.now() - this._searchCache[0] < this.configuration.cache.search) ?
|
138
|
-
this._searchCache[1] :
|
139
|
-
await this.getSearchIndexCompiled(model)
|
140
|
-
.then(i => {
|
141
|
-
this._searchCache = [Date.now(), i];
|
142
|
-
return i;
|
143
|
-
})
|
144
|
-
.catch(error => {
|
145
|
-
throw new EngineError(`The model ${model.toString()} does not have a search index available.`, error);
|
146
|
-
});
|
147
|
-
|
148
|
-
const searchIndex = lunr.Index.load(index);
|
149
|
-
|
150
|
-
const results = searchIndex.search(`*${query}*`);
|
151
|
-
|
152
|
-
const output = [];
|
153
|
-
for (const result of results) {
|
154
|
-
output.push({
|
155
|
-
...result,
|
156
|
-
score: Number(result.score.toFixed(4)),
|
157
|
-
model: await this.get(model, result.ref),
|
158
|
-
});
|
159
|
-
}
|
160
|
-
|
161
|
-
return output;
|
70
|
+
getSearchIndex(_modelConstructor) {
|
71
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getSearchIndex', this));
|
162
72
|
}
|
163
73
|
|
164
74
|
/**
|
165
|
-
*
|
166
|
-
*
|
167
|
-
* @param {
|
168
|
-
* @
|
169
|
-
* @
|
75
|
+
* Put a model's raw and compiled search index data
|
76
|
+
* @param {Model.constructor} _constructor
|
77
|
+
* @param {Record<string, object>} _index
|
78
|
+
* @throws MethodNotImplementedStorageEngineError
|
79
|
+
* @return Promise<void>
|
170
80
|
*/
|
171
|
-
|
172
|
-
|
173
|
-
const index = await this.getIndex(model);
|
174
|
-
|
175
|
-
return new Query(query).execute(model, index);
|
176
|
-
}
|
177
|
-
|
178
|
-
/**
|
179
|
-
* Stores a model and its associated index data into the system.
|
180
|
-
*
|
181
|
-
* @param {Model} model - The model to store.
|
182
|
-
* @throws {EngineError} Throws if the model fails to validate or index fails to save.
|
183
|
-
*/
|
184
|
-
static async put(model) {
|
185
|
-
this.checkConfiguration();
|
186
|
-
const uploadedModels = [];
|
187
|
-
const indexUpdates = {};
|
188
|
-
|
189
|
-
/**
|
190
|
-
* Process a model, putting updates to the model and all linked models.
|
191
|
-
* @param {Model} m
|
192
|
-
* @return {Promise<void>}
|
193
|
-
*/
|
194
|
-
const processModel = async (m) => {
|
195
|
-
if (!uploadedModels.includes(m.id)) {
|
196
|
-
m.validate();
|
197
|
-
|
198
|
-
await this.putModel(m);
|
199
|
-
|
200
|
-
uploadedModels.push(m.id);
|
201
|
-
indexUpdates[m.constructor.name] = {
|
202
|
-
...indexUpdates[m.constructor.name] || {},
|
203
|
-
[m.id]: m,
|
204
|
-
};
|
205
|
-
|
206
|
-
if (m.constructor.searchProperties().length > 0) {
|
207
|
-
const rawSearchIndex = {
|
208
|
-
...await this.getSearchIndexRaw(m.constructor),
|
209
|
-
[m.id]: m.toSearchData(),
|
210
|
-
};
|
211
|
-
|
212
|
-
await this.putSearchIndexRaw(m.constructor, rawSearchIndex);
|
213
|
-
|
214
|
-
const compiledIndex = lunr(function () {
|
215
|
-
this.ref('id');
|
216
|
-
|
217
|
-
for (const field of m.constructor.searchProperties()) {
|
218
|
-
this.field(field);
|
219
|
-
}
|
220
|
-
|
221
|
-
Object.values(rawSearchIndex).forEach(function (doc) {
|
222
|
-
this.add(doc);
|
223
|
-
}, this);
|
224
|
-
});
|
225
|
-
|
226
|
-
await this.putSearchIndexCompiled(m.constructor, compiledIndex);
|
227
|
-
}
|
228
|
-
|
229
|
-
for (const [_, property] of Object.entries(m)) {
|
230
|
-
if (Type.Model.isModel(property)) {
|
231
|
-
await processModel(property);
|
232
|
-
}
|
233
|
-
if (Array.isArray(property) && Type.Model.isModel(property[0])) {
|
234
|
-
for (const subModel of property) {
|
235
|
-
await processModel(subModel);
|
236
|
-
}
|
237
|
-
}
|
238
|
-
}
|
239
|
-
}
|
240
|
-
};
|
241
|
-
|
242
|
-
await processModel(model);
|
243
|
-
await this.putIndex(indexUpdates);
|
244
|
-
}
|
245
|
-
|
246
|
-
/**
|
247
|
-
* Retrieves a model by its ID and converts it to its data representation.
|
248
|
-
*
|
249
|
-
* @param {Model.constructor} model - The model class to retrieve.
|
250
|
-
* @param {string} id - The ID of the model to retrieve.
|
251
|
-
* @returns {Model} The found model.
|
252
|
-
* @throws {NotFoundEngineError} Throws if the model is not found.
|
253
|
-
*/
|
254
|
-
static async get(model, id) {
|
255
|
-
this.checkConfiguration();
|
256
|
-
|
257
|
-
try {
|
258
|
-
const found = await this.getById(id);
|
259
|
-
return model.fromData(found);
|
260
|
-
} catch (error) {
|
261
|
-
if (error.constructor === NotImplementedError) throw error;
|
262
|
-
throw new NotFoundEngineError(`${this.name}.get(${id}) model not found`, error);
|
263
|
-
}
|
264
|
-
}
|
265
|
-
|
266
|
-
/**
|
267
|
-
* Deletes a model
|
268
|
-
*
|
269
|
-
* @param {Model} model
|
270
|
-
* @return {Promise<void>}
|
271
|
-
* @throws {NotFoundEngineError} Throws if the model is not found.
|
272
|
-
*/
|
273
|
-
static async delete(model) {
|
274
|
-
this.checkConfiguration();
|
275
|
-
|
276
|
-
const modelUpdates = [];
|
277
|
-
const processedModels = [];
|
278
|
-
const indexUpdates = {};
|
279
|
-
const additionalDeletions = [];
|
280
|
-
const deletedModels = [];
|
281
|
-
|
282
|
-
/**
|
283
|
-
* Delete the given model, updating search indexes as required.
|
284
|
-
* @param {Model} m - The model to be deleted.
|
285
|
-
* @return {Promise<void>}
|
286
|
-
*/
|
287
|
-
const deleteModel = async (m) => {
|
288
|
-
if (deletedModels.includes(m.id)) return;
|
289
|
-
if (m.constructor.searchProperties().length > 0) {
|
290
|
-
const rawSearchIndex = await this.getSearchIndexRaw(m.constructor);
|
291
|
-
|
292
|
-
delete rawSearchIndex[m.id];
|
293
|
-
|
294
|
-
await this.putSearchIndexRaw(m.constructor, rawSearchIndex);
|
295
|
-
|
296
|
-
const compiledIndex = lunr(function () {
|
297
|
-
this.ref('id');
|
298
|
-
|
299
|
-
for (const field of m.constructor.searchProperties()) {
|
300
|
-
this.field(field);
|
301
|
-
}
|
302
|
-
|
303
|
-
Object.values(rawSearchIndex).forEach(function (doc) {
|
304
|
-
this.add(doc);
|
305
|
-
}, this);
|
306
|
-
});
|
307
|
-
|
308
|
-
await this.putSearchIndexCompiled(m.constructor, compiledIndex);
|
309
|
-
}
|
310
|
-
|
311
|
-
if (m.constructor.indexedProperties().length > 0) {
|
312
|
-
indexUpdates[m.constructor.name] = {
|
313
|
-
...indexUpdates[m.constructor.name] || {},
|
314
|
-
[m.id]: undefined,
|
315
|
-
};
|
316
|
-
}
|
317
|
-
|
318
|
-
await this.deleteById(m.id);
|
319
|
-
deletedModels.push(m.id);
|
320
|
-
};
|
321
|
-
|
322
|
-
/**
|
323
|
-
* Process updates to all sub-models of the given model.
|
324
|
-
* @param {Model} m - The model to process for updates.
|
325
|
-
* @return {Promise<void>}
|
326
|
-
*/
|
327
|
-
const processModelUpdates = async (m) => {
|
328
|
-
if (!processedModels.includes(m.id)) {
|
329
|
-
processedModels.push(m.id);
|
330
|
-
|
331
|
-
for (const [key, property] of Object.entries(m)) {
|
332
|
-
if (Type.Model.isModel(property)) {
|
333
|
-
if (property.id === model.id) {
|
334
|
-
m[key] = undefined;
|
335
|
-
indexUpdates[m.constructor.name] = {
|
336
|
-
...indexUpdates[m.constructor.name] || {},
|
337
|
-
[m.id]: m,
|
338
|
-
};
|
339
|
-
modelUpdates.push(m);
|
340
|
-
}
|
341
|
-
|
342
|
-
if (m.id !== model.id && (Type.Model.isModel(m.constructor[key]) ? m.constructor[key] : m.constructor[key]())._required) {
|
343
|
-
additionalDeletions.push(m);
|
344
|
-
}
|
345
|
-
|
346
|
-
await processModelUpdates(property);
|
347
|
-
}
|
348
|
-
if (Array.isArray(property) && Type.Model.isModel(property[0])) {
|
349
|
-
for (const [index, subModel] of property.entries()) {
|
350
|
-
if (subModel.id === model.id) {
|
351
|
-
m[key].splice(index, 1);
|
352
|
-
indexUpdates[m.constructor.name] = {
|
353
|
-
...indexUpdates[m.constructor.name] || {},
|
354
|
-
[m.id]: m,
|
355
|
-
};
|
356
|
-
modelUpdates.push(m);
|
357
|
-
}
|
358
|
-
await processModelUpdates(subModel);
|
359
|
-
}
|
360
|
-
}
|
361
|
-
}
|
362
|
-
}
|
363
|
-
};
|
364
|
-
|
365
|
-
try {
|
366
|
-
const hydrated = await this.hydrate(model);
|
367
|
-
await processModelUpdates(hydrated);
|
368
|
-
await deleteModel(hydrated);
|
369
|
-
|
370
|
-
for (const updatedModel of modelUpdates) {
|
371
|
-
if (!additionalDeletions.map(m => m.id).includes(updatedModel.id)) {
|
372
|
-
await this.put(updatedModel);
|
373
|
-
}
|
374
|
-
}
|
375
|
-
|
376
|
-
for (const modelToBeDeleted of additionalDeletions) {
|
377
|
-
await deleteModel(modelToBeDeleted);
|
378
|
-
}
|
379
|
-
|
380
|
-
await this.putIndex(indexUpdates);
|
381
|
-
} catch (error) {
|
382
|
-
if (error.constructor === NotImplementedError) throw error;
|
383
|
-
throw new CannotDeleteEngineError(`${this.name}.delete(${model.id}) model cannot be deleted`, error);
|
384
|
-
}
|
385
|
-
}
|
386
|
-
|
387
|
-
/**
|
388
|
-
* Hydrates a model by populating its related properties (e.g., submodels) from stored data.
|
389
|
-
*
|
390
|
-
* @param {Model} model - The model to hydrate.
|
391
|
-
* @returns {Model} The hydrated model.
|
392
|
-
*/
|
393
|
-
static async hydrate(model) {
|
394
|
-
this.checkConfiguration();
|
395
|
-
const hydratedModels = {};
|
396
|
-
|
397
|
-
/**
|
398
|
-
* Hydrate a model
|
399
|
-
* @param {Model} modelToProcess
|
400
|
-
* @return {Promise<Model>}
|
401
|
-
*/
|
402
|
-
const hydrateModel = async (modelToProcess) => {
|
403
|
-
hydratedModels[modelToProcess.id] = modelToProcess;
|
404
|
-
|
405
|
-
for (const [name, property] of Object.entries(modelToProcess)) {
|
406
|
-
if (Type.Model.isDryModel(property)) {
|
407
|
-
// skipcq: JS-0129
|
408
|
-
modelToProcess[name] = await hydrateSubModel(property, modelToProcess, name);
|
409
|
-
} else if (Array.isArray(property) && Type.Model.isDryModel(property[0])) {
|
410
|
-
// skipcq: JS-0129
|
411
|
-
modelToProcess[name] = await hydrateModelList(property, modelToProcess, name);
|
412
|
-
}
|
413
|
-
}
|
414
|
-
|
415
|
-
return modelToProcess;
|
416
|
-
};
|
417
|
-
|
418
|
-
/**
|
419
|
-
* Hydrate a dry sub model
|
420
|
-
* @param property
|
421
|
-
* @param modelToProcess
|
422
|
-
* @param name
|
423
|
-
* @return {Promise<Model>}
|
424
|
-
*/
|
425
|
-
const hydrateSubModel = async (property, modelToProcess, name) => {
|
426
|
-
if (hydratedModels[property.id]) {
|
427
|
-
return hydratedModels[property.id];
|
428
|
-
}
|
429
|
-
|
430
|
-
const subModelClass = getSubModelClass(modelToProcess, name);
|
431
|
-
const subModel = await this.get(subModelClass, property.id);
|
432
|
-
|
433
|
-
const hydratedSubModel = await hydrateModel(subModel);
|
434
|
-
hydratedModels[property.id] = hydratedSubModel;
|
435
|
-
return hydratedSubModel;
|
436
|
-
};
|
437
|
-
|
438
|
-
/**
|
439
|
-
* Hydrate an array of dry models
|
440
|
-
* @param property
|
441
|
-
* @param modelToProcess
|
442
|
-
* @param name
|
443
|
-
* @return {Promise<Awaited<*>[]>}
|
444
|
-
*/
|
445
|
-
const hydrateModelList = async (property, modelToProcess, name) => {
|
446
|
-
const subModelClass = getSubModelClass(modelToProcess, name, true);
|
447
|
-
|
448
|
-
const newModelList = await Promise.all(property.map(subModel => {
|
449
|
-
if (hydratedModels[subModel.id]) {
|
450
|
-
return hydratedModels[subModel.id];
|
451
|
-
}
|
452
|
-
|
453
|
-
return this.get(subModelClass, subModel.id);
|
454
|
-
}));
|
455
|
-
|
456
|
-
return Promise.all(newModelList.map(async subModel => {
|
457
|
-
if (hydratedModels[subModel.id]) {
|
458
|
-
return hydratedModels[subModel.id];
|
459
|
-
}
|
460
|
-
|
461
|
-
const hydratedSubModel = await hydrateModel(subModel);
|
462
|
-
hydratedModels[hydratedSubModel.id] = hydratedSubModel;
|
463
|
-
return hydratedSubModel;
|
464
|
-
}));
|
465
|
-
};
|
466
|
-
|
467
|
-
/**
|
468
|
-
* Get the class of a sub model
|
469
|
-
* @param modelToProcess
|
470
|
-
* @param name
|
471
|
-
* @param isArray
|
472
|
-
* @return {Model.constructor|Type}
|
473
|
-
*/
|
474
|
-
function getSubModelClass(modelToProcess, name, isArray = false) {
|
475
|
-
const constructorField = modelToProcess.constructor[name];
|
476
|
-
|
477
|
-
if (constructorField instanceof Function && !constructorField.prototype) {
|
478
|
-
return isArray ? constructorField()._items : constructorField();
|
479
|
-
}
|
480
|
-
|
481
|
-
return isArray ? constructorField._items : constructorField;
|
482
|
-
}
|
483
|
-
|
484
|
-
return hydrateModel(await this.get(model.constructor, model.id));
|
485
|
-
}
|
486
|
-
|
487
|
-
/**
|
488
|
-
* Configures the engine with specific settings.
|
489
|
-
*
|
490
|
-
* @param {Object} configuration - The configuration settings for the engine.
|
491
|
-
* @returns {StorageEngine} A new engine instance with the applied configuration.
|
492
|
-
*/
|
493
|
-
static configure(configuration) {
|
494
|
-
/**
|
495
|
-
* @class ConfiguredStore
|
496
|
-
* @extends StorageEngine
|
497
|
-
*/
|
498
|
-
class ConfiguredStore extends this {
|
499
|
-
static configuration = configuration;
|
500
|
-
}
|
501
|
-
|
502
|
-
Object.defineProperty(ConfiguredStore, 'name', {value: `${this.toString()}`});
|
503
|
-
|
504
|
-
return ConfiguredStore;
|
505
|
-
}
|
506
|
-
|
507
|
-
/**
|
508
|
-
* Checks if the engine is properly configured.
|
509
|
-
*
|
510
|
-
* @throws {MissConfiguredError} Throws if the engine is misconfigured.
|
511
|
-
* @abstract
|
512
|
-
*/
|
513
|
-
static checkConfiguration() {
|
514
|
-
// Implemented in extending StorageEngine class
|
515
|
-
}
|
516
|
-
|
517
|
-
/**
|
518
|
-
* Returns the name of the engine class.
|
519
|
-
*
|
520
|
-
* @returns {string} The name of the engine class.
|
521
|
-
*/
|
522
|
-
static toString() {
|
523
|
-
return this.name;
|
81
|
+
putSearchIndex(_constructor, _index) {
|
82
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putSearchIndex', this));
|
524
83
|
}
|
525
84
|
}
|
526
85
|
|
527
86
|
/**
|
528
|
-
*
|
529
|
-
*
|
87
|
+
* @class StorageEngineError
|
88
|
+
* @extends Error
|
530
89
|
*/
|
531
|
-
export class
|
532
|
-
/**
|
533
|
-
* The underlying error that caused this engine error, if available.
|
534
|
-
* @type {Error|undefined}
|
535
|
-
*/
|
536
|
-
underlyingError;
|
537
|
-
|
538
|
-
/**
|
539
|
-
* Creates an instance of `EngineError`.
|
540
|
-
*
|
541
|
-
* @param {string} message - The error message.
|
542
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
543
|
-
*/
|
544
|
-
constructor(message, error = undefined) {
|
545
|
-
super(message);
|
546
|
-
this.underlyingError = error;
|
547
|
-
}
|
90
|
+
export class StorageEngineError extends Error {
|
548
91
|
}
|
549
92
|
|
550
93
|
/**
|
551
|
-
*
|
552
|
-
*
|
94
|
+
* @class MisconfiguredStorageEngineError
|
95
|
+
* @extends StorageEngineError
|
553
96
|
*/
|
554
|
-
export class
|
97
|
+
export class MisconfiguredStorageEngineError extends StorageEngineError {
|
555
98
|
/**
|
556
|
-
*
|
557
|
-
*
|
558
|
-
* @param {string} message - The error message.
|
559
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
99
|
+
* @param {string} message
|
100
|
+
* @param {StorageEngine} storageEngine
|
560
101
|
*/
|
102
|
+
constructor(message, storageEngine) {
|
103
|
+
super(`Incorrect configuration given for storage engine ${storageEngine.constructor.name}: ${message}`);
|
104
|
+
}
|
561
105
|
}
|
562
106
|
|
563
107
|
/**
|
564
|
-
*
|
565
|
-
*
|
108
|
+
* @class MethodNotImplementedStorageEngineError
|
109
|
+
* @extends StorageEngineError
|
566
110
|
*/
|
567
|
-
export class
|
111
|
+
export class MethodNotImplementedStorageEngineError extends StorageEngineError {
|
568
112
|
/**
|
569
|
-
*
|
570
|
-
*
|
571
|
-
* @param {string} message - The error message.
|
572
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
113
|
+
* @param {string} method
|
114
|
+
* @param {StorageEngine} storageEngine
|
573
115
|
*/
|
116
|
+
constructor(method, storageEngine) {
|
117
|
+
super(`The method ${method} is not implemented in the storage engine ${storageEngine.constructor.name}`);
|
118
|
+
}
|
574
119
|
}
|
575
120
|
|
576
121
|
/**
|
577
|
-
*
|
578
|
-
*
|
122
|
+
* @class ModelNotFoundStorageEngineError
|
123
|
+
* @extends StorageEngineError
|
579
124
|
*/
|
580
|
-
export class
|
125
|
+
export class ModelNotFoundStorageEngineError extends StorageEngineError {
|
581
126
|
/**
|
582
|
-
*
|
583
|
-
*
|
584
|
-
* @param {string} message - The error message.
|
585
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
127
|
+
* @param {string} modelId
|
586
128
|
*/
|
129
|
+
constructor(modelId) {
|
130
|
+
super(`The model ${modelId} was not found`);
|
131
|
+
}
|
587
132
|
}
|
588
133
|
|
589
|
-
|
590
|
-
* Represents an error indicating that the engine is misconfigured.
|
591
|
-
* Extends the `EngineError` class.
|
592
|
-
*/
|
593
|
-
export class MissConfiguredError extends EngineError {
|
594
|
-
/**
|
595
|
-
* The configuration that led to the misconfiguration error.
|
596
|
-
* @type {Object}
|
597
|
-
*/
|
598
|
-
configuration;
|
599
|
-
|
134
|
+
export class DeleteHasUnintendedConsequencesStorageEngineError extends StorageEngineError {
|
600
135
|
/**
|
601
|
-
*
|
602
|
-
*
|
603
|
-
* @param {Object} configuration - The configuration object that caused the misconfiguration.
|
136
|
+
* @param {string} modelId
|
137
|
+
* @param {object} consequences
|
604
138
|
*/
|
605
|
-
constructor(
|
606
|
-
super(
|
607
|
-
this.
|
139
|
+
constructor(modelId, consequences) {
|
140
|
+
super(`Deleting ${modelId} has unintended consequences`);
|
141
|
+
this.consequences = consequences;
|
608
142
|
}
|
609
143
|
}
|
610
144
|
|
611
|
-
|
145
|
+
/**
|
146
|
+
*
|
147
|
+
* @param {Array<Transaction>} transactions
|
148
|
+
* @param {StorageEngine} engine
|
149
|
+
* @return {StorageEngine}
|
150
|
+
*/
|
151
|
+
export function CreateTransactionalStorageEngine(transactions, engine) {
|
152
|
+
const transactionalEngine = Object.create(engine);
|
153
|
+
|
154
|
+
transactionalEngine.putModel = (...args) => {
|
155
|
+
transactions.push(new Transaction('putModel', ...args));
|
156
|
+
return Promise.resolve();
|
157
|
+
};
|
158
|
+
|
159
|
+
transactionalEngine.deleteModel = (...args) => {
|
160
|
+
transactions.push(new Transaction('deleteModel', ...args));
|
161
|
+
return Promise.resolve();
|
162
|
+
};
|
163
|
+
|
164
|
+
transactionalEngine.putIndex = (...args) => {
|
165
|
+
transactions.push(new Transaction('putIndex', ...args));
|
166
|
+
return Promise.resolve();
|
167
|
+
};
|
168
|
+
|
169
|
+
transactionalEngine.putSearchIndex = (...args) => {
|
170
|
+
transactions.push(new Transaction('putSearchIndex', ...args));
|
171
|
+
return Promise.resolve();
|
172
|
+
};
|
173
|
+
|
174
|
+
return transactionalEngine;
|
175
|
+
}
|