Autobee
Multiwriter Hyperbee: many writers linearized into one deterministic key/value view.
v2.2.2Autobee gives a Hyperbee many writers. Each peer appends to its own local Hypercore; Autobee linearizes those cores into one causal order and replays it through your apply handler, which writes into a shared B-tree view. Every peer that replicates the same nodes derives the same view.
It sits alongside Autobase rather than replacing it. Autobase linearizes into a view of any shape you open; Autobee is the case where that view is always a Hyperbee, with the B-tree write path built in.
Upstream describes Autobee as experimental and under heavy development, with breaking changes expected. Pin an exact version, and re-read this page against your installed release before upgrading. It is documented here against v2.2.2.
The apply handler
Treat apply like a pure reducer: given an ordered batch of nodes and a writable view, derive the next view state deterministically. Mutate only the view passed into apply. Do not:
- read or write external globals,
- open network connections, or
- assume ordering that Autobee has not yet committed. Side effects belong outside the linearization path.
Autobee reorders previously seen nodes when new causal information arrives, undoing and reapplying the view. A non-deterministic apply makes peers diverge, and the undo path cannot roll back effects it never saw.
Node values are raw bytes. Autobee has no valueEncoding, so apply decodes whatever your writers encoded:
async function apply (nodes, view, host) {
for (const node of nodes) {
const op = JSON.parse(b4a.toString(node.value))
if (op.addWriter) {
await host.addWriter(op.addWriter)
continue
}
const batch = view.write()
batch.tryPut(b4a.from(op.key), b4a.from(op.value))
await batch.flush()
}
}Install
npm i autobeeQuickstart
import Corestore from 'corestore'
import Autobee from 'autobee'
import b4a from 'b4a'
const store = new Corestore('./autobee-demo')
const db = new Autobee(store, null, {
async apply (nodes, view) {
for (const node of nodes) {
const op = JSON.parse(b4a.toString(node.value))
const batch = view.write()
batch.tryPut(b4a.from(op.key), b4a.from(op.value))
await batch.flush()
}
}
})
await db.ready()
await db.append(b4a.from(JSON.stringify({ key: 'greeting', value: 'hello' })))
await db.update()
const entry = await db.view.get(b4a.from('greeting'))
console.log(b4a.toString(entry.value))
// helloAPI Reference
Constructor and lifecycle
const db = new Autobee(store, key, options)
Create an Autobee. Passing key joins an existing database; omitting it or passing null creates a new one. options may be passed as the second argument when there is no key.
| Parameter | Type | Default | Description |
|---|---|---|---|
store | Corestore | — | The Corestore holding the system, writer, and view cores. |
key | Buffer|string | null | Key of an existing Autobee to join. Accepts a z-base-32 id or hex string. |
options | object | {} | Handlers and configuration, below. |
| Option | Default | Description |
|---|---|---|
apply | — | apply(nodes, view, host) — reduce a batch of linearized nodes into the writable view. |
open | — | open(bee, db) — wrap the Hyperbee in a custom view. Whatever it returns becomes db.view and the view passed to apply. Defaults to the Hyperbee itself. |
close | — | close(view) — tear down a custom view when the database closes. |
update | — | update(view, changes) — called after an apply cycle that changed something. This is the hook for reacting to view changes; there is no view-changed event. |
optimistic | true | Accept optimistic appends from peers that are not yet writers. Validate them in apply before calling host.ackWriter. |
encryptionKey | — | 32-byte key (or a promise for one) encrypting every writer core and the view at rest. All peers must use the same key. |
encrypted | false | Expect the database to be encrypted. Implied by encryptionKey. |
keyPair | — | Signing key pair (or a promise for one) for the local writer. |
viewName | 'view' | Name of the backing view core. |
bootstrapWeight | 2 | Weight assigned to the bootstrapping writer. See upstream WEIGHTS.md. |
isTrusted | — | isTrusted(key, reference) — whether a writer is trusted for fast-forward. |
mostRecentTrusted | — | mostRecentTrusted(target, reference) — the oplog head you vouch for. |
fastForward | {} | Fast-forward configuration, or false to disable fast-forwarding entirely. See FastForwardOptions. |
preapply | — | One-shot gate awaited before the first apply, for state apply depends on. |
name | null | Label used in debug output only. |
optimistic defaults to true, so by default any peer that can reach your network path may attempt an optimistic append. Verify those blocks inside apply before calling host.ackWriter, or pass optimistic: false.
await db.ready()
Resolves once the database and its view are open.
- Returns:
Promise<void>
await db.close()
Closes the database, its view, and the sessions it opened on store.
- Returns:
Promise<void>
Identity
db.key
The public key of this Autobee. Share it so other peers can join.
- Returns:
Buffer
db.discoveryKey
The discovery key, used as the swarm topic.
- Returns:
Buffer
db.id
db.key as a z-base-32 string — the printable form to hand to peers.
- Returns:
string
db.local
This peer's own writer Hypercore. db.local.key or db.local.id is what an existing writer passes to host.addWriter to grant write access.
- Returns:
Hypercore
State
db.writable
Whether this instance has been added as a writer. Listen for db.on('writable', ...) to be notified when this flips, rather than polling it.
- Returns:
boolean
db.isIndexer
Whether this writer is an indexer.
- Returns:
boolean
db.view
The readable view, updated after each apply cycle. Without an open handler this is the Hyperbee snapshot itself, so read it with the Hyperbee API — view.get(key), view.createReadStream(), view.peek(range).
db.bee
The underlying Hyperbee snapshot. This is what open receives, so it is the same object as db.view only when no open handler is set — with one, db.view is whatever that handler returned and db.bee is still the raw B-tree.
db.stats
Counters for the current session: { undos, fastForwards, drains, applies, appends }. Useful for confirming that apply cycles and reorders are happening.
- Returns:
object
db.optimistic
Whether optimistic appends are accepted.
- Returns:
boolean
db.encrypted
Whether writer cores and the view are encrypted at rest.
- Returns:
boolean
Writes and linearization
await db.append(value, options)
Append one value, or an array of values as one batch, to the local writer and trigger an apply cycle. Strings are converted to buffers; anything else must already be a buffer, so encode structured values yourself.
| Parameter | Type | Description |
|---|---|---|
value | Buffer|string|Array | The value, or array of values, to append. |
options | AppendOptions | Append options. |
- Returns:
Promise<void> - Throws:
Autobee closedif the database is closing.Not writableif the local writer has been removed and the append is not optimistic.
await db.update()
Run an apply cycle over everything currently available. Call it after replicating to process what arrived.
- Returns:
Promise<void>
await db.updated()
Resolves once the in-flight apply cycle has finished, or immediately if none is running.
- Returns:
Promise<void>
await db.flush()
Resolves once the database has finished booting online — that is, once known writers have been indexed.
- Returns:
Promise<void>
Replication
const stream = db.replicate(isInitiatorOrStream, options)
Create a replication stream. Arguments match store.replicate(), and the stream is also registered with the wakeup protocol.
Prefer db.replicate(connection) over store.replicate(connection). Both replicate the cores, but only db.replicate registers the stream for wakeup hints, which is how peers learn that an idle writer has new data.
const swarm = new Hyperswarm()
swarm.join(db.discoveryKey)
swarm.on('connection', (conn) => db.replicate(conn))await db.wakeup({ key, length })
Tell the database that the writer core key has at least length entries, then run a cycle. Use it when you learn about a writer out of band.
- Returns:
Promise<void>
db.hintWakeup(wakeup)
Queue a wakeup hint, or an array of them, without awaiting a cycle.
const cores = await db.cores(options)
Added in v2.2.0. Report which cores a mirror should pin so it can replicate this database without knowing its internal structure — the local writer, this peer's views if it is trusted, and (with wait) the most recently trusted head, so a mirror that pins only what this call returns still lands on a valid entrypoint.
| Option | Default | Description |
|---|---|---|
local | true | Include this peer's own writer core, and its views if this peer is trusted. |
wait | true | Resolve the most recently trusted head and include its writer and view cores too. |
all | false | Also include every core currently known to the view and system bees, not just the trusted head's. Only takes effect when wait is also true — it is gated inside that branch, so { wait: false, all: true } includes nothing extra. |
- Returns:
Promise<object>— resolves to{ key, views, writers }:keyisdb.key,writersis an array of writer core keys,viewsis an array of view/system core keys.
Fast-forward
Fast-forward lets a peer that is far behind jump onto a head another peer vouches for, instead of replaying everything. It deals only in oplog heads — { key, length } of a writer's core.
await db.moveTo(head)
Fast-forward onto head, skipping the usual distance and conservative checks.
- Returns:
Promise<object>— resolves to{ to, from }.
db.getMostRecentHead()
The oplog head this peer most recently vouched for.
Local writer
await db.setLocal(key, options)
Rotate the local writer onto a different key; the new core takes over as the active oplog. Pass { keyPair } to supply its signing keys. Emits rotate-local-writer.
- Returns:
Promise<void>
Static methods
Autobee.isAutobee(value)
Whether value is an Autobee instance.
- Returns:
boolean
const buf = Autobee.encodeValue(value, opts)
Encode a value into an Autobee block with its metadata.
- Returns:
Buffer
const value = Autobee.decodeValue(buf, opts)
Decode an Autobee block back to its value.
Autobee.GENESIS
{ key: null, length: 0 } — the empty head representing the genesis state.
Events
db.on('update', () => { ... })
Emitted after an interrupt, alongside interrupt, and separately whenever the local writer's writable state flips (see db.on('writable', ...) below) — src. It is not a general view-changed signal for an ordinary apply cycle; use the update(view, changes) handler option for that.
db.on('writable', () => { ... })
Emitted when this instance transitions from not-writable to writable — that is, when db.writable flips to true. Fired from ActiveWriters._updateLocalState(), which runs as part of ordinary writer-set processing during an apply cycle, not only from an interrupt.
db.on('unwritable', () => { ... })
Emitted when this instance transitions from writable to not-writable — the counterpart to 'writable', fired by the same _updateLocalState() check.
db.on('interrupt', (reason) => { ... })
Emitted when host.interrupt(reason) is called inside apply.
db.on('error', (err) => { ... })
Emitted when applying fails. With no listener attached the process crashes deliberately, so attach one.
db.on('rotate-local-writer', () => { ... })
Emitted after db.setLocal() swaps the local writer.
db.on('move-to', (to, from) => { ... })
Emitted after a fast-forward, with the heads landed on and left behind.
AutobeeHostCalls
The host argument passed to apply. Its calls change the writer set, and because they run from inside apply every peer reaches the same result from the same nodes.
host.addWriter(key, options)
Add a writer. key may be a buffer, a hex string, or a z-base-32 id.
| Option | Default | Description |
|---|---|---|
isIndexer | true | Add the writer as an indexer, not just a writer. |
weight | 2 when isIndexer, else 1 | Voting weight for this writer. See upstream WEIGHTS.md. |
host.removeWriter(key)
Remove a writer.
host.ackWriter(key)
Acknowledge a writer without changing its permissions. Required for an optimistic block to be applied.
host.interrupt(reason)
Stop the current apply cycle and emit interrupt with reason. An escape hatch for when apply meets a node it cannot handle — a newer block type, say — so the peer stops rather than diverging.
- Throws: only callable inside
apply; asserts otherwise.
const anchor = await host.createAnchor(key, length)
Create a verifiable checkpoint for the node at key/length, which future writers can use to prove causal ordering.
- Returns:
Promise<object>— resolves to{ key, length }of the anchor core. - Throws:
Anchor node is not in systemif that node is not in the batch being applied, or not yet in the system core.
host.genesis
Whether the system has processed no nodes yet. Use it to bootstrap the first writer.
- Returns:
boolean
host.clock
The system flush count — a monotonic counter of committed apply cycles.
- Returns:
number
host also mirrors id, key, discoveryKey, and name from the database.
Two host calls are stubs in v2.2.2 and should not be relied on: host.removeable(key) always returns true, and host.preferFastForward() does nothing.
Types
AppendOptions
Options for db.append().
| Property | Type | Default | Description |
|---|---|---|---|
optimistic | boolean | false | Append without being a confirmed writer. The block is applied only if apply validates it and calls host.ackWriter. |
FastForwardOptions
The fastForward option. Pass false instead of an object to disable fast-forwarding entirely.
| Property | Type | Default | Description |
|---|---|---|---|
boot | object | — | { head, bootCondition, wait } — the oplog head to boot from, an optional gate on the view it would land on, and wait (default false) to force the indefinite-retry path even with no bootCondition. Without either bootCondition or wait, a failed read gives up after one attempt. |
conservative | boolean | true | Only fast-forward onto a head a connected peer can serve whole. The check covers the oplog head, not the system and view cores read afterwards. |
boot.legacy accepts pre-2.0 pointers, where the key is a system head. It boots ungated, ignores bootCondition, and upstream plans to remove it. Don't reach for it in new code.
UpdateChanges
The changes argument passed to the update handler. changes.get(name) returns a record for 'view' or 'system':
| Property | Type | Description |
|---|---|---|
from | object | The head before this cycle. |
to | object | The head after it. |
flushes | number | The system flush count at to. |
incremental | boolean | Whether the change extended the previous state rather than replacing it. Always false in v2.2.2 — track() hardcodes it and nothing reassigns it, so this field carries no information yet. |
See also
- Build a multiwriter database with Autobee—two-peer walkthrough covering
apply, theupdatehook, and granting write access. - Autobase—the general multiwriter linearizer, for views that are not a Hyperbee.
- Hyperbee—the single-writer B-tree whose read API
db.viewexposes. - Corestore—storage and replication manager for the system, writer, and view cores.
- Hypercore—append-only log primitive that each writer appends to.