A drop-in replacement for Val Town's project-scoped std/blob client that
automatically isolates blob storage per branch. Every read and write is
transparently namespaced by the branch of the val that calls it, so main and
your feature branches never see each other's data — with no change to the public
API.
When you fork a branch to experiment, you usually don't want it reading from or
clobbering the blobs your main branch depends on. blob-branch solves this by
prefixing every key with the current branch name behind the scenes.
import { blob } from "https://esm.town/v/nbbaier/blob-branch/main.ts";
await blob.setJSON("config", { theme: "dark" });
const config = await blob.getJSON("config"); // { theme: "dark" }
await blob.list(); // keys for THIS branch, un-namespaced
await blob.listAll(); // this branch + unscoped (legacy) blobs
await blob.delete("config");
The API mirrors std/blob — get, set, getJSON, setJSON, list,
delete, copy, and move — and adds listAll, which also surfaces blobs
that aren't namespaced to any branch (see below).
list()filters by the branch prefix and strips the namespace off returned keys, so callers always see their original key names.listAll()additionally returns blobs that have no__branch/prefix at all (e.g. data written before adopting this client, or by a plainstd/blobcaller). Blobs belonging to other branches are excluded. On a key collision, the current branch wins —listAllonly ever shows the keygetwould actually resolve.copy,move,getJSON, andsetJSONbu``ild on the core functions, so they inherit branch-awareness automatically.
| Branch | Call | Raw stored key |
|---|---|---|
main | blob.set("config", …) | __branch/main/config |
dev | blob.set("config", …) | __branch/dev/config |
The two writes never collide.
| Function | Description |
|---|---|
getBranchName(val?) | Branch of the given val URL, or the current val. |
blob.get(key) | Get a blob as a Response. |
blob.set(key, value) | Store a blob (BodyInit). |
blob.getJSON(key) | Get and parse JSON, or undefined if not found. |
blob.setJSON(key, val) | Store a value as JSON. |
blob.list(prefix?) | List blob metadata for the current branch. |
blob.listAll(prefix?) | List current-branch blobs plus unscoped blobs. |
blob.delete(key) | Delete a blob. |
blob.copy(prev, next) | Copy a blob to a new key. |
blob.move(prev, next) | Copy then delete the original. |
Slash-namespaced keys (e.g. config/user, config/global) work exactly as
you'd expect. Keys are opaque to the store, so they're stored verbatim after the
branch (__branch/<branch>/config/user), and blob.list("config/") filters to
just that namespace. Branch names are always a single segment (no slashes), so
the __branch/<branch>/<key> boundary is unambiguous.
- Existing data isn't migrated. Blobs written before adopting this client
live at their old un-prefixed keys and won't be visible through
list(which is branch-scoped). UselistAll()to discover them, then migrate them under__branch/<branch>/if a branch should own them. Note thatlistAllkeeps surfacing unscoped blobs until they're migrated or deleted. - The reserved prefix is
__branch/. Avoid storing your own keys there.