NYC

effect

SKILL.md

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

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

// 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

// 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

// 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

Weekly Installs
1
Repository
smithery/ai
First Seen
13 days ago
Installed on
amp1
opencode1
kimi-cli1
codex1
github-copilot1
gemini-cli1