@einja/dev-cli 0.1.45 → 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.
Files changed (42) hide show
  1. package/dist/cli.js +7 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/sync.d.ts.map +1 -1
  4. package/dist/commands/sync.js +50 -11
  5. package/dist/commands/sync.js.map +1 -1
  6. package/dist/commands/sync.test.js +1 -1
  7. package/dist/commands/sync.test.js.map +1 -1
  8. package/dist/lib/sync/file-filter.d.ts.map +1 -1
  9. package/dist/lib/sync/file-filter.js +49 -0
  10. package/dist/lib/sync/file-filter.js.map +1 -1
  11. package/dist/lib/sync/file-filter.test.js +40 -0
  12. package/dist/lib/sync/file-filter.test.js.map +1 -1
  13. package/dist/lib/sync/json-processor.d.ts +49 -27
  14. package/dist/lib/sync/json-processor.d.ts.map +1 -1
  15. package/dist/lib/sync/json-processor.js +182 -82
  16. package/dist/lib/sync/json-processor.js.map +1 -1
  17. package/dist/lib/sync/json-processor.test.d.ts +2 -0
  18. package/dist/lib/sync/json-processor.test.d.ts.map +1 -0
  19. package/dist/lib/sync/json-processor.test.js +334 -0
  20. package/dist/lib/sync/json-processor.test.js.map +1 -0
  21. package/dist/lib/sync/metadata-manager.d.ts +6 -1
  22. package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
  23. package/dist/lib/sync/metadata-manager.js +26 -6
  24. package/dist/lib/sync/metadata-manager.js.map +1 -1
  25. package/dist/types/sync.d.ts +2 -0
  26. package/dist/types/sync.d.ts.map +1 -1
  27. package/dist/types/sync.js +1 -0
  28. package/dist/types/sync.js.map +1 -1
  29. package/package.json +1 -1
  30. package/presets/default/.claude/agents/einja/backend-architect.md +27 -17
  31. package/presets/default/.claude/commands/einja/einja-sync.md +6 -6
  32. package/presets/default/.claude/skills/einja-coding-standards/references/testing-strategy.md +3 -3
  33. package/presets/default/docs/einja/instructions/setup-flow.md +40 -0
  34. package/presets/default/docs/einja/steering/architecture.md +7 -1
  35. package/presets/default/docs/einja/steering/development/api-development.md +199 -67
  36. package/presets/default/docs/einja/steering/development/backend-architecture.md +12 -16
  37. package/presets/default/docs/einja/steering/development/frontend-development.md +61 -50
  38. package/presets/default/docs/einja/steering/development/review-guidelines.md +4 -1
  39. package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -3
  40. package/presets/default/package.json +73 -0
  41. package/presets/default/preset.yaml +2 -2
  42. package/presets/default/scripts/ensure-serena.sh +26 -7
@@ -104,10 +104,10 @@ AskUserQuestion:
104
104
 
105
105
  ```bash
106
106
  # dev-cli の場合
107
- npx --yes @einja/dev-cli sync --only <categories> --dry-run --yes
107
+ npx --yes @einja/dev-cli@latest sync --only <categories> --dry-run --yes
108
108
 
109
109
  # create-einja-app の場合
110
- npx --yes create-einja-app sync --categories <categories> --dry-run
110
+ npx --yes create-einja-app@latest sync --categories <categories> --dry-run
111
111
  ```
112
112
 
113
113
  > **注**: 両CLIでカテゴリ指定のオプション名が異なる(dev-cli: `--only` / create-einja-app: `--categories`)
@@ -156,13 +156,13 @@ AskUserQuestion:
156
156
 
157
157
  ```bash
158
158
  # dev-cli(孤児削除あり)
159
- npx --yes @einja/dev-cli sync --only <categories> --yes --json --clean
159
+ npx --yes @einja/dev-cli@latest sync --only <categories> --yes --json --clean
160
160
 
161
161
  # dev-cli(孤児削除なし)
162
- npx --yes @einja/dev-cli sync --only <categories> --yes --json
162
+ npx --yes @einja/dev-cli@latest sync --only <categories> --yes --json
163
163
 
164
164
  # create-einja-app
165
- npx --yes create-einja-app sync --categories <categories>
165
+ npx --yes create-einja-app@latest sync --categories <categories>
166
166
  ```
167
167
 
168
168
  - dev-cli は `--json` オプションで構造化出力を取得しパースする
@@ -228,7 +228,7 @@ f. 解消結果を `Read` で確認し、ユーザーに表示
228
228
 
229
229
  Step 4 で孤児削除を選択した場合、JSON出力の `summary.orphansDeleted` を確認し、削除されたファイル一覧を記録する(Step 9 の詳細表示で使用)。
230
230
 
231
- Step 4 で孤児削除をスキップした場合、孤児ファイル一覧を再表示し「後で `npx --yes @einja/dev-cli sync --only <categories> --clean --yes` で削除できます」と案内する。
231
+ Step 4 で孤児削除をスキップした場合、孤児ファイル一覧を再表示し「後で `npx --yes @einja/dev-cli@latest sync --only <categories> --clean --yes` で削除できます」と案内する。
232
232
 
233
233
  ### Step 7: direnv allow 実行確認
234
234
 
@@ -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',
@@ -202,6 +202,8 @@ sequenceDiagram
202
202
  | `scripts` | `scripts/` | ユーティリティスクリプト(`_` プレフィックスは除外) |
203
203
  | `env` | `.envrc` | direnv 設定 |
204
204
  | `tools` | `.vscode/settings.json` | VS Code 設定 |
205
+ | `root-config` | `package.json`, `.mcp.json` | ルート設定ファイル |
206
+ | `claude-config` | `.claude/settings.json` | Claude Code設定 |
205
207
 
206
208
  **マージ方式:**
207
209
 
@@ -213,6 +215,44 @@ sequenceDiagram
213
215
  | JSON マージ | `.json` 拡張子のファイル | managed/project-private の JSON パス指定に基づきマージ |
214
216
  | 3方向マージ | マーカーなしの通常ファイル | base(前回テンプレート)・local・template の3方向差分で自動マージ。コンフリクト時はマーカー付きで出力 |
215
217
 
218
+ #### JSON マージモード(3モード構成)
219
+
220
+ | モード | 動作 | 設定方法 |
221
+ |--------|------|---------|
222
+ | `managed` | テンプレート値で強制上書き | jsonPaths.managed にパス指定 |
223
+ | `project-private` | 完全除外(テンプレートから追加しない) | jsonPaths["project-private"] にパス指定 |
224
+ | デフォルト | 3方向マージ(base/local/template比較、コンフリクト検出) | 上記以外の全パス |
225
+
226
+ #### JSON ファイルの同期動作(3方向マージ)
227
+
228
+ | 操作 | 結果 |
229
+ |------|------|
230
+ | テンプレートに新キーが追加された(利用者は未変更) | sync時に利用者のファイルに追加される |
231
+ | 利用者が独自キーを追加した | 保持される |
232
+ | 利用者がテンプレート由来のキーを削除(テンプレート側は未変更) | 削除が維持される |
233
+ | 利用者がテンプレート由来のキーを変更(テンプレート側は未変更) | 利用者の変更が保持される |
234
+ | テンプレートがキーを更新(利用者側は未変更) | テンプレートの更新が自動適用される |
235
+ | 両方が同じキーを異なる値に変更 | コンフリクト警告(利用者の値を保持) |
236
+ | project-private 指定のキー | テンプレートから一切追加・変更されない |
237
+ | managed 指定のキー | テンプレート値で常に上書き |
238
+
239
+ #### ファイル別 jsonPaths 設定
240
+
241
+ | ファイル | managed | project-private | 残り |
242
+ |---------|---------|----------------|------|
243
+ | `package.json` | — | name, version, private, workspaces, packageManager, volta | 3方向マージ |
244
+ | `.claude/settings.json` | plansDirectory, includeCoAuthoredBy | — | 3方向マージ |
245
+ | `.vscode/settings.json` | editor.*, eslint.*, prettier.*, [json], [jsonc] | — | 3方向マージ |
246
+ | `.mcp.json` | — | — | 3方向マージ |
247
+
248
+ #### base スナップショット
249
+
250
+ 3方向マージには「前回sync時のテンプレート内容」(base)が必要。
251
+ `.einja-sync.json` の `baseContent` フィールドに保存される。
252
+
253
+ - 初回sync(baseなし): ローカル優先 + テンプレートの新規キーのみ追加
254
+ - 2回目以降: base/local/template の3方向比較でマージ
255
+
216
256
  #### create-einja-app sync の同期対象カテゴリ
217
257
 
218
258
  | カテゴリ | 対象パターン | デフォルト選択 |
@@ -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
  ---