hypercore-fetch 8.5.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +9 -0
  2. package/index.js +49 -0
  3. package/package.json +3 -1
  4. package/test.js +24 -3
package/README.md CHANGED
@@ -130,6 +130,15 @@ The `body` can be either a `String`, an `ArrayBuffer`, a `Blob`, a WHATWG `Reada
130
130
 
131
131
  Your `NAME` will likely be a `name` in most cases to ensure you have a writeable archive.
132
132
 
133
+ ### `fetch('hyper://NAME/folder/', {method: 'PUT', body: new FormData()})`
134
+
135
+ You can add multiple files to a folder using the `PUT` method with a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) body.
136
+
137
+ You can [append](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to a FormData with `formData.append(fieldname, content, 'filename.txt')` where `fieldname` gets ignored (use something like `file`?) the `content` can either be a String, Blob, or some sort of stream.
138
+ The `filename` will be the filename within the directory that gets created.
139
+
140
+ `NAME` can either be the 64 character hex key for an archive, a domain to parse with [dat-dns](https://www.npmjs.com/package/dat-dns), or a name for an archive which allows you to write to it.
141
+
133
142
  ### `fetch('hyper://NAME/example.txt', {method: 'DELETE'})`
134
143
 
135
144
  You can delete a file in an archive by using the `DELETE` method.
package/index.js CHANGED
@@ -7,6 +7,8 @@ const makeDir = require('make-dir')
7
7
  const { Readable } = require('streamx')
8
8
  const makeFetch = require('make-fetch')
9
9
  const { EventIterator } = require('event-iterator')
10
+ const Busboy = require('busboy')
11
+ const posixPath = require('path').posix
10
12
 
11
13
  const DEFAULT_TIMEOUT = 5000
12
14
 
@@ -380,9 +382,56 @@ module.exports = function makeHyperFetch (opts = {}) {
380
382
 
381
383
  if (method === 'PUT') {
382
384
  checkWritable(archive)
385
+ const contentType = headers.get('Content-Type') || headers.get('content-type')
386
+ const isFormData = contentType && contentType.includes('multipart/form-data')
387
+
383
388
  if (path.endsWith('/')) {
384
389
  await makeDir(path, { fs: archive })
390
+ const busboy = new Busboy({ headers: rawHeaders })
391
+
392
+ const toUpload = new EventIterator(({ push, stop, fail }) => {
393
+ busboy.once('error', fail)
394
+ busboy.once('finish', stop)
395
+
396
+ busboy.on('file', async (fieldName, fileData, fileName) => {
397
+ const finalPath = posixPath.join(path, fileName)
398
+
399
+ const source = Readable.from(fileData)
400
+ const destination = archive.createWriteStream(finalPath)
401
+
402
+ source.pipe(destination)
403
+ try {
404
+ Promise.race([
405
+ once(source, 'error').then((e) => { throw e }),
406
+ once(destination, 'error').then((e) => { throw e }),
407
+ once(source, 'end')
408
+ ])
409
+ } catch (e) {
410
+ fail(e)
411
+ }
412
+ })
413
+
414
+ // TODO: Does busboy need to be GC'd?
415
+ return () => {}
416
+ })
417
+
418
+ Readable.from(body).pipe(busboy)
419
+
420
+ await Promise.all(await collect(toUpload))
421
+
422
+ return {
423
+ statusCode: 200,
424
+ headers: responseHeaders,
425
+ data: intoAsyncIterable(canonical)
426
+ }
385
427
  } else {
428
+ if (isFormData) {
429
+ return {
430
+ statusCode: 400,
431
+ headers: responseHeaders,
432
+ data: intoAsyncIterable('FormData only supported for folders (ending with a /)')
433
+ }
434
+ }
386
435
  const parentDir = path.split('/').slice(0, -1).join('/')
387
436
  if (parentDir) {
388
437
  await makeDir(parentDir, { fs: archive })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore-fetch",
3
- "version": "8.5.0",
3
+ "version": "8.6.0",
4
4
  "description": "Implementation of Fetch that uses the Dat SDK for loading p2p content",
5
5
  "bin": {
6
6
  "hypercore-fetch": "bin.js"
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "homepage": "https://github.com/RangerMauve/hypercore-fetch#readme",
26
26
  "dependencies": {
27
+ "busboy": "^0.3.1",
27
28
  "event-iterator": "^2.0.0",
28
29
  "fetch-headers": "^2.0.0",
29
30
  "hyper-dns": "^0.12.0",
@@ -37,6 +38,7 @@
37
38
  "streamx": "^2.10.0"
38
39
  },
39
40
  "devDependencies": {
41
+ "form-data": "^4.0.0",
40
42
  "random-access-memory": "^3.1.1",
41
43
  "tape": "^5.2.2"
42
44
  }
package/test.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const SDK = require('hyper-sdk')
2
2
  const test = require('tape')
3
+ const FormData = require('form-data')
3
4
 
4
5
  runTests()
5
6
 
@@ -84,10 +85,30 @@ async function runTests () {
84
85
  t.equal(await response2.text(), SAMPLE_CONTENT, 'Read back written data')
85
86
  })
86
87
 
87
- test('PUT directory', async (t) => {
88
- const response1 = await fetch('hyper://example/foo/bar/', { method: 'PUT' })
88
+ test.only('PUT FormData to directory', async (t) => {
89
+ const form = new FormData()
89
90
 
90
- t.equal(response1.status, 200, 'Got OK response on directory creation')
91
+ form.append('file', SAMPLE_CONTENT, {
92
+ filename: 'example.txt'
93
+ })
94
+ const body = form.getBuffer()
95
+ const headers = form.getHeaders()
96
+
97
+ const response1 = await fetch('hyper://example/foo/bar/', {
98
+ method: 'PUT',
99
+ headers,
100
+ body
101
+ })
102
+
103
+ t.equal(response1.status, 200, 'Got OK response on directory upload')
104
+
105
+ console.log(await response1.text())
106
+
107
+ const response2 = await fetch('hyper://example/foo/bar/example.txt')
108
+
109
+ t.equal(response2.status, 200, 'Got OK response on read')
110
+
111
+ t.equal(await response2.text(), SAMPLE_CONTENT, 'Read back written data')
91
112
  })
92
113
 
93
114
  test('PUT file in new directory', async (t) => {