Working with externs for the popular Winston logger (NodeJS)

So, another week, another externs challenge. Again, I got helped by @haxiomic’s dts2hx, who is probably sick’n’tired of getting tagged every question I post around here :smiley: But hey, it’s weekend, we’re engineers, WTH (what the heaven).

I did manage to get Winston to work… somewhat. Unfortunately, there’s a configuration object that I don’t know how to instantiate and which makes my version of Winston rather useless (poor beast has a heartbeat but no arms and legs… in English now: the logging works but it doesn’t have any Transports to output to).

Winston uses Transports to log anything. One has to provide a Transport or array of it to the createLogger function.

typedef LoggerOptions = {
var transports : ts.AnyOf2<Transport, Array<Transport>>;

I’ve been trying to use a FileTransportInstance, and since that’s a typedef…

typedef FileTransportInstance = {
	var name : String;
	var filename : String;

I’ve been trying to instantiate it with the object literal initializer, just like I initialize LoggerOptions (which works). But for FileTransportInstance, I get nowhere. Well, I get somewhere but it’s somewhere with lots of errors and I don’t think that’s the where I want to get to :smiley:

I noticed that in TypeScript

interface FileTransportInstance extends Transport {

And Transport does have a new function, but it doesn’t get inherited in Haxe because in Haxe they’re all typedefs.

I did try to instantiate Transport, since it has a new member

function new(?opts:winston.transport.TransportStreamOptions);

But I vaguely remember that new doesn’t work this way in externs. Sorry if I forgot the exact way it works, I’m juggling way too many things in my head this period.

Here’s my code so far (and there’s a github repo below too):

import js.Node.console;
import js.node.Path;
import js.Node.__dirname;
import js.Browser;
import winston.LoggerOptions;
import logform.Format_;
import winston.transports.Transports;
import winston.transports.FileTransportInstance;
import winston.Transport;

class Test {
	static function main() {

		var transportsArr:Array<Transport> = new Array<Transport>();

		// crash: TypeError: winston_Transport is not a constructor
		// var transportErr:Transport = new Transport();

		// compile error: missing fields in object initalizer. After which another error saying
		// { filename : String } should be winston.transports.FileTransportInstance
		// var transportErr:FileTransportInstance = {filename: "error.log"};

		// transportsArr.push(transportErr);

		var loggerOptions:LoggerOptions = {
			level: "info",
			// commenting out transports (and the above errors) actually makes it work,
			// but of course nothing is logged (and the library warns about it).
			// transports: transportsArr,
			format: Format_.json(),
			defaultMeta: {service: 'user-service'},

		var logger:winston.Logger = Winston.createLogger(loggerOptions);

		logger.log("info", "This is an info log.");
		logger.log("error", "This be an error log.");

Aaaaanyway, I added this one to my GitHub repo with Haxe’s World of Externs :slight_smile:. The problem can be easily reproduced with a simple haxe build.hxml in the winston-haxe directory:

Hey, looks like the type definitions for winston are managed separately and so can become out of sync with the code or have mistakes in them

The first issue – new Transport(); “winston_Transport is not a constructor” also happens just the same in typescript

// typescript
import {transport} from 'winston';
new transport({});

When I compile and run this I get

new winston_1.transport({});

TypeError: winston_1.transport is not a constructor

But there’s no error from typescript – this is because winston’s type definitions incorrectly say that transport is an exported member of the ‘winston’ module when it isn’t (you could open an issue on their repo).

The docs seem to suggest Transport is exported in the module ‘winston-transport’ so if you want to use that class you could do

npx dts2hx winston-transport

Then add --library winston-transport and finally use it like new WinstonTransport(); (which I’ve tested works). So dtshx is working as expected there – the index.d.ts file needs correcting.

On FileTransportInstance, looking at the index.d.ts file, the developers have used interface for everything – I’m not sure that’s what they mean to do semantically, I think many of those types should be defined as classes, however in spite of that, it would be nice if dts2hx could support interfaces with constructors. It’s not trivial because haxe doesn’t have native support for this (I’ve explored this before in [js] Constructible variable · Issue #9335 · HaxeFoundation/haxe · GitHub). I can enable this in dts2hx by wrapping these types in abstracts like

typedef F = {
	var filename : String;

abstract FConstructable(F) to F from F {

	public inline function new() {
		this = js.Syntax.construct('F');


But this requires some pretty big changes to dts2hx to make work so it’ll take a while before I get to it I’m afraid (but it is on the roadmap)

Best thing I can suggest for now is to explore improving winston’s .d.ts files as I don’t see a quick-fix haxe-side

Hm. Decided it’s best I look for another library. So… I went for log4js. And, for the kicks, integrated Chalk too:


I had no issues with these 2 externs and could integrate them successfully and easily thanks to dts2hx.

I feel that dts2hx is one of the most important pieces for Haxe’s future in a world so dominated by JS. The amount of code on NPM is staggering. Opening the door to that is key (and the key to opening the door is dts2hx, hehe).

(and even more glorious features await, I’m sure)

1 Like