# Add Bun-native filesystem adapter to `@suaveplan/storage-file` - **Capability**: `storage-file` - **Status**: PROPOSED - **Design**: ./design.md - **Spec delta**: ./specs/storage-file/spec.md ## Why `@suaveplan/storage-file` currently ships a single `NodeFsLike` implementation — `defaultNodeFs()` in `src/node-fs/node-fs.ts` — bound to `node:fs/promises` plus the two `node:fs` stream constructors. Genesis is a Bun-first monorepo. Bun provides a faster, non-emulated filesystem path for the common read-and-write hot loop via `Bun.file()` / `Bun.write()` that skips the `node:fs` emulation layer entirely. Today every read/write inside `NodeFsStorage` takes the slower compatibility path even when running under Bun. This change adds a sibling `defaultBunFs()` factory that satisfies the same `NodeFsLike` structural contract but routes `readFile`/`writeFile` through Bun's native APIs. Operations that have no Bun-specific equivalent (`mkdir`, `readdir`, `rename`, `unlink`, `stat`, `access`, `open`, `createReadStream`, `createWriteStream`) continue to use `node:fs/promises` — under Bun those calls already dispatch to Bun's native Zig implementation. `resolveConfig` gains a runtime-probed default: when the caller does not inject an `fs` and `globalThis.Bun` is present, `defaultBunFs()` is used; otherwise `defaultNodeFs()` is used. Both factories remain explicitly exported so callers can wire either one directly (e.g. forcing the Node path in tests, or in mixed-runtime SSR pipelines). ## What changes - New module `src/bun-fs/bun-fs.ts` exporting `defaultBunFs(): NodeFsLike`. - `readFile` uses `Bun.file(path).bytes()`. - `writeFile` uses `Bun.write(path, data)`. - All other methods delegate to `node:fs/promises` (the same calls `defaultNodeFs()` makes), so the surface stays identical. - New module `src/bun-fs/bun-fs.md` (sibling doc, §4 conformant). - New module `src/bun-fs/bun-fs.test.ts` (`@suaveplan/testing/runner`). - New barrel `src/bun-fs/index.ts` (explicit named re-export per §6a). - `resolveConfig` in `src/node-fs-storage/node-fs-storage.helpers.ts` gains a Bun-vs-Node default selector. Behaviour is unchanged when callers pass `fs` explicitly. - `src/index.ts` re-exports `defaultBunFs` next to `defaultNodeFs`. - `package.json` `exports` map gains `./bun-fs/bun-fs`. - `README.md` Modules section gets a new row. ## Migration No consumer behaviour changes if `fs` is passed explicitly. Consumers that relied on the implicit default got a `node:fs/promises`-backed implementation before this change and continue to get one when running under Node. Under Bun they transparently get the faster path. The `NodeFsLike` contract, all `STORAGE_FILE.*` codes, the `recordOp` instrumentation, and the spec-conformance suite are unchanged. To force the Node path under Bun (e.g. for parity testing): ```ts import { defaultNodeFs, NodeFsStorage } from "@suaveplan/storage-file"; const storage = new NodeFsStorage({ rootDir, fs: defaultNodeFs() }); ``` ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/storage-file` to enumerate. As of authoring, `@suaveplan/storage-file` has no in-repo dependents (it is consumed by the upstream `@suaveplan/storage` factory via the optional registry path). - No in-repo dependents — verified by `bunx turbo ls --filter=...@suaveplan/storage-file`. Storage-cdn-bunnycdn and storage-cdn-rackspace consume `@suaveplan/storage`, not `storage-file` directly; their behaviour is unaffected.