@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.1",
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
- * Wildcards: character '*' can be placed at any location in a query
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.#storage.getSearchIndex(modelConstructor)
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.#storage.getIndex(modelConstructor)
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.
@@ -1,4 +1,4 @@
1
- import lunr from 'lunr';
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.ref]), doc.score));
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 {lunr.Index} The compiled Lunr index.
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 {lunr.Index} The compiled Lunr index.
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 = lunr(function () {
72
- this.ref('id');
73
-
74
- for (const field of model.searchProperties()) {
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, defaultValue = undefined) {
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 (_error) {
102
- return {};
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 (_error) {
139
- return {};
141
+ } catch (error) {
142
+ if (error instanceof NoSuchKey) {
143
+ return {};
144
+ }
145
+ throw error;
140
146
  }
141
147
  }
142
148