h3 icon indicating copy to clipboard operation
h3 copied to clipboard

Allow to chain event handlers

Open tobiasdiez opened this issue 2 years ago • 2 comments

It would be nice if event handlers could be chained to create a new event handler that invokes each handler after each other. Something like:

chain(defineCorsHandler(...), eventHandler(event => ...)) // this is an event handler again

This is especially relevant for usage in nuxt, where one usually declares one event handler for a certain route and then in the declaration would like to specify cors options (using h3-cors) in parallel to invoking the actual handler.

Related: https://github.com/unjs/h3/issues/177

tobiasdiez avatar Oct 14 '22 14:10 tobiasdiez

Hi @tobiasdiez. As explained in the relevant issue, h3 utilities like cors, are meant to be called functions on demand and in the body of the event handler as best practices.

Middleware style chaining is also possible using an app stack, createApp().use(corsMiddleware).use(actualHandler) which itself is an event handler if you prefer chaining. Have you tried this pattern?

For the purpose of pre/post handlers, we might improve defineHandler BTW probably by supporting something like this:

defineHandler((event) => {}, { before: [cors], after: [validate] })

Wdyt?

pi0 avatar Oct 14 '22 15:10 pi0

Thanks for your answer!

As explained in the relevant issue, h3 utilities like cors, are meant to be called functions on demand and in the body of the event handler as best practices.

That works, except if you want to chain two event handlers that you don't really have control (in my case the cors handler, and a apollo-h3-server handler that I'm currently developing). It is also not so convenient if the cors handler sometimes should handle the complete request (preflight) but in other cases only modify the response.

Middleware style chaining is also possible using an app stack

Nice suggestion. Is this production ready or is the overhead of createApp too big? (creating an app sounds expensive, but since h3 is so lightweight...)

defineHandler((event) => {}, { before: [cors], after: [validate] })

Sounds like a good idea. I guess if you need multiple handlers you just add them to the array?

defineHandler((event) => {}, { before: [cors, auth], after: [validate] })

tobiasdiez avatar Oct 14 '22 16:10 tobiasdiez

createApp is not working. ERROR: Invalid lazy handler result. It should be a function

notshekhar avatar Nov 23 '22 11:11 notshekhar

@pi0

notshekhar avatar Nov 23 '22 11:11 notshekhar

import { createApp } from "h3"

export default function eventHandlers(...handlers: Array<any>) {
    const app = createApp()
    app.use(handlers.map((func) => defineEventHandler(func)))
    return app
}

ERROR: [nuxt] [request error] [unhandled] [500] Invalid lazy handler result. It should be a function: at ./node_modules/h3/dist/index.mjs:618:17
at async Object.handler (./node_modules/h3/dist/index.mjs:681:19)
at async Server.toNodeHandle (./node_modules/h3/dist/index.mjs:745:7)

notshekhar avatar Nov 23 '22 11:11 notshekhar

@pi0

notshekhar avatar Nov 25 '22 05:11 notshekhar

@pi0 ?

notshekhar avatar Nov 29 '22 17:11 notshekhar

@notshekhar Currently the code for supporting lazy handlers is plaguing the interpretation of functions

The correct way would be something like

handlers.reduce((a, h) => a.use(eventHandler(h)), app);

nopeless avatar Nov 29 '22 21:11 nopeless

@nopeless I tried this and it worked

 handlers.reduce((a, h) => a.use(eventHandler(h)), app).handler

notshekhar avatar Nov 30 '22 10:11 notshekhar

@nopeless I tried this and it worked

 handlers.reduce((a, h) => a.use(eventHandler(h)), app).handler

Without the .handler it doesn't work?

@notshekhar

nopeless avatar Nov 30 '22 10:11 nopeless

@nopeless No it wasn't working without .handler

notshekhar avatar Nov 30 '22 10:11 notshekhar

@nopeless No it wasn't working without .handler

Oh, right, you were using nuxt. Yeah that should be the behaviour. I really think Nuxt should accept Router and App as well

Thanks for responding quickly

nopeless avatar Nov 30 '22 10:11 nopeless

Found a solution that works here: https://github.com/unjs/h3/issues/127

ThomasBerne avatar Dec 07 '22 07:12 ThomasBerne

You can also try this I am using in my project, This function creates an event handler that executes all of the event handlers passed to it in the order they are received. The function returns the result of the first event handler that returns a value that is not undefined. If all of the event handlers return undefined, the function returns undefined as well. The event handler is asynchronous and awaits the result of each event handler before moving on to the next one. The event handler is also wrapped in another event handler called eventHandler.

function eventHandlers(...handlers: Array<Function>) {
  return eventHandler(async function chained(event: any) {
    let res = undefined;
    for (const func of handlers) {
      res = await func(event);
      if (res !== undefined) return res;
    }
    return res;
  });
}

notshekhar avatar Jan 08 '23 20:01 notshekhar

Hi. Sorry for not adding comments on this for long time. I belive this is something worth to support to allow hooks such as authentication, cors and validation to run before and after event handler but chaining them is probably not best solution. Instead we are gonna support an object syntax for event handlers with before and after hooks for fine gained control and also composibility. Please track via #424 and ping us here if you belive chaining handler might have it's own advantages comparing to object syntax to reopen discussion 👍🏼

pi0 avatar Jul 27 '23 16:07 pi0

Is the before/after syntax only a different syntax or will it behave differently? That is, are the following equivalent?

defineEventHandler({ handler: d, before: [a,b,c], after: [e,f] })

chainEventHandlers([a,b,c,d,e,f])

If they are functionality-wise equivalent, then I'm indeed a fan of the before/after syntax as the ofther handlers are most likely defined globally somewhere else and only imported, and in this case this syntax is shorter and puts the focus on the actual event handler.

tobiasdiez avatar Jul 29 '23 11:07 tobiasdiez