# How the Effect-TS Dependency Injection System Works in Magnitude

> Understand the Effect-TS DI system in Magnitude. Learn how Context Tag Layer and Effect provide enable modular, testable service composition.

- Repository: [Magnitude/magnitude](https://github.com/magnitudedev/magnitude)
- Tags: internals
- Published: 2026-09-08

---

**The Effect-TS DI system in Magnitude uses Context.Tag as type-safe service identifiers, Layer as immutable providers, and Effect.provide to wire dependencies into the runtime, enabling modular, testable service composition.**

The open-source Magnitude project relies entirely on the Effect-TS library for runtime composition and dependency management. Understanding how its Effect-TS dependency injection system functions is essential for contributing to or extending the codebase. This article explains the architectural patterns found in the magnitudedev/magnitude repository, from declaring service tags to composing complex layer graphs.

## Core Concepts: Tag, Layer, and Effect

Effect-TS dependency injection revolves around three primitives that work together to create a type-safe service container.

**Context.Tag** acts as a unique key that identifies a service interface. It carries the type signature of the service, ensuring that consumers receive the correct implementation at compile time.

**Layer** describes how to construct and provide a service. Layers are immutable values that can be composed, merged, or overridden without side effects. They come in two primary flavors: `Layer.succeed` for immediate values and `Layer.effect` for asynchronous construction.

**Effect.provide** attaches a layer to an effect, making the services available at runtime. Inside the effect, you access the implementation using `Effect.service` or `Effect.serviceWith`.

## Defining Services with Context.Tag

Every public service in Magnitude begins as a subclass of `Context.Tag`. The generic arguments specify both the identifier and the service interface.

In [[`packages/storage/src/storage.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/storage.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/storage.ts#L39), the storage layer declares its service tag:

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

export class MagnitudeStorage extends Context.Tag('MagnitudeStorage')<
  MagnitudeStorage,
  { get(key: string): Effect.Effect<unknown, unknown, unknown> }
>() {}

```

This pattern appears throughout the codebase. For example, the VCS filesystem service is defined similarly in [[`packages/vcs/src/vcs-fs.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/vcs-fs.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/vcs-fs.ts), while the query client is defined in [[`packages/effect-query/src/internal.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/effect-query/src/internal.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/effect-query/src/internal.ts#L44).

## Providing Implementations via Layer

Once a tag exists, you supply the concrete implementation through a **Layer**. Magnitude uses two primary strategies for creating layers.

**Layer.effect** constructs the service asynchronously within an Effect workflow. This allows you to fetch dependencies and perform setup logic. The storage service uses this approach in [[`packages/storage/src/storage.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/storage.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/storage.ts#L44):

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

export const StorageLive = Layer.effect(
  MagnitudeStorage,
  Effect.gen(function* (_) {
    const fs = yield* _(/* lower-level filesystem tag */);
    return {
      get: (key) => Effect.succeed(/* read from fs */),
    };
  })
);

```

**Layer.succeed** immediately provides a pre-constructed value. This is ideal for deterministic implementations with no setup cost. The VCS filesystem uses this pattern in [[`packages/vcs/src/vcs-fs.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/vcs-fs.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/vcs-fs.ts#L11):

```typescript
export const VcsFsLive = Layer.succeed(VcsFs, realFs);

```

## Composing the Dependency Graph

Complex applications require multiple services. Magnitude assembles these using **Layer.mergeAll** and **Layer.provide** to build a unified environment.

The VCS layer factory in [[`packages/vcs/src/layer.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/layer.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/layer.ts#L833) demonstrates this composition. It constructs a shadow VCS layer and injects the filesystem dependency:

```typescript
const vcsLayer = makeShadowVcsLayer({ worktreePath, storagePath })
  .pipe(Layer.provide(VcsFsLive));

const appLayer = Layer.mergeAll(StorageLive, vcsLayer, OtherDeps);

```

`Layer.mergeAll` combines multiple individual layers into a single layer that provides every service simultaneously. `Layer.provide` creates a dependency relationship where one layer's requirements are satisfied by another layer's output.

## Accessing Services in Effects

After providing layers, effects access services using `Effect.service` or `Effect.serviceWith`. This retrieves the concrete implementation from the runtime context.

In [[`packages/client-common/src/state/client-services.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/client-common/src/state/client-services.ts)](https://github.com/magnitudedev/magnitude/blob/main/packages/client-common/src/state/client-services.ts#L39), the client query is accessed via its tag:

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

const fetchData = Effect.gen(function* (_) {
  const query = yield* _(Effect.service(ClientEffectQuery));
  return yield* _(query.run(/* ... */));
});

const result = fetchData.pipe(Effect.provide(appLayer));

```

The `Effect.service` function looks up the implementation associated with the tag in the current context. If the layer providing that service has not been applied, the effect fails to compile or run.

## Testing with Mock Layers

The DI system enables testability by allowing you to swap production implementations with mocks. You achieve this by replacing the production layer with `Layer.succeed` containing a test double.

For example, when testing the VCS module, you can replace the real filesystem with an in-memory version:

```typescript
import { Layer, Effect } from "effect";
import { VcsFs } from "@magnitudedev/vcs";
import { MemoryFileSystem } from "./test-utils";

const testLayer = Layer.succeed(VcsFs, new MemoryFileSystem());

test("VCS operation", async () => {
  const result = await someVcsEffect.pipe(
    Effect.provide(testLayer),
    Effect.runPromise
  );
  // assertions...
});

```

This pattern appears throughout Magnitude's test suites, allowing components to be validated in isolation without external side effects.

## Summary

- **Context.Tag** defines type-safe service identifiers that carry interface contracts.
- **Layer.effect** builds services asynchronously with dependency fetching, while **Layer.succeed** provides immediate values.
- **Layer.mergeAll** and **Layer.provide** compose individual layers into a complete application context.
- **Effect.service** retrieves service implementations inside effect workflows.
- Tests replace production layers with mocks using `Layer.succeed` to eliminate side effects.

## Frequently Asked Questions

### What is Context.Tag in Effect-TS?

Context.Tag is a type-safe identifier that acts as a key for services in the dependency injection container. It carries both the service interface and a unique symbol to prevent key collisions. In Magnitude, every major service like `MagnitudeStorage` or `ClientEffectQuery` is declared as a subclass of Context.Tag.

### How does Layer.succeed differ from Layer.effect?

Layer.succeed creates a layer that immediately provides a pre-constructed service value without any effects, ideal for deterministic implementations like the filesystem in `VcsFsLive`. Layer.effect constructs the service asynchronously within an Effect workflow, allowing you to fetch dependencies via `yield*` and perform setup logic, as seen in the `StorageLive` implementation.

### How do you override dependencies for testing?

You replace the production layer with a mock using `Layer.succeed(Tag, mockImplementation)` and then provide that layer to your test effect via `Effect.provide`. This pattern appears throughout Magnitude's test suites, where `VcsFs` is swapped with an in-memory filesystem to avoid side effects during validation.

### What is the purpose of Layer.mergeAll?

Layer.mergeAll combines multiple individual layers into a single layer that provides all dependencies simultaneously, creating a unified environment for complex effects. In Magnitude, this is used to assemble the complete application context from modular service layers like storage, VCS, and client services before executing the main program.