| name | effect-ts-practices |
| description | Applies Effect-first TypeScript architecture for errors, asynchronous workflows, runtime schemas, services, Layers, logging, tests, and Effect Atom. Use when writing or reviewing code in any project that uses Effect, Effect-TS, @effect packages, Effect Schema, or Effect Atom, unless a repository-specific Effect skill provides stricter guidance. |
Effect-TS Practices
Read the repository's Effect guidance and inspect the installed effect and companion-package versions before choosing an API. Project-specific instructions override this portable skill. Do not copy examples across Effect major versions or prerelease lines without checking the matching API and local patterns.
Workflows and errors
- Express application and service workflows as Effect values. Adapt Promise APIs once with
Effect.tryPromise; keep raw async/await only at framework entrypoints that must return a Promise and run an already-built Effect there.
- Do not use JavaScript
try/catch or throw inside an Effect workflow. Wrap throwing boundaries with Effect.try or Effect.tryPromise.
- Model expected failures with specific
Schema.TaggedErrorClass types. Preserve meaningful error distinctions and recover with catchTag or catchTags; do not erase a useful error union with a generic catch.
- Do not run Effects inside services with nested
runSync or runPromise. Keep composition in Effect and run once at the owning boundary.
Schema and types
- Use Effect Schema for new runtime validation, transported data, persistence contracts, and domain models. Do not add Zod in an Effect project.
- When changing an existing Zod boundary, migrate it only when the migration stays focused and preserves the contract.
- Never use
any. Keep unknown at untrusted edges, decode it once, and carry the decoded type. Do not use casts or generic record guards as substitutes for decoding.
- Derive TypeScript types from schemas rather than maintaining parallel interfaces.
Services, Layers, and tests
- Make dependencies visible in Effect's environment and provide implementations with Layers. Keep infrastructure ownership at application or worker boundaries instead of repeatedly providing live dependencies at callsites.
- Follow the service primitive established by the installed Effect version and surrounding project. Inspect current source before using generated accessors, default Layers, dependency fields, or composition helpers.
- Wrap meaningful service operations with named
Effect.fn values so traces describe application work.
- Replace dependencies with test Layers rather than broad module mocks. Tests must not import or invoke live third-party providers unless they are explicitly external integration tests.
- Keep third-party service interfaces, live SDK/configuration Layers, and test Layers isolated so ordinary imports and tests cannot pull in real credentials or networks.
Logging and state
- Use Effect logging instead of
console.* for Effect application code. Keep messages event-specific and attach structured fields, spans, annotations, and causes.
- Do not mirror state already owned by Effect Atom, TanStack Query, a route loader, or another store.
- Avoid
useEffect for derived state, data fetching, or async orchestration. Prefer Effect Atom, TanStack Query, route loaders, render-time derivation, and explicit event handlers. Use an effect only for unavoidable synchronization with an external system and include cleanup.
Ecosystem packages
Effect ecosystem packages evolve independently. Before changing Atom, Platform, HTTP, RPC, Workflow, SQL, OpenTelemetry, AI, or another companion package:
- Identify the installed version.
- Inspect established local usage.
- Read documentation and upstream source or changelogs matching that version when behavior is unclear.
- Prefer an existing library or project abstraction over a custom replacement.
Finish
Before finishing, inspect the changed code for new Zod schemas, any, unchecked casts, try/catch, raw Promise workflows, generic error erasure, nested Effect runners, console.*, broad module mocks, and duplicated state. Judge occurrences at their boundary; do not expand a focused task into an unrelated migration.