11 KiB
Storage Backends
This is the catalogue of every concrete AbstractStorage implementation the platform ships or plans to ship. Each entry lists runtime, capabilities, migration status, and the distinct value it adds.
A backend is "shipped" only if it lives under packages/storage/<pkg>/ on the genesis branch. Anything else is referenced from ../suaveplan (the main branch) or planned.
Capability cheat-sheet
| Backend | Runtime | partialReads | partialWrites | nativeStreams | atomicWrites | contentAddressable | Status |
|---|---|---|---|---|---|---|---|
MemoryStorage |
universal | ✓ | ✓ | ✓ (zero-copy) | ✓ | ✗ | shipped |
NamespacedStorage |
universal | mirrors inner | mirrors inner | mirrors inner | mirrors inner | mirrors inner | shipped |
S3 (storage-s3) |
server | ✓ | ✗ (RMW) | ✓ | ✓ (single-PUT) | ✗ | shipped |
OpfsStorage (storage-browser-opfs) |
browser | ✓ | ✓ | ✓ | partial | ✗ | pending — main |
IndexedDbStorage (storage-browser) |
browser | ✗ (RMW) | ✗ (RMW) | ✗ | ✓ (per-tx) | ✗ | pending — main |
FileStorage (storage-file) |
Node | ✓ | ✓ (fs.write) |
✓ | ✓ (rename) | ✗ | pending — main |
SqliteStorage (planned) |
Bun + browser | ✓ (incrblob) | ✓ (incrblob) | partial | ✓ (per-tx) | ✗ | planned — wire to @suaveplan/db-sqlite |
FatStorage (planned, FAT16/32) |
universal (WASM) | ✓ | ✓ | ✓ (Node) / emulated | ✗ | ✗ | planned — fatfs-wasm or fatfs |
ExfatStorage (deferred) |
universal | — | — | — | — | — | deferred — node-exfat is parser-only |
Ext4Storage (deferred) |
server (native) | ✓ | ✓ | ✗ | ✓ (journal) | ✗ | deferred — lwext4 needs a JS binding + GPL audit |
ContentAddressedStorage (planned) |
wraps any | mirrors inner | ✗ | mirrors inner | inherited | ✓ | planned — derive key from BLAKE3(content) |
GarageStorage |
server (test fixture) | ✓ | ✗ (uses S3) | ✓ | ✓ | ✗ | shipped via testing-integration-garage-s3 |
Backends in detail
MemoryStorage — @suaveplan/storage
In-process map-of-Uint8Array with TTL, OTel metrics, and zero-copy streaming. Used as the default for tests, ephemeral caches, and the in-browser fallback when no persistent backend is available. Capabilities: partialReads, partialWrites, nativeStreams, atomicWrites. Not content-addressable.
NamespacedStorage — @suaveplan/storage
Wraps another AbstractStorage and prefixes every key. Capability mirror: whatever the inner backend can do, the namespaced view can do. This is the primary tool for tenant/partition/share scoping.
S3 — @suaveplan/storage-s3
AWS-SDK-backed S3-compatible backend (works with AWS S3, Garage, MinIO, etc.). set is a single-PUT atomic write; setPartial falls back to RMW because S3 multipart upload requires the upload to be planned in advance and is not byte-addressable in the way setPartial demands. Adds createPresignedUrl(key, options) for delegated browser uploads/downloads.
OpfsStorage (planned — to be ported from main as storage-browser-opfs)
Origin Private File System inside the browser, accessed through the WASM FileSystemSyncAccessHandle API for true byte-addressable reads/writes. This is the right backend for in-browser drives that need real partial writes without round-tripping through a service worker. Capabilities expected: partialReads, partialWrites, nativeStreams. Not yet on genesis.
IndexedDbStorage (planned — to be ported as storage-browser)
IndexedDB key→Blob store. No native partial reads/writes; both fall back to RMW. Right for older browsers, very large numbers of small keys, and structured indexes. Atomic per-transaction.
FileStorage (planned — to be ported as storage-file)
Node fs.promises backed, one file per key. setPartial maps to fs.write(fd, buf, 0, len, offset). Atomic full writes via rename of a temp file. The straight-line backend for any server environment.
SqliteStorage (planned)
Built on @suaveplan/db-sqlite (already shipped). One blob column per key, with incremental_blob_open for byte-addressable reads/writes. The advantage over FileStorage is that you get a journal, transactions, atomic multi-key updates, and a single file to back up. Useful for embedded apps and the browser via @sqlite.org/sqlite-wasm.
FatStorage — FAT16/FAT32 via WASM
Two reference implementations live in /home/ubuntu/web/tmp/:
fatfs-wasm(WASM over CHaN's FatFs, MIT). Production-quality. Supports the full surface —read,write,lseek,unlink,rename,mkdir,stat, dir iteration. Synchronous; whole disk is aUint8Array. Recommended.fatfs(pure JS, Apache-2 + BSD-2). Async + native streams, but missingunlink/rename("Coming Soon™"). Useful only if streaming is critical and you can ship the missing ops yourself.
A FatStorage adapter is a thin mapping:
setPartial(key, offset, bytes) → disk.open(key, WRITE|OPEN_ALWAYS); file.lseek(offset); file.write(bytes); file.close();
read(key) → disk.readFile(key)
delete(key) → disk.unlink(key)
list(prefix) → disk.openDir('/'); iterate; filter
Why FAT? Two real use cases:
- Removable media simulation. A FAT-formatted
Uint8Arraycan be mounted by a real OS — useful for exporting drive snapshots, generating bootable disk images, or interoperating with embedded devices. - Browser-side filesystems with a fixed-size budget. A FAT image inside OPFS gives you POSIX-ish semantics with predictable allocation and trivial backup (it's one blob).
Capabilities: partialReads, partialWrites. nativeStreams=true only for the fatfs (Node) variant; the WASM variant emulates streams via chunked read/write. Not atomic — FAT has no journal.
ExfatStorage — deferred
/home/ubuntu/web/tmp/node-exfat is a parser scaffold, not an FS. It can read VBR/AllocTable/Cluster but exposes no read/write/mkdir/unlink. Until a real exFAT JS binding lands, this backend is deferred. Genesis does not need it — FatStorage covers the FAT family for files <4 GiB; for large files inside an embedded FS, prefer Ext4Storage once it lands.
Ext4Storage — deferred
/home/ubuntu/web/tmp/lwext4 is a C library that supports the full ext2/3/4 surface (extents, sparse files, atomic rename, journal, hardlinks, symlinks, full POSIX permissions, case-sensitive paths). It needs:
- A Node binding (node-gyp / FFI / N-API). ~2 weeks of work.
- License clearance —
lwext4is mixed BSD-3 + GPL-2 (the xattr and extents modules are GPL). Either replace the GPL files or accept GPL-2 for the resulting wasm/binding.
Once both are resolved, the Ext4Storage adapter looks like FatStorage: thin map of setPartial → fseek+fwrite. Capabilities will be partialReads, partialWrites, atomicWrites (journaled).
ContentAddressedStorage — planned wrapper
Wraps any AbstractStorage and replaces the caller-supplied key with BLAKE3(content) (or SHA-256(content), configurable). set becomes idempotent for identical content (cheap dedup), delete(key) becomes a refcount, and capabilities.contentAddressable=true. This is the substrate for drive-level deduplication and for content-addressed chunk stores in network-plugin-content-delivery.
GarageStorage — test only
Garage is an S3-compatible distributed object store used as the integration-test target via @suaveplan/testing-integration-garage-s3. From the application's point of view it is just S3; the fixture spins up a Docker container, configures a layout, creates a bucket, and hands you S3Options you can drop into createS3(). This is how every drive integration test that needs a real network-attached object store gets one.
Choosing a backend
The drive layer is backend-agnostic, but you almost always know which backend is right for a given deployment:
| Use case | Backend |
|---|---|
| Tests, ephemeral caches | MemoryStorage |
| Server-side production, single tenant | FileStorage or SqliteStorage |
| Server-side production, distributed | S3 (any S3-compatible target) |
| Browser, persistent, fast random access | OpfsStorage |
| Browser, structured queries / older browsers | IndexedDbStorage |
| Removable-media export, fixed-size budget, FAT compat | FatStorage |
| Content-addressed chunk pool | wrap any backend in ContentAddressedStorage |
| Multi-tenant scoping | wrap any backend in NamespacedStorage |
You can compose: NamespacedStorage(ContentAddressedStorage(S3(...))) gives you per-tenant content-addressed S3 — the same construction used by drive-plugin-chunks for shared chunk pools.
On-disk shape comparison — 100-file drive
When the drive layer above writes 100 files of ~50 MiB each, the actual files-on-disk count varies dramatically by backend:
| Backend | On-disk shape for 100 × 50 MiB drive | Layout chosen | Why |
|---|---|---|---|
MemoryStorage |
0 files (in-process map) | sparse-file | partialWrites=true |
FileStorage (Node fs) |
~202 files (100 chunk-files + 100 manifests + 2 indexes) | sparse-file | partialWrites=true |
OpfsStorage (browser) |
~202 OPFS handles | sparse-file | partialWrites=true |
S3 |
~80,202 objects (one per chunk) | chunk-keys | partialWrites=false; RMW prohibitive |
IndexedDbStorage |
~80,202 IDB rows | chunk-keys | partialWrites=false |
SqliteStorage |
1 file (one .sqlite with ~80k blob rows) |
sparse-file via incremental_blob_open |
Single-file partition |
FatStorage |
1 file (FAT disk image, mountable by OS) | sparse-file inside FS-in-a-file | True partition file |
ContentAddressedStorage (wrapper) |
~80k unique chunks (deduped across files) | chunk-keys | Hashes coalesce identical content |
So:
FileStorageproduces ~2 files per logical file (chunk-file + manifest).S3produces ~800 objects per logical file (one per chunk + manifest).SqliteStorageandFatStorageproduce one file total — pick these for atomic backup, mountable export, or fixed-size budgets.- The drive's
kind: 'inline'storage hint lets sub-1-KiB files skip the chunks pipeline entirely, reducing the count further.
The drive layer never produces "plaintext files mirroring user paths" on any backend, because that would leak structural metadata to whoever holds the bytes (S3 admins, FAT disk inspectors, etc.). Path mirroring is by-design absent. If plaintext path-mirror semantics are needed, skip the drive layer and write to FileStorage directly — the drive's job is to abstract over storage.
Reading further
architecture.md— the contract every backend implements.implementation-status.md— port-from-main status.../drive/plugins.md— how the chunks plugin picks layouts based on backend capabilities.../drive/index-and-paths.md— the same comparison from the drive side, plus how the two indexes work.../drive/dehydrated-drives.md— server-signed templates and CDN fallback that consume the storage layer.