Hypercore
Hypercore is a secure, distributed append-only log built for sharing large datasets and streams of real-time data. It comes with a secure transport protocol, making it easy to build fast and scalable peer-to-peer applications.
Notable features include:
Improved fork detection in the replication protocol, to improve resilience.
Optional on-disk encryption for blocks (in addition to the existing transport encryption).
A write-ahead log in the storage layer to ensure that power loss or unexpected shutdown cannot lead to data corruption.
Basic:
Methods:
Installation
Install with npm:
A Hypercore can only be modified by its creator; internally it signs updates with a private key that's meant to live on a single machine, and should never be shared. However, the writer can replicate to many readers, in a manner similar to BitTorrent.
Unlike BitTorrent, a Hypercore can be modified after its initial creation, and peers can receive live update notifications whenever the writer adds new blocks.
API
const core = new Hypercore(storage, [key], [options])
const core = new Hypercore(storage, [key], [options])
Creates a new Hypercore instance.
storage
should be set to a directory where to store the data and core metadata.
Alternatively, the user can pass a function instead that is called with every filename Hypercore needs to function and return a abstract-random-access instance that is used to store the data.
By default Hypercore uses random-access-file. This is also useful for storing specific files in other directories.
Hypercore will produce the following files:
oplog
- The internal truncating journal/oplog that tracks mutations, the public key, and other metadata.tree
- The Merkle Tree file.bitfield
- The bitfield marking which data blocks this core has.data
- The raw data of each block.
tree
,data
, andbitfield
are normally very sparse files.
key
can be set to a Hypercore public key. When unset this the public key will be loaded from storage. If no key exists a new key pair will be generated.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| create a new Hypercore key pair if none was present in the storage | Boolean |
|
| overwrite any old Hypercore that might already exist | Boolean |
|
| enable sparse mode, counting unavailable blocks towards core.length and core.byteLength | Boolean |
|
| one of 'json', 'utf-8', or 'binary' | String |
|
| optionally apply an encoding to complete batches | Function |
|
| optionally pass the public key and secret key as a key pair | Object |
|
| optionally pass an encryption key to enable block encryption | String |
|
| hook that is called if gets are waiting for download | Function |
|
| constructor timeout | integer |
|
| disable appends and truncates | Boolean |
|
We can also set valueEncoding to any abstract-encoding or compact-encoding instance.
valueEncodings will be applied to individual blocks, even if we append batches. To control encoding at the batch level, the encodeBatch
option can be used, which is a function that takes a batch and returns a binary-encoded batch. If a custom valueEncoding is provided, it will not be applied prior to encodeBatch
.
Do not attempt to create multiple Hypercores with the same private key (i.e., on two different devices).
Doing so will most definitely cause a Hypercore conflict. A conflict implies that the core was implicitly forked. In such a scenario, replicating peers will 'gossip' that the core should be deemed dead and unrecoverable.
Properties
core.readable
core.readable
Can we read from this core? After closing the core this will be false
.
core.id
core.id
A string containing the ID (z-base-32 of the public key) that identifies this core.
core.key
core.key
Buffer containing the public key identifying this core.
core.keyPair
core.keyPair
An object containing buffers of the core's public and secret key
core.discoveryKey
core.discoveryKey
Buffer containing a key derived from the core's public key. In contrast to core.key,
this key can not be used to verify the data. It can be used to announce or look for peers that are sharing the same core, without leaking the core key.
The above properties are populated after
ready
has been emitted. Will benull
before the event.
core.encryptionKey
core.encryptionKey
Buffer containing the optional block encryption key of this core. Will be null
unless block encryption is enabled.
core.writable
core.writable
Can we append to this core?
Populated after
ready
has been emitted. Will befalse
before the event.
core.length
core.length
The number of blocks of data available on this core. If sparse: false
, this will equal core.contiguousLength
.
core.contiguousLength
core.contiguousLength
The number of blocks contiguously available starting from the first block of this core.
core.fork
core.fork
The current fork id of this core
The above properties are populated after
ready
has been emitted. Will be0
before the event.
core.padding
core.padding
The amount of padding applied to each block of this core. Will be 0
unless block encryption is enabled.
Methods
const { length, byteLength } = await core.append(block)
const { length, byteLength } = await core.append(block)
Append a block of data (or an array of blocks) to the core. Returns the new length and byte length of the core.
This operation is 'atomic'. This means that the block is appended altogether or not at all (in case of I/O failure).
const block = await core.get(index, [options])
const block = await core.get(index, [options])
Get a block of data. If the data is not available locally this method will prioritize and wait for the data to be downloaded.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Wait for the block to be downloaded | Boolean |
|
| Hook that is called if the get is waiting for download | Boolean |
|
| Wait at max some milliseconds (0 means no timeout) | Boolean |
|
| One of 'json', 'utf-8', or 'binary' | String | core's valueEncoding |
| Automatically decrypts the block if encrypted | Boolean |
|
const has = await core.has(start, [end])
const has = await core.has(start, [end])
Check if the core has all blocks between start
and end
.
const updated = await core.update([options])
const updated = await core.update([options])
Wait for the core to try and find a signed update to its length. Does not download any data from peers except for proof of the new core length.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Wait for the meta-data of hypercore to be updated | Boolean |
|
const [index, relativeOffset] = await core.seek(byteOffset, [options])
const [index, relativeOffset] = await core.seek(byteOffset, [options])
Seek a byte offset.
Returns [index, relativeOffset]
, where index
is the data block the byteOffset is contained in and relativeOffset
is the relative byte offset in the data block.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| wait for data to be downloaded | Boolean |
|
| wait for given milliseconds | Integer |
|
const stream = core.createReadStream([options])
const stream = core.createReadStream([options])
Make a read stream to read a range of data out at once.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Starting offset to read a range of data | Integer |
|
| Ending offset to read a range of data | Integer |
|
| Allow realtime data replication | Boolean |
|
| Auto set end to core.length on open or update it on every read | Boolean |
|
const bs = core.createByteStream([options])
const bs = core.createByteStream([options])
Make a byte stream to read a range of bytes.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Starting offset to read a range of bytes | Integer |
|
| Number of bytes that will be read | Integer |
|
| Controls the number of blocks to preload | Integer |
|
const cleared = await core.clear(start, [end], [options])
const cleared = await core.clear(start, [end], [options])
Clears stored blocks between start
and end
, reclaiming storage when possible.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Returned | Boolean |
|
The core will also 'gossip' with peers it is connected to, that no longer have these blocks.
await core.truncate(newLength, [forkId])
await core.truncate(newLength, [forkId])
Truncates the core to a smaller length.
Per default, this will update the fork ID of the core to + 1
, but we can set the preferred fork ID with the option. Note that the fork ID should be incremented in a monotone manner.
await core.purge()
await core.purge()
Purge the Hypercore from storage, completely removing all data.
const hash = await core.treeHash([length])
const hash = await core.treeHash([length])
Get the Merkle Tree hash of the core at a given length, defaulting to the current length of the core.
const range = core.download([range])
const range = core.download([range])
Download a range of data.
We can await until the range has been fully downloaded by doing:
A range can have the following properties:
To download the full core continuously (often referred to as non-sparse mode):
To download a discrete range of blocks, pass a list of indices:
To cancel downloading a range, simply destroy the range instance:
const session = await core.session([options])
const session = await core.session([options])
Creates a new Hypercore instance that shares the same underlying core. Options are inherited from the parent instance, unless they are re-set.
options
are the same as in the constructor.
Be sure to close any sessions made.
const info = await core.info([options])
const info = await core.info([options])
Get information about this core, such as its total size in bytes.
The object will look like this:
options
include:
Property | Description | Type | Default |
---|---|---|---|
| get storage estimates in bytes | Boolean |
|
await core.close()
await core.close()
Close this core and release any underlying resources.
await core.ready()
await core.ready()
Waits for the core to open.
After this has been called core.length
and other properties have been set.
ℹ️ In general, waiting for
ready
is unnecessary unless there's a need to check a synchronous property (likekey
ordiscoverykey
) before any other async API method has been called. All async methods on the public API, awaitready
internally.
const stream = core.replicate(isInitiator|stream, options)
const stream = core.replicate(isInitiator|stream, options)
Creates a replication stream. We should pipe this to another Hypercore instance.
The isInitiator
argument is a boolean indicating whether a peer is the initiator of the connection (ie the client) or the passive peer waiting for connections (i.e., the server).
If a P2P swarm like Hyperswarm is being used, whether a peer is an initiator can be determined by checking if the swarm connection is a client socket or a server socket. In Hyperswarm, a user can check that using the client property on the peer details object.
To multiplex the replication over an existing Hypercore replication stream, another stream instance can be passed instead of the isInitiator
Boolean.
To replicate a Hypercore using Hyperswarm
:
To replicate many Hypercores over a single Hyperswarm connection, see
Corestore
.
If not using Hyperswarm
or Corestore
, specify the isInitiator
field, which will create a fresh protocol stream that can be piped over any transport:
In almost all cases, the use of both Hyperswarm and Corestore Replication is advised and will meet all needs.
const done = core.findingPeers()
const done = core.findingPeers()
Create a hook that tells Hypercore users are finding peers for this core in the background. Call done
when user current discovery iteration is done. If using Hyperswarm, call this after a swarm.flush()
finishes.
This allows core.update
to wait for either the findingPeers
hook to finish or one peer to appear before deciding whether it should wait for a Merkle tree update before returning.
In order to prevent get
and update
from resolving until Hyperswarm (or any other external peer discovery process) has finished, use the following pattern:
core.session([options])
core.session([options])
Returns a new session for the Hypercore.
Used for the resource management of the Hypercores using reference counting. The sessions are individual openings to a Hypercore instance and consequently, the changes made through one session will be reflected across all sessions of the Hypercore.
The returned value of
core.session()
can be used as a Hypercore instance i.e., everything provided by the Hypercore API can be used with it.
options
include:
Property | Description | Type | Default |
---|---|---|---|
| Wait for the block to be downloaded | Boolean |
|
| Hook that is called if the get is waiting for download | Boolean |
|
| Enables sparse mode, counting unavailable blocks towards | Boolean |
|
| class name | Class |
|
core.snapshot([options])
core.snapshot([options])
Returns a snapshot of the core at that particular time. This is useful for ensuring that multiple get
operations are acting on a consistent view of the Hypercore (i.e. if the core forks in between two reads, the second should throw an error).
If core.update()
is explicitly called on the snapshot instance, it will no longer be locked to the previous data. Rather, it will get updated with the current state of the Hypercore instance.
options
are the same as the options to core.session()
.
The fixed-in-time Hypercore clone created via snapshotting does not receive updates from the main Hypercore, unlike the Hypercore instance returned by
core.session()
.
Events
core.on('append')
core.on('append')
Emitted when the core has been appended to (i.e., has a new length/byte length), either locally or remotely.
core.on('truncate', ancestors, forkId)
core.on('truncate', ancestors, forkId)
Emitted when the core has been truncated, either locally or remotely.
core.on('ready')
core.on('ready')
Emitted after the core has initially opened all its internal state.
core.on('close')
core.on('close')
Emitted when the core has been fully closed.
core.on('peer-add')
core.on('peer-add')
Emitted when a new connection has been established with a peer.
core.on('peer-remove')
core.on('peer-remove')
Emitted when a peer's connection has been closed.
Last updated