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,264 @@
1
+ 'use strict';
2
+
3
+ const Nife = require('nife');
4
+ const Inflection = require('inflection');
5
+ const RelationalTypeBase = require('./relational-type-base');
6
+ const Utils = require('../../utils');
7
+
8
+ const NAMED_METHOD = false;
9
+
10
+ async function destroyRelatedModels(connection, field, type, TargetModel, storedModels, invertMatch, options, operation) {
11
+ // Destroy all relations not matching stored set
12
+ let thisMethodName = type.fieldNameToOperationName(field, operation, NAMED_METHOD);
13
+ let parentModelName = this.getModelName();
14
+ let idMap = Utils.getPrimaryKeysForModels(connection, TargetModel, storedModels, { includeRelations: true, skipRelations: [ field.Model.getModelName() ] });
15
+ let { relationalMap } = await Utils.getRelationalModelStatusForField(connection, this, field);
16
+ let sortedModelNames = Nife.subtractFromArray(Utils.sortModelNamesByCreationOrder(connection, Object.keys(relationalMap)), [ parentModelName ]);
17
+ let targetRelationName = sortedModelNames[0];
18
+ let destroyModelNames = sortedModelNames.slice(1).reverse();
19
+ let targetIDs = idMap[targetRelationName];
20
+ let targetPKFieldName = TargetModel.getPrimaryKeyFieldName();
21
+
22
+ if (destroyModelNames.length === 0) {
23
+ // No through-table... this is a one-to-many relation.
24
+ // We can only destroy if the TargetModel has a foreign key
25
+ // to the parent model.
26
+
27
+ // Set destroyModelNames to the first relation.
28
+ // The following code will ensure they only get
29
+ // deleted if there is a foreign key relationship
30
+ // to the parent model.
31
+ destroyModelNames = [ targetRelationName ];
32
+ }
33
+
34
+ for (let i = 0, il = destroyModelNames.length; i < il; i++) {
35
+ let modelName = destroyModelNames[i];
36
+ let ids = idMap[modelName];
37
+
38
+ // If we have no ids for this model, then
39
+ // there is nothing we can destroy
40
+ if (Nife.isEmpty(ids))
41
+ continue;
42
+
43
+ let ThisModel = connection.getModel(modelName);
44
+ let thisModelPKFieldName = ThisModel.getPrimaryKeyFieldName();
45
+ if (!thisModelPKFieldName) {
46
+ // Not sure how this would happen... since we obviously
47
+ // have a list of ids already... but let's be safe anyway
48
+ continue;
49
+ }
50
+
51
+ // We can only destroy models that have a foreign key
52
+ // to the parent model.
53
+ let foreignFieldNames = ThisModel.getForeignKeysTargetFieldNames(connection, parentModelName);
54
+ if (Nife.isEmpty(foreignFieldNames))
55
+ continue;
56
+
57
+ // Create query to destroy related models
58
+ let query = ThisModel.where(connection)[thisModelPKFieldName];
59
+ if (invertMatch)
60
+ query = query.EQ(ids);
61
+ else
62
+ query = query.NOT.EQ(ids);
63
+
64
+ for (let j = 0, jl = foreignFieldNames.length; j < jl; j++) {
65
+ let { targetFieldName, sourceFieldName } = foreignFieldNames[j];
66
+ let parentModelValue = this[targetFieldName];
67
+
68
+ // If this value is empty then something is wrong...
69
+ // so fail early to prevent unwanted data loss
70
+ if (parentModelValue == null)
71
+ throw new Error(`${parentModelName}::${thisMethodName}: Field "${parentModelName}.${targetFieldName}" can not be empty for this operation.`);
72
+
73
+ query = query.AND[sourceFieldName].EQ(parentModelValue);
74
+ }
75
+
76
+ // If there is a foreign field that points to the target
77
+ // relation, then also match against those
78
+ if (targetPKFieldName && Nife.isNotEmpty(targetIDs)) {
79
+ let targetForeignField = ThisModel.getForeignKeysTargetField(connection, targetRelationName, targetPKFieldName);
80
+ if (targetForeignField) {
81
+ if (invertMatch)
82
+ query = query.AND[modelName][targetForeignField.sourceFieldName].EQ(targetIDs);
83
+ else
84
+ query = query.AND[modelName][targetForeignField.sourceFieldName].NOT.EQ(targetIDs);
85
+ }
86
+ }
87
+
88
+ // Now destroy relations to update set
89
+ await query.destroy(options);
90
+ }
91
+ }
92
+
93
+ // These get injected into the class as
94
+ // addTo{fieldName}, get{fieldName}, etc...
95
+ const TYPE_OPERATIONS = {
96
+ 'queryFor': async function({ field, type }, userQuery, options, ...args) {
97
+ return await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
98
+ },
99
+ 'addTo': async function({ field }, _models, options) {
100
+ let models = Nife.toArray(_models).filter(Boolean);
101
+ if (Nife.isEmpty(models))
102
+ return [];
103
+
104
+ // TODO: Needs to manage unique constraints
105
+ // for through tables. Even though the model
106
+ // might be persisted, the through table record
107
+ // might still be created, which might blow up
108
+ // if a constraint fails because the records
109
+ // already exist
110
+ return this.getConnection(options && options.connection).transaction(async (connection) => {
111
+ let currentModels = this[field.fieldName];
112
+ if (Nife.isEmpty(currentModels))
113
+ currentModels = [];
114
+
115
+ let storedModels = await Utils.createAndSaveAllRelatedModels(connection, this, field, models, options);
116
+ let allModels = currentModels.concat(storedModels);
117
+
118
+ this[field.fieldName] = allModels;
119
+
120
+ return storedModels;
121
+ }, options);
122
+ },
123
+ 'get': function({ field, type }, userQuery, options, ...args) {
124
+ const doGet = async function*() {
125
+ let query = await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
126
+ let results = query.all(Object.assign({}, options, { stream: true }));
127
+ let primaryModelRelationalArray = [];
128
+
129
+ this[field.fieldName] = primaryModelRelationalArray;
130
+
131
+ for await (let item of results) {
132
+ primaryModelRelationalArray.push(item);
133
+
134
+ yield item;
135
+ }
136
+ };
137
+
138
+ if (options && options.stream === true)
139
+ return doGet.call(this);
140
+ else
141
+ return Utils.collect(doGet.call(this));
142
+ },
143
+ 'set': async function({ field, type, addTo }, models, options) {
144
+ return this.getConnection(options && options.connection).transaction(async (connection) => {
145
+ let TargetModel = type.getTargetModel(connection);
146
+
147
+ // Reset relation so we don't end up with
148
+ // current persisted model instances
149
+ this[field.fieldName] = [];
150
+
151
+ // First, create models in set
152
+ let storedModels = await addTo.call(this, models, Object.assign({}, options || {}, { connection }));
153
+
154
+ await destroyRelatedModels.call(this, connection, field, type, TargetModel, storedModels, false, options, 'set');
155
+
156
+ return storedModels;
157
+ }, options);
158
+ },
159
+ 'removeFrom': async function({ field, type, get }, _models, _options) {
160
+ return this.getConnection(_options && _options.connection).transaction(async (connection) => {
161
+ let { relationalMap, TargetModel } = await Utils.getRelationalModelStatusForField(connection, this, field);
162
+ let primaryModelName = this.getModelName();
163
+ let targetModelName = TargetModel.getModelName();
164
+ let models = Nife.toArray(_models);
165
+
166
+ const needsLoad = async () => {
167
+ let sortedModelNames = Utils.sortModelNamesByCreationOrder(connection, Object.keys(relationalMap));
168
+
169
+ for (let i = 0, il = models.length; i < il; i++) {
170
+ let model = models[i];
171
+
172
+ if (!(model instanceof TargetModel))
173
+ return true;
174
+
175
+ if (!model.isPersisted())
176
+ return true;
177
+
178
+ for (let j = 0, jl = sortedModelNames.length; j < jl; j++) {
179
+ let modelName = sortedModelNames[j];
180
+ if (modelName === targetModelName || modelName === primaryModelName)
181
+ continue;
182
+
183
+ let relation = relationalMap[modelName];
184
+ let ThisModel = relation.Model;
185
+ let pluralModelName = ThisModel.getPluralModelName();
186
+
187
+ if (!model[pluralModelName])
188
+ return true;
189
+
190
+ let relationSet = model[pluralModelName];
191
+ if (Nife.isEmpty(relationSet))
192
+ return true;
193
+
194
+ if (!relationSet.every((relatedModel) => relatedModel.isPersisted()))
195
+ return true;
196
+ }
197
+ }
198
+
199
+ return false;
200
+ };
201
+
202
+ let options = Object.assign({}, _options || {}, { connection });
203
+ let storedModels;
204
+
205
+ if (await needsLoad()) {
206
+ let query = Utils.generateQueryFromFilter(connection, TargetModel, models);
207
+ if (!query)
208
+ throw new Error(`${this.getModelName()}::${type.fieldNameToOperationName(field, 'removeFrom', NAMED_METHOD)}: Data provided is insufficient to complete operation.`);
209
+
210
+ storedModels = await get.call(this, TargetModel.where(connection).AND(query), Object.assign({}, options, { stream: false, includeRelations: true, connection }));
211
+ } else {
212
+ storedModels = models;
213
+ }
214
+
215
+ // Destroy target relations to removed
216
+ // requested models from set
217
+ await destroyRelatedModels.call(this, connection, field, type, TargetModel, storedModels, true, options, 'removeFrom');
218
+
219
+ return storedModels.length;
220
+ }, _options);
221
+ },
222
+ 'destroy': async function({ field, type }, userQuery, options, ...args) {
223
+ let query = await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
224
+ return await query.destroy(options);
225
+ },
226
+ 'count': async function({ field, type }, userQuery, options, ...args) {
227
+ let query = await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
228
+ return await query.count(null, options);
229
+ },
230
+ 'has': async function({ count }, userQuery, options, ...args) {
231
+ let itemCount = await count.call(this, userQuery, options, ...args);
232
+ return (itemCount > 0);
233
+ },
234
+ };
235
+
236
+ class ModelsType extends RelationalTypeBase {
237
+ static exposeToModel() {
238
+ return false;
239
+ }
240
+
241
+ isManyRelation() {
242
+ return true;
243
+ }
244
+
245
+ fieldNameToOperationName(field, operation, rootMethod) {
246
+ let fieldName = field.pluralName;
247
+ if (!fieldName)
248
+ fieldName = Nife.capitalize(Inflection.pluralize(field.fieldName));
249
+
250
+ if (rootMethod)
251
+ return `_${operation}${fieldName}`;
252
+ else
253
+ return `${operation}${fieldName}`;
254
+ }
255
+
256
+ initialize(connection, self) {
257
+ return super.initialize(connection, self, TYPE_OPERATIONS);
258
+ }
259
+ }
260
+
261
+ module.exports = {
262
+ Models: RelationalTypeBase.wrapConstructor(ModelsType),
263
+ ModelsType,
264
+ };
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ const Nife = require('nife');
4
+ const Type = require('../type');
5
+ const Utils = require('../../utils');
6
+ const { QueryEngine } = require('../../query-engine');
7
+
8
+ const NAMED_METHOD = false;
9
+ const ROOT_METHOD = true;
10
+
11
+ class RelationalTypeBase extends Type {
12
+ static isVirtual() {
13
+ return true;
14
+ }
15
+
16
+ static isRelational() {
17
+ return true;
18
+ }
19
+
20
+ // Model types work by specifying a "target"
21
+ // and a "value provider" (source).
22
+ // These are fully qualified names, meaning
23
+ // they also point to the model as well as the field.
24
+ // If no model is specified, then it always defaults to
25
+ // "this" model. If no field is specified, then it always
26
+ // defaults to "this PK" of the model.
27
+ // Mythix ORM will recursively walk all models and fields
28
+ // defined until it has the full relationships between
29
+ // all fields.
30
+ constructor(_targetModelName, queryFactory, _options) {
31
+ let options = _options || {};
32
+
33
+ super(_targetModelName, queryFactory, options);
34
+
35
+ let targetModelName = _targetModelName;
36
+ if (!targetModelName || !(Nife.instanceOf(targetModelName, 'string') || targetModelName._isMythixModel))
37
+ throw new TypeError(`${this.constructor.name}::constructor: First argument is required to be a model or a model name.`);
38
+
39
+ if (targetModelName._isMythixModel)
40
+ targetModelName = targetModelName.getModelName();
41
+
42
+ Object.defineProperties(this, {
43
+ 'targetModelName': {
44
+ writable: true,
45
+ enumberable: false,
46
+ configurable: true,
47
+ value: targetModelName,
48
+ },
49
+ 'queryFactory': {
50
+ writable: true,
51
+ enumberable: false,
52
+ configurable: true,
53
+ value: queryFactory,
54
+ },
55
+ 'options': {
56
+ writable: true,
57
+ enumberable: false,
58
+ configurable: true,
59
+ value: options,
60
+ },
61
+ });
62
+ }
63
+
64
+ getOptions() {
65
+ return this.options;
66
+ }
67
+
68
+ initialize(connection, self, methods) {
69
+ if (typeof this.queryFactory !== 'function') {
70
+ let debugScope = '';
71
+
72
+ let Model = this.getModel();
73
+ let Field = this.getField();
74
+
75
+ if (Model || Field) {
76
+ let modelName = (Model) ? Model.getModelName() : '<UnknownModel>';
77
+ let fieldName = (Field) ? Field.fieldName : '<UnknownField>';
78
+
79
+ debugScope = `"${modelName}:${fieldName}" `;
80
+ }
81
+
82
+ throw new TypeError(`${this.constructor.name}::initialize: ${debugScope}Bad relation arguments. Expected a method that returns a query.`);
83
+ }
84
+
85
+ super.initialize(connection, self);
86
+
87
+ if (!connection)
88
+ throw new Error(`${this.constructor.name}::initialize: A connection is required to use the ModelType.`);
89
+
90
+ let field = this.getField();
91
+ let context = {};
92
+ let binding = Object.create(context);
93
+ let operationNames = Object.keys(methods);
94
+
95
+ for (let i = 0, il = operationNames.length; i < il; i++) {
96
+ let operation = operationNames[i];
97
+ let method = methods[operation];
98
+ let methodName = this.fieldNameToOperationName(field, operation, NAMED_METHOD);
99
+ let fullMethodName = this.fieldNameToOperationName(field, operation, ROOT_METHOD);
100
+
101
+ context[operation] = function(...args) {
102
+ return this[methodName].apply(this, args);
103
+ };
104
+
105
+ context[`_${operation}`] = function(...args) {
106
+ return this[fullMethodName].apply(this, args);
107
+ };
108
+
109
+ Object.assign(binding, { methodName, fullMethodName, field, type: this });
110
+
111
+ Utils.injectModelMethod(self, method.bind(self, binding), methodName, fullMethodName);
112
+ }
113
+ }
114
+
115
+ async walkQueryRelations(connection, callback, context, ...args) {
116
+ const isAlreadyVisited = (leftContext, rightContext) => {
117
+ // Model can't be related to itself
118
+ if (leftContext.modelName === rightContext.modelName)
119
+ return true;
120
+
121
+ let firstKey = `${leftContext.modelName}:${leftContext.fieldName}<->${rightContext.modelName}:${rightContext.fieldName}`;
122
+ let secondKey = `${rightContext.modelName}:${rightContext.fieldName}<->${leftContext.modelName}:${leftContext.fieldName}`;
123
+ if (alreadyVisited.has(firstKey))
124
+ return true;
125
+
126
+ if (alreadyVisited.has(secondKey))
127
+ return true;
128
+
129
+ alreadyVisited.add(firstKey);
130
+ alreadyVisited.add(secondKey);
131
+
132
+ return false;
133
+ };
134
+
135
+ const prepareSourceAndTarget = (leftContext, rightContext) => {
136
+ const voidFieldForNonRelational = (info) => {
137
+ let { target, source } = info;
138
+
139
+ if (target.field.type.isRelational() && !source.field.type.isRelational()) {
140
+ source.field = null;
141
+ source.fieldName = null;
142
+ }
143
+
144
+ if (!target.field.type.isRelational() && source.field.type.isRelational()) {
145
+ target.field = null;
146
+ target.fieldName = null;
147
+ }
148
+
149
+ return info;
150
+ };
151
+
152
+ let target = {
153
+ Model: leftContext.Model,
154
+ modelName: leftContext.modelName,
155
+ field: leftContext.Field,
156
+ fieldName: leftContext.fieldName,
157
+ };
158
+
159
+ let source = {
160
+ Model: rightContext.Model,
161
+ modelName: rightContext.modelName,
162
+ field: rightContext.Field,
163
+ fieldName: rightContext.fieldName,
164
+ };
165
+
166
+ // The primary model is always the target
167
+ // so swap the order if it is currently
168
+ // marked as the source
169
+ let swapRelation = (source.modelName === primaryModelName);
170
+
171
+ return voidFieldForNonRelational({
172
+ PrimaryModel,
173
+ TargetModel,
174
+ TargetField,
175
+ target: (swapRelation) ? source : target,
176
+ source: (swapRelation) ? target : source,
177
+ });
178
+ };
179
+
180
+ let PrimaryModel = (context.self && context.self.getModel());
181
+ let primaryModelName = PrimaryModel.getModelName();
182
+ let query = await this.prepareQuery(Object.assign({ connection }, context), args);
183
+ let queryParts = query._getRawQuery();
184
+ let queryContext = (queryParts[queryParts.length - 1] || {});
185
+ let TargetModel = queryContext.rootModel;
186
+ let TargetField = queryContext.rootField;
187
+ let alreadyVisited = new Set();
188
+
189
+ for (let i = 0, il = queryParts.length; i < il; i++) {
190
+ let leftQueryContext = queryParts[i];
191
+
192
+ if (!(Object.prototype.hasOwnProperty.call(leftQueryContext, 'condition') && leftQueryContext.condition))
193
+ continue;
194
+
195
+ let conditionValue = leftQueryContext.value;
196
+ let rightQueryContext = null;
197
+
198
+ if (QueryEngine.isQuery(conditionValue)) {
199
+ rightQueryContext = conditionValue._getRawQueryContext();
200
+ if (rightQueryContext.condition)
201
+ continue;
202
+ } else {
203
+ continue;
204
+ }
205
+
206
+ if (isAlreadyVisited(leftQueryContext, rightQueryContext))
207
+ continue;
208
+
209
+ callback(prepareSourceAndTarget(leftQueryContext, rightQueryContext));
210
+
211
+ if (leftQueryContext.Field.type.isForeignKey()) {
212
+ let fkField = leftQueryContext.Field.type;
213
+ let targetModel = fkField.getTargetModel(connection);
214
+ let targetModelName = fkField.getTargetModelName(connection);
215
+ let targetField = fkField.getTargetField(connection);
216
+ let targetFieldName = fkField.getTargetFieldName(connection);
217
+ let fkContext = {
218
+ Model: targetModel,
219
+ modelName: targetModelName,
220
+ field: targetField,
221
+ fieldName: targetFieldName,
222
+ };
223
+
224
+ if (!isAlreadyVisited(fkContext, rightQueryContext))
225
+ callback(prepareSourceAndTarget(fkContext, rightQueryContext));
226
+ }
227
+
228
+ if (rightQueryContext.Field.type.isForeignKey()) {
229
+ let fkField = rightQueryContext.Field.type;
230
+ let targetModel = fkField.getTargetModel(connection);
231
+ let targetModelName = fkField.getTargetModelName(connection);
232
+ let targetField = fkField.getTargetField(connection);
233
+ let targetFieldName = fkField.getTargetFieldName(connection);
234
+ let fkContext = {
235
+ Model: targetModel,
236
+ modelName: targetModelName,
237
+ field: targetField,
238
+ fieldName: targetFieldName,
239
+ };
240
+
241
+ if (!isAlreadyVisited(leftQueryContext, fkContext))
242
+ callback(prepareSourceAndTarget(leftQueryContext, fkContext));
243
+ }
244
+ }
245
+
246
+ if (TargetField.type.isRelational() && TargetField.type.isManyRelation())
247
+ return query;
248
+
249
+ if (context.field.type.isRelational() && context.field.type.isManyRelation())
250
+ return query;
251
+
252
+ // Here the source and target are swapped
253
+ // because the target will be the primary model
254
+ // and the source (target model) is where we will
255
+ // be copying field values from
256
+ let targetModelName = TargetModel.getModelName();
257
+ let source = {
258
+ Model: TargetModel,
259
+ modelName: targetModelName,
260
+ Field: TargetField,
261
+ fieldName: TargetField.fieldName,
262
+ };
263
+ let target = {
264
+ Model: PrimaryModel,
265
+ modelName: PrimaryModel.getModelName(),
266
+ Field: context.field,
267
+ fieldName: context.field.fieldName,
268
+ };
269
+
270
+ if (!isAlreadyVisited(source, target))
271
+ callback(prepareSourceAndTarget(source, target));
272
+
273
+ return query;
274
+ }
275
+
276
+ getTargetModel(_connection) {
277
+ let connection = _connection || this.getModel().getConnection();
278
+ return connection.getModel(this.targetModelName);
279
+ }
280
+
281
+ async prepareQuery({ connection: _connection, self, field, options: _options, userQuery: _userQuery }, args) {
282
+ let options = _options || {};
283
+ let queryFactory = this.queryFactory;
284
+ let connection = _connection || self.getConnection(options.connection);
285
+ let userQuery = _userQuery;
286
+ let TargetModel = this.getTargetModel(connection);
287
+
288
+ if (!QueryEngine.isQuery(userQuery) && Nife.instanceOf(userQuery, 'array', 'object', 'map', 'set'))
289
+ userQuery = Utils.generateQueryFromFilter(connection, TargetModel, userQuery);
290
+
291
+ let rootQuery = await queryFactory.call(
292
+ self,
293
+ Object.assign(
294
+ {
295
+ userQuery: userQuery || undefined,
296
+ type: this,
297
+ self,
298
+ connection,
299
+ field,
300
+ },
301
+ connection.getModels() || {},
302
+ ),
303
+ ...(args || []),
304
+ );
305
+
306
+ if (!QueryEngine.isQuery(rootQuery)) {
307
+ // eslint-disable-next-line no-magic-numbers
308
+ throw new Error(`${this.constructor.name}::prepareQuery: Query factory is required to return a query. Got "${('' + rootQuery).slice(0, 1000)}" instead.`);
309
+ }
310
+
311
+ return rootQuery.AND[TargetModel.getModelName()];
312
+ }
313
+
314
+ toConnectionType() {
315
+ throw new Error(`${this.constructor.name}::toConnectionType: Can not convert relational types to DB types.`);
316
+ }
317
+
318
+ toString() {
319
+ return `${this.constructor.name} {}`;
320
+ }
321
+ }
322
+
323
+ module.exports = RelationalTypeBase;
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ const MiscUtils = require('./misc-utils');
4
+ const ModelUtils = require('./model-utils');
5
+ const QueryUtils = require('./query-utils');
6
+
7
+ const {
8
+ collect,
9
+ copyStaticProps,
10
+ objectAssignSpecial,
11
+ iterateStaticProps,
12
+ } = MiscUtils;
13
+
14
+ const {
15
+ assignRelatedModels,
16
+ buildQueryFromModelsAttributes,
17
+ constructModelsForCreationFromOriginField,
18
+ createAndSaveAllRelatedModels,
19
+ fieldToFullyQualifiedName,
20
+ getPrimaryKeysForModels,
21
+ getRelationalModelStatusForField,
22
+ injectModelMethod,
23
+ isUUID,
24
+ parseQualifiedName,
25
+ sanitizeFieldString,
26
+ setRelationalValues,
27
+ sortModelNamesByCreationOrder,
28
+ sortModelNamesByDependencyOrder,
29
+ } = ModelUtils;
30
+
31
+ const {
32
+ parseFilterFieldAndOperator,
33
+ generateQueryFromFilter,
34
+ } = QueryUtils;
35
+
36
+ module.exports = {
37
+ MiscUtils,
38
+ ModelUtils,
39
+
40
+ // MiscUtils
41
+ collect,
42
+ copyStaticProps,
43
+ objectAssignSpecial,
44
+ iterateStaticProps,
45
+
46
+ // ModelUtils
47
+ assignRelatedModels,
48
+ buildQueryFromModelsAttributes,
49
+ constructModelsForCreationFromOriginField,
50
+ createAndSaveAllRelatedModels,
51
+ fieldToFullyQualifiedName,
52
+ getPrimaryKeysForModels,
53
+ getRelationalModelStatusForField,
54
+ injectModelMethod,
55
+ isUUID,
56
+ parseQualifiedName,
57
+ sanitizeFieldString,
58
+ setRelationalValues,
59
+ sortModelNamesByCreationOrder,
60
+ sortModelNamesByDependencyOrder,
61
+
62
+ // QueryUtils
63
+ parseFilterFieldAndOperator,
64
+ generateQueryFromFilter,
65
+ };