From 8852224f6af55819b1685151a265be71e1d73cad Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 30 Jan 2025 21:01:57 +0000 Subject: [PATCH 1/2] ensure generic h3 functions remain generic, including new overloads --- packages/start-server/src/index.tsx | 170 ++++++++++++++++++++++++---- 1 file changed, 145 insertions(+), 25 deletions(-) diff --git a/packages/start-server/src/index.tsx b/packages/start-server/src/index.tsx index a3e486e3c5..44060c530b 100644 --- a/packages/start-server/src/index.tsx +++ b/packages/start-server/src/index.tsx @@ -68,7 +68,13 @@ import { writeEarlyHints as _writeEarlyHints, } from 'h3' import { getContext as getUnctxContext } from 'unctx' -import type { _RequestMiddleware, _ResponseMiddleware } from 'h3' +import type { + Encoding, + HTTPHeaderName, + InferEventInput, + _RequestMiddleware, + _ResponseMiddleware, +} from 'h3' export { StartServer } from './StartServer' export { createStartHandler } from './createStartHandler' @@ -237,11 +243,22 @@ export function isEvent(obj: any) { // Implement logic to check if obj is an H3Event } -type WrapFunction) => any> = ( - ...args: Parameters extends [H3Event, ...infer TArgs] - ? TArgs | Parameters - : Parameters -) => ReturnType +type Tail = T extends [any, ...infer U] ? U : never + +type PrependOverload< + TOriginal extends (...args: Array) => any, + TOverload extends (...args: Array) => any, +> = TOverload & TOriginal + +// add an overload to the function without the event argument +type WrapFunction) => any> = PrependOverload< + TFn, + ( + ...args: Parameters extends [H3Event, ...infer TArgs] + ? TArgs + : Parameters + ) => ReturnType +> function createWrapperFunction) => any>( h3Function: TFn, @@ -269,21 +286,56 @@ function createWrapperFunction) => any>( } return (h3Function as any)(...args) - } + } as any } // Creating wrappers for each utility and exporting them with their original names -export const readRawBody = createWrapperFunction(_readRawBody) -export const readBody = createWrapperFunction(_readBody) -export const getQuery = createWrapperFunction(_getQuery) +type WrappedReadRawBody = ( + ...args: Tail>> +) => ReturnType> +export const readRawBody: PrependOverload< + typeof _readRawBody, + WrappedReadRawBody +> = createWrapperFunction(_readRawBody) +type WrappedReadBody = >( + ...args: Tail>> +) => ReturnType> +export const readBody: PrependOverload = + createWrapperFunction(_readBody) +type WrappedGetQuery = < + T, + TEventInput = Exclude, undefined>, +>( + ...args: Tail>> +) => ReturnType> +export const getQuery: PrependOverload = + createWrapperFunction(_getQuery) export const isMethod = createWrapperFunction(_isMethod) export const isPreflightRequest = createWrapperFunction(_isPreflightRequest) -export const getValidatedQuery = createWrapperFunction(_getValidatedQuery) +type WrappedGetValidatedQuery = < + T, + TEventInput = InferEventInput<'query', H3Event, T>, +>( + ...args: Tail>> +) => ReturnType> +export const getValidatedQuery: PrependOverload< + typeof _getValidatedQuery, + WrappedGetValidatedQuery +> = createWrapperFunction(_getValidatedQuery) export const getRouterParams = createWrapperFunction(_getRouterParams) export const getRouterParam = createWrapperFunction(_getRouterParam) -export const getValidatedRouterParams = createWrapperFunction( - _getValidatedRouterParams, -) +type WrappedGetValidatedRouterParams = < + T, + TEventInput = InferEventInput<'routerParams', H3Event, T>, +>( + ...args: Tail< + Parameters> + > +) => ReturnType> +export const getValidatedRouterParams: PrependOverload< + typeof _getValidatedRouterParams, + WrappedGetValidatedRouterParams +> = createWrapperFunction(_getValidatedRouterParams) export const assertMethod = createWrapperFunction(_assertMethod) export const getRequestHeaders = createWrapperFunction(_getRequestHeaders) export const getRequestHeader = createWrapperFunction(_getRequestHeader) @@ -301,11 +353,23 @@ export const getResponseStatusText = createWrapperFunction( export const getResponseHeaders = createWrapperFunction(_getResponseHeaders) export const getResponseHeader = createWrapperFunction(_getResponseHeader) export const setResponseHeaders = createWrapperFunction(_setResponseHeaders) -export const setResponseHeader = createWrapperFunction(_setResponseHeader) +type WrappedSetResponseHeader = ( + ...args: Tail>> +) => ReturnType> +export const setResponseHeader: PrependOverload< + typeof _setResponseHeader, + WrappedSetResponseHeader +> = createWrapperFunction(_setResponseHeader) export const appendResponseHeaders = createWrapperFunction( _appendResponseHeaders, ) -export const appendResponseHeader = createWrapperFunction(_appendResponseHeader) +type WrappedAppendResponseHeader = ( + ...args: Tail>> +) => ReturnType> +export const appendResponseHeader: PrependOverload< + typeof _appendResponseHeader, + WrappedAppendResponseHeader +> = createWrapperFunction(_appendResponseHeader) export const defaultContentType = createWrapperFunction(_defaultContentType) export const sendRedirect = createWrapperFunction(_sendRedirect) export const sendStream = createWrapperFunction(_sendStream) @@ -313,7 +377,17 @@ export const writeEarlyHints = createWrapperFunction(_writeEarlyHints) export const sendError = createWrapperFunction(_sendError) export const sendProxy = createWrapperFunction(_sendProxy) export const proxyRequest = createWrapperFunction(_proxyRequest) -export const fetchWithEvent = createWrapperFunction(_fetchWithEvent) +type WrappedFetchWithEvent = < + T = unknown, + TResponse = any, + TFetch extends (req: RequestInfo | URL, opts?: any) => any = typeof fetch, +>( + ...args: Tail>> +) => ReturnType> +export const fetchWithEvent: PrependOverload< + typeof _fetchWithEvent, + WrappedFetchWithEvent +> = createWrapperFunction(_fetchWithEvent) export const getProxyRequestHeaders = createWrapperFunction( _getProxyRequestHeaders, ) @@ -323,10 +397,37 @@ export const getCookie = createWrapperFunction(_getCookie) export const setCookie = createWrapperFunction(_setCookie) export const deleteCookie = createWrapperFunction(_deleteCookie) export const useBase = createWrapperFunction(_useBase) -export const useSession = createWrapperFunction(_useSession) -export const getSession = createWrapperFunction(_getSession) -export const updateSession = createWrapperFunction(_updateSession) -export const sealSession = createWrapperFunction(_sealSession) +// not exported :( +type SessionDataT = Record +type WrappedUseSession = ( + ...args: Tail>> +) => ReturnType> +// we need to `as` these because the WrapFunction doesn't work for them +// because they also accept CompatEvent instead of H3Event +export const useSession = createWrapperFunction(_useSession) as PrependOverload< + typeof _useSession, + WrappedUseSession +> +type WrappedGetSession = ( + ...args: Tail>> +) => ReturnType> +export const getSession = createWrapperFunction(_getSession) as PrependOverload< + typeof _getSession, + WrappedGetSession +> +type WrappedUpdateSession = ( + ...args: Tail>> +) => ReturnType> +export const updateSession: PrependOverload< + typeof _updateSession, + WrappedUpdateSession +> = createWrapperFunction(_updateSession) +type WrappedSealSession = ( + ...args: Tail>> +) => ReturnType> +export const sealSession = createWrapperFunction( + _sealSession, +) as PrependOverload export const unsealSession = createWrapperFunction(_unsealSession) export const clearSession = createWrapperFunction(_clearSession) export const handleCacheHeaders = createWrapperFunction(_handleCacheHeaders) @@ -336,9 +437,19 @@ export const appendCorsPreflightHeaders = createWrapperFunction( _appendCorsPreflightHeaders, ) export const sendWebResponse = createWrapperFunction(_sendWebResponse) -export const appendHeader = createWrapperFunction(_appendHeader) +type WrappedAppendHeader = ( + ...args: Tail>> +) => ReturnType> +export const appendHeader: PrependOverload< + typeof _appendHeader, + WrappedAppendHeader +> = createWrapperFunction(_appendHeader) export const appendHeaders = createWrapperFunction(_appendHeaders) -export const setHeader = createWrapperFunction(_setHeader) +type WrappedSetHeader = ( + ...args: Tail>> +) => ReturnType> +export const setHeader: PrependOverload = + createWrapperFunction(_setHeader) export const setHeaders = createWrapperFunction(_setHeaders) export const getHeader = createWrapperFunction(_getHeader) export const getHeaders = createWrapperFunction(_getHeaders) @@ -350,7 +461,16 @@ export const readFormData = createWrapperFunction(_readFormData) export const readMultipartFormData = createWrapperFunction( _readMultipartFormData, ) -export const readValidatedBody = createWrapperFunction(_readValidatedBody) +type WrappedReadValidatedBody = < + T, + TEventInput = InferEventInput<'body', H3Event, T>, +>( + ...args: Tail>> +) => ReturnType> +export const readValidatedBody: PrependOverload< + typeof _readValidatedBody, + WrappedReadValidatedBody +> = createWrapperFunction(_readValidatedBody) export const removeResponseHeader = createWrapperFunction(_removeResponseHeader) export const getContext = createWrapperFunction(_getContext) export const setContext = createWrapperFunction(_setContext) @@ -374,7 +494,7 @@ function getNitroAsyncContext() { } export function getEvent() { - return (getNitroAsyncContext().use() as any).event + return (getNitroAsyncContext().use() as any).event as H3Event | undefined } export async function handleHTTPEvent(event: H3Event) { From 39af8f3215ff3fa3463f28423416637478fde266 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 30 Jan 2025 21:24:25 +0000 Subject: [PATCH 2/2] assert inside getEvent to guarantee it returns an event --- packages/start-server/src/index.tsx | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/start-server/src/index.tsx b/packages/start-server/src/index.tsx index 44060c530b..b5e462b6f9 100644 --- a/packages/start-server/src/index.tsx +++ b/packages/start-server/src/index.tsx @@ -233,7 +233,9 @@ function getHTTPEvent() { export const HTTPEventSymbol = Symbol('$HTTPEvent') -export function isEvent(obj: any) { +export function isEvent( + obj: any, +): obj is H3Event | { [HTTPEventSymbol]: H3Event } { return ( typeof obj === 'object' && (obj instanceof H3Event || @@ -264,23 +266,17 @@ function createWrapperFunction) => any>( h3Function: TFn, ): WrapFunction { return function (...args: Array) { - let event = args[0] + const event = args[0] if (!isEvent(event)) { if (!(globalThis as any).app.config.server.experimental?.asyncContext) { throw new Error( 'AsyncLocalStorage was not enabled. Use the `server.experimental.asyncContext: true` option in your app configuration to enable it. Or, pass the instance of HTTPEvent that you have as the first argument to the function.', ) } - event = getHTTPEvent() - if (!event) { - throw new Error( - `No HTTPEvent found in AsyncLocalStorage. Make sure you are using the function within the server runtime.`, - ) - } - args.unshift(event) + args.unshift(getHTTPEvent()) } else { args[0] = - event instanceof H3Event || event.__is_event__ + event instanceof H3Event || (event as any).__is_event__ ? event : event[HTTPEventSymbol] } @@ -494,7 +490,15 @@ function getNitroAsyncContext() { } export function getEvent() { - return (getNitroAsyncContext().use() as any).event as H3Event | undefined + const event = (getNitroAsyncContext().use() as any).event as + | H3Event + | undefined + if (!event) { + throw new Error( + `No HTTPEvent found in AsyncLocalStorage. Make sure you are using the function within the server runtime.`, + ) + } + return event } export async function handleHTTPEvent(event: H3Event) {