UNPKG

15.8 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 extension: ['example']
161})
162
163drive.on('extension', (name, message, peer) => {
164 console.log(name, message.toString('utf8'))
165})
166
167drive.on('peer-add', (peer) => {
168 peer.extension('example', Buffer.from('Hello World!', 'utf8'))
169})
170```
171
172#### `drive.on('peer-remove', peer)`
173
174Emitted when a peer has been removed.
175
176#### `drive.on('close')`
177
178Emitted when the drive has been closed.
179
180### Extension Management
181Hyperdrive supports [hypercore](https://github.com/mafintosh/hypercore) extensions, letting you plug custom logic into a drive's replication streams.
182
183#### `drive.on('extension', name, message, peer)`
184
185Emitted 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)`.
186
187#### `drive.extension(name, message)`
188
189Broadcasts 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.
190
191### Version Control
192Since 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.
193
194#### `var oldDrive = drive.checkout(version, [opts])`
195
196Checkout 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.
197
198#### `drive.createTag(name, [version], cb)`
199Create a tag that maps to a given version. If a version is not provided, the current version will be used.
200
201Tags 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.
202
203#### `drive.getTaggedVersion(name, cb)`
204Return the version corresponding to a tag.
205
206Combined with `checkout`, this lets you navigate between tagged versions.
207
208#### `drive.deleteTag(name, cb)`
209Delete a tag. If the tag doesn't exist, this will be a no-op.
210
211#### `drive.getAllTags(cb)`
212Return a Map of all tags. The Map will be of the form:
213```
214{
215 name => version
216}
217```
218
219### Downloading
220In 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.
221
222#### `drive.download([path], [callback])`
223
224Download all files in path of current version.
225If no path is specified this will download all files.
226
227You can use this with `.checkout(version)` to download a specific version of the drive.
228
229``` js
230drive.checkout(version).download()
231```
232
233### Reading and Writing
234
235#### `var stream = drive.createReadStream(name, [options])`
236
237Read a file out as a stream. Similar to fs.createReadStream.
238
239Options include:
240
241``` js
242{
243 start: optionalByteOffset, // similar to fs
244 end: optionalInclusiveByteEndOffset, // similar to fs
245 length: optionalByteLength
246}
247```
248
249#### `drive.readFile(name, [options], callback)`
250
251Read an entire file into memory. Similar to fs.readFile.
252
253Options can either be an object or a string
254
255Options include:
256```js
257{
258 encoding: string
259}
260```
261or a string can be passed as options to simply set the encoding - similar to fs.
262
263#### `var stream = drive.createWriteStream(name, [options])`
264
265Write a file as a stream. Similar to fs.createWriteStream.
266If `options.cached` is set to `true`, this function returns results only if they have already been downloaded.
267`options.metadata` is optionally an object with string keys and buffer objects to set metadata on the file entry.
268
269#### `drive.writeFile(name, buffer, [options], [callback])`
270
271Write a file from a single buffer. Similar to fs.writeFile.
272
273#### `drive.unlink(name, [callback])`
274
275Unlinks (deletes) a file. Similar to fs.unlink.
276
277#### `drive.mkdir(name, [options], [callback])`
278
279Explictly create an directory. Similar to fs.mkdir
280
281#### `drive.rmdir(name, [callback])`
282
283Delete an empty directory. Similar to fs.rmdir.
284
285#### `drive.readdir(name, [options], [callback])`
286
287Lists a directory. Similar to fs.readdir.
288
289Options include:
290
291``` js
292{
293 recursive: false, // Recurse into subdirectories and mounts
294 noMount: false // Do not recurse into mounts when recursive: true
295}
296```
297
298#### `drive.stat(name, [options], callback)`
299
300Stat an entry. Similar to fs.stat. Sample output:
301
302```
303Stat {
304 dev: 0,
305 nlink: 1,
306 rdev: 0,
307 blksize: 0,
308 ino: 0,
309 mode: 16877,
310 uid: 0,
311 gid: 0,
312 size: 0,
313 offset: 0,
314 blocks: 0,
315 atime: 2017-04-10T18:59:00.147Z,
316 mtime: 2017-04-10T18:59:00.147Z,
317 ctime: 2017-04-10T18:59:00.147Z,
318 linkname: undefined
319}
320```
321
322The stat may include a metadata object (string keys, buffer values) with metadata that was passed into `writeFile` or `createWriteStream`.
323
324The output object includes methods similar to fs.stat:
325
326``` js
327var stat = drive.stat('/hello.txt')
328stat.isDirectory()
329stat.isFile()
330stat.isSymlink()
331```
332
333Options include:
334```js
335{
336 wait: true|false // default: true
337}
338```
339
340If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
341
342#### `drive.lstat(name, [options], callback)`
343
344Stat an entry but do not follow symlinks. Similar to fs.lstat.
345
346Options include:
347```js
348{
349 wait: true|false // default: true
350}
351```
352
353If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
354
355#### `drive.access(name, [options], callback)`
356
357Similar to fs.access.
358
359Options include:
360```js
361{
362 wait: true|false // default: true
363}
364```
365
366If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
367
368### File Descriptors
369If 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.
370
371We're still investigating more performant solutions to random-access write and appends, and it's high on our priority list!
372
373#### `drive.open(name, flags, callback)`
374
375Open a file and get a file descriptor back. Similar to fs.open.
376
377Note that currently only read mode is supported in this API.
378
379#### `drive.read(fd, buf, offset, len, position, callback)`
380
381Read from a file descriptor into a buffer. Similar to fs.read.
382
383#### `drive.write(fd, buf, offset, len, pos, cb)`
384
385Write from a buffer into a file descriptor. Similar to fs.write.
386
387#### `drive.symlink(target, linkname, cb)`
388
389Create a symlink from `linkname` to `target`.
390
391### Hyperdrive Mounting
392Hyperdrive 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>)`.
393
394This 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.
395
396#### `drive.mount(name, key, opts, cb)`
397
398Mounts another Hyperdrive at the specified mountpoint.
399
400If a `version` is specified in the options, then the mountpoint will reference a static checkout (it will never update).
401
402Options include:
403```js
404{
405 version: (drive version) // The drive version to checkout.
406}
407```
408
409#### `drive.unmount(name, cb)`
410
411Unmount a previously-mounted Hyperdrive.
412
413#### `drive.createMountStream(opts)`
414
415Create a stream containing content/metadata feeds for all mounted Hyperdrives. Each entry in the stream has the form:
416```js
417{
418 path: '/', // The mountpoint
419 metadata: Hypercore(...), // The mounted metadata feed
420 content: Hypercore(...) // The mounted content feed
421}
422```
423
424#### `drive.getAllMounts(opts, cb)`
425
426Returns 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 '/').
427
428Options include:
429```js
430{
431 memory: true|false // Only list drives currently cached in memory (default: false).
432}
433```
434
435### Closing
436
437#### `drive.close(fd, [callback])`
438
439Close a file. Similar to fs.close.
440
441#### `drive.close([callback])`
442
443Closes all open resources used by the drive.
444The drive should no longer be used after calling this.
445
446#### `archive.destroyStorage([callback])`
447
448Destroys the data stored in the archive and closes it.
449Does not affect mounted archives.
450The archive should no longer be used after calling this.
451
452### License
453
454MIT