@acodeninja/persist 3.0.0-next.14 → 3.0.0-next.15
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 +1 -1
- package/src/Connection.js +52 -33
- package/src/Schema.js +9 -0
- package/src/data/Model.js +8 -0
- package/src/data/SearchIndex.js +44 -2
- package/src/data/properties/Type.js +8 -0
package/package.json
CHANGED
package/src/Connection.js
CHANGED
@@ -7,6 +7,14 @@ import Model from './data/Model.js';
|
|
7
7
|
import SearchIndex from './data/SearchIndex.js';
|
8
8
|
import _ from 'lodash';
|
9
9
|
|
10
|
+
/**
|
11
|
+
* Represents a transactional operation to be executed, typically queued and later committed.
|
12
|
+
*
|
13
|
+
* Stores the method to invoke, the arguments to apply, and tracks the result or error state
|
14
|
+
* of the transaction once it's processed.
|
15
|
+
*
|
16
|
+
* @class Transaction
|
17
|
+
*/
|
10
18
|
export class Transaction {
|
11
19
|
constructor(method, ...args) {
|
12
20
|
this.method = method;
|
@@ -81,6 +89,12 @@ export default class Connection {
|
|
81
89
|
async hydrate(dryModel) {
|
82
90
|
const hydratedModels = {};
|
83
91
|
|
92
|
+
/**
|
93
|
+
* Recursively hydrates a single model and its nested properties.
|
94
|
+
*
|
95
|
+
* @param {Object|Model} modelToProcess - The model instance to hydrate.
|
96
|
+
* @returns {Promise<Model>} The hydrated model instance.
|
97
|
+
*/
|
84
98
|
const hydrateModel = async (modelToProcess) => {
|
85
99
|
hydratedModels[modelToProcess.id] = modelToProcess;
|
86
100
|
|
@@ -97,6 +111,12 @@ export default class Connection {
|
|
97
111
|
return modelToProcess;
|
98
112
|
};
|
99
113
|
|
114
|
+
/**
|
115
|
+
* Hydrates a nested sub-model if it hasn't already been hydrated.
|
116
|
+
*
|
117
|
+
* @param {Object} property - The sub-model with a known ID but incomplete data.
|
118
|
+
* @returns {Promise<Model>} The fully hydrated sub-model.
|
119
|
+
*/
|
100
120
|
const hydrateSubModel = async (property) => {
|
101
121
|
if (hydratedModels[property.id]) {
|
102
122
|
return hydratedModels[property.id];
|
@@ -109,6 +129,12 @@ export default class Connection {
|
|
109
129
|
return hydratedSubModel;
|
110
130
|
};
|
111
131
|
|
132
|
+
/**
|
133
|
+
* Hydrates a list of related sub-models.
|
134
|
+
*
|
135
|
+
* @param {Array<Object>} property - Array of dry sub-models.
|
136
|
+
* @returns {Promise<Array<Model>>} Array of hydrated sub-models.
|
137
|
+
*/
|
112
138
|
const hydrateModelList = async (property) => {
|
113
139
|
const newModelList = await Promise.all(property.map(subModel => {
|
114
140
|
if (hydratedModels[subModel.id]) {
|
@@ -165,7 +191,7 @@ export default class Connection {
|
|
165
191
|
|
166
192
|
if (
|
167
193
|
Boolean(modelToProcess.constructor.indexedProperties().length) &&
|
168
|
-
|
194
|
+
(!currentModel || JSON.stringify(currentModel.toIndexData()) !== JSON.stringify(modelToProcess.toIndexData()))
|
169
195
|
) {
|
170
196
|
const modelToProcessConstructor = this.#getModelConstructorFromId(modelToProcess.id);
|
171
197
|
modelsToReindex[modelToProcessConstructor] = modelsToReindex[modelToProcessConstructor] || [];
|
@@ -174,7 +200,7 @@ export default class Connection {
|
|
174
200
|
|
175
201
|
if (
|
176
202
|
Boolean(modelToProcess.constructor.searchProperties().length) &&
|
177
|
-
|
203
|
+
(!currentModel || JSON.stringify(currentModel.toSearchData()) !== JSON.stringify(modelToProcess.toSearchData()))
|
178
204
|
) {
|
179
205
|
const modelToProcessConstructor = this.#getModelConstructorFromId(modelToProcess.id);
|
180
206
|
modelsToReindexSearch[modelToProcessConstructor] = modelsToReindexSearch[modelToProcessConstructor] || [];
|
@@ -458,28 +484,6 @@ export default class Connection {
|
|
458
484
|
return constructor;
|
459
485
|
}
|
460
486
|
|
461
|
-
/**
|
462
|
-
* Decide if two models indexable fields are different
|
463
|
-
* @param {Model} currentModel
|
464
|
-
* @param {Model} modelToProcess
|
465
|
-
* @return {boolean}
|
466
|
-
* @private
|
467
|
-
*/
|
468
|
-
#indexedFieldsHaveChanged(currentModel, modelToProcess) {
|
469
|
-
return !currentModel || JSON.stringify(currentModel.toIndexData()) !== JSON.stringify(modelToProcess.toIndexData());
|
470
|
-
}
|
471
|
-
|
472
|
-
/**
|
473
|
-
* Decide if two models searchable fields have changed
|
474
|
-
* @param {Model} currentModel
|
475
|
-
* @param {Model} modelToProcess
|
476
|
-
* @return {boolean}
|
477
|
-
* @private
|
478
|
-
*/
|
479
|
-
#searchableFieldsHaveChanged(currentModel, modelToProcess) {
|
480
|
-
return !currentModel || JSON.stringify(currentModel.toSearchData()) !== JSON.stringify(modelToProcess.toSearchData());
|
481
|
-
}
|
482
|
-
|
483
487
|
/**
|
484
488
|
* Get model classes that are directly linked to the given model in either direction
|
485
489
|
* @param {Model.constructor} model
|
@@ -560,22 +564,26 @@ export default class Connection {
|
|
560
564
|
}
|
561
565
|
|
562
566
|
/**
|
567
|
+
* Base class for errors that occur during connection operations.
|
568
|
+
*
|
563
569
|
* @class ConnectionError
|
564
570
|
* @extends Error
|
565
571
|
*/
|
566
572
|
export class ConnectionError extends Error {
|
567
|
-
/**
|
568
|
-
* @param {String} message
|
569
|
-
*/
|
570
|
-
constructor(message) {
|
571
|
-
super(message);
|
572
|
-
}
|
573
573
|
}
|
574
574
|
|
575
|
+
/**
|
576
|
+
* Thrown when a connection is created with missing arguments.
|
577
|
+
*
|
578
|
+
* @class MissingArgumentsConnectionError
|
579
|
+
* @extends ConnectionError
|
580
|
+
*/
|
575
581
|
export class MissingArgumentsConnectionError extends ConnectionError {
|
576
582
|
}
|
577
583
|
|
578
584
|
/**
|
585
|
+
* Thrown when a model class is not registered.
|
586
|
+
*
|
579
587
|
* @class ModelNotRegisteredConnectionError
|
580
588
|
* @extends ConnectionError
|
581
589
|
*/
|
@@ -590,12 +598,23 @@ export class ModelNotRegisteredConnectionError extends ConnectionError {
|
|
590
598
|
}
|
591
599
|
}
|
592
600
|
|
601
|
+
/**
|
602
|
+
* Base class for errors that occur during transactions.
|
603
|
+
*
|
604
|
+
* @class TransactionError
|
605
|
+
* @extends {Error}
|
606
|
+
*/
|
593
607
|
class TransactionError extends Error {
|
594
|
-
constructor(message) {
|
595
|
-
super(message);
|
596
|
-
}
|
597
608
|
}
|
598
609
|
|
610
|
+
/**
|
611
|
+
* Thrown when a transaction fails to commit.
|
612
|
+
*
|
613
|
+
* Contains the original error and the list of transactions involved.
|
614
|
+
*
|
615
|
+
* @class CommitFailedTransactionError
|
616
|
+
* @extends {TransactionError}
|
617
|
+
*/
|
599
618
|
export class CommitFailedTransactionError extends TransactionError {
|
600
619
|
/**
|
601
620
|
*
|
package/src/Schema.js
CHANGED
@@ -26,6 +26,15 @@ class Schema {
|
|
26
26
|
ajvErrors(validation);
|
27
27
|
ajvFormats(validation);
|
28
28
|
|
29
|
+
/**
|
30
|
+
* Recursively builds a JSON-schema-like object from a model or schema segment.
|
31
|
+
*
|
32
|
+
* Handles both `Model` instances and schema property definitions,
|
33
|
+
* including nested models and required property rules.
|
34
|
+
*
|
35
|
+
* @param {Object|Model|Type} schemaSegment - A model or a property descriptor.
|
36
|
+
* @returns {Object} A JSON schema representation of the input segment.
|
37
|
+
*/
|
29
38
|
function BuildSchema(schemaSegment) {
|
30
39
|
const thisSchema = {};
|
31
40
|
|
package/src/data/Model.js
CHANGED
@@ -151,6 +151,14 @@ class Model {
|
|
151
151
|
* @static
|
152
152
|
*/
|
153
153
|
static get required() {
|
154
|
+
/**
|
155
|
+
* A subclass of the current model with the `_required` flag set to `true`.
|
156
|
+
* Used to indicate that the property is required during validation or schema generation.
|
157
|
+
*
|
158
|
+
* @class
|
159
|
+
* @extends {Model}
|
160
|
+
* @private
|
161
|
+
*/
|
154
162
|
class Required extends this {
|
155
163
|
static _required = true;
|
156
164
|
}
|
package/src/data/SearchIndex.js
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
import lunr from 'lunr';
|
2
2
|
|
3
|
+
/**
|
4
|
+
* Represents a single search result with the associated model instance and its relevance score.
|
5
|
+
*
|
6
|
+
* @class SearchResult
|
7
|
+
*/
|
3
8
|
export class SearchResult {
|
4
9
|
constructor(model, score) {
|
5
10
|
this.model = model;
|
@@ -7,11 +12,24 @@ export class SearchResult {
|
|
7
12
|
}
|
8
13
|
}
|
9
14
|
|
15
|
+
/**
|
16
|
+
* A full-text search index wrapper using Lunr.js for a given model.
|
17
|
+
* Supports indexing and querying model data.
|
18
|
+
*
|
19
|
+
* @class SearchIndex
|
20
|
+
*/
|
10
21
|
export default class SearchIndex {
|
11
22
|
#index;
|
12
23
|
#model;
|
13
24
|
#compiledIndex;
|
14
25
|
|
26
|
+
/**
|
27
|
+
* Initializes the search index for the provided model.
|
28
|
+
*
|
29
|
+
* @param {Model} model - The model definition to use for indexing.
|
30
|
+
* @param {Object.<string, Object>} index - A dictionary of model data, keyed by ID.
|
31
|
+
* @throws {NoIndexAvailableSearchIndexError} If the model has no searchable properties.
|
32
|
+
*/
|
15
33
|
constructor(model, index) {
|
16
34
|
this.#index = index;
|
17
35
|
this.#model = model;
|
@@ -21,9 +39,10 @@ export default class SearchIndex {
|
|
21
39
|
}
|
22
40
|
|
23
41
|
/**
|
42
|
+
* Performs a search query on the compiled Lunr index.
|
24
43
|
*
|
25
|
-
* @param {string} query
|
26
|
-
* @return {Array<SearchResult>}
|
44
|
+
* @param {string} query - The search string.
|
45
|
+
* @return {Array<SearchResult>} An array of search results with model instances and scores.
|
27
46
|
*/
|
28
47
|
search(query) {
|
29
48
|
return this.searchIndex
|
@@ -31,10 +50,21 @@ export default class SearchIndex {
|
|
31
50
|
.map(doc => new SearchResult(this.#model.fromData(this.#index[doc.ref]), doc.score));
|
32
51
|
}
|
33
52
|
|
53
|
+
/**
|
54
|
+
* Lazily compiles and returns the Lunr index instance.
|
55
|
+
*
|
56
|
+
* @return {lunr.Index} The compiled Lunr index.
|
57
|
+
*/
|
34
58
|
get searchIndex() {
|
35
59
|
return this.#compiledIndex ?? this.#compileIndex();
|
36
60
|
}
|
37
61
|
|
62
|
+
/**
|
63
|
+
* Compiles the Lunr index using the model's search properties.
|
64
|
+
*
|
65
|
+
* @return {lunr.Index} The compiled Lunr index.
|
66
|
+
* @private
|
67
|
+
*/
|
38
68
|
#compileIndex() {
|
39
69
|
const model = this.#model;
|
40
70
|
const index = this.#index;
|
@@ -54,9 +84,21 @@ export default class SearchIndex {
|
|
54
84
|
}
|
55
85
|
}
|
56
86
|
|
87
|
+
/**
|
88
|
+
* Base error class for search index-related exceptions.
|
89
|
+
*
|
90
|
+
* @class SearchIndexError
|
91
|
+
* @extends {Error}
|
92
|
+
*/
|
57
93
|
export class SearchIndexError extends Error {
|
58
94
|
}
|
59
95
|
|
96
|
+
/**
|
97
|
+
* Thrown when a model does not have any properties defined for indexing.
|
98
|
+
*
|
99
|
+
* @class NoIndexAvailableSearchIndexError
|
100
|
+
* @extends {SearchIndexError}
|
101
|
+
*/
|
60
102
|
export class NoIndexAvailableSearchIndexError extends SearchIndexError {
|
61
103
|
constructor(model) {
|
62
104
|
super(`The model ${model.name} has no search properties`);
|
@@ -53,6 +53,14 @@ class Type {
|
|
53
53
|
* @returns {Type} A subclass of the current type with `_required` set to `true`.
|
54
54
|
*/
|
55
55
|
static get required() {
|
56
|
+
/**
|
57
|
+
* A subclass of the current type with the `_required` flag set to `true`.
|
58
|
+
* Used to indicate that the property is required during validation or schema generation.
|
59
|
+
*
|
60
|
+
* @class
|
61
|
+
* @extends {Type}
|
62
|
+
* @private
|
63
|
+
*/
|
56
64
|
class Required extends this {
|
57
65
|
static _required = true;
|
58
66
|
}
|