Using TypeScript to define a method that returns the type of the current instance

Creating my own MySQL ORM for a project. I have designed an abstract 'model' class that other models I develop can inherit from, inheriting all their methods and properties. My current challenge revolves around specifying that a method will return the type of the class that extends it. Below is some code snippet.

import db from '../db'

export default abstract class Model {
    protected static TableName: string
    protected static Columns: string[]

    protected constructor() {
        console.log('Initializing a model')
    }
                                               //Replace Model here with something like "type of this"
    public static async findByID(id: string): Promise<Model> {
        const query = `select ${this.columns} from ${db.name}.${this.TableName} where ${this.Columns[0]}='${id}'`

        return (await db.query(query) as Model[])[0] //Replace Model here with something like "type of this"
    }

    private static get columns(): string {
        return this.Columns.reduce((acc, cur, i, arr) => i !== arr.length - 1 ? acc + cur + ', ' : acc + cur, '')
    }
}

I acknowledge the risk of potential SQL injection. Thanks!

Answer №1

If you're in search of information on "polymorphic this for static members," it's worth noting that this feature is not currently included in the TypeScript language. There's an ongoing discussion around this topic in a GitHub issue located at microsoft/TypeScript#5863. While there are workarounds mentioned in the issue, official implementation remains uncertain.


One workaround involves using a generic this parameter instead of a polymorphic type when dealing with static methods:

public static async findByID<T extends Model>(
    this: { prototype: T }, id: string
) {
    const thiz = this as any as typeof Model;
    const query = 
      `select ${thiz.columns} from ${db.name}.${thiz.TableName} where ${thiz.Columns[0]}='${id}'`
    return (await db.query(query) as T[])[0]
}

This approach specifies that to call findById(), it should be used as a method of an object with a prototype property of generic type T. The construction allows T to be inferred based on the calling subclass constructor, facilitating access to properties specific to that instance type.

Note that by introducing a this parameter, the default non-polymorphic this type (typeof Model) gets overridden. This prevents direct access to protected methods and properties via the generic this.


For validation purposes, consider putting this approach to the test:

class SubModel extends Model {
    submodelProp = "foo"
}
SubModel.findByID("hello").then(s => s.submodelProp.toUpperCase())

The successful execution of SubModel.findById(), returning a Promise<SubModel>, confirms accessibility to its unique submodelProp attribute.


Hopefully, these insights prove beneficial. Best of luck with your TypeScript endeavors!

Access the code demo here

Answer №2

In the current version of TS 3.9, specifying a class name as it is, such as Model, is not achievable.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Custom CSS identifier for interactive MuiButton text element

I've been searching for a CSS selector to target this dynamic date element, which is identified as MuiButton-label. While I can currently locate it using xpath in Playwright code, I'm hoping to find an alternative method using a CSS selector. Tha ...

"Debating Angular: Comparing the Use of Getters and Methods in

To prevent cluttering the *ngIf directive with logic directly in the template. <div *ngIf="a === 3 && b === 'foo'"></div> I typically create a custom method like this: public isOk(): boolean { return a === 3 & ...

Adjust the colors of two elements by clicking a button's onclick event

As stated in the title, I have a button in the books component. When this button is clicked, the color of a div within the books component and another div in the navbar component should change. - Below is the code for the books component : export class Bo ...

Tips on using the `IsEqual` function to develop a tool that verifies the similarity of different data types

When working with TypeScript, I often utilize the type-fest npm package in my coding. For documentation purposes, I sometimes like to assert that two types are either equal or unequal. Here is an example: const b: IsEqual<{a: 1}, {a: 1}> = true; con ...

Navigating through the Angular Upgrade Roadmap: Transitioning from 5.0 to 6

As per the instructions found in this helpful guide, I executed rxjs-5-to-6-migrate -p src/tsconfig.app.json. However, an error is appearing. All previous steps were completed successfully without any issues. Any suggestions on how to resolve this? Please ...

Generate the JavaScript file only if the Typescript file is error-free, to ensure a smooth compilation process

Take a look at the following TypeScript code snippet: class formal { private startString: String = ""; constructor(startString:String) { this.startString = startString; } public sayHello = function() :Number { alert(thi ...

NGRX refresh does not result in any successful actions

Having an issue with loading users into a mat-selection-list within a form. Everything works fine the first time, but upon page refresh, the selector returns 'undefined'. Initially, both GET_USERS and GET_USERS_SUCCESS are triggered (console log ...

Discovering all invalid elements in an Angular 8 Form using Typescript by revealing required fields post button click

Once the button is clicked, I want to retrieve all invalid elements in the Form and showcase those fields that are either incomplete or required. ...

Issue with Class-based React Component Inheritance: The "render" property cannot be assigned to the identical property in the parent type

I am currently working on a new React Native app with Typescript and I have decided to utilize class based components. So far, I have created two classes: RouteView.tsx export class RouteView extends React.Component { constructor(props: any, private ...

Issue: The Auth Interceptor is expecting a stream but received 'undefined'. Please provide an Observable, Promise, Array, or Iterable instead

I am facing an issue where I need to intercept every request to api, check the status code, and display a message or redirect to a specific component. However, I keep encountering the following error: main.js:1580 TypeError: You provided 'undefined ...

Cypress is unable to retrieve variables stored in the process.env file

I am encountering an issue with retrieving variable values from the process.env file in my Cypress test. I have followed the instructions provided here: https://docs.cypress.io/guides/guides/environment-variables#Option-5-Plugins. However, I keep getting a ...

Constructor of Component invoked on two separate occasions

In my child component, the id property is randomly set like this: export class FileSelectionComponent implements AfterViewInit { public type = 'app-file-selection'; public id = 'FileSelection#' + Math.random().toString(16).slice(2, ...

Tips for transferring information between different components through a collaborative service

Attempting to send data using a subject to another component for the purpose of earning, but experiencing difficulty retrieving the data. Below is the provided code snippet: app.component.ts import { Component } from '@angular/core'; import { s ...

Challenges with specifying types in a Typescript login function

Currently facing an issue with the login code, where it is meant to authenticate a username and password, retrieve the corresponding hash from the database, compare them, generate a JWT, and send it back to the user: async login(username, password): Promi ...

How can I establish a connection to a Unix socket path using a connection string with Slonik?

Hey there! I encountered an issue while attempting to connect to a Google Cloud database using slonik: const pool = createPool( `socket:userName:password@/cloudsql/teest-123986:europe-west3:test?db=dbName` ) The error message I received was: error: throw ...

Implementation of a recursive stream in fp-ts for paginated API with lazy evaluation

My objective involves making requests to an API for transactions and saving them to a database. The API response is paginated, so I need to read each page and save the transactions in batches. After one request/response cycle, I aim to process the data an ...

How can I incorporate percentage values into input text in Angular?

How can I include a percent sign in an input field using Angular, without relying on jQuery? I am looking for a solution that is identical to what I would achieve with jQuery. Here is the current status of my project: ...

TypeScript incorporates a variety of @types versions for react

I made changes to my compilerOptions within the tsconfig.json file with the specified paths "paths": { "react": ["node_modules/@types/react"], "@types/react": ["node_modules/@types/react"] } However, I noticed that @types/react-router is using its o ...

Discovering the most efficient route between two locations within a grid of values

I'm currently working on a game where I need to find the shortest route between two points. https://i.sstatic.net/jBnEd.png In my map, I have a 2D array called matrix: Node[][], class Node{ index: { x: number, y: number }, isAvai ...

The service remains operational while the button's status undergoes a change

In my data table, each row has a column containing buttons. To ensure that only the button in the clicked row is executed, I include the index of that row in the start/pause timer function. I decided to create these functions in a service so that the time ...