From b0e8f28b75ed98f54ad12254bd59fa3e53d9def0 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Tue, 26 May 2020 20:20:00 +0200 Subject: [PATCH 1/5] fix: improve TypeScript types --- @types/index.d.ts | 111 +++++++++++++++++++---------------------- @types/index.test-d.ts | 6 +++ 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index f0eb606dd..d3baf3b7f 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -3,10 +3,16 @@ /* eslint-disable no-var, import/no-mutable-exports */ import {Agent} from 'http'; -import {AbortSignal} from 'abort-controller'; -import Blob from 'fetch-blob'; +import * as Blob from 'fetch-blob'; -type HeadersInit = Headers | string[][] | Record; +export type AbortSignal = { + readonly aborted: boolean; + + addEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { passive?: boolean; once?: boolean; }): void; + removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; +}; + +export type HeadersInit = Headers | string[][] | Record; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. @@ -15,40 +21,38 @@ type HeadersInit = Headers | string[][] | Record; * You can add to this using methods like append() (see Examples.) * In all methods of this interface, header names are matched by case-insensitive byte sequence. * */ -interface Headers { - append: (name: string, value: string) => void; - delete: (name: string) => void; - get: (name: string) => string | null; - has: (name: string) => boolean; - set: (name: string, value: string) => void; - forEach: ( +export class Headers { + constructor(init?: HeadersInit); + + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + has(name: string): boolean; + set(name: string, value: string): void; + forEach( callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any - ) => void; + ): void; - [Symbol.iterator]: () => IterableIterator<[string, string]>; + [Symbol.iterator](): IterableIterator<[string, string]>; /** * Returns an iterator allowing to go through all key/value pairs contained in this object. */ - entries: () => IterableIterator<[string, string]>; + entries(): IterableIterator<[string, string]>; /** * Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ - keys: () => IterableIterator; + keys(): IterableIterator; /** * Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ - values: () => IterableIterator; + values(): IterableIterator; /** Node-fetch extension */ - raw: () => Record; + raw(): Record; } -declare var Headers: { - prototype: Headers; - new (init?: HeadersInit): Headers; -}; -interface RequestInit { +export interface RequestInit { /** * A BodyInit object or null to set request's body. */ @@ -82,35 +86,39 @@ interface RequestInit { highWaterMark?: number; } -interface ResponseInit { +export interface ResponseInit { headers?: HeadersInit; status?: number; statusText?: string; } -type BodyInit = +export type BodyInit = | Blob | Buffer | URLSearchParams | NodeJS.ReadableStream | string; -interface Body { +export interface Body { readonly body: NodeJS.ReadableStream | null; readonly bodyUsed: boolean; readonly size: number; - buffer: () => Promise; - arrayBuffer: () => Promise; - blob: () => Promise; - json: () => Promise; - text: () => Promise; + + buffer(): Promise; + arrayBuffer(): Promise; + blob(): Promise; + json(): Promise; + text(): Promise; } declare var Body: { prototype: Body; - new (body?: BodyInit, opts?: {size?: number}): Body; -}; + new(body?: BodyInit, opts?: {size?: number}): Body; +} + +export type RequestRedirect = 'error' | 'follow' | 'manual'; +export type RequestInfo = string | Body; +export class Request extends Body { + constructor(input: RequestInfo, init?: RequestInit); -type RequestRedirect = 'error' | 'follow' | 'manual'; -interface Request extends Body { /** * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. */ @@ -131,46 +139,30 @@ interface Request extends Body { * Returns the URL of request as a string. */ readonly url: string; - clone: () => Request; + clone(): Request; } -type RequestInfo = string | Body; -declare var Request: { - prototype: Request; - new (input: RequestInfo, init?: RequestInit): Request; -}; -interface Response extends Body { +export class Response extends Body { + constructor(body?: BodyInit | null, init?: ResponseInit); + readonly headers: Headers; readonly ok: boolean; readonly redirected: boolean; readonly status: number; readonly statusText: string; readonly url: string; - clone: () => Response; -} - -declare var Response: { - prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; -}; - -declare function fetch(url: RequestInfo, init?: RequestInit): Promise; - -declare namespace fetch { - function isRedirect(code: number): boolean; + clone(): Response; } -interface FetchError extends Error { +export class FetchError extends Error { + constructor(message: string, type: string, systemError?: object); + name: 'FetchError'; [Symbol.toStringTag]: 'FetchError'; type: string; code?: string; errno?: string; } -declare var FetchError: { - prototype: FetchError; - new (message: string, type: string, systemError?: object): FetchError; -}; export class AbortError extends Error { type: string; @@ -178,5 +170,6 @@ export class AbortError extends Error { [Symbol.toStringTag]: 'AbortError'; } -export {Headers, Request, Response, FetchError}; -export default fetch; +export function isRedirect(code: number): boolean; + +export default function fetch(url: RequestInfo, init?: RequestInit): Promise; diff --git a/@types/index.test-d.ts b/@types/index.test-d.ts index be9b665f8..cf6aa0b05 100644 --- a/@types/index.test-d.ts +++ b/@types/index.test-d.ts @@ -1,5 +1,6 @@ import {expectType} from 'tsd'; import fetch, {Request, Response, Headers, FetchError, AbortError} from '.'; +import AbortController from 'abort-controller'; async function run() { const getRes = await fetch('https://bigfile.com/test.zip'); @@ -58,6 +59,11 @@ async function run() { const response = new Response(); expectType(response.url); + + const abortController = new AbortController() + const request = new Request("url", { + signal: abortController.signal + }); } run().finally(() => { From 79b5372a959869099fbe6455c78281ed2878eaf7 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Wed, 27 May 2020 10:28:24 +0200 Subject: [PATCH 2/5] fix: disable allowSyntheticDefaultImports and esModuleInterop --- @types/index.d.ts | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index d3baf3b7f..70c0c38ca 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -6,7 +6,7 @@ import {Agent} from 'http'; import * as Blob from 'fetch-blob'; export type AbortSignal = { - readonly aborted: boolean; + readonly aborted: boolean; addEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { passive?: boolean; once?: boolean; }): void; removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; diff --git a/package.json b/package.json index b8135bbbe..2d9aa3eb8 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,8 @@ "lib": [ "es2018" ], - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": false, + "esModuleInterop": false } }, "xo": { From 0f9e82e50c379ebd06bfc6bfd27abb5ad5e4737e Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Wed, 27 May 2020 17:08:24 +0200 Subject: [PATCH 3/5] fix: improve HeadersInit types (js/ts) --- @types/index.d.ts | 2 +- @types/index.test-d.ts | 9 ++++++--- src/headers.js | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 70c0c38ca..19ed89a12 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -12,7 +12,7 @@ export type AbortSignal = { removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; }; -export type HeadersInit = Headers | string[][] | Record; +export type HeadersInit = Headers | Record | Iterable | Iterable; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. diff --git a/@types/index.test-d.ts b/@types/index.test-d.ts index cf6aa0b05..315edf883 100644 --- a/@types/index.test-d.ts +++ b/@types/index.test-d.ts @@ -61,9 +61,12 @@ async function run() { expectType(response.url); const abortController = new AbortController() - const request = new Request("url", { - signal: abortController.signal - }); + new Request('url', { signal: abortController.signal }); + + new Headers({'Header': 'value'}); + // new Headers(['header', 'value']); // should not work + new Headers([['header', 'value']]); + new Headers(new Headers()); } run().finally(() => { diff --git a/src/headers.js b/src/headers.js index da1934fd8..83eb561fb 100644 --- a/src/headers.js +++ b/src/headers.js @@ -24,7 +24,7 @@ function validateValue(value) { } /** - * @typedef {Headers | Record | Iterable | Iterable[]} HeadersInit + * @typedef {Headers | Record | Iterable | Iterable} HeadersInit */ /** From 6af33551aa67d5db1b7e479eb96408c1e587fe03 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Thu, 28 May 2020 14:41:42 +0200 Subject: [PATCH 4/5] fix: fully match types to build index.cjs --- @types/index.d.ts | 72 ++++++++++++++++++++++++++++-------------- @types/index.test-d.ts | 30 ++++++++++++++++-- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 19ed89a12..1790e35f1 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -5,14 +5,14 @@ import {Agent} from 'http'; import * as Blob from 'fetch-blob'; -export type AbortSignal = { - readonly aborted: boolean; +type AbortSignal = { + readonly aborted: boolean; - addEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { passive?: boolean; once?: boolean; }): void; - removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; + addEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { passive?: boolean; once?: boolean; }): void; + removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; }; -export type HeadersInit = Headers | Record | Iterable | Iterable; +type HeadersInit = Headers | Record | Iterable | Iterable; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. @@ -21,7 +21,7 @@ export type HeadersInit = Headers | Record | Iterable; } -export interface RequestInit { +interface RequestInit { /** * A BodyInit object or null to set request's body. */ @@ -86,19 +86,22 @@ export interface RequestInit { highWaterMark?: number; } -export interface ResponseInit { +interface ResponseInit { headers?: HeadersInit; status?: number; statusText?: string; } -export type BodyInit = +type BodyInit = | Blob | Buffer | URLSearchParams | NodeJS.ReadableStream | string; -export interface Body { +type BodyType = { [K in keyof Body]: Body[K] }; +declare class Body { + constructor(body?: BodyInit, opts?: { size?: number }); + readonly body: NodeJS.ReadableStream | null; readonly bodyUsed: boolean; readonly size: number; @@ -109,14 +112,10 @@ export interface Body { json(): Promise; text(): Promise; } -declare var Body: { - prototype: Body; - new(body?: BodyInit, opts?: {size?: number}): Body; -} -export type RequestRedirect = 'error' | 'follow' | 'manual'; -export type RequestInfo = string | Body; -export class Request extends Body { +type RequestRedirect = 'error' | 'follow' | 'manual'; +type RequestInfo = string | Body; +declare class Request extends Body { constructor(input: RequestInfo, init?: RequestInit); /** @@ -142,7 +141,7 @@ export class Request extends Body { clone(): Request; } -export class Response extends Body { +declare class Response extends Body { constructor(body?: BodyInit | null, init?: ResponseInit); readonly headers: Headers; @@ -154,9 +153,9 @@ export class Response extends Body { clone(): Response; } -export class FetchError extends Error { +declare class FetchError extends Error { constructor(message: string, type: string, systemError?: object); - + name: 'FetchError'; [Symbol.toStringTag]: 'FetchError'; type: string; @@ -164,12 +163,39 @@ export class FetchError extends Error { errno?: string; } -export class AbortError extends Error { +declare class AbortError extends Error { type: string; name: 'AbortError'; [Symbol.toStringTag]: 'AbortError'; } -export function isRedirect(code: number): boolean; -export default function fetch(url: RequestInfo, init?: RequestInit): Promise; +declare function fetch(url: RequestInfo, init?: RequestInit): Promise; +declare class fetch { + static default: typeof fetch; +} +declare namespace fetch { + export function isRedirect(code: number): boolean; + + export { + HeadersInit, + Headers, + + RequestInit, + RequestRedirect, + RequestInfo, + Request, + + BodyInit, + + ResponseInit, + Response, + + FetchError, + AbortError + }; + + export interface Body extends BodyType { } +} + +export = fetch; diff --git a/@types/index.test-d.ts b/@types/index.test-d.ts index 315edf883..72fda1f89 100644 --- a/@types/index.test-d.ts +++ b/@types/index.test-d.ts @@ -1,7 +1,10 @@ -import {expectType} from 'tsd'; -import fetch, {Request, Response, Headers, FetchError, AbortError} from '.'; +import {expectType, expectAssignable} from 'tsd'; import AbortController from 'abort-controller'; +import fetch, {Request, Response, Headers, Body, FetchError, AbortError} from '.'; +import * as _fetch from '.'; +import __fetch = require('.'); + async function run() { const getRes = await fetch('https://bigfile.com/test.zip'); expectType(getRes.ok); @@ -57,16 +60,37 @@ async function run() { } } + // export * + const wildRes = await _fetch('https://google.com'); + expectType(wildRes.ok); + expectType(wildRes.size); + expectType(wildRes.status); + expectType(wildRes.statusText); + expectType<() => Response>(wildRes.clone); + + // export = require + const reqRes = await __fetch('https://google.com'); + expectType(reqRes.ok); + expectType(reqRes.size); + expectType(reqRes.status); + expectType(reqRes.statusText); + expectType<() => Response>(reqRes.clone); + + // Others const response = new Response(); expectType(response.url); + expectAssignable(response); const abortController = new AbortController() - new Request('url', { signal: abortController.signal }); + const request = new Request('url', { signal: abortController.signal }); + expectAssignable(request); new Headers({'Header': 'value'}); // new Headers(['header', 'value']); // should not work new Headers([['header', 'value']]); new Headers(new Headers()); + + fetch.isRedirect = (code: number) => true; } run().finally(() => { From 9105577287a580398b3a712a026d084eb7faa543 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Thu, 28 May 2020 15:00:23 +0200 Subject: [PATCH 5/5] fix: allow Iterable> in HeadersInit --- @types/index.d.ts | 2 +- @types/index.test-d.ts | 5 +++++ src/headers.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 1790e35f1..31533a2b2 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -12,7 +12,7 @@ type AbortSignal = { removeEventListener(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | { capture?: boolean; }): void; }; -type HeadersInit = Headers | Record | Iterable | Iterable; +type HeadersInit = Headers | Record | Iterable | Iterable>; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. diff --git a/@types/index.test-d.ts b/@types/index.test-d.ts index 72fda1f89..d00d1426a 100644 --- a/@types/index.test-d.ts +++ b/@types/index.test-d.ts @@ -89,6 +89,11 @@ async function run() { // new Headers(['header', 'value']); // should not work new Headers([['header', 'value']]); new Headers(new Headers()); + new Headers([ + new Set(['a', '1']), + ['b', '2'], + new Map([['a', null], ['3', null]]).keys() + ]); fetch.isRedirect = (code: number) => true; } diff --git a/src/headers.js b/src/headers.js index 83eb561fb..20424a841 100644 --- a/src/headers.js +++ b/src/headers.js @@ -24,7 +24,7 @@ function validateValue(value) { } /** - * @typedef {Headers | Record | Iterable | Iterable} HeadersInit + * @typedef {Headers | Record | Iterable | Iterable>} HeadersInit */ /**