76 lines
3.4 KiB
Markdown
76 lines
3.4 KiB
Markdown
# 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.
|