Type safety in mongoose populate

Viewed 117

I am wondering what is the best way to make Typescript notice the Mongoose populate method.

This is my document interface:

export interface TestDoc extends mongoose.Document {
  name: string
  createdBy: ID | UserDoc
  allowedLanguages: TestLanguageName[]
  problems: ID[] | ProblemDoc[]
  candidates: string[]
  invited: number
  completed: number
  incomplete: number
  addCandidate: (candidateId: string) => void
}

and this is the problematic part:

const test = await Test.findOne({
    _id,
    createdBy: req.user._id
  }).populate('createdBy').exec()

my main goal is that when we execute a populate method, test.createdBy should become a type of UserDoc, otherwise it should be an ID.

after some googling, I ran into this solution:

export type Populated<M, K extends keyof M> = Omit<M, K> &
{
  [P in K]: Exclude<M[P], ID[] | ID>
}

that makes it really straightforward:

const test = await Test.findOne({
    _id,
    createdBy: req.user._id
  }).populate('createdBy').exec() as Populated<TestDoc, 'createdBy' | 'problems'>  

however there is one more problem - nested fields, we might want to populate a field inside of another populated document. For example:

const tests = await Test.find({
    createdBy: req.user._id
  })
    .skip(offset ? parseInt(offset, 10) : 0)
    .limit(limit ? parseInt(limit, 10) : 0)
    .populate({
      path: 'candidates',
      populate: {
        path: 'submittedProblems.problem'
      }
    })
    .exec()

since I haven't managed to solve this using Populated interface, that makes me question the approach now :( maybe this is not the way.

How would you guys deal with this? what is the best practice? I checked out Typegoose, but it seems to have same issue.

0 Answers
Related