Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
node-version: ${{steps.get-version.outputs.node}}

- run: npm install
- run: npm install domexception

- run: npm run report -- --colors

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ fetch('https://httpbin.org/post', {
.then(json => console.log(json));
```

### Blob part backed up by filesystem
To use, install [domexception](https://github.com/jsdom/domexception).

```sh
npm install fetch-blob domexception
```

```js
const blobFrom = require('fetch-blob/from.js');
const blob1 = blobFrom('./2-GiB-file.bin');
const blob2 = blobFrom('./2-GiB-file.bin');

// Not a 4 GiB memory snapshot, just holds 3 references
// points to where data is located on the disk
const blob = new Blob([blob1, blob2]);
console.log(blob.size) // 4 GiB
```

See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details.

[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
Expand Down
57 changes: 57 additions & 0 deletions from.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const {statSync, createReadStream} = require('fs');
const Blob = require('.');
const DOMException = require('domexception');

/**
* @param {string} path filepath on the disk
* @returns {Blob}
*/
function blobFrom(path) {
const {size, mtime} = statSync(path);
const blob = new BlobDataItem({path, size, mtime});

return new Blob([blob]);
}

/**
* This is a blob backed up by a file on the disk
* with minium requirement
*
* @private
*/
class BlobDataItem {
constructor(options) {
this.size = options.size;
this.path = options.path;
this.start = options.start;
this.mtime = options.mtime;
}

// Slicing arguments is validated and formated
// by Blob.prototype.slice
slice(start, end) {
return new BlobDataItem({
path: this.path,
start,
mtime: this.mtime,
size: end - start
});
}

stream() {
if (statSync(this.path).mtime > this.mtime) {
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError');
}

return createReadStream(this.path, {
start: this.start,
end: this.start + this.size - 1
});
}

get [Symbol.toStringTag]() {
return 'Blob';
}
}

module.exports = blobFrom;
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "A Blob implementation in Node.js, originally from node-fetch.",
"main": "index.js",
"files": [
"from.js",
"index.js",
"index.d.ts"
],
Expand Down Expand Up @@ -48,5 +49,7 @@
}
]
},
"dependencies": {}
"peerDependencies": {
"domexception": "^2.0.1"
}
}
18 changes: 18 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const fs = require('fs');
const test = require('ava');
const Blob = require('.');
const blobFrom = require('./from');
const getStream = require('get-stream');
const {Response} = require('node-fetch');
const {TextDecoder} = require('util');
Expand Down Expand Up @@ -142,3 +144,19 @@ test('Blob works with node-fetch Response.text()', async t => {
const text = await response.text();
t.is(text, data);
});

test('blob part backed up by filesystem', async t => {
const blob = blobFrom('./LICENSE');
t.is(await blob.slice(0, 3).text(), 'MIT');
t.is(await blob.slice(4, 11).text(), 'License');
});

test('Reading after modified should fail', async t => {
const blob = blobFrom('./LICENSE');
await new Promise(resolve => setTimeout(resolve, 100));
const now = new Date();
// Change modified time
fs.utimesSync('./LICENSE', now, now);
const error = await blob.text().catch(error => error);
t.is(error.name, 'NotReadableError');
});