Hyperbee: a database on a log
Hyperbee is a B-tree encoded as
Hypercore blocks: each put appends a node describing the tree change. The
result is a sorted key/value database with the replication properties of a
log — single writer, many verified readers, sparse download.
const Hypercore = require('hypercore')
const Hyperbee = require('hyperbee')
const bee = new Hyperbee(new Hypercore('./db'), {
keyEncoding: 'utf-8',
valueEncoding: 'json'
})
await bee.put('profile!alice', { nick: 'alice' })
const node = await bee.get('profile!alice') // { key, value } or null
await bee.del('profile!alice')
The idiom: key ranges are your tables
There are no tables or secondary indexes — you design your keyspace so
ordered scans answer your queries:
post!front!new!<invertedTs>!<id> → post ref
comment!<postId>!<ts>!<id> → comment ref
vote!<targetId>!<voter> → direction
Then a prefix scan is a query:
for await (const { key, value } of bee.createReadStream({
gt: 'comment!abc!', lt: 'comment!abc!~'
})) { /* every comment on post abc, in time order */ }
Trick worth stealing: inverted timestamps (999…9 - ms) make ascending
scans return newest-first, so "latest 50 posts" is a limited range read.
Two ways to use it
- Replicated bee — the author writes, readers replicate and query
sparsely (a reader checking one key downloads ~log(n) blocks).
- Local index — a private bee as your app's query layer. This is
p2pbuilders: the indexer folds every followed Hypercore into a local
Hyperbee (post!…, vote!…, rep!…), and the UI only ever queries
the bee. Rebuildable state, ordered queries, no SQL dependency.