@sigiljs/sigil
Version:
TypeScript-first Node.js HTTP framework offering schema-driven routing, modifier-based middleware, plugin extensibility, and flexible response templating
162 lines (161 loc) • 6 kB
JavaScript
import w from "node:fs";
import h from "node:path";
import R from "../requests/process-request-content.mjs";
import { NotFound as f } from "../responses/exceptions/exceptions-basic.mjs";
import p from "../responses/middleware-modification-request.mjs";
import u from "../responses/file-response.mjs";
import H from "../responses/raw-response.mjs";
import g from "../responses/redirect.mjs";
import $ from "../responses/sigil-response.mjs";
import "../utils/make-log.mjs";
import { jsonStringify as l } from "../utils/safe-json.mjs";
import y from "./misc/sigil-responses-list.mjs";
import M from "./sigil-plugin-system.mjs";
import m from "../responses/exceptions/exception.mjs";
class L extends M {
/**
* Binds the incoming message handler implementation.
*/
constructor(t) {
super(t), this.incomingMessageHandler = this.incomingMessageHandler.bind(this), this.$incomingMessageHandlerImpl = this.$incomingMessageHandlerImpl.bind(this), this.$incomingMessageHandlerRef = this.$incomingMessageHandlerImpl;
}
/**
* Entry point for handling HTTP requests.
* Delegates to the internal handler reference.
*
* @param req incoming HTTP message.
* @param res HTTP server response object.
*/
incomingMessageHandler(t, r) {
return this.$incomingMessageHandlerRef?.(t, r);
}
/**
* Sends a formatted response back to the client.
* Applies the response template, triggers plugin hooks,
* logs the request, and writes the HTTP response.
*
* @param request inbound HTTP message for context.
* @param processedRequest processed request
* @param response sigilResponse or Exception to send.
* @param res HTTP server response object.
* @param modification Modified options
* @param at timestamp when request processing started.
*/
async $sendResponse(t, r, e, n, a, c) {
a.code && (e.code = a.code);
for (const s of this.$plugins.values()) {
const i = await s.onBeforeResponseSent(r, e);
if (i) {
n.writeHead(i.code, Object.assign(i.headers.link, a.headers)).end(Buffer.isBuffer(i.content) || typeof i.content == "string" ? i.content : l(i.content, { throw: !0 }));
return;
}
}
const o = this.$responseTemplate(e), d = performance.now() - c;
if (this.logger({
message: `${t.method} ${t.url} - ${e.code} - ${Math.round(d * 1e3) / 1e3}ms`,
level: e.code > 499 ? "error" : "info",
module: "requests",
json: { method: t.method, path: t.url, code: e.code, time: d }
}), o.code > 399 && this.$options.codeOnlyResponse?.includes(o.code)) {
n.writeHead(o.code, a.headers).end();
return;
}
if (e instanceof H) {
const s = typeof e.content == "string" || Buffer.isBuffer(e.content) ? e.content : l(e.content, { throw: !0 });
n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end(s);
return;
}
if (e instanceof u) {
n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end(e.content);
return;
}
if (e instanceof m) {
n.writeHead(e.code, Object.assign(o.headers, a.headers)).end(o.content), e.code > 499 && this.logger({
level: "error",
module: "handler",
message: (s) => ` ${s("-> ")}${s(`${e.name}: ${e.message}`)}`,
json: { name: e.name, message: e.message }
});
return;
}
if (e instanceof g) {
n.writeHead(e.code, Object.assign(e.headers.link, a.headers)).end();
return;
}
n.writeHead(o.code, Object.assign(o.headers, a.headers)).end(
typeof o.content == "string" ? o.content : l(o.content)
);
}
/**
* Executes a registered route handler safely, converting errors to Exception.
*
* @param handler pathfinder handler function.
* @param req client request object.
* @returns handler's response or an Exception on error.
*/
async $executeHandler(t, r) {
try {
return await t(r);
} catch (e) {
return m.fromError(e);
}
}
/**
* Formats a raw handler response into a SigilResponse or Exception.
* Handles file reading, redirections, and exception wrapping.
*
* @param response raw handler response.
* @returns SigilResponse or Exception ready for sending.
*/
async $formatResponse(t) {
if (t instanceof Error) return m.fromError(t);
if (t instanceof g) return t;
if (t instanceof u)
try {
const r = h.isAbsolute(t.content) ? t.content : h.resolve(t.content), e = t.headers.get("content-encoding");
return t.content = await w.promises.readFile(r, e ?? "utf-8"), t;
} catch {
return new f(`File at path ${t.content} not found`);
}
return t instanceof $ ? t : new $(t, 200);
}
/**
* Internal implementation for handling incoming HTTP messages.
* Parses the request, invokes middleware, routes lookup, and
* delegates to handler execution and response sending.
*
* @param req incoming HTTP message.
* @param res HTTP server response object.
*/
async $incomingMessageHandlerImpl(t, r) {
const e = performance.now(), n = await R(t), a = new y();
if (!n) return this.$sendResponse(t, n, new f(), r, {}, e);
for (const d of this.$plugins.values())
d.onRequestReceived(n);
const c = {
headers: {},
code: void 0
};
for (const d of this.$middlewares.values()) {
const s = await d(n, a, c);
if (s !== void 0) {
const i = await this.$formatResponse(s);
if (i instanceof p)
c.headers = { ...c.headers, ...i.headers.link }, c.code = i.code;
else return this.$sendResponse(t, n, i, r, {}, e);
}
}
const o = this.$root.__$pathfinder.lookup(n.method, n.path);
if (o) {
const d = await this.$executeHandler(
o.handler,
n.createClientRequest(o.params)
), s = await this.$formatResponse(d);
return this.$sendResponse(t, n, s, r, c, e);
}
this.$sendResponse(t, n, new f(), r, c, e);
}
}
export {
L as default
};