UNPKG

3.29 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2019,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
6import debugFactory from 'debug';
7import {camelCase} from 'lodash';
8import {InvalidRelationError} from '../../errors';
9import {isTypeResolver} from '../../type-resolver';
10import {HasManyDefinition, RelationType} from '../relation.types';
11
12const debug = debugFactory('loopback:repository:relations:has-many:helpers');
13
14/**
15 * Relation definition with optional metadata (e.g. `keyTo`) filled in.
16 * @internal
17 */
18export type HasManyResolvedDefinition = HasManyDefinition & {
19 keyFrom: string;
20 keyTo: string;
21};
22
23/**
24 * Resolves given hasMany metadata if target is specified to be a resolver.
25 * Mainly used to infer what the `keyTo` property should be from the target's
26 * belongsTo metadata
27 * @param relationMeta - hasMany metadata to resolve
28 * @internal
29 */
30export function resolveHasManyMetadata(
31 relationMeta: HasManyDefinition,
32): HasManyResolvedDefinition {
33 // some checks and relationMeta.keyFrom are handled in here
34 relationMeta = resolveHasManyMetaHelper(relationMeta);
35
36 const targetModel = relationMeta.target();
37 const targetModelProperties = targetModel.definition?.properties;
38
39 const sourceModel = relationMeta.source;
40
41 if (relationMeta.keyTo && targetModelProperties[relationMeta.keyTo]) {
42 // The explicit cast is needed because of a limitation of type inference
43 return relationMeta as HasManyResolvedDefinition;
44 }
45
46 debug(
47 'Resolved model %s from given metadata: %o',
48 targetModel.modelName,
49 targetModel,
50 );
51 const defaultFkName = camelCase(sourceModel.modelName + '_id');
52 const hasDefaultFkProperty = targetModelProperties[defaultFkName];
53
54 if (!hasDefaultFkProperty) {
55 const reason = `target model ${targetModel.name} is missing definition of foreign key ${defaultFkName}`;
56 throw new InvalidRelationError(reason, relationMeta);
57 }
58
59 return Object.assign(relationMeta, {
60 keyTo: defaultFkName,
61 } as HasManyResolvedDefinition);
62}
63
64/**
65 * A helper to check relation type and the existence of the source/target models
66 * and set up keyFrom
67 * for HasMany(Through) relations
68 * @param relationMeta
69 *
70 * @returns relationMeta that has set up keyFrom
71 */
72export function resolveHasManyMetaHelper(
73 relationMeta: HasManyDefinition,
74): HasManyDefinition {
75 if ((relationMeta.type as RelationType) !== RelationType.hasMany) {
76 const reason = 'relation type must be HasMany';
77 throw new InvalidRelationError(reason, relationMeta);
78 }
79
80 if (!isTypeResolver(relationMeta.target)) {
81 const reason = 'target must be a type resolver';
82 throw new InvalidRelationError(reason, relationMeta);
83 }
84
85 const sourceModel = relationMeta.source;
86 if (!sourceModel?.modelName) {
87 const reason = 'source model must be defined';
88 throw new InvalidRelationError(reason, relationMeta);
89 }
90 let keyFrom;
91 if (
92 relationMeta.keyFrom &&
93 relationMeta.source.definition.properties[relationMeta.keyFrom]
94 ) {
95 keyFrom = relationMeta.keyFrom;
96 } else {
97 keyFrom = sourceModel.getIdProperties()[0];
98 }
99 return Object.assign(relationMeta, {keyFrom}) as HasManyDefinition;
100}