By "ergonomic" I mean "doesn't require consumers to use
InstanceType<typeof mod["Foo"]>syntax"
I am writing a .d.ts for a JS source file. In a few months, that JS file will be converted to TS but we can't convert it yet.
The file is something like this:
// module.js
class Foo {
sayFoo(){}
}
export default {
Foo
};
tsc generates a .d.ts like this:
// module.d.ts
declare class Foo {
sayFoo(): void;
}
declare const _default: {
Foo: typeof Foo;
};
export default _default;
The caller has to jump through some really weird hoops to get at the class type:
import mod from "./module"
function takesFoo(foo: InstanceType<typeof mod["Foo"]>) {
foo.sayFoo();
}
But if I lie about the types a bit using namespace:
export default NS;
// module.d.ts
declare namespace NS {
export class Foo {
sayFoo(): void
}
}
then the caller gets a much better type-level API!
import mod from "./module";
function takesFoo(foo: mod.Foo) {
foo.sayFoo();
}
I could just use namespace, but then we're in a bind when we convert the JS file to TS. Our options at that point will be:
(A) Never actually convert the library entrypoint to TS. Author most of the library in TS, then use a tiny JS file with a .d.ts to wrap up the API. This would remove any checks that we are actually conforming to our public API! And it would be confusing.
(B) Force the weird type-level API on callers. Perhaps soften the blow by exporting some interfaces, and teach callers about the split personality of TS class types.
Am I missing an option or not understanding something about type-level dots in TS?