UNPKG

11.1 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
3// Node module: @loopback/repository
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.createHasManyThroughInclusionResolver = void 0;
8const tslib_1 = require("tslib");
9const filter_1 = require("@loopback/filter");
10const debug_1 = tslib_1.__importDefault(require("debug"));
11const lodash_1 = tslib_1.__importDefault(require("lodash"));
12const __1 = require("../..");
13const relation_helpers_1 = require("../relation.helpers");
14const has_many_through_helpers_1 = require("./has-many-through.helpers");
15const debug = (0, debug_1.default)('loopback:repository:relations:has-many-through:inclusion-resolver');
16/**
17 * Creates InclusionResolver for HasManyThrough relation.
18 * Notice that this function only generates the inclusionResolver.
19 * It doesn't register it for the source repository.
20 *
21 *
22 * @param meta - metadata of the hasMany relation (including through)
23 * @param getThroughRepo - through repository getter i.e. where through
24 * instances are
25 * @param getTargetRepo - target repository getter i.e where target instances
26 * are
27 */
28function createHasManyThroughInclusionResolver(meta, getThroughRepo, getTargetRepoDict) {
29 const relationMeta = (0, has_many_through_helpers_1.resolveHasManyThroughMetadata)(meta);
30 return async function fetchHasManyThroughModels(entities, inclusion, options) {
31 var _a, _b;
32 if (!relationMeta.through) {
33 throw new Error(`relationMeta.through must be defined on ${relationMeta}`);
34 }
35 if (!entities.length)
36 return [];
37 debug('Fetching target models for entities:', entities);
38 debug('Relation metadata:', relationMeta);
39 const sourceKey = relationMeta.keyFrom;
40 const sourceIds = entities.map(e => e[sourceKey]);
41 const targetKey = relationMeta.keyTo;
42 if (!relationMeta.through) {
43 throw new Error(`relationMeta.through must be defined on ${relationMeta}`);
44 }
45 const throughKeyTo = relationMeta.through.keyTo;
46 const throughKeyFrom = relationMeta.through.keyFrom;
47 debug('Parameters:', {
48 sourceKey,
49 sourceIds,
50 targetKey,
51 throughKeyTo,
52 throughKeyFrom,
53 });
54 debug('sourceId types', sourceIds.map(i => typeof i));
55 const throughRepo = await getThroughRepo();
56 // find through models
57 const throughFound = await (0, relation_helpers_1.findByForeignKeys)(throughRepo, throughKeyFrom, sourceIds, {}, // scope will be applied at the target level
58 options);
59 const throughResult = (0, relation_helpers_1.flattenTargetsOfOneToManyRelation)(sourceIds, throughFound, throughKeyFrom);
60 const scope = typeof inclusion === 'string'
61 ? {}
62 : inclusion.scope;
63 // whether the polymorphism is configured
64 const targetDiscriminator = relationMeta.through.polymorphic
65 ? relationMeta.through.polymorphic.discriminator
66 : undefined;
67 if (targetDiscriminator) {
68 // put through results into arrays based on the target polymorphic types
69 const throughArrayByTargetType = {};
70 for (const throughArray of throughResult) {
71 if (throughArray) {
72 for (const throughItem of throughArray) {
73 const targetType = String(throughItem[targetDiscriminator]);
74 if (!getTargetRepoDict[targetType]) {
75 throw new __1.InvalidPolymorphismError(targetType, String(targetDiscriminator));
76 }
77 if (!throughArrayByTargetType[targetType]) {
78 throughArrayByTargetType[targetType] = [];
79 }
80 throughArrayByTargetType[targetType].push(throughItem);
81 }
82 }
83 }
84 // get targets based on their polymorphic types
85 const targetOfTypes = {};
86 for (const targetType of Object.keys(throughArrayByTargetType)) {
87 const targetIds = throughArrayByTargetType[targetType].map(throughItem => throughItem[throughKeyTo]);
88 const targetRepo = await getTargetRepoDict[targetType]();
89 const targetEntityList = await (0, relation_helpers_1.findByForeignKeys)(targetRepo, targetKey, targetIds, scope, options);
90 targetOfTypes[targetType] = targetEntityList;
91 }
92 // put targets into arrays reflecting their throughs
93 // Why the order is correct:
94 // e.g. through model = T(target instance), target model 1 = a, target model 2 = b
95 // all entities: [S1, S2, S2]
96 // through-result: [[T(b-11), T(a-12), T(b-13), T(b-14)], [T(a-21), T(a-22), T(b-23)], [T(b-31), T(b-32), T(a-33)]]
97 // through-array-by-target-type: {a:[T(a-12), T(a-21), T(a-22), T(a-33)] b: [T(b-11), T(b-13), T(b-14), T(b-23), T(b-31), T(b-32)]}
98 // target-array-by-target-type: {a:[a-12, a-21, a-22, a-33] b: [b-11, b-13, b-14, b-23, b-31, b-32]}
99 // merged:
100 // through-result[0][0]->b => targets: [[b-11 from b.shift()]]
101 // through-result[0][1]->a => targets: [[b-11, a-12 from a.shift()]]
102 // through-result[0][2]->b => targets: [[b-11, a-12, b-13 from b.shift()]]
103 // through-result[0][3]->b => targets: [[b-11, a-12, b-13, b-14 from b.shift()]]
104 // through-result[1][0]->a => targets: [[b-11, a-12, b-13, b-14], [a-21, from a.shift()]]
105 // through-result[1][1]->a => targets: [[b-11, a-12, b-13, b-14], [a-21, a-22 from a.shift()]]
106 // through-result[1][2]->b => targets: [[b-11, a-12, b-13, b-14], [a-21, a-22, b-23 from b.shift()]]
107 // through-result[2][0]->b => targets: [[b-11, a-12, b-13, b-14], [a-21, a-22, b-23], [b-31, from b.shift()]]
108 // through-result[2][1]->b => targets: [[b-11, a-12, b-13, b-14], [a-21, a-22, b-23], [b-31, b-32 from b.shift()]]
109 // through-result[2][1]->b => targets: [[b-11, a-12, b-13, b-14], [a-21, a-22, b-23], [b-31, b-32, a-33 from a.shift()]]
110 const allTargetsOfThrough = [];
111 for (const throughArray of throughResult) {
112 if (throughArray && throughArray.length > 0) {
113 const currentTargetThroughArray = [];
114 for (const throughItem of throughArray) {
115 const itemToAdd = targetOfTypes[String(throughItem[targetDiscriminator])].shift();
116 if (itemToAdd) {
117 currentTargetThroughArray.push(itemToAdd);
118 }
119 }
120 allTargetsOfThrough.push(currentTargetThroughArray);
121 }
122 else {
123 allTargetsOfThrough.push(undefined);
124 }
125 }
126 return allTargetsOfThrough;
127 }
128 else {
129 const targetRepo = await getTargetRepoDict[relationMeta.target().name]();
130 const result = [];
131 // Normalize field filter to an object like {[field]: boolean}
132 const filterBuilder = new filter_1.FilterBuilder();
133 const fieldFilter = filterBuilder.fields((_a = scope === null || scope === void 0 ? void 0 : scope.fields) !== null && _a !== void 0 ? _a : {}).filter
134 .fields;
135 // We need targetKey to create a map, as such it always needs to be included.
136 // Keep track of whether targetKey should be removed from the final result,
137 // whether by explicit omission (targetKey: false) or by implicit omission
138 // (anyOtherKey: true but no targetKey: true).
139 const omitTargetKeyFromFields = (Object.values(fieldFilter).includes(true) &&
140 fieldFilter[targetKey] !== true) ||
141 fieldFilter[targetKey] === false;
142 if (omitTargetKeyFromFields) {
143 if (fieldFilter[targetKey] === false) {
144 // Undo explicit omission
145 delete fieldFilter[targetKey];
146 }
147 else {
148 // Undo implicit omission
149 fieldFilter[targetKey] = true;
150 }
151 }
152 // get target ids from the through entities by foreign key
153 const allIds = lodash_1.default.uniq(throughResult
154 .filter(throughEntitySet => throughEntitySet !== undefined)
155 .map(throughEntitySet => throughEntitySet === null || throughEntitySet === void 0 ? void 0 : throughEntitySet.map(entity => entity[throughKeyTo]))
156 .flat());
157 // Omit limit from scope as those need to be applied per fK
158 const targetEntityList = await (0, relation_helpers_1.findByForeignKeys)(targetRepo, targetKey, allIds, { ...lodash_1.default.omit(scope !== null && scope !== void 0 ? scope : {}, ['limit', 'fields']), fields: fieldFilter }, {
159 ...options,
160 isThroughModelInclude: true,
161 });
162 const targetEntityIds = targetEntityList.map(targetEntity => { var _a, _b; return (_b = (_a = targetEntity[targetKey]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : targetEntity[targetKey]; });
163 const targetEntityMap = Object.fromEntries(targetEntityList.map(x => [
164 x[targetKey],
165 omitTargetKeyFromFields ? lodash_1.default.omit(x, [targetKey]) : x,
166 ]));
167 // convert from through entities to the target entities
168 for (const entityList of throughResult) {
169 if (entityList) {
170 const relatedIds = entityList.map(x => { var _a, _b; return (_b = (_a = x[throughKeyTo]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : x[throughKeyTo]; });
171 // Use the order of the original result set & apply limit
172 const sortedIds = lodash_1.default.intersection(targetEntityIds, relatedIds).slice(0, (_b = scope === null || scope === void 0 ? void 0 : scope.limit) !== null && _b !== void 0 ? _b : entityList.length);
173 // Make each result its own instance to avoid shenanigans by reference
174 result.push(lodash_1.default.cloneDeep(sortedIds.map(x => targetEntityMap[x])));
175 }
176 else {
177 // no entities found, add undefined to results
178 result.push(entityList);
179 }
180 }
181 debug('fetchHasManyThroughModels result', result);
182 return result;
183 }
184 };
185}
186exports.createHasManyThroughInclusionResolver = createHasManyThroughInclusionResolver;
187//# sourceMappingURL=has-many-through.inclusion-resolver.js.map
\No newline at end of file