3.4 KiB
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.tsexportingdefaultBunFs(): NodeFsLike.readFileusesBun.file(path).bytes().writeFileusesBun.write(path, data).- All other methods delegate to
node:fs/promises(the same callsdefaultNodeFs()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). resolveConfiginsrc/node-fs-storage/node-fs-storage.helpers.tsgains a Bun-vs-Node default selector. Behaviour is unchanged when callers passfsexplicitly.src/index.tsre-exportsdefaultBunFsnext todefaultNodeFs.package.jsonexportsmap gains./bun-fs/bun-fs.README.mdModules 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):
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, notstorage-filedirectly; their behaviour is unaffected.