Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 51764e8

Browse files
committedMar 6, 2025·
feat(openapi-vue-query): Add vue-query suport
1 parent f151f44 commit 51764e8

27 files changed

+5392
-213
lines changed
 

‎docs/openapi-vue-query/about.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: About openapi-vue-query
3+
description: openapi-vue-query Project Goals and contributors
4+
---
5+
<script setup>
6+
import { VPTeamMembers } from 'vitepress/theme';
7+
import contributors from '../data/contributors.json';
8+
</script>
9+
10+
# About
11+
12+
## Project Goals
13+
14+
1. Types should be strict and inferred automatically from OpenAPI schemas with the absolute minimum number of generics needed.
15+
2. Respect the original `@tanstack/vue-query` APIs while reducing boilerplate.
16+
3. Be as light and performant as possible.
17+
18+
## Contributors
19+
20+
This library wouldn’t be possible without all these amazing contributors:
21+
22+
<VPTeamMembers size="small" :members="contributors['openapi-vue-query']" />

‎docs/openapi-vue-query/index.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
title: openapi-vue-query
3+
---
4+
# Introduction
5+
6+
openapi-vue-query is a type-safe tiny wrapper (1 kb) around [@tanstack/vue-query](https://tanstack.com/query/latest/docs/framework/vue/overview) to work with OpenAPI schema.
7+
8+
It works by using [openapi-fetch](../openapi-fetch/) and [openapi-typescript](../introduction) so you get all the following features:
9+
10+
- ✅ No typos in URLs or params.
11+
- ✅ All parameters, request bodies, and responses are type-checked and 100% match your schema
12+
- ✅ No manual typing of your API
13+
- ✅ Eliminates `any` types that hide bugs
14+
- ✅ Also eliminates `as` type overrides that can also hide bugs
15+
16+
::: code-group
17+
18+
```vue [src/my-component.vue]
19+
<script setup lang="ts">
20+
import createFetchClient from "openapi-fetch";
21+
import createClient from "openapi-vue-query";
22+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
23+
24+
const fetchClient = createFetchClient<paths>({
25+
baseUrl: "https://myapi.dev/v1/",
26+
});
27+
const $api = createClient(fetchClient);
28+
29+
const { data, error, isLoading } = $api.useQuery(
30+
"get",
31+
"/blogposts/{post_id}",
32+
{
33+
params: {
34+
path: { post_id: 5 },
35+
},
36+
}
37+
)
38+
</script>
39+
40+
<template>
41+
<div>
42+
<template v-if="isLoading || !data">
43+
Loading...
44+
</template>
45+
<template v-else-if="error">
46+
An error occurred: {{ error.message }}
47+
</template>
48+
<template v-else>
49+
{{ data.title }}
50+
</template>
51+
</div>
52+
</template>
53+
```
54+
55+
:::
56+
57+
## Setup
58+
59+
Install this library along with [openapi-fetch](../openapi-fetch/) and [openapi-typescript](../introduction):
60+
61+
```bash
62+
npm i openapi-vue-query openapi-fetch
63+
npm i -D openapi-typescript typescript
64+
```
65+
66+
::: tip Highly recommended
67+
68+
Enable [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess) in your `tsconfig.json` ([docs](/advanced#enable-nouncheckedindexedaccess-in-tsconfig))
69+
70+
:::
71+
72+
Next, generate TypeScript types from your OpenAPI schema using openapi-typescript:
73+
74+
```bash
75+
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts
76+
```
77+
78+
## Basic usage
79+
80+
Once your types has been generated from your schema, you can create a [fetch client](../introduction.md), a vue-query client and start querying your API.
81+
82+
::: code-group
83+
84+
```vue [src/my-component.vue]
85+
<script setup lang="ts">
86+
import createFetchClient from "openapi-fetch";
87+
import createClient from "openapi-vue-query";
88+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
89+
90+
const fetchClient = createFetchClient<paths>({
91+
baseUrl: "https://myapi.dev/v1/",
92+
});
93+
const $api = createClient(fetchClient);
94+
95+
const { data, error, isLoading } = $api.useQuery(
96+
"get",
97+
"/blogposts/{post_id}",
98+
{
99+
params: {
100+
path: { post_id: 5 },
101+
},
102+
}
103+
)
104+
</script>
105+
106+
<template>
107+
<div>
108+
<template v-if="isLoading || !data">
109+
Loading...
110+
</template>
111+
<template v-else-if="error">
112+
An error occurred: {{ error.message }}
113+
</template>
114+
<template v-else>
115+
{{ data.title }}
116+
</template>
117+
</div>
118+
</template>
119+
```
120+
121+
:::
122+
123+
::: tip
124+
You can find more information about `createFetchClient` on the [openapi-fetch documentation](../openapi-fetch/index.md).
125+
:::
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
title: queryOptions
3+
---
4+
# {{ $frontmatter.title }}
5+
6+
The `queryOptions` method allows you to construct type-safe [Query Options](https://tanstack.com/query/latest/docs/framework/vue/guides/query-options).
7+
8+
`queryOptions` can be used together with `@tanstack/vue-query` APIs that take query options, such as
9+
[useQuery](https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery),
10+
[useQueries](https://tanstack.com/query/latest/docs/framework/vue/reference/useQueries) and
11+
[QueryClient.fetchQuery](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientfetchquery)
12+
among many others.
13+
14+
If you would like to use a query API that is not explicitly supported by `openapi-vue-query`, this is the way to go.
15+
16+
## Examples
17+
18+
[useQuery example](use-query#example) rewritten using `queryOptions`.
19+
20+
::: code-group
21+
22+
```vue [src/App.vue]
23+
<script setup lang="ts">
24+
import { useQuery } from "@tanstack/vue-query";
25+
import { $api } from "./api";
26+
27+
const { data, error, isLoading } = useQuery(
28+
$api.queryOptions("get", "/users/{user_id}", {
29+
params: {
30+
path: { user_id: 5 },
31+
},
32+
}),
33+
);
34+
</script>
35+
36+
<template>
37+
<div>
38+
<template v-if="!data || isLoading">
39+
Loading...
40+
</template>
41+
<template v-else-if="error">
42+
An error occured: {{ error.message }}
43+
</template>
44+
<template v-else>
45+
{{ data.firstname }}
46+
</template>
47+
</div>
48+
</template>
49+
```
50+
51+
```ts [src/api.ts]
52+
import createFetchClient from "openapi-fetch";
53+
import createClient from "openapi-vue-query";
54+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
55+
56+
const fetchClient = createFetchClient<paths>({
57+
baseUrl: "https://myapi.dev/v1/",
58+
});
59+
export const $api = createClient(fetchClient);
60+
```
61+
62+
:::
63+
64+
::: info Good to Know
65+
66+
[useQuery](use-query) uses `queryOptions` under the hood.
67+
68+
:::
69+
70+
Usage with [useQueries](https://tanstack.com/query/latest/docs/framework/vue/reference/useQueries).
71+
72+
::: code-group
73+
74+
```tsx [src/use-users-by-id.ts]
75+
import { useQueries } from '@tanstack/vue-query';
76+
import { $api } from "./api";
77+
78+
export const useUsersById = (userIds: number[]) => (
79+
useQueries({
80+
queries: userIds.map((userId) => (
81+
$api.queryOptions("get", "/users/{user_id}", {
82+
params: {
83+
path: { user_id: userId },
84+
},
85+
})
86+
))
87+
})
88+
);
89+
```
90+
91+
```ts [src/api.ts]
92+
import createFetchClient from "openapi-fetch";
93+
import createClient from "openapi-vue-query";
94+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
95+
96+
const fetchClient = createFetchClient<paths>({
97+
baseUrl: "https://myapi.dev/v1/",
98+
});
99+
export const $api = createClient(fetchClient);
100+
```
101+
102+
:::
103+
104+
## Api
105+
106+
```tsx
107+
const queryOptions = $api.queryOptions(method, path, options, queryOptions);
108+
```
109+
110+
**Arguments**
111+
112+
- `method` **(required)**
113+
- The HTTP method to use for the request.
114+
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
115+
- `path` **(required)**
116+
- The pathname to use for the request.
117+
- Must be an available path for the given method in your schema.
118+
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
119+
- `options`
120+
- The fetch options to use for the request.
121+
- Only required if the OpenApi schema requires parameters.
122+
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
123+
- `queryOptions`
124+
- Additional query options to pass through.
125+
126+
**Returns**
127+
128+
- [Query Options](https://tanstack.com/query/latest/docs/framework/vue/guides/query-options)
129+
- Fully typed thus `data` and `error` will be correctly deducted.
130+
- `queryKey` is `[method, path, params]`.
131+
- `queryFn` is set to a fetcher function.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
title: useInfiniteQuery
3+
---
4+
5+
# {{ $frontmatter.title }}
6+
7+
The `useInfiniteQuery` method allows you to use the original [useInfiniteQuery](https://tanstack.com/query/latest/docs/framework/vue/guides/infinite-queries)
8+
9+
- The result is the same as the original function.
10+
- The `queryKey` is `[method, path, params]`.
11+
- `data` and `error` are fully typed.
12+
- You can pass infinite query options as fourth parameter.
13+
14+
::: tip
15+
You can find more information about `useInfiniteQuery` on the [@tanstack/vue-query documentation](https://tanstack.com/query/latest/docs/framework/vue/guides/infinite-queries).
16+
:::
17+
18+
## Example
19+
20+
::: code-group
21+
22+
```vue [src/App.vue]
23+
<script setup lang="ts">
24+
import { $api } from "./api";
25+
const { data, error, fetchNextPage, hasNextPage, isFetching, isError, isFetchingNextPage } = $api.useInfiniteQuery(
26+
"get",
27+
"/posts",
28+
{
29+
params: {
30+
query: {
31+
limit: 10,
32+
},
33+
},
34+
},
35+
{
36+
getNextPageParam: (lastPage) => lastPage.nextPage,
37+
initialPageParam: 0,
38+
},
39+
);
40+
</script>
41+
42+
<template>
43+
<span v-if="isFetching">Loading...</span>
44+
<span v-else-if="isError">Error: {{ error?.message }}</span>
45+
<div v-else-if="data">
46+
<span v-if="isFetching && !isFetchingNextPage">Fetching...</span>
47+
<ul v-for="(group, index) in data.pages" :key="index">
48+
<li v-for="post in group.posts" :key="post.id">
49+
{{ post.name }}
50+
</li>
51+
</ul>
52+
<button
53+
@click="() => fetchNextPage()"
54+
:disabled="!hasNextPage || isFetchingNextPage"
55+
>
56+
<span v-if="isFetchingNextPage">Loading more...</span>
57+
<span v-else-if="hasNextPage">Load More</span>
58+
<span v-else>Nothing more to load</span>
59+
</button>
60+
</div>
61+
</template>
62+
```
63+
64+
```ts [src/api.ts]
65+
import createFetchClient from "openapi-fetch";
66+
import createClient from "openapi-vue-query";
67+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
68+
69+
const fetchClient = createFetchClient<paths>({
70+
baseUrl: "https://myapi.dev/v1/",
71+
});
72+
export const $api = createClient(fetchClient);
73+
```
74+
75+
:::
76+
77+
## Api
78+
79+
```tsx
80+
const query = $api.useInfiniteQuery(
81+
method,
82+
path,
83+
options,
84+
infiniteQueryOptions,
85+
queryClient
86+
);
87+
```
88+
89+
**Arguments**
90+
91+
- `method` **(required)**
92+
- The HTTP method to use for the request.
93+
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
94+
- `path` **(required)**
95+
- The pathname to use for the request.
96+
- Must be an available path for the given method in your schema.
97+
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
98+
- `options`
99+
- The fetch options to use for the request.
100+
- Only required if the OpenApi schema requires parameters.
101+
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
102+
- `infiniteQueryOptions`
103+
- The original `useInfiniteQuery` options.
104+
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useInfiniteQuery)
105+
- `queryClient`
106+
- The original `queryClient` option.
107+
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useInfiniteQuery)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: useMutation
3+
---
4+
# {{ $frontmatter.title }}
5+
6+
The `useMutation` method allows you to use the original [useMutation](https://tanstack.com/query/latest/docs/framework/vue/guides/mutations).
7+
8+
- The result is the same as the original function.
9+
- The `mutationKey` is `[method, path]`.
10+
- `data` and `error` are fully typed.
11+
12+
::: tip
13+
You can find more information about `useMutation` on the [@tanstack/vue-query documentation](https://tanstack.com/query/latest/docs/framework/vue/guides/mutations).
14+
:::
15+
16+
## Example
17+
18+
::: code-group
19+
20+
```vue [src/App.vue]
21+
<script setup lang="ts">
22+
import { $api } from "./api";
23+
24+
const { mutate } = $api.useMutation("patch", "/users");
25+
</script>
26+
27+
<template>
28+
<button @click="mutate({ body: { firstname: 'John'} })">
29+
Update
30+
</button>
31+
</template>
32+
```
33+
34+
```ts [src/api.ts]
35+
import createFetchClient from "openapi-fetch";
36+
import createClient from "openapi-vue-query";
37+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
38+
39+
const fetchClient = createFetchClient<paths>({
40+
baseUrl: "https://myapi.dev/v1/",
41+
});
42+
export const $api = createClient(fetchClient);
43+
```
44+
45+
:::
46+
47+
## Api
48+
49+
```ts
50+
const query = $api.useMutation(method, path, queryOptions, queryClient);
51+
```
52+
53+
**Arguments**
54+
55+
- `method` **(required)**
56+
- The HTTP method to use for the request.
57+
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
58+
- `path` **(required)**
59+
- The pathname to use for the request.
60+
- Must be an available path for the given method in your schema.
61+
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
62+
- `queryOptions`
63+
- The original `useMutation` options.
64+
- [See more information](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation)
65+
- `queryClient`
66+
- The original `queryClient` option.
67+
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useMutation)

‎docs/openapi-vue-query/use-query.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
title: useQuery
3+
---
4+
# {{ $frontmatter.title }}
5+
6+
The `useQuery` method allows you to use the original [useQuery](https://tanstack.com/query/latest/docs/framework/vue/guides/queries).
7+
8+
- The result is the same as the original function.
9+
- The `functionKey` is `[method, path, params]`.
10+
- `data` and `error` are fully typed.
11+
- You can pass queries options as fourth parameter.
12+
13+
::: tip
14+
You can find more information about `useQuery` on the [@tanstack/vue-query documentation](https://tanstack.com/query/latest/docs/framework/vue/guides/queries).
15+
:::
16+
17+
## Example
18+
19+
::: code-group
20+
21+
```vue [src/App.vue]
22+
<script setup lang="ts">
23+
import { $api } from "./api";
24+
25+
const { data, error, isLoading } = $api.useQuery("get", "/users/{user_id}", {
26+
params: {
27+
path: { user_id: 5 },
28+
},
29+
});
30+
</script>
31+
32+
<template>
33+
<div>
34+
<template v-if="!data || isLoading">
35+
Loading...
36+
</template>
37+
<template v-else-if="error">
38+
An error occured: {{ error.message }}
39+
</template>
40+
<template v-else>
41+
{{ data.firstname }}
42+
</template>
43+
</div>
44+
</template>
45+
```
46+
47+
```ts [src/api.ts]
48+
import createFetchClient from "openapi-fetch";
49+
import createClient from "openapi-vue-query";
50+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
51+
52+
const fetchClient = createFetchClient<paths>({
53+
baseUrl: "https://myapi.dev/v1/",
54+
});
55+
export const $api = createClient(fetchClient);
56+
```
57+
58+
:::
59+
60+
## Api
61+
62+
```ts
63+
const query = $api.useQuery(method, path, options, queryOptions, queryClient);
64+
```
65+
66+
**Arguments**
67+
68+
- `method` **(required)**
69+
- The HTTP method to use for the request.
70+
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
71+
- `path` **(required)**
72+
- The pathname to use for the request.
73+
- Must be an available path for the given method in your schema.
74+
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
75+
- `options`
76+
- The fetch options to use for the request.
77+
- Only required if the OpenApi schema requires parameters.
78+
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
79+
- `queryOptions`
80+
- The original `useQuery` options.
81+
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery)
82+
- `queryClient`
83+
- The original `queryClient` option.
84+
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery)

‎packages/openapi-vue-query/.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.turbo
2+
test
3+
vitest.config.ts
4+
tsconfig*.json
5+
biome.json
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# openapi-vue-query
2+
3+
## 0.0.1
4+
5+
### Patch Changes
6+
7+
- [#1717](https://github.com/openapi-ts/openapi-typescript/pull/1717) [`335530c`](https://github.com/openapi-ts/openapi-typescript/commit/335530c4f8f966d0154f19504585c462f5f5a409) Thanks [@kerwanp](https://github.com/kerwanp)! - Initial release
8+
9+
- Updated dependencies [[`335530c`](https://github.com/openapi-ts/openapi-typescript/commit/335530c4f8f966d0154f19504585c462f5f5a409), [`335530c`](https://github.com/openapi-ts/openapi-typescript/commit/335530c4f8f966d0154f19504585c462f5f5a409)]:
10+
- openapi-fetch@0.10.3
11+
- openapi-typescript-helpers@0.0.10
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Contributing
2+
3+
Thanks for being willing to contribute! 🙏
4+
5+
**Working on your first Pull Request (PR)?** You can learn how from this free series [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
6+
7+
## Open issues
8+
9+
Please check out the [the open issues](https://github.com/openapi-ts/openapi-typescript/issues). Issues labelled [**Good First Issue**](https://github.com/openapi-ts/openapi-typescript/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) are especially good to start with.
10+
11+
Contributing doesn’t have to be in code. Simply answering questions in open issues or providing workarounds is as important as making pull requests.
12+
13+
## Writing code
14+
15+
### Setup
16+
17+
1. Install [pnpm](https://pnpm.io/)
18+
2. [Fork this repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and clone your copy locally
19+
3. Run `pnpm i` to install dependencies
20+
21+
### Testing
22+
23+
This library uses [Vitest](https://vitest.dev/) for testing. There’s a great [VS Code extension](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer) you can optionally use if you’d like in-editor debugging tools.
24+
25+
To run the entire test suite, run:
26+
27+
```bash
28+
pnpm test
29+
```
30+
31+
To run an individual test:
32+
33+
```bash
34+
pnpm test -- [partial filename]
35+
```
36+
37+
To start the entire test suite in watch mode:
38+
39+
```bash
40+
npx vitest
41+
```
42+
43+
#### TypeScript tests
44+
45+
**Don’t neglect writing TS tests!** In the test suite, you’ll see `// @ts-expect-error` comments. These are critical tests in and of themselves—they are asserting that TypeScript throws an error when it should be throwing an error (the test suite will actually fail in places if a TS error is _not_ raised).
46+
47+
As this is just a minimal fetch wrapper meant to provide deep type inference for API schemas, **testing TS types** is arguably more important than testing the runtime. So please make liberal use of `// @ts-expect-error`, and as a general rule of thumb, write more **unwanted** output tests than _wanted_ output tests.
48+
49+
### Running linting
50+
51+
Linting is handled via [Biome](https://biomejs.dev), a faster ESLint replacement. It was installed with `pnpm i` and can be run with:
52+
53+
```bash
54+
pnpm run lint
55+
```
56+
57+
### Changelogs
58+
59+
The changelog is generated via [changesets](https://github.com/changesets/changesets), and is separate from Git commit messages and pull request titles. To write a human-readable changelog for your changes, run:
60+
61+
```
62+
npx changeset
63+
```
64+
65+
This will ask if it’s a `patch`, `minor`, or `major` change ([semver](https://semver.org/)), along with a plain description of what you did. Commit this new file along with the rest of your PR, and during the next release this will go into the official changelog!
66+
67+
## Opening a Pull Request
68+
69+
Pull requests are **welcome** for this repo!
70+
71+
Bugfixes will always be accepted, though in some cases some small changes may be requested.
72+
73+
However, if adding a feature or breaking change, please **open an issue first to discuss.** This ensures no time or work is wasted writing code that won’t be accepted to the project (see [Project Goals](https://openapi-ts.dev/openapi-fetch/about/#project-goals)). Undiscussed feature work may be rejected at the discretion of the maintainers.
74+
75+
### Writing the commit
76+
77+
Create a new branch for your PR with `git checkout -b your-branch-name`. Add the relevant code as well as docs and tests. When you push everything up (`git push`), navigate back to your repo in GitHub and you should see a prompt to open a new PR.
78+
79+
While best practices for commit messages are encouraged (e.g. start with an imperative verb, keep it short, use the body if needed), this repo doesn’t follow any specific guidelines. Clarity is favored over strict rules. Changelogs are generated separately from git (see [the Changelogs section](#changelogs)).
80+
81+
### Writing the PR notes
82+
83+
**Please fill out the template!** It’s a very lightweight template 🙂.
84+
85+
### Adding docs
86+
87+
If you added a feature, or changed how something worked, please [update the docs](../../docs/)!
88+
89+
### Passing CI
90+
91+
All PRs must fix lint errors, and all tests must pass. PRs will not be merged until all CI checks are “green” (✅).

‎packages/openapi-vue-query/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Martin Paucot
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎packages/openapi-vue-query/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# openapi-vue-query
2+
3+
openapi-vue-query is a type-safe tiny wrapper (1 kb) around [@tanstack/vue-query](https://tanstack.com/query/latest/docs/framework/vue/overview) to work with OpenAPI schema.
4+
5+
It works by using [openapi-fetch](../openapi-fetch) and [openapi-typescript](../openapi-typescript) so you get all the following features:
6+
7+
- ✅ No typos in URLs or params.
8+
- ✅ All parameters, request bodies, and responses are type-checked and 100% match your schema
9+
- ✅ No manual typing of your API
10+
- ✅ Eliminates `any` types that hide bugs
11+
- ✅ Eliminates `as` type overrides that can also hide bugs
12+
13+
## Setup
14+
15+
Install this library along with [openapi-fetch](../openapi-fetch) and [openapi-typescript](../openapi-typescript):
16+
17+
```bash
18+
npm i openapi-vue-query openapi-fetch
19+
npm i -D openapi-typescript typescript
20+
```
21+
22+
Next, generate TypeScript types from your OpenAPI schema using openapi-typescript:
23+
24+
```bash
25+
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts
26+
```
27+
28+
## Usage
29+
30+
Once your types have been generated from your schema, you can create a [fetch client](../openapi-fetch), a vue-query client and start querying your API.
31+
32+
```vue
33+
<script setup lang="ts">
34+
import createFetchClient from "openapi-fetch";
35+
import createClient from "openapi-vue-query";
36+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
37+
38+
const fetchClient = createFetchClient<paths>({
39+
baseUrl: "https://myapi.dev/v1/",
40+
});
41+
const $api = createClient(fetchClient);
42+
43+
const { data, error, isPending } = $api.useQuery(
44+
"get",
45+
"/blogposts/{post_id}",
46+
{
47+
params: {
48+
path: { post_id: 5 },
49+
},
50+
}
51+
)
52+
</script>
53+
54+
<template>
55+
<div>
56+
<template v-if="isPending || !data">
57+
Loading...
58+
</template>
59+
<template v-else-if="error">
60+
An error occurred: {{ error.message }}
61+
</template>
62+
<template v-else>
63+
{{ data.title }}
64+
</template>
65+
</div>
66+
</template>
67+
```
68+
69+
> You can find more information about `createFetchClient` in the [openapi-fetch documentation](../openapi-fetch).
70+
71+
## 📓 Docs
72+
73+
[View Docs](https://openapi-ts.dev/openapi-vue-query/)

‎packages/openapi-vue-query/biome.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3+
"extends": ["../../biome.json"],
4+
"files": {
5+
"ignore": ["./test/fixtures/"]
6+
},
7+
"linter": {
8+
"rules": {
9+
"complexity": {
10+
"noBannedTypes": "off"
11+
},
12+
"suspicious": {
13+
"noConfusingVoidType": "off"
14+
}
15+
}
16+
}
17+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"name": "openapi-vue-query",
3+
"description": "Fast, type-safe @tanstack/vue-query client to work with your OpenAPI schema.",
4+
"version": "0.0.1",
5+
"author": {
6+
"name": "Hirotaka Mizutani",
7+
"email": "hirotaka@mizutani.to"
8+
},
9+
"license": "MIT",
10+
"type": "module",
11+
"main": "./dist/index.js",
12+
"module": "./dist/index.js",
13+
"types": "./dist/index.d.ts",
14+
"exports": {
15+
".": {
16+
"import": {
17+
"types": "./dist/index.d.ts",
18+
"default": "./dist/index.js"
19+
},
20+
"require": {
21+
"types": "./dist/index.d.ts",
22+
"default": "./dist/index.cjs"
23+
}
24+
},
25+
"./*": "./*"
26+
},
27+
"homepage": "https://openapi-ts.dev",
28+
"repository": {
29+
"type": "git",
30+
"url": "https://github.com/openapi-ts/openapi-typescript",
31+
"directory": "packages/openapi-vue-query"
32+
},
33+
"bugs": {
34+
"url": "https://github.com/openapi-ts/openapi-typescript/issues"
35+
},
36+
"keywords": [
37+
"openapi",
38+
"swagger",
39+
"rest",
40+
"api",
41+
"oapi_3",
42+
"oapi_3_1",
43+
"typescript",
44+
"fetch",
45+
"vue",
46+
"vue-query",
47+
"tanstack"
48+
],
49+
"scripts": {
50+
"build": "pnpm run build:clean && pnpm run build:esm && pnpm run build:cjs",
51+
"build:clean": "del-cli dist",
52+
"build:esm": "tsc -p tsconfig.build.json",
53+
"build:cjs": "esbuild --bundle --platform=node --target=es2019 --outfile=dist/index.cjs --external:typescript src/index.ts",
54+
"dev": "tsc -p tsconfig.build.json --watch",
55+
"format": "biome format . --write",
56+
"lint": "biome check .",
57+
"generate-types": "openapi-typescript test/fixtures/api.yaml -o test/fixtures/api.d.ts",
58+
"pretest": "pnpm run generate-types",
59+
"test": "pnpm run \"/^test:/\"",
60+
"test:js": "vitest run",
61+
"test:ts": "tsc --noEmit",
62+
"version": "pnpm run prepare && pnpm run build"
63+
},
64+
"dependencies": {
65+
"@tanstack/query-core": "^5.66.11",
66+
"openapi-typescript-helpers": "workspace:^"
67+
},
68+
"devDependencies": {
69+
"@tanstack/vue-query": "^5.66.0",
70+
"@testing-library/vue": "^8.1.0",
71+
"@vitejs/plugin-vue": "^5.2.1",
72+
"@vitejs/plugin-vue-jsx": "^4.1.1",
73+
"@vue/babel-plugin-jsx": "^1.2.5",
74+
"del-cli": "^5.1.0",
75+
"esbuild": "^0.24.2",
76+
"execa": "^8.0.1",
77+
"happy-dom": "^17.1.8",
78+
"jsdom": "^26.0.0",
79+
"msw": "^2.7.0",
80+
"openapi-fetch": "workspace:^",
81+
"openapi-typescript": "workspace:^",
82+
"vitest": "^2.1.8",
83+
"vue": "^3.5.13"
84+
},
85+
"peerDependencies": {
86+
"@tanstack/vue-query": "^5.66.0",
87+
"openapi-fetch": "workspace:^"
88+
}
89+
}
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import {
2+
type InfiniteData,
3+
type QueryClient,
4+
type QueryFunctionContext,
5+
type SkipToken,
6+
type UseInfiniteQueryOptions,
7+
type UseInfiniteQueryReturnType,
8+
type UseQueryOptions,
9+
type UseQueryReturnType,
10+
type UseMutationOptions,
11+
type UseMutationReturnType,
12+
useQuery,
13+
useInfiniteQuery,
14+
useMutation,
15+
} from "@tanstack/vue-query";
16+
import type {
17+
Client as FetchClient,
18+
ClientMethod,
19+
DefaultParamsOption,
20+
FetchResponse,
21+
MaybeOptionalInit,
22+
} from "openapi-fetch";
23+
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
24+
import type { DeepUnwrapRef } from "./types";
25+
26+
// Helper type to dynamically infer the type from the `select` property
27+
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
28+
29+
type InitWithUnknowns<Init> = Init & { [key: string]: unknown };
30+
31+
export type QueryKey<
32+
Paths extends Record<string, Record<HttpMethod, {}>>,
33+
Method extends HttpMethod,
34+
Path extends PathsWithMethod<Paths, Method>,
35+
Init = MaybeOptionalInit<Paths[Path], Method>,
36+
> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
37+
38+
export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
39+
Method extends HttpMethod,
40+
Path extends PathsWithMethod<Paths, Method>,
41+
Init extends MaybeOptionalInit<Paths[Path], Method>,
42+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
43+
Options extends Omit<
44+
DeepUnwrapRef<
45+
UseQueryOptions<
46+
Response["data"],
47+
Response["error"],
48+
InferSelectReturnType<Response["data"], Options["select"]>,
49+
Response["data"],
50+
QueryKey<Paths, Method, Path>
51+
>
52+
>,
53+
"queryKey" | "queryFn"
54+
>,
55+
>(
56+
method: Method,
57+
path: Path,
58+
...[init, options]: RequiredKeysOf<Init> extends never
59+
? [InitWithUnknowns<Init>?, Options?]
60+
: [InitWithUnknowns<Init>, Options?]
61+
) => NoInfer<
62+
Omit<
63+
DeepUnwrapRef<
64+
UseQueryOptions<
65+
Response["data"],
66+
Response["error"],
67+
InferSelectReturnType<Response["data"], Options["select"]>,
68+
Response["data"],
69+
QueryKey<Paths, Method, Path>
70+
>
71+
>,
72+
"queryFn"
73+
> & {
74+
queryFn: Exclude<
75+
DeepUnwrapRef<
76+
UseQueryOptions<
77+
Response["data"],
78+
Response["error"],
79+
InferSelectReturnType<Response["data"], Options["select"]>,
80+
Response["data"],
81+
QueryKey<Paths, Method, Path>
82+
>
83+
>["queryFn"],
84+
SkipToken | undefined
85+
>;
86+
}
87+
>;
88+
89+
export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
90+
Method extends HttpMethod,
91+
Path extends PathsWithMethod<Paths, Method>,
92+
Init extends MaybeOptionalInit<Paths[Path], Method>,
93+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
94+
Options extends Omit<
95+
DeepUnwrapRef<
96+
UseQueryOptions<
97+
Response["data"],
98+
Response["error"],
99+
InferSelectReturnType<Response["data"], Options["select"]>,
100+
Response["data"],
101+
QueryKey<Paths, Method, Path>
102+
>
103+
>,
104+
"queryKey" | "queryFn"
105+
>,
106+
>(
107+
method: Method,
108+
url: Path,
109+
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
110+
? [InitWithUnknowns<Init>?, Options?, QueryClient?]
111+
: [InitWithUnknowns<Init>, Options?, QueryClient?]
112+
) => UseQueryReturnType<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
113+
114+
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
115+
Method extends HttpMethod,
116+
Path extends PathsWithMethod<Paths, Method>,
117+
Init extends MaybeOptionalInit<Paths[Path], Method>,
118+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
119+
Options extends Omit<
120+
UseInfiniteQueryOptions<
121+
Response["data"],
122+
Response["error"],
123+
InfiniteData<Response["data"]>,
124+
Response["data"],
125+
QueryKey<Paths, Method, Path>,
126+
unknown
127+
>,
128+
"queryKey" | "queryFn"
129+
> & {
130+
pageParamName?: string;
131+
},
132+
>(
133+
method: Method,
134+
url: Path,
135+
init: InitWithUnknowns<Init>,
136+
options: Options,
137+
queryClient?: QueryClient,
138+
) => UseInfiniteQueryReturnType<InfiniteData<Response["data"]>, Response["error"]>;
139+
140+
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
141+
Method extends HttpMethod,
142+
Path extends PathsWithMethod<Paths, Method>,
143+
Init extends MaybeOptionalInit<Paths[Path], Method>,
144+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
145+
Options extends Omit<UseMutationOptions<Response["data"], Response["error"], Init>, "mutationKey" | "mutationFn">,
146+
>(
147+
method: Method,
148+
url: Path,
149+
options?: Options,
150+
queryClient?: QueryClient,
151+
) => UseMutationReturnType<Response["data"], Response["error"], Init, unknown>;
152+
153+
export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
154+
queryOptions: QueryOptionsFunction<Paths, Media>;
155+
useQuery: UseQueryMethod<Paths, Media>;
156+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
157+
useMutation: UseMutationMethod<Paths, Media>;
158+
}
159+
160+
export type MethodResponse<
161+
CreatedClient extends OpenapiQueryClient<any, any>,
162+
Method extends HttpMethod,
163+
Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>
164+
? PathsWithMethod<Paths, Method>
165+
: never,
166+
Options = object,
167+
> = CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
168+
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
169+
: never;
170+
171+
// TODO: Add the ability to bring queryClient as argument
172+
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
173+
client: FetchClient<Paths, Media>,
174+
): OpenapiQueryClient<Paths, Media> {
175+
const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
176+
queryKey: [method, path, init],
177+
signal,
178+
}: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
179+
const mth = method.toUpperCase() as Uppercase<typeof method>;
180+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
181+
const { data, error } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any
182+
if (error) {
183+
throw error;
184+
}
185+
186+
return data;
187+
};
188+
189+
const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
190+
// @ts-expect-error FIX: fix type error
191+
queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey<
192+
Paths,
193+
typeof method,
194+
typeof path
195+
>,
196+
// @ts-expect-error FIX: fix type error
197+
queryFn,
198+
...options,
199+
});
200+
201+
return {
202+
queryOptions,
203+
useQuery: (method, path, ...[init, options, queryClient]) =>
204+
// @ts-expect-error FIX: fix type error
205+
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
206+
useInfiniteQuery: (method, path, init, options, queryClient) => {
207+
const { pageParamName = "cursor", ...restOptions } = options;
208+
const { queryKey } = queryOptions(method, path, init);
209+
return useInfiniteQuery(
210+
{
211+
// @ts-expect-error FIX: fix type error
212+
queryKey,
213+
queryFn: async ({ queryKey: [method, path, init], pageParam = 0, signal }) => {
214+
const mth = method.toUpperCase() as Uppercase<typeof method>;
215+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
216+
const mergedInit = {
217+
...init,
218+
signal,
219+
params: {
220+
...(init?.params || {}),
221+
query: {
222+
...(init?.params as { query?: DefaultParamsOption })?.query,
223+
[pageParamName]: pageParam,
224+
},
225+
},
226+
};
227+
228+
// @ts-expect-error FIX: fix type error
229+
const { data, error } = await fn(path, mergedInit as any);
230+
if (error) {
231+
throw error;
232+
}
233+
return data;
234+
},
235+
...restOptions,
236+
},
237+
queryClient,
238+
);
239+
},
240+
useMutation: (method, path, options, queryClient) =>
241+
// @ts-expect-error FIX: fix type error
242+
useMutation(
243+
{
244+
mutationKey: [method, path],
245+
mutationFn: async (init) => {
246+
const mth = method.toUpperCase() as Uppercase<typeof method>;
247+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
248+
const { data, error } = await fn(path, init as InitWithUnknowns<typeof init>);
249+
if (error) {
250+
throw error;
251+
}
252+
253+
return data as Exclude<typeof data, undefined>;
254+
},
255+
...options,
256+
},
257+
queryClient,
258+
),
259+
};
260+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { ComputedRef, Ref, UnwrapRef } from "vue";
2+
3+
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
4+
type UnwrapLeaf =
5+
| Primitive
6+
| Function
7+
| Date
8+
| Error
9+
| RegExp
10+
| Map<any, any>
11+
| WeakMap<any, any>
12+
| Set<any>
13+
| WeakSet<any>;
14+
15+
export type MaybeRef<T> = Ref<T> | ComputedRef<T> | T;
16+
17+
export type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T);
18+
19+
export type MaybeRefDeep<T> = MaybeRef<
20+
T extends Function
21+
? T
22+
: T extends object
23+
? {
24+
[Property in keyof T]: MaybeRefDeep<T[Property]>;
25+
}
26+
: T
27+
>;
28+
29+
export type NoUnknown<T> = Equal<unknown, T> extends true ? never : T;
30+
31+
export type Equal<TTargetA, TTargetB> = (<T>() => T extends TTargetA ? 1 : 2) extends <T>() => T extends TTargetB
32+
? 1
33+
: 2
34+
? true
35+
: false;
36+
37+
export type DeepUnwrapRef<T> = T extends UnwrapLeaf
38+
? T
39+
: T extends Ref<infer U>
40+
? DeepUnwrapRef<U>
41+
: T extends {}
42+
? {
43+
[Property in keyof T]: DeepUnwrapRef<T[Property]>;
44+
}
45+
: UnwrapRef<T>;
46+
47+
export type DistributiveOmit<T, TKeyOfAny extends keyof any> = T extends any ? Omit<T, TKeyOfAny> : never;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { ComputedRef, Ref, UnwrapRef } from "vue";
2+
3+
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
4+
type UnwrapLeaf =
5+
| Primitive
6+
| Function
7+
| Date
8+
| Error
9+
| RegExp
10+
| Map<any, any>
11+
| WeakMap<any, any>
12+
| Set<any>
13+
| WeakSet<any>;
14+
15+
export type MaybeRef<T> = Ref<T> | ComputedRef<T> | T;
16+
17+
export type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T);
18+
19+
export type MaybeRefDeep<T> = MaybeRef<
20+
T extends Function
21+
? T
22+
: T extends object
23+
? {
24+
[Property in keyof T]: MaybeRefDeep<T[Property]>;
25+
}
26+
: T
27+
>;
28+
29+
export type NoUnknown<T> = Equal<unknown, T> extends true ? never : T;
30+
31+
export type Equal<TTargetA, TTargetB> = (<T>() => T extends TTargetA ? 1 : 2) extends <T>() => T extends TTargetB
32+
? 1
33+
: 2
34+
? true
35+
: false;
36+
37+
export type DeepUnwrapRef<T> = T extends UnwrapLeaf
38+
? T
39+
: T extends Ref<infer U>
40+
? DeepUnwrapRef<U>
41+
: T extends {}
42+
? {
43+
[Property in keyof T]: DeepUnwrapRef<T[Property]>;
44+
}
45+
: UnwrapRef<T>;
46+
47+
export type DistributiveOmit<T, TKeyOfAny extends keyof any> = T extends any ? Omit<T, TKeyOfAny> : never;

‎packages/openapi-vue-query/test/fixtures/api.d.ts

Lines changed: 1108 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/openapi-vue-query/test/fixtures/api.yaml

Lines changed: 764 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {
2+
http,
3+
HttpResponse,
4+
type JsonBodyType,
5+
type StrictRequest,
6+
type DefaultBodyType,
7+
type HttpResponseResolver,
8+
type PathParams,
9+
type AsyncResponseResolverReturnType,
10+
} from "msw";
11+
import { setupServer } from "msw/node";
12+
13+
/**
14+
* Mock server instance
15+
*/
16+
export const server = setupServer();
17+
18+
/**
19+
* Default baseUrl for tests
20+
*/
21+
export const baseUrl = "https://api.example.com" as const;
22+
23+
/**
24+
* Test path helper, returns an absolute URL based on
25+
* the given path and base
26+
*/
27+
export function toAbsoluteURL(path: string, base: string = baseUrl) {
28+
// If we have absolute path
29+
// if (URL.canParse(path)) {
30+
// return new URL(path).toString();
31+
// }
32+
33+
// Otherwise we want to support relative paths
34+
// where base may also contain some part of the path
35+
// e.g.
36+
// base = https://api.foo.bar/v1/
37+
// path = /self
38+
// should result in https://api.foo.bar/v1/self
39+
40+
// Construct base URL
41+
const baseUrlInstance = new URL(base);
42+
43+
// prepend base url url pathname to path and ensure only one slash between the URL parts
44+
const newPath = `${baseUrlInstance.pathname}/${path}`.replace(/\/+/g, "/");
45+
46+
return new URL(newPath, baseUrlInstance).toString();
47+
}
48+
49+
export type MswHttpMethod = keyof typeof http;
50+
51+
export interface MockRequestHandlerOptions<
52+
// Recreate the generic signature of the HTTP resolver
53+
// so the arguments passed to http handlers propagate here.
54+
Params extends PathParams<keyof Params> = PathParams,
55+
RequestBodyType extends DefaultBodyType = DefaultBodyType,
56+
ResponseBodyType extends DefaultBodyType = undefined,
57+
> {
58+
baseUrl?: string;
59+
method: MswHttpMethod;
60+
/**
61+
* Relative or absolute path to match.
62+
* When relative, baseUrl will be used as base.
63+
*/
64+
path: string;
65+
body?: JsonBodyType;
66+
headers?: Record<string, string>;
67+
status?: number;
68+
69+
/**
70+
* Optional handler which will be called instead of using the body, headers and status
71+
*/
72+
handler?: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>;
73+
}
74+
75+
/**
76+
* Configures a msw request handler using the provided options.
77+
*/
78+
export function useMockRequestHandler<
79+
// Recreate the generic signature of the HTTP resolver
80+
// so the arguments passed to http handlers propagate here.
81+
Params extends PathParams<keyof Params> = PathParams,
82+
RequestBodyType extends DefaultBodyType = DefaultBodyType,
83+
ResponseBodyType extends DefaultBodyType = undefined,
84+
>({
85+
baseUrl: requestBaseUrl,
86+
method,
87+
path,
88+
body,
89+
headers,
90+
status,
91+
handler,
92+
}: MockRequestHandlerOptions<Params, RequestBodyType, ResponseBodyType>) {
93+
let requestUrl = "";
94+
let receivedRequest: StrictRequest<DefaultBodyType>;
95+
let receivedCookies: Record<string, string> = {};
96+
97+
const resolvedPath = toAbsoluteURL(path, requestBaseUrl);
98+
99+
server.use(
100+
http[method]<Params, RequestBodyType, ResponseBodyType>(resolvedPath, (args) => {
101+
requestUrl = args.request.url;
102+
receivedRequest = args.request.clone();
103+
receivedCookies = { ...args.cookies };
104+
105+
if (handler) {
106+
return handler(args);
107+
}
108+
109+
return HttpResponse.json(body as any, {
110+
status: status ?? 200,
111+
headers,
112+
}) as AsyncResponseResolverReturnType<ResponseBodyType>;
113+
}),
114+
);
115+
116+
return {
117+
getRequestCookies: () => receivedCookies,
118+
getRequest: () => receivedRequest,
119+
getRequestUrl: () => new URL(requestUrl),
120+
};
121+
}

‎packages/openapi-vue-query/test/fixtures/openapi-typescript-codegen.min.js

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { createApp, defineComponent, h } from "vue";
2+
import { VueQueryPlugin } from "@tanstack/vue-query";
3+
4+
type InstanceType<V> = V extends { new (...arg: any[]): infer X } ? X : never;
5+
type VM<V> = InstanceType<V> & { unmount: () => void };
6+
7+
export function renderHook<TResult>(setup: () => TResult): {
8+
result: TResult;
9+
rerender: () => void;
10+
unmount: () => void;
11+
} {
12+
let result!: TResult;
13+
14+
const Comp = defineComponent({
15+
setup() {
16+
result = setup();
17+
return () => h("div");
18+
},
19+
});
20+
21+
const mounted = mount(Comp);
22+
23+
const rerender = () => {
24+
mounted.unmount();
25+
mount(Comp);
26+
};
27+
28+
return {
29+
result,
30+
rerender,
31+
unmount: mounted.unmount,
32+
} as { result: TResult; rerender: () => void; unmount: () => void };
33+
}
34+
35+
const vueQueryPoluginOptions = {
36+
queryClientConfig: {
37+
defaultOptions: {
38+
queries: {
39+
retry: 0,
40+
},
41+
},
42+
},
43+
};
44+
45+
function mount<V>(Comp: V) {
46+
const el = document.createElement("div");
47+
const app = createApp(Comp as any);
48+
app.use(VueQueryPlugin, vueQueryPoluginOptions);
49+
const unmount = () => app.unmount();
50+
const comp = app.mount(el) as any as VM<V>;
51+
comp.unmount = unmount;
52+
53+
return comp;
54+
}

‎packages/openapi-vue-query/test/index.test.tsx

Lines changed: 942 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/openapi-vue-query/todo.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
## Changes
2+
3+
Implements <https://github.com/openapi-ts/openapi-typescript/issues/2160>.
4+
5+
## How to Review
6+
7+
_How can a reviewer review your changes? What should be kept in mind for this review?_
8+
9+
## Todo
10+
11+
The goal of this PR is to implement all features supported by openapi-react-query.
12+
13+
### Roughly implementation
14+
15+
- [x] queryOptions
16+
- [x] useQuery
17+
- [x] useMutation
18+
- [x] useInfiniteQuey
19+
20+
vue-query doesn't support following features:
21+
22+
- useSuspenseQuery
23+
24+
### Skipped test cases
25+
26+
- queryOptions
27+
- [ ] returns query options that can be passed to useQueries
28+
- [ ] returns query options that can be passed to useQuery
29+
- [ ] returns query options without an init
30+
- useQuery
31+
- [ ] should infer correct data and error type
32+
- [ ] should infer correct data when used with select property
33+
- [ ] should use provided custom queryClient
34+
- [ ] uses provided options
35+
- useMutation
36+
- [ ] should use provided custom queryClient
37+
- mutateAsync
38+
- [ ] should use provided custom queryClient
39+
40+
### docs
41+
42+
- [ ] README.md
43+
- [ ] docs/
44+
45+
## Checklist
46+
47+
- [ ] Unit tests updated
48+
- [ ] `docs/` updated (if necessary)
49+
- [ ] `pnpm run update:examples` run (only applicable for openapi-typescript)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"include": ["src"]
4+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"lib": ["ESNext", "DOM"],
5+
"module": "ESNext",
6+
"moduleResolution": "Bundler",
7+
"outDir": "dist",
8+
"skipLibCheck": true,
9+
"sourceRoot": ".",
10+
"jsx": "preserve",
11+
"jsxImportSource": "vue",
12+
"target": "ES2022",
13+
"types": ["vitest/globals"]
14+
// "noErrorTruncation": true
15+
},
16+
"include": ["src", "test", "*.ts"],
17+
"exclude": ["node_modules"]
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineConfig, type Plugin } from "vitest/config";
2+
import vue from "@vitejs/plugin-vue";
3+
import vueJsx from "@vitejs/plugin-vue-jsx";
4+
5+
export default defineConfig({
6+
plugins: [vue() as unknown as Plugin, vueJsx() as unknown as Plugin],
7+
test: {
8+
environment: "happy-dom",
9+
globals: true,
10+
},
11+
});

‎pnpm-lock.yaml

Lines changed: 1114 additions & 213 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.