Search code examples
javascriptnode.jstypescriptmongodbmongoose

Mongoose TypeScript ObjectId casting - Type 'string' is not assignable to type 'Condition<ObjectId | undefined>'


I am having issues with mongoose queries when I query by an ObjectId field. Passing mongoose.Types.ObjectId or string both throws errors. TypeScript only stops complaining when I cast the ObjectId to mongoose.Schema.Types.ObjectId, which then crashes at runtime since it's not a valid ObjectId.

interface DocumentWithTimestamp extends Document<ObjectId> {
  createdAt: Date;
  updatedAt: Date;
}

First of all, I noticed that Document defines _id as ObjectId | undefined, which already complicates everything.

I define my Schemas as follows:

export interface LanguageSchema extends DocumentWithTimestamp {
  langCode: string;
  locale: string;
}


interface LanguageModel extends Model<LanguageSchema> {
  mapToLanguage(language: LeanDocument<LanguageSchema> | LanguageSchema): Language;
}

const languageSchema = new Schema<LanguageSchema, LanguageModel>(
  {
    langCode: { type: String, required: true, trim: true },
    locale: { type: String, unique: true, required: true, trim: true },
  },
  { collection: 'language', timestamps: true, versionKey: false },
);

languageSchema.statics.mapToLanguage = function (language: LeanDocument<LanguageSchema> | LanguageSchema): Language {
  return {
    id: language._id?.toString() || '', // why is _id conditional...?
    langCode: language.langCode,
    locale: language.locale,
  };
};

export const LanguageModel = model<LanguageSchema, LanguageModel>('Language', languageSchema);

Now querying my LanguageModel by _id or any other ObjectId field throws type errors:

export async function findLanguagesByIds(languageIds: string[]) {
  return LanguageModel.find({ _id: { $in: languageIds } }).lean();
}

Error:

Argument of type '{ _id: { $in: string[]; }; }' is not assignable to parameter of type 'Callback<LanguageSchema[]>'.
      Object literal may only specify known properties, and '_id' does not exist in type 'Callback<LanguageSchema[]>'.

When I try to cast string[] to Schema.Types.ObjectId[] array, the error goes away but this cannot be the solution, since it crashes at runtime (Schema.Types.ObjectId is not a valid ObjectId).

const languages = await LanguageModel.find({
  _id: { $in: languageIds.map((id) => new mongoose.Schema.Types.ObjectId(id)) },
}).lean();

If I cast to mongoose.Types.ObjectId(to a real ObjectId) it throws errors again...

Any help is appreciated!


Solution

  • I found the solution to my problem.

    mongoose Document is defined as follows:

    class Document<T = any, TQueryHelpers = any, DocType = any> {
      constructor(doc?: any);
    
      /** This documents _id. */
      _id?: T;
    
      // ...
      }
    

    I extended it using Document<ObjectId>. The ObjectId type however was the Schema type, e.g.

    import { ObjectId } from "mongoose";
    

    But it actually should have been the actual ObjectId Type of mongoose and not the Schema type!

    FIX:

    import { Types } from "mongoose"
    
    interface DocumentWithTimestamp extends Document<Types.ObjectId> {
      createdAt: Date;
      updatedAt: Date;
    }