mcpField
E
effect-typescript

effect-typescript

github.com/darkmatter開発0295 回インストール1 日前に更新
リポジトリ

effect-typescript について

TypeScript の I/O とリソース管理に Effect を適用するためのコーディングスキルです。darkmatter 向けの規約に沿って、エラー、依存関係、クリーンアップ、テスト、実行境界を明示する方法を示します。

主な機能

  1. 01Effect による I/O の指針
  2. 02型付きエラーとサービスレイヤー
  3. 03リソースのライフサイクルとクリーンアップ
  4. 04darkmatter 向け Bun・Alchemy の規約
  5. 05effect-orpc の利用指針

活用シーン

  1. 01TypeScript の I/O コードへの Effect 導入
  2. 02既存の Effect 実装のレビュー
  3. 03effect-orpc を使った型付き RPC ハンドラーの設計
  4. 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-solutions CLI が推奨するベストプラクティスを参照してください。
  • より具体的な情報が必要な場合は、参照ディレクトリに 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
})

コメント