Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
# `bun-fs` — Bun-native `NodeFsLike` adapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.