From b61e4f038440d491368ef80c89c0e26b4568ca82 Mon Sep 17 00:00:00 2001 From: Logan McAnsh Date: Thu, 20 Apr 2023 13:33:14 -0400 Subject: [PATCH] chore: format with prettier defaults Signed-off-by: Logan McAnsh --- .github/workflows/release.yml | 2 +- .gitignore | 2 + package.json | 14 +- packages/blob/CHANGELOG.md | 18 +- packages/blob/Readme.md | 6 +- packages/blob/package.json | 10 +- packages/blob/rollup.config.js | 6 +- packages/blob/src/blob.js | 164 +- packages/blob/src/blob.node.js | 16 +- packages/blob/src/lib.js | 4 +- packages/blob/src/lib.node.js | 8 +- packages/blob/src/package.js | 4 +- packages/blob/test/all.spec.js | 16 +- packages/blob/test/blob.spec.js | 252 +- packages/blob/test/fetch.spec.js | 16 +- packages/blob/test/slice.spec.js | 192 +- packages/blob/test/test.js | 15 +- packages/blob/test/web.spec.js | 12 +- packages/blob/tsconfig.json | 9 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 12 +- .../fetch/.github/ISSUE_TEMPLATE/config.yml | 2 +- .../.github/ISSUE_TEMPLATE/feature-request.md | 2 +- .../ISSUE_TEMPLATE/support-or-usage.md | 5 +- packages/fetch/CODE_OF_CONDUCT.md | 26 +- packages/fetch/LICENSE.md | 1 - packages/fetch/Readme.md | 12 +- packages/fetch/docs/CHANGELOG.md | 6 +- packages/fetch/docs/ERROR-HANDLING.md | 14 +- packages/fetch/docs/v2-LIMITS.md | 10 +- packages/fetch/docs/v2-UPGRADE-GUIDE.md | 2 +- packages/fetch/docs/v3-LIMITS.md | 9 +- packages/fetch/docs/v3-UPGRADE-GUIDE.md | 40 +- packages/fetch/example.js | 19 +- packages/fetch/rollup.config.js | 16 +- packages/fetch/src/body.js | 198 +- packages/fetch/src/errors/abort-error.js | 6 +- packages/fetch/src/errors/base.js | 7 +- packages/fetch/src/errors/fetch-error.js | 5 +- packages/fetch/src/fetch.js | 240 +- packages/fetch/src/headers.js | 158 +- packages/fetch/src/lib.js | 7 +- packages/fetch/src/lib.node.js | 4 +- packages/fetch/src/package.js | 7 +- packages/fetch/src/package.ts | 6 +- packages/fetch/src/request.js | 176 +- packages/fetch/src/response.js | 53 +- packages/fetch/src/utils/form-data.js | 74 +- packages/fetch/src/utils/get-search.js | 9 +- packages/fetch/src/utils/is-redirect.js | 2 +- packages/fetch/src/utils/utf8.js | 6 +- packages/fetch/test/commonjs/package.json | 2 +- packages/fetch/test/commonjs/test-artifact.js | 28 +- packages/fetch/test/external-encoding.js | 48 +- packages/fetch/test/file.js | 23 +- packages/fetch/test/form-data.js | 114 +- packages/fetch/test/headers.js | 308 ++- packages/fetch/test/main.js | 2443 +++++++++-------- packages/fetch/test/request.js | 338 +-- packages/fetch/test/response.js | 214 +- packages/fetch/test/utils/chai-timeout.js | 10 +- packages/fetch/test/utils/server.js | 365 +-- packages/fetch/tsconfig.json | 20 +- packages/file/CHANGELOG.md | 14 +- packages/file/Readme.md | 6 +- packages/file/package.json | 10 +- packages/file/rollup.config.js | 6 +- packages/file/src/file.js | 22 +- packages/file/src/lib.js | 4 +- packages/file/src/lib.node.js | 10 +- packages/file/test/all.spec.js | 12 +- packages/file/test/fetch.spec.js | 2 +- packages/file/test/file.spec.js | 10 +- packages/file/test/test.js | 15 +- packages/file/test/web.spec.js | 8 +- packages/file/tsconfig.json | 5 +- packages/form-data/package.json | 10 +- packages/form-data/rollup.config.js | 6 +- packages/form-data/src/lib.js | 2 +- packages/form-data/src/lib.node.js | 6 +- packages/form-data/test/all.spec.js | 12 +- packages/form-data/test/fetch.spec.js | 2 +- packages/form-data/test/form-data.spec.js | 42 +- packages/form-data/test/test.js | 16 +- packages/form-data/test/web.spec.js | 8 +- packages/form-data/tsconfig.json | 5 +- packages/stream/CHANGELOG.md | 13 +- packages/stream/Readme.md | 2 +- packages/stream/package.json | 10 +- packages/stream/src/lib.js | 2 +- packages/stream/src/lib.node.js | 4 +- packages/stream/test/all.spec.js | 8 +- packages/stream/test/lib.spec.js | 2 +- packages/stream/test/node.spec.cjs | 12 +- packages/stream/test/test.js | 14 +- packages/stream/test/web.spec.js | 8 +- packages/stream/tsconfig.json | 5 +- prettier.config.cjs | 2 + tsconfig.json | 35 +- yarn.lock | 5 + 99 files changed, 3217 insertions(+), 2951 deletions(-) create mode 100644 prettier.config.cjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab9cfb1..82b7458 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 16 - cache: 'yarn' + cache: "yarn" - name: Install run: yarn install --frozen-lockfile - name: Auth diff --git a/.gitignore b/.gitignore index 38b4c00..983ce18 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules dist + +coverage diff --git a/package.json b/package.json index 5c5d544..e6667f0 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,22 @@ "test:file": "yarn --cwd packages/file test", "test:form-data": "yarn --cwd packages/form-data test", "test:fetch": "yarn --cwd packages/fetch test", - "test:stream": "yarn --cwd packages/stream test" + "test:stream": "yarn --cwd packages/stream test", + "precommit": "lint-staged", + "format": "prettier --write --ignore-path .gitignore --ignore-unknown ./" }, "dependencies": { "@changesets/changelog-github": "0.4.4", "@changesets/cli": "^2.22.0", "@manypkg/cli": "0.19.1", - "@manypkg/get-packages": "^1.1.3" + "@manypkg/get-packages": "^1.1.3", + "@types/prettier": "2.7.2", + "prettier": "^2.3.0" + }, + "lint-staged": { + "*.js": [ + "prettier --write", + "git add" + ] } } diff --git a/packages/blob/CHANGELOG.md b/packages/blob/CHANGELOG.md index 245a3c2..81e5ff5 100644 --- a/packages/blob/CHANGELOG.md +++ b/packages/blob/CHANGELOG.md @@ -2,40 +2,34 @@ ### [3.0.4](https://www.github.com/web-std/io/compare/blob-v3.0.3...blob-v3.0.4) (2022-02-24) - ### Changes -* disable node native blob ([#49](https://www.github.com/web-std/io/issues/49)) ([18e426e](https://www.github.com/web-std/io/commit/18e426e0552eb855275faadceab35c41335582f2)) +- disable node native blob ([#49](https://www.github.com/web-std/io/issues/49)) ([18e426e](https://www.github.com/web-std/io/commit/18e426e0552eb855275faadceab35c41335582f2)) ### [3.0.3](https://www.github.com/web-std/io/compare/blob-v3.0.2...blob-v3.0.3) (2022-01-21) - ### Changes -* bump version ([91420e2](https://www.github.com/web-std/io/commit/91420e294b4188a6da9c961ce4ef4eeac93595a1)) +- bump version ([91420e2](https://www.github.com/web-std/io/commit/91420e294b4188a6da9c961ce4ef4eeac93595a1)) ### [3.0.2](https://www.github.com/web-std/io/compare/blob-v3.0.1...blob-v3.0.2) (2022-01-19) - ### Bug Fixes -* ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) +- ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) ### [3.0.1](https://www.github.com/web-std/io/compare/blob-v3.0.0...blob-v3.0.1) (2021-11-08) - ### Changes -* align package versions ([09c8676](https://www.github.com/web-std/io/commit/09c8676348619313d9df24d9597cea0eb82704d2)) +- align package versions ([09c8676](https://www.github.com/web-std/io/commit/09c8676348619313d9df24d9597cea0eb82704d2)) ## 3.0.0 (2021-11-05) - ### Features -* Refactor streams into own subpackage ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) - +- Refactor streams into own subpackage ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) ### Changes -* bump versions ([#27](https://www.github.com/web-std/io/issues/27)) ([0fe5224](https://www.github.com/web-std/io/commit/0fe5224124e318f560dcfbd8a234d05367c9fbcb)) +- bump versions ([#27](https://www.github.com/web-std/io/issues/27)) ([0fe5224](https://www.github.com/web-std/io/commit/0fe5224124e318f560dcfbd8a234d05367c9fbcb)) diff --git a/packages/blob/Readme.md b/packages/blob/Readme.md index 95252dd..6e6bbb4 100644 --- a/packages/blob/Readme.md +++ b/packages/blob/Readme.md @@ -35,10 +35,10 @@ to do with [node-fetch][]. ### Usage ```js -import { Blob } from "@remix-run/web-blob" -const blob = new Blob(["hello", new TextEncoder().encode("world")]) +import { Blob } from "@remix-run/web-blob"; +const blob = new Blob(["hello", new TextEncoder().encode("world")]); for await (const chunk of blob.stream()) { - console.log(chunk) + console.log(chunk); } ``` diff --git a/packages/blob/package.json b/packages/blob/package.json index 15a64ae..bfc679b 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -38,7 +38,6 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "playwright-test": "^7.2.0", - "prettier": "^2.3.0", "rimraf": "3.0.2", "rollup": "2.47.0", "rollup-plugin-multi-input": "1.2.0", @@ -54,13 +53,6 @@ "test:es": "uvu test all.spec.js", "test:web": "playwright-test -r uvu test/web.spec.js", "test:cjs": "rimraf dist && npm run build && node dist/test/all.spec.cjs", - "test": "npm run test:es && npm run test:web && npm run test:cjs", - "precommit": "lint-staged" - }, - "lint-staged": { - "*.js": [ - "prettier --no-semi --write", - "git add" - ] + "test": "npm run test:es && npm run test:web && npm run test:cjs" } } diff --git a/packages/blob/rollup.config.js b/packages/blob/rollup.config.js index 5c8b019..3b44510 100644 --- a/packages/blob/rollup.config.js +++ b/packages/blob/rollup.config.js @@ -1,4 +1,4 @@ -import multiInput from "rollup-plugin-multi-input" +import multiInput from "rollup-plugin-multi-input"; const config = [ ["test", "dist/test"], @@ -13,5 +13,5 @@ const config = [ entryFileNames: "[name].cjs", }, plugins: [multiInput({ relative: base })], -})) -export default config +})); +export default config; diff --git a/packages/blob/src/blob.js b/packages/blob/src/blob.js index aa55ae8..6e31a94 100644 --- a/packages/blob/src/blob.js +++ b/packages/blob/src/blob.js @@ -1,4 +1,4 @@ -import { ReadableStream, TextEncoder, TextDecoder } from "./package.js" +import { ReadableStream, TextEncoder, TextDecoder } from "./package.js"; /** * @implements {globalThis.Blob} @@ -10,48 +10,48 @@ const WebBlob = class Blob { */ constructor(init = [], options = {}) { /** @type {Uint8Array[]} */ - const parts = [] + const parts = []; - let size = 0 + let size = 0; for (const part of init) { if (typeof part === "string") { - const bytes = new TextEncoder().encode(part) - parts.push(bytes) - size += bytes.byteLength + const bytes = new TextEncoder().encode(part); + parts.push(bytes); + size += bytes.byteLength; } else if (part instanceof WebBlob) { - size += part.size + size += part.size; // @ts-ignore - `_parts` is marked private so TS will complain about // accessing it. - parts.push(...part._parts) + parts.push(...part._parts); } else if (part instanceof ArrayBuffer) { - parts.push(new Uint8Array(part)) - size += part.byteLength + parts.push(new Uint8Array(part)); + size += part.byteLength; } else if (part instanceof Uint8Array) { - parts.push(part) - size += part.byteLength + parts.push(part); + size += part.byteLength; } else if (ArrayBuffer.isView(part)) { - const { buffer, byteOffset, byteLength } = part - parts.push(new Uint8Array(buffer, byteOffset, byteLength)) - size += byteLength + const { buffer, byteOffset, byteLength } = part; + parts.push(new Uint8Array(buffer, byteOffset, byteLength)); + size += byteLength; } else { - const bytes = new TextEncoder().encode(String(part)) - parts.push(bytes) - size += bytes.byteLength + const bytes = new TextEncoder().encode(String(part)); + parts.push(bytes); + size += bytes.byteLength; } } /** @private */ - this._size = size + this._size = size; /** @private */ - this._type = readType(options.type) + this._type = readType(options.type); /** @private */ - this._parts = parts + this._parts = parts; Object.defineProperties(this, { _size: { enumerable: false }, _type: { enumerable: false }, _parts: { enumerable: false }, - }) + }); } /** @@ -60,14 +60,14 @@ const WebBlob = class Blob { * @type {string} */ get type() { - return this._type + return this._type; } /** * The size, in bytes, of the data contained in the Blob object. * @type {number} */ get size() { - return this._size + return this._size; } /** @@ -92,42 +92,42 @@ const WebBlob = class Blob { * @returns {Blob} */ slice(start = 0, end = this.size, type = "") { - const { size, _parts } = this - let offset = start < 0 ? Math.max(size + start, 0) : Math.min(start, size) + const { size, _parts } = this; + let offset = start < 0 ? Math.max(size + start, 0) : Math.min(start, size); - let limit = end < 0 ? Math.max(size + end, 0) : Math.min(end, size) - const span = Math.max(limit - offset, 0) - const blob = new Blob([], { type }) + let limit = end < 0 ? Math.max(size + end, 0) : Math.min(end, size); + const span = Math.max(limit - offset, 0); + const blob = new Blob([], { type }); if (span === 0) { - return blob + return blob; } - let blobSize = 0 - const blobParts = [] + let blobSize = 0; + const blobParts = []; for (const part of _parts) { - const { byteLength } = part + const { byteLength } = part; if (offset > 0 && byteLength <= offset) { - offset -= byteLength - limit -= byteLength + offset -= byteLength; + limit -= byteLength; } else { - const chunk = part.subarray(offset, Math.min(byteLength, limit)) - blobParts.push(chunk) - blobSize += chunk.byteLength + const chunk = part.subarray(offset, Math.min(byteLength, limit)); + blobParts.push(chunk); + blobSize += chunk.byteLength; // no longer need to take that into account - offset = 0 + offset = 0; // don't add the overflow to new blobParts if (blobSize >= span) { - break + break; } } } - blob._parts = blobParts - blob._size = blobSize + blob._parts = blobParts; + blob._size = blobSize; - return blob + return blob; } /** @@ -137,14 +137,14 @@ const WebBlob = class Blob { */ // eslint-disable-next-line require-await async arrayBuffer() { - const buffer = new ArrayBuffer(this.size) - const bytes = new Uint8Array(buffer) - let offset = 0 + const buffer = new ArrayBuffer(this.size); + const bytes = new Uint8Array(buffer); + let offset = 0; for (const part of this._parts) { - bytes.set(part, offset) - offset += part.byteLength + bytes.set(part, offset); + offset += part.byteLength; } - return buffer + return buffer; } /** @@ -154,36 +154,36 @@ const WebBlob = class Blob { */ // eslint-disable-next-line require-await async text() { - const decoder = new TextDecoder() - let text = "" + const decoder = new TextDecoder(); + let text = ""; for (const part of this._parts) { - text += decoder.decode(part) + text += decoder.decode(part); } - return text + return text; } /** * @returns {BlobStream} */ stream() { - return new BlobStream(this._parts) + return new BlobStream(this._parts); } /** * @returns {string} */ toString() { - return "[object Blob]" + return "[object Blob]"; } get [Symbol.toStringTag]() { - return "Blob" + return "Blob"; } -} +}; // Marking export as a DOM File object instead of custom class. /** @type {typeof globalThis.Blob} */ -const Blob = WebBlob +const Blob = WebBlob; /** * Blob stream is a `ReadableStream` extension optimized to have minimal @@ -197,9 +197,9 @@ class BlobStream extends ReadableStream { */ constructor(chunks) { // @ts-ignore - super(new BlobStreamController(chunks.values()), { type: "bytes" }) + super(new BlobStreamController(chunks.values()), { type: "bytes" }); /** @private */ - this._chunks = chunks + this._chunks = chunks; } /** @@ -208,9 +208,9 @@ class BlobStream extends ReadableStream { * @returns {AsyncIterator} */ async *[Symbol.asyncIterator](_options) { - const reader = this.getReader() - yield* this._chunks - reader.releaseLock() + const reader = this.getReader(); + yield* this._chunks; + reader.releaseLock(); } } @@ -219,44 +219,44 @@ class BlobStreamController { * @param {Iterator} chunks */ constructor(chunks) { - this.chunks = chunks + this.chunks = chunks; } /** * @param {ReadableStreamDefaultController} controller */ start(controller) { - this.work(controller) - this.isWorking = false - this.isCancelled = false + this.work(controller); + this.isWorking = false; + this.isCancelled = false; } /** * * @param {ReadableStreamDefaultController} controller */ async work(controller) { - const { chunks } = this + const { chunks } = this; - this.isWorking = true + this.isWorking = true; while (!this.isCancelled && (controller.desiredSize || 0) > 0) { - let next = null + let next = null; try { - next = chunks.next() + next = chunks.next(); } catch (error) { - controller.error(error) - break + controller.error(error); + break; } if (next) { if (!next.done && !this.isCancelled) { - controller.enqueue(next.value) + controller.enqueue(next.value); } else { - controller.close() + controller.close(); } } } - this.isWorking = false + this.isWorking = false; } /** @@ -264,11 +264,11 @@ class BlobStreamController { */ pull(controller) { if (!this.isWorking) { - this.work(controller) + this.work(controller); } } cancel() { - this.isCancelled = true + this.isCancelled = true; } } @@ -277,8 +277,8 @@ class BlobStreamController { * @returns {string} */ const readType = (input = "") => { - const type = String(input).toLowerCase() - return /[^\u0020-\u007E]/.test(type) ? "" : type -} + const type = String(input).toLowerCase(); + return /[^\u0020-\u007E]/.test(type) ? "" : type; +}; -export { Blob, ReadableStream, TextEncoder, TextDecoder } +export { Blob, ReadableStream, TextEncoder, TextDecoder }; diff --git a/packages/blob/src/blob.node.js b/packages/blob/src/blob.node.js index c9dd807..b19f5f9 100644 --- a/packages/blob/src/blob.node.js +++ b/packages/blob/src/blob.node.js @@ -1,4 +1,4 @@ -import * as builtin from "buffer" +import * as builtin from "buffer"; /** * @returns {typeof globalThis.Blob|null} @@ -6,15 +6,15 @@ import * as builtin from "buffer" const use = () => { try { // @ts-ignore - const { Blob } = builtin - const view = new Uint16Array(1) + const { Blob } = builtin; + const view = new Uint16Array(1); // Checks if critical issue with node implementation of Blob is fixed // @see https://github.com/nodejs/node/issues/40705 - const isBugFixed = new Blob([view]).size === view.byteLength - return isBugFixed ? Blob : null + const isBugFixed = new Blob([view]).size === view.byteLength; + return isBugFixed ? Blob : null; } catch (error) { - return null + return null; } -} +}; -export const Blob = use() +export const Blob = use(); diff --git a/packages/blob/src/lib.js b/packages/blob/src/lib.js index 355a378..448c0b7 100644 --- a/packages/blob/src/lib.js +++ b/packages/blob/src/lib.js @@ -1,4 +1,4 @@ -export { TextEncoder, TextDecoder, ReadableStream } from "./package.js" +export { TextEncoder, TextDecoder, ReadableStream } from "./package.js"; // On the web we just export native Blob implementation -export const { Blob } = globalThis +export const { Blob } = globalThis; diff --git a/packages/blob/src/lib.node.js b/packages/blob/src/lib.node.js index 0290076..1209cee 100644 --- a/packages/blob/src/lib.node.js +++ b/packages/blob/src/lib.node.js @@ -1,14 +1,14 @@ -export { TextEncoder, TextDecoder, ReadableStream } from "./package.js" +export { TextEncoder, TextDecoder, ReadableStream } from "./package.js"; // import { Blob as NodeBlob } from "./blob.node.js" -import { Blob as WebBlob } from "./blob.js" +import { Blob as WebBlob } from "./blob.js"; /** @type {typeof globalThis.Blob} */ // Our first choise is to use global `Blob` because it may be available e.g. in // electron renderrer process. If not available fall back to node native // implementation, if also not available use our implementation. export const Blob = - globalThis.Blob || + globalThis.Blob || // Disable node native blob until impractical perf issue is fixed // @see https://github.com/nodejs/node/issues/42108 // NodeBlob || - WebBlob + WebBlob; diff --git a/packages/blob/src/package.js b/packages/blob/src/package.js index 0211145..19ae962 100644 --- a/packages/blob/src/package.js +++ b/packages/blob/src/package.js @@ -1,2 +1,2 @@ -export { TextEncoder, TextDecoder } from "web-encoding" -export { ReadableStream } from "@remix-run/web-stream" +export { TextEncoder, TextDecoder } from "web-encoding"; +export { ReadableStream } from "@remix-run/web-stream"; diff --git a/packages/blob/test/all.spec.js b/packages/blob/test/all.spec.js index c1152b8..f55e849 100644 --- a/packages/blob/test/all.spec.js +++ b/packages/blob/test/all.spec.js @@ -1,9 +1,9 @@ -import { test as blobTest } from "./blob.spec.js" -import { test as sliceTest } from "./slice.spec.js" -import { test as fetchTest } from "./fetch.spec.js" -import { test } from "./test.js" +import { test as blobTest } from "./blob.spec.js"; +import { test as sliceTest } from "./slice.spec.js"; +import { test as fetchTest } from "./fetch.spec.js"; +import { test } from "./test.js"; -blobTest(test) -sliceTest(test) -fetchTest(test) -test.run() +blobTest(test); +sliceTest(test); +fetchTest(test); +test.run(); diff --git a/packages/blob/test/blob.spec.js b/packages/blob/test/blob.spec.js index 10ebc06..3bd2b0b 100644 --- a/packages/blob/test/blob.spec.js +++ b/packages/blob/test/blob.spec.js @@ -1,149 +1,149 @@ -import { Blob, TextDecoder } from "@remix-run/web-blob" -import * as lib from "@remix-run/web-blob" -import { assert } from "./test.js" +import { Blob, TextDecoder } from "@remix-run/web-blob"; +import * as lib from "@remix-run/web-blob"; +import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("test baisc", async () => { - assert.isEqual(typeof lib.Blob, "function") - assert.isEqual(typeof lib.TextDecoder, "function") - assert.isEqual(typeof lib.TextEncoder, "function") - assert.isEqual(typeof lib.ReadableStream, "function") - }) + assert.isEqual(typeof lib.Blob, "function"); + assert.isEqual(typeof lib.TextDecoder, "function"); + assert.isEqual(typeof lib.TextEncoder, "function"); + assert.isEqual(typeof lib.ReadableStream, "function"); + }); if (globalThis.window === globalThis) { test("exports built-ins", () => { - assert.equal(lib.Blob, globalThis.Blob) - assert.equal(lib.TextDecoder, globalThis.TextDecoder) - assert.equal(lib.TextEncoder, globalThis.TextEncoder) - assert.equal(lib.ReadableStream, globalThis.ReadableStream) - }) + assert.equal(lib.Blob, globalThis.Blob); + assert.equal(lib.TextDecoder, globalThis.TextDecoder); + assert.equal(lib.TextEncoder, globalThis.TextEncoder); + assert.equal(lib.ReadableStream, globalThis.ReadableStream); + }); } test("test jsdom", async () => { - const blob = new Blob(["TEST"]) - assert.isEqual(blob.size, 4, "Initial blob should have a size of 4") - }) + const blob = new Blob(["TEST"]); + assert.isEqual(blob.size, 4, "Initial blob should have a size of 4"); + }); test("should encode a blob with proper size when given two strings as arguments", async () => { - const blob = new Blob(["hi", "hello"]) - assert.isEqual(blob.size, 7) - }) + const blob = new Blob(["hi", "hello"]); + assert.isEqual(blob.size, 7); + }); test("should encode arraybuffers with right content", async () => { - const bytes = new Uint8Array(5) - for (let i = 0; i < 5; i++) bytes[i] = i - const blob = new Blob([bytes.buffer]) - const buffer = await blob.arrayBuffer() - const result = new Uint8Array(buffer) + const bytes = new Uint8Array(5); + for (let i = 0; i < 5; i++) bytes[i] = i; + const blob = new Blob([bytes.buffer]); + const buffer = await blob.arrayBuffer(); + const result = new Uint8Array(buffer); for (let i = 0; i < 5; i++) { - assert.isEqual(result[i], i) + assert.isEqual(result[i], i); } - }) + }); test("should encode typed arrays with right content", async () => { - const bytes = new Uint8Array(5) - for (let i = 0; i < 5; i++) bytes[i] = i - const blob = new Blob([bytes]) + const bytes = new Uint8Array(5); + for (let i = 0; i < 5; i++) bytes[i] = i; + const blob = new Blob([bytes]); - const buffer = await blob.arrayBuffer() - const result = new Uint8Array(buffer) + const buffer = await blob.arrayBuffer(); + const result = new Uint8Array(buffer); for (let i = 0; i < 5; i++) { - assert.isEqual(result[i], i) + assert.isEqual(result[i], i); } - }) + }); test("should encode sliced typed arrays with right content", async () => { - const bytes = new Uint8Array(5) - for (let i = 0; i < 5; i++) bytes[i] = i - const blob = new Blob([bytes.subarray(2)]) + const bytes = new Uint8Array(5); + for (let i = 0; i < 5; i++) bytes[i] = i; + const blob = new Blob([bytes.subarray(2)]); - const buffer = await blob.arrayBuffer() - const result = new Uint8Array(buffer) + const buffer = await blob.arrayBuffer(); + const result = new Uint8Array(buffer); for (let i = 0; i < 3; i++) { - assert.isEqual(result[i], i + 2) + assert.isEqual(result[i], i + 2); } - }) + }); test("should encode with blobs", async () => { - const bytes = new Uint8Array(5) - for (let i = 0; i < 5; i++) bytes[i] = i - const blob = new Blob([new Blob([bytes.buffer])]) - const buffer = await blob.arrayBuffer() - const result = new Uint8Array(buffer) + const bytes = new Uint8Array(5); + for (let i = 0; i < 5; i++) bytes[i] = i; + const blob = new Blob([new Blob([bytes.buffer])]); + const buffer = await blob.arrayBuffer(); + const result = new Uint8Array(buffer); for (let i = 0; i < 5; i++) { - assert.isEqual(result[i], i) + assert.isEqual(result[i], i); } - }) + }); test("should enode mixed contents to right size", async () => { - const bytes = new Uint8Array(5) + const bytes = new Uint8Array(5); for (let i = 0; i < 5; i++) { - bytes[i] = i + bytes[i] = i; } - const blob = new Blob([bytes.buffer, "hello"]) - assert.isEqual(blob.size, 10) - }) + const blob = new Blob([bytes.buffer, "hello"]); + assert.isEqual(blob.size, 10); + }); test("should accept mime type", async () => { - const blob = new Blob(["hi", "hello"], { type: "text/html" }) - assert.isEqual(blob.type, "text/html") - }) + const blob = new Blob(["hi", "hello"], { type: "text/html" }); + assert.isEqual(blob.type, "text/html"); + }); test("should be an instance of constructor", async () => { - const blob = new Blob(["hi"]) - assert.equal(blob instanceof Blob, true) - }) + const blob = new Blob(["hi"]); + assert.equal(blob instanceof Blob, true); + }); test("from text", async () => { - const blob = new Blob(["hello"]) - assert.isEqual(blob.size, 5, "is right size") - assert.isEqual(blob.type, "", "type is empty") - assert.isEqual(await blob.text(), "hello", "reads as text") + const blob = new Blob(["hello"]); + assert.isEqual(blob.size, 5, "is right size"); + assert.isEqual(blob.type, "", "type is empty"); + assert.isEqual(await blob.text(), "hello", "reads as text"); assert.isEquivalent( new Uint8Array(await blob.arrayBuffer()), - new Uint8Array("hello".split("").map(char => char.charCodeAt(0))) - ) - }) + new Uint8Array("hello".split("").map((char) => char.charCodeAt(0))) + ); + }); test("from text with type", async () => { - const blob = new Blob(["hello"], { type: "text/markdown" }) - assert.isEqual(blob.size, 5, "is right size") - assert.isEqual(blob.type, "text/markdown", "type is set") - assert.isEqual(await blob.text(), "hello", "reads as text") + const blob = new Blob(["hello"], { type: "text/markdown" }); + assert.isEqual(blob.size, 5, "is right size"); + assert.isEqual(blob.type, "text/markdown", "type is set"); + assert.isEqual(await blob.text(), "hello", "reads as text"); assert.isEquivalent( new Uint8Array(await blob.arrayBuffer()), - new Uint8Array("hello".split("").map(char => char.charCodeAt(0))) - ) - }) + new Uint8Array("hello".split("").map((char) => char.charCodeAt(0))) + ); + }); test("empty blob", async () => { - const blob = new Blob([]) - assert.isEqual(blob.size, 0, "size is 0") - assert.isEqual(blob.type, "", "type is empty") - assert.isEqual(await blob.text(), "", "reads as text") + const blob = new Blob([]); + assert.isEqual(blob.size, 0, "size is 0"); + assert.isEqual(blob.type, "", "type is empty"); + assert.isEqual(await blob.text(), "", "reads as text"); assert.isEquivalent( await blob.arrayBuffer(), new ArrayBuffer(0), "returns empty buffer" - ) - }) + ); + }); test("no args", async () => { - const blob = new Blob() - assert.isEqual(blob.size, 0, "size is 0") - assert.isEqual(blob.type, "", "type is empty") - assert.isEqual(await blob.text(), "", "reads as text") + const blob = new Blob(); + assert.isEqual(blob.size, 0, "size is 0"); + assert.isEqual(blob.type, "", "type is empty"); + assert.isEqual(await blob.text(), "", "reads as text"); assert.isEquivalent( await blob.arrayBuffer(), new ArrayBuffer(0), "returns empty buffer" - ) - }) + ); + }); test("all emtpy args", async () => { const blob = new Blob([ @@ -152,76 +152,76 @@ export const test = test => { "", new Uint8Array(0), new ArrayBuffer(0), - ]) - assert.isEqual(blob.size, 0, "size is 0") - assert.isEqual(blob.type, "", "type is empty") - assert.isEqual(await blob.text(), "", "reads as text") + ]); + assert.isEqual(blob.size, 0, "size is 0"); + assert.isEqual(blob.type, "", "type is empty"); + assert.isEqual(await blob.text(), "", "reads as text"); assert.isEquivalent( await blob.arrayBuffer(), new ArrayBuffer(0), "returns empty buffer" - ) - }) + ); + }); test("combined blob", async () => { - const uint8 = new Uint8Array([1, 2, 3]) - const uint16 = new Uint16Array([8, 190]) - const float32 = new Float32Array([5.4, 9, 1.5]) - const string = "hello world" - const blob = new Blob([uint8, uint16, float32, string]) + const uint8 = new Uint8Array([1, 2, 3]); + const uint16 = new Uint16Array([8, 190]); + const float32 = new Float32Array([5.4, 9, 1.5]); + const string = "hello world"; + const blob = new Blob([uint8, uint16, float32, string]); - const b8 = blob.slice(0, uint8.byteLength) - const r8 = new Uint8Array(await b8.arrayBuffer()) - assert.isEquivalent(uint8, r8) + const b8 = blob.slice(0, uint8.byteLength); + const r8 = new Uint8Array(await b8.arrayBuffer()); + assert.isEquivalent(uint8, r8); const b16 = blob.slice( uint8.byteLength, uint8.byteLength + uint16.byteLength - ) - const r16 = new Uint16Array(await b16.arrayBuffer()) - assert.isEquivalent(uint16, r16) + ); + const r16 = new Uint16Array(await b16.arrayBuffer()); + assert.isEquivalent(uint16, r16); const b32 = blob.slice( uint8.byteLength + uint16.byteLength, uint8.byteLength + uint16.byteLength + float32.byteLength - ) - const r32 = new Float32Array(await b32.arrayBuffer()) - assert.isEquivalent(float32, r32) + ); + const r32 = new Float32Array(await b32.arrayBuffer()); + assert.isEquivalent(float32, r32); const bs = blob.slice( uint8.byteLength + uint16.byteLength + float32.byteLength - ) - assert.isEqual(string, await bs.text()) + ); + assert.isEqual(string, await bs.text()); - assert.isEqual("wo", await bs.slice(6, 8).text()) - assert.isEqual("world", await bs.slice(6).text()) - assert.isEqual("world", await blob.slice(-5).text()) - }) + assert.isEqual("wo", await bs.slice(6, 8).text()); + assert.isEqual("world", await bs.slice(6).text()); + assert.isEqual("world", await blob.slice(-5).text()); + }); test("emoji", async () => { - const emojis = `👍🤷🎉😤` - const blob = new Blob([emojis]) - const nestle = new Blob([new Blob([blob, blob])]) - assert.isEqual(emojis + emojis, await nestle.text()) - }) + const emojis = `👍🤷🎉😤`; + const blob = new Blob([emojis]); + const nestle = new Blob([new Blob([blob, blob])]); + assert.isEqual(emojis + emojis, await nestle.text()); + }); test("streams", async () => { - const blob = new Blob(["hello", " ", "world"], { type: "text/plain" }) - const stream = blob.stream() + const blob = new Blob(["hello", " ", "world"], { type: "text/plain" }); + const stream = blob.stream(); - const reader = stream.getReader() - const chunks = [] + const reader = stream.getReader(); + const chunks = []; while (true) { - const { done, value } = await reader.read() + const { done, value } = await reader.read(); if (done) { - break + break; } if (value != null) { - chunks.push(new TextDecoder().decode(value)) + chunks.push(new TextDecoder().decode(value)); } } - assert.deepEqual("hello world", chunks.join("")) - }) -} + assert.deepEqual("hello world", chunks.join("")); + }); +}; diff --git a/packages/blob/test/fetch.spec.js b/packages/blob/test/fetch.spec.js index 08a0e48..9d11f99 100644 --- a/packages/blob/test/fetch.spec.js +++ b/packages/blob/test/fetch.spec.js @@ -1,14 +1,14 @@ -import { Response } from "@remix-run/web-fetch" -import { Blob } from "@remix-run/web-blob" -import { assert } from "./test.js" +import { Response } from "@remix-run/web-fetch"; +import { Blob } from "@remix-run/web-blob"; +import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("nodefetch recognizes blobs", async () => { - const response = new Response(new Blob(["hello"])) + const response = new Response(new Blob(["hello"])); - assert.equal(await response.text(), "hello") - }) -} + assert.equal(await response.text(), "hello"); + }); +}; diff --git a/packages/blob/test/slice.spec.js b/packages/blob/test/slice.spec.js index ae086e7..acf0529 100644 --- a/packages/blob/test/slice.spec.js +++ b/packages/blob/test/slice.spec.js @@ -1,5 +1,5 @@ -import { Blob, TextEncoder } from "@remix-run/web-blob" -import { assert } from "./test.js" +import { Blob, TextEncoder } from "@remix-run/web-blob"; +import { assert } from "./test.js"; /** * @@ -10,27 +10,27 @@ import { assert } from "./test.js" * @param {Uint8Array[]} expected.content */ const assertBlob = async (blob, expected) => { - assert.equal(blob instanceof Blob, true, "blob is instanceof Blob") - assert.equal(String(blob), "[object Blob]", "String(blob) -> [object Blob]") + assert.equal(blob instanceof Blob, true, "blob is instanceof Blob"); + assert.equal(String(blob), "[object Blob]", "String(blob) -> [object Blob]"); assert.equal( blob.toString(), "[object Blob]", "blob.toString() -> [object Blob]" - ) - assert.equal(blob.size, expected.size, `blob.size == ${expected.size}`) - assert.equal(blob.type, expected.type || "", "blob.type") + ); + assert.equal(blob.size, expected.size, `blob.size == ${expected.size}`); + assert.equal(blob.type, expected.type || "", "blob.type"); - const chunks = [] + const chunks = []; // @ts-ignore - https://github.com/microsoft/TypeScript/issues/29867 - const stream = blob.stream() - const reader = stream.getReader() + const stream = blob.stream(); + const reader = stream.getReader(); while (true) { - const chunk = await reader.read() + const chunk = await reader.read(); if (chunk.done) { - reader.releaseLock() - break + reader.releaseLock(); + break; } else { - chunks.push(chunk.value) + chunks.push(chunk.value); } } @@ -38,85 +38,85 @@ const assertBlob = async (blob, expected) => { concatUint8Array(chunks), concatUint8Array(expected.content), "blob.stream() matches expectation" - ) + ); - let text = "" - const encoder = new TextDecoder() + let text = ""; + const encoder = new TextDecoder(); for (const chunk of expected.content) { - text += encoder.decode(chunk) + text += encoder.decode(chunk); } assert.deepEqual( await blob.text(), text, "blob.text() produces expected text" - ) + ); // Not all browsers implement this - const bytes = concatUint8Array(expected.content) - const buffer = await blob.arrayBuffer() - assert.equal(buffer instanceof ArrayBuffer, true) - assert.deepEqual(buffer, bytes.buffer) + const bytes = concatUint8Array(expected.content); + const buffer = await blob.arrayBuffer(); + assert.equal(buffer instanceof ArrayBuffer, true); + assert.deepEqual(buffer, bytes.buffer); assert.deepEqual( new Uint8Array(buffer), bytes, "blob.arrayBuffer() produces expected buffer" - ) -} + ); +}; /** * @param {Uint8Array[]} chunks */ -const concatUint8Array = chunks => { - const bytes = [] +const concatUint8Array = (chunks) => { + const bytes = []; for (const chunk of chunks) { - bytes.push(...chunk) + bytes.push(...chunk); } - return new Uint8Array(bytes) -} + return new Uint8Array(bytes); +}; /** * @param {*} input * @returns {Uint8Array} */ -const toUint8Array = input => { +const toUint8Array = (input) => { if (typeof input === "string") { - return new TextEncoder().encode(input) + return new TextEncoder().encode(input); } else if (input instanceof ArrayBuffer) { - return new Uint8Array(input) + return new Uint8Array(input); } else if (input instanceof Uint8Array) { - return input + return input; } else if (ArrayBuffer.isView(input)) { - return new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + return new Uint8Array(input.buffer, input.byteOffset, input.byteLength); } else { - throw new TypeError(`Invalid input ${input}`) + throw new TypeError(`Invalid input ${input}`); } -} +}; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("new Blob()", async () => { - const blob = new Blob() + const blob = new Blob(); await assertBlob(blob, { type: "", size: 0, content: [], - }) - }) + }); + }); test('new Blob("a=1")', async () => { - const data = "a=1" - const blob = new Blob([data]) + const data = "a=1"; + const blob = new Blob([data]); await assertBlob(blob, { size: 3, type: "", content: [toUint8Array(data)], - }) - }) + }); + }); test("Blob with mixed parts", async () => { const parts = [ @@ -126,138 +126,138 @@ export const test = test => { new Uint8Array([101]).buffer, new TextEncoder().encode("f"), new Blob(["g"]), - ] + ]; await assertBlob(new Blob(parts), { size: 7, content: [...parts.slice(0, -1).map(toUint8Array), toUint8Array("g")], - }) - }) + }); + }); test("Blob slice", async () => { - const parts = ["hello ", "world"] - const blob = new Blob(parts) + const parts = ["hello ", "world"]; + const blob = new Blob(parts); await assertBlob(blob, { size: 11, content: parts.map(toUint8Array), - }) + }); assertBlob(blob.slice(), { size: 11, content: parts.map(toUint8Array), - }) + }); assertBlob(blob.slice(2), { size: 9, content: [toUint8Array("llo "), toUint8Array("world")], - }) + }); assertBlob(blob.slice(5), { size: 6, content: [toUint8Array(" "), toUint8Array("world")], - }) + }); assertBlob(blob.slice(6), { size: 5, content: [toUint8Array("world")], - }) + }); assertBlob(blob.slice(5, 100), { size: 6, content: [toUint8Array(" "), toUint8Array("world")], - }) + }); assertBlob(blob.slice(-5), { size: 5, content: [toUint8Array("world")], - }) + }); assertBlob(blob.slice(-5, -10), { size: 0, content: [], - }) + }); assertBlob(blob.slice(-5, -2), { size: 3, content: [toUint8Array("wor")], - }) + }); assertBlob(blob.slice(-5, 11), { size: 5, content: [toUint8Array("world")], - }) + }); assertBlob(blob.slice(-5, 12), { size: 5, content: [toUint8Array("world")], - }) + }); assertBlob(blob.slice(-5, 10), { size: 4, content: [toUint8Array("worl")], - }) - }) + }); + }); test("Blob type", async () => { - const type = "text/plain" - const blob = new Blob([], { type }) - await assertBlob(blob, { size: 0, type, content: [] }) - }) + const type = "text/plain"; + const blob = new Blob([], { type }); + await assertBlob(blob, { size: 0, type, content: [] }); + }); test("Blob slice type", async () => { - const type = "text/plain" - const blob = new Blob().slice(0, 0, type) - await assertBlob(blob, { size: 0, type, content: [] }) - }) + const type = "text/plain"; + const blob = new Blob().slice(0, 0, type); + await assertBlob(blob, { size: 0, type, content: [] }); + }); test("invalid Blob type", async () => { - const blob = new Blob([], { type: "\u001Ftext/plain" }) - await assertBlob(blob, { size: 0, type: "", content: [] }) - }) + const blob = new Blob([], { type: "\u001Ftext/plain" }); + await assertBlob(blob, { size: 0, type: "", content: [] }); + }); test("invalid Blob slice type", async () => { - const blob = new Blob().slice(0, 0, "\u001Ftext/plain") - await assertBlob(blob, { size: 0, type: "", content: [] }) - }) + const blob = new Blob().slice(0, 0, "\u001Ftext/plain"); + await assertBlob(blob, { size: 0, type: "", content: [] }); + }); test("normalized Blob type", async () => { - const blob = new Blob().slice(0, 0, "text/Plain") - await assertBlob(blob, { size: 0, type: "text/plain", content: [] }) - }) + const blob = new Blob().slice(0, 0, "text/Plain"); + await assertBlob(blob, { size: 0, type: "text/plain", content: [] }); + }); test("Blob slice(0, 1)", async () => { - const data = "abcdefgh" - const blob = new Blob([data]).slice(0, 1) + const data = "abcdefgh"; + const blob = new Blob([data]).slice(0, 1); await assertBlob(blob, { size: 1, content: [toUint8Array("a")], - }) - }) + }); + }); test("Blob slice(-1)", async () => { - const data = "abcdefgh" - const blob = new Blob([data]).slice(-1) + const data = "abcdefgh"; + const blob = new Blob([data]).slice(-1); await assertBlob(blob, { size: 1, content: [toUint8Array("h")], - }) - }) + }); + }); test("Blob slice(0, -1)", async () => { - const data = "abcdefgh" - const blob = new Blob([data]).slice(0, -1) + const data = "abcdefgh"; + const blob = new Blob([data]).slice(0, -1); await assertBlob(blob, { size: 7, content: [toUint8Array("abcdefg")], - }) - }) + }); + }); test("blob.slice(1, 2)", async () => { - const blob = new Blob(["a", "b", "c"]).slice(1, 2) + const blob = new Blob(["a", "b", "c"]).slice(1, 2); await assertBlob(blob, { size: 1, content: [toUint8Array("b")], - }) - }) -} + }); + }); +}; diff --git a/packages/blob/test/test.js b/packages/blob/test/test.js index 69eb734..d05237b 100644 --- a/packages/blob/test/test.js +++ b/packages/blob/test/test.js @@ -1,12 +1,11 @@ -import * as uvu from "uvu" -import * as uvuassert from "uvu/assert" - -const deepEqual = uvuassert.equal -const isEqual = uvuassert.equal -const isEquivalent = uvuassert.equal -export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent } -export const test = uvu.test +import * as uvu from "uvu"; +import * as uvuassert from "uvu/assert"; +const deepEqual = uvuassert.equal; +const isEqual = uvuassert.equal; +const isEquivalent = uvuassert.equal; +export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent }; +export const test = uvu.test; /** * @typedef {uvu.Test} Test diff --git a/packages/blob/test/web.spec.js b/packages/blob/test/web.spec.js index 97d871e..be26e21 100644 --- a/packages/blob/test/web.spec.js +++ b/packages/blob/test/web.spec.js @@ -1,8 +1,8 @@ -import { test as blobTest } from "./blob.spec.js" -import { test as sliceTest } from "./slice.spec.js" -import { test } from "./test.js" +import { test as blobTest } from "./blob.spec.js"; +import { test as sliceTest } from "./slice.spec.js"; +import { test } from "./test.js"; -blobTest(test) -sliceTest(test) +blobTest(test); +sliceTest(test); -test.run() +test.run(); diff --git a/packages/blob/tsconfig.json b/packages/blob/tsconfig.json index 5695f02..238ecbf 100644 --- a/packages/blob/tsconfig.json +++ b/packages/blob/tsconfig.json @@ -6,11 +6,6 @@ "@remix-run/web-blob": ["packages/blob/src/lib.js"] } }, - "include": [ - "src", - "test" - ], - "references": [ - { "path": "../fetch"} - ] + "include": ["src", "test"], + "references": [{ "path": "../fetch" }] } diff --git a/packages/fetch/.github/ISSUE_TEMPLATE/bug_report.md b/packages/fetch/.github/ISSUE_TEMPLATE/bug_report.md index 4f10ab3..3046871 100644 --- a/packages/fetch/.github/ISSUE_TEMPLATE/bug_report.md +++ b/packages/fetch/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,10 +10,10 @@ labels: bug Steps to reproduce the behavior: -1. -2. -3. -4. +1. +2. +3. +4. **Expected behavior** @@ -28,8 +28,8 @@ Steps to reproduce the behavior: -| software | version -| ---------------- | ------- +| software | version | +| ---------------- | ------- | | node-fetch | | node | | npm | diff --git a/packages/fetch/.github/ISSUE_TEMPLATE/config.yml b/packages/fetch/.github/ISSUE_TEMPLATE/config.yml index 17f583a..b02e06c 100644 --- a/packages/fetch/.github/ISSUE_TEMPLATE/config.yml +++ b/packages/fetch/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - - name: Discord Server + - name: Discord Server url: https://discord.gg/Zxbndcm about: You can alternatively ask any questions here. diff --git a/packages/fetch/.github/ISSUE_TEMPLATE/feature-request.md b/packages/fetch/.github/ISSUE_TEMPLATE/feature-request.md index cd179d3..7baf1f0 100644 --- a/packages/fetch/.github/ISSUE_TEMPLATE/feature-request.md +++ b/packages/fetch/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,5 +1,5 @@ --- -name: '✨ Feature Request' +name: "✨ Feature Request" about: Suggest an idea or feature labels: feature --- diff --git a/packages/fetch/.github/ISSUE_TEMPLATE/support-or-usage.md b/packages/fetch/.github/ISSUE_TEMPLATE/support-or-usage.md index bc8389f..a0aa5af 100644 --- a/packages/fetch/.github/ISSUE_TEMPLATE/support-or-usage.md +++ b/packages/fetch/.github/ISSUE_TEMPLATE/support-or-usage.md @@ -18,6 +18,7 @@ Please read and follow the instructions before submitting an issue: ```js + ``` **Expected behavior, if applicable** @@ -28,8 +29,8 @@ A clear and concise description of what you expected to happen. -| software | version -| ---------------- | ------- +| software | version | +| ---------------- | ------- | | node-fetch | | node | | npm | diff --git a/packages/fetch/CODE_OF_CONDUCT.md b/packages/fetch/CODE_OF_CONDUCT.md index 2336057..7ebf3ce 100644 --- a/packages/fetch/CODE_OF_CONDUCT.md +++ b/packages/fetch/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities diff --git a/packages/fetch/LICENSE.md b/packages/fetch/LICENSE.md index 41ca1b6..42d21ba 100644 --- a/packages/fetch/LICENSE.md +++ b/packages/fetch/LICENSE.md @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/packages/fetch/Readme.md b/packages/fetch/Readme.md index 41d13dc..645855d 100644 --- a/packages/fetch/Readme.md +++ b/packages/fetch/Readme.md @@ -13,16 +13,12 @@ The reason this fork exists is because [node-fetch][] chooses to compromise Web API compatibility and by using nodejs native [Readable][] stream. They way they put it is: -> > - Make conscious trade-off when following [WHATWG fetch spec][whatwg-fetch] and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known differences. > - Use native Node streams for body, on both request and response. -> We found these incompatibility to be really problematic when sharing code across nodejs and browser rutimes. This library uses [@remix-run/web-stream][] instead. - - [ci.icon]: https://github.com/web-std/io/workflows/fetch/badge.svg [ci.url]: https://github.com/web-std/io/actions/workflows/fetch.yml [version.icon]: https://img.shields.io/npm/v/@remix-run/web-fetch.svg @@ -37,8 +33,8 @@ across nodejs and browser rutimes. This library uses [@remix-run/web-stream][] i [readablestream]: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream [readable]: https://nodejs.org/api/stream.html#stream_readable_streams [w3c blob.stream]: https://w3c.github.io/FileAPI/#dom-blob-stream -[@remix-run/web-stream]:https://github.com/web-std/io/tree/main/stream -[Uint8Array]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array -[node-fetch]:https://github.com/node-fetch/ -[fetch api]:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API +[@remix-run/web-stream]: https://github.com/web-std/io/tree/main/stream +[uint8array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array +[node-fetch]: https://github.com/node-fetch/ +[fetch api]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API [readable]: https://nodejs.org/api/stream.html#stream_readable_streams diff --git a/packages/fetch/docs/CHANGELOG.md b/packages/fetch/docs/CHANGELOG.md index eb8a8c6..350c4f4 100644 --- a/packages/fetch/docs/CHANGELOG.md +++ b/packages/fetch/docs/CHANGELOG.md @@ -1,5 +1,4 @@ -Changelog -========= +# Changelog # 3.x release @@ -99,7 +98,6 @@ Changelog - Other: dev dependency update. - Other: readme update. - # 2.x release ## v2.6.1 @@ -224,7 +222,6 @@ This is a major release. Check [our upgrade guide](https://github.com/node-fetch - Enhance: more comprehensive API docs - Enhance: add a list of default headers in README - # 1.x release ## Backport releases (v1.7.0 and beyond) @@ -361,7 +358,6 @@ See [changelog on 1.x branch](https://github.com/node-fetch/node-fetch/blob/1.x/ - Enhance: better test coverage and doc - # 0.x release ## v0.1 diff --git a/packages/fetch/docs/ERROR-HANDLING.md b/packages/fetch/docs/ERROR-HANDLING.md index 85b1f0d..3986335 100644 --- a/packages/fetch/docs/ERROR-HANDLING.md +++ b/packages/fetch/docs/ERROR-HANDLING.md @@ -1,6 +1,4 @@ - -Error handling with node-fetch -============================== +# Error handling with node-fetch Because `window.fetch` isn't designed to be transparent about the cause of request errors, we have to come up with our own solutions. @@ -9,20 +7,20 @@ The basics: - A cancelled request is rejected with an [`AbortError`](https://github.com/node-fetch/node-fetch/blob/master/README.md#class-aborterror). You can check if the reason for rejection was that the request was aborted by checking the `Error`'s `name` is `AbortError`. ```js -const fetch = require('node-fetch'); +const fetch = require("node-fetch"); (async () => { try { - await fetch(url, {signal}); + await fetch(url, { signal }); } catch (error) { - if (error.name === 'AbortError') { - console.log('request was aborted'); + if (error.name === "AbortError") { + console.log("request was aborted"); } } })(); ``` -- All [operational errors][joyent-guide] *other than aborted requests* are rejected with a [FetchError](https://github.com/node-fetch/node-fetch/blob/master/README.md#class-fetcherror). You can handle them all through the `try/catch` block or promise `catch` clause. +- All [operational errors][joyent-guide] _other than aborted requests_ are rejected with a [FetchError](https://github.com/node-fetch/node-fetch/blob/master/README.md#class-fetcherror). You can handle them all through the `try/catch` block or promise `catch` clause. - All errors come with an `error.message` detailing the cause of errors. diff --git a/packages/fetch/docs/v2-LIMITS.md b/packages/fetch/docs/v2-LIMITS.md index 849a155..28e88b8 100644 --- a/packages/fetch/docs/v2-LIMITS.md +++ b/packages/fetch/docs/v2-LIMITS.md @@ -1,8 +1,6 @@ +# Known differences -Known differences -================= - -*As of 2.x release* +_As of 2.x release_ - Topics such as Cross-Origin, Content Security Policy, Mixed Content, Service Workers are ignored, given our server-side context. @@ -26,7 +24,7 @@ Known differences - If you are using `res.clone()` and writing an isomorphic app, note that stream on Node.js have a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers). -- Because Node.js stream doesn't expose a [*disturbed*](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly. +- Because Node.js stream doesn't expose a [_disturbed_](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly. [readable-stream]: https://nodejs.org/api/stream.html#stream_readable_streams -[ERROR-HANDLING.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md +[error-handling.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md diff --git a/packages/fetch/docs/v2-UPGRADE-GUIDE.md b/packages/fetch/docs/v2-UPGRADE-GUIDE.md index 3660dfb..6b36f54 100644 --- a/packages/fetch/docs/v2-UPGRADE-GUIDE.md +++ b/packages/fetch/docs/v2-UPGRADE-GUIDE.md @@ -102,7 +102,7 @@ those release branches in 2016. Check out Node.js' official [LTS plan] for more information on Node.js' support lifetime. [whatwg-fetch]: https://fetch.spec.whatwg.org/ -[LTS plan]: https://github.com/nodejs/LTS#lts-plan +[lts plan]: https://github.com/nodejs/LTS#lts-plan [gh-fetch]: https://github.com/github/fetch [chrome-headers]: https://crbug.com/645492 [firefox-headers]: https://bugzilla.mozilla.org/show_bug.cgi?id=1278275 diff --git a/packages/fetch/docs/v3-LIMITS.md b/packages/fetch/docs/v3-LIMITS.md index a53202e..ce52e07 100644 --- a/packages/fetch/docs/v3-LIMITS.md +++ b/packages/fetch/docs/v3-LIMITS.md @@ -1,7 +1,6 @@ -Known differences -================= +# Known differences -*As of 3.x release* +_As of 3.x release_ - Topics such as Cross-Origin, Content Security Policy, Mixed Content, Service Workers are ignored, given our server-side context. @@ -23,8 +22,8 @@ Known differences - If you are using `res.clone()` and writing an isomorphic app, note that stream on Node.js has a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers). Learn [how to get around this][highwatermark-fix]. -- Because Node.js stream doesn't expose a [*disturbed*](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly. +- Because Node.js stream doesn't expose a [_disturbed_](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly. [readable-stream]: https://nodejs.org/api/stream.html#stream_readable_streams -[ERROR-HANDLING.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md +[error-handling.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md [highwatermark-fix]: https://github.com/node-fetch/node-fetch/blob/master/README.md#custom-highwatermark diff --git a/packages/fetch/docs/v3-UPGRADE-GUIDE.md b/packages/fetch/docs/v3-UPGRADE-GUIDE.md index ad9848f..667eecd 100644 --- a/packages/fetch/docs/v3-UPGRADE-GUIDE.md +++ b/packages/fetch/docs/v3-UPGRADE-GUIDE.md @@ -28,20 +28,20 @@ Since Node.js will deprecate version 8 at the end of 2019, we decided that node- Since this was never part of the fetch specification, it was removed. AbortSignal offers a more finegrained control of request timeouts, and is standardized in the Fetch spec. For convenience, you can use [timeout-signal](https://github.com/Richienb/timeout-signal) as a workaround: ```js -const timeoutSignal = require('timeout-signal'); -const fetch = require('node-fetch'); - -const {AbortError} = fetch - -fetch('https://www.google.com', { signal: timeoutSignal(5000) }) - .then(response => { - // Handle response - }) - .catch(error => { - if (error instanceof AbortError) { - // Handle timeout - } - }) +const timeoutSignal = require("timeout-signal"); +const fetch = require("node-fetch"); + +const { AbortError } = fetch; + +fetch("https://www.google.com", { signal: timeoutSignal(5000) }) + .then((response) => { + // Handle response + }) + .catch((error) => { + if (error instanceof AbortError) { + // Handle timeout + } + }); ``` ## `Response.statusText` no longer sets a default message derived from the HTTP status code @@ -57,10 +57,10 @@ Prior to v3.x, we included a `browser` field in the package.json file. Since nod If you want charset encoding detection, please use the [fetch-charset-detection] package ([documentation][fetch-charset-detection-docs]). ```js -const fetch = require('node-fetch'); -const convertBody = require('fetch-charset-detection'); +const fetch = require("node-fetch"); +const convertBody = require("fetch-charset-detection"); -fetch('https://somewebsite.com').then(res => { +fetch("https://somewebsite.com").then((res) => { const text = convertBody(res.buffer(), res.headers); }); ``` @@ -70,9 +70,9 @@ fetch('https://somewebsite.com').then(res => { When attempting to parse invalid json via `res.json()`, a `SyntaxError` will now be thrown instead of a `FetchError` to align better with the spec. ```js -const fetch = require('node-fetch'); +const fetch = require("node-fetch"); -fetch('https://somewebsitereturninginvalidjson.com').then(res => res.json()) +fetch("https://somewebsitereturninginvalidjson.com").then((res) => res.json()); // Throws 'Uncaught SyntaxError: Unexpected end of JSON input' or similar. ``` @@ -122,7 +122,7 @@ Since v3.x you no longer need to install `@types/node-fetch` package in order to [whatwg-fetch]: https://fetch.spec.whatwg.org/ [data-url]: https://fetch.spec.whatwg.org/#data-url-processor -[LTS plan]: https://github.com/nodejs/LTS#lts-plan +[lts plan]: https://github.com/nodejs/LTS#lts-plan [cross-fetch]: https://github.com/lquixada/cross-fetch [fetch-charset-detection]: https://github.com/Richienb/fetch-charset-detection [fetch-charset-detection-docs]: https://richienb.github.io/fetch-charset-detection/globals.html#convertbody diff --git a/packages/fetch/example.js b/packages/fetch/example.js index 31c4f44..86daf66 100644 --- a/packages/fetch/example.js +++ b/packages/fetch/example.js @@ -1,8 +1,8 @@ -const fetch = require('node-fetch'); +const fetch = require("node-fetch"); // Plain text or HTML (async () => { - const response = await fetch('https://github.com/'); + const response = await fetch("https://github.com/"); const body = await response.text(); console.log(body); @@ -10,7 +10,7 @@ const fetch = require('node-fetch'); // JSON (async () => { - const response = await fetch('https://github.com/'); + const response = await fetch("https://github.com/"); const json = await response.json(); console.log(json); @@ -18,7 +18,10 @@ const fetch = require('node-fetch'); // Simple Post (async () => { - const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'}); + const response = await fetch("https://httpbin.org/post", { + method: "POST", + body: "a=1", + }); const json = await response.json(); console.log(json); @@ -26,12 +29,12 @@ const fetch = require('node-fetch'); // Post with JSON (async () => { - const body = {a: 1}; + const body = { a: 1 }; - const response = await fetch('https://httpbin.org/post', { - method: 'post', + const response = await fetch("https://httpbin.org/post", { + method: "post", body: JSON.stringify(body), - headers: {'Content-Type': 'application/json'} + headers: { "Content-Type": "application/json" }, }); const json = await response.json(); diff --git a/packages/fetch/rollup.config.js b/packages/fetch/rollup.config.js index e079c7a..75ea045 100644 --- a/packages/fetch/rollup.config.js +++ b/packages/fetch/rollup.config.js @@ -1,18 +1,18 @@ -import {builtinModules} from 'module'; -import {dependencies} from './package.json'; +import { builtinModules } from "module"; +import { dependencies } from "./package.json"; export default { - input: 'src/lib.node.js', + input: "src/lib.node.js", output: { - file: 'dist/lib.node.cjs', - format: 'cjs', + file: "dist/lib.node.cjs", + format: "cjs", esModule: false, interop: false, sourcemap: true, preferConst: true, - exports: 'named', + exports: "named", // https://github.com/rollup/rollup/issues/1961#issuecomment-534977678 - outro: 'exports = module.exports = Object.assign(fetch, exports);' + outro: "exports = module.exports = Object.assign(fetch, exports);", }, - external: [...builtinModules, ...Object.keys(dependencies)] + external: [...builtinModules, ...Object.keys(dependencies)], }; diff --git a/packages/fetch/src/body.js b/packages/fetch/src/body.js index 2b7a373..18e4162 100644 --- a/packages/fetch/src/body.js +++ b/packages/fetch/src/body.js @@ -5,19 +5,30 @@ * Body interface provides common methods for Request and Response */ -import Stream from 'stream'; -import {types} from 'util'; - -import {Blob, ReadableStream} from './package.js'; - -import {FetchError} from './errors/fetch-error.js'; -import {FetchBaseError} from './errors/base.js'; -import {formDataIterator, getBoundary, getFormDataLength, toFormData} from './utils/form-data.js'; -import {isBlob, isURLSearchParameters, isFormData, isMultipartFormDataStream, isReadableStream} from './utils/is.js'; -import * as utf8 from './utils/utf8.js'; -const {readableHighWaterMark} = new Stream.Readable(); - -const INTERNALS = Symbol('Body internals'); +import Stream from "stream"; +import { types } from "util"; + +import { Blob, ReadableStream } from "./package.js"; + +import { FetchError } from "./errors/fetch-error.js"; +import { FetchBaseError } from "./errors/base.js"; +import { + formDataIterator, + getBoundary, + getFormDataLength, + toFormData, +} from "./utils/form-data.js"; +import { + isBlob, + isURLSearchParameters, + isFormData, + isMultipartFormDataStream, + isReadableStream, +} from "./utils/is.js"; +import * as utf8 from "./utils/utf8.js"; +const { readableHighWaterMark } = new Stream.Readable(); + +const INTERNALS = Symbol("Body internals"); /** * Body mixin @@ -31,9 +42,7 @@ export default class Body { * @param {BodyInit|Stream|null} body * @param {{size?:number}} options */ - constructor(body, { - size = 0 - } = {}) { + constructor(body, { size = 0 } = {}) { const state = { /** @type {null|ReadableStream} */ body: null, @@ -45,7 +54,7 @@ export default class Body { boundary: null, disturbed: false, /** @type {null|Error} */ - error: null + error: null, }; /** @private */ this[INTERNALS] = state; @@ -55,11 +64,11 @@ export default class Body { state.body = null; state.size = 0; } else if (isURLSearchParameters(body)) { - // Body is a URLSearchParams + // Body is a URLSearchParams const bytes = utf8.encode(body.toString()); state.body = fromBytes(bytes); state.size = bytes.byteLength; - state.type = 'application/x-www-form-urlencoded;charset=UTF-8'; + state.type = "application/x-www-form-urlencoded;charset=UTF-8"; } else if (isBlob(body)) { // Body is blob state.size = body.size; @@ -76,7 +85,11 @@ export default class Body { state.size = bytes.byteLength; } else if (ArrayBuffer.isView(body)) { // Body is ArrayBufferView - const bytes = new Uint8Array(body.buffer, body.byteOffset, body.byteLength); + const bytes = new Uint8Array( + body.buffer, + body.byteOffset, + body.byteLength + ); state.body = fromBytes(bytes); state.size = bytes.byteLength; } else if (isReadableStream(body)) { @@ -98,7 +111,7 @@ export default class Body { // None of the above // coerce to string then buffer const bytes = utf8.encode(String(body)); - state.type = 'text/plain;charset=UTF-8'; + state.type = "text/plain;charset=UTF-8"; state.size = bytes.byteLength; state.body = fromBytes(bytes); } @@ -118,7 +131,9 @@ export default class Body { /** @type {Headers} */ /* c8 ignore next 3 */ get headers() { - throw new TypeError(`'get headers' called on an object that does not implements interface.`) + throw new TypeError( + `'get headers' called on an object that does not implements interface.` + ); } get body() { @@ -135,7 +150,7 @@ export default class Body { * @return {Promise} */ async arrayBuffer() { - const {buffer, byteOffset, byteLength} = await consumeBody(this); + const { buffer, byteOffset, byteLength } = await consumeBody(this); return buffer.slice(byteOffset, byteOffset + byteLength); } @@ -145,11 +160,14 @@ export default class Body { * @return Promise */ async blob() { - const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].type) || ''; + const ct = + (this.headers && this.headers.get("content-type")) || + (this[INTERNALS].body && this[INTERNALS].type) || + ""; const buf = await consumeBody(this); return new Blob([buf], { - type: ct + type: ct, }); } @@ -176,19 +194,19 @@ export default class Body { * @returns {Promise} */ async formData() { - return toFormData(this) + return toFormData(this); } } // In browsers, all properties are enumerable. Object.defineProperties(Body.prototype, { - body: {enumerable: true}, - bodyUsed: {enumerable: true}, - arrayBuffer: {enumerable: true}, - blob: {enumerable: true}, - json: {enumerable: true}, - text: {enumerable: true}, - formData: {enumerable: true} + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true }, + formData: { enumerable: true }, }); /** @@ -211,7 +229,7 @@ async function consumeBody(data) { throw state.error; } - const {body} = state; + const { body } = state; // Body is null if (body === null) { @@ -221,20 +239,22 @@ async function consumeBody(data) { // Body is stream // get ready to actually consume the body /** @type {[Uint8Array|null, Uint8Array[], number]} */ - const [buffer, chunks, limit] = data.size > 0 ? - [new Uint8Array(data.size), [], data.size] : - [null, [], Infinity]; + const [buffer, chunks, limit] = + data.size > 0 + ? [new Uint8Array(data.size), [], data.size] + : [null, [], Infinity]; let offset = 0; const source = streamIterator(body); try { for await (const chunk of source) { - const bytes = chunk instanceof Uint8Array ? - chunk : - Buffer.from(chunk); + const bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk); if (offset + bytes.byteLength > limit) { - const error = new FetchError(`content size at ${data.url} over limit: ${limit}`, 'max-size'); + const error = new FetchError( + `content size at ${data.url} over limit: ${limit}`, + "max-size" + ); source.throw(error); throw error; } else if (buffer) { @@ -248,7 +268,10 @@ async function consumeBody(data) { if (buffer) { if (offset < buffer.byteLength) { - throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`, 'premature-close'); + throw new FetchError( + `Premature close of server response while trying to fetch ${data.url}`, + "premature-close" + ); } else { return buffer; } @@ -258,13 +281,19 @@ async function consumeBody(data) { } catch (error) { if (error instanceof FetchBaseError) { throw error; - // @ts-expect-error - we know it will have a name - } else if (error && error.name === 'AbortError') { + // @ts-expect-error - we know it will have a name + } else if (error && error.name === "AbortError") { throw error; } else { - const e = /** @type {import('./errors/fetch-error').SystemError} */(error) + const e = /** @type {import('./errors/fetch-error').SystemError} */ ( + error + ); // Other errors, such as incorrect content-encoding - throw new FetchError(`Invalid response body while trying to fetch ${data.url}: ${e.message}`, 'system', e); + throw new FetchError( + `Invalid response body while trying to fetch ${data.url}: ${e.message}`, + "system", + e + ); } } } @@ -275,12 +304,12 @@ async function consumeBody(data) { * @param {Body} instance Response or Request instance * @return {ReadableStream | null} */ -export const clone = instance => { - const {body} = instance; +export const clone = (instance) => { + const { body } = instance; // Don't allow cloning a used body if (instance.bodyUsed) { - throw new Error('cannot clone body after it is used'); + throw new Error("cannot clone body after it is used"); } if (!body) { @@ -302,7 +331,7 @@ export const clone = instance => { * @param {Body} source Any options.body input * @returns {string | null} */ -export const extractContentType = source => source[INTERNALS].type; +export const extractContentType = (source) => source[INTERNALS].type; /** * The Fetch Standard treats this as if "total bytes" is a property on the body. @@ -313,7 +342,7 @@ export const extractContentType = source => source[INTERNALS].type; * @param {Body} source - Body object from the Body instance. * @returns {number | null} */ -export const getTotalBytes = source => source[INTERNALS].size; +export const getTotalBytes = (source) => source[INTERNALS].size; /** * Write a Body to a Node.js WritableStream (e.g. http.Request) object. @@ -322,7 +351,7 @@ export const getTotalBytes = source => source[INTERNALS].size; * @param {Body} source - Body object from the Body instance. * @returns {void} */ -export const writeToStream = (dest, {body}) => { +export const writeToStream = (dest, { body }) => { if (body === null) { // Body is null dest.end(); @@ -365,7 +394,9 @@ class StreamIterableIterator { * @returns {Promise>} */ next() { - return /** @type {Promise>} */ (this.getReader().read()); + return /** @type {Promise>} */ ( + this.getReader().read() + ); } /** @@ -376,17 +407,17 @@ class StreamIterableIterator { await this.reader.cancel(); } - return {done: true, value: undefined}; + return { done: true, value: undefined }; } /** - * - * @param {any} error + * + * @param {any} error * @returns {Promise>} */ async throw(error) { await this.getReader().cancel(error); - return {done: true, value: undefined}; + return { done: true, value: undefined }; } } @@ -394,7 +425,7 @@ class StreamIterableIterator { * @template T * @param {ReadableStream} stream */ -export const streamIterator = stream => new StreamIterableIterator(stream); +export const streamIterator = (stream) => new StreamIterableIterator(stream); /** * @param {Uint8Array} buffer @@ -415,18 +446,19 @@ const writeBytes = (buffer, chunks) => { * @returns {ReadableStream} */ // @ts-ignore -const fromBytes = bytes => new ReadableStream({ - start(controller) { - controller.enqueue(bytes); - controller.close(); - } -}); +const fromBytes = (bytes) => + new ReadableStream({ + start(controller) { + controller.enqueue(bytes); + controller.close(); + }, + }); /** * @param {AsyncIterable} content * @returns {ReadableStream} */ -export const fromAsyncIterable = content => +export const fromAsyncIterable = (content) => new ReadableStream(new AsyncIterablePump(content)); /** @@ -465,12 +497,12 @@ class AsyncIterablePump { */ cancel(reason) { if (reason) { - if (typeof this.source.throw === 'function') { + if (typeof this.source.throw === "function") { this.source.throw(reason); - } else if (typeof this.source.return === 'function') { + } else if (typeof this.source.return === "function") { this.source.return(); } - } else if (typeof this.source.return === 'function') { + } else if (typeof this.source.return === "function") { this.source.return(); } } @@ -480,7 +512,7 @@ class AsyncIterablePump { * @param {Stream & {readableHighWaterMark?:number}} source * @returns {ReadableStream} */ -export const fromStream = source => { +export const fromStream = (source) => { const pump = new StreamPump(source); const stream = new ReadableStream(pump, pump); return stream; @@ -521,10 +553,10 @@ class StreamPump { */ start(controller) { this.controller = controller; - this.stream.on('data', this.enqueue); - this.stream.once('error', this.error); - this.stream.once('end', this.close); - this.stream.once('close', this.close); + this.stream.on("data", this.enqueue); + this.stream.once("error", this.error); + this.stream.once("end", this.close); + this.stream.once("close", this.close); } pull() { @@ -539,10 +571,10 @@ class StreamPump { this.stream.destroy(reason); } - this.stream.off('data', this.enqueue); - this.stream.off('error', this.error); - this.stream.off('end', this.close); - this.stream.off('close', this.close); + this.stream.off("data", this.enqueue); + this.stream.off("error", this.error); + this.stream.off("end", this.close); + this.stream.off("close", this.close); } /** @@ -551,9 +583,7 @@ class StreamPump { enqueue(chunk) { if (this.controller) { try { - const bytes = chunk instanceof Uint8Array ? - chunk : - Buffer.from(chunk); + const bytes = chunk instanceof Uint8Array ? chunk : Buffer.from(chunk); const available = (this.controller.desiredSize || 0) - bytes.byteLength; this.controller.enqueue(bytes); @@ -561,7 +591,11 @@ class StreamPump { this.pause(); } } catch { - this.controller.error(new Error('Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object')); + this.controller.error( + new Error( + "Could not create Buffer, chunk must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object" + ) + ); this.cancel(); } } @@ -587,7 +621,7 @@ class StreamPump { } /** - * @param {Error} error + * @param {Error} error */ error(error) { if (this.controller) { diff --git a/packages/fetch/src/errors/abort-error.js b/packages/fetch/src/errors/abort-error.js index 65f5c11..3f10122 100644 --- a/packages/fetch/src/errors/abort-error.js +++ b/packages/fetch/src/errors/abort-error.js @@ -1,14 +1,14 @@ -import {FetchBaseError} from './base.js'; +import { FetchBaseError } from "./base.js"; /** * AbortError interface for cancelled requests */ export class AbortError extends FetchBaseError { /** - * @param {string} message + * @param {string} message * @param {string} [type] */ - constructor(message, type = 'aborted') { + constructor(message, type = "aborted") { super(message, type); } } diff --git a/packages/fetch/src/errors/base.js b/packages/fetch/src/errors/base.js index e2b8c41..95594ed 100644 --- a/packages/fetch/src/errors/base.js +++ b/packages/fetch/src/errors/base.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; export class FetchBaseError extends Error { /** - * @param {string} message - * @param {string} type + * @param {string} message + * @param {string} type */ constructor(message, type) { super(message); @@ -21,4 +21,3 @@ export class FetchBaseError extends Error { return this.constructor.name; } } - diff --git a/packages/fetch/src/errors/fetch-error.js b/packages/fetch/src/errors/fetch-error.js index a3b6f0f..17fb9b3 100644 --- a/packages/fetch/src/errors/fetch-error.js +++ b/packages/fetch/src/errors/fetch-error.js @@ -1,5 +1,4 @@ - -import {FetchBaseError} from './base.js'; +import { FetchBaseError } from "./base.js"; /** * @typedef {{ @@ -13,7 +12,7 @@ import {FetchBaseError} from './base.js'; * port?: number * syscall: string * }} SystemError -*/ + */ /** * FetchError interface for operational errors diff --git a/packages/fetch/src/fetch.js b/packages/fetch/src/fetch.js index fcbc379..7fdd803 100644 --- a/packages/fetch/src/fetch.js +++ b/packages/fetch/src/fetch.js @@ -6,28 +6,27 @@ * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. */ -import http from 'http'; -import https from 'https'; -import zlib from 'zlib'; -import fs from 'fs'; -import * as mime from 'mrmime' -import dataUriToBuffer from 'data-uri-to-buffer'; - -import {writeToStream, fromAsyncIterable} from './body.js'; -import Response from './response.js'; -import Headers, {fromRawHeaders} from './headers.js'; -import Request, {getNodeRequestOptions} from './request.js'; -import {FetchError} from './errors/fetch-error.js'; -import {AbortError} from './errors/abort-error.js'; -import {isRedirect} from './utils/is-redirect.js'; -import {pipeline as pump, PassThrough} from 'stream'; -import * as Stream from 'stream'; -import { ReadableStream, Blob, FormData } from './package.js'; - - -export {Headers, Request, Response, ReadableStream, Blob, FormData}; - -const supportedSchemas = new Set(['data:', 'http:', 'https:', 'file:']); +import http from "http"; +import https from "https"; +import zlib from "zlib"; +import fs from "fs"; +import * as mime from "mrmime"; +import dataUriToBuffer from "data-uri-to-buffer"; + +import { writeToStream, fromAsyncIterable } from "./body.js"; +import Response from "./response.js"; +import Headers, { fromRawHeaders } from "./headers.js"; +import Request, { getNodeRequestOptions } from "./request.js"; +import { FetchError } from "./errors/fetch-error.js"; +import { AbortError } from "./errors/abort-error.js"; +import { isRedirect } from "./utils/is-redirect.js"; +import { pipeline as pump, PassThrough } from "stream"; +import * as Stream from "stream"; +import { ReadableStream, Blob, FormData } from "./package.js"; + +export { Headers, Request, Response, ReadableStream, Blob, FormData }; + +const supportedSchemas = new Set(["data:", "http:", "https:", "file:"]); /** * Fetch function @@ -42,34 +41,43 @@ async function fetch(url, options_ = {}) { const request = new Request(url, options_); const options = getNodeRequestOptions(request); if (!supportedSchemas.has(options.protocol)) { - throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${options.protocol.replace(/:$/, '')}" is not supported.`); + throw new TypeError( + `node-fetch cannot load ${url}. URL scheme "${options.protocol.replace( + /:$/, + "" + )}" is not supported.` + ); } - if (options.protocol === 'data:') { + if (options.protocol === "data:") { const data = dataUriToBuffer(request.url.toString()); - const response = new Response(data, {headers: {'Content-Type': data.typeFull}}); + const response = new Response(data, { + headers: { "Content-Type": data.typeFull }, + }); resolve(response); return; } - if (options.protocol === 'file:') { - const stream = fs.createReadStream(new URL(request.url)) - const type = mime.lookup(request.url) || 'application/octet-stream' - const response = new Response(stream, {headers: {'Content-Type': type }}); + if (options.protocol === "file:") { + const stream = fs.createReadStream(new URL(request.url)); + const type = mime.lookup(request.url) || "application/octet-stream"; + const response = new Response(stream, { + headers: { "Content-Type": type }, + }); resolve(response); return; } // Wrap http.request into fetch - const send = (options.protocol === 'https:' ? https : http).request; - const {signal} = request; + const send = (options.protocol === "https:" ? https : http).request; + const { signal } = request; /** @type {Response|null} */ let response = null; /** @type {import('http').IncomingMessage|null} */ let response_ = null; const abort = () => { - const error = new AbortError('The operation was aborted.'); + const error = new AbortError("The operation was aborted."); reject(error); if (request.body) { request.body.cancel(error); @@ -79,7 +87,7 @@ async function fetch(url, options_ = {}) { return; } - response_.emit('error', error); + response_.emit("error", error); }; if (signal && signal.aborted) { @@ -96,25 +104,31 @@ async function fetch(url, options_ = {}) { const request_ = send(options); if (signal) { - signal.addEventListener('abort', abortAndFinalize); + signal.addEventListener("abort", abortAndFinalize); } const finalize = () => { request_.abort(); if (signal) { - signal.removeEventListener('abort', abortAndFinalize); + signal.removeEventListener("abort", abortAndFinalize); } }; - request_.on('error', err => { + request_.on("error", (err) => { // @ts-expect-error - err may not be SystemError - reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + reject( + new FetchError( + `request to ${request.url} failed, reason: ${err.message}`, + "system", + err + ) + ); finalize(); }); - fixResponseChunkedTransferBadEnding(request_, err => { + fixResponseChunkedTransferBadEnding(request_, (err) => { if (signal && signal.aborted) { - return + return; } response_?.emit("error", err); @@ -124,23 +138,28 @@ async function fetch(url, options_ = {}) { if (parseInt(process.version.substring(1)) < 14) { // Before Node.js 14, pipeline() does not fully support async iterators and does not always // properly handle when the socket close/end events are out of order. - request_.on('socket', s => { - s.prependListener('close', hadError => { + request_.on("socket", (s) => { + s.prependListener("close", (hadError) => { // if a data listener is still present we didn't end cleanly - const hasDataListener = s.listenerCount('data') > 0 + const hasDataListener = s.listenerCount("data") > 0; // if end happened before close but the socket didn't emit an error, do it now - if (response && hasDataListener && !hadError && !(signal && signal.aborted)) { - const err = Object.assign(new Error('Premature close'), { - code: 'ERR_STREAM_PREMATURE_CLOSE' + if ( + response && + hasDataListener && + !hadError && + !(signal && signal.aborted) + ) { + const err = Object.assign(new Error("Premature close"), { + code: "ERR_STREAM_PREMATURE_CLOSE", }); - response_?.emit('error', err); + response_?.emit("error", err); } }); }); } - request_.on('response', incoming => { + request_.on("response", (incoming) => { response_ = incoming; request_.setTimeout(0); const headers = fromRawHeaders(response_.rawHeaders); @@ -148,25 +167,31 @@ async function fetch(url, options_ = {}) { // HTTP fetch step 5 if (isRedirect(Number(response_.statusCode))) { // HTTP fetch step 5.2 - const location = headers.get('Location'); + const location = headers.get("Location"); // HTTP fetch step 5.3 - const locationURL = location === null ? null : new URL(location, request.url); + const locationURL = + location === null ? null : new URL(location, request.url); // HTTP fetch step 5.5 switch (request.redirect) { - case 'error': - reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect')); + case "error": + reject( + new FetchError( + `uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, + "no-redirect" + ) + ); finalize(); return; - case 'manual': + case "manual": // Node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. if (locationURL !== null) { - headers.set('Location', locationURL.toString()); + headers.set("Location", locationURL.toString()); } break; - case 'follow': { + case "follow": { // HTTP-redirect fetch step 2 if (locationURL === null) { break; @@ -174,7 +199,12 @@ async function fetch(url, options_ = {}) { // HTTP-redirect fetch step 5 if (request.counter >= request.follow) { - reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + reject( + new FetchError( + `maximum redirect reached at: ${request.url}`, + "max-redirect" + ) + ); finalize(); return; } @@ -192,7 +222,7 @@ async function fetch(url, options_ = {}) { // consumed it already. body: options_.body, signal: signal, - size: request.size + size: request.size, }; // HTTP-redirect fetch step 9 @@ -200,41 +230,57 @@ async function fetch(url, options_ = {}) { requestOptions.body instanceof ReadableStream || requestOptions.body instanceof Stream.Readable; if (response_.statusCode !== 303 && isStreamBody) { - reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + reject( + new FetchError( + "Cannot follow redirect with body being a readable stream", + "unsupported-redirect" + ) + ); finalize(); return; } // HTTP-redirect fetch step 11 - if (response_.statusCode === 303 || ((response_.statusCode === 301 || response_.statusCode === 302) && request.method === 'POST')) { - requestOptions.method = 'GET'; + if ( + response_.statusCode === 303 || + ((response_.statusCode === 301 || response_.statusCode === 302) && + request.method === "POST") + ) { + requestOptions.method = "GET"; requestOptions.body = undefined; - requestOptions.headers.delete('content-length'); + requestOptions.headers.delete("content-length"); } // HTTP-redirect fetch step 15 - fetch(new Request(locationURL.href, requestOptions)).then(resolve, reject); + fetch(new Request(locationURL.href, requestOptions)).then( + resolve, + reject + ); finalize(); return; } default: - return reject(new TypeError(`Redirect option '${request.redirect}' is not a valid value of RequestRedirect`)); + return reject( + new TypeError( + `Redirect option '${request.redirect}' is not a valid value of RequestRedirect` + ) + ); } } // Prepare response if (signal) { - response_.once('end', () => { - signal.removeEventListener('abort', abortAndFinalize); + response_.once("end", () => { + signal.removeEventListener("abort", abortAndFinalize); }); } let body = pump(response_, new PassThrough(), reject); // see https://github.com/nodejs/node/pull/29376 /* c8 ignore next 3 */ - if (process.version < 'v12.10') { - response_.on('aborted', abortAndFinalize); + if (process.version < "v12.10") { + response_.on("aborted", abortAndFinalize); } const responseOptions = { @@ -244,11 +290,11 @@ async function fetch(url, options_ = {}) { headers, size: request.size, counter: request.counter, - highWaterMark: request.highWaterMark + highWaterMark: request.highWaterMark, }; // HTTP-network fetch step 12.1.1.3 - const codings = headers.get('Content-Encoding'); + const codings = headers.get("Content-Encoding"); // HTTP-network fetch step 12.1.1.4: handle content codings @@ -258,7 +304,13 @@ async function fetch(url, options_ = {}) { // 3. no Content-Encoding header // 4. no content response (204) // 5. content not modified response (304) - if (!request.compress || request.method === 'HEAD' || codings === null || response_.statusCode === 204 || response_.statusCode === 304) { + if ( + !request.compress || + request.method === "HEAD" || + codings === null || + response_.statusCode === 204 || + response_.statusCode === 304 + ) { response = new Response(body, responseOptions); resolve(response); return; @@ -271,11 +323,11 @@ async function fetch(url, options_ = {}) { // Always using Z_SYNC_FLUSH is what cURL does. const zlibOptions = { flush: zlib.Z_SYNC_FLUSH, - finishFlush: zlib.Z_SYNC_FLUSH + finishFlush: zlib.Z_SYNC_FLUSH, }; // For gzip - if (codings === 'gzip' || codings === 'x-gzip') { + if (codings === "gzip" || codings === "x-gzip") { body = pump(body, zlib.createGunzip(zlibOptions), reject); response = new Response(fromAsyncIterable(body), responseOptions); resolve(response); @@ -283,13 +335,13 @@ async function fetch(url, options_ = {}) { } // For deflate - if (codings === 'deflate' || codings === 'x-deflate') { + if (codings === "deflate" || codings === "x-deflate") { // Handle the infamous raw deflate response from old servers // a hack for old IIS and Apache servers const raw = pump(response_, new PassThrough(), reject); - raw.once('data', chunk => { + raw.once("data", (chunk) => { // See http://stackoverflow.com/questions/37519828 - if ((chunk[0] & 0x0F) === 0x08) { + if ((chunk[0] & 0x0f) === 0x08) { body = pump(body, zlib.createInflate(), reject); } else { body = pump(body, zlib.createInflateRaw(), reject); @@ -302,7 +354,7 @@ async function fetch(url, options_ = {}) { } // For br - if (codings === 'br') { + if (codings === "br") { body = pump(body, zlib.createBrotliDecompress(), reject); response = new Response(fromAsyncIterable(body), responseOptions); resolve(response); @@ -324,52 +376,52 @@ async function fetch(url, options_ = {}) { * @param {(error:Error) => void} errorCallback */ function fixResponseChunkedTransferBadEnding(request, errorCallback) { - const LAST_CHUNK = Buffer.from('0\r\n\r\n'); + const LAST_CHUNK = Buffer.from("0\r\n\r\n"); let isChunkedTransfer = false; let properLastChunkReceived = false; /** @type {Buffer | undefined} */ let previousChunk; - request.on('response', response => { - const {headers} = response; - isChunkedTransfer = headers['transfer-encoding'] === 'chunked' && !headers['content-length']; + request.on("response", (response) => { + const { headers } = response; + isChunkedTransfer = + headers["transfer-encoding"] === "chunked" && !headers["content-length"]; }); - request.on('socket', socket => { + request.on("socket", (socket) => { const onSocketClose = () => { if (isChunkedTransfer && !properLastChunkReceived) { - const error = Object.assign(new Error('Premature close'), { - code: 'ERR_STREAM_PREMATURE_CLOSE' + const error = Object.assign(new Error("Premature close"), { + code: "ERR_STREAM_PREMATURE_CLOSE", }); errorCallback(error); } }; /** @param {Buffer} buf */ - const onData = buf => { + const onData = (buf) => { properLastChunkReceived = Buffer.compare(buf.slice(-5), LAST_CHUNK) === 0; // Sometimes final 0-length chunk and end of message code are in separate packets if (!properLastChunkReceived && previousChunk) { - properLastChunkReceived = ( - Buffer.compare(previousChunk.slice(-3), LAST_CHUNK.slice(0, 3)) === 0 && - Buffer.compare(buf.slice(-2), LAST_CHUNK.slice(3)) === 0 - ); + properLastChunkReceived = + Buffer.compare(previousChunk.slice(-3), LAST_CHUNK.slice(0, 3)) === + 0 && Buffer.compare(buf.slice(-2), LAST_CHUNK.slice(3)) === 0; } previousChunk = buf; }; - socket.prependListener('close', onSocketClose); - socket.on('data', onData); + socket.prependListener("close", onSocketClose); + socket.on("data", onData); - request.on('close', () => { - socket.removeListener('close', onSocketClose); - socket.removeListener('data', onData); + request.on("close", () => { + socket.removeListener("close", onSocketClose); + socket.removeListener("data", onData); }); }); } -export default fetch -export { fetch } +export default fetch; +export { fetch }; diff --git a/packages/fetch/src/headers.js b/packages/fetch/src/headers.js index 1b3d5e5..e19ca14 100644 --- a/packages/fetch/src/headers.js +++ b/packages/fetch/src/headers.js @@ -4,39 +4,48 @@ * Headers class offers convenient helpers */ -import {types} from 'util'; -import http from 'http'; -import { isIterable } from './utils/is.js' +import { types } from "util"; +import http from "http"; +import { isIterable } from "./utils/is.js"; -const validators = /** @type {{validateHeaderName?:(name:string) => any, validateHeaderValue?:(name:string, value:string) => any}} */ -(http) +const validators = + /** @type {{validateHeaderName?:(name:string) => any, validateHeaderValue?:(name:string, value:string) => any}} */ + (http); -const validateHeaderName = typeof validators.validateHeaderName === 'function' ? - validators.validateHeaderName : - /** - * @param {string} name - */ - name => { - if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) { - const err = new TypeError(`Header name must be a valid HTTP token [${name}]`); - Object.defineProperty(err, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'}); - throw err; - } - }; +const validateHeaderName = + typeof validators.validateHeaderName === "function" + ? validators.validateHeaderName + : /** + * @param {string} name + */ + (name) => { + if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) { + const err = new TypeError( + `Header name must be a valid HTTP token [${name}]` + ); + Object.defineProperty(err, "code", { + value: "ERR_INVALID_HTTP_TOKEN", + }); + throw err; + } + }; -const validateHeaderValue = typeof validators.validateHeaderValue === 'function' ? - validators.validateHeaderValue : - /** - * @param {string} name - * @param {string} value - */ - (name, value) => { - if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) { - const err = new TypeError(`Invalid character in header content ["${name}"]`); - Object.defineProperty(err, 'code', {value: 'ERR_INVALID_CHAR'}); - throw err; - } - }; +const validateHeaderValue = + typeof validators.validateHeaderValue === "function" + ? validators.validateHeaderValue + : /** + * @param {string} name + * @param {string} value + */ + (name, value) => { + if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) { + const err = new TypeError( + `Invalid character in header content ["${name}"]` + ); + Object.defineProperty(err, "code", { value: "ERR_INVALID_CHAR" }); + throw err; + } + }; /** * @typedef {Headers | Record | Iterable | Iterable>} HeadersInit @@ -65,25 +74,25 @@ export default class Headers extends URLSearchParams { if (init instanceof Headers) { const raw = init.raw(); for (const [name, values] of Object.entries(raw)) { - result.push(...values.map(value => [name, value])); + result.push(...values.map((value) => [name, value])); } - } else if (init == null) { // eslint-disable-line no-eq-null, eqeqeq + } else if (init == null) { + // eslint-disable-line no-eq-null, eqeqeq // No op } else if (isIterable(init)) { // Sequence> // Note: per spec we have to first exhaust the lists then process them result = [...init] - .map(pair => { - if ( - typeof pair !== 'object' || types.isBoxedPrimitive(pair) - ) { - throw new TypeError('Each header pair must be an iterable object'); + .map((pair) => { + if (typeof pair !== "object" || types.isBoxedPrimitive(pair)) { + throw new TypeError("Each header pair must be an iterable object"); } return [...pair]; - }).map(pair => { + }) + .map((pair) => { if (pair.length !== 2) { - throw new TypeError('Each header pair must be a name/value tuple'); + throw new TypeError("Each header pair must be a name/value tuple"); } return [...pair]; @@ -92,18 +101,20 @@ export default class Headers extends URLSearchParams { // Record result.push(...Object.entries(init)); } else { - throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence> or record)'); + throw new TypeError( + "Failed to construct 'Headers': The provided value is not of type '(sequence> or record)" + ); } // Validate and lowercase result = - result.length > 0 ? - result.map(([name, value]) => { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return [String(name).toLowerCase(), String(value)]; - }) : - []; + result.length > 0 + ? result.map(([name, value]) => { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return [String(name).toLowerCase(), String(value)]; + }) + : []; super(result); @@ -112,8 +123,8 @@ export default class Headers extends URLSearchParams { return new Proxy(this, { get(target, p, receiver) { switch (p) { - case 'append': - case 'set': + case "append": + case "set": /** * @param {string} name * @param {string} value @@ -128,13 +139,13 @@ export default class Headers extends URLSearchParams { ); }; - case 'delete': - case 'has': - case 'getAll': + case "delete": + case "has": + case "getAll": /** * @param {string} name */ - return name => { + return (name) => { validateHeaderName(name); // @ts-ignore return URLSearchParams.prototype[p].call( @@ -143,16 +154,18 @@ export default class Headers extends URLSearchParams { ); }; - case 'keys': + case "keys": return () => { target.sort(); - return new Set(URLSearchParams.prototype.keys.call(target)).keys(); + return new Set( + URLSearchParams.prototype.keys.call(target) + ).keys(); }; default: return Reflect.get(target, p, receiver); } - } + }, /* c8 ignore next */ }); } @@ -166,8 +179,8 @@ export default class Headers extends URLSearchParams { } /** - * - * @param {string} name + * + * @param {string} name */ get(name) { const values = this.getAll(name); @@ -175,7 +188,7 @@ export default class Headers extends URLSearchParams { return null; } - let value = values.join(', '); + let value = values.join(", "); if (/^content-encoding$/i.test(name)) { value = value.toLowerCase(); } @@ -184,8 +197,8 @@ export default class Headers extends URLSearchParams { } /** - * @param {(value: string, key: string, parent: this) => void} callback - * @param {any} thisArg + * @param {(value: string, key: string, parent: this) => void} callback + * @param {any} thisArg * @returns {void} */ forEach(callback, thisArg = undefined) { @@ -197,18 +210,18 @@ export default class Headers extends URLSearchParams { /** * @returns {IterableIterator} */ - * values() { + *values() { for (const name of this.keys()) { - yield /** @type {string} */(this.get(name)); + yield /** @type {string} */ (this.get(name)); } } /** * @returns {IterableIterator<[string, string]>} */ - * entries() { + *entries() { for (const name of this.keys()) { - yield [name, /** @type {string} */(this.get(name))]; + yield [name, /** @type {string} */ (this.get(name))]; } } @@ -225,25 +238,25 @@ export default class Headers extends URLSearchParams { return [...this.keys()].reduce((result, key) => { result[key] = this.getAll(key); return result; - }, /** @type {Record} */({})); + }, /** @type {Record} */ ({})); } /** * For better console.log(headers) and also to convert Headers into Node.js Request compatible format */ - [Symbol.for('nodejs.util.inspect.custom')]() { + [Symbol.for("nodejs.util.inspect.custom")]() { return [...this.keys()].reduce((result, key) => { const values = this.getAll(key); // Http.request() only supports string as Host header. // This hack makes specifying custom Host header possible. - if (key === 'host') { + if (key === "host") { result[key] = values[0]; } else { result[key] = values.length > 1 ? values : values[0]; } return result; - }, /** @type {Record} */({})); + }, /** @type {Record} */ ({})); } } @@ -253,8 +266,8 @@ export default class Headers extends URLSearchParams { */ Object.defineProperties( Headers.prototype, - ['get', 'entries', 'forEach', 'values'].reduce((result, property) => { - result[property] = {enumerable: true}; + ["get", "entries", "forEach", "values"].reduce((result, property) => { + result[property] = { enumerable: true }; return result; }, /** @type {Record} */ ({})) ); @@ -274,7 +287,7 @@ export function fromRawHeaders(headers = []) { } return result; - }, /** @type {string[][]} */([])) + }, /** @type {string[][]} */ ([])) .filter(([name, value]) => { try { validateHeaderName(name); @@ -284,6 +297,5 @@ export function fromRawHeaders(headers = []) { return false; } }) - ); } diff --git a/packages/fetch/src/lib.js b/packages/fetch/src/lib.js index 5789edf..6b877f9 100644 --- a/packages/fetch/src/lib.js +++ b/packages/fetch/src/lib.js @@ -1,7 +1,4 @@ - - // On the web we just export native fetch implementation -export { ReadableStream, Blob, FormData } from './package.js'; +export { ReadableStream, Blob, FormData } from "./package.js"; export const { Headers, Request, Response } = globalThis; -export default globalThis.fetch - +export default globalThis.fetch; diff --git a/packages/fetch/src/lib.node.js b/packages/fetch/src/lib.node.js index 90e30df..37b3cc1 100644 --- a/packages/fetch/src/lib.node.js +++ b/packages/fetch/src/lib.node.js @@ -1,5 +1,5 @@ export { default, fetch, Headers, Request, Response } from "./fetch.js"; -export { ReadableStream, Blob, FormData } from './package.js'; +export { ReadableStream, Blob, FormData } from "./package.js"; // Node 18+ introduces fetch API globally and it doesn't support our use-cases yet. -// For now we always use the polyfill. \ No newline at end of file +// For now we always use the polyfill. diff --git a/packages/fetch/src/package.js b/packages/fetch/src/package.js index b95f939..b758d8e 100644 --- a/packages/fetch/src/package.js +++ b/packages/fetch/src/package.js @@ -1,5 +1,2 @@ - -export { Blob, ReadableStream } from '@remix-run/web-blob' -export { FormData } from '@remix-run/web-form-data' - - +export { Blob, ReadableStream } from "@remix-run/web-blob"; +export { FormData } from "@remix-run/web-form-data"; diff --git a/packages/fetch/src/package.ts b/packages/fetch/src/package.ts index 015a6f4..881f03c 100644 --- a/packages/fetch/src/package.ts +++ b/packages/fetch/src/package.ts @@ -1,4 +1,2 @@ -export const { FormData, Blob } = globalThis -export { ReadableStream } from "@remix-run/web-stream" - - +export const { FormData, Blob } = globalThis; +export { ReadableStream } from "@remix-run/web-stream"; diff --git a/packages/fetch/src/request.js b/packages/fetch/src/request.js index 9b4cfed..b6cd83b 100644 --- a/packages/fetch/src/request.js +++ b/packages/fetch/src/request.js @@ -1,4 +1,3 @@ - /** * Request.js * @@ -7,17 +6,24 @@ * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/. */ -import {format as formatUrl} from 'url'; -import {AbortController as AbortControllerPolyfill} from 'abort-controller'; -import Headers from './headers.js'; -import Body, {clone, extractContentType, getTotalBytes} from './body.js'; -import {isAbortSignal} from './utils/is.js'; -import {getSearch} from './utils/get-search.js'; +import { format as formatUrl } from "url"; +import { AbortController as AbortControllerPolyfill } from "abort-controller"; +import Headers from "./headers.js"; +import Body, { clone, extractContentType, getTotalBytes } from "./body.js"; +import { isAbortSignal } from "./utils/is.js"; +import { getSearch } from "./utils/get-search.js"; -const INTERNALS = Symbol('Request internals'); +const INTERNALS = Symbol("Request internals"); const forbiddenMethods = new Set(["CONNECT", "TRACE", "TRACK"]); -const normalizedMethods = new Set(["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]); +const normalizedMethods = new Set([ + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "POST", + "PUT", +]); /** * Check if `obj` is an instance of Request. @@ -25,14 +31,10 @@ const normalizedMethods = new Set(["DELETE", "GET", "HEAD", "OPTIONS", "POST", " * @param {any} object * @return {object is Request} */ -const isRequest = object => { - return ( - typeof object === 'object' && - typeof object[INTERNALS] === 'object' - ); +const isRequest = (object) => { + return typeof object === "object" && typeof object[INTERNALS] === "object"; }; - /** * Request class * @implements {globalThis.Request} @@ -71,69 +73,70 @@ export default class Request extends Body { constructor(info, init = {}) { let parsedURL; /** @type {RequestOptions & RequestExtraOptions} */ - let settings + let settings; // Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245) if (isRequest(info)) { parsedURL = new URL(info.url); - settings = (info) + settings = info; } else { parsedURL = new URL(info); settings = {}; } - - // Normalize method: https://fetch.spec.whatwg.org/#methods - let method = init.method || settings.method || 'GET'; + let method = init.method || settings.method || "GET"; if (forbiddenMethods.has(method.toUpperCase())) { - throw new TypeError(`Failed to construct 'Request': '${method}' HTTP method is unsupported.`) + throw new TypeError( + `Failed to construct 'Request': '${method}' HTTP method is unsupported.` + ); } else if (normalizedMethods.has(method.toUpperCase())) { method = method.toUpperCase(); } - const inputBody = init.body != null - ? init.body - : (isRequest(info) && info.body !== null) - ? clone(info) - : null; + const inputBody = + init.body != null + ? init.body + : isRequest(info) && info.body !== null + ? clone(info) + : null; // eslint-disable-next-line no-eq-null, eqeqeq - if (inputBody != null && (method === 'GET' || method === 'HEAD')) { - throw new TypeError('Request with GET/HEAD method cannot have body'); + if (inputBody != null && (method === "GET" || method === "HEAD")) { + throw new TypeError("Request with GET/HEAD method cannot have body"); } super(inputBody, { - size: init.size || settings.size || 0 + size: init.size || settings.size || 0, }); - const input = settings - + const input = settings; - const headers = /** @type {globalThis.Headers} */ + const headers = + /** @type {globalThis.Headers} */ (new Headers(init.headers || input.headers || {})); - if (inputBody !== null && !headers.has('Content-Type')) { + if (inputBody !== null && !headers.has("Content-Type")) { const contentType = extractContentType(this); if (contentType) { - headers.append('Content-Type', contentType); + headers.append("Content-Type", contentType); } } - let signal = 'signal' in init - ? init.signal - : isRequest(input) - ? input.signal - : null; + let signal = + "signal" in init ? init.signal : isRequest(input) ? input.signal : null; // eslint-disable-next-line no-eq-null, eqeqeq if (signal != null && !isAbortSignal(signal)) { - throw new TypeError('Expected signal to be an instanceof AbortSignal or EventTarget'); + throw new TypeError( + "Expected signal to be an instanceof AbortSignal or EventTarget" + ); } if (!signal) { - let AbortControllerConstructor = typeof AbortController != "undefined" - ? AbortController - : AbortControllerPolyfill; + let AbortControllerConstructor = + typeof AbortController != "undefined" + ? AbortController + : AbortControllerPolyfill; /** @type {any} */ let newSignal = new AbortControllerConstructor().signal; signal = newSignal; @@ -142,21 +145,31 @@ export default class Request extends Body { /** @type {RequestState} */ this[INTERNALS] = { method, - redirect: init.redirect || input.redirect || 'follow', + redirect: init.redirect || input.redirect || "follow", headers, - credentials: init.credentials || 'same-origin', + credentials: init.credentials || "same-origin", parsedURL, - signal: signal || null + signal: signal || null, }; /** @type {boolean} */ - this.keepalive + this.keepalive; // Node-fetch-only options /** @type {number} */ - this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow; + this.follow = + init.follow === undefined + ? input.follow === undefined + ? 20 + : input.follow + : init.follow; /** @type {boolean} */ - this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress; + this.compress = + init.compress === undefined + ? input.compress === undefined + ? true + : input.compress + : init.compress; /** @type {number} */ this.counter = init.counter || input.counter || 0; /** @type {Agent|undefined} */ @@ -164,14 +177,15 @@ export default class Request extends Body { /** @type {number} */ this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384; /** @type {boolean} */ - this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false; + this.insecureHTTPParser = + init.insecureHTTPParser || input.insecureHTTPParser || false; } /** * @type {RequestCache} */ get cache() { - return "default" + return "default"; } /** @@ -179,33 +193,33 @@ export default class Request extends Body { */ get credentials() { - return this[INTERNALS].credentials + return this[INTERNALS].credentials; } /** * @type {RequestDestination} */ get destination() { - return "" + return ""; } get integrity() { - return "" + return ""; } /** @type {RequestMode} */ get mode() { - return "cors" + return "cors"; } /** @type {string} */ get referrer() { - return "" + return ""; } /** @type {ReferrerPolicy} */ get referrerPolicy() { - return "" + return ""; } get method() { return this[INTERNALS].method; @@ -247,17 +261,17 @@ export default class Request extends Body { } get [Symbol.toStringTag]() { - return 'Request'; + return "Request"; } } Object.defineProperties(Request.prototype, { - method: {enumerable: true}, - url: {enumerable: true}, - headers: {enumerable: true}, - redirect: {enumerable: true}, - clone: {enumerable: true}, - signal: {enumerable: true} + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true }, + signal: { enumerable: true }, }); /** @@ -266,50 +280,50 @@ Object.defineProperties(Request.prototype, { * * @param {Request & Record} request - A Request instance */ -export const getNodeRequestOptions = request => { - const {parsedURL} = request[INTERNALS]; +export const getNodeRequestOptions = (request) => { + const { parsedURL } = request[INTERNALS]; const headers = new Headers(request[INTERNALS].headers); // Fetch step 1.3 - if (!headers.has('Accept')) { - headers.set('Accept', '*/*'); + if (!headers.has("Accept")) { + headers.set("Accept", "*/*"); } // HTTP-network-or-cache fetch steps 2.4-2.7 let contentLengthValue = null; if (request.body === null && /^(post|put)$/i.test(request.method)) { - contentLengthValue = '0'; + contentLengthValue = "0"; } if (request.body !== null) { const totalBytes = getTotalBytes(request); // Set Content-Length if totalBytes is a number (that is not NaN) - if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) { + if (typeof totalBytes === "number" && !Number.isNaN(totalBytes)) { contentLengthValue = String(totalBytes); } } if (contentLengthValue) { - headers.set('Content-Length', contentLengthValue); + headers.set("Content-Length", contentLengthValue); } // HTTP-network-or-cache fetch step 2.11 - if (!headers.has('User-Agent')) { - headers.set('User-Agent', 'node-fetch'); + if (!headers.has("User-Agent")) { + headers.set("User-Agent", "node-fetch"); } // HTTP-network-or-cache fetch step 2.15 - if (request.compress && !headers.has('Accept-Encoding')) { - headers.set('Accept-Encoding', 'gzip,deflate,br'); + if (request.compress && !headers.has("Accept-Encoding")) { + headers.set("Accept-Encoding", "gzip,deflate,br"); } - let {agent} = request; - if (typeof agent === 'function') { + let { agent } = request; + if (typeof agent === "function") { agent = agent(parsedURL); } - if (!headers.has('Connection') && !agent) { - headers.set('Connection', 'close'); + if (!headers.has("Connection") && !agent) { + headers.set("Connection", "close"); } // HTTP-network fetch step 4.2 @@ -331,9 +345,9 @@ export const getNodeRequestOptions = request => { href: parsedURL.href, method: request.method, // @ts-ignore - not sure what this supposed to do - headers: headers[Symbol.for('nodejs.util.inspect.custom')](), + headers: headers[Symbol.for("nodejs.util.inspect.custom")](), insecureHTTPParser: request.insecureHTTPParser, - agent + agent, }; return requestOptions; diff --git a/packages/fetch/src/response.js b/packages/fetch/src/response.js index ebeba7c..42c82f5 100644 --- a/packages/fetch/src/response.js +++ b/packages/fetch/src/response.js @@ -4,21 +4,21 @@ * Response class provides content decoding */ -import Headers from './headers.js'; -import Body, {clone, extractContentType} from './body.js'; -import {isRedirect} from './utils/is-redirect.js'; +import Headers from "./headers.js"; +import Body, { clone, extractContentType } from "./body.js"; +import { isRedirect } from "./utils/is-redirect.js"; -const INTERNALS = Symbol('Response internals'); +const INTERNALS = Symbol("Response internals"); /** * Response class - * + * * @typedef {Object} Ext * @property {number} [size] * @property {string} [url] * @property {number} [counter] * @property {number} [highWaterMark] - * + * * @implements {globalThis.Response} */ export default class Response extends Body { @@ -32,23 +32,23 @@ export default class Response extends Body { const status = options.status || 200; const headers = new Headers(options.headers); - if (body !== null && !headers.has('Content-Type')) { + if (body !== null && !headers.has("Content-Type")) { const contentType = extractContentType(this); if (contentType) { - headers.append('Content-Type', contentType); + headers.append("Content-Type", contentType); } } /** * @private - */ + */ this[INTERNALS] = { url: options.url, status, - statusText: options.statusText || '', + statusText: options.statusText || "", headers, counter: options.counter || 0, - highWaterMark: options.highWaterMark + highWaterMark: options.highWaterMark, }; } @@ -56,11 +56,11 @@ export default class Response extends Body { * @type {ResponseType} */ get type() { - return "default" + return "default"; } get url() { - return this[INTERNALS].url || ''; + return this[INTERNALS].url || ""; } get status() { @@ -104,7 +104,7 @@ export default class Response extends Body { status: this.status, statusText: this.statusText, headers: this.headers, - size: this.size + size: this.size, }); } @@ -115,29 +115,30 @@ export default class Response extends Body { */ static redirect(url, status = 302) { if (!isRedirect(status)) { - throw new RangeError('Failed to execute "redirect" on "response": Invalid status code'); + throw new RangeError( + 'Failed to execute "redirect" on "response": Invalid status code' + ); } return new Response(null, { headers: { - location: new URL(url).toString() + location: new URL(url).toString(), }, - status + status, }); } get [Symbol.toStringTag]() { - return 'Response'; + return "Response"; } } Object.defineProperties(Response.prototype, { - url: {enumerable: true}, - status: {enumerable: true}, - ok: {enumerable: true}, - redirected: {enumerable: true}, - statusText: {enumerable: true}, - headers: {enumerable: true}, - clone: {enumerable: true} + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + redirected: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true }, }); - diff --git a/packages/fetch/src/utils/form-data.js b/packages/fetch/src/utils/form-data.js index d582a15..ef9f512 100644 --- a/packages/fetch/src/utils/form-data.js +++ b/packages/fetch/src/utils/form-data.js @@ -1,16 +1,17 @@ -import {randomBytes} from 'crypto'; -import { iterateMultipart } from '@web3-storage/multipart-parser'; -import { FormData } from '../package.js'; -import {isBlob} from './is.js'; +import { randomBytes } from "crypto"; +import { iterateMultipart } from "@web3-storage/multipart-parser"; +import { FormData } from "../package.js"; +import { isBlob } from "./is.js"; -const carriage = '\r\n'; -const dashes = '-'.repeat(2); +const carriage = "\r\n"; +const dashes = "-".repeat(2); const carriageLength = Buffer.byteLength(carriage); /** * @param {string} boundary */ -const getFooter = boundary => `${dashes}${boundary}${dashes}${carriage.repeat(2)}`; +const getFooter = (boundary) => + `${dashes}${boundary}${dashes}${carriage.repeat(2)}`; /** * @param {string} boundary @@ -20,15 +21,17 @@ const getFooter = boundary => `${dashes}${boundary}${dashes}${carriage.repeat(2) * @return {string} */ function getHeader(boundary, name, field) { - let header = ''; + let header = ""; header += `${dashes}${boundary}${carriage}`; header += `Content-Disposition: form-data; name="${name}"`; if (isBlob(field)) { - const { name = 'blob', type } = /** @type {Blob & {name?:string}} */ (field); + const { name = "blob", type } = /** @type {Blob & {name?:string}} */ ( + field + ); header += `; filename="${name}"${carriage}`; - header += `Content-Type: ${type || 'application/octet-stream'}`; + header += `Content-Type: ${type || "application/octet-stream"}`; } return `${header}${carriage.repeat(2)}`; @@ -37,20 +40,20 @@ function getHeader(boundary, name, field) { /** * @return {string} */ -export const getBoundary = () => randomBytes(8).toString('hex'); +export const getBoundary = () => randomBytes(8).toString("hex"); /** * @param {FormData} form * @param {string} boundary */ -export async function * formDataIterator(form, boundary) { +export async function* formDataIterator(form, boundary) { const encoder = new TextEncoder(); for (const [name, value] of form) { yield encoder.encode(getHeader(boundary, name, value)); if (isBlob(value)) { // @ts-ignore - we know our streams implement aysnc iteration - yield * value.stream(); + yield* value.stream(); } else { yield encoder.encode(value); } @@ -89,29 +92,32 @@ export function getFormDataLength(form, boundary) { * @param {Body & {headers?:Headers}} source */ export const toFormData = async (source) => { - let { body, headers } = source; - const contentType = headers?.get('Content-Type') || '' + let { body, headers } = source; + const contentType = headers?.get("Content-Type") || ""; - if (contentType.startsWith('application/x-www-form-urlencoded') && body != null) { + if ( + contentType.startsWith("application/x-www-form-urlencoded") && + body != null + ) { const form = new FormData(); let bodyText = await source.text(); new URLSearchParams(bodyText).forEach((v, k) => form.append(k, v)); return form; - } - - const [type, boundary] = contentType.split(/\s*;\s*boundary=/) - if (type === 'multipart/form-data' && boundary != null && body != null) { - const form = new FormData() - const parts = iterateMultipart(body, boundary) - for await (const { name, data, filename, contentType } of parts) { - if (filename) { - form.append(name, new File([data], filename, { type: contentType })) - } else { - form.append(name, new TextDecoder().decode(data), filename) - } - } - return form - } else { - throw new TypeError('Could not parse content as FormData.') - } -} + } + + const [type, boundary] = contentType.split(/\s*;\s*boundary=/); + if (type === "multipart/form-data" && boundary != null && body != null) { + const form = new FormData(); + const parts = iterateMultipart(body, boundary); + for await (const { name, data, filename, contentType } of parts) { + if (filename) { + form.append(name, new File([data], filename, { type: contentType })); + } else { + form.append(name, new TextDecoder().decode(data), filename); + } + } + return form; + } else { + throw new TypeError("Could not parse content as FormData."); + } +}; diff --git a/packages/fetch/src/utils/get-search.js b/packages/fetch/src/utils/get-search.js index 341f82c..e2b5683 100644 --- a/packages/fetch/src/utils/get-search.js +++ b/packages/fetch/src/utils/get-search.js @@ -1,13 +1,14 @@ /** - * @param {URL} parsedURL + * @param {URL} parsedURL * @returns {string} */ -export const getSearch = parsedURL => { +export const getSearch = (parsedURL) => { if (parsedURL.search) { return parsedURL.search; } const lastOffset = parsedURL.href.length - 1; - const hash = parsedURL.hash || (parsedURL.href[lastOffset] === '#' ? '#' : ''); - return parsedURL.href[lastOffset - hash.length] === '?' ? '?' : ''; + const hash = + parsedURL.hash || (parsedURL.href[lastOffset] === "#" ? "#" : ""); + return parsedURL.href[lastOffset - hash.length] === "?" ? "?" : ""; }; diff --git a/packages/fetch/src/utils/is-redirect.js b/packages/fetch/src/utils/is-redirect.js index d1347f0..55d3298 100644 --- a/packages/fetch/src/utils/is-redirect.js +++ b/packages/fetch/src/utils/is-redirect.js @@ -6,6 +6,6 @@ const redirectStatus = new Set([301, 302, 303, 307, 308]); * @param {number} code - Status code * @return {boolean} */ -export const isRedirect = code => { +export const isRedirect = (code) => { return redirectStatus.has(code); }; diff --git a/packages/fetch/src/utils/utf8.js b/packages/fetch/src/utils/utf8.js index 42a3b50..12b2a0c 100644 --- a/packages/fetch/src/utils/utf8.js +++ b/packages/fetch/src/utils/utf8.js @@ -1,4 +1,4 @@ -import {TextEncoder, TextDecoder} from 'util'; +import { TextEncoder, TextDecoder } from "util"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); @@ -6,9 +6,9 @@ const decoder = new TextDecoder(); /** * @param {string} text */ -export const encode = text => encoder.encode(text); +export const encode = (text) => encoder.encode(text); /** * @param {Uint8Array} bytes */ -export const decode = bytes => decoder.decode(bytes); +export const decode = (bytes) => decoder.decode(bytes); diff --git a/packages/fetch/test/commonjs/package.json b/packages/fetch/test/commonjs/package.json index a0df0c8..5bbefff 100644 --- a/packages/fetch/test/commonjs/package.json +++ b/packages/fetch/test/commonjs/package.json @@ -1,3 +1,3 @@ { - "type": "commonjs" + "type": "commonjs" } diff --git a/packages/fetch/test/commonjs/test-artifact.js b/packages/fetch/test/commonjs/test-artifact.js index db6f11b..18e147b 100644 --- a/packages/fetch/test/commonjs/test-artifact.js +++ b/packages/fetch/test/commonjs/test-artifact.js @@ -1,30 +1,30 @@ // @ts-nocheck -const assert = require('assert'); -const fetch = require('@remix-run/web-fetch'); +const assert = require("assert"); +const fetch = require("@remix-run/web-fetch"); assert.strictEqual( typeof fetch, - 'function', - 'default import must be a function' + "function", + "default import must be a function" ); -const {Request, Response, Headers} = require('@remix-run/web-fetch'); +const { Request, Response, Headers } = require("@remix-run/web-fetch"); assert.ok( - new Request('https://www.test.com').headers instanceof Headers, - 'Request class is not exposing correct functionality' + new Request("https://www.test.com").headers instanceof Headers, + "Request class is not exposing correct functionality" ); assert.strictEqual( - new Response(null, {headers: {a: 'a'}}).headers.get('a'), - 'a', - 'Response class is not exposing correct functionality' + new Response(null, { headers: { a: "a" } }).headers.get("a"), + "a", + "Response class is not exposing correct functionality" ); fetch( - `data:text/plain;base64,${Buffer.from('Hello World!').toString('base64')}` + `data:text/plain;base64,${Buffer.from("Hello World!").toString("base64")}` ) - .then(res => res.text()) - .then(text => assert.strictEqual(text, 'Hello World!')) + .then((res) => res.text()) + .then((text) => assert.strictEqual(text, "Hello World!")) .then(() => { - console.log('CommonJS build artifact fitness tests successfully'); + console.log("CommonJS build artifact fitness tests successfully"); }); diff --git a/packages/fetch/test/external-encoding.js b/packages/fetch/test/external-encoding.js index 64ce161..1e6aeda 100644 --- a/packages/fetch/test/external-encoding.js +++ b/packages/fetch/test/external-encoding.js @@ -1,42 +1,50 @@ -import fetch from '@remix-run/web-fetch'; -import chai from 'chai'; +import fetch from "@remix-run/web-fetch"; +import chai from "chai"; -const {expect} = chai; +const { expect } = chai; -describe('external encoding', () => { - describe('data uri', () => { - it('should accept base64-encoded gif data uri', () => { - return fetch('data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=').then(r => { +describe("external encoding", () => { + describe("data uri", () => { + it("should accept base64-encoded gif data uri", () => { + return fetch( + "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" + ).then((r) => { expect(r.status).to.equal(200); - expect(r.headers.get('Content-Type')).to.equal('image/gif'); + expect(r.headers.get("Content-Type")).to.equal("image/gif"); - return r.arrayBuffer().then(b => { + return r.arrayBuffer().then((b) => { expect(b).to.be.an.instanceOf(ArrayBuffer); }); }); }); - it('should accept data uri with specified charset', async () => { - const r = await fetch('data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678'); + it("should accept data uri with specified charset", async () => { + const r = await fetch( + "data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678" + ); expect(r.status).to.equal(200); - expect(r.headers.get('Content-Type')).to.equal('text/plain;charset=UTF-8;page=21'); + expect(r.headers.get("Content-Type")).to.equal( + "text/plain;charset=UTF-8;page=21" + ); const b = await r.text(); - expect(b).to.equal('the data:1234,5678'); + expect(b).to.equal("the data:1234,5678"); }); - it('should accept data uri of plain text', () => { - return fetch('data:,Hello%20World!').then(r => { + it("should accept data uri of plain text", () => { + return fetch("data:,Hello%20World!").then((r) => { expect(r.status).to.equal(200); - expect(r.headers.get('Content-Type')).to.equal('text/plain;charset=US-ASCII'); - return r.text().then(t => expect(t).to.equal('Hello World!')); + expect(r.headers.get("Content-Type")).to.equal( + "text/plain;charset=US-ASCII" + ); + return r.text().then((t) => expect(t).to.equal("Hello World!")); }); }); - it('should reject invalid data uri', () => { - return fetch('data:@@@@').catch(error => { + it("should reject invalid data uri", () => { + return fetch("data:@@@@").catch((error) => { expect(error).to.exist; - expect(error.message).to.include('malformed data: URI'); + expect(error.message).to.include("malformed data: URI"); }); }); }); diff --git a/packages/fetch/test/file.js b/packages/fetch/test/file.js index 33c850f..9a7a2d1 100644 --- a/packages/fetch/test/file.js +++ b/packages/fetch/test/file.js @@ -1,13 +1,14 @@ -import fetch from '@remix-run/web-fetch' -import { assert } from "chai" +import fetch from "@remix-run/web-fetch"; +import { assert } from "chai"; describe("can fetch local files", () => { - it("can fetch local file", async () => { - const response = await fetch(import.meta.url) - assert.equal(response.headers.get('content-type'), "application/javascript") - const code = await response.text() - - assert.ok(code.includes('it("can fetch local file"')) - }) -}) - + it("can fetch local file", async () => { + const response = await fetch(import.meta.url); + assert.equal( + response.headers.get("content-type"), + "application/javascript" + ); + const code = await response.text(); + assert.ok(code.includes('it("can fetch local file"')); + }); +}); diff --git a/packages/fetch/test/form-data.js b/packages/fetch/test/form-data.js index 199444b..1cbcfc2 100644 --- a/packages/fetch/test/form-data.js +++ b/packages/fetch/test/form-data.js @@ -1,104 +1,124 @@ -import FormData from 'formdata-node'; -import {Blob} from '@remix-run/web-blob'; +import FormData from "formdata-node"; +import { Blob } from "@remix-run/web-blob"; -import chai from 'chai'; +import chai from "chai"; -import read from './utils/read-stream.js'; +import read from "./utils/read-stream.js"; -import {getFormDataLength, getBoundary, formDataIterator} from '../src/utils/form-data.js'; +import { + getFormDataLength, + getBoundary, + formDataIterator, +} from "../src/utils/form-data.js"; -const {expect} = chai; +const { expect } = chai; -const carriage = '\r\n'; +const carriage = "\r\n"; -const getFooter = boundary => `--${boundary}--${carriage.repeat(2)}`; +const getFooter = (boundary) => `--${boundary}--${carriage.repeat(2)}`; -describe('FormData', () => { - it('should return a length for empty form-data', () => { +describe("FormData", () => { + it("should return a length for empty form-data", () => { const form = new FormData(); const boundary = getBoundary(); - expect(getFormDataLength(form, boundary)).to.be.equal(Buffer.byteLength(getFooter(boundary))); + expect(getFormDataLength(form, boundary)).to.be.equal( + Buffer.byteLength(getFooter(boundary)) + ); }); - it('should add a Blob field\'s size to the FormData length', () => { + it("should add a Blob field's size to the FormData length", () => { const form = new FormData(); const boundary = getBoundary(); - const string = 'Hello, world!'; + const string = "Hello, world!"; const expected = Buffer.byteLength( `--${boundary}${carriage}` + - `Content-Disposition: form-data; name="field"${carriage.repeat(2)}` + - string + - `${carriage}${getFooter(boundary)}` + `Content-Disposition: form-data; name="field"${carriage.repeat(2)}` + + string + + `${carriage}${getFooter(boundary)}` ); - form.set('field', string); + form.set("field", string); expect(getFormDataLength(form, boundary)).to.be.equal(expected); }); - it('should return a length for a Blob field', () => { + it("should return a length for a Blob field", () => { const form = new FormData(); const boundary = getBoundary(); - const blob = new Blob(['Hello, world!'], {type: 'text/plain'}); + const blob = new Blob(["Hello, world!"], { type: "text/plain" }); - form.set('blob', blob); + form.set("blob", blob); - const expected = blob.size + Buffer.byteLength( - `--${boundary}${carriage}` + - 'Content-Disposition: form-data; name="blob"; ' + - `filename="blob"${carriage}Content-Type: text/plain` + - `${carriage.repeat(3)}${getFooter(boundary)}` - ); + const expected = + blob.size + + Buffer.byteLength( + `--${boundary}${carriage}` + + 'Content-Disposition: form-data; name="blob"; ' + + `filename="blob"${carriage}Content-Type: text/plain` + + `${carriage.repeat(3)}${getFooter(boundary)}` + ); expect(getFormDataLength(form, boundary)).to.be.equal(expected); }); - it('should create a body from empty form-data', async () => { + it("should create a body from empty form-data", async () => { const form = new FormData(); const boundary = getBoundary(); - expect(String(await read(formDataIterator(form, boundary)))).to.be.equal(getFooter(boundary)); + expect(String(await read(formDataIterator(form, boundary)))).to.be.equal( + getFooter(boundary) + ); }); - it('should set default content-type', async () => { + it("should set default content-type", async () => { const form = new FormData(); const boundary = getBoundary(); - form.set('blob', new Blob([])); + form.set("blob", new Blob([])); - expect(String(await read(formDataIterator(form, boundary)))).to.contain('Content-Type: application/octet-stream'); + expect(String(await read(formDataIterator(form, boundary)))).to.contain( + "Content-Type: application/octet-stream" + ); }); - it('should create a body with a FormData field', async () => { + it("should create a body with a FormData field", async () => { const form = new FormData(); const boundary = getBoundary(); - const string = 'Hello, World!'; + const string = "Hello, World!"; - form.set('field', string); + form.set("field", string); - const expected = `--${boundary}${carriage}` + - `Content-Disposition: form-data; name="field"${carriage.repeat(2)}` + - string + - `${carriage}${getFooter(boundary)}`; + const expected = + `--${boundary}${carriage}` + + `Content-Disposition: form-data; name="field"${carriage.repeat(2)}` + + string + + `${carriage}${getFooter(boundary)}`; - expect(String(await read(formDataIterator(form, boundary)))).to.be.equal(expected); + expect(String(await read(formDataIterator(form, boundary)))).to.be.equal( + expected + ); }); - it('should create a body with a FormData Blob field', async () => { + it("should create a body with a FormData Blob field", async () => { const form = new FormData(); const boundary = getBoundary(); - const expected = `--${boundary}${carriage}` + - 'Content-Disposition: form-data; name="blob"; ' + - `filename="blob"${carriage}Content-Type: text/plain${carriage.repeat(2)}` + - 'Hello, World!' + - `${carriage}${getFooter(boundary)}`; + const expected = + `--${boundary}${carriage}` + + 'Content-Disposition: form-data; name="blob"; ' + + `filename="blob"${carriage}Content-Type: text/plain${carriage.repeat( + 2 + )}` + + "Hello, World!" + + `${carriage}${getFooter(boundary)}`; - form.set('blob', new Blob(['Hello, World!'], {type: 'text/plain'})); + form.set("blob", new Blob(["Hello, World!"], { type: "text/plain" })); - expect(String(await read(formDataIterator(form, boundary)))).to.be.equal(expected); + expect(String(await read(formDataIterator(form, boundary)))).to.be.equal( + expected + ); }); }); diff --git a/packages/fetch/test/headers.js b/packages/fetch/test/headers.js index 28bc70d..e250ad6 100644 --- a/packages/fetch/test/headers.js +++ b/packages/fetch/test/headers.js @@ -1,14 +1,14 @@ -import util from 'util'; -import {Headers} from '@remix-run/web-fetch'; -import chai from 'chai'; -import chaiIterator from 'chai-iterator'; +import util from "util"; +import { Headers } from "@remix-run/web-fetch"; +import chai from "chai"; +import chaiIterator from "chai-iterator"; chai.use(chaiIterator); -const {expect} = chai; +const { expect } = chai; -describe('Headers', () => { - it('should have attributes conforming to Web IDL', () => { +describe("Headers", () => { + it("should have attributes conforming to Web IDL", () => { const headers = new Headers(); expect(Object.getOwnPropertyNames(headers)).to.be.empty; const enumerableProperties = []; @@ -18,28 +18,28 @@ describe('Headers', () => { } for (const toCheck of [ - 'append', - 'delete', - 'entries', - 'forEach', - 'get', - 'has', - 'keys', - 'set', - 'values' + "append", + "delete", + "entries", + "forEach", + "get", + "has", + "keys", + "set", + "values", ]) { expect(enumerableProperties).to.contain(toCheck); } }); - it('should allow iterating through all headers with forEach', () => { + it("should allow iterating through all headers with forEach", () => { const headers = new Headers([ - ['b', '2'], - ['c', '4'], - ['b', '3'], - ['a', '1'] + ["b", "2"], + ["c", "4"], + ["b", "3"], + ["a", "1"], ]); - expect(headers).to.have.property('forEach'); + expect(headers).to.have.property("forEach"); const result = []; headers.forEach((value, key) => { @@ -47,50 +47,58 @@ describe('Headers', () => { }); expect(result).to.deep.equal([ - ['a', '1'], - ['b', '2, 3'], - ['c', '4'] + ["a", "1"], + ["b", "2, 3"], + ["c", "4"], ]); }); - it('should be iterable with forEach', () => { + it("should be iterable with forEach", () => { const headers = new Headers(); - headers.append('Accept', 'application/json'); - headers.append('Accept', 'text/plain'); - headers.append('Content-Type', 'text/html'); + headers.append("Accept", "application/json"); + headers.append("Accept", "text/plain"); + headers.append("Content-Type", "text/html"); const results = []; headers.forEach((value, key, object) => { - results.push({value, key, object}); + results.push({ value, key, object }); }); expect(results.length).to.equal(2); - expect({key: 'accept', value: 'application/json, text/plain', object: headers}).to.deep.equal(results[0]); - expect({key: 'content-type', value: 'text/html', object: headers}).to.deep.equal(results[1]); + expect({ + key: "accept", + value: "application/json, text/plain", + object: headers, + }).to.deep.equal(results[0]); + expect({ + key: "content-type", + value: "text/html", + object: headers, + }).to.deep.equal(results[1]); }); it('should set "this" to undefined by default on forEach', () => { - const headers = new Headers({Accept: 'application/json'}); + const headers = new Headers({ Accept: "application/json" }); headers.forEach(function () { expect(this).to.be.undefined; }); }); - it('should accept thisArg as a second argument for forEach', () => { - const headers = new Headers({Accept: 'application/json'}); + it("should accept thisArg as a second argument for forEach", () => { + const headers = new Headers({ Accept: "application/json" }); const thisArg = {}; headers.forEach(function () { expect(this).to.equal(thisArg); }, thisArg); }); - it('should allow iterating through all headers with for-of loop', () => { + it("should allow iterating through all headers with for-of loop", () => { const headers = new Headers([ - ['b', '2'], - ['c', '4'], - ['a', '1'] + ["b", "2"], + ["c", "4"], + ["a", "1"], ]); - headers.append('b', '3'); + headers.append("b", "3"); expect(headers).to.be.iterable; const result = []; @@ -99,182 +107,190 @@ describe('Headers', () => { } expect(result).to.deep.equal([ - ['a', '1'], - ['b', '2, 3'], - ['c', '4'] + ["a", "1"], + ["b", "2, 3"], + ["c", "4"], ]); }); - it('should allow iterating through all headers with entries()', () => { + it("should allow iterating through all headers with entries()", () => { const headers = new Headers([ - ['b', '2'], - ['c', '4'], - ['a', '1'] + ["b", "2"], + ["c", "4"], + ["a", "1"], + ]); + headers.append("b", "3"); + + expect(headers.entries()).to.be.iterable.and.to.deep.iterate.over([ + ["a", "1"], + ["b", "2, 3"], + ["c", "4"], ]); - headers.append('b', '3'); - - expect(headers.entries()).to.be.iterable - .and.to.deep.iterate.over([ - ['a', '1'], - ['b', '2, 3'], - ['c', '4'] - ]); }); - it('should allow iterating through all headers with keys()', () => { + it("should allow iterating through all headers with keys()", () => { const headers = new Headers([ - ['b', '2'], - ['c', '4'], - ['a', '1'] + ["b", "2"], + ["c", "4"], + ["a", "1"], ]); - headers.append('b', '3'); + headers.append("b", "3"); - expect(headers.keys()).to.be.iterable - .and.to.iterate.over(['a', 'b', 'c']); + expect(headers.keys()).to.be.iterable.and.to.iterate.over(["a", "b", "c"]); }); - it('should allow iterating through all headers with values()', () => { + it("should allow iterating through all headers with values()", () => { const headers = new Headers([ - ['b', '2'], - ['c', '4'], - ['a', '1'] + ["b", "2"], + ["c", "4"], + ["a", "1"], ]); - headers.append('b', '3'); + headers.append("b", "3"); - expect(headers.values()).to.be.iterable - .and.to.iterate.over(['1', '2, 3', '4']); + expect(headers.values()).to.be.iterable.and.to.iterate.over([ + "1", + "2, 3", + "4", + ]); }); - it('should reject illegal header', () => { + it("should reject illegal header", () => { const headers = new Headers(); - expect(() => new Headers({'He y': 'ok'})).to.throw(TypeError); - expect(() => new Headers({'Hé-y': 'ok'})).to.throw(TypeError); - expect(() => new Headers({'He-y': 'ăk'})).to.throw(TypeError); - expect(() => headers.append('Hé-y', 'ok')).to.throw(TypeError); - expect(() => headers.delete('Hé-y')).to.throw(TypeError); - expect(() => headers.get('Hé-y')).to.throw(TypeError); - expect(() => headers.has('Hé-y')).to.throw(TypeError); - expect(() => headers.set('Hé-y', 'ok')).to.throw(TypeError); + expect(() => new Headers({ "He y": "ok" })).to.throw(TypeError); + expect(() => new Headers({ "Hé-y": "ok" })).to.throw(TypeError); + expect(() => new Headers({ "He-y": "ăk" })).to.throw(TypeError); + expect(() => headers.append("Hé-y", "ok")).to.throw(TypeError); + expect(() => headers.delete("Hé-y")).to.throw(TypeError); + expect(() => headers.get("Hé-y")).to.throw(TypeError); + expect(() => headers.has("Hé-y")).to.throw(TypeError); + expect(() => headers.set("Hé-y", "ok")).to.throw(TypeError); // Should reject empty header - expect(() => headers.append('', 'ok')).to.throw(TypeError); + expect(() => headers.append("", "ok")).to.throw(TypeError); }); - it('should ignore unsupported attributes while reading headers', () => { - const FakeHeader = function () { }; + it("should ignore unsupported attributes while reading headers", () => { + const FakeHeader = function () {}; // Prototypes are currently ignored // This might change in the future: #181 - FakeHeader.prototype.z = 'fake'; + FakeHeader.prototype.z = "fake"; const res = new FakeHeader(); - res.a = 'string'; - res.b = ['1', '2']; - res.c = ''; + res.a = "string"; + res.b = ["1", "2"]; + res.c = ""; res.d = []; res.e = 1; res.f = [1, 2]; - res.g = {a: 1}; + res.g = { a: 1 }; res.h = undefined; res.i = null; res.j = Number.NaN; res.k = true; res.l = false; - res.m = Buffer.from('test'); + res.m = Buffer.from("test"); const h1 = new Headers(res); - h1.set('n', [1, 2]); - h1.append('n', ['3', 4]); + h1.set("n", [1, 2]); + h1.append("n", ["3", 4]); const h1Raw = h1.raw(); - expect(h1Raw.a).to.include('string'); - expect(h1Raw.b).to.include('1,2'); - expect(h1Raw.c).to.include(''); - expect(h1Raw.d).to.include(''); - expect(h1Raw.e).to.include('1'); - expect(h1Raw.f).to.include('1,2'); - expect(h1Raw.g).to.include('[object Object]'); - expect(h1Raw.h).to.include('undefined'); - expect(h1Raw.i).to.include('null'); - expect(h1Raw.j).to.include('NaN'); - expect(h1Raw.k).to.include('true'); - expect(h1Raw.l).to.include('false'); - expect(h1Raw.m).to.include('test'); - expect(h1Raw.n).to.include('1,2'); - expect(h1Raw.n).to.include('3,4'); + expect(h1Raw.a).to.include("string"); + expect(h1Raw.b).to.include("1,2"); + expect(h1Raw.c).to.include(""); + expect(h1Raw.d).to.include(""); + expect(h1Raw.e).to.include("1"); + expect(h1Raw.f).to.include("1,2"); + expect(h1Raw.g).to.include("[object Object]"); + expect(h1Raw.h).to.include("undefined"); + expect(h1Raw.i).to.include("null"); + expect(h1Raw.j).to.include("NaN"); + expect(h1Raw.k).to.include("true"); + expect(h1Raw.l).to.include("false"); + expect(h1Raw.m).to.include("test"); + expect(h1Raw.n).to.include("1,2"); + expect(h1Raw.n).to.include("3,4"); expect(h1Raw.z).to.be.undefined; }); - it('should wrap headers', () => { + it("should wrap headers", () => { const h1 = new Headers({ - a: '1' + a: "1", }); const h1Raw = h1.raw(); const h2 = new Headers(h1); - h2.set('b', '1'); + h2.set("b", "1"); const h2Raw = h2.raw(); const h3 = new Headers(h2); - h3.append('a', '2'); + h3.append("a", "2"); const h3Raw = h3.raw(); - expect(h1Raw.a).to.include('1'); - expect(h1Raw.a).to.not.include('2'); + expect(h1Raw.a).to.include("1"); + expect(h1Raw.a).to.not.include("2"); - expect(h2Raw.a).to.include('1'); - expect(h2Raw.a).to.not.include('2'); - expect(h2Raw.b).to.include('1'); + expect(h2Raw.a).to.include("1"); + expect(h2Raw.a).to.not.include("2"); + expect(h2Raw.b).to.include("1"); - expect(h3Raw.a).to.include('1'); - expect(h3Raw.a).to.include('2'); - expect(h3Raw.b).to.include('1'); + expect(h3Raw.a).to.include("1"); + expect(h3Raw.a).to.include("2"); + expect(h3Raw.b).to.include("1"); }); - it('should accept headers as an iterable of tuples', () => { + it("should accept headers as an iterable of tuples", () => { let headers; headers = new Headers([ - ['a', '1'], - ['b', '2'], - ['a', '3'] + ["a", "1"], + ["b", "2"], + ["a", "3"], ]); - expect(headers.get('a')).to.equal('1, 3'); - expect(headers.get('b')).to.equal('2'); + expect(headers.get("a")).to.equal("1, 3"); + expect(headers.get("b")).to.equal("2"); headers = new Headers([ - new Set(['a', '1']), - ['b', '2'], - new Map([['a', null], ['3', null]]).keys() + new Set(["a", "1"]), + ["b", "2"], + new Map([ + ["a", null], + ["3", null], + ]).keys(), ]); - expect(headers.get('a')).to.equal('1, 3'); - expect(headers.get('b')).to.equal('2'); - - headers = new Headers(new Map([ - ['a', '1'], - ['b', '2'] - ])); - expect(headers.get('a')).to.equal('1'); - expect(headers.get('b')).to.equal('2'); + expect(headers.get("a")).to.equal("1, 3"); + expect(headers.get("b")).to.equal("2"); + + headers = new Headers( + new Map([ + ["a", "1"], + ["b", "2"], + ]) + ); + expect(headers.get("a")).to.equal("1"); + expect(headers.get("b")).to.equal("2"); }); - it('should throw a TypeError if non-tuple exists in a headers initializer', () => { - expect(() => new Headers([['b', '2', 'huh?']])).to.throw(TypeError); - expect(() => new Headers(['b2'])).to.throw(TypeError); - expect(() => new Headers('b2')).to.throw(TypeError); - expect(() => new Headers({[Symbol.iterator]: 42})).to.throw(TypeError); + it("should throw a TypeError if non-tuple exists in a headers initializer", () => { + expect(() => new Headers([["b", "2", "huh?"]])).to.throw(TypeError); + expect(() => new Headers(["b2"])).to.throw(TypeError); + expect(() => new Headers("b2")).to.throw(TypeError); + expect(() => new Headers({ [Symbol.iterator]: 42 })).to.throw(TypeError); }); - it('should use a custom inspect function', () => { + it("should use a custom inspect function", () => { const headers = new Headers([ - ['Host', 'thehost'], - ['Host', 'notthehost'], - ['a', '1'], - ['b', '2'], - ['a', '3'] + ["Host", "thehost"], + ["Host", "notthehost"], + ["a", "1"], + ["b", "2"], + ["a", "3"], ]); // eslint-disable-next-line quotes - expect(util.format(headers)).to.equal("{ a: [ '1', '3' ], b: '2', host: 'thehost' }"); + expect(util.format(headers)).to.equal( + "{ a: [ '1', '3' ], b: '2', host: 'thehost' }" + ); }); }); diff --git a/packages/fetch/test/main.js b/packages/fetch/test/main.js index cc02728..5c366d9 100644 --- a/packages/fetch/test/main.js +++ b/packages/fetch/test/main.js @@ -1,51 +1,49 @@ // Test tools -import zlib from 'zlib'; -import crypto from 'crypto'; -import http from 'http'; -import fs from 'fs'; -import stream from 'stream'; -import path from 'path'; -import {lookup} from 'dns'; -import vm from 'vm'; -import {TextEncoder} from 'util'; -import chai from 'chai'; -import chaiPromised from 'chai-as-promised'; -import chaiIterator from 'chai-iterator'; -import chaiString from 'chai-string'; -import FormData from 'form-data'; -import FormDataNode from 'formdata-node'; -import delay from 'delay'; -import AbortControllerMysticatea from 'abort-controller'; -import abortControllerPolyfill from 'abortcontroller-polyfill/dist/abortcontroller.js'; -import { ReadableStream } from '../src/package.js'; +import zlib from "zlib"; +import crypto from "crypto"; +import http from "http"; +import fs from "fs"; +import stream from "stream"; +import path from "path"; +import { lookup } from "dns"; +import vm from "vm"; +import { TextEncoder } from "util"; +import chai from "chai"; +import chaiPromised from "chai-as-promised"; +import chaiIterator from "chai-iterator"; +import chaiString from "chai-string"; +import FormData from "form-data"; +import FormDataNode from "formdata-node"; +import delay from "delay"; +import AbortControllerMysticatea from "abort-controller"; +import abortControllerPolyfill from "abortcontroller-polyfill/dist/abortcontroller.js"; +import { ReadableStream } from "../src/package.js"; const AbortControllerPolyfill = abortControllerPolyfill.AbortController; // Test subjects -import {Blob} from '@remix-run/web-blob'; - -import fetch, { - Headers, - Request, - Response -} from '@remix-run/web-fetch'; -import {FetchError} from '../src/errors/fetch-error.js'; -import HeadersOrig, {fromRawHeaders} from '../src/headers.js'; -import RequestOrig from '../src/request.js'; -import ResponseOrig from '../src/response.js'; -import Body, {getTotalBytes, extractContentType, streamIterator} from '../src/body.js'; -import TestServer from './utils/server.js'; - -const { - Uint8Array: VMUint8Array -} = vm.runInNewContext('this'); - -import chaiTimeout from './utils/chai-timeout.js'; +import { Blob } from "@remix-run/web-blob"; + +import fetch, { Headers, Request, Response } from "@remix-run/web-fetch"; +import { FetchError } from "../src/errors/fetch-error.js"; +import HeadersOrig, { fromRawHeaders } from "../src/headers.js"; +import RequestOrig from "../src/request.js"; +import ResponseOrig from "../src/response.js"; +import Body, { + getTotalBytes, + extractContentType, + streamIterator, +} from "../src/body.js"; +import TestServer from "./utils/server.js"; + +const { Uint8Array: VMUint8Array } = vm.runInNewContext("this"); + +import chaiTimeout from "./utils/chai-timeout.js"; chai.use(chaiPromised); chai.use(chaiIterator); chai.use(chaiString); chai.use(chaiTimeout); -const {expect} = chai; +const { expect } = chai; /** * @template T @@ -68,7 +66,7 @@ async function collectStream(stream) { return chunks; } -describe('node-fetch', () => { +describe("node-fetch", () => { const local = new TestServer(); let base; @@ -81,69 +79,82 @@ describe('node-fetch', () => { return local.stop(); }); - it('should return a promise', () => { + it("should return a promise", () => { const url = `${base}hello`; const p = fetch(url); expect(p).to.be.an.instanceof(Promise); - expect(p).to.have.property('then'); + expect(p).to.have.property("then"); }); - it('should expose Headers, Response and Request constructors', () => { + it("should expose Headers, Response and Request constructors", () => { expect(Headers).to.equal(HeadersOrig); expect(Response).to.equal(ResponseOrig); expect(Request).to.equal(RequestOrig); }); - it('should support proper toString output for Headers, Response and Request objects', () => { - expect(new Headers().toString()).to.equal('[object Headers]'); - expect(new Response().toString()).to.equal('[object Response]'); - expect(new Request(base).toString()).to.equal('[object Request]'); + it("should support proper toString output for Headers, Response and Request objects", () => { + expect(new Headers().toString()).to.equal("[object Headers]"); + expect(new Response().toString()).to.equal("[object Response]"); + expect(new Request(base).toString()).to.equal("[object Request]"); }); - it('should reject with error if url is protocol relative', () => { - const url = '//example.com/'; - return expect(fetch(url)).to.eventually.be.rejectedWith(TypeError, /Invalid URL/); + it("should reject with error if url is protocol relative", () => { + const url = "//example.com/"; + return expect(fetch(url)).to.eventually.be.rejectedWith( + TypeError, + /Invalid URL/ + ); }); - it('should reject with error if url is relative path', () => { - const url = '/some/path'; - return expect(fetch(url)).to.eventually.be.rejectedWith(TypeError, /Invalid URL/); + it("should reject with error if url is relative path", () => { + const url = "/some/path"; + return expect(fetch(url)).to.eventually.be.rejectedWith( + TypeError, + /Invalid URL/ + ); }); - it('should reject with error if protocol is unsupported', () => { - const url = 'ftp://example.com/'; - return expect(fetch(url)).to.eventually.be.rejectedWith(TypeError, /URL scheme "ftp" is not supported/); + it("should reject with error if protocol is unsupported", () => { + const url = "ftp://example.com/"; + return expect(fetch(url)).to.eventually.be.rejectedWith( + TypeError, + /URL scheme "ftp" is not supported/ + ); }); - it('should reject with error on network failure', function () { + it("should reject with error on network failure", function () { this.timeout(5000); - const url = 'http://localhost:50000/'; - return expect(fetch(url)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.include({type: 'system', code: 'ECONNREFUSED', errno: 'ECONNREFUSED'}); + const url = "http://localhost:50000/"; + return expect(fetch(url)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.include({ + type: "system", + code: "ECONNREFUSED", + errno: "ECONNREFUSED", + }); }); - it('error should contain system error if one occurred', () => { - const err = new FetchError('a message', 'system', new Error('an error')); - return expect(err).to.have.property('erroredSysCall'); + it("error should contain system error if one occurred", () => { + const err = new FetchError("a message", "system", new Error("an error")); + return expect(err).to.have.property("erroredSysCall"); }); - it('error should not contain system error if none occurred', () => { - const err = new FetchError('a message', 'a type'); - return expect(err).to.not.have.property('erroredSysCall'); + it("error should not contain system error if none occurred", () => { + const err = new FetchError("a message", "a type"); + return expect(err).to.not.have.property("erroredSysCall"); }); - it('system error is extracted from failed requests', function () { + it("system error is extracted from failed requests", function () { this.timeout(5000); - const url = 'http://localhost:50000/'; - return expect(fetch(url)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('erroredSysCall'); + const url = "http://localhost:50000/"; + return expect(fetch(url)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("erroredSysCall"); }); - it('should resolve into response', () => { + it("should resolve into response", () => { const url = `${base}hello`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res).to.be.an.instanceof(Response); expect(res.headers).to.be.an.instanceof(Headers); expect(res.body).to.be.an.instanceof(ReadableStream); @@ -152,751 +163,773 @@ describe('node-fetch', () => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); - expect(res.statusText).to.equal('OK'); + expect(res.statusText).to.equal("OK"); }); }); - it('Response.redirect should resolve into response', () => { - const res = Response.redirect('http://localhost'); + it("Response.redirect should resolve into response", () => { + const res = Response.redirect("http://localhost"); expect(res).to.be.an.instanceof(Response); expect(res.headers).to.be.an.instanceof(Headers); - expect(res.headers.get('location')).to.equal('http://localhost/'); + expect(res.headers.get("location")).to.equal("http://localhost/"); expect(res.status).to.equal(302); }); - it('Response.redirect /w invalid url should fail', () => { + it("Response.redirect /w invalid url should fail", () => { expect(() => { - Response.redirect('localhost'); + Response.redirect("localhost"); }).to.throw(); }); - it('Response.redirect /w invalid status should fail', () => { + it("Response.redirect /w invalid status should fail", () => { expect(() => { - Response.redirect('http://localhost', 200); + Response.redirect("http://localhost", 200); }).to.throw(); }); - it('should accept plain text response', () => { + it("should accept plain text response", () => { const url = `${base}plain`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { expect(res.bodyUsed).to.be.true; - expect(result).to.be.a('string'); - expect(result).to.equal('text'); + expect(result).to.be.a("string"); + expect(result).to.equal("text"); }); }); }); - it('should accept html response (like plain text)', () => { + it("should accept html response (like plain text)", () => { const url = `${base}html`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/html'); - return res.text().then(result => { + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/html"); + return res.text().then((result) => { expect(res.bodyUsed).to.be.true; - expect(result).to.be.a('string'); - expect(result).to.equal(''); + expect(result).to.be.a("string"); + expect(result).to.equal(""); }); }); }); - it('should accept json response', () => { + it("should accept json response", () => { const url = `${base}json`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('application/json'); - return res.json().then(result => { + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("application/json"); + return res.json().then((result) => { expect(res.bodyUsed).to.be.true; - expect(result).to.be.an('object'); - expect(result).to.deep.equal({name: 'value'}); + expect(result).to.be.an("object"); + expect(result).to.deep.equal({ name: "value" }); }); }); }); - it('should send request with custom headers', () => { + it("should send request with custom headers", () => { const url = `${base}inspect`; const options = { - headers: {'x-custom-header': 'abc'} + headers: { "x-custom-header": "abc" }, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.headers['x-custom-header']).to.equal('abc'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.headers["x-custom-header"]).to.equal("abc"); + }); }); - it('should accept headers instance', () => { + it("should accept headers instance", () => { const url = `${base}inspect`; const options = { - headers: new Headers({'x-custom-header': 'abc'}) + headers: new Headers({ "x-custom-header": "abc" }), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.headers['x-custom-header']).to.equal('abc'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.headers["x-custom-header"]).to.equal("abc"); + }); }); - it('should accept custom host header', () => { + it("should accept custom host header", () => { const url = `${base}inspect`; const options = { headers: { - host: 'example.com' - } + host: "example.com", + }, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.headers.host).to.equal('example.com'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.headers.host).to.equal("example.com"); + }); }); - it('should accept custom HoSt header', () => { + it("should accept custom HoSt header", () => { const url = `${base}inspect`; const options = { headers: { - HoSt: 'example.com' - } + HoSt: "example.com", + }, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.headers.host).to.equal('example.com'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.headers.host).to.equal("example.com"); + }); }); - it('should follow redirect code 301', () => { + it("should follow redirect code 301", () => { const url = `${base}redirect/301`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); expect(res.ok).to.be.true; }); }); - it('should follow redirect code 302', () => { + it("should follow redirect code 302", () => { const url = `${base}redirect/302`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should follow redirect code 303', () => { + it("should follow redirect code 303", () => { const url = `${base}redirect/303`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should follow redirect code 307', () => { + it("should follow redirect code 307", () => { const url = `${base}redirect/307`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should follow redirect code 308', () => { + it("should follow redirect code 308", () => { const url = `${base}redirect/308`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should follow redirect chain', () => { + it("should follow redirect chain", () => { const url = `${base}redirect/chain`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should follow POST request redirect code 301 with GET', () => { + it("should follow POST request redirect code 301 with GET", () => { const url = `${base}redirect/301`; const options = { - method: 'POST', - body: 'a=1' + method: "POST", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(result => { - expect(result.method).to.equal('GET'); - expect(result.body).to.equal(''); + return res.json().then((result) => { + expect(result.method).to.equal("GET"); + expect(result.body).to.equal(""); }); }); }); - it('should follow PATCH request redirect code 301 with PATCH', () => { + it("should follow PATCH request redirect code 301 with PATCH", () => { const url = `${base}redirect/301`; const options = { - method: 'PATCH', - body: 'a=1' + method: "PATCH", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(res => { - expect(res.method).to.equal('PATCH'); - expect(res.body).to.equal('a=1'); + return res.json().then((res) => { + expect(res.method).to.equal("PATCH"); + expect(res.body).to.equal("a=1"); }); }); }); - it('should follow POST request redirect code 302 with GET', () => { + it("should follow POST request redirect code 302 with GET", () => { const url = `${base}redirect/302`; const options = { - method: 'POST', - body: 'a=1' + method: "POST", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(result => { - expect(result.method).to.equal('GET'); - expect(result.body).to.equal(''); + return res.json().then((result) => { + expect(result.method).to.equal("GET"); + expect(result.body).to.equal(""); }); }); }); - it('should follow PATCH request redirect code 302 with PATCH', () => { + it("should follow PATCH request redirect code 302 with PATCH", () => { const url = `${base}redirect/302`; const options = { - method: 'PATCH', - body: 'a=1' + method: "PATCH", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(res => { - expect(res.method).to.equal('PATCH'); - expect(res.body).to.equal('a=1'); + return res.json().then((res) => { + expect(res.method).to.equal("PATCH"); + expect(res.body).to.equal("a=1"); }); }); }); - it('should follow redirect code 303 with GET', () => { + it("should follow redirect code 303 with GET", () => { const url = `${base}redirect/303`; const options = { - method: 'PUT', - body: 'a=1' + method: "PUT", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(result => { - expect(result.method).to.equal('GET'); - expect(result.body).to.equal(''); + return res.json().then((result) => { + expect(result.method).to.equal("GET"); + expect(result.body).to.equal(""); }); }); }); - it('should follow PATCH request redirect code 307 with PATCH', () => { + it("should follow PATCH request redirect code 307 with PATCH", () => { const url = `${base}redirect/307`; const options = { - method: 'PATCH', - body: 'a=1' + method: "PATCH", + body: "a=1", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); - return res.json().then(result => { - expect(result.method).to.equal('PATCH'); - expect(result.body).to.equal('a=1'); + return res.json().then((result) => { + expect(result.method).to.equal("PATCH"); + expect(result.body).to.equal("a=1"); }); }); }); - it('should not follow non-GET redirect if body is a readable stream', async () => { + it("should not follow non-GET redirect if body is a readable stream", async () => { const url = `${base}redirect/307`; const options = { - method: 'PATCH', - body: stream.Readable.from('tada') + method: "PATCH", + body: stream.Readable.from("tada"), }; - return expect(fetch(url, options)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'unsupported-redirect'); + return expect(fetch(url, options)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "unsupported-redirect"); }); - it('should obey maximum redirect, reject case', () => { + it("should obey maximum redirect, reject case", () => { const url = `${base}redirect/chain`; const options = { - follow: 1 + follow: 1, }; - return expect(fetch(url, options)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'max-redirect'); + return expect(fetch(url, options)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "max-redirect"); }); - it('should obey redirect chain, resolve case', () => { + it("should obey redirect chain, resolve case", () => { const url = `${base}redirect/chain`; const options = { - follow: 2 + follow: 2, }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(`${base}inspect`); expect(res.status).to.equal(200); }); }); - it('should allow not following redirect', () => { + it("should allow not following redirect", () => { const url = `${base}redirect/301`; const options = { - follow: 0 + follow: 0, }; - return expect(fetch(url, options)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'max-redirect'); + return expect(fetch(url, options)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "max-redirect"); }); - it('should support redirect mode, manual flag', () => { + it("should support redirect mode, manual flag", () => { const url = `${base}redirect/301`; const options = { - redirect: 'manual' + redirect: "manual", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(url); expect(res.status).to.equal(301); - expect(res.headers.get('location')).to.equal(`${base}inspect`); + expect(res.headers.get("location")).to.equal(`${base}inspect`); }); }); - it('should support redirect mode, manual flag, broken Location header', () => { + it("should support redirect mode, manual flag, broken Location header", () => { const url = `${base}redirect/bad-location`; const options = { - redirect: 'manual' + redirect: "manual", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(url); expect(res.status).to.equal(301); - expect(res.headers.get('location')).to.equal(`${base}redirect/%C3%A2%C2%98%C2%83`); + expect(res.headers.get("location")).to.equal( + `${base}redirect/%C3%A2%C2%98%C2%83` + ); }); }); - it('should support redirect mode, error flag', () => { + it("should support redirect mode, error flag", () => { const url = `${base}redirect/301`; const options = { - redirect: 'error' + redirect: "error", }; - return expect(fetch(url, options)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'no-redirect'); + return expect(fetch(url, options)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "no-redirect"); }); - it('should support redirect mode, manual flag when there is no redirect', () => { + it("should support redirect mode, manual flag when there is no redirect", () => { const url = `${base}hello`; const options = { - redirect: 'manual' + redirect: "manual", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(url); expect(res.status).to.equal(200); - expect(res.headers.get('location')).to.be.null; + expect(res.headers.get("location")).to.be.null; }); }); - it('should follow redirect code 301 and keep existing headers', () => { + it("should follow redirect code 301 and keep existing headers", () => { const url = `${base}redirect/301`; const options = { - headers: new Headers({'x-custom-header': 'abc'}) + headers: new Headers({ "x-custom-header": "abc" }), }; - return fetch(url, options).then(res => { - expect(res.url).to.equal(`${base}inspect`); - return res.json(); - }).then(res => { - expect(res.headers['x-custom-header']).to.equal('abc'); - }); + return fetch(url, options) + .then((res) => { + expect(res.url).to.equal(`${base}inspect`); + return res.json(); + }) + .then((res) => { + expect(res.headers["x-custom-header"]).to.equal("abc"); + }); }); - it('should treat broken redirect as ordinary response (follow)', () => { + it("should treat broken redirect as ordinary response (follow)", () => { const url = `${base}redirect/no-location`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.url).to.equal(url); expect(res.status).to.equal(301); - expect(res.headers.get('location')).to.be.null; + expect(res.headers.get("location")).to.be.null; }); }); - it('should treat broken redirect as ordinary response (manual)', () => { + it("should treat broken redirect as ordinary response (manual)", () => { const url = `${base}redirect/no-location`; const options = { - redirect: 'manual' + redirect: "manual", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.url).to.equal(url); expect(res.status).to.equal(301); - expect(res.headers.get('location')).to.be.null; + expect(res.headers.get("location")).to.be.null; }); }); - it('should throw a TypeError on an invalid redirect option', () => { + it("should throw a TypeError on an invalid redirect option", () => { const url = `${base}redirect/301`; const options = { - redirect: 'foobar' + redirect: "foobar", }; - return fetch(url, options).then(() => { - expect.fail(); - }, error => { - expect(error).to.be.an.instanceOf(TypeError); - expect(error.message).to.equal('Redirect option \'foobar\' is not a valid value of RequestRedirect'); - }); + return fetch(url, options).then( + () => { + expect.fail(); + }, + (error) => { + expect(error).to.be.an.instanceOf(TypeError); + expect(error.message).to.equal( + "Redirect option 'foobar' is not a valid value of RequestRedirect" + ); + } + ); }); - it('should set redirected property on response when redirect', () => { + it("should set redirected property on response when redirect", () => { const url = `${base}redirect/301`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.redirected).to.be.true; }); }); - it('should not set redirected property on response without redirect', () => { + it("should not set redirected property on response without redirect", () => { const url = `${base}hello`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.redirected).to.be.false; }); }); - it('should ignore invalid headers', () => { + it("should ignore invalid headers", () => { const headers = fromRawHeaders([ - 'Invalid-Header ', - 'abc\r\n', - 'Invalid-Header-Value', - '\u0007k\r\n', - 'Cookie', - '\u0007k\r\n', - 'Cookie', - '\u0007kk\r\n' + "Invalid-Header ", + "abc\r\n", + "Invalid-Header-Value", + "\u0007k\r\n", + "Cookie", + "\u0007k\r\n", + "Cookie", + "\u0007kk\r\n", ]); expect(headers).to.be.instanceOf(Headers); expect(headers.raw()).to.deep.equal({}); }); - it('should handle client-error response', () => { + it("should handle client-error response", () => { const url = `${base}error/400`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); expect(res.status).to.equal(400); - expect(res.statusText).to.equal('Bad Request'); + expect(res.statusText).to.equal("Bad Request"); expect(res.ok).to.be.false; - return res.text().then(result => { + return res.text().then((result) => { expect(res.bodyUsed).to.be.true; - expect(result).to.be.a('string'); - expect(result).to.equal('client error'); + expect(result).to.be.a("string"); + expect(result).to.equal("client error"); }); }); }); - it('should handle server-error response', () => { + it("should handle server-error response", () => { const url = `${base}error/500`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); expect(res.status).to.equal(500); - expect(res.statusText).to.equal('Internal Server Error'); + expect(res.statusText).to.equal("Internal Server Error"); expect(res.ok).to.be.false; - return res.text().then(result => { + return res.text().then((result) => { expect(res.bodyUsed).to.be.true; - expect(result).to.be.a('string'); - expect(result).to.equal('server error'); + expect(result).to.be.a("string"); + expect(result).to.equal("server error"); }); }); }); - it('should handle network-error response', () => { + it("should handle network-error response", () => { const url = `${base}error/reset`; - return expect(fetch(url)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('code', 'ECONNRESET'); + return expect(fetch(url)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("code", "ECONNRESET"); }); - it('should handle premature close properly', () => { - const url = `${base}redirect/301/rn` + it("should handle premature close properly", () => { + const url = `${base}redirect/301/rn`; // const url = `https://interop.finance/nft/0` - return fetch(url).then(res => { - expect(res.status).to.equal(403) - }) + return fetch(url).then((res) => { + expect(res.status).to.equal(403); + }); }); - it('should handle network-error partial response', () => { + it("should handle network-error partial response", () => { const url = `${base}error/premature`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; - return expect(res.text()).to.eventually.be.rejectedWith(Error) - .and.have.property('message').matches(/Premature close|The operation was aborted|aborted/); + return expect(res.text()) + .to.eventually.be.rejectedWith(Error) + .and.have.property("message") + .matches(/Premature close|The operation was aborted|aborted/); }); }); - it('should handle network-error in chunked response', () => { + it("should handle network-error in chunked response", () => { const url = `${base}error/premature/chunked`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; return expect(collectStream(res.body)) - .to.eventually.be.rejectedWith(Error, 'Premature close') - .and.have.property('code', 'ERR_STREAM_PREMATURE_CLOSE'); + .to.eventually.be.rejectedWith(Error, "Premature close") + .and.have.property("code", "ERR_STREAM_PREMATURE_CLOSE"); }); }); - it('should handle network-error in chunked response async iterator', () => { + it("should handle network-error in chunked response async iterator", () => { const url = `${base}error/premature/chunked`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; return expect(collectStream(res.body)) - .to.eventually.be.rejectedWith(Error, 'Premature close') - .and.have.property('code', 'ERR_STREAM_PREMATURE_CLOSE'); + .to.eventually.be.rejectedWith(Error, "Premature close") + .and.have.property("code", "ERR_STREAM_PREMATURE_CLOSE"); }); }); - it('should handle network-error in chunked response in consumeBody', () => { + it("should handle network-error in chunked response in consumeBody", () => { const url = `${base}error/premature/chunked`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; - return expect(res.text()) - .to.eventually.be.rejectedWith(Error, 'Premature close'); + return expect(res.text()).to.eventually.be.rejectedWith( + Error, + "Premature close" + ); }); }); - it('should follow redirect after empty chunked transfer-encoding', () => { + it("should follow redirect after empty chunked transfer-encoding", () => { const url = `${base}redirect/chunked`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; }); }); - it('should handle chunked response with more than 1 chunk in the final packet', () => { + it("should handle chunked response with more than 1 chunk in the final packet", () => { const url = `${base}chunked/multiple-ending`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.ok).to.be.true; - return res.text().then(result => { - expect(result).to.equal('foobar'); + return res.text().then((result) => { + expect(result).to.equal("foobar"); }); }); }); - it('should handle chunked response with final chunk and EOM in separate packets', () => { + it("should handle chunked response with final chunk and EOM in separate packets", () => { const url = `${base}chunked/split-ending`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.ok).to.be.true; - return res.text().then(result => { - expect(result).to.equal('foobar'); + return res.text().then((result) => { + expect(result).to.equal("foobar"); }); }); }); - it.skip('should handle DNS-error response', () => { - const url = 'http://domain.invalid'; - return expect(fetch(url)).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('code').that.matches(/ENOTFOUND|EAI_AGAIN/); + it.skip("should handle DNS-error response", () => { + const url = "http://domain.invalid"; + return expect(fetch(url)) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("code") + .that.matches(/ENOTFOUND|EAI_AGAIN/); }); - it('should reject invalid json response', () => { + it("should reject invalid json response", () => { const url = `${base}error/json`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('application/json'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("application/json"); return expect(res.json()).to.eventually.be.rejectedWith(Error); }); }); - it('should handle response with no status text', () => { + it("should handle response with no status text", () => { const url = `${base}no-status-text`; - return fetch(url).then(res => { - expect(res.statusText).to.equal(''); + return fetch(url).then((res) => { + expect(res.statusText).to.equal(""); }); }); - it('should handle no content response', () => { + it("should handle no content response", () => { const url = `${base}no-content`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(204); - expect(res.statusText).to.equal('No Content'); + expect(res.statusText).to.equal("No Content"); expect(res.ok).to.be.true; - return res.text().then(result => { - expect(result).to.be.a('string'); + return res.text().then((result) => { + expect(result).to.be.a("string"); expect(result).to.be.empty; }); }); }); - it('should reject when trying to parse no content response as json', () => { + it("should reject when trying to parse no content response as json", () => { const url = `${base}no-content`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(204); - expect(res.statusText).to.equal('No Content'); + expect(res.statusText).to.equal("No Content"); expect(res.ok).to.be.true; return expect(res.json()).to.eventually.be.rejectedWith(Error); }); }); - it('should handle no content response with gzip encoding', () => { + it("should handle no content response with gzip encoding", () => { const url = `${base}no-content/gzip`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(204); - expect(res.statusText).to.equal('No Content'); - expect(res.headers.get('content-encoding')).to.equal('gzip'); + expect(res.statusText).to.equal("No Content"); + expect(res.headers.get("content-encoding")).to.equal("gzip"); expect(res.ok).to.be.true; - return res.text().then(result => { - expect(result).to.be.a('string'); + return res.text().then((result) => { + expect(result).to.be.a("string"); expect(result).to.be.empty; }); }); }); - it('should handle not modified response', () => { + it("should handle not modified response", () => { const url = `${base}not-modified`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(304); - expect(res.statusText).to.equal('Not Modified'); + expect(res.statusText).to.equal("Not Modified"); expect(res.ok).to.be.false; - return res.text().then(result => { - expect(result).to.be.a('string'); + return res.text().then((result) => { + expect(result).to.be.a("string"); expect(result).to.be.empty; }); }); }); - it('should handle not modified response with gzip encoding', () => { + it("should handle not modified response with gzip encoding", () => { const url = `${base}not-modified/gzip`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(304); - expect(res.statusText).to.equal('Not Modified'); - expect(res.headers.get('content-encoding')).to.equal('gzip'); + expect(res.statusText).to.equal("Not Modified"); + expect(res.headers.get("content-encoding")).to.equal("gzip"); expect(res.ok).to.be.false; - return res.text().then(result => { - expect(result).to.be.a('string'); + return res.text().then((result) => { + expect(result).to.be.a("string"); expect(result).to.be.empty; }); }); }); - it('should decompress gzip response', () => { + it("should decompress gzip response", () => { const url = `${base}gzip`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should decompress slightly invalid gzip response', () => { + it("should decompress slightly invalid gzip response", () => { const url = `${base}gzip-truncated`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should make capitalised Content-Encoding lowercase', () => { + it("should make capitalised Content-Encoding lowercase", () => { const url = `${base}gzip-capital`; - return fetch(url).then(res => { - expect(res.headers.get('content-encoding')).to.equal('gzip'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-encoding")).to.equal("gzip"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should decompress deflate response', () => { + it("should decompress deflate response", () => { const url = `${base}deflate`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should decompress deflate raw response from old apache server', () => { + it("should decompress deflate raw response from old apache server", () => { const url = `${base}deflate-raw`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should decompress brotli response', function () { - if (typeof zlib.createBrotliDecompress !== 'function') { + it("should decompress brotli response", function () { + if (typeof zlib.createBrotliDecompress !== "function") { this.skip(); } const url = `${base}brotli`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('hello world'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("hello world"); }); }); }); - it('should handle no content response with brotli encoding', function () { - if (typeof zlib.createBrotliDecompress !== 'function') { + it("should handle no content response with brotli encoding", function () { + if (typeof zlib.createBrotliDecompress !== "function") { this.skip(); } const url = `${base}no-content/brotli`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.status).to.equal(204); - expect(res.statusText).to.equal('No Content'); - expect(res.headers.get('content-encoding')).to.equal('br'); + expect(res.statusText).to.equal("No Content"); + expect(res.headers.get("content-encoding")).to.equal("br"); expect(res.ok).to.be.true; - return res.text().then(result => { - expect(result).to.be.a('string'); + return res.text().then((result) => { + expect(result).to.be.a("string"); expect(result).to.be.empty; }); }); }); - it('should skip decompression if unsupported', () => { + it("should skip decompression if unsupported", () => { const url = `${base}sdch`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.equal('fake sdch string'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.equal("fake sdch string"); }); }); }); - it('should reject if response compression is invalid', () => { + it("should reject if response compression is invalid", () => { const url = `${base}invalid-content-encoding`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return expect(res.text()).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('code', 'Z_DATA_ERROR'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("code", "Z_DATA_ERROR"); }); }); - it('should handle errors on the body stream even if it is not used', done => { + it("should handle errors on the body stream even if it is not used", (done) => { const url = `${base}invalid-content-encoding`; fetch(url) - .then(res => { + .then((res) => { expect(res.status).to.equal(200); }) - .catch(() => { }) + .catch(() => {}) .then(() => { // Wait a few ms to see if a uncaught error occurs setTimeout(() => { @@ -905,44 +938,52 @@ describe('node-fetch', () => { }); }); - it('should collect handled errors on the body stream to reject if the body is used later', () => { + it("should collect handled errors on the body stream to reject if the body is used later", () => { const url = `${base}invalid-content-encoding`; - return fetch(url).then(delay(20)).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return expect(res.text()).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('code', 'Z_DATA_ERROR'); - }); + return fetch(url) + .then(delay(20)) + .then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("code", "Z_DATA_ERROR"); + }); }); - it('should allow disabling auto decompression', () => { + it("should allow disabling auto decompression", () => { const url = `${base}gzip`; const options = { - compress: false + compress: false, }; - return fetch(url, options).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - return res.text().then(result => { - expect(result).to.be.a('string'); - expect(result).to.not.equal('hello world'); + return fetch(url, options).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + return res.text().then((result) => { + expect(result).to.be.a("string"); + expect(result).to.not.equal("hello world"); }); }); }); - it('should not overwrite existing accept-encoding header when auto decompression is true', () => { + it("should not overwrite existing accept-encoding header when auto decompression is true", () => { const url = `${base}inspect`; const options = { compress: true, headers: { - 'Accept-Encoding': 'gzip' - } + "Accept-Encoding": "gzip", + }, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.headers['accept-encoding']).to.equal('gzip'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.headers["accept-encoding"]).to.equal("gzip"); + }); }); - const testAbortController = (name, buildAbortController, moreTests = null) => { + const testAbortController = ( + name, + buildAbortController, + moreTests = null + ) => { describe(`AbortController (${name})`, () => { let controller; @@ -950,180 +991,172 @@ describe('node-fetch', () => { controller = buildAbortController(); }); - it('should support request cancellation with signal', () => { + it("should support request cancellation with signal", () => { const fetches = [ - fetch( - `${base}timeout`, - { - method: 'POST', - signal: controller.signal, - headers: { - 'Content-Type': 'application/json', - body: JSON.stringify({hello: 'world'}) - } - } - ) + fetch(`${base}timeout`, { + method: "POST", + signal: controller.signal, + headers: { + "Content-Type": "application/json", + body: JSON.stringify({ hello: "world" }), + }, + }), ]; setTimeout(() => { controller.abort(); }, 100); - return Promise.all(fetches.map(fetched => expect(fetched) - .to.eventually.be.rejected - .and.be.an.instanceOf(Error) - .and.include({ - type: 'aborted', - name: 'AbortError' - }) - )); + return Promise.all( + fetches.map((fetched) => + expect(fetched) + .to.eventually.be.rejected.and.be.an.instanceOf(Error) + .and.include({ + type: "aborted", + name: "AbortError", + }) + ) + ); }); - it('should support multiple request cancellation with signal', () => { + it("should support multiple request cancellation with signal", () => { const fetches = [ - fetch(`${base}timeout`, {signal: controller.signal}), - fetch( - `${base}timeout`, - { - method: 'POST', - signal: controller.signal, - headers: { - 'Content-Type': 'application/json', - body: JSON.stringify({hello: 'world'}) - } - } - ) + fetch(`${base}timeout`, { signal: controller.signal }), + fetch(`${base}timeout`, { + method: "POST", + signal: controller.signal, + headers: { + "Content-Type": "application/json", + body: JSON.stringify({ hello: "world" }), + }, + }), ]; setTimeout(() => { controller.abort(); }, 100); - return Promise.all(fetches.map(fetched => expect(fetched) - .to.eventually.be.rejected - .and.be.an.instanceOf(Error) - .and.include({ - type: 'aborted', - name: 'AbortError' - }) - )); + return Promise.all( + fetches.map((fetched) => + expect(fetched) + .to.eventually.be.rejected.and.be.an.instanceOf(Error) + .and.include({ + type: "aborted", + name: "AbortError", + }) + ) + ); }); - it('should reject immediately if signal has already been aborted', () => { + it("should reject immediately if signal has already been aborted", () => { const url = `${base}timeout`; const options = { - signal: controller.signal + signal: controller.signal, }; controller.abort(); const fetched = fetch(url, options); - return expect(fetched).to.eventually.be.rejected - .and.be.an.instanceOf(Error) + return expect(fetched) + .to.eventually.be.rejected.and.be.an.instanceOf(Error) .and.include({ - type: 'aborted', - name: 'AbortError' + type: "aborted", + name: "AbortError", }); }); - it('should allow redirects to be aborted', () => { + it("should allow redirects to be aborted", () => { const request = new Request(`${base}redirect/slow`, { - signal: controller.signal + signal: controller.signal, }); setTimeout(() => { controller.abort(); }, 20); - return expect(fetch(request)).to.be.eventually.rejected - .and.be.an.instanceOf(Error) - .and.have.property('name', 'AbortError'); + return expect(fetch(request)) + .to.be.eventually.rejected.and.be.an.instanceOf(Error) + .and.have.property("name", "AbortError"); }); - it('should allow redirected response body to be aborted', () => { + it("should allow redirected response body to be aborted", () => { const request = new Request(`${base}redirect/slow-stream`, { - signal: controller.signal + signal: controller.signal, }); - return expect(fetch(request).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); - const result = res.text(); - controller.abort(); - return result; - })).to.be.eventually.rejected - .and.be.an.instanceOf(Error) - .and.have.property('name', 'AbortError'); - }); - - it('should reject response body with AbortError when aborted before stream has been read completely', () => { - return expect(fetch( - `${base}slow`, - {signal: controller.signal} - )) - .to.eventually.be.fulfilled - .then(res => { - const promise = res.text(); + return expect( + fetch(request).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); + const result = res.text(); controller.abort(); - return expect(promise) - .to.eventually.be.rejected - .and.be.an.instanceof(Error) - .and.have.property('name', 'AbortError'); - }); + return result; + }) + ) + .to.be.eventually.rejected.and.be.an.instanceOf(Error) + .and.have.property("name", "AbortError"); }); - it('should reject response body methods immediately with AbortError when aborted before stream is disturbed', () => { - return expect(fetch( - `${base}slow`, - {signal: controller.signal} - )) - .to.eventually.be.fulfilled - .then(res => { - controller.abort(); - return expect(res.text()) - .to.eventually.be.rejected - .and.be.an.instanceof(Error) - .and.have.property('name', 'AbortError'); - }); + it("should reject response body with AbortError when aborted before stream has been read completely", () => { + return expect( + fetch(`${base}slow`, { signal: controller.signal }) + ).to.eventually.be.fulfilled.then((res) => { + const promise = res.text(); + controller.abort(); + return expect(promise) + .to.eventually.be.rejected.and.be.an.instanceof(Error) + .and.have.property("name", "AbortError"); + }); }); - it('should emit error event to response body with an AbortError when aborted before underlying stream is closed', done => { - expect(fetch( - `${base}slow`, - {signal: controller.signal} - )) - .to.eventually.be.fulfilled - .then(res => { - const collect = async () => { - try { - return await res.arrayBuffer(); - } catch (error) { - expect(error) - .to.be.an.instanceof(Error) - .and.have.property('name', 'AbortError'); - done(); - } - }; + it("should reject response body methods immediately with AbortError when aborted before stream is disturbed", () => { + return expect( + fetch(`${base}slow`, { signal: controller.signal }) + ).to.eventually.be.fulfilled.then((res) => { + controller.abort(); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceof(Error) + .and.have.property("name", "AbortError"); + }); + }); - collect(); - controller.abort(); - }); + it("should emit error event to response body with an AbortError when aborted before underlying stream is closed", (done) => { + expect( + fetch(`${base}slow`, { signal: controller.signal }) + ).to.eventually.be.fulfilled.then((res) => { + const collect = async () => { + try { + return await res.arrayBuffer(); + } catch (error) { + expect(error) + .to.be.an.instanceof(Error) + .and.have.property("name", "AbortError"); + done(); + } + }; + + collect(); + controller.abort(); + }); }); - it('should cancel request body of type Stream with AbortError when aborted', () => { - const body = new stream.Readable({objectMode: true}); - body._read = () => { }; - const promise = fetch( - `${base}slow`, - {signal: controller.signal, body, method: 'POST'} - ); + it("should cancel request body of type Stream with AbortError when aborted", () => { + const body = new stream.Readable({ objectMode: true }); + body._read = () => {}; + const promise = fetch(`${base}slow`, { + signal: controller.signal, + body, + method: "POST", + }); const result = Promise.all([ new Promise((resolve, reject) => { - body.on('error', error => { + body.on("error", (error) => { try { - expect(error).to.be.an.instanceof(Error).and.have.property('name', 'AbortError'); + expect(error) + .to.be.an.instanceof(Error) + .and.have.property("name", "AbortError"); resolve(); } catch (error_) { reject(error_); } }); }), - expect(promise).to.eventually.be.rejected - .and.be.an.instanceof(Error) - .and.have.property('name', 'AbortError') + expect(promise) + .to.eventually.be.rejected.and.be.an.instanceof(Error) + .and.have.property("name", "AbortError"), ]); controller.abort(); @@ -1137,37 +1170,42 @@ describe('node-fetch', () => { }); }; - testAbortController('polyfill', + testAbortController( + "polyfill", () => new AbortControllerPolyfill(), () => { - it('should remove internal AbortSignal event listener after request is aborted', () => { + it("should remove internal AbortSignal event listener after request is aborted", () => { const controller = new AbortControllerPolyfill(); - const {signal} = controller; + const { signal } = controller; setTimeout(() => { controller.abort(); }, 20); - return expect(fetch(`${base}timeout`, {signal})) - .to.eventually.be.rejected - .and.be.an.instanceof(Error) - .and.have.property('name', 'AbortError') + return expect(fetch(`${base}timeout`, { signal })) + .to.eventually.be.rejected.and.be.an.instanceof(Error) + .and.have.property("name", "AbortError") .then(() => { return expect(signal.listeners.abort.length).to.equal(0); }); }); - it('should remove internal AbortSignal event listener after request and response complete without aborting', () => { + it("should remove internal AbortSignal event listener after request and response complete without aborting", () => { const controller = new AbortControllerPolyfill(); - const {signal} = controller; - const fetchHtml = fetch(`${base}html`, {signal}) - .then(res => res.text()); - const fetchResponseError = fetch(`${base}error/reset`, {signal}); - const fetchRedirect = fetch(`${base}redirect/301`, {signal}).then(res => res.json()); + const { signal } = controller; + const fetchHtml = fetch(`${base}html`, { signal }).then((res) => + res.text() + ); + const fetchResponseError = fetch(`${base}error/reset`, { signal }); + const fetchRedirect = fetch(`${base}redirect/301`, { signal }).then( + (res) => res.json() + ); return Promise.all([ - expect(fetchHtml).to.eventually.be.fulfilled.and.equal(''), + expect(fetchHtml).to.eventually.be.fulfilled.and.equal( + "" + ), expect(fetchResponseError).to.be.eventually.rejected, - expect(fetchRedirect).to.eventually.be.fulfilled + expect(fetchRedirect).to.eventually.be.fulfilled, ]).then(() => { expect(signal.listeners.abort.length).to.equal(0); }); @@ -1175,628 +1213,721 @@ describe('node-fetch', () => { } ); - testAbortController('mysticatea', () => new AbortControllerMysticatea()); + testAbortController("mysticatea", () => new AbortControllerMysticatea()); - if (process.version > 'v15') { - testAbortController('native', () => new AbortController()); + if (process.version > "v15") { + testAbortController("native", () => new AbortController()); } - it('should throw a TypeError if a signal is not of type AbortSignal or EventTarget', () => { + it("should throw a TypeError if a signal is not of type AbortSignal or EventTarget", () => { return Promise.all([ - expect(fetch(`${base}inspect`, {signal: {}})) - .to.be.eventually.rejected - .and.be.an.instanceof(TypeError) - .and.have.property('message').includes('AbortSignal'), - expect(fetch(`${base}inspect`, {signal: ''})) - .to.be.eventually.rejected - .and.be.an.instanceof(TypeError) - .and.have.property('message').includes('AbortSignal'), - expect(fetch(`${base}inspect`, {signal: Object.create(null)})) - .to.be.eventually.rejected - .and.be.an.instanceof(TypeError) - .and.have.property('message').includes('AbortSignal') + expect(fetch(`${base}inspect`, { signal: {} })) + .to.be.eventually.rejected.and.be.an.instanceof(TypeError) + .and.have.property("message") + .includes("AbortSignal"), + expect(fetch(`${base}inspect`, { signal: "" })) + .to.be.eventually.rejected.and.be.an.instanceof(TypeError) + .and.have.property("message") + .includes("AbortSignal"), + expect(fetch(`${base}inspect`, { signal: Object.create(null) })) + .to.be.eventually.rejected.and.be.an.instanceof(TypeError) + .and.have.property("message") + .includes("AbortSignal"), ]); }); - it('should gracefully handle a nullish signal', () => { + it("should gracefully handle a nullish signal", () => { return Promise.all([ - fetch(`${base}hello`, {signal: null}).then(res => { + fetch(`${base}hello`, { signal: null }).then((res) => { return expect(res.ok).to.be.true; }), - fetch(`${base}hello`, {signal: undefined}).then(res => { + fetch(`${base}hello`, { signal: undefined }).then((res) => { return expect(res.ok).to.be.true; - }) + }), ]); }); - it('should set default User-Agent', () => { + it("should set default User-Agent", () => { const url = `${base}inspect`; - return fetch(url).then(res => res.json()).then(res => { - expect(res.headers['user-agent']).to.startWith('node-fetch'); - }); + return fetch(url) + .then((res) => res.json()) + .then((res) => { + expect(res.headers["user-agent"]).to.startWith("node-fetch"); + }); }); - it('should allow setting User-Agent', () => { + it("should allow setting User-Agent", () => { const url = `${base}inspect`; const options = { headers: { - 'user-agent': 'faked' - } + "user-agent": "faked", + }, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.headers['user-agent']).to.equal('faked'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.headers["user-agent"]).to.equal("faked"); + }); }); - it('should set default Accept header', () => { + it("should set default Accept header", () => { const url = `${base}inspect`; - fetch(url).then(res => res.json()).then(res => { - expect(res.headers.accept).to.equal('*/*'); - }); + fetch(url) + .then((res) => res.json()) + .then((res) => { + expect(res.headers.accept).to.equal("*/*"); + }); }); - it('should allow setting Accept header', () => { + it("should allow setting Accept header", () => { const url = `${base}inspect`; const options = { headers: { - accept: 'application/json' - } + accept: "application/json", + }, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.headers.accept).to.equal('application/json'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.headers.accept).to.equal("application/json"); + }); }); - it('should allow POST request', () => { + it("should allow POST request", () => { const url = `${base}inspect`; const options = { - method: 'POST' + method: "POST", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('0'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("0"); + }); }); - it('should allow POST request with string body', () => { + it("should allow POST request with string body", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: 'a=1' + method: "POST", + body: "a=1", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.equal('text/plain;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.equal( + "text/plain;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow POST request with buffer body', () => { + it("should allow POST request with buffer body", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: Buffer.from('a=1', 'utf-8') + method: "POST", + body: Buffer.from("a=1", "utf-8"), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow POST request with ArrayBuffer body', () => { + it("should allow POST request with ArrayBuffer body", () => { const encoder = new TextEncoder(); const url = `${base}inspect`; const options = { - method: 'POST', - body: encoder.encode('Hello, world!\n').buffer + method: "POST", + body: encoder.encode("Hello, world!\n").buffer, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('Hello, world!\n'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('14'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("Hello, world!\n"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("14"); + }); }); - it('should allow POST request with ArrayBuffer body from a VM context', () => { + it("should allow POST request with ArrayBuffer body from a VM context", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: new VMUint8Array(Buffer.from('Hello, world!\n')).buffer + method: "POST", + body: new VMUint8Array(Buffer.from("Hello, world!\n")).buffer, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('Hello, world!\n'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('14'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("Hello, world!\n"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("14"); + }); }); - it('should allow POST request with ArrayBufferView (Uint8Array) body', () => { + it("should allow POST request with ArrayBufferView (Uint8Array) body", () => { const encoder = new TextEncoder(); const url = `${base}inspect`; const options = { - method: 'POST', - body: encoder.encode('Hello, world!\n') + method: "POST", + body: encoder.encode("Hello, world!\n"), }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('Hello, world!\n'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('14'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("Hello, world!\n"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("14"); + }); }); - it('should allow POST request with ArrayBufferView (DataView) body', () => { + it("should allow POST request with ArrayBufferView (DataView) body", () => { const encoder = new TextEncoder(); const url = `${base}inspect`; const options = { - method: 'POST', - body: new DataView(encoder.encode('Hello, world!\n').buffer) + method: "POST", + body: new DataView(encoder.encode("Hello, world!\n").buffer), }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('Hello, world!\n'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('14'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("Hello, world!\n"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("14"); + }); }); - it('should allow POST request with ArrayBufferView (Uint8Array) body from a VM context', () => { + it("should allow POST request with ArrayBufferView (Uint8Array) body from a VM context", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: new VMUint8Array(Buffer.from('Hello, world!\n')) + method: "POST", + body: new VMUint8Array(Buffer.from("Hello, world!\n")), }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('Hello, world!\n'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('14'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("Hello, world!\n"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("14"); + }); }); - it('should allow POST request with ArrayBufferView (Uint8Array, offset, length) body', () => { + it("should allow POST request with ArrayBufferView (Uint8Array, offset, length) body", () => { const encoder = new TextEncoder(); const url = `${base}inspect`; const options = { - method: 'POST', - body: encoder.encode('Hello, world!\n').subarray(7, 13) + method: "POST", + body: encoder.encode("Hello, world!\n").subarray(7, 13), }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('world!'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('6'); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("world!"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("6"); + }); }); - it('should allow POST request with blob body without type', () => { + it("should allow POST request with blob body without type", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: new Blob(['a=1']) + method: "POST", + body: new Blob(["a=1"]), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow POST request with blob body with type', () => { + it("should allow POST request with blob body with type", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: new Blob(['a=1'], { - type: 'text/plain;charset=UTF-8' - }) + method: "POST", + body: new Blob(["a=1"], { + type: "text/plain;charset=UTF-8", + }), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.equal('text/plain;charset=utf-8'); - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.equal( + "text/plain;charset=utf-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow POST request with readable stream as body', () => { + it("should allow POST request with readable stream as body", () => { const url = `${base}inspect`; const options = { - method: 'POST', - body: stream.Readable.from('a=1') + method: "POST", + body: stream.Readable.from("a=1"), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.equal('chunked'); - expect(res.headers['content-type']).to.be.undefined; - expect(res.headers['content-length']).to.be.undefined; - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.equal("chunked"); + expect(res.headers["content-type"]).to.be.undefined; + expect(res.headers["content-length"]).to.be.undefined; + }); }); - it('should allow POST request with form-data as body', () => { + it("should allow POST request with form-data as body", () => { const form = new FormData(); - form.append('a', '1'); + form.append("a", "1"); const url = `${base}multipart`; const options = { - method: 'POST', - body: form + method: "POST", + body: form, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.startWith('multipart/form-data; boundary='); - expect(res.headers['content-length']).to.be.a('string'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.startWith( + "multipart/form-data; boundary=" + ); + expect(res.headers["content-length"]).to.be.a("string"); + expect(res.body).to.equal("a=1"); + }); }); - it('should allow POST request with form-data using stream as body', () => { + it("should allow POST request with form-data using stream as body", () => { const form = new FormData(); - form.append('my_field', fs.createReadStream('test/utils/dummy.txt')); + form.append("my_field", fs.createReadStream("test/utils/dummy.txt")); const url = `${base}multipart`; const options = { - method: 'POST', - body: form + method: "POST", + body: form, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.startWith('multipart/form-data; boundary='); - expect(res.headers['content-length']).to.be.undefined; - expect(res.body).to.contain('my_field='); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.startWith( + "multipart/form-data; boundary=" + ); + expect(res.headers["content-length"]).to.be.undefined; + expect(res.body).to.contain("my_field="); + }); }); - it('should allow POST request with form-data as body and custom headers', () => { + it("should allow POST request with form-data as body and custom headers", () => { const form = new FormData(); - form.append('a', '1'); + form.append("a", "1"); const headers = form.getHeaders(); - headers.b = '2'; + headers.b = "2"; const url = `${base}multipart`; const options = { - method: 'POST', + method: "POST", body: form, - headers + headers, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.startWith('multipart/form-data; boundary='); - expect(res.headers['content-length']).to.be.a('string'); - expect(res.headers.b).to.equal('2'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.startWith( + "multipart/form-data; boundary=" + ); + expect(res.headers["content-length"]).to.be.a("string"); + expect(res.headers.b).to.equal("2"); + expect(res.body).to.equal("a=1"); + }); }); - it('should support spec-compliant form-data as POST body', () => { + it("should support spec-compliant form-data as POST body", () => { const form = new FormDataNode(); - const filename = path.join('test', 'utils', 'dummy.txt'); + const filename = path.join("test", "utils", "dummy.txt"); - form.set('field', 'some text'); - form.set('file', fs.createReadStream(filename), { - size: fs.statSync(filename).size + form.set("field", "some text"); + form.set("file", fs.createReadStream(filename), { + size: fs.statSync(filename).size, }); const url = `${base}multipart`; const options = { - method: 'POST', - body: form + method: "POST", + body: form, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.startWith('multipart/form-data'); - expect(res.body).to.contain('field='); - expect(res.body).to.contain('file='); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.startWith("multipart/form-data"); + expect(res.body).to.contain("field="); + expect(res.body).to.contain("file="); + }); }); - it('should support URLSearchParams as POST body', () => { + it("should support URLSearchParams as POST body", () => { const params = new URLSearchParams(); - params.set('key1', 'value1'); - params.set('key2', 'value2'); + params.set("key1", "value1"); + params.set("key2", "value2"); const url = `${base}multipart`; const options = { - method: 'POST', - body: params + method: "POST", + body: params, }; - return fetch(url, options).then(res => res.json()).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.startWith('application/x-www-form-urlencoded'); - expect(res.body).to.contain('key1='); - expect(res.body).to.contain('key2='); - }); + return fetch(url, options) + .then((res) => res.json()) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.startWith( + "application/x-www-form-urlencoded" + ); + expect(res.body).to.contain("key1="); + expect(res.body).to.contain("key2="); + }); }); - it('should allow POST request with object body', () => { + it("should allow POST request with object body", () => { const url = `${base}inspect`; // Note that fetch simply calls tostring on an object const options = { - method: 'POST', - body: {a: 1} + method: "POST", + body: { a: 1 }, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('[object Object]'); - expect(res.headers['content-type']).to.equal('text/plain;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('15'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("[object Object]"); + expect(res.headers["content-type"]).to.equal( + "text/plain;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("15"); + }); }); - it('constructing a Response with URLSearchParams as body should have a Content-Type', () => { + it("constructing a Response with URLSearchParams as body should have a Content-Type", () => { const parameters = new URLSearchParams(); const res = new Response(parameters); - res.headers.get('Content-Type'); - expect(res.headers.get('Content-Type')).to.equal('application/x-www-form-urlencoded;charset=UTF-8'); + res.headers.get("Content-Type"); + expect(res.headers.get("Content-Type")).to.equal( + "application/x-www-form-urlencoded;charset=UTF-8" + ); }); - it('constructing a Request with URLSearchParams as body should have a Content-Type', () => { + it("constructing a Request with URLSearchParams as body should have a Content-Type", () => { const parameters = new URLSearchParams(); - const request = new Request(base, {method: 'POST', body: parameters}); - expect(request.headers.get('Content-Type')).to.equal('application/x-www-form-urlencoded;charset=UTF-8'); + const request = new Request(base, { method: "POST", body: parameters }); + expect(request.headers.get("Content-Type")).to.equal( + "application/x-www-form-urlencoded;charset=UTF-8" + ); }); - it('Reading a body with URLSearchParams should echo back the result', () => { + it("Reading a body with URLSearchParams should echo back the result", () => { const parameters = new URLSearchParams(); - parameters.append('a', '1'); - return new Response(parameters).text().then(text => { - expect(text).to.equal('a=1'); + parameters.append("a", "1"); + return new Response(parameters).text().then((text) => { + expect(text).to.equal("a=1"); }); }); // Body should been cloned... - it('constructing a Request/Response with URLSearchParams and mutating it should not affected body', () => { + it("constructing a Request/Response with URLSearchParams and mutating it should not affected body", () => { const parameters = new URLSearchParams(); - const request = new Request(`${base}inspect`, {method: 'POST', body: parameters}); - parameters.append('a', '1'); - return request.text().then(text => { - expect(text).to.equal(''); + const request = new Request(`${base}inspect`, { + method: "POST", + body: parameters, + }); + parameters.append("a", "1"); + return request.text().then((text) => { + expect(text).to.equal(""); }); }); - it('constructing a Request with URLSearchParams should provide formData()', () => { + it("constructing a Request with URLSearchParams should provide formData()", () => { const parameters = new URLSearchParams(); - parameters.append('key', 'value'); + parameters.append("key", "value"); const request = new Request(base, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + "Content-Type": "application/x-www-form-urlencoded", }, body: parameters, }); - return request.formData().then(formData => { - expect(formData.get('key')).to.equal('value'); + return request.formData().then((formData) => { + expect(formData.get("key")).to.equal("value"); }); }); - it('should allow POST request with URLSearchParams as body', () => { + it("should allow POST request with URLSearchParams as body", () => { const parameters = new URLSearchParams(); - parameters.append('a', '1'); + parameters.append("a", "1"); const url = `${base}inspect`; const options = { - method: 'POST', - body: parameters + method: "POST", + body: parameters, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.equal('application/x-www-form-urlencoded;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('3'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.equal( + "application/x-www-form-urlencoded;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + expect(res.body).to.equal("a=1"); + }); }); - it('should still recognize URLSearchParams when extended', () => { - class CustomSearchParameters extends URLSearchParams { } + it("should still recognize URLSearchParams when extended", () => { + class CustomSearchParameters extends URLSearchParams {} const parameters = new CustomSearchParameters(); - parameters.append('a', '1'); + parameters.append("a", "1"); const url = `${base}inspect`; const options = { - method: 'POST', - body: parameters + method: "POST", + body: parameters, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.equal('application/x-www-form-urlencoded;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('3'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.equal( + "application/x-www-form-urlencoded;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + expect(res.body).to.equal("a=1"); + }); }); /* For 100% code coverage, checks for duck-typing-only detection * where both constructor.name and brand tests fail */ - it('should still recognize URLSearchParams when extended from polyfill', () => { - class CustomPolyfilledSearchParameters extends URLSearchParams { } + it("should still recognize URLSearchParams when extended from polyfill", () => { + class CustomPolyfilledSearchParameters extends URLSearchParams {} const parameters = new CustomPolyfilledSearchParameters(); - parameters.append('a', '1'); + parameters.append("a", "1"); const url = `${base}inspect`; const options = { - method: 'POST', - body: parameters + method: "POST", + body: parameters, }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.headers['content-type']).to.equal('application/x-www-form-urlencoded;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('3'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.headers["content-type"]).to.equal( + "application/x-www-form-urlencoded;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + expect(res.body).to.equal("a=1"); + }); }); - it('should overwrite Content-Length if possible', () => { + it("should overwrite Content-Length if possible", () => { const url = `${base}inspect`; // Note that fetch simply calls tostring on an object const options = { - method: 'POST', + method: "POST", headers: { - 'Content-Length': '1000' + "Content-Length": "1000", }, - body: 'a=1' + body: "a=1", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('POST'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-type']).to.equal('text/plain;charset=UTF-8'); - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("POST"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-type"]).to.equal( + "text/plain;charset=UTF-8" + ); + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow PUT request', () => { + it("should allow PUT request", () => { const url = `${base}inspect`; const options = { - method: 'PUT', - body: 'a=1' + method: "PUT", + body: "a=1", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('PUT'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("PUT"); + expect(res.body).to.equal("a=1"); + }); }); - it('should allow DELETE request', () => { + it("should allow DELETE request", () => { const url = `${base}inspect`; const options = { - method: 'DELETE' + method: "DELETE", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('DELETE'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("DELETE"); + }); }); - it('should allow DELETE request with string body', () => { + it("should allow DELETE request with string body", () => { const url = `${base}inspect`; const options = { - method: 'DELETE', - body: 'a=1' + method: "DELETE", + body: "a=1", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('DELETE'); - expect(res.body).to.equal('a=1'); - expect(res.headers['transfer-encoding']).to.be.undefined; - expect(res.headers['content-length']).to.equal('3'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("DELETE"); + expect(res.body).to.equal("a=1"); + expect(res.headers["transfer-encoding"]).to.be.undefined; + expect(res.headers["content-length"]).to.equal("3"); + }); }); - it('should allow PATCH request', () => { + it("should allow PATCH request", () => { const url = `${base}inspect`; const options = { - method: 'PATCH', - body: 'a=1' + method: "PATCH", + body: "a=1", }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.method).to.equal('PATCH'); - expect(res.body).to.equal('a=1'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.method).to.equal("PATCH"); + expect(res.body).to.equal("a=1"); + }); }); - it('should allow HEAD request', () => { + it("should allow HEAD request", () => { const url = `${base}hello`; const options = { - method: 'HEAD' + method: "HEAD", }; - return fetch(url, options).then(res => { - expect(res.status).to.equal(200); - expect(res.statusText).to.equal('OK'); - expect(res.headers.get('content-type')).to.equal('text/plain'); - expect(res.body).to.be.an.instanceof(ReadableStream); - return res.text(); - }).then(text => { - expect(text).to.equal(''); - }); + return fetch(url, options) + .then((res) => { + expect(res.status).to.equal(200); + expect(res.statusText).to.equal("OK"); + expect(res.headers.get("content-type")).to.equal("text/plain"); + expect(res.body).to.be.an.instanceof(ReadableStream); + return res.text(); + }) + .then((text) => { + expect(text).to.equal(""); + }); }); - it('should allow HEAD request with content-encoding header', () => { + it("should allow HEAD request with content-encoding header", () => { const url = `${base}error/404`; const options = { - method: 'HEAD' + method: "HEAD", }; - return fetch(url, options).then(res => { - expect(res.status).to.equal(404); - expect(res.headers.get('content-encoding')).to.equal('gzip'); - return res.text(); - }).then(text => { - expect(text).to.equal(''); - }); + return fetch(url, options) + .then((res) => { + expect(res.status).to.equal(404); + expect(res.headers.get("content-encoding")).to.equal("gzip"); + return res.text(); + }) + .then((text) => { + expect(text).to.equal(""); + }); }); - it('should allow OPTIONS request', () => { + it("should allow OPTIONS request", () => { const url = `${base}options`; const options = { - method: 'OPTIONS' + method: "OPTIONS", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.status).to.equal(200); - expect(res.statusText).to.equal('OK'); - expect(res.headers.get('allow')).to.equal('GET, HEAD, OPTIONS'); + expect(res.statusText).to.equal("OK"); + expect(res.headers.get("allow")).to.equal("GET, HEAD, OPTIONS"); expect(res.body).to.be.an.instanceof(ReadableStream); }); }); - it('should reject decoding body twice', () => { + it("should reject decoding body twice", () => { const url = `${base}plain`; - return fetch(url).then(res => { - expect(res.headers.get('content-type')).to.equal('text/plain'); + return fetch(url).then((res) => { + expect(res.headers.get("content-type")).to.equal("text/plain"); return res.text().then(() => { expect(res.bodyUsed).to.be.true; return expect(res.text()).to.eventually.be.rejectedWith(Error); @@ -1804,109 +1935,109 @@ describe('node-fetch', () => { }); }); - it('should support maximum response size, multiple chunk', () => { + it("should support maximum response size, multiple chunk", () => { const url = `${base}size/chunk`; const options = { - size: 5 + size: 5, }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.status).to.equal(200); - expect(res.headers.get('content-type')).to.equal('text/plain'); - return expect(res.text()).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'max-size'); + expect(res.headers.get("content-type")).to.equal("text/plain"); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "max-size"); }); }); - it('should support maximum response size, single chunk', () => { + it("should support maximum response size, single chunk", () => { const url = `${base}size/long`; const options = { - size: 5 + size: 5, }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.status).to.equal(200); - expect(res.headers.get('content-type')).to.equal('text/plain'); - return expect(res.text()).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.have.property('type', 'max-size'); + expect(res.headers.get("content-type")).to.equal("text/plain"); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.have.property("type", "max-size"); }); }); - it.skip('should allow piping response body as stream', () => { + it.skip("should allow piping response body as stream", () => { const url = `${base}hello`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.body).to.be.an.instanceof(stream.Transform); - return streamToPromise(res.body, chunk => { + return streamToPromise(res.body, (chunk) => { if (chunk === null) { return; } - expect(chunk.toString()).to.equal('world'); + expect(chunk.toString()).to.equal("world"); }); }); }); - it('should allow cloning a response, and use both as stream', () => { + it("should allow cloning a response, and use both as stream", () => { const url = `${base}hello`; - return fetch(url).then(res => { + return fetch(url).then((res) => { const r1 = res.clone(); expect(res.body).to.be.an.instanceof(ReadableStream); expect(r1.body).to.be.an.instanceof(ReadableStream); - const dataHandler = chunk => { + const dataHandler = (chunk) => { if (chunk === null) { return; } - expect(chunk.toString()).to.equal('world'); + expect(chunk.toString()).to.equal("world"); }; return Promise.all([ streamToPromise(res.body, dataHandler), - streamToPromise(r1.body, dataHandler) + streamToPromise(r1.body, dataHandler), ]); }); }); - it('should allow cloning a json response and log it as text response', () => { + it("should allow cloning a json response and log it as text response", () => { const url = `${base}json`; - return fetch(url).then(res => { + return fetch(url).then((res) => { const r1 = res.clone(); - return Promise.all([res.json(), r1.text()]).then(results => { - expect(results[0]).to.deep.equal({name: 'value'}); + return Promise.all([res.json(), r1.text()]).then((results) => { + expect(results[0]).to.deep.equal({ name: "value" }); expect(results[1]).to.equal('{"name":"value"}'); }); }); }); - it('should allow cloning a json response, and then log it as text response', () => { + it("should allow cloning a json response, and then log it as text response", () => { const url = `${base}json`; - return fetch(url).then(res => { + return fetch(url).then((res) => { const r1 = res.clone(); - return res.json().then(result => { - expect(result).to.deep.equal({name: 'value'}); - return r1.text().then(result => { + return res.json().then((result) => { + expect(result).to.deep.equal({ name: "value" }); + return r1.text().then((result) => { expect(result).to.equal('{"name":"value"}'); }); }); }); }); - it('should allow cloning a json response, first log as text response, then return json object', () => { + it("should allow cloning a json response, first log as text response, then return json object", () => { const url = `${base}json`; - return fetch(url).then(res => { + return fetch(url).then((res) => { const r1 = res.clone(); - return r1.text().then(result => { + return r1.text().then((result) => { expect(result).to.equal('{"name":"value"}'); - return res.json().then(result => { - expect(result).to.deep.equal({name: 'value'}); + return res.json().then((result) => { + expect(result).to.deep.equal({ name: "value" }); }); }); }); }); - it('should not allow cloning a response after its been used', () => { + it("should not allow cloning a response after its been used", () => { const url = `${base}hello`; - return fetch(url).then(res => + return fetch(url).then((res) => res.text().then(() => { expect(() => { res.clone(); @@ -1915,16 +2046,16 @@ describe('node-fetch', () => { ); }); - it('the default highWaterMark should equal 16384', () => { + it("the default highWaterMark should equal 16384", () => { const url = `${base}hello`; - return fetch(url).then(res => { + return fetch(url).then((res) => { expect(res.highWaterMark).to.equal(16384); }); }); - it.skip('should timeout on cloning response without consuming one of the streams when the second packet size is equal default highWaterMark', function () { + it.skip("should timeout on cloning response without consuming one of the streams when the second packet size is equal default highWaterMark", function () { this.timeout(300); - const url = local.mockResponse(res => { + const url = local.mockResponse((res) => { // Observed behavior of TCP packets splitting: // - response body size <= 65438 → single packet sent // - response body size > 65438 → multiple packets sent @@ -1934,289 +2065,301 @@ describe('node-fetch', () => { const secondPacketSize = 16 * 1024; // = defaultHighWaterMark res.end(crypto.randomBytes(firstPacketMaxSize + secondPacketSize)); }); - return expect( - fetch(url).then(res => res.clone().arrayBuffer()) - ).to.timeout; + return expect(fetch(url).then((res) => res.clone().arrayBuffer())).to + .timeout; }); - it.skip('should timeout on cloning response without consuming one of the streams when the second packet size is equal custom highWaterMark', function () { + it.skip("should timeout on cloning response without consuming one of the streams when the second packet size is equal custom highWaterMark", function () { this.timeout(300); - const url = local.mockResponse(res => { + const url = local.mockResponse((res) => { const firstPacketMaxSize = 65438; const secondPacketSize = 10; res.end(crypto.randomBytes(firstPacketMaxSize + secondPacketSize)); }); return expect( - fetch(url, {highWaterMark: 10}).then(res => res.clone().arrayBuffer()) + fetch(url, { highWaterMark: 10 }).then((res) => res.clone().arrayBuffer()) ).to.timeout; }); - it('should not timeout on cloning response without consuming one of the streams when the second packet size is less than default highWaterMark', function () { + it("should not timeout on cloning response without consuming one of the streams when the second packet size is less than default highWaterMark", function () { this.timeout(300); - const url = local.mockResponse(res => { + const url = local.mockResponse((res) => { const firstPacketMaxSize = 65438; const secondPacketSize = 16 * 1024; // = defaultHighWaterMark res.end(crypto.randomBytes(firstPacketMaxSize + secondPacketSize - 1)); }); - return expect( - fetch(url).then(res => res.clone().arrayBuffer()) - ).not.to.timeout; + return expect(fetch(url).then((res) => res.clone().arrayBuffer())).not.to + .timeout; }); - it('should not timeout on cloning response without consuming one of the streams when the second packet size is less than custom highWaterMark', function () { + it("should not timeout on cloning response without consuming one of the streams when the second packet size is less than custom highWaterMark", function () { this.timeout(300); - const url = local.mockResponse(res => { + const url = local.mockResponse((res) => { const firstPacketMaxSize = 65438; const secondPacketSize = 10; res.end(crypto.randomBytes(firstPacketMaxSize + secondPacketSize - 1)); }); return expect( - fetch(url, {highWaterMark: 10}).then(res => res.clone().arrayBuffer()) + fetch(url, { highWaterMark: 10 }).then((res) => res.clone().arrayBuffer()) ).not.to.timeout; }); - it('should not timeout on cloning response without consuming one of the streams when the response size is double the custom large highWaterMark - 1', function () { + it("should not timeout on cloning response without consuming one of the streams when the response size is double the custom large highWaterMark - 1", function () { this.timeout(300); - const url = local.mockResponse(res => { - res.end(crypto.randomBytes((2 * 512 * 1024) - 1)); + const url = local.mockResponse((res) => { + res.end(crypto.randomBytes(2 * 512 * 1024 - 1)); }); return expect( - fetch(url, {highWaterMark: 512 * 1024}).then(res => res.clone().arrayBuffer()) + fetch(url, { highWaterMark: 512 * 1024 }).then((res) => + res.clone().arrayBuffer() + ) ).not.to.timeout; }); - it('should allow get all responses of a header', () => { + it("should allow get all responses of a header", () => { const url = `${base}cookie`; - return fetch(url).then(res => { - const expected = 'a=1, b=1'; - expect(res.headers.get('set-cookie')).to.equal(expected); - expect(res.headers.get('Set-Cookie')).to.equal(expected); + return fetch(url).then((res) => { + const expected = "a=1, b=1"; + expect(res.headers.get("set-cookie")).to.equal(expected); + expect(res.headers.get("Set-Cookie")).to.equal(expected); }); }); - it('should return all headers using raw()', () => { + it("should return all headers using raw()", () => { const url = `${base}cookie`; - return fetch(url).then(res => { - const expected = [ - 'a=1', - 'b=1' - ]; + return fetch(url).then((res) => { + const expected = ["a=1", "b=1"]; - expect(res.headers.raw()['set-cookie']).to.deep.equal(expected); + expect(res.headers.raw()["set-cookie"]).to.deep.equal(expected); }); }); - it('should allow deleting header', () => { + it("should allow deleting header", () => { const url = `${base}cookie`; - return fetch(url).then(res => { - res.headers.delete('set-cookie'); - expect(res.headers.get('set-cookie')).to.be.null; + return fetch(url).then((res) => { + res.headers.delete("set-cookie"); + expect(res.headers.get("set-cookie")).to.be.null; }); }); - it('should send request with connection keep-alive if agent is provided', () => { + it("should send request with connection keep-alive if agent is provided", () => { const url = `${base}inspect`; const options = { agent: new http.Agent({ - keepAlive: true - }) + keepAlive: true, + }), }; - return fetch(url, options).then(res => { - return res.json(); - }).then(res => { - expect(res.headers.connection).to.equal('keep-alive'); - }); + return fetch(url, options) + .then((res) => { + return res.json(); + }) + .then((res) => { + expect(res.headers.connection).to.equal("keep-alive"); + }); }); - it('should support fetch with Request instance', () => { + it("should support fetch with Request instance", () => { const url = `${base}hello`; const request = new Request(url); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('should support fetch with Node.js URL object', () => { + it("should support fetch with Node.js URL object", () => { const url = `${base}hello`; const urlObject = new URL(url); const request = new Request(urlObject); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('should support fetch with WHATWG URL object', () => { + it("should support fetch with WHATWG URL object", () => { const url = `${base}hello`; const urlObject = new URL(url); const request = new Request(urlObject); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('should keep `?` sign in URL when no params are given', () => { + it("should keep `?` sign in URL when no params are given", () => { const url = `${base}question?`; const urlObject = new URL(url); const request = new Request(urlObject); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('if params are given, do not modify anything', () => { + it("if params are given, do not modify anything", () => { const url = `${base}question?a=1`; const urlObject = new URL(url); const request = new Request(urlObject); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('should preserve the hash (#) symbol', () => { + it("should preserve the hash (#) symbol", () => { const url = `${base}question?#`; const urlObject = new URL(url); const request = new Request(urlObject); - return fetch(request).then(res => { + return fetch(request).then((res) => { expect(res.url).to.equal(url); expect(res.ok).to.be.true; expect(res.status).to.equal(200); }); }); - it('should support reading blob as text', () => { - return new Response('hello') + it("should support reading blob as text", () => { + return new Response("hello") .blob() - .then(blob => blob.text()) - .then(body => { - expect(body).to.equal('hello'); + .then((blob) => blob.text()) + .then((body) => { + expect(body).to.equal("hello"); }); }); - it('should support reading blob as arrayBuffer', () => { - return new Response('hello') + it("should support reading blob as arrayBuffer", () => { + return new Response("hello") .blob() - .then(blob => blob.arrayBuffer()) - .then(ab => { + .then((blob) => blob.arrayBuffer()) + .then((ab) => { const string = String.fromCharCode.apply(null, new Uint8Array(ab)); - expect(string).to.equal('hello'); + expect(string).to.equal("hello"); }); }); - it('should support reading blob as stream', () => { - return new Response('hello') - .blob() - .then(blob => streamToPromise(blob.stream(), data => { + it("should support reading blob as stream", () => { + return new Response("hello").blob().then((blob) => + streamToPromise(blob.stream(), (data) => { const string = Buffer.from(data).toString(); - expect(string).to.equal('hello'); - })); + expect(string).to.equal("hello"); + }) + ); }); - it('should support blob round-trip', () => { + it("should support blob round-trip", () => { const url = `${base}hello`; let length; let type; - return fetch(url).then(res => res.blob()).then(blob => { - const url = `${base}inspect`; - length = blob.size; - type = blob.type; - return fetch(url, { - method: 'POST', - body: blob + return fetch(url) + .then((res) => res.blob()) + .then((blob) => { + const url = `${base}inspect`; + length = blob.size; + type = blob.type; + return fetch(url, { + method: "POST", + body: blob, + }); + }) + .then((res) => res.json()) + .then(({ body, headers }) => { + expect(body).to.equal("world"); + expect(headers["content-type"]).to.equal(type); + expect(headers["content-length"]).to.equal(String(length)); }); - }).then(res => res.json()).then(({body, headers}) => { - expect(body).to.equal('world'); - expect(headers['content-type']).to.equal(type); - expect(headers['content-length']).to.equal(String(length)); - }); }); - it('should support overwrite Request instance', () => { + it("should support overwrite Request instance", () => { const url = `${base}inspect`; const request = new Request(url, { - method: 'POST', + method: "POST", headers: { - a: '1' - } + a: "1", + }, }); return fetch(request, { - method: 'GET', + method: "GET", headers: { - a: '2' - } - }).then(res => { - return res.json(); - }).then(body => { - expect(body.method).to.equal('GET'); - expect(body.headers.a).to.equal('2'); - }); + a: "2", + }, + }) + .then((res) => { + return res.json(); + }) + .then((body) => { + expect(body.method).to.equal("GET"); + expect(body.headers.a).to.equal("2"); + }); }); - it('should support arrayBuffer(), blob(), text(), json() and buffer() method in Body constructor', () => { - const body = new Body('a=1'); - expect(body).to.have.property('arrayBuffer'); - expect(body).to.have.property('blob'); - expect(body).to.have.property('text'); - expect(body).to.have.property('json'); + it("should support arrayBuffer(), blob(), text(), json() and buffer() method in Body constructor", () => { + const body = new Body("a=1"); + expect(body).to.have.property("arrayBuffer"); + expect(body).to.have.property("blob"); + expect(body).to.have.property("text"); + expect(body).to.have.property("json"); }); /* eslint-disable-next-line func-names */ - it('should create custom FetchError', function funcName() { - const systemError = new Error('system'); - systemError.code = 'ESOMEERROR'; + it("should create custom FetchError", function funcName() { + const systemError = new Error("system"); + systemError.code = "ESOMEERROR"; - const err = new FetchError('test message', 'test-error', systemError); + const err = new FetchError("test message", "test-error", systemError); expect(err).to.be.an.instanceof(Error); expect(err).to.be.an.instanceof(FetchError); - expect(err.name).to.equal('FetchError'); - expect(err.message).to.equal('test message'); - expect(err.type).to.equal('test-error'); - expect(err.code).to.equal('ESOMEERROR'); - expect(err.errno).to.equal('ESOMEERROR'); + expect(err.name).to.equal("FetchError"); + expect(err.message).to.equal("test message"); + expect(err.type).to.equal("test-error"); + expect(err.code).to.equal("ESOMEERROR"); + expect(err.errno).to.equal("ESOMEERROR"); // Reading the stack is quite slow (~30-50ms) - expect(err.stack).to.include('funcName').and.to.startWith(`${err.name}: ${err.message}`); + expect(err.stack) + .to.include("funcName") + .and.to.startWith(`${err.name}: ${err.message}`); }); - it('should support https request', function () { + it("should support https request", function () { this.timeout(5000); - const url = 'https://github.com/'; + const url = "https://github.com/"; const options = { - method: 'HEAD' + method: "HEAD", }; - return fetch(url, options).then(res => { + return fetch(url, options).then((res) => { expect(res.status).to.equal(200); expect(res.ok).to.be.true; }); }); // Issue #414 - it('should reject if attempt to accumulate body stream throws', () => { - const res = new Response(stream.Readable.from((async function * () { - yield Buffer.from('tada'); - await new Promise(resolve => { - setTimeout(resolve, 200); - }); - yield {tada: 'yes'}; - })())); + it("should reject if attempt to accumulate body stream throws", () => { + const res = new Response( + stream.Readable.from( + (async function* () { + yield Buffer.from("tada"); + await new Promise((resolve) => { + setTimeout(resolve, 200); + }); + yield { tada: "yes" }; + })() + ) + ); - return expect(res.text()).to.eventually.be.rejected - .and.be.an.instanceOf(FetchError) - .and.include({type: 'system'}) - .and.have.property('message').that.include('Could not create Buffer'); + return expect(res.text()) + .to.eventually.be.rejected.and.be.an.instanceOf(FetchError) + .and.include({ type: "system" }) + .and.have.property("message") + .that.include("Could not create Buffer"); }); - it('supports supplying a lookup function to the agent', () => { + it("supports supplying a lookup function to the agent", () => { const url = `${base}redirect/301`; let called = 0; function lookupSpy(hostname, options, callback) { @@ -2224,34 +2367,34 @@ describe('node-fetch', () => { return lookup(hostname, options, callback); } - const agent = http.Agent({lookup: lookupSpy}); - return fetch(url, {agent}).then(() => { + const agent = http.Agent({ lookup: lookupSpy }); + return fetch(url, { agent }).then(() => { expect(called).to.equal(2); }); }); - it('supports supplying a famliy option to the agent', () => { + it("supports supplying a famliy option to the agent", () => { const url = `${base}redirect/301`; const families = []; - const family = Symbol('family'); + const family = Symbol("family"); function lookupSpy(hostname, options, callback) { families.push(options.family); return lookup(hostname, {}, callback); } - const agent = http.Agent({lookup: lookupSpy, family}); - return fetch(url, {agent}).then(() => { + const agent = http.Agent({ lookup: lookupSpy, family }); + return fetch(url, { agent }).then(() => { expect(families).to.have.length(2); expect(families[0]).to.equal(family); expect(families[1]).to.equal(family); }); }); - it('should allow a function supplying the agent', () => { + it("should allow a function supplying the agent", () => { const url = `${base}inspect`; const agent = new http.Agent({ - keepAlive: true + keepAlive: true, }); let parsedURL; @@ -2260,60 +2403,62 @@ describe('node-fetch', () => { agent(_parsedURL) { parsedURL = _parsedURL; return agent; - } - }).then(res => { - return res.json(); - }).then(res => { - // The agent provider should have been called - expect(parsedURL.protocol).to.equal('http:'); - // The agent we returned should have been used - expect(res.headers.connection).to.equal('keep-alive'); - }); + }, + }) + .then((res) => { + return res.json(); + }) + .then((res) => { + // The agent provider should have been called + expect(parsedURL.protocol).to.equal("http:"); + // The agent we returned should have been used + expect(res.headers.connection).to.equal("keep-alive"); + }); }); - it('should calculate content length and extract content type for each body type', () => { + it("should calculate content length and extract content type for each body type", () => { const url = `${base}hello`; - const bodyContent = 'a=1'; + const bodyContent = "a=1"; const streamBody = stream.Readable.from(bodyContent); const streamRequest = new Request(url, { - method: 'POST', + method: "POST", body: streamBody, - size: 1024 + size: 1024, }); - const blobBody = new Blob([bodyContent], {type: 'text/plain'}); + const blobBody = new Blob([bodyContent], { type: "text/plain" }); const blobRequest = new Request(url, { - method: 'POST', + method: "POST", body: blobBody, - size: 1024 + size: 1024, }); const formBody = new FormData(); - formBody.append('a', '1'); + formBody.append("a", "1"); const formRequest = new Request(url, { - method: 'POST', + method: "POST", body: formBody, - size: 1024 + size: 1024, }); const bufferBody = Buffer.from(bodyContent); const bufferRequest = new Request(url, { - method: 'POST', + method: "POST", body: bufferBody, - size: 1024 + size: 1024, }); const stringRequest = new Request(url, { - method: 'POST', + method: "POST", body: bodyContent, - size: 1024 + size: 1024, }); const nullRequest = new Request(url, { - method: 'GET', + method: "GET", body: null, - size: 1024 + size: 1024, }); expect(getTotalBytes(streamRequest)).to.be.null; @@ -2324,14 +2469,16 @@ describe('node-fetch', () => { expect(getTotalBytes(nullRequest)).to.equal(0); expect(extractContentType(streamRequest)).to.be.null; - expect(extractContentType(blobRequest)).to.equal('text/plain'); - expect(extractContentType(formRequest)).to.startWith('multipart/form-data'); + expect(extractContentType(blobRequest)).to.equal("text/plain"); + expect(extractContentType(formRequest)).to.startWith("multipart/form-data"); expect(extractContentType(bufferRequest)).to.be.null; - expect(extractContentType(stringRequest)).to.equal('text/plain;charset=UTF-8'); + expect(extractContentType(stringRequest)).to.equal( + "text/plain;charset=UTF-8" + ); expect(extractContentType(nullRequest)).to.be.null; }); - it('should encode URLs as UTF-8', async () => { + it("should encode URLs as UTF-8", async () => { const url = `${base}möbius`; const res = await fetch(url); expect(res.url).to.equal(`${base}m%C3%B6bius`); diff --git a/packages/fetch/test/request.js b/packages/fetch/test/request.js index d35e42c..ad04dae 100644 --- a/packages/fetch/test/request.js +++ b/packages/fetch/test/request.js @@ -1,19 +1,19 @@ -import http from 'http'; -import {TextEncoder} from 'util'; +import http from "http"; +import { TextEncoder } from "util"; -import AbortController from 'abort-controller'; -import chai from 'chai'; -import FormData from 'form-data'; -import {Blob} from '@remix-run/web-fetch'; -import { ReadableStream } from '@remix-run/web-fetch'; +import AbortController from "abort-controller"; +import chai from "chai"; +import FormData from "form-data"; +import { Blob } from "@remix-run/web-fetch"; +import { ReadableStream } from "@remix-run/web-fetch"; -import TestServer from './utils/server.js'; -import {Request,FormData as WebFormData} from '@remix-run/web-fetch'; -import {File} from '@remix-run/web-file'; +import TestServer from "./utils/server.js"; +import { Request, FormData as WebFormData } from "@remix-run/web-fetch"; +import { File } from "@remix-run/web-file"; -const {expect} = chai; +const { expect } = chai; -describe('Request', () => { +describe("Request", () => { const local = new TestServer(); let base; @@ -27,58 +27,64 @@ describe('Request', () => { return local.stop(); }); - it('should have attributes conforming to Web IDL', () => { - const request = new Request('https://github.com/'); + it("should have attributes conforming to Web IDL", () => { + const request = new Request("https://github.com/"); const enumerableProperties = []; for (const property in request) { enumerableProperties.push(property); } for (const toCheck of [ - 'body', - 'bodyUsed', - 'arrayBuffer', - 'blob', - 'json', - 'text', - 'method', - 'url', - 'headers', - 'redirect', - 'clone', - 'signal' + "body", + "bodyUsed", + "arrayBuffer", + "blob", + "json", + "text", + "method", + "url", + "headers", + "redirect", + "clone", + "signal", ]) { expect(enumerableProperties).to.contain(toCheck); } for (const toCheck of [ - 'body', 'bodyUsed', 'method', 'url', 'headers', 'redirect', 'signal' + "body", + "bodyUsed", + "method", + "url", + "headers", + "redirect", + "signal", ]) { expect(() => { - request[toCheck] = 'abc'; + request[toCheck] = "abc"; }).to.throw(); } }); - it('should support wrapping Request instance', () => { + it("should support wrapping Request instance", () => { const url = `${base}hello`; const form = new FormData(); - form.append('a', '1'); - const {signal} = new AbortController(); + form.append("a", "1"); + const { signal } = new AbortController(); const r1 = new Request(url, { - method: 'POST', + method: "POST", follow: 1, body: form, - signal + signal, }); const r2 = new Request(r1, { - follow: 2 + follow: 2, }); expect(r2.url).to.equal(url); - expect(r2.method).to.equal('POST'); + expect(r2.method).to.equal("POST"); expect(r2.signal).to.equal(signal); // Note that we didn't clone the body expect(r2.body).to.instanceOf(ReadableStream); @@ -88,33 +94,33 @@ describe('Request', () => { expect(r2.counter).to.equal(0); }); - it('should throw a TypeError for forbidden methods', () => { + it("should throw a TypeError for forbidden methods", () => { // https://fetch.spec.whatwg.org/#methods - const forbiddenMethods = [ - "CONNECT", - "TRACE", - "TRACK", - ]; + const forbiddenMethods = ["CONNECT", "TRACE", "TRACK"]; - forbiddenMethods.forEach(method => { + forbiddenMethods.forEach((method) => { try { new Request(base, { method: method.toLowerCase() }); expect(true).to.equal(false); } catch (e) { expect(e instanceof TypeError).to.equal(true); - expect(e.message).to.equal(`Failed to construct 'Request': '${method.toLowerCase()}' HTTP method is unsupported.`) + expect(e.message).to.equal( + `Failed to construct 'Request': '${method.toLowerCase()}' HTTP method is unsupported.` + ); } try { new Request(base, { method: method.toUpperCase() }); expect(true).to.equal(false); } catch (e) { expect(e instanceof TypeError).to.equal(true); - expect(e.message).to.equal(`Failed to construct 'Request': '${method.toUpperCase()}' HTTP method is unsupported.`) + expect(e.message).to.equal( + `Failed to construct 'Request': '${method.toUpperCase()}' HTTP method is unsupported.` + ); } }); }); - it('should normalize method', () => { + it("should normalize method", () => { // https://fetch.spec.whatwg.org/#methods const shouldUpperCaseMethods = [ "DELETE", @@ -126,14 +132,14 @@ describe('Request', () => { ]; const otherMethods = ["PATCH", "CHICKEN"]; - shouldUpperCaseMethods.forEach(method => { + shouldUpperCaseMethods.forEach((method) => { const r1 = new Request(base, { method: method.toLowerCase() }); expect(r1.method).to.equal(method.toUpperCase()); const r2 = new Request(base, { method: method.toUpperCase() }); expect(r2.method).to.equal(method.toUpperCase()); }); - otherMethods.forEach(method => { + otherMethods.forEach((method) => { const r1 = new Request(base, { method: method.toLowerCase() }); expect(r1.method).to.equal(method.toLowerCase()); const r2 = new Request(base, { method: method.toUpperCase() }); @@ -141,33 +147,33 @@ describe('Request', () => { }); }); - it('should override signal on derived Request instances', () => { + it("should override signal on derived Request instances", () => { const parentAbortController = new AbortController(); const derivedAbortController = new AbortController(); const parentRequest = new Request(`${base}hello`, { - signal: parentAbortController.signal + signal: parentAbortController.signal, }); const derivedRequest = new Request(parentRequest, { - signal: derivedAbortController.signal + signal: derivedAbortController.signal, }); expect(parentRequest.signal).to.equal(parentAbortController.signal); expect(derivedRequest.signal).to.equal(derivedAbortController.signal); }); - it('should allow overriding signal on derived Request instances', () => { + it("should allow overriding signal on derived Request instances", () => { const parentAbortController = new AbortController(); const parentRequest = new Request(`${base}hello`, { - signal: parentAbortController.signal + signal: parentAbortController.signal, }); const derivedRequest = new Request(parentRequest, { - signal: null + signal: null, }); expect(parentRequest.signal).to.equal(parentAbortController.signal); expect(derivedRequest.signal).to.not.equal(null); expect(derivedRequest.signal).to.not.equal(parentAbortController.signal); }); - it('should abort signal', () => { + it("should abort signal", () => { const controller = new AbortController(); const request = new Request(base, { signal: controller.signal, @@ -176,7 +182,7 @@ describe('Request', () => { expect(request.signal.aborted).to.equal(true); }); - it('should abort signal after clone', () => { + it("should abort signal after clone", () => { const controller = new AbortController(); const request = new Request(base, { signal: controller.signal, @@ -187,165 +193,173 @@ describe('Request', () => { }); it('should default to "same-origin" as credentials', () => { - const request = new Request(base) - expect(request.credentials).to.equal('same-origin'); - }) - - it('should respect custom credentials value', () => { - expect(new Request(base, { credentials: 'omit'})).to.have.property('credentials', 'omit'); - expect(new Request(base, { credentials: 'include'})).to.have.property('credentials', 'include'); - }) - - it('should throw error with GET/HEAD requests with body', () => { - expect(() => new Request(base, {body: ''})) - .to.throw(TypeError); - expect(() => new Request(base, {body: 'a'})) - .to.throw(TypeError); - expect(() => new Request(base, {body: '', method: 'HEAD'})) - .to.throw(TypeError); - expect(() => new Request(base, {body: 'a', method: 'HEAD'})) - .to.throw(TypeError); - expect(() => new Request(base, {body: 'a', method: 'get'})) - .to.throw(TypeError); - expect(() => new Request(base, {body: 'a', method: 'head'})) - .to.throw(TypeError); + const request = new Request(base); + expect(request.credentials).to.equal("same-origin"); + }); + + it("should respect custom credentials value", () => { + expect(new Request(base, { credentials: "omit" })).to.have.property( + "credentials", + "omit" + ); + expect(new Request(base, { credentials: "include" })).to.have.property( + "credentials", + "include" + ); + }); + + it("should throw error with GET/HEAD requests with body", () => { + expect(() => new Request(base, { body: "" })).to.throw(TypeError); + expect(() => new Request(base, { body: "a" })).to.throw(TypeError); + expect(() => new Request(base, { body: "", method: "HEAD" })).to.throw( + TypeError + ); + expect(() => new Request(base, { body: "a", method: "HEAD" })).to.throw( + TypeError + ); + expect(() => new Request(base, { body: "a", method: "get" })).to.throw( + TypeError + ); + expect(() => new Request(base, { body: "a", method: "head" })).to.throw( + TypeError + ); }); - it('should default to null as body', () => { + it("should default to null as body", () => { const request = new Request(base); expect(request.body).to.equal(null); - return request.text().then(result => expect(result).to.equal('')); + return request.text().then((result) => expect(result).to.equal("")); }); - it('should support parsing headers', () => { + it("should support parsing headers", () => { const url = base; const request = new Request(url, { headers: { - a: '1' - } + a: "1", + }, }); expect(request.url).to.equal(url); - expect(request.headers.get('a')).to.equal('1'); + expect(request.headers.get("a")).to.equal("1"); }); - it('should support arrayBuffer() method', () => { + it("should support arrayBuffer() method", () => { const url = base; const request = new Request(url, { - method: 'POST', - body: 'a=1' + method: "POST", + body: "a=1", }); expect(request.url).to.equal(url); - return request.arrayBuffer().then(result => { + return request.arrayBuffer().then((result) => { expect(result).to.be.an.instanceOf(ArrayBuffer); const string = String.fromCharCode.apply(null, new Uint8Array(result)); - expect(string).to.equal('a=1'); + expect(string).to.equal("a=1"); }); }); - it('should support text() method', () => { + it("should support text() method", () => { const url = base; const request = new Request(url, { - method: 'POST', - body: 'a=1' + method: "POST", + body: "a=1", }); expect(request.url).to.equal(url); - return request.text().then(result => { - expect(result).to.equal('a=1'); + return request.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support json() method', () => { + it("should support json() method", () => { const url = base; const request = new Request(url, { - method: 'POST', - body: '{"a":1}' + method: "POST", + body: '{"a":1}', }); expect(request.url).to.equal(url); - return request.json().then(result => { + return request.json().then((result) => { expect(result.a).to.equal(1); }); }); - it('should support blob() method', () => { + it("should support blob() method", () => { const url = base; const request = new Request(url, { - method: 'POST', - body: Buffer.from('a=1') + method: "POST", + body: Buffer.from("a=1"), }); expect(request.url).to.equal(url); - return request.blob().then(result => { + return request.blob().then((result) => { expect(result).to.be.an.instanceOf(Blob); expect(result.size).to.equal(3); - expect(result.type).to.equal(''); + expect(result.type).to.equal(""); }); }); - it('should support clone() method', () => { + it("should support clone() method", () => { const url = base; const body = new ReadableStream({ start(c) { - c.enqueue('a=1'); + c.enqueue("a=1"); c.close(); - } + }, }); const agent = new http.Agent(); - const {signal} = new AbortController(); + const { signal } = new AbortController(); const request = new Request(url, { body, - method: 'POST', - redirect: 'manual', + method: "POST", + redirect: "manual", headers: { - b: '2' + b: "2", }, follow: 3, compress: false, agent, - signal + signal, }); const cl = request.clone(); expect(cl.url).to.equal(url); - expect(cl.method).to.equal('POST'); - expect(cl.redirect).to.equal('manual'); - expect(cl.headers.get('b')).to.equal('2'); + expect(cl.method).to.equal("POST"); + expect(cl.redirect).to.equal("manual"); + expect(cl.headers.get("b")).to.equal("2"); expect(cl.follow).to.equal(3); expect(cl.compress).to.equal(false); - expect(cl.method).to.equal('POST'); + expect(cl.method).to.equal("POST"); expect(cl.counter).to.equal(0); expect(cl.agent).to.equal(agent); expect(cl.signal).to.equal(signal); // Clone body shouldn't be the same body expect(cl.body).to.not.equal(body); - return Promise.all([cl.text(), request.text()]).then(results => { - expect(results[0]).to.equal('a=1'); - expect(results[1]).to.equal('a=1'); + return Promise.all([cl.text(), request.text()]).then((results) => { + expect(results[0]).to.equal("a=1"); + expect(results[1]).to.equal("a=1"); }); }); - it('should support clone() method with null body', () => { + it("should support clone() method with null body", () => { const url = base; const agent = new http.Agent(); - const {signal} = new AbortController(); + const { signal } = new AbortController(); const request = new Request(url, { - method: 'POST', - redirect: 'manual', + method: "POST", + redirect: "manual", headers: { - b: '2' + b: "2", }, follow: 3, compress: false, agent, - signal + signal, }); const cl = request.clone(); expect(cl.url).to.equal(url); - expect(cl.method).to.equal('POST'); - expect(cl.redirect).to.equal('manual'); - expect(cl.headers.get('b')).to.equal('2'); + expect(cl.method).to.equal("POST"); + expect(cl.redirect).to.equal("manual"); + expect(cl.headers.get("b")).to.equal("2"); expect(cl.follow).to.equal(3); expect(cl.compress).to.equal(false); - expect(cl.method).to.equal('POST'); + expect(cl.method).to.equal("POST"); expect(cl.counter).to.equal(0); expect(cl.agent).to.equal(agent); expect(cl.signal).to.equal(signal); @@ -353,55 +367,55 @@ describe('Request', () => { expect(cl.body).to.equal(null); }); - it('should support ArrayBuffer as body', () => { + it("should support ArrayBuffer as body", () => { const encoder = new TextEncoder(); const request = new Request(base, { - method: 'POST', - body: encoder.encode('a=1').buffer + method: "POST", + body: encoder.encode("a=1").buffer, }); - return request.text().then(result => { - expect(result).to.equal('a=1'); + return request.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support Uint8Array as body', () => { + it("should support Uint8Array as body", () => { const encoder = new TextEncoder(); const request = new Request(base, { - method: 'POST', - body: encoder.encode('a=1') + method: "POST", + body: encoder.encode("a=1"), }); - return request.text().then(result => { - expect(result).to.equal('a=1'); + return request.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support DataView as body', () => { + it("should support DataView as body", () => { const encoder = new TextEncoder(); const request = new Request(base, { - method: 'POST', - body: new DataView(encoder.encode('a=1').buffer) + method: "POST", + body: new DataView(encoder.encode("a=1").buffer), }); - return request.text().then(result => { - expect(result).to.equal('a=1'); + return request.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should read formData after clone with web FormData body',async () => { + it("should read formData after clone with web FormData body", async () => { const ogFormData = new WebFormData(); - ogFormData.append('a', 1); - ogFormData.append('b', 2); - ogFormData.append('file', new File(['content'], 'file.txt')); + ogFormData.append("a", 1); + ogFormData.append("b", 2); + ogFormData.append("file", new File(["content"], "file.txt")); const request = new Request(base, { - method: 'POST', + method: "POST", body: ogFormData, }); const clonedRequest = request.clone(); - return clonedRequest.formData().then(async clonedFormData => { - expect(clonedFormData.get('a')).to.equal("1"); - expect(clonedFormData.get('b')).to.equal("2"); - const file = clonedFormData.get('file') + return clonedRequest.formData().then(async (clonedFormData) => { + expect(clonedFormData.get("a")).to.equal("1"); + expect(clonedFormData.get("b")).to.equal("2"); + const file = clonedFormData.get("file"); if (typeof file !== "object") { throw new Error("File is not an object"); } @@ -409,26 +423,26 @@ describe('Request', () => { expect(file.type).to.equal("application/octet-stream"); expect(file.size).to.equal(7); expect(await file.text()).to.equal("content"); - expect(file.lastModified).to.be.a('number'); + expect(file.lastModified).to.be.a("number"); }); }); - it('should read formData after clone with node FormData body',async () => { + it("should read formData after clone with node FormData body", async () => { const ogFormData = new FormData(); - ogFormData.append('a', '1'); - ogFormData.append('b', '2'); - ogFormData.append('file', Buffer.from('content'), { filename: "file.txt" }); + ogFormData.append("a", "1"); + ogFormData.append("b", "2"); + ogFormData.append("file", Buffer.from("content"), { filename: "file.txt" }); const request = new Request(base, { - method: 'POST', + method: "POST", body: ogFormData, }); const clonedRequest = request.clone(); - return clonedRequest.formData().then(async clonedFormData => { - expect(clonedFormData.get('a')).to.equal("1"); - expect(clonedFormData.get('b')).to.equal("2"); - const file = clonedFormData.get('file') + return clonedRequest.formData().then(async (clonedFormData) => { + expect(clonedFormData.get("a")).to.equal("1"); + expect(clonedFormData.get("b")).to.equal("2"); + const file = clonedFormData.get("file"); if (typeof file !== "object") { throw new Error("File is not an object"); } @@ -436,7 +450,7 @@ describe('Request', () => { expect(file.type).to.equal("text/plain"); expect(file.size).to.equal(7); expect(await file.text()).to.equal("content"); - expect(file.lastModified).to.be.a('number'); + expect(file.lastModified).to.be.a("number"); }); }); }); diff --git a/packages/fetch/test/response.js b/packages/fetch/test/response.js index b4c2957..6945e8f 100644 --- a/packages/fetch/test/response.js +++ b/packages/fetch/test/response.js @@ -1,14 +1,13 @@ +import { TextEncoder } from "util"; +import chai from "chai"; +import { Blob } from "@remix-run/web-blob"; +import { Response } from "@remix-run/web-fetch"; +import TestServer from "./utils/server.js"; +import { ReadableStream } from "../src/package.js"; -import {TextEncoder} from 'util'; -import chai from 'chai'; -import {Blob} from '@remix-run/web-blob'; -import {Response} from '@remix-run/web-fetch'; -import TestServer from './utils/server.js'; -import { ReadableStream } from '../src/package.js'; +const { expect } = chai; -const {expect} = chai; - -describe('Response', () => { +describe("Response", () => { const local = new TestServer(); let base; @@ -21,7 +20,7 @@ describe('Response', () => { return local.stop(); }); - it('should have attributes conforming to Web IDL', () => { + it("should have attributes conforming to Web IDL", () => { const res = new Response(); const enumerableProperties = []; for (const property in res) { @@ -29,202 +28,203 @@ describe('Response', () => { } for (const toCheck of [ - 'body', - 'bodyUsed', - 'arrayBuffer', - 'blob', - 'json', - 'text', - 'url', - 'status', - 'ok', - 'redirected', - 'statusText', - 'headers', - 'clone' + "body", + "bodyUsed", + "arrayBuffer", + "blob", + "json", + "text", + "url", + "status", + "ok", + "redirected", + "statusText", + "headers", + "clone", ]) { expect(enumerableProperties).to.contain(toCheck); } for (const toCheck of [ - 'body', - 'bodyUsed', - 'url', - 'status', - 'ok', - 'redirected', - 'statusText', - 'headers' + "body", + "bodyUsed", + "url", + "status", + "ok", + "redirected", + "statusText", + "headers", ]) { expect(() => { - res[toCheck] = 'abc'; + res[toCheck] = "abc"; }).to.throw(); } }); - it('should support empty options', () => { - const res = new Response(streamFromString('a=1')); - return res.text().then(result => { - expect(result).to.equal('a=1'); + it("should support empty options", () => { + const res = new Response(streamFromString("a=1")); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support parsing headers', () => { + it("should support parsing headers", () => { const res = new Response(null, { headers: { - a: '1' - } + a: "1", + }, }); - expect(res.headers.get('a')).to.equal('1'); + expect(res.headers.get("a")).to.equal("1"); }); - it('should support text() method', () => { - const res = new Response('a=1'); - return res.text().then(result => { - expect(result).to.equal('a=1'); + it("should support text() method", () => { + const res = new Response("a=1"); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support json() method', () => { + it("should support json() method", () => { const res = new Response('{"a":1}'); - return res.json().then(result => { + return res.json().then((result) => { expect(result.a).to.equal(1); }); }); - it('should support blob() method', () => { - const res = new Response('a=1', { - method: 'POST', + it("should support blob() method", () => { + const res = new Response("a=1", { + method: "POST", headers: { - 'Content-Type': 'text/plain' - } + "Content-Type": "text/plain", + }, }); - return res.blob().then(result => { + return res.blob().then((result) => { expect(result).to.be.an.instanceOf(Blob); expect(result.size).to.equal(3); - expect(result.type).to.equal('text/plain'); + expect(result.type).to.equal("text/plain"); }); }); - it('should support clone() method', () => { - const body = streamFromString('a=1'); + it("should support clone() method", () => { + const body = streamFromString("a=1"); const res = new Response(body, { headers: { - a: '1' + a: "1", }, url: base, status: 346, - statusText: 'production' + statusText: "production", }); const cl = res.clone(); - expect(cl.headers.get('a')).to.equal('1'); + expect(cl.headers.get("a")).to.equal("1"); expect(cl.url).to.equal(base); expect(cl.status).to.equal(346); - expect(cl.statusText).to.equal('production'); + expect(cl.statusText).to.equal("production"); expect(cl.ok).to.be.false; // Clone body shouldn't be the same body expect(cl.body).to.not.equal(body); - return cl.text().then(result => { - expect(result).to.equal('a=1'); + return cl.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support clone() method with null body', () => { + it("should support clone() method with null body", () => { const res = new Response(null, { headers: { - a: '1' + a: "1", }, url: base, status: 346, - statusText: 'production' + statusText: "production", }); const cl = res.clone(); - expect(cl.headers.get('a')).to.equal('1'); + expect(cl.headers.get("a")).to.equal("1"); expect(cl.url).to.equal(base); expect(cl.status).to.equal(346); - expect(cl.statusText).to.equal('production'); + expect(cl.statusText).to.equal("production"); expect(cl.ok).to.be.false; // Clone body should also be null expect(cl.body).to.equal(null); - return cl.text().then(result => { - expect(result).to.equal(''); + return cl.text().then((result) => { + expect(result).to.equal(""); }); }); - it('should support stream as body', () => { - const body = streamFromString('a=1'); + it("should support stream as body", () => { + const body = streamFromString("a=1"); const res = new Response(body); - return res.text().then(result => { - expect(result).to.equal('a=1'); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support string as body', () => { - const res = new Response('a=1'); - return res.text().then(result => { - expect(result).to.equal('a=1'); + it("should support string as body", () => { + const res = new Response("a=1"); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support buffer as body', () => { - const res = new Response(Buffer.from('a=1')); - return res.text().then(result => { - expect(result).to.equal('a=1'); + it("should support buffer as body", () => { + const res = new Response(Buffer.from("a=1")); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support ArrayBuffer as body', () => { + it("should support ArrayBuffer as body", () => { const encoder = new TextEncoder(); - const res = new Response(encoder.encode('a=1')); - return res.text().then(result => { - expect(result).to.equal('a=1'); + const res = new Response(encoder.encode("a=1")); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support blob as body', () => { - const res = new Response(new Blob(['a=1'])); - return res.text().then(result => { - expect(result).to.equal('a=1'); + it("should support blob as body", () => { + const res = new Response(new Blob(["a=1"])); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support Uint8Array as body', () => { + it("should support Uint8Array as body", () => { const encoder = new TextEncoder(); - const res = new Response(encoder.encode('a=1')); - return res.text().then(result => { - expect(result).to.equal('a=1'); + const res = new Response(encoder.encode("a=1")); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should support DataView as body', () => { + it("should support DataView as body", () => { const encoder = new TextEncoder(); - const res = new Response(new DataView(encoder.encode('a=1').buffer)); - return res.text().then(result => { - expect(result).to.equal('a=1'); + const res = new Response(new DataView(encoder.encode("a=1").buffer)); + return res.text().then((result) => { + expect(result).to.equal("a=1"); }); }); - it('should default to null as body', () => { + it("should default to null as body", () => { const res = new Response(); expect(res.body).to.equal(null); - return res.text().then(result => expect(result).to.equal('')); + return res.text().then((result) => expect(result).to.equal("")); }); - it('should default to 200 as status code', () => { + it("should default to 200 as status code", () => { const res = new Response(null); expect(res.status).to.equal(200); }); - it('should default to empty string as url', () => { + it("should default to empty string as url", () => { const res = new Response(); - expect(res.url).to.equal(''); + expect(res.url).to.equal(""); }); }); -const streamFromString = text => new ReadableStream({ - start(controller) { - controller.enqueue(Buffer.from(text)); - controller.close(); - } -}); +const streamFromString = (text) => + new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from(text)); + controller.close(); + }, + }); diff --git a/packages/fetch/test/utils/chai-timeout.js b/packages/fetch/test/utils/chai-timeout.js index 6838da3..a10aadd 100644 --- a/packages/fetch/test/utils/chai-timeout.js +++ b/packages/fetch/test/utils/chai-timeout.js @@ -1,15 +1,15 @@ -import pTimeout from 'p-timeout'; +import pTimeout from "p-timeout"; -export default ({Assertion}, utils) => { - utils.addProperty(Assertion.prototype, 'timeout', async function () { +export default ({ Assertion }, utils) => { + utils.addProperty(Assertion.prototype, "timeout", async function () { let timeouted = false; await pTimeout(this._obj, 150, () => { timeouted = true; }); return this.assert( timeouted, - 'expected promise to timeout but it was resolved', - 'expected promise not to timeout but it timed out' + "expected promise to timeout but it was resolved", + "expected promise not to timeout but it timed out" ); }); }; diff --git a/packages/fetch/test/utils/server.js b/packages/fetch/test/utils/server.js index 2c28ab8..bec45d0 100644 --- a/packages/fetch/test/utils/server.js +++ b/packages/fetch/test/utils/server.js @@ -1,7 +1,7 @@ -import http from 'http'; -import zlib from 'zlib'; -import Busboy from 'busboy'; -import {once} from 'events'; +import http from "http"; +import zlib from "zlib"; +import Busboy from "busboy"; +import { once } from "events"; export default class TestServer { constructor() { @@ -9,22 +9,22 @@ export default class TestServer { // Node 8 default keepalive timeout is 5000ms // make it shorter here as we want to close server quickly at the end of tests this.server.keepAliveTimeout = 1000; - this.server.on('error', err => { + this.server.on("error", (err) => { console.log(err.stack); }); - this.server.on('connection', socket => { + this.server.on("connection", (socket) => { socket.setTimeout(1500); }); } async start() { - this.server.listen(0, 'localhost'); - return once(this.server, 'listening'); + this.server.listen(0, "localhost"); + return once(this.server, "listening"); } async stop() { this.server.close(); - return once(this.server, 'close'); + return once(this.server, "close"); } get port() { @@ -32,7 +32,7 @@ export default class TestServer { } get hostname() { - return 'localhost'; + return "localhost"; } mockResponse(responseHandler) { @@ -43,62 +43,64 @@ export default class TestServer { router(request, res) { const p = request.url; - if (p === '/mocked') { + if (p === "/mocked") { if (this.nextResponseHandler) { this.nextResponseHandler(res); this.nextResponseHandler = undefined; } else { - throw new Error('No mocked response. Use ’TestServer.mockResponse()’.'); + throw new Error("No mocked response. Use ’TestServer.mockResponse()’."); } } - if (p === '/hello') { + if (p === "/hello") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('world'); + res.setHeader("Content-Type", "text/plain"); + res.end("world"); } - if (p.includes('question')) { + if (p.includes("question")) { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('ok'); + res.setHeader("Content-Type", "text/plain"); + res.end("ok"); } - if (p === '/plain') { + if (p === "/plain") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('text'); + res.setHeader("Content-Type", "text/plain"); + res.end("text"); } - if (p === '/no-status-text') { - res.writeHead(200, '', {}).end(); + if (p === "/no-status-text") { + res.writeHead(200, "", {}).end(); } - if (p === '/options') { + if (p === "/options") { res.statusCode = 200; - res.setHeader('Allow', 'GET, HEAD, OPTIONS'); - res.end('hello world'); + res.setHeader("Allow", "GET, HEAD, OPTIONS"); + res.end("hello world"); } - if (p === '/html') { + if (p === "/html") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/html'); - res.end(''); + res.setHeader("Content-Type", "text/html"); + res.end(""); } - if (p === '/json') { + if (p === "/json") { res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - name: 'value' - })); + res.setHeader("Content-Type", "application/json"); + res.end( + JSON.stringify({ + name: "value", + }) + ); } - if (p === '/gzip') { + if (p === "/gzip") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'gzip'); - zlib.gzip('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "gzip"); + zlib.gzip("hello world", (err, buffer) => { if (err) { throw err; } @@ -107,11 +109,11 @@ export default class TestServer { }); } - if (p === '/gzip-truncated') { + if (p === "/gzip-truncated") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'gzip'); - zlib.gzip('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "gzip"); + zlib.gzip("hello world", (err, buffer) => { if (err) { throw err; } @@ -121,11 +123,11 @@ export default class TestServer { }); } - if (p === '/gzip-capital') { + if (p === "/gzip-capital") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'GZip'); - zlib.gzip('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "GZip"); + zlib.gzip("hello world", (err, buffer) => { if (err) { throw err; } @@ -134,11 +136,11 @@ export default class TestServer { }); } - if (p === '/deflate') { + if (p === "/deflate") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'deflate'); - zlib.deflate('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "deflate"); + zlib.deflate("hello world", (err, buffer) => { if (err) { throw err; } @@ -147,12 +149,12 @@ export default class TestServer { }); } - if (p === '/brotli') { + if (p === "/brotli") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - if (typeof zlib.createBrotliDecompress === 'function') { - res.setHeader('Content-Encoding', 'br'); - zlib.brotliCompress('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + if (typeof zlib.createBrotliDecompress === "function") { + res.setHeader("Content-Encoding", "br"); + zlib.brotliCompress("hello world", (err, buffer) => { if (err) { throw err; } @@ -162,11 +164,11 @@ export default class TestServer { } } - if (p === '/deflate-raw') { + if (p === "/deflate-raw") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'deflate'); - zlib.deflateRaw('hello world', (err, buffer) => { + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "deflate"); + zlib.deflateRaw("hello world", (err, buffer) => { if (err) { throw err; } @@ -175,189 +177,189 @@ export default class TestServer { }); } - if (p === '/sdch') { + if (p === "/sdch") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'sdch'); - res.end('fake sdch string'); + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "sdch"); + res.end("fake sdch string"); } - if (p === '/invalid-content-encoding') { + if (p === "/invalid-content-encoding") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.setHeader('Content-Encoding', 'gzip'); - res.end('fake gzip string'); + res.setHeader("Content-Type", "text/plain"); + res.setHeader("Content-Encoding", "gzip"); + res.end("fake gzip string"); } - if (p === '/timeout') { + if (p === "/timeout") { setTimeout(() => { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('text'); + res.setHeader("Content-Type", "text/plain"); + res.end("text"); }, 1000); } - if (p === '/slow') { + if (p === "/slow") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.write('test'); + res.setHeader("Content-Type", "text/plain"); + res.write("test"); setTimeout(() => { - res.end('test'); + res.end("test"); }, 1000); } - if (p === '/cookie') { + if (p === "/cookie") { res.statusCode = 200; - res.setHeader('Set-Cookie', ['a=1', 'b=1']); - res.end('cookie'); + res.setHeader("Set-Cookie", ["a=1", "b=1"]); + res.end("cookie"); } - if (p === '/size/chunk') { + if (p === "/size/chunk") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); + res.setHeader("Content-Type", "text/plain"); setTimeout(() => { - res.write('test'); + res.write("test"); }, 10); setTimeout(() => { - res.end('test'); + res.end("test"); }, 20); } - if (p === '/size/long') { + if (p === "/size/long") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('testtest'); + res.setHeader("Content-Type", "text/plain"); + res.end("testtest"); } - if (p === '/redirect/301') { + if (p === "/redirect/301") { res.statusCode = 301; - res.setHeader('Location', '/inspect'); + res.setHeader("Location", "/inspect"); res.end(); } - if (p === '/redirect/301/rn') { - res.statusCode = 301 - res.setHeader('Location', '/403') - res.write('301 Permanently moved.\r\n'); + if (p === "/redirect/301/rn") { + res.statusCode = 301; + res.setHeader("Location", "/403"); + res.write("301 Permanently moved.\r\n"); res.end(); } - if (p === '/403') { + if (p === "/403") { res.statusCode = 403; - res.write('403 Forbidden'); + res.write("403 Forbidden"); res.end(); } - if (p === '/redirect/302') { + if (p === "/redirect/302") { res.statusCode = 302; - res.setHeader('Location', '/inspect'); + res.setHeader("Location", "/inspect"); res.end(); } - if (p === '/redirect/303') { + if (p === "/redirect/303") { res.statusCode = 303; - res.setHeader('Location', '/inspect'); + res.setHeader("Location", "/inspect"); res.end(); } - if (p === '/redirect/307') { + if (p === "/redirect/307") { res.statusCode = 307; - res.setHeader('Location', '/inspect'); + res.setHeader("Location", "/inspect"); res.end(); } - if (p === '/redirect/308') { + if (p === "/redirect/308") { res.statusCode = 308; - res.setHeader('Location', '/inspect'); + res.setHeader("Location", "/inspect"); res.end(); } - if (p === '/redirect/chain') { + if (p === "/redirect/chain") { res.statusCode = 301; - res.setHeader('Location', '/redirect/301'); + res.setHeader("Location", "/redirect/301"); res.end(); } - if (p === '/redirect/no-location') { + if (p === "/redirect/no-location") { res.statusCode = 301; res.end(); } - if (p === '/redirect/slow') { + if (p === "/redirect/slow") { res.statusCode = 301; - res.setHeader('Location', '/redirect/301'); + res.setHeader("Location", "/redirect/301"); setTimeout(() => { res.end(); }, 1000); } - if (p === '/redirect/slow-chain') { + if (p === "/redirect/slow-chain") { res.statusCode = 301; - res.setHeader('Location', '/redirect/slow'); + res.setHeader("Location", "/redirect/slow"); setTimeout(() => { res.end(); }, 10); } - if (p === '/redirect/slow-stream') { + if (p === "/redirect/slow-stream") { res.statusCode = 301; - res.setHeader('Location', '/slow'); + res.setHeader("Location", "/slow"); res.end(); } - if (p === '/redirect/bad-location') { - res.socket.write('HTTP/1.1 301\r\nLocation: ☃\r\nContent-Length: 0\r\n'); - res.socket.end('\r\n'); + if (p === "/redirect/bad-location") { + res.socket.write("HTTP/1.1 301\r\nLocation: ☃\r\nContent-Length: 0\r\n"); + res.socket.end("\r\n"); } - if (p === '/redirect/chunked') { + if (p === "/redirect/chunked") { res.writeHead(301, { - Location: '/inspect', - 'Transfer-Encoding': 'chunked' + Location: "/inspect", + "Transfer-Encoding": "chunked", }); setTimeout(() => res.end(), 10); } - if (p === '/error/400') { + if (p === "/error/400") { res.statusCode = 400; - res.setHeader('Content-Type', 'text/plain'); - res.end('client error'); + res.setHeader("Content-Type", "text/plain"); + res.end("client error"); } - if (p === '/error/404') { + if (p === "/error/404") { res.statusCode = 404; - res.setHeader('Content-Encoding', 'gzip'); + res.setHeader("Content-Encoding", "gzip"); res.end(); } - if (p === '/error/500') { + if (p === "/error/500") { res.statusCode = 500; - res.setHeader('Content-Type', 'text/plain'); - res.end('server error'); + res.setHeader("Content-Type", "text/plain"); + res.end("server error"); } - if (p === '/error/reset') { + if (p === "/error/reset") { res.destroy(); } - if (p === '/error/premature') { - res.writeHead(200, {'content-length': 50}); - res.write('foo'); + if (p === "/error/premature") { + res.writeHead(200, { "content-length": 50 }); + res.write("foo"); setTimeout(() => { res.destroy(); }, 100); } - if (p === '/error/premature/chunked') { + if (p === "/error/premature/chunked") { res.writeHead(200, { - 'Content-Type': 'application/json', - 'Transfer-Encoding': 'chunked' + "Content-Type": "application/json", + "Transfer-Encoding": "chunked", }); - res.write(`${JSON.stringify({data: 'hi'})}\n`); + res.write(`${JSON.stringify({ data: "hi" })}\n`); setTimeout(() => { - res.write(`${JSON.stringify({data: 'bye'})}\n`); + res.write(`${JSON.stringify({ data: "bye" })}\n`); }, 200); setTimeout(() => { @@ -365,105 +367,110 @@ export default class TestServer { }, 400); } - if (p === '/chunked/split-ending') { - res.socket.write('HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n'); - res.socket.write('3\r\nfoo\r\n3\r\nbar\r\n'); + if (p === "/chunked/split-ending") { + res.socket.write("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); + res.socket.write("3\r\nfoo\r\n3\r\nbar\r\n"); setTimeout(() => { - res.socket.write('0\r\n'); + res.socket.write("0\r\n"); }, 10); setTimeout(() => { - res.socket.end('\r\n'); + res.socket.end("\r\n"); }, 20); } - if (p === '/chunked/multiple-ending') { - res.socket.write('HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n'); - res.socket.write('3\r\nfoo\r\n3\r\nbar\r\n0\r\n\r\n'); + if (p === "/chunked/multiple-ending") { + res.socket.write("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); + res.socket.write("3\r\nfoo\r\n3\r\nbar\r\n0\r\n\r\n"); } - if (p === '/error/json') { + if (p === "/error/json") { res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end('invalid json'); + res.setHeader("Content-Type", "application/json"); + res.end("invalid json"); } - if (p === '/no-content') { + if (p === "/no-content") { res.statusCode = 204; res.end(); } - if (p === '/no-content/gzip') { + if (p === "/no-content/gzip") { res.statusCode = 204; - res.setHeader('Content-Encoding', 'gzip'); + res.setHeader("Content-Encoding", "gzip"); res.end(); } - if (p === '/no-content/brotli') { + if (p === "/no-content/brotli") { res.statusCode = 204; - res.setHeader('Content-Encoding', 'br'); + res.setHeader("Content-Encoding", "br"); res.end(); } - if (p === '/not-modified') { + if (p === "/not-modified") { res.statusCode = 304; res.end(); } - if (p === '/not-modified/gzip') { + if (p === "/not-modified/gzip") { res.statusCode = 304; - res.setHeader('Content-Encoding', 'gzip'); + res.setHeader("Content-Encoding", "gzip"); res.end(); } - if (p === '/inspect') { + if (p === "/inspect") { res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - let body = ''; - request.on('data', c => { + res.setHeader("Content-Type", "application/json"); + let body = ""; + request.on("data", (c) => { body += c; }); - request.on('end', () => { - res.end(JSON.stringify({ - method: request.method, - url: request.url, - headers: request.headers, - body - })); + request.on("end", () => { + res.end( + JSON.stringify({ + method: request.method, + url: request.url, + headers: request.headers, + body, + }) + ); }); } - if (p === '/multipart') { + if (p === "/multipart") { res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - const busboy = new Busboy({headers: request.headers}); - let body = ''; - busboy.on('file', async (fieldName, file, fileName) => { + res.setHeader("Content-Type", "application/json"); + const busboy = new Busboy({ headers: request.headers }); + let body = ""; + busboy.on("file", async (fieldName, file, fileName) => { body += `${fieldName}=${fileName}`; // consume file data // eslint-disable-next-line no-empty, no-unused-vars - for await (const c of file) { } + for await (const c of file) { + } }); - busboy.on('field', (fieldName, value) => { + busboy.on("field", (fieldName, value) => { body += `${fieldName}=${value}`; }); - busboy.on('finish', () => { - res.end(JSON.stringify({ - method: request.method, - url: request.url, - headers: request.headers, - body - })); + busboy.on("finish", () => { + res.end( + JSON.stringify({ + method: request.method, + url: request.url, + headers: request.headers, + body, + }) + ); }); request.pipe(busboy); } - if (p === '/m%C3%B6bius') { + if (p === "/m%C3%B6bius") { res.statusCode = 200; - res.setHeader('Content-Type', 'text/plain'); - res.end('ok'); + res.setHeader("Content-Type", "text/plain"); + res.end("ok"); } } } diff --git a/packages/fetch/tsconfig.json b/packages/fetch/tsconfig.json index 35fce01..7a7de7f 100644 --- a/packages/fetch/tsconfig.json +++ b/packages/fetch/tsconfig.json @@ -1,14 +1,10 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "paths": { - "@remix-run/web-fetch": [ - "packages/fetch/src/lib.node.js" - ] - } - }, - "include": [ - "src" - ] + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "paths": { + "@remix-run/web-fetch": ["packages/fetch/src/lib.node.js"] + } + }, + "include": ["src"] } diff --git a/packages/file/CHANGELOG.md b/packages/file/CHANGELOG.md index 2860a43..a33f486 100644 --- a/packages/file/CHANGELOG.md +++ b/packages/file/CHANGELOG.md @@ -2,27 +2,23 @@ ### [3.0.2](https://www.github.com/web-std/io/compare/file-v3.0.1...file-v3.0.2) (2022-01-21) - ### Changes -* **file:** update blob dep version ([767988b](https://www.github.com/web-std/io/commit/767988b9dade84ee04b8cda515c114cba8a1f659)) +- **file:** update blob dep version ([767988b](https://www.github.com/web-std/io/commit/767988b9dade84ee04b8cda515c114cba8a1f659)) ### [3.0.1](https://www.github.com/web-std/io/compare/file-v3.0.0...file-v3.0.1) (2022-01-19) - ### Bug Fixes -* ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) +- ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) ## 3.0.0 (2021-11-05) - ### Features -* revamp the repo ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) - +- revamp the repo ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) ### Changes -* align package versions ([09c8676](https://www.github.com/web-std/io/commit/09c8676348619313d9df24d9597cea0eb82704d2)) -* bump versions ([#27](https://www.github.com/web-std/io/issues/27)) ([0fe5224](https://www.github.com/web-std/io/commit/0fe5224124e318f560dcfbd8a234d05367c9fbcb)) +- align package versions ([09c8676](https://www.github.com/web-std/io/commit/09c8676348619313d9df24d9597cea0eb82704d2)) +- bump versions ([#27](https://www.github.com/web-std/io/issues/27)) ([0fe5224](https://www.github.com/web-std/io/commit/0fe5224124e318f560dcfbd8a234d05367c9fbcb)) diff --git a/packages/file/Readme.md b/packages/file/Readme.md index c735ab9..256647e 100644 --- a/packages/file/Readme.md +++ b/packages/file/Readme.md @@ -9,10 +9,10 @@ Web API compatible [File][] for nodejs. ### Usage ```js -import { File, Blob } from "@remix-run/web-file" -const file = new File(["hello", new TextEncoder().encode("world")], "hello") +import { File, Blob } from "@remix-run/web-file"; +const file = new File(["hello", new TextEncoder().encode("world")], "hello"); for await (const chunk of blob.stream()) { - console.log(chunk) + console.log(chunk); } ``` diff --git a/packages/file/package.json b/packages/file/package.json index 8991893..f0379dd 100644 --- a/packages/file/package.json +++ b/packages/file/package.json @@ -41,7 +41,6 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "playwright-test": "^7.2.0", - "prettier": "^2.3.0", "rimraf": "3.0.2", "rollup": "2.47.0", "rollup-plugin-multi-input": "1.2.0", @@ -57,13 +56,6 @@ "test:es": "uvu test all.spec.js", "test:web": "playwright-test -r uvu test/web.spec.js", "test:cjs": "rimraf dist && npm run build && node dist/test/all.spec.cjs", - "test": "npm run test:es && npm run test:web && npm run test:cjs", - "precommit": "lint-staged" - }, - "lint-staged": { - "*.js": [ - "prettier --no-semi --write", - "git add" - ] + "test": "npm run test:es && npm run test:web && npm run test:cjs" } } diff --git a/packages/file/rollup.config.js b/packages/file/rollup.config.js index 5c8b019..3b44510 100644 --- a/packages/file/rollup.config.js +++ b/packages/file/rollup.config.js @@ -1,4 +1,4 @@ -import multiInput from "rollup-plugin-multi-input" +import multiInput from "rollup-plugin-multi-input"; const config = [ ["test", "dist/test"], @@ -13,5 +13,5 @@ const config = [ entryFileNames: "[name].cjs", }, plugins: [multiInput({ relative: base })], -})) -export default config +})); +export default config; diff --git a/packages/file/src/file.js b/packages/file/src/file.js index b5ee1e3..366490c 100644 --- a/packages/file/src/file.js +++ b/packages/file/src/file.js @@ -1,4 +1,4 @@ -import { Blob } from "./package.js" +import { Blob } from "./package.js"; /** * @implements {globalThis.File} @@ -16,15 +16,15 @@ export class File extends Blob { name = panic(new TypeError("File constructor requires name argument")), options = {} ) { - super(init, options) + super(init, options); // Per File API spec https://w3c.github.io/FileAPI/#file-constructor // Every "/" character of file name must be replaced with a ":". /** @private */ - this._name = name + this._name = name; // It appears that browser do not follow the spec here. // String(name).replace(/\//g, ":") /** @private */ - this._lastModified = options.lastModified || Date.now() + this._lastModified = options.lastModified || Date.now(); } /** @@ -32,7 +32,7 @@ export class File extends Blob { * @type {string} */ get name() { - return this._name + return this._name; } /** @@ -40,7 +40,7 @@ export class File extends Blob { * @type {string} */ get webkitRelativePath() { - return "" + return ""; } /** @@ -49,11 +49,11 @@ export class File extends Blob { * @returns {number} */ get lastModified() { - return this._lastModified + return this._lastModified; } get [Symbol.toStringTag]() { - return "File" + return "File"; } } @@ -61,6 +61,6 @@ export class File extends Blob { * @param {*} error * @returns {never} */ -const panic = error => { - throw error -} +const panic = (error) => { + throw error; +}; diff --git a/packages/file/src/lib.js b/packages/file/src/lib.js index fdb251f..15fd43d 100644 --- a/packages/file/src/lib.js +++ b/packages/file/src/lib.js @@ -1,2 +1,2 @@ -export const File = globalThis.File -export const Blob = globalThis.Blob +export const File = globalThis.File; +export const Blob = globalThis.Blob; diff --git a/packages/file/src/lib.node.js b/packages/file/src/lib.node.js index 143bcbb..cd33796 100644 --- a/packages/file/src/lib.node.js +++ b/packages/file/src/lib.node.js @@ -1,13 +1,13 @@ -"use strict" +"use strict"; -import { Blob } from "./package.js" -import { File as WebFile } from "./file.js" +import { Blob } from "./package.js"; +import { File as WebFile } from "./file.js"; // Electron-renderer should get the browser implementation instead of node // Browser configuration is not enough // Marking export as a DOM File object instead of custom class. /** @type {typeof globalThis.File} */ -const File = WebFile +const File = WebFile; -export { File, Blob } +export { File, Blob }; diff --git a/packages/file/test/all.spec.js b/packages/file/test/all.spec.js index 1354658..c10058f 100644 --- a/packages/file/test/all.spec.js +++ b/packages/file/test/all.spec.js @@ -1,8 +1,8 @@ -import { test as fileTest } from "./file.spec.js" -import { test as fetchTest } from "./fetch.spec.js" +import { test as fileTest } from "./file.spec.js"; +import { test as fetchTest } from "./fetch.spec.js"; -import { test } from "./test.js" +import { test } from "./test.js"; -fileTest(test) -fetchTest(test) -test.run() +fileTest(test); +fetchTest(test); +test.run(); diff --git a/packages/file/test/fetch.spec.js b/packages/file/test/fetch.spec.js index a469cdb..048dc76 100644 --- a/packages/file/test/fetch.spec.js +++ b/packages/file/test/fetch.spec.js @@ -5,7 +5,7 @@ import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("node-fetch recognizes blobs", async () => { const response = new Response(new File(["hello"], "path/file.txt")); diff --git a/packages/file/test/file.spec.js b/packages/file/test/file.spec.js index 3247e46..ce938f8 100644 --- a/packages/file/test/file.spec.js +++ b/packages/file/test/file.spec.js @@ -5,7 +5,7 @@ import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("test baisc", async () => { assert.isEqual(typeof lib.Blob, "function"); assert.isEqual(typeof lib.File, "function"); @@ -25,9 +25,9 @@ export const test = test => { assert.throws(() => new File([]), TypeError); const before = Date.now(); - await new Promise(resolve => setTimeout(resolve, 3)); + await new Promise((resolve) => setTimeout(resolve, 3)); const file = new File(["test"], "name"); - await new Promise(resolve => setTimeout(resolve, 3)); + await new Promise((resolve) => setTimeout(resolve, 3)); const after = Date.now(); assert.equal(file.size, 4); assert.equal(file.name, "name"); @@ -63,7 +63,7 @@ export const test = test => { test("File with type", async () => { const file = new File(["test"], "name", { lastModified: 1594672000418, - type: "text/plain" + type: "text/plain", }); assert.equal(file.size, 4); @@ -74,7 +74,7 @@ export const test = test => { test("File type is normalized", async () => { const file = new File(["test"], "name", { - type: "Text/Plain" + type: "Text/Plain", }); assert.equal(file.size, 4); diff --git a/packages/file/test/test.js b/packages/file/test/test.js index 69eb734..d05237b 100644 --- a/packages/file/test/test.js +++ b/packages/file/test/test.js @@ -1,12 +1,11 @@ -import * as uvu from "uvu" -import * as uvuassert from "uvu/assert" - -const deepEqual = uvuassert.equal -const isEqual = uvuassert.equal -const isEquivalent = uvuassert.equal -export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent } -export const test = uvu.test +import * as uvu from "uvu"; +import * as uvuassert from "uvu/assert"; +const deepEqual = uvuassert.equal; +const isEqual = uvuassert.equal; +const isEquivalent = uvuassert.equal; +export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent }; +export const test = uvu.test; /** * @typedef {uvu.Test} Test diff --git a/packages/file/test/web.spec.js b/packages/file/test/web.spec.js index 228c7de..b98fc5d 100644 --- a/packages/file/test/web.spec.js +++ b/packages/file/test/web.spec.js @@ -1,5 +1,5 @@ -import { test as fileTest } from "./file.spec.js" -import { test } from "./test.js" +import { test as fileTest } from "./file.spec.js"; +import { test } from "./test.js"; -fileTest(test) -test.run() +fileTest(test); +test.run(); diff --git a/packages/file/tsconfig.json b/packages/file/tsconfig.json index 5deead4..75f5098 100644 --- a/packages/file/tsconfig.json +++ b/packages/file/tsconfig.json @@ -14,8 +14,5 @@ "path": "../fetch" } ], - "include": [ - "src", - "test" - ] + "include": ["src", "test"] } diff --git a/packages/form-data/package.json b/packages/form-data/package.json index 58a0d77..494a8f9 100644 --- a/packages/form-data/package.json +++ b/packages/form-data/package.json @@ -41,7 +41,6 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "playwright-test": "^7.2.0", - "prettier": "^2.3.0", "rimraf": "3.0.2", "rollup": "2.47.0", "rollup-plugin-multi-input": "1.2.0", @@ -57,13 +56,6 @@ "test:es": "uvu test all.spec.js", "test:web": "playwright-test -r uvu test/web.spec.js", "test:cjs": "rimraf dist && npm run build && node dist/test/all.spec.cjs", - "test": "npm run test:es && npm run test:cjs", - "precommit": "lint-staged" - }, - "lint-staged": { - "*.js": [ - "prettier --no-semi --write", - "git add" - ] + "test": "npm run test:es && npm run test:cjs" } } diff --git a/packages/form-data/rollup.config.js b/packages/form-data/rollup.config.js index 5c8b019..3b44510 100644 --- a/packages/form-data/rollup.config.js +++ b/packages/form-data/rollup.config.js @@ -1,4 +1,4 @@ -import multiInput from "rollup-plugin-multi-input" +import multiInput from "rollup-plugin-multi-input"; const config = [ ["test", "dist/test"], @@ -13,5 +13,5 @@ const config = [ entryFileNames: "[name].cjs", }, plugins: [multiInput({ relative: base })], -})) -export default config +})); +export default config; diff --git a/packages/form-data/src/lib.js b/packages/form-data/src/lib.js index 24647c9..42748a6 100644 --- a/packages/form-data/src/lib.js +++ b/packages/form-data/src/lib.js @@ -1 +1 @@ -export const { FormData } = globalThis +export const { FormData } = globalThis; diff --git a/packages/form-data/src/lib.node.js b/packages/form-data/src/lib.node.js index cd23ec6..07f8011 100644 --- a/packages/form-data/src/lib.node.js +++ b/packages/form-data/src/lib.node.js @@ -1,6 +1,6 @@ // @ts-check -"use strict" +"use strict"; -import * as polyfill from "./form-data.js" +import * as polyfill from "./form-data.js"; -export const FormData = polyfill.FormData +export const FormData = polyfill.FormData; diff --git a/packages/form-data/test/all.spec.js b/packages/form-data/test/all.spec.js index 57fe669..3c20b38 100644 --- a/packages/form-data/test/all.spec.js +++ b/packages/form-data/test/all.spec.js @@ -1,7 +1,7 @@ -import { test as libTest } from "./form-data.spec.js" -import { test as fetchTest } from "./fetch.spec.js" -import { test } from "./test.js" +import { test as libTest } from "./form-data.spec.js"; +import { test as fetchTest } from "./fetch.spec.js"; +import { test } from "./test.js"; -libTest(test) -fetchTest(test) -test.run() +libTest(test); +fetchTest(test); +test.run(); diff --git a/packages/form-data/test/fetch.spec.js b/packages/form-data/test/fetch.spec.js index 938777a..d846cf2 100644 --- a/packages/form-data/test/fetch.spec.js +++ b/packages/form-data/test/fetch.spec.js @@ -6,7 +6,7 @@ import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("node-fetch recognizes form-data", async () => { const data = new FormData(); data.set("file", new Blob(["hello"])); diff --git a/packages/form-data/test/form-data.spec.js b/packages/form-data/test/form-data.spec.js index 3539ab3..0d86388 100644 --- a/packages/form-data/test/form-data.spec.js +++ b/packages/form-data/test/form-data.spec.js @@ -264,7 +264,7 @@ export const test = (test) => { /** @type {globalThis.HTMLFormElement} */ let form; - if (typeof window === 'undefined') { + if (typeof window === "undefined") { /** @implements {globalThis.HTMLFormElement} */ class FakeForm { get [Symbol.toStringTag]() { @@ -294,28 +294,30 @@ export const test = (test) => { name: "remember-me", value: "on", checked: true, - } - ] + }, + ]; } get id() { - return "my-form" + return "my-form"; } } - form = /** @type {globalThis.HTMLFormElement} */ (/** @type {unknown} */ (new FakeForm())) + form = /** @type {globalThis.HTMLFormElement} */ ( + /** @type {unknown} */ (new FakeForm()) + ); } else { - form = document.createElement('form'); - let inside = document.createElement('input') - let outside = document.createElement('input') - let checkbox = document.createElement('input') - - form.id = 'my-form' - inside.name = 'inside' - outside.name = 'outside' - outside.setAttribute('form', 'my-form') - checkbox.name = "remember-me" - checkbox.type = 'checkbox' + form = document.createElement("form"); + let inside = document.createElement("input"); + let outside = document.createElement("input"); + let checkbox = document.createElement("input"); + + form.id = "my-form"; + inside.name = "inside"; + outside.name = "outside"; + outside.setAttribute("form", "my-form"); + checkbox.name = "remember-me"; + checkbox.type = "checkbox"; checkbox.checked = true; form.appendChild(inside); @@ -325,8 +327,8 @@ export const test = (test) => { } const formData = new FormData(form); - assert.equal(formData.has("inside"), true) - assert.equal(formData.has("outside"), true) - assert.equal(formData.get("remember-me"), "on") - }) + assert.equal(formData.has("inside"), true); + assert.equal(formData.has("outside"), true); + assert.equal(formData.get("remember-me"), "on"); + }); }; diff --git a/packages/form-data/test/test.js b/packages/form-data/test/test.js index c72b754..44a3a3e 100644 --- a/packages/form-data/test/test.js +++ b/packages/form-data/test/test.js @@ -1,9 +1,9 @@ -import * as uvu from "uvu" -import * as uvuassert from "uvu/assert" +import * as uvu from "uvu"; +import * as uvuassert from "uvu/assert"; -const deepEqual = uvuassert.equal -const isEqual = uvuassert.equal -const isEquivalent = uvuassert.equal +const deepEqual = uvuassert.equal; +const isEqual = uvuassert.equal; +const isEquivalent = uvuassert.equal; /** * @param {number} value @@ -11,15 +11,15 @@ const isEquivalent = uvuassert.equal * @param {string} [description] */ const isLessThan = (value, number, description) => - uvuassert.ok(value < number, description) + uvuassert.ok(value < number, description); export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent, isLessThan, -} -export const test = uvu.test +}; +export const test = uvu.test; /** * @typedef {uvu.Test} Test diff --git a/packages/form-data/test/web.spec.js b/packages/form-data/test/web.spec.js index 8c8e933..513450b 100644 --- a/packages/form-data/test/web.spec.js +++ b/packages/form-data/test/web.spec.js @@ -1,6 +1,6 @@ -import { test as formTest } from "./form-data.spec.js" -import { test } from "./test.js" +import { test as formTest } from "./form-data.spec.js"; +import { test } from "./test.js"; -formTest(test) +formTest(test); -test.run() +test.run(); diff --git a/packages/form-data/tsconfig.json b/packages/form-data/tsconfig.json index b076f88..734d997 100644 --- a/packages/form-data/tsconfig.json +++ b/packages/form-data/tsconfig.json @@ -14,8 +14,5 @@ "path": "../blob" } ], - "include": [ - "src", - "test" - ] + "include": ["src", "test"] } diff --git a/packages/stream/CHANGELOG.md b/packages/stream/CHANGELOG.md index c4805bc..66b5849 100644 --- a/packages/stream/CHANGELOG.md +++ b/packages/stream/CHANGELOG.md @@ -2,23 +2,20 @@ ### [1.0.2](https://www.github.com/web-std/io/compare/stream-v1.0.1...stream-v1.0.2) (2022-04-13) - ### Bug Fixes -* **packages/fetch:** only export what's needed so TS doesn't mess up the imports in the output files ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) -* **packages/stream:** no initializers in ambient contexts ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) -* typescript types ([#56](https://www.github.com/web-std/io/issues/56)) ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) +- **packages/fetch:** only export what's needed so TS doesn't mess up the imports in the output files ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) +- **packages/stream:** no initializers in ambient contexts ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) +- typescript types ([#56](https://www.github.com/web-std/io/issues/56)) ([30ad037](https://www.github.com/web-std/io/commit/30ad0377a88ebffc3a998616e3b774ce5bcc584a)) ### [1.0.1](https://www.github.com/web-std/io/compare/stream-v1.0.0...stream-v1.0.1) (2022-01-19) - ### Bug Fixes -* ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) +- ship less files to address TSC issues ([#35](https://www.github.com/web-std/io/issues/35)) ([0651e62](https://www.github.com/web-std/io/commit/0651e62ae42d17eae2db89858c9e44f3342c304c)) ## 1.0.0 (2021-11-05) - ### Features -* Factor out streams into separate package ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) +- Factor out streams into separate package ([#19](https://www.github.com/web-std/io/issues/19)) ([90624cf](https://www.github.com/web-std/io/commit/90624cfd2d4253c2cbc316d092f26e77b5169f47)) diff --git a/packages/stream/Readme.md b/packages/stream/Readme.md index 22615a1..dfd86c0 100644 --- a/packages/stream/Readme.md +++ b/packages/stream/Readme.md @@ -15,7 +15,7 @@ import { ReadableStream, WritableStream, TransformStream, -} from "@remix-run/web-stream" +} from "@remix-run/web-stream"; ``` ### Usage from Typescript diff --git a/packages/stream/package.json b/packages/stream/package.json index fad3113..91237cd 100644 --- a/packages/stream/package.json +++ b/packages/stream/package.json @@ -41,7 +41,6 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "playwright-test": "^7.2.0", - "prettier": "^2.3.0", "rimraf": "3.0.2", "typescript": "^4.4.4", "uvu": "0.5.2" @@ -51,13 +50,6 @@ "test:es": "uvu test all.spec.js", "test:web": "playwright-test -r uvu test/web.spec.js", "test:cjs": "node test/node.spec.cjs", - "test": "npm run test:es && npm run test:web && npm run test:cjs", - "precommit": "lint-staged" - }, - "lint-staged": { - "*.js": [ - "prettier --no-semi --write", - "git add" - ] + "test": "npm run test:es && npm run test:web && npm run test:cjs" } } diff --git a/packages/stream/src/lib.js b/packages/stream/src/lib.js index ee892ae..b7e63d7 100644 --- a/packages/stream/src/lib.js +++ b/packages/stream/src/lib.js @@ -17,4 +17,4 @@ export const { CountQueuingStrategy, TextEncoderStream, TextDecoderStream, -} = globalThis +} = globalThis; diff --git a/packages/stream/src/lib.node.js b/packages/stream/src/lib.node.js index c31bac9..defad96 100644 --- a/packages/stream/src/lib.node.js +++ b/packages/stream/src/lib.node.js @@ -1,5 +1,5 @@ // @ts-ignore -import streams from "./stream.cjs" +import streams from "./stream.cjs"; export const { ReadableStream, ReadableStreamDefaultReader, @@ -16,4 +16,4 @@ export const { CountQueuingStrategy, TextEncoderStream, TextDecoderStream, -} = streams +} = streams; diff --git a/packages/stream/test/all.spec.js b/packages/stream/test/all.spec.js index c2515ab..ec8f4fa 100644 --- a/packages/stream/test/all.spec.js +++ b/packages/stream/test/all.spec.js @@ -1,6 +1,6 @@ -import { test as libTest } from "./lib.spec.js" +import { test as libTest } from "./lib.spec.js"; -import { test } from "./test.js" +import { test } from "./test.js"; -libTest(test) -test.run() +libTest(test); +test.run(); diff --git a/packages/stream/test/lib.spec.js b/packages/stream/test/lib.spec.js index 6411205..a48df1e 100644 --- a/packages/stream/test/lib.spec.js +++ b/packages/stream/test/lib.spec.js @@ -4,7 +4,7 @@ import { assert } from "./test.js"; /** * @param {import('./test').Test} test */ -export const test = test => { +export const test = (test) => { test("test baisc", async () => { assert.isEqual(typeof lib.ReadableStream, "function"); }); diff --git a/packages/stream/test/node.spec.cjs b/packages/stream/test/node.spec.cjs index 338d255..24e7340 100644 --- a/packages/stream/test/node.spec.cjs +++ b/packages/stream/test/node.spec.cjs @@ -1,13 +1,13 @@ -const { test: libTest } = require("./lib.spec.cjs") +const { test: libTest } = require("./lib.spec.cjs"); /** * @typedef {uvu.Test} Test */ const test = async () => { - const { test } = await import("./test.js") - await libTest(test) - test.run() -} + const { test } = await import("./test.js"); + await libTest(test); + test.run(); +}; -test() +test(); diff --git a/packages/stream/test/test.js b/packages/stream/test/test.js index fafd53a..557cfcd 100644 --- a/packages/stream/test/test.js +++ b/packages/stream/test/test.js @@ -1,14 +1,14 @@ -import * as uvu from "uvu" -import * as uvuassert from "uvu/assert" +import * as uvu from "uvu"; +import * as uvuassert from "uvu/assert"; -const deepEqual = uvuassert.equal -const isEqual = uvuassert.equal -const isEquivalent = uvuassert.equal -export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent } +const deepEqual = uvuassert.equal; +const isEqual = uvuassert.equal; +const isEquivalent = uvuassert.equal; +export const assert = { ...uvuassert, deepEqual, isEqual, isEquivalent }; export const test = Object.assign(uvu.test, { test: uvu.test, assert, -}) +}); /** * @typedef {test} Test diff --git a/packages/stream/test/web.spec.js b/packages/stream/test/web.spec.js index 8ff9c9a..a327904 100644 --- a/packages/stream/test/web.spec.js +++ b/packages/stream/test/web.spec.js @@ -1,5 +1,5 @@ -import { test as libTest } from "./lib.spec.js" -import { test } from "./test.js" +import { test as libTest } from "./lib.spec.js"; +import { test } from "./test.js"; -libTest(test) -test.run() +libTest(test); +test.run(); diff --git a/packages/stream/tsconfig.json b/packages/stream/tsconfig.json index c80f2de..5feb9e6 100644 --- a/packages/stream/tsconfig.json +++ b/packages/stream/tsconfig.json @@ -5,8 +5,5 @@ "noEmit": true, "outDir": "dist" }, - "include": [ - "src", - "test", - ] + "include": ["src", "test"] } diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000..1c0267e --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1,2 @@ +/** @type {import('prettier').Config} */ +module.exports = {}; diff --git a/tsconfig.json b/tsconfig.json index a4df98e..877cd88 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,15 +2,15 @@ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Projects */ - "incremental": true, /* Enable incremental compilation */ - "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + "incremental": true /* Enable incremental compilation */, + "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */, // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ @@ -23,11 +23,11 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ /* Modules */ - "module": "ESNext", /* Specify what module code is generated. */ + "module": "ESNext" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, "baseUrl": "./", - "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ @@ -37,14 +37,14 @@ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ - "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - "declarationMap": true, /* Create sourcemaps for d.ts files. */ - "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, + "declarationMap": true /* Create sourcemaps for d.ts files. */, + "emitDeclarationOnly": true /* Only output d.ts files and not JavaScript files. */, "stripInternal": true, // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ @@ -66,13 +66,13 @@ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ // "declarationDir": "./@types", /* Specify the output directory for generated declaration files. */ /* Interop Constraints */ - "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -95,8 +95,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "exclude": [ - "dist", - "node_modules" - ] + "exclude": ["dist", "node_modules"] } diff --git a/yarn.lock b/yarn.lock index 2421e7c..26a0bad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -701,6 +701,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@2.7.2": + version "2.7.2" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + "@types/semver@^6.0.0", "@types/semver@^6.0.1": version "6.2.3" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.3.tgz#5798ecf1bec94eaa64db39ee52808ec0693315aa"