Claude
Skills
Sign in
Back

effect-dependency-injection

Included with Lifetime
$97 forever

Use when Effect dependency injection patterns including Context, Layer, service definitions, and dependency composition. Use for managing dependencies in Effect applications.

General

What this skill does


# Effect Dependency Injection

Master dependency injection and management in Effect applications using Context
and Layers. This skill covers service definitions, layer construction, and
composing complex dependency graphs.

## Context and Services

### Defining Services with Context.Tag

Services are defined using Context.Tag to create type-safe identifiers:

```typescript
import { Context, Effect } from "effect"

// Define service interface
interface UserService {
  getUser: (id: string) => Effect.Effect<User, UserNotFound, never>
  createUser: (data: UserData) => Effect.Effect<User, ValidationError, never>
}

// Create service tag
const UserService = Context.GenericTag<UserService>("UserService")

// Using the service
const program = Effect.gen(function* () {
  const userService = yield* UserService
  const user = yield* userService.getUser("123")
  return user
})
// Effect<User, UserNotFound, UserService>
```

### Multiple Services

```typescript
import { Context, Effect } from "effect"

interface Logger {
  info: (message: string) => Effect.Effect<void, never, never>
  error: (message: string) => Effect.Effect<void, never, never>
}

interface Database {
  query: <T>(sql: string) => Effect.Effect<T, DbError, never>
}

const Logger = Context.GenericTag<Logger>("Logger")
const Database = Context.GenericTag<Database>("Database")

// Using multiple services
const program = Effect.gen(function* () {
  const logger = yield* Logger
  const db = yield* Database

  yield* logger.info("Querying database...")
  const users = yield* db.query<User[]>("SELECT * FROM users")
  yield* logger.info(`Found ${users.length} users`)

  return users
})
// Effect<User[], DbError, Logger | Database>
```

## Creating Layers

Layers are blueprints for constructing services.

### Layer.succeed - Simple Service Implementation

```typescript
import { Context, Effect, Layer } from "effect"

interface Config {
  apiUrl: string
  timeout: number
}

const Config = Context.GenericTag<Config>("Config")

// Create a layer with a fixed value
const ConfigLive = Layer.succeed(
  Config,
  {
    apiUrl: "https://api.example.com",
    timeout: 5000
  }
)
```

### Layer.effect - Service with Dependencies

Create a service that depends on other services:

```typescript
import { Context, Effect, Layer } from "effect"

interface HttpClient {
  get: (url: string) => Effect.Effect<Response, NetworkError, never>
  post: (url: string, body: unknown) => Effect.Effect<Response, NetworkError, never>
}

const HttpClient = Context.GenericTag<HttpClient>("HttpClient")

// HttpClient depends on Config and Logger
const HttpClientLive = Layer.effect(
  HttpClient,
  Effect.gen(function* () {
    const config = yield* Config
    const logger = yield* Logger

    return {
      get: (url: string) =>
        Effect.gen(function* () {
          yield* logger.info(`GET ${url}`)
          const response = yield* Effect.tryPromise({
            try: () => fetch(`${config.apiUrl}${url}`, {
              timeout: config.timeout
            }),
            catch: (error) => ({
              _tag: "NetworkError",
              message: String(error)
            })
          })
          return response
        }),
      post: (url: string, body: unknown) =>
        Effect.gen(function* () {
          yield* logger.info(`POST ${url}`)
          const response = yield* Effect.tryPromise({
            try: () => fetch(`${config.apiUrl}${url}`, {
              method: "POST",
              body: JSON.stringify(body),
              timeout: config.timeout
            }),
            catch: (error) => ({
              _tag: "NetworkError",
              message: String(error)
            })
          })
          return response
        })
    }
  })
)
// Layer<HttpClient, never, Config | Logger>
```

### Layer.scoped - Resources with Cleanup

For services that need cleanup:

```typescript
import { Context, Effect, Layer } from "effect"

interface DatabaseConnection {
  query: <T>(sql: string) => Effect.Effect<T, DbError, never>
}

const DatabaseConnection = Context.GenericTag<DatabaseConnection>("DatabaseConnection")

const DatabaseConnectionLive = Layer.scoped(
  DatabaseConnection,
  Effect.gen(function* () {
    const config = yield* Config

    // Acquire connection
    const connection = yield* Effect.tryPromise({
      try: () => createConnection(config.dbUrl),
      catch: (error) => ({
        _tag: "ConnectionError",
        message: String(error)
      })
    })

    // Register cleanup
    yield* Effect.addFinalizer(() =>
      Effect.sync(() => {
        console.log("Closing database connection")
        connection.close()
      })
    )

    return {
      query: <T>(sql: string) =>
        Effect.tryPromise({
          try: () => connection.query<T>(sql),
          catch: (error) => ({
            _tag: "DbError",
            message: String(error)
          })
        })
    }
  })
)
```

## Providing Layers

### Effect.provide - Provide Single Layer

```typescript
import { Effect, Layer } from "effect"

const program = Effect.gen(function* () {
  const config = yield* Config
  return config.apiUrl
})
// Effect<string, never, Config>

// Provide the Config layer
const runnable = program.pipe(
  Effect.provide(ConfigLive)
)
// Effect<string, never, never>

// Now can run without dependencies
const result = await Effect.runPromise(runnable)
```

### Effect.provideService - Provide Service Directly

For testing or simple cases:

```typescript
import { Effect } from "effect"

const testConfig: Config = {
  apiUrl: "http://localhost:3000",
  timeout: 1000
}

const program = Effect.gen(function* () {
  const config = yield* Config
  return config.apiUrl
})

const runnable = program.pipe(
  Effect.provideService(Config, testConfig)
)
```

## Composing Layers

### Layer.provide - Layer Dependencies

Provide dependencies to a layer:

```typescript
import { Layer } from "effect"

// UserServiceLive needs HttpClient
// HttpClient needs Config and Logger

const UserServiceLive = Layer.effect(
  UserService,
  Effect.gen(function* () {
    const http = yield* HttpClient

    return {
      getUser: (id: string) =>
        Effect.gen(function* () {
          const response = yield* http.get(`/users/${id}`)
          const user = yield* Effect.tryPromise({
            try: () => response.json(),
            catch: () => ({ _tag: "ParseError" })
          })
          return user
        })
    }
  })
)

// Provide HttpClient to UserService
const UserServiceWithDeps = UserServiceLive.pipe(
  Layer.provide(HttpClientLive)
)
// Layer<UserService, never, Config | Logger>
```

### Layer.merge - Combine Layers

Merge multiple independent layers:

```typescript
import { Layer } from "effect"

// Combine Config and Logger
const AppConfigLayer = Layer.merge(
  ConfigLive,
  LoggerLive
)
// Layer<Config | Logger, never, never>

// Use merged layer
const program = Effect.gen(function* () {
  const config = yield* Config
  const logger = yield* Logger
  yield* logger.info(`API URL: ${config.apiUrl}`)
})

const runnable = program.pipe(
  Effect.provide(AppConfigLayer)
)
```

### Layer Pipelines

Build complex dependency graphs:

```typescript
import { Layer, Effect } from "effect"

// Build dependency graph
const AppLayer = Layer.merge(
  ConfigLive,
  LoggerLive
).pipe(
  Layer.provideMerge(HttpClientLive),
  Layer.provideMerge(DatabaseConnectionLive),
  Layer.provideMerge(UserServiceLive)
)

// All services now available
const program = Effect.gen(function* () {
  const userService = yield* UserService
  const logger = yield* Logger

  yield* logger.info("Fetching user...")
  const user = yield* userService.getUser("123")
  yield* logger.info(`User: ${user.name}`)

  return user
})

const runnable = program.pipe(
  Effect.provide(AppLayer)
)
```

## Service Patterns

### Repository Pattern

```typescript
import { Context, Effect, Layer } from "effect"

interface UserRepository {
  findById: (id: string) => Effect.Effect<Option<U

Related in General