Unstoppable, scalable multiwriter Hyperbee.
Still experimental and under heavy development. Expect breaking changes.
npm install autobeeMultiple peers each write to their own local Hypercore. An apply function you provide merges those writes into a shared Hyperbee view deterministically. The view is consistent across all peers once they replicate.
const Autobee = require('autobee')
const Corestore = require('corestore')
const store = new Corestore('./my-db')
const db = new Autobee(store, null, { apply })
await db.ready()
// append some data
await db.append(Buffer.from(JSON.stringify({ hello: 'world' })))
// read it back from the view
const node = await db.view.get(Buffer.from('latest'))
console.log(JSON.parse(node.value))
async function apply(nodes, view, host) {
for (const node of nodes) {
const op = JSON.parse(node.value)
if (op.addWriter) host.addWriter(op.addWriter)
if (op.removeWriter) host.removeWriter(op.removeWriter)
const w = view.write()
w.tryPut(Buffer.from('latest'), node.value)
await w.flush()
}
}To add a second writer and replicate:
const db1 = new Autobee(store1, null, { apply })
await db1.ready()
// share db1.key with others so they can join
const db2 = new Autobee(store2, db1.key, { apply })
await db2.ready()
// db1 adds db2 as a writer
await db1.append(Buffer.from(JSON.stringify({ addWriter: db2.local.id })))
// replicate using any stream
const s1 = db1.replicate(true)
const s2 = db2.replicate(false)
s1.pipe(s2).pipe(s1)Create a new Autobee. store is a Corestore. key is the public key of an existing Autobee to join — omit or pass null to create a new one.
Options:
{
apply (nodes, view, host) {}, // called with batches of new nodes to apply to the view
open (bee, db) {}, // called to create a custom view, return it
close (view) {}, // called when the db closes
update (view, changes) {}, // called after apply when the view has been updated
encryptionKey: Buffer, // 32-byte key to encrypt all data at rest
encrypted: false, // set true if using encryptionKey
keyPair: { publicKey, secretKey }, // custom signing key pair for the local writer
optimistic: true, // allow optimistic writes from unknown writers
isTrusted (key, reference) {}, // do we trust this writer, see Fast-forward
mostRecentTrusted (target, reference) {}, // the head we vouch for, see Fast-forward
fastForward: {} // see Fast-forward
}The public key of this Autobee. Share this with peers so they can join.
The discovery key. Use this to find peers on the network.
The public key encoded as a hex string.
The local writer Hypercore. Use db.local.key or db.local.id to identify this writer to others.
A read-only snapshot of the Hyperbee view. Updated after each apply cycle. Use the standard Hyperbee API to read from it.
Alias for db.view.
true if this instance has been added as a writer.
true if this writer is an indexer.
Append one or more values to the local writer. Triggers an apply cycle.
await db.append(Buffer.from('hello'))
await db.append([buf1, buf2, buf3])Optionally pass { optimistic: true } to write without waiting to be a confirmed writer.
await db.append(buf, { optimistic: true })Trigger a new apply cycle. Useful after replication to process new data.
Wait until the current apply cycle has finished.
Wait until all known writers have been fully indexed.
Create a replication stream. Pass true for the initiating side, false for the other.
const s1 = db1.replicate(true)
const s2 = db2.replicate(false)
s1.pipe(s2).pipe(s1)Hint that a new writer core is available at key with at least length entries. Used to wake up replication when you learn about a peer out of band.
Rotate the local writer to a different key. The new writer takes over as the active oplog.
Returns the current system and view core positions. Used for replication coordination.
Returns true if val is an Autobee instance.
The apply function is called with a batch of nodes from writers, a writable view (Hyperbee batch), and a host object.
async function apply(nodes, view, host) {
for (const node of nodes) {
// node.key — writer public key (Buffer)
// node.value — the value appended (Buffer)
// node.length — position in the writer's core
const op = JSON.parse(node.value)
// manage writers
if (op.addWriter) host.addWriter(op.addWriter)
if (op.removeWriter) host.removeWriter(op.removeWriter)
// write to the view
const w = view.write()
w.tryPut(Buffer.from('key'), node.value)
await w.flush()
}
}Add a writer by public key (Buffer or hex string). Options:
{
isIndexer: true // default
}Remove a writer by public key (Buffer or hex string).
Acknowledge a writer without changing their permissions.
Interrupt the current apply cycle. The db emits 'interrupt' with the reason. Useful for pausing apply while waiting on external data.
Create an anchor node. Returns { key, length }. Anchors are used to create a verifiable checkpoint in the log that can be used by future writers to prove causal ordering.
true if the system has not yet processed any nodes. Use this to bootstrap the first writer.
Fast-forward only ever deals in oplog heads — { key, length } of a writer's core, never a system head.
Each flush stamps the head you vouch for into your own oplog, and peers read those stamps out of the writers they wake up on, so trust travels with the log.
Return whether key is a writer you trust, judged against the reference view.
Positive answers are cached until an undo rewinds the view, and the default is true unless you supply mostRecentTrusted, in which case it is false.
Return the oplog head you most recently vouched for, given the target view being considered.
Called at flush time to stamp your own oplog (with your view as target and a null reference), and again per candidate during discovery.
{
head: { key, length }, // oplog head to boot from
legacy: { key, length }, // pre-2.0 pointer, see below
bootCondition (target, reference) {} // optional gate on the view we would land on
}Pass one of head or legacy, not both.
Without bootCondition this is a single attempt that gives up if the head cannot be read; with one it parks until the condition is satisfied, which today means indefinitely if it never is.
legacy is for records written before boot heads were oplog heads: the key is a system head and a 0 length is resolved from the core. It boots ungated - bootCondition does not apply - and will be removed, so don't reach for it. A bare { key, length } in place of the whole struct means the same thing, older still.
Defaults to true: only fast-forward onto a head a connected peer can serve whole.
The check covers the oplog head only, not the system and view cores the fast-forward then reads.
Fast-forward onto an oplog head, ignoring the usual distance and conservative checks. Resolves { to, from }.
Pass an encryptionKey to encrypt all writer cores and the view at rest.
const db = new Autobee(store, null, {
apply,
encrypted: true,
encryptionKey: crypto.randomBytes(32)
})All peers must use the same encryption key.
Encode a value into an Autobee block with optional metadata.
Decode an Autobee block back to its value.
{ length: 0, key: null }. The empty head used to represent the genesis state.
Apache-2.0