# `bun-fs` — Bun-native `NodeFsLike` adapter _(no submodules)_ ## Purpose `bun-fs` is a sibling of [`node-fs`](../node-fs/node-fs.md) that satisfies the same structural `NodeFsLike` contract consumed by `NodeFsStorage`. Where `defaultNodeFs()` wires every method to `node:fs/promises` and the two `node:fs` stream constructors, `defaultBunFs()` overrides only the two methods that have a measurably faster Bun-native path — `readFile` (routed through `Bun.file(path).bytes()`) and `writeFile` (routed through `Bun.write(path, data)`). Every other method delegates to the Node base because under Bun those calls already dispatch to Bun's native (Zig) implementation transparently via the Node-compat layer, leaving no perf on the table. This module exists so Suaveplan — which targets Bun as its first runtime — does not pay the Node-compat reshaping cost on the hot read/write loop. It is additive: no behaviour changes for callers that inject a custom `fs`, and the Node adapter remains the explicit choice when running outside Bun or when parity testing under Bun. ## Features - `defaultBunFs(): NodeFsLike` — Bun-native adapter. Throws a plain `Error` if called outside a Bun runtime. - `pickDefaultFs(): NodeFsLike` — runtime-probed selector. Returns `defaultBunFs()` when `globalThis.Bun` is defined, otherwise `defaultNodeFs()`. The probe runs on every call (never cached) so tests can switch adapters deterministically. - Returns a fresh object per call so callers can selectively override any single method without mutating shared state. - Zero runtime branching inside individual methods — composition over conditionals. ## Usage: Basic Example ```ts import { NodeFsStorage, defaultBunFs } from "@suaveplan/storage-file"; const storage = new NodeFsStorage({ rootDir: "/var/data", fs: defaultBunFs(), }); await storage.set("hello", new TextEncoder().encode("world")); const bytes = await storage.get("hello"); console.log(new TextDecoder().decode(bytes!)); // "world" ``` ## Usage: Advanced Example Combine runtime detection with a custom override — useful when you want the Bun fast path everywhere except a tracing wrapper around `writeFile`: ```ts import { NodeFsStorage, pickDefaultFs, type NodeFsLike, } from "@suaveplan/storage-file"; function tracedFs(): NodeFsLike { const base = pickDefaultFs(); return { ...base, async writeFile(path, data) { const start = performance.now(); try { await base.writeFile(path, data); } finally { console.info("writeFile", path, performance.now() - start); } }, }; } const storage = new NodeFsStorage({ rootDir: "/var/data", fs: tracedFs(), }); ``` When you construct `NodeFsStorage` without passing `fs`, `resolveConfig` calls `pickDefaultFs()` for you — Bun-first, with Node fallback. ## API Reference ### `defaultBunFs(): NodeFsLike` - **Returns** a fresh `NodeFsLike` whose `readFile` is implemented as `Bun.file(path).bytes()` and whose `writeFile` is implemented as `Bun.write(path, data)`. Every other method (`mkdir`, `readdir`, `rename`, `unlink`, `stat`, `access`, `open`, `createReadStream`, `createWriteStream`) delegates to the `defaultNodeFs()` base. - **Throws** a plain `Error` with the message `defaultBunFs() requires a Bun runtime (globalThis.Bun is undefined)` if the probe finds no Bun global. The intended entry point for adapter selection is `pickDefaultFs`, which performs the probe first. ### `pickDefaultFs(): NodeFsLike` - **Returns** `defaultBunFs()` when `globalThis.Bun` is an object, otherwise `defaultNodeFs()`. Consumed by `resolveConfig` in `node-fs-storage.helpers.ts` to wire the default `fs` for `NodeFsStorage`. ## Implementation Notes - Streams are NOT routed through `Bun.file(path).stream()`. `BunFile.stream()` returns a Web `ReadableStream`, but `NodeFsLike.createReadStream` is typed as a Node `Readable`. Translating between the two introduces a copy that erases the speedup, so the Node `node:fs` constructor is used unchanged — it already dispatches to Bun's native impl under Bun. - The detection probe uses `typeof globalThis.Bun === "object"` rather than `"undefined" !== typeof Bun`. The former works without TypeScript ambient globals and is robust to module-scope mocking via `(globalThis as { Bun?: unknown }).Bun = undefined`. - `Bun.write` accepts both `Uint8Array` and `string`, matching `NodeFsLike.writeFile`'s union type. No buffer copying is performed by the adapter. - The adapter has zero `@suaveplan/*` runtime dependencies. It is internally consumed by `node-fs-storage.helpers.resolveConfig`, which is the only module that should call `pickDefaultFs` directly.