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.
Files changed (124) hide show
  1. package/.eslintrc.js +94 -0
  2. package/.vscode/launch.json +34 -0
  3. package/.vscode/settings.json +10 -0
  4. package/LICENSE +21 -0
  5. package/README.md +15 -0
  6. package/lib/connection/connection-base.js +877 -0
  7. package/lib/connection/index.js +11 -0
  8. package/lib/connection/literals/average-literal.js +14 -0
  9. package/lib/connection/literals/count-literal.js +18 -0
  10. package/lib/connection/literals/distinct-literal.js +14 -0
  11. package/lib/connection/literals/index.js +23 -0
  12. package/lib/connection/literals/literal-base.js +62 -0
  13. package/lib/connection/literals/literal-field-base.js +45 -0
  14. package/lib/connection/literals/literal.js +11 -0
  15. package/lib/connection/literals/max-literal.js +14 -0
  16. package/lib/connection/literals/min-literal.js +14 -0
  17. package/lib/connection/literals/sum-literal.js +14 -0
  18. package/lib/connection/query-generator-base.js +864 -0
  19. package/lib/errors/base-error.js +14 -0
  20. package/lib/errors/connection/access-denied-error.js +13 -0
  21. package/lib/errors/connection/connection-acquire-timeout-error.js +13 -0
  22. package/lib/errors/connection/connection-refused-error.js +13 -0
  23. package/lib/errors/connection/connection-timed-out-error.js +13 -0
  24. package/lib/errors/connection/host-not-found-error.js +13 -0
  25. package/lib/errors/connection/host-not-reachable-error.js +13 -0
  26. package/lib/errors/connection/index.js +19 -0
  27. package/lib/errors/connection/invalid-connection-error.js +13 -0
  28. package/lib/errors/connection-base-error.js +13 -0
  29. package/lib/errors/database/exclusion-constraint-error.js +13 -0
  30. package/lib/errors/database/foreign-key-constraint-error.js +13 -0
  31. package/lib/errors/database/index.js +13 -0
  32. package/lib/errors/database/timeout-error.js +13 -0
  33. package/lib/errors/database/unknown-constraint-error.js +13 -0
  34. package/lib/errors/database-base-error.js +17 -0
  35. package/lib/errors/index.js +44 -0
  36. package/lib/field.js +18 -0
  37. package/lib/index.js +43 -0
  38. package/lib/model.js +1374 -0
  39. package/lib/proxy-class/index.js +3 -0
  40. package/lib/proxy-class/proxy-class.js +269 -0
  41. package/lib/query-engine/field-scope.js +99 -0
  42. package/lib/query-engine/index.js +13 -0
  43. package/lib/query-engine/model-scope.js +198 -0
  44. package/lib/query-engine/query-engine-base.js +325 -0
  45. package/lib/query-engine/query-engine.js +212 -0
  46. package/lib/types/concrete/bigint-type.js +62 -0
  47. package/lib/types/concrete/blob-type.js +46 -0
  48. package/lib/types/concrete/boolean-type.js +41 -0
  49. package/lib/types/concrete/char-type.js +39 -0
  50. package/lib/types/concrete/date-type.js +87 -0
  51. package/lib/types/concrete/datetime-type.js +92 -0
  52. package/lib/types/concrete/float-type.js +47 -0
  53. package/lib/types/concrete/foreign-key-type.js +208 -0
  54. package/lib/types/concrete/index.js +53 -0
  55. package/lib/types/concrete/integer-type.js +51 -0
  56. package/lib/types/concrete/string-type.js +44 -0
  57. package/lib/types/concrete/text-type.js +44 -0
  58. package/lib/types/concrete/uuid-base.js +77 -0
  59. package/lib/types/concrete/uuid-v1-type.js +99 -0
  60. package/lib/types/concrete/uuid-v3-type.js +108 -0
  61. package/lib/types/concrete/uuid-v4-type.js +90 -0
  62. package/lib/types/concrete/uuid-v5-type.js +108 -0
  63. package/lib/types/concrete/xid-type.js +58 -0
  64. package/lib/types/helpers/default-helpers.js +127 -0
  65. package/lib/types/helpers/index.js +35 -0
  66. package/lib/types/index.js +94 -0
  67. package/lib/types/type.js +244 -0
  68. package/lib/types/virtual/index.js +14 -0
  69. package/lib/types/virtual/model-type.js +141 -0
  70. package/lib/types/virtual/models-type.js +264 -0
  71. package/lib/types/virtual/relational-type-base.js +323 -0
  72. package/lib/utils/index.js +65 -0
  73. package/lib/utils/misc-utils.js +73 -0
  74. package/lib/utils/model-utils.js +704 -0
  75. package/lib/utils/query-utils.js +186 -0
  76. package/package.json +63 -0
  77. package/playground/test01.js +15 -0
  78. package/spec/connection/connection-base-spec.js +126 -0
  79. package/spec/connection/literals/average-literal-spec.js +45 -0
  80. package/spec/connection/literals/count-literal-spec.js +42 -0
  81. package/spec/connection/literals/distinct-literal-spec.js +42 -0
  82. package/spec/connection/literals/literal-spec.js +26 -0
  83. package/spec/connection/literals/max-literal-spec.js +42 -0
  84. package/spec/connection/literals/min-literal-spec.js +42 -0
  85. package/spec/connection/literals/sum-literal-spec.js +42 -0
  86. package/spec/helpers/default-helpers-spec.js +108 -0
  87. package/spec/model-spec.js +736 -0
  88. package/spec/proxy-class/proxy-class-spec.js +173 -0
  89. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_chain_query_conditions-001.snapshot +94 -0
  90. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_call-001.snapshot +35 -0
  91. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_sub-001.snapshot +35 -0
  92. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_set_a_default_scope_on_a_model-001.snapshot +57 -0
  93. package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_unscope_default_scope_on_a_model-001.snapshot +35 -0
  94. package/spec/query-engine/query-engine-spec.js +99 -0
  95. package/spec/support/jasmine.json +13 -0
  96. package/spec/support/models/blob-test-model.js +19 -0
  97. package/spec/support/models/extended-user-model.js +38 -0
  98. package/spec/support/models/index.js +27 -0
  99. package/spec/support/models/number-model.js +24 -0
  100. package/spec/support/models/role-model.js +26 -0
  101. package/spec/support/models/role-thing-model.js +41 -0
  102. package/spec/support/models/scoped-user-model.js +13 -0
  103. package/spec/support/models/time-model.js +36 -0
  104. package/spec/support/models/user-model.js +70 -0
  105. package/spec/support/models/user-role-model.js +36 -0
  106. package/spec/support/models/user-thing-model.js +46 -0
  107. package/spec/support/models/validation-test-model.js +40 -0
  108. package/spec/support/snapshots.js +293 -0
  109. package/spec/support/test-helpers.js +13 -0
  110. package/spec/types/concrete/bigint-type-spec.js +84 -0
  111. package/spec/types/concrete/boolean-type-spec.js +83 -0
  112. package/spec/types/concrete/date-type-spec.js +85 -0
  113. package/spec/types/concrete/datetime-type-spec.js +87 -0
  114. package/spec/types/concrete/float-type-spec.js +71 -0
  115. package/spec/types/concrete/foreign-key-type-spec.js +64 -0
  116. package/spec/types/concrete/integer-type-spec.js +71 -0
  117. package/spec/types/concrete/string-type-spec.js +91 -0
  118. package/spec/types/concrete/uuid-v1-type-spec.js +73 -0
  119. package/spec/types/concrete/uuid-v4-type-spec.js +65 -0
  120. package/spec/types/type-spec.js +101 -0
  121. package/spec/types/virtual/model-types-spec.js +401 -0
  122. package/spec/utils/misc-utils-spec.js +61 -0
  123. package/spec/utils/model-utils-spec.js +55 -0
  124. 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;