🚀 Big News:Socket Has Acquired Secure Annex.Learn More →
Socket
Book a DemoSign in
Socket

h3

Package Overview
Dependencies
Maintainers
1
Versions
140
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

h3 - npm Package Compare versions

Comparing version
0.4.2
to
0.5.0
+192
-111
dist/index.cjs

@@ -14,20 +14,16 @@ 'use strict';

const defineHandle = (handler) => handler;
const defineHandler = (handler) => handler;
const defineHandle = defineHandler;
const defineMiddleware = (middleware) => middleware;
function promisifyHandle(handle) {
function promisifyHandler(handler) {
return function(req, res) {
return callHandle(handle, req, res);
return callHandler(handler, req, res);
};
}
function callHandle(handle, req, res) {
const promisifyHandle = promisifyHandler;
function callHandler(handler, req, res) {
return new Promise((resolve, reject) => {
const next = (err) => err ? reject(err) : resolve(void 0);
try {
const returned = handle(req, res, next);
if (returned !== void 0) {
resolve(returned);
} else {
res.once("close", next);
res.once("error", next);
}
return resolve(handler(req, res, next));
} catch (err) {

@@ -38,7 +34,7 @@ next(err);

}
function lazyHandle(handle, promisify) {
function defineLazyHandler(handler, promisify) {
let _promise;
const resolve = () => {
if (!_promise) {
_promise = Promise.resolve(handle()).then((r) => promisify ? promisifyHandle(r.default || r) : r.default || r);
_promise = Promise.resolve(handler()).then((r) => promisify ? promisifyHandler(r.default || r) : r.default || r);
}

@@ -51,6 +47,7 @@ return _promise;

}
function useBase(base, handle) {
const lazyHandle = defineLazyHandler;
function useBase(base, handler) {
base = ufo.withoutTrailingSlash(base);
if (!base) {
return handle;
return handler;
}

@@ -60,14 +57,71 @@ return function(req, res) {

req.url = ufo.withoutBase(req.url || "/", base);
return handle(req, res);
return handler(req, res);
};
}
function useQuery(req) {
return ufo.getQuery(req.url || "");
function defineEventHandler(handler) {
handler.__is_handler__ = true;
return handler;
}
function useMethod(req, defaultMethod = "GET") {
return (req.method || defaultMethod).toUpperCase();
function defineLazyEventHandler(factory) {
let _promise;
let _resolved;
const resolveHandler = () => {
if (_resolved) {
return Promise.resolve(_resolved);
}
if (!_promise) {
_promise = Promise.resolve(factory()).then((r) => {
_resolved = r.default || r;
return _resolved;
});
}
return _promise;
};
return defineEventHandler((event) => {
if (_resolved) {
return _resolved(event);
}
return resolveHandler().then((handler) => handler(event));
});
}
function isMethod(req, expected, allowHead) {
const method = useMethod(req);
function isEventHandler(input) {
return "__is_handler__" in input;
}
function toEventHandler(handler) {
if (isEventHandler(handler)) {
return handler;
}
return defineEventHandler((event) => {
return callHandler(handler, event.req, event.res);
});
}
function createEvent(req, res) {
const event = {
__is_event__: true,
req,
res
};
event.event = event;
req.event = event;
req.req = req;
req.res = res;
res.event = event;
res.res = res;
res.req.res = res;
res.req.req = req;
return event;
}
function isEvent(input) {
return "__is_event__" in input;
}
function useQuery(event) {
return ufo.getQuery(event.req.url || "");
}
function useMethod(event, defaultMethod = "GET") {
return (event.req.method || defaultMethod).toUpperCase();
}
function isMethod(event, expected, allowHead) {
const method = useMethod(event);
if (allowHead && method === "HEAD") {

@@ -85,4 +139,4 @@ return true;

}
function assertMethod(req, expected, allowHead) {
if (!isMethod(req, expected, allowHead)) {
function assertMethod(event, expected, allowHead) {
if (!isMethod(event, expected, allowHead)) {
throw createError({

@@ -98,14 +152,14 @@ statusCode: 405,

const PayloadMethods = ["PATCH", "POST", "PUT", "DELETE"];
function useRawBody(req, encoding = "utf-8") {
assertMethod(req, PayloadMethods);
if (RawBodySymbol in req) {
const promise2 = Promise.resolve(req[RawBodySymbol]);
function useRawBody(event, encoding = "utf-8") {
assertMethod(event, PayloadMethods);
if (RawBodySymbol in event.req) {
const promise2 = Promise.resolve(event.req[RawBodySymbol]);
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
}
if ("body" in req) {
return Promise.resolve(req.body);
if ("body" in event.req) {
return Promise.resolve(event.req.body);
}
const promise = req[RawBodySymbol] = new Promise((resolve, reject) => {
const promise = event.req[RawBodySymbol] = new Promise((resolve, reject) => {
const bodyData = [];
req.on("error", (err) => {
event.req.on("error", (err) => {
reject(err);

@@ -120,9 +174,9 @@ }).on("data", (chunk) => {

}
async function useBody(req) {
if (ParsedBodySymbol in req) {
return req[ParsedBodySymbol];
async function useBody(event) {
if (ParsedBodySymbol in event.req) {
return event.req[ParsedBodySymbol];
}
const body = await useRawBody(req);
const body = await useRawBody(event);
const json = destr__default(body);
req[ParsedBodySymbol] = json;
event.req[ParsedBodySymbol] = json;
return json;

@@ -137,9 +191,9 @@ }

const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
function send(res, data, type) {
function send(event, data, type) {
if (type) {
defaultContentType(res, type);
defaultContentType(event, type);
}
return new Promise((resolve) => {
defer(() => {
res.end(data);
event.res.end(data);
resolve(void 0);

@@ -149,16 +203,16 @@ });

}
function defaultContentType(res, type) {
if (type && !res.getHeader("Content-Type")) {
res.setHeader("Content-Type", type);
function defaultContentType(event, type) {
if (type && !event.res.getHeader("Content-Type")) {
event.res.setHeader("Content-Type", type);
}
}
function sendRedirect(res, location, code = 302) {
res.statusCode = code;
res.setHeader("Location", location);
return send(res, "Redirecting to " + location, MIMES.html);
function sendRedirect(event, location, code = 302) {
event.res.statusCode = code;
event.res.setHeader("Location", location);
return send(event, "Redirecting to " + location, MIMES.html);
}
function appendHeader(res, name, value) {
let current = res.getHeader(name);
function appendHeader(event, name, value) {
let current = event.res.getHeader(name);
if (!current) {
res.setHeader(name, value);
event.res.setHeader(name, value);
return;

@@ -169,10 +223,10 @@ }

}
res.setHeader(name, current.concat(value));
event.res.setHeader(name, current.concat(value));
}
function isStream(data) {
return typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
}
function sendStream(res, data) {
function sendStream(event, data) {
return new Promise((resolve, reject) => {
data.pipe(res);
data.pipe(event.res);
data.on("end", () => resolve(void 0));

@@ -183,14 +237,14 @@ data.on("error", (error) => reject(createError(error)));

function useCookies(req) {
return cookieEs.parse(req.headers.cookie || "");
function useCookies(event) {
return cookieEs.parse(event.req.headers.cookie || "");
}
function useCookie(req, name) {
return useCookies(req)[name];
function useCookie(event, name) {
return useCookies(event)[name];
}
function setCookie(res, name, value, serializeOptions) {
function setCookie(event, name, value, serializeOptions) {
const cookieStr = cookieEs.serialize(name, value, serializeOptions);
appendHeader(res, "Set-Cookie", cookieStr);
appendHeader(event, "Set-Cookie", cookieStr);
}
function deleteCookie(res, name, serializeOptions) {
setCookie(res, name, "", {
function deleteCookie(event, name, serializeOptions) {
setCookie(event, name, "", {
...serializeOptions,

@@ -224,3 +278,3 @@ maxAge: 0

}
function sendError(res, error, debug) {
function sendError(event, error, debug) {
let h3Error;

@@ -233,10 +287,10 @@ if (error instanceof H3Error) {

}
if (res.writableEnded) {
if (event.res.writableEnded) {
return;
}
res.statusCode = h3Error.statusCode;
res.statusMessage = h3Error.statusMessage;
event.res.statusCode = h3Error.statusCode;
event.res.statusMessage = h3Error.statusMessage;
const responseBody = {
statusCode: res.statusCode,
statusMessage: res.statusMessage,
statusCode: event.res.statusCode,
statusMessage: event.res.statusMessage,
stack: [],

@@ -248,4 +302,4 @@ data: h3Error.data

}
res.setHeader("Content-Type", MIMES.json);
res.end(JSON.stringify(responseBody, null, 2));
event.res.setHeader("Content-Type", MIMES.json);
event.res.end(JSON.stringify(responseBody, null, 2));
}

@@ -255,13 +309,17 @@

const stack = [];
const _handle = createHandle(stack, options);
const app = function(req, res) {
return _handle(req, res).catch((error) => {
const handler = createAppEventHandler(stack, options);
const nodeHandler = async function(req, res) {
const event = createEvent(req, res);
try {
await handler(event);
} catch (err) {
if (options.onError) {
return options.onError(error, req, res);
await options.onError(err, event);
}
return sendError(res, error, !!options.debug);
});
await sendError(event, err, !!options.debug);
}
};
const app = nodeHandler;
app.stack = stack;
app._handle = _handle;
app.handler = handler;
app.use = (arg1, arg2, arg3) => use(app, arg1, arg2, arg3);

@@ -276,5 +334,5 @@ return app;

} else if (typeof arg1 === "string") {
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handle: arg2 }));
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
} else if (typeof arg1 === "function") {
app.stack.push(normalizeLayer({ ...arg2, route: "/", handle: arg1 }));
app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
} else {

@@ -285,7 +343,7 @@ app.stack.push(normalizeLayer({ ...arg1 }));

}
function createHandle(stack, options) {
function createAppEventHandler(stack, options) {
const spacing = options.debug ? 2 : void 0;
return async function handle(req, res) {
req.originalUrl = req.originalUrl || req.url || "/";
const reqUrl = req.url || "/";
return defineEventHandler(async (event) => {
event.req.originalUrl = event.req.originalUrl || event.req.url || "/";
const reqUrl = event.req.url || "/";
for (const layer of stack) {

@@ -296,11 +354,11 @@ if (layer.route.length > 1) {

}
req.url = reqUrl.slice(layer.route.length) || "/";
event.req.url = reqUrl.slice(layer.route.length) || "/";
} else {
req.url = reqUrl;
event.req.url = reqUrl;
}
if (layer.match && !layer.match(req.url, req)) {
if (layer.match && !layer.match(event.req.url, event)) {
continue;
}
const val = await layer.handle(req, res);
if (res.writableEnded) {
const val = await layer.handler(event);
if (event.res.writableEnded) {
return;

@@ -310,28 +368,35 @@ }

if (type === "string") {
return send(res, val, MIMES.html);
return send(event, val, MIMES.html);
} else if (isStream(val)) {
return sendStream(res, val);
return sendStream(event, val);
} else if (type === "object" || type === "boolean" || type === "number") {
if (val && val.buffer) {
return send(res, val);
return send(event, val);
} else if (val instanceof Error) {
throw createError(val);
} else {
return send(res, JSON.stringify(val, null, spacing), MIMES.json);
return send(event, JSON.stringify(val, null, spacing), MIMES.json);
}
}
}
if (!res.writableEnded) {
if (!event.res.writableEnded) {
throw createError({ statusCode: 404, statusMessage: "Not Found" });
}
};
});
}
function normalizeLayer(layer) {
if (layer.promisify === void 0) {
layer.promisify = layer.handle.length > 2;
function normalizeLayer(input) {
let handler = input.handler;
if (handler.handler) {
handler = handler.handler;
}
if (!isEventHandler(handler)) {
if (input.lazy) {
handler = defineLazyHandler(handler);
}
handler = toEventHandler(handler);
}
return {
route: ufo.withoutTrailingSlash(layer.route),
match: layer.match,
handle: layer.lazy ? lazyHandle(layer.handle, layer.promisify) : layer.promisify ? promisifyHandle(layer.handle) : layer.handle
route: ufo.withoutTrailingSlash(input.route),
match: input.match,
handler
};

@@ -345,3 +410,3 @@ }

const router = {};
router.add = (path, handle, method = "all") => {
const addRoute = (path, handler, method) => {
let route = routes[path];

@@ -352,10 +417,16 @@ if (!route) {

}
route.handlers[method] = handle;
route.handlers[method] = toEventHandler(handler);
return router;
};
router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
for (const method of RouterMethods) {
router[method] = (path, handle) => router.add(path, handle, method);
}
router.handle = (req, res) => {
const matched = _router.lookup(req.url || "/");
router.handler = defineEventHandler((event) => {
let path = event.req.url || "/";
const queryUrlIndex = path.lastIndexOf("?");
if (queryUrlIndex > -1) {
path = path.substring(0, queryUrlIndex);
}
const matched = _router.lookup(path);
if (!matched) {

@@ -365,6 +436,6 @@ throw createError({

name: "Not Found",
statusMessage: `Cannot find any route matching ${req.url || "/"}.`
statusMessage: `Cannot find any route matching ${event.req.url || "/"}.`
});
}
const method = (req.method || "get").toLowerCase();
const method = (event.req.method || "get").toLowerCase();
const handler = matched.handlers[method] || matched.handlers.all;

@@ -378,5 +449,6 @@ if (!handler) {

}
req.params = matched.params || {};
return handler(req, res);
};
event.event.params = matched.params || {};
event.req.params = event.event.params;
return handler(event);
});
return router;

@@ -389,11 +461,18 @@ }

exports.assertMethod = assertMethod;
exports.callHandle = callHandle;
exports.callHandler = callHandler;
exports.createApp = createApp;
exports.createAppEventHandler = createAppEventHandler;
exports.createError = createError;
exports.createHandle = createHandle;
exports.createEvent = createEvent;
exports.createRouter = createRouter;
exports.defaultContentType = defaultContentType;
exports.defineEventHandler = defineEventHandler;
exports.defineHandle = defineHandle;
exports.defineHandler = defineHandler;
exports.defineLazyEventHandler = defineLazyEventHandler;
exports.defineLazyHandler = defineLazyHandler;
exports.defineMiddleware = defineMiddleware;
exports.deleteCookie = deleteCookie;
exports.isEvent = isEvent;
exports.isEventHandler = isEventHandler;
exports.isMethod = isMethod;

@@ -403,2 +482,3 @@ exports.isStream = isStream;

exports.promisifyHandle = promisifyHandle;
exports.promisifyHandler = promisifyHandler;
exports.send = send;

@@ -409,2 +489,3 @@ exports.sendError = sendError;

exports.setCookie = setCookie;
exports.toEventHandler = toEventHandler;
exports.use = use;

@@ -411,0 +492,0 @@ exports.useBase = useBase;

@@ -1,16 +0,51 @@

import { IncomingMessage, ServerResponse } from 'http';
import http from 'http';
import { CookieSerializeOptions } from 'cookie-es';
import * as ufo from 'ufo';
declare type Encoding = false | 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex';
interface H3Event {
'__is_event__': true;
event: H3Event;
req: IncomingMessage;
res: ServerResponse;
/**
* Request params only filled with h3 Router handlers
*/
params?: Record<string, any>;
}
declare type CompatibilityEvent = H3Event | IncomingMessage | ServerResponse;
declare type _JSONValue<T = string | number | boolean> = T | T[] | Record<string, T>;
declare type JSONValue = _JSONValue<_JSONValue>;
declare type H3Response = void | JSONValue | Buffer;
interface EventHandler {
'__is_handler__'?: true;
(event: CompatibilityEvent): H3Response | Promise<H3Response>;
}
declare function defineEventHandler(handler: EventHandler): EventHandler;
declare function defineLazyEventHandler(factory: () => EventHandler | Promise<EventHandler>): EventHandler;
declare function isEventHandler(input: any): input is EventHandler;
declare type CompatibilityEventHandler = EventHandler | Handler | Middleware;
declare function toEventHandler(handler: CompatibilityEventHandler): EventHandler;
declare function createEvent(req: http.IncomingMessage, res: http.ServerResponse): CompatibilityEvent;
declare function isEvent(input: any): input is H3Event;
declare type Handle<T = any, ReqT = {}> = (req: IncomingMessage & ReqT, res: ServerResponse) => T;
declare type PHandle = Handle<Promise<any>>;
interface IncomingMessage extends http.IncomingMessage {
originalUrl?: string;
event: H3Event;
req: H3Event['req'];
res: H3Event['res'];
}
interface ServerResponse extends http.ServerResponse {
event: H3Event;
res: H3Event['res'];
req: http.ServerResponse['req'] & {
event: H3Event;
originalUrl?: string;
};
}
declare type Handler<T = any, ReqT = {}> = (req: IncomingMessage & ReqT, res: ServerResponse) => T;
declare type PromisifiedHandler = Handler<Promise<any>>;
declare type Middleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any;
declare type LazyHandle = () => Handle | Promise<Handle>;
declare const defineHandle: <T>(handler: Handle<T, {}>) => Handle<T, {}>;
declare const defineMiddleware: (middleware: Middleware) => Middleware;
declare function promisifyHandle(handle: Handle | Middleware): PHandle;
declare function callHandle(handle: Middleware, req: IncomingMessage, res: ServerResponse): Promise<unknown>;
declare function lazyHandle(handle: LazyHandle, promisify?: boolean): PHandle;
declare function useBase(base: string, handle: PHandle): PHandle;
declare type LazyHandler = () => Handler | Promise<Handler>;
declare type Encoding = false | 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'latin1' | 'binary' | 'hex';
declare type HTTPMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE';

@@ -20,3 +55,3 @@ interface Layer {

match?: Matcher;
handle: Handle;
handler: EventHandler;
}

@@ -27,19 +62,20 @@ declare type Stack = Layer[];

match?: Matcher;
handle: Handle | LazyHandle;
handler: Handler | LazyHandler;
lazy?: boolean;
/**
* @deprecated
*/
promisify?: boolean;
}
declare type InputStack = InputLayer[];
declare type Matcher = (url: string, req?: IncomingMessage) => boolean;
declare type Matcher = (url: string, event?: CompatibilityEvent) => boolean;
interface AppUse {
(route: string | string[], handle: Middleware | Middleware[], options?: Partial<InputLayer>): App;
(route: string | string[], handle: Handle | Handle[], options?: Partial<InputLayer>): App;
(handle: Middleware | Middleware[], options?: Partial<InputLayer>): App;
(handle: Handle | Handle[], options?: Partial<InputLayer>): App;
(route: string | string[], handler: CompatibilityEventHandler | CompatibilityEventHandler[], options?: Partial<InputLayer>): App;
(handler: CompatibilityEventHandler | CompatibilityEventHandler[], options?: Partial<InputLayer>): App;
(options: InputLayer): App;
}
interface App {
(req: IncomingMessage, res: ServerResponse): Promise<any>;
declare type NodeHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void | Promise<void>;
interface App extends NodeHandler {
stack: Stack;
_handle: PHandle;
handler: EventHandler;
use: AppUse;

@@ -49,7 +85,7 @@ }

debug?: boolean;
onError?: (error: Error, req: IncomingMessage, res: ServerResponse) => any;
onError?: (error: Error, event: CompatibilityEvent) => any;
}
declare function createApp(options?: AppOptions): App;
declare function use(app: App, arg1: string | Handle | InputLayer | InputLayer[], arg2?: Handle | Partial<InputLayer> | Handle[] | Middleware | Middleware[], arg3?: Partial<InputLayer>): App;
declare function createHandle(stack: Stack, options: AppOptions): PHandle;
declare function use(app: App, arg1: string | Handler | InputLayer | InputLayer[], arg2?: Handler | Partial<InputLayer> | Handler[] | Middleware | Middleware[], arg3?: Partial<InputLayer>): App;
declare function createAppEventHandler(stack: Stack, options: AppOptions): EventHandler;

@@ -83,3 +119,3 @@ /**

*
* @param res {ServerResponse} The ServerResponse object is passed as the second parameter in the handler function
@param event {CompatibilityEvent} H3 event or req passed by h3 handler
* @param error {H3Error|Error} Raised error

@@ -89,13 +125,20 @@ * @param debug {Boolean} Whether application is in debug mode.<br>

*/
declare function sendError(res: ServerResponse, error: Error | H3Error, debug?: boolean): void;
declare function sendError(event: CompatibilityEvent, error: Error | H3Error, debug?: boolean): void;
declare const RawBodySymbol: unique symbol;
interface _IncomingMessage extends IncomingMessage {
[RawBodySymbol]?: Promise<Buffer>;
ParsedBodySymbol?: any;
body?: any;
}
declare const defineHandler: <T>(handler: Handler<T, {}>) => Handler<T, {}>;
/** @deprecated Use defineHandler */
declare const defineHandle: <T>(handler: Handler<T, {}>) => Handler<T, {}>;
declare const defineMiddleware: (middleware: Middleware) => Middleware;
declare function promisifyHandler(handler: Handler | Middleware): PromisifiedHandler;
/** @deprecated Use defineHandler */
declare const promisifyHandle: typeof promisifyHandler;
declare function callHandler(handler: Middleware, req: IncomingMessage, res: ServerResponse): Promise<unknown>;
declare function defineLazyHandler(handler: LazyHandler, promisify?: boolean): PromisifiedHandler;
/** @deprecated Use defineLazyHandler */
declare const lazyHandle: typeof defineLazyHandler;
declare function useBase(base: string, handler: PromisifiedHandler): PromisifiedHandler;
/**
* Reads body of the request and returns encoded raw string (default) or `Buffer` if encoding if falsy.
* @param req {IncomingMessage} An IncomingMessage object is created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or req passed by h3 handler
* @param encoding {Encoding} encoding="utf-8" - The character encoding to use.

@@ -105,6 +148,6 @@ *

*/
declare function useRawBody(req: _IncomingMessage, encoding?: Encoding): Encoding extends false ? Buffer : Promise<string>;
declare function useRawBody(event: CompatibilityEvent, encoding?: Encoding): Encoding extends false ? Buffer : Promise<string | Buffer>;
/**
* Reads request body and try to safely parse using [destr](https://github.com/unjs/destr)
* @param req {IncomingMessage} An IncomingMessage object created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or req passed by h3 handler
* @param encoding {Encoding} encoding="utf-8" - The character encoding to use.

@@ -118,3 +161,3 @@ *

*/
declare function useBody<T = any>(req: _IncomingMessage): Promise<T>;
declare function useBody<T = any>(event: CompatibilityEvent): Promise<T>;

@@ -127,90 +170,4 @@ declare const MIMES: {

/**
* Additional serialization options
*/
interface CookieSerializeOptions {
/**
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no
* domain is set, and most clients will consider the cookie to apply to only
* the current domain.
*/
domain?: string;
/**
* Specifies a function that will be used to encode a cookie's value. Since
* value of a cookie has a limited character set (and must be a simple
* string), this function can be used to encode a value into a string suited
* for a cookie's value.
*
* The default function is the global `encodeURIComponent`, which will
* encode a JavaScript string into UTF-8 byte sequences and then URL-encode
* any that fall outside of the cookie range.
*/
encode?(value: string): string;
/**
* Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default,
* no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete
* it on a condition like exiting a web browser application.
*
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
* possible not all clients by obey this, so if both are set, they should
* point to the same date and time.
*/
expires?: Date;
/**
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}.
* When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
* default, the `HttpOnly` attribute is not set.
*
* *Note* be careful when setting this to true, as compliant clients will
* not allow client-side JavaScript to see the cookie in `document.cookie`.
*/
httpOnly?: boolean;
/**
* Specifies the number (in seconds) to be the value for the `Max-Age`
* `Set-Cookie` attribute. The given number will be converted to an integer
* by rounding down. By default, no maximum age is set.
*
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
* possible not all clients by obey this, so if both are set, they should
* point to the same date and time.
*/
maxAge?: number;
/**
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}.
* By default, the path is considered the "default path".
*/
path?: string;
/**
* Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}.
*
* - `true` will set the `SameSite` attribute to `Strict` for strict same
* site enforcement.
* - `false` will not set the `SameSite` attribute.
* - `'lax'` will set the `SameSite` attribute to Lax for lax same site
* enforcement.
* - `'strict'` will set the `SameSite` attribute to Strict for strict same
* site enforcement.
* - `'none'` will set the SameSite attribute to None for an explicit
* cross-site cookie.
*
* More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}.
*
* *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
*/
sameSite?: true | false | 'lax' | 'strict' | 'none';
/**
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the
* `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*
* *Note* be careful when setting this to `true`, as compliant clients will
* not send the cookie back to the server in the future if the browser does
* not have an HTTPS connection.
*/
secure?: boolean;
}
/**
* Parse the request to get HTTP Cookie header string and returning an object of all cookie name-value pairs.
* @param req {IncomingMessage} An IncomingMessage object created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or req passed by h3 handler
* @returns Object of cookie name-value pairs

@@ -221,6 +178,6 @@ * ```ts

*/
declare function useCookies(req: IncomingMessage): Record<string, string>;
declare function useCookies(event: CompatibilityEvent): Record<string, string>;
/**
* Get a cookie value by name.
* @param req {IncomingMessage} An IncomingMessage object created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or req passed by h3 handler
* @param name Name of the cookie to get

@@ -232,6 +189,6 @@ * @returns {*} Value of the cookie (String or undefined)

*/
declare function useCookie(req: IncomingMessage, name: string): string | undefined;
declare function useCookie(event: CompatibilityEvent, name: string): string | undefined;
/**
* Set a cookie value by name.
* @param res {ServerResponse} A ServerResponse object created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or res passed by h3 handler
* @param name Name of the cookie to set

@@ -244,6 +201,6 @@ * @param value Value of the cookie to set

*/
declare function setCookie(res: ServerResponse, name: string, value: string, serializeOptions?: CookieSerializeOptions): void;
declare function setCookie(event: CompatibilityEvent, name: string, value: string, serializeOptions?: CookieSerializeOptions): void;
/**
* Set a cookie value by name.
* @param res {ServerResponse} A ServerResponse object created by [http.Server](https://nodejs.org/api/http.html#http_class_http_server)
* @param event {CompatibilityEvent} H3 event or res passed by h3 handler
* @param name Name of the cookie to delete

@@ -255,30 +212,26 @@ * @param serializeOptions {CookieSerializeOptions} Cookie options

*/
declare function deleteCookie(res: ServerResponse, name: string, serializeOptions?: CookieSerializeOptions): void;
declare function deleteCookie(event: CompatibilityEvent, name: string, serializeOptions?: CookieSerializeOptions): void;
declare type HTTPMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE';
declare function useQuery(event: CompatibilityEvent): ufo.QueryObject;
declare function useMethod(event: CompatibilityEvent, defaultMethod?: HTTPMethod): HTTPMethod;
declare function isMethod(event: CompatibilityEvent, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): boolean;
declare function assertMethod(event: CompatibilityEvent, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): void;
declare function useQuery(req: IncomingMessage): ufo.QueryObject;
declare function useMethod(req: IncomingMessage, defaultMethod?: HTTPMethod): HTTPMethod;
declare function isMethod(req: IncomingMessage, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): boolean;
declare function assertMethod(req: IncomingMessage, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): void;
declare function send(event: CompatibilityEvent, data: any, type?: string): Promise<void>;
declare function defaultContentType(event: CompatibilityEvent, type?: string): void;
declare function sendRedirect(event: CompatibilityEvent, location: string, code?: number): Promise<void>;
declare function appendHeader(event: CompatibilityEvent, name: string, value: string): void;
declare function isStream(data: any): any;
declare function sendStream(event: CompatibilityEvent, data: any): Promise<void>;
declare function send(res: ServerResponse, data: any, type?: string): Promise<void>;
declare function defaultContentType(res: ServerResponse, type?: string): void;
declare function sendRedirect(res: ServerResponse, location: string, code?: number): Promise<void>;
declare function appendHeader(res: ServerResponse, name: string, value: string): void;
declare function isStream(data: any): boolean;
declare function sendStream(res: ServerResponse, data: any): Promise<unknown>;
declare type RouterMethod = Lowercase<HTTPMethod>;
declare type HandleWithParams = Handle<any, {
params: Record<string, string>;
}>;
declare type AddWithMethod = (path: string, handle: HandleWithParams) => Router;
declare type AddRouteShortcuts = Record<Lowercase<HTTPMethod>, AddWithMethod>;
declare type RouterUse = (path: string, handler: CompatibilityEventHandler, method?: RouterMethod) => Router;
declare type AddRouteShortcuts = Record<RouterMethod, RouterUse>;
interface Router extends AddRouteShortcuts {
add: (path: string, handle: HandleWithParams, method?: RouterMethod | 'all') => Router;
handle: Handle;
add: RouterUse;
use: RouterUse;
handler: EventHandler;
}
declare function createRouter(): Router;
export { AddRouteShortcuts, AddWithMethod, App, AppOptions, AppUse, H3Error, Handle, HandleWithParams, InputLayer, InputStack, Layer, LazyHandle, MIMES, Matcher, Middleware, PHandle, Router, RouterMethod, Stack, appendHeader, assertMethod, callHandle, createApp, createError, createHandle, createRouter, defaultContentType, defineHandle, defineMiddleware, deleteCookie, isMethod, isStream, lazyHandle, promisifyHandle, send, sendError, sendRedirect, sendStream, setCookie, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };
export { AddRouteShortcuts, App, AppOptions, AppUse, CompatibilityEvent, CompatibilityEventHandler, EventHandler, H3Error, H3Event, H3Response, InputLayer, InputStack, JSONValue, Layer, MIMES, Matcher, NodeHandler, Router, RouterMethod, RouterUse, Stack, _JSONValue, appendHeader, assertMethod, callHandler, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineHandle, defineHandler, defineLazyEventHandler, defineLazyHandler, defineMiddleware, deleteCookie, isEvent, isEventHandler, isMethod, isStream, lazyHandle, promisifyHandle, promisifyHandler, send, sendError, sendRedirect, sendStream, setCookie, toEventHandler, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };

@@ -6,20 +6,16 @@ import { withoutTrailingSlash, withoutBase, getQuery } from 'ufo';

const defineHandle = (handler) => handler;
const defineHandler = (handler) => handler;
const defineHandle = defineHandler;
const defineMiddleware = (middleware) => middleware;
function promisifyHandle(handle) {
function promisifyHandler(handler) {
return function(req, res) {
return callHandle(handle, req, res);
return callHandler(handler, req, res);
};
}
function callHandle(handle, req, res) {
const promisifyHandle = promisifyHandler;
function callHandler(handler, req, res) {
return new Promise((resolve, reject) => {
const next = (err) => err ? reject(err) : resolve(void 0);
try {
const returned = handle(req, res, next);
if (returned !== void 0) {
resolve(returned);
} else {
res.once("close", next);
res.once("error", next);
}
return resolve(handler(req, res, next));
} catch (err) {

@@ -30,7 +26,7 @@ next(err);

}
function lazyHandle(handle, promisify) {
function defineLazyHandler(handler, promisify) {
let _promise;
const resolve = () => {
if (!_promise) {
_promise = Promise.resolve(handle()).then((r) => promisify ? promisifyHandle(r.default || r) : r.default || r);
_promise = Promise.resolve(handler()).then((r) => promisify ? promisifyHandler(r.default || r) : r.default || r);
}

@@ -43,6 +39,7 @@ return _promise;

}
function useBase(base, handle) {
const lazyHandle = defineLazyHandler;
function useBase(base, handler) {
base = withoutTrailingSlash(base);
if (!base) {
return handle;
return handler;
}

@@ -52,14 +49,71 @@ return function(req, res) {

req.url = withoutBase(req.url || "/", base);
return handle(req, res);
return handler(req, res);
};
}
function useQuery(req) {
return getQuery(req.url || "");
function defineEventHandler(handler) {
handler.__is_handler__ = true;
return handler;
}
function useMethod(req, defaultMethod = "GET") {
return (req.method || defaultMethod).toUpperCase();
function defineLazyEventHandler(factory) {
let _promise;
let _resolved;
const resolveHandler = () => {
if (_resolved) {
return Promise.resolve(_resolved);
}
if (!_promise) {
_promise = Promise.resolve(factory()).then((r) => {
_resolved = r.default || r;
return _resolved;
});
}
return _promise;
};
return defineEventHandler((event) => {
if (_resolved) {
return _resolved(event);
}
return resolveHandler().then((handler) => handler(event));
});
}
function isMethod(req, expected, allowHead) {
const method = useMethod(req);
function isEventHandler(input) {
return "__is_handler__" in input;
}
function toEventHandler(handler) {
if (isEventHandler(handler)) {
return handler;
}
return defineEventHandler((event) => {
return callHandler(handler, event.req, event.res);
});
}
function createEvent(req, res) {
const event = {
__is_event__: true,
req,
res
};
event.event = event;
req.event = event;
req.req = req;
req.res = res;
res.event = event;
res.res = res;
res.req.res = res;
res.req.req = req;
return event;
}
function isEvent(input) {
return "__is_event__" in input;
}
function useQuery(event) {
return getQuery(event.req.url || "");
}
function useMethod(event, defaultMethod = "GET") {
return (event.req.method || defaultMethod).toUpperCase();
}
function isMethod(event, expected, allowHead) {
const method = useMethod(event);
if (allowHead && method === "HEAD") {

@@ -77,4 +131,4 @@ return true;

}
function assertMethod(req, expected, allowHead) {
if (!isMethod(req, expected, allowHead)) {
function assertMethod(event, expected, allowHead) {
if (!isMethod(event, expected, allowHead)) {
throw createError({

@@ -90,14 +144,14 @@ statusCode: 405,

const PayloadMethods = ["PATCH", "POST", "PUT", "DELETE"];
function useRawBody(req, encoding = "utf-8") {
assertMethod(req, PayloadMethods);
if (RawBodySymbol in req) {
const promise2 = Promise.resolve(req[RawBodySymbol]);
function useRawBody(event, encoding = "utf-8") {
assertMethod(event, PayloadMethods);
if (RawBodySymbol in event.req) {
const promise2 = Promise.resolve(event.req[RawBodySymbol]);
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
}
if ("body" in req) {
return Promise.resolve(req.body);
if ("body" in event.req) {
return Promise.resolve(event.req.body);
}
const promise = req[RawBodySymbol] = new Promise((resolve, reject) => {
const promise = event.req[RawBodySymbol] = new Promise((resolve, reject) => {
const bodyData = [];
req.on("error", (err) => {
event.req.on("error", (err) => {
reject(err);

@@ -112,9 +166,9 @@ }).on("data", (chunk) => {

}
async function useBody(req) {
if (ParsedBodySymbol in req) {
return req[ParsedBodySymbol];
async function useBody(event) {
if (ParsedBodySymbol in event.req) {
return event.req[ParsedBodySymbol];
}
const body = await useRawBody(req);
const body = await useRawBody(event);
const json = destr(body);
req[ParsedBodySymbol] = json;
event.req[ParsedBodySymbol] = json;
return json;

@@ -129,9 +183,9 @@ }

const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
function send(res, data, type) {
function send(event, data, type) {
if (type) {
defaultContentType(res, type);
defaultContentType(event, type);
}
return new Promise((resolve) => {
defer(() => {
res.end(data);
event.res.end(data);
resolve(void 0);

@@ -141,16 +195,16 @@ });

}
function defaultContentType(res, type) {
if (type && !res.getHeader("Content-Type")) {
res.setHeader("Content-Type", type);
function defaultContentType(event, type) {
if (type && !event.res.getHeader("Content-Type")) {
event.res.setHeader("Content-Type", type);
}
}
function sendRedirect(res, location, code = 302) {
res.statusCode = code;
res.setHeader("Location", location);
return send(res, "Redirecting to " + location, MIMES.html);
function sendRedirect(event, location, code = 302) {
event.res.statusCode = code;
event.res.setHeader("Location", location);
return send(event, "Redirecting to " + location, MIMES.html);
}
function appendHeader(res, name, value) {
let current = res.getHeader(name);
function appendHeader(event, name, value) {
let current = event.res.getHeader(name);
if (!current) {
res.setHeader(name, value);
event.res.setHeader(name, value);
return;

@@ -161,10 +215,10 @@ }

}
res.setHeader(name, current.concat(value));
event.res.setHeader(name, current.concat(value));
}
function isStream(data) {
return typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
}
function sendStream(res, data) {
function sendStream(event, data) {
return new Promise((resolve, reject) => {
data.pipe(res);
data.pipe(event.res);
data.on("end", () => resolve(void 0));

@@ -175,14 +229,14 @@ data.on("error", (error) => reject(createError(error)));

function useCookies(req) {
return parse(req.headers.cookie || "");
function useCookies(event) {
return parse(event.req.headers.cookie || "");
}
function useCookie(req, name) {
return useCookies(req)[name];
function useCookie(event, name) {
return useCookies(event)[name];
}
function setCookie(res, name, value, serializeOptions) {
function setCookie(event, name, value, serializeOptions) {
const cookieStr = serialize(name, value, serializeOptions);
appendHeader(res, "Set-Cookie", cookieStr);
appendHeader(event, "Set-Cookie", cookieStr);
}
function deleteCookie(res, name, serializeOptions) {
setCookie(res, name, "", {
function deleteCookie(event, name, serializeOptions) {
setCookie(event, name, "", {
...serializeOptions,

@@ -216,3 +270,3 @@ maxAge: 0

}
function sendError(res, error, debug) {
function sendError(event, error, debug) {
let h3Error;

@@ -225,10 +279,10 @@ if (error instanceof H3Error) {

}
if (res.writableEnded) {
if (event.res.writableEnded) {
return;
}
res.statusCode = h3Error.statusCode;
res.statusMessage = h3Error.statusMessage;
event.res.statusCode = h3Error.statusCode;
event.res.statusMessage = h3Error.statusMessage;
const responseBody = {
statusCode: res.statusCode,
statusMessage: res.statusMessage,
statusCode: event.res.statusCode,
statusMessage: event.res.statusMessage,
stack: [],

@@ -240,4 +294,4 @@ data: h3Error.data

}
res.setHeader("Content-Type", MIMES.json);
res.end(JSON.stringify(responseBody, null, 2));
event.res.setHeader("Content-Type", MIMES.json);
event.res.end(JSON.stringify(responseBody, null, 2));
}

@@ -247,13 +301,17 @@

const stack = [];
const _handle = createHandle(stack, options);
const app = function(req, res) {
return _handle(req, res).catch((error) => {
const handler = createAppEventHandler(stack, options);
const nodeHandler = async function(req, res) {
const event = createEvent(req, res);
try {
await handler(event);
} catch (err) {
if (options.onError) {
return options.onError(error, req, res);
await options.onError(err, event);
}
return sendError(res, error, !!options.debug);
});
await sendError(event, err, !!options.debug);
}
};
const app = nodeHandler;
app.stack = stack;
app._handle = _handle;
app.handler = handler;
app.use = (arg1, arg2, arg3) => use(app, arg1, arg2, arg3);

@@ -268,5 +326,5 @@ return app;

} else if (typeof arg1 === "string") {
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handle: arg2 }));
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
} else if (typeof arg1 === "function") {
app.stack.push(normalizeLayer({ ...arg2, route: "/", handle: arg1 }));
app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
} else {

@@ -277,7 +335,7 @@ app.stack.push(normalizeLayer({ ...arg1 }));

}
function createHandle(stack, options) {
function createAppEventHandler(stack, options) {
const spacing = options.debug ? 2 : void 0;
return async function handle(req, res) {
req.originalUrl = req.originalUrl || req.url || "/";
const reqUrl = req.url || "/";
return defineEventHandler(async (event) => {
event.req.originalUrl = event.req.originalUrl || event.req.url || "/";
const reqUrl = event.req.url || "/";
for (const layer of stack) {

@@ -288,11 +346,11 @@ if (layer.route.length > 1) {

}
req.url = reqUrl.slice(layer.route.length) || "/";
event.req.url = reqUrl.slice(layer.route.length) || "/";
} else {
req.url = reqUrl;
event.req.url = reqUrl;
}
if (layer.match && !layer.match(req.url, req)) {
if (layer.match && !layer.match(event.req.url, event)) {
continue;
}
const val = await layer.handle(req, res);
if (res.writableEnded) {
const val = await layer.handler(event);
if (event.res.writableEnded) {
return;

@@ -302,28 +360,35 @@ }

if (type === "string") {
return send(res, val, MIMES.html);
return send(event, val, MIMES.html);
} else if (isStream(val)) {
return sendStream(res, val);
return sendStream(event, val);
} else if (type === "object" || type === "boolean" || type === "number") {
if (val && val.buffer) {
return send(res, val);
return send(event, val);
} else if (val instanceof Error) {
throw createError(val);
} else {
return send(res, JSON.stringify(val, null, spacing), MIMES.json);
return send(event, JSON.stringify(val, null, spacing), MIMES.json);
}
}
}
if (!res.writableEnded) {
if (!event.res.writableEnded) {
throw createError({ statusCode: 404, statusMessage: "Not Found" });
}
};
});
}
function normalizeLayer(layer) {
if (layer.promisify === void 0) {
layer.promisify = layer.handle.length > 2;
function normalizeLayer(input) {
let handler = input.handler;
if (handler.handler) {
handler = handler.handler;
}
if (!isEventHandler(handler)) {
if (input.lazy) {
handler = defineLazyHandler(handler);
}
handler = toEventHandler(handler);
}
return {
route: withoutTrailingSlash(layer.route),
match: layer.match,
handle: layer.lazy ? lazyHandle(layer.handle, layer.promisify) : layer.promisify ? promisifyHandle(layer.handle) : layer.handle
route: withoutTrailingSlash(input.route),
match: input.match,
handler
};

@@ -337,3 +402,3 @@ }

const router = {};
router.add = (path, handle, method = "all") => {
const addRoute = (path, handler, method) => {
let route = routes[path];

@@ -344,10 +409,16 @@ if (!route) {

}
route.handlers[method] = handle;
route.handlers[method] = toEventHandler(handler);
return router;
};
router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
for (const method of RouterMethods) {
router[method] = (path, handle) => router.add(path, handle, method);
}
router.handle = (req, res) => {
const matched = _router.lookup(req.url || "/");
router.handler = defineEventHandler((event) => {
let path = event.req.url || "/";
const queryUrlIndex = path.lastIndexOf("?");
if (queryUrlIndex > -1) {
path = path.substring(0, queryUrlIndex);
}
const matched = _router.lookup(path);
if (!matched) {

@@ -357,6 +428,6 @@ throw createError({

name: "Not Found",
statusMessage: `Cannot find any route matching ${req.url || "/"}.`
statusMessage: `Cannot find any route matching ${event.req.url || "/"}.`
});
}
const method = (req.method || "get").toLowerCase();
const method = (event.req.method || "get").toLowerCase();
const handler = matched.handlers[method] || matched.handlers.all;

@@ -370,8 +441,9 @@ if (!handler) {

}
req.params = matched.params || {};
return handler(req, res);
};
event.event.params = matched.params || {};
event.req.params = event.event.params;
return handler(event);
});
return router;
}
export { H3Error, MIMES, appendHeader, assertMethod, callHandle, createApp, createError, createHandle, createRouter, defaultContentType, defineHandle, defineMiddleware, deleteCookie, isMethod, isStream, lazyHandle, promisifyHandle, send, sendError, sendRedirect, sendStream, setCookie, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };
export { H3Error, MIMES, appendHeader, assertMethod, callHandler, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineHandle, defineHandler, defineLazyEventHandler, defineLazyHandler, defineMiddleware, deleteCookie, isEvent, isEventHandler, isMethod, isStream, lazyHandle, promisifyHandle, promisifyHandler, send, sendError, sendRedirect, sendStream, setCookie, toEventHandler, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };
{
"name": "h3",
"version": "0.4.2",
"version": "0.5.0",
"description": "Tiny JavaScript Server",

@@ -50,4 +50,5 @@ "repository": "unjs/h3",

"build": "unbuild",
"dev": "jiti ./playground/index.ts",
"dev": "vitest",
"lint": "eslint --ext ts,mjs,cjs .",
"play": "jiti ./playground/index.ts",
"profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",

@@ -57,3 +58,3 @@ "release": "pnpm lint && pnpm test && pnpm build && standard-version && pnpm publish && git push --follow-tags",

},
"readme": "[![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![version](https://img.shields.io/npm/v/h3/latest.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![bundlephobia](https://img.shields.io/bundlephobia/min/h3/latest.svg?style=flat-square)](https://bundlephobia.com/result?p=h3)\n[![build status](https://img.shields.io/github/workflow/status/unjs/h3/ci/main?style=flat-square)](https://github.com/unjs/h3/actions)\n[![coverage](https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat-square)](https://codecov.io/gh/unjs/h3)\n[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/h3)\n\n> H3 is a minimal h(ttp) framework built for high performance and portability\n\n<!-- ![h3 - Tiny JavaScript Server](.github/banner.svg) -->\n\n## Features\n\n✔️ &nbsp;**Portable:** Works perfectly in Serverless, Workers, and Node.js\n\n✔️ &nbsp;**Compatible:** Support connect/express middleware\n\n✔️ &nbsp;**Minimal:** Small, tree-shakable and zero-dependency\n\n✔️ &nbsp;**Modern:** Native promise support\n\n✔️ &nbsp;**Extendable:** Ships with a set of composable utilities but can be extended\n\n✔️ &nbsp;**Router:** Super fast route matching using [unjs/radix3](https://github.com/unjs/radix3)\n\n## Install\n\n```bash\n# Using npm\nnpm install h3\n\n# Using pnpm\npnpm add h3\n\n# Using pnpm\npnpm add h3\n```\n\n## Usage\n\n```ts\nimport { createServer } from 'http'\nimport { createApp } from 'h3'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\ncreateServer(app).listen(process.env.PORT || 3000)\n```\n\n<details>\n <summary>Example using <a href=\"https://github.com/unjs/listhen\">listhen</a> for an elegant listener.</summary>\n\n```ts\nimport { createApp } from 'h3'\nimport { listen } from 'listhen'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\nlisten(app)\n```\n</details>\n\n## Router\n\nThe `app` instance created by `h3` uses a middleware stack (see [how it works](#how-it-works)) with the ability to match route prefix and apply matched middleware.\n\nTo opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance.\n\n```ts\nimport { createApp, createRouter } from 'h3'\n\nconst app = createApp()\n\nconst router = createRouter()\n .get('/', () => 'Hello World!')\n .get('/hello/:name', req => `Hello ${req.params.name}!`)\n\napp.use(router)\n```\n\n**Tip:** We can register same route more than once with different methods.\n\nRoutes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3).\n\n## More usage examples\n\n```js\n// Handle can directly return object or Promise<object> for JSON response\napp.use('/api', (req) => ({ url: req.url }))\n\n// We can have better matching other than quick prefix match\napp.use('/odd', () => 'Is odd!', { match: url => url.substr(1) % 2 })\n\n// Handle can directly return string for HTML response\napp.use(() => '<h1>Hello world!</h1>')\n\n// We can chain calls to .use()\napp.use('/1', () => '<h1>Hello world!</h1>')\n .use('/2', () => '<h1>Goodbye!</h1>')\n\n// Legacy middleware with 3rd argument are automatically promisified\napp.use((req, res, next) => { req.setHeader('X-Foo', 'bar'); next() })\n\n// Force promisify a legacy middleware\n// app.use(someMiddleware, { promisify: true })\n\n// Lazy loaded routes using { lazy: true }\n// app.use('/big', () => import('./big'), { lazy: true })\n```\n\n## Utilities\n\nInstead of adding helpers to `req` and `res`, h3 exposes them as composable utilities.\n\n- `useRawBody(req, encoding?)`\n- `useBody(req)`\n- `useCookies(req)`\n- `useCookie(req, name)`\n- `setCookie(res, name, value, opts?)`\n- `deleteCookie(res, name, opts?)`\n- `useQuery(req)`\n- `send(res, data, type?)`\n- `sendRedirect(res, location, code=302)`\n- `appendHeader(res, name, value)`\n- `createError({ statusCode, statusMessage, data? })`\n- `sendError(res, error, debug?)`\n- `defineHandle(handle)`\n- `defineMiddleware(middlware)`\n- `useMethod(req, default?)`\n- `isMethod(req, expected, allowHead?)`\n- `assertMethod(req, expected, allowHead?)`\n\n👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).\n\n## How it works?\n\nUsing `createApp`, it returns a standard `(req, res)` handler function and internally an array called middleware stack. using`use()` method we can add an item to this internal stack.\n\nWhen a request comes, each stack item that matches the route will be called and resolved until [`res.writableEnded`](https://nodejs.org/api/http.html#http_response_writableended) flag is set, which means the response is sent. If `writableEnded` is not set after all middleware, a `404` error will be thrown. And if one of the stack items resolves to a value, it will be serialized and sent as response as a shorthand method to sending responses.\n\nFor maximum compatibility with connect/express middleware (`req, res, next?` signature), h3 converts classic middleware into a promisified version ready to use with stack runner:\n\n- If middleware has 3rd next/callback param, the promise will `resolve/reject` when called\n- If middleware returns a promise, it will be **chained** to the main promise\n- If calling middleware throws an immediate error, the promise will be rejected\n- On `close` and `error` events of res, the promise will `resolve/reject` (to ensure if middleware simply calls `res.end`)\n\n## License\n\nMIT\n"
"readme": "[![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![version](https://img.shields.io/npm/v/h3/latest.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![bundlephobia](https://img.shields.io/bundlephobia/min/h3/latest.svg?style=flat-square)](https://bundlephobia.com/result?p=h3)\n[![build status](https://img.shields.io/github/workflow/status/unjs/h3/ci/main?style=flat-square)](https://github.com/unjs/h3/actions)\n[![coverage](https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat-square)](https://codecov.io/gh/unjs/h3)\n[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/h3)\n\n> H3 is a minimal h(ttp) framework built for high performance and portability\n\n<!-- ![h3 - Tiny JavaScript Server](.github/banner.svg) -->\n\n## Features\n\n✔️ &nbsp;**Portable:** Works perfectly in Serverless, Workers, and Node.js\n\n✔️ &nbsp;**Compatible:** Support connect/express middleware\n\n✔️ &nbsp;**Minimal:** Small, tree-shakable and zero-dependency\n\n✔️ &nbsp;**Modern:** Native promise support\n\n✔️ &nbsp;**Extendable:** Ships with a set of composable utilities but can be extended\n\n✔️ &nbsp;**Router:** Super fast route matching using [unjs/radix3](https://github.com/unjs/radix3)\n\n## Install\n\n```bash\n# Using npm\nnpm install h3\n\n# Using yarn\nyarn add h3\n\n# Using pnpm\npnpm add h3\n```\n\n## Usage\n\n```ts\nimport { createServer } from 'http'\nimport { createApp } from 'h3'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\ncreateServer(app).listen(process.env.PORT || 3000)\n```\n\n<details>\n <summary>Example using <a href=\"https://github.com/unjs/listhen\">listhen</a> for an elegant listener.</summary>\n\n```ts\nimport { createApp } from 'h3'\nimport { listen } from 'listhen'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\nlisten(app)\n```\n</details>\n\n## Router\n\nThe `app` instance created by `h3` uses a middleware stack (see [how it works](#how-it-works)) with the ability to match route prefix and apply matched middleware.\n\nTo opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance.\n\n```ts\nimport { createApp, createRouter } from 'h3'\n\nconst app = createApp()\n\nconst router = createRouter()\n .get('/', () => 'Hello World!')\n .get('/hello/:name', req => `Hello ${req.params.name}!`)\n\napp.use(router)\n```\n\n**Tip:** We can register same route more than once with different methods.\n\nRoutes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3).\n\n## More usage examples\n\n```js\n// Handle can directly return object or Promise<object> for JSON response\napp.use('/api', (req) => ({ url: req.url }))\n\n// We can have better matching other than quick prefix match\napp.use('/odd', () => 'Is odd!', { match: url => url.substr(1) % 2 })\n\n// Handle can directly return string for HTML response\napp.use(() => '<h1>Hello world!</h1>')\n\n// We can chain calls to .use()\napp.use('/1', () => '<h1>Hello world!</h1>')\n .use('/2', () => '<h1>Goodbye!</h1>')\n\n// Legacy middleware with 3rd argument are automatically promisified\napp.use((req, res, next) => { req.setHeader('X-Foo', 'bar'); next() })\n\n// Force promisify a legacy middleware\n// app.use(someMiddleware, { promisify: true })\n\n// Lazy loaded routes using { lazy: true }\n// app.use('/big', () => import('./big'), { lazy: true })\n```\n\n## Utilities\n\nInstead of adding helpers to `req` and `res`, h3 exposes them as composable utilities.\n\n- `useRawBody(req, encoding?)`\n- `useBody(req)`\n- `useCookies(req)`\n- `useCookie(req, name)`\n- `setCookie(res, name, value, opts?)`\n- `deleteCookie(res, name, opts?)`\n- `useQuery(req)`\n- `send(res, data, type?)`\n- `sendRedirect(res, location, code=302)`\n- `appendHeader(res, name, value)`\n- `createError({ statusCode, statusMessage, data? })`\n- `sendError(res, error, debug?)`\n- `defineHandle(handle)`\n- `defineMiddleware(middlware)`\n- `useMethod(req, default?)`\n- `isMethod(req, expected, allowHead?)`\n- `assertMethod(req, expected, allowHead?)`\n\n👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).\n\n## How it works?\n\nUsing `createApp`, it returns a standard `(req, res)` handler function and internally an array called middleware stack. using`use()` method we can add an item to this internal stack.\n\nWhen a request comes, each stack item that matches the route will be called and resolved until [`res.writableEnded`](https://nodejs.org/api/http.html#http_response_writableended) flag is set, which means the response is sent. If `writableEnded` is not set after all middleware, a `404` error will be thrown. And if one of the stack items resolves to a value, it will be serialized and sent as response as a shorthand method to sending responses.\n\nFor maximum compatibility with connect/express middleware (`req, res, next?` signature), h3 converts classic middleware into a promisified version ready to use with stack runner:\n\n- If middleware has 3rd next/callback param, the promise will `resolve/reject` when called\n- If middleware returns a promise, it will be **chained** to the main promise\n- If calling middleware throws an immediate error, the promise will be rejected\n- On `close` and `error` events of res, the promise will `resolve/reject` (to ensure if middleware simply calls `res.end`)\n\n## License\n\nMIT\n"
}

@@ -32,4 +32,4 @@ [![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3)

# Using pnpm
pnpm add h3
# Using yarn
yarn add h3

@@ -36,0 +36,0 @@ # Using pnpm