UNPKG

@e22m4u/js-repository

Version:

Реализация репозитория для работы с базами данных в Node.js

243 lines (240 loc) 8.73 kB
import {Service} from '@e22m4u/js-service'; import {cloneDeep} from '../utils/index.js'; import {singularize} from '../utils/index.js'; import {InvalidArgumentError} from '../errors/index.js'; import {RepositoryRegistry} from '../repository/index.js'; import {ModelDefinitionUtils} from '../definition/index.js'; /** * Belongs to resolver. */ export class BelongsToResolver extends Service { /** * Include to. * * @param {object[]} entities * @param {string} sourceName * @param {string} targetName * @param {string} relationName * @param {string|undefined} foreignKey * @param {object|undefined} scope * @returns {Promise<void>} */ async includeTo( entities, sourceName, targetName, relationName, foreignKey = undefined, scope = undefined, ) { if (!entities || !Array.isArray(entities)) throw new InvalidArgumentError( 'The parameter "entities" of BelongsToResolver.includeTo requires ' + 'an Array of Object, but %v was given.', entities, ); if (!sourceName || typeof sourceName !== 'string') throw new InvalidArgumentError( 'The parameter "sourceName" of BelongsToResolver.includeTo requires ' + 'a non-empty String, but %v was given.', sourceName, ); if (!targetName || typeof targetName !== 'string') throw new InvalidArgumentError( 'The parameter "targetName" of BelongsToResolver.includeTo requires ' + 'a non-empty String, but %v was given.', targetName, ); if (!relationName || typeof relationName !== 'string') throw new InvalidArgumentError( 'The parameter "relationName" of BelongsToResolver.includeTo requires ' + 'a non-empty String, but %v was given.', relationName, ); if (foreignKey && typeof foreignKey !== 'string') throw new InvalidArgumentError( 'The provided parameter "foreignKey" of BelongsToResolver.includeTo ' + 'should be a String, but %v was given.', foreignKey, ); if (scope && (typeof scope !== 'object' || Array.isArray(scope))) throw new InvalidArgumentError( 'The provided parameter "scope" of BelongsToResolver.includeTo ' + 'should be an Object, but %v was given.', scope, ); if (foreignKey == null) foreignKey = `${relationName}Id`; const targetIds = entities.reduce((acc, entity) => { if (!entity || typeof entity !== 'object' || Array.isArray(entity)) throw new InvalidArgumentError( 'The parameter "entities" of BelongsToResolver.includeTo requires ' + 'an Array of Object, but %v was given.', entity, ); const targetId = entity[foreignKey]; return targetId != null ? [...acc, targetId] : acc; }, []); const targetRepository = this.getService(RepositoryRegistry).getRepository(targetName); const targetPkPropName = this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName( targetName, ); scope = scope ? cloneDeep(scope) : {}; const filter = cloneDeep(scope); filter.where = { and: [ {[targetPkPropName]: {inq: targetIds}}, ...(scope.where ? [scope.where] : []), ], }; const targets = await targetRepository.find(filter); entities.forEach(entity => { const target = targets.find( e => e[targetPkPropName] === entity[foreignKey], ); if (target) entity[relationName] = target; }); } /** * Include polymorphic to. * * @param {object[]} entities * @param {string} sourceName * @param {string} relationName * @param {string|undefined} foreignKey * @param {string|undefined} discriminator * @param {object|undefined} scope * @returns {Promise<void>} */ async includePolymorphicTo( entities, sourceName, relationName, foreignKey = undefined, discriminator = undefined, scope = undefined, ) { if (!entities || !Array.isArray(entities)) throw new InvalidArgumentError( 'The parameter "entities" of BelongsToResolver.includePolymorphicTo ' + 'requires an Array of Object, but %v was given.', entities, ); if (!sourceName || typeof sourceName !== 'string') throw new InvalidArgumentError( 'The parameter "sourceName" of BelongsToResolver.includePolymorphicTo ' + 'requires a non-empty String, but %v was given.', sourceName, ); if (!relationName || typeof relationName !== 'string') throw new InvalidArgumentError( 'The parameter "relationName" of BelongsToResolver.includePolymorphicTo ' + 'requires a non-empty String, but %v was given.', relationName, ); if (foreignKey && typeof foreignKey !== 'string') throw new InvalidArgumentError( 'The provided parameter "foreignKey" of BelongsToResolver.includePolymorphicTo ' + 'should be a String, but %v was given.', foreignKey, ); if (discriminator && typeof discriminator !== 'string') throw new InvalidArgumentError( 'The provided parameter "discriminator" of BelongsToResolver.includePolymorphicTo ' + 'should be a String, but %v was given.', discriminator, ); if (scope && (typeof scope !== 'object' || Array.isArray(scope))) throw new InvalidArgumentError( 'The provided parameter "scope" of BelongsToResolver.includePolymorphicTo ' + 'should be an Object, but %v was given.', scope, ); if (foreignKey == null) { const singularRelationName = singularize(relationName); foreignKey = `${singularRelationName}Id`; } if (discriminator == null) { const singularRelationName = singularize(relationName); discriminator = `${singularRelationName}Type`; } const targetIdsByTargetName = {}; entities.forEach(entity => { if (!entity || typeof entity !== 'object' || Array.isArray(entity)) throw new InvalidArgumentError( 'The parameter "entities" of BelongsToResolver.includePolymorphicTo requires ' + 'an Array of Object, but %v was given.', entity, ); const targetId = entity[foreignKey]; const targetName = entity[discriminator]; if (targetId == null || targetName == null) return; if (targetIdsByTargetName[targetName] == null) targetIdsByTargetName[targetName] = []; if (!targetIdsByTargetName[targetName].includes(targetId)) targetIdsByTargetName[targetName].push(targetId); }); const promises = []; const targetNames = Object.keys(targetIdsByTargetName); scope = scope ? cloneDeep(scope) : {}; const targetEntitiesByTargetNames = {}; targetNames.forEach(targetName => { let targetRepository; try { targetRepository = this.getService(RepositoryRegistry).getRepository(targetName); } catch (error) { if (error instanceof InvalidArgumentError) { if ( error.message === `The model "${targetName}" is not defined.` || error.message === `The model "${targetName}" does not have a specified datasource.` ) { return; } } else { throw error; } } const targetPkPropName = this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName( targetName, ); const targetFilter = cloneDeep(scope); const targetIds = targetIdsByTargetName[targetName]; targetFilter.where = { and: [ {[targetPkPropName]: {inq: targetIds}}, ...(scope.where ? [scope.where] : []), ], }; const promise = targetRepository.find(targetFilter).then(result => { targetEntitiesByTargetNames[targetName] = [ ...(targetEntitiesByTargetNames[targetName] ?? []), ...result, ]; }); promises.push(promise); }); await Promise.all(promises); entities.forEach(entity => { const targetId = entity[foreignKey]; const targetName = entity[discriminator]; if ( targetId == null || targetName == null || targetEntitiesByTargetNames[targetName] == null ) { return; } const targetEntities = targetEntitiesByTargetNames[targetName] ?? []; const targetPkPropName = this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName( targetName, ); const target = targetEntities.find(e => e[targetPkPropName] === targetId); if (target) entity[relationName] = target; }); } }