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

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.
@@ -54,15 +54,15 @@ export class Address extends Persist.Type.Model {
54
54
 
55
55
  By doing this, you ensure that model references are evaluated lazily, after all models have been initialized, preventing `ReferenceError` issues.
56
56
 
57
- ## Using `HTTP` Engine in Browser
57
+ ## Using `HTTP` StorageEngine in Browser
58
58
 
59
59
  When implementing thee `HTTP` engine for code that runs in the web browser, you must pass `fetch` into the engine configuration and bind it to the `window` object.
60
60
 
61
61
  ```javascript
62
62
  import Persist from "@acodeninja/persist";
63
- import HTTPEngine from "@acodeninja/persist/engine/http";
63
+ import HTTPStorageEngine from "@acodeninja/persist/engine/storage/http";
64
64
 
65
- Persist.addEngine('remote', HTTPEngine, {
65
+ Persist.addEngine('remote', HTTPStorageEngine, {
66
66
  host: 'https://api.example.com',
67
67
  fetch: fetch.bind(window),
68
68
  });
@@ -37,9 +37,9 @@ To search for any `Person` who lives on station road, the following search query
37
37
  ```javascript
38
38
  import Persist from "@acodeninja/persist";
39
39
  import Person from "./Person";
40
- import FileEngine from "@acodeninja/persist/engine/file"
40
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
41
41
 
42
- FileEngine
42
+ FileStorageEngine
43
43
  .configure(configuration)
44
44
  .search(Person, 'station road');
45
45
  ```
@@ -2,15 +2,15 @@
2
2
 
3
3
  Persist makes several storage engines available for use with the library
4
4
 
5
- ## Filesystem Storage Engine
5
+ ## Filesystem Storage StorageEngine
6
6
 
7
7
  To store models using the local file system, use the `File` storage engine.
8
8
 
9
9
  ```javascript
10
10
  import Persist from "@acodeninja/persist";
11
- import FileEngine from "@acodeninja/persist/engine/file";
11
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file";
12
12
 
13
- Persist.addEngine('local', FileEngine, {
13
+ Persist.addEngine('local', FileStorageEngine, {
14
14
  path: '/app/storage',
15
15
  });
16
16
 
@@ -18,18 +18,18 @@ export class Tag extends Persist.Type.Model {
18
18
  static tag = Persist.Type.String.required;
19
19
  }
20
20
 
21
- await Persist.getEngine('local', FileEngine).put(new Tag({tag: 'documentation'}));
21
+ await Persist.getEngine('local', FileStorageEngine).put(new Tag({tag: 'documentation'}));
22
22
  ```
23
23
 
24
- ## HTTP Storage Engine
24
+ ## HTTP Storage StorageEngine
25
25
 
26
26
  To store models using an HTTP server, use the `HTTP` storage engine. When using the `HTTP` engine in the browser, refer to [code quirks](./code-quirks.md#using-http-engine-in-browser).
27
27
 
28
28
  ```javascript
29
29
  import Persist from "@acodeninja/persist";
30
- import HTTPEngine from "@acodeninja/persist/engine/http";
30
+ import HTTPStorageEngine from "@acodeninja/persist/engine/storage/http";
31
31
 
32
- Persist.addEngine('remote', HTTPEngine, {
32
+ Persist.addEngine('remote', HTTPStorageEngine, {
33
33
  host: 'https://api.example.com',
34
34
  });
35
35
 
@@ -37,18 +37,18 @@ export class Tag extends Persist.Type.Model {
37
37
  static tag = Persist.Type.String.required;
38
38
  }
39
39
 
40
- await Persist.getEngine('remote', HTTPEngine).put(new Tag({tag: 'documentation'}));
40
+ await Persist.getEngine('remote', HTTPStorageEngine).put(new Tag({tag: 'documentation'}));
41
41
  ```
42
42
 
43
- ## S3 Storage Engine
43
+ ## S3 Storage StorageEngine
44
44
 
45
45
  To store models using an S3 Bucket, use the `S3` storage engine. To use the `S3` engine you must also add the `@aws-sdk/client-s3` dependency to your `package.json` file.
46
46
 
47
47
  ```javascript
48
48
  import Persist from "@acodeninja/persist";
49
- import S3Engine from "@acodeninja/persist/engine/s3";
49
+ import S3StorageEngine from "@acodeninja/persist/engine/storage/s3";
50
50
 
51
- Persist.addEngine('remote', S3Engine, {
51
+ Persist.addEngine('remote', S3StorageEngine, {
52
52
  bucket: 'test-bucket',
53
53
  client: new S3Client(),
54
54
  });
@@ -57,5 +57,5 @@ export class Tag extends Persist.Type.Model {
57
57
  static tag = Persist.Type.String.required;
58
58
  }
59
59
 
60
- await Persist.getEngine('remote', S3Engine).put(new Tag({tag: 'documentation'}));
60
+ await Persist.getEngine('remote', S3StorageEngine).put(new Tag({tag: 'documentation'}));
61
61
  ```
@@ -40,9 +40,9 @@ To query for a `Person` called `Joe Bloggs` an exact query can be written:
40
40
  ```javascript
41
41
  import Persist from "@acodeninja/persist";
42
42
  import Person from "./Person";
43
- import FileEngine from "@acodeninja/persist/engine/file"
43
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
44
44
 
45
- FileEngine
45
+ FileStorageEngine
46
46
  .configure(configuration)
47
47
  .find(Person, {
48
48
  name: {$is: 'Joe Bloggs'},
@@ -56,9 +56,9 @@ To query for a `Person` with name `Joe` a contains query can be written:
56
56
  ```javascript
57
57
  import Persist from "@acodeninja/persist";
58
58
  import Person from "./Person";
59
- import FileEngine from "@acodeninja/persist/engine/file"
59
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
60
60
 
61
- FileEngine
61
+ FileStorageEngine
62
62
  .configure(configuration)
63
63
  .find(Person, {
64
64
  name: {$contains: 'Joe'},
@@ -72,9 +72,9 @@ To query for a `Person` who lives at `SW1 1AA` a combination of contains and exa
72
72
  ```javascript
73
73
  import Persist from "@acodeninja/persist";
74
74
  import Person from "./Person";
75
- import FileEngine from "@acodeninja/persist/engine/file"
75
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
76
76
 
77
- FileEngine
77
+ FileStorageEngine
78
78
  .configure(configuration)
79
79
  .find(Person, {
80
80
  address: {
@@ -92,9 +92,9 @@ To query for anyone called `Joe Bloggs` who lives in the `SW1` postcode area, we
92
92
  ```javascript
93
93
  import Persist from "@acodeninja/persist";
94
94
  import Person from "./Person";
95
- import FileEngine from "@acodeninja/persist/engine/file"
95
+ import FileStorageEngine from "@acodeninja/persist/engine/storage/file"
96
96
 
97
- FileEngine
97
+ FileStorageEngine
98
98
  .configure(configuration)
99
99
  .find(Person, {
100
100
  name: {$is: 'Joe Bloggs'},
@@ -4,9 +4,9 @@ Create transactions to automatically roll back on failure.
4
4
 
5
5
  ```javascript
6
6
  import Persist from "@acodeninja/persist";
7
- import S3Engine from "@acodeninja/persist/engine/s3";
7
+ import S3StorageEngine from "@acodeninja/persist/engine/storage/s3";
8
8
 
9
- Persist.addEngine('remote', S3Engine, {
9
+ Persist.addEngine('remote', S3StorageEngine, {
10
10
  bucket: 'test-bucket',
11
11
  client: new S3Client(),
12
12
  transactions: true,
@@ -16,7 +16,7 @@ export class Tag extends Persist.Type.Model {
16
16
  static tag = Persist.Type.String.required;
17
17
  }
18
18
 
19
- const transaction = Persist.getEngine('remote', S3Engine).start();
19
+ const transaction = Persist.getEngine('remote', S3StorageEngine).start();
20
20
 
21
21
  await transaction.put(new Tag({tag: 'documentation'}));
22
22
  await transaction.commit();
@@ -0,0 +1,3 @@
1
+ import FileStorageEngine from '../../../src/engine/storage/FileStorageEngine.js';
2
+
3
+ export default FileStorageEngine;
@@ -0,0 +1,3 @@
1
+ import HTTPStorageEngine from '../../../src/engine/storage/HTTPStorageEngine.js';
2
+
3
+ export default HTTPStorageEngine;
@@ -0,0 +1,3 @@
1
+ import S3StorageEngine from '../../../src/engine/storage/S3StorageEngine.js';
2
+
3
+ export default S3StorageEngine;
@@ -0,0 +1,26 @@
1
+ /** @type {import('jest').Config} */
2
+ const config = {
3
+ coveragePathIgnorePatterns: [
4
+ 'node_modules',
5
+ 'test/fixtures',
6
+ 'test/mocks',
7
+ 'test/scripts',
8
+ ],
9
+ coverageThreshold: {
10
+ global: {
11
+ branches: 100,
12
+ functions: 100,
13
+ lines: 100,
14
+ statements: 100,
15
+ },
16
+ },
17
+ testMatch: [
18
+ '**/*.test.js',
19
+ ],
20
+ watchPathIgnorePatterns: [
21
+ 'coverage/',
22
+ 'test/fixtures/minified',
23
+ ],
24
+ };
25
+
26
+ module.exports = config;
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@acodeninja/persist",
3
- "version": "2.4.1-next.2",
3
+ "version": "3.0.0-next.2",
4
4
  "description": "A JSON based data modelling and persistence module with alternate storage mechanisms.",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "test": "ava",
8
- "test:watch": "ava --watch",
9
- "test:coverage": "c8 --experimental-monocart --100 --reporter=console-details ava",
10
- "test:coverage:report": "c8 --experimental-monocart --100 --lcov --reporter=console-details --reporter=v8 ava",
7
+ "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest",
8
+ "test:watch": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest --watch",
9
+ "test:coverage": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest --collect-coverage",
11
10
  "lint": "eslint ./",
12
11
  "prepare": "husky"
13
12
  },
@@ -38,14 +37,12 @@
38
37
  "@commitlint/cli": "^19.6.1",
39
38
  "@commitlint/config-conventional": "^19.6.0",
40
39
  "@eslint/js": "^9.19.0",
40
+ "@jest/globals": "^29.7.0",
41
41
  "@semantic-release/commit-analyzer": "^13.0.1",
42
- "ava": "^6.2.0",
43
- "c8": "^10.1.3",
44
42
  "eslint": "^9.19.0",
45
43
  "globals": "^15.14.0",
46
44
  "husky": "^9.1.7",
47
- "monocart-coverage-reports": "^2.12.0",
48
- "semantic-release": "^24.2.1",
49
- "sinon": "^19.0.2"
45
+ "jest": "^29.7.0",
46
+ "semantic-release": "^24.2.1"
50
47
  }
51
48
  }
package/src/Query.js CHANGED
@@ -69,10 +69,10 @@ class Query {
69
69
  *
70
70
  * @private
71
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.
72
+ * @param {Object} inputQuery - The query to match against.
73
73
  * @returns {boolean} True if the subject matches the query, otherwise false.
74
74
  */
75
- _matchesQuery(subject, inputQuery = this.query) {
75
+ _matchesQuery(subject, inputQuery) {
76
76
  if (['string', 'number', 'boolean'].includes(typeof inputQuery)) return subject === inputQuery;
77
77
 
78
78
  if (inputQuery?.$is !== undefined && subject === inputQuery.$is) return true;
@@ -0,0 +1,250 @@
1
+ import Type from '../type/index.js';
2
+ import _ from 'lodash';
3
+
4
+ export default class StorageEngine {
5
+ /**
6
+ * @param {Object} configuration
7
+ * @param {Array<Type.Model.constructor>?} models
8
+ */
9
+ constructor(configuration = {}, models = null) {
10
+ this.configuration = configuration;
11
+ this.models = Object.fromEntries((models ?? []).map(model => [model.name, model]));
12
+ }
13
+
14
+ /**
15
+ * Persists a model if it has changed, and updates all related models and their indexes
16
+ * @param {Type.Model} model
17
+ * @return {Promise<void>}
18
+ */
19
+ async put(model) {
20
+ const processedModels = [];
21
+ const modelsToPut = [];
22
+ const modelsToReindex = {};
23
+
24
+ /**
25
+ * @param {Type.Model} modelToProcess
26
+ * @return {Promise<void>}
27
+ */
28
+ const processModel = async (modelToProcess) => {
29
+ if (processedModels.includes(modelToProcess.id)) return;
30
+
31
+ processedModels.push(modelToProcess.id);
32
+
33
+ if (!Object.keys(this.models).includes(modelToProcess.constructor.name)) throw new ModelNotRegisteredStorageEngineError(modelToProcess, this);
34
+
35
+ modelToProcess.validate();
36
+ const currentModel = await this.get(modelToProcess.id).catch(() => null);
37
+
38
+ const modelToProcessHasChanged = (JSON.stringify(currentModel?.toData() || {}) !== JSON.stringify(modelToProcess.toData()));
39
+
40
+ if (modelToProcessHasChanged) modelsToPut.push(modelToProcess);
41
+
42
+ if (
43
+ Boolean(modelToProcess.constructor.indexedProperties().length) &&
44
+ this._indexedFieldsHaveChanged(currentModel, modelToProcess)
45
+ ) {
46
+ const modelToProcessConstructor = this.getModelConstructorFromId(modelToProcess.id);
47
+ modelsToReindex[modelToProcessConstructor] = modelsToReindex[modelToProcessConstructor] || [];
48
+ modelsToReindex[modelToProcessConstructor].push(modelToProcess);
49
+ }
50
+
51
+ for (const [field, value] of Object.entries(modelToProcess)) {
52
+ if (Type.Model.isModel(value)) {
53
+ await processModel(modelToProcess[field]);
54
+ }
55
+ }
56
+ };
57
+
58
+ await processModel(model);
59
+
60
+ await Promise.all(modelsToPut.map(m => this._putModel(m.toData())));
61
+ await Promise.all(Object.entries(modelsToReindex).map(async ([constructor, models]) => {
62
+ const modelConstructor = this.models[constructor];
63
+ const index = await this._getIndex(modelConstructor);
64
+
65
+ await this._putIndex(modelConstructor, {
66
+ ...index || {},
67
+ ...Object.fromEntries(models.map(m => [m.id, m.toIndexData()])),
68
+ });
69
+ }));
70
+ }
71
+
72
+ /**
73
+ * Decide if two models indexable fields have changed
74
+ * @param {Type.Model} currentModel
75
+ * @param {Type.Model} modelToProcess
76
+ * @return {boolean}
77
+ * @private
78
+ */
79
+ _indexedFieldsHaveChanged(currentModel, modelToProcess) {
80
+ return !currentModel || Boolean(
81
+ modelToProcess.constructor.indexedProperties()
82
+ .filter(field => JSON.stringify(_.get(currentModel, field)) !== JSON.stringify(_.get(modelToProcess, field)))
83
+ .length,
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Get a model by its id
89
+ * @param {string} modelId
90
+ * @return {Promise<void>}
91
+ */
92
+ get(modelId) {
93
+ try {
94
+ this.getModelConstructorFromId(modelId);
95
+ } catch (e) {
96
+ return Promise.reject(e);
97
+ }
98
+ return this._getModel(modelId);
99
+ }
100
+
101
+ /**
102
+ * Get the model constructor from a model id
103
+ * @param {string} modelId
104
+ * @return {Model.constructor}
105
+ */
106
+ getModelConstructorFromId(modelId) {
107
+ const modelName = modelId.split('/')[0];
108
+ const constructor = this.models[modelName];
109
+
110
+ if (!constructor) throw new ModelNotRegisteredStorageEngineError(modelName, this);
111
+
112
+ return constructor;
113
+ }
114
+
115
+ /**
116
+ * Get model classes that are directly linked to the given model in either direction
117
+ * @param {Type.Model.constructor} model
118
+ * @return {Record<string, Record<string, Type.Model.constructor>>}
119
+ */
120
+ getLinksFor(model) {
121
+ return Object.fromEntries(
122
+ Object.entries(this.getAllModelLinks())
123
+ .filter(([modelName, links]) =>
124
+ model.name === modelName ||
125
+ Object.values(links).some((link) => link.name === model.name),
126
+ ),
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Get all model links
132
+ * @return {Record<string, Record<string, Type.Model.constructor>>}
133
+ */
134
+ getAllModelLinks() {
135
+ return Object.entries(this.models)
136
+ .map(([registeredModelName, registeredModelClass]) =>
137
+ Object.entries(registeredModelClass)
138
+ .map(([propertyName, propertyType]) => [
139
+ registeredModelName,
140
+ propertyName,
141
+ typeof propertyType === 'function' && !Type.Model.isModel(propertyType) ? propertyType() : propertyType,
142
+ ])
143
+ .filter(([_m, _p, type]) => Type.Model.isModel(type))
144
+ .map(([containingModel, propertyName, propertyType]) => ({
145
+ containingModel,
146
+ propertyName,
147
+ propertyType,
148
+ })),
149
+ )
150
+ .flat()
151
+ .reduce((accumulator, {containingModel, propertyName, propertyType}) => ({
152
+ ...accumulator,
153
+ [containingModel]: {
154
+ ...accumulator[containingModel] || {},
155
+ [propertyName]: propertyType,
156
+ },
157
+ }), {});
158
+ }
159
+
160
+ /**
161
+ * Update a model
162
+ * @param {Model} _model
163
+ * @throws MethodNotImplementedStorageEngineError
164
+ * @return Promise<void>
165
+ */
166
+ _putModel(_model) {
167
+ return Promise.reject(new MethodNotImplementedStorageEngineError('_putModel', this));
168
+ }
169
+
170
+ /**
171
+ * Get a model
172
+ * @param {string} _id
173
+ * @throws MethodNotImplementedStorageEngineError
174
+ * @throws ModelNotFoundStorageEngineError
175
+ * @return Promise<void>
176
+ */
177
+ _getModel(_id) {
178
+ return Promise.reject(new MethodNotImplementedStorageEngineError('_getModel', this));
179
+ }
180
+
181
+ /**
182
+ * Get a model's index data
183
+ * @param {Model.constructor} _modelConstructor
184
+ * @throws MethodNotImplementedStorageEngineError
185
+ * @return Promise<void>
186
+ */
187
+ _getIndex(_modelConstructor) {
188
+ return Promise.reject(new MethodNotImplementedStorageEngineError('_getIndex', this));
189
+ }
190
+
191
+ /**
192
+ * Put a model's index data
193
+ * @param {Model.constructor} _modelConstructor
194
+ * @param {object} _data
195
+ * @throws MethodNotImplementedStorageEngineError
196
+ * @return Promise<void>
197
+ */
198
+ _putIndex(_modelConstructor, _data) {
199
+ return Promise.reject(new MethodNotImplementedStorageEngineError('_putIndex', this));
200
+ }
201
+ }
202
+
203
+ /**
204
+ * @class StorageEngineError
205
+ * @extends Error
206
+ */
207
+ export class StorageEngineError extends Error {
208
+ }
209
+
210
+ /**
211
+ * @class ModelNotRegisteredStorageEngineError
212
+ * @extends StorageEngineError
213
+ */
214
+ export class ModelNotRegisteredStorageEngineError extends StorageEngineError {
215
+ /**
216
+ * @param {Type.Model} model
217
+ * @param {StorageEngine} storageEngine
218
+ */
219
+ constructor(model, storageEngine) {
220
+ const modelName = typeof model === 'string' ? model : model.constructor.name;
221
+ super(`The model ${modelName} is not registered in the storage engine ${storageEngine.constructor.name}`);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * @class MethodNotImplementedStorageEngineError
227
+ * @extends StorageEngineError
228
+ */
229
+ export class MethodNotImplementedStorageEngineError extends StorageEngineError {
230
+ /**
231
+ * @param {string} method
232
+ * @param {StorageEngine} storageEngine
233
+ */
234
+ constructor(method, storageEngine) {
235
+ super(`The method ${method} is not implemented in the storage engine ${storageEngine.constructor.name}`);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * @class ModelNotFoundStorageEngineError
241
+ * @extends StorageEngineError
242
+ */
243
+ export class ModelNotFoundStorageEngineError extends StorageEngineError {
244
+ /**
245
+ * @param {string} modelId
246
+ */
247
+ constructor(modelId) {
248
+ super(`The model ${modelId} was not found`);
249
+ }
250
+ }
@@ -1,35 +1,35 @@
1
- import Engine, { EngineError, MissConfiguredError } from './Engine.js';
1
+ import StorageEngine, { EngineError, MissConfiguredError } from './StorageEngine.js';
2
2
  import { dirname, join } from 'node:path';
3
3
  import fs from 'node:fs/promises';
4
4
 
5
5
  /**
6
- * Custom error class for FileEngine-related errors.
6
+ * Custom error class for FileStorageEngine-related errors.
7
7
  * Extends the base `EngineError` class.
8
8
  */
9
- class FileEngineError extends EngineError {}
9
+ class FileStorageEngineError extends EngineError {}
10
10
 
11
11
  /**
12
- * Error thrown when writing to a file fails in `FileEngine`.
13
- * Extends the `FileEngineError` class.
12
+ * Error thrown when writing to a file fails in `FileStorageEngine`.
13
+ * Extends the `FileStorageEngineError` class.
14
14
  */
15
- class FailedWriteFileEngineError extends FileEngineError {}
15
+ class FailedWriteFileStorageEngineError extends FileStorageEngineError {}
16
16
 
17
17
  /**
18
- * `FileEngine` class extends the base `Engine` class to implement
18
+ * `FileStorageEngine` class extends the base `StorageEngine` class to implement
19
19
  * file system-based storage and retrieval of model data.
20
20
  *
21
- * @class FileEngine
22
- * @extends Engine
21
+ * @class FileStorageEngine
22
+ * @extends StorageEngine
23
23
  */
24
- class FileEngine extends Engine {
24
+ class FileStorageEngine extends StorageEngine {
25
25
  /**
26
- * Configures the FileEngine with a given configuration object.
26
+ * Configures the FileStorageEngine with a given configuration object.
27
27
  * Adds default `filesystem` configuration if not provided.
28
28
  *
29
- * @param {Object} configuration - Configuration settings for FileEngine.
29
+ * @param {Object} configuration - Configuration settings for FileStorageEngine.
30
30
  * @param {Object} [configuration.filesystem] - Custom filesystem module (default: Node.js fs/promises).
31
31
  * @param {Object} [configuration.path] - The absolute path on the filesystem to write models to.
32
- * @returns {FileEngine} A configured instance of FileEngine.
32
+ * @returns {FileStorageEngine} A configured instance of FileStorageEngine.
33
33
  */
34
34
  static configure(configuration) {
35
35
  if (!configuration.filesystem) {
@@ -39,7 +39,7 @@ class FileEngine extends Engine {
39
39
  }
40
40
 
41
41
  /**
42
- * Checks if the FileEngine has been configured correctly.
42
+ * Checks if the FileStorageEngine has been configured correctly.
43
43
  * Ensures that `path` and `filesystem` settings are present.
44
44
  *
45
45
  * @throws {MissConfiguredError} Throws if required configuration is missing.
@@ -94,7 +94,7 @@ class FileEngine extends Engine {
94
94
  * Saves a model to the file system.
95
95
  *
96
96
  * @param {Model} model - The model to save.
97
- * @throws {FailedWriteFileEngineError} Throws if the model cannot be written to the file system.
97
+ * @throws {FailedWriteFileStorageEngineError} Throws if the model cannot be written to the file system.
98
98
  */
99
99
  static async putModel(model) {
100
100
  const filePath = join(this.configuration.path, `${model.id}.json`);
@@ -102,7 +102,7 @@ class FileEngine extends Engine {
102
102
  await this.configuration.filesystem.mkdir(dirname(filePath), { recursive: true });
103
103
  await this.configuration.filesystem.writeFile(filePath, JSON.stringify(model.toData()));
104
104
  } catch (error) {
105
- throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
105
+ throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
106
106
  }
107
107
  }
108
108
 
@@ -110,9 +110,16 @@ class FileEngine extends Engine {
110
110
  * Saves the index for multiple models to the file system.
111
111
  *
112
112
  * @param {Object} index - An object where keys are locations and values are key value pairs of models and their ids.
113
- * @throws {FailedWriteFileEngineError} Throws if the index cannot be written to the file system.
113
+ * @throws {FailedWriteFileStorageEngineError} Throws if the index cannot be written to the file system.
114
114
  */
115
115
  static async putIndex(index) {
116
+ /**
117
+ * Process an index of models
118
+ * @param {string} location
119
+ * @param {Array<Model>} models
120
+ * @throws FailedWriteFileStorageEngineError
121
+ * @return {Promise<void>}
122
+ */
116
123
  const processIndex = async (location, models) => {
117
124
  const filePath = join(this.configuration.path, location, '_index.json');
118
125
  const currentIndex = JSON.parse((await this.configuration.filesystem.readFile(filePath).catch(() => '{}')).toString());
@@ -125,7 +132,7 @@ class FileEngine extends Engine {
125
132
  ),
126
133
  }));
127
134
  } catch (error) {
128
- throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
135
+ throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
129
136
  }
130
137
  };
131
138
 
@@ -175,14 +182,14 @@ class FileEngine extends Engine {
175
182
  *
176
183
  * @param {Model.constructor} model - The model for which the compiled search index is saved.
177
184
  * @param {Object} compiledIndex - The compiled search index to save.
178
- * @throws {FailedWriteFileEngineError} Throws if the compiled index cannot be written to the file system.
185
+ * @throws {FailedWriteFileStorageEngineError} Throws if the compiled index cannot be written to the file system.
179
186
  */
180
187
  static async putSearchIndexCompiled(model, compiledIndex) {
181
188
  const filePath = join(this.configuration.path, model.toString(), '_search_index.json');
182
189
  try {
183
190
  await this.configuration.filesystem.writeFile(filePath, JSON.stringify(compiledIndex));
184
191
  } catch (error) {
185
- throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
192
+ throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
186
193
  }
187
194
  }
188
195
 
@@ -191,16 +198,16 @@ class FileEngine extends Engine {
191
198
  *
192
199
  * @param {Model.constructor} model - The model for which the raw search index is saved.
193
200
  * @param {Object} rawIndex - The raw search index to save.
194
- * @throws {FailedWriteFileEngineError} Throws if the raw index cannot be written to the file system.
201
+ * @throws {FailedWriteFileStorageEngineError} Throws if the raw index cannot be written to the file system.
195
202
  */
196
203
  static async putSearchIndexRaw(model, rawIndex) {
197
204
  const filePath = join(this.configuration.path, model.toString(), '_search_index_raw.json');
198
205
  try {
199
206
  await this.configuration.filesystem.writeFile(filePath, JSON.stringify(rawIndex));
200
207
  } catch (error) {
201
- throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
208
+ throw new FailedWriteFileStorageEngineError(`Failed to put file://${filePath}`, error);
202
209
  }
203
210
  }
204
211
  }
205
212
 
206
- export default FileEngine;
213
+ export default FileStorageEngine;
@@ -1,22 +1,22 @@
1
- import Engine, {EngineError, MissConfiguredError} from './Engine.js';
1
+ import StorageEngine, {EngineError, MissConfiguredError} from './StorageEngine.js';
2
2
 
3
3
  /**
4
4
  * Represents an error specific to HTTP engine operations.
5
- * @class HTTPEngineError
5
+ * @class HTTPStorageEngineError
6
6
  * @extends EngineError
7
7
  */
8
- export class HTTPEngineError extends EngineError {}
8
+ export class HTTPStorageEngineError extends EngineError {}
9
9
 
10
10
  /**
11
11
  * Error indicating a failed HTTP request.
12
12
  * @class HTTPRequestFailedError
13
- * @extends HTTPEngineError
13
+ * @extends HTTPStorageEngineError
14
14
  *
15
15
  * @param {string} url - The URL of the failed request.
16
16
  * @param {Object} options - The options used in the fetch request.
17
17
  * @param {Response} response - The HTTP response object.
18
18
  */
19
- export class HTTPRequestFailedError extends HTTPEngineError {
19
+ export class HTTPRequestFailedError extends HTTPStorageEngineError {
20
20
  constructor(url, options, response) {
21
21
  const method = options.method?.toLowerCase() || 'get';
22
22
  super(`Failed to ${method} ${url}`);
@@ -27,13 +27,13 @@ export class HTTPRequestFailedError extends HTTPEngineError {
27
27
  }
28
28
 
29
29
  /**
30
- * HTTPEngine is an extension of the Engine class that provides methods for interacting with HTTP-based APIs.
30
+ * HTTPStorageEngine is an extension of the StorageEngine class that provides methods for interacting with HTTP-based APIs.
31
31
  * It uses the Fetch API for sending and receiving data.
32
32
  *
33
- * @class HTTPEngine
34
- * @extends Engine
33
+ * @class HTTPStorageEngine
34
+ * @extends StorageEngine
35
35
  */
36
- class HTTPEngine extends Engine {
36
+ class HTTPStorageEngine extends StorageEngine {
37
37
 
38
38
  /**
39
39
  * Configures the HTTP engine with additional fetch options.
@@ -186,6 +186,12 @@ class HTTPEngine extends Engine {
186
186
  * @throws {HTTPRequestFailedError} Thrown if the PUT request fails.
187
187
  */
188
188
  static async putIndex(index) {
189
+ /**
190
+ * Process an index of models
191
+ * @param {string} location
192
+ * @param {Array<Model>} models
193
+ * @return {Promise<void>}
194
+ */
189
195
  const processIndex = async (location, models) => {
190
196
  const url = new URL([
191
197
  this.configuration.host,
@@ -315,4 +321,4 @@ class HTTPEngine extends Engine {
315
321
  }
316
322
  }
317
323
 
318
- export default HTTPEngine;
324
+ export default HTTPStorageEngine;
@@ -1,28 +1,28 @@
1
1
  import {DeleteObjectCommand, GetObjectCommand, PutObjectCommand} from '@aws-sdk/client-s3';
2
- import Engine, {EngineError, MissConfiguredError} from './Engine.js';
2
+ import StorageEngine, {EngineError, MissConfiguredError} from './StorageEngine.js';
3
3
 
4
4
  /**
5
5
  * Represents an error specific to the S3 engine operations.
6
- * @class S3EngineError
6
+ * @class S3StorageEngineError
7
7
  * @extends EngineError
8
8
  */
9
- class S3EngineError extends EngineError {}
9
+ class S3StorageEngineError extends EngineError {}
10
10
 
11
11
  /**
12
12
  * Error indicating a failure when putting an object to S3.
13
- * @class FailedPutS3EngineError
14
- * @extends S3EngineError
13
+ * @class FailedPutS3StorageEngineError
14
+ * @extends S3StorageEngineError
15
15
  */
16
- class FailedPutS3EngineError extends S3EngineError {}
16
+ class FailedPutS3StorageEngineError extends S3StorageEngineError {}
17
17
 
18
18
  /**
19
- * S3Engine is an extension of the Engine class that provides methods for interacting with AWS S3.
19
+ * S3StorageEngine is an extension of the StorageEngine class that provides methods for interacting with AWS S3.
20
20
  * It allows for storing, retrieving, and managing model data in an S3 bucket.
21
21
  *
22
- * @class S3Engine
23
- * @extends Engine
22
+ * @class S3StorageEngine
23
+ * @extends StorageEngine
24
24
  */
25
- class S3Engine extends Engine {
25
+ class S3StorageEngine extends StorageEngine {
26
26
  /**
27
27
  * Configures the S3 engine with additional options.
28
28
  *
@@ -92,7 +92,7 @@ class S3Engine extends Engine {
92
92
  * @param {Model} model - The model object to upload.
93
93
  * @returns {Promise<void>}
94
94
  *
95
- * @throws {FailedPutS3EngineError} Thrown if there is an error during the S3 PutObject operation.
95
+ * @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
96
96
  */
97
97
  static async putModel(model) {
98
98
  const Key = [this.configuration.prefix, `${model.id}.json`].join('/');
@@ -105,7 +105,7 @@ class S3Engine extends Engine {
105
105
  ContentType: 'application/json',
106
106
  }));
107
107
  } catch (error) {
108
- throw new FailedPutS3EngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
108
+ throw new FailedPutS3StorageEngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
109
109
  }
110
110
  }
111
111
 
@@ -133,9 +133,16 @@ class S3Engine extends Engine {
133
133
  *
134
134
  * @param {Object} index - An object where keys are locations and values are key value pairs of models and their ids.
135
135
  * @returns {Promise<void>}
136
- * @throws {FailedPutS3EngineError} Thrown if there is an error during the S3 PutObject operation.
136
+ * @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
137
137
  */
138
138
  static async putIndex(index) {
139
+ /**
140
+ * Process an index of models
141
+ * @param {string} location
142
+ * @param {Array<Model>} models
143
+ * @throws FailedPutS3StorageEngineError
144
+ * @return {Promise<void>}
145
+ */
139
146
  const processIndex = async (location, models) => {
140
147
  const Key = [this.configuration.prefix, location, '_index.json'].filter(e => Boolean(e)).join('/');
141
148
  const currentIndex = await this.getIndex(location);
@@ -153,7 +160,7 @@ class S3Engine extends Engine {
153
160
  }),
154
161
  }));
155
162
  } catch (error) {
156
- throw new FailedPutS3EngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
163
+ throw new FailedPutS3StorageEngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
157
164
  }
158
165
  };
159
166
 
@@ -205,7 +212,7 @@ class S3Engine extends Engine {
205
212
  * @param {Object} compiledIndex - The compiled search index data.
206
213
  * @returns {Promise<void>}
207
214
  *
208
- * @throws {FailedPutS3EngineError} Thrown if there is an error during the S3 PutObject operation.
215
+ * @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
209
216
  */
210
217
  static async putSearchIndexCompiled(model, compiledIndex) {
211
218
  const Key = [this.configuration.prefix, model.toString(), '_search_index.json'].join('/');
@@ -218,7 +225,7 @@ class S3Engine extends Engine {
218
225
  ContentType: 'application/json',
219
226
  }));
220
227
  } catch (error) {
221
- throw new FailedPutS3EngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
228
+ throw new FailedPutS3StorageEngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
222
229
  }
223
230
  }
224
231
 
@@ -229,7 +236,7 @@ class S3Engine extends Engine {
229
236
  * @param {Object} rawIndex - The raw search index data.
230
237
  * @returns {Promise<void>}
231
238
  *
232
- * @throws {FailedPutS3EngineError} Thrown if there is an error during the S3 PutObject operation.
239
+ * @throws {FailedPutS3StorageEngineError} Thrown if there is an error during the S3 PutObject operation.
233
240
  */
234
241
  static async putSearchIndexRaw(model, rawIndex) {
235
242
  const Key = [this.configuration.prefix, model.toString(), '_search_index_raw.json'].join('/');
@@ -242,9 +249,9 @@ class S3Engine extends Engine {
242
249
  ContentType: 'application/json',
243
250
  }));
244
251
  } catch (error) {
245
- throw new FailedPutS3EngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
252
+ throw new FailedPutS3StorageEngineError(`Failed to put s3://${this.configuration.bucket}/${Key}`, error);
246
253
  }
247
254
  }
248
255
  }
249
256
 
250
- export default S3Engine;
257
+ export default S3StorageEngine;
@@ -1,14 +1,14 @@
1
- import Query from '../Query.js';
2
- import Type from '../type/index.js';
1
+ import Query from '../../Query.js';
2
+ import Type from '../../type/index.js';
3
3
  import lunr from 'lunr';
4
4
 
5
5
  /**
6
- * The `Engine` class provides a base interface for implementing data storage and retrieval engines.
6
+ * The `StorageEngine` class provides a base interface for implementing data storage and retrieval engines.
7
7
  * It includes methods for handling models, indexes, and search functionality.
8
8
  *
9
- * @class Engine
9
+ * @class StorageEngine
10
10
  */
11
- class Engine {
11
+ class StorageEngine {
12
12
  static configuration = undefined;
13
13
  static _searchCache = undefined;
14
14
 
@@ -186,6 +186,11 @@ class Engine {
186
186
  const uploadedModels = [];
187
187
  const indexUpdates = {};
188
188
 
189
+ /**
190
+ * Process a model, putting updates to the model and all linked models.
191
+ * @param {Model} m
192
+ * @return {Promise<void>}
193
+ */
189
194
  const processModel = async (m) => {
190
195
  if (!uploadedModels.includes(m.id)) {
191
196
  m.validate();
@@ -389,6 +394,11 @@ class Engine {
389
394
  this.checkConfiguration();
390
395
  const hydratedModels = {};
391
396
 
397
+ /**
398
+ * Hydrate a model
399
+ * @param {Model} modelToProcess
400
+ * @return {Promise<Model>}
401
+ */
392
402
  const hydrateModel = async (modelToProcess) => {
393
403
  hydratedModels[modelToProcess.id] = modelToProcess;
394
404
 
@@ -405,6 +415,13 @@ class Engine {
405
415
  return modelToProcess;
406
416
  };
407
417
 
418
+ /**
419
+ * Hydrate a dry sub model
420
+ * @param property
421
+ * @param modelToProcess
422
+ * @param name
423
+ * @return {Promise<Model>}
424
+ */
408
425
  const hydrateSubModel = async (property, modelToProcess, name) => {
409
426
  if (hydratedModels[property.id]) {
410
427
  return hydratedModels[property.id];
@@ -418,6 +435,13 @@ class Engine {
418
435
  return hydratedSubModel;
419
436
  };
420
437
 
438
+ /**
439
+ * Hydrate an array of dry models
440
+ * @param property
441
+ * @param modelToProcess
442
+ * @param name
443
+ * @return {Promise<Awaited<*>[]>}
444
+ */
421
445
  const hydrateModelList = async (property, modelToProcess, name) => {
422
446
  const subModelClass = getSubModelClass(modelToProcess, name, true);
423
447
 
@@ -440,6 +464,13 @@ class Engine {
440
464
  }));
441
465
  };
442
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
+ */
443
474
  function getSubModelClass(modelToProcess, name, isArray = false) {
444
475
  const constructorField = modelToProcess.constructor[name];
445
476
 
@@ -457,9 +488,13 @@ class Engine {
457
488
  * Configures the engine with specific settings.
458
489
  *
459
490
  * @param {Object} configuration - The configuration settings for the engine.
460
- * @returns {Engine} A new engine instance with the applied configuration.
491
+ * @returns {StorageEngine} A new engine instance with the applied configuration.
461
492
  */
462
493
  static configure(configuration) {
494
+ /**
495
+ * @class ConfiguredStore
496
+ * @extends StorageEngine
497
+ */
463
498
  class ConfiguredStore extends this {
464
499
  static configuration = configuration;
465
500
  }
@@ -476,7 +511,7 @@ class Engine {
476
511
  * @abstract
477
512
  */
478
513
  static checkConfiguration() {
479
- // Implemented in extending Engine class
514
+ // Implemented in extending StorageEngine class
480
515
  }
481
516
 
482
517
  /**
@@ -568,9 +603,9 @@ export class MissConfiguredError extends EngineError {
568
603
  * @param {Object} configuration - The configuration object that caused the misconfiguration.
569
604
  */
570
605
  constructor(configuration) {
571
- super('Engine is miss-configured');
606
+ super('StorageEngine is miss-configured');
572
607
  this.configuration = configuration;
573
608
  }
574
609
  }
575
610
 
576
- export default Engine;
611
+ export default StorageEngine;
package/src/type/index.js CHANGED
@@ -15,23 +15,18 @@ import StringType from './simple/StringType.js';
15
15
  * @property {DateType} Date
16
16
  * @property {ArrayType} Array
17
17
  * @property {CustomType} Custom
18
- * @property {ResolvedType} Resolved
18
+ * @property {{Slug: SlugType}} Resolved
19
19
  * @property {Model} Model
20
20
  */
21
- const Type = {};
22
-
23
- Type.String = StringType;
24
- Type.Number = NumberType;
25
- Type.Boolean = BooleanType;
26
- Type.Date = DateType;
27
- Type.Array = ArrayType;
28
- Type.Custom = CustomType;
29
-
30
- /**
31
- * @class ResolvedType
32
- * @property {SlugType} Slug
33
- */
34
- Type.Resolved = {Slug: SlugType};
35
- Type.Model = Model;
21
+ class Type {
22
+ static Model = Model;
23
+ static String = StringType;
24
+ static Number = NumberType;
25
+ static Boolean = BooleanType;
26
+ static Date = DateType;
27
+ static Array = ArrayType;
28
+ static Custom = CustomType;
29
+ static Resolved = {Slug: SlugType};
30
+ }
36
31
 
37
32
  export default Type;
@@ -1,15 +1,15 @@
1
- import SimpleType from './SimpleType.js';
1
+ import Type from '../Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a boolean type.
5
5
  *
6
6
  * This class is used to define and handle data of the boolean type.
7
- * It extends the {@link SimpleType} class to represent string-specific behavior.
7
+ * It extends the {@link Type} class to represent string-specific behavior.
8
8
  *
9
9
  * @class BooleanType
10
- * @extends SimpleType
10
+ * @extends Type
11
11
  */
12
- class BooleanType extends SimpleType {
12
+ class BooleanType extends Type {
13
13
  static {
14
14
  /**
15
15
  * @static
@@ -1,15 +1,15 @@
1
- import SimpleType from './SimpleType.js';
1
+ import Type from '../Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a date type with ISO date-time format.
5
5
  *
6
6
  * This class is used to define and handle data of the date type.
7
- * It extends the {@link SimpleType} class to represent string-specific behavior.
7
+ * It extends the {@link Type} class to represent string-specific behavior.
8
8
  *
9
9
  * @class DateType
10
- * @extends SimpleType
10
+ * @extends Type
11
11
  */
12
- class DateType extends SimpleType {
12
+ class DateType extends Type {
13
13
  static {
14
14
  /**
15
15
  * @static
@@ -1,15 +1,15 @@
1
- import SimpleType from './SimpleType.js';
1
+ import Type from '../Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a number type.
5
5
  *
6
6
  * This class is used to define and handle data of the number type.
7
- * It extends the {@link SimpleType} class to represent string-specific behavior.
7
+ * It extends the {@link Type} class to represent string-specific behavior.
8
8
  *
9
9
  * @class NumberType
10
- * @extends SimpleType
10
+ * @extends Type
11
11
  */
12
- class NumberType extends SimpleType {
12
+ class NumberType extends Type {
13
13
  static {
14
14
  /**
15
15
  * @static
@@ -1,15 +1,15 @@
1
- import SimpleType from './SimpleType.js';
1
+ import Type from '../Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a string type.
5
5
  *
6
6
  * This class is used to define and handle data of the string type.
7
- * It extends the {@link SimpleType} class to represent string-specific behavior.
7
+ * It extends the {@link Type} class to represent string-specific behavior.
8
8
  *
9
9
  * @class StringType
10
- * @extends SimpleType
10
+ * @extends Type
11
11
  */
12
- class StringType extends SimpleType {
12
+ class StringType extends Type {
13
13
  static {
14
14
  /**
15
15
  * @static
@@ -1,3 +0,0 @@
1
- import FileEngine from '../../src/engine/FileEngine.js';
2
-
3
- export default FileEngine;
@@ -1,3 +0,0 @@
1
- import HTTPEngine from '../../src/engine/HTTPEngine.js';
2
-
3
- export default HTTPEngine;
@@ -1,3 +0,0 @@
1
- import S3Engine from '../../src/engine/S3Engine.js';
2
-
3
- export default S3Engine;
@@ -1,14 +0,0 @@
1
- import Type from '../Type.js';
2
-
3
- /**
4
- * Class representing a simple type.
5
- *
6
- * This serves as a base class for primitive or simple types such as string, number, or boolean.
7
- *
8
- * @class SimpleType
9
- * @extends Type
10
- */
11
- class SimpleType extends Type {
12
- }
13
-
14
- export default SimpleType;