# ManagedRuntime

Not every entry point is an Effect. Web frameworks, message-queue consumers, and
legacy callback APIs call *you* with plain functions, and they expect promises
or synchronous returns — not effects. `ManagedRuntime` is the bridge: you build
one runtime from your application's `Layer`, and it lets you run effects from
imperative code while keeping all your domain logic in services and layers.

```ts
// file: src/server.ts
import { Context, Effect, Layer, ManagedRuntime, Ref, Schema } from "effect"
import { Hono } from "hono"

class Todo extends Schema.Class<Todo>("Todo")({
  id: Schema.Number,
  title: Schema.String,
  completed: Schema.Boolean
}) {}

class CreateTodoPayload extends Schema.Class<CreateTodoPayload>("CreateTodoPayload")({
  title: Schema.String
}) {}

class TodoNotFound extends Schema.TaggedErrorClass<TodoNotFound>()("TodoNotFound", {
  id: Schema.Number
}) {}

// Ordinary Effect service — no awareness of Hono or HTTP at all.
export class TodoRepo extends Context.Service<TodoRepo, {
  readonly getAll: Effect.Effect<ReadonlyArray<Todo>>
  getById(id: number): Effect.Effect<Todo, TodoNotFound>
  create(payload: CreateTodoPayload): Effect.Effect<Todo>
}>()("myapp/TodoRepo") {
  static readonly layer = Layer.effect(
    TodoRepo,
    Effect.gen(function*() {
      const store = new Map<number, Todo>()
      const nextId = yield* Ref.make(1)

      const getAll = Effect.sync(() => Array.from(store.values()))

      const getById = Effect.fn("TodoRepo.getById")(function*(id: number) {
        const todo = store.get(id)
        if (todo === undefined) return yield* new TodoNotFound({ id })
        return todo
      })

      const create = Effect.fn("TodoRepo.create")(function*(payload: CreateTodoPayload) {
        const id = yield* Ref.getAndUpdate(nextId, (n) => n + 1)
        const todo = new Todo({ id, title: payload.title, completed: false })
        store.set(id, todo)
        return todo
      })

      return TodoRepo.of({ getAll, getById, create })
    })
  )
}

// Build ONE runtime from the application layer, shared by every handler. It owns
// the lifecycle of TodoRepo and anything it depends on.
const runtime = ManagedRuntime.make(TodoRepo.layer)

const app = new Hono()

app.get("/todos", async (c) => {
  // `runPromise` runs an effect and returns a Promise — exactly what Hono wants.
  // `TodoRepo.use` reaches the service without an Effect.gen wrapper.
  const todos = await runtime.runPromise(TodoRepo.use((repo) => repo.getAll))
  return c.json(todos)
})

app.get("/todos/:id", async (c) => {
  const id = Number(c.req.param("id"))
  if (!Number.isFinite(id)) return c.json({ message: "id must be a number" }, 400)

  // Handle the typed error at the boundary, mapping it to an HTTP status.
  const todo = await runtime.runPromise(
    TodoRepo.use((repo) => repo.getById(id)).pipe(
      Effect.catchTag("TodoNotFound", () => Effect.succeed(null))
    )
  )

  return todo === null ? c.json({ message: "not found" }, 404) : c.json(todo)
})

const decode = Schema.decodeUnknownEffect(CreateTodoPayload)

app.post("/todos", async (c) => {
  const body = await c.req.json()
  // Decode at the edge; on success continue into the repo, all in one effect.
  const result = await runtime.runPromiseExit(
    Effect.gen(function*() {
      const payload = yield* decode(body)
      return yield* TodoRepo.use((repo) => repo.create(payload))
    })
  )
  return result._tag === "Success"
    ? c.json(result.value, 201)
    : c.json({ message: "invalid request body" }, 400)
})

// Dispose the runtime on shutdown so layer resources are released.
const shutdown = () => { void runtime.dispose() }
process.once("SIGINT", shutdown)
process.once("SIGTERM", shutdown)

export { app, runtime }
```

## How it works

`ManagedRuntime.make(layer)` builds the layer lazily and hands you an object with
imperative *run* methods. Each one provides the layer's services to the effect
you pass, so your handlers never deal with requirements directly:

- **`runPromise(effect)`** — run and resolve to a `Promise<A>`; rejects on
  failure. The workhorse for async handlers.
- **`runPromiseExit(effect)`** — resolve to an `Exit`, so success and failure are
  both values you can branch on (used above to turn a decode failure into a 400).
- **`runSync(effect)`** — run a *synchronous* effect and return its value; only
  works when the effect has no async boundaries.
- **`runFork(effect)`** — start the effect as a background `Fiber`.
- **`runCallback(effect, { onExit })`** — for callback-style APIs.
- **`dispose()` / `disposeEffect`** — finalize the layer and release every
  resource it acquired.
**Caution:** Always `dispose` a managed runtime when it's no longer needed. Skipping disposal
leaks whatever the layer acquired — connection pools, file handles, background
fibers. Wire it into your process's shutdown signals as shown above.

## One runtime, shared across handlers

Build the runtime **once** at module scope and reuse it for every request.
Creating a runtime per request would rebuild your entire layer graph each time —
reopening connection pools, re-running setup. A single shared runtime builds the
graph once and amortizes it across all traffic.

If your app runs several runtimes that should share memoized layers (for
example, separate runtimes per worker that all use the same database layer), pass
a shared memo map so a layer included in both is built only once:

```ts
import { Layer, ManagedRuntime } from "effect"
import { TodoRepo } from "./server.ts"

// A process-wide memo map shared across runtimes.
const memoMap = Layer.makeMemoMapUnsafe()

const runtimeA = ManagedRuntime.make(TodoRepo.layer, { memoMap })
const runtimeB = ManagedRuntime.make(TodoRepo.layer, { memoMap })
```

The same bridge pattern works for Express, Fastify, Koa, worker queues, and any
other framework — only the surrounding glue changes. For a fully Effect-native
HTTP server (no `ManagedRuntime` glue), see [HTTP API](https://effect.plants.sh/http-api/). For more on
running effects and runtime configuration, see [running effects](https://effect.plants.sh/runtime/running-effects/).

## Lifecycle

A `ManagedRuntime<R, ER>` has a deliberately simple lifecycle:

1. **Created lazily.** `ManagedRuntime.make` returns immediately without building
   anything. The layer is built the **first time** any `run*`, `context`, or
   `contextEffect` is used.
2. **Context cached.** Once built, the resulting `Context<R>` is cached on the
   runtime and reused for every subsequent run — no rebuild, no re-acquisition.
3. **Resources owned.** Anything the layer acquires (connections, servers, files,
   background fibers) lives in a scope owned by the runtime.
4. **Disposed once.** `dispose()` / `disposeEffect` closes that scope, running
   every finalizer. **After disposal the runtime cannot be reused** — its
   `contextEffect` is replaced with a defect, so further runs die with
   `"ManagedRuntime disposed"`.

```ts
import { Context, Effect, Layer, ManagedRuntime } from "effect"

class Db extends Context.Service<Db, {
  readonly query: (sql: string) => Effect.Effect<ReadonlyArray<unknown>>
}>()("Db") {
  // `Layer.effect` with an acquire/release finalizer simulates a connection pool.
  static readonly layer = Layer.effect(
    Db,
    Effect.acquireRelease(
      Effect.sync(() => {
        console.log("open pool")
        return Db.of({ query: () => Effect.succeed([]) })
      }),
      () => Effect.sync(() => console.log("close pool"))
    )
  )
}

const runtime = ManagedRuntime.make(Db.layer)

await runtime.runPromise(Db.use((db) => db.query("SELECT 1")))
// => "open pool"   (built lazily on first run)
await runtime.runPromise(Db.use((db) => db.query("SELECT 2")))
// => (no rebuild — context is cached)

await runtime.dispose()
// => "close pool"  (finalizers run)

// Reusing a disposed runtime is a defect, not a recoverable error.
await runtime.runPromise(Db.use((db) => db.query("SELECT 3")))
// => rejects: "ManagedRuntime disposed"
```

### Sharing a MemoMap across runtimes

The `memoMap` option lets multiple runtimes share the memoization that `Layer`
construction uses internally. A layer that appears in more than one runtime is
built **once** and its context shared — useful when each runtime is a thin
wrapper around the same expensive base layer.

```ts
import { Effect, Layer, ManagedRuntime } from "effect"

let built = 0
const Shared = Layer.effectDiscard(Effect.sync(() => { built++ }))

const memoMap = Layer.makeMemoMapUnsafe()
const a = ManagedRuntime.make(Shared, { memoMap })
const b = ManagedRuntime.make(Shared, { memoMap })

await a.runPromise(Effect.void)
await b.runPromise(Effect.void)
console.log(built)
// => 1   (Shared built once, reused by both runtimes)
```

Without a shared `memoMap`, each `make` call uses its own — created via
`Layer.makeMemoMapUnsafe()` — so the shared layer would be built twice. You can
also build standalone layers against the runtime's own map by reading
`runtime.memoMap` (see below). For the full memoization story see
[Layer memoization](https://effect.plants.sh/services-and-layers/layer-composition/#memoization) and the `Layer.MemoMap` /
`Layer.makeMemoMap` / `Layer.forkMemoMap` APIs.

## Integration examples

### Express + graceful shutdown

The runtime is built once at module scope and disposed on shutdown signals.
Domain logic stays in services; the handler is a thin `runPromise` boundary.

```ts
import { Context, Effect, Layer, ManagedRuntime, Schema } from "effect"
import express from "express"

class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()("UserNotFound", {
  id: Schema.Number
}) {}

class Users extends Context.Service<Users, {
  find(id: number): Effect.Effect<{ id: number; name: string }, UserNotFound>
}>()("Users") {
  static readonly layer = Layer.succeed(Users)({
    find: Effect.fn("Users.find")(function*(id: number) {
      if (id !== 1) return yield* new UserNotFound({ id })
      return { id, name: "Ada" }
    })
  })
}

const runtime = ManagedRuntime.make(Users.layer)
const app = express()

app.get("/users/:id", async (req, res) => {
  const id = Number(req.params.id)
  const exit = await runtime.runPromiseExit(Users.use((u) => u.find(id)))
  if (exit._tag === "Success") {
    res.json(exit.value)
  } else {
    res.status(404).json({ message: "not found" })
  }
})

const server = app.listen(3000)
const shutdown = () => server.close(() => void runtime.dispose())
process.once("SIGINT", shutdown)
process.once("SIGTERM", shutdown)
```

### Tests (Vitest)

In a test suite, build a runtime in `beforeAll` from a test layer and dispose it
in `afterAll`. Each test runs effects with `runPromise`, getting your real
services with test implementations swapped in.

```ts
import { Context, Effect, Layer, ManagedRuntime } from "effect"
import { afterAll, beforeAll, expect, test } from "vitest"

class Clock extends Context.Service<Clock, { readonly now: Effect.Effect<number> }>()("Clock") {
  static readonly test = Layer.succeed(Clock)({ now: Effect.succeed(0) })
}

let runtime: ManagedRuntime.ManagedRuntime<Clock, never>

beforeAll(() => {
  runtime = ManagedRuntime.make(Clock.test)
})

afterAll(() => runtime.dispose())

test("clock reads zero", async () => {
  const now = await runtime.runPromise(Clock.use((c) => c.now))
  expect(now).toBe(0)
  // => 0
})
```
**Tip:** If your **entire program** is an Effect (for example a CLI or server whose top
level is itself effectful), you usually don't need `ManagedRuntime` at all —
prefer a platform [`runMain`](https://effect.plants.sh/runtime/run-main/) (such as `NodeRuntime.runMain`),
which builds the layer, runs your program, and handles disposal plus signal
interruption for you. Reach for
`ManagedRuntime` specifically when a **non-Effect** framework owns the entry
points and calls into your effects repeatedly.

## Reference

`ManagedRuntime.make(layer, options?)` is the only constructor. Every other entry
point is a method or property of the returned `ManagedRuntime<R, ER>`. The two
type parameters are the layer's provided services `R` and the layer's
**construction error** `ER` — `ER` is merged into the error channel of every
runner, because building the layer can fail.

### `ManagedRuntime.make`

Creates a runtime from a `Layer<R, ER, never>` (the layer must have no remaining
requirements). The optional `{ memoMap }` lets the runtime share layer
memoization with other builds.

```ts
import { Layer, ManagedRuntime } from "effect"

// Signature:
// make<R, ER>(
//   layer: Layer.Layer<R, ER, never>,
//   options?: { readonly memoMap?: Layer.MemoMap }
// ): ManagedRuntime<R, ER>

declare const layer: Layer.Layer<"MyService", "BuildError", never>
const runtime = ManagedRuntime.make(layer)
// => ManagedRuntime<"MyService", "BuildError">
```

### `.runPromise`

Runs an effect and returns a `Promise<A>` that resolves with the success value or
**rejects** with the first failure/defect. The default runner for `async`
handlers. Accepts `Effect.RunOptions` (e.g. `{ signal }` for cancellation).

```ts
import { Effect, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

await runtime.runPromise(Effect.succeed(42))
// => 42

await runtime.runPromise(Effect.fail("boom")).catch((e) => e)
// => "boom" (Promise rejects)
```

### `.runPromiseExit`

Like `runPromise`, but the `Promise` always **resolves** with an `Exit<A, E | ER>`
— never rejects. Branch on `Exit.isSuccess` / `_tag` to handle failure as a value.

```ts
import { Effect, Exit, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

const exit = await runtime.runPromiseExit(Effect.fail("boom"))
Exit.isFailure(exit)
// => true
exit._tag
// => "Failure"
```

### `.runSync`

Runs a **synchronous** effect and returns its value directly. **Throws** if the
effect fails or hits an asynchronous boundary — use `runPromise` for async work.

```ts
import { Effect, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

runtime.runSync(Effect.sync(() => 1 + 1))
// => 2
```

### `.runSyncExit`

The non-throwing variant of `runSync`: returns an `Exit<A, E | ER>` instead of
throwing. A failure to run synchronously (an async boundary) shows up as a
failure in the `Exit`.

```ts
import { Effect, Exit, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

const exit = runtime.runSyncExit(Effect.succeed("ok"))
Exit.isSuccess(exit)
// => true
```

### `.runFork`

Starts the effect on a background `Fiber<A, E | ER>` and returns it immediately,
without waiting. Fibers started this way are attached to the runtime's scope, so
they are interrupted when the runtime is disposed. Accepts `Effect.RunOptions`.

```ts
import { Effect, Fiber, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

const fiber = runtime.runFork(Effect.succeed("done"))
const result = await runtime.runPromise(Fiber.await(fiber))
// => Exit.succeed("done")
```

### `.runCallback`

Runs the effect asynchronously and invokes `onExit` with the final
`Exit<A, E | ER>`. Returns a function you can call to **interrupt** the running
fiber. Use it to bridge Node-style callback APIs.

```ts
import { Effect, Exit, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

const cancel = runtime.runCallback(Effect.succeed(123), {
  onExit: (exit) => {
    if (Exit.isSuccess(exit)) console.log(exit.value)
    // => 123
  }
})

// Call the returned function to interrupt before completion:
cancel()
```

### `.dispose`

Closes the runtime's scope from `Promise`-land, running every finalizer the
layer registered, and returns `Promise<void>`. Call it during process shutdown.
After disposal the runtime is unusable.

```ts
import { ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)
await runtime.dispose()
// => undefined (resources released)
```

### `.disposeEffect`

The `Effect` version of `dispose` — an `Effect<void, never, never>` you can
compose inside other effects (for instance with `Effect.ensuring` so the runtime
is torn down when a top-level program finishes).

```ts
import { Effect, ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

const program = Effect.log("working").pipe(
  Effect.ensuring(runtime.disposeEffect)
)

await runtime.runPromise(program)
// => logs "working", then disposes the runtime
```

### `.context`

Forces the layer to build (if it hasn't) and returns a `Promise<Context<R>>` of
the cached service context. Handy when you need the raw `Context` to hand to
lower-level Effect APIs.

```ts
import { Context, Effect, Layer, ManagedRuntime } from "effect"

class Greeter extends Context.Service<Greeter, { readonly hi: string }>()("Greeter") {
  static readonly layer = Layer.succeed(Greeter)({ hi: "hello" })
}

const runtime = ManagedRuntime.make(Greeter.layer)

const context = await runtime.context()
Context.get(context, Greeter).hi
// => "hello"
```

### `.contextEffect`

An `Effect<Context<R>, ER>` that builds the layer (lazily, once) and yields the
cached context. This is the effectful counterpart to `context()`; the `run*`
methods use it internally to provide services to your effects.

```ts
import { Context, Effect, Layer, ManagedRuntime } from "effect"

class Greeter extends Context.Service<Greeter, { readonly hi: string }>()("Greeter") {
  static readonly layer = Layer.succeed(Greeter)({ hi: "hello" })
}

const runtime = ManagedRuntime.make(Greeter.layer)

const hi = await runtime.runPromise(
  Effect.map(runtime.contextEffect, (ctx) => Context.get(ctx, Greeter).hi)
)
// => "hello"   (layer built lazily, context cached)
```

### `.memoMap`

The `Layer.MemoMap` this runtime uses for layer memoization — either the one you
passed via `options.memoMap` or a fresh one created with
`Layer.makeMemoMapUnsafe()`. Read it to build additional layers that share this
runtime's memoization.

```ts
import { Effect, Layer, ManagedRuntime, Scope } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

runtime.memoMap
// => the Layer.MemoMap backing this runtime

// Build an extra layer against the same memo map:
const extra = await runtime.runPromise(
  Effect.scoped(
    Effect.gen(function*() {
      const scope = yield* Scope.make()
      return yield* Layer.buildWithMemoMap(Layer.empty, runtime.memoMap, scope)
    })
  )
)
```

### `ManagedRuntime.isManagedRuntime`

Type guard that narrows an `unknown` to `ManagedRuntime<unknown, unknown>` by
checking the internal marker. Note: a **disposed** runtime still carries the
marker, so this does not prove the runtime is still usable.

```ts
import { ManagedRuntime, Layer } from "effect"

const runtime = ManagedRuntime.make(Layer.empty)

ManagedRuntime.isManagedRuntime(runtime)
// => true
ManagedRuntime.isManagedRuntime({})
// => false
```

### Type helpers: `ManagedRuntime.Services` / `ManagedRuntime.Error`

Type-level utilities that extract the provided services `R` and the layer
construction error `ER` from a `ManagedRuntime` type.

```ts
import type { ManagedRuntime } from "effect"

declare const runtime: ManagedRuntime.ManagedRuntime<"MyService", "BuildError">

type R = ManagedRuntime.ManagedRuntime.Services<typeof runtime>
// => "MyService"
type E = ManagedRuntime.ManagedRuntime.Error<typeof runtime>
// => "BuildError"
```