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.
@@ -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.all(Object.assign({}, options, { stream: true }));
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._getRawQuery();
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._getRawQueryContext();
189
+ rightQueryContext = conditionValue.getOperationContext();
200
190
  if (rightQueryContext.condition)
201
191
  continue;
202
192
  } else {
@@ -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(str) {
11
- return ('' + str).replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
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;
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix-orm",
3
- "version": "1.8.2",
3
+ "version": "1.10.2",
4
4
  "description": "ORM for Mythix framework",
5
5
  "main": "lib/index",
6
6
  "type": "commonjs",