Autobase
Multi-writer linearization layer for building deterministic views from many Hypercores.
Autobase is a higher-level composition over underlying Hypercores. Writers append causal nodes to local cores, Autobase linearizes that graph into an eventually consistent order, and your apply handler materializes a deterministic view, often into a Hyperbee.
The apply handler
Treat apply like a pure reducer: given an ordered batch of nodes and a view handle, 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 Autobase has not yet committed. Side effects belong outside the linearization path.
Autobase can reorder previously seen nodes when new causal information arrives. If apply is non-deterministic, different peers will diverge. Writer management (host.addWriter, indexer flags) and migration between apply versions are advanced topics—keep them inside apply only when they operate on the provided view and host handles.
Install
npm i autobaseQuickstart
This minimal flow opens a view, appends one record, updates the linearizer, and reads the materialized value back from base.view.
import Corestore from 'corestore'
import Autobase from 'autobase'
const store = new Corestore('./autobase-demo')
const base = new Autobase(store, null, {
open: (viewStore) => viewStore.get({ name: 'messages', valueEncoding: 'json' }),
apply: async (nodes, view) => {
for (const { value } of nodes) await view.append(value)
}
})
await base.ready()
await base.append({ type: 'message', text: 'hello from Autobase' })
await base.update()
console.log(await base.view.get(0))
// { type: 'message', text: 'hello from Autobase' }Autobase can reorder previously seen nodes as new causal information arrives. Keep open and apply deterministic, derive state from the provided store, and mutate only the provided view inside apply.
API Reference
Constructor and lifecycle
new Autobase(store, bootstrap, opts)
Instantiate an Autobase.
| Parameter | Type | Default | Description |
|---|---|---|---|
store | Corestore | — | The Corestore that holds the system, writer, and view cores. |
bootstrap | Buffer|string | — | Key of an existing Autobase to load; omit or pass null to create a new one. |
opts | object | {} | Configuration: open, apply, close, valueEncoding, ackInterval, fastForward, encrypt/encryptionKey, optimistic, and more (see the options table). |
| Option | Default | Description |
|---|---|---|
open | — | create the view |
apply | — | handle nodes to update view |
optimistic | false | Autobase supports optimistic appends |
close | — | close the view |
valueEncoding | — | encoding |
ackInterval | 1000 | enable auto acking with the interval |
encryptionKey | — | Key to encrypt the base |
encrypt | false | Encrypt the base if unencrypted & no encryptionKey is set |
encrypted | false | Expect the base to be encrypted, will throw an error otherwise, defaults to true if encryptionKey is set |
fastForward | true | Enable fast forwarding. If passing { key: base.core.key }, they autobase will fastforward to that key first. |
wakeup | — | Set a custom wakeup protocol for hinting which writers are active, see protomux-wakeup for protocol details |
bigBatches | false | Enable big batches. See base.setBigBatches() for details. |
const base = new Autobase(store, bootstrap, {
optimistic: true,
async apply(nodes, view, host) {
for (const node of nodes) {
const { value } = node
// Verify block
if (value.password !== 'secrets') continue
// Acknowledge only even numbers
if (value.num % 2 === 0) await host.ackWriter(node.from.key)
await view.append(value.num)
}
}
})
// Passing the password 'secrets' represents being verifiable
await base.append({ num: 3, password: 'secrets' }, { optimistic: true }) // will not be applied because `ackWriter` isnt called
await base.append({ num: 2, password: 'secrets' }, { optimistic: true }) // will be applied
await base.append({ num: 4, password: 'incorrect' }, { optimistic: true }) // will not be applied because the block isn't verifiableWhen optimistic is enabled, validate optimistic blocks inside apply before calling host.ackWriter(key). Otherwise any peer that can write to the underlying network path could attempt to inject values.
await base.ready()
Resolves once the base and its view are open and ready to use.
State and derived view
base.view
The view of the autobase derived from writer inputs. The view is created in the open handler and can have any shape. The most common view is a hyperbee.
- Returns:
*
base.key
The primary key of the autobase.
- Returns:
Buffer
base.discoveryKey
The discovery key associated with the autobase.
- Returns:
Buffer
base.isIndexer
Whether the instance is an indexer.
- Returns:
boolean
base.writable
Whether the instance is a writer for the autobase.
- Returns:
boolean
base.length
The length of the system core. This is neither the length of the local writer nor the length of the view. The system core tracks the autobase as a whole.
- Returns:
number
base.signedLength
The index of the system core that has been signed by a quorum of indexers. The system up until this point will not change.
- Returns:
number
base.paused
Whether the base is currently paused.
- Returns:
boolean—trueif the autobase is currently paused, otherwise returnsfalse.
Writes and linearization
await base.append(value, opts)
Append a new entry to the autobase.
| Parameter | Type | Description |
|---|---|---|
value | *|Array<*> | The value, or array of values, to append. |
opts | AppendOptions | Append options. |
- Returns:
Promise<number>— Resolves to the new core length once the value (or batch) has been appended. - Throws:
- if the autobase is closing.
- if the local writer is not active/writable.
await base.update()
Fetch all available data and update the linearizer.
- Returns:
Promise<void>— Resolves once the linearizer has advanced.
await base.update()await base.ack(bg = false)
Manually acknowledge the current state by appending a null node that references known head nodes. null nodes are ignored by the apply handler and only serve as a way to acknowledge the current linearized state. Only indexers can ack.
| Parameter | Type | Default | Description |
|---|---|---|---|
bg | boolean | false | If bg is set to true, the ack will not be appended immediately but will set the automatic ack timer to trigger as soon as possible. |
- Returns:
Promise<void>— Resolves once the ack has been handled.
await base.ack()Only indexers can acknowledge. An ack appends a null node that references the known heads so peers can converge faster without changing the view.
base.heads()
Gets the current writer heads. A writer head is a node which has no causal dependents, aka it is the latest write. If there is more than one head, there is a causal fork which is pretty common.
- Returns:
Array<object>— The head nodes (empty until the base is ready).
const heads = base.heads()await base.pause()
Pauses the autobase, preventing the next apply from running until resume() is called.
- Returns:
void
base.pause()await base.resume()
Resumes a paused autobase and will check for an update.
- Returns:
void
base.resume()base.setBigBatches(enable = true)
Set the autobase to enable or disable big batches. Big batches allow autobase to run the apply function on more blocks at once at the expense of being slower and less responsive.
| Parameter | Type | Default | Description |
|---|---|---|---|
enable | boolean | true | Whether to enable big batches. |
- Returns:
void
base.setBigBatches(true)Big batches let Autobase call apply with more nodes at once, trading responsiveness for larger deterministic batches.
Replication and metadata
await base.hash()
Merkle-tree hash of the system core at its current length.
- Returns:
Promise<Buffer>— the hash of the system core's merkle tree roots.
const hash = await base.hash()base.replicate(isInitiator || stream, opts)
Creates a replication stream for replicating the autobase. Arguments are the same as corestores's .replicate().
| Parameter | Type | Description |
|---|---|---|
isInitiator | boolean|Stream | true/false to open a new stream, or an existing replication stream to attach to. |
opts | object | Replication options forwarded to the underlying Corestore. |
- Returns:
Stream— The replication stream.
const swarm = new Hyperswarm()
// Join a topic
swarm.join(base.discoveryKey)
swarm.on('connection', (connection) => base.replicate(connection))await base.setUserData(key, value)
Sets the User Data value for the provided key. key is a string. value can be either a string or a buffer.
| Parameter | Type |
|---|---|
key | string |
value | Buffer|string |
- Returns:
Promise<void>— Resolves once written.
await base.setUserData('nickname', 'alice')await base.getUserData(key)
Read a local-only value previously stored with setUserData().
| Parameter | Type | Description |
|---|---|---|
key | string | is a string. |
- Returns:
Promise<Buffer\|null>— the User Data value for the providedkey.
const nickname = await base.getUserData('nickname')Static helpers
Autobase.getLocalCore(store, handlers, encryptionKey)
Generate a local core to be used for an Autobase.
| Parameter | Type | Description |
|---|---|---|
store | Corestore | The Corestore to open the core from. |
handlers | object | are any options passed to store to get the core. |
encryptionKey | Buffer | Encryption key, when the core is encrypted. |
- Returns:
Hypercore— The local writer core.
const core = Autobase.getLocalCore(store)await Autobase.getUserData(core)
Get user data associated with an autobase core. referrer is the .key of the autobase the core is from. view is the name of the view.
| Parameter | Type | Description |
|---|---|---|
core | Hypercore | The bootstrap/local core to read from. |
- Returns:
Promise<{referrer: Buffer, view: string\|null}>— The referrer and view name.
const { referrer, view } = await Autobase.getUserData(core)await Autobase.isAutobase(core, opts)
Detect whether a core's first block is an Autobase oplog message.
| Parameter | Type | Default | Description |
|---|---|---|---|
core | Hypercore | — | The core to inspect. |
opts | object | {} | are the same options as core.get(index, opts). |
- Returns:
Promise<boolean>— whether the core is an autobase core.
const isBase = await Autobase.isAutobase(core)View store helper
store.get(name || { name, valueEncoding })
Load a Hypercore by name (passed as name). name should be passed as a string.
- Throws: if no
nameis provided.
This store instance is the AutoStore passed into open, and it is the intended place to create the cores that make up your deterministic view.
Apply host calls
await host.addWriter(key, { indexer = true })
Add a writer with the given key to the autobase allowing their local core to append. If indexer is true, it will be added as an indexer.
| Parameter | Default |
|---|---|
{ indexer | true } |
- Throws: if called on the read-only public view.
await host.removeWriter(key)
Remove a writer from the autobase. This will throw if the writer cannot be removed.
- Throws: if called on the read-only public view, or if it would remove the last indexer.
This throws when the writer cannot be removed, such as when it would leave the system without a removable indexer set.
await host.ackWriter(key)
Acknowledge a writer even if they haven't been added before. This is most useful for applying optimistic blocks from writers that are not currently a writer.
- Throws: if called on the read-only public view.
host.interrupt(reason)
Interrupt the applying of writer blocks optionally giving a reason. This will emit an interrupt event passing the reason to the callback and close the autobase.
- Throws: if called on the read-only public view.
Interrupting closes the Autobase and emits the interrupt event so the application can upgrade its apply logic or recover intentionally.
host.removeable(key)
Whether the writer with key can be removed without leaving the system
without a removable indexer set.
| Parameter | Type | Description |
|---|---|---|
key | Buffer | The writer's key. |
- Returns:
boolean— whether the writer for the givenkeycan be removed.
if (base.removeable(writerKey)) await host.removeWriter(writerKey)The last indexer cannot be removed.
Events
base.on('update', () => { ... })
Triggered when the autobase view updates after apply has finished running.
base.on('interrupt', (reason) => { ... })
Triggered when host.interrupt(reason) is called in the apply handler. See host.interrupt(reason) for when interrupts are used.
base.on('fast-forward', (to, from) => { ... })
Triggered when the autobase fast forwards to a state already with a quorum. to and from are the .signedLength after and before the fast forward respectively.
base.on('is-indexer', () => { ... })
Triggered when the autobase instance is an indexer.
base.on('is-non-indexer', () => { ... })
Triggered when the autobase instance is not an indexer.
base.on('writable', () => { ... })
Triggered when the autobase instance is now a writer.
base.on('unwritable', () => { ... })
Triggered when the autobase instance is no longer a writer.
base.on('warning', (warning) => { ... })
Triggered when a warning is triggered.
base.on('error', (err) => { ... })
Triggered when an error is triggered while updating the autobase.
Types
AppendOptions
Options for base.append().
| Property | Type | Default | Description |
|---|---|---|---|
optimistic | boolean | false | Allow appending on an optimistic Autobase while not a writer; the block is only applied if validated in apply. |
See also
- Work with many Hypercores using Corestore—manage the groups of cores that Autobase coordinates.
- Corestore—storage and replication manager typically used for Autobase system, writer, and view cores.
- Hypercore—append-only log primitive that Autobase writers build on.
- Hyperbee—common materialized-view target for deterministic indexed state.