mythix-orm 1.8.2 → 1.10.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/lib/connection/connection-base.d.ts +1 -0
- package/lib/connection/connection-base.js +2303 -6
- package/lib/connection/query-generator-base.js +14 -11
- package/lib/model.d.ts +17 -0
- package/lib/model.js +22 -3
- package/lib/query-engine/field-scope.js +2 -2
- package/lib/query-engine/model-scope.js +15 -15
- package/lib/query-engine/query-engine-base.js +111 -38
- package/lib/query-engine/query-engine.d.ts +29 -24
- package/lib/query-engine/query-engine.js +33 -22
- package/lib/types/concrete/date-type.js +2 -2
- package/lib/types/concrete/datetime-type.js +2 -2
- package/lib/types/concrete/foreign-key-type.js +2 -2
- package/lib/types/type.js +1 -1
- package/lib/types/virtual/model-type.js +151 -0
- package/lib/types/virtual/models-type.js +148 -1
- package/lib/types/virtual/relational-type-base.js +2 -12
- package/lib/utils/model-utils.js +47 -2
- package/lib/utils/query-utils.js +2 -2
- package/package.json +1 -1
|
@@ -125,7 +125,7 @@ const TYPE_OPERATIONS = {
|
|
|
125
125
|
'get': function({ field, type }, userQuery, options, ...args) {
|
|
126
126
|
const doGet = async function*() {
|
|
127
127
|
let query = await type.prepareQuery({ connection: null, self: this, field, options }, [ userQuery ].concat(args));
|
|
128
|
-
let results = query.
|
|
128
|
+
let results = query.cursor(options);
|
|
129
129
|
let primaryModelRelationalArray = [];
|
|
130
130
|
|
|
131
131
|
this[field.fieldName] = primaryModelRelationalArray;
|
|
@@ -239,6 +239,124 @@ const TYPE_OPERATIONS = {
|
|
|
239
239
|
},
|
|
240
240
|
};
|
|
241
241
|
|
|
242
|
+
/// A "virtual" field `type` that defines a one to many,
|
|
243
|
+
/// or many to many relationship with other model(s).
|
|
244
|
+
///
|
|
245
|
+
/// Many to many relationships interact with "sets", which in
|
|
246
|
+
/// Mythix ORM is the term used to define an "array of models",
|
|
247
|
+
/// in our N to many relationship.
|
|
248
|
+
///
|
|
249
|
+
/// Relationships (also known as "associations") in Mythix ORM
|
|
250
|
+
/// are defined by virtual fields that return a "relationship query"
|
|
251
|
+
/// from a method known as a "query generator" that is defined along
|
|
252
|
+
/// with the type definition.
|
|
253
|
+
/// This "relationship query" itself defines the model relationship, no matter
|
|
254
|
+
/// how complicated. Other ORMs require that you define relationships
|
|
255
|
+
/// via their "through table", matching foreign key, type, etc...
|
|
256
|
+
/// In Mythix ORM, everything is simply defined as a query for the
|
|
257
|
+
/// relationship.
|
|
258
|
+
///
|
|
259
|
+
/// This field `type` can be used to define a virtual field
|
|
260
|
+
/// that defines a relationship to other models. When your
|
|
261
|
+
/// model is instantiated into an instance, then this type will
|
|
262
|
+
/// inject the following methods into your model instance to
|
|
263
|
+
/// assist you in interacting with the relationship set. The "injected methods"
|
|
264
|
+
/// listed below are "prefixes". The full names of these methods are
|
|
265
|
+
/// always postfixed with the actual name of your field that defines
|
|
266
|
+
/// the relationship. For example, if you had a `roles` field
|
|
267
|
+
/// that defined this type of relationship, then the methods injected
|
|
268
|
+
/// would be `model.getRoles`, `model.destroyRoles`, `model.pluckRoles`, etc...
|
|
269
|
+
/// | Injected Method Prefix | Full Method Name | Signature | Description |
|
|
270
|
+
/// | ---------------------- | ---------------- | ----------- | --------- |
|
|
271
|
+
/// | `addTo` | `addTo${fieldName}` | `addTo(models: Array<Model \| object>, options?: object): Promise<Array<Model>>` | Create the models defined by the relationship, and add them to the set. Returns the newly created models. |
|
|
272
|
+
/// | `count` | `count${fieldName}` | `count(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<number>` | Count the number of models in the set. |
|
|
273
|
+
/// | `destroy` | `destroy${fieldName}` | `destroy(options?: object, ...args: Array<any>): Promise<number>` | Destroy the models defined by the relationship set. Returns the number of models destroyed. Note that this not only destroys the target models, but also any through-table relationships involved. |
|
|
274
|
+
/// | `get` | `get${fieldName}` | `get(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<Array<Model>>` | Get the models specified by the relationship set. Return the entire model set. |
|
|
275
|
+
/// | `has` | `has${fieldName}` | `has(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<boolean>` | Check if the relationship set contains any models. |
|
|
276
|
+
/// | `pluck` | `pluck${fieldName}` | `pluck(userQuery?: QueryEngine, fields: Field \| string \| Array<Field \| string>, options?: object, ...args: Array<any>): Promise<Array<any>>` | Pluck specific fields from the related models (set). |
|
|
277
|
+
/// | `queryFor` | `queryFor${fieldName}` | `queryFor(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<QueryEngine>` | Get the raw relationship query defined by this field type (returned by the "query generator"). |
|
|
278
|
+
/// | `removeFrom` | `removeFrom${fieldName}` | `removeFrom(models: Array<Model>, options?: object): Promise<number>` | Remove the specified models from the relationship set without destroying the target models (destroy only the through-table linking models). Returns the number of models remaining in the set after the operation. |
|
|
279
|
+
/// | `set` | `set${fieldName}` | `set(models: Array<Model \| object>, options?: object): Promise<Array<Model>>` | Overwrite the model set with the models specified, destroying models outside the specified set of models. Returns the new models. |
|
|
280
|
+
///
|
|
281
|
+
/// The `...args` provided for each injected method are pass-through args for the user, that allow
|
|
282
|
+
/// the user to pass any arguments they desire to the "relational query generator" method defined
|
|
283
|
+
/// by the field (the `...userArgs` as seen in the example below). One minor exception to this is
|
|
284
|
+
/// any method that has a `userQuery` argument. The `userQuery` argument will always also be passed as the
|
|
285
|
+
/// first argument to the `...userArgs` of the "query generator" method. The `userQuery` argument
|
|
286
|
+
/// is to allow the user to define an extra query to merge into the primary/root query of the relationship.
|
|
287
|
+
/// For example, in the example provided below, we could call `await user.getRoles(Role.where.name.EQ('admin'))`
|
|
288
|
+
/// to only get the related roles where each role also has a `name` attribute with the value `'admin'`.
|
|
289
|
+
///
|
|
290
|
+
/// This type also injects duplicates of each of these relational methods with an underscore prefix, i.e.
|
|
291
|
+
/// `_getRoles`. These are known as "root methods". They are provided so the user can overload the
|
|
292
|
+
/// default implementations. For example, you could define a `getRoles` method on your model class,
|
|
293
|
+
/// and from that call the "super" method simply by calling `this._getRoles`. This is required because
|
|
294
|
+
/// with method injection `super` doesn't actually exist, because the method was injected, instead of being
|
|
295
|
+
/// defined on the model class's prototype.
|
|
296
|
+
///
|
|
297
|
+
/// Example:
|
|
298
|
+
/// class User extends Model {
|
|
299
|
+
/// static fields = {
|
|
300
|
+
/// ...,
|
|
301
|
+
/// // Add a virtual relationship field, which creates a
|
|
302
|
+
/// // one to many relationship with the Role model.
|
|
303
|
+
/// //
|
|
304
|
+
/// // Notice how a "type" can always be used directly as
|
|
305
|
+
/// // the field definition, instead of defining an object
|
|
306
|
+
/// // with a "type" property.
|
|
307
|
+
/// roles: Types.Models(
|
|
308
|
+
/// // Specify the target/root model.
|
|
309
|
+
/// 'Role',
|
|
310
|
+
/// // Define our "query generator" method that
|
|
311
|
+
/// // will return our "relationship query".
|
|
312
|
+
/// (context, connectionModels, ...userArgs) => {
|
|
313
|
+
/// // Get the model we need from the connection,
|
|
314
|
+
/// // which is conveniently passed to us as the
|
|
315
|
+
/// // `connectionModels` argument here.
|
|
316
|
+
/// let { Role } = connectionModels;
|
|
317
|
+
///
|
|
318
|
+
/// // Get the "self", which is the model instance
|
|
319
|
+
/// // calling this method
|
|
320
|
+
/// // (i.e. with `model.getRoles()`, "self" would be "model")
|
|
321
|
+
/// let { self } = context;
|
|
322
|
+
///
|
|
323
|
+
/// // Now return a relationship query
|
|
324
|
+
/// return Role.where
|
|
325
|
+
/// .userID
|
|
326
|
+
/// .EQ(self.id)
|
|
327
|
+
/// .AND
|
|
328
|
+
/// .MERGE(userArgs[0]); // Apply the `userQuery` (will do nothing if nullish)
|
|
329
|
+
/// },
|
|
330
|
+
/// ),
|
|
331
|
+
/// };
|
|
332
|
+
/// }
|
|
333
|
+
///
|
|
334
|
+
/// // ... later on
|
|
335
|
+
/// // get the "roles" for the first user in the database
|
|
336
|
+
/// let user = await User.first();
|
|
337
|
+
/// // Call relationship method injected by the `Types.Models` type.
|
|
338
|
+
/// let allUserRoles = await user.getRoles();
|
|
339
|
+
///
|
|
340
|
+
/// Note:
|
|
341
|
+
/// See the [Associations](./Associations) article for a more in-depth discussion
|
|
342
|
+
/// of Mythix ORM model relationship/associations.
|
|
343
|
+
///
|
|
344
|
+
/// Note:
|
|
345
|
+
/// The "query generator" method defined with the type can be an asynchronous method.
|
|
346
|
+
///
|
|
347
|
+
/// Note:
|
|
348
|
+
/// Relational methods **will not** be injected into the model instance if a method of
|
|
349
|
+
/// the same name already exists on the model instance. For example, if you define a
|
|
350
|
+
/// `getRoles` method on your model class, then the default `get` relational method
|
|
351
|
+
/// (`getRoles` in our example) won't be injected. However, the "root method" `_getRoles`
|
|
352
|
+
/// will still be injected, allowing you to call that as the `super` method instead.
|
|
353
|
+
///
|
|
354
|
+
/// Note:
|
|
355
|
+
/// This field type will not be "exposed" to models. This means that your model will not
|
|
356
|
+
/// have an attribute with the name provided by the field defining this type. The only
|
|
357
|
+
/// thing that will exist on your model will be the relational methods injected by this type.
|
|
358
|
+
/// Said another way, and using the examples provided, there will be no "roles" attribute
|
|
359
|
+
/// on your `User` model.
|
|
242
360
|
class ModelsType extends RelationalTypeBase {
|
|
243
361
|
static exposeToModel() {
|
|
244
362
|
return false;
|
|
@@ -248,6 +366,23 @@ class ModelsType extends RelationalTypeBase {
|
|
|
248
366
|
return true;
|
|
249
367
|
}
|
|
250
368
|
|
|
369
|
+
/// Internal method used to generate injected method names.
|
|
370
|
+
///
|
|
371
|
+
/// This method likely should never be called by the user.
|
|
372
|
+
/// It is used to generate the injected method names.
|
|
373
|
+
///
|
|
374
|
+
/// Return: string
|
|
375
|
+
/// The method name to inject into the model instance.
|
|
376
|
+
///
|
|
377
|
+
/// Arguments:
|
|
378
|
+
/// field: <see>Field</see>
|
|
379
|
+
/// The parent field defining this relationship.
|
|
380
|
+
/// operation: string
|
|
381
|
+
/// The operation being injected into the model instance, which is one of
|
|
382
|
+
/// the prefixes listed above, i.e. `addTo`, `get`, `destroy`, etc...
|
|
383
|
+
/// rootMethod: boolean
|
|
384
|
+
/// If `true`, then return a generated "root method" name, instead of a "user method".
|
|
385
|
+
/// The only difference is that the "root method" name is prefixed with an underscore `_`.
|
|
251
386
|
fieldNameToOperationName(field, operation, rootMethod) {
|
|
252
387
|
let fieldName = field.pluralName;
|
|
253
388
|
if (!fieldName)
|
|
@@ -259,6 +394,18 @@ class ModelsType extends RelationalTypeBase {
|
|
|
259
394
|
return `${operation}${fieldName}`;
|
|
260
395
|
}
|
|
261
396
|
|
|
397
|
+
/// Inject type methods into the model instance.
|
|
398
|
+
///
|
|
399
|
+
/// The <see>Type.initialize</see> method is always called
|
|
400
|
+
/// for each model that is instantiated. For this type, this
|
|
401
|
+
/// is used to inject the relational methods into the model
|
|
402
|
+
/// instance.
|
|
403
|
+
///
|
|
404
|
+
/// Arguments:
|
|
405
|
+
/// connection: <see>Connection</see>
|
|
406
|
+
/// The connection for the model being instantiated.
|
|
407
|
+
/// self: <see>Model</see>
|
|
408
|
+
/// The model instance the type is being initialized for.
|
|
262
409
|
initialize(connection, self) {
|
|
263
410
|
return super.initialize(connection, self, TYPE_OPERATIONS);
|
|
264
411
|
}
|
|
@@ -17,16 +17,6 @@ class RelationalTypeBase extends Type {
|
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Model types work by specifying a "target"
|
|
21
|
-
// and a "value provider" (source).
|
|
22
|
-
// These are fully qualified names, meaning
|
|
23
|
-
// they also point to the model as well as the field.
|
|
24
|
-
// If no model is specified, then it always defaults to
|
|
25
|
-
// "this" model. If no field is specified, then it always
|
|
26
|
-
// defaults to "this PK" of the model.
|
|
27
|
-
// Mythix ORM will recursively walk all models and fields
|
|
28
|
-
// defined until it has the full relationships between
|
|
29
|
-
// all fields.
|
|
30
20
|
constructor(_targetModelName, queryFactory, _options) {
|
|
31
21
|
let options = _options || {};
|
|
32
22
|
|
|
@@ -180,7 +170,7 @@ class RelationalTypeBase extends Type {
|
|
|
180
170
|
let PrimaryModel = (context.self && context.self.getModel());
|
|
181
171
|
let primaryModelName = PrimaryModel.getModelName();
|
|
182
172
|
let query = await this.prepareQuery(Object.assign({ connection }, context), args);
|
|
183
|
-
let queryParts = query.
|
|
173
|
+
let queryParts = query.getOperationStack();
|
|
184
174
|
let queryContext = (queryParts[queryParts.length - 1] || {});
|
|
185
175
|
let TargetModel = queryContext.rootModel;
|
|
186
176
|
let TargetField = queryContext.rootField;
|
|
@@ -196,7 +186,7 @@ class RelationalTypeBase extends Type {
|
|
|
196
186
|
let rightQueryContext = null;
|
|
197
187
|
|
|
198
188
|
if (QueryEngine.isQuery(conditionValue)) {
|
|
199
|
-
rightQueryContext = conditionValue.
|
|
189
|
+
rightQueryContext = conditionValue.getOperationContext();
|
|
200
190
|
if (rightQueryContext.condition)
|
|
201
191
|
continue;
|
|
202
192
|
} else {
|
package/lib/utils/model-utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
///! DocScope: ModelUtils
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const Nife = require('nife');
|
|
@@ -7,10 +9,53 @@ function isUUID(value) {
|
|
|
7
9
|
return UUID.validate(value);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
function sanitizeFieldString(
|
|
11
|
-
|
|
12
|
+
function sanitizeFieldString(_str) {
|
|
13
|
+
let str = (typeof _str === 'symbol') ? (_str.toString()) : (_str + '');
|
|
14
|
+
return str.replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
/// Parse a fully qualified field name.
|
|
18
|
+
///
|
|
19
|
+
/// A fully qualified field name is a field name that
|
|
20
|
+
/// is prefixed by its parent model. For example,
|
|
21
|
+
/// `'User:email'` is a fully qualified field name,
|
|
22
|
+
/// whereas just `'email'` is not.
|
|
23
|
+
///
|
|
24
|
+
/// This method will actually parse both formats however.
|
|
25
|
+
/// It will parse fully qualified field names, and short-hand
|
|
26
|
+
/// field names (just the field name). If just a field name
|
|
27
|
+
/// is provided with no model name prefix, then the resulting
|
|
28
|
+
/// `modelName` property of the returned object will be `undefined`.
|
|
29
|
+
///
|
|
30
|
+
/// Field names themselves can be deeply nested. For example,
|
|
31
|
+
/// `'User:metadata.ipAddress'`. Field nesting was designed into
|
|
32
|
+
/// the Mythix ORM framework, and is partially supported in some
|
|
33
|
+
/// areas of the framework. However, field nesting likely won't work
|
|
34
|
+
/// in many places without further support being added. This feature
|
|
35
|
+
/// was left in-place for now, because it will likely be expanded upon
|
|
36
|
+
/// in the future.
|
|
37
|
+
/// Because of this "field nesting", instead of just one field name being
|
|
38
|
+
/// returned, an array of field names is *always* returned. Generally,
|
|
39
|
+
/// to get the field name you are looking for you would just get the first
|
|
40
|
+
/// index (`fieldNames[0]`) on the returned object.
|
|
41
|
+
///
|
|
42
|
+
/// This method can and will also parse just a model name, so long as the provided
|
|
43
|
+
/// name starts with an upper-case letter. If no colon is found (meaning no model
|
|
44
|
+
/// prefix is present), then the provided value will be assumed to be a model name
|
|
45
|
+
/// only if it starts with an upper-case letter. Otherwise, it is treated as a field
|
|
46
|
+
/// name, and returned in the `fieldNames` array.
|
|
47
|
+
///
|
|
48
|
+
/// Arguments:
|
|
49
|
+
/// fieldName: string
|
|
50
|
+
/// A model name, field name, or fully qualified field name to parse.
|
|
51
|
+
///
|
|
52
|
+
/// Return: object
|
|
53
|
+
/// An object with the shape `{ modelName: string | undefined; fieldNames: Array<string> }`.
|
|
54
|
+
/// The `modelName` property will contain the name of the model for the field, if it is known,
|
|
55
|
+
/// which will only be the case for a fully qualified name (i.e. `'User:email'`), or a
|
|
56
|
+
/// model name alone (i.e. `'User'`). `fieldNames` will always be an array of field names
|
|
57
|
+
/// parsed, which will generally only be one. It can be empty if no field name was provided,
|
|
58
|
+
/// for example if only a model name was provided (i.e. `'User'`).
|
|
14
59
|
function parseQualifiedName(str) {
|
|
15
60
|
let fieldNames = [];
|
|
16
61
|
let modelName;
|
package/lib/utils/query-utils.js
CHANGED
|
@@ -62,11 +62,11 @@ const FILTER_OPERATORS = {
|
|
|
62
62
|
},
|
|
63
63
|
// Like
|
|
64
64
|
'*': (Model, fieldName, query, value) => {
|
|
65
|
-
return query.LIKE(value);
|
|
65
|
+
return query.AND[fieldName].LIKE(value);
|
|
66
66
|
},
|
|
67
67
|
// NOT Like
|
|
68
68
|
'!*': (Model, fieldName, query, value) => {
|
|
69
|
-
return query.NOT_LIKE(value);
|
|
69
|
+
return query.AND[fieldName].NOT_LIKE(value);
|
|
70
70
|
},
|
|
71
71
|
};
|
|
72
72
|
|