Search code examples
typescriptmongoosetypescript-genericstypegoose

Extracting raw Typegoose document's type with infer gives unknown


I've created a codesandbox for easier trial. But below are also the parts that are more related to my question.

This is the type that is expected to extract the raw Mongoose document's type (It doesn't.):

// ⭐ class Document<T = any, TQueryHelpers = any, DocType = any>
type ObtainRawDocType<T> = T extends mongoose.Document<
  infer A,
  infer B,
  infer C
>
  ? C // <--- ⭐ Must return the raw document's type.
  : never;

This is where the above generic is used (The return type is not right on purpose, in order to test the above generic.):

export function clearFields<
  T extends mongoose.Document,
  U extends PropertyKey[]
>(dataObj: T, privateFields: U): ObtainRawDocType<T> {

However, this cc1's type is detected to be unknown:

const cc1 = clearFields(c1, ["birthdate"]);

Solution

  • this question was also asked on the typegoose discord, where we came to the conclusion that the resulting unknown is because typescript discards the original type if it has been AND'd with a Omit type and is not structurally used anywhere.

    typescript playground example of the problem

    the answer is basically to augment the mongoose.Document type to structurally use the generic that is wanted (in this case the currently named DocType generic (mongoose 7.3.1)

    // NodeJS: 19.9.0
    // MongoDB: 5.0 (Docker)
    // Typescript 4.9.5
    import { getModelForClass, prop } from '@typegoose/typegoose'; // @typegoose/[email protected]
    import * as mongoose from 'mongoose'; // [email protected]
    
    declare module 'mongoose' {
      interface Document<T = any, TQueryHelpers = any, DocType = any> {
        __DOCTYPE: DocType; // some kind of property to structurally use the generic
      }
    }
    
    type ObtainRawDocType<T> = T extends mongoose.Document<infer A, infer B, infer C> ? C : never;
    
    function clearFields<T extends mongoose.Document, U extends PropertyKey[]>(dataObj: T, privateFields: U): ObtainRawDocType<T> {
      return undefined as any; // Temporary
    }
    
    class User {
      @prop()
      public username?: string;
    }
    
    const UserModel = getModelForClass(User);
    
    const c1 = new UserModel({});
    
    const cc1 = clearFields(c1, ['birthdate']);
    

    Notes:

    • import "mongoose" has to be done where the declare module "mongoose" is happening, otherwise the whole mongoose module / namespace is overwritten instead of augmented
    • the generics of the augmented type have to be kept in sync with the source, otherwise typescript will complain about All declarations of 'Document' must have identical type parameters.ts(2428)