Skip to content

Commit 6c13639

Browse files
committed
chore: improve interceptors
1 parent 18a7cc1 commit 6c13639

File tree

9 files changed

+258
-32
lines changed

9 files changed

+258
-32
lines changed

docs/.vitepress/config/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default defineConfig({
7777
},
7878
{
7979
link: '/openapi-ts/clients/nuxt',
80-
text: 'Nuxt <span data-soon>soon</span>',
80+
text: 'Nuxt',
8181
},
8282
{
8383
link: '/openapi-ts/clients/legacy',

docs/.vitepress/theme/Layout.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const { Layout } = DefaultTheme;
2929
background-color: var(--vp-c-bg);
3030
border-bottom: 1px solid var(--vp-c-divider);
3131
color: var(--vp-c-text-1);
32-
column-gap: 1.5rem;
32+
column-gap: 0.5rem;
3333
display: flex;
3434
font-size: var(--announcement-font-size);
3535
height: var(--announcement-height);

docs/openapi-ts/clients.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Hey API natively supports the following clients.
2828
- [Fetch API](/openapi-ts/clients/fetch)
2929
- [Axios](/openapi-ts/clients/axios)
3030
- [Next.js](/openapi-ts/clients/next-js) <span data-soon>Soon</span>
31-
- [Nuxt](/openapi-ts/clients/nuxt) <span data-soon>Soon</span>
31+
- [Nuxt](/openapi-ts/clients/nuxt)
3232
- [Legacy](/openapi-ts/clients/legacy)
3333

3434
Don't see your client? Let us know your interest by [opening an issue](https://github.com/hey-api/openapi-ts/issues).

docs/openapi-ts/clients/next-js.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Next.js
2+
title: Next.js client
33
description: Next.js client for Hey API. Compatible with all our features.
44
---
55

docs/openapi-ts/clients/nuxt.md

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,215 @@
11
---
2-
title: Nuxt
2+
title: Nuxt client
33
description: Nuxt client for Hey API. Compatible with all our features.
44
---
55

6-
# Nuxt <span data-soon>soon</span>
6+
# Nuxt
77

88
::: warning
9-
This feature isn't in development yet. Help us prioritize it by voting on [GitHub](https://github.com/hey-api/openapi-ts/issues/998).
9+
Nuxt client is currently in beta. The interface might change before it becomes stable. We encourage you to leave feedback on [GitHub](https://github.com/hey-api/openapi-ts/issues).
1010
:::
1111

1212
[Nuxt](https://nuxt.com/) is an open source framework that makes web development intuitive and powerful.
1313

14+
## Installation
15+
16+
Start by adding `@hey-api/client-nuxt` to your dependencies.
17+
18+
::: code-group
19+
20+
```sh [npm]
21+
npm install @hey-api/client-nuxt
22+
```
23+
24+
```sh [pnpm]
25+
pnpm add @hey-api/client-nuxt
26+
```
27+
28+
```sh [yarn]
29+
yarn add @hey-api/client-nuxt
30+
```
31+
32+
```sh [bun]
33+
bun add @hey-api/client-nuxt
34+
```
35+
36+
:::
37+
38+
In your [configuration](/openapi-ts/get-started), set `client` to `@hey-api/client-nuxt` and you'll be ready to use the Nuxt client. :tada:
39+
40+
::: code-group
41+
42+
```js [config]
43+
export default {
44+
client: '@hey-api/client-nuxt', // [!code ++]
45+
input: 'path/to/openapi.json',
46+
output: 'src/client',
47+
};
48+
```
49+
50+
```sh [cli]
51+
npx @hey-api/openapi-ts \
52+
-c @hey-api/client-nuxt \ # [!code ++]
53+
-i path/to/openapi.json \
54+
-o src/client
55+
```
56+
57+
:::
58+
59+
## Configuration
60+
61+
If you're using SDKs, you will want to configure the internal client instance. You can do that with the `setConfig()` method. Call it at the beginning of your application.
62+
63+
```js
64+
import { client } from 'client/sdk.gen';
65+
66+
client.setConfig({
67+
baseURL: 'https://example.com',
68+
});
69+
```
70+
71+
If you aren't using SDKs, you can create your own client instance.
72+
73+
```js
74+
import { createClient } from '@hey-api/client-nuxt';
75+
76+
const client = createClient({
77+
baseURL: 'https://example.com',
78+
});
79+
```
80+
81+
## Interceptors
82+
83+
Interceptors (middleware) can be used to modify requests before they're sent or responses before they're returned to your application. Nuxt provides interceptors through ofetch, please refer to their documentation on [$fetch](https://nuxt.com/docs/api/utils/dollarfetch).
84+
85+
You can pass any Nuxt/ofetch arguments to the client instance.
86+
87+
```js
88+
import { client } from 'client/sdk.gen';
89+
90+
const result = await client.get({
91+
composable: '$fetch',
92+
onRequest: (context) => {
93+
// do something
94+
},
95+
url: '/foo',
96+
});
97+
```
98+
99+
## Customization
100+
101+
The Nuxt client is built as a thin wrapper on top of Nuxt, extending its functionality to work with Hey API. If you're already familiar with Nuxt, customizing your client will feel like working directly with Nuxt. You can customize requests in three ways – through SDKs, per client, or per request.
102+
103+
### SDKs
104+
105+
This is the most common requirement. The generated SDKs consume an internal client instance, so you will want to configure that.
106+
107+
```js
108+
import { client } from 'client/sdk.gen';
109+
110+
client.setConfig({
111+
baseURL: 'https://example.com',
112+
});
113+
```
114+
115+
You can pass any Nuxt configuration option to `setConfig()`, and even your own `$fetch` implementation.
116+
117+
### Client
118+
119+
If you need to create a client pointing to a different domain, you can create your own client instance.
120+
121+
```js
122+
import { createClient } from '@hey-api/client-nuxt';
123+
124+
const myClient = createClient({
125+
baseURL: 'https://example.com',
126+
});
127+
```
128+
129+
You can then pass this instance to any SDK function through the `client` option. This will override the internal instance.
130+
131+
```js
132+
const response = await getFoo({
133+
client: myClient,
134+
});
135+
```
136+
137+
### Request
138+
139+
Alternatively, you can pass the Nuxt configuration options to each SDK function. This is useful if you don't want to create a client instance for one-off use cases.
140+
141+
```js
142+
const response = await getFoo({
143+
baseURL: 'https://example.com', // <-- override internal configuration
144+
});
145+
```
146+
147+
## Auth
148+
149+
The SDKs include auth mechanisms for every endpoint. You will want to configure the `auth` field to pass the right token for each request. The `auth` field can be a string or a function returning a string representing the token. The returned value will be attached only to requests that require auth.
150+
151+
```js
152+
import { client } from 'client/sdk.gen';
153+
154+
client.setConfig({
155+
auth: () => '<my_token>', // [!code ++]
156+
baseURL: 'https://example.com',
157+
});
158+
```
159+
160+
If you're not using SDKs or generating auth, using interceptors is a common approach to configuring auth for each request.
161+
162+
```js
163+
import { client } from 'client/sdk.gen';
164+
165+
client.setConfig({
166+
onRequest: ({ options }) => {
167+
options.headers.set('Authorization', 'Bearer <my_token>'); // [!code ++]
168+
},
169+
});
170+
```
171+
172+
## Build URL
173+
174+
If you need to access the compiled URL, you can use the `buildUrl()` method. It's loosely typed by default to accept almost any value; in practice, you will want to pass a type hint.
175+
176+
```ts
177+
type FooData = {
178+
path: {
179+
fooId: number;
180+
};
181+
query?: {
182+
bar?: string;
183+
};
184+
url: '/foo/{fooId}';
185+
};
186+
187+
const url = client.buildUrl<FooData>({
188+
path: {
189+
fooId: 1,
190+
},
191+
query: {
192+
bar: 'baz',
193+
},
194+
url: '/foo/{fooId}',
195+
});
196+
console.log(url); // prints '/foo/1?bar=baz'
197+
```
198+
199+
## Bundling
200+
201+
Sometimes, you may not want to declare client packages as a dependency. This scenario is common if you're using Hey API to generate output that is repackaged and published for other consumers under your own brand. For such cases, our clients support bundling through the `client.bundle` configuration option.
202+
203+
```js
204+
export default {
205+
client: {
206+
bundle: true, // [!code ++]
207+
name: '@hey-api/client-nuxt',
208+
},
209+
input: 'path/to/openapi.json',
210+
output: 'src/client',
211+
};
212+
```
213+
214+
<!--@include: ../../examples.md-->
14215
<!--@include: ../../sponsors.md-->

examples/openapi-ts-nuxt/app.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ client.setConfig({
1313
headers: {
1414
Authorization: 'Bearer <token_from_service_client>',
1515
},
16+
onRequest: () => {
17+
console.log('onRequest: global');
18+
},
19+
onResponse: () => {
20+
console.log('onResponse: global');
21+
},
1622
});
1723
</script>
1824

examples/openapi-ts-nuxt/components/home.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ watch(lazyFetch.data, (newPet) => {
123123
async function handleFetch() {
124124
const result = await getPetById({
125125
composable: '$fetch',
126+
onRequest: [
127+
() => {
128+
console.log('onRequest: local');
129+
},
130+
],
131+
onResponse: [
132+
() => {
133+
console.log('onResponse: local');
134+
},
135+
],
126136
path: {
127137
petId,
128138
},

packages/client-nuxt/src/index.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
createConfig,
1313
mergeConfigs,
1414
mergeHeaders,
15+
mergeInterceptors,
1516
setAuthParams,
1617
unwrapRefs,
1718
} from './utils';
@@ -37,49 +38,45 @@ export const createClient = (config: Config = {}): Client => {
3738
...options,
3839
$fetch: options.$fetch ?? _config.$fetch ?? $fetch,
3940
headers: mergeHeaders(_config.headers, options.headers),
41+
onRequest: mergeInterceptors(_config.onRequest, options.onRequest),
42+
onResponse: mergeInterceptors(_config.onResponse, options.onResponse),
4043
};
4144

4245
const { responseTransformer, responseValidator, security } = opts;
4346
if (security) {
44-
const _onRequest = opts.onRequest;
4547
// auth must happen in interceptors otherwise we'd need to require
4648
// asyncContext enabled
4749
// https://nuxt.com/docs/guide/going-further/experimental-features#asynccontext
48-
opts.onRequest = async (context) => {
49-
const { options } = context;
50-
51-
await setAuthParams({
52-
auth: opts.auth,
53-
headers: options.headers,
54-
query: options.query,
55-
security,
56-
});
57-
58-
if (typeof _onRequest === 'function') {
59-
await _onRequest(context);
60-
}
61-
};
50+
opts.onRequest = [
51+
async ({ options }) => {
52+
await setAuthParams({
53+
auth: opts.auth,
54+
headers: options.headers,
55+
query: options.query,
56+
security,
57+
});
58+
},
59+
...opts.onRequest,
60+
];
6261
}
6362

6463
if (responseTransformer || responseValidator) {
65-
const _onResponse = opts.onResponse;
66-
opts.onResponse = async (context) => {
67-
const { options, response } = context;
64+
opts.onResponse = [
65+
...opts.onResponse,
66+
async ({ options, response }) => {
67+
if (options.responseType && options.responseType !== 'json') {
68+
return;
69+
}
6870

69-
if (!options.responseType || options.responseType === 'json') {
7071
if (responseValidator) {
7172
await responseValidator(response._data);
7273
}
7374

7475
if (responseTransformer) {
7576
response._data = await responseTransformer(response._data);
7677
}
77-
}
78-
79-
if (typeof _onResponse === 'function') {
80-
await _onResponse(context);
81-
}
82-
};
78+
},
79+
];
8380
}
8481

8582
if (opts.body && opts.bodySerializer) {

packages/client-nuxt/src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
1919
type ObjectStyle = 'form' | 'deepObject';
2020
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
2121

22+
type MaybeArray<T> = T | T[];
23+
2224
export type QuerySerializer = (
2325
query: Parameters<Client['buildUrl']>[0]['query'],
2426
) => string;
@@ -474,6 +476,16 @@ export const mergeHeaders = (
474476
return mergedHeaders;
475477
};
476478

479+
export const mergeInterceptors = <T>(...args: Array<MaybeArray<T>>): Array<T> =>
480+
args.reduce<Array<T>>((acc, item) => {
481+
if (typeof item === 'function') {
482+
acc.push(item);
483+
} else if (Array.isArray(item)) {
484+
return acc.concat(item);
485+
}
486+
return acc;
487+
}, []);
488+
477489
const serializeFormDataPair = (data: FormData, key: string, value: unknown) => {
478490
if (typeof value === 'string' || value instanceof Blob) {
479491
data.append(key, value);

0 commit comments

Comments
 (0)