mythix-orm 1.8.3 → 1.11.0

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/field.js CHANGED
@@ -118,8 +118,8 @@
118
118
  /// If `true`, then this field will be indexed in the database.
119
119
  /// If an array is provided, then it must be an array containing
120
120
  /// other field names to combine with this field to create a combined
121
- /// index. For example: `index: [ true, 'firstName', 'lastName', [ 'firstName', 'lastName' ] ]`
122
- /// would create four indexes: `true` means index this field, `firstName` would create
121
+ /// index. For example: `index: [ true, 'firstName', 'lastName', [ 'firstName', 'lastName' ] ]` would create four indexes:
122
+ /// `true` means index this field, `firstName` would create
123
123
  /// the combination index `this_field_first_name`, `lastName` would create the
124
124
  /// combination index `this_field_last_name`, and `[ 'firstName', 'lastName ]` would
125
125
  /// create the combination index `this_field_first_name_last_name`.
@@ -234,6 +234,13 @@ class Field {
234
234
  return false;
235
235
  }
236
236
 
237
+ isField(value) {
238
+ if (arguments.length === 0)
239
+ return true;
240
+
241
+ return this.constructor.isField(value);
242
+ }
243
+
237
244
  /// Construct a Field.
238
245
  ///
239
246
  /// This method will create a new instance of <see>Field</see>.
package/lib/model.d.ts CHANGED
@@ -42,6 +42,21 @@ export declare interface IterateFieldsContext {
42
42
 
43
43
  export declare type IterateFieldsCallback = (context: IterateFieldsContext) => any;
44
44
 
45
+ export declare interface FinalizeQueryContext {
46
+ type: 'create' | 'read' | 'update' | 'delete';
47
+ query: QueryEngine;
48
+ queryDepth: number;
49
+ connection: ConnectionBase;
50
+ Model: Model;
51
+ modelName: string;
52
+ operationIndex: number;
53
+ operation: GenericObject;
54
+ operations: Array<GenericObject>;
55
+ parent: GenericObject | null;
56
+ contextKey: string | null;
57
+ options: GenericObject | null;
58
+ }
59
+
45
60
  export declare class Model {
46
61
  declare public static fields: LooseFields | undefined;
47
62
  declare public static _sortedFields: Array<Field> | null;
@@ -83,6 +98,8 @@ export declare class Model {
83
98
  public static defaultScope(query: QueryEngine): QueryEngine;
84
99
  public defaultScope(query: QueryEngine): QueryEngine;
85
100
 
101
+ public static finalizeQuery(context: FinalizeQueryContext): Promise<QueryEngine>;
102
+
86
103
  public static getQueryEngine(connection?: ConnectionBase, options?: GenericObject): QueryEngine;
87
104
  public getQueryEngine(connection?: ConnectionBase, options?: GenericObject): QueryEngine;
88
105
 
@@ -153,8 +170,6 @@ export declare class Model {
153
170
  public static getConcreteFieldCount(): number;
154
171
  public getConcreteFieldCount(): number;
155
172
 
156
- public static defaultOrder(options?: GenericObject): Array<string>;
157
-
158
173
  public static getWhereWithConnection(options?: { connection: ConnectionBase }): QueryEngine;
159
174
  public getWhereWithConnection(options?: { connection: ConnectionBase }): QueryEngine;
160
175
 
package/lib/model.js CHANGED
@@ -104,8 +104,8 @@ function bindStaticWhereToModelClass(ModelClass) {
104
104
  };
105
105
 
106
106
  Object.defineProperties(ModelClass, {
107
- 'where': whereProp,
108
- '$': whereProp,
107
+ 'where': whereProp,
108
+ '$': whereProp,
109
109
  });
110
110
  }
111
111
 
@@ -557,7 +557,7 @@ class Model {
557
557
  get: () => {
558
558
  return ModelClass.getQueryEngine(ModelClass._getConnection() || connection);
559
559
  },
560
- set: () => {},
560
+ set: () => {},
561
561
  };
562
562
 
563
563
  Object.defineProperties(ModelClass, {
@@ -567,8 +567,8 @@ class Model {
567
567
  configurable: true,
568
568
  value: connection,
569
569
  },
570
- 'where': whereProp,
571
- '$': whereProp,
570
+ 'where': whereProp,
571
+ '$': whereProp,
572
572
  });
573
573
 
574
574
  return ModelClass;
@@ -637,14 +637,16 @@ class Model {
637
637
 
638
638
  /// One can apply a "default scope" to any model simply
639
639
  /// by providing this static method on the model. By
640
- /// default it will simply return the <see name="derp">QueryEngine</see>
640
+ /// default it will simply return the <see>QueryEngine</see>
641
641
  /// instance (a query) that it was provided.
642
642
  ///
643
643
  /// The way this works is simple. The caller provides the
644
644
  /// query as the first and only argument. The user, by
645
645
  /// overloading this method can then easily modify this
646
646
  /// provided query, changing the "default scope" whenever
647
- /// the model is used for queries. For example, let's say
647
+ /// the model is used for queries. This can be used to set
648
+ /// a default order for a model, or to set default conditions
649
+ /// to apply to any query for the model. For example, let's say
648
650
  /// you have a User model, and by default, you only want
649
651
  /// to query against Users that have their `active` column
650
652
  /// set to `true`. In order to do this, you could easily
@@ -656,7 +658,7 @@ class Model {
656
658
  /// static defaultScope(baseQuery) {
657
659
  /// // `baseQuery` is equal to `User.where`
658
660
  /// // without a default scope.
659
- /// return baseQuery.active.EQ(true);
661
+ /// return baseQuery.active.EQ(true).ORDER.ASC('User:createdAt');
660
662
  /// }
661
663
  /// }
662
664
  ///
@@ -673,6 +675,11 @@ class Model {
673
675
  return this.constructor.defaultScope(queryEngine);
674
676
  }
675
677
 
678
+ // eslint-disable-next-line no-unused-vars
679
+ static finalizeQuery({ type, query, queryDepth, operationIndex, operation, operations, parent, contextKey, options }) {
680
+ return query;
681
+ }
682
+
676
683
  /// This method is called any time a `Model.where` or
677
684
  /// `Model.$` property is accessed. It returns a query,
678
685
  /// based off this model class (as the root model).
@@ -1602,37 +1609,6 @@ class Model {
1602
1609
  return this.constructor.getConcreteFieldCount();
1603
1610
  }
1604
1611
 
1605
- /// Specify the default SELECT "ORDER" for the model.
1606
- /// This method will be called if no "ORDER" was specified
1607
- /// for any given query. This method should return an
1608
- /// Array of fully qualified field names. The `options`
1609
- /// argument is the current options for the specific
1610
- /// query being operated on.
1611
- ///
1612
- /// Note:
1613
- /// Internally, Mythix ORM will call `this.connection.defaultOrder(options)`,
1614
- /// which--if not overloaded--will simply call this method from the
1615
- /// model itself.
1616
- ///
1617
- /// Note:
1618
- /// A "fully qualified field name" in Mythix ORM means
1619
- /// a full field definition, including the model name.
1620
- /// An example of a fully qualified field name might be
1621
- /// `"User:id"`. The model name is separated from the field
1622
- /// name by a colon `:`. A short hand, *not fully qualified
1623
- /// field name* example would be simply `"id"`. Since this
1624
- /// contains no model prefix, this is "short hand", and is
1625
- /// **not** a fully qualified field name.
1626
- ///
1627
- /// Return: Array<string> | null
1628
- /// An array of fully qualified field names to specify the ORDER.
1629
- /// Prefix a field name with `+` to specify ASCending order, i.e.
1630
- /// `[ "+User:id" ]`. Prefix the field name with `-` to specify
1631
- /// DESCending order, i.e. `[ "-User:id" ]`.
1632
- // eslint-disable-next-line no-unused-vars
1633
- static defaultOrder(options) {
1634
- }
1635
-
1636
1612
  /// This method is called anytime a `Model.where` or `Model.$`
1637
1613
  /// attribute is accessed. It will provide the instantiated
1638
1614
  /// <see>QueryEngine</see> with the connection specified (if any).
@@ -1700,14 +1676,18 @@ class Model {
1700
1676
  /// Return the number of models stored in the database for this model type.
1701
1677
  ///
1702
1678
  /// Arguments:
1679
+ /// field?: string | Field
1680
+ /// A fully qualified field name, or a `Field` instance. If supplied, this is the
1681
+ /// column in the table that will be counted (i.e. `COUNT(column)`). If not supplied,
1682
+ /// then a `COUNT(*)` type operation will be carried out.
1703
1683
  /// options?: object
1704
1684
  /// An "options" object to pass off to the underlying <see>Connection</see> methods.
1705
1685
  /// If a `connection` key is specified in this object, then that will be used
1706
1686
  /// as the connection for the operation. This can be important if for example
1707
1687
  /// you are calling this from a `transaction`, in which case you most certainly
1708
1688
  /// would want to provide the `connection` for the transaction.
1709
- static count(options) {
1710
- return this.getWhereWithConnection(options).count(null, options);
1689
+ static count(field, options) {
1690
+ return this.getWhereWithConnection(options).count(field, options);
1711
1691
  }
1712
1692
 
1713
1693
  /// Fetch all models from the database for this model type.
@@ -1890,7 +1870,7 @@ class Model {
1890
1870
  get: () => {
1891
1871
  return this.constructor.where(this._getConnection());
1892
1872
  },
1893
- set: () => {},
1873
+ set: () => {},
1894
1874
  };
1895
1875
 
1896
1876
  Object.defineProperties(this, {
@@ -1960,10 +1940,10 @@ class Model {
1960
1940
  get: () => {
1961
1941
  return this._getDirtyFields();
1962
1942
  },
1963
- set: () => {},
1943
+ set: () => {},
1964
1944
  },
1965
- 'where': whereProp,
1966
- '$': whereProp,
1945
+ 'where': whereProp,
1946
+ '$': whereProp,
1967
1947
  });
1968
1948
 
1969
1949
  this._constructor(data);
@@ -2032,12 +2012,12 @@ class Model {
2032
2012
  _constructField(fieldName, field) {
2033
2013
  Object.defineProperties(this, {
2034
2014
  [fieldName]: {
2035
- enumerable: false,
2015
+ enumerable: true,
2036
2016
  configurable: true,
2037
2017
  get: () => {
2038
2018
  return this._getFieldValue(fieldName, field);
2039
2019
  },
2040
- set: (value) => {
2020
+ set: (value) => {
2041
2021
  this._setFieldValue(fieldName, field, value);
2042
2022
  },
2043
2023
  },
@@ -2082,9 +2062,12 @@ class Model {
2082
2062
  _initializeModelData(_data) {
2083
2063
  let dirtyFieldData = this._dirtyFieldData;
2084
2064
  let data = _data || {};
2065
+ let fieldNames = new Set();
2085
2066
 
2086
2067
  // First initialize field values from data
2087
2068
  this.iterateFields(({ field, fieldName }) => {
2069
+ fieldNames.add(fieldName);
2070
+
2088
2071
  if (!field.type.exposeToModel())
2089
2072
  return;
2090
2073
 
@@ -2104,6 +2087,15 @@ class Model {
2104
2087
  let fieldValue = (data) ? data[fieldName] : undefined;
2105
2088
  this._initializeFieldData(fieldName, field, fieldValue, data);
2106
2089
  });
2090
+
2091
+ let keys = Object.keys(data);
2092
+ for (let i = 0, il = keys.length; i < il; i++) {
2093
+ let key = keys[i];
2094
+ if (fieldNames.has(key))
2095
+ continue;
2096
+
2097
+ this[key] = data[key];
2098
+ }
2107
2099
  }
2108
2100
 
2109
2101
  /// This casts a field value to its type,
@@ -2129,9 +2121,9 @@ class Model {
2129
2121
  return value;
2130
2122
 
2131
2123
  return type.castToType({
2132
- connection: this.getConnection(),
2133
- Model: this.getModel(),
2134
- self: this,
2124
+ connection: this.getConnection(),
2125
+ Model: this.getModel(),
2126
+ self: this,
2135
2127
  field,
2136
2128
  value,
2137
2129
  });
@@ -2189,10 +2181,10 @@ class Model {
2189
2181
 
2190
2182
  if (shouldRunDefaultValueOnInitialize()) {
2191
2183
  defaultValue = defaultValue({
2192
- model: this,
2193
- self: this,
2194
- connection: this.getConnection(),
2195
- _initial: true,
2184
+ model: this,
2185
+ self: this,
2186
+ connection: this.getConnection(),
2187
+ _initial: true,
2196
2188
  field,
2197
2189
  fieldName,
2198
2190
  fieldValue,
@@ -2218,8 +2210,8 @@ class Model {
2218
2210
  }
2219
2211
 
2220
2212
  field.type.onSetFieldValue({
2221
- self: this,
2222
- value: initialValue,
2213
+ self: this,
2214
+ value: initialValue,
2223
2215
  field,
2224
2216
  fieldName,
2225
2217
  });
@@ -2322,8 +2314,8 @@ class Model {
2322
2314
 
2323
2315
  // Does the type itself report that we are dirty?
2324
2316
  let value = field.type.isDirty({
2325
- self: this,
2326
- value: this.getDataValue(fieldName),
2317
+ self: this,
2318
+ value: this.getDataValue(fieldName),
2327
2319
  field,
2328
2320
  fieldName,
2329
2321
  connection,
@@ -2422,10 +2414,10 @@ class Model {
2422
2414
 
2423
2415
  if (typeof field.get === 'function') {
2424
2416
  return field.get.call(this, {
2425
- model: this,
2426
- self: this,
2427
- set: this.setDataValue.bind(this, fieldName),
2428
- get: this.getDataValue.bind(this, fieldName),
2417
+ model: this,
2418
+ self: this,
2419
+ set: this.setDataValue.bind(this, fieldName),
2420
+ get: this.getDataValue.bind(this, fieldName),
2429
2421
  value,
2430
2422
  field,
2431
2423
  fieldName,
@@ -2468,10 +2460,10 @@ class Model {
2468
2460
  _setFieldValue(fieldName, field, value) {
2469
2461
  if (typeof field.set === 'function') {
2470
2462
  field.set.call(this, {
2471
- model: this,
2472
- self: this,
2473
- set: this.setDataValue.bind(this, fieldName),
2474
- get: this.getDataValue.bind(this, fieldName),
2463
+ model: this,
2464
+ self: this,
2465
+ set: this.setDataValue.bind(this, fieldName),
2466
+ get: this.getDataValue.bind(this, fieldName),
2475
2467
  value,
2476
2468
  field,
2477
2469
  fieldName,
@@ -2670,8 +2662,8 @@ class Model {
2670
2662
  let newValue = this._castFieldValue(field, value);
2671
2663
 
2672
2664
  field.type.onSetFieldValue({
2673
- self: this,
2674
- value: newValue,
2665
+ self: this,
2666
+ value: newValue,
2675
2667
  field,
2676
2668
  fieldName,
2677
2669
  });
@@ -2810,9 +2802,9 @@ class Model {
2810
2802
  return false;
2811
2803
 
2812
2804
  return pkField.type.isValidValue(pkValue, {
2813
- connection: this.getConnection(),
2814
- Model: this.getModel(),
2815
- self: this,
2805
+ connection: this.getConnection(),
2806
+ Model: this.getModel(),
2807
+ self: this,
2816
2808
  });
2817
2809
  }
2818
2810
 
@@ -9,24 +9,57 @@ function addOperatorToQuery(name, inverseName, value, extraOptions) {
9
9
  throw new Error(`QueryEngine::addOperatorToQuery: ${name}(${value}) makes no sense...`);
10
10
 
11
11
  let conditionalParams = {
12
- condition: true,
13
- operator: name,
14
- inverseOperator: inverseName,
15
- value: this._fetchOperatorValue(value),
16
- hasCondition: true,
12
+ condition: true,
13
+ operator: name,
14
+ queryProp: name,
15
+ queryExtraArgs: (extraOptions) ? [ extraOptions ] : [],
16
+ inverseOperator: inverseName,
17
+ value: this._fetchOperatorValue(value),
18
+ hasCondition: true,
17
19
  };
18
20
 
19
21
  if (extraOptions)
20
22
  conditionalParams = Object.assign(conditionalParams, extraOptions);
21
23
 
22
- this._addToQuery(conditionalParams);
24
+ this._pushOperationOntoStack(conditionalParams);
23
25
 
24
26
  return this._fetchScope('model');
25
27
  }
26
28
 
29
+ function wrapAnyAll(func) {
30
+ const checkQueryProjection = (_query, subType) => {
31
+ let query = _query;
32
+ let queryContext = (QueryEngineBase.isQuery(query)) ? query.getOperationContext() : null;
33
+ if (!queryContext || !queryContext.hasCondition)
34
+ throw new Error(`QueryEngine::FieldScope::${subType}: Provided value must be a query with conditions.`);
35
+
36
+ if (!queryContext.projection) {
37
+ let Model = queryContext.Model;
38
+ let pkField = Model.getPrimaryKeyField();
39
+
40
+ if (pkField)
41
+ query = query.clone().PROJECT(pkField);
42
+ else
43
+ throw new Error(`QueryEngine::FieldScope::${subType}: Provided query must have only a single field projected.`);
44
+ }
45
+
46
+ return query;
47
+ };
48
+
49
+ func.ANY = (_query) => {
50
+ return func.call(this, checkQueryProjection(_query, 'ANY'), { subType: 'ANY' });
51
+ };
52
+
53
+ func.ALL = (_query) => {
54
+ return func.call(this, checkQueryProjection(_query, 'ANY'), { subType: 'ALL' });
55
+ };
56
+
57
+ return func;
58
+ }
59
+
27
60
  class FieldScope extends QueryEngineBase {
28
61
  NOT = ProxyClass.autoCall(function() {
29
- this._addToQuery({ logical: true, operator: 'NOT', not: !this.currentContext.not });
62
+ this._pushOperationOntoStack({ logical: true, operator: 'NOT', queryProp: 'NOT', not: !this.currentContext.not });
30
63
  return this[ProxyClass.PROXY];
31
64
  });
32
65
 
@@ -65,29 +98,29 @@ class FieldScope extends QueryEngineBase {
65
98
  return value;
66
99
  }
67
100
 
68
- EQ(value) {
69
- return addOperatorToQuery.call(this, 'EQ', 'NEQ', value);
70
- }
101
+ EQ = wrapAnyAll.call(this, (value, options) => {
102
+ return addOperatorToQuery.call(this, 'EQ', 'NEQ', value, options);
103
+ });
71
104
 
72
- NEQ(value) {
73
- return addOperatorToQuery.call(this, 'NEQ', 'EQ', value);
74
- }
105
+ NEQ = wrapAnyAll.call(this, (value, options) => {
106
+ return addOperatorToQuery.call(this, 'NEQ', 'EQ', value, options);
107
+ });
75
108
 
76
- GT(value) {
77
- return addOperatorToQuery.call(this, 'GT', 'LTE', value);
78
- }
109
+ GT = wrapAnyAll.call(this, (value, options) => {
110
+ return addOperatorToQuery.call(this, 'GT', 'LTE', value, options);
111
+ });
79
112
 
80
- GTE(value) {
81
- return addOperatorToQuery.call(this, 'GTE', 'LT', value);
82
- }
113
+ GTE = wrapAnyAll.call(this, (value, options) => {
114
+ return addOperatorToQuery.call(this, 'GTE', 'LT', value, options);
115
+ });
83
116
 
84
- LT(value) {
85
- return addOperatorToQuery.call(this, 'LT', 'GTE', value);
86
- }
117
+ LT = wrapAnyAll.call(this, (value, options) => {
118
+ return addOperatorToQuery.call(this, 'LT', 'GTE', value, options);
119
+ });
87
120
 
88
- LTE(value) {
89
- return addOperatorToQuery.call(this, 'LTE', 'GT', value);
90
- }
121
+ LTE = wrapAnyAll.call(this, (value, options) => {
122
+ return addOperatorToQuery.call(this, 'LTE', 'GT', value, options);
123
+ });
91
124
 
92
125
  LIKE(value, options) {
93
126
  let caseSensitive = ((options && options.caseSensitive) === true);