From e6bfe4d419aff7eb00887c6f6085dc6b8b43dcc3 Mon Sep 17 00:00:00 2001 From: Maxime LUCE Date: Fri, 29 May 2020 02:41:26 +0200 Subject: [PATCH] fix: improve TypeScript types (#841) * fix: improve TypeScript types * fix: disable allowSyntheticDefaultImports and esModuleInterop * fix: improve HeadersInit types (js/ts) * fix: fully match types to build index.cjs * fix: allow Iterable> in HeadersInit --- @types/index.d.ts | 133 +++++++++++++++++++++++------------------ @types/index.test-d.ts | 42 ++++++++++++- package.json | 3 +- src/headers.js | 2 +- 4 files changed, 119 insertions(+), 61 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index f0eb606dd..31533a2b2 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; +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; +}; + +type HeadersInit = Headers | Record | Iterable | Iterable>; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. @@ -15,38 +21,36 @@ 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: ( +declare 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 { /** @@ -94,23 +98,26 @@ type BodyInit = | URLSearchParams | NodeJS.ReadableStream | string; -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; - 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; -}; type RequestRedirect = 'error' | 'follow' | 'manual'; -interface Request extends Body { +type RequestInfo = string | Body; +declare class Request extends Body { + constructor(input: RequestInfo, init?: RequestInit); + /** * 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,52 +138,64 @@ 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 { +declare 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; + clone(): Response; } -declare var Response: { - prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; -}; - -declare function fetch(url: RequestInfo, init?: RequestInit): Promise; +declare class FetchError extends Error { + constructor(message: string, type: string, systemError?: object); -declare namespace fetch { - function isRedirect(code: number): boolean; -} - -interface FetchError extends Error { 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 { +declare class AbortError extends Error { type: string; name: 'AbortError'; [Symbol.toStringTag]: 'AbortError'; } -export {Headers, Request, Response, FetchError}; -export default fetch; + +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 be9b665f8..d00d1426a 100644 --- a/@types/index.test-d.ts +++ b/@types/index.test-d.ts @@ -1,5 +1,9 @@ -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'); @@ -56,8 +60,42 @@ 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() + 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()); + new Headers([ + new Set(['a', '1']), + ['b', '2'], + new Map([['a', null], ['3', null]]).keys() + ]); + + fetch.isRedirect = (code: number) => true; } run().finally(() => { diff --git a/package.json b/package.json index 0f3f8fff6..a3bf79017 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,8 @@ "lib": [ "es2018" ], - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": false, + "esModuleInterop": false } }, "xo": { diff --git a/src/headers.js b/src/headers.js index da1934fd8..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 */ /**