Typescript Support - Deno
Is your feature request related to a problem? Please describe.
I would like to be able to write functions in Typescript, especially using Deno. I find working with NodeJS after using Deno is difficult, because Deno is just so easy to work with. Maybe NodeJS can be convinced to run Typescript, but that wouldn't be as nice in terms of DX and Standard Libraries.
Describe the solution you'd like
I want a new environment called Deno which runs Typescript natively. Functions should be written in a syntax which mostly borrows from https://docs.deno.com/deploy/tutorials/simple-api/
Describe alternatives you've considered
Building a binary with Deno is an option, but requires a build pipeline.
This link works: https://docs.deno.com/deploy/tutorials/simple-api/
I agree, I think this would be sweet.
I'm imagining a builder that looks very similar to the nodejs builder, with, e.g., deno install instead of npm install - or skipping the install step unless there is a package.json?
I tried with create new env with Bun (based on nodejs env) and it works like a charm
I modified and implemented a typescript environment that met my needs and worked well.
First of all, the project structure of the environment is as follows:
node-ts
├── package.json
└── server.ts
The content of server.ts is as follows:
"use strict";
import path from "path";
import process from "process";
import express, { Request, Response } from "express";
import request from "request";
import morgan from "morgan";
import ws from "ws";
import minimist from "minimist";
import { createServer } from "http";
const bodyParserLimit = process.env.BODY_PARSER_LIMIT || "1mb";
const argv = minimist(process.argv.slice(1));
const app = express();
const server = createServer();
if (!argv.port) argv.port = 8888;
let timeout = process.env.TIMEOUT ?? "60000";
let userFunction: CallableFunction;
process.on("uncaughtException", (err) => {
console.error(`Caught exception: ${err}`);
});
function loadFunction(modulepath: string, funcname: string = "default") {
try {
let startTime = process.hrtime();
let userFunction = require(modulepath)[funcname];
let elapsed = process.hrtime(startTime);
console.log(
`user code loaded in ${elapsed[0]}sec ${elapsed[1] / 1000000}ms`
);
return userFunction;
} catch (e) {
console.error(`user code load error: ${e}`);
return e;
}
}
const withEnsureGeneric = (func) => {
return (req, res) => {
if (userFunction) {
res.status(400).send("Not a generic container");
return;
}
func(req, res);
};
};
const isFunction = (func) => typeof func === "function";
app.use(morgan("combined"));
app.use(
express.urlencoded({
extended: false,
limit: bodyParserLimit,
})
);
app.use(
express.json({
limit: bodyParserLimit,
})
);
app.use(
express.raw({
limit: bodyParserLimit,
})
);
app.use(
express.text({
type: "text/*",
limit: bodyParserLimit,
})
);
type SpecializeRequest = {
functionName: string;
filepath: string;
FunctionMetadata: object;
envVersion: number;
};
app.post(
"/v2/specialize",
withEnsureGeneric((req, res) => {
req.body as SpecializeRequest;
const entrypoint = (req.body.functionName ?? "").split(".");
const modulepath = path.join(req.body.filepath, entrypoint[0] || "");
const result = loadFunction(modulepath, entrypoint[1]);
if (isFunction(result)) {
userFunction = result;
res.status(202).send();
} else {
res.status(500).send(JSON.stringify(result));
}
})
);
app.all("*", async (req, res) => {
try {
console.log("req.body: ", req.body);
if (!userFunction)
res.status(500).send("Generic container: no requests supported");
else {
let result = await Promise.resolve(userFunction(req.body));
if (result instanceof Promise) result = await result;
res.status(200).send(JSON.stringify(result));
}
} catch (error) {
console.error("Error processing request:", error);
return res.status(500).send(error.message);
}
});
server.on("request", app);
const wsStartEvent = {
url: "http://127.0.0.1:8000/wsevent/start",
};
const wsInactiveEvent = {
url: "http://127.0.0.1:8000/wsevent/end",
};
class MyWebSocket extends ws.WebSocket {
isAlive?: boolean;
}
let wss = new ws.Server({
server: server,
WebSocket: MyWebSocket,
});
const noop = () => {};
var warm = false;
let interval = setInterval(() => {
if (warm) {
if (wss.clients.size > 0) {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
} else {
request(wsInactiveEvent, (err, res) => {
if (err || res.statusCode != 200) {
if (err) {
console.log(err);
} else {
console.log("Unexpected response");
}
return;
}
});
return;
}
}
}, Number(timeout));
wss.on("connection", (ws) => {
if (warm == false) {
warm = true;
request(wsStartEvent, (err, res) => {
if (err || res.statusCode != 200) {
if (err) {
console.log(err);
} else {
console.log("Unexpected response");
}
ws.send("Error");
return;
}
});
}
ws.isAlive = true;
ws.on("pong", function () {
this["isAlive"] = true;
});
wss.on("close", () => {
clearInterval(interval);
});
try {
userFunction(ws, wss.clients);
} catch (err) {
console.log(`Function error: ${err}`);
ws.close();
}
});
server.listen(argv.port, () => {});
This file is almost the same as server.js, except that it is refactored using ts.
The key operation is to install tsx and add the following to package.json:
"scripts": {
"start": "tsx server.ts"
}
This causes the runtime container to execute tsx server.ts
Node 23 can run TypeScript files without any extra configuration.
https://www.totaltypescript.com/typescript-is-coming-to-node-23
My team github.com/polyseam/cndi has built a FaaS runtime using github.com/supabase/edge-runtime without using Fission. If the goal is Deno on Fission specifically, this may be useful as prior art.