name effect-platform-abstraction description Use Effect platform abstractions for cross-platform file I/O, process spawning, HTTP clients, cryptography, and terminal I/O. Apply when writing filesystem/process/HTTP/crypto/console code that must stay portable across Node.js, Bun, and browser adapters.
Platform Abstraction with Effect
Effect Source Reference
The Effect v4 source is available at ~/.cache/effect-v4/.
Browse and read files there directly to look up APIs, types, and implementations.
Reference this for:
FileSystem source: packages/effect/src/FileSystem.ts
Path source: packages/effect/src/Path.ts
Crypto source: packages/effect/src/Crypto.ts
Socket source: packages/effect/src/unstable/socket/
Platform layers: packages/platform-node/, packages/platform-bun/, and packages/platform-browser/
Migration guide: MIGRATION.md
Effect source: packages/effect/src/
Overview
Effect provides platform-independent abstractions with Node.js and Bun adapters, plus browser adapters for supported services such as HTTP and Crypto. Instead of using runtime-specific APIs directly, you write code once using Effect Platform services and provide the appropriate layer at the edge.
When to use this skill:
Writing file system operations
Spawning child processes or executing commands
Making HTTP requests
Generating cryptographic random bytes, UUIDs, or digests
Reading CLI arguments or environment variables
Performing console/terminal I/O
Working with paths across different operating systems
Building cross-platform applications or libraries
Why Effect Platform?
1. Cross-Platform Compatibility
Write once, run anywhere:
import { Effect , FileSystem } from 'effect' ;
const readConfig = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFileString ('config.json' );
});
2. Type-Safe Error Handling
All operations track errors in the Effect type signature:
import { Effect , FileSystem } from 'effect' ;
3. Resource Safety
Automatic cleanup with Scope:
import { Effect , FileSystem } from 'effect' ;
const program = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFile ('data.txt' );
});
4. Testability
Easy to mock and stub services:
import { Effect , FileSystem , Layer } from 'effect' ;
declare const myProgram : Effect .Effect <void , never , FileSystem .FileSystem >;
const TestFileSystem = Layer .succeed (
FileSystem .FileSystem ,
FileSystem .make ({
readFile : () => Effect .succeed (new Uint8Array ())
})
);
const test = myProgram.pipe (Effect .provide (TestFileSystem ));
5. Composability
Integrates naturally with Effect's service system:
import { Effect , FileSystem , Layer , Path , Context } from 'effect' ;
interface ConfigService {
readonly load : (name : string ) => Effect .Effect <string >;
}
const ConfigService = Context .Service <ConfigService >('ConfigService' );
const ConfigServiceLive = Layer .effect (
ConfigService ,
Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const path = yield * Path .Path ;
return {
load : (name : string ) =>
Effect .gen (function * () {
const configPath = path.join ('configs' , name);
return yield * fs.readFileString (configPath);
})
};
})
);
Core Platform Modules
FileSystem - File Operations
The FileSystem service provides comprehensive file and directory operations.
Anti-Pattern - Direct Node/Bun APIs:
import * as fs from 'fs' ;
import { readFile } from 'fs/promises' ;
const content = fs.readFileSync ('file.txt' , 'utf-8' );
const asyncContent = await readFile ('file.txt' , 'utf-8' );
declare const Bun : {
file : (path : string ) => { text : () => Promise <string > };
};
const file = Bun .file ('file.txt' );
const content = await file.text ();
Correct Pattern - FileSystem Service:
import { Effect , FileSystem } from 'effect' ;
const readFile = (path : string ) =>
Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFileString (path);
});
Common Operations:
import { Effect , FileSystem } from 'effect' ;
const fileOperations = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const text = yield * fs.readFileString ('data.txt' );
const bytes = yield * fs.readFile ('binary.dat' );
yield * fs.writeFileString ('output.txt' , 'Hello World' );
yield * fs.makeDirectory ('new-dir' , { recursive : true });
const files = yield * fs.readDirectory ('src' );
const stats = yield * fs.stat ('file.txt' );
const exists = yield * fs.exists ('config.json' );
yield * fs.copy ('source.txt' , 'dest.txt' );
yield * fs.rename ('old.txt' , 'new.txt' );
yield * fs.remove ('temp-file.txt' );
yield * fs.remove ('temp-dir' , { recursive : true });
const tempFile = yield * fs.makeTempFileScoped ();
yield * fs.writeFileString (tempFile, 'temporary data' );
});
Streaming Files:
import { Effect , FileSystem , Stream } from 'effect' ;
declare const processChunk : (chunk : Uint8Array ) => Effect .Effect <void >;
const processLargeFile = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const stream = fs.stream ('large-file.txt' , { chunkSize : 64 * 1024 });
yield * stream.pipe (
Stream .mapEffect ((chunk ) => processChunk (chunk)),
Stream .run (fs.sink ('output.txt' ))
);
});
Path - Path Manipulation
The Path service provides cross-platform path operations.
Anti-Pattern - Manual String Manipulation:
import path from 'path' ;
declare const process : { cwd : () => string };
declare const filename : string ;
const configPath = './config/' + filename + '.json' ;
const absPath = process.cwd () + '/' + configPath;
const joined = path.join ('src' , 'components' , 'Button.tsx' );
Correct Pattern - Path Service:
import { Effect , Path } from 'effect' ;
const buildPath = (filename : string ) =>
Effect .gen (function * () {
const path = yield * Path .Path ;
const configPath = path.join ('config' , `${filename} .json` );
const absolutePath = path.resolve (configPath);
const dir = path.dirname (absolutePath);
const base = path.basename (absolutePath);
const ext = path.extname (absolutePath);
const parsed = path.parse (absolutePath);
return absolutePath;
});
Path Operations:
import { Effect , Path } from 'effect' ;
const pathOps = Effect .gen (function * () {
const path = yield * Path .Path ;
const sep = path.sep ;
const filePath = path.join ('src' , 'lib' , 'utils.ts' );
const absolute = path.resolve ('..' , 'config' , 'app.json' );
const rel = path.relative ('/app/src' , '/app/dist' );
const isAbs = path.isAbsolute ('/usr/local' );
const normalized = path.normalize ('src/../lib/./utils.ts' );
const url = yield * path.toFileUrl ('/path/to/file' );
const fromUrl = yield * path.fromFileUrl (new URL ('file:///path/to/file' ));
});
ChildProcess - Process Execution
The ChildProcess and ChildProcessSpawner services enable safe process spawning.
Anti-Pattern - Direct child_process:
import { spawn, exec } from 'child_process' ;
import { promisify } from 'util' ;
const execAsync = promisify (exec);
const { stdout } = await execAsync ('ls -la' );
declare const Bun : {
spawn : (cmd : string [] ) => { stdout : ReadableStream };
};
declare const Response : {
new (stream : ReadableStream ): { text : () => Promise <string > };
};
const proc = Bun .spawn (['ls' , '-la' ]);
const output = await new Response (proc.stdout ).text ();
Correct Pattern - ChildProcess + ChildProcessSpawner:
import { ChildProcess , ChildProcessSpawner } from 'effect/unstable/process' ;
import { Effect , Stream } from 'effect' ;
const runCommand = Effect .gen (function * () {
const spawner = yield * ChildProcessSpawner .ChildProcessSpawner ;
const output = yield * spawner.string (ChildProcess .make ('ls' , ['-la' ]));
return output;
});
Advanced ChildProcess Usage:
import { ChildProcess , ChildProcessSpawner } from 'effect/unstable/process' ;
import { Console , Effect , Stream } from 'effect' ;
const commandExamples = Effect .gen (function * () {
const spawner = yield * ChildProcessSpawner .ChildProcessSpawner ;
const stdout = yield * spawner.string (ChildProcess .make ('git' , ['status' ]));
const lines = yield * spawner.lines (
ChildProcess .make ('git' , ['log' , '--pretty=format:%s' , '-n' , '10' ])
);
const pipeline = ChildProcess .make ('cat' , ['file.txt' ]).pipe (
ChildProcess .pipeTo (ChildProcess .make ('grep' , ['error' ])),
ChildProcess .pipeTo (ChildProcess .make ('wc' , ['-l' ]))
);
const pipelineOutput = yield * spawner.string (pipeline);
const withEnv = ChildProcess .make ('node' , ['script.js' ], {
env : { NODE_ENV : 'production' , API_KEY : 'secret' },
extendEnv : true
});
const handle = yield * spawner.spawn (
ChildProcess .make ('npm' , ['run' , 'build' ])
);
yield * handle.all .pipe (
Stream .decodeText (),
Stream .splitLines ,
Stream .runForEach ((line ) => Console .log (`[build] ${line} ` ))
);
const exitCode = yield * handle.exitCode ;
return exitCode;
});
Terminal - Terminal I/O
The Terminal service provides interactive terminal capabilities.
Anti-Pattern - Direct Console:
declare const console : {
log : (msg : string ) => void ;
error : (msg : string ) => void ;
};
declare const process : {
stdout : { write : (msg : string ) => void };
};
declare const prompt : (msg : string ) => string | null ;
console .log ('Hello World' );
console .error ('Error occurred' );
process.stdout .write ('Output\n' );
const input = prompt ('Enter name:' );
Correct Pattern - Terminal Service:
import { Effect , Terminal } from 'effect' ;
const interactiveProgram = Effect .gen (function * () {
const terminal = yield * Terminal .Terminal ;
yield * terminal.display ('Hello World\n' );
const name = yield * terminal.readLine ;
yield * terminal.display (`Welcome, ${name} !\n` );
const cols = yield * terminal.columns ;
const rows = yield * terminal.rows ;
yield * terminal.display (`Terminal size: ${cols} x${rows} \n` );
});
For Simple Logging - Use Console or Effect.log:
import { Console , Effect } from 'effect' ;
const logging = Effect .gen (function * () {
yield * Console .log ('Info message' );
yield * Console .error ('Error message' );
yield * Console .warn ('Warning' );
yield * Console .debug ('Debug info' );
});
const structuredLog = Effect .gen (function * () {
yield * Effect .log ('Operation started' );
yield * Effect .logDebug ('Debug details' );
yield * Effect .logError ('Error occurred' );
yield * Effect .log ('User action' ).pipe (
Effect .annotateLogs ('userId' , '123' ),
Effect .annotateLogs ('action' , 'login' )
);
});
Crypto - Cryptographic Randomness, UUIDs, and Digests
The Crypto.Crypto service provides platform-backed cryptographic random bytes, UUIDv4/v7 generation, and message digests. Prefer it over globalThis.crypto, crypto.randomUUID(), or ad-hoc randomness when code should stay platform-abstract and testable.
import { Crypto , Effect } from 'effect' ;
const cryptoProgram = Effect .gen (function * () {
const crypto = yield * Crypto .Crypto ;
const bytes = yield * crypto.randomBytes (32 );
const uuidV4 = yield * crypto.randomUUIDv4 ;
const uuidV7 = yield * crypto.randomUUIDv7 ;
const digest = yield * crypto.digest ('SHA-256' , bytes);
return { bytes, uuidV4, uuidV7, digest };
});
NodeServices.layer and BunServices.layer include Crypto.Crypto. Browser applications can provide BrowserCrypto.layer from @effect/platform-browser.
HttpClient - HTTP Requests
The HttpClient service provides type-safe HTTP operations.
Anti-Pattern - Direct fetch/axios:
declare const fetch : (url : string ) => Promise <{ json : () => Promise <unknown > }>;
const response = await fetch ('https://api.example.com/data' );
const data = await response.json ();
import axios from 'axios' ;
declare const axios : {
get : (url : string ) => Promise <{ data : unknown }>;
};
const result = await axios.get ('https://api.example.com/data' );
Correct Pattern - HttpClient Service:
import { HttpClient , HttpClientRequest } from 'effect/unstable/http' ;
import { Effect } from 'effect' ;
const fetchData = Effect .gen (function * () {
const client = yield * HttpClient .HttpClient ;
const response = yield * client.get ('https://api.example.com/data' );
const data = yield * response.json ;
return data;
});
Advanced HTTP Operations:
import {
HttpClient ,
HttpClientRequest ,
HttpClientResponse
} from 'effect/unstable/http' ;
import { Effect , Schema , Schedule } from 'effect' ;
const User = Schema .Struct ({
id : Schema .Number ,
name : Schema .String ,
email : Schema .String
});
const httpExamples = Effect .gen (function * () {
const client = yield * HttpClient .HttpClient ;
const getUsers = client.get ('https://api.example.com/users' , {
urlParams : { page : '1' , limit : '10' }
});
const createUser = HttpClientRequest .post (
'https://api.example.com/users'
).pipe (
HttpClientRequest .bodyJsonUnsafe ({
name : 'John Doe' ,
email : 'john@example.com'
}),
client.execute
);
const withAuthRequest = HttpClientRequest .get (
'https://api.example.com/protected'
).pipe (HttpClientRequest .setHeader ('Authorization' , 'Bearer token' ));
const withAuth = client.execute (withAuthRequest);
const users = yield * client
.get ('https://api.example.com/users' )
.pipe (
Effect .flatMap (
HttpClientResponse .schemaBodyJson (Schema .Array (User ))
)
);
const safeRequest = client.get ('https://api.example.com/data' ).pipe (
Effect .catchTag ('HttpClientError' , (error ) => {
switch (error.reason ._tag ) {
case 'TransportError' :
return Effect .succeed ({ error : 'Network error' });
case 'StatusCodeError' :
return Effect .succeed ({
error : `HTTP ${error.response?.status} `
});
default :
return Effect .succeed ({
error : `Client error: ${error.reason._tag} `
});
}
})
);
const withRetries = client.get ('https://api.example.com/data' ).pipe (
Effect .retry ({
times : 3 ,
schedule : Schedule .exponential ('100 millis' )
})
);
return users;
});
KeyValueStore - Key-Value Storage
The KeyValueStore service provides platform-independent key-value storage.
Anti-Pattern - Direct localStorage/file-based storage:
declare const localStorage : {
setItem : (key : string , value : string ) => void ;
getItem : (key : string ) => string | null ;
};
localStorage .setItem ('key' , 'value' );
const value = localStorage .getItem ('key' );
import fs from 'fs' ;
declare const fs : {
writeFileSync : (path : string , data : string ) => void ;
readFileSync : (path : string , encoding : string ) => string ;
};
fs.writeFileSync ('.cache/key' , 'value' );
const value2 = fs.readFileSync ('.cache/key' , 'utf-8' );
Correct Pattern - KeyValueStore Service:
import { KeyValueStore } from 'effect/unstable/persistence' ;
import { Effect , Schema } from 'effect' ;
const cacheData = Effect .gen (function * () {
const store = yield * KeyValueStore .KeyValueStore ;
yield * store.set ('user:123' , 'John Doe' );
const name = yield * store.get ('user:123' );
const bytes = yield * store.getUint8Array ('avatar:123' );
const hasUser = yield * store.has ('user:123' );
yield * store.remove ('user:123' );
yield * store.clear ;
return name;
});
Schema-Based Store:
import { KeyValueStore } from 'effect/unstable/persistence' ;
import { Effect , Schema } from 'effect' ;
const User = Schema .Struct ({
id : Schema .Number ,
name : Schema .String ,
email : Schema .String
});
const typedStore = Effect .gen (function * () {
const store = yield * KeyValueStore .KeyValueStore ;
const userStore = KeyValueStore .toSchemaStore (store, User );
yield * userStore.set ('user:123' , {
id : 123 ,
name : 'John Doe' ,
email : 'john@example.com'
});
const user = yield * userStore.get ('user:123' );
});
CLI Arguments - effect/unstable/cli
For CLI applications, use effect/unstable/cli instead of direct process.argv.
Anti-Pattern - Direct process.argv:
declare const process : { argv : string [] };
const args = process.argv .slice (2 );
const input = args[0 ];
const verbose = args.includes ('--verbose' );
import yargs from 'yargs' ;
declare const yargs : (args : string [] ) => { argv : Record <string , unknown > };
const argv = yargs (process.argv .slice (2 )).argv ;
Correct Pattern - effect/unstable/cli:
import { Argument , Command as CliCommand , Flag } from 'effect/unstable/cli' ;
import { NodeServices , NodeRuntime } from '@effect/platform-node' ;
import { Console , Effect } from 'effect' ;
declare const process : { argv : string [] };
declare const someOperation : Effect .Effect <string >;
const inputArg = Argument .file ('input' );
const verboseFlag = Flag .boolean ('verbose' ).pipe (Flag .withAlias ('v' ));
const command = CliCommand .make (
'process' ,
{ input : inputArg, verbose : verboseFlag },
Effect .fn (function * ({ input, verbose }) {
if (verbose) {
yield * Console .log (`Processing file: ${input} ` );
}
yield * someOperation;
})
);
command.pipe (
CliCommand .run ({ version : '1.0.0' }),
Effect .provide (NodeServices .layer ),
NodeRuntime .runMain
);
Platform Module Reference
Complete reference table of platform abstractions:
Need Use Instead of Import from File I/O FileSystem.FileSystemfs, Bun.fileeffectPath Operations Path.Pathpath, string concateffectProcess Spawning ChildProcess + ChildProcessSpawnerchild_process, Bun.spawneffect/unstable/processTerminal I/O Terminal.Terminalprocess.stdin/stdouteffectConsole Logging Console.log or Effect.logconsole.logeffectCrypto Crypto.CryptoglobalThis.crypto, crypto.randomUUID()effectHTTP Client HttpClient.HttpClientfetch, axioseffect/unstable/httpHTTP Server HttpServer.HttpServerhttp.createServereffect/unstable/httpSockets Socket.Socket / SocketServer.SocketServerraw TCP/WebSocket APIs effect/unstable/socketKey-Value Store KeyValueStore.KeyValueStorelocalStorage, manual fileseffect/unstable/persistenceCLI Arguments Argument + Flag + Commandprocess.argv, yargseffect/unstable/cliEnvironment Variables Config from effectprocess.enveffectStreams StreamNode streams, ReadableStream effect
Socket services live in effect/unstable/socket. Use Socket.Socket for scoped bidirectional string/binary frame transports and SocketServer.SocketServer for accepting connections. Provide service-specific layers such as BrowserSocket.layerWebSocket(url), NodeSocket.layerWebSocket(url), NodeSocket.layerNet(options), BunSocket.layerWebSocket(url), or Node/Bun socket-server layers; NodeServices.layer and BunServices.layer do not provide sockets.
Setting Up Platform-Specific Layers
To use platform services, provide the appropriate platform layer.
NodeServices.layer and BunServices.layer provide core process services such as FileSystem, Path, ChildProcessSpawner, Stdio/Terminal, and Crypto.Crypto. They do not provide specialized integrations such as HTTP clients/servers, sockets, workers, or Redis; provide those with service-specific platform layers. For HTTP clients, provide an HTTP-specific layer such as FetchHttpClient.layer, Node's NodeHttpClient.{layerFetch, layerUndici, layerNodeHttp}, BunHttpClient.layer, or Browser's BrowserHttpClient.{layerFetch, layerXMLHttpRequest}. For HTTP servers, use server layers such as NodeHttpServer.layer(...), BunHttpServer.layer(...), or their layerHttpServices variants where appropriate.
Node.js:
import { NodeServices , NodeRuntime } from '@effect/platform-node' ;
import { Effect , FileSystem } from 'effect' ;
const program = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFileString ('file.txt' );
});
program.pipe (Effect .provide (NodeServices .layer ), NodeRuntime .runMain );
Bun:
import { BunServices , BunRuntime } from '@effect/platform-bun' ;
import { Effect , FileSystem } from 'effect' ;
const program = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFileString ('file.txt' );
});
program.pipe (Effect .provide (BunServices .layer ), BunRuntime .runMain );
Complete Example: Cross-Platform File Processor
import { NodeServices , NodeRuntime } from '@effect/platform-node' ;
import { BunServices , BunRuntime } from '@effect/platform-bun' ;
import { Console , Effect , FileSystem , Path , Schema } from 'effect' ;
import { ChildProcess , ChildProcessSpawner } from 'effect/unstable/process' ;
const Config = Schema .Struct ({
inputDir : Schema .String ,
outputDir : Schema .String ,
compress : Schema .Boolean
});
const processFiles = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const path = yield * Path .Path ;
const spawner = yield * ChildProcessSpawner .ChildProcessSpawner ;
const configData = yield * fs.readFileString ('config.json' );
const config = yield * Schema .decode (Config )(JSON .parse (configData));
yield * fs.makeDirectory (config.outputDir , { recursive : true });
const files = yield * fs.readDirectory (config.inputDir );
yield * Console .log (`Processing ${files.length} files...` );
yield * Effect .forEach (
files,
(file ) =>
Effect .gen (function * () {
const inputPath = path.join (config.inputDir , file);
const outputPath = path.join (config.outputDir , file);
yield * fs.copy (inputPath, outputPath);
if (config.compress ) {
yield * spawner.string (
ChildProcess .make ('gzip' , [outputPath])
);
}
yield * Console .log (`Processed: ${file} ` );
}),
{ concurrency : 4 }
);
yield * Console .log ('All files processed!' );
});
processFiles.pipe (Effect .provide (NodeServices .layer ), NodeRuntime .runMain );
processFiles.pipe (Effect .provide (BunServices .layer ), BunRuntime .runMain );
Testing with Platform Abstractions
One major benefit of platform abstractions is testability:
import { Effect , FileSystem , Layer } from 'effect' ;
declare const myFileProcessor : Effect .Effect <
void ,
never ,
FileSystem .FileSystem
>;
const TestFileSystem = Layer .succeed (
FileSystem .FileSystem ,
FileSystem .makeNoop ({
readFile : (path ) => {
if (path === 'config.json' ) {
const data = JSON .stringify ({ key : 'value' });
return Effect .succeed (new TextEncoder ().encode (data));
}
return Effect .fail (new Error ('File not found' ));
},
exists : (path ) => Effect .succeed (true )
})
);
const testProgram = myFileProcessor.pipe (Effect .provide (TestFileSystem ));
Effect .runPromise (testProgram);
Quality Checklist
Before completing code that uses platform operations:
All file I/O uses FileSystem.FileSystem service
All path operations use Path.Path service
Process spawning uses ChildProcess + ChildProcessSpawner
Console output uses Console.log or Effect.log (not console.log)
CLI arguments parsed with effect/unstable/cli (not process.argv)
HTTP requests use HttpClient.HttpClient (not fetch/axios)
Cryptographic operations use Crypto.Crypto (not direct platform crypto APIs)
Platform services accessed through Effect type system
Appropriate platform/service layer provided (HTTP, sockets, workers, Redis, and other specialized integrations need service-specific layers, not just NodeServices.layer / BunServices.layer)
No direct imports from fs, path, child_process, http, etc.
No Bun-specific APIs (Bun.file, Bun.spawn, etc.)
No browser-specific APIs without platform abstraction
Code is testable with mock platform services
Common Mistakes to Avoid
1. Mixing Platform APIs
import { Effect , FileSystem } from 'effect' ;
import fs from 'fs' ;
const bad = Effect .gen (function * () {
const filesystem = yield * FileSystem .FileSystem ;
const content1 = yield * filesystem.readFileString ('file1.txt' );
const content2 = fs.readFileSync ('file2.txt' , 'utf-8' );
});
const good = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const content1 = yield * fs.readFileString ('file1.txt' );
const content2 = yield * fs.readFileString ('file2.txt' );
});
2. Forgetting Platform Layer
import { NodeServices , NodeRuntime } from '@effect/platform-node' ;
import { Effect , FileSystem } from 'effect' ;
const program = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
return yield * fs.readFileString ('file.txt' );
});
Effect .runPromise (program);
program.pipe (Effect .provide (NodeServices .layer ), NodeRuntime .runMain );
3. Using console.log
import { Console , Effect } from 'effect' ;
declare const someOperation : () => Effect .Effect <string >;
const badProgram = Effect .gen (function * () {
console .log ('Starting...' );
const result = yield * someOperation ();
console .log ('Done!' );
return result;
});
const goodProgram = Effect .gen (function * () {
yield * Console .log ('Starting...' );
const result = yield * someOperation ();
yield * Console .log ('Done!' );
return result;
});
Migration Guide
From Node.js fs to FileSystem
import { Effect , FileSystem } from 'effect' ;
import fs from 'fs/promises' ;
declare const fs : {
readFile : (path : string , encoding : string ) => Promise <string >;
writeFile : (path : string , data : string ) => Promise <void >;
existsSync : (path : string ) => boolean ;
};
const data = await fs.readFile ('file.txt' , 'utf-8' );
await fs.writeFile ('output.txt' , data);
const exists = fs.existsSync ('config.json' );
const program = Effect .gen (function * () {
const fs = yield * FileSystem .FileSystem ;
const data = yield * fs.readFileString ('file.txt' );
yield * fs.writeFileString ('output.txt' , data);
const exists = yield * fs.exists ('config.json' );
});
From fetch to HttpClient
import { Effect } from 'effect' ;
import { HttpClient , HttpClientRequest } from 'effect/unstable/http' ;
declare const fetch : (
url : string ,
options : {
method: string ;
headers: Record<string , string >;
body: string ;
}
) => Promise <{ json : () => Promise <unknown > }>;
const response = await fetch ('https://api.example.com/data' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify ({ key : 'value' })
});
const data = await response.json ();
const program = Effect .gen (function * () {
const client = yield * HttpClient .HttpClient ;
const response = yield * HttpClientRequest .post (
'https://api.example.com/data'
).pipe (
HttpClientRequest .bodyJsonUnsafe ({ key : 'value' }),
client.execute
);
const data = yield * response.json ;
return data;
});
From child_process to ChildProcess
import { Effect } from 'effect' ;
import { ChildProcess , ChildProcessSpawner } from 'effect/unstable/process' ;
import { exec } from 'child_process' ;
import { promisify } from 'util' ;
declare const exec : (
cmd : string ,
callback : (error: Error | null , result: { stdout: string }) => void
) => void ;
declare const promisify : <T>(fn : T ) => (...args : any [] ) => Promise <any >;
const execAsync = promisify (exec);
const { stdout } = await execAsync ('git status' );
const program = Effect .gen (function * () {
const spawner = yield * ChildProcessSpawner .ChildProcessSpawner ;
const stdout = yield * spawner.string (ChildProcess .make ('git' , ['status' ]));
return stdout;
});
Summary
Effect provides a complete abstraction layer over platform-specific APIs, enabling you to:
Write once, run anywhere - Same code works on Node.js and Bun
Type-safe operations - All errors tracked in Effect type signatures
Resource safety - Automatic cleanup with Scope
Easy testing - Mock services without touching the filesystem
Full Effect integration - Compose with services, layers, and error handling
Always prefer Effect platform abstractions over direct platform APIs for maximum portability, safety, and testability.