1 | # Hyperdrive
|
2 |
|
3 | #### *Note*: This is a prerelease version of Hyperdrive that's backed by [Hypertrie](https://github.com/mafintosh/hypertrie)
|
4 | #### This version is not yet API-complete.
|
5 |
|
6 | Hyperdrive is a secure, real time distributed file system
|
7 |
|
8 | ``` js
|
9 | npm install hyperdrive@prerelease
|
10 | ```
|
11 |
|
12 | [![Build Status](https://travis-ci.org/mafintosh/hyperdrive.svg?branch=master)](https://travis-ci.org/mafintosh/hyperdrive)
|
13 |
|
14 | ## Usage
|
15 |
|
16 | Hyperdrive aims to implement the same API as Node.js' core fs module.
|
17 |
|
18 | ``` js
|
19 | var hyperdrive = require('hyperdrive')
|
20 | var archive = hyperdrive('./my-first-hyperdrive') // content will be stored in this folder
|
21 |
|
22 | archive.writeFile('/hello.txt', 'world', function (err) {
|
23 | if (err) throw err
|
24 | archive.readdir('/', function (err, list) {
|
25 | if (err) throw err
|
26 | console.log(list) // prints ['hello.txt']
|
27 | archive.readFile('/hello.txt', 'utf-8', function (err, data) {
|
28 | if (err) throw err
|
29 | console.log(data) // prints 'world'
|
30 | })
|
31 | })
|
32 | })
|
33 | ```
|
34 |
|
35 | A big difference is that you can replicate the file system to other computers! All you need is a stream.
|
36 |
|
37 | ``` js
|
38 | var net = require('net')
|
39 |
|
40 | // ... on one machine
|
41 |
|
42 | var server = net.createServer(function (socket) {
|
43 | socket.pipe(archive.replicate()).pipe(socket)
|
44 | })
|
45 |
|
46 | server.listen(10000)
|
47 |
|
48 | // ... on another
|
49 |
|
50 | var clonedArchive = hyperdrive('./my-cloned-hyperdrive', origKey)
|
51 | var socket = net.connect(10000)
|
52 |
|
53 | socket.pipe(clonedArchive.replicate()).pipe(socket)
|
54 | ```
|
55 |
|
56 | It also comes with build in versioning and real time replication. See more below.
|
57 |
|
58 | ## API
|
59 |
|
60 | #### `var archive = hyperdrive(storage, [key], [options])`
|
61 |
|
62 | Create a new hyperdrive.
|
63 |
|
64 | The `storage` parameter defines how the contents of the archive will be stored. It can be one of the following, depending on how much control you require over how the archive is stored.
|
65 |
|
66 | - If you pass in a string, the archive content will be stored in a folder at the given path.
|
67 | - You can also pass in a function. This function will be called with the name of each of the required files for the archive, and needs to return a [`random-access-storage`](https://github.com/random-access-storage/) instance.
|
68 | - If you require complete control, you can also pass in an object containing a `metadata` and a `content` field. Both of these need to be functions, and are called with the following arguments:
|
69 |
|
70 | - `name`: the name of the file to be stored
|
71 | - `opts`
|
72 | - `key`: the [feed key](https://github.com/mafintosh/hypercore#feedkey) of the underlying Hypercore instance
|
73 | - `discoveryKey`: the [discovery key](https://github.com/mafintosh/hypercore#feeddiscoverykey) of the underlying Hypercore instance
|
74 | - `archive`: the current Hyperdrive instance
|
75 |
|
76 | The functions need to return a a [`random-access-storage`](https://github.com/random-access-storage/) instance.
|
77 |
|
78 | Options include:
|
79 |
|
80 | ``` js
|
81 | {
|
82 | sparse: true, // only download data on content feed when it is specifically requested
|
83 | sparseMetadata: true // only download data on metadata feed when requested
|
84 | metadataStorageCacheSize: 65536 // how many entries to use in the metadata hypercore's LRU cache
|
85 | contentStorageCacheSize: 65536 // how many entries to use in the content hypercore's LRU cache
|
86 | extensions: [], // The list of extension message types to use
|
87 | }
|
88 | ```
|
89 |
|
90 | Note that a cloned hyperdrive archive can be "sparse". Usually (by setting `sparse: true`) this means that the content is not downloaded until you ask for it, but the entire metadata feed is still downloaded. If you want a _very_ sparse archive, where even the metadata feed is not downloaded until you request it, then you should _also_ set `sparseMetadata: true`.
|
91 |
|
92 | #### `var stream = archive.replicate([options])`
|
93 |
|
94 | Replicate this archive. Options include
|
95 |
|
96 | ``` js
|
97 | {
|
98 | live: false, // keep replicating
|
99 | download: true, // download data from peers?
|
100 | upload: true // upload data to peers?
|
101 | }
|
102 | ```
|
103 |
|
104 | #### `archive.version`
|
105 |
|
106 | Get the current version of the archive (incrementing number).
|
107 |
|
108 | #### `archive.key`
|
109 |
|
110 | The public key identifying the archive.
|
111 |
|
112 | #### `archive.discoveryKey`
|
113 |
|
114 | A key derived from the public key that can be used to discovery other peers sharing this archive.
|
115 |
|
116 | #### `archive.writable`
|
117 |
|
118 | A boolean indicating whether the archive is writable.
|
119 |
|
120 | #### `archive.peers`
|
121 |
|
122 | A list of peers currently replicating with this archive
|
123 |
|
124 | #### `archive.on('ready')`
|
125 |
|
126 | Emitted when the archive is fully ready and all properties has been populated.
|
127 |
|
128 | #### `archive.on('error', err)`
|
129 |
|
130 | Emitted when a critical error during load happened.
|
131 |
|
132 | #### `archive.on('update')`
|
133 |
|
134 | Emitted when there is a new update to the archive.
|
135 |
|
136 | #### `archive.on('extension', name, message, peer)`
|
137 |
|
138 | Emitted 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)`.
|
139 |
|
140 | #### `archive.on('peer-add', peer)`
|
141 |
|
142 | Emitted when a new peer has been added.
|
143 |
|
144 | ```js
|
145 | const archive = Hyperdrive({
|
146 | extension: ['example']
|
147 | })
|
148 |
|
149 | archive.on('extension', (name, message, peer) => {
|
150 | console.log(name, message.toString('utf8'))
|
151 | })
|
152 |
|
153 | archive.on('peer-add', (peer) => {
|
154 | peer.extension('example', Buffer.from('Hello World!', 'utf8'))
|
155 | })
|
156 | ```
|
157 |
|
158 | #### `archive.on('peer-remove', peer)`
|
159 |
|
160 | Emitted when a peer has been removed.
|
161 |
|
162 | #### `archive.on('close')`
|
163 |
|
164 | Emitted when the archive has been closed.
|
165 |
|
166 | #### `archive.extension(name, message)`
|
167 |
|
168 | Broadcasts 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.
|
169 |
|
170 | #### `var oldDrive = archive.checkout(version, [opts])`
|
171 |
|
172 | Checkout a readonly copy of the archive at an old version. Options are used to configure the `oldDrive`:
|
173 |
|
174 | ```js
|
175 | {
|
176 | metadataStorageCacheSize: 65536 // how many entries to use in the metadata hypercore's LRU cache
|
177 | contentStorageCacheSize: 65536 // how many entries to use in the content hypercore's LRU cache
|
178 | treeCacheSize: 65536 // how many entries to use in the append-tree's LRU cache
|
179 | }
|
180 | ```
|
181 |
|
182 | #### `archive.download([path], [callback])`
|
183 |
|
184 | Download all files in path of current version.
|
185 | If no path is specified this will download all files.
|
186 |
|
187 | You can use this with `.checkout(version)` to download a specific version of the archive.
|
188 |
|
189 | ``` js
|
190 | archive.checkout(version).download()
|
191 | ```
|
192 |
|
193 | #### `var stream = archive.createReadStream(name, [options])`
|
194 |
|
195 | Read a file out as a stream. Similar to fs.createReadStream.
|
196 |
|
197 | Options include:
|
198 |
|
199 | ``` js
|
200 | {
|
201 | start: optionalByteOffset, // similar to fs
|
202 | end: optionalInclusiveByteEndOffset, // similar to fs
|
203 | length: optionalByteLength
|
204 | }
|
205 | ```
|
206 |
|
207 | #### `archive.readFile(name, [options], callback)`
|
208 |
|
209 | Read an entire file into memory. Similar to fs.readFile.
|
210 |
|
211 | Options can either be an object or a string
|
212 |
|
213 | Options include:
|
214 | ```js
|
215 | {
|
216 | encoding: string
|
217 | cached: true|false // default: false
|
218 | }
|
219 | ```
|
220 | or a string can be passed as options to simply set the encoding - similar to fs.
|
221 |
|
222 | If `cached` is set to `true`, this function returns results only if they have already been downloaded.
|
223 |
|
224 | #### `var stream = archive.createWriteStream(name, [options])`
|
225 |
|
226 | Write a file as a stream. Similar to fs.createWriteStream.
|
227 | If `options.cached` is set to `true`, this function returns results only if they have already been downloaded.
|
228 | `options.metadata` is optionally an object with string keys and buffer objects to set metadata on the file entry.
|
229 |
|
230 | #### `archive.writeFile(name, buffer, [options], [callback])`
|
231 |
|
232 | Write a file from a single buffer. Similar to fs.writeFile.
|
233 |
|
234 | #### `archive.unlink(name, [callback])`
|
235 |
|
236 | Unlinks (deletes) a file. Similar to fs.unlink.
|
237 |
|
238 | #### `archive.mkdir(name, [options], [callback])`
|
239 |
|
240 | Explictly create an directory. Similar to fs.mkdir
|
241 |
|
242 | #### `archive.rmdir(name, [callback])`
|
243 |
|
244 | Delete an empty directory. Similar to fs.rmdir.
|
245 |
|
246 | #### `archive.readdir(name, [options], [callback])`
|
247 |
|
248 | Lists a directory. Similar to fs.readdir.
|
249 |
|
250 | Options include:
|
251 |
|
252 | ``` js
|
253 | {
|
254 | recursive: false, // Recurse into subdirectories and mounts
|
255 | noMount: false // Do not recurse into mounts when recursive: true
|
256 | }
|
257 | ```
|
258 |
|
259 | #### `archive.stat(name, [options], callback)`
|
260 |
|
261 | Stat an entry. Similar to fs.stat. Sample output:
|
262 |
|
263 | ```
|
264 | Stat {
|
265 | dev: 0,
|
266 | nlink: 1,
|
267 | rdev: 0,
|
268 | blksize: 0,
|
269 | ino: 0,
|
270 | mode: 16877,
|
271 | uid: 0,
|
272 | gid: 0,
|
273 | size: 0,
|
274 | offset: 0,
|
275 | blocks: 0,
|
276 | atime: 2017-04-10T18:59:00.147Z,
|
277 | mtime: 2017-04-10T18:59:00.147Z,
|
278 | ctime: 2017-04-10T18:59:00.147Z,
|
279 | linkname: undefined }
|
280 | ```
|
281 |
|
282 | The stat may include a metadata object (string keys, buffer values) with metadata that was passed into `writeFile` or `createWriteStream`.
|
283 |
|
284 | The output object includes methods similar to fs.stat:
|
285 |
|
286 | ``` js
|
287 | var stat = archive.stat('/hello.txt')
|
288 | stat.isDirectory()
|
289 | stat.isFile()
|
290 | ```
|
291 |
|
292 | Options include:
|
293 | ```js
|
294 | {
|
295 | wait: true|false // default: true
|
296 | }
|
297 | ```
|
298 |
|
299 | If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
|
300 |
|
301 | #### `archive.lstat(name, [options], callback)`
|
302 |
|
303 | Stat an entry but do not follow symlinks. Similar to fs.lstat.
|
304 |
|
305 | Options include:
|
306 | ```js
|
307 | {
|
308 | wait: true|false // default: true
|
309 | }
|
310 | ```
|
311 |
|
312 | If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
|
313 |
|
314 | #### `archive.access(name, [options], callback)`
|
315 |
|
316 | Similar to fs.access.
|
317 |
|
318 | Options include:
|
319 | ```js
|
320 | {
|
321 | wait: true|false // default: true
|
322 | }
|
323 | ```
|
324 |
|
325 | If `wait` is set to `true`, this function will wait for data to be downloaded. If false, will return an error.
|
326 |
|
327 | #### `archive.open(name, flags, callback)`
|
328 |
|
329 | Open a file and get a file descriptor back. Similar to fs.open.
|
330 |
|
331 | Note that currently only read mode is supported in this API.
|
332 |
|
333 | #### `archive.read(fd, buf, offset, len, position, callback)`
|
334 |
|
335 | Read from a file descriptor into a buffer. Similar to fs.read.
|
336 |
|
337 | #### `archive.write(fd, buf, offset, len, pos, cb)`
|
338 |
|
339 | Write from a buffer into a file descriptor. Similar to fs.write.
|
340 |
|
341 | #### `archive.symlink(target, linkname, cb)`
|
342 |
|
343 | Create a symlink from `linkname` to `target`.
|
344 |
|
345 | #### `archive.mount(name, key, opts, cb)`
|
346 |
|
347 | Mounts another Hyperdrive at the specified mountpoint.
|
348 |
|
349 | If a `version` is specified in the options, then the mountpoint will reference a static checkout (it will never update).
|
350 |
|
351 | Options include:
|
352 | ```js
|
353 | {
|
354 | version: (drive version) // The drive version to checkout.
|
355 | }
|
356 | ```
|
357 |
|
358 | #### `archive.unmount(name, cb)`
|
359 |
|
360 | Unmount a previously-mounted Hyperdrive.
|
361 |
|
362 | #### `archive.createMountStream(opts)`
|
363 |
|
364 | Create a stream containing content/metadata feeds for all mounted Hyperdrives. Each entry in the stream has the form:
|
365 | ```js
|
366 | {
|
367 | path: '/', // The mountpoint
|
368 | metadata: Hypercore(...), // The mounted metadata feed
|
369 | content: Hypercore(...) // The mounted content feed
|
370 | }
|
371 | ```
|
372 |
|
373 | #### `archive.getAllMounts(opts, cb)`
|
374 |
|
375 | Returns 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 '/').
|
376 |
|
377 | Options include:
|
378 | ```js
|
379 | {
|
380 | memory: true|false // Only list drives currently cached in memory (default: false).
|
381 | }
|
382 | ```
|
383 |
|
384 | #### `archive.close(fd, [callback])`
|
385 |
|
386 | Close a file. Similar to fs.close.
|
387 |
|
388 | #### `archive.close([callback])`
|
389 |
|
390 | Closes all open resources used by the archive.
|
391 | The archive should no longer be used after calling this.
|