The official Dat SDK
Dat consists of a bunch of low level building blocks for working with data in distributed applications. Although this modularity makes it easy to mix and match pieces, it adds complexity when it comes to actually building something.
The Dat SDK combines the lower level pieces of the Dat ecosystem into high level APIs that you can use across platforms so that you can focus on your application rather than the gritty details of how it works.
- High level API
- Cross-platform with same codebase
- ✔ Node
- ✔ Web
- ✔ Electron
- React-Native (with nodejs-mobile-react-native?)
Node.js / Browserify workflows:
npm install --save dat-sdk
const SDK = require('dat-sdk')
git clone git@github.com:datproject/sdk.git
cd sdk
# Compile the SDK into a single JS file
npm run build
# Copy `dat-sdk-bundle.js` into your project
<script src="dat-sdk-bundle.js"></script>
<script>
const SDK = window.datSDK
// Look at the examples from here
</script>
npm install --save-dev browserify
npm install --save dat-sdk@next
Add this as the build
command in your package.json
browserify index.js > bundle.js
Then you can use bundle.js
in your project.
To bundle with webpack, you'll need to alias some dependencies.
const path = require('path')
module.exports = {
entry: './index.js',
target: 'web',
resolve: {
alias: {
fs: 'graceful-fs',
'sodium-native': '@geut/sodium-javascript-plus',
'sodium-universal': '@geut/sodium-javascript-plus',
hyperswarm: 'hyperswarm-web',
util: './node_modules/util/util.js'
}
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
Then you can include ./dist/bundle.js
in your HTML page.
const SDK = require('dat-sdk')
const sdk = await SDK();
const {
Hypercore,
Hyperdrive,
resolveName,
close
} = sdk
// Create a new Hyperdrive.
// If you want to create a new archive, pass in a name for it
// This will be used to derive a secret key
// Every time you open a drive with that name it'll derive the same key
// This uses a master key that's generated once per device
// That means the same name will yield a different key on a different machine
const archive = Hyperdrive('My archive name', {
// This archive will disappear after the process exits
// This is here so that running the example doesn't clog up your history
persist: false,
// storage can be set to an instance of `random-access-*`
// const RAI = require('random-access-idb')
// otherwise it defaults to `random-access-web` in the browser
// and `random-access-file` in node
storage: null //storage: RAI
})
// You should wait for the archive to be totally initialized
await archive.ready()
const url = `dat://${archive.key.toString('hex')}`
// TODO: Save this for later!
console.log(`Here's your URL: ${url}`)
// Check out the hyperdrive docs for what you can do with it
// https://www.npmjs.com/package/hyperdrive#api
await archive.writeFile('/example.txt', 'Hello World!')
console.log('Written example file!')
// This example is currently broken because Beaker's website isn't on Dat 2 yet
const key = await resolveName('dat://beakerbrowser.com')
const archive = Hyperdrive(key)
await archive.download()
// Pure all the data
await archive.destroyStorage()
const SOME_URL = 'dat://0a9e202b8055721bd2bc93b3c9bbc03efdbda9cfee91f01a123fdeaadeba303e/'
const someArchive = Hyperdrive(SOME_URL)
console.log(await someArchive.readdir('/'))
// Create a hypercore
// Check out the hypercore docs for what you can do with it
// https://github.com/mafintosh/hypercore
const myCore = Hypercore('my hypercore name', {
valueEncoding: 'json',
persist: false,
// storage can be set to an instance of `random-access-*`
// const RAI = require('random-access-idb')
// otherwise it defaults to `random-access-web` in the browser
// and `random-access-file` in node
storage: null // storage: RAI
})
// Add some data to it
await myCore.append(JSON.stringify({
name: 'Alice'
}))
// Use extension messages for sending extra data over the p2p connection
const discoveryCoreKey = 'dat://bee80ff3a4ee5e727dc44197cb9d25bf8f19d50b0f3ad2984cfe5b7d14e75de7'
const discoveryCore = new Hypercore(discoveryCoreKey)
// Register the extension message handler
const extension = discoveryCore.registerExtension('discovery', {
// Set the encoding type for messages
encoding: 'binary',
onmessage: (message, peer) => {
// Recieved messages will be automatically decoded
console.log('Got key from peer!', message)
const otherCore = new Hypercore(message, {
valueEncoding: 'json',
persist: false
})
// Render the peer's data from their core
otherCore.get(0, console.log)
}
})
// When you find a peer tell them about your core
discoveryCore.on('peer-add', (peer) => {
console.log('Got a peer!')
extension.send(myCore.key, peer)
})
const hypertrie = require('hypertrie')
// Pass in hypercores from the SDK into other dat data structures
// Check out what you can do with hypertrie from there:
// https://github.com/mafintosh/hypertrie
const trie = hypertrie(null, {
feed: new Hypercore('my trie core', {
persist: false
})
})
trie.put('key', 'value', () => {
trie.get('key', (err, node) => {
console.log('Got key: ', node.key)
console.log('Loaded value from trie: ', node.value)
})
})
The API supports both promises and callbacks. Everywhere where you see await
, you can instead pass a node-style callback.
Creates an instance of the Dat SDK based on the options.
opts.storage
: An optional random-access-storage instance for storing data.opts.applicationName
: An optional name for the application using the SDK. This will automatically silo your data from other applications using the SDK and will store it in the appropriate place using random-access-applicationopts.corestore
: An optional Corestore instance for using as hypercore storage.opts.corestoreOpts
: Options to pass into Corestore when it's initialized.opts.swarmOpts
: This lets you configure hyperswarm and hyperswarm-webmaxPeers
: The maximum number of connections to keep for this swarm.ephemeral **NODE**
: Set tofalse
if this is going to be in a long running process on a server.bootstap **NODE**
: An array of addresses to use for the DHT bootstrapingwebrtcBootstrap: ['https://geut-webrtc-signal.herokuapp.com/'] **BROWSER**
: The WebRTC bootstrap server list used by discovery-swarm-webrtcwsProxy: 'wss://hyperswarm.mauve.moe' **BROWSER**
: The Websocket proxy used for hyperswarm-proxy-ws
opts.driveOpts
: This lets you configure the behavior of Hyperdrive instancessparse: true
: Whether the history should be loaded on the fly instead of replicating the full historypersist: true
: Whether the data should be persisted to storage. Set to false to create in-memory archives
opts.coreOpts
: This lets you configure the behavior of Hypercore instancessparse: true
: Whether the history should be loaded on the fly instead of replicating the full historypersist: true
: Whether the data should be persisted to storage. Set to false to create in-memory feedsextensions
: The set of extension message types to use with this feed when replicating.valueEncoding: 'json' | 'utf-8' | 'binary'
: The encoding to use for the data stored in the hypercore. Use JSON to store / retrieve objects.
opts.dnsOpts
: Configure the dat dns resolution module. You probably shouldn't mess with this.recordName: 'dat'
: name of .well-known fileprotocolRegex: /^dat:\/\/([0-9a-f]{64})/i
: RegExp object for custom protocolhashRegex: /^[0-9a-f]{64}?$/i
: RegExp object for custom hash i.e.txtRegex: /"?datkey=([0-9a-f]{64})"?/i
: RegExp object for DNS TXT record of custom protocol
This closes all resources used by the SDK so you can safely end your process. cb
will be invoked once resources are closed or if there's an error.
Resolve a DNS name to a Dat key.
url
is a Dat URL likedat://dat.foundation
key
will be the Dat key that you can pass tohyperdrive
This gives you the public / private keypair used for the Noise protocol encryption when connecting to peers.
You can use this to identify peers in the network using peer.remotePublicKey
Derives a secret key based on the SDK's master key.
namespace
can be used to namespace different applications, and name
is the name of the key you want.
This can be used as a seed for generating secure private keys without needing to store an extra key on disk.
This initializes a Hyperdrive (aka a Dat archive), the SDK will begin finding peers for it and will de-duplicate calls to initializing the same archive more than once.
keyOrName
: This must be provided. It's either a Dat URL / key or a string identifying the name. If you want to have a writable archive, you can use the name to generate one and use the name later to get the same archive back without having to save the key somewhere.opts
: These are the options for configuring the hyperdrive.sparse: true
: Whether the history should be loaded on the fly instead of replicating the full historypersist: true
: Whether the data should be persisted to storage. Set to false to create in-memory archivessecretKey
: A secret key for granting write access. This can be useful when restoring backups.discoveryKey
: Optionally specify which discovery key you'd like to use for finding peers for this archive.lookup: true
: Specify whether you wish to lookup peers for this archive. Setfalse
along withannounce
to avoid advertisingannounce: true
: Specify whether you wish to advertise yourself as having the archive.
The rest of the Hyperdrive docs were taken from the Hyperdrive README. Note that we're wrapping over the APIs with Hyperdrive-Promise so any callback methods can be await
ed instead.
Get the current version of the archive (incrementing number).
The public key identifying the archive.
A key derived from the public key that can be used to discovery other peers sharing this archive.
A boolean indicating whether the archive is writable.
Emitted when the archive is fully ready and all properties has been populated.
Emitted when the archive has got a new change.
Emitted when a critical error during load happened.
Emitted when the archive has been closed
Emitted when a new peer has started replicating wiht the archive.
Emitted when a peer has stopped replicating wit the archive.
Checkout a readonly copy of the archive at an old version. Options are used to configure the oldDrive
:
{
metadataStorageCacheSize: 65536 // how many entries to use in the metadata hypercore's LRU cache
contentStorageCacheSize: 65536 // how many entries to use in the content hypercore's LRU cache
treeCacheSize: 65536 // how many entries to use in the append-tree's LRU cache
}
Download all files in path of current version. If no path is specified this will download all files.
You can use this with .checkout(version)
to download a specific version of the archive.
archive.checkout(version).download()
Get a stream of all changes and their versions from this archive.
Read a file out as a stream. Similar to fs.createReadStream.
Options include:
{
start: optionalByteOffset, // similar to fs
end: optionalInclusiveByteEndOffset, // similar to fs
length: optionalByteLength
}
Read an entire file into memory. Similar to fs.readFile.
Options can either be an object or a string
Options include:
{
encoding: string
cached: true|false // default: false
}
or a string can be passed as options to simply set the encoding - similar to fs.
If cached
is set to true
, this function returns results only if they have already been downloaded.
Diff this archive with another version. version
can both be a version number of a checkout instance of the archive. The data
objects looks like this
{
type: 'put' | 'del',
name: '/some/path/name.txt',
value: {
// the stat object
}
}
Write a file as a stream. Similar to fs.createWriteStream.
If options.cached
is set to true
, this function returns results only if they have already been downloaded.
Write a file from a single buffer. Similar to fs.writeFile.
Unlinks (deletes) a file. Similar to fs.unlink.
Explictly create an directory. Similar to fs.mkdir
Delete an empty directory. Similar to fs.rmdir.
Lists a directory. Similar to fs.readdir.
Options include:
{
cached: true|false, // default: false
}
If cached
is set to true
, this function returns results from the local version of the archive’s append-tree. Default behavior is to fetch the latest remote version of the archive before returning list of directories.
Stat an entry. Similar to fs.stat. Sample output:
Stat {
dev: 0,
nlink: 1,
rdev: 0,
blksize: 0,
ino: 0,
mode: 16877,
uid: 0,
gid: 0,
size: 0,
offset: 0,
blocks: 0,
atime: 2017-04-10T18:59:00.147Z,
mtime: 2017-04-10T18:59:00.147Z,
ctime: 2017-04-10T18:59:00.147Z,
linkname: undefined }
The output object includes methods similar to fs.stat:
var stat = archive.stat('/hello.txt')
stat.isDirectory()
stat.isFile()
Options include:
{
cached: true|false // default: false,
wait: true|false // default: true
}
If cached
is set to true
, this function returns results only if they have already been downloaded.
If wait
is set to true
, this function will wait for data to be downloaded. If false, will return an error.
Stat an entry but do not follow symlinks. Similar to fs.lstat.
Options include:
{
cached: true|false // default: false,
wait: true|false // default: true
}
If cached
is set to true
, this function returns results only if they have already been downloaded.
If wait
is set to true
, this function will wait for data to be downloaded. If false, will return an error.
Similar to fs.access.
Options include:
{
cached: true|false // default: false,
wait: true|false // default: true
}
If cached
is set to true
, this function returns results only if they have already been downloaded.
If wait
is set to true
, this function will wait for data to be downloaded. If false, will return an error.
Open a file and get a file descriptor back. Similar to fs.open.
Note that currently only read mode is supported in this API.
Read from a file descriptor into a buffer. Similar to fs.read.
Close a file. Similar to fs.close.
Closes all open resources used by the archive. The archive should no longer be used after calling this.
Closes all resources used by the archive, and destroys its data from storage. The archive should no longer be used after calling this.
Initializes a Hypercore (aka Feed) and begins replicating it.
keyOrName
: This must be provided. It's either a Dat URL / key or a string identifying the name of the feed. If you want to have a writable feed, you can use the name to generate one and use the name later to get the same feed back without having to save the key somewhere.opts
: The options for configuring this feedsparse: true
: Whether the history should be loaded on the fly instead of replicating the full historypersist: true
: Whether the data should be persisted to storage. Set to false to create in-memory feedsvalueEncoding: 'json' | 'utf-8' | 'binary'
: The encoding to use for the data stored in the hypercore. Use JSON to store / retrieve objects.secretKey
: The secret key to use for the feed. Useful for restoring from backups.discoveryKey
: Optionally specify which discovery key you'd like to use for finding peers for this feed.lookup: true
: Specify whether you wish to lookup peers for this feed. Set tofalse
along withannounce
to avoid advertising.announce: true
: Specify whether you wish to advertise yourself as having the feed.
Append a block of data to the feed.
Callback is called with (err, seq)
when all data has been written at the returned seq
or an error occurred.
Get a block of data. If the data is not available locally this method will prioritize and wait for the data to be downloaded before calling the callback.
Options include
{
wait: true, // wait for index to be downloaded
timeout: 0, // wait at max some milliseconds (0 means no timeout)
valueEncoding: 'json' | 'utf-8' | 'binary' // defaults to the feed's valueEncoding
}
Callback is called with (err, data)
Get a range of blocks efficiently. Options include
{
wait: sameAsAbove,
timeout: sameAsAbove,
valueEncoding: sameAsAbove
}
Get the block of data at the tip of the feed. This will be the most recently appended block.
Accepts the same options
as feed.get()
.
Download a range of data. Callback is called when all data has been downloaded. A range can have the following properties:
{
start: startIndex,
end: nonInclusiveEndIndex,
linear: false // download range linearly and not randomly
}
If you do not mark a range the entire feed will be marked for download.
If you have not enabled sparse mode (sparse: true
in the feed constructor) then the entire
feed will be marked for download for you when the feed is created.
Cancel a previous download request.
Get a signature proving the correctness of the block at index, or the whole stream.
Callback is called with (err, signature)
.
The signature has the following properties:
{
index: lastSignedBlock,
signature: Buffer
}
Verify a signature is correct for the data up to index, which must be the last signed block associated with the signature.
Callback is called with (err, success)
where success is true only if the signature is
correct.
Retrieve the root hashes for given index
.
Callback is called with (err, roots)
; roots
is an Array of Node objects:
Node {
index: location in the merkle tree of this root
size: total bytes in children of this root
hash: hash of the children of this root (32-byte buffer)
}
Returns total number of downloaded blocks within range.
If end
is not specified it will default to the total number of blocks.
If start
is not specified it will default to 0.
Return true if a data block is available locally. False otherwise.
Return true if all data blocks within a range are available locally. False otherwise.
Clear a range of data from the local cache. Will clear the data from the bitfield and make a call to the underlying storage provider to delete the byte range the range occupies.
end
defaults to start + 1
.
Seek to a byte offset.
Calls the callback with (err, index, relativeOffset)
, where index
is the data block the byteOffset is contained in and relativeOffset
is
the relative byte offset in the data block.
Wait for the feed to contain at least minLength
elements.
If you do not provide minLength
it will be set to current length + 1.
Does not download any data from peers except for a proof of the new feed length.
console.log('length is', feed.length)
feed.update(function () {
console.log('length has increased', feed.length)
})
Create a readable stream of data.
Options include:
{
start: 0, // read from this index
end: feed.length, // read until this index
snapshot: true, // if set to false it will update `end` to `feed.length` on every read
tail: false, // sets `start` to `feed.length`
live: false, // set to true to keep reading forever
timeout: 0, // timeout for each data event (0 means no timeout)
wait: true // wait for data to be downloaded
}
Create a writable stream.
Fully close this feed.
Calls the callback with (err)
when all storage has been closed.
Closes the feed and deletes all of it's data from storage.
Audit all data in the feed. Will check that all current data stored matches the hashes in the merkle tree and clear the bitfield if not.
When done a report is passed to the callback that looks like this:
{
valid: 10, // how many data blocks matches the hashes
invalid: 0, // how many did not
}
If a block does not match the hash it is cleared from the data bitfield.
Listens on extension messages of type name
on the feeds replication channels.
handlers.encoding
: The encoding to use for messages.json
,binary
, 'utf8'handlers.onmessage(message, peer)
: Function to invoke when a peer sends you a message for this extension type.handlers.onerror(err, peer)
: Function to invoke when a peer has sent you a mis-coded message on this extension.
You can respond to messages with extension.send(message, peer)
.
Can we append to this feed?
Populated after ready
has been emitted. Will be false
before the event.
Can we read from this feed? After closing a feed this will be false.
Populated after ready
has been emitted. Will be false
before the event.
Buffer containing the public key identifying this feed.
Populated after ready
has been emitted. Will be null
before the event.
Buffer containing a key derived from the feed.key.
In contrast to feed.key
this key does not allow you to verify the data but can be used to announce or look for peers that are sharing the same feed, without leaking the feed key.
Populated after ready
has been emitted. Will be null
before the event.
How many blocks of data are available on this feed?
Populated after ready
has been emitted. Will be 0
before the event.
How much data is available on this feed in bytes?
Populated after ready
has been emitted. Will be 0
before the event.
Return per-peer and total upload/download counts.
The returned object is of the form:
{
totals: {
uploadedBytes: 100,
uploadedBlocks: 1,
downloadedBytes: 0,
downloadedBlocks: 0
},
peers: [
{
uploadedBytes: 100,
uploadedBlocks: 1,
downloadedBytes: 0,
downloadedBlocks: 0
},
...
]
}
Stats will be collected by default, but this can be disabled by setting opts.stats
to false.
Emitted when the feed is ready and all properties have been populated.
Emitted when the feed experiences a critical error.
Emitted when a data block has been downloaded.
Emitted when a data block is uploaded.
Emitted when the feed has been appended to (i.e. has a new length / byteLength)
Emitted every time ALL data from 0
to feed.length
has been downloaded.
Emitted when the feed has been fully closed
Emitted when a new peer has started replicating with the feed.
Emitted when a peer has stopped replicating with the feed.