Skip to content

Augment H3Context to improve event.context typing #511

Description

@stafyniaksacha

Describe the feature

With 1.8.0 can now have better typing for query parameters and body content using eventHandler generic.
We also have a proper way to define per event middlewares stack using onRequest

The issue is that it's difficult to type event context.

We can use typescript interface augmentation, but this impact all events in the project:

declare module 'h3' {
  interface H3EventContext {
    auth: { name: string }
  }
}

export default defineEventHandler(async (event) => {
  event.context.auth.name // string
}

Before 1.8.0, we could create a custom eventHandler wrapper and use an extended version of H3Event:

import type { H3Event, H3EventContext } from 'h3'

// custom event
interface H3CustomEvent extends H3Event {
  context: H3EventContext & {
    auth: { name: string }
  }
}

// custom event handler
function defineCustomEventHandler<T>(handler: (event: H3CustomEvent) => T) {
  return defineEventHandler(async (event) => {
    event.context.auth = { name: 'username' }
    eturn handler(event as H3CustomEvent)
  }
})

// usage
export default defineCustomEventHandler(async (event) => {
  event.context.auth.name // string
}
Same wrapper with `1.8.0`
// EventHandlerRequest, EventHandlerResponse, EventHandler are not exported
interface EventHandlerRequest {
  body?: any
  query?: QueryObject
}
type EventHandlerResponse<T = any> = T | Promise<T>

interface EventHandler<
  Request extends EventHandlerRequest = EventHandlerRequest,
  Response extends EventHandlerResponse = EventHandlerResponse,
> {
  __is_handler__?: true
  (event: H3Event<Request>): Response
}

// custom event
interface H3CustomEvent<T extends EventHandlerRequest = EventHandlerRequest>
  extends H3Event<T> {
  context: H3EventContext & {
    auth: { name: string }
  }
}

// custom event handler
export function customEventHandler<
  Request extends EventHandlerRequest = EventHandlerRequest,
  Response extends EventHandlerResponse = EventHandlerResponse,
>(
  handler: EventHandler<
    Request extends EventHandlerRequest ? Request : EventHandlerRequest,
    Request extends EventHandlerRequest ? Response : Request
  >,
): EventHandler<
  Request extends EventHandlerRequest ? Request : EventHandlerRequest,
  Request extends EventHandlerRequest ? Response : Request
> {
  //
  return eventHandler((event) => {
    event.context.auth = { name: 'username' }

    return handler(event as H3CustomEvent<Request>)
  })
}

// usage
export default customEventHandler((event) => {
  event.context.auth.name // string
})

Since usage of generics (and methods overloading), it's harder to wrap.

How about extending context with EventHandlerRequest, same as query and body like:

const auth = defineRequestMiddleware<{
  context: {
    auth: {
      name: string
    }
  }
}>((event) => {
  event.context.auth.name // string
})

export default eventHandler<{
  context: {
    auth: {
      name: string
    }
  }
}>({
  onRequest: [auth],
  async handler(event) {
    event.context.auth.name // string
  },
})

Additional information

  • Would you be willing to help implement this feature?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions