francescov1 / mongoose-tsgen

A plug-n-play Typescript generator for Mongoose.
102 stars 24 forks source link

How to use PopulatedDocument with nested population? #64

Closed giraffesyo closed 2 years ago

giraffesyo commented 3 years ago

Hello, thanks for the awesome library. I had already written my interfaces by hand but I'm testing out using this instead, as it would be nice to avoid the duplication.

I am wondering if I have an object which has something like this structure:

export const fruitStandSchema: FruitStandSchema = new Schema({
  name: { type: String, unique: true },
  fruits: {
    apple: { type: Schema.Types.ObjectId, ref: 'Fruit' },
    tomato: { type: Schema.Types.ObjectId, ref: 'Fruit' },
  },
})

I'm attaching the completely populated object to an incoming request in a middleware, like this:

    const fruitStand = await FruitStand.findOne({_id})
      .populate('fruits.apple')
      .populate('fruits.tomato')

This gives me a fruitStand object, but the population is not working. fruitStand.fruits.apple is of type Types.ObjectId | FruitDocument | undefined still.

Secondly, since I'm trying to populate multiple fields here, when I'm using PopulatedDocument how can I ensure both apple and tomato are populated? My first thought was to do an intersection something like PopulatedDocument<FruitDocument, "fruits.apple"> & PopulatedDocument<FruitDocument, "fruits.tomato"> , but that doesn't work because of the nesting.

Any tips/hints would be greatly appreciated!

francescov1 commented 3 years ago

Awesome to hear! It's a nice feeling when you only have to update one side of the schemas 😅 To answer your questions:

This gives me a fruitStand object, but the population is not working. fruitStand.fruits.apple is of type Types.ObjectId | FruitDocument | undefined still.

Unfortunately nested population doesn't currently get picked up from the query types, the reason for this is that the PopulatedDocument type will check if fruits.apple and fruits.tomato are keys in FruitStandDocument (using T extends keyof DocType) but Typescript can't parse the string as a nested property (if you have any ideas for how to tackle this, would love to hear them!). As a workaround, you just need to add the PopulatedDocument typing manually, which I'll explain for your next question.

Secondly, since I'm trying to populate multiple fields here, when I'm using PopulatedDocument how can I ensure both apple and tomato are populated? My first thought was to do an intersection something like PopulatedDocument<FruitDocument, "fruits.apple"> & PopulatedDocument<FruitDocument, "fruits.tomato"> , but that doesn't work because of the nesting.

For populating two properties, you can nest PopulatedDocument like so:

PopulatedDocument<PopulatedDocument<FruitDocument, "fruits.apple">, "fruits.tomato">

Its not the prettiest so I'm hoping to come up with a cleaner way to implement this in the near future! Any ideas are welcome here as well 🙂

francescov1 commented 3 years ago

This stack overflow thread looks promising for handling nested population, I'll start experimenting with this!

francescov1 commented 2 years ago

Found a way to tackle nested populations as per https://github.com/francescov1/mongoose-tsgen/pull/66, live in 8.3.7 🚀

Currently only supports one level of population, but will be looking to generalize the solution in the near future to support multi-level nesting!