#definitely typed types

56 messages · Page 1 of 1 (latest)

viral haven
#

im trying to type an untyped library and the api exports a Chessboard class. The tricky part is that if you specify an Extension some functions might be registered to the chessboard class.
Meaning the constructor of the RenderVideo will get the chessboard and add some functions to the chessboard class.
I am having difficulties coming up with something which types this

export class Extension {
    chessboard: Chessboard
    constructor(chessboard: Chessboard);
    registerExtensionPoint(name: ExtensionPoint, callback: Function): void
}

export class RenderVideo extends Extension {
    constructor(chessboard: Chessboard, props: {
        mediaType: string
        safariMode: boolean
    });

    cloneImageAndRender(): Promise<void>;
    transferComputedStyle(element: Element): void;
    makeSpriteInline(): void;
 }

export class Chessboard {
    constructor(context: HTMLElement, props: {
        extensions: Array<{ class: typeof Extension; props: any }>;
    });
}
#

a real world example looks like this

    const boardElement = document.getElementById("board")
    const chessboard = new Chessboard(boardElement, {
        position: FEN.start,
        assetsUrl: "../../assets/",
        extensions: [{class: RenderVideo}]})
    await chessboard.recorderInit()
    await chessboard.recorderStart()

here it adds recorderInit, recorderStart , recorderStop and recorderPause to the chessboard...

livid trellis
#

How does RenderVideo add recorderInit/etc?

#

I don't see any of those methods in your RenderVideo class.

viral haven
#

he has them in the constructor

    constructor(chessboard, props) {
        super(chessboard)
        chessboard.recorderInit = () => {}
livid trellis
#

Well you need those information to be available in TS in some way.

viral haven
#

ik but im wondering what the best way to do this would be

viral haven
#

i mean i could easily just type them on the class RenderVideo but my other question then still stands, how I can make it so that the class Chessboard can also use these methods when instantiated with a RenderVideo Extension

livid trellis
#

Eh yeah I'm not sure if this is doable

#

Presumably the extensions option allows multiple extensions, and I can't think of a way to do it.

viral haven
#

yes..

livid trellis
#

And I'm guessing you can't changed the underlying JS code.

viral haven
#

the author hates microsoft

#

so i dont think he'll approve any changes with the reason to type his lib / provide types for it

livid trellis
#

FWIW here's one attempt at it, but this approach sucks (requires @ts-expect-error) and it wouldn't work for multiple extensions.

fierce eagleBOT
#
nonspicyburrito#0

Preview:```ts
declare const __type: unique symbol

declare class Extension<T> {
private declare [__type]: T
}

declare class Chessboard<T> {
constructor(extension: typeof Extension<T>)
}

// @ts-expect-error
interface Chessboard<T extends object> extends T {}

//----------
...```

livid trellis
#

I'm going to try somethign else rather than using classes.

#

!*:u2i

fierce eagleBOT
#
tjjfvi#0
`!t6:u2i`:
type U2I<U> = (
  U extends U ? (u: U) => 0 : never
) extends (i: infer I) => 0 ? Extract<I, U> : never
#
nonspicyburrito#0

Preview:```ts
type U2I<U> = (
U extends U ? (u: U) => 0 : never
) extends (i: infer I) => 0
? Extract<I, U>
: never

declare const __type: unique symbol

declare class Extension<T> {
private declare [__type]: T
}

type ExtensionOption = {
class: typeof Extension<unknown>
}
...```

viral haven
#

wow!

viral haven
#

do you happen to know if i should type private meant function for the extension class ?

livid trellis
#

Hmm, not sure what you mean by that.

viral haven
#

i.e.

export class Persistence extends Extension {
    constructor(chessboard, props) {
    }

    savePosition() {
    }

    loadPosition() {
    }
}

so he also has this extension which doesnt add any functions to the chessboard and it only uses these functions internally.
I guess I should type these too but say they are private?

#

or leave them as public or just not type them at all

livid trellis
#

Aren't savePosition/loadPosition supposed to be added to Chessboard?

viral haven
#

no

#
export class Persistence extends Extension {
    props: any
    
    constructor(chessboard, props) {
        super(chessboard)
        console.warn("The Persistence extension is work in progress, don't use it in production.")
        this.props = props
        this.registerExtensionPoint(EXTENSION_POINT.positionChanged, this.savePosition.bind(this))
        chessboard.initialized.then(() => {
            this.loadPosition()
        })
    }

    savePosition() {
        localStorage.setItem("chessboard", JSON.stringify(this.chessboard.getPosition()))
    }

    loadPosition() {
        const position = localStorage.getItem("chessboard")
        if (position) {
            this.chessboard.setPosition(JSON.parse(position))
        } else {
            this.chessboard.setPosition(this.props.initialPosition)
        }
    }
}
livid trellis
#

Oh then just type them if they are public

viral haven
#

ah wait

livid trellis
#

If they are private, up to you 🤷

viral haven
#

this.registerExtensionPoint(EXTENSION_POINT.positionChanged, this.savePosition.bind(this))
mhh he adds them to the chessboard class via this

#

god this is super annoying

livid trellis
#

Yeah then you'd want those methods in generic type parameter of Extension<T>.

viral haven
#

can we make Chessboard a class? Im having some troubles passing the definitely typed tests with the current approach

#

Interfaces cannot be constructed, only classes. Did you mean declare class?

livid trellis
#

Don't think there's a way to make it work as a class.

#

Since constructor cannot be generic.

viral haven
#

any idea if we can more or less do the same for the props now too?

fierce eagleBOT
#
disservin#0

Preview:```ts
type U2I<U> = (
U extends U ? (u: U) => 0 : never
) extends (i: infer I) => 0
? Extract<I, U>
: never

declare const __type: unique symbol

declare class Extension<T> {
private declare [__type]: T
}

type ExtensionOption = {
class: typeof Extension<unknown>
}
...```

#
disservin#0

Preview:ts ... new <T extends ExtensionOption>(props?: { extensions?: (T & { props?: any })[] }): Chessboard & ExtractExtension<T> ...

viral haven
#

ahh heres a new one, i did manage to make it work with addExtension but im struggeling a bit with the constructor

livid trellis
#

Do props just get attached to the chessboard instance?

#

If that's the case, you can use the same idea and just give the constructor another generic type parameter, and merge the props into the return.

#

As for addExtension, you can use asserts this is ... to achieve that.

viral haven
livid trellis
#

This is some convoluted API.

#

I almost feel like it's easier to just rewrite whatever in TS instead, with a better design.

#

Although not sure how much work it is.

viral haven
#

😂

viral haven
#

@livid trellis i came up with this now which basically fixes my props issue in the constructor but it doesnt work with multiple classes...
is there anyway we can bring back the functionality to allow multiple class's ?

fierce eagleBOT
#
disservin#0

Preview:```ts
type U2I<U> = (
U extends U ? (u: U) => 0 : never
) extends (i: infer I) => 0
? Extract<I, U>
: never

declare const __type: unique symbol

declare class Extension<T> {
private declare [__type]: T
constructor(chessboard: Chessboard)
}

type ExtractBaseClassMethods<
T extends typeof Extension<unknown>

= U2I<
T extends ty
...```

viral haven
#

ah meh, i just realized that this isnt even really possible...

chessboard2.addExtension(RenderVideo, {
    foo: "2",
    // @ts-expect-error
    x: 2
})

chessboard2.renderVideoMethod();

theres no way I think to say that calling this function with alter the functions of chessboard2...

fierce eagleBOT
#
interface Foo {
    addExtension<T>(extension: T): asserts this is this & T
}

declare const foo: Foo

foo.addExtension({ doStuff() {} })

foo.doStuff()
livid trellis
#

Although there are caveats, namely it doesn't work across files (which makes sense, it's impossible for TS to know which files will be executed at what order)