Function: createFsArtifactStore()
createFsArtifactStore(
root):ArtifactStore
Defined in: createFsArtifactStore.ts:98
Builds the reference ArtifactStore over a directory — the store the studio, the example app
and the documented @nubbin/cli setup all publish through, and the one a replacement adapter is
measured against.
Three kinds of file live under root, and nothing else does:
<root>/
artifacts/<hash>.json one per written artifact, never rewritten
routes/%2Fpromotions%2Fsummer.json the pointer — the only file that moves
history/%2Fpromotions%2Fsummer.jsonl one line per publish, oldest first
A route key is percent-encoded, so a route with slashes is one flat filename rather than a
directory tree, and manifest() can list routes/ to find every published route. The logs sit
in their own directory for that reason: a .jsonl filed among the pointers would be read as one
and break the listing.
Nothing is created until something is written — root need not exist, and each write makes the
directory it needs. Reading before then is not a failure: an unknown hash and an unpublished
route both read as null, and a store with no routes/ yet manifests as no routes.
Repeating a call is safe, which is what a publish retried after a timeout needs, but the two
repeats differ. Writing a hash the store already holds leaves the file alone — a content address
that already resolves holds the same bytes by construction. Publishing the same route and hash
again does not: the pointer is rewritten with a fresh updatedAt and a second move is appended
to the log. Content addressing dedupes artifacts, not moves.
Every ArtifactStore method is implemented, history included, so a caller reading a route's
moves needs no ?? [] fallback for this store. Within one publish the pointer moves first and
the log is appended after: a crash between the two leaves the log one entry short, which
under-reports. The opposite order would let the log claim a publish that never went live.
Parameters
root
string
The directory the store owns, absolute or resolved against process.cwd(). It
owns everything beneath it, so give it a directory nothing else writes into. Two stores over
one root are the same store, and so is a second process pointed at it — the state is the files.
Returns
ArtifactStore
An ArtifactStore bound to root. It holds no cache and no open handle: every call
reads the filesystem, so one built at module scope stays correct as other processes publish
underneath it.
Throws
From publish, coded artifact-not-stored when nothing has been written
at that hash. A pointer at an unwritten hash would be a live 404, so the existence check comes
before the pointer is touched.
Throws
From publish, coded invalid-route when the route addresses no page.
Core's parseMatchKind judges it, so an adapter that never called compile still cannot
publish an unaddressable route.
Throws
A Node filesystem error, unchanged, when the operation itself fails — no permission on
root, a full disk, or a path taken by something that is not the expected kind of file.
ENOENT alone is not one of these: it is how absence is read.
Throws
A SyntaxError from read, pointer or manifest when a file under root is not the
JSON this store wrote. Hand-editing the directory, or pointing two tools at one root, is what
produces that.
Examples
Compile, store, publish
import { compile } from "@nubbin/core";
import { createFsArtifactStore } from "@nubbin/store-fs";
const store = createFsArtifactStore("./.nubbin");
const { artifact } = compile(version, catalog, registry, "/promotions/summer");
await store.write(artifact);
await store.publish(artifact.route, artifact.hash);
Serve a request from the store
const pointer = await store.pointer("/promotions/summer");
const artifact = pointer === null ? null : await store.read(pointer.hash);
Roll a route back to what it pointed at before
const moves = await store.history?.("/promotions/summer") ?? [];
const previous = moves.at(-2);
if (previous !== undefined) {
await store.publish("/promotions/summer", previous.hash);
}