UNPKG

@intuitionrobotics/thunderstorm

Version:
64 lines 2.71 kB
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