UNPKG

10.9 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.resolveHasManyThroughMetadata = exports.createThroughConstraintFromTarget = exports.getTargetIdsFromTargetModels = exports.createThroughConstraintFromSource = exports.getTargetKeysFromThroughModels = exports.createTargetConstraintFromThrough = void 0;
8const tslib_1 = require("tslib");
9const debug_1 = tslib_1.__importDefault(require("debug"));
10const lodash_1 = require("lodash");
11const __1 = require("../..");
12const has_many_helpers_1 = require("./has-many.helpers");
13const debug = (0, debug_1.default)('loopback:repository:relations:has-many-through:helpers');
14/**
15 * Creates target constraint based on through models
16 * @param relationMeta - resolved hasManyThrough metadata
17 * @param throughInstances - an array of through instances
18 *
19 * @example
20 * ```ts
21 * const resolvedMetadata = {
22 * // .. other props
23 * keyFrom: 'id',
24 * keyTo: 'id',
25 * through: {
26 * model: () => CategoryProductLink,
27 * keyFrom: 'categoryId',
28 * keyTo: 'productId',
29 * },
30 * };
31 * createTargetConstraintFromThrough(resolvedMetadata,[{
32 id: 2,
33 categoryId: 2,
34 productId: 8,
35 }]);
36 * >>> {id: 8}
37 * createTargetConstraintFromThrough(resolvedMetadata, [
38 {
39 id: 2,
40 categoryId: 2,
41 productId: 8,
42 }, {
43 id: 1,
44 categoryId: 2,
45 productId: 9,
46 }
47 ]);
48
49 >>> {id: {inq: [9, 8]}}
50 * ```
51 */
52function createTargetConstraintFromThrough(relationMeta, throughInstances) {
53 const fkValues = getTargetKeysFromThroughModels(relationMeta, throughInstances);
54 const targetPrimaryKey = relationMeta.keyTo;
55 // eslint-disable-next-line @typescript-eslint/no-explicit-any
56 const constraint = {
57 [targetPrimaryKey]: fkValues.length === 1 ? fkValues[0] : { inq: fkValues },
58 };
59 return constraint;
60}
61exports.createTargetConstraintFromThrough = createTargetConstraintFromThrough;
62/**
63 * Returns an array of target fks of the given throughInstances.
64 *
65 * @param relationMeta - resolved hasManyThrough metadata
66 * @param throughInstances - an array of through instances
67 *
68 * @example
69 * ```ts
70 * const resolvedMetadata = {
71 * // .. other props
72 * keyFrom: 'id',
73 * keyTo: 'id',
74 * through: {
75 * model: () => CategoryProductLink,
76 * keyFrom: 'categoryId',
77 * keyTo: 'productId',
78 * },
79 * };
80 * getTargetKeysFromThroughModels(resolvedMetadata,[{
81 id: 2,
82 categoryId: 2,
83 productId: 8,
84 }]);
85 * >>> [8]
86 * getTargetKeysFromThroughModels(resolvedMetadata, [
87 {
88 id: 2,
89 categoryId: 2,
90 productId: 8,
91 }, {
92 id: 1,
93 categoryId: 2,
94 productId: 9,
95 }
96 ]);
97 >>> [8, 9]
98 */
99function getTargetKeysFromThroughModels(relationMeta, throughInstances) {
100 const targetFkName = relationMeta.through.keyTo;
101 // eslint-disable-next-line @typescript-eslint/no-explicit-any
102 let fkValues = throughInstances.map((throughInstance) => throughInstance[targetFkName]);
103 fkValues = (0, __1.deduplicate)(fkValues);
104 return fkValues;
105}
106exports.getTargetKeysFromThroughModels = getTargetKeysFromThroughModels;
107/**
108 * Creates through constraint based on the source key
109 *
110 * @param relationMeta - resolved hasManyThrough metadata
111 * @param fkValue - foreign key of the source instance
112 * @internal
113 *
114 * @example
115 * ```ts
116 * const resolvedMetadata = {
117 * // .. other props
118 * keyFrom: 'id',
119 * keyTo: 'id',
120 * through: {
121 * model: () => CategoryProductLink,
122 * keyFrom: 'categoryId',
123 * keyTo: 'productId',
124 * },
125 * };
126 * createThroughConstraintFromSource(resolvedMetadata, 1);
127 *
128 * >>> {categoryId: 1}
129 * ```
130 */
131function createThroughConstraintFromSource(relationMeta, fkValue) {
132 const sourceFkName = relationMeta.through.keyFrom;
133 // eslint-disable-next-line @typescript-eslint/no-explicit-any
134 const constraint = { [sourceFkName]: fkValue };
135 return constraint;
136}
137exports.createThroughConstraintFromSource = createThroughConstraintFromSource;
138/**
139 * Returns an array of target ids of the given target instances.
140 *
141 * @param relationMeta - resolved hasManyThrough metadata
142 * @param targetInstances - an array of target instances
143 *
144 * @example
145 * ```ts
146 * const resolvedMetadata = {
147 * // .. other props
148 * keyFrom: 'id',
149 * keyTo: 'id',
150 * through: {
151 * model: () => CategoryProductLink,
152 * keyFrom: 'categoryId',
153 * keyTo: 'productId',
154 * },
155 * };
156 * getTargetKeysFromTargetModels(resolvedMetadata,[{
157 id: 2,
158 des: 'a target',
159 }]);
160 * >>> [2]
161 * getTargetKeysFromTargetModels(resolvedMetadata, [
162 {
163 id: 2,
164 des: 'a target',
165 }, {
166 id: 1,
167 des: 'a target',
168 }
169 ]);
170 >>> [2, 1]
171 */
172function getTargetIdsFromTargetModels(relationMeta, targetInstances) {
173 const targetId = relationMeta.keyTo;
174 // eslint-disable-next-line @typescript-eslint/no-explicit-any
175 let ids = [];
176 ids = targetInstances.map((targetInstance) => targetInstance[targetId]);
177 ids = (0, __1.deduplicate)(ids);
178 return ids;
179}
180exports.getTargetIdsFromTargetModels = getTargetIdsFromTargetModels;
181/**
182 * Creates through constraint based on the target foreign key
183 *
184 * @param relationMeta - resolved hasManyThrough metadata
185 * @param fkValue an array of the target instance foreign keys
186 * @internal
187 *
188 * @example
189 * ```ts
190 * const resolvedMetadata = {
191 * // .. other props
192 * keyFrom: 'id',
193 * keyTo: 'id',
194 * through: {
195 * model: () => CategoryProductLink,
196 * keyFrom: 'categoryId',
197 * keyTo: 'productId',
198 * },
199 * };
200 * createThroughConstraintFromTarget(resolvedMetadata, [3]);
201 *
202 * >>> {productId: 3}
203 *
204 * createThroughConstraintFromTarget(resolvedMetadata, [3,4]);
205 *
206 * >>> {productId: {inq:[3,4]}}
207 */
208function createThroughConstraintFromTarget(relationMeta, fkValues) {
209 if (fkValues === undefined || fkValues.length === 0) {
210 throw new Error('"fkValue" must be provided');
211 }
212 const targetFkName = relationMeta.through.keyTo;
213 // eslint-disable-next-line @typescript-eslint/no-explicit-any
214 const constraint = fkValues.length === 1
215 ? { [targetFkName]: fkValues[0] }
216 : { [targetFkName]: { inq: fkValues } };
217 return constraint;
218}
219exports.createThroughConstraintFromTarget = createThroughConstraintFromTarget;
220/**
221 * Resolves given hasMany metadata if target is specified to be a resolver.
222 * Mainly used to infer what the `keyTo` property should be from the target's
223 * belongsTo metadata
224 * @param relationMeta - hasManyThrough metadata to resolve
225 * @internal
226 */
227function resolveHasManyThroughMetadata(relationMeta) {
228 var _a, _b, _c, _d, _e, _f;
229 // some checks and relationMeta.keyFrom are handled in here
230 relationMeta = (0, has_many_helpers_1.resolveHasManyMetaHelper)(relationMeta);
231 if (!relationMeta.through) {
232 const reason = 'through must be specified';
233 throw new __1.InvalidRelationError(reason, relationMeta);
234 }
235 if (!(0, __1.isTypeResolver)((_a = relationMeta.through) === null || _a === void 0 ? void 0 : _a.model)) {
236 const reason = 'through.model must be a type resolver';
237 throw new __1.InvalidRelationError(reason, relationMeta);
238 }
239 const throughModel = relationMeta.through.model();
240 const throughModelProperties = (_b = throughModel.definition) === null || _b === void 0 ? void 0 : _b.properties;
241 const targetModel = relationMeta.target();
242 const targetModelProperties = (_c = targetModel.definition) === null || _c === void 0 ? void 0 : _c.properties;
243 // check if metadata is already complete
244 if (relationMeta.through.keyTo &&
245 throughModelProperties[relationMeta.through.keyTo] &&
246 relationMeta.through.keyFrom &&
247 throughModelProperties[relationMeta.through.keyFrom] &&
248 relationMeta.keyTo &&
249 targetModelProperties[relationMeta.keyTo] &&
250 (relationMeta.through.polymorphic === false ||
251 (typeof relationMeta.through.polymorphic === 'object' &&
252 relationMeta.through.polymorphic.discriminator.length > 0))) {
253 // The explicit cast is needed because of a limitation of type inference
254 return relationMeta;
255 }
256 const sourceModel = relationMeta.source;
257 debug('Resolved model %s from given metadata: %o', targetModel.modelName, targetModel);
258 debug('Resolved model %s from given metadata: %o', throughModel.modelName, throughModel);
259 const sourceFkName = (_d = relationMeta.through.keyFrom) !== null && _d !== void 0 ? _d : (0, lodash_1.camelCase)(sourceModel.modelName + '_id');
260 if (!throughModelProperties[sourceFkName]) {
261 const reason = `through model ${throughModel.name} is missing definition of source foreign key`;
262 throw new __1.InvalidRelationError(reason, relationMeta);
263 }
264 const targetFkName = (_e = relationMeta.through.keyTo) !== null && _e !== void 0 ? _e : (0, lodash_1.camelCase)(targetModel.modelName + '_id');
265 if (!throughModelProperties[targetFkName]) {
266 const reason = `through model ${throughModel.name} is missing definition of target foreign key`;
267 throw new __1.InvalidRelationError(reason, relationMeta);
268 }
269 const targetPrimaryKey = (_f = relationMeta.keyTo) !== null && _f !== void 0 ? _f : targetModel.definition.idProperties()[0];
270 if (!targetPrimaryKey || !targetModelProperties[targetPrimaryKey]) {
271 const reason = `target model ${targetModel.modelName} does not have any primary key (id property)`;
272 throw new __1.InvalidRelationError(reason, relationMeta);
273 }
274 let throughPolymorphic;
275 if (relationMeta.through.polymorphic === undefined ||
276 relationMeta.through.polymorphic === false ||
277 !relationMeta.through.polymorphic) {
278 const polymorphicFalse = false;
279 throughPolymorphic = polymorphicFalse;
280 }
281 else {
282 if (relationMeta.through.polymorphic === true) {
283 const polymorphicObject = {
284 discriminator: (0, lodash_1.camelCase)(relationMeta.target().name + '_type'),
285 };
286 throughPolymorphic = polymorphicObject;
287 }
288 else {
289 const polymorphicObject = relationMeta.through
290 .polymorphic;
291 throughPolymorphic = polymorphicObject;
292 }
293 }
294 return Object.assign(relationMeta, {
295 keyTo: targetPrimaryKey,
296 keyFrom: relationMeta.keyFrom,
297 through: {
298 ...relationMeta.through,
299 keyTo: targetFkName,
300 keyFrom: sourceFkName,
301 polymorphic: throughPolymorphic,
302 },
303 });
304}
305exports.resolveHasManyThroughMetadata = resolveHasManyThroughMetadata;
306//# sourceMappingURL=has-many-through.helpers.js.map
\No newline at end of file