0
import { FilterQuery } from "mongodb";

abstract class Base<T extends Base<any>> {
    public static async findOne<T extends Base<any>>(
        this: new (...a: any[]) => T,
        query: FilterQuery<T>
    ): Promise<T> {
        console.log(this.getSomeString()); // currently: "getSomeString" dosnt exists on type "new (...a: any[]) => T"
        // already tried "typeof T" - because "typeof" is not allowed on generics
        // already tried https://stackoverflow.com/a/52358194/8944059 's static solution
        // already tried "T" - which makes "this: any"
        // and i tried various other things, which resulted in "await SomeExtendingClass" cannot be assigned to "this"

        // Edit for @jcalz
        console.log(this.someProtectedMethod()); // error "someProtectedMethod" does not exists on type "this"

        return undefined;
    }

    public static getSomeString(): string {
        this.someProtectedMethod(); // this would work
        return "Hello";
    }

    // Edit for @jcalz
    protected static someProtectedMethod() {
        return "hello2";
    }
}

class SomeExtendingClass extends Base<SomeExtendingClass> {
    public someValue: string;
}

(async () => {
    await SomeExtendingClass.findOne({ someValue: "hi" });
})();

Goal is to have all statics in this while being able to call it with new this(...args) and while having all keys of (Parent class) insert-able into query and without having to remove abstract

Already tried:
- https://stackoverflow.com/a/58037767/8944059
- https://stackoverflow.com/a/52358194/8944059 's static solution

PS: i dont understand most of the magic of typescript types, that is why i'm asking...

PPS: here is the project i would need it

4
  • 1
    Can you change findOne()'s this parameter type to Pick<typeof Base, keyof typeof Base> & (new (...a: any[]) => T)? Commented Nov 13, 2019 at 17:42
  • @jcalz thanks, this done it, but do you know how to include protected functions / values? Commented Nov 13, 2019 at 22:44
  • I don't know; can you edit your code to be a minimal reproducible example so I can test it myself? It's possible you can just use typeof Base & (new (...a: any[]) => T) but I'd have to see a full minimal reproducible example to know if it works. The issue with private and protected members is they are intentionally hidden from keyof, see microsoft/TypeScript#13543 Commented Nov 14, 2019 at 14:08
  • @jcalz i added an example method & how i would call it Commented Nov 14, 2019 at 17:12

2 Answers 2

0

Thanks to @jcalz

His answer was to replace

new (...a: any[]) => T

with

Pick<typeof Base, keyof typeof Base> & (new (...a: any[]) => T),

this fixed it for me mostly

what i found is that this does not include protected values/functions

this question will be edited if an answer to the protected sub-question can be found

Sign up to request clarification or add additional context in comments.

Comments

0

Your example code doesn't show why you need any this parameter at all. For example, the following code works even with protected static methods. The default type of this is just typeof Base:

import { FilterQuery } from "mongodb";
abstract class Base<T extends Base<any>> {
    public static async findOne<T extends Base<any>>(
        query: FilterQuery<T>
    ): Promise<T> {
        console.log(this.getSomeString()); 
        console.log(this.someProtectedMethod()); 
        return undefined!; // <-- not a Promise
    }

    public static getSomeString(): string {
        return "Hello";
    }
    protected static someProtectedMethod() {
        return "hello2";
    }
}
class SomeExtendingClass extends Base<SomeExtendingClass> {
    public someValue: string = "123"; // needs initialization
}
(async () => {
    await SomeExtendingClass.findOne({ someValue: "hi" });
})();

If the above doesn't suffice for your use case, please edit your example code to show some places where it breaks. Presumably there's some reason you needed to add a this parameter in the first place; what's the reason?

Anyway, hope that helps; good luck!

Link to code


Edit: if you need this to be constructible with zero args inside findOne(), and not allow findOne() to be called on a class that isn't constructible that way, you could possibly do this:

abstract class Base<T extends Base<any>> {
    public static async findOne<T extends Base<any>>(
        this: typeof Base & (new () => any),
        query: FilterQuery<T>
    ): Promise<T> {
        console.log(this.getSomeString());
        console.log(this.someProtectedMethod());
        new this();
        return null!;
    }
    public static getSomeString(): string {
        return "Hello";
    }
    protected static someProtectedMethod() {
        return "hello2";
    }
}

Base.findOne(null!); // error, abstract

class SomeExtendingClass extends Base<SomeExtendingClass> {
    public someValue: string = "123"; // needs initialization
}
SomeExtendingClass.findOne(null!); // okay

class InvalidExtendingClass extends Base<InvalidExtendingClass> {
    constructor(x: string) {
        super();
    }
}
InvalidExtendingClass.findOne(null!); // error, requires an argument

Link to code

If you have other requirements (like allowing different constructor arguments for subclasses) then let me know. Good luck again!

9 Comments

"If the above doesn't suffice for your use case" that is why i included the mongodb type, without defining "this" you dont have type support for all the values defined in the inherited class, in this case it is someValue in .findOne({ someValue: "hi" }), PS: i mean without doing .findOne<SomeExtendingClass>() and just having it infered from the "this"
Sorry for the late response, didnt see the edit - "initialization" is a no-go in my use-case, and otherwise, i get the error i mentioned already "The "this" context of Type "typeof SomeClass" cannot be assigned to the type "typeof Base"" - this might relate to some other complex types i (have to) use
"Your example code doesn't show why you need any this" sorry, but i linked to the repo i would need | use it in, which has a constructor with arguments
Initialization is just a type safety concern; if you don't want to do it, it doesn't affect the rest of the answer (but you should do it, or make the property optional. uninitialized properties are a source of bugs).
An external repo doesn't really count as a minimal reproducible example, unfortunately. External links are good for convenience but not if they contain substantive information needed for the question. All pertinent information should be in the text of the question as suggested in the guidelines for How to Ask (and in How to Answer also, fwiw).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.