| name | effect-filesystem |
| description | Use Effect FileSystem for platform-abstract file I/O with Node.js/Bun layers or custom implementations. |
FileSystem Platform Abstraction
Use effect FileSystem for platform-abstract file I/O. Stock layers are provided for Node.js and Bun; @effect/platform-browser does not provide a FileSystem layer in beta.74, so browser code needs a custom/injected implementation.
Basic Pattern
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const content = yield* fs.readFileString('path/to/file.txt');
return content;
});
Reading Operations
Read File (Binary)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const readBinary = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const bytes = yield* fs.readFile('data.bin');
return bytes;
});
Read File (String)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const readText = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const content = yield* fs.readFileString('config.json');
return content;
});
Stream File
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const streamFile = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const stream = fs.stream('large-file.log');
return stream;
});
Read Directory
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const listFiles = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const entries = yield* fs.readDirectory('src/');
return entries;
});
Read Symbolic Link
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const readLink = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const target = yield* fs.readLink('symlink');
return target;
});
Writing Operations
Write File (Binary)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const writeBinary = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
yield* fs.writeFile('output.bin', data);
});
Write File (String)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const writeText = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.writeFileString('output.txt', 'Hello, World!');
});
Sink (Stream Writing)
import { FileSystem } from 'effect';
import { Effect, Stream, pipe } from 'effect';
const writeStream = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const sink = fs.sink('output.log');
yield* pipe(
Stream.fromIterable(['line 1\n', 'line 2\n', 'line 3\n']),
Stream.mapEffect((s) => Effect.succeed(new TextEncoder().encode(s))),
Stream.run(sink)
);
});
File Operations
Copy File
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const copyFile = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.copyFile('source.txt', 'dest.txt');
});
Copy (Recursive Directory)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const copyDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.copy('src-dir/', 'dest-dir/');
});
Rename/Move
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const renameFile = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.rename('old-name.txt', 'new-name.txt');
});
Remove
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const removeFile = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.remove('file.txt');
});
const removeDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.remove('directory/', { recursive: true });
});
Open File Handle
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const useFileHandle = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* Effect.scoped(
Effect.gen(function* () {
const file = yield* fs.open('data.txt', { flag: 'r' });
const buffer = new Uint8Array(1024);
const bytesRead = yield* file.read(buffer);
})
);
});
Directory Operations
Make Directory
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const createDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.makeDirectory('new-dir/');
yield* fs.makeDirectory('path/to/nested/dir/', { recursive: true });
});
Make Temp Directory
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const useTempDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const tempPath = yield* fs.makeTempDirectory();
yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
yield* fs.remove(tempPath, { recursive: true });
});
Make Temp Directory (Scoped)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const useScopedTempDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const tempPath = yield* fs.makeTempDirectoryScoped();
yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
}).pipe(Effect.scoped);
Metadata Operations
Stat (File Info)
import { FileSystem } from 'effect';
import { Effect, Console } from 'effect';
const getFileInfo = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const info = yield* fs.stat('file.txt');
yield* Console.log(`Type: ${info.type}`);
yield* Console.log(`Size: ${info.size}`);
yield* Console.log(`Modified: ${info.mtime}`);
yield* Console.log(`Accessed: ${info.atime}`);
yield* Console.log(`Created: ${info.birthtime}`);
});
Access (Check Permissions)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const checkAccess = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.access('file.txt', { readable: true });
yield* fs.access('file.txt', { writable: true });
yield* fs.access('script.sh', { ok: true });
});
Exists
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const fileExists = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const exists = yield* fs.exists('file.txt');
return exists;
});
Real Path (Resolve Symlinks)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const resolvePath = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const realPath = yield* fs.realPath('symlink-or-relative-path');
return realPath;
});
Permission Operations
Change Mode (chmod)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const changeMode = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.chmod('script.sh', 0o755);
});
Change Owner (chown)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const changeOwner = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.chown('file.txt', 1000, 1000);
});
Update Times (utimes)
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const updateTimes = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const now = new Date();
yield* fs.utimes('file.txt', now, now);
});
Links
Hard Link
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const createHardLink = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.link('original.txt', 'hardlink.txt');
});
Symbolic Link
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const createSymlink = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
yield* fs.symlink('target.txt', 'symlink.txt');
});
Watching
Watch Files/Directories
import { FileSystem } from 'effect';
import { Effect, Stream, Console, pipe } from 'effect';
const watchFiles = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const events = fs.watch('src/');
return events;
});
const consumeWatchEvents = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const events = fs.watch('config/');
yield* pipe(
events,
Stream.runForEach((event) =>
Console.log(`Event: ${event._tag}, Path: ${event.path}`)
)
);
});
Size Helpers
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const { Size, KiB, MiB, GiB, TiB, PiB } = FileSystem;
const oneKb = Size(1024);
const tenKb = KiB(10);
const oneMb = MiB(1);
const fiveGb = GiB(5);
const oneTb = TiB(1);
const onePb = PiB(1);
const checkFileSize = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const info = yield* fs.stat('large-file.bin');
const maxSize = MiB(100);
if (info.size > BigInt(maxSize)) {
yield* Effect.fail(new Error('File too large'));
}
});
Error Handling
SystemErrorTag Values
FileSystem operations fail with PlatformError containing a SystemErrorTag:
AlreadyExists - File/directory already exists
BadResource - Invalid file descriptor or handle
Busy - Resource is busy
InvalidData - Invalid data format
NotFound - File/directory not found
PermissionDenied - Insufficient permissions
TimedOut - Operation timed out
UnexpectedEof - Unexpected end of file
Unknown - Unknown error
WouldBlock - Operation would block
WriteZero - Write operation wrote zero bytes
Error Handling Pattern
import { FileSystem } from 'effect';
import { Effect, pipe } from 'effect';
const readConfigWithFallback = pipe(
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
return yield* fs.readFileString('config.json');
}),
Effect.catchTag('PlatformError', (error) => {
if (error.reason._tag === 'NotFound') {
return Effect.succeed('{}');
}
if (error.reason._tag === 'PermissionDenied') {
return Effect.fail(
new Error('Cannot read config: permission denied')
);
}
return Effect.fail(error);
})
);
Typed Error Recovery
import { FileSystem } from 'effect';
import { Effect, Schema, pipe } from 'effect';
class ConfigNotFound extends Schema.TaggedErrorClass<ConfigNotFound>()(
'ConfigNotFound',
{
path: Schema.String
}
) {}
class ConfigInvalid extends Schema.TaggedErrorClass<ConfigInvalid>()(
'ConfigInvalid',
{
path: Schema.String,
reason: Schema.String
}
) {}
const readConfig = (path: string) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const content = yield* pipe(
fs.readFileString(path),
Effect.mapError((error) =>
error.reason._tag === 'NotFound'
? new ConfigNotFound({ path })
: new ConfigInvalid({ path, reason: error.message })
)
);
return content;
});
Scoped Resources Pattern
import { FileSystem } from 'effect';
import { Effect } from 'effect';
const processInTempDir = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const tempDir = yield* fs.makeTempDirectoryScoped();
const inputPath = `${tempDir}/input.txt`;
const outputPath = `${tempDir}/output.txt`;
yield* fs.writeFileString(inputPath, 'data');
const content = yield* fs.readFileString(inputPath);
yield* fs.writeFileString(outputPath, content.toUpperCase());
const result = yield* fs.readFileString(outputPath);
return result;
}).pipe(Effect.scoped);
Layer Provision
Node.js
import { FileSystem } from 'effect';
import { NodeFileSystem } from '@effect/platform-node';
import { Effect } from 'effect';
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
return yield* fs.readFileString('data.txt');
});
const runnable = program.pipe(Effect.provide(NodeFileSystem.layer));
Effect.runPromise(runnable);
Bun
import { FileSystem } from 'effect';
import { BunFileSystem } from '@effect/platform-bun';
import { Effect } from 'effect';
declare const program: Effect.Effect<string, never, FileSystem.FileSystem>;
const runnable = program.pipe(Effect.provide(BunFileSystem.layer));
Effect.runPromise(runnable);
DO
- Import from
effect
- Use
yield* FileSystem.FileSystem for service injection
- Provide platform layer at entry point only
- Use scoped temp directories with
makeTempDirectoryScoped
- Handle
PlatformError with catchTag("PlatformError", ...)
- Use size helpers:
Size(), KiB(), MiB(), GiB(), TiB(), PiB()
- Stream large files with
stream() and sink()
DON'T
- Import
node:fs, fs/promises, or platform-specific modules in business logic
- Use synchronous fs operations
- Forget to cleanup temp directories (use scoped version)
- Mix platform-specific code with business logic
- Use
Date.now() - use Clock service instead (see testability requirements)
- Hardcode platform-specific paths - use
Path service for path operations