Skip to content

types: stricter projection typing with 1-level deep nesting #15418

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

Merged
merged 4 commits into from
May 16, 2025

Conversation

vkarpov15
Copy link
Collaborator

Re: #15327

Summary

@pshaddel please take a look as well.

Simplified alternative to #15327 that seems to compile with Typegoose. The one major tradeoff is that it only considers 1 level deep nested paths, as opposed to 3 in #15327. However, that should be sufficient for most use cases. There's also the added benefit that this PR reuses the existing With1LevelDeepNestedPaths type, with some fixes to handle arrays, which should reduce risk.

Examples

@vkarpov15 vkarpov15 added this to the 8.15 milestone May 12, 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 refines projection typing by enforcing stricter type constraints for 1-level deep nested projection paths while maintaining compatibility with Typegoose.

  • Enforces that projection values are strictly boolean or 0/1.
  • Adds comprehensive tsd tests to validate valid and invalid projection use cases.
  • Supports manual casting via ProjectionType for unknown projection fields.

@pshaddel
Copy link
Contributor

Re: #15327

Summary

@pshaddel please take a look as well.

Simplified alternative to #15327 that seems to compile with Typegoose. The one major tradeoff is that it only considers 1 level deep nested paths, as opposed to 3 in #15327. However, that should be sufficient for most use cases. There's also the added benefit that this PR reuses the existing With1LevelDeepNestedPaths type, with some fixes to handle arrays, which should reduce risk.

Examples

Sure. I will also take a look at the original PR + Typegoose this week.

BTW. On this branch, I do not get any type suggestions for projection.

@vkarpov15
Copy link
Collaborator Author

@pshaddel which IDE are you using and can you try restarting?

Type suggestions for projections seem to work well for me on this branch in zed@0.181.8

image

@pshaddel
Copy link
Contributor

@pshaddel which IDE are you using and can you try restarting?

Type suggestions for projections seem to work well for me on this branch in zed@0.181.8

image

You are right, it needed a restart.
See this:
Screenshot 2025-05-12 at 23 17 14

These methods from here are also included:

child?: PopulatedDoc<HydratedDocument<Child>>

I have a draft on my PR trying to remove these methods:

  /**
   * Extract whatever is inside the PopulatedDoc
   * @example
   * type Doc = { name: string };
   * type Sample = PopulatedDoc<Doc>;
   * type DepopulatedDoc = DepopulatedDoc<Sample>; // Result: Doc
   */
  export type DepopulatedDoc<T> = T extends PopulatedDoc<infer U, infer RawID> ? Exclude<U, RawID> : T;
  /**
   * Extract the raw document from a hydrated document
   * @example
   * type Doc = { name: string };
   * type Sample = HydratedDocument<Doc>;
   * type ExtractedDoc = DehydrateDocument<Sample>; // Result: Doc
   */
  export type DehydrateDocument<T> = T extends HydratedDocument<infer U> ? U : T;

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.

Can confirm this works with current typegoose types, thanks.

@hasezoey hasezoey added the typescript Types or Types-test related issue / Pull Request label May 14, 2025
@vkarpov15 vkarpov15 requested a review from hasezoey May 14, 2025 16:30
@vkarpov15
Copy link
Collaborator Author

@pshaddel @hasezoey I successfully got my code to handle avoiding document methods, like $assertPopulated() and toJSON(), in projections. Please take another look 👍

@vkarpov15 vkarpov15 mentioned this pull request May 14, 2025
@pshaddel
Copy link
Contributor

@pshaddel @hasezoey I successfully got my code to handle avoiding document methods, like $assertPopulated() and toJSON(), in projections. Please take another look 👍

LGTM! :)

@vkarpov15 vkarpov15 changed the base branch from master to 8.15 May 16, 2025 16:32
@vkarpov15 vkarpov15 merged commit 33a22b5 into 8.15 May 16, 2025
6 checks passed
@hasezoey hasezoey deleted the vkarpov15/gh-15327 branch May 17, 2025 09:22
@msalafia
Copy link

@vkarpov15 I am not able to do something like this even though it should be allowed:

const currentWeekDoc = await this.currentWeekModel
      .findOne(
        { _id: currentWeekId },
        {
          _id: 0,           // <----- error here because it should be allowed
          season: 1,
          seasonType: 1,
          week: 1,
          timestamp: 1,
        },
      )
      .lean()
      .exec();

@DavideViolante
Copy link
Contributor

DavideViolante commented May 22, 2025

@msalafia it is happening also into my project, but I found out the reason.
It seems like this is happening due to an "old definition" of our models in the code using an interface.

I had to do the following change to remove that error:

-const userSchema = new Schema<IUser>({
+const userSchema = new Schema({

and

-const User = model<IUser>('User', userSchema);
+const User = model('User', userSchema);

or
you edit the interface (IUser in my case) and remove the _id property

@msalafia
Copy link

@DavideViolante this looks like a workaround. Passing an interface is still a valid option in mongose (see here).

Since i am using nestjs/mongoose and not mongoose directly, it's library itself that creates Schema for me (and does it with interfaces),, thus i cannot use your suggestion.

Anyway i still think there is something broken in the new type definition of ProjectionType.

@vkarpov15
Copy link
Collaborator Author

@msalafia we will have a fix in 8.15.1. As a workaround in the interim, you can do the following:

import { ProjectionType } from 'mongoose';

const currentWeekDoc = await this.currentWeekModel
      .findOne(
        { _id: currentWeekId },
        {
          _id: 0,
          season: 1,
          seasonType: 1,
          week: 1,
          timestamp: 1,
        } as ProjectionType<any>,
      )
      .lean()
      .exec();

@msalafia
Copy link

@vkarpov15 Nice! Thank you!

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

Successfully merging this pull request may close these issues.

5 participants