typescript-utility-types
Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.
effect-resource-management
Use when Effect resource management patterns including Scope, addFinalizer, scoped effects, and automatic cleanup. Use for managing resources in Effect applications.
Full skill instructions
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.
A Scope represents the lifetime of resources. When a scope closes, all registered finalizers execute automatically.
import { Effect, Scope } from "effect"
const program = Effect.scoped(
Effect.gen(function* () {
// Resources acquired here are tied to this scope
const resource = yield* acquireResource()
// Use resource
const result = yield* useResource(resource)
return result
// Scope closes here, resources cleaned up automatically
})
)
import { Effect } from "effect"
const acquireFile = (path: string) =>
Effect.gen(function* () {
// Acquire resource
const file = yield* Effect.sync(() => openFile(path))
// Register cleanup
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
console.log(`Closing file: ${path}`)
file.close()
})
)
return file
})
// Usage
const program = Effect.scoped(
Effect.gen(function* () {
const file = yield* acquireFile("data.txt")
const content = yield* readFile(file)
return content
// File automatically closed on scope exit
})
)
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"
})
)
// Output:
// Finalizer 3
// Finalizer 2
// Finalizer 1
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")
}
})
)
// Acquire resource
const resource = yield* Effect.sync(() => createResource())
return resource
})
import { Effect } from "effect"
interface DbConnection {
query: <T>(sql: string) => Promise<T>
close: () => Promise<void>
}
const acquireConnection = (config: DbConfig) =>
Effect.gen(function* () {
// Acquire connection
const conn = yield* Effect.tryPromise({
try: () => createConnection(config),
catch: (error) => ({
_tag: "ConnectionError",
message: String(error)
})
})
// Register cleanup
yield* Effect.addFinalizer(() =>
Effect.tryPromise({
try: () => conn.close(),
catch: (error) => ({
_tag: "CloseError",
message: String(error)
})
}).pipe(
Effect.catchAll((error) =>
Effect.log(`Failed to close connection: ${error.message}`)
)
)
)
return conn
})
// Usage
const queryDatabase = Effect.scoped(
Effect.gen(function* () {
const conn = yield* acquireConnection(dbConfig)
const users = yield* Effect.tryPromise(() =>
conn.query<User[]>("SELECT * FROM users")
)
return users
// Connection automatically closed
})
)
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* () {
// Acquire file handle
const handle = yield* Effect.tryPromise({
try: () => fs.open(path, "r"),
catch: (error) => ({
_tag: "FileError",
message: String(error)
})
})
// Register cleanup
yield* Effect.addFinalizer(() =>
Effect.tryPromise(() => handle.close()).pipe(
Effect.catchAll(() => Effect.void)
)
)
// Use file
return yield* use(handle)
})
)
// Usage
const readFileContent = withFile("data.txt", (handle) =>
Effect.tryPromise(() => handle.readFile({ encoding: "utf8" }))
)
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* Effect.addFinalizer(() =>
Effect.sync(() => {
console.log("Closing WebSocket")
ws.close()
})
)
return ws
})
Simplified resource acquisition:
import { Effect } from "effect"
const resource = Effect.acquireRelease(
// Acquire
Effect.sync(() => {
console.log("Acquiring resource")
return createResource()
}),
// Release
(resource) =>
Effect.sync(() => {
console.log("Releasing resource")
resource.cleanup()
})
)
// Usage
const program = Effect.scoped(
Effect.gen(function* () {
const r = yield* resource
return yield* useResource(r)
})
)
One-shot resource usage:
import { Effect } from "effect"
const readConfig = Effect.acquireUseRelease(
// Acquire
Effect.tryPromise(() => fs.open("config.json", "r")),
// Use
(handle) =>
Effect.tryPromise(() =>
handle.readFile({ encoding: "utf8" })
).pipe(
Effect.map((content) => JSON.parse(content))
),
// Release
(handle) =>
Effect.tryPromise(() => handle.close()).pipe(
Effect.orDie
)
)
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)
// Transaction scope ends, resources cleaned up
})
)
// DB connection still alive
yield* runQuery(db)
// DB scope ends, connection closed
})
)
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
// Both connections closed automatically
})
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(0)
// Initialize pool
yield* Effect.forEach(
Array.from({ length: size }),
() =>
Effect.gen(function* () {
const resource = yield* create
yield* Queue.offer(available, resource)
}),
{ concurrency: "unbounded" }
)
// Register pool cleanup
yield* Effect.addFinalizer(() =>
Effect.gen(function* () {
const resources = yield* Queue.takeAll(available)
yield* Effect.forEach(
resources,
(r) => destroy(r),
{ concurrency: "unbounded" }
)
})
)
return {
acquire: Effect.gen(function* () {
const resource = yield* Queue.take(available)
yield* Effect.addFinalizer(() => Queue.offer(available, resource))
return resource
}),
release: (resource) => Queue.offer(available, 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.some(value)))
),
onSome: (value) => Effect.succeed(value)
})
)
)
})
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.
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.
Use effect-resource-management when you need to:
Use when typeScript utility types, mapped types, and advanced type manipulation. Use when creating flexible, type-safe TypeScript code.
Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.
Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.
Generate production-ready code from a Figma component or frame using the Figma MCP server
Use when integrating Dialyzer into development workflows and CI/CD pipelines for Erlang/Elixir projects.
Use when ruboCop configuration including YAML config, departments, inheritance, and customization.
Use when Elixir OTP patterns including GenServer, Supervisor, Agent, and Task. Use when building concurrent, fault-tolerant Elixir applications.
Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
Use when swift's optional handling patterns including optional binding, chaining, nil coalescing, and modern approaches to safely working with optional values while avoiding common pitfalls and force unwrapping.
Use when annotating code with structured metadata, tags, and markers for AI-assisted development workflows. Covers annotation formats, semantic tags, and integration with development tools.
Use when scala collections including immutable/mutable variants, List, Vector, Set, Map operations, collection transformations, lazy evaluation with views, parallel collections, and custom collection builders for efficient data processing.
Use when creating custom React Flow nodes, edges, and handles. Covers custom node components, resizable nodes, toolbars, and advanced customization.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience