Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly type array of object IDs in mongoose with Typescript

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?

like image 900
Tiago Brandão Avatar asked Oct 07 '21 15:10

Tiago Brandão


People also ask

What is the type of mongoose object ID?

In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents. Here is an example: var mongoose = require('mongoose'); var Schema = mongoose. Schema, ObjectId = Schema.

Does Mongoose auto generate ID?

_id field is auto generated by Mongoose and gets attached to the Model, and at the time of saving/inserting the document into MongoDB, MongoDB will use that unique _id field which was generated by Mongoose.

Are Mongoose IDS unique?

yes, the unique: true index guarantees it "in this one collection" - the algorithm "almost" guarantees it universally.


3 Answers

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.

like image 120
AlexZeDim Avatar answered Nov 15 '22 04:11

AlexZeDim


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).

like image 36
Tiago Brandão Avatar answered Nov 15 '22 05:11

Tiago Brandão


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

like image 20
Hamit YILDIRIM Avatar answered Nov 15 '22 04:11

Hamit YILDIRIM