0Z4C0
// navigation
// @ozaco/std

IO

A single effect-native IO capability — filesystem, process, crypto, paths and networking — with swappable runtime backends.

import from@ozaco/std/io

IO is a plugin protocol (defineProtocol) describing every side-effecting capability the standard library needs: reading and writing files, spawning processes, hashing and signing, joining paths, and opening TCP/UDP sockets. The protocol carries only the shape; a concrete runtime implementation must be installed before its actions run.

Pick a backend by import — BunIO, NodeIO or WebIO — and register it once with install(...) from std:plugin. From then on every action is reached through IO.actions.* and consumed inside an effect operation with yield*. Almost every action returns a Future<T> (an effect that resolves to a value); the streaming ones (readStream, writeStream, watch, fromReadable) return an effect Stream.

Paths accept a PathLike — a string or a file: URL — normalized through toPath. Because the surface is one protocol, code written against IO runs unchanged on Bun, Node or the browser; the web backend simply fails clearly for capabilities a browser cannot provide (filesystem, processes, sockets).

// additional entry points
  • @ozaco/std/io/impl/bun`BunIO` — the Bun runtime backend. Uses `Bun.file`/`Bun.write` for fast file IO and Bun-native process spawning, with Web Crypto for hashing and random bytes.
  • @ozaco/std/io/impl/node`NodeIO` — the Node.js backend. Built on `node:fs/promises`, `node:crypto` and `node:child_process`; the portable default for any Node deployment.
  • @ozaco/std/io/impl/web`WebIO` — the browser backend. Provides crypto (Web Crypto) and a best-effort `env`; filesystem, process and socket actions fail with an `io-unsupported` failure instead of pretending to work.
01

Protocol

The capability protocol and its flag set.

const

IO

The IO capability protocol. Reach its actions via `IO.actions.*`; install a backend first.

signature
const IO: Protocol<AnyType, [], IOActions>

Created with defineProtocol({ name: 'io', version: '0.0.1' }). A runtime backend (BunIO/NodeIO/WebIO) is built from it with IO.implement(meta).build(actions) and registered with install(...). Consumers never touch a backend directly — they call IO.actions.readText(...) and the installed implementation answers.

example
import { install } from 'std:plugin'
import { IO } from '@ozaco/std/io'
import { BunIO } from '@ozaco/std/io/impl/bun'

yield* install(BunIO)

const text = yield* IO.actions.readText('config.json')
const id = yield* IO.actions.uuid()
const

IO_FLAGS

Bit flags for write/copy/rename/walk behavior, combined with `|`.

signature
enum IO_FLAGS {
  NONE = 0,
  FOLLOW_SYMLINKS = 1 << 2,
  FILES = 1 << 3,
  DIRS = 1 << 4,
  APPEND = 1 << 5,
  EXCLUSIVE = 1 << 6,
}

APPEND and EXCLUSIVE shape write/copy/rename (exclusive fails if the destination exists); FILES/DIRS/FOLLOW_SYMLINKS filter walk. Combine them with a bitwise OR, e.g. IO_FLAGS.FILES | IO_FLAGS.DIRS.

example
import { IO, IO_FLAGS } from '@ozaco/std/io'

// append instead of truncate
yield* IO.actions.write('log.txt', 'line\n', { flags: IO_FLAGS.APPEND })

// walk files only
const files = yield* IO.actions.walk('src', { flags: IO_FLAGS.FILES })
02

Filesystem

Read, write and manage files and directories. Every action takes a `PathLike`.

fn

read

Read a whole file as raw bytes.

signature
IO.actions.read(path: PathLike): Future<Uint8Array>
example
const bytes = yield* IO.actions.read('logo.png')
fn

readText

Read a whole file as text (defaults to UTF-8).

signature
IO.actions.readText(path: PathLike, encoding?: string): Future<string>
example
const source = yield* IO.actions.readText('main.ts')
const latin = yield* IO.actions.readText('legacy.txt', 'latin1')
fn

write

Write bytes or text to a file, truncating unless `APPEND`/`EXCLUSIVE` is set.

signature
IO.actions.write(path: PathLike, data: Uint8Array | string, options?: { flags?: number }): Future<void>
example
yield* IO.actions.write('out.json', JSON.stringify(data))
fn

append

Append bytes to a file, creating it if missing.

signature
IO.actions.append(path: PathLike, data: Uint8Array): Future<void>
fn

copy

Copy a file; with `EXCLUSIVE` fails if the destination exists.

signature
IO.actions.copy(src: PathLike, dest: PathLike, options?: { flags?: number }): Future<void>
fn

rename

Move or rename a file; with `EXCLUSIVE` fails if the destination exists.

signature
IO.actions.rename(src: PathLike, dest: PathLike, options?: { flags?: number }): Future<void>
fn

rm

Remove a file or directory, optionally recursively and forcefully.

signature
IO.actions.rm(path: PathLike, options?: { recursive?: boolean; force?: boolean }): Future<void>
example
yield* IO.actions.rm('dist', { recursive: true, force: true })
fn

exists

Whether a path exists (file or directory).

signature
IO.actions.exists(path: PathLike): Future<boolean>
example
if (yield* IO.actions.exists('.env')) {
  // ...
}
fn

stat

File metadata, following symlinks. Returns an `IOStat`.

signature
IO.actions.stat(path: PathLike): Future<IOStat>
fn

lstat

Like `stat`, but does not follow symlinks.

signature
IO.actions.lstat(path: PathLike): Future<IOStat>
fn

readdir

List directory entries, optionally recursively.

signature
IO.actions.readdir(path: PathLike, options?: { recursive?: boolean }): Future<string[]>
fn

ensureDir

Create a directory (and parents) if it does not already exist.

signature
IO.actions.ensureDir(path: PathLike): Future<void>
fn

ensureFile

Create an empty file (and parent directories) if it does not already exist.

signature
IO.actions.ensureFile(path: PathLike): Future<void>
fn

emptyDir

Ensure a directory exists and is empty, removing any existing contents.

signature
IO.actions.emptyDir(path: PathLike): Future<void>
fn

walk

Recursively collect directory entries, filtered by flags, depth and regex match/skip lists.

signature
IO.actions.walk(root: PathLike, options?: WalkOptions): Future<WalkEntry[]>
example
const entries = yield* IO.actions.walk('src', {
  flags: IO_FLAGS.FILES,
  match: [/\.ts$/],
  skip: [/node_modules/],
})
fn

watch

Watch a file or directory, streaming `WatchEvent`s until torn down.

signature
IO.actions.watch(path: PathLike, options?: WatchOptions): Stream<WatchEvent, never>

Event-based (backed by fsPromises.watch), recursive-capable. The stream never settles on its own — it runs until the surrounding scope tears it down.

fn

chmod

Change a path’s permission mode.

signature
IO.actions.chmod(path: PathLike, mode: number): Future<void>
03

Streams

Byte streams and adapters between effect `Stream`s and platform readables.

fn

readStream

Open a file as a byte `Stream` (read lazily, chunk by chunk).

signature
IO.actions.readStream(path: PathLike): Stream<Uint8Array, StreamClose>
fn

writeStream

Drain a byte `Stream` into a file.

signature
IO.actions.writeStream(path: PathLike, source: Stream<Uint8Array, unknown>, options?: { flags?: number }): Future<void>
fn

fromReadable

Adapt a Node or Web readable into an effect byte `Stream`.

signature
IO.actions.fromReadable(target: ReadableLike, options?: { destroy?: boolean }): Stream<Uint8Array, StreamClose>

Works in every backend (including the browser) — the one stream action WebIO supports, for wrapping an existing web ReadableStream.

fn

toReadable

Turn an effect byte `Stream` into a Web `ReadableStream` plus a `pump` operation to drive it.

signature
IO.actions.toReadable(source: Stream<Uint8Array, unknown>): Future<{ readable: ReadableStream<Uint8Array>; pump: Operation<void> }>
04

Process & env

Read environment variables and run child processes.

fn

env

Read + shape environment variables through a mapper, asserting required keys are present.

signature
IO.actions.env<R, K extends keyof R = never>(
  mapper: (data: Record<string, string | undefined>) => R,
  optional?: readonly K[],
): Future<{ [P in keyof R]: P extends K ? R[P] : NonNullable<R[P]> }>

Keys not listed in optional are asserted non-nullable in the result type. WebIO reads a best-effort source.

example
const ENV = IO.actions.env(
  data => ({ port: data.PORT, level: data.LOG_LEVEL }),
  ['level'],
)
const env = yield* ENV //=> { port: string; level: string | undefined }
fn

exec

Run a command to completion and buffer its output. Returns an `ExecResult`.

signature
IO.actions.exec(cmd: string, args?: readonly string[], options?: ExecOptions): Future<ExecResult>
example
const { stdout, success } = yield* IO.actions.exec('git', ['rev-parse', 'HEAD'])
fn

spawn

Spawn a child process and return a live `ProcessHandle` (streamed stdout/stderr, stdin, kill).

signature
IO.actions.spawn(cmd: string, args?: readonly string[], options?: SpawnOptions): Future<ProcessHandle>
05

Networking

TCP and UDP sockets, plus interface discovery.

fn

tcpListen

Start a TCP server; `onConnection` runs per accepted socket as a child of the caller’s scope.

signature
IO.actions.tcpListen(options: TcpListenOptions, onConnection: TcpHandler): Future<TcpServer>
fn

tcpConnect

Open a client TCP connection, returning a bidirectional `TcpSocket`.

signature
IO.actions.tcpConnect(options: TcpConnectOptions): Future<TcpSocket>
fn

udpBind

Bind a UDP socket for send/receive of datagrams.

signature
IO.actions.udpBind(options?: UdpBindOptions): Future<UdpSocket>
fn

ip

List the machine’s network interface addresses.

signature
IO.actions.ip(): Future<NetworkInterface[]>
06

Crypto & IDs

Random bytes, sortable/unique identifiers, hashing, symmetric encryption and Ed25519 signing.

fn

randomBytes

Generate cryptographically strong random bytes.

signature
IO.actions.randomBytes(length: number): Future<Uint8Array>
fn

ulid

Generate a ULID — lexicographically sortable and monotonic within a time `window`.

signature
IO.actions.ulid(options?: UlidOptions): Future<string>
example
const id = yield* IO.actions.ulid({ bucket: 'msg_' })
fn

uuid

Generate an RFC 4122 version-4 (random) UUID string.

signature
IO.actions.uuid(): Future<string>
example
const session = yield* IO.actions.uuid()
fn

hash

Hash bytes with SHA-256/384/512.

signature
IO.actions.hash(algorithm: HashAlgorithm, data: Uint8Array): Future<Uint8Array>
fn

hmac

Compute an HMAC over bytes with a key and SHA-256/384/512.

signature
IO.actions.hmac(algorithm: HashAlgorithm, key: Uint8Array, data: Uint8Array): Future<Uint8Array>
fn

encrypt

Encrypt with a secret (AES-256-GCM, key derived via scrypt). Reversible via `decrypt`.

signature
IO.actions.encrypt(data: Uint8Array | string, secret: string): Future<Uint8Array>

Not available in WebIO.

fn

decrypt

Decrypt what `encrypt` produced; fails on a wrong secret or tampered data.

signature
IO.actions.decrypt(data: Uint8Array, secret: string): Future<Uint8Array>
fn

generateKeyPair

Generate an Ed25519 key pair for `sign`/`verify`.

signature
IO.actions.generateKeyPair(): Future<KeyPair>
fn

sign

Sign data with an Ed25519 private key; returns a 64-byte signature.

signature
IO.actions.sign(data: Uint8Array | string, privateKey: Uint8Array): Future<Uint8Array>
fn

verify

Verify an Ed25519 signature against a public key.

signature
IO.actions.verify(data: Uint8Array | string, signature: Uint8Array, publicKey: Uint8Array): Future<boolean>
07

Paths

Path composition and inspection. The path helpers return a `Future`; `toPath` is a plain sync export.

fn

join

Join path segments with the platform separator and normalize.

signature
IO.actions.join(...segments: string[]): Future<string>
example
const target = yield* IO.actions.join(dir, 'config', 'app.json')
fn

dirname

The directory portion of a path.

signature
IO.actions.dirname(path: string): Future<string>
fn

basename

The final portion of a path; strips a trailing `suffix` when it matches.

signature
IO.actions.basename(path: string, suffix?: string): Future<string>
fn

extname

The extension (including the leading dot), or `''` when there is none.

signature
IO.actions.extname(path: string): Future<string>
fn

isAbsolute

Whether the path is absolute.

signature
IO.actions.isAbsolute(path: string): Future<boolean>
fn

toPath

Normalize a `PathLike` (string or `file:` URL) into a plain filesystem path string.

signature
function toPath(pathOrUrl: PathLike): string

A synchronous, non-effect helper. file:// URLs are decoded to their path; plain strings pass through unchanged.

example
import { toPath } from '@ozaco/std/io'

toPath('file:///etc/hosts') //=> '/etc/hosts'
toPath('relative/file.txt') //=> 'relative/file.txt'
08

Types

The action contract and the value shapes actions return.

type

IOActions

The full record of IO actions the protocol declares (the shape behind `IO.actions`).

signature
type IOActions = { read: ...; write: ...; exec: ...; /* every action */ }
type

PathLike

A path argument: a string or a `URL`.

signature
type PathLike = string | URL
type

HashAlgorithm

Supported digest algorithms for `hash`/`hmac`.

signature
type HashAlgorithm = 'SHA-256' | 'SHA-384' | 'SHA-512'
type

StreamClose

The value a byte stream settles with: `true` on a clean end, or the failure that interrupted it.

signature
type StreamClose = true | Result.Failure<unknown>
iface

IOStat

File metadata from `stat`/`lstat`.

signature
interface IOStat {
  isFile: boolean
  isDirectory: boolean
  isSymlink: boolean
  size: number
  mtime: Date | null
  atime: Date | null
  birthtime: Date | null
}
iface

WalkEntry

A single entry produced by `walk`.

signature
interface WalkEntry {
  path: string
  name: string
  isFile: boolean
  isDirectory: boolean
  isSymlink: boolean
}
iface

WatchEvent

A filesystem change reported by `watch`.

signature
interface WatchEvent {
  type: 'rename' | 'change'
  path: string | null
}
iface

ProcessStatus

The exit status of a child process.

signature
interface ProcessStatus {
  code: number | null
  signal: string | null
  success: boolean
}
iface

ExecResult

The buffered result of running a command to completion via `exec`.

signature
interface ExecResult extends ProcessStatus {
  stdout: Uint8Array
  stderr: Uint8Array
}
iface

ProcessHandle

A live handle to a spawned child process.

signature
interface ProcessHandle {
  readonly pid: number
  readonly stdout: Stream<Uint8Array, StreamClose>
  readonly stderr: Stream<Uint8Array, StreamClose>
  exited: () => Future<ProcessStatus>
  write: (chunk: Uint8Array | string) => Future<void>
  closeStdin: () => Future<void>
  kill: (signal?: number | string) => Future<void>
}
iface

KeyPair

An Ed25519 key pair (public = SPKI, private = PKCS8 DER bytes).

signature
interface KeyPair {
  publicKey: Uint8Array
  privateKey: Uint8Array
}
iface

NetworkInterface

A single network interface address reported by `ip`.

signature
interface NetworkInterface {
  name: string
  address: string
  family: 'IPv4' | 'IPv6'
  internal: boolean
  mac: string
  netmask: string
  cidr: string | null
}
iface

TcpSocket

A bidirectional TCP byte channel (accepted connection or client socket).

signature
interface TcpSocket {
  readonly remoteAddress: string
  readonly remotePort: number
  readonly localPort: number
  data: Stream<Uint8Array, StreamClose>
  write: (chunk: Uint8Array | string) => Future<void>
  close: () => Future<void>
}
type

TcpHandler

A per-connection handler passed to `tcpListen`.

signature
type TcpHandler = (socket: TcpSocket) => Operation<void>
iface

TcpServer

A handle to a listening TCP server.

signature
interface TcpServer {
  readonly port: number
  readonly hostname: string
  close: () => Future<void>
}
iface

UdpDatagram

A single received UDP datagram plus its sender.

signature
interface UdpDatagram {
  data: Uint8Array
  address: string
  port: number
}
iface

UdpSocket

A handle to a bound UDP socket.

signature
interface UdpSocket {
  readonly port: number
  messages: Stream<UdpDatagram, StreamClose>
  send: (data: Uint8Array | string, port: number, address: string) => Future<void>
  close: () => Future<void>
}
type

ReadableLike

A Node or Web readable accepted by `fromReadable`.

signature
type ReadableLike = NodeReadableLike | WebReadableLike
iface

NodeReadableLike

The event-emitter readable shape (`on`/`off`/`destroy?`).

signature
interface NodeReadableLike {
  on(event: string, listener: (...args: AnyType[]) => void): this
  off(event: string, listener: (...args: AnyType[]) => void): this
  destroy?(error?: Error): this
}
iface

WebReadableLike

The Web `ReadableStreamDefaultReader` shape (`read`/`cancel`/`releaseLock`).

signature
interface WebReadableLike {
  read(): Promise<ReadableStreamReadResult<Uint8Array>>
  cancel(reason?: AnyType): Promise<void>
  releaseLock(): void
}
iface

WritableLike

A writable sink shape (`write`/`end`/`on`/`once`/`off`).

signature
interface WritableLike {
  write(chunk: Uint8Array): boolean
  end(): this
  destroy?(error?: Error): this
  on(event: string, listener: (...args: AnyType[]) => void): this
  once(event: string, listener: (...args: AnyType[]) => void): this
  off(event: string, listener: (...args: AnyType[]) => void): this
}
09

Options

Option bags accepted by the actions above.

iface

WalkOptions

Filters for `walk`: flags, depth cap and regex match/skip lists.

signature
interface WalkOptions {
  flags?: number
  maxDepth?: number
  match?: RegExp[]
  skip?: RegExp[]
}
iface

WatchOptions

Options for `watch` — recurse into nested directories (default `false`).

signature
interface WatchOptions {
  recursive?: boolean
}
iface

UlidOptions

Shape a generated ULID: quantize the timestamp, set total length, or add a bucket prefix.

signature
interface UlidOptions {
  window?: number
  length?: number
  bucket?: string
}
iface

ProcessOptions

Base child-process options: working directory and environment overrides.

signature
interface ProcessOptions {
  cwd?: PathLike
  env?: Record<string, string | undefined>
}
iface

ExecOptions

Options for `exec`: adds buffered `stdin` and a kill `timeout`.

signature
interface ExecOptions extends ProcessOptions {
  stdin?: Uint8Array | string
  timeout?: number
}
type

SpawnOptions

Options for `spawn` (an alias of `ProcessOptions`).

signature
type SpawnOptions = ProcessOptions
iface

TcpListenOptions

Options for `tcpListen`: port, bind hostname, and `SO_REUSEPORT`.

signature
interface TcpListenOptions {
  port: number
  hostname?: string
  reusePort?: boolean
}
iface

TcpConnectOptions

Options for `tcpConnect`: destination port and hostname.

signature
interface TcpConnectOptions {
  port: number
  hostname?: string
}
iface

UdpBindOptions

Options for `udpBind`: local port (ephemeral if omitted) and bind hostname.

signature
interface UdpBindOptions {
  port?: number
  hostname?: string
}