@acodeninja/persist 3.0.0-next.9 → 3.0.1-next.1
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 +14 -14
- package/docs/defining-models.md +61 -0
- package/docs/http.openapi.yml +138 -0
- package/docs/{model-property-types.md → model-properties.md} +76 -43
- package/docs/models-as-properties.md +46 -46
- package/docs/search-queries.md +11 -13
- package/docs/storage-engines.md +19 -35
- package/docs/structured-queries.md +59 -48
- 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 +750 -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 +95 -55
- package/src/data/Property.js +21 -0
- package/src/data/SearchIndex.js +106 -0
- package/src/{type/complex → data/properties}/ArrayType.js +5 -3
- package/src/{type/simple → data/properties}/BooleanType.js +3 -3
- package/src/{type/complex → data/properties}/CustomType.js +5 -5
- package/src/{type/simple → data/properties}/DateType.js +4 -4
- package/src/{type/simple → data/properties}/NumberType.js +3 -3
- package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
- package/src/{type/resolved → data/properties}/SlugType.js +1 -1
- package/src/{type/simple → data/properties}/StringType.js +3 -3
- package/src/{type → data/properties}/Type.js +13 -3
- package/src/engine/storage/HTTPStorageEngine.js +149 -253
- package/src/engine/storage/S3StorageEngine.js +108 -195
- package/src/engine/storage/StorageEngine.js +131 -549
- 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 -196
- package/src/Transactions.js +0 -145
- package/src/engine/StorageEngine.js +0 -472
- package/src/engine/storage/FileStorageEngine.js +0 -213
- package/src/type/index.js +0 -32
@@ -1,611 +1,193 @@
|
|
1
|
-
|
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
|
-
|
1
|
+
export default class StorageEngine {
|
39
2
|
/**
|
40
|
-
*
|
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
|
3
|
+
* @param {Object} configuration
|
46
4
|
*/
|
47
|
-
|
48
|
-
|
49
|
-
}
|
50
|
-
|
51
|
-
/**
|
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
|
58
|
-
*/
|
59
|
-
static getIndex(_model) {
|
60
|
-
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getIndex()`));
|
5
|
+
constructor(configuration = {}) {
|
6
|
+
this.configuration = configuration;
|
61
7
|
}
|
62
8
|
|
63
9
|
/**
|
64
|
-
*
|
65
|
-
*
|
66
|
-
* @
|
67
|
-
* @throws
|
68
|
-
* @
|
69
|
-
* @abstract
|
10
|
+
* Get a model
|
11
|
+
* @param {string} _id
|
12
|
+
* @throws MethodNotImplementedStorageEngineError
|
13
|
+
* @throws ModelNotFoundStorageEngineError
|
14
|
+
* @return Promise<Object>
|
70
15
|
*/
|
71
|
-
|
72
|
-
return Promise.reject(new
|
16
|
+
getModel(_id) {
|
17
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getModel', this));
|
73
18
|
}
|
74
19
|
|
75
20
|
/**
|
76
|
-
*
|
77
|
-
*
|
78
|
-
* @
|
79
|
-
* @
|
80
|
-
* @returns {Promise<Object>} - Returns a promise resolving to the compiled search index.
|
81
|
-
* @abstract
|
21
|
+
* Update a model
|
22
|
+
* @param {Object} _model
|
23
|
+
* @throws MethodNotImplementedStorageEngineError
|
24
|
+
* @return Promise<void>
|
82
25
|
*/
|
83
|
-
|
84
|
-
return Promise.reject(new
|
26
|
+
putModel(_model) {
|
27
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putModel', this));
|
85
28
|
}
|
86
29
|
|
87
30
|
/**
|
88
|
-
*
|
89
|
-
*
|
90
|
-
* @
|
91
|
-
* @throws
|
92
|
-
* @
|
93
|
-
* @abstract
|
31
|
+
* Delete a model
|
32
|
+
* @param {string} _id
|
33
|
+
* @throws MethodNotImplementedStorageEngineError
|
34
|
+
* @throws ModelNotFoundStorageEngineError
|
35
|
+
* @return Promise<void>
|
94
36
|
*/
|
95
|
-
|
96
|
-
return Promise.reject(new
|
37
|
+
deleteModel(_id) {
|
38
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('deleteModel', this));
|
97
39
|
}
|
98
40
|
|
99
41
|
/**
|
100
|
-
*
|
101
|
-
*
|
102
|
-
* @
|
103
|
-
* @
|
104
|
-
* @throws {NotImplementedError} Throws if the method is not implemented.
|
105
|
-
* @returns {Promise<void>}
|
106
|
-
* @abstract
|
42
|
+
* Get a model's index data
|
43
|
+
* @param {Model.constructor} _modelConstructor
|
44
|
+
* @throws MethodNotImplementedStorageEngineError
|
45
|
+
* @return Promise<Record<String, Object>>
|
107
46
|
*/
|
108
|
-
|
109
|
-
return Promise.reject(new
|
47
|
+
getIndex(_modelConstructor) {
|
48
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getIndex', this));
|
110
49
|
}
|
111
50
|
|
112
51
|
/**
|
113
|
-
*
|
114
|
-
*
|
115
|
-
* @param {
|
116
|
-
* @
|
117
|
-
* @
|
118
|
-
* @returns {Promise<void>}
|
119
|
-
* @abstract
|
52
|
+
* Put a model's index data
|
53
|
+
* @param {Model.constructor} _modelConstructor
|
54
|
+
* @param {Record<String, Object>} _data
|
55
|
+
* @throws MethodNotImplementedStorageEngineError
|
56
|
+
* @return Promise<void>
|
120
57
|
*/
|
121
|
-
|
122
|
-
return Promise.reject(new
|
58
|
+
putIndex(_modelConstructor, _data) {
|
59
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putIndex', this));
|
123
60
|
}
|
124
61
|
|
125
62
|
/**
|
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.
|
63
|
+
* Get a model's raw search index data
|
64
|
+
* @param {Model.constructor} _modelConstructor
|
65
|
+
* @throws MethodNotImplementedStorageEngineError
|
66
|
+
* @return Promise<Record<String, Object>>
|
132
67
|
*/
|
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;
|
68
|
+
getSearchIndex(_modelConstructor) {
|
69
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('getSearchIndex', this));
|
162
70
|
}
|
163
71
|
|
164
72
|
/**
|
165
|
-
*
|
166
|
-
*
|
167
|
-
* @param {
|
168
|
-
* @
|
169
|
-
* @
|
73
|
+
* Put a model's raw and compiled search index data
|
74
|
+
* @param {Model.constructor} _constructor
|
75
|
+
* @param {Record<string, object>} _index
|
76
|
+
* @throws MethodNotImplementedStorageEngineError
|
77
|
+
* @return Promise<Record<String, Object>>
|
170
78
|
*/
|
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;
|
79
|
+
putSearchIndex(_constructor, _index) {
|
80
|
+
return Promise.reject(new MethodNotImplementedStorageEngineError('putSearchIndex', this));
|
524
81
|
}
|
525
82
|
}
|
526
83
|
|
527
84
|
/**
|
528
|
-
*
|
529
|
-
*
|
85
|
+
* @class StorageEngineError
|
86
|
+
* @extends Error
|
530
87
|
*/
|
531
|
-
export class
|
532
|
-
|
533
|
-
* The underlying error that caused this engine error, if available.
|
534
|
-
* @type {Error|undefined}
|
535
|
-
*/
|
536
|
-
underlyingError;
|
88
|
+
export class StorageEngineError extends Error {
|
89
|
+
}
|
537
90
|
|
91
|
+
/**
|
92
|
+
* @class MisconfiguredStorageEngineError
|
93
|
+
* @extends StorageEngineError
|
94
|
+
*/
|
95
|
+
export class MisconfiguredStorageEngineError extends StorageEngineError {
|
538
96
|
/**
|
539
|
-
*
|
540
|
-
*
|
541
|
-
* @param {string} message - The error message.
|
542
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
97
|
+
* @param {string} message
|
98
|
+
* @param {StorageEngine} storageEngine
|
543
99
|
*/
|
544
|
-
constructor(message,
|
545
|
-
super(message);
|
546
|
-
this.underlyingError = error;
|
100
|
+
constructor(message, storageEngine) {
|
101
|
+
super(`Incorrect configuration given for storage engine ${storageEngine.constructor.name}: ${message}`);
|
547
102
|
}
|
548
103
|
}
|
549
104
|
|
550
105
|
/**
|
551
|
-
*
|
552
|
-
*
|
106
|
+
* @class MethodNotImplementedStorageEngineError
|
107
|
+
* @extends StorageEngineError
|
553
108
|
*/
|
554
|
-
export class
|
109
|
+
export class MethodNotImplementedStorageEngineError extends StorageEngineError {
|
555
110
|
/**
|
556
|
-
*
|
557
|
-
*
|
558
|
-
* @param {string} message - The error message.
|
559
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
111
|
+
* @param {string} method
|
112
|
+
* @param {StorageEngine} storageEngine
|
560
113
|
*/
|
114
|
+
constructor(method, storageEngine) {
|
115
|
+
super(`The method ${method} is not implemented in the storage engine ${storageEngine.constructor.name}`);
|
116
|
+
}
|
561
117
|
}
|
562
118
|
|
563
119
|
/**
|
564
|
-
*
|
565
|
-
*
|
120
|
+
* @class ModelNotFoundStorageEngineError
|
121
|
+
* @extends StorageEngineError
|
566
122
|
*/
|
567
|
-
export class
|
123
|
+
export class ModelNotFoundStorageEngineError extends StorageEngineError {
|
568
124
|
/**
|
569
|
-
*
|
570
|
-
*
|
571
|
-
* @param {string} message - The error message.
|
572
|
-
* @param {Error} [error] - An optional underlying error that caused this error.
|
125
|
+
* @param {string} modelId
|
573
126
|
*/
|
127
|
+
constructor(modelId) {
|
128
|
+
super(`The model ${modelId} was not found`);
|
129
|
+
}
|
574
130
|
}
|
575
131
|
|
576
|
-
|
577
|
-
* Represents an error indicating that a certain method or functionality is not implemented in the engine.
|
578
|
-
* Extends the `EngineError` class.
|
579
|
-
*/
|
580
|
-
export class NotImplementedError extends EngineError {
|
132
|
+
export class DeleteHasUnintendedConsequencesStorageEngineError extends StorageEngineError {
|
581
133
|
/**
|
582
|
-
*
|
583
|
-
*
|
584
|
-
* @param {
|
585
|
-
* @param {
|
134
|
+
* @param {string} modelId
|
135
|
+
* @param {Object} consequences
|
136
|
+
* @param {Array<String>?} consequences.willDelete
|
137
|
+
* @param {Array<String>?} consequences.willUpdate
|
586
138
|
*/
|
139
|
+
constructor(modelId, consequences) {
|
140
|
+
super(`Deleting ${modelId} has unintended consequences`);
|
141
|
+
this.consequences = consequences;
|
142
|
+
}
|
587
143
|
}
|
588
144
|
|
589
145
|
/**
|
590
|
-
* Represents
|
591
|
-
*
|
146
|
+
* Represents a transactional operation to be executed, typically queued and later committed.
|
147
|
+
*
|
148
|
+
* Stores the method to invoke, the arguments to apply, and tracks the result or error state
|
149
|
+
* of the transaction once it's processed.
|
150
|
+
*
|
151
|
+
* @class Operation
|
592
152
|
*/
|
593
|
-
export class
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
/**
|
601
|
-
* Creates an instance of `MissConfiguredError`.
|
602
|
-
*
|
603
|
-
* @param {Object} configuration - The configuration object that caused the misconfiguration.
|
604
|
-
*/
|
605
|
-
constructor(configuration) {
|
606
|
-
super('StorageEngine is miss-configured');
|
607
|
-
this.configuration = configuration;
|
153
|
+
export class Operation {
|
154
|
+
constructor(method, ...args) {
|
155
|
+
this.method = method;
|
156
|
+
this.args = args;
|
157
|
+
this.original = undefined;
|
158
|
+
this.error = undefined;
|
159
|
+
this.committed = false;
|
608
160
|
}
|
609
161
|
}
|
610
162
|
|
611
|
-
|
163
|
+
/**
|
164
|
+
*
|
165
|
+
* @param {Array<Operation>} transactions
|
166
|
+
* @param {StorageEngine} engine
|
167
|
+
* @return {StorageEngine}
|
168
|
+
*/
|
169
|
+
export function CreateTransactionalStorageEngine(operations, engine) {
|
170
|
+
const transactionalEngine = Object.create(engine);
|
171
|
+
|
172
|
+
transactionalEngine.putModel = (...args) => {
|
173
|
+
operations.push(new Operation('putModel', ...args));
|
174
|
+
return Promise.resolve();
|
175
|
+
};
|
176
|
+
|
177
|
+
transactionalEngine.deleteModel = (...args) => {
|
178
|
+
operations.push(new Operation('deleteModel', ...args));
|
179
|
+
return Promise.resolve();
|
180
|
+
};
|
181
|
+
|
182
|
+
transactionalEngine.putIndex = (...args) => {
|
183
|
+
operations.push(new Operation('putIndex', ...args));
|
184
|
+
return Promise.resolve();
|
185
|
+
};
|
186
|
+
|
187
|
+
transactionalEngine.putSearchIndex = (...args) => {
|
188
|
+
operations.push(new Operation('putSearchIndex', ...args));
|
189
|
+
return Promise.resolve();
|
190
|
+
};
|
191
|
+
|
192
|
+
return transactionalEngine;
|
193
|
+
}
|