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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differentiating Type when using lean() vs no lean() #14471

Closed
1 task done
ochoav opened this issue Mar 27, 2024 · 7 comments
Closed
1 task done

Differentiating Type when using lean() vs no lean() #14471

ochoav opened this issue Mar 27, 2024 · 7 comments
Labels
help wanted help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary

Comments

@ochoav
Copy link

ochoav commented Mar 27, 2024

Prerequisites

  • I have written a descriptive issue title

Mongoose version

7.6.8

Node.js version

20.10.0

MongoDB version

6.0.14

Operating system

None

Operating system version (i.e. 20.04, 11.3, 10)

No response

Issue

Hello 馃憢

We are trying to use the getters and setters feature from Mongoose as follows:

import Decimal from 'decimal.js'

interface IOrder {
  total: Decimal
}

const orderSchema = new mongoose.Schema(
{
  total: {
      type: Schema.Types.Decimal128, 
      required: false, 
      get: (value: any) => return new Decimal(value.toString())
   }
})

const Order = mongoose.model<IOrder>('Order', orderSchema)

The idea here is that when querying for documents from the DB that have Types.Decimal128 fields, they are immediately made into Decimal instances.
However, when we do this with .lean() calls, mongoose getters do not come with the result. But, TypeScript still believes the total type to be Decimal. We would like for this to be type-safe in combination with Mongoose. Therefore, I am looking for a way for Mongoose and TypeScript to be in agreement that the type with Decimal is returned on non lean() types, but is Types.Decimal128 on lean() types. As follows:

const orderTotal = (await Order.findOne()).total // orderTotal here is of type Decimal
const leanOrderTotal= (await Order.findOne().lean()).total // leanOrderTotal here is of type Types.Decimal128

I have tried using the hydrated document type suggestions from these 2 issues:
#12880
#13676

But they address a different issue, and do not differentiate on lean() or not lean() types!
The other alternative seems to use type assertions and casting with the TypeScript as keyword...but them seems like it could lead to mistakes and issues with the compiler!

Thank you! Not sure if a feature request, or can just be helped directly.

@ochoav ochoav added help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary help wanted labels Mar 27, 2024
@vkarpov15 vkarpov15 added this to the 7.6.11 milestone Mar 29, 2024
@mostafaroshdy1
Copy link

BSON already supports Decimal128 , that's why you may not need mongoose hydration for that,

https://www.mongodb.com/docs/manual/reference/bson-types/

@ochoav
Copy link
Author

ochoav commented Apr 5, 2024

Hey @mostafaroshdy1 , not sure I understand how your comment addresses my issue. 馃槗 Could you provide a little more explanation please 馃槃

@ochoav
Copy link
Author

ochoav commented Apr 5, 2024

@vkarpov15 Hey Valeri! Notice you added this to a milestone. I'd be more than happy to contribute to the project with this :) Might just need a little guidance to get up and running on where I should look and the like! Let me know 馃槃 Thank you

@mostafaroshdy1
Copy link

Hey @mostafaroshdy1 , not sure I understand how your comment addresses my issue. 馃槗 Could you provide a little more explanation please 馃槃

if you use .lean() , the document will not be hydrated (will not be instance of mongoose ) that's why the get will not work , and i believe it is by design not an issue.

but one way to work around this is to use the middleware after find like this

orderSchema.post("findOne", function (res) {
    res.total = new Decimal(res.total.toString());
    return res;
});

this should work whether you use .lean() or not

@ochoav
Copy link
Author

ochoav commented Apr 5, 2024

Oh yes! I understand that's why it's not working! But I was suggesting that my main issue was the lack of proper typing for that.

However, this follow up example may be just what we want to get the transformation in both a lean and non lean call! I will give that a shot!! Thank you

@mostafaroshdy1
Copy link

you're most welcome

@vkarpov15
Copy link
Collaborator

The easiest solution would be to add a separate IOrderLean type and pass that type to lean<> as follows:

import Decimal from 'decimal.js'
import mongoose from 'mongoose'

const { Schema } = mongoose;

interface IOrder {
  total: Decimal
}

const orderSchema = new mongoose.Schema(
{
  total: {
      type: Schema.Types.Decimal128,
      required: false, 
      get: (value: any) => new Decimal(value.toString())
   }
})

type IOrderLean = { total?: mongoose.Types.Decimal128 };

const Order = mongoose.model<IOrder>('Order', orderSchema)

async function run() {
  const orderTotal: Decimal = (await Order.findOne().orFail()).total
  const leanOrderTotal: mongoose.Types.Decimal128 | undefined = (
    await Order.findOne().orFail().lean<IOrderLean>()
  ).total 
}

The more "correct" approach would be to make IOrder store total as Types.Decimal128, because Mongoose's assumption is that the first generic param to mongoose.model<> represents primitive types as they're stored in MongoDB (effectively the lean type) and then there's a separate hydrated document type. The following is a bit more verbose, but means you don't need the type override for lean<>:

import Decimal from 'decimal.js'
import mongoose from 'mongoose'

const { Schema } = mongoose;

interface IOrder {
  total?: mongoose.Types.Decimal128
}

type OrderHydratedDocument = mongoose.HydratedDocument<
  IOrder,
  { total?: Decimal }
>;

type OrderModelType = mongoose.Model<
  IOrder,
  {},
  {},
  {},
  OrderHydratedDocument
>;

const orderSchema = new mongoose.Schema(
{
  total: {
      type: Schema.Types.Decimal128,
      required: false, 
      get: (value: any) => new Decimal(value.toString())
   }
})

const Order = mongoose.model<
  IOrder,
  OrderModelType
>('Order', orderSchema)

async function run() {
  const orderTotal: Decimal | undefined = (await Order.findOne().orFail()).total
  const leanOrderTotal: mongoose.Types.Decimal128 | undefined = (
    await Order.findOne().orFail().lean()
  ).total
}

The suggestions from #12880 and #13676 are for the hydrated document type, which is exactly the type that lean() is meant to avoid, so those suggestions won't help in OP's case unless you do the refactoring above.

@vkarpov15 vkarpov15 removed this from the 7.6.11 milestone Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted help This issue can likely be resolved in GitHub issues. No bug fixes, features, or docs necessary
Projects
None yet
Development

No branches or pull requests

3 participants