UNPKG

16.1 kBMarkdownView Raw
1# Hyperdrive
2[![Build Status](https://travis-ci.org/hypercore-protocol/hyperdrive.svg?branch=master)](https://travis-ci.org/hypercore-protocol/hyperdrive)
3
4Hyperdrive is a secure, real-time distributed file system designed for easy P2P file sharing.
5
6It has a handful of cool features:
7* __Version Controlled__: Files are versioned by default, making it easy to see historical changes and prevent data loss.
8* __Composable__: Using our mount system, Hyperdrives can be nested within other Hyperdrives, enabling powerful multi-user collaboration tools.
9* __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.
10* __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.
11* __Fast Lookups__: File metadata is stored in a distributed trie structure, meaning files can be located with minimal network lookups.
12* __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.
13
14Hyperdrive can also be used in a variety of ways:
15* [__The Daemon__](https://github.com/hypercore-protocol/hyperdrive-daemon): The Hyperdrive daemon provides both a gRPC API for managing remote Hyperdrives, and a FUSE API that turns Hyperdrives into normal folders on your computer.
16* [__The Client__](https://github.com/hypercore-protocol/hyperdrive-daemon-client): A Node.js client for the daemon. With this you can build services targeting remote drives.
17* [__Beaker__](https://beakerbrowser.com): An experimental browser that has first-class support for Hyperdrive.
18* [__Standalone__](#api): Hyperdrive has flexible storage/networking interfaces, making it easy to embed within larger projects.
19
20## Installation
21If you're looking for a "batteries included" experience, check out the [Hyperdrive daemon](https://github.com/hypercore-protocol/hyperdrive-daemon).
22
23For standalone use in your modules, you can install through NPM:
24``` js
25npm install hyperdrive
26```
27
28## Usage
29
30Hyperdrive aims to implement the same API as Node.js' core `fs` module, and mirrors many POSIX APIs.
31
32``` js
33var hyperdrive = require('hyperdrive')
34var drive = hyperdrive('./my-first-hyperdrive') // content will be stored in this folder
35
36drive.writeFile('/hello.txt', 'world', function (err) {
37 if (err) throw err
38 drive.readdir('/', function (err, list) {
39 if (err) throw err
40 console.log(list) // prints ['hello.txt']
41 drive.readFile('/hello.txt', 'utf-8', function (err, data) {
42 if (err) throw err
43 console.log(data) // prints 'world'
44 })
45 })
46})
47```
48
49Hyperdrives can easily be replicated to other machines over any stream-based transport layer!
50
51``` js
52var net = require('net')
53
54// ... on one machine
55
56var server = net.createServer(function (socket) {
57 socket.pipe(drive.replicate()).pipe(socket)
58})
59
60server.listen(10000)
61
62// ... on another
63
64var clonedDrive = hyperdrive('./my-cloned-hyperdrive', origKey)
65var socket = net.connect(10000)
66
67socket.pipe(clonedDrive.replicate()).pipe(socket)
68```
69
70It also comes with build in versioning, live replication (where the replication streams remain open, syncing new changes), and nested Hyperdrive mounting. See more below.
71
72## API
73
74#### `var drive = hyperdrive(storage, [key], [options])`
75
76Create a new Hyperdrive.
77
78The `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.
79
80- If you pass in a string, the drive content will be stored in a folder at the given path.
81- 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.
82- If you require complete control, you can also pass in a [corestore](https://github.com/andrewosh/corestore) instance (or an API-compatible replacement).
83
84 - `name`: the name of the file to be stored
85 - `opts`
86 - `key`: the [feed key](https://github.com/mafintosh/hypercore#feedkey) of the underlying Hypercore instance
87 - `discoveryKey`: the [discovery key](https://github.com/mafintosh/hypercore#feeddiscoverykey) of the underlying Hypercore instance
88 - `drive`: the current Hyperdrive instance
89
90Options include:
91
92``` js
93{
94 sparse: true, // only download data on content feed when it is specifically requested
95 sparseMetadata: true // only download data on metadata feed when requested
96 extensions: [], // The list of extension message types to use
97}
98```
99
100For more storage configuration, you can also provide any corestore option.
101
102Note 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.
103
104### Replication
105Hyperdrive 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.
106
107#### `var stream = drive.replicate([options])`
108
109Replicate this drive. Options include
110
111``` js
112{
113 live: false, // keep replicating,
114 encrypt: true // Enable NOISE encryption.
115}
116```
117
118### Public Fields
119
120#### `drive.version`
121
122Get the current version of the drive (incrementing number).
123
124#### `drive.key`
125
126The public key identifying the drive.
127
128#### `drive.discoveryKey`
129
130A key derived from the public key that can be used to discovery other peers sharing this drive.
131
132#### `drive.writable`
133
134A boolean indicating whether the drive is writable.
135
136#### `drive.peers`
137
138A list of peers currently replicating with this drive
139
140### Lifecycle Events
141
142#### `drive.on('ready')`
143
144Emitted when the drive is fully ready and all properties has been populated.
145
146#### `drive.on('error', err)`
147
148Emitted when a critical error during load happened.
149
150#### `drive.on('update')`
151
152Emitted when there is a new update to the drive.
153
154#### `drive.on('peer-add', peer)`
155
156Emitted when a new peer has been added.
157
158```js
159const drive = Hyperdrive()
160
161drive.on('peer-add', (peer) => {
162 console.log('Connected peer', peer.remotePublicKey)
163})
164```
165
166#### `drive.on('peer-open', peer)`
167
168Emitted when a peer has been added and has finished handshaking.
169
170#### `drive.on('peer-remove', peer)`
171
172Emitted when a peer has been removed.
173
174#### `drive.on('close')`
175
176Emitted when the drive has been closed.
177
178### Extension Management
179
180Hyperdrive supports [hypercore](https://github.com/hypercore-protocol/hypercore#ext--feedregisterextensionname-handlers) extensions, letting you plug custom logic into a drive's replication streams.
181
182#### `ext = drive.registerExtension(name, handlers)`
183
184Register a new replication extension. `name` should be the name of your extension and `handlers` should look like this:
185
186```js
187{
188 encoding: 'json' | 'binary' | 'utf-8' | anyAbstractEncoding,
189 onmessage (message, peer) {
190 // called when a message is received from a peer
191 // will be decoded using the encoding you provide
192 },
193 onerror (err) {
194 // called in case of an decoding error
195 }
196}
197```
198
199#### `ext.send(message, peer)`
200
201Send an extension message to a specific peer.
202
203#### `ext.broadcast(message)`
204
205Send a message to every peer you are connected to.
206
207### Version Control
208Since 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.
209
210#### `var oldDrive = drive.checkout(version, [opts])`
211
212Checkout 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.
213
214#### `drive.createTag(name, [version], cb)`
215Create a tag that maps to a given version. If a version is not provided, the current version will be used.
216
217Tags 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.
218
219#### `drive.getTaggedVersion(name, cb)`
220Return the version corresponding to a tag.
221
222Combined with `checkout`, this lets you navigate between tagged versions.
223
224#### `drive.deleteTag(name, cb)`
225Delete a tag. If the tag doesn't exist, this will be a no-op.
226
227#### `drive.getAllTags(cb)`
228Return a Map of all tags. The Map will be of the form:
229```
230{
231 name => version
232}
233```
234
235### Downloading
236In 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.
237
238#### `drive.download([path], [callback])`
239
240Download all files in path of current version.
241If no path is specified this will download all files.
242
243You can use this with `.checkout(version)` to download a specific version of the drive.
244
245``` js
246drive.checkout(version).download()
247```
248
249### Reading and Writing
250
251#### `var stream = drive.createReadStream(name, [options])`
252
253Read a file out as a stream. Similar to fs.createReadStream.
254
255Options include:
256
257``` js
258{
259 start: optionalByteOffset, // similar to fs
260 end: optionalInclusiveByteEndOffset, // similar to fs
261 length: optionalByteLength
262}
263```
264
265#### `drive.readFile(name, [options], callback)`
266
267Read an entire file into memory. Similar to fs.readFile.
268
269Options can either be an object or a string
270
271Options include:
272```js
273{
274 encoding: string
275}
276```
277or a string can be passed as options to simply set the encoding - similar to fs.
278
279#### `var stream = drive.createWriteStream(name, [options])`
280
281Write a file as a stream. Similar to fs.createWriteStream.
282If `options.cached` is set to `true`, this function returns results only if they have already been downloaded.
283`options.metadata` is optionally an object with string keys and buffer objects to set metadata on the file entry.
284
285#### `drive.writeFile(name, buffer, [options], [callback])`
286
287Write a file from a single buffer. Similar to fs.writeFile.
288
289#### `drive.unlink(name, [callback])`
290
291Unlinks (deletes) a file. Similar to fs.unlink.
292
293#### `drive.mkdir(name, [options], [callback])`
294
295Explictly create an directory. Similar to fs.mkdir
296
297#### `drive.rmdir(name, [callback])`
298
299Delete an empty directory. Similar to fs.rmdir.
300
301#### `drive.readdir(name, [options], [callback])`
302
303Lists a directory. Similar to fs.readdir.
304
305Options include:
306
307``` js
308{
309 recursive: false, // Recurse into subdirectories and mounts
310 noMount: false // Do not recurse into mounts when recursive: true
311}
312```
313
314#### `drive.stat(name, [options], callback)`
315
316Stat an entry. Similar to fs.stat. Sample output:
317
318```
319Stat {
320 dev: 0,
321 nlink: 1,
322 rdev: 0,
323 blksize: 0,
324 ino: 0,
325 mode: 16877,
326 uid: 0,
327 gid: 0,
328 size: 0,
329 offset: 0,
330 blocks: 0,
331 atime: 2017-04-10T18:59:00.147Z,
332 mtime: 2017-04-10T18:59:00.147Z,
333 ctime: 2017-04-10T18:59:00.147Z,
334 linkname: undefined
335}
336```
337
338The stat may include a metadata object (string keys, buffer values) with metadata that was passed into `writeFile` or `createWriteStream`.
339
340The output object includes methods similar to fs.stat:
341
342``` js
343var stat = drive.stat('/hello.txt')
344stat.isDirectory()
345stat.isFile()
346stat.isSymlink()
347```
348
349Options include:
350```js
351{
352 wait: true|false // default: true
353}
354```
355
356If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
357
358#### `drive.lstat(name, [options], callback)`
359
360Stat an entry but do not follow symlinks. Similar to fs.lstat.
361
362Options include:
363```js
364{
365 wait: true|false // default: true
366}
367```
368
369If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
370
371#### `drive.info(name, callback)`
372
373Gets mount information about an entry.
374
375The mount information takes the form:
376```js
377{
378 feed, // The metadata feed for the mountpoint.
379 mountPath, // The absolute path of the entry's parent mount.
380 mountInfo // The mount metadata record
381}
382```
383
384#### `drive.access(name, [options], callback)`
385
386Similar to fs.access.
387
388Options include:
389```js
390{
391 wait: true|false // default: true
392}
393```
394
395If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
396
397### File Descriptors
398If 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.
399
400We're still investigating more performant solutions to random-access write and appends, and it's high on our priority list!
401
402#### `drive.open(name, flags, callback)`
403
404Open a file and get a file descriptor back. Similar to fs.open.
405
406Note that currently only read mode is supported in this API.
407
408#### `drive.read(fd, buf, offset, len, position, callback)`
409
410Read from a file descriptor into a buffer. Similar to fs.read.
411
412#### `drive.write(fd, buf, offset, len, pos, cb)`
413
414Write from a buffer into a file descriptor. Similar to fs.write.
415
416#### `drive.symlink(target, linkname, cb)`
417
418Create a symlink from `linkname` to `target`.
419
420### Hyperdrive Mounting
421Hyperdrive 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>)`.
422
423This 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.
424
425#### `drive.mount(name, key, opts, cb)`
426
427Mounts another Hyperdrive at the specified mountpoint.
428
429If a `version` is specified in the options, then the mountpoint will reference a static checkout (it will never update).
430
431Options include:
432```js
433{
434 version: (drive version) // The drive version to checkout.
435}
436```
437
438#### `drive.unmount(name, cb)`
439
440Unmount a previously-mounted Hyperdrive.
441
442#### `drive.createMountStream(opts)`
443
444Create a stream containing content/metadata feeds for all mounted Hyperdrives. Each entry in the stream has the form:
445```js
446{
447 path: '/', // The mountpoint
448 metadata: Hypercore(...), // The mounted metadata feed
449 content: Hypercore(...) // The mounted content feed
450}
451```
452
453#### `drive.getAllMounts(opts, cb)`
454
455Returns 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 '/').
456
457Options include:
458```js
459{
460 memory: true|false // Only list drives currently cached in memory (default: false).
461}
462```
463
464### Closing
465
466#### `drive.close(fd, [callback])`
467
468Close a file. Similar to fs.close.
469
470#### `drive.close([callback])`
471
472Closes all open resources used by the drive.
473The drive should no longer be used after calling this.
474
475#### `archive.destroyStorage([callback])`
476
477Destroys the data stored in the archive and closes it.
478Does not affect mounted archives.
479The archive should no longer be used after calling this.
480
481### License
482
483MIT