@acodeninja/persist 3.0.0-next.26 → 3.0.0-next.28
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/README.md +4 -4
- package/docs/code-quirks.md +9 -9
- package/docs/defining-models.md +8 -8
- package/docs/model-properties.md +54 -23
- package/docs/models-as-properties.md +30 -30
- package/docs/search-queries.md +8 -8
- package/docs/structured-queries.md +8 -8
- package/package.json +1 -1
- package/src/Connection.js +344 -217
- package/src/Schema.js +3 -3
- package/src/data/Model.js +63 -9
- package/src/data/Property.js +2 -0
- package/src/data/properties/ArrayType.js +4 -2
- package/src/data/properties/BooleanType.js +2 -2
- package/src/data/properties/CustomType.js +4 -4
- package/src/data/properties/DateType.js +3 -3
- package/src/data/properties/NumberType.js +2 -2
- package/src/data/properties/SlugType.js +1 -1
- package/src/data/properties/StringType.js +2 -2
- package/src/data/properties/Type.js +5 -3
- package/src/engine/storage/StorageEngine.js +3 -1
package/src/Schema.js
CHANGED
@@ -14,7 +14,7 @@ class Schema {
|
|
14
14
|
* It uses AJV for the validation process and integrates with model types and their specific validation rules.
|
15
15
|
*
|
16
16
|
* @param {Object|Model} rawSchema - The raw schema or model definition to be compiled.
|
17
|
-
* @returns {CompiledSchema} - A
|
17
|
+
* @returns {CompiledSchema} - A compiled schema, ready to validate instances of the model.
|
18
18
|
*
|
19
19
|
* @example
|
20
20
|
* const schemaClass = Schema.compile(MyModelSchema);
|
@@ -39,7 +39,7 @@ class Schema {
|
|
39
39
|
const thisSchema = {};
|
40
40
|
|
41
41
|
if (Model.isModel(schemaSegment)) {
|
42
|
-
thisSchema.required = [
|
42
|
+
thisSchema.required = [];
|
43
43
|
thisSchema.type = 'object';
|
44
44
|
thisSchema.additionalProperties = false;
|
45
45
|
thisSchema.properties = {
|
@@ -62,7 +62,7 @@ class Schema {
|
|
62
62
|
thisSchema.properties[name] = {
|
63
63
|
type: 'object',
|
64
64
|
additionalProperties: false,
|
65
|
-
required: [
|
65
|
+
required: [],
|
66
66
|
properties: {
|
67
67
|
id: {
|
68
68
|
type: 'string',
|
package/src/data/Model.js
CHANGED
@@ -151,6 +151,8 @@ class Model {
|
|
151
151
|
* @static
|
152
152
|
*/
|
153
153
|
static get required() {
|
154
|
+
const ThisModel = this;
|
155
|
+
|
154
156
|
/**
|
155
157
|
* A subclass of the current model with the `_required` flag set to `true`.
|
156
158
|
* Used to indicate that the property is required during validation or schema generation.
|
@@ -159,11 +161,11 @@ class Model {
|
|
159
161
|
* @extends {Model}
|
160
162
|
* @private
|
161
163
|
*/
|
162
|
-
class Required extends
|
164
|
+
class Required extends ThisModel {
|
163
165
|
static _required = true;
|
164
166
|
}
|
165
167
|
|
166
|
-
Object.defineProperty(Required, 'name', {value:
|
168
|
+
Object.defineProperty(Required, 'name', {value: ThisModel.name});
|
167
169
|
|
168
170
|
return Required;
|
169
171
|
}
|
@@ -192,11 +194,17 @@ class Model {
|
|
192
194
|
*/
|
193
195
|
static indexedPropertiesResolved() {
|
194
196
|
return [
|
195
|
-
...Object.entries(this)
|
196
|
-
.filter(([
|
197
|
+
...Object.entries(this.properties)
|
198
|
+
.filter(([name, type]) => !['indexedProperties', 'searchProperties'].includes(name) && !type._type && (this.isModel(type) || (typeof type === 'function' && this.isModel(type()))))
|
197
199
|
.map(([name, _type]) => `${name}.id`),
|
198
|
-
...Object.entries(this)
|
199
|
-
.filter(([_name, type]) =>
|
200
|
+
...Object.entries(this.properties)
|
201
|
+
.filter(([_name, type]) => {
|
202
|
+
return !Model.isModel(type) && (
|
203
|
+
(type._type === 'array' && this.isModel(type._items))
|
204
|
+
||
|
205
|
+
(!type._type && typeof type === 'function' && this.isModel(type()._items))
|
206
|
+
);
|
207
|
+
})
|
200
208
|
.map(([name, _type]) => `${name}.[*].id`),
|
201
209
|
...this.indexedProperties(),
|
202
210
|
'id',
|
@@ -285,16 +293,62 @@ class Model {
|
|
285
293
|
* @static
|
286
294
|
*
|
287
295
|
* @example
|
288
|
-
* export default class TestModel {
|
296
|
+
* export default class TestModel extends Model {
|
289
297
|
* static {
|
290
|
-
*
|
291
|
-
*
|
298
|
+
* TestModel.withName('TestModel');
|
299
|
+
* TestModel.string = Persist.Property.String;
|
292
300
|
* }
|
293
301
|
* }
|
294
302
|
*/
|
295
303
|
static withName(name) {
|
296
304
|
Object.defineProperty(this, 'name', {value: name});
|
297
305
|
}
|
306
|
+
|
307
|
+
/**
|
308
|
+
* Discover model properties all the way up the prototype chain.
|
309
|
+
*
|
310
|
+
* @return {Model}
|
311
|
+
*/
|
312
|
+
static get properties() {
|
313
|
+
const modelClass = this;
|
314
|
+
const props = {};
|
315
|
+
const chain = [];
|
316
|
+
|
317
|
+
let current = modelClass;
|
318
|
+
while (current !== Function.prototype) {
|
319
|
+
chain.push(current);
|
320
|
+
current = Object.getPrototypeOf(current);
|
321
|
+
}
|
322
|
+
|
323
|
+
for (const item of chain) {
|
324
|
+
for (const property of Object.getOwnPropertyNames(item)) {
|
325
|
+
if (
|
326
|
+
[
|
327
|
+
'_required',
|
328
|
+
'fromData',
|
329
|
+
'indexedProperties',
|
330
|
+
'indexedPropertiesResolved',
|
331
|
+
'isDryModel',
|
332
|
+
'isModel',
|
333
|
+
'length',
|
334
|
+
'name',
|
335
|
+
'properties',
|
336
|
+
'prototype',
|
337
|
+
'required',
|
338
|
+
'searchProperties',
|
339
|
+
'toString',
|
340
|
+
'withName',
|
341
|
+
].includes(property)
|
342
|
+
) continue;
|
343
|
+
|
344
|
+
if (Object.keys(props).includes(property)) continue;
|
345
|
+
|
346
|
+
props[property] = item[property];
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
return Object.assign(modelClass, props);
|
351
|
+
}
|
298
352
|
}
|
299
353
|
|
300
354
|
export default Model;
|
package/src/data/Property.js
CHANGED
@@ -5,6 +5,7 @@ import DateType from './properties/DateType.js';
|
|
5
5
|
import NumberType from './properties/NumberType.js';
|
6
6
|
import SlugType from './properties/SlugType.js';
|
7
7
|
import StringType from './properties/StringType.js';
|
8
|
+
import Type from './properties/Type.js';
|
8
9
|
|
9
10
|
const Property = {
|
10
11
|
Array: ArrayType,
|
@@ -14,6 +15,7 @@ const Property = {
|
|
14
15
|
Number: NumberType,
|
15
16
|
Slug: SlugType,
|
16
17
|
String: StringType,
|
18
|
+
Type,
|
17
19
|
};
|
18
20
|
|
19
21
|
export default Property;
|
@@ -51,12 +51,14 @@ class ArrayType {
|
|
51
51
|
* const requiredArrayOfStrings = ArrayType.of(StringType).required;
|
52
52
|
*/
|
53
53
|
static get required() {
|
54
|
+
const ThisType = this;
|
55
|
+
|
54
56
|
/**
|
55
57
|
* @class RequiredArrayOf
|
56
58
|
* @extends ArrayOf
|
57
59
|
* Represents a required array of a specific type.
|
58
60
|
*/
|
59
|
-
class Required extends
|
61
|
+
class Required extends ThisType {
|
60
62
|
/** @type {boolean} Indicates that the array is required */
|
61
63
|
static _required = true;
|
62
64
|
|
@@ -70,7 +72,7 @@ class ArrayType {
|
|
70
72
|
}
|
71
73
|
}
|
72
74
|
|
73
|
-
Object.defineProperty(Required, 'name', {value: `Required${
|
75
|
+
Object.defineProperty(Required, 'name', {value: `Required${ThisType.name}`});
|
74
76
|
|
75
77
|
return Required;
|
76
78
|
}
|
@@ -15,9 +15,9 @@ class BooleanType extends Type {
|
|
15
15
|
* @static
|
16
16
|
* @property {string} _type - The type identifier for BooleanType, set to `'boolean'`.
|
17
17
|
*/
|
18
|
-
|
18
|
+
BooleanType._type = 'boolean';
|
19
19
|
|
20
|
-
Object.defineProperty(
|
20
|
+
Object.defineProperty(BooleanType, 'name', {value: 'Boolean'});
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|
@@ -37,12 +37,12 @@ class CustomType {
|
|
37
37
|
class Custom extends Type {
|
38
38
|
static {
|
39
39
|
/** @type {string} The data type, which is 'object' */
|
40
|
-
|
40
|
+
Custom._type = 'object';
|
41
41
|
|
42
42
|
/** @type {Object} The JSON schema that defines the structure and validation rules */
|
43
|
-
|
43
|
+
Custom._schema = schema;
|
44
44
|
|
45
|
-
Object.defineProperty(
|
45
|
+
Object.defineProperty(Custom, 'name', {value: 'Custom'});
|
46
46
|
}
|
47
47
|
}
|
48
48
|
|
@@ -50,7 +50,7 @@ class CustomType {
|
|
50
50
|
}
|
51
51
|
|
52
52
|
static {
|
53
|
-
Object.defineProperty(
|
53
|
+
Object.defineProperty(CustomType, 'name', {value: 'Custom'});
|
54
54
|
}
|
55
55
|
}
|
56
56
|
|
@@ -15,15 +15,15 @@ class DateType extends Type {
|
|
15
15
|
* @static
|
16
16
|
* @property {string} _type - The type identifier for DateType, set to `'string'`.
|
17
17
|
*/
|
18
|
-
|
18
|
+
DateType._type = 'string';
|
19
19
|
|
20
20
|
/**
|
21
21
|
* @static
|
22
22
|
* @property {string} _format - The format for DateType, set to `'iso-date-time'`.
|
23
23
|
*/
|
24
|
-
|
24
|
+
DateType._format = 'iso-date-time';
|
25
25
|
|
26
|
-
Object.defineProperty(
|
26
|
+
Object.defineProperty(DateType, 'name', {value: 'Date'});
|
27
27
|
}
|
28
28
|
|
29
29
|
/**
|
@@ -15,9 +15,9 @@ class NumberType extends Type {
|
|
15
15
|
* @static
|
16
16
|
* @property {string} _type - The type identifier for NumberType, set to `'number'`.
|
17
17
|
*/
|
18
|
-
|
18
|
+
NumberType._type = 'number';
|
19
19
|
|
20
|
-
Object.defineProperty(
|
20
|
+
Object.defineProperty(NumberType, 'name', {value: 'Number'});
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|
@@ -15,9 +15,9 @@ class StringType extends Type {
|
|
15
15
|
* @static
|
16
16
|
* @property {string} _type - The type identifier for the string type.
|
17
17
|
*/
|
18
|
-
|
18
|
+
StringType._type = 'string';
|
19
19
|
|
20
|
-
Object.defineProperty(
|
20
|
+
Object.defineProperty(StringType, 'name', {value: 'String'});
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|
@@ -53,6 +53,8 @@ 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
|
+
const ThisType = this;
|
57
|
+
|
56
58
|
/**
|
57
59
|
* A subclass of the current type with the `_required` flag set to `true`.
|
58
60
|
* Used to indicate that the property is required during validation or schema generation.
|
@@ -61,18 +63,18 @@ class Type {
|
|
61
63
|
* @extends {Type}
|
62
64
|
* @private
|
63
65
|
*/
|
64
|
-
class Required extends
|
66
|
+
class Required extends ThisType {
|
65
67
|
static _required = true;
|
66
68
|
}
|
67
69
|
|
68
70
|
// Define the class name as "Required<OriginalTypeName>"
|
69
|
-
Object.defineProperty(Required, 'name', {value: `Required${
|
71
|
+
Object.defineProperty(Required, 'name', {value: `Required${ThisType.name}`});
|
70
72
|
|
71
73
|
return Required;
|
72
74
|
}
|
73
75
|
|
74
76
|
static {
|
75
|
-
Object.defineProperty(
|
77
|
+
Object.defineProperty(Type, 'name', {value: 'Type'});
|
76
78
|
}
|
77
79
|
}
|
78
80
|
|
@@ -132,7 +132,9 @@ export class ModelNotFoundStorageEngineError extends StorageEngineError {
|
|
132
132
|
export class DeleteHasUnintendedConsequencesStorageEngineError extends StorageEngineError {
|
133
133
|
/**
|
134
134
|
* @param {string} modelId
|
135
|
-
* @param {
|
135
|
+
* @param {Object} consequences
|
136
|
+
* @param {Array<String>?} consequences.willDelete
|
137
|
+
* @param {Array<String>?} consequences.willUpdate
|
136
138
|
*/
|
137
139
|
constructor(modelId, consequences) {
|
138
140
|
super(`Deleting ${modelId} has unintended consequences`);
|