Scope.ts
Scope.ts overview
Section titled “Scope.ts overview”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.
Since v2.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”combinators
Section titled “combinators”addFinalizer
Section titled “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.
Example (Adding finalizers)
import { Console, Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { const scope = yield* Scope.make()
// Add simple finalizers yield* Scope.addFinalizer(scope, Console.log("Cleanup task 1")) yield* Scope.addFinalizer(scope, Console.log("Cleanup task 2")) yield* Scope.addFinalizer(scope, Effect.log("Cleanup task 3"))
// Do some work yield* Console.log("Doing work...")
// Close the scope yield* Scope.close(scope, Exit.void)})Signature
declare const addFinalizer: (scope: Scope, finalizer: Effect<unknown>) => Effect<void>Since v2.0.0
addFinalizerExit
Section titled “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.
Example (Adding an exit-aware finalizer)
import { Console, Effect, Exit, Scope } from "effect"
const withResource = Effect.gen(function* () { const scope = yield* Scope.make()
// Add a finalizer for cleanup yield* Scope.addFinalizerExit(scope, (exit) => Console.log(`Cleaning up resource. Exit: ${Exit.isSuccess(exit) ? "Success" : "Failure"}`) )
// Use the resource yield* Console.log("Using resource")
// Close the scope yield* Scope.close(scope, Exit.void)})Signature
declare const addFinalizerExit: (scope: Scope, finalizer: (exit: Exit<any, any>) => Effect<unknown>) => Effect<void>Since v2.0.0
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.
Example (Running scope finalizers)
import { Console, Effect, Exit, Scope } from "effect"
const resourceManagement = Effect.gen(function* () { const scope = yield* Scope.make("sequential")
// Add multiple finalizers yield* Scope.addFinalizer(scope, Console.log("Close database connection")) yield* Scope.addFinalizer(scope, Console.log("Close file handle")) yield* Scope.addFinalizer(scope, Console.log("Release memory"))
// Do some work... yield* Console.log("Performing operations...")
// Close scope - finalizers run in reverse order of registration yield* Scope.close(scope, Exit.succeed("Success!")) // Output: "Release memory", "Close file handle", "Close database connection"})Signature
declare const close: <A, E>(self: Scope, exit: Exit<A, E>) => Effect<void>Since v2.0.0
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.
Example (Creating a child scope)
import { Console, Effect, Exit, Scope } from "effect"
const nestedScopes = Effect.gen(function* () { const parentScope = yield* Scope.make("sequential")
// Add finalizer to parent yield* Scope.addFinalizer(parentScope, Console.log("Parent cleanup"))
// Create child scope const childScope = yield* Scope.fork(parentScope, "parallel")
// Add finalizer to child yield* Scope.addFinalizer(childScope, Console.log("Child cleanup"))
// Close child first, then parent yield* Scope.close(childScope, Exit.void) yield* Scope.close(parentScope, Exit.void)})Signature
declare const fork: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => Effect<Closeable>Since v2.0.0
forkUnsafe
Section titled “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.
Example (Creating a child scope synchronously)
import { Console, Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { const parentScope = Scope.makeUnsafe("sequential") const childScope = Scope.forkUnsafe(parentScope, "parallel")
// Add finalizers to both scopes yield* Scope.addFinalizer(parentScope, Console.log("Parent cleanup")) yield* Scope.addFinalizer(childScope, Console.log("Child cleanup"))
// Close child first, then parent yield* Scope.close(childScope, Exit.void) yield* Scope.close(parentScope, Exit.void)})Signature
declare const forkUnsafe: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => CloseableSince v4.0.0
provide
Section titled “provide”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.
Example (Providing a scope)
import { Console, Effect, Scope } from "effect"
// An effect that requires a Scopeconst program = Effect.gen(function* () { const scope = yield* Scope.Scope yield* Scope.addFinalizer(scope, Console.log("Cleanup")) yield* Console.log("Working...")})
// Provide a scope to the programconst withScope = Effect.gen(function* () { const scope = yield* Scope.make() yield* Scope.provide(scope)(program)})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>>}Since v4.0.0
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>>}Since v2.0.0
constructors
Section titled “constructors”Creates a new Scope with the specified finalizer strategy.
Example (Creating a scope)
import { Console, Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { // Create a scope with sequential cleanup const scope = yield* Scope.make("sequential")
// Add finalizers yield* Scope.addFinalizer(scope, Console.log("Cleanup 1")) yield* Scope.addFinalizer(scope, Console.log("Cleanup 2"))
// Close the scope (finalizers run in reverse order) yield* Scope.close(scope, Exit.void) // Output: "Cleanup 2", then "Cleanup 1"})Signature
declare const make: (finalizerStrategy?: "sequential" | "parallel") => Effect<Closeable>Since v2.0.0
makeUnsafe
Section titled “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.
Example (Creating a scope synchronously)
import { Console, Effect, Exit, Scope } from "effect"
// Create a scope immediatelyconst scope = Scope.makeUnsafe("sequential")
// Use it in an Effect programconst program = Effect.gen(function* () { yield* Scope.addFinalizer(scope, Console.log("Cleanup")) yield* Scope.close(scope, Exit.void)})Signature
declare const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => CloseableSince v4.0.0
models
Section titled “models”Closeable (interface)
Section titled “Closeable (interface)”A Closeable scope extends the base Scope interface with the ability
to be closed, executing all registered finalizers.
Example (Closing a scope)
import { Console, Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { const scope = yield* Scope.make()
// Add a finalizer yield* Scope.addFinalizer(scope, Console.log("Cleanup!"))
// Scope can be closed yield* Scope.close(scope, Exit.void)})Signature
export interface Closeable extends Scope { readonly [CloseableTypeId]: typeof CloseableTypeId}Since v2.0.0
Scope (interface)
Section titled “Scope (interface)”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.
Example (Managing scoped resources)
import { Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { const scope = yield* Scope.make("sequential")
// Scope has a strategy and state console.log(scope.strategy) // "sequential" console.log(scope.state._tag) // "Open"
// Close the scope yield* Scope.close(scope, Exit.void) console.log(scope.state._tag) // "Closed"})Signature
export interface Scope { readonly [TypeId]: typeof TypeId readonly strategy: "sequential" | "parallel" state: State.Open | State.Closed | State.Empty}Since v2.0.0
services
Section titled “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.
Example (Accessing the scope service)
import { Effect, Scope } from "effect"
const program = Effect.gen(function* () { // Access the scope from the context const scope = yield* Scope.Scope
// Use the scope for resource management yield* Scope.addFinalizer(scope, Effect.log("Cleanup"))})
// Provide a scope to the programconst scoped = Effect.scoped(program)Signature
declare const Scope: Context.Service<Scope, Scope>Since v2.0.0
unsafe
Section titled “unsafe”closeUnsafe
Section titled “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
closefor the usual effectful close operation that always returns anEffect
Signature
declare const closeUnsafe: <A, E>(self: Scope, exit_: Exit<A, E>) => Effect<void, never, never> | undefinedSince v4.0.0
State (namespace)
Section titled “State (namespace)”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"
// Example of checking scope statesconst program = Effect.gen(function* () { const scope = yield* Scope.make()
// When open, the scope accepts finalizers if (scope.state._tag === "Open") { console.log("Scope is open") }
yield* Scope.close(scope, Exit.void)
// When closed, the scope no longer accepts finalizers if (scope.state._tag === "Closed") { console.log("Scope is closed") }})Since v4.0.0
Empty (type alias)
Section titled “Empty (type alias)”Represents an open scope with no registered finalizers yet.
Details
Adding the first finalizer transitions the scope to Open; closing an
empty scope transitions directly to Closed without producing a finalizer
effect.
Example (Inspecting an empty scope state)
import { Scope } from "effect"
const scope = Scope.makeUnsafe()
// When scope is open, you can check its stateif (scope.state._tag === "Open") { console.log("Scope is open and accepting finalizers") console.log(scope.state.finalizers.size) // Number of registered finalizers}Signature
type Empty = { readonly _tag: "Empty"}Since v4.0.0
Open (type alias)
Section titled “Open (type alias)”Represents an open scope state where finalizers can be added and the scope is still accepting new resources.
Example (Inspecting an open scope state)
import { Scope } from "effect"
const scope = Scope.makeUnsafe()
// When scope is open, you can check its stateif (scope.state._tag === "Open") { console.log("Scope is open and accepting finalizers") console.log(scope.state.finalizers.size) // Number of registered finalizers}Signature
type Open = { readonly _tag: "Open" readonly finalizers: Map<{}, (exit: Exit<any, any>) => Effect<void>>}Since v4.0.0
Closed (type alias)
Section titled “Closed (type alias)”Represents a closed scope state where finalizers have been executed and the scope is no longer accepting new resources.
Example (Inspecting a closed scope state)
import { Effect, Exit, Scope } from "effect"
const program = Effect.gen(function* () { const scope = yield* Scope.make()
// Close the scope yield* Scope.close(scope, Exit.succeed("Done"))
// Check if scope is closed if (scope.state._tag === "Closed") { console.log("Scope is closed") console.log(scope.state.exit) // The exit value used to close the scope }})Signature
type Closed = { readonly _tag: "Closed" readonly exit: Exit<any, any>}Since v4.0.0