home / skills / michaelvessia / nixos-config / effect

This skill helps you write and review Effect-TS code by applying domain-focused patterns for services, errors, layers, and testing.

npx playbooks add skill michaelvessia/nixos-config --skill effect

Review the files below or copy the command above to add this skill to your agents.

Files (11)
SKILL.md
4.0 KB
---
name: effect
description: Effect-TS best practices for services, errors, layers, schemas, and testing. Use when writing/reviewing Effect code, implementing services, handling errors, or composing layers.
---

# Effect-TS Best Practices

Opinionated patterns for Effect-TS codebases. Effect provides typed functional programming with composable errors, dependency injection, and observability.

## Critical Rules

1. **NEVER use `any` or type casts (`as Type`)** - Use `Schema.make()` for branded types, `Schema.decodeUnknown()` for parsing
2. **Don't use `catchAll` when error type is `never`** - No errors to catch
3. **Never use global `Error` in Effect channels** - Use `Schema.TaggedError` for domain errors
4. **Ban `{ disableValidation: true }`** - Lint against this
5. **Don't wrap safe operations in Effect** - Only use `Effect.try()` for throwing operations
6. **Use `mapError` not `catchAllCause`** - Distinguish expected errors from bugs
7. **Never silently swallow errors** - Failures MUST be visible in the Effect's error channel E

## Quick Reference

| Pattern | DON'T | DO |
|---------|-------|-----|
| Service definition | `Context.Tag` | `Effect.Service` with `dependencies` array |
| Error types | Generic `Error` | `Schema.TaggedError` with context fields |
| Branded IDs | Raw `string` | `Schema.String.pipe(Schema.brand("@Ns/Entity"))` |
| Running effects | `runSync`/`runPromise` in services | Return `Effect`, run at edge |
| Logging | `console.log` | `Effect.log` with structured data |
| Configuration | `process.env` | `Config` with validation |
| Method tracing | Manual spans | `Effect.fn("Service.method")` |
| Nullable results | `null`/`undefined` | `Option` types |
| State | Mutable variables | `Ref` |
| Time | `Date.now()`, `new Date()` | `Clock` service |

## Service Pattern

```typescript
class UserService extends Effect.Service<UserService>()("UserService", {
  dependencies: [DatabaseService.Default],
  effect: Effect.gen(function* () {
    const db = yield* DatabaseService

    return {
      findById: Effect.fn("UserService.findById")(
        (id: UserId) => db.query(/* ... */)
      ),
    }
  }),
}) {}

// Usage - dependencies auto-provided
UserService.findById(userId)
```

## Error Handling

```typescript
// Define domain-specific errors
class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
  "UserNotFoundError",
  { userId: UserId, message: Schema.String }
) {}

// Handle with catchTag (preserves type info)
effect.pipe(
  Effect.catchTag("UserNotFoundError", (e) => /* handle */),
  Effect.catchTag("AuthExpiredError", (e) => /* handle */)
)
```

## Schema Pattern

```typescript
// Branded ID
const UserId = Schema.String.pipe(Schema.brand("@App/UserId"))

// Domain entity with Schema.Class
class User extends Schema.Class<User>("User")({
  id: UserId,
  email: Schema.String,
  createdAt: Schema.DateFromSelf,
}) {
  get displayName() { return this.email.split("@")[0] }
}
```

## Layer Composition

```typescript
// Declare dependencies in service, not at usage
const MainLayer = Layer.mergeAll(
  UserServiceLive,
  AuthServiceLive,
  DatabaseLive
)

// Run program
Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
```

## Detailed Guides

- [Anti-Patterns](./references/anti-patterns.md) - Forbidden patterns with fixes
- [Error Patterns](./references/error-patterns.md) - Domain errors, rich context, HTTP mapping
- [Schema Patterns](./references/schema-patterns.md) - Branded types, transforms, validation
- [Service Patterns](./references/service-patterns.md) - Effect.Service, dependency injection
- [Layer Patterns](./references/layer-patterns.md) - Composition, memoization, testing
- [Observability Patterns](./references/observability-patterns.md) - Logging, tracing, metrics
- [SQL Patterns](./references/sql-patterns.md) - Database integration, transactions
- [Testing Patterns](./references/testing-patterns.md) - effect-vitest, property testing, Testcontainers
- [Atom Patterns](./references/atom-patterns.md) - React state management with Effect
- [RPC & Cluster Patterns](./references/rpc-cluster-patterns.md) - Distributed systems

Overview

This skill codifies opinionated best practices for writing and reviewing Effect-TS code focused on services, errors, layers, schemas, and testing. It surfaces concrete patterns and anti-patterns to keep code safe, typed, and composable. Use it to standardize architecture, reduce runtime surprises, and improve observability in Effect-based projects.

How this skill works

The skill inspects Effect code and recommends fixes aligned to a strict rule set: no any/type-casts, domain-tagged errors, schema-driven validation, service patterns using Effect.Service, and explicit layer composition. It highlights forbidden patterns (global Error, disableValidation, unchecked effects) and suggests idiomatic replacements (Schema.TaggedError, Schema.decodeUnknown, Ref, Clock, Effect.fn, Layer.mergeAll). It also provides guidance for error handling, logging, and testing.

When to use it

  • During code reviews to enforce Effect-TS best practices
  • When implementing new services, dependencies, or layers
  • When defining domain errors and schema validations
  • While composing application layers and startup logic
  • When writing tests that depend on layers, effects, or DB interactions

Best practices

  • Never use any or as Type; prefer Schema.make and Schema.decodeUnknown for parsing and branded IDs
  • Model domain failures with Schema.TaggedError instead of generic Error and avoid swallowing errors
  • Define services with Effect.Service and declare dependencies in the service definition, not at call sites
  • Return Effect from services; run effects only at the application edge using provided layers
  • Use Effect.fn for method tracing, Effect.log for structured logging, and Clock/Ref for time and mutable state
  • Use mapError to handle expected errors and avoid catchAll with error type never

Example use cases

  • Refactor a service using Context.Tag to Effect.Service with explicit dependencies for testability
  • Convert untyped IDs and runtime casts to branded Schema.String IDs with Schema.brand
  • Replace console.log statements with Effect.log and add structured fields for observability
  • Compose database, auth, and user services into a single MainLayer and provide it at program start
  • Write tests with effect-vitest and Testcontainers using test-specific Layers and Ref mocks

FAQ

Why avoid any and type casts?

They bypass compile-time safety. Use Schema.make/Schema.decodeUnknown and branded schemas to preserve strong typing and safe parsing.

When should I run effects vs return them?

Return Effect from libraries and services. Execute effects only at the application edge with properly composed Layers to keep code pure and testable.