Reviews server-side TypeScript code for functional domain modeling principles: Discriminated Unions, Companion Objects, Branded Types, immutability, pure state transitions, Result error handling, boundary defenses, and type-safe tests.
npx claudepluginhub iwasa-kosui/functional-ts-principlesThis skill uses the workspace's default tool permissions.
`functional-ts-ja` スキルが定める関数型ドメインモデリング原則に照らしてサーバーサイドTypeScriptコードをレビューする。本スキルは `functional-ts-ja` と**同じナレッジを参照する**。各チェック項目はあちらの章と1対1で対応し、根拠の所在を相対リンクで明示する。
Reviews server-side TypeScript code for functional domain modeling principles: discriminated unions, companion objects, branded types, immutability, file structure, pure state transitions, exhaustiveness, Result error handling, boundary defense, and declarative style.
Provides TypeScript functional patterns for ADTs, discriminated unions, Result/Option types, branded types. Use for state machines, type-safe domain models, and error handling.
Reviews TypeScript code for strict mode, ESM, type safety, branded types, discriminated unions, and anti-patterns. Activates automatically on tsconfig.json, .ts, or .tsx files.
Share bugs, ideas, or general feedback.
functional-ts-ja スキルが定める関数型ドメインモデリング原則に照らしてサーバーサイドTypeScriptコードをレビューする。本スキルは functional-ts-ja と同じナレッジを参照する。各チェック項目はあちらの章と1対1で対応し、根拠の所在を相対リンクで明示する。
../functional-ts-ja/SKILL.md — 原則のインデックス../functional-ts-ja/error-handling.md../functional-ts-ja/boundary-defense.md../functional-ts-ja/state-modeling.mdpackage.json に応じたバリデーションライブラリガイド (../functional-ts-ja/validation-libraries/ 配下の zod.md / valibot.md / arktype.md)package.json に応じた Result ライブラリガイド (../functional-ts-ja/result-libraries/ 配下の neverthrow.md / byethrow.md / fp-ts.md / option-t.md)functional-ts-ja/SKILL.md の章立てと一致)でスキャンする。チェック項目は ../functional-ts-ja/SKILL.md の構造をそのまま反映する。各項目は正典の章へリンクする。
参照: ../functional-ts-ja/SKILL.md §1「Discriminated Unionで状態を表現する」
兆候: 多数の optional プロパティと string の状態フィールドを持つ単一の型(例: { state: string; driverId?: string; startTime?: Date })。状態ごとに型を分けて union にし、状態固有プロパティを必須にするよう提案する。
kind で統一されているか参照: ../functional-ts-ja/SKILL.md §1「discriminantは kind で統一する」
兆候: type, status, state, _tag など kind 以外の discriminant 名。コードベースの一貫性のため kind への変更を提案する。
参照: ../functional-ts-ja/SKILL.md §1「Discriminated Unionで状態を表現する」 および Companion Object パターン。
ドメインエンティティ・値オブジェクトの定義に class を使っている場合、Discriminated Union + Companion Object パターンへの変更を提案する。外部ライブラリが class 継承を要求する場合は正当な逸脱。
参照: ../functional-ts-ja/SKILL.md §1「Companion Object パターン」
以下を確認する:
const に集約されているか。XxxSchema ではなく companion object の .schema プロパティとして公開されているか。xxxAssignDriver のような独立関数として散在していないか。interface を使っていないか参照: ../functional-ts-ja/SKILL.md §1「type を使う(interface ではなく)」
declaration merging により型の形状が暗黙に変わる危険がある。ドメイン型は type で定義する。interface はライブラリの型拡張(augmentation)の場合のみ許容。
参照: ../functional-ts-ja/SKILL.md §1「関数プロパティ記法を使う(メソッド記法ではなく)」
メソッド記法(save(task: Task): Promise<void>)はパラメータが bivariant になり、依存注入時に狭い型の実装(save(task: DoingTask): …)が型チェックを通過する。関数プロパティ記法(save: (task: Task) => Promise<void>)への変更を提案する。
参照: ../functional-ts-ja/SKILL.md §1「Branded Typesで意味を区別する」。プロジェクトのバリデーションライブラリガイド (../functional-ts-ja/validation-libraries/) も参照。
兆候: ID や意味の異なる値(UserId, OrderId, Email, 金額など)が素の string / number で扱われている。バリデーションライブラリがある場合はそのブランド機能で(as キャスト不要)、ない場合は unique symbol パターンで定義されているかを確認する。
Readonly<> か参照: ../functional-ts-ja/SKILL.md §1「Readonly<> で不変性を保証する」
兆候: ドメインオブジェクトの型定義が Readonly<…>(または各プロパティの readonly)で保護されていない。状態変更は新しいオブジェクトの生成で表現する。
参照: ../functional-ts-ja/SKILL.md §1「ファイル構成: 1概念1ファイル」
兆候: types.ts, models.ts, domain.ts のような catch-all ファイルに多数のドメイン型が集約されている、特に companion object が別ファイルにある場合。barrel file(index.ts)は re-export のみ。
参照: ../functional-ts-ja/SKILL.md §2 および ../functional-ts-ja/state-modeling.md
兆候: 遷移関数の引数型が個別の状態(Waiting)ではなく union(TaxiRequest)になっている。広い型を受け取ると、無効な遷移元での呼び出しが許されてしまう。
switch に assertNever があるか参照: ../functional-ts-ja/SKILL.md §2「網羅性チェック」
兆候: kind で分岐する switch に default: return assertNever(x) がない。新バリアント追加時にコンパイルエラーで検出できなくなる。
参照: ../functional-ts-ja/SKILL.md §3, ../functional-ts-ja/error-handling.md, プロジェクトの Result ライブラリガイド (../functional-ts-ja/result-libraries/)。
兆候: エンティティ・値オブジェクト・ユースケース内の throw。Result 型への変更を提案する。許容: assertNever 内の throw(到達不能)、インフラ層の予期しない障害。
兆候: Error のサブクラス、自由形式の string エラーコード、Result<T, string>。Discriminated Union({ kind: "DriverNotAvailable"; driverId } | { kind: "RequestAlreadyAssigned" })への変更を提案し、呼び出し元が網羅的に分岐できるようにする。
プロジェクトに対応する Result ライブラリの API(.map, .andThen, Result.do など)でチェーン合成しているかを確認する。即 unwrap して if/else に展開している場合は、../functional-ts-ja/result-libraries/ 配下の該当ガイドを引用して適切なコンビネータを提案する。
参照: ../functional-ts-ja/SKILL.md §4, ../functional-ts-ja/boundary-defense.md, プロジェクトのバリデーションライブラリガイド (../functional-ts-ja/validation-libraries/)。
兆候: API ハンドラ、DB 結果のマッピング、キュー・メッセージハンドラ、ファイル・設定の読み込み、環境変数の読み取りなどで、生のデータをバリデーションライブラリ(Zod / Valibot / ArkType)でパースせずにドメイン型として扱っている。
as による型アサーションがないか参照: ../functional-ts-ja/SKILL.md §4「型アサーション(as)を使わない」
すべての as を洗い出し、以下のいずれかに該当するか確認する:
as: バリデーションライブラリを使わない場合(unique symbol パターン)のみ許容。Sensitive<T> でラップされているか参照: ../functional-ts-ja/SKILL.md §4「PIIの防御」, ../functional-ts-ja/boundary-defense.md
兆候: 個人情報を含みうるフィールド(氏名、メールアドレス、電話番号、住所、各種ID、決済情報、健康・診断情報、IP アドレスなど)が素の string / number のまま。特にログやエラーメッセージに出力されうるオブジェクトを重点的にチェックする。バリデーションスキーマで Sensitive.of による自動ラップが行われているかも確認する。
参照: ../functional-ts-ja/SKILL.md §5, ../functional-ts-ja/state-modeling.md
兆候: filter / map / reduce で表現できる変換を、for / for…of ループで命令的に組み立てている。述語関数を companion object に定義し、tasks.filter(Task.isActive) のように書くよう提案する。
兆候: 状態変更コードが共有のイベントログを mutate している、あるいは state-modeling ガイドが要求する場面でドメインイベントが発行されていない。Readonly<{ eventId; eventAt; eventName; payload; aggregateId }> としてリポジトリと分離して記録する。
参照: ../functional-ts-ja/SKILL.md §6
as const satisfies Type で定義されているか兆候: テストフィクスチャが : Type = や as Type で型付けされており、discriminant のリテラル型が string に widening されている。as const satisfies Type への変更を提案し、kind のリテラル型を保持する。
各指摘には以下を含める:
path:line)。../functional-ts-ja/... への参照リンク付き)と、違反した場合のリスク。### メソッド記法の使用
`src/repository/task-repository.ts:15`
`save(task: Task): Promise<void>` はメソッド記法です。
[`../functional-ts-ja/SKILL.md` §1「関数プロパティ記法を使う」](../functional-ts-ja/SKILL.md)
にあるとおり、メソッド記法ではパラメータが bivariant になり、
`save(task: DoingTask): Promise<void>` のような狭い型の実装が依存注入時に型チェックを通過します。
修正案:
\`\`\`typescript
type TaskRepository = {
save: (task: Task) => Promise<void>;
};
\`\`\`
| 重要度 | 項目 | 理由 |
|---|---|---|
| High | as 型アサーション (4.2) | ランタイムエラーの直接原因 |
| High | PII 未保護 (4.3) | コンプライアンス違反リスク |
| High | 外部境界のスキーマバリデーション不足 (4.1) | ランタイムエラーの直接原因 |
| High | 意味の異なるプリミティブの Branded Types 不足 (1.7) | 異種 ID の取り違えがランタイムで発生 |
| Medium | class 使用 (1.3) | 拡張時の型安全性低下 |
| Medium | optional プロパティでの状態モデリング (1.1) | 不正な状態が表現可能になる |
| Medium | ドメイン層での throw (3.1) | エラーハンドリングの一貫性欠如 |
| Medium | 非 Discriminated Union のエラー型 (3.2) | 呼び出し元が網羅的に分岐できない |
| Medium | assertNever 不足 (2.2) | 新バリアント追加時の見落とし |
| Medium | union 型を受ける状態遷移関数 (2.1) | 無効な遷移がコンパイルを通る |
| Medium | catch-all 型ファイル (1.9) | 循環依存・型と振る舞いの分離 |
| Medium | Companion Object パターン違反・スキーマ単独 export (1.4) | 実装詳細の漏洩 |
| Low | メソッド記法 (1.6) | 特定条件下でのみ問題顕在化 |
| Low | ドメイン型の interface 使用 (1.5) | declaration merging 事故は稀 |
| Low | Readonly<> 未使用のドメイン型 (1.8) | mutation はレビューで気付ける場合が多い |
| Low | discriminant が kind 以外 (1.2) | バグというよりスタイル不一致 |
| Low | 命令的な配列ループ (5.1) | 正確性ではなく可読性 |
| Low | ドメインイベント不発行 (5.2) | event sourcing の採否次第 |
| Low | フィクスチャに as const satisfies がない (6.1) | 実務上はテストで検出される |