7

I have a Post Model:

const PostSchema = new Schema<IPost>(
  {
    // ...
    likes: [{ type: Schema.Types.ObjectId, ref: "User" }],
    // ...
  }
)

export default model<IPost>("Post", PostSchema)
export interface IPost {
  // ...
  likes: ObjectId[]
  // ...
}

export interface IPostDocument extends Document, IPost {}

And I'm trying to toggle a user like:

export const toggleLike: TController = async (req, res, next) => {
  const user = req.user as IUserDocument;
  const userId = user._id;
  const postId = req.params.postId;
  try {
    const disliked = await PostModel.findOneAndUpdate(
      { _id: postId, likes: userId },
      { $pull: { likes: userId } }
    ); // works with no problem
    if (disliked)
      res.json({ message: `User ${userId} disliked post ${postId}` });
    else {
      const liked = await PostModel.findOneAndUpdate(
        { _id: postId },
        { $push: { likes: userId } }
      ); // the $push throws an error "Type instantiation is excessively deep and possibly infinite."
      if (liked) res.json({ message: `User ${userId} liked post ${postId}` });
      else return next(createError(404, "Post not found"));
    }
  } catch (error) {
    next(createError(500, error as Error));
  }
};

The mongo $push operator is throwing an error "Type instantiation is excessively deep and possibly infinite."

I doubt it helps but the description of the error is:

(property) likes?: _AllowStringsForIds<(((((((((((... | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[]> | ArrayOperator<(_AllowStringsForIds<(((((((((((... | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[])[] | any[]> | undefined)[]> | undefined

Any idea what's happening?

diedu
  • 19,277
  • 4
  • 32
  • 49
Tiago Brandão
  • 207
  • 3
  • 12
  • Would you mind sharing sample data in valid JSON format? – barrypicker Oct 18 '21 at 17:20
  • The error is indicating the data in the database is not a single level array, but an array of arrays. Actually probably an array of arrays of arrays of arrays of arrays... of arrays... etc. – barrypicker Oct 18 '21 at 17:22
  • could you add details of your dependencies versions? I created a minimal reproduction [here](https://gist.github.com/diedu89/2f430fc90f36db87fb7215f64cc1dad8) and it's working for me with `"mongoose": "^6.0.11"`, `"@types/mongoose": "^5.11.97"`, and `"typescript": "^4.4.4"` – diedu Oct 20 '21 at 03:28
  • Thanks for your comments but this is resolved. On my interface I was using ObjectId but wasn't importing it from anywhere. Once I imported it from mongoose everything worked. I have no idea what is the other ObjectId I was using... – Tiago Brandão Oct 20 '21 at 09:11

3 Answers3

5

I guess your question is very relevant to this one mine: Nestjs: Correct schema for array of subdocuments in mongoose (without default _id or redefine ObjectId). Except I don't want to have ObjectID but you are.

The problem is that MongooseDocument<Array> and TS Array are pretty the same, but have different methods.

I am working with Nestjs, so I will provide an example for it, but I guess you found my example very useful.

For example, for array of embed documents you need to have the following schema interface:

In child schema:

@Schema()
class Field extends Document {
  @Prop({ type: MongooseSchema.Types.ObjectId })
  _id: MongooseSchema.Types.ObjectId

  @Prop({ type: String })
  name: string;
}

export const FieldSchema = SchemaFactory.createForClass(Field);

In parent schema:

  @Prop({ _id: true, type: [FieldSchema] })
  field: MongooseSchema.Types.Array<Field>;

And the most important part are imports:

import { Document, Schema as MongooseSchema, Types } from "mongoose";
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';

Of course in the default TS project, without Nest.js you don't have nest/mongoose but as you may already understand, everything is relevant with interfaces and its types we are taking from mongoose itself.

So class is a Schema interface, which is extended from mongoose Document interface and all the arrays are not just plain TS arrays, but Schema.Types.Array (from mongoose). You may also use not just ObjectId, but other special Mongo only types, like Decimal128 numbers and Mixed and so on, according to the docs.

So in your case with Arrays of ObjectIds, you just need to use:

MongooseSchema.Types.Array<MongooseSchema.Types.ObjectId>

ArrayOfObjectIds

or a bit more primitive way:

ArrayOfObjectIds2

In Nest in @Prop decorator you provide type for mongoDB field type, so it's the same as default TS: new Schema({ languages: { type: ObjectId} })

So I hope it will help you in your project.

AlexZeDim
  • 3,520
  • 2
  • 28
  • 64
3

Importing ObjectId from mongoose on my interface declaration solved the issue. Before that, I was using it but without importing it from anywhere (I didn't even know what that was).

ouflak
  • 2,458
  • 10
  • 44
  • 49
Tiago Brandão
  • 207
  • 3
  • 12
2

I am not sure but it might be interesting with: The type script side might be expecting other items to come there, because it's focused as an array.

Could you try to use each { $push: { : { $each: [ , ... ] } } }

value1 is enough on your position

Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35