@loopback/repository 4.1.1 → 5.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 (250) hide show
  1. package/LICENSE +1 -1
  2. package/dist/connectors/connector.js +1 -1
  3. package/dist/connectors/connector.js.map +1 -1
  4. package/dist/connectors/crud.connector.js +1 -1
  5. package/dist/connectors/crud.connector.js.map +1 -1
  6. package/dist/connectors/index.js +4 -4
  7. package/dist/connectors/index.js.map +1 -1
  8. package/dist/connectors/kv.connector.js +1 -1
  9. package/dist/connectors/kv.connector.js.map +1 -1
  10. package/dist/datasource.js +1 -1
  11. package/dist/datasource.js.map +1 -1
  12. package/dist/decorators/index.js +4 -4
  13. package/dist/decorators/index.js.map +1 -1
  14. package/dist/decorators/metadata.js +1 -1
  15. package/dist/decorators/metadata.js.map +1 -1
  16. package/dist/decorators/model.decorator.js +1 -1
  17. package/dist/decorators/model.decorator.js.map +1 -1
  18. package/dist/decorators/repository.decorator.js +2 -2
  19. package/dist/decorators/repository.decorator.js.map +1 -1
  20. package/dist/define-model-class.js +2 -2
  21. package/dist/define-model-class.js.map +1 -1
  22. package/dist/define-repository-class.js +2 -2
  23. package/dist/define-repository-class.js.map +1 -1
  24. package/dist/errors/entity-not-found.error.js +1 -1
  25. package/dist/errors/entity-not-found.error.js.map +1 -1
  26. package/dist/errors/index.d.ts +1 -0
  27. package/dist/errors/index.js +4 -3
  28. package/dist/errors/index.js.map +1 -1
  29. package/dist/errors/invalid-polymorphism.error.d.ts +5 -0
  30. package/dist/errors/invalid-polymorphism.error.js +22 -0
  31. package/dist/errors/invalid-polymorphism.error.js.map +1 -0
  32. package/dist/errors/invalid-relation.error.d.ts +1 -1
  33. package/dist/errors/invalid-relation.error.js +1 -1
  34. package/dist/errors/invalid-relation.error.js.map +1 -1
  35. package/dist/index.js +17 -17
  36. package/dist/index.js.map +1 -1
  37. package/dist/mixins/index.js +2 -2
  38. package/dist/mixins/index.js.map +1 -1
  39. package/dist/mixins/repository.mixin.d.ts +49 -50
  40. package/dist/mixins/repository.mixin.js +2 -2
  41. package/dist/mixins/repository.mixin.js.map +1 -1
  42. package/dist/model.d.ts +7 -1
  43. package/dist/model.js +15 -1
  44. package/dist/model.js.map +1 -1
  45. package/dist/relations/belongs-to/belongs-to.accessor.d.ts +6 -2
  46. package/dist/relations/belongs-to/belongs-to.accessor.js +20 -7
  47. package/dist/relations/belongs-to/belongs-to.accessor.js.map +1 -1
  48. package/dist/relations/belongs-to/belongs-to.decorator.js +1 -1
  49. package/dist/relations/belongs-to/belongs-to.decorator.js.map +1 -1
  50. package/dist/relations/belongs-to/belongs-to.helpers.d.ts +3 -0
  51. package/dist/relations/belongs-to/belongs-to.helpers.js +34 -10
  52. package/dist/relations/belongs-to/belongs-to.helpers.js.map +1 -1
  53. package/dist/relations/belongs-to/belongs-to.inclusion-resolver.d.ts +5 -2
  54. package/dist/relations/belongs-to/belongs-to.inclusion-resolver.js +82 -9
  55. package/dist/relations/belongs-to/belongs-to.inclusion-resolver.js.map +1 -1
  56. package/dist/relations/belongs-to/belongs-to.repository.d.ts +28 -5
  57. package/dist/relations/belongs-to/belongs-to.repository.js +50 -11
  58. package/dist/relations/belongs-to/belongs-to.repository.js.map +1 -1
  59. package/dist/relations/belongs-to/index.d.ts +2 -2
  60. package/dist/relations/belongs-to/index.js +5 -5
  61. package/dist/relations/belongs-to/index.js.map +1 -1
  62. package/dist/relations/has-many/has-many-through.helpers.d.ts +3 -0
  63. package/dist/relations/has-many/has-many-through.helpers.js +27 -3
  64. package/dist/relations/has-many/has-many-through.helpers.js.map +1 -1
  65. package/dist/relations/has-many/has-many-through.inclusion-resolver.d.ts +3 -1
  66. package/dist/relations/has-many/has-many-through.inclusion-resolver.js +94 -23
  67. package/dist/relations/has-many/has-many-through.inclusion-resolver.js.map +1 -1
  68. package/dist/relations/has-many/has-many-through.repository-factory.d.ts +3 -1
  69. package/dist/relations/has-many/has-many-through.repository-factory.js +8 -2
  70. package/dist/relations/has-many/has-many-through.repository-factory.js.map +1 -1
  71. package/dist/relations/has-many/has-many-through.repository.d.ts +66 -11
  72. package/dist/relations/has-many/has-many-through.repository.js +212 -36
  73. package/dist/relations/has-many/has-many-through.repository.js.map +1 -1
  74. package/dist/relations/has-many/has-many.decorator.js +1 -1
  75. package/dist/relations/has-many/has-many.decorator.js.map +1 -1
  76. package/dist/relations/has-many/has-many.helpers.js +2 -2
  77. package/dist/relations/has-many/has-many.helpers.js.map +1 -1
  78. package/dist/relations/has-many/has-many.inclusion-resolver.js +2 -2
  79. package/dist/relations/has-many/has-many.inclusion-resolver.js.map +1 -1
  80. package/dist/relations/has-many/has-many.repository-factory.js +2 -2
  81. package/dist/relations/has-many/has-many.repository-factory.js.map +1 -1
  82. package/dist/relations/has-many/has-many.repository.js +1 -1
  83. package/dist/relations/has-many/has-many.repository.js.map +1 -1
  84. package/dist/relations/has-many/index.d.ts +2 -2
  85. package/dist/relations/has-many/index.js +7 -7
  86. package/dist/relations/has-many/index.js.map +1 -1
  87. package/dist/relations/has-one/has-one.decorator.js +1 -1
  88. package/dist/relations/has-one/has-one.decorator.js.map +1 -1
  89. package/dist/relations/has-one/has-one.helpers.d.ts +3 -0
  90. package/dist/relations/has-one/has-one.helpers.js +36 -10
  91. package/dist/relations/has-one/has-one.helpers.js.map +1 -1
  92. package/dist/relations/has-one/has-one.inclusion-resolver.d.ts +6 -3
  93. package/dist/relations/has-one/has-one.inclusion-resolver.js +83 -8
  94. package/dist/relations/has-one/has-one.inclusion-resolver.js.map +1 -1
  95. package/dist/relations/has-one/has-one.repository-factory.d.ts +9 -3
  96. package/dist/relations/has-one/has-one.repository-factory.js +15 -5
  97. package/dist/relations/has-one/has-one.repository-factory.js.map +1 -1
  98. package/dist/relations/has-one/has-one.repository.d.ts +58 -11
  99. package/dist/relations/has-one/has-one.repository.js +119 -16
  100. package/dist/relations/has-one/has-one.repository.js.map +1 -1
  101. package/dist/relations/has-one/index.js +4 -4
  102. package/dist/relations/has-one/index.js.map +1 -1
  103. package/dist/relations/index.d.ts +2 -0
  104. package/dist/relations/index.js +9 -7
  105. package/dist/relations/index.js.map +1 -1
  106. package/dist/relations/references-many/index.d.ts +4 -0
  107. package/dist/relations/references-many/index.js +12 -0
  108. package/dist/relations/references-many/index.js.map +1 -0
  109. package/dist/relations/references-many/references-many.accessor.d.ts +17 -0
  110. package/dist/relations/references-many/references-many.accessor.js +40 -0
  111. package/dist/relations/references-many/references-many.accessor.js.map +1 -0
  112. package/dist/relations/references-many/references-many.decorator.d.ts +11 -0
  113. package/dist/relations/references-many/references-many.decorator.js +73 -0
  114. package/dist/relations/references-many/references-many.decorator.js.map +1 -0
  115. package/dist/relations/references-many/references-many.helpers.d.ts +17 -0
  116. package/dist/relations/references-many/references-many.helpers.js +63 -0
  117. package/dist/relations/references-many/references-many.helpers.js.map +1 -0
  118. package/dist/relations/references-many/references-many.inclusion-resolver.d.ts +14 -0
  119. package/dist/relations/references-many/references-many.inclusion-resolver.js +42 -0
  120. package/dist/relations/references-many/references-many.inclusion-resolver.js.map +1 -0
  121. package/dist/relations/references-many/references-many.repository.d.ts +28 -0
  122. package/dist/relations/references-many/references-many.repository.js +33 -0
  123. package/dist/relations/references-many/references-many.repository.js.map +1 -0
  124. package/dist/relations/relation.decorator.d.ts +0 -6
  125. package/dist/relations/relation.decorator.js +2 -14
  126. package/dist/relations/relation.decorator.js.map +1 -1
  127. package/dist/relations/relation.filter.solver.d.ts +2 -0
  128. package/dist/relations/relation.filter.solver.js +57 -0
  129. package/dist/relations/relation.filter.solver.js.map +1 -0
  130. package/dist/relations/relation.helpers.js +20 -5
  131. package/dist/relations/relation.helpers.js.map +1 -1
  132. package/dist/relations/relation.types.d.ts +52 -1
  133. package/dist/relations/relation.types.js +1 -1
  134. package/dist/relations/relation.types.js.map +1 -1
  135. package/dist/repositories/constraint-utils.js +1 -1
  136. package/dist/repositories/constraint-utils.js.map +1 -1
  137. package/dist/repositories/index.d.ts +2 -2
  138. package/dist/repositories/index.js +6 -6
  139. package/dist/repositories/index.js.map +1 -1
  140. package/dist/repositories/kv.repository.bridge.js +1 -1
  141. package/dist/repositories/kv.repository.bridge.js.map +1 -1
  142. package/dist/repositories/kv.repository.d.ts +2 -2
  143. package/dist/repositories/kv.repository.js +1 -1
  144. package/dist/repositories/kv.repository.js.map +1 -1
  145. package/dist/repositories/legacy-juggler-bridge.d.ts +35 -8
  146. package/dist/repositories/legacy-juggler-bridge.js +39 -17
  147. package/dist/repositories/legacy-juggler-bridge.js.map +1 -1
  148. package/dist/repositories/repository.js +1 -1
  149. package/dist/repositories/repository.js.map +1 -1
  150. package/dist/type-resolver.js +1 -1
  151. package/dist/type-resolver.js.map +1 -1
  152. package/dist/types/any.js +1 -1
  153. package/dist/types/any.js.map +1 -1
  154. package/dist/types/array.js +2 -2
  155. package/dist/types/array.js.map +1 -1
  156. package/dist/types/boolean.js +1 -1
  157. package/dist/types/boolean.js.map +1 -1
  158. package/dist/types/buffer.js +2 -2
  159. package/dist/types/buffer.js.map +1 -1
  160. package/dist/types/date.js +2 -2
  161. package/dist/types/date.js.map +1 -1
  162. package/dist/types/index.js +1 -1
  163. package/dist/types/index.js.map +1 -1
  164. package/dist/types/model.js +1 -1
  165. package/dist/types/model.js.map +1 -1
  166. package/dist/types/null.js +1 -1
  167. package/dist/types/null.js.map +1 -1
  168. package/dist/types/number.js +2 -2
  169. package/dist/types/number.js.map +1 -1
  170. package/dist/types/object.js +2 -2
  171. package/dist/types/object.js.map +1 -1
  172. package/dist/types/string.js +1 -1
  173. package/dist/types/string.js.map +1 -1
  174. package/dist/types/type.js +1 -1
  175. package/dist/types/type.js.map +1 -1
  176. package/dist/types/union.js +2 -2
  177. package/dist/types/union.js.map +1 -1
  178. package/package.json +17 -17
  179. package/src/connectors/connector.ts +1 -1
  180. package/src/connectors/crud.connector.ts +1 -1
  181. package/src/connectors/index.ts +1 -1
  182. package/src/connectors/kv.connector.ts +1 -1
  183. package/src/datasource.ts +1 -1
  184. package/src/decorators/index.ts +1 -1
  185. package/src/decorators/metadata.ts +1 -1
  186. package/src/decorators/model.decorator.ts +1 -1
  187. package/src/decorators/repository.decorator.ts +1 -1
  188. package/src/define-model-class.ts +1 -1
  189. package/src/define-repository-class.ts +1 -1
  190. package/src/errors/entity-not-found.error.ts +1 -1
  191. package/src/errors/index.ts +2 -1
  192. package/src/errors/invalid-polymorphism.error.ts +28 -0
  193. package/src/errors/invalid-relation.error.ts +2 -2
  194. package/src/index.ts +1 -1
  195. package/src/mixins/index.ts +1 -1
  196. package/src/mixins/repository.mixin.ts +1 -6
  197. package/src/model.ts +20 -1
  198. package/src/relations/belongs-to/belongs-to.accessor.ts +36 -7
  199. package/src/relations/belongs-to/belongs-to.decorator.ts +2 -2
  200. package/src/relations/belongs-to/belongs-to.helpers.ts +37 -10
  201. package/src/relations/belongs-to/belongs-to.inclusion-resolver.ts +109 -18
  202. package/src/relations/belongs-to/belongs-to.repository.ts +78 -18
  203. package/src/relations/belongs-to/index.ts +3 -3
  204. package/src/relations/has-many/has-many-through.helpers.ts +28 -2
  205. package/src/relations/has-many/has-many-through.inclusion-resolver.ts +118 -27
  206. package/src/relations/has-many/has-many-through.repository-factory.ts +22 -4
  207. package/src/relations/has-many/has-many-through.repository.ts +344 -77
  208. package/src/relations/has-many/has-many.decorator.ts +1 -1
  209. package/src/relations/has-many/has-many.helpers.ts +1 -1
  210. package/src/relations/has-many/has-many.inclusion-resolver.ts +1 -1
  211. package/src/relations/has-many/has-many.repository-factory.ts +1 -1
  212. package/src/relations/has-many/has-many.repository.ts +1 -1
  213. package/src/relations/has-many/index.ts +3 -3
  214. package/src/relations/has-one/has-one.decorator.ts +1 -1
  215. package/src/relations/has-one/has-one.helpers.ts +41 -14
  216. package/src/relations/has-one/has-one.inclusion-resolver.ts +110 -16
  217. package/src/relations/has-one/has-one.repository-factory.ts +35 -7
  218. package/src/relations/has-one/has-one.repository.ts +189 -36
  219. package/src/relations/has-one/index.ts +1 -1
  220. package/src/relations/index.ts +3 -1
  221. package/src/relations/references-many/index.ts +9 -0
  222. package/src/relations/references-many/references-many.accessor.ts +76 -0
  223. package/src/relations/references-many/references-many.decorator.ts +100 -0
  224. package/src/relations/references-many/references-many.helpers.ts +82 -0
  225. package/src/relations/references-many/references-many.inclusion-resolver.ts +80 -0
  226. package/src/relations/references-many/references-many.repository.ts +55 -0
  227. package/src/relations/relation.decorator.ts +1 -13
  228. package/src/relations/relation.filter.solver.ts +56 -0
  229. package/src/relations/relation.helpers.ts +17 -2
  230. package/src/relations/relation.types.ts +52 -1
  231. package/src/repositories/constraint-utils.ts +1 -1
  232. package/src/repositories/index.ts +3 -3
  233. package/src/repositories/kv.repository.bridge.ts +1 -1
  234. package/src/repositories/kv.repository.ts +3 -3
  235. package/src/repositories/legacy-juggler-bridge.ts +79 -15
  236. package/src/repositories/repository.ts +1 -1
  237. package/src/type-resolver.ts +1 -1
  238. package/src/types/any.ts +1 -1
  239. package/src/types/array.ts +1 -1
  240. package/src/types/boolean.ts +1 -1
  241. package/src/types/buffer.ts +1 -1
  242. package/src/types/date.ts +1 -1
  243. package/src/types/index.ts +1 -1
  244. package/src/types/model.ts +1 -1
  245. package/src/types/null.ts +1 -1
  246. package/src/types/number.ts +1 -1
  247. package/src/types/object.ts +1 -1
  248. package/src/types/string.ts +1 -1
  249. package/src/types/type.ts +1 -1
  250. package/src/types/union.ts +1 -1
@@ -1,4 +1,4 @@
1
- // Copyright IBM Corp. 2019,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
@@ -18,6 +18,7 @@ const debug = debugFactory('loopback:repository:relations:has-one:helpers');
18
18
  export type HasOneResolvedDefinition = HasOneDefinition & {
19
19
  keyFrom: string;
20
20
  keyTo: string;
21
+ polymorphic: false | {discriminator: string};
21
22
  };
22
23
 
23
24
  /**
@@ -60,25 +61,51 @@ export function resolveHasOneMetadata(
60
61
  keyFrom = sourceModel.getIdProperties()[0];
61
62
  }
62
63
 
64
+ let keyTo;
63
65
  // Make sure that if it already keys to the foreign key property,
64
66
  // the key exists in the target model
65
67
  if (relationMeta.keyTo && targetModelProperties[relationMeta.keyTo]) {
66
68
  // The explicit cast is needed because of a limitation of type inference
67
- return Object.assign(relationMeta, {keyFrom}) as HasOneResolvedDefinition;
68
- }
69
+ keyTo = relationMeta.keyTo;
70
+ } else {
71
+ debug(
72
+ 'Resolved model %s from given metadata: %o',
73
+ targetModel.modelName,
74
+ targetModel,
75
+ );
76
+ keyTo = camelCase(sourceModel.modelName + '_id');
77
+ const hasDefaultFkProperty = targetModelProperties[keyTo];
69
78
 
70
- debug(
71
- 'Resolved model %s from given metadata: %o',
72
- targetModel.modelName,
73
- targetModel,
74
- );
75
- const defaultFkName = camelCase(sourceModel.modelName + '_id');
76
- const hasDefaultFkProperty = targetModelProperties[defaultFkName];
79
+ if (!hasDefaultFkProperty) {
80
+ const reason = `target model ${targetModel.name} is missing definition of foreign key ${keyTo}`;
81
+ throw new InvalidRelationError(reason, relationMeta);
82
+ }
83
+ }
77
84
 
78
- if (!hasDefaultFkProperty) {
79
- const reason = `target model ${targetModel.name} is missing definition of foreign key ${defaultFkName}`;
80
- throw new InvalidRelationError(reason, relationMeta);
85
+ let polymorphic: false | {discriminator: string};
86
+ if (
87
+ relationMeta.polymorphic === undefined ||
88
+ relationMeta.polymorphic === false ||
89
+ !relationMeta.polymorphic
90
+ ) {
91
+ const polymorphicFalse = false as const;
92
+ polymorphic = polymorphicFalse;
93
+ } else {
94
+ if (relationMeta.polymorphic === true) {
95
+ const polymorphicObject: {discriminator: string} = {
96
+ discriminator: camelCase(relationMeta.target().name + '_type'),
97
+ };
98
+ polymorphic = polymorphicObject;
99
+ } else {
100
+ const polymorphicObject: {discriminator: string} =
101
+ relationMeta.polymorphic as {discriminator: string};
102
+ polymorphic = polymorphicObject;
103
+ }
81
104
  }
82
105
 
83
- return Object.assign(relationMeta, {keyFrom, keyTo: defaultFkName});
106
+ return Object.assign(relationMeta, {
107
+ keyFrom: keyFrom,
108
+ keyTo: keyTo,
109
+ polymorphic: polymorphic,
110
+ });
84
111
  }
@@ -1,9 +1,11 @@
1
- // Copyright IBM Corp. 2019,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {Filter, InclusionFilter} from '@loopback/filter';
7
+ import {cloneDeep} from 'lodash';
8
+ import {includeFieldIfNot, InvalidPolymorphismError} from '../../';
7
9
  import {AnyObject, Options} from '../../common-types';
8
10
  import {Entity} from '../../model';
9
11
  import {EntityCrudRepository} from '../../repositories';
@@ -22,18 +24,22 @@ import {resolveHasOneMetadata} from './has-one.helpers';
22
24
  *
23
25
  * Notice: scope field for inclusion is not supported yet.
24
26
  *
25
- * @param meta
26
- * @param getTargetRepo
27
+ * @param meta - resolved HasOneMetadata
28
+ * @param getTargetRepoDict - dictionary of target model type - target repository
29
+ * i.e where related instances for different types are
27
30
  */
31
+
28
32
  export function createHasOneInclusionResolver<
29
33
  Target extends Entity,
30
34
  TargetID,
31
35
  TargetRelations extends object,
32
36
  >(
33
37
  meta: HasOneDefinition,
34
- getTargetRepo: Getter<
35
- EntityCrudRepository<Target, TargetID, TargetRelations>
36
- >,
38
+ getTargetRepoDict: {
39
+ [repoType: string]: Getter<
40
+ EntityCrudRepository<Target, TargetID, TargetRelations>
41
+ >;
42
+ },
37
43
  ): InclusionResolver<Entity, Target> {
38
44
  const relationMeta = resolveHasOneMetadata(meta);
39
45
 
@@ -44,22 +50,110 @@ export function createHasOneInclusionResolver<
44
50
  ): Promise<((Target & TargetRelations) | undefined)[]> {
45
51
  if (!entities.length) return [];
46
52
 
53
+ // Source ids are grouped by their target polymorphic types
54
+ // Each type search for target instances and then merge together in a merge-sort-like manner
55
+
47
56
  const sourceKey = relationMeta.keyFrom;
48
- const sourceIds = entities.map(e => (e as AnyObject)[sourceKey]);
49
57
  const targetKey = relationMeta.keyTo as StringKeyOf<Target>;
58
+ const targetDiscriminator: keyof Entity | undefined =
59
+ relationMeta.polymorphic
60
+ ? (relationMeta.polymorphic.discriminator as keyof Entity)
61
+ : undefined;
50
62
 
51
63
  const scope =
52
64
  typeof inclusion === 'string' ? {} : (inclusion.scope as Filter<Target>);
53
65
 
54
- const targetRepo = await getTargetRepo();
55
- const targetsFound = await findByForeignKeys(
56
- targetRepo,
57
- targetKey,
58
- sourceIds,
59
- scope,
60
- options,
61
- );
66
+ // sourceIds in {targetType -> sourceId}
67
+ const sourceIdsCategorized: {
68
+ [concreteItemType: string]: Target[StringKeyOf<Target>][];
69
+ } = {};
70
+ if (targetDiscriminator) {
71
+ entities.forEach((value, index, allEntites) => {
72
+ const concreteType = String(value[targetDiscriminator]);
73
+ if (!getTargetRepoDict[concreteType]) {
74
+ throw new InvalidPolymorphismError(concreteType, targetDiscriminator);
75
+ }
76
+ if (!sourceIdsCategorized[concreteType]) {
77
+ sourceIdsCategorized[concreteType] = [];
78
+ }
79
+ sourceIdsCategorized[concreteType].push(
80
+ (value as AnyObject)[sourceKey],
81
+ );
82
+ });
83
+ } else {
84
+ const concreteType = relationMeta.target().name;
85
+ if (!getTargetRepoDict[concreteType]) {
86
+ throw new InvalidPolymorphismError(concreteType);
87
+ }
88
+ entities.forEach((value, index, allEntites) => {
89
+ if (!sourceIdsCategorized[concreteType]) {
90
+ sourceIdsCategorized[concreteType] = [];
91
+ }
92
+ sourceIdsCategorized[concreteType].push(
93
+ (value as AnyObject)[sourceKey],
94
+ );
95
+ });
96
+ }
97
+
98
+ // Ensure targetKey is included otherwise flatten function cannot work
99
+ const changedTargetKeyField = includeFieldIfNot(scope?.fields, targetKey);
100
+ let needToRemoveTargetKeyFieldLater = false;
101
+ if (changedTargetKeyField !== false) {
102
+ scope.fields = changedTargetKeyField;
103
+ needToRemoveTargetKeyFieldLater = true;
104
+ }
105
+ // Each sourceIds array with same target type extract target instances
106
+ const targetCategorized: {
107
+ [concreteItemType: string]: ((Target & TargetRelations) | undefined)[];
108
+ } = {};
109
+ for (const k of Object.keys(sourceIdsCategorized)) {
110
+ const targetRepo = await getTargetRepoDict[k]();
111
+ const targetsFound = await findByForeignKeys(
112
+ targetRepo,
113
+ targetKey,
114
+ sourceIdsCategorized[k],
115
+ scope,
116
+ Object.assign(cloneDeep(options ?? {}), {polymorphicType: k}),
117
+ );
118
+ targetCategorized[k] = flattenTargetsOfOneToOneRelation(
119
+ sourceIdsCategorized[k],
120
+ targetsFound,
121
+ targetKey,
122
+ );
123
+
124
+ // Remove targetKey if should be excluded but included above
125
+ if (needToRemoveTargetKeyFieldLater) {
126
+ targetCategorized[k] = targetCategorized[k].map(e => {
127
+ if (e) {
128
+ delete e[targetKey];
129
+ }
130
+ return e;
131
+ });
132
+ }
133
+ }
62
134
 
63
- return flattenTargetsOfOneToOneRelation(sourceIds, targetsFound, targetKey);
135
+ // Merge
136
+ // Why the order is correct:
137
+ // e.g. target model 1 = a, target model 2 = b
138
+ // all entities: [S(a-1), S(a-2), S(b-3), S(a-4), S(b-5)]
139
+ // a-result: [a-1, a-2, a-4]
140
+ // b-result: [b-3, b-4]
141
+ // merged:
142
+ // entities[1]->a => targets: [a-1 from a-result.shift()]
143
+ // entities[2]->a => targets: [a-1, a-2 from a-result.shift()]
144
+ // entities[3]->b => targets: [a-1, a-2, b-3 from b-result.shift()]
145
+ // entities[4]->a => targets: [a-1, a-2, b-3, a-4 from a-result.shift()]
146
+ // entities[5]->b => targets: [a-1, a-2, b-3, a-4, b-5 from b-result.shift()]
147
+ if (targetDiscriminator) {
148
+ const allTargets: ((Target & TargetRelations) | undefined)[] = [];
149
+ entities.forEach((value, index, allEntites) => {
150
+ allTargets.push(
151
+ targetCategorized[String(value[targetDiscriminator])].shift(),
152
+ );
153
+ });
154
+ return allTargets;
155
+ } else {
156
+ return targetCategorized[relationMeta.target().name];
157
+ }
64
158
  };
65
159
  }
@@ -1,4 +1,4 @@
1
- // Copyright IBM Corp. 2018,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
@@ -36,10 +36,14 @@ export interface HasOneRepositoryFactory<
36
36
  * via a HasOne relation, then, the relational repository returned by the
37
37
  * factory function would be constrained by a Customer model instance's id(s).
38
38
  *
39
+ * If the target model is polymorphic, i.e. stored within different repositories,
40
+ * supply the targetRepositoryGetter with a dictionary in the form of {[typeName: string]: repositoryGetter}
41
+ *
39
42
  * @param relationMetadata - The relation metadata used to describe the
40
43
  * relationship and determine how to apply the constraint.
41
- * @param targetRepositoryGetter - The repository which represents the target model of a
42
- * relation attached to a datasource.
44
+ * @param targetRepositoryGetter - The repository or a dictionary of classname - repository,
45
+ * which represents the target model of a relation attached to a datasource.
46
+ * For the dictionary, the key is the class name of the concrete class the the polymorphic model.
43
47
  * @returns The factory function which accepts a foreign key value to constrain
44
48
  * the given target repository
45
49
  */
@@ -49,9 +53,21 @@ export function createHasOneRepositoryFactory<
49
53
  ForeignKeyType,
50
54
  >(
51
55
  relationMetadata: HasOneDefinition,
52
- targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
56
+ targetRepositoryGetter:
57
+ | Getter<EntityCrudRepository<Target, TargetID>>
58
+ | {
59
+ [repoType: string]: Getter<EntityCrudRepository<Target, TargetID>>;
60
+ },
53
61
  ): HasOneRepositoryFactory<Target, ForeignKeyType> {
54
62
  const meta = resolveHasOneMetadata(relationMetadata);
63
+ // resolve the repositoryGetter into a dictionary
64
+ if (typeof targetRepositoryGetter === 'function') {
65
+ targetRepositoryGetter = {
66
+ [meta.target().name]: targetRepositoryGetter as Getter<
67
+ EntityCrudRepository<Target, TargetID>
68
+ >,
69
+ };
70
+ }
55
71
  debug('Resolved HasOne relation metadata: %o', meta);
56
72
  const result: HasOneRepositoryFactory<Target, ForeignKeyType> = function (
57
73
  fkValue: ForeignKeyType,
@@ -62,11 +78,23 @@ export function createHasOneRepositoryFactory<
62
78
  Target,
63
79
  TargetID,
64
80
  EntityCrudRepository<Target, TargetID>
65
- >(targetRepositoryGetter, constraint as DataObject<Target>);
81
+ >(
82
+ targetRepositoryGetter as {
83
+ [repoType: string]: Getter<EntityCrudRepository<Target, TargetID>>;
84
+ },
85
+ constraint as DataObject<Target>,
86
+ relationMetadata.target,
87
+ );
66
88
  };
67
- result.inclusionResolver = createHasOneInclusionResolver(
89
+ result.inclusionResolver = createHasOneInclusionResolver<
90
+ Target,
91
+ TargetID,
92
+ object
93
+ >(
68
94
  meta,
69
- targetRepositoryGetter,
95
+ targetRepositoryGetter as {
96
+ [repoType: string]: Getter<EntityCrudRepository<Target, TargetID>>;
97
+ },
70
98
  );
71
99
  return result;
72
100
  }
@@ -1,12 +1,14 @@
1
- // Copyright IBM Corp. 2018,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {Getter} from '@loopback/core';
7
7
  import {Filter} from '@loopback/filter';
8
+ import {cloneDeep} from 'lodash';
9
+ import {TypeResolver} from '../../';
8
10
  import {Count, DataObject, Options} from '../../common-types';
9
- import {EntityNotFoundError} from '../../errors';
11
+ import {EntityNotFoundError, InvalidPolymorphismError} from '../../errors';
10
12
  import {Entity} from '../../model';
11
13
  import {
12
14
  constrainDataObject,
@@ -23,39 +25,58 @@ export interface HasOneRepository<Target extends Entity> {
23
25
  * Create a target model instance
24
26
  * @param targetModelData - The target model data
25
27
  * @param options - Options for the operation
28
+ * options.polymorphicType - If polymorphic target model,
29
+ * specify of which concrete model the created instance should be
26
30
  * @returns A promise which resolves to the newly created target model instance
27
31
  */
28
32
  create(
29
33
  targetModelData: DataObject<Target>,
30
- options?: Options,
34
+ options?: Options & {polymorphicType?: string},
31
35
  ): Promise<Target>;
32
36
 
33
37
  /**
34
38
  * Find the only target model instance that belongs to the declaring model.
35
39
  * @param filter - Query filter without a Where condition
36
40
  * @param options - Options for the operations
41
+ * options.polymorphicType - a string or a string array of polymorphic type names
42
+ * to specify which repositories should are expected to be searched
43
+ * It is highly recommended to contain this param especially for
44
+ * datasources using deplicated ids across tables
37
45
  * @returns A promise resolved with the target object or rejected
38
46
  * with an EntityNotFoundError when target model instance was not found.
39
47
  */
40
48
  get(
41
49
  filter?: Pick<Filter<Target>, Exclude<keyof Filter<Target>, 'where'>>,
42
- options?: Options,
50
+ options?: Options & {polymorphicType?: string | string[]},
43
51
  ): Promise<Target>;
44
52
 
45
53
  /**
46
54
  * Delete the related target model instance
47
55
  * @param options
56
+ * options.polymorphicType - a string or a string array of polymorphic type names
57
+ * to specify which repositories should are expected to be searched
58
+ * It is highly recommended to contain this param especially for
59
+ * datasources using deplicated ids across tables
48
60
  * @returns A promise which resolves the deleted target model instances
49
61
  */
50
- delete(options?: Options): Promise<Count>;
62
+ delete(
63
+ options?: Options & {polymorphicType?: string | string[]},
64
+ ): Promise<Count>;
51
65
 
52
66
  /**
53
67
  * Patch the related target model instance
54
68
  * @param dataObject - The target model fields and their new values to patch
69
+ * If the target models are of different types, this should be a dictionary
55
70
  * @param options
71
+ * options.isPolymorphic - whether dataObject is a dictionary
56
72
  * @returns A promise which resolves the patched target model instances
57
73
  */
58
- patch(dataObject: DataObject<Target>, options?: Options): Promise<Count>;
74
+ patch(
75
+ dataObject:
76
+ | DataObject<Target>
77
+ | {[polymorphicType: string]: DataObject<Target>},
78
+ options?: Options & {isPolymorphic?: boolean},
79
+ ): Promise<Count>;
59
80
  }
60
81
 
61
82
  export class DefaultHasOneRepository<
@@ -66,20 +87,65 @@ export class DefaultHasOneRepository<
66
87
  {
67
88
  /**
68
89
  * Constructor of DefaultHasOneEntityCrudRepository
69
- * @param getTargetRepository - the getter of the related target model repository instance
90
+ * @param getTargetRepository - either a dictionary of target model type - target repository instance
91
+ * or a single target repository instance
92
+ * e.g. if the target is of a non-polymorphic type "Student", put the studentRepositoryGetterInstance
93
+ * if the target is of a polymorphic type "Person" which can be either a "Student" or a "Teacher"
94
+ * then put "{Student: studentRepositoryGetterInstance, Teacher: teacherRepositoryGetterInstance}"
70
95
  * @param constraint - the key value pair representing foreign key name to constrain
71
96
  * the target repository instance
97
+ * @param targetResolver - () => Target to resolve the target class
98
+ * e.g. if the target is of type "Student", then put "() => Student"
72
99
  */
100
+
73
101
  constructor(
74
- public getTargetRepository: Getter<TargetRepository>,
102
+ public getTargetRepository:
103
+ | Getter<TargetRepository>
104
+ | {
105
+ [repoType: string]: Getter<TargetRepository>;
106
+ },
75
107
  public constraint: DataObject<TargetEntity>,
76
- ) {}
108
+ public targetResolver: TypeResolver<Entity, typeof Entity>,
109
+ ) {
110
+ if (typeof getTargetRepository === 'function') {
111
+ this.getTargetRepositoryDict = {
112
+ [targetResolver().name]:
113
+ getTargetRepository as Getter<TargetRepository>,
114
+ };
115
+ } else {
116
+ this.getTargetRepositoryDict = getTargetRepository as {
117
+ [repoType: string]: Getter<TargetRepository>;
118
+ };
119
+ }
120
+ }
121
+
122
+ public getTargetRepositoryDict: {
123
+ [repoType: string]: Getter<TargetRepository>;
124
+ };
77
125
 
78
126
  async create(
79
127
  targetModelData: DataObject<TargetEntity>,
80
- options?: Options,
128
+ options?: Options & {polymorphicType?: string},
81
129
  ): Promise<TargetEntity> {
82
- const targetRepository = await this.getTargetRepository();
130
+ let polymorphicTypeName = options?.polymorphicType;
131
+ if (polymorphicTypeName) {
132
+ if (!this.getTargetRepositoryDict[polymorphicTypeName]) {
133
+ throw new InvalidPolymorphismError(polymorphicTypeName);
134
+ }
135
+ } else {
136
+ if (Object.keys(this.getTargetRepositoryDict).length > 1) {
137
+ console.warn(
138
+ 'It is highly recommended to specify the polymorphicType param when using polymorphic types.',
139
+ );
140
+ }
141
+ polymorphicTypeName = this.targetResolver().name;
142
+ if (!this.getTargetRepositoryDict[polymorphicTypeName]) {
143
+ throw new InvalidPolymorphismError(polymorphicTypeName);
144
+ }
145
+ }
146
+ const targetRepository = await this.getTargetRepositoryDict[
147
+ polymorphicTypeName
148
+ ]();
83
149
  return targetRepository.create(
84
150
  constrainDataObject(targetModelData, this.constraint),
85
151
  options,
@@ -91,37 +157,124 @@ export class DefaultHasOneRepository<
91
157
  Filter<TargetEntity>,
92
158
  Exclude<keyof Filter<TargetEntity>, 'where'>
93
159
  >,
94
- options?: Options,
160
+ options?: Options & {polymorphicType?: string | string[]},
95
161
  ): Promise<TargetEntity> {
96
- const targetRepository = await this.getTargetRepository();
97
- const found = await targetRepository.find(
98
- Object.assign({limit: 1}, constrainFilter(filter, this.constraint)),
99
- options,
100
- );
101
- if (found.length < 1) {
102
- // We don't have a direct access to the foreign key value here :(
103
- const id = 'constraint ' + JSON.stringify(this.constraint);
104
- throw new EntityNotFoundError(targetRepository.entityClass, id);
162
+ let polymorphicTypes = options?.polymorphicType;
163
+ let allKeys: string[];
164
+ if (Object.keys(this.getTargetRepositoryDict).length <= 1) {
165
+ allKeys = Object.keys(this.getTargetRepositoryDict);
166
+ } else if (!polymorphicTypes || polymorphicTypes.length === 0) {
167
+ console.warn(
168
+ 'It is highly recommended to specify the polymorphicType param when using polymorphic types.',
169
+ );
170
+ allKeys = Object.keys(this.getTargetRepositoryDict);
171
+ } else {
172
+ if (typeof polymorphicTypes === 'string') {
173
+ polymorphicTypes = [polymorphicTypes];
174
+ }
175
+ allKeys = [];
176
+ new Set(polymorphicTypes!).forEach(element => {
177
+ if (Object.keys(this.getTargetRepositoryDict).includes(element)) {
178
+ allKeys.push(element);
179
+ }
180
+ });
105
181
  }
106
- return found[0];
182
+ for (const key of allKeys) {
183
+ const targetRepository = await this.getTargetRepositoryDict[key]();
184
+ const found = await targetRepository.find(
185
+ Object.assign({limit: 1}, constrainFilter(filter, this.constraint)),
186
+ Object.assign(cloneDeep(options ?? {}), {polymorphicType: key}),
187
+ );
188
+ if (found.length >= 1) {
189
+ return found[0];
190
+ }
191
+ }
192
+ // We don't have a direct access to the foreign key value here :(
193
+ const id = 'constraint ' + JSON.stringify(this.constraint);
194
+ throw new EntityNotFoundError(this.targetResolver().name, id);
107
195
  }
108
- async delete(options?: Options): Promise<Count> {
109
- const targetRepository = await this.getTargetRepository();
110
- return targetRepository.deleteAll(
111
- constrainWhere({}, this.constraint),
112
- options,
113
- );
196
+
197
+ async delete(
198
+ options?: Options & {polymorphicType?: string | string[]},
199
+ ): Promise<Count> {
200
+ let polymorphicTypes = options?.polymorphicType;
201
+ let allKeys: string[];
202
+ if (Object.keys(this.getTargetRepositoryDict).length <= 1) {
203
+ allKeys = Object.keys(this.getTargetRepositoryDict);
204
+ } else if (!polymorphicTypes || polymorphicTypes.length === 0) {
205
+ console.warn(
206
+ 'It is highly recommended to specify the polymorphicType param when using polymorphic types.',
207
+ );
208
+ allKeys = Object.keys(this.getTargetRepositoryDict);
209
+ } else {
210
+ if (typeof polymorphicTypes === 'string') {
211
+ polymorphicTypes = [polymorphicTypes];
212
+ }
213
+ allKeys = [];
214
+ new Set(polymorphicTypes!).forEach(element => {
215
+ if (Object.keys(this.getTargetRepositoryDict).includes(element)) {
216
+ allKeys.push(element);
217
+ }
218
+ });
219
+ }
220
+ let total = 0;
221
+ for (const key of allKeys) {
222
+ const targetRepository = await this.getTargetRepositoryDict[key]();
223
+ total +=
224
+ (
225
+ await targetRepository.deleteAll(
226
+ constrainWhere({}, this.constraint),
227
+ options,
228
+ )
229
+ )?.count ?? 0;
230
+ }
231
+ return {count: total};
114
232
  }
115
233
 
116
234
  async patch(
117
- dataObject: DataObject<TargetEntity>,
118
- options?: Options,
235
+ dataObject:
236
+ | DataObject<TargetEntity>
237
+ | {[polymorphicType: string]: DataObject<TargetEntity>},
238
+ options?: Options & {isPolymorphic?: boolean},
119
239
  ): Promise<Count> {
120
- const targetRepository = await this.getTargetRepository();
121
- return targetRepository.updateAll(
122
- constrainDataObject(dataObject, this.constraint),
123
- constrainWhere({}, this.constraint),
124
- options,
125
- );
240
+ const isMultipleTypes = options?.isPolymorphic;
241
+ let allKeys: string[];
242
+ if (!isMultipleTypes) {
243
+ if (Object.keys(this.getTargetRepositoryDict).length > 1) {
244
+ console.warn(
245
+ 'It is highly recommended to specify the isPolymorphic param and pass in a dictionary of dataobjects when using polymorphic types.',
246
+ );
247
+ }
248
+ allKeys = Object.keys(this.getTargetRepositoryDict);
249
+ } else {
250
+ allKeys = [];
251
+ new Set(Object.keys(dataObject)).forEach(element => {
252
+ if (Object.keys(this.getTargetRepositoryDict).includes(element)) {
253
+ allKeys.push(element);
254
+ }
255
+ });
256
+ }
257
+ let total = 0;
258
+ for (const key of allKeys) {
259
+ const targetRepository = await this.getTargetRepositoryDict[key]();
260
+ total +=
261
+ (
262
+ await targetRepository.updateAll(
263
+ constrainDataObject(
264
+ isMultipleTypes
265
+ ? (
266
+ dataObject as {
267
+ [polymorphicType: string]: DataObject<TargetEntity>;
268
+ }
269
+ )[key]
270
+ : (dataObject as DataObject<TargetEntity>),
271
+ this.constraint,
272
+ ),
273
+ constrainWhere({}, this.constraint),
274
+ options,
275
+ )
276
+ )?.count ?? 0;
277
+ }
278
+ return {count: total};
126
279
  }
127
280
  }
@@ -1,4 +1,4 @@
1
- // Copyright IBM Corp. 2018,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
@@ -1,4 +1,4 @@
1
- // Copyright IBM Corp. 2018,2020. All Rights Reserved.
1
+ // Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/repository
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
@@ -6,6 +6,8 @@
6
6
  export * from './belongs-to';
7
7
  export * from './has-many';
8
8
  export * from './has-one';
9
+ export * from './references-many';
9
10
  export * from './relation.decorator';
11
+ export * from './relation.filter.solver';
10
12
  export * from './relation.helpers';
11
13
  export * from './relation.types';
@@ -0,0 +1,9 @@
1
+ // Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2
+ // Node module: @loopback/repository
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ export * from './references-many.accessor';
7
+ export * from './references-many.decorator';
8
+ export * from './references-many.inclusion-resolver';
9
+ export * from './references-many.repository';