@acodeninja/persist 3.0.0-next.2 → 3.0.0-next.21

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.
Files changed (41) hide show
  1. package/README.md +60 -4
  2. package/docs/code-quirks.md +6 -6
  3. package/docs/defining-models.md +61 -0
  4. package/docs/http.openapi.yml +138 -0
  5. package/docs/{model-property-types.md → model-properties.md} +37 -35
  6. package/docs/models-as-properties.md +40 -40
  7. package/docs/search-queries.md +3 -5
  8. package/docs/storage-engines.md +19 -35
  9. package/docs/structured-queries.md +56 -45
  10. package/docs/transactions.md +6 -7
  11. package/exports/storage/http.js +3 -0
  12. package/exports/storage/s3.js +3 -0
  13. package/jest.config.cjs +8 -12
  14. package/package.json +2 -2
  15. package/src/Connection.js +631 -0
  16. package/src/Persist.js +29 -30
  17. package/src/Schema.js +175 -0
  18. package/src/{Query.js → data/FindIndex.js} +40 -24
  19. package/src/{type → data}/Model.js +41 -26
  20. package/src/data/Property.js +19 -0
  21. package/src/data/SearchIndex.js +106 -0
  22. package/src/{type/complex → data/properties}/ArrayType.js +1 -1
  23. package/src/{type/simple → data/properties}/BooleanType.js +1 -1
  24. package/src/{type/complex → data/properties}/CustomType.js +1 -1
  25. package/src/{type/simple → data/properties}/DateType.js +1 -1
  26. package/src/{type/simple → data/properties}/NumberType.js +1 -1
  27. package/src/{type/resolved → data/properties}/ResolvedType.js +3 -2
  28. package/src/{type/simple → data/properties}/StringType.js +1 -1
  29. package/src/{type → data/properties}/Type.js +8 -0
  30. package/src/engine/storage/HTTPStorageEngine.js +149 -253
  31. package/src/engine/storage/S3StorageEngine.js +108 -195
  32. package/src/engine/storage/StorageEngine.js +114 -550
  33. package/exports/engine/storage/file.js +0 -3
  34. package/exports/engine/storage/http.js +0 -3
  35. package/exports/engine/storage/s3.js +0 -3
  36. package/src/SchemaCompiler.js +0 -192
  37. package/src/Transactions.js +0 -145
  38. package/src/engine/StorageEngine.js +0 -250
  39. package/src/engine/storage/FileStorageEngine.js +0 -213
  40. package/src/type/index.js +0 -32
  41. /package/src/{type/resolved → data/properties}/SlugType.js +0 -0
package/src/Persist.js CHANGED
@@ -1,45 +1,44 @@
1
- import Type from './type/index.js';
2
- import enableTransactions from './Transactions.js';
1
+ import Connection from './Connection.js';
2
+ import Model from './data/Model.js';
3
+ import Property from './data/Property.js';
4
+ import {ValidationError} from './Schema.js';
3
5
 
4
6
  /**
5
7
  * @class Persist
6
8
  */
7
9
  class Persist {
8
- static _engine = {};
9
- /**
10
- * @memberof Persist
11
- * @type {Type}
12
- * @static
13
- */
14
- static Type = Type;
10
+ static Property = Property;
11
+ static Model = Model;
12
+ static Connection = Connection;
13
+
14
+ static Errors = {
15
+ ValidationError,
16
+ };
17
+
18
+ static #connections = new Map();
15
19
 
16
20
  /**
17
- * @function getEngine
18
- * @memberof Persist
19
- * @static
20
- * @param {string} group - Name of the group containing the engine
21
- * @param {Engine} engine - The engine class you wish to retrieve
22
- * @return {Engine|null}
21
+ * Register a new connection.
22
+ * @param {string} name
23
+ * @param {StorageEngine} storage
24
+ * @param {Array<Model.constructor>} models
25
+ * @return {Connection}
23
26
  */
24
- static getEngine(group, engine) {
25
- return this._engine[group]?.[engine.name] ?? null;
27
+ static registerConnection(name, storage, models) {
28
+ const connection = new Connection(storage, models);
29
+
30
+ Persist.#connections.set(name, connection);
31
+
32
+ return connection;
26
33
  }
27
34
 
28
35
  /**
29
- * @function addEngine
30
- * @memberof Persist
31
- * @static
32
- * @param {string} group - Name of the group containing the engine
33
- * @param {Engine} engine - The engine class you wish to configure and add to the group
34
- * @param {object?} configuration - The configuration to use with the engine
36
+ * Get a persist connection by its name.
37
+ * @param {string} name
38
+ * @return {Connection|undefined}
35
39
  */
36
- static addEngine(group, engine, configuration) {
37
- if (!this._engine[group]) this._engine[group] = {};
38
-
39
- this._engine[group][engine.name] =
40
- configuration.transactions ?
41
- enableTransactions(engine.configure(configuration)) :
42
- engine.configure(configuration);
40
+ static getConnection(name) {
41
+ return Persist.#connections.get(name);
43
42
  }
44
43
  }
45
44
 
package/src/Schema.js ADDED
@@ -0,0 +1,175 @@
1
+ import Model from './data/Model.js';
2
+ import ajv from 'ajv';
3
+ import ajvErrors from 'ajv-errors';
4
+ import ajvFormats from 'ajv-formats';
5
+
6
+ /**
7
+ * A class responsible for compiling raw schema definitions into a format that can be validated using the AJV (Another JSON Validator) library.
8
+ */
9
+ class Schema {
10
+ /**
11
+ * Compiles a raw schema into a validation-ready schema, and returns a class that extends `CompiledSchema`.
12
+ *
13
+ * This method converts a given schema into a JSON schema-like format, setting up properties, types, formats, and validation rules.
14
+ * It uses AJV for the validation process and integrates with model types and their specific validation rules.
15
+ *
16
+ * @param {Object|Model} rawSchema - The raw schema or model definition to be compiled.
17
+ * @returns {CompiledSchema} - A class that extends `CompiledSchema`, with the compiled schema and validator attached.
18
+ *
19
+ * @example
20
+ * const schemaClass = Schema.compile(MyModelSchema);
21
+ * const isValid = schemaClass.validate(data); // Throws ValidationError if data is invalid.
22
+ */
23
+ static compile(rawSchema) {
24
+ const validation = new ajv({allErrors: true});
25
+
26
+ ajvErrors(validation);
27
+ ajvFormats(validation);
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
+ */
38
+ function BuildSchema(schemaSegment) {
39
+ const thisSchema = {};
40
+
41
+ if (Model.isModel(schemaSegment)) {
42
+ thisSchema.required = ['id'];
43
+ thisSchema.type = 'object';
44
+ thisSchema.additionalProperties = false;
45
+ thisSchema.properties = {
46
+ id: {
47
+ type: 'string',
48
+ pattern: `^${schemaSegment.toString()}/[A-Z0-9]+$`,
49
+ },
50
+ };
51
+
52
+ for (const [name, type] of Object.entries(schemaSegment)) {
53
+ if (['indexedProperties', 'searchProperties'].includes(name)) continue;
54
+
55
+ const property = type instanceof Function && !type.prototype ? type() : type;
56
+
57
+ if (property?._required || property?._items?._type?._required) {
58
+ thisSchema.required.push(name);
59
+ }
60
+
61
+ if (Model.isModel(property)) {
62
+ thisSchema.properties[name] = {
63
+ type: 'object',
64
+ additionalProperties: false,
65
+ required: ['id'],
66
+ properties: {
67
+ id: {
68
+ type: 'string',
69
+ pattern: `^${property.toString()}/[A-Z0-9]+$`,
70
+ },
71
+ },
72
+ };
73
+ continue;
74
+ }
75
+
76
+ thisSchema.properties[name] = BuildSchema(property);
77
+ }
78
+
79
+ return thisSchema;
80
+ }
81
+
82
+ if (schemaSegment._schema) {
83
+ return schemaSegment._schema;
84
+ }
85
+
86
+ thisSchema.type = schemaSegment?._type;
87
+
88
+ if (schemaSegment?._format) {
89
+ thisSchema.format = schemaSegment._format;
90
+ }
91
+
92
+ if (schemaSegment?._items) {
93
+ thisSchema.items = {};
94
+ thisSchema.items.type = schemaSegment._items._type;
95
+ if (schemaSegment._items._format)
96
+ thisSchema.items.format = schemaSegment._items._format;
97
+ }
98
+
99
+ return thisSchema;
100
+ }
101
+
102
+ const builtSchema = BuildSchema(rawSchema);
103
+
104
+ return new CompiledSchema(validation.compile(builtSchema));
105
+ }
106
+ }
107
+
108
+
109
+ /**
110
+ * Represents a compiled schema used for validating data models.
111
+ * This class provides a mechanism to validate data using a precompiled schema and a validator function.
112
+ */
113
+ export class CompiledSchema {
114
+ /**
115
+ * The validator function used to validate data against the schema.
116
+ * @type {?Function}
117
+ * @private
118
+ */
119
+ #validator = null;
120
+
121
+ constructor(validator) {
122
+ this.#validator = validator;
123
+ }
124
+
125
+ /**
126
+ * Validates the given data against the compiled schema.
127
+ *
128
+ * If the data is an instance of a model, it will be converted to a plain object via `toData()` before validation.
129
+ *
130
+ * @param {Object|Model} data - The data or model instance to be validated.
131
+ * @returns {boolean} - Returns `true` if the data is valid according to the schema.
132
+ * @throws {ValidationError} - Throws a `ValidationError` if the data is invalid.
133
+ */
134
+ validate(data) {
135
+ let inputData = structuredClone(data);
136
+
137
+ if (Model.isModel(data)) {
138
+ inputData = data.toData();
139
+ }
140
+
141
+ const valid = this.#validator?.(inputData);
142
+
143
+ if (valid) return valid;
144
+
145
+ throw new ValidationError(inputData, this.#validator.errors);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Represents a validation error that occurs when a model or data fails validation.
151
+ * Extends the built-in JavaScript `Error` class.
152
+ */
153
+ export class ValidationError extends Error {
154
+ /**
155
+ * Creates an instance of `ValidationError`.
156
+ *
157
+ * @param {Object} data - The data that failed validation.
158
+ * @param {Array<Object>} errors - A list of validation errors, each typically containing details about what failed.
159
+ */
160
+ constructor(data, errors) {
161
+ super('Validation failed');
162
+ /**
163
+ * An array of validation errors, containing details about each failed validation.
164
+ * @type {Array<Object>}
165
+ */
166
+ this.errors = errors;
167
+ /**
168
+ * The data that caused the validation error.
169
+ * @type {Object}
170
+ */
171
+ this.data = data;
172
+ }
173
+ }
174
+
175
+ export default Schema;
@@ -26,37 +26,47 @@
26
26
  * }
27
27
  * });
28
28
  */
29
- class Query {
29
+ class FindIndex {
30
30
  /**
31
31
  * The query object that defines the search criteria.
32
32
  * @type {Object}
33
+ * @private
33
34
  */
34
- query;
35
+ #index;
36
+
37
+ /**
38
+ * @type {Model.constructor} - The model class.
39
+ * @private
40
+ */
41
+ #modelConstructor;
35
42
 
36
43
  /**
37
44
  * Constructs a new `Query` instance with the provided query object.
38
45
  *
39
- * @param {Object} query - The structured query object defining the search criteria.
46
+ * @param {Model.constructor} modelConstructor - The model class.
47
+ * @param {Record<string, Model>} index - The index dataset to search through.
40
48
  */
41
- constructor(query) {
42
- this.query = query;
49
+ constructor(modelConstructor, index) {
50
+ this.#index = index;
51
+ this.#modelConstructor = modelConstructor;
43
52
  }
44
53
 
45
54
  /**
46
55
  * Executes the query against a model's index and returns the matching results.
47
56
  *
48
- * @param {Model.constructor} model - The model class that contains the `fromData` method for constructing models from data.
49
- * @param {Object<string, Model>} index - The index dataset to search through.
57
+ * @param {Object} query The structured query.
50
58
  * @returns {Array<Model>} The models that match the query.
51
59
  */
52
- execute(model, index) {
53
- return Object.values(index)
60
+ query(query) {
61
+ const splitQuery = this.#splitQuery(query);
62
+
63
+ return Object.values(this.#index)
54
64
  .filter(m =>
55
- this._splitQuery(this.query)
56
- .map(query => Boolean(this._matchesQuery(m, query)))
65
+ splitQuery
66
+ .map(q => Boolean(this.#matchesQuery(m, q)))
57
67
  .every(c => c),
58
68
  )
59
- .map(m => model.fromData(m));
69
+ .map(m => this.#modelConstructor.fromData(m));
60
70
  }
61
71
 
62
72
  /**
@@ -72,26 +82,32 @@ class Query {
72
82
  * @param {Object} inputQuery - The query to match against.
73
83
  * @returns {boolean} True if the subject matches the query, otherwise false.
74
84
  */
75
- _matchesQuery(subject, inputQuery) {
76
- if (['string', 'number', 'boolean'].includes(typeof inputQuery)) return subject === inputQuery;
85
+ #matchesQuery(subject, inputQuery) {
86
+ if (['string', 'number', 'boolean'].includes(typeof inputQuery))
87
+ return subject === inputQuery;
77
88
 
78
- if (inputQuery?.$is !== undefined && subject === inputQuery.$is) return true;
89
+ if (inputQuery?.$is !== undefined && subject === inputQuery.$is)
90
+ return true;
79
91
 
80
92
  if (inputQuery?.$contains !== undefined) {
81
- if (subject.includes?.(inputQuery.$contains)) return true;
93
+ if (subject.includes?.(inputQuery.$contains))
94
+ return true;
82
95
 
83
- for (const value of subject) {
84
- if (this._matchesQuery(value, inputQuery.$contains)) return true;
85
- }
96
+ if (typeof subject[Symbol.iterator] === 'function')
97
+ for (const value of subject) {
98
+ if (this.#matchesQuery(value, inputQuery.$contains))
99
+ return true;
100
+ }
86
101
  }
87
102
 
88
103
  for (const key of Object.keys(inputQuery)) {
89
104
  if (!['$is', '$contains'].includes(key))
90
- if (this._matchesQuery(subject[key], inputQuery[key])) return true;
105
+ if (this.#matchesQuery(subject[key], inputQuery[key]))
106
+ return true;
91
107
  }
92
108
 
93
109
  return false;
94
- };
110
+ }
95
111
 
96
112
  /**
97
113
  * Recursively splits an object into an array of objects,
@@ -105,14 +121,14 @@ class Query {
105
121
  * @returns {Array<Object>} An array of objects, where each object contains a single key-value pair
106
122
  * from the original query or its nested objects.
107
123
  */
108
- _splitQuery(query) {
124
+ #splitQuery(query) {
109
125
  return Object.entries(query)
110
126
  .flatMap(([key, value]) =>
111
127
  typeof value === 'object' && value !== null && !Array.isArray(value)
112
- ? this._splitQuery(value).map(nestedObj => ({[key]: nestedObj}))
128
+ ? this.#splitQuery(value).map(nestedObj => ({[key]: nestedObj}))
113
129
  : {[key]: value},
114
130
  );
115
131
  }
116
132
  }
117
133
 
118
- export default Query;
134
+ export default FindIndex;
@@ -1,5 +1,5 @@
1
- import SchemaCompiler from '../SchemaCompiler.js';
2
- import StringType from './simple/StringType.js';
1
+ import Schema from '../Schema.js';
2
+ import StringType from './properties/StringType.js';
3
3
  import _ from 'lodash';
4
4
  import {monotonicFactory} from 'ulid';
5
5
 
@@ -51,10 +51,9 @@ class Model {
51
51
  /**
52
52
  * Serializes the model instance into an object, optionally retaining complex types.
53
53
  *
54
- * @param {boolean} [simple=true] - Determines whether to format the output using only JSON serialisable types.
55
54
  * @returns {Object} - A serialized representation of the model.
56
55
  */
57
- toData(simple = true) {
56
+ toData() {
58
57
  const model = {...this};
59
58
 
60
59
  for (const [name, property] of Object.entries(this.constructor)) {
@@ -70,21 +69,6 @@ class Model {
70
69
  }
71
70
  return value;
72
71
  }),
73
- (key, value) => {
74
- if (!simple) {
75
- if (this.constructor[key]) {
76
- if (this.constructor[key].name.endsWith('Date')) {
77
- return new Date(value);
78
- }
79
-
80
- if (this.constructor[key].name.endsWith('ArrayOf(Date)')) {
81
- return value.map(d => new Date(d));
82
- }
83
- }
84
- }
85
-
86
- return value;
87
- },
88
72
  );
89
73
  }
90
74
 
@@ -95,16 +79,16 @@ class Model {
95
79
  * @throws {ValidationError} - Throws this error if validation fails.
96
80
  */
97
81
  validate() {
98
- return SchemaCompiler.compile(this.constructor).validate(this);
82
+ return Schema.compile(this.constructor).validate(this);
99
83
  }
100
84
 
101
85
  /**
102
86
  * Extracts data from the model based on the indexed properties defined in the class.
103
- *
87
+ * Includes the ID of any linked models.
104
88
  * @returns {Object} - A representation of the model's indexed data.
105
89
  */
106
90
  toIndexData() {
107
- return this._extractData(this.constructor.indexedProperties());
91
+ return this._extractData(this.constructor.indexedPropertiesResolved());
108
92
  }
109
93
 
110
94
  /**
@@ -167,6 +151,14 @@ class Model {
167
151
  * @static
168
152
  */
169
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
+ */
170
162
  class Required extends this {
171
163
  static _required = true;
172
164
  }
@@ -179,6 +171,10 @@ class Model {
179
171
  /**
180
172
  * Returns a list of properties that are indexed.
181
173
  *
174
+ * - To link to properties of a model use `<name>`
175
+ * - To link to properties of linked models use `<model>.<name>`
176
+ * - To link to properties of many linked models use `<model>.[*].<name>`
177
+ *
182
178
  * @returns {Array<string>} - The indexed properties.
183
179
  * @abstract
184
180
  * @static
@@ -187,6 +183,25 @@ class Model {
187
183
  return [];
188
184
  }
189
185
 
186
+ /**
187
+ * Returns a list of properties that are indexed including links to other models.
188
+ *
189
+ * @returns {Array<string>} - The indexed properties.
190
+ * @abstract
191
+ * @static
192
+ */
193
+ static indexedPropertiesResolved() {
194
+ return [
195
+ ...Object.entries(this)
196
+ .filter(([_name, type]) => !type._type && (this.isModel(type) || this.isModel(type())))
197
+ .map(([name, _type]) => `${name}.id`),
198
+ ...Object.entries(this)
199
+ .filter(([_name, type]) => !type._type && !this.isModel(type) && !type._items?._type && (this.isModel(type._items) || this.isModel(type()._items)))
200
+ .map(([name, _type]) => `${name}.[*].id`),
201
+ ...this.indexedProperties(),
202
+ ];
203
+ }
204
+
190
205
  /**
191
206
  * Returns a list of properties used for search.
192
207
  *
@@ -211,12 +226,12 @@ class Model {
211
226
  for (const [name, value] of Object.entries(data)) {
212
227
  if (this[name]?._resolved) continue;
213
228
 
214
- if (this[name].name.endsWith('Date')) {
229
+ if (this[name]?.name.endsWith('Date')) {
215
230
  model[name] = new Date(value);
216
231
  continue;
217
232
  }
218
233
 
219
- if (this[name].name.endsWith('ArrayOf(Date)')) {
234
+ if (this[name]?.name.endsWith('ArrayOf(Date)')) {
220
235
  model[name] = data[name].map(d => new Date(d));
221
236
  continue;
222
237
  }
@@ -271,12 +286,12 @@ class Model {
271
286
  * @example
272
287
  * export default class TestModel {
273
288
  * static {
289
+ * this.withName('TestModel');
274
290
  * this.string = Persist.Type.String;
275
- * Object.defineProperty(this, 'name', {value: 'TestModel'});
276
291
  * }
277
292
  * }
278
293
  */
279
- static setMinifiedName(name) {
294
+ static withName(name) {
280
295
  Object.defineProperty(this, 'name', {value: name});
281
296
  }
282
297
  }
@@ -0,0 +1,19 @@
1
+ import ArrayType from './properties/ArrayType.js';
2
+ import BooleanType from './properties/BooleanType.js';
3
+ import CustomType from './properties/CustomType.js';
4
+ import DateType from './properties/DateType.js';
5
+ import NumberType from './properties/NumberType.js';
6
+ import SlugType from './properties/SlugType.js';
7
+ import StringType from './properties/StringType.js';
8
+
9
+ const Property = {
10
+ Array: ArrayType,
11
+ Boolean: BooleanType,
12
+ Custom: CustomType,
13
+ Date: DateType,
14
+ Number: NumberType,
15
+ Slug: SlugType,
16
+ String: StringType,
17
+ };
18
+
19
+ export default Property;
@@ -0,0 +1,106 @@
1
+ import lunr from 'lunr';
2
+
3
+ /**
4
+ * Represents a single search result with the associated model instance and its relevance score.
5
+ *
6
+ * @class SearchResult
7
+ */
8
+ export class SearchResult {
9
+ constructor(model, score) {
10
+ this.model = model;
11
+ this.score = score;
12
+ }
13
+ }
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
+ */
21
+ export default class SearchIndex {
22
+ #index;
23
+ #model;
24
+ #compiledIndex;
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
+ */
33
+ constructor(model, index) {
34
+ this.#index = index;
35
+ this.#model = model;
36
+ if (model.searchProperties().length === 0) {
37
+ throw new NoIndexAvailableSearchIndexError(this.#model);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Performs a search query on the compiled Lunr index.
43
+ *
44
+ * @param {string} query - The search string.
45
+ * @return {Array<SearchResult>} An array of search results with model instances and scores.
46
+ */
47
+ search(query) {
48
+ return this.searchIndex
49
+ .search(query)
50
+ .map(doc => new SearchResult(this.#model.fromData(this.#index[doc.ref]), doc.score));
51
+ }
52
+
53
+ /**
54
+ * Lazily compiles and returns the Lunr index instance.
55
+ *
56
+ * @return {lunr.Index} The compiled Lunr index.
57
+ */
58
+ get searchIndex() {
59
+ return this.#compiledIndex ?? this.#compileIndex();
60
+ }
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
+ */
68
+ #compileIndex() {
69
+ const model = this.#model;
70
+ 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);
81
+ });
82
+
83
+ return this.#compiledIndex;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Base error class for search index-related exceptions.
89
+ *
90
+ * @class SearchIndexError
91
+ * @extends {Error}
92
+ */
93
+ export class SearchIndexError extends Error {
94
+ }
95
+
96
+ /**
97
+ * Thrown when a model does not have any properties defined for indexing.
98
+ *
99
+ * @class NoIndexAvailableSearchIndexError
100
+ * @extends {SearchIndexError}
101
+ */
102
+ export class NoIndexAvailableSearchIndexError extends SearchIndexError {
103
+ constructor(model) {
104
+ super(`The model ${model.name} has no search properties`);
105
+ }
106
+ }
@@ -1,4 +1,4 @@
1
- import Type from '../Type.js';
1
+ import Type from './Type.js';
2
2
 
3
3
  /**
4
4
  * Represents an array type definition, allowing the specification of an array of a certain type.
@@ -1,4 +1,4 @@
1
- import Type from '../Type.js';
1
+ import Type from './Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a boolean type.
@@ -1,4 +1,4 @@
1
- import Type from '../Type.js';
1
+ import Type from './Type.js';
2
2
  import ajv from 'ajv';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import Type from '../Type.js';
1
+ import Type from './Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a date type with ISO date-time format.
@@ -1,4 +1,4 @@
1
- import Type from '../Type.js';
1
+ import Type from './Type.js';
2
2
 
3
3
  /**
4
4
  * Class representing a number type.