UNPKG

15.1 kBMarkdownView Raw
1# Hyperdrive
2[![Build Status](https://travis-ci.org/mafintosh/hyperdrive.svg?branch=master)](https://travis-ci.org/mafintosh/hyperdrive)
3
4#### *Note*: This is a beta version of Hyperdrive that's backed by [Hypertrie](https://github.com/mafintosh/hypertrie)
5
6Hyperdrive is a secure, real-time distributed file system designed for easy P2P file sharing.
7
8It has a handful of cool features:
9* __Version Controlled__: Files are versioned by default, making it easy to see historical changes and prevent data loss.
10* __Composable__: Using our mount system, Hyperdrives can be nested within other Hyperdrives, enabling powerful multi-user collaboration tools.
11* __Shareable with One Link__: You can share an entire Hyperdrive with others by sending them a single 32-byte key. If you'd like more granularity, our mount system enables the fine-grained sharing of specific directories.
12* __Sparse Downloading__ By default, readers only download the portions of files they need, on demand. You can stream media from friends without jumping through hoops! Seeking is snappy and there's no buffering.
13* __Fast Lookups__: File metadata is stored in a distributed trie structure, meaning files can be located with minimal network lookups.
14* __Version Tagging__: You can assign string names to Hyperdrive versions and store these within the drive, making it straightforward to switch between semantically-meaningful versions.
15
16You can use Hyperdrive standalone within your applications, or you can use it through the [Hyperdrive daemon](https://github.com/andrewosh/hyperdrive-daemon) which handles storage and DHT networking for you. The daemon provides both a gRPC API for managing remote Hyperdrives, and a FUSE API that turns Hyperdrives into normal folders on your computer.
17
18## Installation
19``` js
20npm install hyperdrive@beta
21```
22
23## Usage
24
25Hyperdrive aims to implement the same API as Node.js' core `fs` module, and mirrors many POSIX APIs.
26
27``` js
28var hyperdrive = require('hyperdrive')
29var drive = hyperdrive('./my-first-hyperdrive') // content will be stored in this folder
30
31drive.writeFile('/hello.txt', 'world', function (err) {
32 if (err) throw err
33 drive.readdir('/', function (err, list) {
34 if (err) throw err
35 console.log(list) // prints ['hello.txt']
36 drive.readFile('/hello.txt', 'utf-8', function (err, data) {
37 if (err) throw err
38 console.log(data) // prints 'world'
39 })
40 })
41})
42```
43
44Hyperdrives can easily be replicated to other machines over any stream-based transport layer!
45
46``` js
47var net = require('net')
48
49// ... on one machine
50
51var server = net.createServer(function (socket) {
52 socket.pipe(drive.replicate()).pipe(socket)
53})
54
55server.listen(10000)
56
57// ... on another
58
59var clonedDrive = hyperdrive('./my-cloned-hyperdrive', origKey)
60var socket = net.connect(10000)
61
62socket.pipe(clonedDrive.replicate()).pipe(socket)
63```
64
65It also comes with build in versioning, live replication (where the replication streams remain open, syncing new changes), and nested Hyperdrive mounting. See more below.
66
67## API
68
69#### `var drive = hyperdrive(storage, [key], [options])`
70
71Create a new Hyperdrive.
72
73The `storage` parameter defines how the contents of the drive will be stored. It can be one of the following, depending on how much control you require over how the drive is stored.
74
75- If you pass in a string, the drive content will be stored in a folder at the given path.
76- You can also pass in a function. This function will be called with the name of each of the required files for the drive, and needs to return a [`random-access-storage`](https://github.com/random-access-storage/) instance.
77- If you require complete control, you can also pass in a [corestore](https://github.com/andrewosh/corestore) instance (or an API-compatible replacement).
78
79 - `name`: the name of the file to be stored
80 - `opts`
81 - `key`: the [feed key](https://github.com/mafintosh/hypercore#feedkey) of the underlying Hypercore instance
82 - `discoveryKey`: the [discovery key](https://github.com/mafintosh/hypercore#feeddiscoverykey) of the underlying Hypercore instance
83 - `drive`: the current Hyperdrive instance
84
85Options include:
86
87``` js
88{
89 sparse: true, // only download data on content feed when it is specifically requested
90 sparseMetadata: true // only download data on metadata feed when requested
91 extensions: [], // The list of extension message types to use
92}
93```
94
95For more storage configuration, you can also provide any corestore option.
96
97Note that a cloned hyperdrive drive is fully "sparse" by default, meaning that the `sparse` and `sparseMetadata` options are both true. This is usually the best way to use Hyperdrive, but you can also set these options to false to enable eager downloading of both the content and the metadata. If you'd like more control over download strategies, you can use the `download` method directly.
98
99### Replication
100Hyperdrive replication occurs through streams, meaning you can pipe a drive's replication stream into any stream-based transport system you'd like. If you have many nested Hyperdrives mounted within a parent drive, `replicate` will sync all children as well.
101
102#### `var stream = drive.replicate([options])`
103
104Replicate this drive. Options include
105
106``` js
107{
108 live: false, // keep replicating,
109 encrypt: true // Enable NOISE encryption.
110}
111```
112
113### Public Fields
114
115#### `drive.version`
116
117Get the current version of the drive (incrementing number).
118
119#### `drive.key`
120
121The public key identifying the drive.
122
123#### `drive.discoveryKey`
124
125A key derived from the public key that can be used to discovery other peers sharing this drive.
126
127#### `drive.writable`
128
129A boolean indicating whether the drive is writable.
130
131#### `drive.peers`
132
133A list of peers currently replicating with this drive
134
135### Lifecycle Events
136
137#### `drive.on('ready')`
138
139Emitted when the drive is fully ready and all properties has been populated.
140
141#### `drive.on('error', err)`
142
143Emitted when a critical error during load happened.
144
145#### `drive.on('update')`
146
147Emitted when there is a new update to the drive.
148
149#### `drive.on('peer-add', peer)`
150
151Emitted when a new peer has been added.
152
153```js
154const drive = Hyperdrive({
155 extension: ['example']
156})
157
158drive.on('extension', (name, message, peer) => {
159 console.log(name, message.toString('utf8'))
160})
161
162drive.on('peer-add', (peer) => {
163 peer.extension('example', Buffer.from('Hello World!', 'utf8'))
164})
165```
166
167#### `drive.on('peer-remove', peer)`
168
169Emitted when a peer has been removed.
170
171#### `drive.on('close')`
172
173Emitted when the drive has been closed.
174
175### Extension Management
176Hyperdrive supports [hypercore](https://github.com/mafintosh/hypercore) extensions, letting you plug custom logic into a drive's replication streams.
177
178#### `drive.on('extension', name, message, peer)`
179
180Emitted when a peer has sent you an extension message. The `name` is a string from one of the extension types in the constructor, `message` is a buffer containing the message contents, and `peer` is a reference to the peer that sent the extension. You can send an extension back with `peer.extension(name, message)`.
181
182#### `drive.extension(name, message)`
183
184Broadcasts an extension message to all connected peers. The `name` must be a string for an extension passed in the constructor and the message must be a buffer.
185
186### Version Control
187Since Hyperdrive is built on top of append-only logs, old versions of files are preserved by default. You can get a read-only snapshot of a drive at any point in time with the `checkout` function, which takes a version number. Additionally, you can tag versions with string names, making them more parseable.
188
189#### `var oldDrive = drive.checkout(version, [opts])`
190
191Checkout a readonly copy of the drive at an old version. Options for the checkout are duplicated from the parent by default, but you can also pass in additional Hyperdrive options.
192
193#### `drive.createTag(name, [version], cb)`
194Create a tag that maps to a given version. If a version is not provided, the current version will be used.
195
196Tags are stored inside the drive's "hidden trie," meaning they're not enumerable using Hyperdrive's standard filesystem methods. They will replicate with all the other data in the drive, though.
197
198#### `drive.getTaggedVersion(name, cb)`
199Return the version corresponding to a tag.
200
201Combined with `checkout`, this lets you navigate between tagged versions.
202
203#### `drive.deleteTag(name, cb)`
204Delete a tag. If the tag doesn't exist, this will be a no-op.
205
206#### `drive.getAllTags(cb)`
207Return a Map of all tags. The Map will be of the form:
208```
209{
210 name => version
211}
212```
213
214### Downloading
215In sparse mode (which is the default), data will be downloaded from peers on-demand. If you'd like more control over this, you can use the `download` function to explicitly mark certain files/directory for immediate downloading.
216
217#### `drive.download([path], [callback])`
218
219Download all files in path of current version.
220If no path is specified this will download all files.
221
222You can use this with `.checkout(version)` to download a specific version of the drive.
223
224``` js
225drive.checkout(version).download()
226```
227
228### Reading and Writing
229
230#### `var stream = drive.createReadStream(name, [options])`
231
232Read a file out as a stream. Similar to fs.createReadStream.
233
234Options include:
235
236``` js
237{
238 start: optionalByteOffset, // similar to fs
239 end: optionalInclusiveByteEndOffset, // similar to fs
240 length: optionalByteLength
241}
242```
243
244#### `drive.readFile(name, [options], callback)`
245
246Read an entire file into memory. Similar to fs.readFile.
247
248Options can either be an object or a string
249
250Options include:
251```js
252{
253 encoding: string
254}
255```
256or a string can be passed as options to simply set the encoding - similar to fs.
257
258#### `var stream = drive.createWriteStream(name, [options])`
259
260Write a file as a stream. Similar to fs.createWriteStream.
261If `options.cached` is set to `true`, this function returns results only if they have already been downloaded.
262`options.metadata` is optionally an object with string keys and buffer objects to set metadata on the file entry.
263
264#### `drive.writeFile(name, buffer, [options], [callback])`
265
266Write a file from a single buffer. Similar to fs.writeFile.
267
268#### `drive.unlink(name, [callback])`
269
270Unlinks (deletes) a file. Similar to fs.unlink.
271
272#### `drive.mkdir(name, [options], [callback])`
273
274Explictly create an directory. Similar to fs.mkdir
275
276#### `drive.rmdir(name, [callback])`
277
278Delete an empty directory. Similar to fs.rmdir.
279
280#### `drive.readdir(name, [options], [callback])`
281
282Lists a directory. Similar to fs.readdir.
283
284Options include:
285
286``` js
287{
288 recursive: false, // Recurse into subdirectories and mounts
289 noMount: false // Do not recurse into mounts when recursive: true
290}
291```
292
293#### `drive.stat(name, [options], callback)`
294
295Stat an entry. Similar to fs.stat. Sample output:
296
297```
298Stat {
299 dev: 0,
300 nlink: 1,
301 rdev: 0,
302 blksize: 0,
303 ino: 0,
304 mode: 16877,
305 uid: 0,
306 gid: 0,
307 size: 0,
308 offset: 0,
309 blocks: 0,
310 atime: 2017-04-10T18:59:00.147Z,
311 mtime: 2017-04-10T18:59:00.147Z,
312 ctime: 2017-04-10T18:59:00.147Z,
313 linkname: undefined
314}
315```
316
317The stat may include a metadata object (string keys, buffer values) with metadata that was passed into `writeFile` or `createWriteStream`.
318
319The output object includes methods similar to fs.stat:
320
321``` js
322var stat = drive.stat('/hello.txt')
323stat.isDirectory()
324stat.isFile()
325stat.isSymlink()
326```
327
328Options include:
329```js
330{
331 wait: true|false // default: true
332}
333```
334
335If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
336
337#### `drive.lstat(name, [options], callback)`
338
339Stat an entry but do not follow symlinks. Similar to fs.lstat.
340
341Options include:
342```js
343{
344 wait: true|false // default: true
345}
346```
347
348If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
349
350#### `drive.access(name, [options], callback)`
351
352Similar to fs.access.
353
354Options include:
355```js
356{
357 wait: true|false // default: true
358}
359```
360
361If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
362
363### File Descriptors
364If you want more control over your reads and writes, you can open file descriptors. The file descriptor API mirrors Node's descriptors. Importantly, Hyperdrive does not currently handle random-access writes. Similarly, appends require the previous contents of the file to be duplicated, though this all happens internally. Random-access reads, on the other hand, are fully supported and very fast.
365
366We're still investigating more performant solutions to random-access write and appends, and it's high on our priority list!
367
368#### `drive.open(name, flags, callback)`
369
370Open a file and get a file descriptor back. Similar to fs.open.
371
372Note that currently only read mode is supported in this API.
373
374#### `drive.read(fd, buf, offset, len, position, callback)`
375
376Read from a file descriptor into a buffer. Similar to fs.read.
377
378#### `drive.write(fd, buf, offset, len, pos, cb)`
379
380Write from a buffer into a file descriptor. Similar to fs.write.
381
382#### `drive.symlink(target, linkname, cb)`
383
384Create a symlink from `linkname` to `target`.
385
386### Hyperdrive Mounting
387Hyperdrive supports "mounting" other Hyperdrives at paths within a parent drive. This means that if your friend has a photo album drive, you can nest their drive within your own by calling `myDrive.mount('photos/my-friends-album', <my-friends-album-key>)`.
388
389This feature is useful for composing larger collections out of smaller shareable units, or for aggregating content from many users into one aggregate drive. One pattern you might want to try is a "group" where each user has a structured drive with standard directory names within a parent (i.e. `my-group/userA/docs`, `my-group/userB/docs`). Using this pattern, it's easy to aggregate all "docs" with a recursive readdir over the group.
390
391#### `drive.mount(name, key, opts, cb)`
392
393Mounts another Hyperdrive at the specified mountpoint.
394
395If a `version` is specified in the options, then the mountpoint will reference a static checkout (it will never update).
396
397Options include:
398```js
399{
400 version: (drive version) // The drive version to checkout.
401}
402```
403
404#### `drive.unmount(name, cb)`
405
406Unmount a previously-mounted Hyperdrive.
407
408#### `drive.createMountStream(opts)`
409
410Create a stream containing content/metadata feeds for all mounted Hyperdrives. Each entry in the stream has the form:
411```js
412{
413 path: '/', // The mountpoint
414 metadata: Hypercore(...), // The mounted metadata feed
415 content: Hypercore(...) // The mounted content feed
416}
417```
418
419#### `drive.getAllMounts(opts, cb)`
420
421Returns a Map of the content/metadata feeds for all mounted Hyperdrives, keyed by their mountpoints. The results will always include the top-level feeds (with key '/').
422
423Options include:
424```js
425{
426 memory: true|false // Only list drives currently cached in memory (default: false).
427}
428```
429
430### Closing
431
432#### `drive.close(fd, [callback])`
433
434Close a file. Similar to fs.close.
435
436#### `drive.close([callback])`
437
438Closes all open resources used by the drive.
439The drive should no longer be used after calling this.
440
441### License
442
443MIT