Overview
#Integrations
unfs connects to other tools in two places: underneath it, and on top of it.
Underneath, everything is stored in a KVStore, a small interface over string keys and byte values. Anything that can get and put bytes can hold a whole filesystem.
On top, createFs (or its synchronous counterpart createFsSync) gives you a node:fs-shaped API. Anything written against node:fs can run on it.
#What's available
| Integration | Plugs in | How it ships |
|---|---|---|
| On-disk store | underneath | built in, unfs/fs |
| SQLite store | underneath | built in, unfs/sqlite |
| LMDB store | underneath | built in, unfs/lmdb |
| unstorage | underneath | built in, unfs/unstorage |
| Remote stores | underneath | built in, unfs/http |
| just-bash | on top | built in, unfs/just-bash |
| @platformatic/vfs | on top | an example adapter to copy |
Start with the on-disk store if you just want your tree to survive the process. Point it at a directory and install nothing.
The SQLite store is the same store in one file instead of a directory, over the SQLite your runtime already ships. Reach for it when you'd rather move one file around, or when enumeration should be an index lookup rather than a directory walk.
Both are synchronous as well as durable, so createFsSync works over either and memory is no longer the only option there.
The LMDB store is the one that brings a real database engine: a memory-mapped B+tree with transactions and one writer at a time across processes. Reach for it when several processes write to the same store, or when read throughput is the thing you're optimizing — and accept a native dependency in exchange.
unstorage covers the most ground. One adapter gives you around 40 drivers, so picking a backend stops being a unfs question at all.
Remote stores are the one backend unfs ships itself, because that wire format is part of the specification.
just-bash gives a sandboxed shell a content-addressed disk, and @platformatic/vfs puts a unfs tree behind Node's own require() and fs.
#Installing them
Each built-in integration is a separate entry point with its own bundle:
import { … } from "unfs/unstorage";You only pay for the ones you import, and importing unfs itself stays exactly as small as it was before any adapter existed.
Where an integration wraps another package, that package is an optional peer dependency. unfs uses it for types only, never for a value, so you install just what you use:
npm install unfs unstorage # or lmdb, or just-bash, or none of themAn adapter never adds a dependency of its own, which is how unfs keeps its zero-runtime-dependency promise for everyone else. unfs/fs wraps no package at all, and imports nothing either, not even node:fs, which it asks the runtime for instead. unfs/sqlite does the same with the runtime's own SQLite.
#Write your own
Both sides are small, and both are public API.
#A new backend
A backend is a KVStore. Only get(key) and set(key, value) are required. has, delete, and keys(prefix) are optional, and adding them unlocks existence checks, gc, and ref enumeration.
Two more are optional and go together in spirit: setIfMatch(key, expected, value) writes only if the key still holds expected, and deleteIfMatch(key, expected) removes it only if it does. They're what make concurrent writers safe, and each is probed on its own — implement them only if your backend can really compare and write in one step. Never emulate one with a read followed by a write: unfs raises UNSUPPORTED for a missing capability, which is the honest answer, and a caller that needs one (a git host deleting a branch, say) can then refuse instead of racing.
Three more are the batched forms — getMany(keys), hasMany(keys) and setMany(entries) — and the rule for all three is the same: implement one only if your backend genuinely has a multi-key operation to map it onto. Where it doesn't, leave the method off. unfs's fallback issues every single-key call before awaiting any, so an async backend already overlaps them, and a method that only re-runs that loop adds a name and no speed. setMany is a batch and not a transaction: unfs assumes nothing about which pairs landed if it rejects, and it never asks for one key to be ordered before another — a block and the ref naming it always arrive as two batches.
Copy unfs/unstorage as a template. It's about 130 lines including comments, and all it has to carry is the key space from §10: three prefixes, nothing more.
Two things to keep in mind before you start:
- Values are opaque bytes and must round-trip exactly. Anything JSON-shaped in the path will corrupt blocks.
- Blocks never change, but refs do. See Layered Stores if you want a fast layer in front of a durable one.
#A new consumer
A consumer is a thin translation layer over createFs or createFsSync. The surface is already node:fs-compatible, so most of the work is renaming and reshaping rather than real logic. unfs/just-bash exports its three translation helpers, so a variant doesn't have to start from scratch.
If your target needs synchronous calls, it also needs a synchronous KV driver underneath: memoryStore(), fsStoreSync(), sqliteStore(), or lmdbStoreSync(). If it can await, anything works.
Built one worth sharing? Open an issue. The list above is short by circumstance, not by design.