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.

Typescript: Typed model lacking type inference within the definitions of static and instance methods

See original GitHub issue

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

What is the current behavior? When constructing a model that uses Typescript and contains type definitions for static and instance methods, the types of those methods are not “inferred” when implementing the methods later on. A code example is below that might make things clearer.

If the current behavior is a bug, please provide the steps to reproduce. See this script:

import { Schema, Model, createConnection } from 'mongoose'

interface ITestModel {
    name: string
}

interface InstanceMethods {
    iMethod1: (param1: string) => string
    iMethod2: (param1: string) => string
}

interface TestModel extends Model<ITestModel, {}, InstanceMethods> {
    sMethod1: (param1: string) => string
    sMethod2: (param1: string) => string
}

const ModelSchema = new Schema<ITestModel, TestModel>({
    name: String
})

// NOT a type error (incorrect)
ModelSchema.statics.sMethod1 = function() {
    // type error (correct, params are wrong)
    this.sMethod2()
}

// NOT a type error (incorrect)
ModelSchema.statics.sMethod2 = function() {

}

// NOT a type error (incorrect)
ModelSchema.methods.iMethod1 = function() {
    // type error (incorrect, this method exists)
    this.iMethod2("test")
}

// NOT a type error (incorrect)
ModelSchema.methods.iMethod2 = function() {

}

// create lazy connection object to connect later
const connection = createConnection()

export const TestModelCompiled = connection.model<ITestModel, TestModel>("testModel", ModelSchema)

const modelInstance = new TestModelCompiled()

// type error (correct, method exists but param omitted)
modelInstance.iMethod1()
// type error (correct, method exists but param omitted)
TestModelCompiled.sMethod1()

What I was hoping for by defining the types for these models is that the implementations for each instance and static method would infer their types from the definitions provided to the schema. I would also expect that the this context in both the instance and static methods would have knowledge of the other instance / static methods available on the model.

By this example:

  1. The instance and static methods themselves don’t seem to be inferring their types from the type definitions above. This is also the case when using the schema.static('methodName, function() {}) style syntax. This allows me to implement the methods in a way that violates the type definitions
  2. The static methods seem to be aware of the other static methods defined on the model, so when I call this.sMethod2() with no arguments I (correctly) get a type error. This is not the case in instance methods, where this.iMethod2() tells me the method is not available.

Both instance and static methods have correct types when using them outside the model’s methods themselves, as seen on the last two lines.

This may also be due to a misunderstanding on my part of how to define the model’s types. I’m relatively new to Typescript so forgive me if I’m getting something wrong.

tsconfig.json

{
    "compilerOptions": {
        "allowJs": true,
        "checkJs": false,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "noEmit": true,
        "resolveJsonModule": true
    },
    "include": [
        "server-src",
        "lib",
        "src",
        "__tests__"
    ],
    "exclude": [
        "node_modules/**/*",
        "../../node_modules/**/*"
    ]
}

What is the expected behavior? See above, I was expecting the instance and static methods to correctly infer their types from the model’s type definition, and for the instance method context to contain the other instance methods defined on the type.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that “latest” is not a version. mongoose: 5.12.13 Node.js: 14.15.0 Typescript: 4.2.3

Issue Analytics

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

github_iconTop GitHub Comments

2reactions
vkarpov15commented, Jun 15, 2021

I can confirm the below script compiles successfully with 928ca4a. The issue is that you’ll need to add a 4th generic param to the Schema constructor as shown below:

import { Schema, Model, createConnection } from 'mongoose'

interface ITestModel {
    name: string
}
interface InstanceMethods {
    iMethod1: (param1: string) => string
    iMethod2: (param1: string) => string
}
interface TestModel extends Model<ITestModel, {}, InstanceMethods> {
    sMethod1: (param1: string) => string
    sMethod2: (param1: string) => string
}
const ModelSchema = new Schema<ITestModel, TestModel, undefined, InstanceMethods>({ // <-- add `InstanceMethods` here
    name: String
})

ModelSchema.statics.sMethod1 = function() {
    this.sMethod2('test')
}
ModelSchema.statics.sMethod2 = function() {}

ModelSchema.methods.iMethod1 = function() {
    this.iMethod2("test")
}
ModelSchema.methods.iMethod2 = function() {
}

// create lazy connection object to connect later
const connection = createConnection()

export const TestModelCompiled = connection.model<ITestModel, TestModel>("testModel", ModelSchema)

const modelInstance = new TestModelCompiled()

modelInstance.iMethod1('test')

TestModelCompiled.sMethod1('test')

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Type Inference - TypeScript
This kind of inference takes place when initializing variables and members, setting parameter default values, and determining function return types. In most ...
Read more >
TypeScript: JavaScript With Syntax For Types.
TypeScript understands JavaScript and uses type inference to give you great tooling without additional code. Get Started. Handbook. Learn the language.
Read more >
Documentation - Type Compatibility - TypeScript
Type compatibility in TypeScript is based on structural subtyping. Structural typing is a way of relating types based solely on their members.
Read more >
Handbook - Interfaces - TypeScript
In TypeScript, interfaces fill the role of naming these types, and are a powerful way of defining contracts within your code as well...
Read more >
Documentation - Narrowing - TypeScript
Much like how TypeScript analyzes runtime values using static types, it overlays type analysis on JavaScript's runtime control flow constructs like if/else , ......
Read more >

github_iconTop Related Medium Post

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