I started using TypeORM + type-graphql and trying to implement different use cases.
A Queue
has a many-to-many relationship with User
.
My problem is that I want to retrieve a list of users given a list of queue ids:
Queue Entity
@ObjectType()
@Entity()
export class Queue extends BaseEntity {
@Field(() => Int)
@PrimaryGeneratedColumn()
id!: number
@Field(() => [User])
@ManyToMany(() => User, (user) => user.adminOfQueues, { onDelete: "CASCADE" })
@JoinTable()
admins!: User[]
}
User Entity
@ObjectType()
@Entity()
export class User extends BaseEntity {
@Field(() => Int)
@PrimaryGeneratedColumn()
id!: number
@Field(() => [Queue], { nullable: true })
@ManyToMany(() => Queue, (queue) => queue.admins)
adminOfQueues!: Queue[]
Queue Resolver
@Resolver(() => Queue)
export class QueueResolver {
@FieldResolver(() => User)
async admins(
@Root() queue: Queue,
@Ctx() { userFromQueueLoader }: MyContext
) {
const q = await Queue.findOne(queue.id, { relations: ["admins"] })
const admins = q.admins?.map?.((user) => user.id)
return User.find({
where: {
id: In(admins),
},
})
}
@Query(() => Queue)
async queues(): Promise<Queue> {
const qb = getConnection()
.getRepository(Queue)
.createQueryBuilder("q") // alias
.orderBy("q.createdAt", "DESC")
const queues = await qb.getMany()
return queues
}
^ works fine, however I would like to use a dataloader because as far as I understood the n+1 problem will bite me when fetching all queues (and the need to query the admin users in the db on each of them separately).
so in the admin FieldResolver I would just use it like:
@FieldResolver(() => User)
async admins(
@Root() queue: Queue,
@Ctx() { userFromQueueLoader }: MyContext
) {
const users = await userFromQueueLoader.load(queue.id)
return users
}
However I don't know how to retrieve a list of admin users given a list of queueIds in the dataloader
// [queueId] > batched queueIds
// => [{User}] > should return a list of users where user.adminOfQueues contains a queueId
export const createUserFromQueueLoader = () =>
new DataLoader<number, User>(async (queueIds) => {
const users = await User.find({
join: {
alias: "userQueue",
innerJoinAndSelect: {
adminOfQueues: "userQueue.adminOfQueues",
},
},
where: { adminOfQueues: In(queueIds as number[]) }, // << can't compare 2 arrays, how to achieve that?
})
const userIdToUser: Record<number, User> = {}
users.forEach((user: User) => {
const usersQueues = user.adminOfQueues
usersQueues.forEach((userQueue) => {
const userQueueId = userQueue.id
userIdToUser[userQueueId] = userIdToUser[userQueueId]
? [...userIdToUser[userQueueId], user]
: [user]
})
})
return queueIds.map((queueId) => userIdToUser[queueId])
})
Following doesn't help either:
const users = await User.find({
relations: ['adminOfQueues'],
where: { adminOfQueues: In(queueIds as number[]) }, // << can't compare 2 arrays, how to achieve that?
})
How is this feasable? There must be a way to query all users given a list of ids for the many-to-many relation, no?
Help is highly appreciated :) Thanks