FileSystem
The FileSystem service is Effect’s abstraction over the file system. Every
method returns an Effect that fails with a PlatformError
instead of throwing, so missing files, permission errors, and bad arguments all
flow through the typed error channel. You depend on the abstract FileSystem
interface and provide a concrete implementation — NodeFileSystem.layer on
Node.js — at the edge of your program.
import { NodeFileSystem } from "@effect/platform-node"import { Context, Effect, FileSystem, Layer, PlatformError } from "effect"
// A small service that persists JSON snapshots to a directory.class SnapshotStore extends Context.Service<SnapshotStore, { readonly save: (name: string, value: unknown) => Effect.Effect<void, PlatformError.PlatformError> readonly load: (name: string) => Effect.Effect<unknown, PlatformError.PlatformError>}>()("app/SnapshotStore") { static readonly layer = Layer.effect( SnapshotStore, Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const dir = "snapshots"
// Ensure the target directory exists. `recursive` makes this a no-op // when the directory is already present, like `mkdir -p`. yield* fs.makeDirectory(dir, { recursive: true })
const save = (name: string, value: unknown) => // `writeFileString` writes (and creates) the file in one call. fs.writeFileString(`${dir}/${name}.json`, JSON.stringify(value, null, 2))
const load = (name: string) => fs.readFileString(`${dir}/${name}.json`).pipe( Effect.map((text) => JSON.parse(text) as unknown) )
return { save, load } as const }) ).pipe( // The store needs a concrete FileSystem; wire in the Node implementation. Layer.provide(NodeFileSystem.layer) )}
const program = Effect.gen(function*() { const store = yield* SnapshotStore yield* store.save("user-42", { id: 42, name: "Ada" }) const restored = yield* store.load("user-42") yield* Effect.log(restored)}).pipe(Effect.provide(SnapshotStore.layer))Getting the service
Section titled “Getting the service”Request the service with yield* FileSystem.FileSystem inside an effect, then
provide a platform layer when you run the program. On Node.js that layer is
NodeFileSystem.layer from @effect/platform-node:
import { NodeFileSystem } from "@effect/platform-node"import { Effect, FileSystem } from "effect"
const program = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem return yield* fs.readFileString("package.json")})
// `FileSystem.FileSystem` is the requirement; the Node layer satisfies it.const runnable = program.pipe(Effect.provide(NodeFileSystem.layer))For tests you do not need a real file system: FileSystem.layerNoop provides a
stub whose behavior you override per test (see the reference at the end of this
page).
Reading and writing whole files
Section titled “Reading and writing whole files”The most common operations come in byte and string flavours. Use the String
variants when you are working with text and the raw variants when you need exact
bytes. Both writers accept flag (an OpenFlag) and mode
options:
import { Effect, FileSystem } from "effect"
const program = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem
// Text helpers return / accept `string`. yield* fs.writeFileString("notes.txt", "first line\n") const text = yield* fs.readFileString("notes.txt") // => "first line\n"
// `readFileString` takes an optional encoding (passed to TextDecoder). const latin1 = yield* fs.readFileString("legacy.txt", "latin1")
// Byte helpers return / accept `Uint8Array`. const bytes: Uint8Array = yield* fs.readFile("notes.txt") yield* fs.writeFile("copy.bin", bytes)
// `flag: "a"` appends instead of truncating; `mode` sets POSIX permissions. yield* fs.writeFileString("notes.txt", "second line\n", { flag: "a", mode: 0o644 })
yield* Effect.log(`${text.length} chars, ${bytes.length} bytes`)})Inspecting the file system
Section titled “Inspecting the file system”exists, stat, and readDirectory let you query the file system before
acting on it. Note that timestamps on stat are wrapped in Option because not
every platform reports them:
import { Effect, FileSystem, Option } from "effect"
const describe = Effect.fn("describe")(function*(path: string) { const fs = yield* FileSystem.FileSystem
if (!(yield* fs.exists(path))) { return `${path} does not exist` }
// `stat` returns a `File.Info`: type, size (a branded bigint), mode, and // optional timestamps. const info = yield* fs.stat(path)
const modified = Option.match(info.mtime, { onNone: () => "unknown", onSome: (date) => date.toISOString() })
return `${info.type} — ${info.size} bytes — modified ${modified}`})To enumerate a directory, readDirectory returns the entry names; pass
recursive: true to walk nested directories:
import { Effect, FileSystem } from "effect"
const listTree = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem return yield* fs.readDirectory("src", { recursive: true })})Directories and moving things around
Section titled “Directories and moving things around”makeDirectory, remove, copy, copyFile, and rename cover the usual
directory and path-management tasks. recursive and force options mirror the
familiar shell flags:
import { Effect, FileSystem } from "effect"
const program = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem
// `recursive: true` is like `mkdir -p` — creates parents, no error if present. yield* fs.makeDirectory("build/artifacts", { recursive: true })
// `copy` is recursive like `cp -r`; `copyFile` copies a single file. yield* fs.copy("src", "build/src") yield* fs.copyFile("README.md", "build/README.md")
// `rename` moves or renames a file or directory. yield* fs.rename("build/README.md", "build/README.txt")
// `recursive` removes nested entries; `force` ignores a missing path. yield* fs.remove("build", { recursive: true, force: true })})Streaming large files
Section titled “Streaming large files”Reading a multi-gigabyte file into memory with readFile is wasteful. The
stream method returns a Stream of byte chunks so you can
process a file incrementally, and sink gives you a writable counterpart. Here
we count the lines of a file without ever holding it all in memory:
import { Effect, FileSystem, Stream } from "effect"
const countLines = Effect.fn("countLines")(function*(path: string) { const fs = yield* FileSystem.FileSystem
return yield* fs.stream(path).pipe( Stream.decodeText(), // Uint8Array chunks -> string chunks Stream.splitLines, // re-chunk on line boundaries Stream.runFold(() => 0, (count) => count + 1) )})You can tune chunkSize, offset, and bytesToRead on stream to read a
specific window of a file. Use the Size helpers (KiB,
MiB, …) for readable byte counts:
import { Effect, FileSystem, Stream } from "effect"
const tail64KiB = Effect.fn("tail64KiB")(function*(path: string) { const fs = yield* FileSystem.FileSystem const { size } = yield* fs.stat(path)
// Read the last 64 KiB in 8 KiB chunks. return fs.stream(path, { offset: size - FileSystem.KiB(64), bytesToRead: FileSystem.KiB(64), chunkSize: FileSystem.KiB(8) })})sink is the inverse: pipe a Stream<Uint8Array> into a file. Combine it with
Stream.run to copy or transform data with backpressure:
import { Effect, FileSystem, Stream } from "effect"
const copyStreamed = Effect.fn("copyStreamed")(function*(from: string, to: string) { const fs = yield* FileSystem.FileSystem // Read `from` as a Stream and drain it into `to` via the writable Sink. return yield* Stream.run(fs.stream(from), fs.sink(to))})Temporary files and scopes
Section titled “Temporary files and scopes”Temporary files and directories are tied to a Scope
through the *Scoped variants, so they are deleted automatically when the scope
closes — even if the effect fails:
import { Effect, FileSystem } from "effect"
const withScratch = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem
// Created inside the surrounding scope; removed when the scope closes. const dir = yield* fs.makeTempDirectoryScoped({ prefix: "build-" })
yield* fs.writeFileString(`${dir}/manifest.json`, "{}") yield* Effect.log(`working in ${dir}`)}).pipe( // `Effect.scoped` provides and closes the scope, triggering cleanup. Effect.scoped)The non-scoped makeTempFile / makeTempDirectory return the path and leave
cleanup to you — reach for them only when the temp resource must outlive the
current scope.
Opening a file with fs.open is similarly scoped: the returned File handle is
closed when the scope ends, so you never leak file descriptors.
Watching for changes
Section titled “Watching for changes”watch returns a Stream of WatchEvent values (Create,
Update, Remove) so you can react to file system activity — handy for dev
tooling and hot reload:
import { Effect, FileSystem, Stream } from "effect"
const watchSrc = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem
yield* fs.watch("src").pipe( Stream.runForEach((event) => // event._tag is "Create" | "Update" | "Remove"; event.path is the path. Effect.log(`${event._tag}: ${event.path}`) ) )})The low-level File handle
Section titled “The low-level File handle”When whole-file and streaming helpers are not enough, open gives you a
File handle for positioned reads and writes. The handle
is scoped, so it is closed automatically:
import { Effect, FileSystem } from "effect"
const patch = Effect.fn("patch")(function*(path: string) { const fs = yield* FileSystem.FileSystem
// The handle is closed when the surrounding scope ends. const file = yield* fs.open(path, { flag: "r+" })
// Move the cursor 10 bytes from the start, then overwrite 5 bytes. yield* file.seek(10, "start") yield* file.writeAll(new TextEncoder().encode("HELLO")) yield* file.sync // flush to disk
// `readAlloc` allocates a buffer and returns Option.none() at EOF. yield* file.seek(0, "start") return yield* file.readAlloc(FileSystem.KiB(4))}, Effect.scoped)Handling errors
Section titled “Handling errors”Because failures are values, narrow them with Effect.catchTag. A
PlatformError exposes a human-readable message and a structured reason
(either a SystemError or a BadArgument):
import { Effect, FileSystem } from "effect"
const readOrDefault = Effect.fn("readOrDefault")(function*(path: string) { const fs = yield* FileSystem.FileSystem return yield* fs.readFileString(path)}).pipe( // Fall back to an empty document when the read fails for any reason. Effect.catchTag("PlatformError", (error) => Effect.as(Effect.logWarning(`read failed: ${error.message}`), "") ))See Manipulate paths with Path for building the paths you pass to these methods in a cross-platform way.
FileSystem reference
Section titled “FileSystem reference”Every method below is accessed off the service value
(const fs = yield* FileSystem.FileSystem). All return effects fail with
PlatformError.
access
Section titled “access”Checks whether a path can be accessed, optionally at a given access level
(readable / writable). Fails if the check does not pass.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.access("config.json", { readable: true }) // => void (succeeds) | fails with PlatformError if not readable})Changes the permission bits of a file to the given numeric mode.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.chmod("deploy.sh", 0o755) // rwxr-xr-x // => void})Changes the owner (uid) and group (gid) of a file.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.chown("data.db", 1000, 1000) // => void})Recursively copies a file or directory from fromPath to toPath (like
cp -r). Options: overwrite, preserveTimestamps.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.copy("src", "backup/src", { overwrite: true }) // => void})copyFile
Section titled “copyFile”Copies a single file from fromPath to toPath.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.copyFile("a.txt", "b.txt") // => void})exists
Section titled “exists”Returns true if a path exists, false otherwise. Unlike access, a missing
path is not an error.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const has = yield* fs.exists("config.json") // => true | false})Creates a hard link at toPath pointing to fromPath.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.link("original.txt", "hardlink.txt") // => void})makeDirectory
Section titled “makeDirectory”Creates a directory. Options: recursive (create missing parents, like
mkdir -p) and mode.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.makeDirectory("a/b/c", { recursive: true }) // => void})makeTempDirectory
Section titled “makeTempDirectory”Creates a temporary directory and returns its path. Options: directory (parent
location) and prefix. You are responsible for removing it.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const dir = yield* fs.makeTempDirectory({ prefix: "job-" }) // => "/tmp/job-Xy3kP1" (path string)})makeTempDirectoryScoped
Section titled “makeTempDirectoryScoped”Like makeTempDirectory, but the directory is deleted when the surrounding
Scope closes. Requires Scope in the environment.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const dir = yield* fs.makeTempDirectoryScoped({ prefix: "job-" }) // => path string; removed automatically on scope close}).pipe(Effect.scoped)makeTempFile
Section titled “makeTempFile”Creates a temporary file with a randomly generated name and returns its path.
Options: directory, prefix, suffix.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const file = yield* fs.makeTempFile({ suffix: ".json" }) // => "/tmp/Ab12Cd.json" (path string)})makeTempFileScoped
Section titled “makeTempFileScoped”Like makeTempFile, but the file is deleted when the Scope closes.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const file = yield* fs.makeTempFileScoped({ prefix: "upload-" }) // => path string; removed automatically on scope close}).pipe(Effect.scoped)Opens a file and returns a scoped File handle (closed on
scope exit). Options: flag (an OpenFlag, default "r") and
mode.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const file = yield* fs.open("log.txt", { flag: "a" }) // => File handle (auto-closed when scope ends)}).pipe(Effect.scoped)readDirectory
Section titled “readDirectory”Lists the entry names of a directory. Option: recursive to walk nested
directories.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const entries = yield* fs.readDirectory("src") // => ["index.ts", "util.ts", ...]})readFile
Section titled “readFile”Reads an entire file into a Uint8Array.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const bytes = yield* fs.readFile("image.png") // => Uint8Array(...)})readFileString
Section titled “readFileString”Reads an entire file and decodes it to a string. Optional encoding is passed
to TextDecoder (default UTF-8).
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const text = yield* fs.readFileString("notes.txt") // => "file contents"})readLink
Section titled “readLink”Reads the target of a symbolic link.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const target = yield* fs.readLink("current") // => "releases/v2" (the link's destination)})realPath
Section titled “realPath”Resolves a path to its canonical absolute pathname, following symlinks.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const abs = yield* fs.realPath("./src/../package.json") // => "/home/me/project/package.json"})remove
Section titled “remove”Removes a file or directory. Options: recursive (remove nested entries) and
force (ignore a missing path).
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.remove("tmp", { recursive: true, force: true }) // => void})rename
Section titled “rename”Moves or renames a file or directory from oldPath to newPath.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.rename("draft.md", "final.md") // => void})Creates a writable Sink that writes incoming Uint8Array
chunks to a file. Options: flag (default "w") and mode.
import { Effect, FileSystem, Stream } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const data = Stream.make(new TextEncoder().encode("hello\n")) yield* Stream.run(data, fs.sink("out.txt")) // => void (file "out.txt" now contains "hello\n")})Returns metadata for a path as a File.Info.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const info = yield* fs.stat("package.json") // => { type: "File", size: 1234n, mode: 420, mtime: Option.some(...), ... }})stream
Section titled “stream”Creates a readable Stream of Uint8Array chunks for a file.
Options: offset (start position), bytesToRead (limit), and chunkSize
(default 64 KiB) — all SizeInput values.
import { Effect, FileSystem, Stream } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const bytes = yield* fs.stream("big.log", { chunkSize: FileSystem.KiB(16) }).pipe( Stream.runFold(() => 0, (acc, chunk) => acc + chunk.length) ) // => total number of bytes read})symlink
Section titled “symlink”Creates a symbolic link at toPath pointing to fromPath.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.symlink("releases/v2", "current") // => void})truncate
Section titled “truncate”Truncates a file to a given length (a SizeInput),
defaulting to 0.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.truncate("data.bin", FileSystem.KiB(4)) // => void (file is now exactly 4 KiB)})utimes
Section titled “utimes”Sets the access (atime) and modification (mtime) timestamps of a file.
Accepts Date or epoch-millisecond numbers.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const now = new Date() yield* fs.utimes("notes.txt", now, now) // => void})Creates a Stream of WatchEvent values for a
file or directory. Watch behavior is platform-dependent.
import { Effect, FileSystem, Stream } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.watch("src").pipe( Stream.runForEach((e) => Effect.log(`${e._tag} ${e.path}`)) ) // => emits { _tag: "Update", path: "src/index.ts" }, ...})writeFile
Section titled “writeFile”Writes a Uint8Array to a file. Options: flag (an OpenFlag) and
mode.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.writeFile("out.bin", new Uint8Array([1, 2, 3])) // => void})writeFileString
Section titled “writeFileString”Encodes a string (UTF-8) and writes it to a file. Same flag / mode options
as writeFile; use flag: "a" to append.
import { Effect, FileSystem } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.writeFileString("log.txt", "line\n", { flag: "a" }) // => void})The File interface
Section titled “The File interface”open returns a File handle for low-level, positioned I/O. It carries a
branded fd (File.Descriptor) and the members below.
file.stat
Section titled “file.stat”An effect that returns the handle’s File.Info.
const info = yield* file.stat// => { type: "File", size: 42n, ... }file.seek
Section titled “file.seek”Moves the read/write cursor by offset (SizeInput) relative
to a SeekMode ("start" or "current"). Cannot fail.
yield* file.seek(128, "start") // absolute position 128yield* file.seek(16, "current") // 16 bytes forward// => voidfile.read
Section titled “file.read”Reads bytes into the provided buffer and returns the number of bytes read as a
Size (bigint).
const buffer = new Uint8Array(64)const bytesRead = yield* file.read(buffer)// => 64n (or fewer near EOF)file.readAlloc
Section titled “file.readAlloc”Allocates a buffer of the given size (SizeInput), reads
into it, and returns Option.some(bytes) — or Option.none() at end of file.
import { Option } from "effect"
const chunk = yield* file.readAlloc(FileSystem.KiB(4))Option.match(chunk, { onNone: () => "EOF", onSome: (bytes) => `read ${bytes.length} bytes`})// => "read 4096 bytes" | "EOF"file.write
Section titled “file.write”Writes buffer at the current cursor and returns the number of bytes written as
a Size.
const written = yield* file.write(new TextEncoder().encode("hi"))// => 2nfile.writeAll
Section titled “file.writeAll”Writes the entire buffer, looping internally until all bytes are flushed.
yield* file.writeAll(new TextEncoder().encode("complete payload"))// => voidfile.truncate
Section titled “file.truncate”Truncates the open file to length (SizeInput), defaulting
to 0.
yield* file.truncate(FileSystem.KiB(1))// => voidfile.sync
Section titled “file.sync”An effect that flushes buffered data and metadata to the storage device.
yield* file.sync// => voidThe handle is closed automatically when its Scope ends — there is no explicit
close method; rely on Effect.scoped (or open inside a Layer.effect).
Supporting types
Section titled “Supporting types”File.Info
Section titled “File.Info”The metadata record returned by stat and file.stat. Timestamps and several
numeric fields are wrapped in Option because not all platforms report them.
| Field | Type | Notes |
|---|---|---|
type | File.Type | "File", "Directory", "SymbolicLink", … |
size | Size | byte count (branded bigint) |
mode | number | POSIX permission bits |
mtime / atime / birthtime | Option<Date> | modify / access / creation time |
dev | number | device id |
ino | Option<number> | inode number |
nlink | Option<number> | hard link count |
uid / gid | Option<number> | owner / group ids |
rdev | Option<number> | device id for special files |
blksize | Option<Size> | block size for I/O |
blocks | Option<number> | number of allocated blocks |
import { Effect, FileSystem, Option } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const info: FileSystem.File.Info = yield* fs.stat("notes.txt") const created = Option.getOrElse(info.birthtime, () => new Date(0)) // => info.type === "File", info.size is a bigint})File.Type
Section titled “File.Type”The kind of a file system entry: "File", "Directory", "SymbolicLink",
"BlockDevice", "CharacterDevice", "FIFO", "Socket", or "Unknown".
const info = yield* fs.stat("/dev/null")if (info.type === "CharacterDevice") { /* ... */ }File.Descriptor
Section titled “File.Descriptor”A branded number identifying an open file, exposed as file.fd. Construct one
with FileSystem.FileDescriptor when implementing a custom FileSystem.
import { FileSystem } from "effect"
const fd = FileSystem.FileDescriptor(3)// => branded 3 (File.Descriptor)OpenFlag
Section titled “OpenFlag”POSIX-style open mode accepted by open, writeFile, writeFileString, and
sink. The full set: "r", "r+", "w", "wx", "w+", "wx+", "a",
"ax", "a+", "ax+". Read more in the inline list below.
"r"— read; file must exist."r+"— read/write; file must exist."w"— write; truncates or creates."wx"— like"w"but fails if the file exists."w+"— read/write; truncates or creates."wx+"— like"w+"but fails if the file exists."a"— append; creates if missing."ax"— like"a"but fails if the file exists."a+"— read/append; creates if missing."ax+"— like"a+"but fails if the file exists.
yield* fs.open("new.txt", { flag: "wx" }) // create exclusivelySeekMode
Section titled “SeekMode”Reference point for file.seek: "start" (from the beginning of the file) or
"current" (from the current cursor position).
yield* file.seek(0, "start") // rewind to beginningyield* file.seek(16, "current") // advance 16 bytes from the cursorSize / SizeInput
Section titled “Size / SizeInput”Size is a branded bigint byte count. SizeInput is bigint | number | Size
— anywhere a size is accepted you may pass any of them. Use the constructors for
readable values; all return a Size.
import { FileSystem } from "effect"
FileSystem.Size(1024) // => 1024nFileSystem.KiB(64) // => 65536n (64 * 1024)FileSystem.MiB(10) // => 10485760nFileSystem.GiB(1) // => 1073741824nFileSystem.TiB(1) // => 1099511627776nFileSystem.PiB(2) // => 2251799813685248nWatchEvent
Section titled “WatchEvent”The events emitted by watch. A tagged union of { _tag: "Create", path },
{ _tag: "Update", path }, and { _tag: "Remove", path }.
import { Effect, FileSystem, Stream } from "effect"
Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* fs.watch(".").pipe( Stream.runForEach((event) => { switch (event._tag) { case "Create": return Effect.log(`created ${event.path}`) case "Update": return Effect.log(`changed ${event.path}`) case "Remove": return Effect.log(`deleted ${event.path}`) } }) )})Constructors and testing
Section titled “Constructors and testing”FileSystem.make
Section titled “FileSystem.make”Builds a concrete FileSystem from a partial implementation, deriving exists,
readFileString, stream, sink, and writeFileString from the core methods
you provide. Used by platform packages to define their layer.
import { FileSystem } from "effect"
declare const coreImpl: Omit< FileSystem.FileSystem, "~effect/platform/FileSystem" | "exists" | "readFileString" | "stream" | "sink" | "writeFileString">const fs = FileSystem.make(coreImpl)// => a full FileSystemFileSystem.makeNoop / FileSystem.layerNoop
Section titled “FileSystem.makeNoop / FileSystem.layerNoop”makeNoop returns a stub FileSystem for tests: by default exists returns
false, remove succeeds, most reads fail with NotFound, and temp operations
die as not implemented. Pass overrides for the methods a test needs.
layerNoop wraps the same overrides in a Layer.
import { Effect, FileSystem } from "effect"
const testLayer = FileSystem.layerNoop({ readFileString: () => Effect.succeed("mocked content"), exists: () => Effect.succeed(true)})
const program = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem return yield* fs.readFileString("anything.txt") // => "mocked content"}).pipe(Effect.provide(testLayer))isFile
Section titled “isFile”Type guard that narrows an unknown value to a File by
checking its runtime marker.
import { FileSystem } from "effect"
declare const value: unknownif (FileSystem.isFile(value)) { // value is FileSystem.File here}