AgentSkillsCN

effect

针对服务、错误处理、分层架构、数据模型及测试,提供Effect-TS的最佳实践指南。适用于编写或审查Effect代码、实现各类服务、妥善处理错误,以及进行多层架构的组合与调用。

SKILL.md
--- frontmatter
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

PatternDON'TDO
Service definitionContext.TagEffect.Service with dependencies array
Error typesGeneric ErrorSchema.TaggedError with context fields
Branded IDsRaw stringSchema.String.pipe(Schema.brand("@Ns/Entity"))
Running effectsrunSync/runPromise in servicesReturn Effect, run at edge
Loggingconsole.logEffect.log with structured data
Configurationprocess.envConfig with validation
Method tracingManual spansEffect.fn("Service.method")
Nullable resultsnull/undefinedOption types
StateMutable variablesRef
TimeDate.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