@acodeninja/persist 2.1.1 → 2.2.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/package.json +2 -2
- package/src/Query.js +62 -0
- package/src/engine/Engine.js +9 -7
- package/src/engine/FileEngine.js +2 -8
- package/src/engine/HTTPEngine.js +0 -9
- package/src/engine/S3Engine.js +0 -10
- package/src/type/Model.js +23 -4
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@acodeninja/persist",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.2.1",
|
4
4
|
"description": "A JSON based data modelling and persistence module with alternate storage mechanisms.",
|
5
5
|
"type": "module",
|
6
6
|
"scripts": {
|
@@ -26,6 +26,7 @@
|
|
26
26
|
"ajv": "^8.16.0",
|
27
27
|
"ajv-errors": "^3.0.0",
|
28
28
|
"ajv-formats": "^3.0.1",
|
29
|
+
"lodash": "^4.17.21",
|
29
30
|
"lunr": "^2.3.9",
|
30
31
|
"slugify": "^1.6.6",
|
31
32
|
"ulid": "^2.3.0"
|
@@ -43,7 +44,6 @@
|
|
43
44
|
"eslint": "^9.6.0",
|
44
45
|
"globals": "^15.8.0",
|
45
46
|
"husky": "^9.1.1",
|
46
|
-
"lodash": "^4.17.21",
|
47
47
|
"monocart-coverage-reports": "^2.10.2",
|
48
48
|
"semantic-release": "^24.0.0",
|
49
49
|
"sinon": "^18.0.0"
|
package/src/Query.js
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
/**
|
2
|
+
* persist query language features:
|
3
|
+
* - value match {title: 'test'} or {title: {$is: 'test'}}
|
4
|
+
* - contains match {list: {$contains: 'test'}} or {string: {$contains: 'es'}}
|
5
|
+
* - nested query {list: {$contains: {slug: 'test'}}}
|
6
|
+
* - deep nesting queries {list: {$contains: {string: {$contains: 'test'}}}}
|
7
|
+
*/
|
8
|
+
|
9
|
+
/**
|
10
|
+
* @class Query
|
11
|
+
*/
|
12
|
+
class Query {
|
13
|
+
query;
|
14
|
+
|
15
|
+
/**
|
16
|
+
*
|
17
|
+
* @param {object} query
|
18
|
+
*/
|
19
|
+
constructor(query) {
|
20
|
+
this.query = query;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Using the input query, find records in an index that match
|
25
|
+
*
|
26
|
+
* @param {typeof Model} model
|
27
|
+
* @param {object} index
|
28
|
+
*/
|
29
|
+
execute(model, index) {
|
30
|
+
const matchIs = (query) => !!query?.$is;
|
31
|
+
const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query);
|
32
|
+
const matchContains = (query) => !!query?.$contains;
|
33
|
+
|
34
|
+
const matchesQuery = (subject, inputQuery = this.query) => {
|
35
|
+
if (!subject || !inputQuery) return false;
|
36
|
+
|
37
|
+
if (matchPrimitive(inputQuery)) return subject === inputQuery;
|
38
|
+
|
39
|
+
if (matchIs(inputQuery))
|
40
|
+
if (subject === inputQuery.$is) return true;
|
41
|
+
|
42
|
+
if (matchContains(inputQuery)) {
|
43
|
+
if (subject.includes?.(inputQuery.$contains)) return true;
|
44
|
+
|
45
|
+
for (const value of subject) {
|
46
|
+
if (matchesQuery(value, inputQuery.$contains)) return true;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
for (const key of Object.keys(inputQuery)) {
|
51
|
+
if (!['$is', '$contains'].includes(key))
|
52
|
+
if (matchesQuery(subject[key], inputQuery[key])) return true;
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
56
|
+
return Object.values(index)
|
57
|
+
.filter(m => matchesQuery(m))
|
58
|
+
.map(m => model.fromData(m));
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
export default Query;
|
package/src/engine/Engine.js
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import Query from '../Query.js';
|
1
2
|
import Type from '../type/index.js';
|
2
3
|
import lunr from 'lunr';
|
3
4
|
|
@@ -15,6 +16,10 @@ export default class Engine {
|
|
15
16
|
throw new NotImplementedError(`${this.name} must implement .putModel()`);
|
16
17
|
}
|
17
18
|
|
19
|
+
static async getIndex(_model) {
|
20
|
+
throw new NotImplementedError(`${this.name} does not implement .getIndex()`);
|
21
|
+
}
|
22
|
+
|
18
23
|
static async putIndex(_index) {
|
19
24
|
throw new NotImplementedError(`${this.name} does not implement .putIndex()`);
|
20
25
|
}
|
@@ -35,10 +40,6 @@ export default class Engine {
|
|
35
40
|
throw new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`);
|
36
41
|
}
|
37
42
|
|
38
|
-
static async findByValue(_model, _parameters) {
|
39
|
-
throw new NotImplementedError(`${this.name} does not implement .findByValue()`);
|
40
|
-
}
|
41
|
-
|
42
43
|
static async search(model, query) {
|
43
44
|
this.checkConfiguration();
|
44
45
|
|
@@ -61,10 +62,11 @@ export default class Engine {
|
|
61
62
|
return output;
|
62
63
|
}
|
63
64
|
|
64
|
-
static async find(model,
|
65
|
+
static async find(model, query) {
|
65
66
|
this.checkConfiguration();
|
66
|
-
const
|
67
|
-
|
67
|
+
const index = await this.getIndex(model);
|
68
|
+
|
69
|
+
return new Query(query).execute(model, index);
|
68
70
|
}
|
69
71
|
|
70
72
|
static async put(model) {
|
package/src/engine/FileEngine.js
CHANGED
@@ -31,14 +31,8 @@ export default class FileEngine extends Engine {
|
|
31
31
|
return JSON.parse(await this._configuration.filesystem.readFile(filePath).then(f => f.toString()));
|
32
32
|
}
|
33
33
|
|
34
|
-
static async
|
35
|
-
|
36
|
-
|
37
|
-
return Object.values(index)
|
38
|
-
.filter((model) =>
|
39
|
-
Object.entries(parameters)
|
40
|
-
.some(([name, value]) => model[name] === value),
|
41
|
-
);
|
34
|
+
static async getIndex(model) {
|
35
|
+
return JSON.parse((await this._configuration.filesystem.readFile(join(this._configuration.path, model.name, '_index.json')).catch(() => '{}')).toString());
|
42
36
|
}
|
43
37
|
|
44
38
|
static async putModel(model) {
|
package/src/engine/HTTPEngine.js
CHANGED
@@ -75,15 +75,6 @@ export default class HTTPEngine extends Engine {
|
|
75
75
|
return await this._processFetch(url, this._getReadOptions());
|
76
76
|
}
|
77
77
|
|
78
|
-
static async findByValue(model, parameters) {
|
79
|
-
const index = await this.getIndex(model.name);
|
80
|
-
return Object.values(index)
|
81
|
-
.filter((model) =>
|
82
|
-
Object.entries(parameters)
|
83
|
-
.some(([name, value]) => model[name] === value),
|
84
|
-
);
|
85
|
-
}
|
86
|
-
|
87
78
|
static async putModel(model) {
|
88
79
|
const url = new URL([
|
89
80
|
this._configuration.host,
|
package/src/engine/S3Engine.js
CHANGED
@@ -24,16 +24,6 @@ export default class S3Engine extends Engine {
|
|
24
24
|
return JSON.parse(await data.Body.transformToString());
|
25
25
|
}
|
26
26
|
|
27
|
-
static async findByValue(model, parameters) {
|
28
|
-
const index = await this.getIndex(model.name);
|
29
|
-
|
30
|
-
return Object.values(index)
|
31
|
-
.filter((model) =>
|
32
|
-
Object.entries(parameters)
|
33
|
-
.some(([name, value]) => model[name] === value),
|
34
|
-
);
|
35
|
-
}
|
36
|
-
|
37
27
|
static async putModel(model) {
|
38
28
|
const Key = [this._configuration.prefix, `${model.id}.json`].join('/');
|
39
29
|
|
package/src/type/Model.js
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import SchemaCompiler from '../SchemaCompiler.js';
|
2
2
|
import StringType from './simple/StringType.js';
|
3
|
+
import _ from 'lodash';
|
3
4
|
import {monotonicFactory} from 'ulid';
|
4
5
|
|
5
6
|
const createID = monotonicFactory();
|
@@ -47,13 +48,31 @@ export default class Model {
|
|
47
48
|
}
|
48
49
|
|
49
50
|
toIndexData() {
|
50
|
-
const
|
51
|
+
const output = { id: this.id };
|
52
|
+
const index = this.constructor.indexedProperties();
|
51
53
|
|
52
|
-
for (const
|
53
|
-
|
54
|
+
for (const key of index) {
|
55
|
+
if (_.has(this, key)) {
|
56
|
+
_.set(output, key, _.get(this, key));
|
57
|
+
}
|
58
|
+
|
59
|
+
if (key.includes('[*]')) {
|
60
|
+
const segments = key.split('.');
|
61
|
+
|
62
|
+
const preWildcard = segments.slice(0, segments.indexOf('[*]'));
|
63
|
+
const postWildcard = segments.slice(segments.indexOf('[*]') + 1);
|
64
|
+
|
65
|
+
_.set(
|
66
|
+
output,
|
67
|
+
preWildcard,
|
68
|
+
_.get(this, preWildcard, [])
|
69
|
+
.map((e, i) =>
|
70
|
+
_.set(_.get(output, preWildcard, [])[i] ?? {}, postWildcard, _.get(e, postWildcard))),
|
71
|
+
);
|
72
|
+
}
|
54
73
|
}
|
55
74
|
|
56
|
-
return
|
75
|
+
return output;
|
57
76
|
}
|
58
77
|
|
59
78
|
toSearchData() {
|