mythix-orm 1.0.1
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/.eslintrc.js +94 -0
- package/.vscode/launch.json +34 -0
- package/.vscode/settings.json +10 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/lib/connection/connection-base.js +877 -0
- package/lib/connection/index.js +11 -0
- package/lib/connection/literals/average-literal.js +14 -0
- package/lib/connection/literals/count-literal.js +18 -0
- package/lib/connection/literals/distinct-literal.js +14 -0
- package/lib/connection/literals/index.js +23 -0
- package/lib/connection/literals/literal-base.js +62 -0
- package/lib/connection/literals/literal-field-base.js +45 -0
- package/lib/connection/literals/literal.js +11 -0
- package/lib/connection/literals/max-literal.js +14 -0
- package/lib/connection/literals/min-literal.js +14 -0
- package/lib/connection/literals/sum-literal.js +14 -0
- package/lib/connection/query-generator-base.js +864 -0
- package/lib/errors/base-error.js +14 -0
- package/lib/errors/connection/access-denied-error.js +13 -0
- package/lib/errors/connection/connection-acquire-timeout-error.js +13 -0
- package/lib/errors/connection/connection-refused-error.js +13 -0
- package/lib/errors/connection/connection-timed-out-error.js +13 -0
- package/lib/errors/connection/host-not-found-error.js +13 -0
- package/lib/errors/connection/host-not-reachable-error.js +13 -0
- package/lib/errors/connection/index.js +19 -0
- package/lib/errors/connection/invalid-connection-error.js +13 -0
- package/lib/errors/connection-base-error.js +13 -0
- package/lib/errors/database/exclusion-constraint-error.js +13 -0
- package/lib/errors/database/foreign-key-constraint-error.js +13 -0
- package/lib/errors/database/index.js +13 -0
- package/lib/errors/database/timeout-error.js +13 -0
- package/lib/errors/database/unknown-constraint-error.js +13 -0
- package/lib/errors/database-base-error.js +17 -0
- package/lib/errors/index.js +44 -0
- package/lib/field.js +18 -0
- package/lib/index.js +43 -0
- package/lib/model.js +1374 -0
- package/lib/proxy-class/index.js +3 -0
- package/lib/proxy-class/proxy-class.js +269 -0
- package/lib/query-engine/field-scope.js +99 -0
- package/lib/query-engine/index.js +13 -0
- package/lib/query-engine/model-scope.js +198 -0
- package/lib/query-engine/query-engine-base.js +325 -0
- package/lib/query-engine/query-engine.js +212 -0
- package/lib/types/concrete/bigint-type.js +62 -0
- package/lib/types/concrete/blob-type.js +46 -0
- package/lib/types/concrete/boolean-type.js +41 -0
- package/lib/types/concrete/char-type.js +39 -0
- package/lib/types/concrete/date-type.js +87 -0
- package/lib/types/concrete/datetime-type.js +92 -0
- package/lib/types/concrete/float-type.js +47 -0
- package/lib/types/concrete/foreign-key-type.js +208 -0
- package/lib/types/concrete/index.js +53 -0
- package/lib/types/concrete/integer-type.js +51 -0
- package/lib/types/concrete/string-type.js +44 -0
- package/lib/types/concrete/text-type.js +44 -0
- package/lib/types/concrete/uuid-base.js +77 -0
- package/lib/types/concrete/uuid-v1-type.js +99 -0
- package/lib/types/concrete/uuid-v3-type.js +108 -0
- package/lib/types/concrete/uuid-v4-type.js +90 -0
- package/lib/types/concrete/uuid-v5-type.js +108 -0
- package/lib/types/concrete/xid-type.js +58 -0
- package/lib/types/helpers/default-helpers.js +127 -0
- package/lib/types/helpers/index.js +35 -0
- package/lib/types/index.js +94 -0
- package/lib/types/type.js +244 -0
- package/lib/types/virtual/index.js +14 -0
- package/lib/types/virtual/model-type.js +141 -0
- package/lib/types/virtual/models-type.js +264 -0
- package/lib/types/virtual/relational-type-base.js +323 -0
- package/lib/utils/index.js +65 -0
- package/lib/utils/misc-utils.js +73 -0
- package/lib/utils/model-utils.js +704 -0
- package/lib/utils/query-utils.js +186 -0
- package/package.json +63 -0
- package/playground/test01.js +15 -0
- package/spec/connection/connection-base-spec.js +126 -0
- package/spec/connection/literals/average-literal-spec.js +45 -0
- package/spec/connection/literals/count-literal-spec.js +42 -0
- package/spec/connection/literals/distinct-literal-spec.js +42 -0
- package/spec/connection/literals/literal-spec.js +26 -0
- package/spec/connection/literals/max-literal-spec.js +42 -0
- package/spec/connection/literals/min-literal-spec.js +42 -0
- package/spec/connection/literals/sum-literal-spec.js +42 -0
- package/spec/helpers/default-helpers-spec.js +108 -0
- package/spec/model-spec.js +736 -0
- package/spec/proxy-class/proxy-class-spec.js +173 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_chain_query_conditions-001.snapshot +94 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_call-001.snapshot +35 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_sub-001.snapshot +35 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_set_a_default_scope_on_a_model-001.snapshot +57 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_unscope_default_scope_on_a_model-001.snapshot +35 -0
- package/spec/query-engine/query-engine-spec.js +99 -0
- package/spec/support/jasmine.json +13 -0
- package/spec/support/models/blob-test-model.js +19 -0
- package/spec/support/models/extended-user-model.js +38 -0
- package/spec/support/models/index.js +27 -0
- package/spec/support/models/number-model.js +24 -0
- package/spec/support/models/role-model.js +26 -0
- package/spec/support/models/role-thing-model.js +41 -0
- package/spec/support/models/scoped-user-model.js +13 -0
- package/spec/support/models/time-model.js +36 -0
- package/spec/support/models/user-model.js +70 -0
- package/spec/support/models/user-role-model.js +36 -0
- package/spec/support/models/user-thing-model.js +46 -0
- package/spec/support/models/validation-test-model.js +40 -0
- package/spec/support/snapshots.js +293 -0
- package/spec/support/test-helpers.js +13 -0
- package/spec/types/concrete/bigint-type-spec.js +84 -0
- package/spec/types/concrete/boolean-type-spec.js +83 -0
- package/spec/types/concrete/date-type-spec.js +85 -0
- package/spec/types/concrete/datetime-type-spec.js +87 -0
- package/spec/types/concrete/float-type-spec.js +71 -0
- package/spec/types/concrete/foreign-key-type-spec.js +64 -0
- package/spec/types/concrete/integer-type-spec.js +71 -0
- package/spec/types/concrete/string-type-spec.js +91 -0
- package/spec/types/concrete/uuid-v1-type-spec.js +73 -0
- package/spec/types/concrete/uuid-v4-type-spec.js +65 -0
- package/spec/types/type-spec.js +101 -0
- package/spec/types/virtual/model-types-spec.js +401 -0
- package/spec/utils/misc-utils-spec.js +61 -0
- package/spec/utils/model-utils-spec.js +55 -0
- package/spec/utils/query-utils-spec.js +105 -0
package/lib/model.js
ADDED
|
@@ -0,0 +1,1374 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Nife = require('nife');
|
|
4
|
+
const Inflection = require('inflection');
|
|
5
|
+
const Type = require('./types/type');
|
|
6
|
+
const DefaultHelpers = require('./types/helpers/default-helpers');
|
|
7
|
+
const Field = require('./field');
|
|
8
|
+
|
|
9
|
+
// Used as a unique key for cache
|
|
10
|
+
class CacheKey {
|
|
11
|
+
constructor(number) {
|
|
12
|
+
this.number = (number == null) ? 0 : (number.valueOf() + 1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
valueOf() {
|
|
16
|
+
return this.number;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function bindStaticWhereToModelClass(ModelClass) {
|
|
21
|
+
const whereProp = {
|
|
22
|
+
enumberable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
set: function() {},
|
|
25
|
+
get: function() {
|
|
26
|
+
const self = this;
|
|
27
|
+
|
|
28
|
+
function unboundWhere(_connection, _propName) {
|
|
29
|
+
let connection = _connection;
|
|
30
|
+
|
|
31
|
+
if (arguments.length === 0) {
|
|
32
|
+
connection = self._getConnection();
|
|
33
|
+
if (!connection)
|
|
34
|
+
throw new Error(`${self.getModelName()}::where: Model has no bound connection. You need to provide a connection by calling "${self.getModelName()}.where(connection)" instead.`);
|
|
35
|
+
|
|
36
|
+
return self.getQueryEngine(connection);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!connection) {
|
|
40
|
+
connection = self._getConnection();
|
|
41
|
+
if (!connection)
|
|
42
|
+
throw new Error(`${self.getModelName()}::where: Model has no bound connection. You need to provide a connection by calling "${self.getModelName()}.where(connection)" instead.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let propName = _propName;
|
|
46
|
+
|
|
47
|
+
if (connection.constructor && !connection.constructor._isMythixConnection) {
|
|
48
|
+
connection = self._getConnection();
|
|
49
|
+
if (!connection)
|
|
50
|
+
throw new Error(`${self.getModelName()}::where: Model has no bound connection. You need to provide a connection by calling "${self.getModelName()}.where(connection)" instead.`);
|
|
51
|
+
|
|
52
|
+
propName = _connection;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let queryEngine = self.getQueryEngine(connection, {
|
|
56
|
+
models: {
|
|
57
|
+
[self.getModelName()]: self,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (propName)
|
|
62
|
+
return queryEngine[propName];
|
|
63
|
+
else
|
|
64
|
+
return queryEngine;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Proxy(unboundWhere, {
|
|
68
|
+
get: (_, propName) => {
|
|
69
|
+
let connection = self._getConnection();
|
|
70
|
+
if (!connection)
|
|
71
|
+
throw new Error(`${self.getModelName()}::where: Model has no bound connection. You need to provide a connection by calling "${self.getModelName()}.where(connection)" instead.`);
|
|
72
|
+
|
|
73
|
+
// This will throw an exception
|
|
74
|
+
// if no connection is available
|
|
75
|
+
let queryEngine = self.getQueryEngine(connection);
|
|
76
|
+
return queryEngine[propName];
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
Object.defineProperties(ModelClass, {
|
|
83
|
+
'where': whereProp,
|
|
84
|
+
'$': whereProp,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class Model {
|
|
89
|
+
static _isMythixModel = true;
|
|
90
|
+
|
|
91
|
+
static isModelClass(value) {
|
|
92
|
+
if (!value)
|
|
93
|
+
return false;
|
|
94
|
+
|
|
95
|
+
if (value.prototype instanceof Model)
|
|
96
|
+
return true;
|
|
97
|
+
|
|
98
|
+
if (value._isMythixModel)
|
|
99
|
+
return true;
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static isModel(value) {
|
|
105
|
+
if (!value)
|
|
106
|
+
return false;
|
|
107
|
+
|
|
108
|
+
if (value instanceof Model)
|
|
109
|
+
return true;
|
|
110
|
+
|
|
111
|
+
if (value.constructor && value.constructor._isMythixModel)
|
|
112
|
+
return true;
|
|
113
|
+
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static toString(showFields) {
|
|
118
|
+
let fieldNames = [];
|
|
119
|
+
|
|
120
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
121
|
+
if (field.type.isVirtual())
|
|
122
|
+
return;
|
|
123
|
+
|
|
124
|
+
fieldNames.push(` ${fieldName}: ${field.type.getDisplayName()}`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let fieldsStr = (showFields && fieldNames.length) ? `\n${fieldNames.join(',\n')}\n` : '';
|
|
128
|
+
|
|
129
|
+
return `[model ${this.getModelName()}] {${fieldsStr}}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// eslint-disable-next-line no-unused-vars
|
|
133
|
+
static [Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) {
|
|
134
|
+
return this.toString((depth !== 0));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static _getConnection(_connection) {
|
|
138
|
+
return _connection || this._mythixBoundConnection;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_getConnection(connection) {
|
|
142
|
+
if (connection)
|
|
143
|
+
return connection;
|
|
144
|
+
|
|
145
|
+
if (this._connection)
|
|
146
|
+
return this._connection;
|
|
147
|
+
|
|
148
|
+
return this.constructor._getConnection();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static getConnection(_connection) {
|
|
152
|
+
let connection = this._getConnection(_connection);
|
|
153
|
+
if (!connection)
|
|
154
|
+
throw new Error(`${this.getModelName()}::getConnection: No connection bound to model. You need to provide a connection for this operation.`);
|
|
155
|
+
|
|
156
|
+
return connection;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getConnection(_connection) {
|
|
160
|
+
if (_connection)
|
|
161
|
+
return _connection;
|
|
162
|
+
|
|
163
|
+
let connection = this._getConnection();
|
|
164
|
+
if (!connection)
|
|
165
|
+
return this.constructor.getConnection();
|
|
166
|
+
|
|
167
|
+
return connection;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static bindConnection(connection) {
|
|
171
|
+
let ModelClass = this;
|
|
172
|
+
|
|
173
|
+
// Ensure that the model fields
|
|
174
|
+
// are constructed properly
|
|
175
|
+
ModelClass.getFields();
|
|
176
|
+
|
|
177
|
+
let connectionOptions = connection.getOptions();
|
|
178
|
+
if (connectionOptions && connectionOptions.bindModels === false) {
|
|
179
|
+
let modelName = ModelClass.getModelName();
|
|
180
|
+
|
|
181
|
+
ModelClass = class BoundModel extends ModelClass {
|
|
182
|
+
static getModelName() {
|
|
183
|
+
return modelName;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
ModelClass.fields = ModelClass.mergeFields();
|
|
188
|
+
ModelClass._sortedFields = null; // Clear cache
|
|
189
|
+
} else {
|
|
190
|
+
if (Object.prototype.hasOwnProperty.call(ModelClass, '_mythixBoundConnection') && ModelClass._mythixBoundConnection)
|
|
191
|
+
throw new Error(`${this.getModelName()}:bindConnection: Model is already bound to a connection. You can not bind a model to a connection more than once.`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const whereProp = {
|
|
195
|
+
enumberable: false,
|
|
196
|
+
configurable: true,
|
|
197
|
+
get: () => {
|
|
198
|
+
return ModelClass.getQueryEngine(connection);
|
|
199
|
+
},
|
|
200
|
+
set: () => {},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
Object.defineProperties(ModelClass, {
|
|
204
|
+
'_mythixBoundConnection': {
|
|
205
|
+
writable: true,
|
|
206
|
+
enumberable: false,
|
|
207
|
+
configurable: true,
|
|
208
|
+
value: connection,
|
|
209
|
+
},
|
|
210
|
+
'where': whereProp,
|
|
211
|
+
'$': whereProp,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return ModelClass;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static getQueryEngineClass(connection) {
|
|
218
|
+
return this.getConnection(connection).getQueryEngineClass();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getQueryEngineClass(connection) {
|
|
222
|
+
return this.constructor.getQueryEngineClass(connection);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static getUnscopedQueryEngine(connection, options) {
|
|
226
|
+
let QueryEngineClass = this.getQueryEngineClass(connection);
|
|
227
|
+
let queryEngine = new QueryEngineClass(
|
|
228
|
+
Object.assign(
|
|
229
|
+
{},
|
|
230
|
+
(options || {}),
|
|
231
|
+
{
|
|
232
|
+
connection: this.getConnection(connection),
|
|
233
|
+
},
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
let modelName = this.getModelName();
|
|
238
|
+
return queryEngine[modelName];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getUnscopedQueryEngine(connection, options) {
|
|
242
|
+
return this.constructor.getUnscopedQueryEngine(connection, options);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
defaultScope(queryEngine) {
|
|
246
|
+
return this.constructor.defaultScope(queryEngine);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static getQueryEngine(connection, options) {
|
|
250
|
+
let queryEngine = this.getUnscopedQueryEngine(connection, options);
|
|
251
|
+
let defaultScope = this.defaultScope(queryEngine, options);
|
|
252
|
+
|
|
253
|
+
return defaultScope;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getQueryEngine(connection, options) {
|
|
257
|
+
return this.constructor.getQueryEngine(connection, options);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static getForeignKeyFieldsMap(connection) {
|
|
261
|
+
let foreignKeyFieldsMap = connection._getFromModelCache(this, 'foreignKeyFieldsMap');
|
|
262
|
+
if (foreignKeyFieldsMap)
|
|
263
|
+
return foreignKeyFieldsMap;
|
|
264
|
+
|
|
265
|
+
let foreignKeyFields = new Map();
|
|
266
|
+
|
|
267
|
+
Nife.iterate(this.getFields(), ({ value: field }) => {
|
|
268
|
+
let fieldName = field.fieldName;
|
|
269
|
+
|
|
270
|
+
if (field.type.isForeignKey()) {
|
|
271
|
+
let targetModelName = field.type.getTargetModelName(connection);
|
|
272
|
+
let targetFieldName = field.type.getTargetFieldName(connection);
|
|
273
|
+
let relationSet = foreignKeyFields.get(targetModelName);
|
|
274
|
+
|
|
275
|
+
if (!relationSet) {
|
|
276
|
+
relationSet = [];
|
|
277
|
+
foreignKeyFields.set(targetModelName, relationSet);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
relationSet.push({ targetFieldName: targetFieldName, sourceFieldName: fieldName });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
connection._setToModelCache(this, 'foreignKeyFieldsMap', foreignKeyFields);
|
|
285
|
+
|
|
286
|
+
return foreignKeyFields;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
getForeignKeyFieldsMap(connection) {
|
|
290
|
+
return this.constructor.getForeignKeyFieldsMap(connection);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static getForeignKeysTargetModels(connection) {
|
|
294
|
+
let foreignKeyTargetModels = connection._getFromModelCache(this, 'foreignKeyTargetModels');
|
|
295
|
+
if (foreignKeyTargetModels)
|
|
296
|
+
return foreignKeyTargetModels;
|
|
297
|
+
|
|
298
|
+
let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
|
|
299
|
+
let foreignKeyTargetModelNames = Array.from(foreignKeyFieldsMap.keys());
|
|
300
|
+
|
|
301
|
+
foreignKeyTargetModels = new Map();
|
|
302
|
+
|
|
303
|
+
for (let i = 0, il = foreignKeyTargetModelNames.length; i < il; i++) {
|
|
304
|
+
let modelName = foreignKeyTargetModelNames[i];
|
|
305
|
+
let Model = connection.getModel(modelName);
|
|
306
|
+
|
|
307
|
+
foreignKeyTargetModels.set(modelName, Model);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
connection._setToModelCache(this, 'foreignKeyTargetModels', foreignKeyTargetModels);
|
|
311
|
+
|
|
312
|
+
return foreignKeyTargetModels;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getForeignKeysTargetModels(connection) {
|
|
316
|
+
return this.constructor.getForeignKeysTargetModels(connection);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
static getForeignKeysTargetModelNames(connection) {
|
|
320
|
+
let foreignKeyTargetModelNames = connection._getFromModelCache(this, 'foreignKeyTargetModelNames');
|
|
321
|
+
if (foreignKeyTargetModelNames)
|
|
322
|
+
return foreignKeyTargetModelNames;
|
|
323
|
+
|
|
324
|
+
let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
|
|
325
|
+
foreignKeyTargetModelNames = Array.from(foreignKeyFieldsMap.keys());
|
|
326
|
+
|
|
327
|
+
connection._setToModelCache(this, 'foreignKeyTargetModelNames', foreignKeyTargetModelNames);
|
|
328
|
+
|
|
329
|
+
return foreignKeyTargetModelNames;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
getForeignKeysTargetModelNames(connection) {
|
|
333
|
+
return this.constructor.getForeignKeysTargetModelNames(connection);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static getForeignKeysTargetFieldNames(connection, modelName) {
|
|
337
|
+
let cacheKey = `${modelName}:foreignKeyFieldNames`;
|
|
338
|
+
let foreignKeyFieldNames = connection._getFromModelCache(this, cacheKey);
|
|
339
|
+
if (foreignKeyFieldNames)
|
|
340
|
+
return foreignKeyFieldNames;
|
|
341
|
+
|
|
342
|
+
let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
|
|
343
|
+
let fieldNames = (foreignKeyFieldsMap.get(modelName) || []);
|
|
344
|
+
|
|
345
|
+
connection._setToModelCache(this, cacheKey, fieldNames);
|
|
346
|
+
|
|
347
|
+
return fieldNames;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
getForeignKeysTargetFieldNames(connection, modelName) {
|
|
351
|
+
return this.constructor.getForeignKeysTargetFieldNames(connection, modelName);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
static getForeignKeysTargetField(connection, modelName, fieldName) {
|
|
355
|
+
let cacheKey = `${modelName}:${fieldName}:foreignKeyTargetField`;
|
|
356
|
+
let foreignKeyFieldNames = connection._getFromModelCache(this, cacheKey);
|
|
357
|
+
if (foreignKeyFieldNames)
|
|
358
|
+
return foreignKeyFieldNames;
|
|
359
|
+
|
|
360
|
+
let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
|
|
361
|
+
let fields = foreignKeyFieldsMap.get(modelName);
|
|
362
|
+
if (!fields)
|
|
363
|
+
return;
|
|
364
|
+
|
|
365
|
+
let fieldInfo = fields.find((field) => (field.targetFieldName === fieldName));
|
|
366
|
+
|
|
367
|
+
connection._setToModelCache(this, cacheKey, fieldInfo);
|
|
368
|
+
|
|
369
|
+
return fieldInfo;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
getForeignKeysTargetField(connection, modelName, fieldName) {
|
|
373
|
+
return this.constructor.getForeignKeysTargetField(connection, modelName, fieldName);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
static isForeignKeyTargetModel(connection, modelName) {
|
|
377
|
+
let foreignKeyFieldsMap = this.getForeignKeyFieldsMap(connection);
|
|
378
|
+
return foreignKeyFieldsMap.has(modelName);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
isForeignKeyTargetModel(connection, modelName) {
|
|
382
|
+
return this.constructor.isForeignKeyTargetModel(connection, modelName);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
static getTableName(connection) {
|
|
386
|
+
if (!connection) {
|
|
387
|
+
let tableName = Nife.camelCaseToSnakeCase(this.getPluralModelName());
|
|
388
|
+
return tableName;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let cacheKey = `${this.getModelName()}:tableName`;
|
|
392
|
+
let modelTableName = connection._getFromModelCache(this, cacheKey);
|
|
393
|
+
if (modelTableName)
|
|
394
|
+
return modelTableName;
|
|
395
|
+
|
|
396
|
+
let tableName = Nife.camelCaseToSnakeCase(this.getPluralModelName());
|
|
397
|
+
|
|
398
|
+
connection._setToModelCache(this, cacheKey, tableName);
|
|
399
|
+
|
|
400
|
+
return tableName;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getTableName(connection) {
|
|
404
|
+
return this.constructor.getTableName(connection);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static getModelName() {
|
|
408
|
+
return this.name;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getModelName() {
|
|
412
|
+
return this.constructor.getModelName();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
static getSingularName() {
|
|
416
|
+
return this.getModelName();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getSingularName() {
|
|
420
|
+
return this.constructor.getSingularName();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
static getPluralModelName() {
|
|
424
|
+
return Inflection.pluralize(this.getSingularName());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
getPluralModelName() {
|
|
428
|
+
return this.constructor.getPluralModelName();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static getModel() {
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
getModel() {
|
|
436
|
+
return this.constructor.getModel();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static getFields(fieldNames) {
|
|
440
|
+
let fields = this.initializeFields(this.fields);
|
|
441
|
+
if (fields !== this.fields) {
|
|
442
|
+
Object.defineProperties(this, {
|
|
443
|
+
'fields': {
|
|
444
|
+
writable: true,
|
|
445
|
+
enumberable: true,
|
|
446
|
+
configurable: true,
|
|
447
|
+
value: fields,
|
|
448
|
+
},
|
|
449
|
+
'_sortedFields': {
|
|
450
|
+
writable: true,
|
|
451
|
+
enumberable: true,
|
|
452
|
+
configurable: true,
|
|
453
|
+
value: null,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (Nife.isNotEmpty(fieldNames)) {
|
|
459
|
+
let filteredFields = [];
|
|
460
|
+
|
|
461
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
462
|
+
if (fieldNames.indexOf(fieldName) < 0)
|
|
463
|
+
return;
|
|
464
|
+
|
|
465
|
+
filteredFields.push(field);
|
|
466
|
+
}, fields);
|
|
467
|
+
|
|
468
|
+
return filteredFields;
|
|
469
|
+
} else {
|
|
470
|
+
return fields;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
getFields(fieldNames) {
|
|
475
|
+
return this.constructor.getFields(fieldNames);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
static getSortedFields(fieldNames) {
|
|
479
|
+
if (Object.prototype.hasOwnProperty.call(this, '_sortedFields') && this._sortedFields)
|
|
480
|
+
return this._sortedFields;
|
|
481
|
+
|
|
482
|
+
let fields = this.getFields(fieldNames);
|
|
483
|
+
let sortedFields = [];
|
|
484
|
+
let primaryKeyFieldName = this.getPrimaryKeyFieldName();
|
|
485
|
+
|
|
486
|
+
Nife.iterate(fields, ({ value, context}) => context.push(value), sortedFields);
|
|
487
|
+
|
|
488
|
+
sortedFields = sortedFields.sort((a, b) => {
|
|
489
|
+
let x = a.fieldName;
|
|
490
|
+
let y = b.fieldName;
|
|
491
|
+
|
|
492
|
+
if (primaryKeyFieldName) {
|
|
493
|
+
if (x === primaryKeyFieldName)
|
|
494
|
+
return -1;
|
|
495
|
+
else if (y === primaryKeyFieldName)
|
|
496
|
+
return 1;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (x === y)
|
|
500
|
+
return 0;
|
|
501
|
+
|
|
502
|
+
return (x < y) ? -1 : 1;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
this._sortedFields = sortedFields;
|
|
506
|
+
|
|
507
|
+
return sortedFields;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
getSortedFields(fieldNames) {
|
|
511
|
+
return this.constructor.getSortedFields(fieldNames);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
static mergeFields(mergeFields) {
|
|
515
|
+
const cloneField = ({ value: field, key: _fieldName, index, type }) => {
|
|
516
|
+
if (!field)
|
|
517
|
+
return;
|
|
518
|
+
|
|
519
|
+
if (field instanceof Field) {
|
|
520
|
+
let clonedField = field.clone();
|
|
521
|
+
|
|
522
|
+
if (Type.isType(clonedField.type)) {
|
|
523
|
+
clonedField.type.setField(clonedField);
|
|
524
|
+
clonedField.type.setModel(ModelClass);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
clonedField.setModel(ModelClass);
|
|
528
|
+
|
|
529
|
+
clonedFields.set(field.fieldName, clonedField);
|
|
530
|
+
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let fieldName = (type === 'Set' || type === 'Array') ? field.fieldName : (field.fieldName || _fieldName);
|
|
535
|
+
if (Nife.isEmpty(fieldName))
|
|
536
|
+
throw new Error(`${this.name}::mergeFields: "fieldName" is missing on field index ${index}.`);
|
|
537
|
+
|
|
538
|
+
let fieldCopy = Object.assign({}, field, { Model: ModelClass });
|
|
539
|
+
if (Type.isType(fieldCopy.type)) {
|
|
540
|
+
fieldCopy.type.setField(fieldCopy);
|
|
541
|
+
fieldCopy.type.setModel(ModelClass);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
clonedFields.set(fieldName, fieldCopy);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const mapToObject = (map) => {
|
|
548
|
+
let obj = {};
|
|
549
|
+
|
|
550
|
+
for (let [ key, value ] of map.entries())
|
|
551
|
+
obj[key] = value;
|
|
552
|
+
|
|
553
|
+
return obj;
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
let ModelClass = this;
|
|
557
|
+
let fields = this.fields;
|
|
558
|
+
let clonedFields = new Map();
|
|
559
|
+
|
|
560
|
+
Nife.iterate(fields, cloneField);
|
|
561
|
+
Nife.iterate(mergeFields, cloneField);
|
|
562
|
+
|
|
563
|
+
return (Nife.instanceOf(fields, 'array', 'set')) ? Array.from(clonedFields.values()) : mapToObject(clonedFields);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
static initializeFields(fields) {
|
|
567
|
+
if (!fields)
|
|
568
|
+
return fields;
|
|
569
|
+
|
|
570
|
+
if (fields._mythixFieldsInitialized)
|
|
571
|
+
return fields;
|
|
572
|
+
|
|
573
|
+
let ModelClass = this;
|
|
574
|
+
let isArray = Array.isArray(fields);
|
|
575
|
+
let finalizedFields = (isArray) ? [] : {};
|
|
576
|
+
let primaryKeyField;
|
|
577
|
+
|
|
578
|
+
Nife.iterate(fields, ({ value: _field, key: _fieldName, index, type: collectionType }) => {
|
|
579
|
+
let field = _field;
|
|
580
|
+
if (!field)
|
|
581
|
+
return;
|
|
582
|
+
|
|
583
|
+
// If we have a type instead of an object,
|
|
584
|
+
// then mutate it into the proper structure
|
|
585
|
+
if (Type.isTypeClass(field) || Type.isType(field)) {
|
|
586
|
+
field = {
|
|
587
|
+
type: field,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let fieldName = (collectionType === 'Set' || collectionType === 'Array') ? field.fieldName : (field.fieldName || _fieldName);
|
|
592
|
+
if (Nife.isEmpty(fieldName))
|
|
593
|
+
throw new Error(`${ModelClass.getModelName()}::initializeFields: "fieldName" is missing on field index ${index}.`);
|
|
594
|
+
|
|
595
|
+
field.fieldName = fieldName;
|
|
596
|
+
|
|
597
|
+
if (!field.type)
|
|
598
|
+
throw new Error(`${ModelClass.getModelName()}::initializeFields: "type" not found on "${this.getModelName()}.${fieldName}". "type" is required for all fields.`);
|
|
599
|
+
|
|
600
|
+
if (!field.columnName)
|
|
601
|
+
field.columnName = fieldName;
|
|
602
|
+
|
|
603
|
+
field.Model = ModelClass;
|
|
604
|
+
|
|
605
|
+
if (!(field instanceof Field))
|
|
606
|
+
field = new Field(field);
|
|
607
|
+
|
|
608
|
+
if (field.primaryKey)
|
|
609
|
+
primaryKeyField = field;
|
|
610
|
+
|
|
611
|
+
let type = field.type = Type.instantiateType(field.type);
|
|
612
|
+
type.setField(field);
|
|
613
|
+
type.setModel(ModelClass);
|
|
614
|
+
|
|
615
|
+
if (isArray)
|
|
616
|
+
finalizedFields.push(field);
|
|
617
|
+
else
|
|
618
|
+
finalizedFields[fieldName] = field;
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
Object.defineProperties(finalizedFields, {
|
|
622
|
+
'_mythixFieldsInitialized': {
|
|
623
|
+
writable: true,
|
|
624
|
+
enumberable: false,
|
|
625
|
+
configurable: true,
|
|
626
|
+
value: true,
|
|
627
|
+
},
|
|
628
|
+
'_primaryKeyField': {
|
|
629
|
+
writable: true,
|
|
630
|
+
enumberable: false,
|
|
631
|
+
configurable: true,
|
|
632
|
+
value: primaryKeyField,
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
return finalizedFields;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
static iterateFields(callback, _fields, sorted) {
|
|
640
|
+
if (typeof callback !== 'function')
|
|
641
|
+
return [];
|
|
642
|
+
|
|
643
|
+
let fields = _fields;
|
|
644
|
+
if (!fields)
|
|
645
|
+
fields = (sorted !== false) ? this.getSortedFields() : this.getFields();
|
|
646
|
+
|
|
647
|
+
return Nife.iterate(fields, ({ value: field, index, context, stop, isStopped }) => {
|
|
648
|
+
let result = callback({ field, fieldName: field.fieldName, fields, index, stop, isStopped });
|
|
649
|
+
|
|
650
|
+
if (!isStopped())
|
|
651
|
+
context.push(result);
|
|
652
|
+
}, []);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
iterateFields(callback, _fields, sorted) {
|
|
656
|
+
return this.constructor.iterateFields(callback, _fields, sorted);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
static hasRemoteFieldValues() {
|
|
660
|
+
let hasRemote = false;
|
|
661
|
+
|
|
662
|
+
this.iterateFields(({ field, stop }) => {
|
|
663
|
+
if (field.type.isRemote()) {
|
|
664
|
+
hasRemote = true;
|
|
665
|
+
stop();
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
return hasRemote;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
hasRemoteFieldValues() {
|
|
673
|
+
return this.constructor.hasRemoteFieldValues();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static getPrimaryKeyField() {
|
|
677
|
+
let fields = this.getFields();
|
|
678
|
+
if (!fields)
|
|
679
|
+
return;
|
|
680
|
+
|
|
681
|
+
return fields._primaryKeyField;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
getPrimaryKeyField() {
|
|
685
|
+
return this.constructor.getPrimaryKeyField();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
static getPrimaryKeyFieldName() {
|
|
689
|
+
let primaryKeyField = this.getPrimaryKeyField();
|
|
690
|
+
if (!primaryKeyField)
|
|
691
|
+
return;
|
|
692
|
+
|
|
693
|
+
return primaryKeyField.fieldName;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
getPrimaryKeyFieldName() {
|
|
697
|
+
return this.constructor.getPrimaryKeyFieldName();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
static primaryKeyHasRemoteValue() {
|
|
701
|
+
let primaryKeyField = this.getPrimaryKeyField();
|
|
702
|
+
if (!primaryKeyField)
|
|
703
|
+
return false;
|
|
704
|
+
|
|
705
|
+
return primaryKeyField.type.isRemote();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
primaryKeyHasRemoteValue() {
|
|
709
|
+
return this.constructor.primaryKeyHasRemoteValue();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
static getField(findFieldName) {
|
|
713
|
+
let fields = this.getFields();
|
|
714
|
+
if (!fields || !findFieldName)
|
|
715
|
+
return;
|
|
716
|
+
|
|
717
|
+
let foundField;
|
|
718
|
+
|
|
719
|
+
this.iterateFields(({ field, fieldName, stop }) => {
|
|
720
|
+
if (fieldName === findFieldName) {
|
|
721
|
+
foundField = field;
|
|
722
|
+
stop();
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
return foundField;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
getField(findFieldName) {
|
|
730
|
+
return this.constructor.getField(findFieldName);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
static hasField(fieldName) {
|
|
734
|
+
return !!this.getField(fieldName);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
hasField(fieldName) {
|
|
738
|
+
return this.constructor.hasField(fieldName);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
static getConcreteFieldCount() {
|
|
742
|
+
let count = 0;
|
|
743
|
+
|
|
744
|
+
this.iterateFields(({ field }) => {
|
|
745
|
+
if (field.type.isVirtual())
|
|
746
|
+
return;
|
|
747
|
+
|
|
748
|
+
count++;
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
return count;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
getConcreteFieldCount() {
|
|
755
|
+
return this.constructor.getConcreteFieldCount();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
static defaultScope(queryEngine) {
|
|
759
|
+
return queryEngine;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static getDefaultOrder() {
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
static getWhereWithConnection(options) {
|
|
766
|
+
if (options && options.connection)
|
|
767
|
+
return this.where(options.connection);
|
|
768
|
+
else
|
|
769
|
+
return this.where;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getWhereWithConnection(options) {
|
|
773
|
+
return this.constructor.getWhereWithConnection(options);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
static async create(models, options) {
|
|
777
|
+
let connection = this.getConnection() || (options && options.connection);
|
|
778
|
+
if (!connection)
|
|
779
|
+
throw new Error(`${this.constructor.name}::create: Connection not found. A connection is required to be bound to the model. Is your model not yet initialized through a connection?`);
|
|
780
|
+
|
|
781
|
+
let result = await connection.insert(this.getModel(), models, options);
|
|
782
|
+
if (Array.isArray(models))
|
|
783
|
+
return result;
|
|
784
|
+
|
|
785
|
+
return (Array.isArray(result)) ? result[0] : result;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
static count(options) {
|
|
789
|
+
return this.getWhereWithConnection(options).count(null, options);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
static all(options) {
|
|
793
|
+
return this.getWhereWithConnection(options).all(options);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
static first(limit, options) {
|
|
797
|
+
if (limit != null && !Nife.instanceOf(limit, 'number'))
|
|
798
|
+
throw new Error(`${this.getModelName()}::first: "limit" must be null, or a number. If you want to supply a query, use "${this.getModelName()}.where.{query}.first(limit)" instead.`);
|
|
799
|
+
|
|
800
|
+
return this.getWhereWithConnection(options).first(limit, options);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
static last(limit, options) {
|
|
804
|
+
if (limit != null && !Nife.instanceOf(limit, 'number'))
|
|
805
|
+
throw new Error(`${this.getModelName()}::last: "limit" must be null, or a number. If you want to supply a query, use "${this.getModelName()}.where.{query}.last(limit)" instead.`);
|
|
806
|
+
|
|
807
|
+
return this.getWhereWithConnection(options).last(limit, options);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
static pluck(fields, options) {
|
|
811
|
+
return this.getWhereWithConnection(options).pluck(fields);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
constructor(data, _options) {
|
|
815
|
+
let options = _options || {};
|
|
816
|
+
|
|
817
|
+
const whereProp = {
|
|
818
|
+
enumberable: false,
|
|
819
|
+
configurable: true,
|
|
820
|
+
get: () => {
|
|
821
|
+
return this.constructor.where(this._getConnection());
|
|
822
|
+
},
|
|
823
|
+
set: () => {},
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
Object.defineProperties(this, {
|
|
827
|
+
'_mythixModelInstance': {
|
|
828
|
+
writable: false,
|
|
829
|
+
enumberable: false,
|
|
830
|
+
configurable: false,
|
|
831
|
+
value: true,
|
|
832
|
+
},
|
|
833
|
+
'_connection': {
|
|
834
|
+
writable: false,
|
|
835
|
+
enumberable: false,
|
|
836
|
+
configurable: false,
|
|
837
|
+
value: this._getConnection(options.connection),
|
|
838
|
+
},
|
|
839
|
+
'_fieldData': {
|
|
840
|
+
writable: true,
|
|
841
|
+
enumberable: false,
|
|
842
|
+
configurable: true,
|
|
843
|
+
value: {},
|
|
844
|
+
},
|
|
845
|
+
'_dirtyFieldData': {
|
|
846
|
+
writable: true,
|
|
847
|
+
enumberable: false,
|
|
848
|
+
configurable: true,
|
|
849
|
+
value: {},
|
|
850
|
+
},
|
|
851
|
+
'dirtyID': {
|
|
852
|
+
writable: true,
|
|
853
|
+
enumberable: false,
|
|
854
|
+
configurable: true,
|
|
855
|
+
value: new CacheKey(),
|
|
856
|
+
},
|
|
857
|
+
'_persisted': {
|
|
858
|
+
writable: true,
|
|
859
|
+
enumberable: false,
|
|
860
|
+
configurable: true,
|
|
861
|
+
value: false,
|
|
862
|
+
},
|
|
863
|
+
'__order': {
|
|
864
|
+
writable: true,
|
|
865
|
+
enumberable: false,
|
|
866
|
+
configurable: true,
|
|
867
|
+
value: 0,
|
|
868
|
+
},
|
|
869
|
+
'__assignedRelatedModels': {
|
|
870
|
+
writable: true,
|
|
871
|
+
enumberable: false,
|
|
872
|
+
configurable: true,
|
|
873
|
+
value: new Map(),
|
|
874
|
+
},
|
|
875
|
+
'changes': {
|
|
876
|
+
enumberable: false,
|
|
877
|
+
configurable: true,
|
|
878
|
+
get: () => {
|
|
879
|
+
return this._getDirtyFields();
|
|
880
|
+
},
|
|
881
|
+
set: () => {},
|
|
882
|
+
},
|
|
883
|
+
'where': whereProp,
|
|
884
|
+
'$': whereProp,
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
this._constructor(data);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
_constructor(data) {
|
|
891
|
+
this._constructFields();
|
|
892
|
+
this._initializeModelData(data);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
_constructFields() {
|
|
896
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
897
|
+
field.type.initialize(this._getConnection(), this);
|
|
898
|
+
if (field.type.exposeToModel())
|
|
899
|
+
this._constructField(fieldName, field);
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
_constructField(fieldName, field) {
|
|
904
|
+
Object.defineProperties(this, {
|
|
905
|
+
[fieldName]: {
|
|
906
|
+
enumberable: false,
|
|
907
|
+
configurable: true,
|
|
908
|
+
get: () => {
|
|
909
|
+
return this._getFieldValue(fieldName, field);
|
|
910
|
+
},
|
|
911
|
+
set: (value) => {
|
|
912
|
+
this._setFieldValue(fieldName, field, value);
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
_initializeModelData(_data) {
|
|
919
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
920
|
+
let data = _data || {};
|
|
921
|
+
|
|
922
|
+
// First initialize field values from data
|
|
923
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
924
|
+
if (!field.type.exposeToModel())
|
|
925
|
+
return;
|
|
926
|
+
|
|
927
|
+
let fieldValue = data[fieldName];
|
|
928
|
+
if (fieldValue === undefined)
|
|
929
|
+
return;
|
|
930
|
+
|
|
931
|
+
dirtyFieldData[fieldName] = fieldValue;
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// Next initialize default values
|
|
935
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
936
|
+
field.type.initialize(this._getConnection(), this);
|
|
937
|
+
if (!field.type.exposeToModel())
|
|
938
|
+
return;
|
|
939
|
+
|
|
940
|
+
let fieldValue = (data) ? data[fieldName] : undefined;
|
|
941
|
+
this._initializeFieldData(fieldName, field, fieldValue, data);
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
_castFieldValue(field, value) {
|
|
946
|
+
let type = field.type;
|
|
947
|
+
if (!type)
|
|
948
|
+
return value;
|
|
949
|
+
|
|
950
|
+
return type.castToType({
|
|
951
|
+
connection: this.getConnection(),
|
|
952
|
+
Model: this.getModel(),
|
|
953
|
+
self: this,
|
|
954
|
+
value,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
_initializeFieldData(fieldName, field, fieldValue, data) {
|
|
959
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
960
|
+
let fieldData = this._fieldData;
|
|
961
|
+
let defaultValue = fieldValue;
|
|
962
|
+
|
|
963
|
+
// If the attribute given by "data" is a function
|
|
964
|
+
// then we always want to call it
|
|
965
|
+
if (typeof defaultValue === 'function')
|
|
966
|
+
defaultValue = defaultValue({ field, fieldName, fieldValue, data, _initial: true });
|
|
967
|
+
|
|
968
|
+
// If data provided no value, then fallback
|
|
969
|
+
// to trying "defaultValue" key from field schema
|
|
970
|
+
if (defaultValue === undefined)
|
|
971
|
+
defaultValue = field.defaultValue;
|
|
972
|
+
|
|
973
|
+
if (typeof defaultValue === 'function') {
|
|
974
|
+
const shouldRunDefaultValueOnInitialize = () => {
|
|
975
|
+
// No flags means we are running on initialize
|
|
976
|
+
if (!defaultValue.mythixFlags)
|
|
977
|
+
return true;
|
|
978
|
+
|
|
979
|
+
// Are we running on initialize?
|
|
980
|
+
if (defaultValue.mythixFlags & DefaultHelpers.FLAG_ON_INITIALIZE)
|
|
981
|
+
return true;
|
|
982
|
+
|
|
983
|
+
return false;
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
if (shouldRunDefaultValueOnInitialize()) {
|
|
987
|
+
defaultValue = defaultValue({
|
|
988
|
+
model: this,
|
|
989
|
+
connection: this.getConnection(),
|
|
990
|
+
_initial: true,
|
|
991
|
+
field,
|
|
992
|
+
fieldName,
|
|
993
|
+
fieldValue,
|
|
994
|
+
data,
|
|
995
|
+
});
|
|
996
|
+
} else {
|
|
997
|
+
defaultValue = undefined;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (defaultValue === undefined || !data)
|
|
1002
|
+
fieldData[fieldName] = (defaultValue != null) ? this._castFieldValue(field, defaultValue) : defaultValue;
|
|
1003
|
+
else
|
|
1004
|
+
dirtyFieldData[fieldName] = this._castFieldValue(field, defaultValue);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
_getDirtyFields(_options) {
|
|
1008
|
+
let options = _options || {};
|
|
1009
|
+
let fieldData = this._fieldData;
|
|
1010
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
1011
|
+
let dirtyFields = {};
|
|
1012
|
+
let connection = this.getConnection();
|
|
1013
|
+
let queryGenerator = (connection) ? connection.getQueryGenerator() : null;
|
|
1014
|
+
|
|
1015
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1016
|
+
if (field.type.isVirtual())
|
|
1017
|
+
return;
|
|
1018
|
+
|
|
1019
|
+
if (Object.prototype.hasOwnProperty.call(dirtyFieldData, fieldName)) {
|
|
1020
|
+
dirtyFields[fieldName] = { previous: fieldData[fieldName], current: dirtyFieldData[fieldName] };
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (connection && typeof connection.dirtyFieldHelper === 'function') {
|
|
1025
|
+
let value = connection.dirtyFieldHelper({ options, fieldData, dirtyFieldData, dirtyFields, field, fieldName });
|
|
1026
|
+
if (value !== undefined) {
|
|
1027
|
+
// Cache this result so subsequent calls
|
|
1028
|
+
// to dirty fields doesn't get a new value
|
|
1029
|
+
dirtyFieldData[fieldName] = value;
|
|
1030
|
+
|
|
1031
|
+
dirtyFields[fieldName] = {
|
|
1032
|
+
previous: fieldData[fieldName],
|
|
1033
|
+
current: value,
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (!queryGenerator)
|
|
1041
|
+
return;
|
|
1042
|
+
|
|
1043
|
+
if (options.update && DefaultHelpers.checkDefaultValueFlags(field.defaultValue, [ 'onUpdate' ])) {
|
|
1044
|
+
let value = queryGenerator.getFieldDefaultValue(field, fieldName, Object.assign({ isUpdateOperation: true, rawLiterals: true, escape: false }));
|
|
1045
|
+
|
|
1046
|
+
// Cache this result so subsequent calls
|
|
1047
|
+
// to dirty fields doesn't get a new value
|
|
1048
|
+
dirtyFieldData[fieldName] = value;
|
|
1049
|
+
|
|
1050
|
+
dirtyFields[fieldName] = {
|
|
1051
|
+
previous: fieldData[fieldName],
|
|
1052
|
+
current: value,
|
|
1053
|
+
};
|
|
1054
|
+
} else if (options.insert && DefaultHelpers.checkDefaultValueFlags(field.defaultValue, [ 'onInsert' ])) {
|
|
1055
|
+
let value = queryGenerator.getFieldDefaultValue(field, fieldName, Object.assign({ isInsertOperation: true, rawLiterals: true, escape: false }));
|
|
1056
|
+
|
|
1057
|
+
// Cache this result so subsequent calls
|
|
1058
|
+
// to dirty fields doesn't get a new value
|
|
1059
|
+
dirtyFieldData[fieldName] = value;
|
|
1060
|
+
|
|
1061
|
+
dirtyFields[fieldName] = {
|
|
1062
|
+
previous: fieldData[fieldName],
|
|
1063
|
+
current: value,
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
return dirtyFields;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
_getFieldValue(fieldName, field) {
|
|
1072
|
+
let value = this.getDataValue(fieldName);
|
|
1073
|
+
|
|
1074
|
+
if (typeof field.get === 'function')
|
|
1075
|
+
return field.get.call(this, { value, field, fieldName });
|
|
1076
|
+
|
|
1077
|
+
return value;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
_setFieldValue(fieldName, field, value) {
|
|
1081
|
+
if (typeof field.set === 'function') {
|
|
1082
|
+
field.set.call(this, { value, field, fieldName });
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
this.setDataValue(fieldName, value);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
isPersisted() {
|
|
1090
|
+
return this._persisted;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
updateDirtyID() {
|
|
1094
|
+
this.dirtyID = new CacheKey(this.dirtyID);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
isDirty(fieldName) {
|
|
1098
|
+
if (!fieldName)
|
|
1099
|
+
return (Object.keys(this._dirtyFieldData).length > 0);
|
|
1100
|
+
else
|
|
1101
|
+
return Object.prototype.hasOwnProperty.call(this._dirtyFieldData, fieldName);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
clearDirty(fieldName) {
|
|
1105
|
+
if (fieldName && Object.prototype.hasOwnProperty.call(this._dirtyFieldData, fieldName)) {
|
|
1106
|
+
this._fieldData[fieldName] = this._dirtyFieldData[fieldName];
|
|
1107
|
+
delete this._dirtyFieldData[fieldName];
|
|
1108
|
+
} else {
|
|
1109
|
+
Object.assign(this._fieldData, this._dirtyFieldData);
|
|
1110
|
+
this._dirtyFieldData = {};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
this.updateDirtyID();
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
getDirtyFields(options) {
|
|
1117
|
+
let modelChanges = this._getDirtyFields(options);
|
|
1118
|
+
let dirtyFieldNames = Object.keys(modelChanges);
|
|
1119
|
+
|
|
1120
|
+
return this.getFields(dirtyFieldNames);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
getDataValue(fieldName) {
|
|
1124
|
+
let fieldData = this._fieldData;
|
|
1125
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
1126
|
+
let value;
|
|
1127
|
+
|
|
1128
|
+
if (Object.prototype.hasOwnProperty.call(dirtyFieldData, fieldName))
|
|
1129
|
+
value = dirtyFieldData[fieldName];
|
|
1130
|
+
else
|
|
1131
|
+
value = fieldData[fieldName];
|
|
1132
|
+
|
|
1133
|
+
return value;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
setDataValue(fieldName, value) {
|
|
1137
|
+
let fieldData = this._fieldData;
|
|
1138
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
1139
|
+
let field = this.getField(fieldName);
|
|
1140
|
+
|
|
1141
|
+
if (!field)
|
|
1142
|
+
throw new Error(`${this.getModelName()}::setDataValue: Unable to find field named "${fieldName}".`);
|
|
1143
|
+
|
|
1144
|
+
let newValue = this._castFieldValue(field, value);
|
|
1145
|
+
|
|
1146
|
+
// If the values are exactly the same,
|
|
1147
|
+
// then we are no longer dirty, and
|
|
1148
|
+
// can just return
|
|
1149
|
+
if (fieldData[fieldName] === newValue) {
|
|
1150
|
+
delete dirtyFieldData[fieldName];
|
|
1151
|
+
|
|
1152
|
+
this.updateDirtyID();
|
|
1153
|
+
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
dirtyFieldData[fieldName] = newValue;
|
|
1158
|
+
|
|
1159
|
+
this.updateDirtyID();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
getAttributes() {
|
|
1163
|
+
let result = {};
|
|
1164
|
+
|
|
1165
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1166
|
+
if (field.type.isVirtual())
|
|
1167
|
+
return;
|
|
1168
|
+
|
|
1169
|
+
let fieldValue = this[fieldName];
|
|
1170
|
+
if (fieldValue === undefined)
|
|
1171
|
+
return;
|
|
1172
|
+
|
|
1173
|
+
result[fieldName] = fieldValue;
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
return result;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
setAttributes(attributes, noPrimaryKey) {
|
|
1180
|
+
let isObject = Nife.instanceOf(attributes, 'object');
|
|
1181
|
+
|
|
1182
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1183
|
+
if (field.type.isVirtual())
|
|
1184
|
+
return;
|
|
1185
|
+
|
|
1186
|
+
if (field.primaryKey === true && noPrimaryKey === true)
|
|
1187
|
+
return;
|
|
1188
|
+
|
|
1189
|
+
if (isObject && !Object.prototype.hasOwnProperty.call(attributes, fieldName))
|
|
1190
|
+
return;
|
|
1191
|
+
|
|
1192
|
+
let fieldValue = attributes[fieldName];
|
|
1193
|
+
if (fieldValue === undefined)
|
|
1194
|
+
return;
|
|
1195
|
+
|
|
1196
|
+
this[fieldName] = fieldValue;
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
hasValidPrimaryKey() {
|
|
1201
|
+
let pkField = this.getPrimaryKeyField();
|
|
1202
|
+
if (!pkField)
|
|
1203
|
+
return false;
|
|
1204
|
+
|
|
1205
|
+
let pkFieldName = pkField.fieldName;
|
|
1206
|
+
let pkValue = this[pkFieldName];
|
|
1207
|
+
if (pkValue == null)
|
|
1208
|
+
return false;
|
|
1209
|
+
|
|
1210
|
+
return pkField.type.isValidValue(pkValue, {
|
|
1211
|
+
connection: this.getConnection(),
|
|
1212
|
+
Model: this.getModel(),
|
|
1213
|
+
self: this,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async onValidate(context) {
|
|
1218
|
+
let promises = [];
|
|
1219
|
+
|
|
1220
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1221
|
+
if (field.type.isVirtual())
|
|
1222
|
+
return;
|
|
1223
|
+
|
|
1224
|
+
if (typeof field.validate !== 'function')
|
|
1225
|
+
return;
|
|
1226
|
+
|
|
1227
|
+
try {
|
|
1228
|
+
let fieldValue = this[fieldName];
|
|
1229
|
+
let promise = field.validate.call(this, fieldValue, context);
|
|
1230
|
+
if (!(promise instanceof Promise))
|
|
1231
|
+
promise = Promise.resolve(promise);
|
|
1232
|
+
|
|
1233
|
+
promises.push(promise);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
promises.push(Promise.reject(error));
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
await Promise.all(promises);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// eslint-disable-next-line no-unused-vars
|
|
1243
|
+
async onBeforeCreate(context) {
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// eslint-disable-next-line no-unused-vars
|
|
1247
|
+
async onBeforeUpdate(context) {
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// eslint-disable-next-line no-unused-vars
|
|
1251
|
+
async onBeforeSave(context) {
|
|
1252
|
+
await this.onValidate(context);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// eslint-disable-next-line no-unused-vars
|
|
1256
|
+
async onAfterCreate(context) {
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// eslint-disable-next-line no-unused-vars
|
|
1260
|
+
async onAfterUpdate(context) {
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// eslint-disable-next-line no-unused-vars
|
|
1264
|
+
async onAfterSave(context) {
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async save(_options) {
|
|
1268
|
+
let options = _options || {};
|
|
1269
|
+
|
|
1270
|
+
if (options.force !== true && Nife.isEmpty(this.changes)) {
|
|
1271
|
+
return false;
|
|
1272
|
+
} else if (options.force) {
|
|
1273
|
+
// Mark all fields as dirty
|
|
1274
|
+
let dirtyFieldData = this._dirtyFieldData;
|
|
1275
|
+
|
|
1276
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1277
|
+
if (field.type.isVirtual())
|
|
1278
|
+
return;
|
|
1279
|
+
|
|
1280
|
+
dirtyFieldData[fieldName] = this[fieldName];
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
let connection = this.getConnection(options.connection);
|
|
1285
|
+
|
|
1286
|
+
if (this.isPersisted())
|
|
1287
|
+
await connection.update(this.getModel(), [ this ], options);
|
|
1288
|
+
else
|
|
1289
|
+
await connection.insert(this.getModel(), [ this ], options);
|
|
1290
|
+
|
|
1291
|
+
this.clearDirty();
|
|
1292
|
+
|
|
1293
|
+
return this;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async reload(options) {
|
|
1297
|
+
let pkFieldName = this.getPrimaryKeyFieldName();
|
|
1298
|
+
if (!pkFieldName)
|
|
1299
|
+
throw new Error(`${this.getModelName()}::reload: Unable to reload models that have no primary key defined.`);
|
|
1300
|
+
|
|
1301
|
+
let id = this[pkFieldName];
|
|
1302
|
+
if (!id)
|
|
1303
|
+
return;
|
|
1304
|
+
|
|
1305
|
+
let storedModel = await this.getWhereWithConnection(options)[pkFieldName].EQ(id).first();
|
|
1306
|
+
if (!storedModel)
|
|
1307
|
+
return;
|
|
1308
|
+
|
|
1309
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1310
|
+
if (field.type.isVirtual())
|
|
1311
|
+
return;
|
|
1312
|
+
|
|
1313
|
+
let fieldValue = storedModel[fieldName];
|
|
1314
|
+
this[fieldName] = fieldValue;
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
this._persisted = true;
|
|
1318
|
+
this.clearDirty();
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
async destroy(options) {
|
|
1322
|
+
if (!this.isPersisted())
|
|
1323
|
+
return 0;
|
|
1324
|
+
|
|
1325
|
+
let primaryKeyFieldName = this.getPrimaryKeyFieldName();
|
|
1326
|
+
if (!primaryKeyFieldName || !this[primaryKeyFieldName])
|
|
1327
|
+
throw new Error(`${this.getModelName()}::destroy: Model has no primary key field, or value. Refusing to destroy this model to prevent possible data loss. Please call "connection.destroy" yourself, manually providing your own query.`);
|
|
1328
|
+
|
|
1329
|
+
return await this.getModel().getWhereWithConnection(options)[primaryKeyFieldName].EQ(this.id).destroy(options);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
toString() {
|
|
1333
|
+
return `${this.getModelName()} ${JSON.stringify(this.toJSON(), undefined, 2)}`;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
toJSON() {
|
|
1337
|
+
let result = {};
|
|
1338
|
+
let pkField = this.getPrimaryKeyField();
|
|
1339
|
+
|
|
1340
|
+
// PK always comes first
|
|
1341
|
+
if (pkField) {
|
|
1342
|
+
let pkFieldName = pkField.fieldName;
|
|
1343
|
+
let fieldValue = this[pkFieldName];
|
|
1344
|
+
|
|
1345
|
+
if (fieldValue !== undefined)
|
|
1346
|
+
result[pkFieldName] = pkField.type.serialize(fieldValue);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
this.iterateFields(({ field, fieldName }) => {
|
|
1350
|
+
if (field.type.isVirtual())
|
|
1351
|
+
return;
|
|
1352
|
+
|
|
1353
|
+
if (field.primaryKey)
|
|
1354
|
+
return;
|
|
1355
|
+
|
|
1356
|
+
let fieldValue = this[fieldName];
|
|
1357
|
+
if (fieldValue === undefined)
|
|
1358
|
+
return;
|
|
1359
|
+
|
|
1360
|
+
result[fieldName] = field.type.serialize(fieldValue);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
return result;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// eslint-disable-next-line no-unused-vars
|
|
1367
|
+
[Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) {
|
|
1368
|
+
return inspect(this.toJSON(), inspectOptions);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
bindStaticWhereToModelClass(Model);
|
|
1373
|
+
|
|
1374
|
+
module.exports = Model;
|