diff --git a/src/event/event.ts b/src/event/event.ts index d9774406..067e539d 100644 --- a/src/event/event.ts +++ b/src/event/event.ts @@ -98,10 +98,23 @@ export class H3Event< } } +/** + * Checks if the input is an H3Event object. + * @param input - The input to check. + * @returns True if the input is an H3Event object, false otherwise. + * @see H3Event + */ export function isEvent(input: any): input is H3Event { return hasProp(input, "__is_event__"); } +/** + * Creates a new H3Event instance from the given Node.js request and response objects. + * @param req - The NodeIncomingMessage object. + * @param res - The NodeServerResponse object. + * @returns A new H3Event instance. + * @see H3Event + */ export function createEvent( req: NodeIncomingMessage, res: NodeServerResponse, diff --git a/src/event/utils.ts b/src/event/utils.ts index 07067af4..24ee2b1e 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -101,6 +101,11 @@ export function defineResponseMiddleware< return fn; } +/** + * Checks if any kind of input is an event handler. + * @param input The input to check. + * @returns True if the input is an event handler, false otherwise. + */ export function isEventHandler(input: any): input is EventHandler { return hasProp(input, "__is_handler__"); } diff --git a/src/utils/body.ts b/src/utils/body.ts index 4f502fe8..9640793d 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -3,7 +3,10 @@ import destr from "destr"; import type { Encoding, HTTPMethod, InferEventInput } from "../types"; import type { H3Event } from "../event"; import { createError } from "../error"; -import { parse as parseMultipartData } from "./internal/multipart"; +import { + type MultiPartData, + parse as parseMultipartData, +} from "./internal/multipart"; import { assertMethod, getRequestHeader, toWebRequest } from "./request"; import { ValidateFunction, validateData } from "./internal/validate"; import { hasProp } from "./internal/object"; @@ -117,8 +120,8 @@ export function readRawBody( /** * Reads request body and tries to safely parse using [destr](https://github.com/unjs/destr). - * @param event {H3Event} H3 event passed by h3 handler - * @param encoding {Encoding} encoding="utf-8" - The character encoding to use. + * @param event H3 event passed by h3 handler + * @param encoding The character encoding to use, defaults to 'utf-8'. * * @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request JSON body * @@ -157,6 +160,26 @@ export async function readBody< return parsed as unknown as _T; } +/** + * Tries to read the request body via `readBody`, then uses the provided validation function and either throws a validation error or returns the result. + * @param event The H3Event passed by the handler. + * @param validate The function to use for body validation. It will be called passing the read request body. If the result is not false, the parsed body will be returned. + * @throws If the validation function returns `false` or throws, a validation error will be thrown. + * @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request JSON body. + * @see {readBody} + * + * ```ts + * // With a custom validation function + * const body = await readValidatedBody(event, (body) => { + * return typeof body === "object" && body !== null + * }) + * + * // With a zod schema + * import { z } from 'zod' + * const objectSchema = z.object() + * const body = await readValidatedBody(event, objectSchema.safeParse) + * ``` + */ export async function readValidatedBody< T, Event extends H3Event = H3Event, @@ -166,7 +189,30 @@ export async function readValidatedBody< return validateData(_body, validate); } -export async function readMultipartFormData(event: H3Event) { +/** + * Tries to read and parse the body of a an H3Event as multipart form. + * @param event The H3Event object to read multipart form from. + * + * @return The parsed form data. If no form could be detected because the content type is not multipart/form-data or no boundary could be found. + * + * ```ts + * const formData = await readMultipartFormData(event) + * // The result could look like: + * // [ + * // { + * // "data": "other", + * // "name": "baz", + * // }, + * // { + * // "data": "something", + * // "name": "some-other-data", + * // }, + * // ] + * ``` + */ +export async function readMultipartFormData( + event: H3Event, +): Promise { const contentType = getRequestHeader(event, "content-type"); if (!contentType || !contentType.startsWith("multipart/form-data")) { return; @@ -183,9 +229,8 @@ export async function readMultipartFormData(event: H3Event) { } /** - * Constructs a FormData object from an event. - * @param event {H3Event} - * @returns {FormData} + * Constructs a FormData object from an event, after converting it to a a web request. + * @param event The H3Event object to read the form data from. * * ```ts * const eventHandler = event => { @@ -195,10 +240,15 @@ export async function readMultipartFormData(event: H3Event) { * } * ``` */ -export async function readFormData(event: H3Event) { +export async function readFormData(event: H3Event): Promise { return await toWebRequest(event).formData(); } +/** + * Captures a stream from a request. + * @param event The H3Event object containing the request information. + * @returns Undefined if the request can't transport a payload, otherwise a ReadableStream of the request body. + */ export function getRequestWebStream( event: H3Event, ): undefined | ReadableStream { diff --git a/src/utils/internal/object.ts b/src/utils/internal/object.ts index 544992c2..a174e954 100644 --- a/src/utils/internal/object.ts +++ b/src/utils/internal/object.ts @@ -1,5 +1,12 @@ // TODO: Benchmark typeof vs try/catch (i guess try/catch is faster in non-edge cases!) -export function hasProp(obj: object, prop: string | symbol) { + +/** + * Checks if a certain input has a given property. + * @param obj - The input to check. + * @param prop - The property to check for. + * @returns A boolean indicating whether the input is an object and has the property. + */ +export function hasProp(obj: any, prop: string | symbol) { try { return prop in obj; } catch { diff --git a/src/utils/internal/validate.ts b/src/utils/internal/validate.ts index 6f78e143..32e581a3 100644 --- a/src/utils/internal/validate.ts +++ b/src/utils/internal/validate.ts @@ -9,6 +9,14 @@ export type ValidateFunction = ( data: unknown, ) => ValidateResult | Promise>; +/** + * Validates the given data using the provided validation function. + * @template T The expected type of the validated data. + * @param data The data to validate. + * @param fn The validation function to use - can be async. + * @returns A Promise that resolves with the validated data if it passes validation, meaning the validation function does not throw and returns a value other than false. + * @throws {ValidationError} If the validation function returns false or throws an error. + */ export async function validateData( data: unknown, fn: ValidateFunction, diff --git a/src/utils/route.ts b/src/utils/route.ts index cf93cf9b..6dd74550 100644 --- a/src/utils/route.ts +++ b/src/utils/route.ts @@ -2,6 +2,11 @@ import { withoutTrailingSlash, withoutBase } from "ufo"; import { EventHandler } from "../types"; import { eventHandler } from "../event"; +/** + * Prefixes and executes a handler with a base path. + * @param base The base path to prefix. When set to an empty string, the handler will be run as is. + * @param handler The event handler to use with the adapted path. + */ export function useBase(base: string, handler: EventHandler): EventHandler { base = withoutTrailingSlash(base); @@ -10,7 +15,7 @@ export function useBase(base: string, handler: EventHandler): EventHandler { } return eventHandler(async (event) => { - // Keep original incoming url accessable + // Keep original incoming url accessible event.node.req.originalUrl = event.node.req.originalUrl || event.node.req.url || "/";