From ee0d5e931e53d37487f562ea2c9a3e7a8e3ce922 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 7 Aug 2023 12:59:24 +0100 Subject: [PATCH 01/18] feat: add `validate` handler to object syntax --- src/event/utils.ts | 15 +++++++++++---- src/types.ts | 9 ++++++--- test/types.test-d.ts | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index f0e57ec8..7ee70472 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -10,11 +10,13 @@ import type { H3Event } from "./event"; export function defineEventHandler< Request extends EventHandlerRequest = EventHandlerRequest, Response = EventHandlerResponse, + ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, >( handler: | EventHandler - | EventHandlerObject, -): EventHandler; + | EventHandlerObject, +): EventHandler; // TODO: remove when appropriate // This signature provides backwards compatibility with previous signature where first generic was return type export function defineEventHandler< @@ -32,11 +34,13 @@ export function defineEventHandler< export function defineEventHandler< Request extends EventHandlerRequest, Response = EventHandlerResponse, + ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, >( handler: | EventHandler - | EventHandlerObject, -): EventHandler { + | EventHandlerObject, +): EventHandler { // Function Syntax if (typeof handler === "function") { return Object.assign(handler, { __is_handler__: true }); @@ -49,6 +53,9 @@ export function defineEventHandler< } async function _callHandler(event: H3Event, handler: EventHandlerObject) { + if (handler.validate) { + await handler.validate(event); + } if (handler.before) { for (const hook of handler.before) { await hook(event); diff --git a/src/types.ts b/src/types.ts index 32758386..3e6b59a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,11 +65,14 @@ export interface EventHandler< export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, Response extends EventHandlerResponse = EventHandlerResponse, + ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, > = { - handler: EventHandler; - before?: ((event: H3Event) => void | Promise)[]; + validate?: ValidateFunction; + handler: EventHandler; + before?: ((event: H3Event) => void | Promise)[]; after?: (( - event: H3Event, + event: H3Event, response: { body?: Response }, ) => void | Promise)[]; }; diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 68c346fe..fb238ad2 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -34,6 +34,27 @@ describe("types", () => { foo: string; }>(); }); + it("object syntax definition with inferred validation", async () => { + const handler = eventHandler({ + async validate(event) { + await Promise.resolve(); + expectTypeOf(event).toEqualTypeOf(); + + return event as H3Event<{ body: { id: string } }>; + }, + async handler(event) { + expectTypeOf(event).toEqualTypeOf>(); + + const body = await readBody(event); + expectTypeOf(body).toEqualTypeOf<{ id: string }>(); + + return { foo: "bar" }; + }, + }); + expectTypeOf(await handler({} as H3Event)).toEqualTypeOf<{ + foo: string; + }>(); + }); it("return type (inferred)", () => { const handler = eventHandler(() => { return { From d9ae5c6a80a729fb33b70e6af761d3a0afd8381a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:02:49 +0000 Subject: [PATCH 02/18] chore: apply automated lint fixes --- src/event/utils.ts | 24 ++++++++++++++++++++---- src/types.ts | 12 ++++++++++-- test/types.test-d.ts | 4 +++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 7ee70472..11f4023d 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -10,8 +10,16 @@ import type { H3Event } from "./event"; export function defineEventHandler< Request extends EventHandlerRequest = EventHandlerRequest, Response = EventHandlerResponse, - ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, + ValidateFunction extends ( + event: H3Event, + ) => H3Event | Promise> = ( + event: H3Event, + ) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited< + ReturnType + > extends H3Event + ? R + : Request, >( handler: | EventHandler @@ -34,8 +42,16 @@ export function defineEventHandler< export function defineEventHandler< Request extends EventHandlerRequest, Response = EventHandlerResponse, - ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, + ValidateFunction extends ( + event: H3Event, + ) => H3Event | Promise> = ( + event: H3Event, + ) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited< + ReturnType + > extends H3Event + ? R + : Request, >( handler: | EventHandler diff --git a/src/types.ts b/src/types.ts index 3e6b59a6..16bfcee3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,8 +65,16 @@ export interface EventHandler< export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, Response extends EventHandlerResponse = EventHandlerResponse, - ValidateFunction extends (event: H3Event) => H3Event | Promise> = (event: H3Event) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited> extends H3Event ? R : Request, + ValidateFunction extends ( + event: H3Event, + ) => H3Event | Promise> = ( + event: H3Event, + ) => H3Event | Promise>, + ValidatedRequest extends EventHandlerRequest = Awaited< + ReturnType + > extends H3Event + ? R + : Request, > = { validate?: ValidateFunction; handler: EventHandler; diff --git a/test/types.test-d.ts b/test/types.test-d.ts index fb238ad2..09f9c887 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -43,7 +43,9 @@ describe("types", () => { return event as H3Event<{ body: { id: string } }>; }, async handler(event) { - expectTypeOf(event).toEqualTypeOf>(); + expectTypeOf(event).toEqualTypeOf< + H3Event<{ body: { id: string } }> + >(); const body = await readBody(event); expectTypeOf(body).toEqualTypeOf<{ id: string }>(); From 4d69e746193ea15b69e510cf4e12a8c05963974b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 7 Aug 2023 13:36:21 +0100 Subject: [PATCH 03/18] fix: don't default to any --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 16bfcee3..bc83f556 100644 --- a/src/types.ts +++ b/src/types.ts @@ -69,7 +69,7 @@ export type EventHandlerObject< event: H3Event, ) => H3Event | Promise> = ( event: H3Event, - ) => H3Event | Promise>, + ) => H3Event | Promise>, ValidatedRequest extends EventHandlerRequest = Awaited< ReturnType > extends H3Event From 229f5c60a2990ec79c45023f1f5d37df26a4f777 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 7 Aug 2023 13:36:54 +0100 Subject: [PATCH 04/18] fix: ... or there --- src/event/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 11f4023d..e1fb5441 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -14,7 +14,7 @@ export function defineEventHandler< event: H3Event, ) => H3Event | Promise> = ( event: H3Event, - ) => H3Event | Promise>, + ) => H3Event | Promise>, ValidatedRequest extends EventHandlerRequest = Awaited< ReturnType > extends H3Event @@ -46,7 +46,7 @@ export function defineEventHandler< event: H3Event, ) => H3Event | Promise> = ( event: H3Event, - ) => H3Event | Promise>, + ) => H3Event | Promise>, ValidatedRequest extends EventHandlerRequest = Awaited< ReturnType > extends H3Event From a019ad4c433f6440959cb6e5783c60343b2fde4b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 18:52:38 +0200 Subject: [PATCH 05/18] refactor: extract EventValidateFunction and EventValidatedRequest type utils --- src/event/utils.ts | 48 +++++++++++++++++++++++----------------------- src/types.ts | 32 +++++++++++++++++-------------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index e1fb5441..628a3c8c 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -4,27 +4,28 @@ import type { EventHandlerRequest, EventHandlerResponse, EventHandlerObject, + EventValidateFunction, + EventValidatedRequest, } from "../types"; import type { H3Event } from "./event"; export function defineEventHandler< Request extends EventHandlerRequest = EventHandlerRequest, Response = EventHandlerResponse, - ValidateFunction extends ( - event: H3Event, - ) => H3Event | Promise> = ( - event: H3Event, - ) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited< - ReturnType - > extends H3Event - ? R - : Request, + _ValidateFunction extends + EventValidateFunction = EventValidateFunction, + _ValidatedRequest extends + EventValidatedRequest<_ValidateFunction> = EventValidatedRequest<_ValidateFunction>, >( handler: | EventHandler - | EventHandlerObject, -): EventHandler; + | EventHandlerObject< + Request, + Response, + _ValidateFunction, + _ValidatedRequest + >, +): EventHandler<_ValidatedRequest, Response>; // TODO: remove when appropriate // This signature provides backwards compatibility with previous signature where first generic was return type export function defineEventHandler< @@ -42,21 +43,20 @@ export function defineEventHandler< export function defineEventHandler< Request extends EventHandlerRequest, Response = EventHandlerResponse, - ValidateFunction extends ( - event: H3Event, - ) => H3Event | Promise> = ( - event: H3Event, - ) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited< - ReturnType - > extends H3Event - ? R - : Request, + _ValidateFunction extends + EventValidateFunction = EventValidateFunction, + _ValidatedRequest extends + EventValidatedRequest<_ValidateFunction> = EventValidatedRequest<_ValidateFunction>, >( handler: | EventHandler - | EventHandlerObject, -): EventHandler { + | EventHandlerObject< + Request, + Response, + _ValidateFunction, + _ValidatedRequest + >, +): EventHandler<_ValidatedRequest, Response> { // Function Syntax if (typeof handler === "function") { return Object.assign(handler, { __is_handler__: true }); diff --git a/src/types.ts b/src/types.ts index 5311781c..e7a23f98 100644 --- a/src/types.ts +++ b/src/types.ts @@ -69,25 +69,29 @@ export interface EventHandler< (event: H3Event): Response; } +export type EventValidateFunction< + Request extends EventHandlerRequest = EventHandlerRequest, +> = (event: H3Event) => H3Event | Promise>; + +export type EventValidatedRequest< + ValidateFunction extends EventValidateFunction, +> = Awaited> extends H3Event + ? R + : Request; + export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, Response extends EventHandlerResponse = EventHandlerResponse, - ValidateFunction extends ( - event: H3Event, - ) => H3Event | Promise> = ( - event: H3Event, - ) => H3Event | Promise>, - ValidatedRequest extends EventHandlerRequest = Awaited< - ReturnType - > extends H3Event - ? R - : Request, + _ValidateFunction extends + EventValidateFunction = EventValidateFunction, + _ValidatedRequest extends + EventValidatedRequest<_ValidateFunction> = EventValidatedRequest<_ValidateFunction>, > = { - validate?: ValidateFunction; - handler: EventHandler; - before?: ((event: H3Event) => void | Promise)[]; + validate?: _ValidateFunction; + handler: EventHandler<_ValidatedRequest, Response>; + before?: ((event: H3Event<_ValidatedRequest>) => void | Promise)[]; after?: (( - event: H3Event, + event: H3Event<_ValidatedRequest>, response: { body?: Response }, ) => void | Promise)[]; }; From 6d0a4c2e3fee36768071829de65ef945aad0a214 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:05:49 +0200 Subject: [PATCH 06/18] add `validateEvent` --- src/event/utils.ts | 14 +++++++++++++ test/types.test-d.ts | 47 +++++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 628a3c8c..1a0e3fc7 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -163,3 +163,17 @@ export function defineLazyEventHandler( }) as Awaited>; } export const lazyEventHandler = defineLazyEventHandler; + +export async function validateEvent< + Request extends EventHandlerRequest = EventHandlerRequest, + _ValidateFunction extends + EventValidateFunction = EventValidateFunction, + _ValidatedRequest extends + EventValidatedRequest<_ValidateFunction> = EventValidatedRequest<_ValidateFunction>, +>( + event: H3Event, + validate: _ValidateFunction, +): Promise> { + await validate(event); + return event as H3Event<_ValidatedRequest>; +} diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 09f9c887..fc568b30 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -7,6 +7,7 @@ import { readBody, readValidatedBody, getValidatedQuery, + validateEvent, } from "../src"; describe("types", () => { @@ -34,12 +35,30 @@ describe("types", () => { foo: string; }>(); }); - it("object syntax definition with inferred validation", async () => { + + it("return type (inferred)", () => { + const handler = eventHandler(() => { + return { + foo: "bar", + }; + }); + const response = handler({} as H3Event); + expectTypeOf(response).toEqualTypeOf<{ foo: string }>(); + }); + + it("return type (simple generic)", () => { + const handler = eventHandler(() => { + return ""; + }); + const response = handler({} as H3Event); + expectTypeOf(response).toEqualTypeOf(); + }); + + it("inferred validation", async () => { const handler = eventHandler({ async validate(event) { await Promise.resolve(); expectTypeOf(event).toEqualTypeOf(); - return event as H3Event<{ body: { id: string } }>; }, async handler(event) { @@ -57,22 +76,18 @@ describe("types", () => { foo: string; }>(); }); - it("return type (inferred)", () => { - const handler = eventHandler(() => { - return { - foo: "bar", - }; - }); - const response = handler({} as H3Event); - expectTypeOf(response).toEqualTypeOf<{ foo: string }>(); - }); + }); - it("return type (simple generic)", () => { - const handler = eventHandler(() => { - return ""; + describe("validateEvent", () => { + it("inferred validation", () => { + eventHandler(async (_event) => { + const event = await validateEvent(_event, async (event) => { + await Promise.resolve(); + expectTypeOf(event).toEqualTypeOf(); + return event as H3Event<{ body: { id: string } }>; + }); + expectTypeOf(event).toEqualTypeOf>(); }); - const response = handler({} as H3Event); - expectTypeOf(response).toEqualTypeOf(); }); }); From 1c5e653535f3af211c5582d61de326d46b46d7ae Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:20:44 +0200 Subject: [PATCH 07/18] add tests --- test/validate.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/validate.test.ts b/test/validate.test.ts index b7ae8f83..88506c11 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -9,6 +9,9 @@ import { readValidatedBody, getValidatedQuery, ValidateFunction, + H3Event, + createError, + validateEvent, } from "../src"; // Custom validator @@ -142,4 +145,44 @@ describe("Validate", () => { }); }); }); + + describe("object syntax validate", () => { + it("works", async () => { + app.use( + eventHandler({ + validate: (event) => { + if (event.path === "/invalid") { + throw createError({ message: "Invalid path", status: 400 }); + } + return undefined as any; + }, + handler: () => { + return "ok"; + }, + }), + ); + const res = await request.get("/invalid"); + expect(res.text).include("Invalid path"); + expect(res.status).toEqual(400); + }); + }); + + describe("validateEvent", () => { + it("works", async () => { + app.use( + eventHandler(async (_event) => { + await validateEvent(_event, (event) => { + if (event.path === "/invalid") { + throw createError({ message: "Invalid path", status: 400 }); + } + return undefined as any; + }); + return "ok"; + }), + ); + const res = await request.get("/invalid"); + expect(res.text).include("Invalid path"); + expect(res.status).toEqual(400); + }); + }); }); From 64a07e1ac3f536767740d7425feb75cb19289543 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:23:10 +0200 Subject: [PATCH 08/18] refactor: reuse validateEvent --- src/event/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 1a0e3fc7..3800a7c4 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -70,7 +70,7 @@ export function defineEventHandler< async function _callHandler(event: H3Event, handler: EventHandlerObject) { if (handler.validate) { - await handler.validate(event); + await validateEvent(event, handler.validate); } if (handler.before) { for (const hook of handler.before) { From 5c997ccfa1d6e6f87077d6ff9e6f06df3f774bd2 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:29:03 +0200 Subject: [PATCH 09/18] reusable syntax with defineEventValidateFunction --- src/event/utils.ts | 6 ++++++ test/validate.test.ts | 32 +++++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 3800a7c4..136d8ab1 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -177,3 +177,9 @@ export async function validateEvent< await validate(event); return event as H3Event<_ValidatedRequest>; } + +export function defineEventValidateFunction< + _ValidateFunction extends EventValidateFunction = EventValidateFunction, +>(validate: _ValidateFunction): _ValidateFunction { + return validate; +} diff --git a/test/validate.test.ts b/test/validate.test.ts index 88506c11..be55cd8a 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -12,6 +12,7 @@ import { H3Event, createError, validateEvent, + defineEventValidateFunction, } from "../src"; // Custom validator @@ -146,16 +147,18 @@ describe("Validate", () => { }); }); - describe("object syntax validate", () => { - it("works", async () => { + describe("event validation", () => { + const eventValidator = defineEventValidateFunction((event) => { + if (event.path === "/invalid") { + throw createError({ message: "Invalid path", status: 400 }); + } + return undefined as any; + }); + + it("object syntax validate", async () => { app.use( eventHandler({ - validate: (event) => { - if (event.path === "/invalid") { - throw createError({ message: "Invalid path", status: 400 }); - } - return undefined as any; - }, + validate: eventValidator, handler: () => { return "ok"; }, @@ -164,25 +167,20 @@ describe("Validate", () => { const res = await request.get("/invalid"); expect(res.text).include("Invalid path"); expect(res.status).toEqual(400); + expect((await request.get("/")).text).toBe("ok"); }); - }); - describe("validateEvent", () => { - it("works", async () => { + it("validateEvent", async () => { app.use( eventHandler(async (_event) => { - await validateEvent(_event, (event) => { - if (event.path === "/invalid") { - throw createError({ message: "Invalid path", status: 400 }); - } - return undefined as any; - }); + await validateEvent(_event, eventValidator); return "ok"; }), ); const res = await request.get("/invalid"); expect(res.text).include("Invalid path"); expect(res.status).toEqual(400); + expect((await request.get("/")).text).toBe("ok"); }); }); }); From 26ff85655691f3128ef0295c6db48c3e7b785bbc Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:29:39 +0200 Subject: [PATCH 10/18] rename to defineEventValidator --- src/event/utils.ts | 2 +- test/validate.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index 136d8ab1..a44622fb 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -178,7 +178,7 @@ export async function validateEvent< return event as H3Event<_ValidatedRequest>; } -export function defineEventValidateFunction< +export function defineEventValidator< _ValidateFunction extends EventValidateFunction = EventValidateFunction, >(validate: _ValidateFunction): _ValidateFunction { return validate; diff --git a/test/validate.test.ts b/test/validate.test.ts index be55cd8a..7add4b93 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -12,7 +12,7 @@ import { H3Event, createError, validateEvent, - defineEventValidateFunction, + defineEventValidator, } from "../src"; // Custom validator @@ -148,7 +148,7 @@ describe("Validate", () => { }); describe("event validation", () => { - const eventValidator = defineEventValidateFunction((event) => { + const eventValidator = defineEventValidator((event) => { if (event.path === "/invalid") { throw createError({ message: "Invalid path", status: 400 }); } From b63be70238752d62ce085267544f2fb7a21b1e88 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 8 Aug 2023 19:31:01 +0200 Subject: [PATCH 11/18] test: remove unused import --- test/validate.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/validate.test.ts b/test/validate.test.ts index 7add4b93..6858f1b8 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -9,7 +9,6 @@ import { readValidatedBody, getValidatedQuery, ValidateFunction, - H3Event, createError, validateEvent, defineEventValidator, From 0e42bd517924286b02a415619b6de7a13314bb20 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 9 Aug 2023 11:44:16 +0100 Subject: [PATCH 12/18] fix: support validate functions without referencing `H3Event` --- src/types.ts | 6 ++++-- test/types.test-d.ts | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index e7a23f98..8f27b540 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,13 +71,15 @@ export interface EventHandler< export type EventValidateFunction< Request extends EventHandlerRequest = EventHandlerRequest, -> = (event: H3Event) => H3Event | Promise>; +> = (event: H3Event) => H3Event | Promise> | Record; export type EventValidatedRequest< ValidateFunction extends EventValidateFunction, > = Awaited> extends H3Event ? R - : Request; + : Awaited> extends EventHandlerRequest + ? Awaited> + : Request; export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, diff --git a/test/types.test-d.ts b/test/types.test-d.ts index fc568b30..ec70977b 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -89,6 +89,29 @@ describe("types", () => { expectTypeOf(event).toEqualTypeOf>(); }); }); + + it("inferred validation without H3Event type requirement", async () => { + const handler = eventHandler({ + async validate(event) { + await Promise.resolve(); + expectTypeOf(event).toEqualTypeOf(); + return {} as { body: { id: string } } + }, + async handler(event) { + expectTypeOf(event).toEqualTypeOf< + H3Event<{ body: { id: string } }> + >(); + + const body = await readBody(event); + expectTypeOf(body).toEqualTypeOf<{ id: string }>(); + + return { foo: "bar" }; + }, + }); + expectTypeOf(await handler({} as H3Event)).toEqualTypeOf<{ + foo: string; + }>(); + }); }); describe("readBody", () => { From ba6779bd9a05a18a1c539ddde8a40a850431aea5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:45:00 +0000 Subject: [PATCH 13/18] chore: apply automated lint fixes --- src/types.ts | 8 +++++--- test/types.test-d.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index 8f27b540..c6ad27b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,15 +71,17 @@ export interface EventHandler< export type EventValidateFunction< Request extends EventHandlerRequest = EventHandlerRequest, -> = (event: H3Event) => H3Event | Promise> | Record; +> = ( + event: H3Event, +) => H3Event | Promise> | Record; export type EventValidatedRequest< ValidateFunction extends EventValidateFunction, > = Awaited> extends H3Event ? R : Awaited> extends EventHandlerRequest - ? Awaited> - : Request; + ? Awaited> + : Request; export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, diff --git a/test/types.test-d.ts b/test/types.test-d.ts index ec70977b..6a1eac7e 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -95,7 +95,7 @@ describe("types", () => { async validate(event) { await Promise.resolve(); expectTypeOf(event).toEqualTypeOf(); - return {} as { body: { id: string } } + return {} as { body: { id: string } }; }, async handler(event) { expectTypeOf(event).toEqualTypeOf< From 11dac2163c3a54bb70b4cf67c3813a32433f5134 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 11 Aug 2023 11:46:36 +0100 Subject: [PATCH 14/18] fix: call validate after `before hooks --- src/event/utils.ts | 6 +++--- src/types.ts | 2 +- test/types.test-d.ts | 12 +++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index a44622fb..ceea3cab 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -69,9 +69,6 @@ export function defineEventHandler< } async function _callHandler(event: H3Event, handler: EventHandlerObject) { - if (handler.validate) { - await validateEvent(event, handler.validate); - } if (handler.before) { for (const hook of handler.before) { await hook(event); @@ -80,6 +77,9 @@ async function _callHandler(event: H3Event, handler: EventHandlerObject) { } } } + if (handler.validate) { + await validateEvent(event, handler.validate); + } const body = await handler.handler(event); const response = { body }; if (handler.after) { diff --git a/src/types.ts b/src/types.ts index c6ad27b8..c4c73d48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -93,7 +93,7 @@ export type EventHandlerObject< > = { validate?: _ValidateFunction; handler: EventHandler<_ValidatedRequest, Response>; - before?: ((event: H3Event<_ValidatedRequest>) => void | Promise)[]; + before?: ((event: H3Event) => void | Promise)[]; after?: (( event: H3Event<_ValidatedRequest>, response: { body?: Response }, diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 6a1eac7e..f354c96c 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -8,6 +8,7 @@ import { readValidatedBody, getValidatedQuery, validateEvent, + EventHandlerRequest, } from "../src"; describe("types", () => { @@ -16,11 +17,11 @@ describe("types", () => { const handler = eventHandler({ before: [ (event) => { - expectTypeOf(event).toEqualTypeOf(); + expectTypeOf(event).toEqualTypeOf>(); }, ], async handler(event) { - expectTypeOf(event).toEqualTypeOf(); + expectTypeOf(event).toEqualTypeOf>(); const body = await readBody(event); // TODO: Default to unknown in next major version @@ -56,9 +57,14 @@ describe("types", () => { it("inferred validation", async () => { const handler = eventHandler({ + before: [ + (event) => { + expectTypeOf(event).toEqualTypeOf>(); + }, + ], async validate(event) { await Promise.resolve(); - expectTypeOf(event).toEqualTypeOf(); + expectTypeOf(event).toEqualTypeOf>(); return event as H3Event<{ body: { id: string } }>; }, async handler(event) { From 5ec877cc0d487f1512cbf8487cf1995cf1642b91 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Fri, 11 Aug 2023 11:56:10 +0100 Subject: [PATCH 15/18] fix: correct inferred validated type --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index c4c73d48..3b181c5f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -81,7 +81,7 @@ export type EventValidatedRequest< ? R : Awaited> extends EventHandlerRequest ? Awaited> - : Request; + : EventHandlerRequest; export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, From 6f2ea16b3434347690502575e5f88179e8fec025 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sat, 12 Aug 2023 20:56:37 +0100 Subject: [PATCH 16/18] feat: merge return type with `context` and preserve body/query --- src/event/event.ts | 9 ++++++--- src/event/utils.ts | 24 +++++++++++++++++++++--- src/types.ts | 22 +++++++++++++++++++--- src/utils/body.ts | 5 ++--- src/utils/request.ts | 4 ++++ src/utils/symbols.ts | 3 +++ test/types.test-d.ts | 9 +++++++-- 7 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 src/utils/symbols.ts diff --git a/src/event/event.ts b/src/event/event.ts index a816976f..8bca5600 100644 --- a/src/event/event.ts +++ b/src/event/event.ts @@ -19,13 +19,16 @@ export interface NodeEventContext { export class H3Event< // eslint-disable-next-line @typescript-eslint/no-unused-vars _RequestT extends EventHandlerRequest = EventHandlerRequest, + Context = unknown, > implements Pick { "__is_event__" = true; // Context node: NodeEventContext; - context: H3EventContext = {}; + context = {} as unknown extends Context + ? H3EventContext + : H3EventContext & Context; // Request _request: Request | undefined; @@ -56,7 +59,7 @@ export class H3Event< get url() { if (!this._url) { - this._url = getRequestURL(this); + this._url = getRequestURL(this as H3Event); } return this._url; } @@ -132,7 +135,7 @@ export class H3Event< respondWith(response: Response | PromiseLike): Promise { return Promise.resolve(response).then((_response) => - sendWebResponse(this, _response), + sendWebResponse(this as H3Event, _response), ); } diff --git a/src/event/utils.ts b/src/event/utils.ts index ceea3cab..b8d1c2bf 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -1,3 +1,4 @@ +import { ParsedBodySymbol, ParsedQuerySymbol } from "../utils/symbols"; import type { EventHandler, LazyEventHandler, @@ -63,12 +64,15 @@ export function defineEventHandler< } // Object Syntax const _handler: EventHandler = (event) => { - return _callHandler(event, handler); + return _callHandler(event, handler as EventHandlerObject); }; return Object.assign(_handler, { __is_handler__: true }); } -async function _callHandler(event: H3Event, handler: EventHandlerObject) { +async function _callHandler( + event: H3Event, + handler: EventHandlerObject, +) { if (handler.before) { for (const hook of handler.before) { await hook(event); @@ -78,7 +82,21 @@ async function _callHandler(event: H3Event, handler: EventHandlerObject) { } } if (handler.validate) { - await validateEvent(event, handler.validate); + const res = (await validateEvent(event, handler.validate)) as Record< + string, + any + >; + if (res && typeof res === "object") { + for (const property in res) { + if (property === "body") { + (event.node.req as any)[ParsedBodySymbol] = res.body; + } else if ("query" in res) { + (event.node.req as any)[ParsedQuerySymbol] = res.query; + } else { + event.context[property] = res[property]; + } + } + } } const body = await handler.handler(event); const response = { body }; diff --git a/src/types.ts b/src/types.ts index 3b181c5f..b2bfd960 100644 --- a/src/types.ts +++ b/src/types.ts @@ -57,9 +57,13 @@ export interface EventHandlerRequest { export type InferEventInput< Key extends keyof EventHandlerRequest, - Event extends H3Event, + Event extends H3Event, T, -> = void extends T ? (Event extends H3Event ? E[Key] : never) : T; +> = unknown extends T + ? Event extends H3Event + ? E[Key] + : never + : T; export interface EventHandler< Request extends EventHandlerRequest = EventHandlerRequest, @@ -83,6 +87,18 @@ export type EventValidatedRequest< ? Awaited> : EventHandlerRequest; +export type Simplify = TType extends any[] | Date + ? TType + : { [K in keyof TType]: TType[K] }; + +export type EventFromValidatedRequest = + keyof Request extends "body" | "query" + ? H3Event + : H3Event< + Simplify>, + Simplify> + >; + export type EventHandlerObject< Request extends EventHandlerRequest = EventHandlerRequest, Response extends EventHandlerResponse = EventHandlerResponse, @@ -92,7 +108,7 @@ export type EventHandlerObject< EventValidatedRequest<_ValidateFunction> = EventValidatedRequest<_ValidateFunction>, > = { validate?: _ValidateFunction; - handler: EventHandler<_ValidatedRequest, Response>; + handler: (event: EventFromValidatedRequest<_ValidatedRequest>) => Response; before?: ((event: H3Event) => void | Promise)[]; after?: (( event: H3Event<_ValidatedRequest>, diff --git a/src/utils/body.ts b/src/utils/body.ts index 21b361fb..56461c0a 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -6,11 +6,10 @@ import { createError } from "../error"; import { parse as parseMultipartData } from "./internal/multipart"; import { assertMethod, getRequestHeader } from "./request"; import { ValidateFunction, validateData } from "./internal/validate"; +import { ParsedBodySymbol, RawBodySymbol } from "./symbols"; export type { MultiPartData } from "./internal/multipart"; -const RawBodySymbol = Symbol.for("h3RawBody"); -const ParsedBodySymbol = Symbol.for("h3ParsedBody"); type InternalRequest = IncomingMessage & { [RawBodySymbol]?: Promise; [ParsedBodySymbol]?: T; @@ -94,7 +93,7 @@ export function readRawBody( export async function readBody< T, - Event extends H3Event = H3Event, + Event extends H3Event = H3Event, _T = InferEventInput<"body", Event, T>, >(event: Event, options: { strict?: boolean } = {}): Promise<_T> { const request = event.node.req as InternalRequest; diff --git a/src/utils/request.ts b/src/utils/request.ts index dcea8a39..8d6f2e50 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -3,12 +3,16 @@ import { createError } from "../error"; import type { HTTPMethod, InferEventInput, RequestHeaders } from "../types"; import type { H3Event } from "../event"; import { validateData, ValidateFunction } from "./internal/validate"; +import { ParsedQuerySymbol } from "./symbols"; export function getQuery< T, Event extends H3Event = H3Event, _T = Exclude, undefined>, >(event: Event): _T { + if (ParsedQuerySymbol in event) { + return event[ParsedQuerySymbol] as _T; + } return _getQuery(event.path || "") as _T; } diff --git a/src/utils/symbols.ts b/src/utils/symbols.ts new file mode 100644 index 00000000..10e4a40a --- /dev/null +++ b/src/utils/symbols.ts @@ -0,0 +1,3 @@ +export const RawBodySymbol = Symbol.for("h3RawBody"); +export const ParsedBodySymbol = Symbol.for("h3ParsedBody"); +export const ParsedQuerySymbol = Symbol.for("h3ParsedQuery"); diff --git a/test/types.test-d.ts b/test/types.test-d.ts index f354c96c..6f9186a2 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -101,15 +101,20 @@ describe("types", () => { async validate(event) { await Promise.resolve(); expectTypeOf(event).toEqualTypeOf(); - return {} as { body: { id: string } }; + return {} as { body: { id: string }; other: true }; }, async handler(event) { expectTypeOf(event).toEqualTypeOf< - H3Event<{ body: { id: string } }> + H3Event<{ body: { id: string } }, { other: true }> >(); + expectTypeOf(event.context.other).toEqualTypeOf(); + // TODO: should be unknown in future version of h3 + expectTypeOf(event.context.body).toBeAny(); + const body = await readBody(event); expectTypeOf(body).toEqualTypeOf<{ id: string }>(); + expectTypeOf(await readBody(event)).toEqualTypeOf(); return { foo: "bar" }; }, From 8029e83ede91ca2d3f0811e68672bc1f57ea998c Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 14 Aug 2023 10:51:14 +0200 Subject: [PATCH 17/18] refactor: merge context assingment to validateEvent and not merged into util cached values --- src/event/utils.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/event/utils.ts b/src/event/utils.ts index b8d1c2bf..f9879a51 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -1,4 +1,3 @@ -import { ParsedBodySymbol, ParsedQuerySymbol } from "../utils/symbols"; import type { EventHandler, LazyEventHandler, @@ -82,21 +81,7 @@ async function _callHandler( } } if (handler.validate) { - const res = (await validateEvent(event, handler.validate)) as Record< - string, - any - >; - if (res && typeof res === "object") { - for (const property in res) { - if (property === "body") { - (event.node.req as any)[ParsedBodySymbol] = res.body; - } else if ("query" in res) { - (event.node.req as any)[ParsedQuerySymbol] = res.query; - } else { - event.context[property] = res[property]; - } - } - } + await validateEvent(event, handler.validate); } const body = await handler.handler(event); const response = { body }; @@ -192,7 +177,10 @@ export async function validateEvent< event: H3Event, validate: _ValidateFunction, ): Promise> { - await validate(event); + const validatedContext = await validate(event); + if (validatedContext && typeof validatedContext === "object") { + Object.assign(event.context, validatedContext); + } return event as H3Event<_ValidatedRequest>; } From 58c0256cfd7432dbb221c8da4c3624edb1ffec89 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 19 Jun 2024 14:11:57 +0200 Subject: [PATCH 18/18] reduce diff --- src/utils/body.ts | 3 ++- src/utils/request.ts | 4 ---- src/utils/symbols.ts | 3 --- 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 src/utils/symbols.ts diff --git a/src/utils/body.ts b/src/utils/body.ts index 35acb02d..11c01d25 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -9,11 +9,12 @@ import { } from "./internal/multipart"; import { assertMethod, getRequestHeader, toWebRequest } from "./request"; import { ValidateFunction, validateData } from "./internal/validate"; -import { ParsedBodySymbol, RawBodySymbol } from "./symbols"; import { hasProp } from "./internal/object"; export type { MultiPartData } from "./internal/multipart"; +const RawBodySymbol = Symbol.for("h3RawBody"); +const ParsedBodySymbol = Symbol.for("h3ParsedBody"); type InternalRequest = IncomingMessage & { [RawBodySymbol]?: Promise; [ParsedBodySymbol]?: T; diff --git a/src/utils/request.ts b/src/utils/request.ts index 2bfa3269..aa96aec1 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -8,7 +8,6 @@ import type { } from "../types"; import type { H3Event } from "../event"; import { validateData, ValidateFunction } from "./internal/validate"; -import { ParsedQuerySymbol } from "./symbols"; import { getRequestWebStream } from "./body"; /** @@ -24,9 +23,6 @@ export function getQuery< Event extends H3Event = H3Event, _T = Exclude, undefined>, >(event: Event): _T { - if (ParsedQuerySymbol in event) { - return event[ParsedQuerySymbol] as _T; - } return _getQuery(event.path || "") as _T; } diff --git a/src/utils/symbols.ts b/src/utils/symbols.ts deleted file mode 100644 index 10e4a40a..00000000 --- a/src/utils/symbols.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const RawBodySymbol = Symbol.for("h3RawBody"); -export const ParsedBodySymbol = Symbol.for("h3ParsedBody"); -export const ParsedQuerySymbol = Symbol.for("h3ParsedQuery");