| name | effect-resource-management |
| user-invocable | false |
| description | Use when Effect resource management patterns including Scope, addFinalizer, scoped effects, and automatic cleanup. Use for managing resources in Effect applications. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
Effect Resource Management
Master automatic resource management in Effect using Scopes and finalizers.
This skill covers resource acquisition, cleanup, scoped effects, and patterns
for building leak-free Effect applications.
Scope Fundamentals
A Scope represents the lifetime of resources. When a scope closes, all
registered finalizers execute automatically.
Basic Scope Usage
import { Effect, Scope } from "effect"
const program = Effect.scoped(
Effect.gen(function* () {
const resource = yield* acquireResource()
const result = yield* useResource(resource)
return result
})
)
Adding Finalizers
import { Effect } from "effect"
const acquireFile = (path: string) =>
Effect.gen(function* () {
const file = yield* Effect.sync(() => openFile(path))
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
console.log(`Closing file: ${path}`)
file.close()
})
)
return file
})
const program = Effect.scoped(
Effect.gen(function* () {
const file = yield* acquireFile("data.txt")
const content = yield* readFile(file)
return content
})
)
Finalizer Behavior
Execution Order
Finalizers execute in reverse order of registration (LIFO):
import { Effect } from "effect"
const program = Effect.scoped(
Effect.gen(function* () {
yield* Effect.addFinalizer(() =>
Effect.log("Finalizer 1")
)
yield* Effect.addFinalizer(() =>
Effect.log("Finalizer 2")
)
yield* Effect.addFinalizer(() =>
Effect.log("Finalizer 3")
)
return "done"
})
)
Exit Information
Finalizers receive exit information:
import { Effect, Exit } from "effect"
const acquireWithContext = Effect.gen(function* () {
yield* Effect.addFinalizer((exit) =>
Effect.sync(() => {
if (Exit.isSuccess(exit)) {
console.log("Scope exited successfully:", exit.value)
} else if (Exit.isFailure(exit)) {
console.log("Scope failed:", exit.cause)
} else {
console.log("Scope interrupted")
}
})
)
const resource = yield* Effect.sync(() => createResource())
return resource
})
Resource Patterns
Database Connection
import { Effect } from "effect"
interface DbConnection {
query: <T>(sql: string) => Promise<T>
close: () => Promise<void>
}
const acquireConnection = (config: DbConfig) =>
Effect.gen(function* () {
const conn = yield* Effect.tryPromise({
try: () => createConnection(config),
catch: (error) => ({
_tag: "ConnectionError",
message: String(error)
})
})
yield* Effect.addFinalizer(() =>
Effect.tryPromise({
try: () => conn.close(),
catch: (error) => ({
_tag: ,
: (error)
})
}).(
.(
.()
)
)
)
conn
})
queryDatabase = .(
.(* () {
conn = * (dbConfig)
users = * .(
conn.<[]>()
)
users
})
)
File Operations
import { Effect } from "effect"
import * as fs from "fs/promises"
const withFile = <A, E, R>(
path: string,
use: (handle: fs.FileHandle) => Effect.Effect<A, E, R>
) =>
Effect.scoped(
Effect.gen(function* () {
const handle = yield* Effect.tryPromise({
try: () => fs.open(path, "r"),
catch: (error) => ({
_tag: "FileError",
message: String(error)
})
})
yield* Effect.addFinalizer(() =>
Effect.tryPromise(() => handle.close()).pipe(
Effect.catchAll(() => Effect.)
)
)
* (handle)
})
)
readFileContent = (,
.( handle.({ : }))
)
Network Resources
import { Effect } from "effect"
interface WebSocket {
send: (data: string) => void
close: () => void
onMessage: (handler: (data: string) => void) => void
}
const acquireWebSocket = (url: string) =>
Effect.gen(function* () {
const ws = yield* Effect.async<WebSocket, never>((resume) => {
const socket = new WebSocket(url)
socket.onopen = () => {
resume(Effect.succeed(socket))
}
socket.onerror = () => {
resume(Effect.fail({ _tag: "ConnectionError" }))
}
})
yield* .(
.( {
.()
ws.()
})
)
ws
})
Scoped Effects
Effect.acquireRelease
Simplified resource acquisition:
import { Effect } from "effect"
const resource = Effect.acquireRelease(
Effect.sync(() => {
console.log("Acquiring resource")
return createResource()
}),
(resource) =>
Effect.sync(() => {
console.log("Releasing resource")
resource.cleanup()
})
)
const program = Effect.scoped(
Effect.gen(function* () {
const r = yield* resource
return yield* useResource(r)
})
)
Effect.acquireUseRelease
One-shot resource usage:
import { Effect } from "effect"
const readConfig = Effect.acquireUseRelease(
Effect.tryPromise(() => fs.open("config.json", "r")),
(handle) =>
Effect.tryPromise(() =>
handle.readFile({ encoding: "utf8" })
).pipe(
Effect.map((content) => JSON.parse(content))
),
(handle) =>
Effect.tryPromise(() => handle.close()).pipe(
Effect.orDie
)
)
Nested Scopes
Scope Nesting
Scopes can be nested for hierarchical cleanup:
import { Effect } from "effect"
const program = Effect.scoped(
Effect.gen(function* () {
const db = yield* acquireConnection()
yield* Effect.scoped(
Effect.gen(function* () {
const transaction = yield* beginTransaction(db)
yield* updateUsers(transaction)
yield* commitTransaction(transaction)
})
)
yield* runQuery(db)
})
)
Parallel Scopes
import { Effect } from "effect"
const parallelResources = Effect.gen(function* () {
const results = yield* Effect.all([
Effect.scoped(
Effect.gen(function* () {
const conn1 = yield* acquireConnection(db1Config)
return yield* queryDb(conn1)
})
),
Effect.scoped(
Effect.gen(function* () {
const conn2 = yield* acquireConnection(db2Config)
return yield* queryDb(conn2)
})
)
])
return results
})
Advanced Patterns
Resource Pool
import { Effect, Queue, Ref } from "effect"
interface Pool<R> {
acquire: Effect.Effect<R, never, Scope.Scope>
release: (resource: R) => Effect.Effect<void, never, never>
}
const createPool = <R, E>(
create: Effect.Effect<R, E, never>,
destroy: (resource: R) => Effect.Effect<void, never, never>,
size: number
): Effect.Effect<Pool<R>, E, Scope.Scope> =>
Effect.gen(function* () {
const available = yield* Queue.bounded<R>(size)
const counter = yield* Ref.make()
* .(
.({ : size }),
.(* () {
resource = * create
* .(available, resource)
}),
{ : }
)
* .(
.(* () {
resources = * .(available)
* .(
resources,
(r),
{ : }
)
})
)
{
: .(* () {
resource = * .(available)
* .( .(available, resource))
resource
}),
: .(available, resource)
}
})
Cached Resource
import { Effect, Ref } from "effect"
const cached = <A, E, R>(
acquire: Effect.Effect<A, E, R>
): Effect.Effect<Effect.Effect<A, E, never>, never, Scope.Scope | R> =>
Effect.gen(function* () {
const ref = yield* Ref.make<Option<A>>(Option.none())
yield* Effect.addFinalizer(() =>
ref.set(Option.none())
)
return ref.get.pipe(
Effect.flatMap((option) =>
Option.match(option, {
onNone: () =>
acquire.pipe(
Effect.tap((value) => ref.set(Option.(value)))
),
: .(value)
})
)
)
})
Best Practices
-
Always Use Scoped: Acquire resources within Effect.scoped.
-
Register Finalizers Immediately: Add finalizers right after acquisition.
-
Handle Cleanup Errors: Catch and log errors in finalizers.
-
Reverse Order: Rely on LIFO finalizer execution for dependencies.
-
Use acquireRelease: Prefer acquireRelease for simple acquire/release
patterns.
-
Test Cleanup: Verify finalizers execute correctly.
-
Avoid Manual Cleanup: Don't manually clean up scoped resources.
-
Nest Appropriately: Use nested scopes for hierarchical resources.
-
Pool Expensive Resources: Use resource pools for expensive acquisitions.
-
Document Scope Requirements: Make it clear which effects need scopes.
Common Pitfalls
-
Missing Scoped: Acquiring resources without Effect.scoped.
-
Not Adding Finalizers: Forgetting to register cleanup.
-
Finalizer Errors: Throwing errors in finalizers without handling.
-
Wrong Scope Nesting: Closing scopes in wrong order.
-
Resource Leaks: Not cleaning up on all exit paths.
-
Duplicate Cleanup: Cleaning up resources multiple times.
-
Blocking Finalizers: Using long-running operations in finalizers.
-
Ignoring Exit Info: Not using exit information appropriately.
-
Scope Scope Confusion: Confusing when scopes close.
-
Missing Error Handling: Not handling errors during acquisition.
When to Use This Skill
Use effect-resource-management when you need to:
- Manage database connections
- Handle file operations safely
- Work with network resources
- Implement connection pools
- Build transaction systems
- Ensure cleanup on all exit paths
- Manage WebSocket connections
- Handle distributed locks
- Implement caching with cleanup
- Build leak-free applications
Resources
Official Documentation
Related Skills
- effect-core-patterns - Basic Effect operations
- effect-concurrency - Managing fiber lifecycles
- effect-dependency-injection - Layer cleanup with scoped