Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

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.

14 exports Since v2.0.0 Source

Combinators

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"]

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]
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. By default, finalizers run uninterruptibly, so interrupting the closing fiber waits for them to finish; a finalizer can explicitly restore interruptibility.

Signature

declare const close: <A, E>(self: Closeable, 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"]
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.

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"]

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") => Closeable

Example 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"]
Since v4.0.0

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"]
Since v2.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

  • provide for providing a scope without closing it automatically
  • Effect.scoped for 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

Since v2.0.0

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"]

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") => Closeable

Example 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

Closeable

interface
Since v2.0.0

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

Since v4.0.0

Scope states: Empty has no finalizers, Open has at least one, and Closed holds the exit value.

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

Since v2.0.0

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"]

Scope

interface
Since v2.0.0

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";
readonly parent: Scope | undefined;
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

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. The caller must run the returned effect uninterruptibly: the scope is already closed, so interruption during finalization can permanently skip remaining finalizers.

See

  • close for the usual effectful close operation that always returns an Effect

Signature

declare const closeUnsafe: <A, E>(self: Closeable, exit_: Exit<A, E>) => Effect<void, never, never> | undefined