diff --git a/src/packages/core/src/base-loader.ts b/src/packages/core/src/base-loader.ts index 49f872a39..3fc7f8490 100644 --- a/src/packages/core/src/base-loader.ts +++ b/src/packages/core/src/base-loader.ts @@ -20,6 +20,7 @@ type LoaderMap = { [key: string]: DataLoader }; type LoadOneOptions = { gqlEntityType: { new (...args: any[]): G }; id: string; + filter?: Filter; }; type LoadByRelatedIdOptions = { @@ -39,12 +40,14 @@ const getGqlEntityName = (gqlEntityType: any) => { const getBaseLoadOneLoader = ({ gqlEntityType, + filter, keyStore, }: { gqlEntityType: { new (...args: any[]): G; }; keyStore: LoaderMap; + filter?: Filter; }) => { const gqlTypeName = getGqlEntityName(gqlEntityType); if (!keyStore[gqlTypeName]) { @@ -59,11 +62,17 @@ const getBaseLoadOneLoader = ({ ); const primaryKeyField = graphweaverMetadata.primaryKeyFieldForEntity(entity) as keyof D; - const listFilter = { + let listFilter = { [`${String(primaryKeyField)}_in`]: keys, // Note: Typecast here shouldn't be necessary, but FilterEntity doesn't like this. } as Filter; + if (filter) { + listFilter = { + _and: [listFilter, filter], + } as Filter; + } + const backendFilter = isTransformableGraphQLEntityClass(entity.target) && entity.target.toBackendEntityFilter ? entity.target.toBackendEntityFilter(listFilter) @@ -214,9 +223,9 @@ export class BaseLoader { private loadOneLoaderMap: LoaderMap = {}; private relatedIdLoaderMap: LoaderMap = {}; - public loadOne({ gqlEntityType, id }: LoadOneOptions) { - const loader = getBaseLoadOneLoader({ gqlEntityType, keyStore: this.loadOneLoaderMap }); - return loader.load(id); + public loadOne(args: LoadOneOptions) { + const loader = getBaseLoadOneLoader({ ...args, keyStore: this.loadOneLoaderMap }); + return loader.load(args.id); } public loadByRelatedId(args: LoadByRelatedIdOptions) { diff --git a/src/packages/core/src/resolvers.ts b/src/packages/core/src/resolvers.ts index ea3d68a7d..28e2011fa 100644 --- a/src/packages/core/src/resolvers.ts +++ b/src/packages/core/src/resolvers.ts @@ -695,7 +695,7 @@ const _listRelationshipField = async ( gqlEntityType, relatedField: field.relationshipInfo.relatedField as keyof D & string, id: String(source[sourcePrimaryKeyField]), - filter: relatedEntityFilter as Filter, + filter, }); } else if (idValue) { logger.trace('Loading with loadOne'); @@ -705,6 +705,7 @@ const _listRelationshipField = async ( BaseLoaders.loadOne({ gqlEntityType, id: String(id), + filter, }) ) ); diff --git a/src/packages/mikroorm/src/provider/provider.ts b/src/packages/mikroorm/src/provider/provider.ts index 180e96f2c..9a00fa973 100644 --- a/src/packages/mikroorm/src/provider/provider.ts +++ b/src/packages/mikroorm/src/provider/provider.ts @@ -17,7 +17,7 @@ import { graphweaverMetadata, } from '@exogee/graphweaver'; import { logger } from '@exogee/logger'; -import { Reference, RequestContext, sql } from '@mikro-orm/core'; +import { LoadStrategy, Reference, RequestContext, sql } from '@mikro-orm/core'; import { AutoPath, PopulateHint } from '@mikro-orm/postgresql'; import { pluginManager, apolloPluginManager } from '@exogee/graphweaver-server'; @@ -401,13 +401,30 @@ export class MikroBackendProvider implements BackendProvider { trace?: TraceOptions ): Promise { trace?.span.updateName(`Mikro-Orm - findByRelatedId ${this.entityType.name}`); - const queryFilter = { - $and: [{ [relatedField]: { $in: relatedFieldIds } }, ...[gqlToMikro(filter) ?? []]], - }; + + // Any is the actual type from MikroORM, sorry folks. + let queryFilter: any = { [relatedField]: { $in: relatedFieldIds } }; + + if (filter) { + // Since the user has supplied a filter, we need to and it in. + queryFilter = { + $and: [queryFilter, ...[gqlToMikro(filter)]], + }; + } const populate = [relatedField as AutoPath]; const result = await this.database.em.find(entity, queryFilter, { + // We only need one result per entity. + flags: [QueryFlag.DISTINCT], + + // We do want to populate the relation, however, see below. populate, + + // We'd love to use the default joined loading strategy, but it doesn't work with the populateWhere option. + strategy: LoadStrategy.SELECT_IN, + + // This tells MikroORM we only need to load the related entities if they match the filter specified above. + populateWhere: PopulateHint.INFER, }); return result as D[];