A filesystem in the browser.
OPFS or IndexedDB holds the working files and .git. Edits and commits happen locally.
Your browser is a working copy.
The bucket is the shared history.
Put a filesystem in the browser, let each tab make real Git commits, and use a write-ahead log to publish them to Cloudflare R2.
See how the pieces fitOPFS or IndexedDB holds the working files and .git. Edits and commits happen locally.
Give every replica its own storage namespace. One copy can change while another stays put.
isomorphic-git creates Git objects. A walgit-inspired log coordinates what becomes shared.
packs/<hash>.packBase historywal/<entry>.jsonNo new entriesmanifest.jsonmain → c0ffee1All three copies start at the same commit. Make a change in Tab A to see how it reaches the others.
This page illustrates the proposed architecture. It does not run Git, create an OPFS filesystem, or connect to an R2 bucket.
Cloudflare Pages serves the website. These are the pieces behind the proposed experience.
Store working files, the Git index, objects, and refs. Start with LightningFS for isomorphic-git, or use an OPFS adapter. Unpublished work lives here.
Create commits, read trees, make branches, and build packs inside the browser. A Web Worker can keep Git work off the page’s main thread.
Upload immutable Git data and log entries. Advance one manifest with a conditional write. New copies recover published history from a checkpoint and the log.
Check repository access in the Function. Use R2’s ETag conditions to reject stale writes. Keep bucket credentials on the server.
Read the manifest and its ETag together. A write succeeds only if that ETag still matches. If it changed, fetch the new history and reconcile.
R2 performs this check atomically. A read followed by an unconditional overwrite would leave a race.
// Objects and WAL entry are already uploaded.
const result = await env.REPOS.put(
manifestKey,
JSON.stringify(nextManifest),
{ onlyIf: { etagMatches: expectedEtag } }
);
if (result === null) {
// Another writer changed the manifest.
return new Response("Fetch and reconcile", {
status: 412
});
}Browser storage is shared by origin. Give each copy its own directory or database namespace. Tabs using the same copy need a local lock.
R2 documents a limit of one write per second to the same key. Keep frequent edits local and publish commits in batches when needed.
Read it through the R2 binding and return it without caching. Immutable packs can be cached. Give every update a new sequence and operation ID.
Upstream walgit is a Rust server. A browser version needs a JavaScript WAL implementation. Standard git clone would also need a Git protocol endpoint.