nomad.fs is the API for hyper:// drives — reading and writing files, drive lifecycle (create/fork/configure), and multi-writer collaboration, all through one surface. Every drive is an Autobase (multi-writer-capable, with a stable URL for life).

stat() returns real mtime, ctime, and size, and get(path, 'json') parses JSON for you.

API

nomad.fs.drive(url)

Create a scoped drive handle. Its methods accept paths relative to the drive rather than full URLs.

var drive = nomad.fs.drive('hyper://abc123../')
await drive.list('/')

Returns an FsDrive instance with all the methods below (scoped to the drive).

Every method also exists as a top-level, URL-first helper — e.g. nomad.fs.readFile('hyper://abc123../index.html').


Reading

  • getInfo([opts]) — Drive info: { key, url, writable, title, description, type, collaborative }. collaborative is whether the drive currently accepts writer-access requests (see Collaboration).
  • stat(path[, opts]){ isFile(), isDirectory(), size, mtime, ctime, metadata }. Real timestamps.
  • entry(path[, opts]) — The raw view entry { key, value: { blob, metadata } }.
  • get(path[, opts]) — Read a file. opts.encoding is one of utf8 (default), binary, base64, hex, or json (parses and returns the object).
  • readFile(path[, opts]) — Alias of get.
  • list(path[, opts]) — Entries under path (a flat, recursive key listing).
  • readdir(path[, opts]) — Immediate children. Pass { includeStats: true } for { name, stat }.
  • query(path[, opts]) — Backend-agnostic listing under a path/prefix.
  • diff(other[, opts]) — Changes vs another version (Hyperdrive only; empty on Autobase for now).
var drive = nomad.fs.drive('hyper://abc123../')
var manifest = await drive.get('/index.json', 'json')   // parsed object
var st = await drive.stat('/index.json')                // st.mtime, st.ctime, st.size
var html = await drive.readFile('/index.html')
var posts = await drive.query('/posts/')

Writing

  • put(path, data[, opts]) — Write a file. data may be a string, Buffer, or (with encoding:'json') an object.
  • writeFile(path, data[, opts]) — Alias of put.
  • del(path[, opts]) / unlink(path[, opts]) — Delete a file.
  • mkdir(path[, opts]) / rmdir(path[, opts]) — Create / remove a directory.
  • copy(src, dst[, opts]) / rename(src, dst[, opts]) — Works within and across drives.
var drive = nomad.fs.drive('hyper://abc123../')
await drive.writeFile('/notes.txt', 'hello')
await drive.put('/data.json', { hi: true }, { encoding: 'json' })
await drive.rename('/notes.txt', '/notes-2.txt')
await drive.del('/data.json')

Writes require the drive to be writable — a Hyperdrive you own, or an Autobase drive you are a writer of.

Watching

nomad.fs.watch(url[, pathSpec][, onChanged])

Watch a drive (or a path prefix) for changes. Returns an EventTarget that emits changed.

var drive = nomad.fs.drive('hyper://abc123../')
drive.watch('/posts/', () => rerender())
// or url-first:
nomad.fs.watch('hyper://abc123../', () => rerender())

Drive lifecycle

These are top-level nomad.fs methods. createDrive/createCollaborativeDrive/forkDrive return a scoped FsDrive.

  • nomad.fs.createDrive([opts]) — Create a new drive. opts: { title, description, collaborative }. Locked / single-writer unless collaborative: true.
  • nomad.fs.createCollaborativeDrive([opts]) — Same as createDrive but defaults to accepting writers; equivalent to createDrive({ ..., collaborative: true }).
  • nomad.fs.forkDrive(url[, opts]) — Fork an existing drive into a new one.
  • nomad.fs.configure(url, settings[, opts]) — Update the drive manifest (title, description, type, thumb, links) and/or collaborative (see below). Also on the scoped handle as drive.configure(settings).
  • nomad.fs.isCollaborativeDrive(url) — Boolean.
// Every new drive is a multi-writer-capable Autobase, but "collaborative" is a policy flag.
var drive = await nomad.fs.createDrive({ title: 'Notes' })          // locked (single-writer)
await nomad.fs.configure(drive.url, { collaborative: true })        // unlock later — SAME URL

Collaboration

Every drive is an Autobase and can gain writers without its URL ever changing. Whether it accepts writers is a policy flag, collaborative, locked by default:

  • A locked drive ignores writer-access requests entirely (it doesn’t advertise the request channel).
  • Unlock it any time via configure(url, { collaborative: true }) — or automatically by inviting/approving a writer. The URL is unchanged, so a drive can start private and open up later.
  • The owner is always the sole gate on who becomes a writer; collaborative just controls whether unsolicited requests are accepted.

Writer management (owner-side unless noted; also available on the scoped drive handle):

  • createInvite(url[, opts]) — Returns an invite URL to share. Unlocks the drive.
  • claimInvite(inviteUrl[, opts]) — Recipient redeems an invite to request write access.
  • requestAccess(url[, opts]) — Ask an already-collaborative drive for write access.
  • listRequests(url) — Pending requests [{ writerKey, profileUrl }].
  • watchRequests(url[, onChanged])EventTarget emitting changed when a request arrives.
  • approveRequest(url, writerKey[, opts]) / denyRequest(url, writerKey) — Owner accepts/rejects.
  • removeWriter(url, writerKey) — Revoke a writer.
  • listWriters(url)[{ writerKey, profileUrl }].
var drive = nomad.fs.drive('hyper://abc123../')
var invite = await drive.createInvite()          // share this; drive is now collaborative
// …recipient: await nomad.fs.claimInvite(invite)
await drive.approveRequest(writerKey)

Draft Mode

Stage edits to a drive privately — synced across your own devices but invisible to every other peer — until you Publish. Backed by your Vault (ADR-0012), so a Draft never enters the drive’s replicated log. While Draft Mode is on, put/writeFile/del stage instead of going live; reads stay on the published view unless you pass { draft: true } to preview the merge.

  • beginDraft(url) / endDraft(url) — Turn a drive’s Draft Mode on/off. { on }.
  • draftStatus(url){ mode, changes: [{ path, op: 'put'|'del', conflict }] }. conflict is true when the base moved under a staged path since it was staged.
  • publishDraft(url[, opts]) — Fold the Draft onto the drive. opts.paths restricts to a subset/subtree; opts.force applies conflicting paths anyway. { published, conflicts }.
  • discardDraft(url[, opts]) — Drop the Draft (or opts.paths subset). { discarded }.
  • watchDraft(url[, onChanged])EventTarget emitting changed when the Draft mutates.
  • setDraftPreview(url, on) — Render the merged Draft in this tab (used by the browser chrome’s “Preview Draft” toggle). Local-only; never replicated.
var drive = nomad.fs.drive('hyper://abc123../')
await drive.beginDraft()
await drive.writeFile('/posts/2026-07-04-hi/post.json', '{"title":"Hi"}')  // staged, not replicated
var preview = await drive.readFile('/posts/2026-07-04-hi/post.json', { draft: true })
await drive.publishDraft({ paths: ['/posts/2026-07-04-hi/'] })             // this post goes live

Publish works only on a device that can write the drive; on a device you merely paired into, Publish is rejected — draft on your phone, Publish from the owning device.

Import / export

  • importFromFilesystem(opts) / exportToFilesystem(opts) / exportToDrive(opts) — Bulk copy between a drive and the local filesystem (or another drive).

Notes

  • Nomad hyper:// is a Nomad dialect. Nomad drives are Autobase-backed and are not readable by generic hyper:// clients.