@acodeninja/persist 3.0.0-next.2 → 3.0.0-next.20

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 +6 -6
  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} +37 -35
  6. package/docs/models-as-properties.md +40 -40
  7. package/docs/search-queries.md +3 -5
  8. package/docs/storage-engines.md +19 -35
  9. package/docs/structured-queries.md +56 -45
  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 +631 -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 +41 -26
  20. package/src/data/Property.js +19 -0
  21. package/src/data/SearchIndex.js +106 -0
  22. package/src/{type/complex → data/properties}/ArrayType.js +1 -1
  23. package/src/{type/simple → data/properties}/BooleanType.js +1 -1
  24. package/src/{type/complex → data/properties}/CustomType.js +1 -1
  25. package/src/{type/simple → data/properties}/DateType.js +1 -1
  26. package/src/{type/simple → data/properties}/NumberType.js +1 -1
  27. package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
  28. package/src/{type/simple → data/properties}/StringType.js +1 -1
  29. package/src/{type → data/properties}/Type.js +8 -0
  30. package/src/engine/storage/HTTPStorageEngine.js +149 -253
  31. package/src/engine/storage/S3StorageEngine.js +108 -195
  32. package/src/engine/storage/StorageEngine.js +114 -550
  33. package/exports/engine/storage/file.js +0 -3
  34. package/exports/engine/storage/http.js +0 -3
  35. package/exports/engine/storage/s3.js +0 -3
  36. package/src/SchemaCompiler.js +0 -192
  37. package/src/Transactions.js +0 -145
  38. package/src/engine/StorageEngine.js +0 -250
  39. package/src/engine/storage/FileStorageEngine.js +0 -213
  40. package/src/type/index.js +0 -32
  41. /package/src/{type/resolved → data/properties}/SlugType.js +0 -0
@@ -1,611 +1,175 @@
1
- import Query from '../../Query.js';
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
- * Retrieves the index for a given model. This method must be implemented by subclasses.
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
- static getIndex(_model) {
60
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .getIndex()`));
7
+ constructor(configuration = {}) {
8
+ this.configuration = configuration;
61
9
  }
62
10
 
63
11
  /**
64
- * Saves the index for a given model. This method must be implemented by subclasses.
65
- *
66
- * @param {Object} _index - The index data to save.
67
- * @throws {NotImplementedError} Throws if the method is not implemented.
68
- * @returns {Promise<void>}
69
- * @abstract
12
+ * Get a model
13
+ * @param {string} _id
14
+ * @throws MethodNotImplementedStorageEngineError
15
+ * @throws ModelNotFoundStorageEngineError
16
+ * @return Promise<Model>
70
17
  */
71
- static putIndex(_index) {
72
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .putIndex()`));
18
+ getModel(_id) {
19
+ return Promise.reject(new MethodNotImplementedStorageEngineError('getModel', this));
73
20
  }
74
21
 
75
22
  /**
76
- * Retrieves the compiled search index for a model. This method must be implemented by subclasses.
77
- *
78
- * @param {Model.constructor} _model - The model to retrieve the compiled search index for.
79
- * @throws {NotImplementedError} Throws if the method is not implemented.
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
- static getSearchIndexCompiled(_model) {
84
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`));
28
+ putModel(_model) {
29
+ return Promise.reject(new MethodNotImplementedStorageEngineError('putModel', this));
85
30
  }
86
31
 
87
32
  /**
88
- * Retrieves the raw search index for a model. This method must be implemented by subclasses.
89
- *
90
- * @param {Model.constructor} _model - The model to retrieve the raw search index for.
91
- * @throws {NotImplementedError} Throws if the method is not implemented.
92
- * @returns {Promise<Object>} - Returns a promise resolving to the raw search index.
93
- * @abstract
33
+ * Delete a model
34
+ * @param {string} _id
35
+ * @throws MethodNotImplementedStorageEngineError
36
+ * @throws ModelNotFoundStorageEngineError
37
+ * @return Promise<void>
94
38
  */
95
- static getSearchIndexRaw(_model) {
96
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`));
39
+ deleteModel(_id) {
40
+ return Promise.reject(new MethodNotImplementedStorageEngineError('deleteModel', this));
97
41
  }
98
42
 
99
43
  /**
100
- * Saves the compiled search index for a model. This method must be implemented by subclasses.
101
- *
102
- * @param {Model.constructor} _model - The model for which the compiled search index is saved.
103
- * @param {Object} _compiledIndex - The compiled search index data.
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
- static putSearchIndexCompiled(_model, _compiledIndex) {
109
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`));
49
+ getIndex(_modelConstructor) {
50
+ return Promise.reject(new MethodNotImplementedStorageEngineError('getIndex', this));
110
51
  }
111
52
 
112
53
  /**
113
- * Saves the raw search index for a model. This method must be implemented by subclasses.
114
- *
115
- * @param {Model.constructor} _model - The model for which the raw search index is saved.
116
- * @param {Object} _rawIndex - The raw search index data.
117
- * @throws {NotImplementedError} Throws if the method is not implemented.
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
- static putSearchIndexRaw(_model, _rawIndex) {
122
- return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`));
60
+ putIndex(_modelConstructor, _data) {
61
+ return Promise.reject(new MethodNotImplementedStorageEngineError('putIndex', this));
123
62
  }
124
63
 
125
64
  /**
126
- * Performs a search query on a model's index and returns the matching models.
127
- *
128
- * @param {Model.constructor} model - The model class.
129
- * @param {string} query - The search query string.
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
- static async search(model, query) {
134
- this.checkConfiguration();
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
- * Finds models that match a query in the model's index.
166
- *
167
- * @param {Model.constructor} model - The model class to search.
168
- * @param {object} query - The query object containing search criteria.
169
- * @returns {Array<Model>} An array of models matching the query.
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
- static async find(model, query) {
172
- this.checkConfiguration();
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
- * Represents a general error that occurs within the engine.
529
- * Extends the built-in `Error` class.
87
+ * @class StorageEngineError
88
+ * @extends Error
530
89
  */
531
- export class EngineError extends Error {
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
- * Represents an error that occurs when a requested resource or item is not found in the engine.
552
- * Extends the `EngineError` class.
94
+ * @class MisconfiguredStorageEngineError
95
+ * @extends StorageEngineError
553
96
  */
554
- export class NotFoundEngineError extends EngineError {
97
+ export class MisconfiguredStorageEngineError extends StorageEngineError {
555
98
  /**
556
- * Creates an instance of `NotFoundEngineError`.
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
- * Represents an error that occurs when a requested resource or item cannot be deleted by the engine.
565
- * Extends the `EngineError` class.
108
+ * @class MethodNotImplementedStorageEngineError
109
+ * @extends StorageEngineError
566
110
  */
567
- export class CannotDeleteEngineError extends EngineError {
111
+ export class MethodNotImplementedStorageEngineError extends StorageEngineError {
568
112
  /**
569
- * Creates an instance of `CannotDeleteEngineError`.
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
- * Represents an error indicating that a certain method or functionality is not implemented in the engine.
578
- * Extends the `EngineError` class.
122
+ * @class ModelNotFoundStorageEngineError
123
+ * @extends StorageEngineError
579
124
  */
580
- export class NotImplementedError extends EngineError {
125
+ export class ModelNotFoundStorageEngineError extends StorageEngineError {
581
126
  /**
582
- * Creates an instance of `NotImplementedError`.
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
- * Creates an instance of `MissConfiguredError`.
602
- *
603
- * @param {Object} configuration - The configuration object that caused the misconfiguration.
136
+ * @param {string} modelId
137
+ * @param {object} consequences
604
138
  */
605
- constructor(configuration) {
606
- super('StorageEngine is miss-configured');
607
- this.configuration = configuration;
139
+ constructor(modelId, consequences) {
140
+ super(`Deleting ${modelId} has unintended consequences`);
141
+ this.consequences = consequences;
608
142
  }
609
143
  }
610
144
 
611
- export default StorageEngine;
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
+ }