question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

UpdateQuery generic type makes no sense on a schema with virtual getter and setter

See original GitHub issue

Do you want to request a feature or report a bug? bug

What is the current behavior? UpdateQuery requires a Document interface generic but this doesn’t work when your document has a virtual with getter and setter. If a virtual has both getter and setter I want to only allow the user to update either the get properties or the set property but the required document interface includes both.

If the current behavior is a bug, please provide the steps to reproduce.

// sample.model.ts

import mongoose from "mongoose"

export interface SampleInput {
  name: {
    firstName: string,
    lastName: string
  } | {
    fullName: string
  }
}

export interface SampleDoc extends SampleInput, mongoose.Document {
  name: { firstName: string, lastName: string, fullName: string }
}

const SampleSchema = new mongoose.Schema<SampleDoc>({
  name: {
    firstName: { type: String, required: true },
    lastName: { type: String, required: true }
  }
}

SampleSchema.virtual("name.fullName")
  .get(function (this: SampleDoc) { return this.name.firstName + " " + this.name.lastName })
  .set(function (this: SampleInput, fullName: string) {
    this.name = {
      firstName: fullName.split(" ")[0],
      lastName: fullName.split(" ")[1]
    }
  })

export const Sample = mongoose.model<SampleDoc>("Sample", SampleSchema)
// sample.service.ts

import { SampleInput, Sample } from "sample.model"

export const update = async (id: string, doc: SampleInput /* or UpdateQuery<SampleInput> */) => {
  // findByIdAndUpdate requires doc to be of type SampleDoc (or UpdateQuery<SampleDoc>) but that makes no sense
  // because SampleDoc requires all 3 name properties even though that would interfere with the virtual setter
  // hence why I want it to be SampleInput instead
  return await Sample.findByIdAndUpdate(id, doc).exec()
}

/* Error:
await Sample.findByIdAndUpdate(id, doc).exec()
                                   ^^^
No overload matches this call.
  The last overload gave the following error.
    Argument of type 'SampleInput' is not assignable to parameter of type 'UpdateWithAggregationPipeline | UpdateQuery<SampleDoc> | undefined'.
      Type 'SampleInput' is not assignable to type 'UpdateQuery<SampleDoc>'.
        Type 'SampleInput' is not assignable to type 'ReadonlyPartial<_AllowStringsForIds<LeanDocument<SampleDoc>>>'.
          Types of property 'name' are incompatible.
            Type '{firstName: string, lastName: string} | {fullName: string}' is not assignable to type '({firstName: string, lastName: string, fullName: string}) | undefined'.
              Type '{firstName: string, lastName: string}' is not assignable to type '{firstName: string, lastName: string, fullName: string}'.
                Property 'fullName' is missing in type '{firstName: string, lastName: string}' but required in type '{firstName: string, lastName: string, fullName: string}'. [ts(2769)]
*/
// tsconfig.json

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "Node",
    "lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "types": ["@types/node"]
  },
  "exclude": ["node_modules", "dist"]
}

What is the expected behavior? UpdateQuery integrates getters and setters in its generic type so it doesn’t allow for both to be set at the same time.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that “latest” is not a version. node v14.16.1 mongoose 5.13.3 mongodb 5.0.1

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6

github_iconTop GitHub Comments

1reaction
vkarpov15commented, Sep 20, 2021

Our TypeScript docs don’t recommend passing in an object that extends Document to Schema and Model. The spirit of DocType is that it should represent what your document looks like in the database. No virtuals, methods, etc.

We unfortunately don’t have an ideal solution for virtuals right now, but the workaround is to pass a separate interface containing virtuals as the TInstanceMethods generic as shown below.

import { Schema, Model, model } from 'mongoose';

interface SampleInput {
  name: {
    firstName: string;
    lastName: string;
  }
}

interface SampleInputVirtuals {
  name: {
    fullName: string;
  }
}

type SampleModel = Model<SampleInput, {}, SampleInputVirtuals>; // <-- add virtuals here...

const schema = new Schema<SampleInput, SampleModel, SampleInputVirtuals>({ // <-- and here
  name: {
    firstName: String,
    lastName: String
  }
});

schema.virtual('name.fullName').get(function() {
  return `${this.name.firstName} ${this.name.lastName}`;
});

const Sample = model<SampleInput, SampleModel>('Sample', schema);

const doc = new Sample({ name: { firstName: 'Jean-Luc', lastName: 'Picard' } });
doc.name.firstName.toUpperCase();
doc.name.lastName.toUpperCase();
doc.name.fullName.toUpperCase();

In v6.0.7 we’re adding a TVirtuals to the Model interface so you’ll be able to do:

type SampleModel = Model<SampleInput, {}, {}, SampleInputVirtuals>; // <-- add virtuals as a separate generic so they don't conflict with methods
Read more comments on GitHub >

github_iconTop Results From Across the Web

Mongoose v6.8.2: API docs
Used for declaring paths in your schema that should be MongoDB ObjectIds. Do not use this to create a new ObjectId instance, use...
Read more >
Is there a way to dynamically map keys to getters/setters of ...
I know these are not real getters and setters but it mimics the behaviour. type Example = { prop: number; }; type Mapped<T>...
Read more >
https://unpkg.com/mongoose@5.13.7/index.d.ts
export type Mixed = Schema.Types.Mixed; /** * Mongoose constructor. ... typeof mongoose; /** Getter/setter around function for pluralizing collection names.
Read more >
Hibernate ORM 5.2.18.Final User Guide - Red Hat on GitHub
2.5. Entity types. 2.5.1. POJO Models; 2.5.2. Prefer non-final classes; 2.5.3. Implement a no-argument constructor; 2.5.4. Declare getters and setters for ...
Read more >
backend/node_modules/mongoose/History.md - Git01Lab
fix(index.d.ts): make Mongoose collection inherit MongoDB ... parameter to virtual getters and setters to enable using arrow functions #4143 ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found