diff --git a/.changeset/pink-flowers-wait.md b/.changeset/pink-flowers-wait.md new file mode 100644 index 0000000..a2faae3 --- /dev/null +++ b/.changeset/pink-flowers-wait.md @@ -0,0 +1,5 @@ +--- +'fetch-nodeshim': minor +--- + +Add web standard type/globals re-exports and polyfill `File` from `node:buffer`. diff --git a/package.json b/package.json index 4692da7..215cac5 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "rollup-plugin-cjs-check": "^1.0.3", "rollup-plugin-dts": "^6.1.1", "typescript": "^5.7.3", - "undici-types": "^7.3.0", + "undici-types": "^6.20.0", "vitest": "^3.0.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cac16e8..bc2449e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,8 +69,8 @@ importers: specifier: ^5.7.3 version: 5.7.3 undici-types: - specifier: ^7.3.0 - version: 7.3.0 + specifier: ^6.20.0 + version: 6.20.0 vitest: specifier: ^3.0.4 version: 3.0.4(@types/node@22.12.0)(terser@5.37.0)(yaml@2.7.0) @@ -1911,9 +1911,6 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - undici-types@7.3.0: - resolution: {integrity: sha512-z2pHpkN2BEJl3QlQo0GtfGCyuhuBbWX60vzGwyn7ex/seM2UkvyGEfEV0Qb9pXc5StNfcJpsstgaf2YTEJa63Q==} - universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -4032,8 +4029,6 @@ snapshots: undici-types@6.20.0: {} - undici-types@7.3.0: {} - universalify@0.1.2: {} update-browserslist-db@1.1.2(browserslist@4.24.4): diff --git a/src/fetch.ts b/src/fetch.ts index d7b42d2..78d42be 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -5,6 +5,7 @@ import * as url from 'node:url'; import { extractBody } from './body'; import { createContentDecoder } from './encoding'; +import { URL, Request, RequestInit, Response } from './webstd'; /** Maximum allowed redirects (matching Chromium's limit) */ const MAX_REDIRECTS = 20; @@ -102,7 +103,7 @@ function createResponse( return response; } -export async function fetch( +async function _fetch( input: string | URL | Request, requestInit?: RequestInit ): Promise { @@ -131,7 +132,7 @@ export async function fetch( signal, } satisfies http.RequestOptions; - function _fetch( + function _call( resolve: (response: Response | Promise) => void, reject: (reason?: any) => void ) { @@ -196,7 +197,7 @@ export async function fetch( requestOptions, urlToHttpOptions((requestUrl = locationURL)) ); - return _fetch(resolve, reject); + return _call(resolve, reject); } } @@ -258,5 +259,7 @@ export async function fetch( } } - return await new Promise(_fetch); + return await new Promise(_call); } + +export { _fetch as fetch }; diff --git a/src/index.ts b/src/index.ts index 6f95cf1..00fba55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export { fetch, fetch as default } from './fetch'; +export * from './webstd'; diff --git a/src/webstd.ts b/src/webstd.ts new file mode 100644 index 0000000..84caf4b --- /dev/null +++ b/src/webstd.ts @@ -0,0 +1,81 @@ +/// + +import * as buffer from 'node:buffer'; + +type Or = void extends T ? U : T; + +export type BodyInit = + | ArrayBuffer + | AsyncIterable + | Blob + | FormData + | Iterable + | NodeJS.ArrayBufferView + | URLSearchParams + | null + | string; + +// See: https://nodejs.org/docs/latest-v20.x/api/globals.html#class-file +// The `File` global was only added in Node.js 20 +interface _File extends Or {} +const _File: Or = buffer.File; +if (typeof globalThis.File === 'undefined') { + globalThis.File = _File; +} + +declare global { + var File: typeof _File; + + // NOTE: In case undici was used, but its types aren't applied, this needs to be added + interface RequestInit { + duplex?: 'half'; + } +} + +// There be dragons here. +// This is complex because of overlapping definitions in lib.dom, @types/node, and undici-types +// Some types define and overload constructor interfaces with type interfaces +// Here, we have to account for global differences and split the overloads apart + +interface _RequestInit extends Or {} +interface _ResponseInit extends Or {} + +interface _URLSearchParams + extends Or {} +interface URLSearchParamsClass + extends Or {} +const _URLSearchParams: URLSearchParamsClass = URLSearchParams as any; + +interface _URL extends Or {} +interface URLClass extends Or {} +const _URL: URLClass = URL; + +interface _Request extends Or {} +interface RequestClass extends Or {} +const _Request: RequestClass = Request; + +interface _Response extends Or {} +interface ResponseClass + extends Or {} +const _Response: ResponseClass = Response; + +interface _Headers extends Or {} +interface HeadersClass extends Or {} +const _Headers: HeadersClass = Headers; + +interface _FormData extends Or {} +interface FormDataClass + extends Or {} +const _FormData: FormDataClass = FormData; + +export { + type _RequestInit as RequestInit, + type _ResponseInit as ResponseInit, + _File as File, + _URL as URL, + _URLSearchParams as URLSearchParams, + _Request as Request, + _Response as Response, + _Headers as Headers, + _FormData as FormData, +};