Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

effect-best-practices

Effect-TSにおけるサービス、エラー、レイヤー、アトムの実装パターンを強制します。`Effect.Service`、`Schema.TaggedError`、Layerの合成、またはeffect-atomを使ったReactコンポーネントを記述する際に使用してください。

description の原文を見る

Enforces Effect-TS patterns for services, errors, layers, and atoms. Use when writing code with Effect.Service, Schema.TaggedError, Layer composition, or effect-atom React components.

SKILL.md 本文

Effect-TS ベストプラクティス

このスキルは Effect-TS コードベースのための意見的で一貫したパターンを強制します。これらのパターンは型安全性、テスト可能性、可観測性、保守性を最適化します。

Effect Language Server (必須)

Effect Language Server は Effect 開発に不可欠です。 TypeScript だけでは検出できないエラーを編集時に捕捉し、Effect 固有のリファクタリングを提供し、開発者の生産性を向上させます。

セットアップ

  1. インストール:
npm install @effect/language-service --save-dev
  1. tsconfig.json に追加:
{
  "compilerOptions": {
    "plugins": [{ "name": "@effect/language-service" }]
  }
}
  1. エディタを設定してワークスペース TypeScript を使用:
    • VSCode: F1 → "TypeScript: Select TypeScript Version" → "Use Workspace Version"
    • JetBrains: Settings → Languages & Frameworks → TypeScript → Use workspace version

機能

  • 診断: 30 以上の Effect 固有の問題を検出 (フローティング Effect、欠落した要件、不正な yield パターン)
  • クイック情報: ホバーして Effect 型パラメータ (Success、Error、Requirements) を表示
  • 補完: Self、Duration 文字列、Schema ブランドの自動補完
  • リファクタリング: async → Effect.gen の変換、Layer の自動合成、Schema への変換

ビルド時診断

CI での強制:

npx effect-language-service patch

設定オプションと CLI ツールについては references/language-server.md を参照してください。

クイックリファレンス: 重要なルール

カテゴリすることしないこと
サービスaccessors: true 付き Effect.Serviceビジネスロジックに Context.Tag
依存関係サービス内で dependencies: [Dep.Default]使用箇所で手動 Layer.provide
レイヤーフラット合成に Layer.mergeAll深くネストされた Layer.provide チェーン
レイヤーチェーンインクリメンタル合成に Layer.provideMerge複数 Layer.provide (ネストされた型を生成)
エラーmessage フィールド付き Schema.TaggedErrorプレーンクラスまたは汎用 Error
エラー特異性UserNotFoundError, SessionExpiredError汎用 NotFoundError, BadRequestError
エラー処理catchTag/catchTagscatchAll または mapError
IDSchema.UUID.pipe(Schema.brand("@App/EntityId"))エンティティ ID に対する単純 string
関数Effect.fn("Service.method")匿名ジェネレータ
ログ構造化データ付き Effect.logconsole.log
設定バリデーション付き Config.*process.env を直接使用
オプション両方のケースで Option.matchOption.getOrThrow
Null 可能性ドメイン型に Option<T>null/undefined
Atomコンポーネント外で Atom.makeレンダー内にアトムを作成
Atom 状態グローバル状態に Atom.keepAlive永続状態で keepAlive を忘れる
Atom 更新React コンポーネントで useAtomSetReact から Atom.update を命令的に使用
Atom クリーンアップサイドエフェクトに get.addFinalizer()イベントリスナーのクリーンアップを忘れる
Atom 結果onErrorTag 付き Result.builderローディング/エラー状態を無視

サービス定義パターン

常にビジネスロジックサービスに Effect.Service を使用します。 これにより、自動アクセサ、組み込み Default レイヤー、適切な依存関係宣言が提供されます。

import { Effect } from "effect"

export class UserService extends Effect.Service<UserService>()("UserService", {
    accessors: true,
    dependencies: [UserRepo.Default, CacheService.Default],
    effect: Effect.gen(function* () {
        const repo = yield* UserRepo
        const cache = yield* CacheService

        const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
            const cached = yield* cache.get(id)
            if (Option.isSome(cached)) return cached.value

            const user = yield* repo.findById(id)
            yield* cache.set(id, user)
            return user
        })

        const create = Effect.fn("UserService.create")(function* (data: CreateUserInput) {
            const user = yield* repo.create(data)
            yield* Effect.log("User created", { userId: user.id })
            return user
        })

        return { findById, create }
    }),
}) {}

// 使用 - 依存関係は既にワイアリング済み
const program = Effect.gen(function* () {
    const user = yield* UserService.findById(userId)
    return user
})

// アプリルートで
const MainLive = Layer.mergeAll(UserService.Default, OtherService.Default)

Context.Tag が許容される場合:

  • ランタイムインジェクション (Cloudflare KV、ワーカーバインディング) を伴うインフラストラクチャ
  • リソースが外部から提供されるファクトリパターン

詳細なパターンについては references/service-patterns.md を参照してください。

エラー定義パターン

常にエラーに Schema.TaggedError を使用します。 これにより、シリアライズ可能になり (RPC に必須)、一貫した構造が提供されます。

import { Schema } from "effect"
import { HttpApiSchema } from "@effect/platform"

export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    {
        userId: UserId,
        message: Schema.String,
    },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class UserCreateError extends Schema.TaggedError<UserCreateError>()(
    "UserCreateError",
    {
        message: Schema.String,
        cause: Schema.optional(Schema.String),
    },
    HttpApiSchema.annotations({ status: 400 }),
) {}

エラー処理 - catchTag/catchTags を使用:

// 正しい - 型情報を保持
yield* repo.findById(id).pipe(
    Effect.catchTag("DatabaseError", (err) =>
        Effect.fail(new UserNotFoundError({ userId: id, message: "Lookup failed" }))
    ),
    Effect.catchTag("ConnectionError", (err) =>
        Effect.fail(new ServiceUnavailableError({ message: "Database unreachable" }))
    ),
)

// 正しい - 複数のタグを一度に処理
yield* effect.pipe(
    Effect.catchTags({
        DatabaseError: (err) => Effect.fail(new UserNotFoundError({ userId: id, message: err.message })),
        ValidationError: (err) => Effect.fail(new InvalidEmailError({ email: input.email, message: err.message })),
    }),
)

汎用エラーより明示的なものを優先

すべての異なる失敗理由は独自のエラータイプを持つべきです。 複数の失敗モードを汎用 HTTP エラーに崩し込まないでください。

// 間違っている - 汎用エラーで情報が失われる
export class NotFoundError extends Schema.TaggedError<NotFoundError>()(
    "NotFoundError",
    { message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

// その後すべてをマップ:
Effect.catchTags({
    UserNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    ChannelNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    MessageNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
})
// フロントエンドは無駄な情報を得る: { _tag: "NotFoundError", message: "Not found" }
// どのリソース? ユーザー? チャネル? メッセージ? わかりません!
// 正しい - リッチなコンテキストを持つ明示的なドメインエラー
export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    { userId: UserId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class ChannelNotFoundError extends Schema.TaggedError<ChannelNotFoundError>()(
    "ChannelNotFoundError",
    { channelId: ChannelId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class SessionExpiredError extends Schema.TaggedError<SessionExpiredError>()(
    "SessionExpiredError",
    { sessionId: SessionId, expiredAt: Schema.DateTimeUtc, message: Schema.String },
    HttpApiSchema.annotations({ status: 401 }),
) {}

// フロントエンドは特定の UI を表示できます:
// - UserNotFoundError → "ユーザーが存在しません"
// - ChannelNotFoundError → "チャネルが削除されました"
// - SessionExpiredError → "セッションの有効期限が切れています。再度ログインしてください。"

エラーの再マッピングと再試行パターンについては references/error-patterns.md を参照してください。

Schema とブランド型パターン

すべてのエンティティ ID をブランドします サービス境界全体での型安全性のため:

import { Schema } from "effect"

// エンティティ ID - 常にブランド化
export const UserId = Schema.UUID.pipe(Schema.brand("@App/UserId"))
export type UserId = Schema.Schema.Type<typeof UserId>

export const OrganizationId = Schema.UUID.pipe(Schema.brand("@App/OrganizationId"))
export type OrganizationId = Schema.Schema.Type<typeof OrganizationId>

// ドメイン型 - Schema.Struct を使用
export const User = Schema.Struct({
    id: UserId,
    email: Schema.String,
    name: Schema.String,
    organizationId: OrganizationId,
    createdAt: Schema.DateTimeUtc,
})
export type User = Schema.Schema.Type<typeof User>

// ミューテーション用入力型
export const CreateUserInput = Schema.Struct({
    email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
    name: Schema.String.pipe(Schema.minLength(1)),
    organizationId: OrganizationId,
})
export type CreateUserInput = Schema.Schema.Type<typeof CreateUserInput>

ブランド化しない場合:

  • サービス境界を越えない単純な文字列 (URL、ファイルパス)
  • プリミティブ設定値

変換と高度なパターンについては references/schema-patterns.md を参照してください。

Effect.fn を使用した関数パターン

常にサービスメソッドに Effect.fn を使用します。 これにより、適切なスパン名による自動トレースが提供されます:

// 正しい - 説明的な名前を付けた Effect.fn
const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
    yield* Effect.annotateCurrentSpan("userId", id)
    const user = yield* repo.findById(id)
    return user
})

// 正しい - 複数のパラメータを持つ Effect.fn
const transfer = Effect.fn("AccountService.transfer")(
    function* (fromId: AccountId, toId: AccountId, amount: number) {
        yield* Effect.annotateCurrentSpan("fromId", fromId)
        yield* Effect.annotateCurrentSpan("toId", toId)
        yield* Effect.annotateCurrentSpan("amount", amount)
        // ...
    }
)

レイヤーの合成

使用箇所ではなく、サービス内に依存関係を宣言します:

// 正しい - サービス定義内の依存関係
export class OrderService extends Effect.Service<OrderService>()("OrderService", {
    accessors: true,
    dependencies: [
        UserService.Default,
        ProductService.Default,
        PaymentService.Default,
    ],
    effect: Effect.gen(function* () {
        const users = yield* UserService
        const products = yield* ProductService
        const payments = yield* PaymentService
        // ...
    }),
}) {}

// アプリルートで - シンプルなマージ
const AppLive = Layer.mergeAll(
    OrderService.Default,
    // インフラストラクチャレイヤー (意図的に依存関係に含まない)
    DatabaseLive,
    RedisLive,
)

レイヤー合成パターン:

// 同じレベルのレイヤーのフラット合成に Layer.mergeAll を使用
const RepoLive = Layer.mergeAll(
    UserRepo.Default,
    OrderRepo.Default,
    ProductRepo.Default,
)

// インクリメンタルチェーンに Layer.provideMerge を使用 (Layer.provide より平らな型)
const MainLive = DatabaseLive.pipe(
    Layer.provideMerge(ConfigServiceLive),
    Layer.provideMerge(LoggerLive),
    Layer.provideMerge(CacheLive),
)

レイヤーを Effect.provide より優先する理由:

  • 重複排除: レイヤーは構築をメモ化します - 同じサービスは一度だけインスタンス化されます。Effect.provide は呼び出しごとに新しいインスタンスを作成します。
  • TypeScript パフォーマンス: 深い Layer.provide ネストは複雑な再帰型を生成し LSP を遅くします。Layer.mergeAllLayer.provideMerge はより平らな型を生成します。
  • リソース管理: スコープ付きレイヤーはリソースを適切に共有してクリーンアップします。

テストレイヤー、設定依存レイヤー、layerConfig パターンについては references/layer-patterns.md を参照してください。

Option の処理

Option.getOrThrow を決して使用しないでください。 常に両方のケースを明示的に処理:

// 正しい - 明示的な処理
yield* Option.match(maybeUser, {
    onNone: () => Effect.fail(new UserNotFoundError({ userId, message: "Not found" })),
    onSome: (user) => Effect.succeed(user),
})

// 正しい - デフォルト値に getOrElse を使用
const name = Option.getOrElse(maybeName, () => "Anonymous")

// 正しい - 変換に Option.map を使用
const upperName = Option.map(maybeName, (n) => n.toUpperCase())

Effect Atom (フロントエンド状態)

Effect Atom は Effect 統合を備えた React のリアクティブ状態管理を提供します。

基本的な Atom

import { Atom } from "@effect-atom/atom-react"

// アトムはコンポーネント外で定義
const countAtom = Atom.make(0)

// 永続化すべきグローバル状態に keepAlive を使用
const userPrefsAtom = Atom.make({ theme: "dark" }).pipe(Atom.keepAlive)

// エンティティごとの状態に Atom families を使用
const modalAtomFamily = Atom.family((type: string) =>
    Atom.make({ isOpen: false }).pipe(Atom.keepAlive)
)

React 統合

import { useAtomValue, useAtomSet, useAtom, useAtomMount } from "@effect-atom/atom-react"

function Counter() {
    const count = useAtomValue(countAtom)           // 読み取り専用
    const setCount = useAtomSet(countAtom)          // 書き込み専用
    const [value, setValue] = useAtom(countAtom)    // 読み取り + 書き込み

    return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

// 値を読まずにサイドエフェクト atom をマウント
function App() {
    useAtomMount(keyboardShortcutsAtom)
    return <>{children}</>
}

Result.builder を使用した結果の処理

effectful な atom 結果をレンダリングするには Result.builder を使用します。 これは onErrorTag での連鎖可能なエラー処理を提供:

import { Result } from "@effect-atom/atom-react"

function UserProfile() {
    const userResult = useAtomValue(userAtom) // Result<User, Error>

    return Result.builder(userResult)
        .onInitial(() => <div>Loading...</div>)
        .onErrorTag("NotFoundError", () => <div>User not found</div>)
        .onError((error) => <div>Error: {error.message}</div>)
        .onSuccess((user) => <div>Hello, {user.name}</div>)
        .render()
}

サイドエフェクト付き Atom

const scrollYAtom = Atom.make((get) => {
    const onScroll = () => get.setSelf(window.scrollY)

    window.addEventListener("scroll", onScroll)
    get.addFinalizer(() => window.removeEventListener("scroll", onScroll)) // 必須

    return window.scrollY
}).pipe(Atom.keepAlive)

React ミューテーション

ミューテーション atom の場合、useState の代わりに result.waiting からローディング状態を派生させます:

const [result, mutate] = useAtom(deleteMutation, { mode: "promise" })
const isLoading = result.waiting // 自動的に更新、useState/finally 不要

ダイアログの所有権: ミューテーションロジックをダイアログコンポーネントに移動します。ダイアログはミューテーションフック、ローディング状態、トーストを所有します。親はデータプロパティと onSuccess コールバックを提供します。

キャッシュ無効化: ミューテーションとクエリアトムの両方で reactivityKeys を使用してミューテーション後のクエリを自動的に無効化 - 手動 refresh() 呼び出しを置き換えます。

完全なパターンについては references/effect-atom-patterns.md を参照してください (families、localStorage、ミューテーション、アンチパターンを含む)。

RPC とクラスターパターン

RPC コントラクトとクラスターワークフローについては、以下を参照してください:

  • references/rpc-cluster-patterns.md - RpcGroup、Workflow.make、Activity パターン

アンチパターン (禁止)

これらのパターンは 決して許容されません:

// 禁止 - サービス内での runSync/runPromise
const result = Effect.runSync(someEffect) // これを決してしないでください

// 禁止 - Effect.gen 内での throw
yield* Effect.gen(function* () {
    if (bad) throw new Error("No!") // Effect.fail を代わりに使用
})

// 禁止 - 型情報を失う catchAll
yield* effect.pipe(Effect.catchAll(() => Effect.fail(new GenericError())))

// 禁止 - console.log
console.log("debug") // Effect.log を使用

// 禁止 - process.env を直接使用
const key = process.env.API_KEY // Config.string("API_KEY") を使用

// 禁止 - ドメイン型で null/undefined を使用
type User = { name: string | null } // Option<string> を使用

論理を伴う完全なリストについては references/anti-patterns.md を参照してください。

可観測性

// 構造化ログ
yield* Effect.log("Processing order", { orderId, userId, amount })

// メトリクス
const orderCounter = Metric.counter("orders_processed")
yield* Metric.increment(orderCounter)

// バリデーション付き設定
const config = Config.all({
    port: Config.integer("PORT").pipe(Config.withDefault(3000)),
    apiKey: Config.redacted("API_KEY"),
    maxRetries: Config.integer("MAX_RETRIES").pipe(
        Config.validate({ message: "Must be positive", validation: (n) => n > 0 })
    ),
})

メトリクスとトレースパターンについては references/observability-patterns.md を参照してください。

リファレンスファイル

詳細なパターンについては references/ ディレクトリのこれらのリファレンスファイルを参照してください:

  • language-server.md - Effect Language Service セットアップ、診断、リファクタリング、CLI ツール
  • service-patterns.md - サービス定義、Effect.fn、Context.Tag 例外
  • error-patterns.md - Schema.TaggedError、エラー再マッピング、再試行パターン
  • schema-patterns.md - ブランド型、変換、Schema.Class
  • layer-patterns.md - 依存関係合成、テストレイヤー
  • rpc-cluster-patterns.md - RpcGroup、Workflow、Activity パターン
  • effect-atom-patterns.md - Atom、families、React フック、Result 処理
  • anti-patterns.md - 禁止パターンの完全なリスト
  • observability-patterns.md - ログ、メトリクス、設定パターン

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
makisuo
リポジトリ
makisuo/skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/makisuo/skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: makisuo · makisuo/skills · ライセンス: MIT