Skip to content

Scope.ts

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



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>

Source

Since v2.0.0

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>

Source

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>

Source

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>

Source

Since v2.0.0

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

Source

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.

Example (Providing a scope)

import { Console, Effect, Scope } from "effect"
// An effect that requires a Scope
const 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 program
const 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>>
}

Source

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

  • 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>>
}

Source

Since v2.0.0

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>

Source

Since v2.0.0

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 immediately
const scope = Scope.makeUnsafe("sequential")
// Use it in an Effect program
const program = Effect.gen(function* () {
yield* Scope.addFinalizer(scope, Console.log("Cleanup"))
yield* Scope.close(scope, Exit.void)
})

Signature

declare const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => Closeable

Source

Since v4.0.0

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
}

Source

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.

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
}

Source

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.

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 program
const scoped = Effect.scoped(program)

Signature

declare const Scope: Context.Service<Scope, Scope>

Source

Since v2.0.0

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

Source

Since v4.0.0

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 states
const 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")
}
})

Source

Since v4.0.0

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 state
if (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"
}

Source

Since v4.0.0

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 state
if (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>>
}

Source

Since v4.0.0

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>
}

Source

Since v4.0.0