https://github.com/holepunchto/hyperbee
An append-only B-tree running on a Hypercore
https://github.com/holepunchto/hyperbee
Last synced: about 1 month ago
JSON representation
An append-only B-tree running on a Hypercore
- Host: GitHub
- URL: https://github.com/holepunchto/hyperbee
- Owner: holepunchto
- License: mit
- Created: 2020-07-27T19:08:16.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2024-05-20T04:44:40.000Z (12 months ago)
- Last Synced: 2024-05-22T04:06:15.314Z (12 months ago)
- Language: JavaScript
- Homepage: https://docs.holepunch.to
- Size: 336 KB
- Stars: 246
- Watchers: 21
- Forks: 29
- Open Issues: 22
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-starred - holepunchto/hyperbee - An append-only B-tree running on a Hypercore (others)
README
# Hyperbee 🐝
[See API docs at docs.holepunch.to](https://docs.holepunch.to/building-blocks/hyperbee)
An append-only B-tree running on a Hypercore. Allows sorted iteration and more.
```
npm install hyperbee
```## Usage
```js
const Hyperbee = require('hyperbee')
const Hypercore = require('hypercore')
const RAM = require('random-access-memory')const core = new Hypercore(RAM)
const db = new Hyperbee(core, { keyEncoding: 'utf-8', valueEncoding: 'binary' })// If you own the core
await db.put('key1', 'value1')
await db.put('key2', 'value2')
await db.del('some-key')// If you want to insert/delete batched values
const batch = db.batch()await batch.put('key', 'value')
await batch.del('some-key')
await batch.flush() // Execute the batch// Query the core
const entry = await db.get('key') // => null or { key, value }// Read all entries
for await (const entry of db.createReadStream()) {
// ..
}// Read a range
for await (const entry of db.createReadStream({ gte: 'a', lt: 'd' })) {
// Anything >=a and { seq: 1, key: 'number', value: '123' }await db.put('number', '123', { cas })
console.log(await db.get('number')) // => { seq: 1, key: 'number', value: '123' }
// Without cas this would have been { seq: 2, ... }, and the next { seq: 3 }await db.put('number', '456', { cas })
console.log(await db.get('number')) // => { seq: 2, key: 'number', value: '456' }function cas (prev, next) {
// You can use same-data or same-object lib, depending on the value complexity
return prev.value !== next.value
}
```#### `const { seq, key, value } = await db.get(key)`
Get a key's value. Returns `null` if key doesn't exists.
`seq` is the Hypercore index at which this key was inserted.
#### `await db.del(key, [options])`
Delete a key.
`options` include:
```js
{
cas (prev, next) { return true }
}
```##### Compare And Swap (cas)
`cas` option is a function comparator to control whether the `del` succeeds.By returning `true` it will delete the value, otherwise it won't.
It only receives one arg: `prev` which is the current node entry.
```js
// This won't get deleted
await db.del('number', { cas })
console.log(await db.get('number')) // => { seq: 1, key: 'number', value: 'value' }// Change the value so the next time we try to delete it then "cas" will return true
await db.put('number', 'can-be-deleted')await db.del('number', { cas })
console.log(await db.get('number')) // => nullfunction cas (prev) {
return prev.value === 'can-be-deleted'
}
```#### `const { key, value } = await db.getBySeq(seq, [options])`
Get the key and value from a block number.
`seq` is the Hypercore index. Returns `null` if block doesn't exists.
#### `const stream = db.replicate(isInitiatorOrStream)`
See more about how replicate works at [core.replicate][core-replicate-docs].
#### `const batch = db.batch()`
Make a new atomic batch that is either fully processed or not processed at all.
If you have several inserts and deletions then a batch can be much faster.
#### `await batch.put(key, [value], [options])`
Insert a key into a batch.
`options` are the same as `db.put` method.
#### `const { seq, key, value } = await batch.get(key)`
Get a key, value out of a batch.
#### `await batch.del(key, [options])`
Delete a key into the batch.
`options` are the same as `db.del` method.
#### `await batch.flush()`
Commit the batch to the database, and releases any locks it has acquired.
#### `await batch.close()`
Destroy a batch, and releases any locks it has acquired on the db.
Call this if you want to abort a batch without flushing it.
#### `const stream = db.createReadStream([range], [options])`
Make a read stream. Sort order is based on the binary value of the keys.
All entries in the stream are similar to the ones returned from `db.get`.
`range` should specify the range you want to read and looks like this:
```js
{
gt: 'only return keys > than this',
gte: 'only return keys >= than this',
lt: 'only return keys < than this',
lte: 'only return keys <= than this'
}
````options` include:
```js
{
reverse: false // Set to true to get them in reverse order,
limit: -1 // Set to the max number of entries you want
}
```#### `const { seq, key, value } = await db.peek([range], [options])`
Similar to doing a read stream and returning the first value, but a bit faster than that.
#### `const stream = db.createHistoryStream([options])`
Create a stream of all entries ever inserted or deleted from the db.
Each entry has an additional `type` property indicating if it was a `put` or `del` operation.
`options` include:
```js
{
live: false, // If true the stream will wait for new data and never end
reverse: false, // If true get from the newest to the oldest
gte: seq, // Start with this seq (inclusive)
gt: seq, // Start after this index
lte: seq, // Stop after this index
lt: seq, // Stop before this index
limit: -1 // Set to the max number of entries you want
}
```If any of the `gte`, `gt`, `lte`, `lt` arguments are `< 0` then
they'll implicitly be added with the version before starting so
doing `{ gte: -1 }` makes a stream starting at the last index.#### `const stream = db.createDiffStream(otherVersion, [options])`
Efficiently create a stream of the shallow changes between two versions of the db.
`options` are the same as `db.createReadStream`, except for `reverse`.
Each entry is sorted by key and looks like this:
```js
{
left: Object, // The entry in the `db`
right: Object // The entry in `otherVersion`
}
```If an entry exists in db but not in the other version, then `left` is set
and `right` will be null, and vice versa.If the entries are causally equal (i.e. the have the same seq), they are not
returned, only the diff.#### `const entryWatcher = await db.getAndWatch(key, [options])`
Returns a watcher which listens to changes on the given key.
`entryWatcher.node` contains the current entry in the same format as the result of `bee.get(key)`, and will be updated as it changes.
By default, the node will have the bee's key- and value encoding, but you can overwrite it by setting the `keyEncoding` and `valueEncoding` options.
You can listen to `entryWatcher.on('update')` to be notified when the value of node has changed.
Call `await watcher.close()` to stop the watcher.
#### `const watcher = db.watch([range])`
Listens to changes that are on the optional `range`.
`range` options are the same as `db.createReadStream` except for `reverse`.
By default, the yielded snapshots will have the bee's key- and value encoding, but you can overwrite them by setting the `keyEncoding` and `valueEncoding` options.
Usage example:
```js
for await (const [current, previous] of watcher) {
console.log(current.version)
console.log(previous.version)
}
```Returns a new value after a change, `current` and `previous` are snapshots that are auto-closed before next value.
Don't close those snapshots yourself because they're used internally, let them be auto-closed.
Watchers on subs and checkouts are not supported. Instead, use the range option to limit scope.
`await watcher.ready()`
Waits until the watcher is loaded and detecting changes.
`await watcher.close()`
Stops the watcher. You could also stop it by using `break` in the loop.
#### `const snapshot = db.checkout(version)`
Get a read-only snapshot of a previous version.
#### `const snapshot = db.snapshot()`
Shorthand for getting a checkout for the current version.
#### `const sub = db.sub('sub-prefix', options = {})`
Create a sub-database where all entries will be prefixed by a given value.
This makes it easy to create namespaces within a single Hyperbee.
`options` include:
```js
{
sep: Buffer.alloc(1), // A namespace separator
valueEncoding, // Optional sub valueEncoding (defaults to the parents)
keyEncoding // Optional sub keyEncoding (defaults to the parents)
}
```For example:
```js
const root = new Hyperbee(core)
const sub = root.sub('a')// In root, this will have the key ('a' + separator + 'b')
await sub.put('b', 'hello')// Returns => { key: 'b', value: 'hello')
await sub.get('b')
```#### `const header = await db.getHeader([options])`
Returns the header contained in the first block. Throws if undecodable.
`options` are the same as the `core.get` method.
#### `const isHyperbee = await Hyperbee.isHyperbee(core, [options])`
Returns `true` if the core contains a Hyperbee, `false` otherwise.
This requests the first block on the core, so it can throw depending on the options.
`options` are the same as the `core.get` method.
[core-replicate-docs]: https://github.com/holepunchto/hypercore#const-stream--corereplicateisinitiatororreplicationstream