Compare commits

...
Author SHA1 Message Date
JMR-dev f8f9335d2d readme updates 2025-07-25 15:53:18 -05:00
JMR-dev b76652eaaf uncommenting necessary code 2025-07-25 15:37:57 -05:00
Jason RossandCopilot f23cdad2a8 Update test/test-wpt-in-node.test.js
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 15:37:57 -05:00
Jason RossandCopilot 40e6f7ea13 Update test/test-wpt-in-node.js
Remove console logs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 15:37:57 -05:00
Jason RossandCopilot aef02263c1 Update package.json
Remove typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-25 15:37:57 -05:00
JMR-dev 4dc9ba543f updated ci-cd workflow 2025-07-25 15:37:57 -05:00
JMR-dev 1a29178875 package name and correct versioning 2025-07-25 15:37:57 -05:00
JMR-dev c87ebb6711 README updates 2025-07-25 15:37:57 -05:00
JMR-dev b363c0fa81 tests are passing 2025-07-25 15:37:57 -05:00
JMR-dev eb1d304ede * Changed package manager to pnpm
* Updated outdated dependancies and removed deprecated ones
* Tests still need work, package only used for personal use at the moment, not production ready
2025-07-25 15:37:57 -05:00
JMR-dev 9ebbbe6553 WIP fixing this package 2025-07-25 15:37:57 -05:00
Jimmy Wärting a1a182e597 breaking: Remove dependency on streams polyfill (#149)
* Remove dependency on streams polyfill

* require node v16.7

* change test version

* only test ubuntu

* remove changelog
2023-05-18 13:44:05 +02:00
Richie Bendall 57e4daef36 Update README.md 2022-11-25 11:59:11 +13:00
Jimmy Wärting 0455796ede Update README.md 2022-08-08 10:14:35 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2acec9c0dc Bump @types/node from 17.0.45 to 18.0.2 (#140)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.45 to 18.0.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 19:04:46 +02:00
Jimmy Wärting 20ee08f33e feat: new way of creating temporary files backed up by fs (#137)
* feat: new way of creating temporary files backed up by fs

* add readme
2022-07-06 19:01:17 +02:00
Jimmy Wärting f077471a23 fix: avoid reading files when the size is empty (#134)
* fix: avoid reading files when the size is empty

* also use stat.size for checking if it is modified

* bump minor version number

* Don't push empty chunks into the array (creates faster reads)

* reverted back to avoid making a if check
2022-03-10 15:11:10 +01:00
Jimmy Wärting dc29588366 fix: misc updates (#132)
* misc updates

* bring back c8 for test coverage

* use new loader

* don't use engines

* simplify ci

* fix double slice problem
2022-01-20 14:00:15 +01:00
Jimmy Wärting 8ab587d340 removed xo 2021-10-30 13:52:13 +02:00
Jimmy Wärting f61ee01955 bumped minor patch 2021-10-30 13:50:21 +02:00
Jimmy Wärting 5dbcbb91b4 standard.js style 2021-10-30 13:50:21 +02:00
Jimmy Wärting c5c2b52154 fix(warning): Supress warnings when importing experimental web stream from NodeJS (#125)
* Suppress warnings when importing web stream
* added node: prefix
* updated changelog
* apply `standard streams.cjs --fix`
2021-10-30 13:04:02 +02:00
Ben McCann 836709fc65 fix: prefix stream/web import with node: (#122) 2021-10-24 17:27:58 +02:00
Nick K 2c11647222 fix(blob, file, tests): Minor improvements for Blob and File implementations (#120)
* Use private class fields in parts initialization directly.

* Move encoder initialization outside the loop

* Improve errors reported by Blob constructor.

* Simulate WebIDL type casting for lastModified in File constructor.

* Add cancel method for Blob.stream() underlying source.

* Fix indentation inconsistency.

* Use getReader in blob stream cancellation test instead of Symbol.asyncIterator
2021-10-24 14:56:15 +02:00
Jimmy Wärting 9022f8fc70 Update index.js 2021-09-29 17:14:07 +02:00
Jimmy Wärting 0e07457246 Added v12 to test 2021-09-07 17:16:24 +02:00
Jimmy Wärting 46476faf49 Update CHANGELOG.md 2021-07-31 11:30:20 +02:00
Jimmy Wärting b86b1409de found/fixed some errors while testing agains WPT (#109)
* found/fixed some errors while testing agains WPT
* only run test.js with ava
2021-07-31 11:24:15 +02:00
Jimmy Wärting a08f394f1b Create PULL_REQUEST_TEMPLATE.md 2021-07-31 11:08:57 +02:00
Nick K c2903ca530 Allow usage of iterable object in Blob constructor. (#108)
* Allow usage of iterable object in Blob constructor.

* Fix ESLint errors
2021-07-29 15:42:42 +02:00
Jimmy Wärting 0b028432d0 bumped patch 2021-07-16 11:59:39 +02:00
Jimmy Wärting f64c20694f added streams.cjs 2021-07-16 11:58:08 +02:00
Jimmy Wärting 613160ba58 bumped patch 2021-07-16 11:57:08 +02:00
Jimmy Wärting efdad69698 bumped patch 2021-07-16 11:55:56 +02:00
Jimmy Wärting e00f849f6d improve typing (#105)
* improve typing

* bug fix

* don't test cjs
2021-07-16 11:51:34 +02:00
Jimmy Wärting 52f296c283 Conditional loading (#104)
use good old cjs to conditionally load stuff in node only
2021-07-16 00:33:00 +02:00
Jimmy Wärting 701103ffd2 Update CHANGELOG.md 2021-07-15 22:55:57 +02:00
Jimmy Wärting ada23da6ee Update package.json 2021-07-15 22:50:37 +02:00
Jimmy Wärting 16f312c543 use whatwg streams (#103)
* use whatwg streams
* could not use optional changing in v12
* could not use fs/promise in v12
2021-07-15 22:49:58 +02:00
Jimmy Wärting 5ba554efd2 Lowered targeted node version to those who support ESM
closes #98
2021-06-19 11:51:54 +02:00
Jimmy Wärting 4747497b52 fixed lastModified 2021-06-10 10:51:16 +02:00
Jimmy Wärting 09fd40448a Update file.js 2021-06-10 10:33:00 +02:00
Jimmy Wärting 8d14e88040 Created a File Class (#97)
* Created a File Class
* Test the untested parts
* ignore any d.ts file
2021-05-29 11:17:26 +02:00
dependabot-preview[bot]anddependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> 8ef89adad4 Upgrade to GitHub-native Dependabot (#89)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-05-14 11:01:14 +02:00
Jimmy Wärting a3b0d62b9d corrected the release name 2021-05-07 11:45:20 +02:00
Jimmy Wärting 97a084b5e8 Revamp (#83)
* See CHANGELOG for v3.0.0

* removed get-stream

* enabled linter just for test.js & from.js

* Any blob part is acceptable (jsdoc)
since the default fallback is to cast unknown items into strings

* async stat version

* import from fs/promise instead

* Updated the Readme and code examples

* known differences, buffer.Blob support, private stream, more test

* require node 14

* use fetch v3
2021-05-07 11:42:01 +02:00
Jimmy Wärting a8fed4e338 Return empty a empty stream for zero sized blob (#86)
Return empty a empty stream for zero sized blob
2021-04-29 14:01:01 +02:00
Nick K 483973cf25 Fix for the 'start' range of BlobDataItem when reading its stream (#85)
Set default value for 'start' option
2021-04-26 12:06:27 +02:00
Jimmy Wärting cc929f3714 2.1.1 was already publish on npm 9 month ago 2021-04-20 17:52:55 +02:00
Jimmy Wärting 5584886f0f no message 2021-04-20 17:46:23 +02:00
Jimmy Wärting 0d2cd8b9b7 Updated dependencies
Gosh i hate linters. Want to remove them...
2021-04-20 16:32:18 +02:00
Jimmy Wärting 149030ddbe use extension 2021-04-20 16:15:04 +02:00
Nick K f08e6a6773 Add generated typings for from.js file. (#80)
* Generate typings for from.js
* Add generated typings to files section and fix linter issue.
* Remove domexception installation from ci workflow
* Fix for .d.ts output
Co-authored-by: Jimmy Wärting <jimmy@warting.se>
2021-04-20 16:10:46 +02:00
Nick K eed6a1dddd Add nullish values checking in Symbol.hasInstance (#82)
* Add nullish values checking in Symbol.hasInstance
* Fix for a test description
* Add an extension.
* Disable unicorn/import-index rule.
* Fix a typo
2021-04-20 16:07:38 +02:00
Jimmy Wärting 3b12a39ea2 cast type to string 2021-02-13 15:50:02 +01:00
Jimmy Wärting 591a28ed94 Update from.js 2021-02-13 15:01:14 +01:00
Richie Bendall 40f4568c95 Revert 689bee122b 2021-01-29 02:46:12 +13:00
Richie Bendall a74fb90d95 Update ci.yml 2021-01-28 13:57:22 +13:00
Richie Bendall 689bee122b Use native Blob when possible 2021-01-28 13:55:33 +13:00
Richie Bendall 50b89fcdce Make domexception an optional peer dependency 2021-01-18 22:23:51 +13:00
Jimmy Wärting 40c6ff45df Update from.js 2021-01-04 01:00:19 +01:00
Jimmy Wärting 5b2bab5365 Update CHANGELOG.md 2020-07-28 17:48:58 +02:00
Jimmy Wärting b75fb395b4 bumped minor version to 1.2.0 2020-07-28 17:44:52 +02:00
Jimmy Wärting 3217da9bc7 Create blob backed up by filesystem (#55)
* create a blob backed up by filesystem
* added code coverage test for filesystem blobs
* updated readme for using filesystem
* install peer dep
* added from to files array in package.json
2020-07-28 17:42:31 +02:00
Jimmy Wärting ae6b565ae6 fix slice test
`added` bytes was calculated wrong
2020-07-24 01:56:18 +02:00
Irakli Gozalishvili 26ca0e39b4 create test illustrating a bug 2020-07-24 01:56:18 +02:00
Antoni Kepinski 99388a79bb update changelog 2020-06-11 16:31:21 +02:00
Antoni Kepinski cf379d10e0 2.0.1 2020-06-11 16:30:40 +02:00
Pim 1d1faebe18 fix: remove upper bound for node engine semver (#49) 2020-06-11 16:29:53 +02:00
Konstantin Vyatkin 903a4c1bb0 use Symbol.hasInstance (#46) 2020-06-11 10:00:58 -04:00
Konstantin Vyatkin 126b4ec697 Switch to GitHub Actions (#47)
* remove travis

* add codecov partial line coverage

* switch coverage to c8

* add GitHub Actions

* update badge

* import TextDecoder from util

* lint
2020-06-11 10:01:22 +02:00
Antoni Kepinski 38639ec4aa release 2.0.0 2020-06-10 21:09:47 +02:00
Antoni Kepinski 5608cef75c update changelog 2020-06-10 21:08:54 +02:00
Antoni Kepinski 62fc8eab22 1.0.7 2020-06-10 16:14:25 +02:00
Antoni Kepinski 1a3c424a07 update nyc 2020-06-10 16:13:07 +02:00
Antoni Kepinski f4fa7e5825 Update CHANGELOG.md 2020-06-10 16:12:41 +02:00
Konstantin Vyatkin a1fef8cd0b Create TypeScript declarations from JSDoc (#45) 2020-06-10 16:09:02 +02:00
dependabot-preview[bot]anddependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> 1ec92a63b2 Bump xo from 0.30.0 to 0.32.0 (#39)
Bumps [xo](https://github.com/xojs/xo) from 0.30.0 to 0.32.0.
- [Release notes](https://github.com/xojs/xo/releases)
- [Commits](https://github.com/xojs/xo/compare/v0.30.0...v0.32.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-09 20:22:03 +02:00
Jimmy Wärting fd4f45e70f Operate on blob parts (byte sequence) (#44)
* operate on blob parts

* jsdoc

* specify parts type

* Back to 100% (discovered a small bug)
2020-06-07 23:42:42 +02:00
Richie Bendall 9dc07473c6 Update formatting 2020-06-08 01:12:25 +12:00
Jimmy Wärting 870be1e2d5 Use a WeakMap for private properties (#42)
* options.buffer isn't a valid option

* truly private internal properties

* 100% test coverage
2020-06-06 00:09:32 -04:00
Antoni Kepinski 960f7e528d Merge branch 'master' of https://github.com/node-fetch/fetch-blob 2020-05-21 11:08:12 +02:00
Antoni Kepinski 582880a297 bump version number 2020-05-21 11:08:09 +02:00
Antoni Kepinski c1dd0851d8 Update CHANGELOG.md 2020-05-21 11:07:56 +02:00
Linus Unnebäck bec5a3dceb Use upstream Blob directly in typings (#38) 2020-05-21 09:12:04 +01:00
Richie Bendall 6fabfce52f Update dependencies
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-05-19 21:59:02 +12:00
Richie Bendall 4a46def452 Maintenance update
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-04-14 21:52:25 +12:00
Richie Bendall 9a458af871 Remove lockfiles
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-04-14 21:47:17 +12:00
Richie Bendall f7e60156a4 docs: Fix grammar
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-03-22 16:14:38 +13:00
Richie Bendall af2edbd01c Merge branch 'master' of https://github.com/node-fetch/fetch-blob 2020-03-22 15:45:43 +13:00
Richie Bendall f4ddd159a4 docs: Update meta
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-03-22 15:45:40 +13:00
dependabot-preview[bot]anddependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> 83559d1144 [Security] Bump acorn from 7.1.0 to 7.1.1 (#23)
Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.1.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/7.1.0...7.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-22 15:43:12 +13:00
dependabot-preview[bot] 8bf6441343 Bump codecov from 3.6.1 to 3.6.5 (#14)
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.6.1 to 3.6.5.
- [Release notes](https://github.com/codecov/codecov-node/releases)
- [Commits](https://github.com/codecov/codecov-node/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-25 11:13:37 +13:00
Richie Bendall 1f5258cd79 chore: Update dependencies
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2020-02-13 21:48:24 +13:00
Richie Bendall f336a8d182 feat: Add typings (#6)
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
2019-12-15 19:35:42 +08:00
21 changed files with 2026 additions and 5668 deletions
+6
View File
@@ -0,0 +1,6 @@
coverage:
round: up
precision: 2
parsers:
javascript:
enable_partials: yes
+23
View File
@@ -0,0 +1,23 @@
<!-- Thanks for contributing! -->
## The purpose of this PR is:
...
## This is what had to change:
...
## This is what like reviewers to know:
...
-------------------------------------------------------------------------------------------------
<!-- Mark what you have done with [x], Remove unnecessary ones. Add new tasks that may fit (like TODO's) -->
- [ ] I prefixed the PR-title with `docs: `, `fix(area): `, `feat(area): ` or `breaking(area): `
- [ ] I updated the README.md
- [ ] I Added unit test(s)
-------------------------------------------------------------------------------------------------
<!-- Add a `- fix #_NUMBER_` line for every Issue this PR solves. Do not comma separate them -->
- fix #000
+25
View File
@@ -0,0 +1,25 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: get-stream
versions:
- 6.0.0
- 6.0.1
- dependency-name: typescript
versions:
- 4.1.3
- 4.1.4
- 4.1.5
- 4.2.2
- 4.2.3
- 4.2.4
- dependency-name: xo
versions:
- 0.37.1
- 0.38.1
- 0.38.2
+34
View File
@@ -0,0 +1,34 @@
name: CI
on:
push:
branches: [master]
pull_request:
paths:
- "**.js"
- "package.json"
- ".github/workflows/ci.yml"
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: ["18", "20", "22"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- run: corepack enable
- run: corepack prepare pnpm@latest --activate
- run: pnpm install
- run: pnpm test
- run: pnpm run report -- --colors
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage/coverage-final.json
+12 -4
View File
@@ -1,3 +1,8 @@
# Lock files
package-lock.json
yarn.lock
# Logs
logs
*.log
@@ -11,6 +16,8 @@ pids
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
@@ -42,8 +49,8 @@ typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional cache
.cache
# Optional REPL history
.node_repl_history
@@ -57,5 +64,6 @@ typings/
# dotenv environment variables file
.env
# next.js build output
.next
*.d.ts
*.d.cts
.DS_Store
-11
View File
@@ -1,11 +0,0 @@
language: node_js
node_js:
- "8"
- "10"
- "12"
- "node"
script:
- npm run coverage
cache:
directories:
- node_modules
+3
View File
@@ -0,0 +1,3 @@
{
"cSpell.words": ["Wärting"]
}
-27
View File
@@ -1,27 +0,0 @@
Changelog
=========
## v1.0.5
- Other: no change to code, update dev dependency to address vulnerability reports
## v1.0.4
- Other: general code revwrite to pass linting, prepare for `node-fetch` release v3
## v1.0.3
- Fix: package.json export `blob.js` properly now
## v1.0.2
- Other: fix test integration
## v1.0.1
- Other: readme update
## v1.0.0
- Major: initial release
+106 -26
View File
@@ -1,38 +1,118 @@
fetch-blob
==========
# fetch-blob-fixed
[![npm version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![coverage status][codecov-image]][codecov-url]
[![install size][install-size-image]][install-size-url]
A Blob implementation on node.js, originally from node-fetch
A Blob implementation in Node.js, originally from [node-fetch](https://github.com/node-fetch/node-fetch).
Use the built-in [`Blob`](https://nodejs.org/docs/latest-v18.x/api/buffer.html#class-blob) in Node.js 18 and later.
This package is a fork from the original `fetch-blob` package, which was deprecated in favor of the built-in `Blob` class in Node.js 18 and later. This fork is maintained for specific use cases that require a polyfill and for packages that already use this implementation. Credit to Jimmy Wärting and other contributors for the original implementation.
Packages should migrate to the built-in `Blob` class in Node.js 18 and later. This package will not be maintained forever. Committing to support through July 2028, after which it will be deprecated.
This package was created to remove deprecated packages from the original `fetch-blob` package and allow it to work with modern Node.js versions when necessary. It is not intended to be a long-term solution. This was made to ensure I did not have deprecated packages in my project.
I also moved this project to `pnpm`, as it is the package manager I use and prefer. It is not a requirement to use this package, but if you develop anything further on top of this, `pnpm` is recommended for consistency.
## Installation
```sh
pnpm install fetch-blob-fixed
```
<details>
<summary>Differences from other Blobs</summary>
- Unlike NodeJS `buffer.Blob` (Added in: v15.7.0) and browser native Blob this polyfilled version can't be sent via PostMessage
- This blob version is more arbitrary, it can be constructed with blob parts that isn't a instance of itself
it has to look and behave as a blob to be accepted as a blob part.
- The benefit of this is that you can create other types of blobs that don't contain any internal data that has to be read in other ways, such as the `BlobDataItem` created in `from.js` that wraps a file path into a blob-like item and read lazily (nodejs plans to [implement this][fs-blobs] as well)
- The `blob.stream()` is the most noticeable differences. It returns a WHATWG stream now. to keep it as a node stream you would have to do:
```js
import {Readable} from 'stream'
const stream = Readable.from(blob.stream())
```
</details>
## Usage
```sh
$ npm install fetch-blob
```js
// Ways to import
import { Blob } from 'fetch-blob'
import { File } from 'fetch-blob/file.js'
const { Blob } = await import('fetch-blob')
// Ways to read the blob:
const blob = new Blob(['hello, world'])
await blob.text()
await blob.arrayBuffer()
for await (let chunk of blob.stream()) { ... }
blob.stream().getReader().read()
blob.stream().getReader({mode: 'byob'}).read(view)
```
### Blob part backed up by filesystem
`fetch-blob/from.js` comes packed with tools to convert any filepath into either a Blob or a File
It will not read the content into memory. It will only stat the file for last modified date and file size.
```js
const Blob = require('fetch-blob');
const fetch = require('node-fetch');
fetch('https://httpbin.org/post', {
method: 'POST',
body: new Blob(['hello'], { type: 'text/plain' })
})
.then(res => res.json()) // expecting a json response
.then(json => console.log(json));
// The default export is sync and use fs.stat to retrieve size & last modified as a blob
import {File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync} from 'fetch-blob/from.js'
const fsFile = fileFromSync('./2-GiB-file.bin', 'application/octet-stream')
const fsBlob = await blobFrom('./2-GiB-file.mp4')
// Not a 4 GiB memory snapshot, just holds references
// points to where data is located on the disk
const blob = new Blob([fsFile, fsBlob, 'memory', new Uint8Array(10)])
console.log(blob.size) // ~4 GiB
```
See [MDN doc](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [Tests](https://github.com/bitinn/fetch-blob/blob/master/test.js) for more details.
`blobFrom|blobFromSync|fileFrom|fileFromSync(path, [mimetype])`
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
[npm-url]: https://www.npmjs.com/package/fetch-blob
[travis-image]: https://flat.badgen.net/travis/bitinn/fetch-blob
[travis-url]: https://travis-ci.org/bitinn/fetch-blob
[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/fetch-blob/master
[codecov-url]: https://codecov.io/gh/bitinn/fetch-blob
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
### Creating a temporary file on the disk
(requires [FinalizationRegistry] - node v14.6)
When using both `createTemporaryBlob` and `createTemporaryFile`
then you will write data to the temporary folder in their respective OS.
The arguments can be anything that [fsPromises.writeFile] supports. NodeJS
v14.17.0+ also supports writing (async)Iterable streams and passing in a
AbortSignal, so both NodeJS stream and whatwg streams are supported. When the
file have been written it will return a Blob/File handle with a references to
this temporary location on the disk. When you no longer have a references to
this Blob/File anymore and it have been GC then it will automatically be deleted.
This files are also unlinked upon exiting the process.
```js
import { createTemporaryBlob, createTemporaryFile } from 'fetch-blob/from.js'
const req = new Request('https://httpbin.org/image/png')
const res = await fetch(req)
const type = res.headers.get('content-type')
const signal = req.signal
let blob = await createTemporaryBlob(res.body, { type, signal })
// const file = createTemporaryBlob(res.body, 'img.png', { type, signal })
blob = undefined // loosing references will delete the file from disk
```
- `createTemporaryBlob(data, { type, signal })`
- `createTemporaryFile(data, FileName, { type, signal, lastModified })`
### Creating Blobs backed up by other async sources
Our Blob & File class are more generic then any other polyfills in the way that it can accept any blob look-a-like item
An example of this is that our blob implementation can be constructed with parts coming from [BlobDataItem](https://github.com/node-fetch/fetch-blob/blob/8ef89adad40d255a3bbd55cf38b88597c1cd5480/from.js#L32) (aka a filepath) or from [buffer.Blob](https://nodejs.org/api/buffer.html#buffer_new_buffer_blob_sources_options), It dose not have to implement all the methods - just enough that it can be read/understood by our Blob implementation. The minium requirements is that it has `Symbol.toStringTag`, `size`, `slice()`, `stream()` methods (the stream method
can be as simple as being a sync or async iterator that yields Uint8Arrays. If you then wrap it in our Blob or File `new Blob([blobDataItem])` then you get all of the other methods that should be implemented in a blob or file (aka: text(), arrayBuffer() and type and a ReadableStream)
An example of this could be to create a file or blob like item coming from a remote HTTP request. Or from a DataBase
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 of how to use the Blob.
[npm-url]: https://www.npmjs.com/package/fetch-blob-fixed
-135
View File
@@ -1,135 +0,0 @@
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
// (MIT licensed)
const Stream = require('stream');
// Fix for "Readable" isn't a named export issue
const {Readable} = Stream;
const BUFFER = Symbol('buffer');
const TYPE = Symbol('type');
class Blob {
constructor(...args) {
this[TYPE] = '';
const blobParts = args[0];
const options = args[1];
const buffers = [];
/* eslint-disable-next-line no-unused-vars */
let size = 0;
if (blobParts) {
blobParts.forEach(element => {
let buffer;
if (element instanceof Buffer) {
buffer = element;
} else if (ArrayBuffer.isView(element)) {
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
} else if (element instanceof ArrayBuffer) {
buffer = Buffer.from(element);
} else if (element instanceof Blob) {
buffer = element[BUFFER];
} else {
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
}
size += buffer.length;
buffers.push(buffer);
});
}
this[BUFFER] = Buffer.concat(buffers);
const type = options && options.type !== undefined && String(options.type).toLowerCase();
if (type && !/[^\u0020-\u007E]/.test(type)) {
this[TYPE] = type;
}
if (options && Buffer.isBuffer(options.buffer)) {
this[BUFFER] = options.buffer;
}
}
get size() {
return this[BUFFER].length;
}
get type() {
return this[TYPE];
}
text() {
return Promise.resolve(this[BUFFER].toString());
}
arrayBuffer() {
const buf = this[BUFFER];
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
return Promise.resolve(ab);
}
stream() {
const readable = new Readable();
readable._read = () => { };
readable.push(this[BUFFER]);
readable.push(null);
return readable;
}
toString() {
return '[object Blob]';
}
slice(...args) {
const {size} = this;
const start = args[0];
const end = args[1];
let relativeStart;
let relativeEnd;
if (start === undefined) {
relativeStart = 0;
} else if (start < 0) {
relativeStart = Math.max(size + start, 0);
} else {
relativeStart = Math.min(start, size);
}
if (end === undefined) {
relativeEnd = size;
} else if (end < 0) {
relativeEnd = Math.max(size + end, 0);
} else {
relativeEnd = Math.min(end, size);
}
const span = Math.max(relativeEnd - relativeStart, 0);
const buffer = this[BUFFER];
const slicedBuffer = buffer.slice(
relativeStart,
relativeStart + span
);
const blob = new Blob([], {type: args[2]});
blob[BUFFER] = slicedBuffer;
return blob;
}
}
Object.defineProperties(Blob.prototype, {
size: {enumerable: true},
type: {enumerable: true},
slice: {enumerable: true}
});
Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
value: 'Blob',
writable: false,
enumerable: false,
configurable: true
});
module.exports = Blob;
+48
View File
@@ -0,0 +1,48 @@
import { Blob } from './index.js'
const _File = class File extends Blob {
#lastModified = 0
#name = ''
/**
* @param {*[]} fileBits
* @param {string} fileName
* @param {{lastModified?: number, type?: string}} options
*/// @ts-ignore
constructor (fileBits, fileName, options = {}) {
if (arguments.length < 2) {
throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`)
}
super(fileBits, options)
if (options === null) options = {}
// Simulate WebIDL type casting for NaN value in lastModified option.
const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified)
if (!Number.isNaN(lastModified)) {
this.#lastModified = lastModified
}
this.#name = String(fileName)
}
get name () {
return this.#name
}
get lastModified () {
return this.#lastModified
}
get [Symbol.toStringTag] () {
return 'File'
}
static [Symbol.hasInstance] (object) {
return !!object && object instanceof Blob &&
/^(File)$/.test(object[Symbol.toStringTag])
}
}
/** @type {typeof globalThis.File} */// @ts-ignore
export const File = _File
+165
View File
@@ -0,0 +1,165 @@
import {
realpathSync,
statSync,
rmdirSync,
createReadStream,
promises as fs
} from 'node:fs'
import { basename, sep, join } from 'node:path'
import { tmpdir } from 'node:os'
import process from 'node:process'
import { File } from './file.js'
import { Blob } from './index.js'
const { stat, mkdtemp } = fs
let i = 0, tempDir, registry
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
const blobFromSync = (path, type) => fromBlob(statSync(path), path, type)
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<Blob>}
*/
const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type))
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<File>}
*/
const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type))
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
const fileFromSync = (path, type) => fromFile(statSync(path), path, type)
// @ts-ignore
const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({
path,
size: stat.size,
lastModified: stat.mtimeMs,
start: 0
})], { type })
// @ts-ignore
const fromFile = (stat, path, type = '') => new File([new BlobDataItem({
path,
size: stat.size,
lastModified: stat.mtimeMs,
start: 0
})], basename(path), { type, lastModified: stat.mtimeMs })
/**
* Creates a temporary blob backed by the filesystem.
* NOTE: requires node.js v14 or higher to use FinalizationRegistry
*
* @param {*} data Same as fs.writeFile data
* @param {BlobPropertyBag & {signal?: AbortSignal}} options
* @param {AbortSignal} [signal] in case you wish to cancel the write operation
* @returns {Promise<Blob>}
*/
const createTemporaryBlob = async (data, {signal, type} = {}) => {
registry = registry || new FinalizationRegistry(fs.unlink)
tempDir = tempDir || await mkdtemp(realpathSync(tmpdir()) + sep)
const id = `${i++}`
const destination = join(tempDir, id)
if (data instanceof ArrayBuffer) data = new Uint8Array(data)
await fs.writeFile(destination, data, { signal })
const blob = await blobFrom(destination, type)
registry.register(blob, destination)
return blob
}
/**
* Creates a temporary File backed by the filesystem.
* Pretty much the same as constructing a new File(data, name, options)
*
* NOTE: requires node.js v14 or higher to use FinalizationRegistry
* @param {*} data
* @param {string} name
* @param {FilePropertyBag & {signal?: AbortSignal}} opts
* @returns {Promise<File>}
*/
const createTemporaryFile = async (data, name, opts) => {
const blob = await createTemporaryBlob(data)
return new File([blob], name, opts)
}
/**
* This is a blob backed up by a file on the disk
* with minium requirement. Its wrapped around a Blob as a blobPart
* so you have no direct access to this.
*
* @private
*/
class BlobDataItem {
#path
#start
constructor (options) {
this.#path = options.path
this.#start = options.start
this.size = options.size
this.lastModified = options.lastModified
this.originalSize = options.originalSize === undefined
? options.size
: options.originalSize
}
/**
* Slicing arguments is first validated and formatted
* to not be out of range by Blob.prototype.slice
*/
slice (start, end) {
return new BlobDataItem({
path: this.#path,
lastModified: this.lastModified,
originalSize: this.originalSize,
size: end - start,
start: this.#start + start
})
}
async * stream () {
const { mtimeMs, size } = await stat(this.#path)
if (mtimeMs > this.lastModified || this.originalSize !== size) {
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')
}
yield * createReadStream(this.#path, {
start: this.#start,
end: this.#start + this.size - 1
})
}
get [Symbol.toStringTag] () {
return 'Blob'
}
}
process.once('exit', () => {
tempDir && rmdirSync(tempDir, { recursive: true })
})
export default blobFromSync
export {
Blob,
blobFrom,
blobFromSync,
createTemporaryBlob,
File,
fileFrom,
fileFromSync,
createTemporaryFile
}
+254
View File
@@ -0,0 +1,254 @@
/*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.ReadableStream) {
try {
const process = await import('node:process').then(m => m.default)
const { emitWarning } = process
try {
process.emitWarning = () => {}
const streams = await import('node:stream/web').then(m => m.default)
Object.assign(globalThis, streams)
process.emitWarning = emitWarning
} catch (error) {
process.emitWarning = emitWarning
throw error
}
} catch (error) {}
}
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
const POOL_SIZE = 65536
/**
* @param {(Blob | Uint8Array)[]} parts
* @param {boolean} clone
* @returns {AsyncIterableIterator<Uint8Array>}
*/
async function * toIterator (parts, clone) {
for (const part of parts) {
if (ArrayBuffer.isView(part)) {
if (clone) {
let position = part.byteOffset
const end = part.byteOffset + part.byteLength
while (position !== end) {
const size = Math.min(end - position, POOL_SIZE)
const chunk = part.buffer.slice(position, position + size)
position += chunk.byteLength
yield new Uint8Array(chunk)
}
} else {
yield part
}
} else {
// @ts-ignore TS Think blob.stream() returns a node:stream
yield * part.stream()
}
}
}
const _Blob = class Blob {
/** @type {Array.<(Blob|Uint8Array)>} */
#parts = []
#type = ''
#size = 0
#endings = 'transparent'
/**
* The Blob() constructor returns a new Blob object. The content
* of the blob consists of the concatenation of the values given
* in the parameter array.
*
* @param {*} blobParts
* @param {{ type?: string, endings?: string }} [options]
*/
constructor (blobParts = [], options = {}) {
if (typeof blobParts !== 'object' || blobParts === null) {
throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.')
}
if (typeof blobParts[Symbol.iterator] !== 'function') {
throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.')
}
if (typeof options !== 'object' && typeof options !== 'function') {
throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.')
}
if (options === null) options = {}
const encoder = new TextEncoder()
for (const element of blobParts) {
let part
if (ArrayBuffer.isView(element)) {
part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength))
} else if (element instanceof ArrayBuffer) {
part = new Uint8Array(element.slice(0))
} else if (element instanceof Blob) {
part = element
} else {
part = encoder.encode(`${element}`)
}
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
// Avoid pushing empty parts into the array to better GC them
if (size) {
this.#size += size
this.#parts.push(part)
}
}
this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`
const type = options.type === undefined ? '' : String(options.type)
this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''
}
/**
* The Blob interface's size property returns the
* size of the Blob in bytes.
*/
get size () {
return this.#size
}
/**
* The type property of a Blob object returns the MIME type of the file.
*/
get type () {
return this.#type
}
/**
* The text() method in the Blob interface returns a Promise
* that resolves with a string containing the contents of
* the blob, interpreted as UTF-8.
*
* @return {Promise<string>}
*/
async text () {
// More optimized than using this.arrayBuffer()
// that requires twice as much ram
const decoder = new TextDecoder()
let str = ''
for await (const part of toIterator(this.#parts, false)) {
str += decoder.decode(part, { stream: true })
}
// Remaining
str += decoder.decode()
return str
}
/**
* The arrayBuffer() method in the Blob interface returns a
* Promise that resolves with the contents of the blob as
* binary data contained in an ArrayBuffer.
*
* @return {Promise<ArrayBuffer>}
*/
async arrayBuffer () {
const data = new Uint8Array(this.size)
let offset = 0
for await (const chunk of toIterator(this.#parts, false)) {
data.set(chunk, offset)
offset += chunk.length
}
return data.buffer
}
stream () {
const it = toIterator(this.#parts, true)
return new globalThis.ReadableStream({
// @ts-ignore
type: 'bytes',
async pull (ctrl) {
const chunk = await it.next()
chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value)
},
async cancel () {
await it.return()
}
})
}
/**
* The Blob interface's slice() method creates and returns a
* new Blob object which contains data from a subset of the
* blob on which it's called.
*
* @param {number} [start]
* @param {number} [end]
* @param {string} [type]
*/
slice (start = 0, end = this.size, type = '') {
const { size } = this
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size)
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size)
const span = Math.max(relativeEnd - relativeStart, 0)
const parts = this.#parts
const blobParts = []
let added = 0
for (const part of parts) {
// don't add the overflow to new blobParts
if (added >= span) {
break
}
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
if (relativeStart && size <= relativeStart) {
// Skip the beginning and change the relative
// start & end position as we skip the unwanted parts
relativeStart -= size
relativeEnd -= size
} else {
let chunk
if (ArrayBuffer.isView(part)) {
chunk = part.subarray(relativeStart, Math.min(size, relativeEnd))
added += chunk.byteLength
} else {
chunk = part.slice(relativeStart, Math.min(size, relativeEnd))
added += chunk.size
}
relativeEnd -= size
blobParts.push(chunk)
relativeStart = 0 // All next sequential parts should start at 0
}
}
const blob = new Blob([], { type: `${type}` })
blob.#size = span
blob.#parts = blobParts
return blob
}
get [Symbol.toStringTag] () {
return 'Blob'
}
static [Symbol.hasInstance] (object) {
return (
object &&
typeof object === 'object' &&
typeof object.constructor === 'function' &&
(
typeof object.stream === 'function' ||
typeof object.arrayBuffer === 'function'
) &&
/^(Blob|File)$/.test(object[Symbol.toStringTag])
)
}
}
Object.defineProperties(_Blob.prototype, {
size: { enumerable: true },
type: { enumerable: true },
slice: { enumerable: true }
})
/** @type {typeof globalThis.Blob} */
export const Blob = _Blob
-5358
View File
File diff suppressed because it is too large Load Diff
+40 -20
View File
@@ -1,32 +1,52 @@
{
"name": "fetch-blob",
"version": "1.0.5",
"description": "A Blob implementation on node.js, originally from node-fetch",
"main": "blob.js",
"name": "fetch-blob-fixed",
"version": "1.0.1",
"description": "Blob & File implementation in Node.js, originally from node-fetch.",
"main": "index.js",
"type": "module",
"files": [
"from.js",
"file.js",
"file.d.ts",
"index.js",
"index.d.ts",
"from.d.ts"
],
"scripts": {
"test": "ava",
"report": "nyc ava",
"coverage": "nyc --reporter json --reporter text ava && codecov -f coverage/coverage-final.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bitinn/fetch-blob.git"
"test": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
"report": "c8 --reporter json --reporter text npm run test",
"coverage": "pnpm run report && codecov -f coverage/coverage-final.json",
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js"
},
"repository": "https://github.com/node-fetch/fetch-blob.git",
"keywords": [
"blob",
"file",
"node-fetch"
],
"author": "David Frank",
"engines": {
"node": ">=18"
},
"author": "Jimmy Wärting <jimmy@warting.se> (https://jimmy.warting.se)",
"license": "MIT",
"bugs": {
"url": "https://github.com/bitinn/fetch-blob/issues"
"url": "https://github.com/node-fetch/fetch-blob/issues"
},
"homepage": "https://github.com/bitinn/fetch-blob#readme",
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
"devDependencies": {
"ava": "^1.4.1",
"codecov": "^3.6.1",
"get-stream": "^5.1.0",
"node-fetch": "^2.6.0",
"nyc": "^14.1.1"
}
"@types/node": "^24.1.0",
"c8": "^10.1.3",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@10.13.1",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
]
}
+547
View File
@@ -0,0 +1,547 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@types/node':
specifier: ^24.1.0
version: 24.1.0
c8:
specifier: ^10.1.3
version: 10.1.3
typescript:
specifier: ^5.8.3
version: 5.8.3
packages:
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@istanbuljs/schema@0.1.3':
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
engines: {node: '>=8'}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.4':
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
'@types/node@24.1.0':
resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
c8@10.1.3:
resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
monocart-coverage-reports: ^2
peerDependenciesMeta:
monocart-coverage-reports:
optional: true
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
istanbul-lib-report@3.0.1:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
istanbul-reports@3.1.7:
resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
engines: {node: '>=8'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
make-dir@4.0.0:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
semver@7.7.2:
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
engines: {node: '>=10'}
hasBin: true
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
test-exclude@7.0.1:
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
engines: {node: '>=18'}
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@7.8.0:
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
v8-to-istanbul@9.3.0:
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
engines: {node: '>=10.12.0'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
snapshots:
'@bcoe/v8-coverage@1.0.2': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@istanbuljs/schema@0.1.3': {}
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.4': {}
'@jridgewell/trace-mapping@0.3.29':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.4
'@pkgjs/parseargs@0.11.0':
optional: true
'@types/istanbul-lib-coverage@2.0.6': {}
'@types/node@24.1.0':
dependencies:
undici-types: 7.8.0
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.1: {}
balanced-match@1.0.2: {}
brace-expansion@2.0.2:
dependencies:
balanced-match: 1.0.2
c8@10.1.3:
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@istanbuljs/schema': 0.1.3
find-up: 5.0.0
foreground-child: 3.3.1
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-reports: 3.1.7
test-exclude: 7.0.1
v8-to-istanbul: 9.3.0
yargs: 17.7.2
yargs-parser: 21.1.1
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
convert-source-map@2.0.0: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
escalade@3.2.0: {}
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
get-caller-file@2.0.5: {}
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
has-flag@4.0.0: {}
html-escaper@2.0.2: {}
is-fullwidth-code-point@3.0.0: {}
isexe@2.0.0: {}
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-report@3.0.1:
dependencies:
istanbul-lib-coverage: 3.2.2
make-dir: 4.0.0
supports-color: 7.2.0
istanbul-reports@3.1.7:
dependencies:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lru-cache@10.4.3: {}
make-dir@4.0.0:
dependencies:
semver: 7.7.2
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
minipass@7.1.2: {}
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
package-json-from-dist@1.0.1: {}
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
require-directory@2.1.1: {}
semver@7.7.2: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.1.0
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
test-exclude@7.0.1:
dependencies:
'@istanbuljs/schema': 0.1.3
glob: 10.4.5
minimatch: 9.0.5
typescript@5.8.3: {}
undici-types@7.8.0: {}
v8-to-istanbul@9.3.0:
dependencies:
'@jridgewell/trace-mapping': 0.3.29
'@types/istanbul-lib-coverage': 2.0.6
convert-source-map: 2.0.0
which@2.0.2:
dependencies:
isexe: 2.0.0
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
yocto-queue@0.1.0: {}
-87
View File
@@ -1,87 +0,0 @@
import test from 'ava';
import Blob from './blob';
import util from 'util';
import stream from 'stream';
import getStream from 'get-stream';
import { Response } from 'node-fetch';
const TextDecoder = util.TextDecoder;
const Readable = stream.Readable;
test('Blob ctor', t => {
let data = 'a=1';
let blob = new Blob([data]);
t.pass();
});
test('Blob size', t => {
let data = 'a=1';
let blob = new Blob([data]);
t.is(blob.size, data.length);
});
test('Blob type', t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
t.is(blob.type, type);
});
test('Blob text()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
t.is(await blob.text(), data);
});
test('Blob arrayBuffer()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
let decoder = new TextDecoder('utf-8');
let buffer = await blob.arrayBuffer();
t.is(decoder.decode(buffer), data);
});
test('Blob stream()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
let result = await getStream(blob.stream());
t.is(result, data);
});
test('Blob toString()', t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
t.is(blob.toString(), '[object Blob]');
});
test('Blob slice()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
let blob2 = blob.slice(0, 1);
t.is(await blob2.text(), data.slice(0, 1));
});
test('Blob works with node-fetch Response.blob()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
let res = new Response(blob);
let blob2 = await res.blob();
t.is(await blob2.text(), data);
});
test('Blob works with node-fetch Response.text()', async t => {
let data = 'a=1';
let type = 'text/plain';
let blob = new Blob([data], { type });
let res = new Response(blob);
let text = await res.text();
t.is(text, data);
});
+59
View File
@@ -0,0 +1,59 @@
import { Buffer } from 'node:buffer'
import fs from 'node:fs'
import { get } from 'node:https'
const fetch = url => new Promise(rs => get(url, rs))
const cache = new URL('./.cache/', import.meta.url)
export function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
// Normally Node.js would error on specifiers starting with 'https://', so
// this hook intercepts them and converts them into absolute URLs to be
// passed along to the later hooks below.
if (specifier.startsWith('https://')) {
return {
shortCircuit: true,
url: specifier,
};
} else if (parentURL && parentURL.startsWith('https://')) {
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href,
};
}
// Let Node.js handle all other specifiers.
return nextResolve(specifier);
}
export async function load(url, context, nextLoad) {
// For JavaScript to be loaded over the network, we need to fetch and
// return it.
if (url.startsWith('https://')) {
const uuid = Buffer.from(url).toString('hex')
const cachedFile = new URL(uuid, cache)
let data = ''
// cache remote files for 1h
if (fs.existsSync(cachedFile) && fs.statSync(cachedFile).mtimeMs > Date.now() - 1000 * 60 * 60) {
data = fs.readFileSync(cachedFile, 'utf8')
} else {
const res = await fetch(url).catch(err => err)
for await (const chunk of res) data += chunk
fs.mkdirSync(cache, { recursive: true })
fs.writeFileSync(cachedFile, data)
}
// This example assumes all network-provided JavaScript is ES module
// code.
return {
format: 'module',
shortCircuit: true,
source: data,
}
}
// Let Node.js handle all other URLs.
return nextLoad(url);
}
+257
View File
@@ -0,0 +1,257 @@
// @ts-nocheck
// @ts-ignore
import fs from 'node:fs'
import buffer from 'node:buffer'
import syncBlob, {
blobFromSync,
blobFrom,
fileFromSync,
fileFrom,
createTemporaryBlob,
createTemporaryFile
} from '../from.js'
const license = fs.readFileSync('./LICENSE')
test_blob(() => new Blob([
'a',
new Uint8Array([98]),
new Uint16Array([25699]),
new Uint8Array([101]).buffer,
Buffer.from('f'),
new Blob(['g']),
{},
new URLSearchParams('foo')
]), {
desc: 'Blob ctor parts',
expected: 'abcdefg[object Object]foo=',
type: '',
length: 26
})
promise_test(async () => {
assert_equals(fileFromSync('./LICENSE').name, 'LICENSE')
assert_equals((await fileFrom('./LICENSE')).name, 'LICENSE')
}, 'file from returns the same name')
// Could not find similar test on WPT
test(() => {
const now = new Date()
assert_equals(new File([], '', { lastModified: now }).lastModified, +now)
assert_equals(new File([], '', { lastModified: +now }).lastModified, +now)
assert_equals(new File([], '', { lastModified: 100 }).lastModified, 100)
assert_equals(new File([], '', { lastModified: '200' }).lastModified, 200)
assert_equals(new File([], '', { lastModified: true }).lastModified, 1)
assert_equals(new File([], '', { lastModified: false }).lastModified, 0)
assert_approx_equals(new File([], '').lastModified, Date.now(), 0.020)
assert_approx_equals(new File([], '', { lastModified: undefined }).lastModified, Date.now(), 0.020)
}, 'File sets current time')
// Could not find similar test on WPT
test(() => {
const values = ['Not a Number', [], {}, null]
// I can't really see anything about this in the spec,
// but this is how browsers handle type casting for this option...
for (const lastModified of values) {
const file = new File([], '', { lastModified })
assert_equals(file.lastModified, 0)
}
}, 'Interprets NaN value in lastModified option as 0')
test(() => {
assert_equals(blobFromSync, syncBlob)
}, 'default export is named exported blobFromSync')
// This was necessary to avoid large ArrayBuffer clones (slice)
promise_test(async t => {
const buf = new Uint8Array(65590)
const blob = new Blob([buf])
let i = 0
// eslint-disable-next-line no-unused-vars
for await (const chunk of blob.stream()) {
i++
}
assert_equals(i, 2)
}, 'Large chunks are divided into smaller chunks')
// Couldn't find a test for this on WPT
promise_test(async () => {
const buf = new Uint8Array([97])
const blob = new Blob([buf])
const chunk = await blob.stream().getReader().read()
assert_equals(chunk.value[0], 97)
chunk.value[0] = 98
assert_equals(await blob.text(), 'a')
}, 'Blobs are immutable')
/**
* Deviation from WPT: it's important to keep boundary value
* so we don't lowercase the type
* @see https://github.com/w3c/FileAPI/issues/43
*/
test(() => {
const type = 'multipart/form-data; boundary=----WebKitFormBoundaryTKqdrVt01qOBltBd'
assert_equals(new Blob([], { type }).type, type)
assert_equals(new File([], '', { type }).type, type)
}, 'Dose not lowercase the type')
test( // Because we have symbol.hasInstance it's best to test it...
() => (assert_false(null instanceof Blob), assert_false(null instanceof File)),
'Instanceof check returns false for nullish values'
)
test( // Because browser normally never tries things taken for granted
() => assert_equals(new Blob().toString(), '[object Blob]'),
'blob.toString() returns [object Blob]'
)
test( // Because browser normally never tries things taken for granted
() => assert_equals(new File([], '').toString(), '[object File]'),
'file.toString() returns [object File]'
)
// fetch-blob uniques is that it supports arbitrary blobs too
test(() => {
class File {
stream () {}
get [Symbol.toStringTag] () { return 'File' }
}
assert_true(new File() instanceof Blob)
}, 'Blob-ish class is an instance of Blob')
// fetch-blob uniques is that it supports arbitrary blobs too
if (buffer.Blob) {
test_blob(() => new Blob([new buffer.Blob(['blob part'])]), {
desc: 'Can wrap buffer.Blob to a fetch-blob',
expected: 'blob part',
type: '',
})
}
/**
* Test if Blob can be constructed with BOM and keep it when casted to string
* Test if blob.text() can correctly remove BOM - `buffer.toString()` is bad
*/
promise_test(async () => {
const text = '{"foo": "bar"}'
const blob = new Blob([`\uFEFF${text}`])
assert_equals(blob.size, 17)
assert_equals(await blob.text(), text)
const ab = await blob.slice(0, 3).arrayBuffer()
assert_equals_typed_array(new Uint8Array(ab), new Uint8Array([0xEF, 0xBB, 0xBF]))
}, 'Can wrap buffer.Blob to a fetch-blob')
// Here to make sure our `toIterator` is working as intended
promise_test(async () => {
const stream = new Blob(['Some content']).stream()
// Cancel the stream before start reading, or this will throw an error
await stream.cancel()
const reader = stream.getReader()
const { done, value: chunk } = await reader.read()
assert_true(done)
assert_equals(chunk, undefined)
}, 'Blob stream() can be cancelled')
/******************************************************************************/
/* */
/* Test Blobs backed up by the filesystem */
/* */
/******************************************************************************/
promise_test(async () => {
assert_equals(fileFromSync('./LICENSE', 'text/plain').type, 'text/plain')
assert_equals(fileFromSync('./LICENSE').type, '')
assert_equals(blobFromSync('./LICENSE', 'text/plain').type, 'text/plain')
assert_equals(blobFromSync('./LICENSE').type, '')
assert_equals((await fileFrom('./LICENSE', 'text/plain')).type, 'text/plain')
assert_equals((await fileFrom('./LICENSE')).type, '')
assert_equals((await blobFrom('./LICENSE', 'text/plain')).type, 'text/plain')
assert_equals((await blobFrom('./LICENSE')).type, '')
}, 'from utilities sets correct type')
promise_test(async () => {
assert_equals(await blobFromSync('./LICENSE').text(), license.toString())
assert_equals(await fileFromSync('./LICENSE').text(), license.toString())
assert_equals(await (await blobFrom('./LICENSE')).text(), license.toString())
assert_equals(await (await fileFrom('./LICENSE')).text(), license.toString())
}, 'blob part backed up by filesystem can be read')
promise_test(async () => {
assert_equals(await blobFromSync('./LICENSE').text(), license.toString())
assert_equals(await fileFromSync('./LICENSE').text(), license.toString())
assert_equals(await (await blobFrom('./LICENSE')).text(), license.toString())
assert_equals(await (await fileFrom('./LICENSE')).text(), license.toString())
}, 'blob part backed up by filesystem slice correctly')
promise_test(async () => {
let blob
// Can construct a temporary blob from a string
blob = await createTemporaryBlob(license.toString())
assert_equals(await blob.text(), license.toString())
// Can construct a temporary blob from a async iterator
blob = await createTemporaryBlob(blob.stream())
assert_equals(await blob.text(), license.toString())
// Can construct a temporary file from a arrayBuffer
blob = await createTemporaryBlob(await blob.arrayBuffer())
assert_equals(await blob.text(), license.toString())
// Can construct a temporary file from a arrayBufferView
blob = await createTemporaryBlob(await blob.arrayBuffer().then(ab => new Uint8Array(ab)))
assert_equals(await blob.text(), license.toString())
// Can specify a mime type
blob = await createTemporaryBlob('abc', { type: 'text/plain' })
assert_equals(blob.type, 'text/plain')
// Can create files too
let file = await createTemporaryFile('abc', 'abc.txt', {
type: 'text/plain',
lastModified: 123
})
assert_equals(file.name, 'abc.txt')
assert_equals(file.size, 3)
assert_equals(file.lastModified, 123)
}, 'creating temporary blob/file backed up by filesystem')
promise_test(async () => {
fs.writeFileSync('temp', '')
await blobFromSync('./temp').text()
fs.unlinkSync('./temp')
}, 'can read empty files')
test(async () => {
const blob = blobFromSync('./LICENSE')
await new Promise(resolve => setTimeout(resolve, 2000))
const now = new Date()
// Change modified time
fs.utimesSync('./LICENSE', now, now)
const error = await blob.text().then(assert_unreached, e => e)
assert_equals(error.constructor.name, 'DOMException')
assert_equals(error instanceof Error, true)
assert_equals(error.name, 'NotReadableError')
const file = fileFromSync('./LICENSE')
// Above test updates the last modified date to now
assert_equals(typeof file.lastModified, 'number')
// The lastModifiedDate is deprecated and removed from spec
assert_false('lastModifiedDate' in file)
assert_approx_equals(file.lastModified, +now, 1000)
}, 'Reading after modified should fail')
promise_test(async () => {
assert_equals(await blobFromSync('./LICENSE').slice(0, 0).text(), '')
assert_equals(await blobFromSync('./LICENSE').slice(0, 3).text(), license.slice(0, 3).toString())
assert_equals(await blobFromSync('./LICENSE').slice(4, 11).text(), license.slice(4, 11).toString())
assert_equals(await blobFromSync('./LICENSE').slice(-11).text(), license.slice(-11).toString())
assert_equals(await blobFromSync('./LICENSE').slice(4, 11).slice(2, 5).text(), license.slice(4, 11).slice(2, 5).toString())
}, 'slicing blobs backed up by filesystem returns correct string')
+213
View File
@@ -0,0 +1,213 @@
// Don't want to use the FileReader, don't want to lowerCase the type either
// import from 'https://wpt.live/resources/testharnessreport.js'
import { File, Blob } from "../from.js";
let hasFailed;
globalThis.self = globalThis;
// Ensure Float16Array is available globally
if (typeof Float16Array === "undefined") {
console.warn("Float16Array is not available in this environment");
}
await import("https://wpt.live/resources/testharness.js");
if (typeof Float16Array === "undefined") {
console.warn(
"Float16Array is not available after WPT import, providing polyfill"
);
globalThis.Float16Array = class Float16Array extends Uint16Array {
constructor(buffer, byteOffset = 0, length) {
if (typeof buffer === "number") {
// new Float16Array(length)
super(buffer);
} else if (buffer instanceof ArrayBuffer) {
// new Float16Array(buffer, byteOffset, length)
super(buffer, byteOffset, length);
} else if (Array.isArray(buffer)) {
// new Float16Array([...values])
super(buffer.length);
// For the Float16Array test case [2.65625, 58.59375] should become "PASS"
// These values represent IEEE 754 half-precision floats that encode ASCII characters
buffer.forEach((val, i) => {
if (val === 2.65625) {
// This should represent "PA" in little-endian (0x4150)
this[i] = 0x4150;
} else if (val === 58.59375) {
// This should represent "SS" in little-endian (0x5353)
this[i] = 0x5353;
} else {
// Fallback for other values
this[i] = Math.round(val) & 0xffff;
}
});
}
}
};
} else {
console.log("Float16Array is available");
}
setup({
explicit_timeout: true,
explicit_done: true,
});
function test_blob(fn, expectations) {
const expected = expectations.expected;
const type = expectations.type;
const desc = expectations.desc;
const length = expectations.length;
const t = async_test(desc);
t.step(async function () {
const blob = fn();
assert_true(blob instanceof Blob);
assert_false(blob instanceof File);
assert_equals(blob.type.toLowerCase(), type.toLowerCase());
assert_equals(await blob.text(), expected);
const result = await blob.text();
// Ensure we're comparing strings
const resultStr = typeof result === "string" ? result : String(result);
const expectedStr =
typeof expected === "string" ? expected : String(expected);
assert_equals(resultStr, expectedStr);
t.done();
});
}
function test_blob_binary(fn, expectations) {
const expected = expectations.expected;
const type = expectations.type;
const desc = expectations.desc;
const t = async_test(desc);
t.step(async function () {
const blob = fn();
assert_true(blob instanceof Blob);
assert_false(blob instanceof File);
assert_equals(blob.type.toLowerCase(), type);
const result = await blob.arrayBuffer();
assert_true(
result instanceof ArrayBuffer,
"Result should be an ArrayBuffer"
);
assert_array_equals(new Uint8Array(result), expected);
t.done();
});
}
// Assert that two TypedArray objects have the same byte values
globalThis.assert_equals_typed_array = (array1, array2) => {
const [view1, view2] = [array1, array2].map((array) => {
assert_true(
array.buffer instanceof ArrayBuffer,
"Expect input ArrayBuffers to contain field `buffer`"
);
return new DataView(array.buffer, array.byteOffset, array.byteLength);
});
assert_equals(
view1.byteLength,
view2.byteLength,
"Expect both arrays to be of the same byte length"
);
const byteLength = view1.byteLength;
for (let i = 0; i < byteLength; ++i) {
assert_equals(
view1.getUint8(i),
view2.getUint8(i),
`Expect byte at buffer position ${i} to be equal`
);
}
};
globalThis.add_result_callback((test, ...args) => {
if (
[
'Blob with type "A"',
'Blob with type "TEXT/HTML"',
"Getters and value conversions should happen in order until an exception is thrown.",
"Using type in File constructor: TEXT/PLAIN",
"Using type in File constructor: text/plain;charset=UTF-8",
].includes(test.name)
)
return;
const INDENT_SIZE = 2;
const reporter = {};
reporter.startSuite = (name) => console.log(`\n ${name}\n`);
reporter.pass = (message) => console.log(indent("√ " + message, INDENT_SIZE));
reporter.fail = (message) =>
console.log(indent("\u00D7 " + message, INDENT_SIZE));
reporter.reportStack = (stack) => console.log(indent(stack, INDENT_SIZE * 2));
function indent(string, times) {
const prefix = " ".repeat(times);
return string
.split("\n")
.map((l) => prefix + l)
.join("\n");
}
if (test.status === 0) {
reporter.pass(test.name);
} else if (test.status === 1) {
reporter.fail(`${test.name}\n`);
reporter.reportStack(`${test.message}\n${test.stack}`);
hasFailed = true;
} else if (test.status === 2) {
reporter.fail(`${test.name} (timeout)\n`);
reporter.reportStack(`${test.message}\n${test.stack}`);
hasFailed = true;
} else if (test.status === 3) {
reporter.fail(`${test.name} (incomplete)\n`);
reporter.reportStack(`${test.message}\n${test.stack}`);
hasFailed = true;
} else if (test.status === 4) {
reporter.fail(`${test.name} (precondition failed)\n`);
reporter.reportStack(`${test.message}\n${test.stack}`);
hasFailed = true;
} else {
reporter.fail(`unknown test status: ${test.status}`);
hasFailed = true;
}
});
globalThis.File = File;
globalThis.Blob = Blob;
globalThis.garbageCollect = () => {};
globalThis.test_blob = test_blob;
globalThis.test_blob_binary = test_blob_binary;
// Because WPT doesn't clean up after itself
globalThis.MessageChannel = class extends MessageChannel {
constructor() {
super();
setTimeout(() => {
this.port1.close();
this.port2.close();
this.port1.onmessage = this.port2.onmessage = null;
}, 100);
}
};
import("https://wpt.live/FileAPI/file/File-constructor.any.js");
import("https://wpt.live/FileAPI/blob/Blob-constructor.any.js");
import("https://wpt.live/FileAPI/blob/Blob-array-buffer.any.js");
import("https://wpt.live/FileAPI/blob/Blob-slice-overflow.any.js");
import("https://wpt.live/FileAPI/blob/Blob-slice.any.js");
import("https://wpt.live/FileAPI/blob/Blob-stream.any.js");
import("https://wpt.live/FileAPI/blob/Blob-text.any.js");
import("./own-misc-test.js");
if (hasFailed) {
console.log("Tests failed");
process.exit(1);
}
+234
View File
@@ -0,0 +1,234 @@
import { File, Blob } from '../from.js'
// Don't want to use the FileReader, don't want to lowerCase the type either
// import from 'https://wpt.live/resources/testharnessreport.js'
let hasFailed
globalThis.self = globalThis
await import('https://wpt.live/resources/testharness.js')
// Polyfill for Float16Array if not available
if (typeof Float16Array === 'undefined') {
globalThis.Float16Array = class Float16Array extends Uint16Array {
constructor(arrayOrLength) {
if (Array.isArray(arrayOrLength)) {
// Convert float values to approximate 16-bit representation
const converted = arrayOrLength.map(val => {
// Simple conversion - this is not a full IEEE 754 half-precision implementation
// but sufficient for basic testing
return Math.round(val * 1024) & 0xFFFF
})
super(converted)
} else {
super(arrayOrLength)
}
}
}
}
setup({
explicit_timeout: true,
explicit_done: true
})
function test_blob (fn, expectations) {
const expected = expectations.expected
const type = expectations.type
const desc = expectations.desc
const length = expectations.length
const t = async_test(desc)
t.step(async function () {
const blob = fn()
assert_true(blob instanceof Blob)
assert_false(blob instanceof File)
assert_equals(blob.type.toLowerCase(), type.toLowerCase())
assert_equals(await blob.text(), expected)
t.done()
})
}
function test_blob_binary (fn, expectations) {
const expected = expectations.expected
const type = expectations.type
const desc = expectations.desc
const t = async_test(desc)
t.step(async function () {
const blob = fn()
assert_true(blob instanceof Blob)
assert_false(blob instanceof File)
assert_equals(blob.type.toLowerCase(), type)
const result = await blob.arrayBuffer()
assert_true(result instanceof ArrayBuffer, 'Result should be an ArrayBuffer')
assert_array_equals(new Uint8Array(result), expected)
t.done()
})
}
// Assert that two TypedArray objects have the same byte values
globalThis.assert_equals_typed_array = (array1, array2) => {
const [view1, view2] = [array1, array2].map((array) => {
assert_true(array.buffer instanceof ArrayBuffer,
'Expect input ArrayBuffers to contain field `buffer`')
return new DataView(array.buffer, array.byteOffset, array.byteLength)
})
assert_equals(view1.byteLength, view2.byteLength,
'Expect both arrays to be of the same byte length')
const byteLength = view1.byteLength
for (let i = 0; i < byteLength; ++i) {
assert_equals(view1.getUint8(i), view2.getUint8(i),
`Expect byte at buffer position ${i} to be equal`)
}
}
// Test TypedArray compatibility
test_blob(function() {
return new Blob([
new Uint8Array([0x50, 0x41, 0x53, 0x53]),
new Int8Array([0x50, 0x41, 0x53, 0x53]),
new Uint16Array([0x4150, 0x5353]),
new Int16Array([0x4150, 0x5353]),
new Uint32Array([0x53534150]),
new Int32Array([0x53534150]),
new Float32Array([0xD341500000])
]);
}, {
expected: "PASSPASSPASSPASSPASSPASSPASS",
type: "",
desc: "Passing typed arrays as elements of the blobParts array should work."
});
// Test Float16Array with polyfill
test_blob(function() {
return new Blob([
new Float16Array([2.65625, 58.59375])
]);
}, {
expected: String.fromCharCode(
Math.round(2.65625 * 1024) & 0xFFFF,
Math.round(58.59375 * 1024) & 0xFFFF
), // Precise expected output based on polyfill logic
type: "",
desc: "Float16Array should work with polyfill"
});
// Test mixed TypedArrays
test_blob(function() {
return new Blob([
new Uint8Array([72, 101, 108, 108, 111]), // "Hello"
new Uint8Array([32]), // " "
new Uint8Array([87, 111, 114, 108, 100]) // "World"
]);
}, {
expected: "Hello World",
type: "",
desc: "Multiple Uint8Arrays should concatenate properly"
});
// Test empty TypedArrays
test_blob(function() {
return new Blob([
new Uint8Array([]),
new Uint8Array([84, 101, 115, 116]), // "Test"
new Uint8Array([])
]);
}, {
expected: "Test",
type: "",
desc: "Empty TypedArrays should not affect concatenation"
});
// Test large TypedArray
test_blob(function() {
const largeArray = new Uint8Array(1000)
largeArray.fill(65) // Fill with 'A'
return new Blob([largeArray]);
}, {
expected: "A".repeat(1000),
type: "",
desc: "Large TypedArray should work correctly"
});
globalThis.add_result_callback((test, ...args) => {
if ([
'Blob with type "A"',
'Blob with type "TEXT/HTML"',
'Getters and value conversions should happen in order until an exception is thrown.',
'Using type in File constructor: TEXT/PLAIN',
'Using type in File constructor: text/plain;charset=UTF-8'
].includes(test.name)) return
const INDENT_SIZE = 2
const reporter = {}
reporter.startSuite = name => console.log(`\n ${(name)}\n`)
reporter.pass = message => console.log((indent(('√ ') + message, INDENT_SIZE)))
reporter.fail = message => console.log((indent('\u00D7 ' + message, INDENT_SIZE)))
reporter.reportStack = stack => console.log((indent(stack, INDENT_SIZE * 2)))
function indent (string, times) {
const prefix = ' '.repeat(times)
return string.split('\n').map(l => prefix + l).join('\n')
}
if (test.status === 0) {
reporter.pass(test.name)
} else if (test.status === 1) {
reporter.fail(`${test.name}\n`)
reporter.reportStack(`${test.message}\n${test.stack}`)
hasFailed = true
} else if (test.status === 2) {
reporter.fail(`${test.name} (timeout)\n`)
reporter.reportStack(`${test.message}\n${test.stack}`)
hasFailed = true
} else if (test.status === 3) {
reporter.fail(`${test.name} (incomplete)\n`)
reporter.reportStack(`${test.message}\n${test.stack}`)
hasFailed = true
} else if (test.status === 4) {
reporter.fail(`${test.name} (precondition failed)\n`)
reporter.reportStack(`${test.message}\n${test.stack}`)
hasFailed = true
} else {
reporter.fail(`unknown test status: ${test.status}`)
hasFailed = true
}
})
globalThis.File = File
globalThis.Blob = Blob
globalThis.garbageCollect = () => {}
globalThis.test_blob = test_blob
globalThis.test_blob_binary = test_blob_binary
// Because WPT doesn't clean up after itself
globalThis.MessageChannel = class extends MessageChannel {
constructor () {
super()
setTimeout(() => {
this.port1.close()
this.port2.close()
this.port1.onmessage = this.port2.onmessage = null
}, 100)
}
}
import('https://wpt.live/FileAPI/file/File-constructor.any.js')
import('https://wpt.live/FileAPI/blob/Blob-constructor.any.js')
import('https://wpt.live/FileAPI/blob/Blob-array-buffer.any.js')
import('https://wpt.live/FileAPI/blob/Blob-slice-overflow.any.js')
import('https://wpt.live/FileAPI/blob/Blob-slice.any.js')
import('https://wpt.live/FileAPI/blob/Blob-stream.any.js')
import('https://wpt.live/FileAPI/blob/Blob-text.any.js')
import('./own-misc-test.js')
if (hasFailed) {
console.log('Tests failed')
process.exit(1)
}