Question Details

No question body available.

Tags

typescript typescript-generics

Answers (1)

Accepted Answer Available
Accepted Answer
August 17, 2025 Score: 1 Rep: 339,611 Quality: High Completeness: 80%

You can't merely say that ApiObject has an api of type Api, because there's nothing to say that any particular subclass of ApiObject's api will produce the same subclass of ApiObject. Right now Api knows about ApiObject's subtyping, but ApiObject doesn't know anything about Api's subtyping. They both need to know about each other for it to work.

If subclasses of both Api and ApiObject need to know specifically about each other's type details to work, then you'll need to use recursive constraints (a.k.a. F-bounded quantification). You already have an ApiObject constraint on Api's O parameter, but you don't have anything in ApiObject's constraints that point to Api or to itself. In general you might have to add both an A (for Api) and an O to both classes, but it looks like you can get away with having just O:

class ApiObject {
    constructor(private obj: T, private api: Api) { }
    delete() { }
}

class Api { constructor(private creator: new (obj: T, api: Api) => O) { } get(obj: T) { return new this.creator(obj, this); } }

See how Api's creator produces an O, and ApiObject takes an Api, so the type information you care about is reserved. Now your subclasses need explicit references to themselves also:

type Test = { x: number };

class TestObject extends ApiObject { change() { } }

class TestApi extends Api { constructor() { super(TestObject); // okay } }

const api = new TestApi(); const obj = api.get({ x: 1 }); obj.change(); // okay

And this works. Note that sometimes you can avoid such explicit recursive generic constraints by using the polymorphic this type, which is effectively an implicit generic type parameter that's constrained by the class. That is, the type of this inside ApiObject would be used in place of O. But polymorphic this is only allowed inside instances of classes, and you'd need the constructor (which is static) for it to work here. Without something like microsoft/TypeScript#5863, it's not currently possible in TypeScript. So you need the explicit recursive generic. Annoying, but it works.

Playground link to code