effect-typescript について
TypeScript の I/O とリソース管理に Effect を適用するためのコーディングスキルです。darkmatter 向けの規約に沿って、エラー、依存関係、クリーンアップ、テスト、実行境界を明示する方法を示します。
主な機能
- 01Effect による I/O の指針
- 02型付きエラーとサービスレイヤー
- 03リソースのライフサイクルとクリーンアップ
- 04darkmatter 向け Bun・Alchemy の規約
- 05effect-orpc の利用指針
活用シーン
- 01TypeScript の I/O コードへの Effect 導入
- 02既存の Effect 実装のレビュー
- 03effect-orpc を使った型付き RPC ハンドラーの設計
- 04darkmatter 向け TypeScript インフラの構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ darkmatter/ skills --skill effect-typescriptインストール後、Claude Code で/effect-typescriptを入力して読み込まれたか確認します。
同梱ファイルeffect-package-map.md
詳細
Effect TypeScript
アプリケーションの I/O には Effect を使い、失敗、依存関係、リソースのライフタイムを明示します。Promise ベースのドライバーや SDK は境界で一度だけ適合させ、内部の処理は Effect で記述します。Promise が必要な呼び出し元、またはプログラムを開始するランタイム境界で Effect を実行します。純粋な変換は通常の TypeScript のままにします。
共通の読みやすさに関するルールと例については、codebase-design に従ってください。関連するクエリ、バインディング、デコード、プライベートヘルパーはアダプター内にまとめます。複雑さを隠せる場合や、有用な再利用につながる場合にのみ抽出してください。処理は完了、エラー、クリーンアップを担うか、その責任を呼び出し元に明示的に引き継ぎます。
このスキルでは、Effect のガイダンスを darkmatter の規約に合わせています。darkmatter プロジェクトでは pnpm ではなく Bun のコマンドを使い、デプロイ可能なインフラストラクチャには Alchemy を優先してください。
ブートストラップ
effect-solutions が $PATH にない場合は、インストールしてください:bun add -g effect-solutions@latest。API の詳細については、リポジトリに固定された Effect のソースまたはインストール済みのパッケージを優先してください。どちらも利用できない場合は、バージョンに一致するチェックアウトを使ってください。
新しい Darkmatter TypeScript リポジトリでは、依存関係を
darkmatter/template のルートカタログに合わせてください。Effect 4、@effect/tsgo、
@effect/vitest、@effect/platform-bun、effect-solutions を使い、エージェントのループの契約を独自に考案する前に、Effect のエージェント API(effect/unstable/ai)を優先してください。
ガイドライン
- Effect を使ったコードを書くときは、
effect-solutionsCLI が推奨するベストプラクティスを参照してください。 - より具体的な情報が必要な場合は、参照ディレクトリに Effect のソースのローカルチェックアウトがあります。
使用する場合
- 意味のある I/O があり、失敗、依存関係、リソース、リトライ、検証、テストの境界を明示することで作業が改善される場合。
- コードですでに Effect を使っており、Effect のコードを追加またはレビューする場合。
- TypeScript/Bun の機能で Effect を使うべきか判断する場合。
- 外部 API、データベース、キュー、ワーカー、CLI、スケジュール、リトライ、設定、シークレット、構造化ログ、ランタイム検証、リソースのクリーンアップ、並行ワークフローが関わる場合。
- 構造化されていない例外ではなく、型付きのドメインエラーが必要な場合。
- サービスと Layer を通じて、差し替え可能な本番用/テスト用の実装が必要な場合。
- TypeScript のインフラストラクチャやワーカーをデプロイし、Alchemy を考慮した規約が必要な場合。
使用しない場合
- 小規模な単発スクリプトを、通常の TypeScript で明快に書ける場合。たとえば、リトライも依存関係の注入も長期間保持するリソースもなく、1 つのファイルを読み込み、純粋なデータを変換し、1 つのファイルに書き込む場合です。要件が増えたら、I/O に必要な箇所で Effect を採用し、一貫した責務を抽出してください。行数だけを理由に実装を分散させてはいけません。必要な制限は、文書化された対象限定の例外を設けて維持してください。
- 純粋関数、単純なデータマッパー、UI 内のローカル状態、小規模なグルーコードには Effect のラッパーは不要です。
- プロジェクトに Effect の依存関係がなく、その機能で型付きエラー、Layer、リソース安全性、リトライ、可観測性のメリットも得られない場合。
- チームが通常の非同期コードでの戦術的な修正だけを必要としている場合。ついでのリファクタリングとして Effect を導入してはいけません。
- サービス、Layer、エラー、テストの構成を説明できない場合。あちこちに
Effect.runPromiseを追加するのではなく、まず作業を止めて設計してください。
Darkmatter の規約
- Bun のコマンドを使用します:
bun install、bun test、bun run <script>、bunx <tool>。 - 上流の
pnpmの例は機械的に置き換えます。例:プロジェクトで Bun test を使用している場合はpnpm test file.test.tsをbun test file.test.tsに、testがパッケージスクリプトの場合はbun run test file.test.tsに置き換えます。 - 関連する場合は、
@effect/platform-bunや Bun のエントリーポイント向けのBunRuntime.runMainなど、Bun ランタイムのパッケージを優先します。 - デプロイ可能なインフラには Alchemy を優先します。インフラは
alchemy.run.tsに記述し、Alchemy でリソースを作成してワーカー/サービスにバインドし、Alchemy.Stackを構築します。Alchemy コードを記述する場合や新しいインフラコードを追加する場合は、alchemy スキルを参照してください。 - アプリケーションデータには Postgres を優先します。クエリーの多い TypeScript アダプターには Kysely +
pgを、Effect サービスおよび Layers として永続化機能を公開する場合は Effect SQL Postgres を使用します。D1/sqlite は、限定的なエッジローカル状態、デモ、またはプロジェクトで明示的に決定した場合に限る例外です。 - 設定は名前付き設定ファイル(
config/<name>.json)からConfigを通じて読み込みます。セレクター用のフラグはありません。対話型の実行ではリストから選択し(Prompt.select)、非対話型の実行ではAPP_CONFIGを読み取るか、名前の一覧を表示して失敗します。環境変数とフラグはキーごとの上書きとしてConfigProvider.orElseでチェーンします。シークレットはeffect-sopsを使って、隣接する.sops.jsonファイルから取得します。プログラムのセットアップを、フラグや環境変数の一覧として設計しないでください。ADR-0014 を参照してください。
effect-orpc による型付き RPC
サービスに型付き RPC プロシージャー、生成済みまたは共有の oRPC クライアントコントラクト、Effect サービスの注入、型付き Effect エラーが必要な場合は、effect-orpc を使用します。単純な HTTP ルート、Webhook、静的コンテンツ、ストリーミング/SSE、またはクライアント向けの型コントラクトがないのに RPC コントラクトを設けると煩雑になるフレームワークアダプターには、素の Effect HttpRouter または Hono を使用します。
effect-orpc を追加する場合は、カタログで Effect 4 ビルドを固定します。
{
"workspaces": {
"catalog": {
"effect-orpc": "1.0.0-effect-v4.8",
"@orpc/server": "^1.13.0",
"@orpc/client": "^1.13.0",
"@orpc/contract": "^1.13.0",
"@orpc/shared": "^1.13.0"
}
}
}
effect-orpc@latest はインストールしないでください。npm の latest は Effect 3 系列(0.5.0)です。effect-v4 dist-tag が現在指すのは 1.0.0-effect-v4.8 で、その peer 依存関係では上記の oRPC パッケージと Effect 4(effect >=4.0.0-beta.98)が必要です。
以下の上流の形式を優先します。
eos.provide(AppLayer)またはmakeEffectORPC(runtime)からプロシージャーを構築します。Postgres プール、テレメトリー SDK、共有キャッシュなど、スコープ付きレイヤーを一度だけ取得し、アプリケーションのシャットダウン時に破棄する必要がある場合は、呼び出し側が所有するManagedRuntimeを使用します。- ハンドラーと Effect ミドルウェアは
.effect(function* (...) { ... })で、ジェネレーターの.use(function* ({ next }) { return yield* next(...) })で記述し、サービス要件、スパン、キャンセル、エラーが Effect 内に保たれるようにします。 - HTTP エラーは
ORPCTaggedErrorでモデル化し、それらのクラスを.errors(...)/eoc.errors(...)に渡します。同じエラーコントラクトを、無関係なスロー例外とレスポンススキーマの両方で重複して定義しないでください。 - リポジトリがコントラクトファーストで、すでに oRPC コントラクトを所有している場合は、
implementEffect(contract, layerOrRuntime)を使用します。コントラクトのリーフは、宣言された入力、出力、エラーの範囲を保ちながら、.effect(...)ハンドラーを追加します。 - FiberRefs をネイティブ oRPC ミドルウェアやフレームワーク境界を越えて保持するために Node の
AsyncLocalStorageが必要な場合にのみ、effect-orpc/nodeをインポートします。リクエストローカルの FiberRef 状態が oRPC パイプラインの外部で作られ、ハンドラー内でも参照できる必要がある場合は、withFiberContext(() => next())を使用します。
パッケージレイアウトの参照
ドメインの所有権、公開パッケージのエントリーポイント、アダプター、ワークフロー、テスト、ランタイム境界については、effect-package-map.md を参照してください。関数や Effect の各エクスポートに対してファイルを指定するものではなく、責務について説明しています。
上流の参照先
記憶に頼るのではなく、最新のEffectのソースやサンプルが必要な場合は、reference/effectまたはeffect-solutions CLIを使用してください。
reference/effect/AGENTS.md— pnpmの検証コマンド、生成されるバレル、changeset、it.effectの規約など、上流リポジトリのルールが記載されています。reference/effect/packages/effect/— コアライブラリのソースとテストです。reference/effect/packages/platform-bun/およびreference/effect/packages/platform-node/— ランタイム/プラットフォームのサンプルです。reference/effect/packages/vitest/— Effect対応のVitestヘルパーです。
新規チェックアウトでサブモジュールがない場合は、ローカルの参照先を使う前に初期化してください。
git submodule update --init skills/effect-typescript/reference/effect
darkmatterアプリケーションの作業では、サブモジュールを読み取り専用として扱い、上流のpnpmコマンドをリポジトリのパッケージマネージャーに置き換えてください。上流のEffectへの直接のコントリビューションでは、reference/effect/AGENTS.mdに厳密に従ってください。上流リポジトリ内では、darkmatterのBunのデフォルト設定を適用しないでください。
JSONのエンコードとデコード
Schema.fromJsonStringを使用すると、JSON文字列をパースし、1つのステップでスキーマに照らして検証できます。これにより、JSONのパースとスキーマのデコードを1つのステップで行い、逆方向ではJSON.stringifyとスキーマのエンコードを組み合わせられます。
import { Effect, Schema } from "effect"
const Row = Schema.Literals(["A", "B", "C", "D", "E", "F", "G", "H"])
const Column = Schema.Literals(["1", "2", "3", "4", "5", "6", "7", "8"])
class Position extends Schema.Class<Position>("Position")({
row: Row,
column: Column,
}) {}
class Move extends Schema.Class<Move>("Move")({
from: Position,
to: Position,
}) {}
// fromJsonString combines JSON.parse + schema decoding
// MoveFromJson is a schema that takes a JSON string and returns a Move
const MoveFromJson = Schema.fromJsonString(Move)
const program = Effect.gen(function* () {
// Parse and validate JSON string in one step
// Use MoveFromJson (not Move) to decode from JSON string
const jsonString = '{"from":{"row":"A","column":"1"},"to":{"row":"B","column":"2"}}'
const move = yield* Schema.decodeUnknownEffect(MoveFromJson)(jsonString)
yield* Effect.log("Decoded move", move)
// Encode to JSON string in one step (typed as string)
// Use MoveFromJson (not Move) to encode to JSON string
const json = yield* Schema.encodeEffect(MoveFromJson)(move)
return json
})