0
votes

Currently I am tasked with creating node modules from our Vanilla JS and moving them to typescript. I rewrote them as classes, added some functionality, wrote a legacy wrapper and the corresponding web pack configuration. The problem is that some of these modules are singletons, so instead of exporting the class by default we want to export an class instance as default. The problem is that I don't get the type checking to work properly:

import DebugJs from 'debug';
const test = (test: DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

The problem here is that DebugJs is not recognized as a type. So currently I have to import an additional Interface in order to set the type properly.

Just for comparsion, this is what I currently do instead:

import DebugJs, { DebugJsInterface } from 'debug';
const test = (test: DebugJsInterface) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

I tried around with namespaces and module declaration, but to be honest, as someone who is totally new to node module creation and quite new to typescript I don't really know what I am doing there.

Here is my current index.d.ts file setup

import DebugJs from './src/debugJsModule';
import {DebugLevels} from "./src/types/types";

export interface DebugJsInterface {
    levels:DebugLevels;
    enabled:boolean;
    level:number;
    console(...arguments: any): void;
    enableDebug(): void;
    disableDebug(): void;
    setLevel(level:number): void;
}

export interface Module {
    DebugJs: DebugJsInterface;
}

export default DebugJs;
declare module 'debug';

Here the DebugJsInterface is defined as a work around. I am also a little puzzled since I thought that the idnex.d.ts should only have type information. But if I don't export the class instance from here my module import won't be recognized as a class properly.

This is my debugJsModule wrapper which returns a class instance:

import DebugJs from './class/DebugJs';
import { DebugLevels } from 'debug/src/types/types';

const DebugJsInstance: DebugJs = new DebugJs();

export default DebugJsInstance;

The class itself is simply defined as a class and exported as a default export as well

 class DebugJs { ... } 

Just to be clear, functionality wise everything works, all I want to figure out how I can have the proper type of my importet class instance by using the same import name, in this case DebugJs, without having to rely on an extra importet interface as a workaround.

2
You should paste how you're defining DebugJs, exporting it and how the singleton is made. In your first example, DebugJS should absolutely work as a type if it is a class or a type. However, if you're exporting the singleton as default it wont work. It is not clear what DebugJs is and how you're exporting the type/class and the singleton. - Sergeon
Actually how the singelton is made is at the bottom, the class itself is simply a class definition: ``` class DebugJs { ... } ``` Also exported as default. Everything works fine as long as I export the class itself, but as soon as i export the instance instead I cant use the export as the Type definition anymore. - Dustin Gogoll
Oh yes, I saw the import DebugJs and though that file was merely reexporting things, me bad - Sergeon

2 Answers

1
votes

I don't now if this kind of patterns may suite you, but I usually do this by placing the class and the singleton in the same file, and exporting the singleton as default and the class as a named export:

class DebugJs {
  // code
}

const debug = new DebugJs();

export default debug;
export {DebugJs};

Then in a client if I import the singleton:

import debug from '.../path';

debug.format().log().whatever(); // typings and intellisense works fine. debug will be recognised as DebugJs instance.

And, in the event you need a fresh instance of DebugJs unrelated to the singleton, you can:

import {DebugJs} from '.../path';

const debug = new DebugJs(); // typing and intellisense will work

I'm pretty sure this will work because I use this pattern a lot.

However I don't know properly why your setup is not working. I don't know if using default exports for types, or maybe combining that with the singleton re-export may be causing your issue.

1
votes

This snippet doesn't really make that much sense:

import DebugJs from 'debug';
const test = (test: DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

Here you are not asking for an instanceof DebugJs, you are actually telling typescript to expect an instance of an instance of DebugJs.

Thing is, I don't think it makes any sense here. Why import a instance of something and then immediately not use it?

Anyway, it is possible, it just requires this syntax:

const test = (test: typeof DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

You'll probably either want to :

  1. Not import the instance, but only import DebugJsInterface instead. That's what you should use for the type guard.
  2. Or: don't require it as an argument.

Example 1:

const test = (test: DebugJsInterface) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

Example 2:

const test = () => {
    DebugJs.console('warn', 'does', 'respond', 'with a warning', test);
};

I suspect you want one of the above