Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#2160): support @tanstack/vue-query #2161

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/openapi-vue-query/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: About openapi-vue-query
description: openapi-vue-query Project Goals and contributors
---
<script setup>
import { VPTeamMembers } from 'vitepress/theme';
import contributors from '../data/contributors.json';
</script>

# About

## Project Goals

1. Types should be strict and inferred automatically from OpenAPI schemas with the absolute minimum number of generics needed.
2. Respect the original `@tanstack/vue-query` APIs while reducing boilerplate.
3. Be as light and performant as possible.

## Contributors

This library wouldn’t be possible without all these amazing contributors:

<VPTeamMembers size="small" :members="contributors['openapi-vue-query']" />
125 changes: 125 additions & 0 deletions docs/openapi-vue-query/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: openapi-vue-query
---
# Introduction

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.

It works by using [openapi-fetch](../openapi-fetch/) and [openapi-typescript](../introduction) so you get all the following features:

- ✅ No typos in URLs or params.
- ✅ All parameters, request bodies, and responses are type-checked and 100% match your schema
- ✅ No manual typing of your API
- ✅ Eliminates `any` types that hide bugs
- ✅ Also eliminates `as` type overrides that can also hide bugs

::: code-group

```vue [src/my-component.vue]
<script setup lang="ts">
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
const $api = createClient(fetchClient);

const { data, error, isLoading } = $api.useQuery(
"get",
"/blogposts/{post_id}",
{
params: {
path: { post_id: 5 },
},
}
)
</script>

<template>
<div>
<template v-if="isLoading || !data">
Loading...
</template>
<template v-else-if="error">
An error occurred: {{ error.message }}
</template>
<template v-else>
{{ data.title }}
</template>
</div>
</template>
```

:::

## Setup

Install this library along with [openapi-fetch](../openapi-fetch/) and [openapi-typescript](../introduction):

```bash
npm i openapi-vue-query openapi-fetch
npm i -D openapi-typescript typescript
```

::: tip Highly recommended

Enable [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess) in your `tsconfig.json` ([docs](/advanced#enable-nouncheckedindexedaccess-in-tsconfig))

:::

Next, generate TypeScript types from your OpenAPI schema using openapi-typescript:

```bash
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts
```

## Basic usage

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.

::: code-group

```vue [src/my-component.vue]
<script setup lang="ts">
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
const $api = createClient(fetchClient);

const { data, error, isLoading } = $api.useQuery(
"get",
"/blogposts/{post_id}",
{
params: {
path: { post_id: 5 },
},
}
)
</script>

<template>
<div>
<template v-if="isLoading || !data">
Loading...
</template>
<template v-else-if="error">
An error occurred: {{ error.message }}
</template>
<template v-else>
{{ data.title }}
</template>
</div>
</template>
```

:::

::: tip
You can find more information about `createFetchClient` on the [openapi-fetch documentation](../openapi-fetch/index.md).
:::
131 changes: 131 additions & 0 deletions docs/openapi-vue-query/query-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
title: queryOptions
---
# {{ $frontmatter.title }}

The `queryOptions` method allows you to construct type-safe [Query Options](https://tanstack.com/query/latest/docs/framework/vue/guides/query-options).

`queryOptions` can be used together with `@tanstack/vue-query` APIs that take query options, such as
[useQuery](https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery),
[useQueries](https://tanstack.com/query/latest/docs/framework/vue/reference/useQueries) and
[QueryClient.fetchQuery](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientfetchquery)
among many others.

If you would like to use a query API that is not explicitly supported by `openapi-vue-query`, this is the way to go.

## Examples

[useQuery example](use-query#example) rewritten using `queryOptions`.

::: code-group

```vue [src/App.vue]
<script setup lang="ts">
import { useQuery } from "@tanstack/vue-query";
import { $api } from "./api";

const { data, error, isLoading } = useQuery(
$api.queryOptions("get", "/users/{user_id}", {
params: {
path: { user_id: 5 },
},
}),
);
</script>

<template>
<div>
<template v-if="!data || isLoading">
Loading...
</template>
<template v-else-if="error">
An error occured: {{ error.message }}
</template>
<template v-else>
{{ data.firstname }}
</template>
</div>
</template>
```

```ts [src/api.ts]
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
export const $api = createClient(fetchClient);
```

:::

::: info Good to Know

[useQuery](use-query) uses `queryOptions` under the hood.

:::

Usage with [useQueries](https://tanstack.com/query/latest/docs/framework/vue/reference/useQueries).

::: code-group

```tsx [src/use-users-by-id.ts]
import { useQueries } from '@tanstack/vue-query';
import { $api } from "./api";

export const useUsersById = (userIds: number[]) => (
useQueries({
queries: userIds.map((userId) => (
$api.queryOptions("get", "/users/{user_id}", {
params: {
path: { user_id: userId },
},
})
))
})
);
```

```ts [src/api.ts]
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
export const $api = createClient(fetchClient);
```

:::

## Api

```tsx
const queryOptions = $api.queryOptions(method, path, options, queryOptions);
```

**Arguments**

- `method` **(required)**
- The HTTP method to use for the request.
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `path` **(required)**
- The pathname to use for the request.
- Must be an available path for the given method in your schema.
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `options`
- The fetch options to use for the request.
- Only required if the OpenApi schema requires parameters.
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `queryOptions`
- Additional query options to pass through.

**Returns**

- [Query Options](https://tanstack.com/query/latest/docs/framework/vue/guides/query-options)
- Fully typed thus `data` and `error` will be correctly deducted.
- `queryKey` is `[method, path, params]`.
- `queryFn` is set to a fetcher function.
107 changes: 107 additions & 0 deletions docs/openapi-vue-query/use-infinite-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: useInfiniteQuery
---

# {{ $frontmatter.title }}

The `useInfiniteQuery` method allows you to use the original [useInfiniteQuery](https://tanstack.com/query/latest/docs/framework/vue/guides/infinite-queries)

- The result is the same as the original function.
- The `queryKey` is `[method, path, params]`.
- `data` and `error` are fully typed.
- You can pass infinite query options as fourth parameter.

::: tip
You can find more information about `useInfiniteQuery` on the [@tanstack/vue-query documentation](https://tanstack.com/query/latest/docs/framework/vue/guides/infinite-queries).
:::

## Example

::: code-group

```vue [src/App.vue]
<script setup lang="ts">
import { $api } from "./api";
const { data, error, fetchNextPage, hasNextPage, isFetching, isError, isFetchingNextPage } = $api.useInfiniteQuery(
"get",
"/posts",
{
params: {
query: {
limit: 10,
},
},
},
{
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: 0,
},
);
</script>

<template>
<span v-if="isFetching">Loading...</span>
<span v-else-if="isError">Error: {{ error?.message }}</span>
<div v-else-if="data">
<span v-if="isFetching && !isFetchingNextPage">Fetching...</span>
<ul v-for="(group, index) in data.pages" :key="index">
<li v-for="post in group.posts" :key="post.id">
{{ post.name }}
</li>
</ul>
<button
@click="() => fetchNextPage()"
:disabled="!hasNextPage || isFetchingNextPage"
>
<span v-if="isFetchingNextPage">Loading more...</span>
<span v-else-if="hasNextPage">Load More</span>
<span v-else>Nothing more to load</span>
</button>
</div>
</template>
```

```ts [src/api.ts]
import createFetchClient from "openapi-fetch";
import createClient from "openapi-vue-query";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const fetchClient = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
export const $api = createClient(fetchClient);
```

:::

## Api

```tsx
const query = $api.useInfiniteQuery(
method,
path,
options,
infiniteQueryOptions,
queryClient
);
```

**Arguments**

- `method` **(required)**
- The HTTP method to use for the request.
- The method is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `path` **(required)**
- The pathname to use for the request.
- Must be an available path for the given method in your schema.
- The pathname is used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `options`
- The fetch options to use for the request.
- Only required if the OpenApi schema requires parameters.
- The options `params` are used as key. See [Query Keys](https://tanstack.com/query/latest/docs/framework/vue/guides/query-keys) for more information.
- `infiniteQueryOptions`
- The original `useInfiniteQuery` options.
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useInfiniteQuery)
- `queryClient`
- The original `queryClient` option.
- [See more information](https://tanstack.com/query/latest/docs/framework/vue/reference/useInfiniteQuery)
Loading