From 1df8f02f792385b92a329269174f2b003866dea2 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 2 Aug 2023 09:15:58 +0200 Subject: [PATCH 1/4] feat(app): `onRequest` and `onResponse` global hooks --- src/app.ts | 12 ++++++++++++ test/app.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 97dc842b..8a713623 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,7 +48,10 @@ export interface AppUse { export interface AppOptions { debug?: boolean; + jsonSpace?: number; onError?: (error: H3Error, event: H3Event) => any; + onRequest?: (event: H3Event) => void | Promise; + onResponse?: (event: H3Event, response?: unknown) => void | Promise; } export interface App { @@ -113,6 +116,11 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { // Layer path is the path without the prefix let _layerPath: string; + // Call onRequest hook + if (options.onRequest) { + await options.onRequest(event); + } + for (const layer of stack) { // 1. Remove prefix from path if (layer.route.length > 1) { @@ -140,6 +148,9 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { const handledVal = val !== undefined && handleHandlerResponse(event, val, spacing); if (handledVal !== false) { + if (options.onResponse) { + await options.onResponse(event, val); + } return handledVal; } @@ -148,6 +159,7 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { return; } } + if (!event.handled) { throw createError({ statusCode: 404, diff --git a/test/app.test.ts b/test/app.test.ts index 837fd6a8..6e224adc 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -1,6 +1,6 @@ import { Readable, Transform } from "node:stream"; import supertest, { SuperTest, Test } from "supertest"; -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; import { createApp, toNodeListener, @@ -14,11 +14,19 @@ describe("app", () => { let app: App; let request: SuperTest; + const onRequest = vi.fn(); + const onResponse = vi.fn(); + const onError = vi.fn(); + beforeEach(() => { - app = createApp({ debug: true }); + app = createApp({ debug: true, onError, onRequest, onResponse }); request = supertest(toNodeListener(app)); }); + afterEach(() => { + vi.resetAllMocks(); + }); + it("can return JSON directly", async () => { app.use( "/api", @@ -372,4 +380,15 @@ describe("app", () => { const res = await request.get("/"); expect(res.body).toEqual({ works: 1 }); }); + + it("calls onRequest and onResponse", async () => { + app.use(() => "Hello World!"); + await request.get("/foo"); + + expect(onRequest).toHaveBeenCalledTimes(1); + expect(onRequest.mock.calls[0][0].path).toBe("/foo"); + + expect(onResponse).toHaveBeenCalledTimes(1); + expect(onResponse.mock.calls[0][1]).toBe("Hello World!"); + }); }); From f147e2dd445ec34380bfa2b728a66f5933d35361 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 2 Aug 2023 09:18:28 +0200 Subject: [PATCH 2/4] call response hook on handled cases --- src/app.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app.ts b/src/app.ts index 8a713623..ce3ec681 100644 --- a/src/app.ts +++ b/src/app.ts @@ -156,6 +156,9 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { // Already handled if (event.handled) { + if (options.onResponse) { + await options.onResponse(event, val); + } return; } } @@ -166,6 +169,10 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { statusMessage: `Cannot find any path matching ${event.path || "/"}.`, }); } + + if (options.onResponse) { + await options.onResponse(event, undefined); + } }); } From 544fb303bc1fdd094c7bcf717dfb0d27239535f1 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 2 Aug 2023 09:35:43 +0200 Subject: [PATCH 3/4] use before/after hooks --- src/app.ts | 32 +++++++++++++++++++++----------- test/app.test.ts | 20 +++++++++++++++----- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/app.ts b/src/app.ts index ce3ec681..8ae1aaa5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -51,7 +51,14 @@ export interface AppOptions { jsonSpace?: number; onError?: (error: H3Error, event: H3Event) => any; onRequest?: (event: H3Event) => void | Promise; - onResponse?: (event: H3Event, response?: unknown) => void | Promise; + onBeforeResponse?: ( + event: H3Event, + response: { body?: unknown } + ) => void | Promise; + onAfterResponse?: ( + event: H3Event, + response?: { body?: unknown } + ) => void | Promise; } export interface App { @@ -145,19 +152,22 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { const val = await layer.handler(event); // 5. Try to handle return value - const handledVal = - val !== undefined && handleHandlerResponse(event, val, spacing); - if (handledVal !== false) { - if (options.onResponse) { - await options.onResponse(event, val); + const _response = val === undefined ? undefined : { body: await val }; + if (_response !== undefined) { + if (options.onBeforeResponse) { + await options.onBeforeResponse(event, _response); } - return handledVal; + await handleHandlerResponse(event, val, spacing); + if (options.onAfterResponse) { + await options.onAfterResponse(event, _response); + } + return; } // Already handled if (event.handled) { - if (options.onResponse) { - await options.onResponse(event, val); + if (options.onAfterResponse) { + await options.onAfterResponse(event, _response); } return; } @@ -170,8 +180,8 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { }); } - if (options.onResponse) { - await options.onResponse(event, undefined); + if (options.onAfterResponse) { + await options.onAfterResponse(event, undefined); } }); } diff --git a/test/app.test.ts b/test/app.test.ts index 6e224adc..932b82ca 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -15,11 +15,18 @@ describe("app", () => { let request: SuperTest; const onRequest = vi.fn(); - const onResponse = vi.fn(); + const onBeforeResponse = vi.fn(); + const onAfterResponse = vi.fn(); const onError = vi.fn(); beforeEach(() => { - app = createApp({ debug: true, onError, onRequest, onResponse }); + app = createApp({ + debug: true, + onError, + onRequest, + onBeforeResponse, + onAfterResponse, + }); request = supertest(toNodeListener(app)); }); @@ -382,13 +389,16 @@ describe("app", () => { }); it("calls onRequest and onResponse", async () => { - app.use(() => "Hello World!"); + app.use(() => Promise.resolve("Hello World!")); await request.get("/foo"); expect(onRequest).toHaveBeenCalledTimes(1); expect(onRequest.mock.calls[0][0].path).toBe("/foo"); - expect(onResponse).toHaveBeenCalledTimes(1); - expect(onResponse.mock.calls[0][1]).toBe("Hello World!"); + expect(onBeforeResponse).toHaveBeenCalledTimes(1); + expect(onBeforeResponse.mock.calls[0][1].body).toBe("Hello World!"); + + expect(onAfterResponse).toHaveBeenCalledTimes(1); + expect(onAfterResponse.mock.calls[0][1].body).toBe("Hello World!"); }); }); From 3cb4a438a9b9c688777ee3c15632553a8e8d6001 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 2 Aug 2023 09:38:05 +0200 Subject: [PATCH 4/4] remove unrelated change --- src/app.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 8ae1aaa5..9d7c23ea 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,7 +48,6 @@ export interface AppUse { export interface AppOptions { debug?: boolean; - jsonSpace?: number; onError?: (error: H3Error, event: H3Event) => any; onRequest?: (event: H3Event) => void | Promise; onBeforeResponse?: (