Kubbを使ってOpenAPIスキーマからTanStack Queryのhooksを生成する

# はじめに
今回はKubbというコード生成ライブラリを紹介します

直近ではorvalを個人的に使っているのですが、Kubbというライブラリを見つけたので素振りがてら記事を書くことにしました
仕事で良くTanStack Queryを使用するので、TanStack Queryのhooks生成までを今回はやってみることにします

ちなみにKubbはスウェーデン生まれのスポーツで、スウェーデン語で「薪」を意味していているらしい（多分）

https://kubb.dev/

# とりあえずインストール
Kubbは各機能をヘルパーやプラグインとして提供しているので、インストールするものは少し多いです

軽く概要↓
- @kubb/cli：cliツール
- @kubb/core：ファイルシステムの読み書きなど根幹の機能
- @kubb/swagger-tanstack-query：TanStack Queryのhooksを生成するためのプラグイン
- @kubb/swagger-ts：Swaggerファイルに基づいてTypeScript型を作成


```shell
yarn add @kubb/cli @kubb/core @kubb/swagger-tanstack-query @kubb/swagger-ts
```

# 設定ファイル
設定はorvalとほぼ変わらず、大枠inputとoutputで構成されています。
pluginを設定出来る点だけが違いそうですね。eslintのルールを足していく感覚と少しにているかも。

```ts:kubb.config.ts
import { defineConfig } from "@kubb/core"
import { pluginTanstackQuery } from "@kubb/swagger-tanstack-query"
import { pluginOas } from "@kubb/plugin-oas"
import { pluginTs } from "@kubb/swagger-ts"

export default defineConfig({
  root: ".",
  input: {
    path: "./petStore.yaml",
  },
  output: {
    path: "./src/gen",
    clean: true,
  },
  plugins: [
    pluginTanstackQuery({
      output: {
        path: "./hooks",
      },
    }),
    pluginTs(),
    pluginOas(),
  ],
})

```

# 生成してみる
これだけでOK
`kubb`は`kubb generate`のエイリアスなので`kubb`でも可
```shell
yarn kubb
```

jsonは要らなかったけどなぜか生成された。`@kubb/plugin-oas`が悪さしてそうだけど、gen時に足りないよって怒られたから入れてみた。

genディレクトリ配下↓
```shell
.
├── hooks
│   ├── index.ts
│   ├── useCreatePets.ts
│   ├── useListPets.ts
│   └── useShowPetById.ts
├── index.ts
├── schemas
│   ├── Error.json
│   ├── Pet.json
│   └── Pets.json
└── types
    ├── CreatePets.ts
    ├── Error.ts
    ├── ListPets.ts
    ├── Pet.ts
    ├── Pets.ts
    ├── ShowPetById.ts
    └── index.ts
```

APIクライアントは別途設定する必要がありそう。
```typescript:src/hooks/useListPets.ts
import client from "@kubb/swagger-client/client";
import { useQuery } from "@tanstack/react-query";
import type { ListPetsQueryResponse, ListPetsQueryParams } from "../types/ListPets";
import type { UseBaseQueryOptions, UseQueryResult, QueryKey, WithRequired } from "@tanstack/react-query";

type ListPetsClient = typeof client<ListPetsQueryResponse, never, never>;
type ListPets = {
    data: ListPetsQueryResponse;
    error: never;
    request: never;
    pathParams: never;
    queryParams: ListPetsQueryParams;
    headerParams: never;
    response: ListPetsQueryResponse;
    client: {
        parameters: Partial<Parameters<ListPetsClient>[0]>;
        return: Awaited<ReturnType<ListPetsClient>>;
    };
};
export const listPetsQueryKey = (params?: ListPets["queryParams"]) => [{ url: "/pets" }, ...(params ? [params] : [])] as const;
export type ListPetsQueryKey = ReturnType<typeof listPetsQueryKey>;
export function listPetsQueryOptions<TData = ListPets["response"], TQueryData = ListPets["response"]>(params?: ListPets["queryParams"], options: ListPets["client"]["parameters"] = {}): WithRequired<UseBaseQueryOptions<ListPets["response"], ListPets["error"], TData, TQueryData>, "queryKey"> {
    const queryKey = listPetsQueryKey(params);
    return {
        queryKey,
        queryFn: async () => {
            const res = await client<ListPets["data"], ListPets["error"]>({
                method: "get",
                url: `/pets`,
                params,
                ...options
            });
            return res.data;
        },
    };
}
/**
 * @summary List all pets
 * @link /pets
 */
export function useListPets<TData = ListPets["response"], TQueryData = ListPets["response"], TQueryKey extends QueryKey = ListPetsQueryKey>(params?: ListPets["queryParams"], options: {
    query?: Partial<UseBaseQueryOptions<ListPets["response"], ListPets["error"], TData, TQueryData, TQueryKey>>;
    client?: ListPets["client"]["parameters"];
} = {}): UseQueryResult<TData, ListPets["error"]> & {
    queryKey: TQueryKey;
} {
    const { query: queryOptions, client: clientOptions = {} } = options ?? {};
    const queryKey = queryOptions?.queryKey ?? listPetsQueryKey(params);
    const query = useQuery<ListPets["data"], ListPets["error"], TData, any>({
        ...listPetsQueryOptions<TData, TQueryData>(params, clientOptions),
        queryKey,
        ...queryOptions
    }) as UseQueryResult<TData, ListPets["error"]> & {
        queryKey: TQueryKey;
    };
    query.queryKey = queryKey as TQueryKey;
    return query;
}
```

# 所感
細かく設定出来る反面、必要なモノの取捨選択の難易度が一定ありそうな雰囲気を感じました。楽に始められる点で考えるとorvalの方が、軍配が上がるかなという所感です

その他プラグインもこれだけ存在し、かなり自由度高く設定出来そうです。
- @kubb/plugin-oas
- @kubb/swagger-client
- @kubb/swagger-ts
- @kubb/swagger-zod
- @kubb/swagger-zodios
- @kubb/swagger-swr
- @kubb/swagger-faker
- @kubb/swagger-msw
- @kubb/plugin-redoc
- @kubb/parser-ts
- @kubb/oas
- @kubb/react 

# Kubbの行く末
orval作成時期は4年前、Kubbは1年前でありダウンロード数も2024/06/19時点で天と地の差がありますが、各機能をモジュールという形で提供していたり、作者がBlogで

>At this moment Kubb is a library to create clients based on a Swagger file but Kubb was designed to also work with something else than Swagger. Because everything is a plugin, it will also make it really easy for other developers to create their plugin that implements for example a generation for GraphQL. See Create your plugin for more information about how to create your plugin.

このように言っていたりすることを考えると、今後のスタンダードになり得る候補の一つかなと思いました。

ダウンロード数↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864822/66e77d1f-9e67-0ab7-668f-6df7ec2721fd.png)

作成日↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864822/7ae5ab12-9539-f212-aaf4-74adf44ac84c.png)



# おわりに
株式会社HRBrainでは新しいメンバーを募集しています。

https://www.hrbrain.co.jp/recruit