1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import {Filter, InclusionFilter} from '@loopback/filter';
|
7 | import debugFactory from 'debug';
|
8 | import {InvalidPolymorphismError} from '../..';
|
9 | import {AnyObject, Options} from '../../common-types';
|
10 | import {Entity} from '../../model';
|
11 | import {EntityCrudRepository} from '../../repositories';
|
12 | import {
|
13 | findByForeignKeys,
|
14 | flattenTargetsOfOneToManyRelation,
|
15 | StringKeyOf,
|
16 | } from '../relation.helpers';
|
17 | import {Getter, HasManyDefinition, InclusionResolver} from '../relation.types';
|
18 | import {resolveHasManyThroughMetadata} from './has-many-through.helpers';
|
19 |
|
20 | const debug = debugFactory(
|
21 | 'loopback:repository:relations:has-many-through:inclusion-resolver',
|
22 | );
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | export function createHasManyThroughInclusionResolver<
|
37 | Through extends Entity,
|
38 | ThroughID,
|
39 | ThroughRelations extends object,
|
40 | Target extends Entity,
|
41 | TargetID,
|
42 | TargetRelations extends object,
|
43 | >(
|
44 | meta: HasManyDefinition,
|
45 | getThroughRepo: Getter<
|
46 | EntityCrudRepository<Through, ThroughID, ThroughRelations>
|
47 | >,
|
48 | getTargetRepoDict: {
|
49 | [repoType: string]: Getter<
|
50 | EntityCrudRepository<Target, TargetID, TargetRelations>
|
51 | >;
|
52 | },
|
53 | ): InclusionResolver<Entity, Target> {
|
54 | const relationMeta = resolveHasManyThroughMetadata(meta);
|
55 |
|
56 | return async function fetchHasManyThroughModels(
|
57 | entities: Entity[],
|
58 | inclusion: InclusionFilter,
|
59 | options?: Options,
|
60 | ): Promise<((Target & TargetRelations)[] | undefined)[]> {
|
61 | if (!relationMeta.through) {
|
62 | throw new Error(
|
63 | `relationMeta.through must be defined on ${relationMeta}`,
|
64 | );
|
65 | }
|
66 |
|
67 | if (!entities.length) return [];
|
68 |
|
69 | debug('Fetching target models for entities:', entities);
|
70 | debug('Relation metadata:', relationMeta);
|
71 |
|
72 | const sourceKey = relationMeta.keyFrom;
|
73 | const sourceIds = entities.map(e => (e as AnyObject)[sourceKey]);
|
74 | const targetKey = relationMeta.keyTo as StringKeyOf<Target>;
|
75 | if (!relationMeta.through) {
|
76 | throw new Error(
|
77 | `relationMeta.through must be defined on ${relationMeta}`,
|
78 | );
|
79 | }
|
80 | const throughKeyTo = relationMeta.through.keyTo as StringKeyOf<Through>;
|
81 | const throughKeyFrom = relationMeta.through.keyFrom as StringKeyOf<Through>;
|
82 |
|
83 | debug('Parameters:', {
|
84 | sourceKey,
|
85 | sourceIds,
|
86 | targetKey,
|
87 | throughKeyTo,
|
88 | throughKeyFrom,
|
89 | });
|
90 |
|
91 | debug(
|
92 | 'sourceId types',
|
93 | sourceIds.map(i => typeof i),
|
94 | );
|
95 |
|
96 | const throughRepo = await getThroughRepo();
|
97 |
|
98 |
|
99 | const throughFound = await findByForeignKeys(
|
100 | throughRepo,
|
101 | throughKeyFrom,
|
102 | sourceIds,
|
103 | {},
|
104 | options,
|
105 | );
|
106 |
|
107 | const throughResult = flattenTargetsOfOneToManyRelation(
|
108 | sourceIds,
|
109 | throughFound,
|
110 | throughKeyFrom,
|
111 | );
|
112 |
|
113 | const scope =
|
114 | typeof inclusion === 'string' ? {} : (inclusion.scope as Filter<Target>);
|
115 |
|
116 |
|
117 | const targetDiscriminator: keyof (Through & ThroughRelations) | undefined =
|
118 | relationMeta.through!.polymorphic
|
119 | ? (relationMeta.through!.polymorphic.discriminator as keyof (Through &
|
120 | ThroughRelations))
|
121 | : undefined;
|
122 | if (targetDiscriminator) {
|
123 |
|
124 | const throughArrayByTargetType: {
|
125 | [targetType: string]: (Through & ThroughRelations)[];
|
126 | } = {};
|
127 | for (const throughArray of throughResult) {
|
128 | if (throughArray) {
|
129 | for (const throughItem of throughArray) {
|
130 | const targetType = String(throughItem[targetDiscriminator]);
|
131 | if (!getTargetRepoDict[targetType]) {
|
132 | throw new InvalidPolymorphismError(
|
133 | targetType,
|
134 | String(targetDiscriminator),
|
135 | );
|
136 | }
|
137 | if (!throughArrayByTargetType[targetType]) {
|
138 | throughArrayByTargetType[targetType] = [];
|
139 | }
|
140 | throughArrayByTargetType[targetType].push(throughItem);
|
141 | }
|
142 | }
|
143 | }
|
144 |
|
145 | const targetOfTypes: {
|
146 | [targetType: string]: (Target & TargetRelations)[];
|
147 | } = {};
|
148 | for (const targetType of Object.keys(throughArrayByTargetType)) {
|
149 | const targetIds = throughArrayByTargetType[targetType].map(
|
150 | throughItem => throughItem[throughKeyTo],
|
151 | );
|
152 | const targetRepo = await getTargetRepoDict[targetType]();
|
153 | const targetEntityList = await findByForeignKeys<
|
154 | Target,
|
155 | TargetRelations,
|
156 | StringKeyOf<Target>
|
157 | >(targetRepo, targetKey, targetIds as unknown as [], scope, options);
|
158 | targetOfTypes[targetType] = targetEntityList;
|
159 | }
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | const allTargetsOfThrough: ((Target & TargetRelations)[] | undefined)[] =
|
179 | [];
|
180 | for (const throughArray of throughResult) {
|
181 | if (throughArray && throughArray.length > 0) {
|
182 | const currentTargetThroughArray: (Target & TargetRelations)[] = [];
|
183 | for (const throughItem of throughArray) {
|
184 | const itemToAdd =
|
185 | targetOfTypes[String(throughItem[targetDiscriminator])].shift();
|
186 | if (itemToAdd) {
|
187 | currentTargetThroughArray.push(itemToAdd);
|
188 | }
|
189 | }
|
190 | allTargetsOfThrough.push(currentTargetThroughArray);
|
191 | } else {
|
192 | allTargetsOfThrough.push(undefined);
|
193 | }
|
194 | }
|
195 | return allTargetsOfThrough;
|
196 | } else {
|
197 | const targetRepo = await getTargetRepoDict[relationMeta.target().name]();
|
198 | const result = [];
|
199 |
|
200 |
|
201 | for (const entityList of throughResult) {
|
202 | if (entityList) {
|
203 |
|
204 | const targetIds = entityList.map(entity => entity[throughKeyTo]);
|
205 |
|
206 |
|
207 | const targetEntityList = await findByForeignKeys<
|
208 | Target,
|
209 | TargetRelations,
|
210 | StringKeyOf<Target>
|
211 | >(targetRepo, targetKey, targetIds as unknown as [], scope, {
|
212 | ...options,
|
213 | isThroughModelInclude: true,
|
214 | });
|
215 | result.push(targetEntityList);
|
216 | } else {
|
217 |
|
218 | result.push(entityList);
|
219 | }
|
220 | }
|
221 |
|
222 | debug('fetchHasManyThroughModels result', result);
|
223 | return result;
|
224 | }
|
225 | };
|
226 | }
|