@einja/dev-cli 0.1.48 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,11 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  var _a;
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
3
6
  import chalk from "chalk";
4
7
  import { Command } from "commander";
5
8
  import { initCommand } from "./commands/init.js";
6
9
  import { listCommand } from "./commands/list.js";
7
10
  import { syncCommand } from "./commands/sync.js";
8
11
  import { taskLoopCommand } from "./commands/task-loop/index.js";
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf-8"));
9
15
  const program = new Command();
10
16
  // 旧パッケージ名での実行時に非推奨警告を表示
11
17
  const isLegacyPackageName = (_a = process.argv[1]) === null || _a === void 0 ? void 0 : _a.includes("@einja/claude-cli");
@@ -16,7 +22,7 @@ if (isLegacyPackageName) {
16
22
  program
17
23
  .name("einja")
18
24
  .description("Einja CLI - .claude設定とテンプレート同期をnpxでインストール")
19
- .version("0.1.0");
25
+ .version(pkg.version);
20
26
  program
21
27
  .command("init")
22
28
  .description(".claudeディレクトリをセットアップ")
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,wBAAwB;AACxB,MAAM,mBAAmB,GAAG,MAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,0CAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;AAC3E,IAAI,mBAAmB,EAAE,CAAC;IACxB,OAAO,CAAC,IAAI,CACV,KAAK,CAAC,MAAM,CACV,+DAA+D,CAChE,CACF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,oCAAoC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,EAAE,SAAS,CAAC;KACxE,MAAM,CAAC,aAAa,EAAE,kBAAkB,CAAC;KACzC,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC;KACrC,MAAM,CAAC,WAAW,EAAE,uBAAuB,CAAC;KAC5C,MAAM,CAAC,aAAa,EAAE,uBAAuB,CAAC;KAC9C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC;KACnC,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAE3E,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,eAAe,CAAC;KAC5B,MAAM,CAAC,yBAAyB,EAAE,oBAAoB,CAAC;KACvD,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC;KAC3C,MAAM,CAAC,aAAa,EAAE,uBAAuB,CAAC;KAC9C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC;KACnC,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC;KACpC,MAAM,CAAC,SAAS,EAAE,0BAA0B,CAAC;KAC7C,MAAM,CAAC,aAAa,EAAE,uBAAuB,CAAC;KAC9C,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,mBAAmB,CAAC;KAC5B,WAAW,CAAC,uBAAuB,CAAC;KACpC,MAAM,CAAC,0BAA0B,EAAE,aAAa,CAAC;KACjD,MAAM,CAAC,qBAAqB,EAAE,SAAS,CAAC;KACxC,MAAM,CAAC,eAAe,CAAC,CAAC;AAE3B,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@einja/dev-cli",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "description": "Einja CLI - .claude設定とテンプレート同期をnpxでインストール",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -93,13 +93,15 @@ graph TD
93
93
 
94
94
  **実装例**:
95
95
  ```typescript
96
- // apps/web/src/app/api/posts/route.ts
96
+ // apps/web/src/app/api/rpc/posts/[[...route]]/route.ts
97
97
  import { Hono } from "hono"
98
+ import { handle } from "hono/vercel"
98
99
  import { zValidator } from "@hono/zod-validator"
99
100
  import { postSchema } from "@repo/server-core/domain/validators/post"
100
101
  import { postUseCases } from "@/application/use-cases/PostUseCases"
101
102
 
102
103
  const app = new Hono()
104
+ .basePath("/api/rpc/posts")
103
105
  .post("/", zValidator("json", postSchema), async (c) => {
104
106
  const data = c.req.valid("json")
105
107
  const result = await postUseCases.create(data)
@@ -111,8 +113,10 @@ const app = new Hono()
111
113
  return c.json(result.value, 201)
112
114
  })
113
115
 
114
- export const GET = app.fetch
115
- export const POST = app.fetch
116
+ export type PostsAppType = typeof app
117
+
118
+ export const GET = handle(app)
119
+ export const POST = handle(app)
116
120
  ```
117
121
 
118
122
  ##### 📘 Application層(UseCases)
@@ -602,37 +606,43 @@ Honoでは、**必ずメソッドチェーン形式**でルートを定義しま
602
606
 
603
607
  **重要: メソッドチェーンを使用する理由**
604
608
 
605
- Hono Clientの型推論は `typeof app` から型情報を抽出します。メソッドチェーンを使用しない場合、TypeScriptが各ルート定義の返り値型を追跡できず、`AppType`に完全なルート情報が含まれません。
609
+ Hono Clientの型推論は `typeof app` から型情報を抽出します。メソッドチェーンを使用しない場合、TypeScriptが各ルート定義の返り値型を追跡できず、ドメイン型(例: `PostsAppType`)に完全なルート情報が含まれません。
606
610
 
607
611
  ```typescript
608
612
  // ❌ NG: 個別呼び出し - 型推論が損なわれる
609
613
  const app = new Hono()
610
614
  app.get('/posts', handler1) // 返り値が破棄される
611
615
  app.post('/posts', handler2) // 返り値が破棄される
612
- export type AppType = typeof app // ルート情報が不完全
616
+ export type PostsAppType = typeof app // ルート情報が不完全
613
617
 
614
618
  // ✅ OK: メソッドチェーン - 完全な型推論
615
619
  const app = new Hono()
616
- .get('/posts', handler1)
617
- .post('/posts', handler2)
618
- export type AppType = typeof app // 全ルート情報を含む
620
+ .basePath("/api/posts")
621
+ .get('/', handler1)
622
+ .post('/', handler2)
623
+ export type PostsAppType = typeof app // 全ルート情報を含む
619
624
  ```
620
625
 
621
626
  #### ミドルウェア適用
622
627
 
623
- **⚠️ サブルート内で`.use()`を使うと型推論が壊れる。メインアプリ側で適用すること。**
628
+ **ドメインベースRPC分割では、各ドメインのroute.tsで直接ミドルウェアを適用します。**
624
629
 
625
630
  ```typescript
626
- // ❌ NG: サブルート内で.use() → 型が ClientRequest<{}> になる
627
- export const adminUserRoutes = new Hono()
628
- .use("*", adminAuthMiddleware)
629
- .delete("/:id", handler)
631
+ // apps/web/src/app/api/admin/users/[[...route]]/route.ts
632
+ import { Hono } from "hono"
633
+ import { handle } from "hono/vercel"
634
+ import { adminAuthMiddleware } from "@/middleware/admin-auth"
630
635
 
631
- // ✅ OK: メインアプリ側で.use()を適用
632
636
  const app = new Hono()
633
- .basePath("/api")
634
- .use("/admin/*", adminAuthMiddleware) // ← ここで適用
635
- .route("/admin", adminApp)
637
+ .basePath("/api/rpc/admin/users")
638
+ .use("/*", adminAuthMiddleware) // ドメインroute.ts内で適用
639
+ .get("/", listUsersHandler)
640
+ .delete("/:id", deleteUserHandler)
641
+
642
+ export type AdminUsersAppType = typeof app
643
+
644
+ export const GET = handle(app)
645
+ export const DELETE = handle(app)
636
646
  ```
637
647
 
638
648
  ### 7. Zodバリデーション戦略
@@ -425,7 +425,7 @@ describe('UserRepository - データベースエラー', () => {
425
425
 
426
426
  ```typescript
427
427
  // apps/web/__tests__/integration/post-api.test.ts
428
- import { apiClient } from '@/lib/api-client'
428
+ import { rpc } from '@/lib/api/rpc'
429
429
  import { prisma } from '@repo/server-core/infrastructure/database/client'
430
430
 
431
431
  describe('Post API統合テスト', () => {
@@ -454,7 +454,7 @@ describe('Post API統合テスト', () => {
454
454
  })
455
455
 
456
456
  // When: API呼び出し
457
- const response = await apiClient.posts.$get({
457
+ const response = await rpc.posts.$get({
458
458
  query: { page: '1', limit: '10' }
459
459
  })
460
460
 
@@ -475,7 +475,7 @@ describe('Post API統合テスト', () => {
475
475
  })
476
476
 
477
477
  // When: 投稿作成APIを呼び出し
478
- const response = await apiClient.posts.$post({
478
+ const response = await rpc.posts.$post({
479
479
  json: {
480
480
  title: 'New Post',
481
481
  content: 'Content',
@@ -92,17 +92,23 @@ project-root/
92
92
  │ ├── web/ # ユーザー向けWebアプリ
93
93
  │ │ ├── src/
94
94
  │ │ │ ├── app/ # Next.js App Router
95
+ │ │ │ │ └── api/rpc/ # ドメインベースRPC分割
96
+ │ │ │ │ └── {domain}/[[...route]]/route.ts
95
97
  │ │ │ ├── application/ # アプリケーション層(UseCases)⭐
96
98
  │ │ │ ├── components/ # Reactコンポーネント
97
- │ │ │ ├── lib/ # ライブラリ(Hono Client、API呼び出し)
99
+ │ │ │ ├── lib/ # ライブラリ
100
+ │ │ │ │ └── rpc.ts # ドメインごとのHono RPCクライアント
98
101
  │ │ │ └── hooks/ # カスタムフック(Tanstack Query)
99
102
  │ │ └── package.json
100
103
  │ ├── admin/ # 管理画面
101
104
  │ │ ├── src/
102
105
  │ │ │ ├── app/
106
+ │ │ │ │ └── api/rpc/ # ドメインベースRPC分割
107
+ │ │ │ │ └── {domain}/[[...route]]/route.ts
103
108
  │ │ │ ├── application/ # アプリケーション層(UseCases)⭐
104
109
  │ │ │ ├── components/
105
110
  │ │ │ ├── lib/
111
+ │ │ │ │ └── rpc.ts # ドメインごとのHono RPCクライアント
106
112
  │ │ │ └── hooks/
107
113
  │ │ └── package.json
108
114
  │ └── cron-worker/ # バッチ処理・定期実行
@@ -15,6 +15,7 @@
15
15
  - [メソッドチェーンパターン](#メソッドチェーンパターン)
16
16
  - [ミドルウェアと型推論の注意点](#ミドルウェアと型推論の注意点) ⚠️
17
17
  - [basePathとHono Clientの関係](#basepathとhono-clientの関係) ⚠️
18
+ - [ドメインベースRPC分割の設計原則](#ドメインベースrpc分割の設計原則)
18
19
  2. [Web APIエンドポイント一覧](#2-web-apiエンドポイント一覧)
19
20
  3. [Admin APIエンドポイント一覧](#3-admin-apiエンドポイント一覧)
20
21
  4. [Cron Worker CLIコマンド](#4-cron-worker-cliコマンド)
@@ -34,10 +35,51 @@
34
35
 
35
36
  Honoは型安全なWebフレームワークで、すべてのNext.js APIルートで使用します。
36
37
 
37
- **エントリーポイント**:
38
+ **エントリーポイント**(ドメインベースRPC分割):
38
39
  ```
39
- apps/web/app/api/[[...route]]/route.ts # Web API
40
- apps/admin/app/api/[[...route]]/route.ts # Admin API
40
+ apps/web/app/api/rpc/{domain}/[[...route]]/route.ts # Web API(ドメインごとに分割)
41
+ apps/admin/app/api/rpc/{domain}/[[...route]]/route.ts # Admin API(ドメインごとに分割)
42
+ ```
43
+
44
+ **ドメインroute.tsテンプレート**:
45
+
46
+ 各ドメインのroute.tsは以下のパターンで実装します。`basePath`でドメインのフルパスを指定し、`hc<...>("/")`で型ツリーからドメインクライアントを抽出します。
47
+
48
+ ```typescript
49
+ // apps/web/app/api/rpc/users/[[...route]]/route.ts
50
+ import { userRoutes } from "@web/server/presentation/routes/userRoutes";
51
+ import { Hono } from "hono";
52
+ import { handle } from "hono/vercel";
53
+
54
+ const app = new Hono().basePath("/api/rpc/users");
55
+ const routes = app.route("/", userRoutes);
56
+
57
+ export type UsersAppType = typeof routes;
58
+
59
+ export const GET = handle(app);
60
+ export const POST = handle(app);
61
+ export const PUT = handle(app);
62
+ export const DELETE = handle(app);
63
+ export const PATCH = handle(app);
64
+ ```
65
+
66
+ **RPCクライアントのセットアップ**:
67
+
68
+ > **Note**: 以下は複数ドメインを追加した場合の完成形の例です。現在のテンプレートでは `users` ドメインのみ実装されています。
69
+
70
+ ```typescript
71
+ // apps/web/src/lib/api/rpc.ts
72
+ import type { AuthAppType } from "@/app/api/rpc/auth/[[...route]]/route";
73
+ import type { PostsAppType } from "@/app/api/rpc/posts/[[...route]]/route";
74
+ import { hc } from "hono/client";
75
+
76
+ const authClient = hc<AuthAppType>("/");
77
+ const postsClient = hc<PostsAppType>("/");
78
+
79
+ export const rpc = {
80
+ auth: authClient.api.rpc.auth,
81
+ posts: postsClient.api.rpc.posts,
82
+ } as const;
41
83
  ```
42
84
 
43
85
  **ルート定義の配置**:
@@ -59,45 +101,70 @@ Hono Clientの型推論は `typeof app` から型情報を抽出します。メ
59
101
  const app = new Hono()
60
102
  app.get('/posts', handler1) // 返り値が破棄される
61
103
  app.post('/posts', handler2) // 返り値が破棄される
62
- export type AppType = typeof app // ルート情報が不完全
104
+ export type PostsAppType = typeof app // ルート情報が不完全
63
105
 
64
106
  // ✅ OK: メソッドチェーン - 完全な型推論
65
107
  const app = new Hono()
66
108
  .get('/posts', handler1)
67
109
  .post('/posts', handler2)
68
- export type AppType = typeof app // 全ルート情報を含む
110
+ export type PostsAppType = typeof app // 全ルート情報を含む
69
111
  ```
70
112
 
71
- メソッドチェーンにより、Hono Client (`hc<AppType>`) でエンドツーエンドの型安全なAPI呼び出しが実現できます。
113
+ メソッドチェーンにより、Hono Client (`hc<PostsAppType>`) でエンドツーエンドの型安全なAPI呼び出しが実現できます。
72
114
 
73
115
  ### ミドルウェアと型推論の注意点
74
116
 
75
- **サブルート内で`.use()`を使うと型推論が壊れる。メインアプリ側で適用すること。**
117
+ **サブルート内で`.use()`を使うと型推論が壊れる。各ドメインroute.tsのアプリ側で適用すること。**
118
+
119
+ ドメインベースRPC分割では、各ドメインのroute.tsが独立したHonoアプリを持つため、ミドルウェアはそのアプリに直接適用します。
76
120
 
77
121
  ```typescript
78
122
  // ❌ NG: サブルート内で.use() → 型が ClientRequest<{}> になる
79
- export const adminUserRoutes = new Hono()
80
- .use("*", adminAuthMiddleware)
81
- .delete("/:id", handler)
123
+ export const postRoutes = new Hono()
124
+ .use("*", authMiddleware)
125
+ .post("/", handler)
82
126
 
83
- // ✅ OK: メインアプリ側で.use()を適用
127
+ // ✅ OK: ドメインroute.tsのアプリ側で.use()を適用
128
+ // apps/web/app/api/rpc/posts/[[...route]]/route.ts
84
129
  const app = new Hono()
85
- .basePath("/api")
86
- .use("/admin/*", adminAuthMiddleware) // ← ここで適用
87
- .route("/admin", adminApp)
130
+ .basePath("/api/rpc/posts")
131
+ .use("/*", authMiddleware); // ← 認証必要なドメインではここで適用
132
+ const routes = app.route("/", postRoutes);
133
+ export type PostsAppType = typeof routes;
134
+ ```
135
+
136
+ **ドメインごとのミドルウェア適用パターン**:
137
+
138
+ ```typescript
139
+ // 認証不要のドメイン(auth等)
140
+ const app = new Hono().basePath("/api/rpc/auth");
141
+ const routes = app.route("/", authRoutes);
142
+ export type AuthAppType = typeof routes;
143
+
144
+ // 認証必要のドメイン(posts等)
145
+ const app = new Hono()
146
+ .basePath("/api/rpc/posts")
147
+ .use("/*", authMiddleware);
148
+ const routes = app.route("/", postRoutes);
149
+ export type PostsAppType = typeof routes;
88
150
  ```
89
151
 
90
152
  ### basePathとHono Clientの関係
91
153
 
92
- `basePath("/api/rpc")` を使用する場合、クライアント側も `api.rpc` を含めてアクセスする。
154
+ ドメインベースRPC分割では`basePath`で**ドメインのフルパスを指定**する。`hono/vercel`の`handle()`関数は完全なURLパスをHonoに渡すため、Honoがルートを正しくマッチするには`basePath`が必要。
93
155
 
94
156
  ```typescript
95
- // サーバー: basePath("/api/rpc") を設定
96
- const app = new Hono().basePath("/api/rpc").route("/users", userRoutes)
157
+ // サーバー: basePathでドメインのフルパスを指定
158
+ const app = new Hono().basePath("/api/rpc/users");
159
+ const routes = app.route("/", userRoutes);
160
+ export type UsersAppType = typeof routes;
97
161
 
98
- // クライアント: api.rpc を含める
99
- apiClient.api.rpc.users.$get() // OK
100
- apiClient.users.$get() // ❌ NG(型エラー)
162
+ // クライアント: hc("/")で型ツリーを構築し、ドメイン部分を抽出
163
+ import { hc } from "hono/client";
164
+ const client = hc<UsersAppType>("/");
165
+ const usersRpc = client.api.rpc.users;
166
+
167
+ usersRpc.$get() // ✅ OK(/api/rpc/users にリクエスト)
101
168
  ```
102
169
 
103
170
  **使用例**:
@@ -128,6 +195,30 @@ app
128
195
  export default app
129
196
  ```
130
197
 
198
+ ### ドメインベースRPC分割の設計原則
199
+
200
+ #### なぜドメインで分割するか
201
+
202
+ 1. **Vercel Function独立**: 各ドメインが独立したServerless Functionとしてデプロイされるため、障害の影響範囲が限定される
203
+ 2. **コールドスタート改善**: 各Functionのバンドルサイズが小さくなり、コールドスタート時間が短縮される
204
+ 3. **スケーラビリティ**: トラフィックの多いドメイン(例: posts)だけが独立してスケールできる
205
+
206
+ #### ドメイングループ設計の基準
207
+
208
+ ドメインの分割は**依存の重さ・頻度**で分類する。
209
+
210
+ | 基準 | 説明 | 例 |
211
+ |------|------|-----|
212
+ | 依存の独立性 | 外部サービスやDB接続が異なる | auth(認証サービス依存)vs posts(DB依存) |
213
+ | リクエスト頻度 | 高頻度ドメインを分離して他に影響させない | analytics(低頻度)vs posts(高頻度) |
214
+ | ミドルウェアの違い | 認証要否など共通処理が異なる | auth(認証不要)vs users(認証必要) |
215
+
216
+ #### 新ドメイン追加手順
217
+
218
+ 1. **route.ts作成**: `app/api/rpc/{domain}/[[...route]]/route.ts` にドメインroute.tsテンプレートを配置
219
+ 2. **rpc.tsに追加**: `lib/api/rpc.ts` の `rpc` オブジェクトに新ドメインのクライアントを追加
220
+ 3. **フック作成**: 必要に応じて `hooks/api/use{Domain}*.ts` にTanstack Queryフックを作成
221
+
131
222
  ### zValidatorの統合
132
223
 
133
224
  **必須**: すべてのリクエストボディとクエリパラメータは、zValidatorでバリデーションを行う。
@@ -176,23 +267,23 @@ async (c) => {
176
267
  |---------|---------------|------|-----------|-----------|------|
177
268
  | GET | `/api/health` | システム稼働確認 | - | `{status: "ok"}` | 不要 |
178
269
 
179
- ### 認証エンドポイント
270
+ ### 認証エンドポイント(ドメイン: auth)
180
271
 
181
272
  | メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
182
273
  |---------|---------------|------|-----------|-----------|------|
183
- | POST | `/api/auth/login` | ユーザーログイン | `{email, password}` | `{token, user}` | 不要 |
184
- | POST | `/api/auth/logout` | ログアウト | - | `{success: true}` | 必要 |
185
- | GET | `/api/auth/session` | セッション確認 | - | `{user}` | 必要 |
274
+ | POST | `/api/rpc/auth/login` | ユーザーログイン | `{email, password}` | `{token, user}` | 不要 |
275
+ | POST | `/api/rpc/auth/logout` | ログアウト | - | `{success: true}` | 必要 |
276
+ | GET | `/api/rpc/auth/session` | セッション確認 | - | `{user}` | 必要 |
186
277
 
187
- ### 投稿エンドポイント
278
+ ### 投稿エンドポイント(ドメイン: posts)
188
279
 
189
280
  | メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
190
281
  |---------|---------------|------|-----------|-----------|------|
191
- | GET | `/api/posts` | 投稿一覧取得 | `?page=1&limit=10` | `{posts[], total}` | オプション |
192
- | POST | `/api/posts` | 投稿作成 | `{title, content, status?}` | `{post}` | 必要 |
193
- | GET | `/api/posts/:id` | 投稿詳細取得 | - | `{post}` | オプション |
194
- | PUT | `/api/posts/:id` | 投稿更新 | `{title?, content?, status?}` | `{post}` | 必要 |
195
- | DELETE | `/api/posts/:id` | 投稿削除 | - | `{success: true}` | 必要 |
282
+ | GET | `/api/rpc/posts` | 投稿一覧取得 | `?page=1&limit=10` | `{posts[], total}` | オプション |
283
+ | POST | `/api/rpc/posts` | 投稿作成 | `{title, content, status?}` | `{post}` | 必要 |
284
+ | GET | `/api/rpc/posts/:id` | 投稿詳細取得 | - | `{post}` | オプション |
285
+ | PUT | `/api/rpc/posts/:id` | 投稿更新 | `{title?, content?, status?}` | `{post}` | 必要 |
286
+ | DELETE | `/api/rpc/posts/:id` | 投稿削除 | - | `{success: true}` | 必要 |
196
287
 
197
288
  **ページネーション設計**:
198
289
  - `page`: ページ番号(デフォルト: 1)
@@ -214,27 +305,27 @@ async (c) => {
214
305
  |---------|---------------|------|-----------|-----------|------|
215
306
  | GET | `/api/health` | システム稼働確認 | - | `{status: "ok"}` | 不要 |
216
307
 
217
- ### ユーザー管理
308
+ ### ユーザー管理(ドメイン: users)
218
309
 
219
310
  | メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
220
311
  |---------|---------------|------|-----------|-----------|------|
221
- | GET | `/api/admin/users` | ユーザー一覧取得 | `?page=1&limit=20` | `{users[], total}` | 管理者 |
222
- | GET | `/api/admin/users/:id` | ユーザー詳細取得 | - | `{user}` | 管理者 |
223
- | PUT | `/api/admin/users/:id` | ユーザー情報更新 | `{name?, email?}` | `{user}` | 管理者 |
224
- | DELETE | `/api/admin/users/:id` | ユーザー削除 | - | `{success: true}` | 管理者 |
312
+ | GET | `/api/rpc/users` | ユーザー一覧取得 | `?page=1&limit=20` | `{users[], total}` | 管理者 |
313
+ | GET | `/api/rpc/users/:id` | ユーザー詳細取得 | - | `{user}` | 管理者 |
314
+ | PUT | `/api/rpc/users/:id` | ユーザー情報更新 | `{name?, email?}` | `{user}` | 管理者 |
315
+ | DELETE | `/api/rpc/users/:id` | ユーザー削除 | - | `{success: true}` | 管理者 |
225
316
 
226
- ### 投稿管理
317
+ ### 投稿管理(ドメイン: posts)
227
318
 
228
319
  | メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
229
320
  |---------|---------------|------|-----------|-----------|------|
230
- | GET | `/api/admin/posts` | 全投稿一覧取得 | `?status=all&page=1` | `{posts[], total}` | 管理者 |
231
- | PUT | `/api/admin/posts/:id/status` | 投稿ステータス変更 | `{status}` | `{post}` | 管理者 |
321
+ | GET | `/api/rpc/posts` | 全投稿一覧取得 | `?status=all&page=1` | `{posts[], total}` | 管理者 |
322
+ | PUT | `/api/rpc/posts/:id/status` | 投稿ステータス変更 | `{status}` | `{post}` | 管理者 |
232
323
 
233
- ### 分析
324
+ ### 分析(ドメイン: analytics)
234
325
 
235
326
  | メソッド | エンドポイント | 説明 | リクエスト | レスポンス | 認証 |
236
327
  |---------|---------------|------|-----------|-----------|------|
237
- | GET | `/api/admin/analytics` | システム統計取得 | `?from=&to=` | `{stats}` | 管理者 |
328
+ | GET | `/api/rpc/analytics` | システム統計取得 | `?from=&to=` | `{stats}` | 管理者 |
238
329
 
239
330
  **管理者認証**:
240
331
  - すべてのAdmin APIは、管理者権限(`role='admin'`)のチェックが必要
@@ -567,13 +658,13 @@ Hono Client + Tanstack Queryでは、`parseResponse`関数を使用してレス
567
658
  import { useQuery } from "@tanstack/react-query";
568
659
  import { parseResponse } from "@/lib/api/parse-response";
569
660
  import { paginatedPostListSchema } from "@/shared/schemas/post";
570
- import { apiClient } from "@/lib/api/client";
661
+ import { rpc } from "@/lib/api/rpc";
571
662
 
572
663
  export function usePostList(page: number, limit: number) {
573
664
  return useQuery({
574
665
  queryKey: ["posts", page, limit],
575
666
  queryFn: async () => {
576
- const response = await apiClient.api.rpc.posts.$get({
667
+ const response = await rpc.posts.$get({
577
668
  query: { page: String(page), limit: String(limit) },
578
669
  });
579
670
  return parseResponse(response, paginatedPostListSchema);
@@ -651,26 +742,45 @@ export const adminMiddleware = createMiddleware(async (c, next) => {
651
742
  })
652
743
  ```
653
744
 
654
- ### ミドルウェアの適用
745
+ ### ミドルウェアの適用(ドメインベースRPC分割)
746
+
747
+ ドメインベースRPC分割では、各ドメインのroute.tsが独立したHonoアプリを持つため、ミドルウェアはドメインごとに個別に適用します。
655
748
 
656
749
  ```typescript
657
- import { Hono } from 'hono'
658
- import { authMiddleware } from '@/server/middleware/auth'
750
+ // 認証不要のドメイン: apps/web/app/api/rpc/auth/[[...route]]/route.ts
751
+ import { authRoutes } from "@web/server/presentation/routes/authRoutes";
752
+ import { Hono } from "hono";
753
+ import { handle } from "hono/vercel";
659
754
 
660
- const app = new Hono()
755
+ const app = new Hono().basePath("/api/rpc/auth");
756
+ const routes = app.route("/", authRoutes);
757
+ export type AuthAppType = typeof routes;
661
758
 
662
- // 認証不要なルート
663
- app.get('/health', (c) => c.json({ status: 'ok' }))
759
+ export const GET = handle(app);
760
+ export const POST = handle(app);
761
+ ```
664
762
 
665
- // 認証が必要なルート
666
- app.use('/posts/*', authMiddleware)
667
- app.post('/posts', async (c) => {
668
- const user = c.get('user') // ミドルウェアで設定されたユーザー情報
669
- // ...
670
- })
763
+ ```typescript
764
+ // 認証必要のドメイン: apps/web/app/api/rpc/posts/[[...route]]/route.ts
765
+ import { postRoutes } from "@web/server/presentation/routes/postRoutes";
766
+ import { authMiddleware } from "@/server/middleware/auth";
767
+ import { Hono } from "hono";
768
+ import { handle } from "hono/vercel";
769
+
770
+ const app = new Hono()
771
+ .basePath("/api/rpc/posts")
772
+ .use("/*", authMiddleware); // ← ドメインroute.tsで認証ミドルウェアを適用
773
+ const routes = app.route("/", postRoutes);
774
+ export type PostsAppType = typeof routes;
775
+
776
+ export const GET = handle(app);
777
+ export const POST = handle(app);
778
+ export const PUT = handle(app);
779
+ export const DELETE = handle(app);
780
+ export const PATCH = handle(app);
671
781
  ```
672
782
 
673
- **⚠️ 重要**: サブルート内で`.use()`を使用するとHono RPC型推論が壊れます。
783
+ **⚠️ 重要**: サブルート(postRoutes等)内で`.use()`を使用するとHono RPC型推論が壊れます。ミドルウェアは必ずドメインroute.tsのアプリ側で適用してください。
674
784
  詳細は[ミドルウェアと型推論の注意点](#ミドルウェアと型推論の注意点)を参照してください。
675
785
 
676
786
  ---
@@ -789,24 +899,46 @@ app.post(
789
899
  export default app
790
900
  ```
791
901
 
792
- **エントリーポイント**:
902
+ **エントリーポイント(ドメインroute.ts)**:
793
903
 
794
904
  ```typescript
795
- // apps/web/src/app/api/rpc/[[...route]]/route.ts
796
- import { Hono } from 'hono'
797
- import { handle } from 'hono/vercel'
798
- import { userRoutes } from '@web/server/presentation/routes/userRoutes'
905
+ // apps/web/app/api/rpc/posts/[[...route]]/route.ts
906
+ import { postRoutes } from "@web/server/presentation/routes/postRoutes";
907
+ import { authMiddleware } from "@/server/middleware/auth";
908
+ import { Hono } from "hono";
909
+ import { handle } from "hono/vercel";
799
910
 
800
- const app = new Hono().basePath('/api/rpc')
911
+ const app = new Hono()
912
+ .basePath("/api/rpc/posts")
913
+ .use("/*", authMiddleware);
914
+ const routes = app.route("/", postRoutes);
915
+
916
+ export type PostsAppType = typeof routes;
801
917
 
802
- const routes = app.route('/users', userRoutes)
918
+ export const GET = handle(app);
919
+ export const POST = handle(app);
920
+ export const PUT = handle(app);
921
+ export const DELETE = handle(app);
922
+ export const PATCH = handle(app);
923
+ ```
803
924
 
804
- export const GET = handle(app)
805
- export const POST = handle(app)
806
- export const PUT = handle(app)
807
- export const DELETE = handle(app)
925
+ **RPCクライアント**:
808
926
 
809
- export type AppType = typeof routes
927
+ > **Note**: 以下は複数ドメインを追加した場合の完成形の例です。現在のテンプレートでは `users` ドメインのみ実装されています。
928
+
929
+ ```typescript
930
+ // apps/web/src/lib/api/rpc.ts
931
+ import type { AuthAppType } from "@/app/api/rpc/auth/[[...route]]/route";
932
+ import type { PostsAppType } from "@/app/api/rpc/posts/[[...route]]/route";
933
+ import { hc } from "hono/client";
934
+
935
+ const authClient = hc<AuthAppType>("/");
936
+ const postsClient = hc<PostsAppType>("/");
937
+
938
+ export const rpc = {
939
+ auth: authClient.api.rpc.auth,
940
+ posts: postsClient.api.rpc.posts,
941
+ } as const;
810
942
  ```
811
943
 
812
944
  **フロントエンドでの使用**:
@@ -194,7 +194,7 @@ graph TD
194
194
 
195
195
  #### 📕 Presentation層(API Routes)
196
196
 
197
- **配置**: `apps/web/src/app/api/`, `apps/admin/src/app/api/`
197
+ **配置**: `apps/web/src/app/api/rpc/{domain}/`, `apps/admin/src/app/api/rpc/{domain}/`
198
198
 
199
199
  **責務**:
200
200
  - HTTPリクエスト/レスポンスの処理
@@ -204,28 +204,24 @@ graph TD
204
204
 
205
205
  **技術**: Hono、zValidator、Zod
206
206
 
207
+ **エントリーポイント**: `/api/rpc/{domain}/[[...route]]/route.ts` (ドメインベースRPC分割)
208
+
207
209
  **実装例**:
208
210
  ```typescript
209
- // apps/web/src/app/api/posts/route.ts
211
+ // apps/web/src/app/api/rpc/posts/[[...route]]/route.ts
212
+ import { postRoutes } from "@web/server/presentation/routes/postRoutes"
210
213
  import { Hono } from "hono"
211
- import { zValidator } from "@hono/zod-validator"
212
- import { postSchema } from "@repo/server-core/domain/validators/post"
213
- import { postUseCases } from "@/application/use-cases/PostUseCases" // アプリ内のApplication層
214
+ import { handle } from "hono/vercel"
214
215
 
215
216
  const app = new Hono()
216
- .post("/", zValidator("json", postSchema), async (c) => {
217
- const data = c.req.valid("json")
218
- const result = await postUseCases.create(data)
219
-
220
- if (!result.isSuccess) {
221
- return c.json({ error: result.error.message }, result.error.statusCode)
222
- }
217
+ .basePath("/api/rpc/posts")
218
+ .use("/*", authMiddleware)
219
+ const routes = app.route("/", postRoutes)
223
220
 
224
- return c.json(result.value, 201)
225
- })
221
+ export type PostsAppType = typeof routes
226
222
 
227
- export const GET = app.fetch
228
- export const POST = app.fetch
223
+ export const GET = handle(app)
224
+ export const POST = handle(app)
229
225
  ```
230
226
 
231
227
  ---
@@ -60,10 +60,10 @@ apps/web/
60
60
  │ │ │ │ └── page.tsx # 投稿詳細
61
61
  │ │ │ └── profile/
62
62
  │ │ │ └── page.tsx
63
- │ │ ├── api/ # API Routes
64
- │ │ │ └── rpc/
65
- │ │ │ └── [[...route]]/
66
- │ │ │ └── route.ts # Honoエントリーポイント
63
+ │ │ ├── api/rpc/ # ドメインベースRPC
64
+ │ │ │ ├── users/[[...route]]/route.ts
65
+ │ │ │ ├── auth/[[...route]]/route.ts
66
+ │ │ │ └── posts/[[...route]]/route.ts
67
67
  │ │ ├── layout.tsx # ルートレイアウト
68
68
  │ │ └── page.tsx # トップページ
69
69
  │ ├── components/ # UIコンポーネント
@@ -84,7 +84,7 @@ apps/web/
84
84
  │ │ └── RegisterForm.tsx
85
85
  │ ├── lib/ # ユーティリティ
86
86
  │ │ ├── api/
87
- │ │ │ ├── client.ts # Hono Client設定
87
+ │ │ │ ├── rpc.ts # Hono RPCクライアント設定
88
88
  │ │ │ └── parse-response.ts # レスポンスパース&バリデーション
89
89
  │ │ ├── query-client.ts # Tanstack Query設定
90
90
  │ │ └── utils.ts # 共通ユーティリティ
@@ -116,10 +116,9 @@ apps/admin/
116
116
  │ │ │ │ │ └── page.tsx
117
117
  │ │ │ │ └── analytics/
118
118
  │ │ │ │ └── page.tsx
119
- │ │ ├── api/
120
- │ │ │ └── rpc/
121
- │ │ │ └── [[...route]]/
122
- │ │ │ └── route.ts
119
+ │ │ ├── api/rpc/ # ドメインベースRPC
120
+ │ │ │ ├── users/[[...route]]/route.ts
121
+ │ │ │ └── posts/[[...route]]/route.ts
123
122
  │ │ ├── layout.tsx
124
123
  │ │ └── page.tsx
125
124
  │ ├── components/
@@ -198,27 +197,39 @@ export const paginatedUserListSchema = z.object({
198
197
 
199
198
  ### セットアップ
200
199
 
201
- **Hono Clientの初期化**:
200
+ **RPCクライアントの初期化**:
202
201
 
203
- ```typescript
204
- // apps/web/src/lib/api/client.ts
205
- import { hc } from 'hono/client'
206
- import type { AppType } from '@/app/api/rpc/[[...route]]/route'
202
+ > **Note**: 以下は複数ドメインを追加した場合の完成形の例です。現在のテンプレートでは `users` ドメインのみ実装されています。
207
203
 
208
- export const apiClient = hc<AppType>('/')
204
+ ```typescript
205
+ // apps/web/src/lib/api/rpc.ts
206
+ import type { UsersAppType } from "@/app/api/rpc/users/[[...route]]/route";
207
+ import type { AuthAppType } from "@/app/api/rpc/auth/[[...route]]/route";
208
+ import type { PostsAppType } from "@/app/api/rpc/posts/[[...route]]/route";
209
+ import { hc } from "hono/client";
210
+
211
+ const usersClient = hc<UsersAppType>("/");
212
+ const authClient = hc<AuthAppType>("/");
213
+ const postsClient = hc<PostsAppType>("/");
214
+
215
+ export const rpc = {
216
+ users: usersClient.api.rpc.users,
217
+ auth: authClient.api.rpc.auth,
218
+ posts: postsClient.api.rpc.posts,
219
+ } as const;
209
220
  ```
210
221
 
211
- **型定義のエクスポート**:
222
+ **型定義のエクスポート**(ドメインごとのルートファイル):
212
223
 
213
224
  ```typescript
214
- // apps/web/src/app/api/rpc/[[...route]]/route.ts
225
+ // apps/web/src/app/api/rpc/users/[[...route]]/route.ts
215
226
  import { Hono } from 'hono'
216
227
  import { handle } from 'hono/vercel'
217
228
  import { userRoutes } from '@web/server/presentation/routes/userRoutes'
218
229
 
219
- const app = new Hono().basePath('/api/rpc')
230
+ const app = new Hono().basePath('/api/rpc/users')
220
231
 
221
- const routes = app.route('/users', userRoutes)
232
+ const routes = app.route('/', userRoutes)
222
233
 
223
234
  export const GET = handle(app)
224
235
  export const POST = handle(app)
@@ -226,7 +237,7 @@ export const PUT = handle(app)
226
237
  export const DELETE = handle(app)
227
238
 
228
239
  // 型のエクスポート(フロントエンドで使用)
229
- export type AppType = typeof routes
240
+ export type UsersAppType = typeof routes
230
241
  ```
231
242
 
232
243
  ### API呼び出しパターン
@@ -235,7 +246,7 @@ export type AppType = typeof routes
235
246
 
236
247
  ```typescript
237
248
  // ユーザー一覧取得
238
- const response = await apiClient.api.rpc.users.$get({
249
+ const response = await rpc.users.$get({
239
250
  query: { page: '1', limit: '10' }
240
251
  })
241
252
  const data = await response.json() // 型推論: { users: User[], total: number }
@@ -245,7 +256,7 @@ const data = await response.json() // 型推論: { users: User[], total: number
245
256
 
246
257
  ```typescript
247
258
  // ユーザー作成
248
- const response = await apiClient.api.rpc.users.$post({
259
+ const response = await rpc.users.$post({
249
260
  json: { email: 'user@example.com', name: 'User Name' }
250
261
  })
251
262
  const data = await response.json() // 型推論: { user: User }
@@ -255,7 +266,7 @@ const data = await response.json() // 型推論: { user: User }
255
266
 
256
267
  ```typescript
257
268
  // ユーザー詳細取得
258
- const response = await apiClient.api.rpc.users[':id'].$get({
269
+ const response = await rpc.users[':id'].$get({
259
270
  param: { id: '123' }
260
271
  })
261
272
  const data = await response.json() // 型推論: { user: User }
@@ -265,7 +276,7 @@ const data = await response.json() // 型推論: { user: User }
265
276
 
266
277
  ```typescript
267
278
  // ユーザー更新
268
- const response = await apiClient.api.rpc.users[':id'].$put({
279
+ const response = await rpc.users[':id'].$put({
269
280
  param: { id: '123' },
270
281
  json: { name: 'Updated Name' }
271
282
  })
@@ -276,7 +287,7 @@ const data = await response.json() // 型推論: { user: User }
276
287
 
277
288
  ```typescript
278
289
  // ユーザー削除
279
- const response = await apiClient.api.rpc.users[':id'].$delete({
290
+ const response = await rpc.users[':id'].$delete({
280
291
  param: { id: '123' }
281
292
  })
282
293
  const data = await response.json() // 型推論: { success: true }
@@ -344,13 +355,13 @@ export async function parseResponse<T>(
344
355
  import { useQuery } from "@tanstack/react-query";
345
356
  import { parseResponse } from "@/lib/api/parse-response";
346
357
  import { paginatedUserListSchema } from "@/shared/schemas/user";
347
- import { apiClient } from "@/lib/api/client";
358
+ import { rpc } from "@/lib/api/rpc";
348
359
 
349
360
  export function useUsers(filters: UserFilters = {}) {
350
361
  return useQuery({
351
362
  queryKey: ["users", filters],
352
363
  queryFn: async () => {
353
- const response = await apiClient.api.rpc.users.$get({
364
+ const response = await rpc.users.$get({
354
365
  query: { page: String(filters.page || 1), limit: String(filters.limit || 10) },
355
366
  });
356
367
  return parseResponse(response, paginatedUserListSchema);
@@ -417,11 +428,11 @@ try {
417
428
  ```typescript
418
429
  // app/posts/page.tsx (Server Component - デフォルト)
419
430
  import { PostList } from '@/components/features/posts/PostList'
420
- import { apiClient } from '@/lib/api-client'
431
+ import { rpc } from '@/lib/api/rpc'
421
432
 
422
433
  export default async function PostsPage() {
423
434
  // サーバー側でデータフェッチ
424
- const response = await apiClient.posts.$get({
435
+ const response = await rpc.posts.$get({
425
436
  query: { page: '1', limit: '10' }
426
437
  })
427
438
  const data = await response.json()
@@ -546,11 +557,11 @@ export default function PostsPage() {
546
557
  import { Header } from '@/components/features/Header'
547
558
  import { Sidebar } from '@/components/features/Sidebar'
548
559
  import { PostListContainer } from '@/components/features/posts/PostListContainer'
549
- import { apiClient } from '@/lib/api-client'
560
+ import { rpc } from '@/lib/api/rpc'
550
561
 
551
562
  export default async function PostsPage() {
552
563
  // サーバー側でデータフェッチ
553
- const response = await apiClient.posts.$get()
564
+ const response = await rpc.posts.$get()
554
565
  const data = await response.json()
555
566
 
556
567
  return (
@@ -596,11 +607,11 @@ export function PostListContainer({ initialData }) {
596
607
  ```typescript
597
608
  // app/posts/[id]/page.tsx (Server Component)
598
609
  import { PostDetail } from '@/components/features/posts/PostDetail'
599
- import { apiClient } from '@/lib/api-client'
610
+ import { rpc } from '@/lib/api/rpc'
600
611
 
601
612
  export default async function PostDetailPage({ params }: { params: { id: string } }) {
602
613
  // サーバー側でデータフェッチ
603
- const response = await apiClient.posts[':id'].$get({
614
+ const response = await rpc.posts[':id'].$get({
604
615
  param: { id: params.id }
605
616
  })
606
617
  const { post } = await response.json()
@@ -797,13 +808,13 @@ export default function RootLayout({ children }) {
797
808
 
798
809
  ```typescript
799
810
  import { useQuery } from '@tanstack/react-query'
800
- import { apiClient } from '@/lib/api-client'
811
+ import { rpc } from '@/lib/api/rpc'
801
812
 
802
813
  export function usePostList(page: number, limit: number) {
803
814
  return useQuery({
804
815
  queryKey: ['posts', page, limit], // キャッシュキー
805
816
  queryFn: async () => {
806
- const response = await apiClient.posts.$get({
817
+ const response = await rpc.posts.$get({
807
818
  query: { page: String(page), limit: String(limit) }
808
819
  })
809
820
  if (!response.ok) {
@@ -841,7 +852,7 @@ export function PostList() {
841
852
 
842
853
  ```typescript
843
854
  import { useMutation, useQueryClient } from '@tanstack/react-query'
844
- import { apiClient } from '@/lib/api-client'
855
+ import { rpc } from '@/lib/api/rpc'
845
856
  import type { CreatePostInput } from '@repo/server-core/domain/validators/post'
846
857
 
847
858
  export function useCreatePost() {
@@ -849,7 +860,7 @@ export function useCreatePost() {
849
860
 
850
861
  return useMutation({
851
862
  mutationFn: async (data: CreatePostInput) => {
852
- const response = await apiClient.posts.$post({ json: data })
863
+ const response = await rpc.posts.$post({ json: data })
853
864
  if (!response.ok) {
854
865
  throw new Error('Failed to create post')
855
866
  }
@@ -1111,7 +1122,7 @@ export function PostCard({ post }: PostCardProps) {
1111
1122
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
1112
1123
  import { parseResponse } from '@/lib/api/parse-response'
1113
1124
  import { paginatedPostListSchema } from '@/shared/schemas/post'
1114
- import { apiClient } from '@/lib/api/client'
1125
+ import { rpc } from '@/lib/api/rpc'
1115
1126
  import type { CreatePostInput, UpdatePostInput } from '@repo/server-core/domain/validators/post'
1116
1127
 
1117
1128
  // 投稿一覧取得
@@ -1119,7 +1130,7 @@ export function usePostList(page: number, limit: number) {
1119
1130
  return useQuery({
1120
1131
  queryKey: ['posts', page, limit],
1121
1132
  queryFn: async () => {
1122
- const response = await apiClient.api.posts.$get({
1133
+ const response = await rpc.posts.$get({
1123
1134
  query: { page: String(page), limit: String(limit) }
1124
1135
  })
1125
1136
  return parseResponse(response, paginatedPostListSchema)
@@ -1132,7 +1143,7 @@ export function usePost(id: string) {
1132
1143
  return useQuery({
1133
1144
  queryKey: ['posts', id],
1134
1145
  queryFn: async () => {
1135
- const response = await apiClient.api.posts[':id'].$get({ param: { id } })
1146
+ const response = await rpc.posts[':id'].$get({ param: { id } })
1136
1147
  return parseResponse(response, postSchema)
1137
1148
  },
1138
1149
  })
@@ -1144,7 +1155,7 @@ export function useCreatePost() {
1144
1155
 
1145
1156
  return useMutation({
1146
1157
  mutationFn: async (data: CreatePostInput) => {
1147
- const response = await apiClient.api.posts.$post({ json: data })
1158
+ const response = await rpc.posts.$post({ json: data })
1148
1159
  return parseResponse(response, postSchema)
1149
1160
  },
1150
1161
  onSuccess: () => {
@@ -1159,7 +1170,7 @@ export function useUpdatePost(id: string) {
1159
1170
 
1160
1171
  return useMutation({
1161
1172
  mutationFn: async (data: UpdatePostInput) => {
1162
- const response = await apiClient.api.posts[':id'].$put({
1173
+ const response = await rpc.posts[':id'].$put({
1163
1174
  param: { id },
1164
1175
  json: data
1165
1176
  })
@@ -1178,7 +1189,7 @@ export function useDeletePost() {
1178
1189
 
1179
1190
  return useMutation({
1180
1191
  mutationFn: async (id: string) => {
1181
- const response = await apiClient.api.posts[':id'].$delete({ param: { id } })
1192
+ const response = await rpc.posts[':id'].$delete({ param: { id } })
1182
1193
  return parseResponse(response, deleteResponseSchema)
1183
1194
  },
1184
1195
  onSuccess: () => {
@@ -1332,12 +1343,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
1332
1343
  // ✅ page.tsxはServer Component('use client'なし)
1333
1344
  import { PostListContainer } from '@/components/features/posts/PostListContainer'
1334
1345
  import { Button } from '@/components/ui/button'
1335
- import { apiClient } from '@/lib/api-client'
1346
+ import { rpc } from '@/lib/api/rpc'
1336
1347
  import Link from 'next/link'
1337
1348
 
1338
1349
  export default async function PostsPage() {
1339
1350
  // サーバー側でデータフェッチ
1340
- const response = await apiClient.posts.$get({
1351
+ const response = await rpc.posts.$get({
1341
1352
  query: { page: '1', limit: '10' }
1342
1353
  })
1343
1354
  const data = await response.json()
@@ -1402,11 +1413,11 @@ export default function PostNewPage() {
1402
1413
  ```typescript
1403
1414
  // ✅ page.tsxはServer Component('use client'なし)
1404
1415
  import { PostDetail } from '@/components/features/posts/PostDetail'
1405
- import { apiClient } from '@/lib/api-client'
1416
+ import { rpc } from '@/lib/api/rpc'
1406
1417
 
1407
1418
  export default async function PostDetailPage({ params }: { params: { id: string } }) {
1408
1419
  // サーバー側でデータフェッチ
1409
- const response = await apiClient.posts[':id'].$get({
1420
+ const response = await rpc.posts[':id'].$get({
1410
1421
  param: { id: params.id }
1411
1422
  })
1412
1423
  const { post } = await response.json()
@@ -1453,7 +1464,7 @@ export function usePostList(page: number, limit: number) {
1453
1464
  return useQuery({
1454
1465
  queryKey: ['posts', page, limit],
1455
1466
  queryFn: async () => {
1456
- const response = await apiClient.posts.$get({
1467
+ const response = await rpc.posts.$get({
1457
1468
  query: { page: String(page), limit: String(limit) }
1458
1469
  })
1459
1470
 
@@ -1531,7 +1542,7 @@ export class ErrorBoundary extends Component<Props, State> {
1531
1542
  // ✅ page.tsxはServer Component('use client'なし)
1532
1543
  import { PostListWithPagination } from '@/components/features/posts/PostListWithPagination'
1533
1544
  import { Button } from '@/components/ui/button'
1534
- import { apiClient } from '@/lib/api-client'
1545
+ import { rpc } from '@/lib/api/rpc'
1535
1546
  import Link from 'next/link'
1536
1547
 
1537
1548
  interface PostsPageProps {
@@ -1543,7 +1554,7 @@ export default async function PostsPage({ searchParams }: PostsPageProps) {
1543
1554
  const limit = 10
1544
1555
 
1545
1556
  // サーバー側でデータフェッチ
1546
- const response = await apiClient.posts.$get({
1557
+ const response = await rpc.posts.$get({
1547
1558
  query: { page: String(page), limit: String(limit) }
1548
1559
  })
1549
1560
  const data = await response.json()
@@ -71,6 +71,8 @@ APIエンドポイントやアプリケーション機能の実装レビュー
71
71
  - Zodスキーマによるバリデーションが全エンドポイントで実装されているか
72
72
  - **🔗 Honoのメソッドチェーン形式が守られているか** - `new Hono().get().post().put().delete()`の形式で実装すること。個別呼び出し形式(`app.get(); app.post();`)では`typeof app`による型推論が損なわれ、Hono Clientでの型安全性が失われる(詳細: [API開発ガイド - メソッドチェーンパターン](./development/api-development.md#メソッドチェーンパターン))
73
73
  - **🔗 サブルート内で`.use()`を使っていないか** - ミドルウェアはメインアプリ側で適用すること。サブルート内で`.use()`を使うと型が`ClientRequest<{}>`になり型推論が壊れる(詳細: [API開発ガイド - ミドルウェアと型推論](./development/api-development.md#ミドルウェアと型推論の注意点))
74
+ - **🔗 ドメインベースRPC分割が守られているか** - 各ドメインごとに `/api/rpc/{domain}/[[...route]]/route.ts` のエントリーポイントを作成すること
75
+ - **🔗 単一catch-allに全ルートを集約していないか** - 1つの `[[...route]]/route.ts` に複数ドメインのルートをまとめて登録してはならない。ドメインごとにエントリーポイントを分割すること
74
76
  - エラーハンドリングがApplicationErrorで統一されているか
75
77
  - loggerが使用され、console.logが使われていないか
76
78
  - **相対パスの使用禁止** - import文、require文、ファイルパス指定で`../`や`./`などの相対パスが使用されていないか。必ずアプリ固有エイリアス(`@web/*`、`@admin/*`等)またはパッケージ名(`@repo/server-core`等)を使用すること
@@ -82,7 +84,8 @@ APIエンドポイントやアプリケーション機能の実装レビュー
82
84
  - [ ] **APIクライアント実装ガイドの実装ルール遵守**
83
85
  - Tanstack QueryとHonoクライアントを使用した型安全な実装になっているか
84
86
  - Zodスキーマによるレスポンス検証が実装されているか
85
- - apiClientsディレクトリ構成が守られているか(フェッチ関数とhooksの分離)
87
+ - `rpc`オブジェクト(ドメインごとのHono Client)を使用しているか
88
+ - 旧`apiClient`パターン(単一クライアント)を使用していないか(使用禁止)
86
89
  - useMutationでのキャッシュ無効化が適切に実装されているか
87
90
  - エラーハンドリングが実装されているか
88
91
  - 型定義の整合性が保たれているか(バックエンドとフロントエンド間)
@@ -425,7 +425,7 @@ describe('UserRepository - データベースエラー', () => {
425
425
 
426
426
  ```typescript
427
427
  // apps/web/__tests__/integration/post-api.test.ts
428
- import { apiClient } from '@/lib/api-client'
428
+ import { rpc } from '@/lib/api/rpc'
429
429
  import { prisma } from '@repo/server-core/infrastructure/database/client'
430
430
 
431
431
  describe('Post API統合テスト', () => {
@@ -454,7 +454,7 @@ describe('Post API統合テスト', () => {
454
454
  })
455
455
 
456
456
  // When: API呼び出し
457
- const response = await apiClient.posts.$get({
457
+ const response = await rpc.posts.$get({
458
458
  query: { page: '1', limit: '10' }
459
459
  })
460
460
 
@@ -475,7 +475,7 @@ describe('Post API統合テスト', () => {
475
475
  })
476
476
 
477
477
  // When: 投稿作成APIを呼び出し
478
- const response = await apiClient.posts.$post({
478
+ const response = await rpc.posts.$post({
479
479
  json: {
480
480
  title: 'New Post',
481
481
  content: 'Content',