I'm working on creating an extern for CORS via Express JS

Hey :). As part of building an open source stack for everybody to witness the wonder that is Haxe, I’m working at a Node JS Express server (complete with watchers, debug via VS Code, source maps integrated in the error reports in the console and more).

For the past few hours I’ve been tinkering at getting CORS to work with Express. I’ve been using @haxiomic 's dts2hx to generate the externs for both Express and CORS. Express worked fine, but I got stuck with CORS.

I think dts2hx doesn’t completely process the cors package. The .ts files have a namespace declaration called “e”. dts2hx does extract the members in the namespace, but only at a “root level” so to say. It also misses a critical function needed to wire up cors to Express.

The function is at the end of the node_modules\@types\cors\index.d.ts file:

//the rest of the file...

declare function e(
    options?: e.CorsOptions | e.CorsOptionsDelegate
): express.RequestHandler;
export = e;

And this is missing in the Haxe extern. So…

I’ve been working at declaring an extern for this e. I haven’t wrote a single extern before, but I do understand the principles of it. So, I did /some/ work.

But now I need a pointer regarding how would one create an extern for a JS function that is located outside of a class.

This is the class I’ve added to the cors dts2hx-generated package:

And this is how I’m trying to use it, of course, trying to hook it in the Express middleware by using app.use()

Of course, this can’t work anyway because I created the class as HowToMapThis, because I don’t know how to reach that e function :slight_smile:

Oh, and if you want to actually run the project and work directly on it, see the readme.md in the repo root :slight_smile:

dts2hx should be converting that function, I’ve opened a bug to track

The cors definitions aren’t really clear; the function e isn’t actually called e in the source code, it’s just the object you get when you import the cors module. In a sense the function is the module

So to call this function in javascript you might do

let cors = require('cors');
let requestHandler = cors({ /* options */ }); // this is the function 'e'

In haxe, modules must be types or classes so a js function-as-a-module must be wrapped in a class, and use can use the @:selfCall metadata to mark a function as calling the module

// Cors.hx
@:jsRequire('cors') extern class Cors {
    @:selfCall
    static function call(?options: EitherType<CorsOptions, CorsOptionsDelegate>): express.RequestHandler;
}

// usage:
import Cors;
var requestHandler = Cors.call(options);
// or
app.use(Cors.call(options));

This is the same pattern used in the express library, which is why in haxe you do Express.call() rather than express() as you might in js.

Module-level functions are in nightly haxe builds so in the future we might be able to change it so it becomes

// Cors.hx
extern function cors(?options: EitherType<CorsOptions, CorsOptionsDelegate>): express.RequestHandler;

// usage:
import Cors;
var requestHandler = cors(options);

(But this currently doesn’t work!)

3 Likes

@haxiomic ! :slight_smile: damn am I happy you replied. Even happier that my question wasn’t completely stupid. I actually looked at the adaptation of Express and saw that @:selfCall, but didn’t know how to implement it. And yes, I noticed the stuff with .call() and I also had a stab at doing that but it didn’t work :slight_smile:

One question: how do I define the external JS Cors class? In your proposal of Cors.hx you refer to extern class Cors. I suppose I should wrap your JS code in a Cors JavaScript class (and modify cors in node_modules)? I’m going to experiment a bit (& look in the 3 different Express.hx and the Express TS), but in case you’re still around, I’m leaving this here :).

Currently hacked this into its weird existence: :smiley:

export default class Corsifier
{
    corsWrapper(options)
    {
        let cors = require('cors');
        return requestHandler = cors(options); 
    }
}

Just defining Cors.hx as this

@:jsRequire("cors") extern class Cors {
	@:selfCall
	static function call(?options:ts.AnyOf2<cors.CorsOptions, cors.CorsOptionsDelegate>):express.RequestHandler<express_serve_static_core.ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>;
}

Is all you need to do – later in your haxe code if you write

app.use(Cors.call(options))

It will work because the cors module is imported via @:jsRequire, I just tested with

class Main {

	static function main() {
		Cors.call();
	}

}

And I got this js output

// Generated by Haxe 4.1.3
(function ($global) { "use strict";
var Cors = require("cors");
var Main = function() { };
Main.main = function() {
	Cors();
};
Main.main();
})({});

Which is correct

I’ve found the issue in dts2hx, so I’ll probably have a new version up before tomorrow :slight_smile:

Interesting solution :). You answered exactly as I was celebrating that my hack worked :smiley: . I don’t exactly understand how your solution works just because the Cors module is imported in that way. Because in JS there is no literal named Cors from what I can see. How is the mapping done?

Anyway, in the meantime I also got my hack to work :smiley: . Although maybe it works for reasons that are not exactly what I think. Here’s my code:

Cors.hx

package cors;

import cors.CorsOptions;
import cors.CorsOptionsDelegate;
import express.RequestHandler;
import express_serve_static_core.ParamsDictionary;

@:jsRequire("cors") @:jsRequire("express") extern class Corsifier
{
	@:selfCall
	static function call(?options: ts.AnyOf2<CorsOptions, CorsOptionsDelegate>): RequestHandler<ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>;
}

Corsifier.js created in node_modules/cors

export default class Corsifier
{
    corsWrapper(options)
    {
        let cors = require('cors');
        return cors(options); 
    }
}

What’s the second issue? :slight_smile:

Is your goal to make dts2hx be able to generate the cors extern? If not, then perhaps I can publish this extern somewhere until a permanent solution is found.

This should just work with dts2hx, so it’s currently broken in that regard

The class I posted works because of @:jsRequire and @:selfCall.

  • @:jsRequire maps references to the Cors class to require('cors'). So if you did in haxe Cors.someField, it compiles into the equivalent of require('cors').someField
  • @:selfCall maps a class field to a function call on the class – so if you have @:selfCall on a static function in Cors called someFunction, then in haxe Cors.someFunction() is rewritten into Cors(). Putting the two together you get Cors.call() translated to require('cors')(). In practise a haxe generates a reference for us so we’re not calling require('cors') all the time, so it actually generates:
var Cors = require("cors");
Cors();
1 Like

This has been fixed in dts2hx 14.0 :slight_smile: – Cors.hx is now generated as it should be

Let me know if you have any troubles after updating @Kyliathy

1 Like

Beautiful work @haxiomic :smiley:

I’m taking a short break now after some intense days. I’ll stay with my own-crafted Cors.hx. I’m happy to have done that with your help so I’ll keep it on of sentimental reasons :smiley:

Also, in the meantime, I had to deal with writing an extern for a small JS library. So yes, it unfortunately had no d.ts files. I tried generating them using some tools I found online but they couldn’t do much, and then dts2hx could do even less. So I started getting the hang of writing externs and happy for this. Your comments helped me a lot to understand how Haxe interacts with JS code.

1 Like

I am guessing you are already on your way with handwriting externs…

In the past, I have written a little bit about that: hxexterns/howto.md at master · MatthijsKamstra/hxexterns · GitHub

Not sure if it’s helpful (it already 17 months old)

3 Likes

Very useful! Thank you! :slight_smile:

@haxiomic I’m back to finish what I started :slight_smile: That is, to publish a nice helper boilerplate repository for people to use for JS stacks.

So I tried updating dts2hx via npm install -g and I got the new version. Unfortunately, upon running npx dts2hx cors it doesn’t generate Cors.hx in 0.14.6 … Or I’m screwing something up.

You can run it yourself in this repo:

Until I manage to fix this, I added my own Cors.hx file in .haxelib-patched :slight_smile:

Works fine for me, what output are you getting and where are you running that command?

If i make a new directory and run

npm i @types/cors dts2hx
npx dts2hx cors

It generates .haxelib/cors

(I’d recommend using dts2hx as a local npm dependency rather than global so it’s easy for users to reproduce the files in .haxelib if they need)

Hm. Using the commands you’ve given above did produce Cors.hx in an empty directory. But not in MY directory. Even though wiped the previous .haxelib cors, and then I uninstalled and then re-installed the cors JS and its @types definitions. Here’s the output I get:

> Converting module cors
> Saved externs for cors into .haxelib/cors/2,8,8/
> Converting module express
> Saved externs for express into .haxelib/express/4,17,8/
> Converting module express-serve-static-core
> Saved externs for express-serve-static-core into .haxelib/express-serve-static-core/4,17,12/
> Converting module serve-static
> Saved externs for serve-static into .haxelib/serve-static/1,13,5/
> Converting module body-parser
> Saved externs for body-parser into .haxelib/body-parser/1,19,0/
> Converting module qs
> Saved externs for qs into .haxelib/qs/6,9,5/
> Converting module node
> Saved externs for node into .haxelib/node/14,11,1/
> Converting module range-parser
> Saved externs for range-parser into .haxelib/range-parser/1,2,3/
> Converting module mime
> Saved externs for mime into .haxelib/mime/2,0,3/
> Converting module connect
> Saved externs for connect into .haxelib/connect/3,4,33/	

The differences are some patch version increments. Most notably, the empty directory version has node at 14.14.7 whereas mine is at 14.11.1. But I doubt this is the issue?

Both commands were run via npx. Very strange. I suspect there’s something weird going on in my dependencies, since I see that when dts2hx runs, it goes through a ton of cross-dependencies. Perhaps something got stuck in the pipe.

… 5 minutes later …

I just wiped everything, node_modules, .haxelib, ran npm update, installed everything again, ran the command and… no Cors.hx this time either!

What version does it say dts2hx is in the generated readme in .haxelib?

Make sure you clear package-lock.json too before reinstalling everything – that will be locking dts2hx to your older version

You’re right. It’s an older version. Don’t understand why this happened because I thought if I use npx it uses the version I installed using npm install -g. It did so in the empty directory where I ran the commands you suggested.

And now I see in package.json

  "devDependencies": {
    "dts2hx": "^0.13.3"
  }

Even after deleting package-lock, it doesn’t update it on npm update. No clue why.

Anyway, yes, I can confirm the fix :slight_smile:

Yeah, I’ve always found npm’s way of doing things pretty confusing – I often just delete all the node files and start again for things like this :stuck_out_tongue:

npm update only updates the main dependencies. You must add the --dev parameter to also update the development dependencies (i.e. npm update --dev):

As with all commands that install packages, the --dev flag will cause devDependencies to be processed as well.

1 Like