Skip to main content
@ooneex/fs wraps Bun’s optimized file I/O behind two ergonomic classes, File and Directory. Every operation is async, returns rich objects instead of raw paths, and throws typed exceptions (FileException, DirectoryException) carrying a status code and contextual data when something goes wrong.

Installation

Add the package with Bun:
bun add @ooneex/fs

Usage

Create a File from a path or URL, then read, write, or inspect it.
import { File } from "@ooneex/fs";

const file = new File("/path/to/config.json");

if (await file.exists()) {
  // Read in different shapes
  const raw = await file.text();
  const config = await file.json<{ name: string; version: number }>();
  const bytes = await file.bytes();

  // Metadata
  console.log(file.getName());      // "config.json"
  console.log(file.getExtension()); // "json"
  console.log(file.getSize());      // size in bytes
}

// Write, append, copy, delete
await file.write("Hello, World!");
await file.append("\nmore content");
const backup = await file.copy("/path/to/config.backup.json");
await backup.delete();

Streaming large files

Iterate over a file incrementally instead of loading it all into memory. streamAsJson reads a JSON array file and yields each element one at a time.
import { File } from "@ooneex/fs";

const file = new File("/path/to/large-data.json");

for await (const item of file.streamAsJson<{ id: number }>()) {
  console.log(item.id);
}

// Or stream raw chunks / decoded text
const log = new File("/path/to/app.log");
for await (const line of log.streamAsText()) {
  process.stdout.write(line);
}

Downloading and buffered writing

import { File } from "@ooneex/fs";

// Download a remote file to disk
const downloaded = await File.download("https://example.com/data.zip", "/tmp/data.zip");

// Buffered (incremental) writes via a FileSink
const out = new File("/tmp/output.txt");
const writer = out.writer();
writer.write("Line 1\n");
writer.write("Line 2\n");
writer.end(); // flush and close

Working with directories

Directory mirrors common shell operations and exposes async generators for traversal.
import { Directory } from "@ooneex/fs";

const dir = new Directory("/path/to/project");

await dir.mkdir();                       // recursive by default
const names = await dir.ls({ recursive: true });
const totalBytes = await dir.getSize();

// Navigate fluently
const srcDir = dir.cd("src", "components");

// Iterate only files matching a pattern
for await (const file of srcDir.getFiles({ recursive: true, pattern: /\.ts$/ })) {
  console.log(file.getPath());
}

// Copy, move, clean up
await dir.cp("/path/to/copy", { overwrite: true });
if (await dir.isEmpty()) {
  await dir.rm({ force: true });
}

// Watch for changes
const watcher = dir.watch((event, filename) => {
  console.log(`${event}: ${filename}`);
}, { recursive: true });
// watcher.close();

When to use it

  • You are building on Bun and want a clean, object-oriented wrapper around its file APIs.
  • You need typed, descriptive errors (FileException / DirectoryException) with status codes and context instead of raw errno failures.
  • You process large files and want generator-based streaming (stream, streamAsText, streamAsJson).
  • You traverse, filter, copy, move, or watch directory trees and want File/Directory objects rather than bare strings.
  • Skip it for trivial one-off reads where Bun.file(...).text() is enough, or in non-Bun runtimes (the package targets Bun’s I/O).