typescript-standards について
コードベースで TypeScript を設計・記述するための規約です。エージェントや開発者を対象に、型、エラー処理、境界での解析、テスト、変更範囲を定めます。
主な機能
- 01型とモジュール設計の指針
- 02境界での解析とスキーマ利用
- 03エラー分類と処理の原則
- 04テストと可観測性の指針
- 05差分を最小限に保つ方針
活用シーン
- 01エージェントによる TypeScript 実装
- 02リファクタリング時のエラー処理統一
- 03型、モジュール、テストの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alexander-zuev/ agent-skills --skill typescript-standardsインストール後、Claude Code で/typescript-standardsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/modules-and-ports.mdstyle-and-files.mdtesting.md詳細
これらの規約では、このコードベースにおける TypeScript コードの設計方法と記述方法を定めています。特にエージェントを対象としており、パターン、ライブラリ、アダプター、抽象化を追加する前に既存のコードを調べることを求めています。ただし、これらの規約は、新規およびリファクタリングするすべての動作に適用してください。既存の規約は、これらの規約と矛盾しない場合にのみ従ってください。
ルールの適用範囲
このスキルは、型、スキーマ、モジュール設計、コメント、一般的なテストを対象とします。 FSD、React、クエリ、ミューテーション、UI の規約には、tanstack-frontend を使用してください。 アプリケーションの境界、トランスポート契約、リトライ、永続化には、cloudflare-backend を使用してください。
これらのスキルでは、それぞれが担当するトピックを定めています。内部の Result 規約は、トランスポートエンベロープを規定するものではありません。 明示的なタスク指示とグローバルなワークフロー制約も引き続き適用されます。 新規およびリファクタリングする動作には、これらの規約を適用してください。既存の互換性のないパターンは、その境界で封じ込めてください。 無関係なコードは変更しないでください。長期にわたって有効なアーキテクチャ上の決定が必要な場合は、ADR に記録してください。
基本原則
- デフォルトでは、型付きの throw されるエラーと reject される Promise を使用してください。確立された Result または Effect の契約は維持してください。
- 検証するのではなくパースする。早い段階で、できるだけ合成ルートまたはアプリケーションルートに近い場所でパースしてください。学習した情報を破棄して、検証だけで済ませないでください。
- 実用上可能な場合は、不正な状態を表現できないようにしてください。
- 規約に依存した不変条件より、構築によって正しさが保証される API を優先してください。
- 識別子や単位の取り違え、パースの回避、無効な値の構築など、現実的な誤りを防げる場合は、ブランド型、精緻化型、ドメイン型を優先してください。
- 継承より合成を優先してください。
- 命令型シェル/関数型コアを優先してください。
- 呼び出し側の負担が少ない、一貫性のある深いモジュールを設計してください。
- 実際の接点を通じて動作をテストし、モジュールのモックやスパイに依存したテストは避けてください。
- 人間とエージェントがコードを見つけやすい状態に保ってください。
再利用と操作の契約
汎用ロジックを書く前に、どのランタイム、フレームワーク、SDK、またはインストール済みの依存関係がその役割を担えるかを特定してください。 エクスポートとドキュメントを確認してください。スタックに適切な機能がない場合は、メンテナンスされているライブラリを検討してください。 独自のロジックを書くのは最後にし、その理由を明確にしてください。
所有する操作は、約束した効果が存在する場合にのみ応答してください。 ダウンストリームにポーリング、遅延レジストリ、完了待機処理を追加するのではなく、その操作自体を修正してください。 イベントソースから完了を直接取得できない場合に限り、そのイベントを Promise に変換する処理は、所有者の側で行ってください。 非同期操作は、受け付けたジョブを返してもかまいません。その契約では、受付と完了を区別してください。
シグネチャでは、関数が受け取るものを正確に宣言してください。パラメーター型が許容する範囲を超えるオブジェクトを渡さないでください。 宣言された API レスポンス型を使用してください。場当たり的なガードでレスポンス型を再構築する前に、クライアント設定とジェネリック型の推論を確認してください。 所有する API では、境界で不足しているスキーマまたは型を修正してください。信頼できない入力はスキーマでパースしてください。
既存のコードベースへの適用
新しいパターンやライブラリを追加する前に、リポジトリで次の項目に関する既存の選択を調べてください。
- エラー処理
- スキーマのパース
- 依存性の注入
- テスト
- 可観測性
- アダプター/サービス
- モジュール構成
すべての新しいコードと、リファクタリング対象の動作全体に、これらの規約を適用してください。一貫性を保つためだけに、より弱いパターンを残さないでください。無関係な古いコードはそのままにし、互換性のないパターンは最も近い境界で変換してください。
ほとんどのアプリでは、throw されるエラーと reject される Promise を使用します。新規およびリファクタリングするコードでは、その慣例に従ってください。
better-result への依存があること(間接依存を含む)は、Result の採用を正当化する理由にはなりません。
既存のログ、トレース、メトリクス、エラー報告のフックを維持してください。
最小限の差分
変更は、合意した動作に必要な行だけに加えます。それ以外の既存行は、すべてバイト単位で同一に保ちます。 既存コードの移動、インデントの変更、折り返し、言い換え、名前の変更、再入力はしないでください。新しいコードは既存コードの周囲に追加し、合わせるために構造を変えないでください。 依頼されていない整理は、ユーザーの同意がある場合、または実質的な改善となる場合に限り、その場合も別の変更として行ってください。 リファクタリングは、ユーザーが依頼した場合に限り例外です。変更に伴って他の行の移動が必要になる場合は、まずその理由を説明してください。
依頼されていない編集は、レビューで本当の変更を見えにくくし、git blame の記録を誤ったコミットに移してしまいます。エクスポートされたシンボルの名前を変えると、呼び出し元が壊れることもあります。
エラーと失敗
各操作で発生する失敗を定義する
TypeScript では戻り値の型にスローされるエラーを宣言できない場合でも、既知の失敗を検討してください。 安定したタグまたはコードと有用なコンテキストを持つ型付きエラーを使用してください。メッセージテキストからエラーを分類してはいけません。
実装の前に、以下を特定してください。
- キャンセルや部分完了を含め、操作で発生し得る失敗。
- 呼び出し元が区別する必要のある失敗と、それぞれに必要な対応。
- 外部の失敗を変換するレイヤーと、最終的なエラーをログに記録する境界。
- 繰り返し実行が安全かどうか、およびどのレイヤーが再試行を担うか。
- 重要な失敗動作とクリーンアップを証明する、対象を絞ったテスト。
自明でないスローの失敗には @throws で説明を記述してください。チェック例外を示すためだけに、使用されないエラー共用体を追加しないでください。
必要に応じて、外部の失敗を、それを所有するアダプターで変換してください。その原因と、呼び出し元に必要な区別を維持してください。
想定される不在、完了、何もしない結果は、エラーではなく通常のデータとして扱ってもかまいません。
Result の限定的な使用
明示的な Result の合成が具体的な利点をもたらす場合に限り、分離された操作で better-result を使用してください。
例として、細かな再試行の分類や、複数の回復可能な結果を伴う例外的なワークフローがあります。
再試行があるからといって、Result が必須になるわけではありません。まず、プロジェクトに既存の再試行機能があるか確認してください。
その箇所での利点を明示し、変換はその操作の境界に留めてください。このスキルを満たすために、呼び出し元へ Result を広げないでください。
依存関係が利用可能だから、または想定される失敗があるからという理由だけで導入しないでください。
アプリ全体で Result を採用するには、明示的に承認された別の変更が必要です。
既存の Result または Effect のコードは維持してください。この既定方針は、それらをスロー方式へ移行することを求めるものではありません。
トランスポートでの変換
トランスポートアダプターでは、そのトランスポートの失敗チャネルを使用してください。共有ミドルウェアがシリアライズとエラー変換を担います。 分離された操作が Result を返す場合、そのアダプターで成功値を取り出すか、型付きエラーをスローして既存のミドルウェアに渡せます。 プロジェクトに既存の変換ヘルパーがあれば、それを使用してください。成功を包むエンベロープを追加したり、関数ごとにシリアライズを繰り返したりしないでください。 サーバースタブを追加する場合も含め、この契約を変更する前にバックエンドのスキルを確認してください。
不具合はスローまたはパニックで表現できる
本番コードでは throw new Error(...) を決して使用しないでください。安定したタグまたはコードを持つ型付きエラーを使用してください。変更する箇所にある生の throw は移行してください。意図的なテストや失敗の検証での throw は例外です。
型付き throw は想定される失敗を表現できます。パニックおよび defect ヘルパーは、正しい実行が不可能になる条件に限って使用してください。defect には以下が含まれます。
- 内部不変条件への違反
- 到達不可能な分岐
- 一時的な
notYetImplementedの経路 - 破滅的なランタイム条件
既知の設定失敗には型付きエラーを使用し、コンポジションルートで安全に報告して起動を終了してください。
利用可能な場合は既存の共有 defect ヘルパーを使用してください。ない場合は、プロジェクトの Result ライブラリにある panic ヘルパーを使用してください。
export function casesHandled(unexpectedCase: never): never;
export function shouldNeverHappen(msg?: string): never;
export function notYetImplemented(msg?: string): never;
共用体を網羅的に処理するには casesHandled を使用してください。プロジェクトに既存のヘルパーがある場合は、absurd のような名前や、その場限りの assertNever ヘルパーを避けてください。
カスタムエラー
想定される失敗には、通常、次のいずれかを継承するカスタムのタグ付きエラーを使用します。
Errorbetter-resultのTaggedError- Effect のコードベースでは
Schema.TaggedErrorClass
カスタムエラーには、次の要素を含めます。
'as const'を使用した安定したタグ- 有用なメッセージ
- 構造化されたコンテキストフィールド
- 安全なテレメトリフィールド
- 任意の
cause: unknown
例:
export class UserStoreUnavailable extends Error {
readonly _tag = "UserStoreUnavailable" as const;
constructor(
readonly operation: "findActiveByEmail",
readonly provider: "postgres",
readonly cause: unknown,
) {
super(`User store unavailable during ${operation}`);
}
}
モジュール境界では、エラーユニオンを正確に保ちます。
Result<User, UserNotFound | UserStoreUnavailable>
エントリーポイント、オーケストレーション、ロギング、レンダリングの各レイヤー付近を除き、広範な AppError 形式の型は避けます。
エラーのラップと再スロー
呼び出し元に内部エラー型を見せるべきではないポートをまたぐ場合に限り、エラーをラップします。各内部タグを、呼び出し元がすでに分岐処理できる外側のタグにマッピングします。内部エラーは cause として保持します。呼び出し元は外側のタグに基づいて分岐し、判断のために cause の中身を調べてはいけません。
次の場合はラップしないでください。
- 内部タグを所有するレイヤー内にいる
- すべての内部エラーが同じ外側のタグになる(これは誤りです。無効な入力は「利用不可」ではありません)
- 内部型のインポートを避けることだけがラップの目的である
以下の例では、既存の throw ベースの境界契約を維持しています。 内部の Result ポートでは、マッピングしたエラーを型付きの値として返します。
適切 — ポートでマッピングする:
async function startGrok(signal: AbortSignal): Promise<AcpTransport> {
try {
return await AcpTransport.start({ command: "grok", signal, /* … */ })
} catch (cause) {
throw new CodingAgentUnavailableError({ cause })
}
}
Spawn に失敗しました。コーディングエージェントのポートには ACP spawn 型がありません。呼び出し元が対処できる事実は Unavailable です。
不適切 — すべてを捕捉して再スローする:
async function grokRequest(transport: AcpTransport, input: Request): Promise<Response> {
try {
return await transport.request(input)
} catch (cause) {
throw new CodingAgentUnavailableError({ cause })
}
}
request はタイムアウト、プロセスの終了、JSON-RPC の拒否をすでに分類しています。このラップでは、その区別が失われます。不正な configId が「Grok がダウンしている」ように見えてしまいます。代わりに transport.request を呼び出すか、AcpRpcError を CodingAgentRequestError にマッピングし、transport/timeout/exit の各タグのみを CodingAgentUnavailableError にマッピングしてください。
機密データ、テレメトリ、デバッグ
リクエスト、ジョブ、ワークフロー、アプリケーションモジュール、アダプター、外部呼び出しをエンドツーエンドで構造化トレースできるようにすることを推奨します。
トレース/ログには、安全なフィールドを使って失敗を診断できる情報を含めます。
- ドメイン ID
- 操作名
- 依存関係/プロバイダー名
- 状態タグ
- 再試行回数
- 型付きエラータグ
- 安全な要約
エラー、トレース、ログ、スナップショットに秘密情報を含めてはいけません。
トークン、API キー、パスワード、生の認証情報、シークレットなどの機密値には、Redacted<T> ラッパーを使用します。Effect のコードベースでは Effect の Redacted.Redacted を、そうでない場合は共有の Redacted<T> ラッパーを使用することを推奨します。
境界で機密値をラップし、生の値が必要な箇所(通常は外部呼び出しを行うアダプター内)でのみラップを解除します。
検証するのではなく、パースする
境界コードでは、未知の入力や構造化が不十分な入力を、内部コードに渡す前にアプリケーション型またはドメイン型へ変換します。
形状や意味が十分に異なり、分けることに価値がある場合に限り、別個のプロトコル投影を使用します。DTO は説明文で境界における役割を示す語です。シンボル名には DTO や Dto を使用しないでください。CreateUserRequest、StripeCustomerResponse、UserRecord のように、実際のプロトコル上または永続化上の意味に基づいてシンボル名を付けます。
unknown -> CreateUserRequest -> CreateUserInput -> EmailAddress/UserId/etc.
それ以外の場合は、アプリケーション入力に直接パースします。
unknown -> CreateUserInput
スキーマから推論されたトランスポート形状を、アプリケーション全体に渡してはいけません。
unknown -> z.infer<typeof CreateUserSchema>
意味を保つ名前を使用します。
- 信頼できない入力や構造化が不十分な入力には、
parseX(input): Result<X, ParseXError> - すでに型付けされた要素からスマートコンストラクターを作る場合は、
makeX(...)/createX(...) - 真の述語には、
isX(value): value is X assertX(...)は、主にテスト/フレームワークの境界で、まれに使用します
関数が絞り込まれた値を返す場合は、validateX を避けてください。これは何かをパースしています。
スキーマ
スキーマライブラリは、コアロジックに場当たり的なバリデーターを散在させるのではなく、境界でのパーサーとして使用してください。
優先順位:
- リポジトリで確立されたスキーマライブラリがあれば、それを使用してください
- Effect のコードベースでは Effect Schema を使用してください
- 汎用ヘルパーでは Standard Schema 互換を優先してください
- それ以外の場合は Zod 4 を優先してください
- 小さなドメイン型をより明確に扱える場合は、手書きのスマートコンストラクター/パーサーを使用してください
スキーマのパースでは、可能な限り、絞り込み済みのドメイン型と型付きのカスタムエラーを生成してください。
Zod 4 の場合:
- トップレベルのフォーマットヘルパーを使用してください:
z.uuid()、z.email()、z.url()、z.httpUrl()、z.iso.datetime()、z.iso.date()、z.iso.time()。 - 該当する場合は、
z.uuidv7()、z.nanoid()、z.ipv4()、z.base64()、z.jwt()、z.stringbool()を使用してください。 - メッセージは
messageやinvalid_type_errorではなく、{ error: "message" }で設定してください。 - 型付き ID には、単なる
z.string()ではなく、UserIdSchemaなどのブランド付き ID スキーマを使用してください。
ブランド型と適切な構築
現実的な誤用や不正な構築を防げる場合は、特に次のような型にブランド型/絞り込み型を使用してください。
- ID:
UserId、OrgId、WorkflowId - パース済み文字列:
EmailAddress、NonEmptyString、Url - 制約付き数値:
PositiveInt、Cents、Percentage - 単位:
Milliseconds、Bytes、UsdCents
ブランド値は、パーサーまたはスマートコンストラクターを通じて構築してください。ドメイン型が存在する場合は、生の文字列や数値を渡さないでください。
値が必須の関数では、optional/null/undefined の値を避けてください。オプショナル性は外側に押し出してください。呼び出し前に分岐するか、パースしてください。
部分性が実際のドメイン概念でない限り、アプリケーション/ドメインの入力として Partial<T> を使用しないでください。各操作には明示的な入力型を優先してください。
1つの事実は1つの表現で
行、契約、イベント、キャッシュ、状態、プロパティ、設定において、独立した事実はそれぞれ一度だけ表現してください。 正規の事実または権威ある結果を保持してください。決定論的な値は、所有するレイヤーまたは利用するレイヤーで導出してください。 導出にビジネスポリシーが含まれる場合は、結果を公開してください。利便性のために入力を重複させないでください。
フィールドを追加する前に、次の点を確認してください。
- これは独立した事実ですか?
- 既存のフィールドから正確に導出できますか?
- 値が食い違った場合、どうなりますか?
- どの値が正規ですか?
- どのレイヤーが導出を担いますか?
外部互換性、不変の監査スナップショット、実測によるパフォーマンス上の理由、または非公開の元データを取得できない場合に限り、冗長性を許容してください。 正規の情報源を特定してください。可能な場合はアトミックに同期し、不変条件を適用してください。値が食い違うケースをテストしてください。
状態マシンとブール値による意味の不明瞭さ
エンティティに意味のあるライフサイクル状態がある場合は、タグ付きユニオンまたは同等の値クラスでモデル化してください。
次を優先してください。
type Invoice =
| { readonly _tag: "Draft"; readonly id: InvoiceId; readonly lines: NonEmptyArray<LineItem> }
| { readonly _tag: "Sent"; readonly id: InvoiceId; readonly sentAt: Instant }
| { readonly _tag: "Paid"; readonly id: InvoiceId; readonly paidAt: Instant };
次は避けてください。
type Invoice = {
readonly isSent: boolean;
readonly isPaid: boolean;
readonly sentAt?: Date;
readonly paidAt?: Date;
};
動作を制御するブール値のパラメーターは避けてください。
createUser(input, true);
名前付きオプションまたはドメイン型を優先してください。
createUser(input, { emailVerification: "skip" });
明確な述語の戻り値としてのブール値は問題ありません。
isExpired(token): boolean;
hasPermission(user, permission): boolean;
モジュールと抽象化
Domain Module、Application Service Module、Adapter Module は、フォルダーではなく責務を表します。モジュール、ポート、またはアダプターを設計またはレビューする前に、モジュールとポートを読んでください。各ロール、深いモジュール、アプリケーション所有のポート、アダプター再利用の監査、リポジトリについて説明しています。
関数型コア、命令型シェル、エントリーポイント
ドメインモジュールは関数型コアを構成します。アプリケーションサービスモジュールとアダプターモジュールは命令型シェルを構成しますが、テクノロジー固有の関心事を含むのはアダプターだけです。これにより、同じアプリケーション操作を REST、CLI、GraphQL、ワーカー、その他のエントリーポイントから再利用できます。
関数型コアには、ドメインパーサー、不変条件、状態遷移、計算、コンビネーター、判断関数が含まれます。I/O、隠れた依存関係、暗黙の時刻や乱数、想定される失敗のスロー、フレームワーク固有の関心事は避けます。
命令型シェルには、次の2つの明確な責務があります。
- アプリケーションサービスは、アプリケーションポリシーを適用し、明示的なポートを通じて副作用を順序立てて実行します。
- アダプターは、境界値を解析または射影し、外部の失敗を分類して、具体的なI/Oを実行します。
エントリーポイントアダプターは、薄いプロトコル変換レイヤーにします。プロトコル固有の入力を解析し、ドメインモジュールのパーサーを呼び出して精緻化された値を取得し、アプリケーションポリシーや副作用が関わる場合はアプリケーションサービスを呼び出して、プロトコル固有の出力を生成します。モジュールとポートで説明しているとおり、純粋な操作ではドメインモジュールを直接呼び出してもかまいません。コントローラー、リゾルバー、コマンド、ハンドラーでビジネスルールを重複させないでください。
認証と認可では、インバウンドアダプターが境界の認証情報を検証し、Principal、Session、CommandActorなどの解析済みアイデンティティを生成します。ドメインモジュールは、解析済みのドメイン値に対する純粋な権限判断を定義できます。アプリケーションサービスは、必要なコンテキストを集め、アプリケーション操作を実行しながらそれらの判断を適用します。認証情報の欠落や無効、および操作の拒否をプロトコル固有の結果に射影するのはアダプターであり、権限ポリシーを定義するのではありません。
ワークフロー、トランザクション、冪等性
単純な単一境界の操作には、通常の関数呼び出しまたはデータベーストランザクションを使用します。
プロセスの停止や再配信後も処理の進行状況を保持する必要がある場合、または操作に長い遅延、補償、再開可能性、タイマー、人間による承認、サービス間の調整、複数のトランザクション境界が必要な場合は、サーガまたは永続的なワークフローを使用します。短時間の再試行だけであれば、永続的なワークフローの仕組みは必要ありません。
安全で短時間の技術的な再試行はアダプターが担当します。アプリケーション操作を再試行すべきかどうかは、アプリケーションサービスが判断します。クラッシュ、遅延、再配信を経ても継続する必要がある再試行は、永続的なワークフローが担当します。
ネットワーク呼び出し中や長時間実行される操作中に、データベーストランザクションを開いたままにしないでください。
外部から観測可能な変更や状態遷移で、再試行される可能性があるものには、明示的な冪等性戦略が必要です。
- 冪等性キー
- 自然キーによる一意制約
- 重複排除レコード
- ステートマシンの遷移ガード
- トランザクショナルアウトボックス/インボックス
再試行を「おそらく安全」な副作用に頼らせないでください。
テスト
vi.mockやjest.mockは決して使用せず、実際の境界を通じてテストしてください。テストを作成、レビュー、または高速化する前に、テストを読んでください。
これはすべてのプロジェクトにおける既定の方針です。プロジェクトのテスト文書には例外のみを記載します。
TypeScriptのスタイルと安全性
any、非 null アサーション、SAFETY:コメントのないasキャストは禁止です。TypeScriptを作成またはレビューする前に、スタイルとファイルを読んでください。コンパイラー設定、キャスト、インポート、エクスポート、ファイル名について説明しています。
コメントと JSDoc
JSDoc 以外のコメントは、すべて必ず1行にします。理由を説明します。制約、採用しなかった代替案、または外部ルールを記述してください。 コードを見ればわかる内容を説明するコメントは削除します。
JSDoc は通常2〜3行にします。呼び出し元にとって自明でない契約、副作用、順序、または存続期間のルールを示す場合に限り、行を追加します。
主要なモジュールと、エクスポートされたアーキテクチャ上の境界を文書化します。呼び出し元が正しく使用できるようになったら、それ以上は記述しません。
TypeScript の型を繰り返したり、すべてのフィールドに定型文を追加したりしないでください。再エクスポートではなく、元の宣言を文書化します。
@inheritDoc や @inherit は使用しないでください。
欠陥、フレームワークで必須の throw、一時的な未実装箇所には @throws を使用します。
内部で想定される失敗は、戻り値として文書化します。通信の失敗チャネルには、バックエンドのスキルに従います。
非推奨のシンボルやパスには、それぞれ言語の非推奨ディレクティブを使用します。 ディレクティブがない場合に限り、1行コメントを使用します。非推奨項目ごとに Linear の課題を作成します。 削除の条件、そのパスを残す理由、削除を検証で確認する方法を明記します。
ワークスペースの境界
アプリケーションには apps/、共有コードには packages/ を使用します。
ワークスペースの依存方向は packages → ∅ および apps → packages です。アプリケーション間のインポートは禁止です。
Core は共有型、スキーマ、定数、共通ロジックをエクスポートします。
ローカル依存関係には workspace:* を使用します。サードパーティのバージョンとカタログの管理には AGENTS.md に従います。
プロジェクトのスクリプトまたはタスクグラフを使って、共有依存関係を利用側より先にビルドします。
設定とリソース
環境変数/設定は、起動時または最初の境界で解析して、必要に応じてブランド型や秘匿化された値を使った型付き設定にします。既知の設定エラーは、タグ付きエラー値として返します。コンポジションルートでは、安全な起動メッセージを表示して終了し、無効な設定を内部欠陥として扱わないようにします。
アプリ内のあちこちで process.env を読み取らないでください。設定の不足や無効な値は、有用かつ安全な情報を伴う起動エラーです。
真のエントリーポイント/ブートストラップファイルを除き、トップレベルの副作用を避けます。モジュールはインポート時にサーバーを起動したり、接続を開いたり、環境変数を読み取ったり、ハンドラーを登録したり、I/O を実行したりしてはなりません。
リソースの作成とクリーンアップは明示的に行い、ブートストラップ/命令型シェルのコード、または Effect を使用する場合は Effect レイヤーで管理します。
可変のシングルトンやグローバル状態は避けます。定数や純粋なルックアップテーブルは問題ありません。フレームワーク/ランタイムでシングルトンが必要な場合は、境界に隔離します。
依存関係を持つモジュールには、Clock/Random サービスを注入します。純粋なドメイン関数では、明示的な now/乱数値を受け取れます。
エージェント向けクイックチェックリスト
コーディング前に:
- エラー、スキーマ、テスト、アダプター、テレメトリー、モジュール構成に関する既存の規約を確認します。
- 変更する各関心事を、Domain Module、Application Service Module、Adapter Module、またはコンポジションルートの配線として分類します。
- 新しいものを作る前に、既存の Domain Modules、Application Services、Adapters を再利用します。
- エフェクトの依存関係は、アプリケーション所有の狭いポートとして定義します。外部の生の型は Adapters またはコンポジションルートに留めます。
- 入力は境界で解析し、内部ではドメイン型を使用します。
- コア/アプリケーションロジックでは、生の DTO、生の ID、null 許容の寄せ集め、
Partial<T>を避けます。 - 新たに想定される失敗には、型付きエラーを値として優先します。
- 既存の可観測性/エラー処理の仕組みを維持します。
- 公開インターフェースと実際の接合点を通じてテストします。
- 実用的な場合は、テストデータの生成に
fast-checkの任意値生成器を使用します。 - 型だけでは伝わらない情報が呼び出し元に必要な場合は、エクスポートされた契約を文書化します。
- Adapter/Application Service の再利用監査で、長期的なアーキテクチャ境界、共有パターン、プロバイダー戦略、または意図的な例外が判明した場合に限り、ADR を追加します。