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
@@ -0,0 +1,704 @@
1
+ 'use strict';
2
+
3
+ const Nife = require('nife');
4
+ const UUID = require('uuid');
5
+
6
+ function isUUID(value) {
7
+ return UUID.validate(value);
8
+ }
9
+
10
+ function sanitizeFieldString(str) {
11
+ return ('' + str).replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
12
+ }
13
+
14
+ function parseQualifiedName(str) {
15
+ let fieldNames = [];
16
+ let modelName;
17
+
18
+ let parts = sanitizeFieldString(str).split(/:/).filter(Boolean);
19
+ if (parts.length > 1) {
20
+ modelName = parts[0];
21
+
22
+ let fieldPart = parts.slice(1).join('');
23
+ if (fieldPart !== '*')
24
+ fieldNames = fieldPart.split(/\.+/g).filter(Boolean);
25
+ } else {
26
+ if ((/^[A-Z]/).test('' + parts[0]))
27
+ return { modelName: parts[0], fieldNames: [] };
28
+
29
+ fieldNames = parts.join('').split(/\.+/g).filter(Boolean);
30
+
31
+ if (fieldNames.length === 1 && fieldNames[0].match(/^[A-Z]/)) {
32
+ modelName = fieldNames[0];
33
+ fieldNames = [];
34
+ }
35
+ }
36
+
37
+ return { modelName, fieldNames };
38
+ }
39
+
40
+ function injectModelMethod(self, method, methodName, fullMethodName) {
41
+ Object.defineProperties(self, {
42
+ [fullMethodName]: {
43
+ writable: true,
44
+ enumberable: false,
45
+ configurable: true,
46
+ value: method,
47
+ },
48
+ });
49
+
50
+ if (!(methodName in self)) {
51
+ Object.defineProperties(self, {
52
+ [methodName]: {
53
+ writable: true,
54
+ enumberable: false,
55
+ configurable: true,
56
+ value: function(...args) {
57
+ return method.apply(this, args);
58
+ },
59
+ },
60
+ });
61
+ }
62
+ }
63
+
64
+ function fieldToFullyQualifiedName(field, Model) {
65
+ if (!field)
66
+ return;
67
+
68
+ if (field.Model && field.fieldName)
69
+ return `${field.Model.getModelName()}:${field.fieldName}`;
70
+
71
+ if (!Nife.instanceOf(field, 'string'))
72
+ return;
73
+
74
+ let def = parseQualifiedName(field);
75
+ if (!def.modelName && Model)
76
+ def.modelName = Model.getModelName();
77
+
78
+ if (Nife.isEmpty(def.modelName) || Nife.isEmpty(def.fieldNames))
79
+ return;
80
+
81
+ return `${def.modelName}:${def.fieldNames[0]}`;
82
+ }
83
+
84
+ function sortModelNamesByDependencyOrder(connection, modelNames, dependencyHelper) {
85
+ let modelOrderMap = {};
86
+ for (let i = 0, il = modelNames.length; i < il; i++) {
87
+ let modelName = modelNames[i];
88
+ let Model = connection.getModel(modelName);
89
+ let dependentModelNames = dependencyHelper(Model, modelName);
90
+
91
+ modelOrderMap[modelName] = dependentModelNames;
92
+ }
93
+
94
+ let sortedModelNames = modelNames.sort((a, b) => {
95
+ if (a === b)
96
+ return 0;
97
+
98
+ let orderA = modelOrderMap[a];
99
+ let orderB = modelOrderMap[b];
100
+
101
+ if (orderA.indexOf(b) >= 0)
102
+ return 1;
103
+
104
+ if (orderB.indexOf(a) >= 0)
105
+ return -1;
106
+
107
+ return Math.sign(orderA.length - orderB.length);
108
+ });
109
+
110
+ return sortedModelNames;
111
+ }
112
+
113
+ function sortModelNamesByCreationOrder(connection, modelNames) {
114
+ const getForeignKeyTargets = (Model) => {
115
+ let relatedModelNames = new Set();
116
+
117
+ Model.iterateFields(({ field }) => {
118
+ let fieldType = field.type;
119
+ if (!fieldType.isForeignKey())
120
+ return;
121
+
122
+ let TargetModel = fieldType.getTargetModel(connection);
123
+ if (!TargetModel)
124
+ return;
125
+
126
+ let targetModelName = TargetModel.getModelName();
127
+ relatedModelNames.add(targetModelName);
128
+ });
129
+
130
+ return Array.from(relatedModelNames.values());
131
+ };
132
+
133
+ return sortModelNamesByDependencyOrder(connection, modelNames, getForeignKeyTargets);
134
+ }
135
+
136
+ async function getRelationalModelStatusForField(connection, self, field, ...args) {
137
+ if (!field || !field.type.isRelational())
138
+ return { relationalMap: {} };
139
+
140
+ let PrimaryModel = self.getModel();
141
+ let primaryModelName = self.getModelName();
142
+ let relationalMap = {
143
+ [primaryModelName]: {
144
+ create: !self.isPersisted(),
145
+ instance: self,
146
+ modelName: primaryModelName,
147
+ Model: PrimaryModel,
148
+ fields: new Map(),
149
+ },
150
+ };
151
+
152
+ const hasValidPrimaryKey = (model) => {
153
+ if (!model)
154
+ return false;
155
+
156
+ if (typeof model.hasValidPrimaryKey === 'function')
157
+ return model.hasValidPrimaryKey();
158
+
159
+ return false;
160
+ };
161
+
162
+ const addModelStatus = (relation, opposingRelation, swapFieldOrder, _status) => {
163
+ if (relation.field && relation.field.type.isRelational() && relation.field.type.isManyRelation())
164
+ return;
165
+
166
+ if (opposingRelation.field && opposingRelation.field.type.isRelational() && opposingRelation.field.type.isManyRelation())
167
+ return;
168
+
169
+ let status = _status || {};
170
+ let relationFieldName = (relation.fieldName) ? `:${relation.fieldName}` : '';
171
+ let relationFQName = `${relation.modelName}${relationFieldName}`;
172
+ let opposingRelationFieldName = (opposingRelation.fieldName) ? `:${opposingRelation.fieldName}` : '';
173
+ let opposingRelationFQName = `${opposingRelation.modelName}${opposingRelationFieldName}`;
174
+ let fields;
175
+
176
+ let modelRelation = relationalMap[relation.modelName];
177
+ if (!modelRelation) {
178
+ fields = new Map();
179
+ relationalMap[relation.modelName] = Object.assign(status, { create: !status.instance, Model: relation.Model, modelName: relation.modelName }, { fields });
180
+ } else {
181
+ let instance = modelRelation.instance || status.instance;
182
+
183
+ fields = modelRelation.fields || new Map();
184
+ relationalMap[relation.modelName] = Object.assign(relationalMap[relation.modelName], status, { create: !instance, instance, fields });
185
+ }
186
+
187
+ // this key/value pair must be flipped so the
188
+ // parent faces the child. This is so that the field
189
+ // set can hold many model types and fields.
190
+ fields.set((swapFieldOrder) ? relationFQName : opposingRelationFQName, (swapFieldOrder) ? opposingRelationFQName : relationFQName);
191
+ };
192
+
193
+ const findAvailableRelationalModel = (modelName, field) => {
194
+ if (modelName === primaryModelName)
195
+ return self;
196
+
197
+ if (!field)
198
+ return;
199
+
200
+ if (!field.type.isRelational())
201
+ return;
202
+
203
+ // Don't attempt multi-relational fields
204
+ if (field.type.isManyRelation())
205
+ return;
206
+
207
+ let relation = relationalMap[modelName];
208
+ let instance = (relation) ? relation.instance : null;
209
+ if (!instance)
210
+ return;
211
+
212
+ let value = instance[field.fieldName];
213
+ if (Array.isArray(value))
214
+ return; // we need to create
215
+
216
+ return value;
217
+ };
218
+
219
+ const addRelationalStatus = async (source, target) => {
220
+ if (field.type.isManyRelation()) {
221
+ // we need to create
222
+ addModelStatus(source, target);
223
+ addModelStatus(target, source);
224
+ return;
225
+ }
226
+
227
+ let model = findAvailableRelationalModel(source.modelName, source.field);
228
+ if (!hasValidPrimaryKey(model))
229
+ addModelStatus(source, target, { instance: model });
230
+ else
231
+ addModelStatus(source, target, { instance: model });
232
+
233
+ model = findAvailableRelationalModel(target.modelName, target.field);
234
+ if (!hasValidPrimaryKey(model))
235
+ addModelStatus(target, source, { instance: model });
236
+ else
237
+ addModelStatus(target, source, { instance: model });
238
+ };
239
+
240
+ let TargetModel;
241
+ let TargetField;
242
+
243
+ await field.type.walkQueryRelations(connection, async ({ TargetModel: _TargetModel, TargetField: _TargetField, source, target }) => {
244
+ if (!TargetModel)
245
+ TargetModel = _TargetModel;
246
+
247
+ if (!TargetField)
248
+ TargetField = _TargetField;
249
+
250
+ await addRelationalStatus(source, target);
251
+ }, { self, field: field }, ...args);
252
+
253
+ return { relationalMap, TargetModel, TargetField };
254
+ }
255
+
256
+ async function constructModelsForCreationFromOriginField(connection, self, field, attributes) {
257
+ if (!field)
258
+ return;
259
+
260
+ if (!field.type.isRelational())
261
+ return;
262
+
263
+ const getRelationalModel = (relationalMap, modelName) => {
264
+ let status = relationalMap[modelName];
265
+ return (status) ? status.instance : null;
266
+ };
267
+
268
+ let {
269
+ relationalMap,
270
+ TargetModel,
271
+ } = await getRelationalModelStatusForField(connection, self, field);
272
+
273
+ let targetModelName = TargetModel.getModelName();
274
+ let sortedModelNames = sortModelNamesByCreationOrder(connection, Array.from(Object.keys(relationalMap)));
275
+
276
+ for (let i = 0, il = sortedModelNames.length; i < il; i++) {
277
+ let modelName = sortedModelNames[i];
278
+ let relationStatus = relationalMap[modelName];
279
+ let RelatedModel = relationStatus.Model;
280
+ let thisModelInstance = relationStatus.instance;
281
+
282
+ if (!(thisModelInstance instanceof RelatedModel)) {
283
+ let modelAttributes = relationStatus.instance;
284
+ if (modelName === targetModelName)
285
+ modelAttributes = attributes;
286
+
287
+ // Is this a persisted model?
288
+ if (modelAttributes instanceof RelatedModel && modelAttributes.isPersisted())
289
+ thisModelInstance = modelAttributes;
290
+ else
291
+ thisModelInstance = new RelatedModel(modelAttributes, { connection });
292
+ }
293
+
294
+ setRelationalValues(connection, TargetModel, thisModelInstance, self.getModel(), self);
295
+ relationStatus.create = !thisModelInstance.isPersisted();
296
+ relationStatus.instance = thisModelInstance;
297
+
298
+ for (let [ sourceFQField, targetFQField ] of relationStatus.fields.entries()) {
299
+ let targetDef = parseQualifiedName(targetFQField);
300
+ if (Nife.isEmpty(targetDef.fieldNames))
301
+ continue;
302
+
303
+ let targetFieldName = targetDef.fieldNames[0];
304
+ let TargetField = connection.getField(targetFieldName, targetDef.modelName);
305
+
306
+ // We never assign to the primaryID
307
+ if (TargetField.primaryKey)
308
+ continue;
309
+
310
+ let sourceDef = parseQualifiedName(sourceFQField);
311
+ let relatedModelInstance = (modelName === sourceDef.modelName) ? thisModelInstance : getRelationalModel(relationalMap, sourceDef.modelName);
312
+ if (!relatedModelInstance)
313
+ continue;
314
+
315
+ // If target field is relational then that
316
+ // means we want to assign the entire model.
317
+ if (TargetField.type.isRelational()) {
318
+ // We don't play around with many relational fields
319
+ if (TargetField.type.isManyRelation())
320
+ continue;
321
+
322
+ thisModelInstance[targetDef.fieldNames[0]] = relatedModelInstance;
323
+ continue;
324
+ }
325
+
326
+ thisModelInstance[targetDef.fieldNames[0]] = relatedModelInstance[sourceDef.fieldNames[0]];
327
+ }
328
+ }
329
+
330
+ return { relationalMap, sortedModelNames };
331
+ }
332
+
333
+ async function createAndSaveAllRelatedModels(connection, self, field, allModelAttributes, options) {
334
+ if (!self)
335
+ return;
336
+
337
+ if (!self.isPersisted())
338
+ throw new Error('ModelUtils::createAndSaveAllRelatedModels: Parent model must be persisted before you attempt to save related child models.');
339
+
340
+ // Find related models that need to be created
341
+ let TargetModel = field.type.getTargetModel(connection);
342
+ let targetModelName = TargetModel.getModelName();
343
+ let parentModelName = self.getModelName();
344
+ let storedModelMap = new Map();
345
+ let parentModelSet = new Set();
346
+ let relatedInfos = [];
347
+
348
+ parentModelSet.add(self);
349
+ storedModelMap.set(parentModelName, parentModelSet);
350
+
351
+ for (let i = 0, il = allModelAttributes.length; i < il; i++) {
352
+ let modelAttributes = allModelAttributes[i];
353
+ if (!modelAttributes)
354
+ continue;
355
+
356
+ let result = await constructModelsForCreationFromOriginField(connection, self, field, modelAttributes);
357
+ relatedInfos.push(result);
358
+ }
359
+
360
+ // Create a map of all model instances by type
361
+ // so that we can bulk insert models
362
+ let fullModelMap = new Map();
363
+ let relationalModelMap = new Map();
364
+ let persistedModelMap = new Map();
365
+ let index = 0;
366
+
367
+ for (let i = 0, il = relatedInfos.length; i < il; i++) {
368
+ let info = relatedInfos[i];
369
+ let { relationalMap, sortedModelNames } = info;
370
+
371
+ for (let j = 0, jl = sortedModelNames.length; j < jl; j++) {
372
+ let modelName = sortedModelNames[j];
373
+ let modelSet = fullModelMap.get(modelName);
374
+ if (!modelSet) {
375
+ modelSet = new Set();
376
+ fullModelMap.set(modelName, modelSet);
377
+ }
378
+
379
+ let thisStatus = relationalMap[modelName];
380
+ let thisModelInstance = thisStatus.instance;
381
+
382
+ if (!thisModelInstance.isPersisted()) {
383
+ thisModelInstance.__order = index;
384
+ modelSet.add(thisModelInstance);
385
+ } else {
386
+ let storedSet = storedModelMap.get(modelName);
387
+ if (!storedSet) {
388
+ storedSet = new Set();
389
+ storedModelMap.set(modelName, storedSet);
390
+ }
391
+
392
+ thisModelInstance.__order = index;
393
+ storedSet.add(thisModelInstance);
394
+
395
+ let persistedSet = persistedModelMap.get(modelName);
396
+ if (!persistedSet) {
397
+ persistedSet = new Set();
398
+ persistedModelMap.set(modelName, persistedSet);
399
+ }
400
+
401
+ persistedSet.add(thisModelInstance);
402
+ }
403
+
404
+ index++;
405
+
406
+ relationalModelMap.set(thisModelInstance, info);
407
+ }
408
+ }
409
+
410
+ const updateRelatedModelAttributes = (SetModel, modelName, models, assignRelated) => {
411
+ // Iterate all models and update their
412
+ // related model attributes
413
+ for (let model of models) {
414
+ let relationInfo = relationalModelMap.get(model);
415
+ let { relationalMap, sortedModelNames } = relationInfo;
416
+
417
+ // Assign related attributes from stored models
418
+ for (let k = 0, kl = sortedModelNames.length; k < kl; k++) {
419
+ let relatedModelName = sortedModelNames[k];
420
+
421
+ // Model can't be related to itself
422
+ if (relatedModelName === modelName)
423
+ continue;
424
+
425
+ let relationStatus = relationalMap[relatedModelName];
426
+ let SourceModel = relationStatus.Model;
427
+ let sourceModelInstance = relationStatus.instance;
428
+
429
+ if (assignRelated)
430
+ assignRelatedModels(model, [ sourceModelInstance ]);
431
+
432
+ // Is this model related to the set model?
433
+ // If not, simply continue
434
+ if (!SetModel.isForeignKeyTargetModel(connection, relatedModelName))
435
+ continue;
436
+
437
+ setRelationalValues(connection, SetModel, model, SourceModel, sourceModelInstance);
438
+ }
439
+ }
440
+ };
441
+
442
+ // Create each model and all related models
443
+ index = 0;
444
+ for (let [ modelName, models ] of fullModelMap) {
445
+ let SetModel = connection.getModel(modelName);
446
+
447
+ if (index > 0) {
448
+ // Now assign related attributes from all other
449
+ // related models before we save
450
+ updateRelatedModelAttributes(SetModel, modelName, models);
451
+ }
452
+
453
+ index++;
454
+
455
+ if (models.size === 0)
456
+ continue;
457
+
458
+ // Create models
459
+ let storedModels = await connection.insert(SetModel, models, options);
460
+ let storedSet = storedModelMap.get(modelName);
461
+ if (!storedSet) {
462
+ storedSet = new Set();
463
+ storedModelMap.set(modelName, storedSet);
464
+ }
465
+
466
+ for (let j = 0, jl = storedModels.length; j < jl; j++) {
467
+ let storedModel = storedModels[j];
468
+ storedSet.add(storedModel);
469
+ }
470
+ }
471
+
472
+ // Now update all related fields for all stored models
473
+ for (let [ modelName, models ] of fullModelMap) {
474
+ let SetModel = connection.getModel(modelName);
475
+
476
+ // Now assign related attributes from all other
477
+ // related models before we save
478
+ updateRelatedModelAttributes(SetModel, modelName, models, true);
479
+ }
480
+
481
+ // Now update all related fields for all pre-persisted models
482
+ // Models that were already persisted before the operation
483
+ // are split apart and treated separately
484
+ for (let [ modelName, models ] of persistedModelMap) {
485
+ let SetModel = connection.getModel(modelName);
486
+
487
+ // Now assign related attributes from all other
488
+ // related models before we save
489
+ updateRelatedModelAttributes(SetModel, modelName, models, true);
490
+ }
491
+
492
+ // Finally return created models
493
+ // Sorting final results by provided model
494
+ // order is important, since we split out any
495
+ // pre-persisted models and treat them separately
496
+ let finalModels = Array.from(storedModelMap.get(targetModelName).values()).sort((a, b) => {
497
+ let x = a.__order;
498
+ let y = b.__order;
499
+
500
+ if (x === y)
501
+ return 0;
502
+
503
+ return (x < y) ? -1 : 1;
504
+ });
505
+
506
+ return finalModels;
507
+ }
508
+
509
+ function setRelationalValues(connection, TargetModel, targetModelInstance, SourceModel, sourceModelInstance) {
510
+ let targetModelName = TargetModel.getModelName();
511
+ let sourceModelName = SourceModel.getModelName();
512
+
513
+ if (targetModelName === sourceModelName)
514
+ return targetModelInstance;
515
+
516
+ let fieldSets = TargetModel.getForeignKeysTargetFieldNames(connection, sourceModelName);
517
+
518
+ // Update fields to related model
519
+ for (let i = 0, il = fieldSets.length; i < il; i++) {
520
+ let fieldSet = fieldSets[i];
521
+
522
+ // This might be confusing... the right hand side is
523
+ // pulling from the "source" table in the relationship
524
+ // (aka the "child" relationship), whereas we are pulling
525
+ // from the "target" or "parent" relationship on the left.
526
+ // Source table -> target model (copy destination).
527
+ let targetFieldName = fieldSet.sourceFieldName;
528
+ let sourceFieldName = fieldSet.targetFieldName;
529
+ let sourceModelValue = (sourceModelInstance) ? sourceModelInstance[sourceFieldName] : null;
530
+
531
+ targetModelInstance[targetFieldName] = sourceModelValue;
532
+ }
533
+
534
+ return targetModelInstance;
535
+ }
536
+
537
+ function assignRelatedModels(model, _relatedModels) {
538
+ if (!model || !_relatedModels)
539
+ return;
540
+
541
+ let relatedModels = Nife.toArray(_relatedModels);
542
+ for (let i = 0, il = relatedModels.length; i < il; i++) {
543
+ let relatedModel = relatedModels[i];
544
+ let modelName = relatedModel.getModelName();
545
+ let pluralName = relatedModel.getPluralModelName();
546
+ let relatedScope = model[pluralName];
547
+
548
+ if (!relatedScope) {
549
+ relatedScope = model[pluralName] = [];
550
+
551
+ let RelatedModel = relatedModel.getModel();
552
+
553
+ model.__assignedRelatedModels.set(modelName, RelatedModel);
554
+
555
+ Object.defineProperty(relatedScope, 'Model', {
556
+ writable: true,
557
+ enumberable: false,
558
+ configurable: true,
559
+ value: RelatedModel,
560
+ });
561
+ }
562
+
563
+ if (relatedScope.indexOf(relatedModel) < 0) {
564
+ relatedScope.push(relatedModel);
565
+
566
+ let pkFieldName = relatedModel.getPrimaryKeyFieldName();
567
+ let pkValue = (pkFieldName) ? relatedModel[pkFieldName] : null;
568
+
569
+ if (Nife.isNotEmpty(pkValue)) {
570
+ Object.defineProperty(relatedScope, pkValue, {
571
+ writable: true,
572
+ enumberable: false,
573
+ configurable: true,
574
+ value: relatedModel,
575
+ });
576
+ }
577
+ }
578
+ }
579
+ }
580
+
581
+ function getPrimaryKeysForModels(connection, Model, _models, _options) {
582
+ let pkFieldName = Model.getPrimaryKeyFieldName();
583
+ if (!pkFieldName)
584
+ return [];
585
+
586
+ let models = Nife.toArray(_models).filter(Boolean);
587
+ let primaryIDs = models.map((model) => model[pkFieldName]).filter((id) => (id != null));
588
+
589
+ let options = _options || {};
590
+ if (!options.includeRelations)
591
+ return primaryIDs;
592
+
593
+ let primaryModelName = Model.getModelName();
594
+ let idMap = { [primaryModelName]: primaryIDs };
595
+ let providedRelationNames = (Array.isArray(options.includeRelations)) ? (options.includeRelations.filter(Boolean)) : null;
596
+ let skipRelations = options.skipRelations;
597
+
598
+ for (let i = 0, il = models.length; i < il; i++) {
599
+ let model = models[i];
600
+ if (!model)
601
+ continue;
602
+
603
+ let relatedModels = model.__assignedRelatedModels;
604
+ for (let [ relationModelName, RelatedModel ] of relatedModels) {
605
+ if (relationModelName === primaryModelName)
606
+ continue;
607
+
608
+ if (providedRelationNames && providedRelationNames.indexOf(relationModelName) < 0)
609
+ continue;
610
+
611
+ if (skipRelations && skipRelations.indexOf(relationModelName) >= 0)
612
+ continue;
613
+
614
+ let pluralModelName = RelatedModel.getPluralModelName();
615
+ let relatedModels = model[pluralModelName];
616
+ if (Nife.isEmpty(relatedModels))
617
+ continue;
618
+
619
+ let relationPKFieldName = RelatedModel.getPrimaryKeyFieldName();
620
+ if (!relationPKFieldName)
621
+ continue;
622
+
623
+ let idScope = idMap[relationModelName];
624
+ if (!idScope)
625
+ idScope = idMap[relationModelName] = [];
626
+
627
+ for (let k = 0, kl = relatedModels.length; k < kl; k++) {
628
+ let relatedModel = relatedModels[k];
629
+ let id = relatedModel[relationPKFieldName];
630
+
631
+ if (!id)
632
+ continue;
633
+
634
+ if (idScope.indexOf(id) >= 0)
635
+ continue;
636
+
637
+ idScope.push(id);
638
+ }
639
+ }
640
+ }
641
+
642
+ return idMap;
643
+ }
644
+
645
+ function buildQueryFromModelsAttributes(connection, Model, _models) {
646
+ let models = Nife.toArray(_models);
647
+ if (Nife.isEmpty(models))
648
+ return;
649
+
650
+ let isValidQuery = false;
651
+ let query = Model.where(connection);
652
+ let concreteFieldNames = [];
653
+
654
+ Model.iterateFields(({ fieldName, field }) => {
655
+ if (field.type.isVirtual())
656
+ return;
657
+
658
+ concreteFieldNames.push(fieldName);
659
+ });
660
+
661
+ if (concreteFieldNames.length === 0)
662
+ return;
663
+
664
+ for (let i = 0, il = models.length; i < il; i++) {
665
+ let model = models[i];
666
+ if (!model)
667
+ continue;
668
+
669
+ let subQuery = Model.where(connection);
670
+ for (let j = 0, jl = concreteFieldNames.length; j < jl; j++) {
671
+ let fieldName = concreteFieldNames[j];
672
+ let value = model[fieldName];
673
+ if (value == null)
674
+ continue;
675
+
676
+ isValidQuery = true;
677
+ subQuery = subQuery.AND[fieldName].EQ(value);
678
+ }
679
+
680
+ query = query.AND(subQuery);
681
+ }
682
+
683
+ if (!isValidQuery)
684
+ return;
685
+
686
+ return query;
687
+ }
688
+
689
+ module.exports = {
690
+ assignRelatedModels,
691
+ buildQueryFromModelsAttributes,
692
+ constructModelsForCreationFromOriginField,
693
+ createAndSaveAllRelatedModels,
694
+ fieldToFullyQualifiedName,
695
+ getPrimaryKeysForModels,
696
+ getRelationalModelStatusForField,
697
+ injectModelMethod,
698
+ isUUID,
699
+ parseQualifiedName,
700
+ sanitizeFieldString,
701
+ setRelationalValues,
702
+ sortModelNamesByCreationOrder,
703
+ sortModelNamesByDependencyOrder,
704
+ };