ufiber
Version:
Next-gen webserver for node-js developer
240 lines (238 loc) • 5.95 kB
JavaScript
const require_consts = require('../consts.cjs');
const require_status = require('../status.cjs');
const require_request = require('./request.cjs');
//#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 require_request.Request {
/** Whether the request was aborted by the client */
aborted = false;
/** Whether response has already been sent */
finished = false;
[require_consts.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[require_consts.kCtxRes].aborts.forEach((cb) => cb());
this[require_consts.kCtxRes].aborts = [];
});
}
/**
* Register callback for when client disconnects
*
* @example
* ```ts
* ctx.onAbort(() => db.release());
* ```
*/
onAbort(fn) {
this[require_consts.kCtxRes].aborts.push(fn);
}
/**
* Store transient data between middlewares
*
* @example
* ```ts
* ctx.set('user', { id: 1 });
* ```
*/
set(key, value) {
this[require_consts.kCtxRes].vars ??= /* @__PURE__ */ new Map();
this[require_consts.kCtxRes].vars.set(key, value);
return this;
}
/**
* Retrieve stored middleware data
*
* @example
* ```ts
* const user = ctx.get<{ id: number }>('user');
* ```
*/
get(key) {
return this[require_consts.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[require_consts.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[require_consts.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[require_consts.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[require_consts.kCtxRes];
const statusMessage = require_status.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[require_consts.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[require_consts.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[require_consts.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[require_consts.kCtxRes];
r.statusCode = status;
r.headers["location"] = url;
this.end();
}
};
//#endregion
exports.Context = Context;