8.6 KiB
8.6 KiB
@suaveplan/storage-file - Node.js filesystem AbstractStorage adapter for @suaveplan/storage: atomic writes via temp-rename, AbortSignal everywhere, strict key sanitisation, native Web Streams, partial reads, transient-error retry via @suaveplan/retry, full OTel surface.
Summary
Node.js filesystem AbstractStorage adapter for @suaveplan/storage. Implements atomic writes via temp-file-then-rename, AbortSignal threaded through every async method, strict key sanitisation, transient-error retry via @suaveplan/retry, native Web Streams via node:fs.createReadStream/createWriteStream, and positioned partial reads. Full OpenTelemetry surface: per-op spans, latency histograms, byte-flow histograms, kind-split counters.
Documentation tree
- bun-fs —
bun-fsis a sibling ofnode-fsthat satisfies the same structuralNodeFsLikecontract consumed byNodeFsStorage…- bun-fs —
bun-fsis a sibling ofnode-fsthat satisfies the same structuralNodeFsLikecontract consumed byNodeFsStorage…
- bun-fs —
- key-sanitizer — (no submodules)
- key-sanitizer — (no submodules)
- node-fs — (no submodules)
- node-fs — (no submodules)
- node-fs-storage — (no submodules) - node-fs-storage.ops — Houses the file-system operat…
- node-fs-storage.helpers — Houses the pure helpers consumed by
NodeFsStorageso the orchestrator class stays under the file-size budget and the… - node-fs-storage — (no submodules) - node-fs-storage.ops — Houses the file-system operat…
- node-fs-storage.ops — Houses the file-system operation helpers consumed by
NodeFsStorage. Each function here wraps a single boundary call w…
- node-fs-storage.helpers — Houses the pure helpers consumed by
- otel — (no submodules)
- otel — (no submodules)
- registry — (no submodules)
- registry — (no submodules)
- retry-helper — (no submodules)
- retry-helper — (no submodules)
- streams — (no submodules)
- streams — (no submodules)
- error-codes — Defines the frozen
STORAGE_FILE_CODESregistry and theStorageFileErrorclass used by every public path in `@suavep…
- key-sanitizer — (no submodules)- node-fs — (no submodules) - node-fs — (no submodules)- node-fs-storage — (no submodules) - node-fs-storage.helpers — Houses the pure helpers consumed by
NodeFsStorageso the orchestrator class stays under the file-size budget and the… - node-fs-storage — (no submodules) - node-fs-storage.ops — Houses the file-system operation helpers consumed by
NodeFsStorage. Each function here wraps a single boundary call w… - otel — (no submodules) - otel — (no submodules)- registry — (no submodules) - registry — (no submodules)- retry-helper — (no submodules) - retry-helper — (no submodules)- streams — (no submodules) - streams — (no submodules)- error-codes — Defines the frozen
STORAGE_FILE_CODESregistry and theStorageFileErrorclass used by every public path in `@suavep…
Installation
bun add @suaveplan/storage-file
Quick Start
import { NodeFsStorage } from "@suaveplan/storage-file";
const storage = new NodeFsStorage({ rootDir: "/var/lib/app/storage" });
await storage.set("orders/42.json", new TextEncoder().encode("{\"id\":42}"));
const bytes = await storage.get("orders/42.json");
console.log(new TextDecoder().decode(bytes!));
// Streams + partial reads
const writer = storage.createWriteStream("large.bin").getWriter();
await writer.write(new Uint8Array(1024 * 1024));
await writer.close();
const slice = await storage.getPartial("large.bin", 0, 16);
// Factory registration (optional)
import { registerNodeFsProvider } from "@suaveplan/storage-file";
import { createStorage } from "@suaveplan/storage/factory";
registerNodeFsProvider();
const viaFactory = createStorage({ type: "file", config: { rootDir: "/tmp/store" } });
Modules
src/error-codes—STORAGE_FILE_CODES,StorageFileError,StorageFileErrorCodesrc/key-sanitizer/key-sanitizer—sanitizeKeystrict boundarysrc/node-fs/node-fs—defaultNodeFsadapter wrappingnode:fs/promisessrc/bun-fs/bun-fs—defaultBunFs/pickDefaultFs— Bun-nativeBun.file/Bun.writefast pathsrc/retry-helper/retry-helper—withFsRetry, transient classifiersrc/otel/otel— OTel instruments andrecordOpsrc/streams/streams— Web Streams bridge with atomic writesrc/node-fs-storage/node-fs-storage— mainNodeFsStorageclasssrc/node-fs-storage/node-fs-storage.helpers— pure helperssrc/registry/registry— factory registration hooksrc/types/fs-adapter.types—NodeFsLikeinterfacesrc/types/node-fs-storage.types— options + Zod schema
Architecture
- Tier: server (consumes
node:fs/promisesandnode:fsstream constructors). Browser consumers should pick@suaveplan/storage/memory, an HTTP/IndexedDB adapter, or@suaveplan/storage-s3. - Base class: extends
AbstractStoragefrom@suaveplan/storage/abstract. Every consumer that talks toAbstractStorage(or theStorageLikeshim) works against this adapter unchanged. - Atomicity: every public write (
set, metadata sidecar,createWriteStream.close) writes to a temp file in the destination directory andfs.renames onto the destination. POSIX rename is atomic within a filesystem. - Cancellation: every public async method accepts an optional
AbortSignal. Pre-aborted signals throwSTORAGE_FILE.ABORTEDbefore any IO call. - Resilience: transient errors (
EBUSY,EMFILE,ENFILE,EAGAIN) are retried via@suaveplan/retrywith exponential backoff and injectableTimersLike. Non-transient errors surface asSTORAGE_FILE.IO_FAILEDorSTORAGE_FILE.PERMISSION_DENIED. - Security: every public method passes its key through
sanitizeKey. Path-traversal, absolute paths, drive-letter prefixes, null bytes, control chars, and over-length keys all surface asSTORAGE_FILE.INVALID_KEY/KEY_TOO_LONGbefore any filesystem call. - Observability: OTel counters, histograms, and a tracer emit from production paths. Tests assert against a real in-memory MeterProvider — no
vi.mock.