Skip to content
LogoLogo

FileSystem

FileSystem exposes Effect's portable file operations in VibeLang. Request the service inside a function, call its operations, and provide a runtime implementation or test layer around the application.

Requesting the file-system service

// run: provide FileSystem.layerNoop({ readFileString: () => '{"theme":"dark"}' }) { Console.log(readSettings()) }
function readSettings() {
  const fs = Effect.service(FileSystem.FileSystem)
  return fs.readFileString("./settings.json")
}
// string!PlatformError.PlatformError!FileSystem.FileSystem
// Output: {"theme":"dark"}

FileSystem is the imported module; FileSystem.FileSystem is its service key. Effect.service retrieves the implementation and adds that service to R. readFileString returns text on success and reports a PlatformError as an expected failure.

Performing file operations

// run: provide NodeFileSystem.layer { Console.log(JSON.stringify(saveSummary("ready"))) }
import { NodeFileSystem } from "@effect/platform-node"
function saveSummary(text: string) {
  const fs = Effect.service(FileSystem.FileSystem)
  fs.makeDirectory("./output", { recursive: true })
  fs.writeFileString("./output/summary.txt", text)
  const content = fs.readFileString("./output/summary.txt", "utf8")
  return { content, entries: fs.readDirectory("./output") }
}
// Output: {"content":"ready","entries":["summary.txt"]}

Calls run sequentially: the write completes before the read starts. Expected I/O failures enter E and can be handled with try/catch. readFile and writeFile handle byte arrays when text encoding is not appropriate.

OperationsPurpose
access, existsCheck accessibility or presence
copy, copyFileCopy a tree or a single file
chmod, chownChange permissions or ownership
link, symlink, readLinkWork with hard and symbolic links
makeDirectory, readDirectoryCreate and list directories
makeTempDirectory, makeTempFileCreate temporary paths owned by the caller
makeTempDirectoryScoped, makeTempFileScopedCreate temporary paths with scope-managed removal
openOpen a scope-managed file handle
readFile, readFileStringRead bytes or text
writeFile, writeFileStringWrite bytes or text
stat, realPathInspect metadata or resolve a canonical path
remove, rename, truncate, utimesRemove, move, resize, or change timestamps
stream, sink, watchStream bytes, write through a sink, or observe changes
// run: provide NodeFileSystem.layer { const fs = Effect.service(FileSystem.FileSystem); using path = fs.makeTempFileScoped(); fs.writeFileString(path, "hello"); Console.log(String(fileSize(path))) }
function fileSize(path: string) {
  const fs = Effect.service(FileSystem.FileSystem)
  using file = fs.open(path)
  return file.stat().size
}
// Output: 5

open requires a Scope and registers closing automatically. using makes that acquisition belong to the enclosing block, so the handle closes when the function exits, including on failure or interruption. Without using, the acquisition keeps Scope in R and belongs to the caller's scope.

Supplying platform implementations

// main.vibe
import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
function main() {
  const fs = Effect.service(FileSystem.FileSystem)
  using path = fs.makeTempFileScoped()
  fs.writeFileString(path, "Project notes")
  Console.log(fs.readFileString(path))
}
 
NodeRuntime.runMain(() => Effect.provide(() => main(), NodeFileSystem.layer))
// Output: Project notes

NodeFileSystem.layer supplies just the file-system implementation. NodeServices.layer supplies the broader Node platform bundle, and Bun applications use the corresponding Bun adapter. Application functions need only FileSystem, not a concrete adapter package.

Replacing behavior in tests

// run: provide FilesTest { Console.log(readFixture()) }
function readFixture() {
  const fs = Effect.service(FileSystem.FileSystem)
  if (!fs.exists("/fixtures/title.txt")) return "Untitled"
  return fs.readFileString("/fixtures/title.txt")
}
 
const FilesTest = FileSystem.layerNoop({
  exists: (path) => path === "/fixtures/title.txt",
  readFileString: () => "Release notes"
})
// Output: Release notes
// files.test.vibe
import { it } from "@effect/vitest"
 
it.effect("reads the configured title") {
  const title = Effect.provide(() => readFixture(), FilesTest)
  if (title !== "Release notes") throw new Error("Unexpected title")
}
// Output:

layerNoop supplies a complete service with selected method overrides. Its defaults fail or die for unsupported operations; it is not an in-memory file system. Override every operation the test exercises and let unexpected access expose a missing test behavior.

Coming from Effect

Test overrides use ordinary VibeLang functions. Contextual typing converts them to the Effect-returning methods the service interface expects, so readFileString: () => "Release notes" replaces a TypeScript Effect.succeed body.


The matching Effect page: effect.website/docs/v4/platform/file-system