@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acodeninja/persist",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "A JSON based data modelling and persistence module with alternate storage mechanisms.",
5
5
  "type": "module",
6
6
  "scripts": {
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
- const matchIs = (query) => query?.$is !== undefined;
54
- const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query);
55
- const matchContains = (query) => !!query?.$contains;
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
- const matchesQuery = (subject, inputQuery = this.query) => {
58
- if (matchPrimitive(inputQuery)) return subject === inputQuery;
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
- if (matchIs(inputQuery))
61
- if (subject === inputQuery.$is) return true;
78
+ if (inputQuery?.$is !== undefined && subject === inputQuery.$is) return true;
62
79
 
63
- if (matchContains(inputQuery)) {
64
- if (subject.includes?.(inputQuery.$contains)) return true;
80
+ if (inputQuery?.$contains !== undefined) {
81
+ if (subject.includes?.(inputQuery.$contains)) return true;
65
82
 
66
- for (const value of subject) {
67
- if (matchesQuery(value, inputQuery.$contains)) return true;
68
- }
83
+ for (const value of subject) {
84
+ if (this._matchesQuery(value, inputQuery.$contains)) return true;
69
85
  }
86
+ }
70
87
 
71
- for (const key of Object.keys(inputQuery)) {
72
- if (!['$is', '$contains'].includes(key))
73
- if (matchesQuery(subject[key], inputQuery[key])) return true;
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 Object.values(index)
78
- .filter(m => matchesQuery(m))
79
- .map(m => model.fromData(m));
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
 
@@ -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
- static async put(model) {
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
 
@@ -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 async getById(_id) {
22
- throw new NotImplementedError(`${this.name} must implement .getById()`);
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 async putModel(_data) {
33
- throw new NotImplementedError(`${this.name} must implement .putModel()`);
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 async getIndex(_model) {
44
- throw new NotImplementedError(`${this.name} does not implement .getIndex()`);
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 async putIndex(_index) {
55
- throw new NotImplementedError(`${this.name} does not implement .putIndex()`);
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 async getSearchIndexCompiled(_model) {
66
- throw new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`);
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 async getSearchIndexRaw(_model) {
77
- throw new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`);
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 async putSearchIndexCompiled(_model, _compiledIndex) {
89
- throw new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`);
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 async putSearchIndexRaw(_model, _rawIndex) {
101
- throw new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`);
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
  /**
@@ -102,7 +102,7 @@ class HTTPEngine extends Engine {
102
102
  *
103
103
  * @throws {HTTPRequestFailedError} Thrown if the fetch request fails.
104
104
  */
105
- static async _processFetch(url, options, defaultValue = undefined) {
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 => !!e).join('/'));
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 async putModel(model) {
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 => !!e).join('/'));
154
+ ].filter(e => Boolean(e)).join('/'));
155
155
 
156
- return await this._processFetch(url, {
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 => !!e).join('/'));
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 => !!e).join('/'));
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 => !!e).join('/'));
285
+ ].filter(e => Boolean(e)).join('/'));
286
286
 
287
287
  return await this._processFetch(url, {
288
288
  ...this._getWriteOptions(),
@@ -105,7 +105,7 @@ class S3Engine extends Engine {
105
105
  }));
106
106
 
107
107
  return JSON.parse(await data.Body.transformToString());
108
- } catch (_) {
108
+ } catch (_error) {
109
109
  return {};
110
110
  }
111
111
  }
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
- !!possibleDryModel.id.match(/[A-Za-z]+\/[A-Z0-9]+/)
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
  }