| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- 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 given.',
- entities,
- );
- if (!sourceName || typeof sourceName !== 'string')
- throw new InvalidArgumentError(
- 'The parameter "sourceName" of BelongsToResolver.includeTo requires ' +
- 'a non-empty String, but %v given.',
- sourceName,
- );
- if (!targetName || typeof targetName !== 'string')
- throw new InvalidArgumentError(
- 'The parameter "targetName" of BelongsToResolver.includeTo requires ' +
- 'a non-empty String, but %v given.',
- targetName,
- );
- if (!relationName || typeof relationName !== 'string')
- throw new InvalidArgumentError(
- 'The parameter "relationName" of BelongsToResolver.includeTo requires ' +
- 'a non-empty String, but %v given.',
- relationName,
- );
- if (foreignKey && typeof foreignKey !== 'string')
- throw new InvalidArgumentError(
- 'The provided parameter "foreignKey" of BelongsToResolver.includeTo ' +
- 'should be a String, but %v 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 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 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 given.',
- entities,
- );
- if (!sourceName || typeof sourceName !== 'string')
- throw new InvalidArgumentError(
- 'The parameter "sourceName" of BelongsToResolver.includePolymorphicTo ' +
- 'requires a non-empty String, but %v given.',
- sourceName,
- );
- if (!relationName || typeof relationName !== 'string')
- throw new InvalidArgumentError(
- 'The parameter "relationName" of BelongsToResolver.includePolymorphicTo ' +
- 'requires a non-empty String, but %v given.',
- relationName,
- );
- if (foreignKey && typeof foreignKey !== 'string')
- throw new InvalidArgumentError(
- 'The provided parameter "foreignKey" of BelongsToResolver.includePolymorphicTo ' +
- 'should be a String, but %v given.',
- foreignKey,
- );
- if (discriminator && typeof discriminator !== 'string')
- throw new InvalidArgumentError(
- 'The provided parameter "discriminator" of BelongsToResolver.includePolymorphicTo ' +
- 'should be a String, but %v 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 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 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;
- });
- }
- }
|