@intuitionrobotics/thunderstorm
Version:
64 lines • 2.71 kB
JavaScript
import { Logger } from "@intuitionrobotics/ts-common";
import {} from "express";
import {} from "../../../shared/types.js";
const logger = new Logger("api-handler");
/**
* Wrap a plain typed handler fn as an Express `RequestHandler`.
*
* Why a wrapper is needed at all on Express 4: a thrown/rejected async handler
* is NOT forwarded to `app.use((err,…)=>)` — so we `.catch(next)` to route it
* to the app-level `terminalErrorHandler`. We also auto-serialize the return
* value (object→json, "<html…>"→html, else text), matching the legacy
* `ServerApi.call()` behavior so handlers can simply `return`.
*
* A handler may instead write to `res` directly (stream/SSE/redirect): once
* `res.headersSent`, autoSend is a no-op. Rule: return a value XOR write `res`.
*/
export function handler(fn, opts = {}) {
return (req, res, next) => {
Promise.resolve()
.then(() => fn(req, res, next))
.then(value => autoSend(req, res, value, opts))
.catch(next);
};
}
/**
* Mirrors the response auto-detect of the legacy `ServerApi.call()`
* (server-api.ts:170-187) — including the 2-space JSON formatting and the
* case-insensitive "<html" sniff — so migrated endpoints stay byte-identical.
*/
function autoSend(req, res, value, opts) {
if (res.headersSent)
return; // handler wrote the response itself (stream/SSE/redirect/manual res.*)
if (value === undefined || value === null) {
res.status(200).end();
return;
}
if (opts.printResponse !== false)
logger.logVerbose(`${req.method} ${req.originalUrl} -- Response:`, value);
if (typeof value === "object") {
res.status(200).set("Content-Type", "application/json").send(JSON.stringify(value, null, 2));
return;
}
if (typeof value === "string" && value.toLowerCase().startsWith("<html")) {
res.status(200).set("Content-Type", "text/html").send(value);
return;
}
res.status(200).set("Content-Type", "text/plain").send(String(value));
}
/**
* A redirect endpoint as an Express `RequestHandler` — the functional
* replacement for the old `ServerApi_Redirect`. Appends the incoming query
* params to the target URL (same shape the class produced) and redirects.
*
* router.all("/old-path", redirectHandler(301, "/api/v1/new-path"));
*/
export function redirectHandler(responseCode, redirectUrl) {
return (req, res) => {
const q = (req.query ?? {});
const keys = Object.keys(q);
const query = keys.length ? keys.reduce((c, k) => `${c}&${k}=${q[k]}`, "?") : "";
res.redirect(responseCode, `${redirectUrl}${query}`);
};
}
//# sourceMappingURL=handler.js.map