Skip to content

Conversation

vkarpov15
Copy link
Collaborator

Fix #15557
Re: #13840
Re: #15327

Summary

#13840 made it so that Mongoose requires as const for projections defined using const projection = { /* fields here */ }. That can cause some problems like in #15557. I'm not 100% convinced we want to make the change in this PR, I'd like to hear @hasezoey and @AbdelrahmanHafez 's thoughts

Examples

@vkarpov15 vkarpov15 added this to the 8.17.1 milestone Aug 4, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses TypeScript typing issues with MongoDB projections in Mongoose by loosening projection type validation. The change allows users to pass projection objects without requiring the as const assertion, which was causing problems for some use cases while maintaining type safety where possible.

  • Removes strict validation that previously required as const for projection objects
  • Updates test cases to reflect the more permissive projection typing
  • Maintains basic projection validation while allowing more flexible usage patterns
Comments suppressed due to low confidence (3)

test/types/queries.test.ts:158

  • This test now allows invalid projection values (3 is not a valid MongoDB projection value). Consider adding a comment explaining why this is now permitted or verify this aligns with the intended behavior change.
Test.find({}, { name: 3 });

test/types/queries.test.ts:159

  • This test now allows mixing inclusion and exclusion projection operators, which is invalid in MongoDB. This significant behavior change should be documented to clarify whether this is intentional relaxation of validation.
Test.find({}, { name: true, age: false, endDate: true, tags: 1 });

test/types/queries.test.ts:180

  • The comment states 'does not accept any' but the test expects an error for a string value 'taco', while line 158 allows the number 3. This inconsistency in what projection values are accepted should be clarified.
expectError(Test.find({}, { 'docs.id': 'taco' })); // Dot notation should be allowed and does not accept any

Copy link
Collaborator

@hasezoey hasezoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me.

It would be really great if typescript could automatically narrow to as const by default instead of defaulting to wide types. (I think this has issue microsoft/TypeScript#51930)
As long as that is not the case, we either need to enforce as const for users, do something like Narrow in ts-toolbelt (reddit post) or well, revert to less-specific types like in this PR.
(Note that the ts-toolbelt type i just came across and have not used, but looks like it might fit this case somewhat)

@vkarpov15
Copy link
Collaborator Author

Thanks for finding ts-toolbelt. Unfortunately it doesn't look like Narrow<> helps here, I still get a compiler error with the following type changes

    findOne<ResultDoc = THydratedDocumentType>(
      filter?: RootFilterQuery<TRawDocType>,
      projection?: Function.Narrow<ProjectionType<TRawDocType>> | null,
      options?: QueryOptions<TRawDocType> & mongodb.Abortable | null
    ): QueryWithHelpers<ResultDoc | null, ResultDoc, TQueryHelpers, TRawDocType, 'findOne', TInstanceMethods & TVirtuals>;
gh-15557.ts:14:21 - error TS2769: No overload matches this call.
  Overload 1 of 4, '(filter: RootFilterQuery<{ active?: boolean | null | undefined; }>, projection: Narrow<ProjectionType<{ active?: boolean | null | undefined; }> | null>, options?: (QueryOptions<...> & Abortable) | ... 1 more ... | undefined): Query<...>', gave the following error.
    Argument of type '{ _id: number; }' is not assignable to parameter of type 'Narrow<ProjectionType<{ active?: boolean | null | undefined; }> | null>'.
      Type '{ _id: number; }' is not assignable to type '{ [x: string]: any; active?: NarrowRaw<false | 0 | undefined>; _id?: NarrowRaw<boolean | 0 | 1 | undefined>; }'.
        Types of property '_id' are incompatible.
          Type 'number' is not assignable to type 'NarrowRaw<boolean | 0 | 1 | undefined>'.
  Overload 2 of 4, '(filter?: RootFilterQuery<{ active?: boolean | null | undefined; }> | undefined, projection?: Narrow<ProjectionType<{ active?: boolean | null | undefined; }> | null> | undefined): Query<...>', gave the following error.
    Argument of type '{ _id: number; }' is not assignable to parameter of type 'Narrow<ProjectionType<{ active?: boolean | null | undefined; }> | null> | undefined'.
      Type '{ _id: number; }' is not assignable to type '{ [x: string]: any; active?: NarrowRaw<false | 0 | undefined>; _id?: NarrowRaw<boolean | 0 | 1 | undefined>; }'.
        Types of property '_id' are incompatible.
          Type 'number' is not assignable to type 'NarrowRaw<boolean | 0 | 1 | undefined>'.

14     .findOne(query, projection)

@vkarpov15 vkarpov15 merged commit 6373d80 into master Aug 5, 2025
5 checks passed
@hasezoey hasezoey deleted the vkarpov15/gh-15557 branch August 6, 2025 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Projections now requiring use of as const. Type 'number' is not assignable to type 'boolean | 0 | 1'
2 participants