Bun (1.3.9) を使用。npm/yarn/pnpm は使わない。
bun install— 依存関係インストールbun add <pkg>/bun add -d <pkg>— パッケージ追加bun remove <pkg>— パッケージ削除bun run <script>— スクリプト実行bunfig.tomlでexact = trueが設定済み(バージョン固定)
Turbo でワークスペースを管理するモノレポ。
apps/
admin/ — @hackz/admin NFC管理画面 (React + Vite)
mobile/ — @hackz/mobile 来場者スマホ画面 (React + Vite)
projector/ — @hackz/projector プロジェクター画面 (React + Vite)
packages/
server/ — @hackz/server Hono + tRPC バックエンド
shared/ — @hackz/shared Zod スキーマ・定数
tsconfig/ — @hackz/tsconfig 共有 tsconfig (base/react/bun)
| コマンド | 説明 |
|---|---|
bun run dev |
全アプリ並列起動 (Turbo) |
bun run build |
全アプリビルド |
bun run lint |
oxlint でリント |
bun run lint:fix |
oxlint で自動修正 |
bun run format |
oxfmt でフォーマットチェック |
bun run format:fix |
oxfmt で自動フォーマット |
bun run db:init |
DynamoDB Local テーブル初期化 |
bun test |
テスト実行 |
bun test --watch |
テスト watch モード |
Bun 組み込みテストランナーを使用。
- ファイル命名:
*.test.ts/*.test.tsx - 配置: ソースファイルと同階層、または
__tests__/ディレクトリ
- oxlint (1.50.0) — リンター
- oxfmt (0.35.0) — フォーマッター
- lefthook — pre-commit フック (sort-package-json → oxlint --fix → oxfmt)
tRPC で End-to-End 型安全な API を構築。Zod スキーマが型の Single Source of Truth。
packages/shared/src/schemas.ts に全 Zod スキーマを定義。型は z.infer<> で導出。
- Domain models:
userSchema,costumeSchema,userCostumeSchema,costumeBuildSchema,sessionSchema等 - Procedure inputs:
nfcLoginInputSchema,equipBuildInputSchema,createBuildInputSchema,updateBuildInputSchema等 - SSE event types:
projectorEventSchema,sessionEventSchema
packages/server/src/trpc/ に配置:
trpc.ts— initTRPC,publicProcedure,protectedProcedure(JWT 認証 middleware)context.ts— リクエストから JWT トークンを解析しコンテキストに userId を設定ee.ts— EventEmitter ベースのリアルタイムイベント配信(Socket.IO rooms の代替)routers/_app.ts—AppRouter型を export(フロントエンドの型推論に使用)routers/auth.ts— NFC ログインrouters/users.ts— ユーザー情報取得・写真アップロードrouters/gacha.ts— ガチャ(結果を projector に broadcast)routers/costumes.ts— コスチューム一覧・装備routers/sessions.ts— セッション作成・取得routers/synthesis.ts— 合成開始・ステータス確認routers/subscriptions.ts— SSE サブスクリプション + NFC スキャン mutation
Socket.IO の代わりに tRPC SSE subscriptions を使用:
sub.onProjector— プロジェクター向けイベント(NFC スキャン結果、ガチャ結果)sub.onSession— セッション単位の更新(ステータス変更、合成完了)sub.nfcScan— NFC スキャン報告(admin → projector へ broadcast)
各アプリに共通パターン:
src/lib/trpc.ts—createTRPCReact<AppRouter>()でクライアント作成src/lib/trpc-provider.tsx— QueryClient + tRPC ProviderhttpBatchLinkで query/mutationhttpSubscriptionLinkで SSE subscription
src/routes/__root.tsxで<TRPCProvider>ラップ
- フレームワーク: Hono (
packages/server/src/index.ts) - API: tRPC (
/trpc/*にマウント、@hono/trpc-serverアダプタ) - 認証: JWT (jose, HS256, 24h 有効期限) — tRPC middleware で自動検証
- ミドルウェア:
src/middleware/cors.ts - ドメイン層:
src/domain/— ビジネスロジック(純粋関数、インフラ非依存) - リポジトリ層:
src/repositories/— データアクセス抽象化 + DynamoDB 実装(楽観的ロック付き) - サービス:
src/services/(dynamodb, s3, bedrock) - ローカル開発時:
DYNAMODB_ENDPOINT設定時に.local/uploads/でファイル配信
- tRPC ルーターにビジネスロジックを書かない →
domain/に純粋関数として配置 - 入力バリデーションは tRPC の
.input()+ Zod スキーマ(packages/shared/src/schemas.ts)で実施 - DynamoDB 更新は
versionフィールドによる楽観的ロック必須(OptimisticLockError) - SSE subscription で送信するデータは受信者に応じてフィルタリング(不要な内部情報を含めない)
| 変数 | 説明 | ローカル開発時の値 |
|---|---|---|
DYNAMODB_ENDPOINT |
DynamoDB エンドポイント | http://localhost:8787 |
AWS_REGION |
AWS リージョン | ap-northeast-1 |
AWS_ACCESS_KEY_ID |
AWS アクセスキー | local |
AWS_SECRET_ACCESS_KEY |
AWS シークレットキー | local |
S3_UPLOADS_BUCKET |
S3 アップロードバケット | hackz-nulab-26-uploads |
S3_CONTENTS_BUCKET |
S3 コンテンツバケット | hackz-nulab-26-contents |
JWT_SECRET |
JWT 署名キー | 任意の文字列 |
PORT |
サーバーポート | 3000 |
| テーブル | PK | SK | GSI |
|---|---|---|---|
| Users | id | — | nfcId-index (nfcId) |
| Costumes | id | — | rarity-index (rarity) |
| UserCostumes | userId | costumeId | — |
| CostumeBuilds | userId | buildId | — |
| Sessions | id | — | userId-index (userId + createdAt) |
docker compose up -d で DynamoDB Local を起動(ポート 8787)し、bun run db:init でテーブル作成。
3 アプリとも同一構成: React 19 + Vite + TanStack Router + Tailwind CSS 4 + tRPC。
- tsconfig は
@hackz/tsconfig/react.jsonを継承 - API 呼び出しは tRPC React hooks (
trpc.xxx.useQuery(),trpc.xxx.useMutation()) - リアルタイム通信は
trpc.sub.onProjector.useSubscription()等
GitHub Actions (.github/workflows/ci.yml) で push / PR 時に実行:
bun install → bun run lint → bun run format → bun run build → bun test