@acodeninja/persist 2.2.2 → 2.2.3
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/package.json +1 -1
- package/src/Query.js +55 -20
- package/src/Transactions.js +80 -2
- package/src/engine/Engine.js +25 -17
- package/src/engine/HTTPEngine.js +8 -8
- package/src/engine/S3Engine.js +1 -1
- package/src/type/Model.js +2 -2
package/package.json
CHANGED
package/src/Query.js
CHANGED
@@ -50,33 +50,68 @@ class Query {
|
|
50
50
|
* @returns {Array<Model>} The models that match the query.
|
51
51
|
*/
|
52
52
|
execute(model, index) {
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
return Object.values(index)
|
54
|
+
.filter(m =>
|
55
|
+
this._splitQuery(this.query)
|
56
|
+
.map(query => Boolean(this._matchesQuery(m, query)))
|
57
|
+
.every(c => c),
|
58
|
+
)
|
59
|
+
.map(m => model.fromData(m));
|
60
|
+
}
|
56
61
|
|
57
|
-
|
58
|
-
|
62
|
+
/**
|
63
|
+
* Recursively checks if a subject matches a given query.
|
64
|
+
*
|
65
|
+
* This function supports matching:
|
66
|
+
* - Primitive values directly (`string`, `number`, `boolean`)
|
67
|
+
* - The `$is` property for exact matches
|
68
|
+
* - The `$contains` property for substring or array element matches
|
69
|
+
*
|
70
|
+
* @private
|
71
|
+
* @param {*} subject - The subject to be matched.
|
72
|
+
* @param {Object} [inputQuery=this.query] - The query to match against. Defaults to `this.query` if not provided.
|
73
|
+
* @returns {boolean} True if the subject matches the query, otherwise false.
|
74
|
+
*/
|
75
|
+
_matchesQuery(subject, inputQuery = this.query) {
|
76
|
+
if (['string', 'number', 'boolean'].includes(typeof inputQuery)) return subject === inputQuery;
|
59
77
|
|
60
|
-
|
61
|
-
if (subject === inputQuery.$is) return true;
|
78
|
+
if (inputQuery?.$is !== undefined && subject === inputQuery.$is) return true;
|
62
79
|
|
63
|
-
|
64
|
-
|
80
|
+
if (inputQuery?.$contains !== undefined) {
|
81
|
+
if (subject.includes?.(inputQuery.$contains)) return true;
|
65
82
|
|
66
|
-
|
67
|
-
|
68
|
-
}
|
83
|
+
for (const value of subject) {
|
84
|
+
if (this._matchesQuery(value, inputQuery.$contains)) return true;
|
69
85
|
}
|
86
|
+
}
|
70
87
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
};
|
88
|
+
for (const key of Object.keys(inputQuery)) {
|
89
|
+
if (!['$is', '$contains'].includes(key))
|
90
|
+
if (this._matchesQuery(subject[key], inputQuery[key])) return true;
|
91
|
+
}
|
76
92
|
|
77
|
-
return
|
78
|
-
|
79
|
-
|
93
|
+
return false;
|
94
|
+
};
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Recursively splits an object into an array of objects,
|
98
|
+
* where each key-value pair from the input query becomes a separate object.
|
99
|
+
*
|
100
|
+
* If the value of a key is a nested object (and not an array),
|
101
|
+
* the function recursively splits it, preserving the parent key.
|
102
|
+
*
|
103
|
+
* @private
|
104
|
+
* @param {Object} query - The input object to be split into individual key-value pairs.
|
105
|
+
* @returns {Array<Object>} An array of objects, where each object contains a single key-value pair
|
106
|
+
* from the original query or its nested objects.
|
107
|
+
*/
|
108
|
+
_splitQuery(query) {
|
109
|
+
return Object.entries(query)
|
110
|
+
.flatMap(([key, value]) =>
|
111
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
112
|
+
? this._splitQuery(value).map(nestedObj => ({[key]: nestedObj}))
|
113
|
+
: {[key]: value},
|
114
|
+
);
|
80
115
|
}
|
81
116
|
}
|
82
117
|
|
package/src/Transactions.js
CHANGED
@@ -1,32 +1,110 @@
|
|
1
|
+
/**
|
2
|
+
* Class representing a transaction-related error.
|
3
|
+
*
|
4
|
+
* @class TransactionError
|
5
|
+
* @extends Error
|
6
|
+
*/
|
1
7
|
class TransactionError extends Error {
|
2
8
|
}
|
3
9
|
|
10
|
+
/**
|
11
|
+
* Error thrown when a transaction is already committed.
|
12
|
+
*
|
13
|
+
* @class TransactionCommittedError
|
14
|
+
* @extends TransactionError
|
15
|
+
*/
|
4
16
|
export class TransactionCommittedError extends TransactionError {
|
17
|
+
/**
|
18
|
+
* Creates an instance of TransactionCommittedError.
|
19
|
+
* This error is thrown when attempting to commit an already committed transaction.
|
20
|
+
* @property {string} message - The error message.
|
21
|
+
*/
|
5
22
|
message = 'Transaction was already committed.';
|
6
23
|
}
|
7
24
|
|
25
|
+
/**
|
26
|
+
* Enables transaction support for the provided engine.
|
27
|
+
*
|
28
|
+
* This function enhances an engine class with transaction capabilities, allowing multiple
|
29
|
+
* changes to be grouped into a single transaction that can be committed or rolled back.
|
30
|
+
*
|
31
|
+
* @param {Engine.constructor} engine - The base engine class to be enhanced with transaction support.
|
32
|
+
* @returns {TransactionalEngine.constructor} TransactionalEngine - The enhanced engine class with transaction functionality.
|
33
|
+
*/
|
8
34
|
export default function enableTransactions(engine) {
|
35
|
+
/**
|
36
|
+
* A class representing an engine with transaction capabilities.
|
37
|
+
* @class TransactionalEngine
|
38
|
+
* @extends {engine}
|
39
|
+
*/
|
9
40
|
class TransactionalEngine extends engine {
|
10
41
|
}
|
11
42
|
|
43
|
+
/**
|
44
|
+
* Starts a transaction on the engine. Returns a Transaction class that can handle
|
45
|
+
* put, commit, and rollback actions for the transaction.
|
46
|
+
*
|
47
|
+
* @returns {Transaction.constructor} Transaction - A class that manages the transaction's operations.
|
48
|
+
*/
|
12
49
|
TransactionalEngine.start = () => {
|
50
|
+
/**
|
51
|
+
* A class representing an active transaction on the engine.
|
52
|
+
* Contains methods to put changes, commit the transaction, or roll back in case of failure.
|
53
|
+
*
|
54
|
+
* @class Transaction
|
55
|
+
*/
|
13
56
|
class Transaction extends TransactionalEngine {
|
57
|
+
/**
|
58
|
+
* @property {Array<Object>} transactions - An array storing all the operations within the transaction.
|
59
|
+
* @static
|
60
|
+
*/
|
14
61
|
static transactions = [];
|
62
|
+
|
63
|
+
/**
|
64
|
+
* @property {boolean} committed - Indicates if the transaction has been committed.
|
65
|
+
* @static
|
66
|
+
*/
|
15
67
|
static committed = false;
|
68
|
+
|
69
|
+
/**
|
70
|
+
* @property {boolean} failed - Indicates if the transaction has failed.
|
71
|
+
* @static
|
72
|
+
*/
|
16
73
|
static failed = false;
|
17
74
|
|
18
|
-
|
75
|
+
/**
|
76
|
+
* Adds a model to the transaction queue.
|
77
|
+
*
|
78
|
+
* @param {Object} model - The model to be added to the transaction.
|
79
|
+
* @returns {Promise<void>} A promise that resolves once the model is added.
|
80
|
+
*/
|
81
|
+
static put(model) {
|
19
82
|
this.transactions.push({
|
20
83
|
hasRun: false,
|
21
84
|
hasRolledBack: false,
|
22
85
|
model,
|
23
86
|
});
|
87
|
+
|
88
|
+
return Promise.resolve();
|
24
89
|
}
|
25
90
|
|
91
|
+
/**
|
92
|
+
* Checks if the transaction has already been committed. If true, throws a TransactionCommittedError.
|
93
|
+
*
|
94
|
+
* @throws {TransactionCommittedError} If the transaction has already been committed.
|
95
|
+
* @private
|
96
|
+
*/
|
26
97
|
static _checkCommitted() {
|
27
98
|
if (this.committed) throw new TransactionCommittedError();
|
28
99
|
}
|
29
100
|
|
101
|
+
/**
|
102
|
+
* Commits the transaction, applying all the changes to the engine.
|
103
|
+
* Rolls back if any part of the transaction fails.
|
104
|
+
*
|
105
|
+
* @returns {Promise<void>} A promise that resolves once the transaction is committed, or rejects if an error occurs.
|
106
|
+
* @throws {Error} If any operation in the transaction fails.
|
107
|
+
*/
|
30
108
|
static async commit() {
|
31
109
|
this._checkCommitted();
|
32
110
|
|
@@ -34,7 +112,7 @@ export default function enableTransactions(engine) {
|
|
34
112
|
for (const [index, {model}] of this.transactions.entries()) {
|
35
113
|
try {
|
36
114
|
this.transactions[index].original = await engine.get(model.constructor, model.id);
|
37
|
-
} catch (
|
115
|
+
} catch (_error) {
|
38
116
|
this.transactions[index].original = null;
|
39
117
|
}
|
40
118
|
|
package/src/engine/Engine.js
CHANGED
@@ -16,10 +16,11 @@ class Engine {
|
|
16
16
|
*
|
17
17
|
* @param {string} _id - The ID of the model to retrieve.
|
18
18
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
19
|
+
* @returns {Promise<Object>} - Returns a promise resolving to the raw data of the requested model.
|
19
20
|
* @abstract
|
20
21
|
*/
|
21
|
-
static
|
22
|
-
|
22
|
+
static getById(_id) {
|
23
|
+
return Promise.reject(new NotImplementedError(`${this.name} must implement .getById()`));
|
23
24
|
}
|
24
25
|
|
25
26
|
/**
|
@@ -27,10 +28,11 @@ class Engine {
|
|
27
28
|
*
|
28
29
|
* @param {Model} _data - The model data to save.
|
29
30
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
31
|
+
* @returns {Promise<void>}
|
30
32
|
* @abstract
|
31
33
|
*/
|
32
|
-
static
|
33
|
-
|
34
|
+
static putModel(_data) {
|
35
|
+
return Promise.reject(new NotImplementedError(`${this.name} must implement .putModel()`));
|
34
36
|
}
|
35
37
|
|
36
38
|
/**
|
@@ -38,10 +40,11 @@ class Engine {
|
|
38
40
|
*
|
39
41
|
* @param {Model.constructor} _model - The model to retrieve the index for.
|
40
42
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
43
|
+
* @returns {Promise<Object>} - Returns a promise resolving to the model index.
|
41
44
|
* @abstract
|
42
45
|
*/
|
43
|
-
static
|
44
|
-
|
46
|
+
static getIndex(_model) {
|
47
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getIndex()`));
|
45
48
|
}
|
46
49
|
|
47
50
|
/**
|
@@ -49,10 +52,11 @@ class Engine {
|
|
49
52
|
*
|
50
53
|
* @param {Object} _index - The index data to save.
|
51
54
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
55
|
+
* @returns {Promise<void>}
|
52
56
|
* @abstract
|
53
57
|
*/
|
54
|
-
static
|
55
|
-
|
58
|
+
static putIndex(_index) {
|
59
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putIndex()`));
|
56
60
|
}
|
57
61
|
|
58
62
|
/**
|
@@ -60,10 +64,11 @@ class Engine {
|
|
60
64
|
*
|
61
65
|
* @param {Model.constructor} _model - The model to retrieve the compiled search index for.
|
62
66
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
67
|
+
* @returns {Promise<Object>} - Returns a promise resolving to the compiled search index.
|
63
68
|
* @abstract
|
64
69
|
*/
|
65
|
-
static
|
66
|
-
|
70
|
+
static getSearchIndexCompiled(_model) {
|
71
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`));
|
67
72
|
}
|
68
73
|
|
69
74
|
/**
|
@@ -71,10 +76,11 @@ class Engine {
|
|
71
76
|
*
|
72
77
|
* @param {Model.constructor} _model - The model to retrieve the raw search index for.
|
73
78
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
79
|
+
* @returns {Promise<Object>} - Returns a promise resolving to the raw search index.
|
74
80
|
* @abstract
|
75
81
|
*/
|
76
|
-
static
|
77
|
-
|
82
|
+
static getSearchIndexRaw(_model) {
|
83
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`));
|
78
84
|
}
|
79
85
|
|
80
86
|
/**
|
@@ -83,10 +89,11 @@ class Engine {
|
|
83
89
|
* @param {Model.constructor} _model - The model for which the compiled search index is saved.
|
84
90
|
* @param {Object} _compiledIndex - The compiled search index data.
|
85
91
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
92
|
+
* @returns {Promise<void>}
|
86
93
|
* @abstract
|
87
94
|
*/
|
88
|
-
static
|
89
|
-
|
95
|
+
static putSearchIndexCompiled(_model, _compiledIndex) {
|
96
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`));
|
90
97
|
}
|
91
98
|
|
92
99
|
/**
|
@@ -95,10 +102,11 @@ class Engine {
|
|
95
102
|
* @param {Model.constructor} _model - The model for which the raw search index is saved.
|
96
103
|
* @param {Object} _rawIndex - The raw search index data.
|
97
104
|
* @throws {NotImplementedError} Throws if the method is not implemented.
|
105
|
+
* @returns {Promise<void>}
|
98
106
|
* @abstract
|
99
107
|
*/
|
100
|
-
static
|
101
|
-
|
108
|
+
static putSearchIndexRaw(_model, _rawIndex) {
|
109
|
+
return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`));
|
102
110
|
}
|
103
111
|
|
104
112
|
/**
|
@@ -321,7 +329,7 @@ class Engine {
|
|
321
329
|
* @abstract
|
322
330
|
*/
|
323
331
|
static checkConfiguration() {
|
324
|
-
|
332
|
+
// Implemented in extending Engine class
|
325
333
|
}
|
326
334
|
|
327
335
|
/**
|
package/src/engine/HTTPEngine.js
CHANGED
@@ -102,7 +102,7 @@ class HTTPEngine extends Engine {
|
|
102
102
|
*
|
103
103
|
* @throws {HTTPRequestFailedError} Thrown if the fetch request fails.
|
104
104
|
*/
|
105
|
-
static
|
105
|
+
static _processFetch(url, options, defaultValue = undefined) {
|
106
106
|
return this.configuration.fetch(url, options)
|
107
107
|
.then(response => {
|
108
108
|
if (!response.ok) {
|
@@ -133,7 +133,7 @@ class HTTPEngine extends Engine {
|
|
133
133
|
this.configuration.host,
|
134
134
|
this.configuration.prefix,
|
135
135
|
`${id}.json`,
|
136
|
-
].filter(e =>
|
136
|
+
].filter(e => Boolean(e)).join('/'));
|
137
137
|
|
138
138
|
return await this._processFetch(url, this._getReadOptions());
|
139
139
|
}
|
@@ -146,14 +146,14 @@ class HTTPEngine extends Engine {
|
|
146
146
|
*
|
147
147
|
* @throws {HTTPRequestFailedError} Thrown if the PUT request fails.
|
148
148
|
*/
|
149
|
-
static
|
149
|
+
static putModel(model) {
|
150
150
|
const url = new URL([
|
151
151
|
this.configuration.host,
|
152
152
|
this.configuration.prefix,
|
153
153
|
`${model.id}.json`,
|
154
|
-
].filter(e =>
|
154
|
+
].filter(e => Boolean(e)).join('/'));
|
155
155
|
|
156
|
-
return
|
156
|
+
return this._processFetch(url, {
|
157
157
|
...this._getWriteOptions(),
|
158
158
|
body: JSON.stringify(model.toData()),
|
159
159
|
});
|
@@ -175,7 +175,7 @@ class HTTPEngine extends Engine {
|
|
175
175
|
this.configuration.prefix,
|
176
176
|
location,
|
177
177
|
'_index.json',
|
178
|
-
].filter(e =>
|
178
|
+
].filter(e => Boolean(e)).join('/'));
|
179
179
|
|
180
180
|
return await this._processFetch(url, {
|
181
181
|
...this._getWriteOptions(),
|
@@ -259,7 +259,7 @@ class HTTPEngine extends Engine {
|
|
259
259
|
this.configuration.prefix,
|
260
260
|
model.toString(),
|
261
261
|
'_search_index.json',
|
262
|
-
].filter(e =>
|
262
|
+
].filter(e => Boolean(e)).join('/'));
|
263
263
|
|
264
264
|
return this._processFetch(url, {
|
265
265
|
...this._getWriteOptions(),
|
@@ -282,7 +282,7 @@ class HTTPEngine extends Engine {
|
|
282
282
|
this.configuration.prefix,
|
283
283
|
model.toString(),
|
284
284
|
'_search_index_raw.json',
|
285
|
-
].filter(e =>
|
285
|
+
].filter(e => Boolean(e)).join('/'));
|
286
286
|
|
287
287
|
return await this._processFetch(url, {
|
288
288
|
...this._getWriteOptions(),
|
package/src/engine/S3Engine.js
CHANGED
package/src/type/Model.js
CHANGED
@@ -253,9 +253,9 @@ class Model {
|
|
253
253
|
return (
|
254
254
|
!this.isModel(possibleDryModel) &&
|
255
255
|
Object.keys(possibleDryModel).includes('id') &&
|
256
|
-
|
256
|
+
new RegExp(/[A-Za-z]+\/[A-Z0-9]+/).test(possibleDryModel.id)
|
257
257
|
);
|
258
|
-
} catch (
|
258
|
+
} catch (_error) {
|
259
259
|
return false;
|
260
260
|
}
|
261
261
|
}
|