Skip to content

Commit

Permalink
feat: add runtime hooks for API request and response handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmess1221 committed Nov 11, 2024
1 parent f36722a commit 01b63e4
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 7 deletions.
21 changes: 20 additions & 1 deletion docs/guide/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ For more information on how to work with hooks, see the [Nuxt documentation](htt
| ---------- | --------- | ----------- |
| `api-party:extend` | `options` | Called during module initialization after the options have been resolved. Can be used to modify the endpoint configuration. |

## Usage
### Usage

To use hooks, define them in the `hooks` property of your `nuxt.config.ts` file. The following example demonstrates how to use the `api-party:extend` hook:

Expand All @@ -26,3 +26,22 @@ export default defineNuxtConfig({
},
})
```

## Nuxt Runtime Hooks

Register these hooks with a client plugin.

| Hook name | Arguments | Description
| -------------------- | ---------- | -----------
| `api-party:request` | `ctx` | Called before each request is made. Can be used to log or modify the request.
| `api-party:response` | `ctx` | Called after each request is made. Can be used to log or modify the response.

## Nitro Runtime Hooks

Register these hooks with a server plugin.

| Hook name | Arguments | Description
| -------------------- | ------------ | -----------
| `api-party:request` | `ctx, event` | Called before each request is made. Can be used to log or modify the request.
| `api-party:response` | `ctx, event` | Called after each request is made. Can be used to log or modify the response.

14 changes: 14 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { camelCase, pascalCase } from 'scule'
import { createJiti } from 'jiti'
import { addImportsSources, addServerHandler, addTemplate, createResolver, defineNuxtModule, useLogger } from '@nuxt/kit'
import type { HookResult } from '@nuxt/schema'
import type { H3Event } from 'h3'
import type { OpenAPI3, OpenAPITSOptions } from 'openapi-typescript'
import type { QueryObject } from 'ufo'
import type { FetchContext } from 'ofetch'
import { name } from '../package.json'
import { generateDeclarationTypes } from './openapi'

Expand Down Expand Up @@ -92,6 +94,18 @@ declare module '@nuxt/schema' {
'api-party:extend': (options: ModuleOptions) => HookResult
}
}
declare module '#app' {
interface RuntimeNuxtHooks {
'api-party:request': (options: FetchContext) => HookResult
'api-party:response': (options: FetchContext & { response: Response }) => HookResult
}
}
declare module 'nitropack' {
interface NitroRuntimeHooks {
'api-party:request': (options: FetchContext, event?: H3Event) => HookResult
'api-party:response': (options: FetchContext & { response: Response }, event?: H3Event) => HookResult
}
}

export default defineNuxtModule<ModuleOptions>({
meta: {
Expand Down
12 changes: 12 additions & 0 deletions src/runtime/composables/$api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { ModuleOptions } from '../../module'
import { CACHE_KEY_PREFIX } from '../constants'
import type { EndpointFetchOptions } from '../types'
import type { FetchResponseData, FilterMethods, MethodOption, ParamsOption, RequestBodyOption } from '../openapi'
import { mergeFetchHooks } from '../hooks'
import { useNuxtApp, useRequestHeaders, useRuntimeConfig } from '#imports'

export interface SharedFetchOptions {
Expand Down Expand Up @@ -113,8 +114,18 @@ export function _$api<T = unknown>(

const endpoint = (apiParty.endpoints || {})[endpointId]

const fetchHooks = mergeFetchHooks(fetchOptions, {
onRequest: async (ctx) => {
await nuxt.callHook('api-party:request', ctx)
},
onResponse: async (ctx) => {
await nuxt.callHook('api-party:response', ctx)
},
})

const clientFetcher = () => globalThis.$fetch<T>(resolvePathParams(path, pathParams), {
...fetchOptions,
...fetchHooks,
baseURL: endpoint.url,
method,
query: {
Expand All @@ -132,6 +143,7 @@ export function _$api<T = unknown>(
const serverFetcher = async () =>
(await globalThis.$fetch<T>(joinURL('/api', apiParty.server.basePath!, endpointId), {
...fetchOptions,
...fetchHooks,
method: 'POST',
body: {
path: resolvePathParams(path, pathParams),
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/composables/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../../.nuxt/tsconfig.json"
}
22 changes: 18 additions & 4 deletions src/runtime/composables/useApiData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { headersToObject, serializeMaybeEncodedBody } from '../utils'
import { isFormData } from '../form-data'
import type { EndpointFetchOptions } from '../types'
import type { FetchResponseData, FetchResponseError, FilterMethods, ParamsOption, RequestBodyOption } from '../openapi'
import { mergeFetchHooks } from '../hooks'
import { useAsyncData, useRequestHeaders, useRuntimeConfig } from '#imports'

type ComputedOptions<T> = {
Expand Down Expand Up @@ -56,17 +57,19 @@ export type SharedAsyncDataOptions<ResT, DataT = ResT> = Omit<AsyncDataOptions<R

export type UseApiDataOptions<T> = Pick<
ComputedOptions<NitroFetchOptions<string>>,
| 'onRequest'
| 'onRequestError'
| 'onResponse'
| 'onResponseError'
| 'query'
| 'headers'
| 'method'
| 'retry'
| 'retryDelay'
| 'retryStatusCodes'
| 'timeout'
> & Pick<
NitroFetchOptions<string>,
| 'onRequest'
| 'onRequestError'
| 'onResponse'
| 'onResponseError'
> & {
path?: MaybeRefOrGetter<Record<string, string>>
body?: MaybeRef<string | Record<string, any> | FormData | null>
Expand Down Expand Up @@ -191,10 +194,20 @@ export function _useApiData<T = unknown>(

let result: T | undefined

const fetchHooks = mergeFetchHooks(fetchOptions, {
onRequest: async (ctx) => {
await nuxt?.callHook('api-party:request', ctx)
},
onResponse: async (ctx) => {
await nuxt?.callHook('api-party:response', ctx)
},
})

try {
if (client) {
result = (await globalThis.$fetch<T>(_path.value, {
..._fetchOptions,
...fetchHooks,
signal: controller.signal,
baseURL: endpoint.url,
method: _endpointFetchOptions.method,
Expand All @@ -215,6 +228,7 @@ export function _useApiData<T = unknown>(
joinURL('/api', apiParty.server.basePath!, endpointId),
{
..._fetchOptions,
...fetchHooks,
signal: controller.signal,
method: 'POST',
body: {
Expand Down
21 changes: 21 additions & 0 deletions src/runtime/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { FetchHooks } from 'ofetch'

type Arrayify<T> = { [P in keyof T]-?: Extract<T[P], unknown[]> }
type Hooks = Arrayify<Pick<FetchHooks, 'onRequest' | 'onResponse'>>

export function mergeFetchHooks(...hooks: FetchHooks[]): Hooks {
const result: Hooks = {
onRequest: [],
onResponse: [],
}

hooks.forEach((hook) => {
for (const name of Object.keys(result) as (keyof Hooks)[]) {
if (hook) {
result[name].push(...(Array.isArray(hook) ? hook : [hook]))
}
}
})

return result
}
13 changes: 12 additions & 1 deletion src/runtime/server/$api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { headersToObject } from '../utils'
import { resolvePathParams } from '../openapi'
import type { ModuleOptions } from '../../module'
import type { ApiClientFetchOptions } from '../composables/$api'
import { useRuntimeConfig } from '#imports'
import { mergeFetchHooks } from '../hooks'
import { useNitroApp, useRuntimeConfig } from '#imports'

export function _$api<T = unknown>(
endpointId: string,
Expand All @@ -14,8 +15,18 @@ export function _$api<T = unknown>(
const endpoints = apiParty.endpoints || {}
const endpoint = endpoints[endpointId]

const nitro = useNitroApp()

return globalThis.$fetch<T>(resolvePathParams(path, pathParams), {
...fetchOptions,
...mergeFetchHooks(fetchOptions, {
onRequest: async (ctx) => {
await nitro.hooks.callHook('api-party:request', ctx)
},
onResponse: async (ctx) => {
await nitro.hooks.callHook('api-party:response', ctx)
},
}),
baseURL: endpoint.url,
query: {
...endpoint.query,
Expand Down
10 changes: 9 additions & 1 deletion src/runtime/server/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
import { deserializeMaybeEncodedBody } from '../utils'
import type { ModuleOptions } from '../../module'
import type { EndpointFetchOptions } from '../types'
import { useRuntimeConfig } from '#imports'
import { useRuntimeConfig, useNitroApp } from '#imports'

Check failure on line 15 in src/runtime/server/handler.ts

View workflow job for this annotation

GitHub Actions / typecheck

Module '"#imports"' has no exported member 'useNitroApp'.

export default defineEventHandler(async (event) => {
const nitro = useNitroApp()
const endpointId = getRouterParam(event, 'endpointId')!
const apiParty = useRuntimeConfig().apiParty as Required<ModuleOptions>
const endpoints = apiParty.endpoints || {}
Expand Down Expand Up @@ -79,6 +80,13 @@ export default defineEventHandler(async (event) => {
...(body && { body: await deserializeMaybeEncodedBody(body) }),
responseType: 'arrayBuffer',
ignoreResponseError: true,

onRequest: async (ctx) => {
await nitro.hooks.callHook('api-party:request', ctx, event)
},
onResponse: async (ctx) => {
await nitro.hooks.callHook('api-party:response', ctx, event)
},
},
)

Expand Down
3 changes: 3 additions & 0 deletions src/runtime/server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../../.nuxt/tsconfig.server.json"
}

0 comments on commit 01b63e4

Please sign in to comment.