@acodeninja/persist 3.0.1 → 3.1.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.
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@acodeninja/persist",
|
3
|
-
"version": "3.0.
|
3
|
+
"version": "3.1.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": {
|
@@ -25,8 +25,8 @@
|
|
25
25
|
"ajv": "^8.17.1",
|
26
26
|
"ajv-errors": "^3.0.0",
|
27
27
|
"ajv-formats": "^3.0.1",
|
28
|
+
"fuse.js": "^7.1.0",
|
28
29
|
"lodash": "^4.17.21",
|
29
|
-
"lunr": "^2.3.9",
|
30
30
|
"slugify": "^1.6.6",
|
31
31
|
"ulid": "^2.3.0"
|
32
32
|
},
|
package/src/Connection.js
CHANGED
@@ -468,24 +468,28 @@ export default class Connection {
|
|
468
468
|
|
469
469
|
/**
|
470
470
|
* Search the given model's search properties for matching results.
|
471
|
-
*
|
472
|
-
* Fields: search for a specific field's value with 'field:value'
|
473
|
-
* Boosting: if foo is important try 'foo^10 bar'
|
474
|
-
* Fuzzy: 'foo~1' will match 'boo' but not 'bao', 'foo~2' would match 'bao'
|
475
|
-
* Must include: '+foo bar' must include 'foo' and may include 'bar'
|
476
|
-
* Must not include: '-foo bar' must not include 'foo' and may include 'bar'
|
477
|
-
* Mixed include: '+foo -bar' must include 'foo' must not include 'bar'
|
471
|
+
*
|
478
472
|
* @param {Model.constructor} modelConstructor
|
479
473
|
* @param {string} query
|
480
474
|
* @return {Promise<Array<SearchResult>>}
|
481
475
|
*/
|
482
476
|
async search(modelConstructor, query) {
|
483
|
-
const searchIndex = await this
|
484
|
-
.then(index => new SearchIndex(modelConstructor, index));
|
477
|
+
const searchIndex = await this.getSearchIndex(modelConstructor);
|
485
478
|
|
486
479
|
return searchIndex.search(query);
|
487
480
|
}
|
488
481
|
|
482
|
+
/**
|
483
|
+
* Retrieve a search index to query at your leisure.
|
484
|
+
*
|
485
|
+
* @param {Model.constructor} modelConstructor
|
486
|
+
* @return {SearchIndex}
|
487
|
+
*/
|
488
|
+
async getSearchIndex(modelConstructor) {
|
489
|
+
return await this.#storage.getSearchIndex(modelConstructor)
|
490
|
+
.then(index => new SearchIndex(modelConstructor, index));
|
491
|
+
}
|
492
|
+
|
489
493
|
/**
|
490
494
|
* Find using a structured query and indexed fields.
|
491
495
|
*
|
@@ -494,12 +498,22 @@ export default class Connection {
|
|
494
498
|
* @return {Promise<Array<SearchResult>>}
|
495
499
|
*/
|
496
500
|
async find(modelConstructor, query) {
|
497
|
-
const findIndex = await this
|
498
|
-
.then(index => new FindIndex(modelConstructor, index));
|
501
|
+
const findIndex = await this.getIndex(modelConstructor);
|
499
502
|
|
500
503
|
return findIndex.query(query);
|
501
504
|
}
|
502
505
|
|
506
|
+
/**
|
507
|
+
* Retrieve a index to query at your leisure.
|
508
|
+
*
|
509
|
+
* @param {Model.constructor} modelConstructor
|
510
|
+
* @return {FindIndex}
|
511
|
+
*/
|
512
|
+
async getIndex(modelConstructor) {
|
513
|
+
return await this.#storage.getIndex(modelConstructor)
|
514
|
+
.then(index => new FindIndex(modelConstructor, index));
|
515
|
+
}
|
516
|
+
|
503
517
|
/**
|
504
518
|
* Start a transaction, allowing multiple queries to be queued up and committed in one go.
|
505
519
|
* Should an error occur, any committed operations will be reverted.
|
package/src/data/SearchIndex.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import Fuse from 'fuse.js';
|
2
2
|
|
3
3
|
/**
|
4
4
|
* Represents a single search result with the associated model instance and its relevance score.
|
@@ -47,13 +47,13 @@ export default class SearchIndex {
|
|
47
47
|
search(query) {
|
48
48
|
return this.searchIndex
|
49
49
|
.search(query)
|
50
|
-
.map(doc => new SearchResult(this.#model.fromData(this.#index[doc.
|
50
|
+
.map(doc => new SearchResult(this.#model.fromData(this.#index[doc.item.id]), doc.score));
|
51
51
|
}
|
52
52
|
|
53
53
|
/**
|
54
54
|
* Lazily compiles and returns the Lunr index instance.
|
55
55
|
*
|
56
|
-
* @return {
|
56
|
+
* @return {Fuse} The compiled Lunr index.
|
57
57
|
*/
|
58
58
|
get searchIndex() {
|
59
59
|
return this.#compiledIndex ?? this.#compileIndex();
|
@@ -62,22 +62,15 @@ export default class SearchIndex {
|
|
62
62
|
/**
|
63
63
|
* Compiles the Lunr index using the model's search properties.
|
64
64
|
*
|
65
|
-
* @return {
|
65
|
+
* @return {Fuse} The compiled Lunr index.
|
66
66
|
* @private
|
67
67
|
*/
|
68
68
|
#compileIndex() {
|
69
|
-
const model = this.#model;
|
70
69
|
const index = this.#index;
|
71
|
-
this.#compiledIndex =
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
this.field(field);
|
76
|
-
}
|
77
|
-
|
78
|
-
Object.values(index).forEach(function (doc) {
|
79
|
-
this.add(doc);
|
80
|
-
}, this);
|
70
|
+
this.#compiledIndex = new Fuse(Object.values(index), {
|
71
|
+
includeScore: true,
|
72
|
+
ignoreDiacritics: true,
|
73
|
+
keys: this.#model.searchProperties().map(p => p.replace(/\[\*]\./g, '')),
|
81
74
|
});
|
82
75
|
|
83
76
|
return this.#compiledIndex;
|
@@ -81,8 +81,12 @@ export default class HTTPStorageEngine extends StorageEngine {
|
|
81
81
|
return this.#processFetch(
|
82
82
|
this.#generateURL([modelConstructor.name]),
|
83
83
|
this.#getReadOptions(),
|
84
|
-
|
85
|
-
|
84
|
+
).catch(error => {
|
85
|
+
if (error?.response?.status === 404) {
|
86
|
+
return {};
|
87
|
+
}
|
88
|
+
throw error;
|
89
|
+
});
|
86
90
|
}
|
87
91
|
|
88
92
|
/**
|
@@ -109,8 +113,12 @@ export default class HTTPStorageEngine extends StorageEngine {
|
|
109
113
|
return this.#processFetch(
|
110
114
|
this.#generateURL([modelConstructor.name, 'search']),
|
111
115
|
this.#getReadOptions(),
|
112
|
-
|
113
|
-
|
116
|
+
).catch(error => {
|
117
|
+
if (error?.response?.status === 404) {
|
118
|
+
return {};
|
119
|
+
}
|
120
|
+
throw error;
|
121
|
+
});
|
114
122
|
}
|
115
123
|
|
116
124
|
/**
|
@@ -158,18 +166,13 @@ export default class HTTPStorageEngine extends StorageEngine {
|
|
158
166
|
*
|
159
167
|
* @param {string|URL} url - The URL to fetch.
|
160
168
|
* @param {Object} options - The fetch options.
|
161
|
-
* @param {*} [defaultValue] - A default value to return if the fetch fails.
|
162
169
|
* @returns {Promise<Object>} The parsed JSON response.
|
163
170
|
* @throws {HTTPRequestFailedError} Thrown if the fetch request fails.
|
164
171
|
*/
|
165
|
-
#processFetch(url, options
|
172
|
+
#processFetch(url, options) {
|
166
173
|
return this.configuration.fetch(url, options)
|
167
174
|
.then(response => {
|
168
175
|
if (!response.ok) {
|
169
|
-
if (defaultValue !== undefined) {
|
170
|
-
return {json: () => Promise.resolve(defaultValue)};
|
171
|
-
}
|
172
|
-
|
173
176
|
throw new HTTPRequestFailedError(url, options, response);
|
174
177
|
}
|
175
178
|
|
@@ -98,8 +98,11 @@ class S3StorageEngine extends StorageEngine {
|
|
98
98
|
}));
|
99
99
|
|
100
100
|
return JSON.parse(await data.Body.transformToString());
|
101
|
-
} catch (
|
102
|
-
|
101
|
+
} catch (error) {
|
102
|
+
if (error instanceof NoSuchKey) {
|
103
|
+
return {};
|
104
|
+
}
|
105
|
+
throw error;
|
103
106
|
}
|
104
107
|
}
|
105
108
|
|
@@ -135,8 +138,11 @@ class S3StorageEngine extends StorageEngine {
|
|
135
138
|
}));
|
136
139
|
|
137
140
|
return JSON.parse(await data.Body.transformToString());
|
138
|
-
} catch (
|
139
|
-
|
141
|
+
} catch (error) {
|
142
|
+
if (error instanceof NoSuchKey) {
|
143
|
+
return {};
|
144
|
+
}
|
145
|
+
throw error;
|
140
146
|
}
|
141
147
|
}
|
142
148
|
|