UNPKG

@loopback/repository

Version:

Define and implement a common set of interfaces for interacting with databases

101 lines (87 loc) 3.29 kB
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved. // Node module: @loopback/repository // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import debugFactory from 'debug'; import {camelCase} from 'lodash'; import {InvalidRelationError} from '../../errors'; import {isTypeResolver} from '../../type-resolver'; import {HasManyDefinition, RelationType} from '../relation.types'; const debug = debugFactory('loopback:repository:relations:has-many:helpers'); /** * Relation definition with optional metadata (e.g. `keyTo`) filled in. * @internal */ export type HasManyResolvedDefinition = HasManyDefinition & { keyFrom: string; keyTo: string; }; /** * Resolves given hasMany metadata if target is specified to be a resolver. * Mainly used to infer what the `keyTo` property should be from the target's * belongsTo metadata * @param relationMeta - hasMany metadata to resolve * @internal */ export function resolveHasManyMetadata( relationMeta: HasManyDefinition, ): HasManyResolvedDefinition { // some checks and relationMeta.keyFrom are handled in here relationMeta = resolveHasManyMetaHelper(relationMeta); const targetModel = relationMeta.target(); const targetModelProperties = targetModel.definition?.properties; const sourceModel = relationMeta.source; if (relationMeta.keyTo && targetModelProperties[relationMeta.keyTo]) { // The explicit cast is needed because of a limitation of type inference return relationMeta as HasManyResolvedDefinition; } debug( 'Resolved model %s from given metadata: %o', targetModel.modelName, targetModel, ); const defaultFkName = camelCase(sourceModel.modelName + '_id'); const hasDefaultFkProperty = targetModelProperties[defaultFkName]; if (!hasDefaultFkProperty) { const reason = `target model ${targetModel.name} is missing definition of foreign key ${defaultFkName}`; throw new InvalidRelationError(reason, relationMeta); } return Object.assign(relationMeta, { keyTo: defaultFkName, } as HasManyResolvedDefinition); } /** * A helper to check relation type and the existence of the source/target models * and set up keyFrom * for HasMany(Through) relations * @param relationMeta * * @returns relationMeta that has set up keyFrom */ export function resolveHasManyMetaHelper( relationMeta: HasManyDefinition, ): HasManyDefinition { if ((relationMeta.type as RelationType) !== RelationType.hasMany) { const reason = 'relation type must be HasMany'; throw new InvalidRelationError(reason, relationMeta); } if (!isTypeResolver(relationMeta.target)) { const reason = 'target must be a type resolver'; throw new InvalidRelationError(reason, relationMeta); } const sourceModel = relationMeta.source; if (!sourceModel?.modelName) { const reason = 'source model must be defined'; throw new InvalidRelationError(reason, relationMeta); } let keyFrom; if ( relationMeta.keyFrom && relationMeta.source.definition.properties[relationMeta.keyFrom] ) { keyFrom = relationMeta.keyFrom; } else { keyFrom = sourceModel.getIdProperties()[0]; } return Object.assign(relationMeta, {keyFrom}) as HasManyDefinition; }