diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8897cfc..56dea56 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -88,6 +88,7 @@ function nav(): DefaultTheme.NavItem[] { { text: 'Cookies', link: '/guide/cookies' }, { text: 'Retries', link: '/guide/retries' }, { text: 'Dynamic Backend URL', link: '/guide/dynamic-backend-url' }, + { text: 'Hooks', link: '/guide/hooks' }, ], }, ], @@ -150,6 +151,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { { text: 'Cookies', link: '/guide/cookies' }, { text: 'Retries', link: '/guide/retries' }, { text: 'Dynamic Backend URL', link: '/guide/dynamic-backend-url' }, + { text: 'Hooks', link: '/guide/hooks' }, ], }, { text: 'Migration', link: '/guide/migration' }, diff --git a/docs/config/index.md b/docs/config/index.md index 0810826..f4cdf36 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -25,7 +25,7 @@ Main module configuration for your API endpoints. Each key represents an endpoin - `headers`: Headers to send with each request (optional) - `cookies`: Whether to send cookies with each request (optional) - `allowedUrls`: A list of allowed URLs to change the [backend URL at runtime](/guide/dynamic-backend-url) (optional) -- `schema`: A URL, file path, object, or async function pointing to an [OpenAPI Schema](https://swagger.io/resources/open-api) used to [generate types](/guide/openapi-types) (optional) +- `schema`: A URL, file path, or object representing an [OpenAPI Schema](https://swagger.io/resources/open-api) used to [generate types](/guide/openapi-types) (optional) - `openAPITS`: [Configuration options](https://openapi-ts.pages.dev/node/#options) for `openapi-typescript`. Options defined here will override the global `openAPITS` ::: info @@ -44,7 +44,7 @@ interface ApiEndpoint { headers?: Record cookies?: boolean allowedUrls?: string[] - schema?: string | URL | OpenAPI3 | (() => Promise) + schema?: string | URL | OpenAPI3 openAPITS?: OpenAPITSOptions } diff --git a/docs/guide/hooks.md b/docs/guide/hooks.md new file mode 100644 index 0000000..7008138 --- /dev/null +++ b/docs/guide/hooks.md @@ -0,0 +1,28 @@ +# Hooks + +Nuxt API Party provides a number of hooks that can be used to customize the module's behavior. Hooks are functions that are called at specific points in the module's lifecycle. You can use hooks to modify the module's configuration. + +For more information on how to work with hooks, see the [Nuxt documentation](https://nuxt.com/docs/guide/going-further/hooks). + +## Available Hooks + +| Hook name | Arguments | Description | +| ---------- | --------- | ----------- | +| `api-party:extend` | `options` | Called during module initialization after the options have been resolved. Can be used to modify the endpoint configuration. | + +## 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: + +```ts +// `nuxt.config.ts` +export default defineNuxtConfig({ + modules: ['nuxt-api-party'], + + hooks: { + 'api-party:extend': async (options) => { + console.log(`Resolved server endpoints:`, options.endpoints) + }, + }, +}) +``` diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 13016be..ecad95e 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -6,6 +6,12 @@ export default defineNuxtConfig({ compatibilityDate: '2024-04-03', + hooks: { + 'api-party:extend': async (options) => { + console.log(`[Build] Resolved endpoints:`, options.endpoints) + }, + }, + apiParty: { endpoints: { jsonPlaceholder: { diff --git a/src/module.ts b/src/module.ts index 9bfdd4a..129cd3f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -4,6 +4,7 @@ import { joinURL } from 'ufo' 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 { OpenAPI3, OpenAPITSOptions } from 'openapi-typescript' import type { QueryObject } from 'ufo' import { name } from '../package.json' @@ -16,7 +17,7 @@ export interface ApiEndpoint { headers?: Record cookies?: boolean allowedUrls?: string[] - schema?: string | URL | OpenAPI3 | (() => Promise) + schema?: string | OpenAPI3 openAPITS?: OpenAPITSOptions } @@ -86,6 +87,10 @@ declare module '@nuxt/schema' { interface RuntimeConfig { apiParty: ModuleOptions } + + interface NuxtHooks { + 'api-party:extend': (options: ModuleOptions) => HookResult + } } export default defineNuxtModule({ @@ -129,6 +134,8 @@ export default defineNuxtModule({ const resolvedOptions = nuxt.options.runtimeConfig.apiParty as Required + nuxt.callHook('api-party:extend', resolvedOptions) + // Write options to public runtime config if client requests are enabled // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: `client` types are not compatible diff --git a/src/openapi.ts b/src/openapi.ts index 1707fc1..d313f97 100644 --- a/src/openapi.ts +++ b/src/openapi.ts @@ -3,13 +3,20 @@ import { useNuxt } from '@nuxt/kit' import type { OpenAPI3, OpenAPITSOptions } from 'openapi-typescript' import type { ApiEndpoint } from './module' +/** @deprecated Hooks should be used instead */ +type SchemaFn = () => Promise> + +type SchemaEndpoint = ApiEndpoint & { + schema: NonNullable | SchemaFn +} + export async function generateDeclarationTypes( endpoints: Record, globalOpenAPIOptions: OpenAPITSOptions, ) { const resolvedSchemaEntries = await Promise.all( Object.entries(endpoints) - .filter(([, endpoint]) => Boolean(endpoint.schema)) + .filter((entry): entry is [string, SchemaEndpoint] => Boolean(entry[1].schema)) .map(async ([id, endpoint]) => { const types = await generateSchemaTypes({ id, endpoint, openAPITSOptions: globalOpenAPIOptions }) return [id, types] as const @@ -38,14 +45,14 @@ ${normalizeIndentation(types).trimEnd()} async function generateSchemaTypes(options: { id: string - endpoint: ApiEndpoint + endpoint: SchemaEndpoint openAPITSOptions?: OpenAPITSOptions }, ) { // openapi-typescript < 7 does not have named exports const openAPITS = await interopDefault(import('openapi-typescript')) const { astToString } = await import('openapi-typescript') - const schema = await resolveSchema(options.endpoint) + const schema = await resolveSchema(options.id, options.endpoint) try { const ast = await openAPITS(schema, { @@ -80,12 +87,13 @@ export type operations = Record } } -async function resolveSchema({ schema }: ApiEndpoint): Promise { +async function resolveSchema(id: string, { schema }: SchemaEndpoint): Promise { const nuxt = useNuxt() - if (typeof schema === 'function') + if (typeof schema === 'function') { + console.warn(`[nuxt-api-party] Passing a function to "apiParty.endpoints.${id}.schema" is deprecated. Use a hook instead.`) return await schema() - + } if (typeof schema === 'string' && !isValidUrl(schema)) return new URL(resolve(nuxt.options.rootDir, schema), import.meta.url)