node.jstypescriptnode-mongodb-native

let generic function match another function's specific overload signature


Try to write a generic function to match the second overload signature of findOne() method of Collection class.

import { Document, Filter, FindCursor, FindOptions, MongoClient, WithId } from 'mongodb'; // tags: ^5.6.0

const uri = '<connection string uri>';
const client = new MongoClient(uri);

function findUsers<TSchema extends Document = Document>(
    find_pattern: Filter<TSchema>,
    options?: FindOptions,
): FindCursor<WithId<TSchema>> {
    return client.db('main').collection('accounts').find(find_pattern, options);
}

The overload signatures of findOne():

export declare class Collection<TSchema extends Document = Document> {
    //...
    find(): FindCursor<WithId<TSchema>>;
    find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;
    find<T extends Document>(filter: Filter<TSchema>, options?: FindOptions): FindCursor<T>;
    //...
}

Try to match below overload signature.

find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;

TSC throws two errors, the first error throws at the return keyword.

Type 'FindCursor<WithId<Document>>' is not assignable to type 'FindCursor<WithId<TSchema>>'.
  Type 'WithId<Document>' is not assignable to type 'WithId<TSchema>'.
    Type 'WithId<Document>' is not assignable to type 'EnhancedOmit<TSchema, "_id">'.(2322)

The second error throws at the .find(find_pattern, options).

No overload matches this call.
  Overload 1 of 3, '(filter: Filter<Document>, options?: FindOptions<Document> | undefined): FindCursor<WithId<Document>>', gave the following error.
    Argument of type 'Filter<TSchema>' is not assignable to parameter of type 'Filter<Document>'.
      Type 'Filter<TSchema>' is not assignable to type 'RootFilterOperators<WithId<Document>>'.
        Types of property '$and' are incompatible.
          Type 'Filter<WithId<TSchema>>[] | undefined' is not assignable to type 'Filter<WithId<Document>>[] | undefined'.
            Type 'Filter<WithId<TSchema>>[]' is not assignable to type 'Filter<WithId<Document>>[]'.
              Type 'Filter<WithId<TSchema>>' is not assignable to type 'Filter<WithId<Document>>'.
                Type 'Filter<WithId<TSchema>>' is not assignable to type 'RootFilterOperators<WithId<WithId<Document>>>'.
                  Types of property '$where' are incompatible.
                    Type 'string | ((this: WithId<WithId<TSchema>>) => boolean) | undefined' is not assignable to type 'string | ((this: WithId<WithId<Document>>) => boolean) | undefined'.
                      Type '(this: WithId<WithId<TSchema>>) => boolean' is not assignable to type 'string | ((this: WithId<WithId<Document>>) => boolean) | undefined'.
                        Type '(this: WithId<WithId<TSchema>>) => boolean' is not assignable to type '(this: WithId<WithId<Document>>) => boolean'.
                          The 'this' types of each signature are incompatible.
                            Type 'WithId<WithId<Document>>' is not assignable to ty

Playground Link

package versions:

"typescript": "^5.1.6",
"mongodb": "^5.6.0"

Solution

  • In the findOne overloads, it doesn't accept TSchema, but the Document:

    export declare class Collection<TSchema extends Document = Document> {
        //...
        find(): FindCursor<WithId<TSchema>>;
        find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;
        find<T extends Document>(filter: Filter<TSchema>, options?: FindOptions): FindCursor<T>;
        //...
    }
    

    The TSchema is accepted by the Collection class, thus you can fix it just by passing your schema to the collection method:

    function findUsers<TSchema extends Document = Document>(
        find_pattern: Filter<TSchema>,
        options?: FindOptions,
    ): FindCursor<WithId<TSchema>> {
        return client.db('main').collection<TSchema>('accounts').find(find_pattern, options);
    }
    

    Link to Playground