+192
-111
@@ -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; |
+103
-150
@@ -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 }; |
+182
-110
@@ -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 }; |
+4
-3
| { | ||
| "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": "[](https://npmjs.com/package/h3)\n[](https://npmjs.com/package/h3)\n[](https://bundlephobia.com/result?p=h3)\n[](https://github.com/unjs/h3/actions)\n[](https://codecov.io/gh/unjs/h3)\n[](https://www.jsdocs.io/package/h3)\n\n> H3 is a minimal h(ttp) framework built for high performance and portability\n\n<!--  -->\n\n## Features\n\nâď¸ **Portable:** Works perfectly in Serverless, Workers, and Node.js\n\nâď¸ **Compatible:** Support connect/express middleware\n\nâď¸ **Minimal:** Small, tree-shakable and zero-dependency\n\nâď¸ **Modern:** Native promise support\n\nâď¸ **Extendable:** Ships with a set of composable utilities but can be extended\n\nâď¸ **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": "[](https://npmjs.com/package/h3)\n[](https://npmjs.com/package/h3)\n[](https://bundlephobia.com/result?p=h3)\n[](https://github.com/unjs/h3/actions)\n[](https://codecov.io/gh/unjs/h3)\n[](https://www.jsdocs.io/package/h3)\n\n> H3 is a minimal h(ttp) framework built for high performance and portability\n\n<!--  -->\n\n## Features\n\nâď¸ **Portable:** Works perfectly in Serverless, Workers, and Node.js\n\nâď¸ **Compatible:** Support connect/express middleware\n\nâď¸ **Minimal:** Small, tree-shakable and zero-dependency\n\nâď¸ **Modern:** Native promise support\n\nâď¸ **Extendable:** Ships with a set of composable utilities but can be extended\n\nâď¸ **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" | ||
| } |
+2
-2
@@ -32,4 +32,4 @@ [](https://npmjs.com/package/h3) | ||
| # Using pnpm | ||
| pnpm add h3 | ||
| # Using yarn | ||
| yarn add h3 | ||
@@ -36,0 +36,0 @@ # Using pnpm |
50239
3.3%1088
10.79%