Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript errors with .lean() after upgrading from Mongoose 8.7.1 to 8.7.2 #14974

Open
2 tasks done
Towerss opened this issue Oct 19, 2024 · 2 comments
Open
2 tasks done
Labels
typescript Types or Types-test related issue / Pull Request

Comments

@Towerss
Copy link

Towerss commented Oct 19, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

8.7.2

Node.js version

20.15.1

MongoDB server version

7.0.14

Typescript version (if applicable)

5.6.3

Description

Hi Mongoose team,

After upgrading Mongoose from version 8.7.1 to 8.7.2, I'm encountering TypeScript errors when running queries that use the .lean() method. The issue seems to be related to changes in the type utility chain, specifically in how FlattenMaps and BufferToBinary are applied.

Here’s the TypeScript error:

The types of 'options.session' are incompatible between these types.
Type 'Binary | null | undefined' is not assignable to type 'Buffer<ArrayBufferLike> | undefined'.
Type 'null' is not assignable to type 'Buffer<ArrayBufferLike> | undefined'.ts(2322)

Code that previously ran without issues, such as:

const blogs: Blog[] = await BlogModel.find({ status: BlogStatus.Published })
  .select('slug language')
  .lean()
  .exec();

or

const account: Account | null = await AccountModel.findById(accountId)
  .select('-hashedPassword -mfa -oauthId')
  .lean()
  .exec();

is now throwing the above TypeScript error.

Previously, the type chain in mongoose\types\query.d.ts was:

Require_id<FlattenMaps<BufferToBinary<RawDocType>>>

in the code:

  type GetLeanResultType<RawDocType, ResultType, QueryOp> = QueryOp extends QueryOpThatReturnsDocument
    ? (ResultType extends any[] ? Require_id<FlattenMaps<BufferToBinary<RawDocType>>>[] : Require_id<FlattenMaps<BufferToBinary<RawDocType>>>)
    : ResultType;

But after the update, the order of FlattenMaps and BufferToBinary has been swapped to:

Require_id<BufferToBinary<FlattenMaps<RawDocType>>>

like:

  type GetLeanResultType<RawDocType, ResultType, QueryOp> = QueryOp extends QueryOpThatReturnsDocument
    ? (ResultType extends any[] ? Require_id<BufferToBinary<FlattenMaps<RawDocType>>>[] : Require_id<BufferToBinary<FlattenMaps<RawDocType>>>)
    : ResultType;

This change appears to have introduced stricter typing or structural mismatches in queries that return lean documents, particularly when using .lean() or .lean() without explicit typing. The flipping of FlattenMaps and BufferToBinary seems to cause issues when dealing with Map or Buffer fields in the schema. I think this is breaking change, and should be reverted.

Steps to Reproduce

  1. Use Mongoose 8.7.1 and run a similar query like:
const blogs: Blog[] = await BlogModel.find({ status: BlogStatus.Published })
  .select('slug language')
  .lean()
  .exec();

or

const account: Account | null = await AccountModel.findById(accountId)
	.select('-hashedPassword -mfa -oauthId')
	.lean()
	.exec();

This will run without issues.

  1. Upgrade to Mongoose 8.7.2

  2. Run the same queries, and then TypeScript will show an error.

Expected Behavior

Expected Behavior:

Queries using .lean() should return plain objects that conform to the expected TypeScript interface (e.g., Blog[] in the case of a Blog model) without any TypeScript errors, as it worked in version 8.7.1.

As a workaround, I’ve had to explicitly type the result of .lean(), but this was not required in previous versions:

const blogs: Blog[] = await BlogModel.find({ status: BlogStatus.Published })
  .select('slug language')
  .lean<Blog>()
  .exec();
@y-nk
Copy link

y-nk commented Oct 21, 2024

EDIT: I've opened a separate issue to avoid polluting this one
#14976

@richardsimko
Copy link

richardsimko commented Oct 21, 2024

Seeing the same thing, probably related to #14902 and #14967

Here is a fully reproducible example which worked in 8.7.1:

import { Schema, model } from 'mongoose';

import type { Document, ObjectId } from 'mongoose';

export interface Foo {
  _id: ObjectId;
  bar: string;
}
export type FooDocument = Foo & Document<ObjectId>;

const schema = new Schema<Foo>(
  {
    bar: { type: String, required: true },
  },
  { collection: 'foo', timestamps: true },
);

export const FooModel = model<Foo>('foo', schema);

const test: FooDocument[] = await FooModel.find({ bar: 'asd' }).lean();
core/models/test.ts:20:7 - error TS2322: Type '({ _id: { auto: (turnOn: boolean) => ObjectId; defaultOptions: { [x: string]: any; }; OptionsConstructor: { [x: string]: any; type?: any; alias?: string | string[]; validate?: Function | ... 6 more ... | readonly (Function | ... 4 more ... | { ...; }[])[]; ... 28 more ...; maxlength?: number | ... 1 more ... | reado...' is not assignable to type 'FooDocument[]'.
  Type '{ _id: { auto: (turnOn: boolean) => ObjectId; defaultOptions: { [x: string]: any; }; OptionsConstructor: { [x: string]: any; type?: any; alias?: string | string[]; validate?: Function | ... 6 more ... | readonly (Function | ... 4 more ... | { ...; }[])[]; ... 28 more ...; maxlength?: number | ... 1 more ... | readon...' is not assignable to type 'FooDocument'.
    Type '{ _id: { auto: (turnOn: boolean) => ObjectId; defaultOptions: { [x: string]: any; }; OptionsConstructor: { [x: string]: any; type?: any; alias?: string | string[]; validate?: Function | ... 6 more ... | readonly (Function | ... 4 more ... | { ...; }[])[]; ... 28 more ...; maxlength?: number | ... 1 more ... | readon...' is not assignable to type 'Foo'.
      The types of '_id.OptionsConstructor.index' are incompatible between these types.
        Type 'boolean | IndexDirection | { expires?: string | number; weights?: { [x: string]: number; }; background?: boolean; unique?: boolean; name?: string; partialFilterExpression?: { ...; }; ... 38 more ...; explain?: ExplainVerbosityLike; }' is not assignable to type 'boolean | IndexDirection | IndexOptions'.
          Type '{ expires?: string | number; weights?: { [x: string]: number; }; background?: boolean; unique?: boolean; name?: string; partialFilterExpression?: { [x: string]: any; }; sparse?: boolean; expireAfterSeconds?: number; ... 36 more ...; explain?: ExplainVerbosityLike; }' is not assignable to type 'boolean | IndexDirection | IndexOptions'.
            Type '{ expires?: string | number; weights?: { [x: string]: number; }; background?: boolean; unique?: boolean; name?: string; partialFilterExpression?: { [x: string]: any; }; sparse?: boolean; expireAfterSeconds?: number; ... 36 more ...; explain?: ExplainVerbosityLike; }' is not assignable to type 'IndexOptions'.
              The types of 'session.clientOptions.autoEncryption.keyVaultClient.options.session' are incompatible between these types.
                Type 'Binary' is missing the following properties from type 'Buffer': slice, subarray, equals, compare, and 93 more.
20 const test: FooDocument[] = await FooModel.find({ bar: 'asd' }).lean();
         ~~~~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typescript Types or Types-test related issue / Pull Request
Projects
None yet
Development

No branches or pull requests

4 participants