Scope
Controls how long resources stay open.
A scope is a lifetime boundary. Code can register cleanup effects on it, and
closing the scope runs those cleanups with the Exit value that ended the
work. Most application code uses higher-level APIs such as Effect.scoped
and Layer, while this module is useful when code needs to create, provide,
fork, close, or inspect scopes directly.
Combinators
addFinalizer
Registers a finalizer effect on a scope.
Details
If the scope is open, the finalizer runs when the scope closes, regardless of whether the scope closes successfully or with an error. If the scope is already closed, the finalizer runs immediately.
Signature
declare const addFinalizer: (scope: Scope, finalizer: Effect<unknown>) => Effect<void>Example
(Adding finalizers)
import { Effect, Exit, Scope } from "effect"
const events: Array<string> = []const program = Effect.gen(function*() { const scope = yield* Scope.make() yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 1"))) yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 2"))) yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 3"))) events.push("work") yield* Scope.close(scope, Exit.void)})
Effect.runSync(program)events // => ["work", "cleanup 3", "cleanup 2", "cleanup 1"]addFinalizerExit
Registers an exit-aware finalizer on a scope.
When to use
Use when cleanup needs to know whether the scope closed with success, failure, or interruption.
Details
If the scope is open, the finalizer runs when the scope closes and receives the scope's exit value. If the scope is already closed, the finalizer runs immediately with the stored exit value.
Signature
declare const addFinalizerExit: (scope: Scope, finalizer: (exit: Exit<any, any>) => Effect<unknown>) => Effect<void>Example
(Adding an exit-aware finalizer)
import { Effect, Exit, Scope } from "effect"
const exits: Array<Exit.Exit<unknown, unknown>> = []const withResource = Effect.gen(function*() { const scope = yield* Scope.make() yield* Scope.addFinalizerExit(scope, (exit) => Effect.sync(() => exits.push(exit))) yield* Scope.close(scope, Exit.void)})
Effect.runSync(withResource)exits // => [Exit.void]Closes a scope and runs its registered finalizers.
When to use
Use to close a scope manually with a specific exit value.
Details
Finalizers run in the scope's configured order and receive the supplied
Exit.
Signature
declare const close: <A, E>(self: Scope, exit: Exit<A, E>) => Effect<void>Example
(Running scope finalizers)
import { Effect, Exit, Scope } from "effect"
const events: Array<string> = []const resourceManagement = Effect.gen(function*() { const scope = yield* Scope.make("sequential") yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("database"))) yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("file"))) yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("memory"))) events.push("work") yield* Scope.close(scope, Exit.succeed("Success!"))})
Effect.runSync(resourceManagement)events // => ["work", "memory", "file", "database"]Creates a closeable child scope registered with a parent scope.
Details
Closing the parent closes the child with the same exit value, and closing the
child detaches it from the parent. The optional finalizer strategy configures
the child scope and defaults to "sequential" when omitted.
Signature
declare const fork: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => Effect<Closeable>Example
(Creating a child scope)
import { Effect, Exit, Scope } from "effect"
const cleanups: Array<string> = []const nestedScopes = Effect.gen(function*() { const parentScope = yield* Scope.make("sequential") yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent"))) const childScope = yield* Scope.fork(parentScope, "parallel") yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child"))) yield* Scope.close(childScope, Exit.void) yield* Scope.close(parentScope, Exit.void)})
Effect.runSync(nestedScopes)cleanups // => ["child", "parent"]forkUnsafe
Creates a closeable child scope synchronously and registers it with a parent scope.
When to use
Use when a child scope must be created synchronously and the caller controls both parent and child scope lifetimes.
Details
Closing the parent closes the child with the same exit value, and closing the
child detaches it from the parent. The optional finalizer strategy configures
the child scope and defaults to "sequential" when omitted.
Signature
declare const forkUnsafe: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => CloseableExample
(Creating a child scope synchronously)
import { Effect, Exit, Scope } from "effect"
const cleanups: Array<string> = []const program = Effect.gen(function*() { const parentScope = Scope.makeUnsafe("sequential") const childScope = Scope.forkUnsafe(parentScope, "parallel") yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent"))) yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child"))) yield* Scope.close(childScope, Exit.void) yield* Scope.close(parentScope, Exit.void)})
Effect.runSync(program)cleanups // => ["child", "parent"]Provides a concrete Scope to an effect.
When to use
Use to run an effect that requires Scope with a scope managed by the
caller.
Details
Providing the scope removes the Scope requirement from the effect context.
Signature
declare const provide: { (value: Scope): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, Scope>>; <A, E, R>(self: Effect<A, E, R>, value: Scope): Effect<A, E, Exclude<R, Scope>>;}Example
(Providing a scope)
import { Effect, Exit, Scope } from "effect"
const events: Array<string> = []const program = Effect.gen(function*() { const scope = yield* Scope.Scope yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup"))) events.push("working")})
const withScope = Effect.gen(function*() { const scope = yield* Scope.make() yield* Scope.provide(scope)(program) yield* Scope.close(scope, Exit.void)})
Effect.runSync(withScope)events // => ["working", "cleanup"]Runs an effect with the provided closeable scope in its context and closes that scope when the effect exits.
When to use
Use when you already have a Closeable scope and want to run an effect that
requires Scope while automatically closing that scope when the effect exits.
Details
The scope is closed with the same exit value as the effect, so registered finalizers can observe whether the effect succeeded, failed, or was interrupted.
See
providefor providing a scope without closing it automaticallyEffect.scopedfor creating and closing a fresh scope around a workflow
Signature
declare const use: { (scope: Closeable): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, Scope>>; <A, E, R>(self: Effect<A, E, R>, scope: Closeable): Effect<A, E, Exclude<R, Scope>>;}Constructors
Creates a new Scope with the specified finalizer strategy.
Signature
declare const make: (finalizerStrategy?: "sequential" | "parallel") => Effect<Closeable>Example
(Creating a scope)
import { Effect, Exit, Scope } from "effect"
const cleanups: Array<string> = []const program = Effect.gen(function*() { const scope = yield* Scope.make("sequential") yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 1"))) yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 2"))) yield* Scope.close(scope, Exit.void)})
Effect.runSync(program)cleanups // => ["Cleanup 2", "Cleanup 1"]makeUnsafe
Creates a new Scope synchronously without wrapping it in an Effect.
This is useful when you need a scope immediately but should be used with caution
as it doesn't provide the same safety guarantees as the Effect-wrapped version.
When to use
Use when a scope must be allocated synchronously and the caller will close it manually.
Signature
declare const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => CloseableExample
(Creating a scope synchronously)
import { Effect, Exit, Scope } from "effect"
const scope = Scope.makeUnsafe("sequential")const cleanups: Array<string> = []const program = Effect.gen(function*() { yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup"))) yield* Scope.close(scope, Exit.void)})
Effect.runSync(program)cleanups // => ["Cleanup"]Models
A Closeable scope extends the base Scope interface with the ability
to be closed, executing all registered finalizers.
Signature
interface Closeable extends Scope { readonly "~effect/Scope/Closeable": "~effect/Scope/Closeable";}Example
(Closing a scope)
import { Effect, Exit, Scope } from "effect"
const cleanups: Array<string> = []const program = Effect.gen(function*() { const scope = yield* Scope.make() yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup!"))) yield* Scope.close(scope, Exit.void)})
Effect.runSync(program)cleanups // => ["Cleanup!"]Other
The State namespace contains the concrete states of a scope: Empty
before any finalizers are registered, Open with registered finalizers, and
Closed with the exit value used to close the scope.
Example
(Checking scope states)
import { Effect, Exit, Scope } from "effect"
const program = Effect.gen(function*() { const scope = yield* Scope.make() const before = scope.state._tag yield* Scope.close(scope, Exit.void) return [before, scope.state._tag]})
Effect.runSync(program) // => ["Empty", "Closed"]Services
Service tag for the active resource lifetime.
When to use
Use to access the active lifetime when registering finalizers or sharing resources with the surrounding scope.
Signature
declare const Scope: Service<Scope, Scope>Example
(Accessing the scope service)
import { Effect, Scope } from "effect"
const cleanups: Array<string> = []const program = Effect.gen(function*() { const scope = yield* Scope.Scope yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup")))})
Effect.runSync(Effect.scoped(program))cleanups // => ["Cleanup"]A Scope represents a context where resources can be acquired and
automatically cleaned up when the scope is closed. Scopes can use
either sequential or parallel finalization strategies.
Signature
interface Scope { readonly "~effect/Scope": "~effect/Scope"; state: Open | Closed | Empty; readonly strategy: "sequential" | "parallel";}Example
(Managing scoped resources)
import { Effect, Exit, Scope } from "effect"
const program = Effect.gen(function*() { const scope = yield* Scope.make("sequential")
const initial = [scope.strategy, scope.state._tag] yield* Scope.close(scope, Exit.void) return [initial, scope.state._tag]})
Effect.runSync(program) // => [["sequential", "Empty"], "Closed"]Unsafe
closeUnsafe
Closes a scope unsafely with the provided exit value.
When to use
Use when implementing lower-level scope machinery that must transition a
scope to Closed immediately and can run the returned finalizer effect when
one is produced.
Details
Returns an effect that runs registered finalizers, or undefined when the
scope was already closed or no finalizers need to run.
Gotchas
Ignoring the returned effect skips registered finalizers.
See
- close for the usual effectful close operation that always returns an
Effect
Signature
declare const closeUnsafe: <A, E>(self: Scope, exit_: Exit<A, E>) => Effect<void, never, never> | undefined