UNPKG

ufiber

Version:

Next-gen webserver for node-js developer

240 lines (238 loc) 5.66 kB
import { kCtxRes } from "../consts.js"; import { HttpStatus } from "../status.js"; import { Request } from "./request.js"; //#region src/http/context.ts /** * ⚡ Unified high-performance Context for uWebSockets.js * * Combines both Request + Response APIs into one streamlined object. * Inspired by Fiber GO, Hono, and Oak — but built for uWS zero-copy speed. * * @example * ```ts * app.get('/users/:id', async (ctx) => { * const id = ctx.param('id'); * const data = await ctx.jsonParse(); * ctx.status(200).json({ id, data }); * }); * ``` */ var Context = class extends Request { /** Whether the request was aborted by the client */ aborted = false; /** Whether response has already been sent */ finished = false; [kCtxRes] = { aborts: [], headers: Object.create(null), headerSent: false }; constructor({ req, res, appName, isSSL, bodyLimit, methods }) { super({ req, res, bodyLimit, isSSL, methods }); this.res.writeHeader("x-powered-by", appName || "uFiber"); this.res.writeHeader("keep-alive", "timeout=10"); res.onAborted(() => { if (this.aborted || this.finished) return; this.aborted = true; this.finished = true; this.destroy(); this[kCtxRes].aborts.forEach((cb) => cb()); this[kCtxRes].aborts = []; }); } /** * Register callback for when client disconnects * * @example * ```ts * ctx.onAbort(() => db.release()); * ``` */ onAbort(fn) { this[kCtxRes].aborts.push(fn); } /** * Store transient data between middlewares * * @example * ```ts * ctx.set('user', { id: 1 }); * ``` */ set(key, value) { this[kCtxRes].vars ??= /* @__PURE__ */ new Map(); this[kCtxRes].vars.set(key, value); return this; } /** * Retrieve stored middleware data * * @example * ```ts * const user = ctx.get<{ id: number }>('user'); * ``` */ get(key) { return this[kCtxRes].vars?.get(key); } /** * Set HTTP status code * Chainable, so you can call: ctx.status(201).json({...}) * * @example * ```ts * ctx.status(201).json({ created: true }); * ctx.status(404).text('Not Found'); * ``` */ status = (code) => { this[kCtxRes].statusCode = code; return this; }; /** * setter response headers with type safety * * @example * ```ts * ctx.setHeader('Content-Type', 'application/json'); * ctx.setHeader('x-custom', 'value', true); // append * ``` */ setHeader = (field, value, append) => { const r = this[kCtxRes]; if (r.headerSent) throw new Error("Cannot set headers after they are sent to the client"); if (value === void 0) { delete r.headers[field]; return; } if (append) { const existing = r.headers[field]; if (existing === void 0) r.headers[field] = value; else if (Array.isArray(existing)) existing.push(value); else r.headers[field] = [existing, value]; return; } r.headers[field] = value; }; writeHeaders() { const r = this[kCtxRes]; for (const header in r.headers) { const value = r.headers[header]; if (Array.isArray(value)) for (const val of value) this.res.writeHeader(header, val); else this.res.writeHeader(header, value); } r.headerSent = true; } writeStatus() { const r = this[kCtxRes]; const statusMessage = HttpStatus[`${r.statusCode || 200}_NAME`] ?? ""; this.res.writeStatus(`${r.statusCode} ${statusMessage}`); } /** * End response with optional body * * @example * ```ts * ctx.end('Hello World'); * ``` */ end(body, cb) { if (this.finished || this.aborted) return; this.res.cork(() => { this.writeHeaders(); this.writeStatus(); if (this.method === "HEAD") { const len = body && typeof body === "string" ? Buffer.byteLength(body) : 0; this.res.endWithoutBody(len); return; } if (body === void 0) { this.res.end(); return; } if (Buffer.isBuffer(body)) { const arrBuf = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength); this.res.end(arrBuf); return; } queueMicrotask(() => { this.finished = true; cb?.(); }); this.res.end(body); }); } /** * Send plain text response * Automatically sets Content-Type to text/plain with UTF-8 * * @example * ```ts * ctx.text('Hello World'); * ctx.status(200).text('Success'); * ``` */ text(body, status) { const r = this[kCtxRes]; if (status !== void 0) r.statusCode = status; if (!r.headers["content-type"]) r.headers["content-type"] = "text/plain; charset=utf-8"; this.end(body); } /** * Send JSON response * Automatically stringifies and sets Content-Type * * @example * ```ts * ctx.json({ users: [...] }); * ctx.json({ error: 'Not Found' }, 404); * ``` */ json(body, status) { const r = this[kCtxRes]; if (status !== void 0) r.statusCode = status; if (!r.headers["content-type"]) r.headers["content-type"] = "application/json; charset=utf-8"; this.end(JSON.stringify(body)); } /** * Send HTML response * Automatically sets Content-Type to text/html * * @example * ```ts * ctx.html('<h1>Welcome</h1>'); * ctx.html('<p>Error</p>', 500); * ``` */ html(body, status) { const r = this[kCtxRes]; if (status !== void 0) r.statusCode = status; if (!r.headers["content-type"]) r.headers["content-type"] = "text/html; charset=utf-8"; this.end(body); } /** * Redirect to another URL * Sends Location header with empty body (browser handles the redirect) * * @example * ```ts * ctx.redirect('/login'); * ctx.redirect('/home', 301); // Permanent redirect * ctx.redirect('https://example.com'); * ``` */ redirect(url, status = 302) { const r = this[kCtxRes]; r.statusCode = status; r.headers["location"] = url; this.end(); } }; //#endregion export { Context };