Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

blob-branch

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.

Why

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.

Usage

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/blobget, 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 plain std/blob caller). Blobs belonging to other branches are excluded. On a key collision, the current branch wins — listAll only ever shows the key get would actually resolve.
  • copy, move, getJSON, and setJSON bu``ild on the core functions, so they inherit branch-awareness automatically.

Example: same key, different data per branch

BranchCallRaw stored key
mainblob.set("config", …)__branch/main/config
devblob.set("config", …)__branch/dev/config

The two writes never collide.

API

FunctionDescription
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.

Key namespacing

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.

Caveats

  • 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). Use listAll() to discover them, then migrate them under __branch/<branch>/ if a branch should own them. Note that listAll keeps surfacing unscoped blobs until they're migrated or deleted.
  • The reserved prefix is __branch/. Avoid storing your own keys there.