Skip to content
Docs menu / Resourceful Streams

Resourceful Streams

Resources acquired by a stream must remain open for the whole period in which the stream is consumed. Compose Effect.acquireRelease, Stream.fromEffect, and Stream.scoped to tie the resource lifetime to the stream. Use Stream.ensuring when a stream only needs a finalizer.

Acquire Release

The following example acquires a file, emits its lines, and closes it when stream consumption ends.

import { Stream, Console, Effect } from "effect"
// Simulating File operations
const open = (filename: string) =>
Effect.gen(function* () {
yield* Console.log(`Opening ${filename}`)
return {
getLines: Effect.succeed(["Line 1", "Line 2", "Line 3"]),
close: Console.log(`Closing ${filename}`),
}
})
const stream = Stream.scoped(
Stream.fromEffect(
Effect.acquireRelease(open("file.txt"), (file) => file.close),
),
).pipe(Stream.flatMap((file) => Stream.fromIterableEffect(file.getLines)))
await Effect.runPromise(Stream.runCollect(stream)) // => ["Line 1", "Line 2", "Line 3"]

Effect.acquireRelease registers file.close in the scope created by Stream.scoped. The file therefore remains open while Stream.fromIterableEffect emits its contents.

Finalization

Stream.ensuring runs a finalizer after the stream’s own finalizers, whether the stream succeeds, fails, or is interrupted.

import { Stream, Console, Effect } from "effect"
const application = Stream.fromEffect(Console.log("Application Logic."))
const deleteDir = (dir: string) => Console.log(`Deleting dir: ${dir}`)
const program = application.pipe(
Stream.ensuring(
deleteDir("tmp").pipe(
Effect.andThen(Console.log("Temporary directory was deleted.")),
),
),
)
await Effect.runPromise(Stream.runCollect(program)) // => [undefined]