【TypeScript / tRPC】既に存在する型から楽にスキーマを作成したい

# 使用環境
OS: windows11
エディタ: Cursor（VSCodeでもOK？）
フレームワーク: Next.js
# 結論
1.  VSCode拡張機能のts-type-expandを使い、型の情報をテキストで取得
1.  取得した情報をAIにぶん投げる
# 背景（読み飛ばしてOK）
## コードの状況
tRPCを使う多人数開発のプロジェクトで、スキーマを定義しないままrouterを通していたファイルがあった。
既にfetchHoge.tsは完成されており、筆者がuseHoge.tsを作る、という状況。
```:src/features/hoge/
./
├ _api/
│    ├ fetchHoge.ts/
│    ├ useHoge.ts/ # new!
│    └ router.ts/
├ _components/
│    └ ...some files
├ types.ts
```
```:router.ts
import { fetchHogeHandler } from "./fetchHoge";
import { useHogeHandler } from "./useHoge";
import { createTRPCRouter, protectedProcedure } from "...";
import { z } from "zod";

export const HogeRouter = createTRPCRouter({
  fetchHoge: protectedProcedure
    .input(
      z.object({
      ...
      }),
    )
    .mutation(async ({ ctx, input }) => {
      return fetchHoge({
        ...
      });
    }),

  useHoge: protectedProcedure
    .input(
      z.object({
        Hoge: // ここにスキーマがほしい 
      }),
    )
    .mutation(async ({ ctx, input }) => {
      return useHogeHandler({
        Hoge: input.hoge
      });
    }),
});

```
fetchHogeプロシージャにはoutputの型は示されていないが、tRPCで提供されている型推察のRouterOutputを使うことにより、クライアント側では型安全な処理が出来ている。
[tRPCの型推察について](#https://trpc.io/docs/client/vanilla/infer-types)
しかし、スキーマは存在しないので、useHogeプロシージャのinput用にどうにか型情報からスキーマを作成したい…

## 試したこと
### 手入力で作ろうとした
おそらく一番無難な解決方法。
…が、Hogeにあるプロパティ数が多すぎてVSCode上でカーソルを当てたときに省略されてしまう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067269/98d52cf1-ae0d-49d0-8f88-d280ab08610d.png)
そもそもプロパティの多い型を可視化できないか？と思って調べたところ、ts-type-expandを見つける。
[VSCodeのホバー情報で省略されてしまうTypeScriptの型情報をたっぷり表示させたい](#http://qiita.com/fukasawah/items/2f8b37db8eb711448cd8#%E3%81%8A%E3%81%BE%E3%81%91-%E5%A4%89%E6%95%B0%E3%81%AE%E5%9E%8B%E6%83%85%E5%A0%B1%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AB%E4%BD%BF%E3%81%88%E3%81%9D%E3%81%86%E3%81%AA%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD)
tsconfig.jsonをいじる手もあるみたいだが、これは.gitignoreに乗っていないためローカルのみの変更ができず、断念。

# スキーマの作り方（本編）
1. [ts-type-expand](#https://marketplace.visualstudio.com/items?itemName=kimuson.ts-type-expand)をインストールし起動
1. スキーマを作りたい型を準備

    ```:hogeType.ts
    type hoge = {
      enumProperty: "option1" | "option2" | "option3";
      arbitralProperty?: string;
      nullableProperty: number | null;
      arrayProperty: string[];
      objectProperty?: {
        nestedProperty: boolean;
      };
      unionProperty: number | string;
      intersectionProperty: number & string;
    };
    ```
    
1. hogeをクリックしてts-type-expand上に表示
1. 型情報の全体をCopyボタンでテキスト出力

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4067269/a583b5fc-b38e-426d-833e-48b421b3ac55.png)
    ```:出力した型
    type hoge = { 'enumProperty': "option1" | "option2" | "option3", 'arbitralProperty': undefined | string, 'nullableProperty': null | number, 'arrayProperty': Array<string>, 'objectProperty': undefined | { 'nestedProperty': false | true }, 'unionProperty': string | number, 'intersectionProperty': never }
    ```

1. AIに以下の文とともに投げる
    ```:プロンプト to gpt-4o-mini
    このテキストで示した型と同じ構造のものをzodオブジェクトとして出力してください。
    undefinedやnullとのUnion型は.optional()や.nullable()を使用、
    literal型のUnion型として表現されているものは、enum型に変更することで可読性を高めてください。
    type hoge = ...
    ```
```:result.ts
import { z } from 'zod';

const HogeEnum = z.enum(["option1", "option2", "option3"]);

const HogeSchema = z.object({
  enumProperty: HogeEnum,
  arbitralProperty: z.string().optional(),
  nullableProperty: z.number().nullable(),
  arrayProperty: z.array(z.string()),
  objectProperty: z.object({
    nestedProperty: z.boolean()
  }).optional(),
  unionProperty: z.union([z.string(), z.number()]),
  intersectionProperty: z.never()
});
```
これで手書きせずにスキーマを書き起こすことができた。
筆者はCursorを使っているのでCursorChat内のgpt-4o-miniを使ったが、おそらくただのチャットボットを叩いても同じ結果が返ってくるはず。
仮に違う結果でも、手書きの工数は大幅に削減できそう。

# 参考

- [VSCodeのホバー情報で省略されてしまうTypeScriptの型情報をたっぷり表示させたい](#http://qiita.com/fukasawah/items/2f8b37db8eb711448cd8#%E3%81%8A%E3%81%BE%E3%81%91-%E5%A4%89%E6%95%B0%E3%81%AE%E5%9E%8B%E6%83%85%E5%A0%B1%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AB%E4%BD%BF%E3%81%88%E3%81%9D%E3%81%86%E3%81%AA%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD)