Skip to content

FilesystemObjectStorageOptions

Defined in: src/persistence/object-storage/FilesystemObjectStorageBackend.ts:57

Filesystem-backed ObjectStorageBackend — stores each object as a file under a root directory, with the storage key mapped 1:1 to a relative path. Suitable for unit tests, local development, and “S3-API parity without the cloud”, and safe for concurrent multi-process writers: every put / delete acquires a per-key advisory file lock (atomic O_EXCL create) so the CAS check + write block is serialized at the filesystem layer, and writes use a temp-file + rename so concurrent readers never observe a half-written object.

The backend lazy-imports node:fs/promises and node:path so this module is harmless to include on Bun / Deno where those built-ins already exist (Node-compat layer) — only the actual operations touch them.

Implementation notes:

  • Disk is canonical. Etags are content-derived (computeEtag — deterministic FNV-1a + length). No in-memory map; the file content alone determines the etag, so a fresh process sees the exact same etags every other process does. Same key, same bytes → same etag, regardless of who wrote them or when.
  • Per-key advisory lock. The lock file lives next to the target file as <key>.lock. Acquisition uses fs.writeFile(lockPath, ..., { flag: 'wx' }), which is atomic-create-only on every POSIX and NTFS filesystem the framework targets.
  • Stale-lock recovery. Lock files older than staleLockMs (default 30 s) are assumed to be left behind by a crashed writer and forcibly removed; one final acquisition retry is then made. This keeps the pathological “process died holding a lock” case from blocking the directory forever, at the cost of being technically incorrect if a real writer is taking longer than staleLockMs for a single put — which shouldn’t happen for the small payloads this backend targets.
  • Atomic body writes. put writes to a per-process tmp file (<key>.tmp.<pid>.<ts>.<rand>), then renames over the target. On POSIX rename(2) is atomic on the same filesystem; on Windows MoveFileEx(MOVEFILE_REPLACE_EXISTING) provides equivalent behaviour. Concurrent readers always see either the old body or the new body, never a truncated buffer.

readonly dir: string

Defined in: src/persistence/object-storage/FilesystemObjectStorageBackend.ts:59

Root directory. Will be created (recursively) if it doesn’t exist.


readonly optional lockTimeoutMs?: number

Defined in: src/persistence/object-storage/FilesystemObjectStorageBackend.ts:66

How long to wait when contending for a per-key write lock before giving up with ObjectStorageBackendError. Default 5_000 ms — long enough that legitimate contenders complete first, short enough that a stuck holder gets surfaced quickly.


readonly optional staleLockMs?: number

Defined in: src/persistence/object-storage/FilesystemObjectStorageBackend.ts:73

Lock files older than this are assumed stale (left behind by a crashed writer) and forcibly removed. Default 30_000 ms — well above the expected duration of any single put, so legitimate writers never get their lock yanked.