@wooksjs/event-http
Version:
@wooksjs/event-http
2,140 lines • 73.8 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) {
__defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _wooksjs_event_core = require("@wooksjs/event-core");
let buffer = require("buffer");
let node_stream = require("node:stream");
let node_util = require("node:util");
let node_zlib = require("node:zlib");
let url = require("url");
let http = require("http");
http = __toESM(http);
let stream = require("stream");
let wooks = require("wooks");
let net = require("net");
//#region packages/event-http/src/http-kind.ts
/** Event kind definition for HTTP requests. Provides typed context slots for `req`, `response`, and `requestLimits`. */
const httpKind = (0, _wooksjs_event_core.defineEventKind)("http", {
req: (0, _wooksjs_event_core.slot)(),
response: (0, _wooksjs_event_core.slot)(),
requestLimits: (0, _wooksjs_event_core.slot)()
});
//#endregion
//#region packages/event-http/src/utils/helpers.ts
function escapeRegex(s) {
return s.replace(/[$()*+\-./?[\\\]^{|}]/gu, "\\$&");
}
function safeDecode(f, v) {
try {
return f(v);
} catch {
return v;
}
}
function safeDecodeURIComponent(uri) {
if (!uri.includes("%")) return uri;
return safeDecode(decodeURIComponent, uri);
}
//#endregion
//#region packages/event-http/src/composables/cookies.ts
const cookieRegExpCache = /* @__PURE__ */ new Map();
function getCookieRegExp(name) {
let re = cookieRegExpCache.get(name);
if (!re) {
re = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, "i");
cookieRegExpCache.set(name, re);
}
return re;
}
const parseCookieValue = (0, _wooksjs_event_core.cachedBy)((name, ctx) => {
const cookie = ctx.get(httpKind.keys.req).headers.cookie;
if (cookie) {
const result = getCookieRegExp(name).exec(cookie);
return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
}
return null;
});
/**
* Provides access to parsed request cookies.
* @example
* ```ts
* const { getCookie, raw } = useCookies()
* const sessionId = getCookie('session_id')
* ```
*/
const useCookies = (0, _wooksjs_event_core.defineWook)((ctx) => ({
raw: ctx.get(httpKind.keys.req).headers.cookie,
getCookie: (name) => parseCookieValue(name, ctx)
}));
//#endregion
//#region packages/event-http/src/composables/header-accept.ts
const ACCEPT_TYPE_MAP = {
json: "application/json",
html: "text/html",
xml: "application/xml",
text: "text/plain"
};
const acceptsMime = (0, _wooksjs_event_core.cachedBy)((type, ctx) => {
const accept = ctx.get(httpKind.keys.req).headers.accept;
const mime = ACCEPT_TYPE_MAP[type] || type;
return !!(accept && (accept === "*/*" || accept.includes(mime)));
});
/** Provides helpers to check the request's Accept header for supported MIME types. */
const useAccept = (0, _wooksjs_event_core.defineWook)((ctx) => {
return {
accept: ctx.get(httpKind.keys.req).headers.accept,
has: (type) => acceptsMime(type, ctx)
};
});
//#endregion
//#region packages/event-http/src/composables/header-authorization.ts
const authTypeSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
if (authorization) {
const space = authorization.indexOf(" ");
return authorization.slice(0, space);
}
return null;
});
const authCredentialsSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
if (authorization) {
const space = authorization.indexOf(" ");
return authorization.slice(space + 1);
}
return null;
});
const basicCredentialsSlot = (0, _wooksjs_event_core.cached)((ctx) => {
if (ctx.get(httpKind.keys.req).headers.authorization) {
if (ctx.get(authTypeSlot)?.toLocaleLowerCase() === "basic") {
const [username, password] = buffer.Buffer.from(ctx.get(authCredentialsSlot) || "", "base64").toString("ascii").split(":");
return {
username,
password
};
}
}
return null;
});
const authIsSlot = (0, _wooksjs_event_core.cachedBy)((type, ctx) => {
return ctx.get(authTypeSlot)?.toLowerCase() === type.toLowerCase();
});
/**
* Provides parsed access to the Authorization header (type, credentials, Basic decoding).
* @example
* ```ts
* const { is, credentials, basicCredentials } = useAuthorization()
* if (is('bearer')) { const token = credentials() }
* ```
*/
const useAuthorization = (0, _wooksjs_event_core.defineWook)((ctx) => {
return {
authorization: ctx.get(httpKind.keys.req).headers.authorization,
type: () => ctx.get(authTypeSlot),
credentials: () => ctx.get(authCredentialsSlot),
is: (type) => authIsSlot(type, ctx),
basicCredentials: () => ctx.get(basicCredentialsSlot)
};
});
//#endregion
//#region packages/event-http/src/compressor/body-compressor.ts
const compressors = { identity: {
compress: (v) => v,
uncompress: (v) => v,
stream: {
compress: (data) => data,
uncompress: (data) => data
}
} };
function encodingSupportsStream(encodings) {
return encodings.every((enc) => compressors[enc]?.stream);
}
async function uncompressBody(encodings, compressed) {
let buf = compressed;
for (const enc of encodings.slice().toReversed()) {
const c = compressors[enc];
if (!c) throw new Error(`Unsupported compression type "${enc}".`);
buf = await c.uncompress(buf);
}
return buf;
}
async function uncompressBodyStream(encodings, src) {
if (!encodingSupportsStream(encodings)) throw new Error("Some encodings lack a streaming decompressor");
let out = src;
for (const enc of Array.from(encodings).toReversed()) out = await compressors[enc].stream.uncompress(out);
return out;
}
//#endregion
//#region packages/event-http/src/compressor/zlib-compressors.ts
const pipeline = node_stream.pipeline;
function iterableToReadable(src) {
return node_stream.Readable.from(src, { objectMode: false });
}
function pump(src, transform) {
pipeline(iterableToReadable(src), transform, (err) => {
if (err) transform.destroy(err);
});
return transform;
}
function addStreamCodec(name, createDeflater, createInflater) {
const c = compressors[name] ?? (compressors[name] = {
compress: (v) => v,
uncompress: (v) => v
});
c.stream = {
compress: async (src) => pump(src, createDeflater()),
uncompress: async (src) => pump(src, createInflater())
};
}
addStreamCodec("gzip", node_zlib.createGzip, node_zlib.createGunzip);
addStreamCodec("deflate", node_zlib.createDeflate, node_zlib.createInflate);
addStreamCodec("br", node_zlib.createBrotliCompress, node_zlib.createBrotliDecompress);
let zp;
async function zlib() {
if (!zp) {
const { gzip, gunzip, deflate, inflate, brotliCompress, brotliDecompress } = await import("node:zlib");
zp = {
gzip: (0, node_util.promisify)(gzip),
gunzip: (0, node_util.promisify)(gunzip),
deflate: (0, node_util.promisify)(deflate),
inflate: (0, node_util.promisify)(inflate),
brotliCompress: (0, node_util.promisify)(brotliCompress),
brotliDecompress: (0, node_util.promisify)(brotliDecompress)
};
}
return zp;
}
compressors.gzip.compress = async (b) => (await zlib()).gzip(b);
compressors.gzip.uncompress = async (b) => (await zlib()).gunzip(b);
compressors.deflate.compress = async (b) => (await zlib()).deflate(b);
compressors.deflate.uncompress = async (b) => (await zlib()).inflate(b);
compressors.br.compress = async (b) => (await zlib()).brotliCompress(b);
compressors.br.uncompress = async (b) => (await zlib()).brotliDecompress(b);
//#endregion
//#region packages/event-http/src/utils/status-codes.ts
/** Maps numeric HTTP status codes to their human-readable descriptions. */
const httpStatusCodes = {
100: "Continue",
101: "Switching protocols",
102: "Processing",
103: "Early Hints",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found (Previously \"Moved Temporarily\")",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Switch Proxy",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Payload Too Large",
414: "URI Too Long",
415: "Unsupported Media Type",
416: "Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a Teapot",
421: "Misdirected Request",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
425: "Too Early",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Request Header Fields Too Large",
451: "Unavailable For Legal Reasons",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
510: "Not Extended",
511: "Network Authentication Required"
};
/** Enum of all standard HTTP status codes (100–511). */
let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode) {
EHttpStatusCode[EHttpStatusCode["Continue"] = 100] = "Continue";
EHttpStatusCode[EHttpStatusCode["SwitchingProtocols"] = 101] = "SwitchingProtocols";
EHttpStatusCode[EHttpStatusCode["Processing"] = 102] = "Processing";
EHttpStatusCode[EHttpStatusCode["EarlyHints"] = 103] = "EarlyHints";
EHttpStatusCode[EHttpStatusCode["OK"] = 200] = "OK";
EHttpStatusCode[EHttpStatusCode["Created"] = 201] = "Created";
EHttpStatusCode[EHttpStatusCode["Accepted"] = 202] = "Accepted";
EHttpStatusCode[EHttpStatusCode["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
EHttpStatusCode[EHttpStatusCode["NoContent"] = 204] = "NoContent";
EHttpStatusCode[EHttpStatusCode["ResetContent"] = 205] = "ResetContent";
EHttpStatusCode[EHttpStatusCode["PartialContent"] = 206] = "PartialContent";
EHttpStatusCode[EHttpStatusCode["MultiStatus"] = 207] = "MultiStatus";
EHttpStatusCode[EHttpStatusCode["AlreadyReported"] = 208] = "AlreadyReported";
EHttpStatusCode[EHttpStatusCode["IMUsed"] = 226] = "IMUsed";
EHttpStatusCode[EHttpStatusCode["MultipleChoices"] = 300] = "MultipleChoices";
EHttpStatusCode[EHttpStatusCode["MovedPermanently"] = 301] = "MovedPermanently";
EHttpStatusCode[EHttpStatusCode["Found"] = 302] = "Found";
EHttpStatusCode[EHttpStatusCode["SeeOther"] = 303] = "SeeOther";
EHttpStatusCode[EHttpStatusCode["NotModified"] = 304] = "NotModified";
EHttpStatusCode[EHttpStatusCode["UseProxy"] = 305] = "UseProxy";
EHttpStatusCode[EHttpStatusCode["SwitchProxy"] = 306] = "SwitchProxy";
EHttpStatusCode[EHttpStatusCode["TemporaryRedirect"] = 307] = "TemporaryRedirect";
EHttpStatusCode[EHttpStatusCode["PermanentRedirect"] = 308] = "PermanentRedirect";
EHttpStatusCode[EHttpStatusCode["BadRequest"] = 400] = "BadRequest";
EHttpStatusCode[EHttpStatusCode["Unauthorized"] = 401] = "Unauthorized";
EHttpStatusCode[EHttpStatusCode["PaymentRequired"] = 402] = "PaymentRequired";
EHttpStatusCode[EHttpStatusCode["Forbidden"] = 403] = "Forbidden";
EHttpStatusCode[EHttpStatusCode["NotFound"] = 404] = "NotFound";
EHttpStatusCode[EHttpStatusCode["MethodNotAllowed"] = 405] = "MethodNotAllowed";
EHttpStatusCode[EHttpStatusCode["NotAcceptable"] = 406] = "NotAcceptable";
EHttpStatusCode[EHttpStatusCode["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
EHttpStatusCode[EHttpStatusCode["RequestTimeout"] = 408] = "RequestTimeout";
EHttpStatusCode[EHttpStatusCode["Conflict"] = 409] = "Conflict";
EHttpStatusCode[EHttpStatusCode["Gone"] = 410] = "Gone";
EHttpStatusCode[EHttpStatusCode["LengthRequired"] = 411] = "LengthRequired";
EHttpStatusCode[EHttpStatusCode["PreconditionFailed"] = 412] = "PreconditionFailed";
EHttpStatusCode[EHttpStatusCode["PayloadTooLarge"] = 413] = "PayloadTooLarge";
EHttpStatusCode[EHttpStatusCode["URITooLong"] = 414] = "URITooLong";
EHttpStatusCode[EHttpStatusCode["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
EHttpStatusCode[EHttpStatusCode["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
EHttpStatusCode[EHttpStatusCode["ExpectationFailed"] = 417] = "ExpectationFailed";
EHttpStatusCode[EHttpStatusCode["ImATeapot"] = 418] = "ImATeapot";
EHttpStatusCode[EHttpStatusCode["MisdirectedRequest"] = 421] = "MisdirectedRequest";
EHttpStatusCode[EHttpStatusCode["UnprocessableEntity"] = 422] = "UnprocessableEntity";
EHttpStatusCode[EHttpStatusCode["Locked"] = 423] = "Locked";
EHttpStatusCode[EHttpStatusCode["FailedDependency"] = 424] = "FailedDependency";
EHttpStatusCode[EHttpStatusCode["TooEarly"] = 425] = "TooEarly";
EHttpStatusCode[EHttpStatusCode["UpgradeRequired"] = 426] = "UpgradeRequired";
EHttpStatusCode[EHttpStatusCode["PreconditionRequired"] = 428] = "PreconditionRequired";
EHttpStatusCode[EHttpStatusCode["TooManyRequests"] = 429] = "TooManyRequests";
EHttpStatusCode[EHttpStatusCode["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
EHttpStatusCode[EHttpStatusCode["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
EHttpStatusCode[EHttpStatusCode["InternalServerError"] = 500] = "InternalServerError";
EHttpStatusCode[EHttpStatusCode["NotImplemented"] = 501] = "NotImplemented";
EHttpStatusCode[EHttpStatusCode["BadGateway"] = 502] = "BadGateway";
EHttpStatusCode[EHttpStatusCode["ServiceUnavailable"] = 503] = "ServiceUnavailable";
EHttpStatusCode[EHttpStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
EHttpStatusCode[EHttpStatusCode["HTTPVersionNotSupported"] = 505] = "HTTPVersionNotSupported";
EHttpStatusCode[EHttpStatusCode["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
EHttpStatusCode[EHttpStatusCode["InsufficientStorage"] = 507] = "InsufficientStorage";
EHttpStatusCode[EHttpStatusCode["LoopDetected"] = 508] = "LoopDetected";
EHttpStatusCode[EHttpStatusCode["NotExtended"] = 510] = "NotExtended";
EHttpStatusCode[EHttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
return EHttpStatusCode;
}({});
//#endregion
//#region packages/event-http/src/errors/http-error.ts
/** Represents an HTTP error with a status code and optional structured body. */
var HttpError = class extends Error {
constructor(code = 500, _body = "") {
const prev = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
super(typeof _body === "string" ? _body : _body.message);
this.code = code;
this._body = _body;
this.name = "HttpError";
Error.stackTraceLimit = prev;
}
get body() {
return typeof this._body === "string" ? {
statusCode: this.code,
message: this.message,
error: httpStatusCodes[this.code]
} : {
...this._body,
statusCode: this.code,
message: this.message,
error: httpStatusCodes[this.code]
};
}
};
//#endregion
//#region packages/event-http/src/composables/request.ts
const xForwardedFor = "x-forwarded-for";
/** Default safety limits for request body reading (size, ratio, timeout). */
const DEFAULT_LIMITS = {
maxCompressed: 1 * 1024 * 1024,
maxInflated: 10 * 1024 * 1024,
maxRatio: 100,
readTimeoutMs: 1e4
};
const contentEncodingsSlot = (0, _wooksjs_event_core.cached)((ctx) => {
return (ctx.get(httpKind.keys.req).headers["content-encoding"] || "").split(",").map((p) => p.trim()).filter((p) => !!p);
});
const isCompressedSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const parts = ctx.get(contentEncodingsSlot);
for (const p of parts) if ([
"deflate",
"gzip",
"br"
].includes(p)) return true;
return false;
});
/** @internal Exported for test pre-seeding via `ctx.set(rawBodySlot, ...)`. */
const rawBodySlot = (0, _wooksjs_event_core.cached)(async (ctx) => {
const req = ctx.get(httpKind.keys.req);
const encs = ctx.get(contentEncodingsSlot);
const isZip = ctx.get(isCompressedSlot);
const streamable = isZip && encodingSupportsStream(encs);
const limits = ctx.get(httpKind.keys.requestLimits);
const maxCompressed = limits?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
const maxInflated = limits?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
const maxRatio = limits?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
const timeoutMs = limits?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
const cl = Number(req.headers["content-length"] ?? 0);
const upfrontLimit = isZip ? maxCompressed : maxInflated;
if (cl && cl > upfrontLimit) throw new HttpError(413, "Payload Too Large");
for (const enc of encs) if (!compressors[enc]) throw new HttpError(415, `Unsupported Content-Encoding "${enc}"`);
let timer = null;
function resetTimer() {
if (timeoutMs === 0) return;
clearTimer();
timer = setTimeout(() => {
clearTimer();
req.destroy();
}, timeoutMs);
}
function clearTimer() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
let rawBytes = 0;
async function* limitedCompressed() {
resetTimer();
try {
for await (const chunk of req) {
rawBytes += chunk.length;
if (rawBytes > upfrontLimit) {
req.destroy();
throw new HttpError(413, "Payload Too Large");
}
resetTimer();
yield chunk;
}
} finally {
clearTimer();
}
}
let stream = limitedCompressed();
if (streamable) stream = await uncompressBodyStream(encs, stream);
const chunks = [];
let inflatedBytes = 0;
try {
for await (const chunk of stream) {
inflatedBytes += chunk.length;
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
chunks.push(chunk);
}
} catch (error) {
if (error instanceof HttpError) throw error;
throw new HttpError(408, "Request body timeout");
}
let body = buffer.Buffer.concat(chunks);
if (!streamable && isZip) {
body = await uncompressBody(encs, body);
inflatedBytes = body.byteLength;
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
}
if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
return body;
});
const forwardedIpSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const req = ctx.get(httpKind.keys.req);
if (typeof req.headers[xForwardedFor] === "string" && req.headers[xForwardedFor]) return req.headers[xForwardedFor].split(",").shift()?.trim();
return "";
});
const remoteIpSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const req = ctx.get(httpKind.keys.req);
return req.socket.remoteAddress || req.connection.remoteAddress || "";
});
const ipListSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const req = ctx.get(httpKind.keys.req);
return {
remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || "",
forwarded: (req.headers[xForwardedFor] || "").split(",").map((s) => s.trim())
};
});
/**
* Provides access to the incoming HTTP request (method, url, headers, body, IP).
* @example
* ```ts
* const { method, url, raw, rawBody, getIp } = useRequest()
* const body = await rawBody()
* ```
*/
const useRequest = (0, _wooksjs_event_core.defineWook)((ctx) => {
const req = ctx.get(httpKind.keys.req);
const limits = () => ctx.get(httpKind.keys.requestLimits);
const setLimit = (limitKey, value) => {
let obj = limits();
if (!obj?.perRequest) {
obj = {
...obj,
perRequest: true
};
ctx.set(httpKind.keys.requestLimits, obj);
}
obj[limitKey] = value;
};
const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
const setMaxInflated = (limit) => setLimit("maxInflated", limit);
const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
const setMaxRatio = (limit) => setLimit("maxRatio", limit);
const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
function getIp(options) {
if (options?.trustProxy) return ctx.get(forwardedIpSlot) || ctx.get(remoteIpSlot);
return ctx.get(remoteIpSlot);
}
return {
raw: req,
url: req.url,
method: req.method,
headers: req.headers,
rawBody: () => ctx.get(rawBodySlot),
reqId: (0, _wooksjs_event_core.useEventId)(ctx).getId,
getIp,
getIpList: () => ctx.get(ipListSlot),
isCompressed: () => ctx.get(isCompressedSlot),
getMaxCompressed,
setMaxCompressed,
getReadTimeoutMs,
setReadTimeoutMs,
getMaxInflated,
setMaxInflated,
getMaxRatio,
setMaxRatio
};
});
//#endregion
//#region packages/event-http/src/composables/headers.ts
/**
* Returns the incoming request headers.
* @example
* ```ts
* const { host, authorization } = useHeaders()
* ```
*/
function useHeaders(ctx) {
return useRequest(ctx).headers;
}
//#endregion
//#region packages/event-http/src/composables/response.ts
/**
* Returns the HttpResponse instance for the current request.
* All response operations (status, headers, cookies, cache control, sending)
* are methods on the returned object.
*
* @example
* ```ts
* const response = useResponse()
* response.status = 200
* response.setHeader('x-custom', 'value')
* response.setCookie('session', 'abc', { httpOnly: true })
* ```
*/
function useResponse(ctx) {
return (ctx ?? (0, _wooksjs_event_core.current)()).get(httpKind.keys.response);
}
//#endregion
//#region packages/event-http/src/utils/url-search-params.ts
const ILLEGAL_KEYS = new Set([
"__proto__",
"constructor",
"prototype"
]);
/**
* Extended `URLSearchParams` with safe JSON conversion.
*
* Rejects prototype-pollution keys (`__proto__`, `constructor`, `prototype`) and duplicate non-array keys.
* Array parameters are detected by a trailing `[]` in the key name (e.g. `tags[]=a&tags[]=b`).
*/
var WooksURLSearchParams = class extends url.URLSearchParams {
/** Converts query parameters to a plain object. Array params (keys ending with `[]`) become `string[]`. */
toJson() {
const json = Object.create(null);
for (const [key, value] of this.entries()) if (isArrayParam(key)) (json[key] = json[key] || []).push(value);
else {
if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
json[key] = value;
}
return json;
}
};
function isArrayParam(name) {
return name.endsWith("[]");
}
//#endregion
//#region packages/event-http/src/composables/search-params.ts
const rawSearchParamsSlot = (0, _wooksjs_event_core.cached)((ctx) => {
const url = ctx.get(httpKind.keys.req).url || "";
const i = url.indexOf("?");
return i >= 0 ? url.slice(i) : "";
});
const urlSearchParamsSlot = (0, _wooksjs_event_core.cached)((ctx) => new WooksURLSearchParams(ctx.get(rawSearchParamsSlot)));
/**
* Provides access to URL search (query) parameters from the request.
* @example
* ```ts
* const { params, toJson } = useUrlParams()
* const page = params().get('page')
* ```
*/
const useUrlParams = (0, _wooksjs_event_core.defineWook)((ctx) => ({
raw: () => ctx.get(rawSearchParamsSlot),
params: () => ctx.get(urlSearchParamsSlot),
toJson: () => ctx.get(urlSearchParamsSlot).toJson()
}));
//#endregion
//#region packages/event-http/src/event-http.ts
/** Creates an HTTP event context and runs `fn` inside it. */
function createHttpContext(options, seeds, fn) {
return (0, _wooksjs_event_core.createEventContext)(options, httpKind, seeds, fn);
}
/** Returns the current HTTP event context. */
function useHttpContext(ctx) {
return ctx ?? (0, _wooksjs_event_core.current)();
}
//#endregion
//#region packages/event-http/src/utils/time.ts
function convertTime(time, unit = "ms") {
if (typeof time === "number") return time / units[unit];
const rg = /(\d+)(\w+)/gu;
let t = 0;
let r;
while (r = rg.exec(time)) t += Number(r[1]) * (units[r[2]] || 0);
return t / units[unit];
}
const units = {
ms: 1,
s: 1e3,
m: 1e3 * 60,
h: 1e3 * 60 * 60,
d: 1e3 * 60 * 60 * 24,
w: 1e3 * 60 * 60 * 24 * 7,
M: 1e3 * 60 * 60 * 24 * 30,
Y: 1e3 * 60 * 60 * 24 * 365
};
//#endregion
//#region packages/event-http/src/utils/cache-control.ts
/** Renders a `TCacheControl` object into a `Cache-Control` header string. */
function renderCacheControl(data) {
let attrs = "";
for (const [a, v] of Object.entries(data)) {
if (v === void 0) continue;
const func = cacheControlFunc[a];
if (typeof func === "function") {
const val = func(v);
if (val) attrs += attrs ? `, ${val}` : val;
} else throw new TypeError(`Unknown Cache-Control attribute ${a}`);
}
return attrs;
}
const cacheControlFunc = {
mustRevalidate: (v) => v ? "must-revalidate" : "",
noCache: (v) => v ? typeof v === "string" ? `no-cache="${v}"` : "no-cache" : "",
noStore: (v) => v ? "no-store" : "",
noTransform: (v) => v ? "no-transform" : "",
public: (v) => v ? "public" : "",
private: (v) => v ? typeof v === "string" ? `private="${v}"` : "private" : "",
proxyRevalidate: (v) => v ? "proxy-revalidate" : "",
maxAge: (v) => `max-age=${convertTime(v, "s").toString()}`,
sMaxage: (v) => `s-maxage=${convertTime(v, "s").toString()}`
};
//#endregion
//#region packages/event-http/src/utils/set-cookie.ts
const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
function sanitizeCookieAttrValue(v) {
return v.replace(/[;\r\n]/g, "");
}
function renderCookie(key, data) {
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
let attrs = "";
for (const [a, v] of Object.entries(data.attrs)) {
const func = cookieAttrFunc[a];
if (typeof func === "function") {
const val = func(v);
attrs += val ? `; ${val}` : "";
} else throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
}
return `${key}=${encodeURIComponent(data.value)}${attrs}`;
}
const cookieAttrFunc = {
expires: (v) => v === void 0 ? "" : `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
maxAge: (v) => v === void 0 ? "" : `Max-Age=${convertTime(v, "s").toString()}`,
domain: (v) => v === void 0 ? "" : `Domain=${sanitizeCookieAttrValue(String(v))}`,
path: (v) => v === void 0 ? "" : `Path=${sanitizeCookieAttrValue(String(v))}`,
secure: (v) => v ? "Secure" : "",
httpOnly: (v) => v ? "HttpOnly" : "",
sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
};
//#endregion
//#region packages/event-http/src/response/http-response.ts
const hasFetchResponse = typeof globalThis.Response === "function";
const defaultStatus = {
GET: EHttpStatusCode.OK,
POST: EHttpStatusCode.Created,
PUT: EHttpStatusCode.Created,
PATCH: EHttpStatusCode.Accepted,
DELETE: EHttpStatusCode.Accepted
};
/**
* Manages response status, headers, cookies, cache control, and body for an HTTP request.
*
* All header mutations are accumulated in memory and flushed in a single `writeHead()` call
* when `send()` is invoked. Setter methods are chainable.
*
* @example
* ```ts
* const response = useResponse()
* response.setStatus(200).setHeader('x-custom', 'value')
* response.setCookie('session', 'abc', { httpOnly: true })
* ```
*/
var HttpResponse = class {
/**
* @param _res - The underlying Node.js `ServerResponse`.
* @param _req - The underlying Node.js `IncomingMessage`.
* @param _logger - Logger instance for error reporting.
* @param defaultHeaders - Optional headers to pre-populate on this response (e.g. from `securityHeaders()`).
*/
constructor(_res, _req, _logger, defaultHeaders, _captureMode = false) {
this._res = _res;
this._req = _req;
this._logger = _logger;
this._captureMode = _captureMode;
this._status = 0;
this._body = void 0;
this._headers = {};
this._cookies = {};
this._rawCookies = [];
this._hasCookies = false;
this._responded = false;
if (defaultHeaders) for (const key in defaultHeaders) this._headers[key] = defaultHeaders[key];
}
/** The HTTP status code. If not set, it is inferred automatically when `send()` is called. */
get status() {
return this._status;
}
set status(value) {
this._status = value;
}
/** Sets the HTTP status code (chainable). */
setStatus(value) {
this._status = value;
return this;
}
/** The response body. Automatically serialized by `send()` (objects → JSON, strings → text). */
get body() {
return this._body;
}
set body(value) {
this._body = value;
}
/** Sets the response body (chainable). */
setBody(value) {
this._body = value;
return this;
}
/** Sets a single response header (chainable). Arrays produce multi-value headers. */
setHeader(name, value) {
this._headers[name] = Array.isArray(value) ? value : value.toString();
return this;
}
/** Batch-sets multiple response headers from a record (chainable). Existing keys are overwritten. */
setHeaders(headers) {
for (const key in headers) this._headers[key] = headers[key];
return this;
}
/** Returns the value of a response header, or `undefined` if not set. */
getHeader(name) {
return this._headers[name];
}
/** Removes a response header (chainable). */
removeHeader(name) {
delete this._headers[name];
return this;
}
/** Returns a read-only snapshot of all response headers. */
headers() {
return this._headers;
}
/** Sets the `Content-Type` response header (chainable). */
setContentType(value) {
this._headers["content-type"] = value;
return this;
}
/** Returns the current `Content-Type` header value. */
getContentType() {
return this._headers["content-type"];
}
/** Sets the `Access-Control-Allow-Origin` header (chainable). Defaults to `'*'`. */
enableCors(origin = "*") {
this._headers["access-control-allow-origin"] = origin;
return this;
}
/** Sets an outgoing `Set-Cookie` header with optional attributes (chainable). */
setCookie(name, value, attrs) {
this._cookies[name] = {
value,
attrs: attrs || {}
};
this._hasCookies = true;
return this;
}
/** Returns a previously set cookie's data, or `undefined` if not set. */
getCookie(name) {
return this._cookies[name];
}
/** Removes a cookie from the outgoing set list (chainable). */
removeCookie(name) {
delete this._cookies[name];
return this;
}
/** Removes all outgoing cookies (chainable). */
clearCookies() {
this._cookies = {};
this._rawCookies = [];
this._hasCookies = false;
return this;
}
/** Appends a raw `Set-Cookie` header string (chainable). Use when you need full control over the cookie format. */
setCookieRaw(rawValue) {
this._rawCookies.push(rawValue);
this._hasCookies = true;
return this;
}
/**
* Renders all buffered cookies (named via `setCookie()`, then raw via `setCookieRaw()`)
* as `Set-Cookie` header strings, without responding.
*
* Non-destructive: the buffers stay intact, so a later `send()` still emits the same
* cookies — callers that drain cookies onto the wire themselves should not also send
* through this wrapper. Cookies placed directly into headers (via `setHeader('set-cookie', …)`
* or default headers) are not included.
*/
getSetCookieStrings() {
const rendered = [];
for (const [name, data] of Object.entries(this._cookies)) if (data) rendered.push(renderCookie(name, data));
rendered.push(...this._rawCookies);
return rendered;
}
/** Sets the `Cache-Control` header from a directive object (chainable). */
setCacheControl(data) {
this._headers["cache-control"] = renderCacheControl(data);
return this;
}
/** Sets the `Age` header in seconds (chainable). Accepts a number or time string (e.g. `'2h 15m'`). */
setAge(value) {
this._headers.age = convertTime(value, "s").toString();
return this;
}
/** Sets the `Expires` header (chainable). Accepts a `Date`, date string, or timestamp. */
setExpires(value) {
this._headers.expires = typeof value === "string" || typeof value === "number" ? new Date(value).toUTCString() : value.toUTCString();
return this;
}
/** Sets or clears the `Pragma: no-cache` header (chainable). */
setPragmaNoCache(value = true) {
this._headers.pragma = value ? "no-cache" : "";
return this;
}
/**
* Returns the underlying Node.js `ServerResponse`.
* @param passthrough - If `true`, the framework still manages the response lifecycle. If `false` (default), the response is marked as "responded" and the framework will not touch it.
*/
getRawRes(passthrough) {
if (!passthrough) this._responded = true;
return this._res;
}
/** Whether the response has already been sent (or the underlying stream is no longer writable). */
get responded() {
return this._responded || !this._res.writable || this._res.writableEnded;
}
/**
* Builds a Web Standard `Response` from the accumulated response state
* (status, headers, cookies, body) without writing to the underlying `ServerResponse`.
*
* Used by `WooksHttp.fetch()` for programmatic invocation.
*/
toWebResponse() {
this.finalizeCookies();
const body = this._body;
const method = this._req.method;
if (body instanceof stream.Readable) {
this.autoStatus(true);
return new globalThis.Response(method === "HEAD" ? null : stream.Readable.toWeb(body), {
status: this._status,
headers: this._buildWebHeaders()
});
}
if (hasFetchResponse && body instanceof globalThis.Response) {
this._status = this._status || body.status;
this.mergeFetchResponseHeaders(body);
return new globalThis.Response(method === "HEAD" ? null : body.body, {
status: this._status,
headers: this._buildWebHeaders()
});
}
const rendered = this.renderBody();
this.autoStatus(!!rendered);
if (rendered) {
const contentLength = typeof rendered === "string" ? Buffer.byteLength(rendered) : rendered.byteLength;
this._headers["content-length"] = contentLength.toString();
}
const webBody = method === "HEAD" ? null : rendered instanceof Uint8Array ? rendered.buffer : rendered || null;
const webResponse = new globalThis.Response(webBody, {
status: this._status,
headers: this._buildWebHeaders()
});
if (typeof rendered === "string" && rendered) webResponse.text = () => Promise.resolve(rendered);
if (typeof body === "object" && body !== null && !(body instanceof Uint8Array) && !(body instanceof stream.Readable) && !(hasFetchResponse && body instanceof globalThis.Response)) {
const original = body;
webResponse.json = () => Promise.resolve(original);
}
return webResponse;
}
_buildWebHeaders() {
return recordToWebHeaders(this._headers);
}
/**
* Merges headers from a handler-returned fetch `Response` into the buffered headers.
* Explicitly buffered headers win. `set-cookie` is appended in array form so multiple
* cookies survive (`Headers` iteration would otherwise keep only the first).
*/
mergeFetchResponseHeaders(fetchResponse) {
fetchResponse.headers.forEach((value, key) => {
if (key !== "set-cookie" && !this._headers[key]) this._headers[key] = value;
});
const setCookies = typeof fetchResponse.headers.getSetCookie === "function" ? fetchResponse.headers.getSetCookie() : [];
if (setCookies.length > 0) {
const existing = this._headers["set-cookie"];
this._headers["set-cookie"] = existing ? [...Array.isArray(existing) ? existing : [existing], ...setCookies] : setCookies;
}
}
renderBody() {
const body = this._body;
if (body === void 0 || body === null) return "";
if (typeof body === "string") {
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
return body;
}
if (typeof body === "boolean" || typeof body === "number") {
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
return body.toString();
}
if (body instanceof Uint8Array) return body;
if (typeof body === "object") {
if (!this._headers["content-type"]) this._headers["content-type"] = "application/json";
return JSON.stringify(body);
}
throw new Error(`Unsupported body format "${typeof body}"`);
}
renderError(data, _ctx) {
this._status = data.statusCode || 500;
this._headers["content-type"] = "application/json";
this._body = JSON.stringify(data);
}
/** Renders and sends an HTTP error response. Called automatically by the framework when a handler throws an `HttpError`. */
sendError(error, ctx) {
const data = error.body;
this.renderError(data, ctx);
return this.send();
}
/**
* Finalizes and sends the response.
*
* Flushes all accumulated headers (including cookies) in a single `writeHead()` call,
* then writes the body. Supports `Readable` streams, `fetch` `Response` objects, and regular values.
*
* @throws Error if the response was already sent.
*/
send() {
if (this._responded) {
const err = /* @__PURE__ */ new Error("The response was already sent.");
this._logger.error(err.message, err);
throw err;
}
this._responded = true;
if (this._captureMode) {
this.finalizeCookies();
return;
}
this.finalizeCookies();
const body = this._body;
const method = this._req.method;
if (body instanceof stream.Readable) return this.sendStream(body, method);
if (hasFetchResponse && body instanceof Response) return this.sendFetchResponse(body, method);
this.sendRegular(method);
}
finalizeCookies() {
if (!this._hasCookies) return;
const rendered = this.getSetCookieStrings();
if (rendered.length > 0) {
const existing = this._headers["set-cookie"];
if (existing) this._headers["set-cookie"] = [...Array.isArray(existing) ? existing : [existing], ...rendered];
else this._headers["set-cookie"] = rendered;
}
this._hasCookies = false;
}
autoStatus(hasBody) {
if (this._status) return;
if (!hasBody) {
this._status = EHttpStatusCode.NoContent;
return;
}
this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
}
sendStream(stream$1, method) {
this.autoStatus(true);
this._res.writeHead(this._status, this._headers);
this._req.once("close", () => {
stream$1.destroy();
});
if (method === "HEAD") {
stream$1.destroy();
this._res.end();
return Promise.resolve();
}
return new Promise((resolve, reject) => {
stream$1.on("error", (e) => {
this._logger.error("Stream error", e);
stream$1.destroy();
this._res.end();
reject(e);
});
stream$1.on("close", () => {
stream$1.destroy();
resolve();
});
stream$1.pipe(this._res);
});
}
async sendFetchResponse(fetchResponse, method) {
this._status = this._status || fetchResponse.status;
this.mergeFetchResponseHeaders(fetchResponse);
this._res.writeHead(this._status, this._headers);
if (method === "HEAD") {
this._res.end();
return;
}
const fetchBody = fetchResponse.body;
if (fetchBody) try {
for await (const chunk of fetchBody) this._res.write(chunk);
} catch (error) {
this._logger.error("Error streaming fetch response body", error);
}
if (!this._res.writableEnded) this._res.end();
}
sendRegular(method) {
const renderedBody = this.renderBody();
this.autoStatus(!!renderedBody);
const contentLength = typeof renderedBody === "string" ? Buffer.byteLength(renderedBody) : renderedBody.byteLength;
this._headers["content-length"] = contentLength.toString();
this._res.writeHead(this._status, this._headers).end(method === "HEAD" ? "" : renderedBody);
}
};
/** Converts a Record of headers to a Web Standard `Headers` object. */
function recordToWebHeaders(record) {
const headers = new Headers();
for (const [key, value] of Object.entries(record)) if (Array.isArray(value)) for (const v of value) headers.append(key, v);
else if (value) headers.set(key, value);
return headers;
}
//#endregion
//#region packages/event-http/src/errors/403.tl.svg
function _403_tl_default(ctx) {
return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
<path d="M50 90.625C64.4042 87.1875 83.5751 69.8667 83.5751 48.6937V24.0854L50 9.375L16.425 24.0833V48.6937C16.425 69.8667 35.5959 87.1875 50 90.625Z" fill="#ff000050">
<animate attributeName="fill" dur="2s" repeatCount="indefinite"
values="#ff000000;#ff000050;#ff000000" />
</path>
<path d="M61.5395 46.0812H38.4604C37.1061 46.0812 36.0083 47.1791 36.0083 48.5333V65.075C36.0083 66.4292 37.1061 67.5271 38.4604 67.5271H61.5395C62.8938 67.5271 63.9916 66.4292 63.9916 65.075V48.5333C63.9916 47.1791 62.8938 46.0812 61.5395 46.0812Z" />
<path d="M41.7834 46.0834V39.6813C41.7834 37.5021 42.6491 35.4121 44.1901 33.8712C45.731 32.3303 47.8209 31.4646 50.0001 31.4646C52.1793 31.4646 54.2693 32.3303 55.8102 33.8712C57.3511 35.4121 58.2168 37.5021 58.2168 39.6813V46.0813" />
</svg>
`;
}
//#endregion
//#region packages/event-http/src/errors/404.tl.svg
function _404_tl_default(ctx) {
return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="sheet" d="M86.5 36.5V47.5H97.5V92.5H52.5V36.5H86.5ZM63 68.5H87V67.5H63V68.5ZM63 63.5H87V62.5H63V63.5ZM63 58.5H87V57.5H63V58.5ZM96.793 46.5H87.5V37.207L96.793 46.5Z" fill="#88888833" stroke="#888888aa"/>
</defs>
<g id="queue" transform="translate(-5 -10)">
<use href="#sheet" opacity="0">
<animateTransform attributeName="transform" type="translate"
dur="3s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="30 0; -20 0; -20 0; -70 0; -70 0" />
<animate attributeName="opacity"
dur="3s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="0;1;1;0;0" />
</use>
<use href="#sheet" opacity="0">
<animateTransform attributeName="transform" type="translate"
dur="3s" begin="1s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="30 0; -20 0; -20 0; -70 0; -70 0" />
<animate attributeName="opacity"
dur="3s" begin="1s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="0;1;1;0;0" />
</use>
<use href="#sheet" opacity="0">
<animateTransform attributeName="transform" type="translate"
dur="3s" begin="2s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="30 0; -20 0; -20 0; -70 0; -70 0" />
<animate attributeName="opacity"
dur="3s" begin="2s" repeatCount="indefinite"
keyTimes="0;0.1;0.32;0.42;1"
values="0;1;1;0;0" />
</use>
</g>
<g>
<path d="M49.5 32.5C58.3366 32.5 65.5 39.6634 65.5 48.5C65.5 54.4781 62.222 59.6923 57.3584 62.4404C55.0386 63.7512 52.3591 64.5 49.5 64.5C40.6634 64.5 33.5 57.3366 33.5 48.5C33.5 39.6634 40.6634 32.5 49.5 32.5Z" fill="#ffffff50" stroke="#888888" stroke-width="3"/>
<path d="M62.7101 74.5691C63.117 75.2907 64.0318 75.5459 64.7534 75.139C65.4751 74.7321 65.7302 73.8173 65.3233 73.0957L62.7101 74.5691ZM58.05 63.25L56.7434 63.9867L62.7101 74.5691L64.0167 73.8324L65.3233 73.0957L59.3567 62.5133L58.05 63.25Z" fill="#888888"/>
</g>
</svg>
`;
}
//#endregion
//#region packages/event-http/src/errors/500.tl.svg
function _500_tl_default(ctx) {
return `<svg height="64" viewBox="0 0 120 100" xmlns="http://www.w3.org/2000/svg">
<g id="server">
<g fill="#88888888" stroke="#88888888" stroke-width="2" >
<path d="M18 90C13.5817 90 10 86.4182 10 82V38C10 33.5817 13.5817 30 18 30H50.5L58 43L52.5 53L56 68.5L49.0098 89.97L61.2141 71.4358L58.363 54.315L64.7769 43.5511L58.2763 30.2943L104.243 32.0434C108.658 32.2114 112.101 35.9267 111.933 40.3418L110.26 84.31C110.092 88.725 106.377 92.168 101.962 92L49 90H18Z" />
</g>
<circle cx="30" cy="60" r="6" fill="red">
<animate attributeName="fill" dur="0.8s"
values="red;#2d0000;red" repeatCount="indefinite"/>
</circle>
</g>
<g fill="lightgray" opacity="0.75">
<circle cx="50" cy="35" r="6">
<animate attributeName="cy" from="35" to="15" dur="2s"
repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.75;0" dur="2s"
repeatCount="indefinite"/>
</circle>
<circle cx="60" cy="40" r="4">
<animate attributeName="cy" from="40" to="20" dur="2s"
begin="0.4s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.75;0" dur="2s"
begin="0.4s" repeatCount="indefinite"/>
</circle>
</g>
</svg>
`;
}
//#endregion
//#region packages/event-http/src/errors/error.tl.html
function error_tl_default(ctx) {
const { statusCode, statusMessage, icon, message, details, link, image, version } = ctx;
return `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${statusCode} ${statusMessage}</title>
<style>
body {
font-family:
-apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
margin: 0;
padding: 0 20px;
box-sizing: border-box;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
.error-container {
padding: 48px;
padding-bottom: 12px !important;
background-color: #ffffff;
border-radius: 0 0 12px 12px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
text-align: center;
max-width: 650px;
width: 100%;
transition:
background-color 0.3s ease,
border-color 0.3s ease,
box-shadow 0.3s ease;
}
.status-code {
font-size: 5rem;
font-weight: 900;
margin-bottom: 5px;
line-height: 1;
transition: color 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
position: relative;
margin-right: 24px;
}
.status-text {
font-size: 2.25rem;
font-weight: 700;
margin-bottom: 25px;
transition: color 0.3s ease;
}
.error-message {
font-size: 1.25rem;
margin-bottom: 40px;
line-height: 1.7;
transition: color 0.3s ease;
}
.json-details-container {
padding: 20px;
border-radius: 8px;
text-align: left;
overflow-x: auto;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 0.9rem;
border: 1px solid;
transition:
background-color 0.3s ease,
color 0.3s ease,
border-color 0.3s ease;
}
.json-details-container pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
.json-details-container code {
display: block;
}
body {
background-color: #f8fafc;
color: #1f2937;
}
.error-container {
background-color: #ffffff;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}
.status-code {
color: #dc2626;
}
.status-text {
color: #1f2937;
}
.error-message {
color: #4b5563;
}
.json-details-container {
background-color: #f0f4f8;
color: #374151;
border-color: #d1d5db;
}
.json-details-container p {
color: #6b7280;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #2d3748;
color: #e2e8f0;
}
.error-container {
background-color: #1a202c;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
}
.status-code {
color: #f56565;
}
.status-text {
color: #cbd5e1;
}
.error-message {
color: #a0aec0;
}
.json-details-container {
background-color: #2d3748;
color: #e2e8f0;
border-color: #4a5568;
}
.json-details-container p {
color: #a0aec0;
}
}
.footer {
display: flex;
gap: 0.25rem;
font-size: 0.6em;
justify-content: flex-end;
align-items: center;
margin-top: 12px;
opacity: 0.5;
transition: 0.25s ease-in-out;
}
.footer img {
filter: grayscale(0.5);
transition: 0.25s ease-in-out;
}
.footer:hover {
opacity: 1;
}
.footer:hover img {
filter: grayscale(0);
}
@media (max-width: 768px) {
body {
padding: 15px;
}
.error-container {
padding: 32px;
}
.status-code {
font-size: 4rem;
}
.status-text {
font-size: 1.8rem;
}
.error-message {
font-size: 1.1rem;
margin-bottom: 30px;
}
.json-details-container {
font-size: 0.85rem;
}
}
@media (max-width: 480px) {
body {
padding: 10px;
}
.error-container {
padding: 24px;
border-radius: 8px;
}
.status-code {
font-size: 3rem;
}
.status-text {
font-size: 1.5rem;
margin-bottom: 20px;
}
.error-message {
font-size: 1rem;
margin-bottom: 25px;
}
.json-details-container {
padding: 15px;
font-size: 0.8rem;
}
}
</style>
</head>
<body>
<div class="error-container">
<div id="statusCode" class="status-code">${icon} ${statusCode}</div>
<div id="statusText" class="status-text">${statusMessage}</div>
<p id="errorMessage" class="error-message">${message}</p>
<!-- prettier-ignore -->
<div class="json-details-container"style="display: ${details ? "block" : "none"};">
<p class="text-sm">Technical Details:</p>
<pre><code id="jsonDetails">${details}</code></pre>
</div>
<div class="footer">
Powered by
<a href="${link}" target="_blank">
<img height="20" alt="%{poweredBy}" src="${image}" />
</a>
v${version}
</div>
</div>
</body>
</html>
`;
}
//#endregion
//#region packages/event-http/src/utils/escape-html.ts
/**
* Escapes a value for safe interpolation into HTML text and attribute contexts.
*
* Use at the boundary between untrusted data (e.g. attacker-controlled URL
* parameters, error messages, user input) and rendered HTML responses.
*/
function escapeHtml(value) {
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
//#endregion
//#region packages/event-http/src/response/wooks-http-response.ts
let framework = {
version: "0.7.21",
poweredBy: "wooksjs",
link: "https://wooks.moost.org/",
image: "https://wooks.moost.org/wooks-full-logo.svg"
};
const icons = {
401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
403: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
404: typeof _404_tl_default === "function" ? _404_tl_default({}) : "",
500: typeof _500_tl_default === "function" ? _500_tl_default({}) : ""
};
/**
* Default `HttpResponse` subclass used by `createHttpApp`.
*
* Overrides error rendering to produce content-negotiated responses (JSON, HTML, or plain text)
* based on the request's `Accept` header. HTML error pages include SVG icons and framework branding.
*/
var WooksHttpResponse = class extends HttpResponse {
/** Registers framework metadata (name, version, link, logo) used in HTML error pages. */
static registerFramework(opts) {
framework = opts;
}
renderError(data, ctx) {
this._status = data.statusCode || 500;
const { has } = useAccept(ctx);
if (has("json")) {
this._headers["content-type"] = "application/json";
this._body = JSON.stringify(data);
} else if (has("html")) {
this._headers["content-type"] = "text/html";
this._body = renderErrorHtml(data);
} else if (has("text")) {
this._headers["content-type"] = "text/plain";
this._body = renderErrorText(data);
} else {
this._headers["content-type"] = "application/json";
this._body = JSON.stringify(data);
}
}
};
function renderErrorHtml(data) {
const hasDetails = Object.keys(data).length > 3;
const icon = data.statusCode >= 500 ? icons[500] : icons[data.statusCode] || "";
return typeof error_tl_default === "function" ? error_tl_default({
icon,
statusCode: data.statusCode,
statusMessage: httpStatusCodes[data.statusCode],
message: escapeHtml(data.message),
details: hasDetails ? escapeHtml(JSON.stringify(data, null, " ")) : "",
version: framework.version,
poweredBy: framework.poweredBy,
link: framework.link,
image: framework.image
}) : JSON.stringify(data, null, " ");
}
function renderErrorText(data) {
const keys = Object.keys(data).filter((key) => ![
"statusCode",
"error",
"message"
].includes(key));
return `${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}\n\n${keys.length > 0 ? JSON.stringify({
...data,
statusCode: void 0,
message: void 0,
error: void 0
}, null, " ") : ""}`;
}
//#endregion
//#region packages/event-http/src/http-adapter.ts
/**
* Identity headers forwarded from the calling HTTP context during programmatic `fetch()`
* when no `forwardHeaders` option is configured.
*
* The `forwardHeaders` option REPLACES this list. To extend it instead, spread the constant:
* ```ts
* createHttpApp({ forwardHeaders: [...DEFAULT_FORWARD_HEADERS, 'cloudfront-viewer-address'] })
* ```
*/
const DEFAULT_FORWARD_HEADERS = Object.freeze([
"authorization",
"cookie",
"accept-language",
"x-forwarded-for",
"x-request-id"
]);
/** HTTP adapter for Wooks that provides route registration, server lifecycle, and request handling. */
var WooksHttp = class extends wooks.WooksAdapterBase {
constructor(opts, wooks$1) {
super(wooks$1, opts?.logger, opts?.router);
this.opts = opts;
this.logger = opts?.logger || this.getLogger(`[96m[wooks-http]`);
this.ResponseClass = opts?.responseClass ?? WooksHttpResponse;
this.eventContextOptions = this.getEventContextOptions();
}
/** Registers a handler for all HTTP methods on the given path. */
all(path, handler) {
return this.on("*", path, handler);
}
/** Registers a GET route handler. */
get(path, handler) {
return this.on("GET", path, handler);
}
/** Registers a POST route handler. */
post(path, handler) {
return this.on("POST", path, handler);
}
/** Registers a PUT route handler. */
put(path, handler) {
return this.on("PUT", path, handler);
}
/** Registers a PATCH route handler. */
patch(path, handler) {
return this.on("PATCH", path, handler);
}
/** Registers a DELETE route handler. */
delete(path, handler) {
return this.on("DELETE", path, handler);
}
/** Registers a HEAD route handler. */
head(path, handler) {
return this.on("HEAD", path, handler);
}
/** Registers an OPTIONS route handler. */
options(path, handler) {
return this.on("OPTIONS", path, handler);
}
/** Registers an UPGRADE route handler for WebSocket upgrade requests. */
upgrade(path, handler) {
return this.on("UPGRADE", path, handler);
}
/** Register a WebSocket upgrade handler that implements the WooksUpgradeHandler contract. */
ws(handler) {
this.wsHandler = handler;
}
async listen(port, hostname, backlog, listeningListener) {
const server = this.server = http.default.createServer(this.getServerCb());
if (this.wsHandler) {
const upgradeCb = this.getUpgradeCb();
server.on("upgrade", upgradeCb);
}
return new Promise((resolve, reject) => {
server.once("listening", resolve);
server.once("error", reject);
let args = [
port,
hostname,
backlog,
listeningListener
];
const ui = args.indexOf(void 0);
if (ui >= 0) args = args.slice(0, ui);
server.listen(...args);
});
}
/**
* Stops the server if it was attached or passed via argument
* @param server
*/
close(server) {
const srv = server || this.server;
return new Promise((resolve, reject) => {
srv?.close((err) => {
if (err) {
reject(err);
return;
}
resolve(srv);
});
});
}
/**
* Returns http(s) server that was attached to Wooks
*
* See attachServer method docs
* @returns Server
*/
getServer() {
return this.server;
}
/**
* Attaches http(s) server instance
* to Wooks.
*
* Use it only if you want to `close` method to stop the server.
* @param server Server
*/
attachServer(server) {
this.server = server;
}
respond(data, response, ctx) {
if (response.responded) return;
if (data instanceof Error) {
const httpError = data instanceof HttpError ? data : new HttpError(500, data.message);
return response.sendError(httpError, ctx);
}
if (data !== response) response.body = data;
return response.send();
}
/**
* Returns server callback function
* that can be passed to any node server:
* ```js
* import { createHttpApp } from '@wooksjs/event-http'
* import http from 'http'
*
* const app = createHttpApp()
* const server = http.createServer(app.getServerCb())
* server.listen(3000)
* ```
*/
getServerCb(onNoMatch) {
const ctxOptions = this.eventContextOptions;
const RequestLimits = this.opts?.requestLimits;
const notFoundHandler = this.opts?.onNotFound;
const defaultHeaders = this.opts?.defaultHeaders;
return (req, res) => {
const response = new this.ResponseClass(res, req, ctxOptions.logger, defaultHeaders);
const method = req.method || "";
const url = req.url || "";
createHttpContext(ctxOptions, {
req,
response,
requestLimits: RequestLimits
}, () => {
const ctx = (0, _wooksjs_event_core.current)();
const handlers = this.wooks.lookupHandlers(method, url, ctx);
if (handlers) return this.processAndCatch(handlers, ctx, response);
else if (onNoMatch) onNoMatch(req, res);
else if (notFoundHandler) return this.processAndCatch([notFoundHandler], ctx, response);
else {
this.logger.debug(`404 Not found (${method})${url}`);
const error = new HttpError(404);
this.respond(error, response, ctx);
return error;
}
});
};
}
/**
* Returns upgrade callback function for the HTTP server's 'upgrade' event.
* Creates an HTTP context, seeds it with upgrade data, and routes as method 'UPGRADE'.
*/
getUpgradeCb() {
const ctxOptions = this.eventContextOptions;
const requestLimits = this.opts?.requestLimits;
const wsHandler = this.wsHandler;
return (req, socket, head) => {
if (!wsHandler) {
socket.destroy();
return;
}
const url = req.url || "";
createHttpContext(ctxOptions, {
req,
response: void 0,
requestLimits
}, () => {
const ctx = (0, _wooksjs_event_core.current)();
ctx.set(wsHandler.reqKey, req);
ctx.set(wsHandler.socketKey, socket);
ctx.set(wsHandler.headKey, head);
const handlers = this.wooks.lookupHandlers("UPGRADE", url, ctx);
if (handlers) return this.processUpgradeHandlers(handlers, ctx, socket);
else return wsHandler.handleUpgrade(req, socket, head);
});
};
}
processUpgradeHandlers(handlers, ctx, socket) {
for (let i = 0; i < handlers.length; i++) {
const handler = handlers[i];
const isLastHandler = i === handlers.length - 1;
try {
const result = handler();
if (result !== null && result !== void 0 && typeof result.then === "function") {
result.catch((error) => {
this.logger.error(`Upgrade handler error: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
socket.destroy();
});
return;
}
return;
} catch (error) {
if (!(error instanceof HttpError)) this.logger.error(`Upgrade handler error: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
if (isLastHandler) {
socket.destroy();
return;
}
}
}
}
/** Runs handlers and attaches a `.catch()` for async results to avoid unhandled rejections. */
processAndCatch(handlers, ctx, response) {
const result = this.processHandlers(handlers, ctx, response);
if (result !== null && result !== void 0 && typeof result.then === "function") result.catch((error) => {
this.logger.error("Internal error, please report", error);
this.respond(error, response, ctx);
});
return result;
}
processHandlers(handlers, ctx, response) {
for (let i = 0; i < handlers.length; i++) {
const handler = handlers[i];
const isLastHandler = i === handlers.length - 1;
try {
const result = handler();
if (result !== null && result !== void 0 && typeof result.then === "function") return this.processAsyncResult(result, handlers, i, ctx, response);
this.respond(result, response, ctx);
return;
} catch (error) {
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
if (isLastHandler) {
this.respond(error, response, ctx);
return;
}
}
}
}
async processAsyncResult(promise, handlers, startIndex, ctx, response) {
try {
const result = await promise;
await this.respond(result, response, ctx);
return result;
} catch (error) {
const isLastHandler = startIndex === handlers.length - 1;
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
if (isLastHandler) {
await this.respond(error, response, ctx);
return error;
}
}
for (let i = startIndex + 1; i < handlers.length; i++) {
const handler = handlers[i];
const isLastHandler = i === handlers.length - 1;
try {
const result = await handler();
await this.respond(result, response, ctx);
return result;
} catch (error) {
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
if (isLastHandler) {
await this.respond(error, response, ctx);
return error;
}
}
}
}
/**
* Programmatic route invocation using the Web Standard fetch API.
* Goes through the full dispatch pipeline: context creation, route matching,
* handler execution, response finalization.
*
* When called from within an existing HTTP context (e.g. during SSR),
* identity headers (authorization, cookie) are automatically forwarded
* from the calling request unless already present on the given Request.
*
* @param request - A Web Standard Request object.
* @returns A Web Standard Response, or `null` if no route matched (and no `onNotFound` handler is set).
*/
async fetch(request) {
const url = new URL(request.url);
const method = request.method;
const pathname = url.pathname + url.search;
const callerCtx = (0, _wooksjs_event_core.tryGetCurrent)();
let callerReq;
if (callerCtx) try {
callerReq = callerCtx.get(httpKind.keys.req);
} catch {}
const fakeReq = createFakeIncomingMessage(request, pathname, callerReq, this.opts?.forwardHeaders);
const fakeRes = new http.ServerResponse(fakeReq);
let rawChunks;
let rawHeaders;
let rawStatusCode = 0;
fakeRes.writeHead = ((...args) => {
rawStatusCode = args[0];
for (const arg of args) if (typeof arg === "object" && arg !== null) {
if (!rawHeaders) rawHeaders = {};
for (const [k, v] of Object.entries(arg)) rawHeaders[k] = v;
}
return fakeRes;
});
fakeRes.write = ((chunk, _encoding, cb) => {
if (chunk !== null && chunk !== void 0) {
if (!rawChunks) rawChunks = [];
rawChunks.push(typeof chunk === "string" ? buffer.Buffer.from(chunk) : chunk);
}
if (typeof cb === "function") cb();
return true;
});
fakeRes.end = ((chunk, _encoding, cb) => {
if (chunk !== null && chunk !== void 0 && typeof chunk !== "function") {
if (!rawChunks) rawChunks = [];
rawChunks.push(typeof chunk === "string" ? buffer.Buffer.from(chunk) : chunk);
}
if (typeof chunk === "function") chunk();
else if (typeof cb === "function") cb();
return fakeRes;
});
const response = new this.ResponseClass(fakeRes, fakeReq, this.logger, this.opts?.defaultHeaders, true);
let bodyBuffer;
if (request.body) bodyBuffer = buffer.Buffer.from(await request.bytes());
const ctxOptions = this.eventContextOptions;
const requestLimits = this.opts?.requestLimits;
return createHttpContext(ctxOptions, {
req: fakeReq,
response,
requestLimits
}, async () => {
const ctx = (0, _wooksjs_event_core.current)();
if (bodyBuffer) ctx.set(rawBodySlot, Promise.resolve(bodyBuffer));
try {
const handlers = this.wooks.lookupHandlers(method, pathname, ctx);
if (handlers) {
const result = this.processHandlers(handlers, ctx, response);
if (result !== null && result !== void 0 && typeof result.then === "function") await result.catch((error) => {
if (!response.responded) this.respond(error, response, ctx);
});
} else return null;
} finally {
fakeReq.emit("end");
fakeReq.emit("close");
fakeReq.destroy();
fakeRes.destroy();
}
let webResponse;
if (rawChunks || rawStatusCode > 0) {
const body = rawChunks ? buffer.Buffer.concat(rawChunks) : null;
webResponse = new Response(body && body.length > 0 ? body : null, {
status: rawStatusCode || 200,
headers: rawHeaders ? recordToWebHeaders(rawHeaders) : void 0
});
} else webResponse = response.toWebResponse();
if (callerReq) try {
const parentResponse = callerCtx?.get(httpKind.keys.response);
if (parentResponse) for (const cookie of webResponse.headers.getSetCookie()) parentResponse.setCookieRaw(cookie);
} catch {}
return webResponse;
});
}
/**
* Convenience wrapper for programmatic route invocation.
* Accepts a URL string (relative paths auto-prefixed with `http://localhost`),
* URL object, or Request, plus optional `RequestInit`.
*
* @param input - URL string, URL object, or Request.
* @param init - Optional RequestInit (method, headers, body, etc.).
* @returns A Web Standard Response.
*/
request(input, init) {
if (typeof input === "string" && !input.startsWith("http://") && !input.startsWith("https://")) input = `http://localhost${input.startsWith("/") ? "" : "/"}${input}`;
const req = input instanceof Request ? input : new Request(input, init);
return this.fetch(req);
}
/**
* Runs `fn` inside an HTTP event context seeded from a real `(req, res)` pair,
* WITHOUT route dispatch. Composables that read request state (`useRequest`,
* `useHeaders`, `useCookies`, `useAuthorization`) work; route-scoped state is empty.
* Nested `fetch()` calls made during `fn` see this context as their caller,
* so `forwardHeaders` and parent `Set-Cookie` propagation apply.
*
* Never writes to `res` — the caller owns the wire. The response wrapper is
* created in capture mode, so even a stray `response.send()` inside `fn` only
* finalizes state without touching the socket. Buffered response state
* (e.g. `Set-Cookie` collected from nested fetches) can be applied by the
* caller via the returned wrapper:
* ```ts
* const { result: html, response } = await http.withHttpContext(req, res, () => render(url))
* for (const cookie of response.getSetCookieStrings()) {
* res.appendHeader('Set-Cookie', cookie)
* }
* ```
*/
async withHttpContext(req, res, fn) {
const ctxOptions = this.eventContextOptions;
const response = new this.ResponseClass(res, req, ctxOptions.logger, this.opts?.defaultHeaders, true);
return {
result: await createHttpContext(ctxOptions, {
req,
response,
requestLimits: this.opts?.requestLimits
}, fn),
response
};
}
};
var NoopSocket = class extends stream.Duplex {
constructor(..._args) {
super(..._args);
this.remoteAddress = "127.0.0.1";
}
_read() {}
_write(_chunk, _enc, cb) {
cb();
}
};
const NOOP_SOCKET = new NoopSocket();
function createFakeIncomingMessage(request, pathname, forwardFrom, forwardHeaders) {
const req = new http.IncomingMessage(NOOP_SOCKET);
req.method = request.method;
req.url = pathname;
const headers = {};
if (forwardFrom && forwardHeaders !== false) {
const headerList = Array.isArray(forwardHeaders) ? forwardHeaders : DEFAULT_FORWARD_HEADERS;
for (const h of headerList) {
const val = forwardFrom.headers[h];
if (typeof val === "string" && val) headers[h] = val;
}
}
for (const [key, value] of request.headers) headers[key] = value;
req.headers = headers;
return req;
}
/**
* Creates a new WooksHttp application instance.
* @example
* ```ts
* const app = createHttpApp()
* app.get('/hello', () => 'Hello World!')
* app.listen(3000)
* ```
*/
function createHttpApp(opts, wooks$2) {
return new WooksHttp(opts, wooks$2);
}
//#endregion
//#region packages/event-http/src/utils/security-headers.ts
const HEADER_MAP = [
[
"contentSecurityPolicy",
"content-security-policy",
"default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
],
[
"crossOriginOpenerPolicy",
"cross-origin-opener-policy",
"same-origin"
],
[
"crossOriginResourcePolicy",
"cross-origin-resource-policy",
"same-origin"
],
[
"referrerPolicy",
"referrer-policy",
"no-referrer"
],
[
"strictTransportSecurity",
"strict-transport-security",
void 0
],
[
"xContentTypeOptions",
"x-content-type-options",
"nosniff"
],
[
"xFrameOptions",
"x-frame-options",
"SAMEORIGIN"
]
];
/**
* Returns a record of recommended HTTP security headers.
*
* Each option accepts a `string` (override value) or `false` (disable).
* Omitting an option uses the default value.
*
* `strictTransportSecurity` is opt-in only (no default) — HSTS is dangerous if not on HTTPS.
*/
function securityHeaders(opts) {
const result = {};
for (const [optKey, headerName, defaultValue] of HEADER_MAP) {
const value = opts?.[optKey];
if (value === false) continue;
if (typeof value === "string") result[headerName] = value;
else if (defaultValue !== void 0) result[headerName] = defaultValue;
}
return result;
}
//#endregion
//#region packages/event-http/src/testing.ts
/**
* Creates a fully initialized HTTP event context for testing.
*
* Sets up an `EventContext` with a fake `IncomingMessage`, `HttpResponse`, route params,
* and optional pre-seeded body. Returns a runner function that executes callbacks inside the context scope.
*
* @example
* ```ts
* const run = prepareTestHttpContext({ url: '/users/42', params: { id: '42' } })
* run(() => {
* const { params } = useRouteParams()
* expect(params.id).toBe('42')
* })
* ```
*/
function prepareTestHttpContext(options) {
const req = new http.IncomingMessage(new net.Socket({}));
req.method = options.method || "GET";
req.headers = options.headers || {};
req.url = options.url;
const response = new HttpResponse(new http.ServerResponse(req), req, console, options.defaultHeaders);
const ctx = new _wooksjs_event_core.EventContext({ logger: console });
ctx.seed(httpKind, {
req,
response,
requestLimits: options.requestLimits
});
if (options.params) ctx.set(_wooksjs_event_core.routeParamsKey, options.params);
if (options.rawBody !== void 0) {
const buf = buffer.Buffer.isBuffer(options.rawBody) ? options.rawBody : buffer.Buffer.from(options.rawBody);
ctx.set(rawBodySlot, Promise.resolve(buf));
}
return (cb) => (0, _wooksjs_event_core.run)(ctx, cb);
}
//#endregion
exports.DEFAULT_FORWARD_HEADERS = DEFAULT_FORWARD_HEADERS;
exports.DEFAULT_LIMITS = DEFAULT_LIMITS;
exports.EHttpStatusCode = EHttpStatusCode;
exports.HttpError = HttpError;
exports.HttpResponse = HttpResponse;
exports.WooksHttp = WooksHttp;
exports.WooksHttpResponse = WooksHttpResponse;
exports.WooksURLSearchParams = WooksURLSearchParams;
exports.createHttpApp = createHttpApp;
exports.createHttpContext = createHttpContext;
exports.httpKind = httpKind;
exports.httpStatusCodes = httpStatusCodes;
exports.prepareTestHttpContext = prepareTestHttpContext;
exports.rawBodySlot = rawBodySlot;
exports.recordToWebHeaders = recordToWebHeaders;
exports.renderCacheControl = renderCacheControl;
exports.securityHeaders = securityHeaders;
exports.useAccept = useAccept;
exports.useAuthorization = useAuthorization;
exports.useCookies = useCookies;
exports.useHeaders = useHeaders;
exports.useHttpContext = useHttpContext;
Object.defineProperty(exports, 'useLogger', {
enumerable: true,
get: function () {
return _wooksjs_event_core.useLogger;
}
});
exports.useRequest = useRequest;
exports.useResponse = useResponse;
Object.defineProperty(exports, 'useRouteParams', {
enumerable: true,
get: function () {
return _wooksjs_event_core.useRouteParams;
}
});
exports.useUrlParams = useUrlParams;