Skip to content

Commit

Permalink
Breaking: Revamp TypeScript declarations (#810)
Browse files Browse the repository at this point in the history
* revamp types
* add GitHub Action for TypeScript check

Co-authored-by: Linus Unnebäck <linus@folkdatorn.se>
Co-authored-by: Antoni Kepinski <xxczaki@pm.me>
  • Loading branch information
3 people authored May 24, 2020
1 parent eb8c89c commit 4824abe
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 335 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI

on:
pull_request:
paths:
- '**.ts'
- package.json
- .github/workflows/types.yml

jobs:
typescript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1

- run: npm install

- name: Check typings file
run: npm run test-types
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
package-lock=false
save-exact=false
184 changes: 184 additions & 0 deletions @types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/// <reference types="node" />

/* eslint-disable no-var, import/no-mutable-exports */

import {Agent} from 'http';
import {AbortSignal} from 'abort-controller';
import Blob from 'fetch-blob';

type HeadersInit = Headers | string[][] | Record<string, string>;

/**
* This Fetch API interface allows you to perform various actions on HTTP request and response headers.
* These actions include retrieving, setting, adding to, and removing.
* A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.
* 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: (
callbackfn: (value: string, key: string, parent: Headers) => void,
thisArg?: any
) => void;

[Symbol.iterator]: () => IterableIterator<[string, string]>;
/**
* Returns an iterator allowing to go through all key/value pairs contained in this object.
*/
entries: () => IterableIterator<[string, string]>;
/**
* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object.
*/
keys: () => IterableIterator<string>;
/**
* Returns an iterator allowing to go through all values of the key/value pairs contained in this object.
*/
values: () => IterableIterator<string>;

/** Node-fetch extension */
raw: () => Record<string, string[]>;
}
declare var Headers: {
prototype: Headers;
new (init?: HeadersInit): Headers;
};

interface RequestInit {
/**
* A BodyInit object or null to set request's body.
*/
body?: BodyInit | null;
/**
* A Headers object, an object literal, or an array of two-item arrays to set request's headers.
*/
headers?: HeadersInit;
/**
* A string to set request's method.
*/
method?: string;
/**
* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect.
*/
redirect?: RequestRedirect;
/**
* An AbortSignal to set request's signal.
*/
signal?: AbortSignal | null;

// Node-fetch extensions to the whatwg/fetch spec
agent?: Agent | ((parsedUrl: URL) => Agent);
compress?: boolean;
counter?: number;
follow?: number;
hostname?: string;
port?: number;
protocol?: string;
size?: number;
timeout?: number;
highWaterMark?: number;
}

interface ResponseInit {
headers?: HeadersInit;
status?: number;
statusText?: string;
}

type BodyInit =
| Blob
| Buffer
| URLSearchParams
| NodeJS.ReadableStream
| string;
interface Body {
readonly body: NodeJS.ReadableStream | null;
readonly bodyUsed: boolean;
readonly size: number;
readonly timeout: number;
buffer: () => Promise<Buffer>;
arrayBuffer: () => Promise<ArrayBuffer>;
blob: () => Promise<Blob>;
json: () => Promise<unknown>;
text: () => Promise<string>;
}
declare var Body: {
prototype: Body;
new (body?: BodyInit, opts?: {size?: number; timeout?: number}): Body;
};

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.
*/
readonly headers: Headers;
/**
* Returns request's HTTP method, which is "GET" by default.
*/
readonly method: string;
/**
* Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default.
*/
readonly redirect: RequestRedirect;
/**
* Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler.
*/
readonly signal: AbortSignal;
/**
* Returns the URL of request as a string.
*/
readonly url: string;
clone: () => Request;
}
type RequestInfo = string | Body;
declare var Request: {
prototype: Request;
new (input: RequestInfo, init?: RequestInit): Request;
};

interface Response extends Body {
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<Response>;

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 {
type: string;
name: 'AbortError';
[Symbol.toStringTag]: 'AbortError';
}

export {Headers, Request, Response, FetchError};
export default fetch;
66 changes: 66 additions & 0 deletions @types/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {expectType} from 'tsd';
import fetch, {Request, Response, Headers, FetchError, AbortError} from '.';

async function run() {
const getRes = await fetch('https://bigfile.com/test.zip');
expectType<boolean>(getRes.ok);
expectType<number>(getRes.size);
expectType<number>(getRes.status);
expectType<string>(getRes.statusText);
expectType<() => Response>(getRes.clone);

// Test async iterator over body
expectType<NodeJS.ReadableStream | null>(getRes.body);
if (getRes.body) {
for await (const data of getRes.body) {
expectType<Buffer | string>(data);
}
}

// Test Buffer
expectType<Buffer>(await getRes.buffer());

// Test arrayBuffer
expectType<ArrayBuffer>(await getRes.arrayBuffer());

// Test JSON, returns unknown
expectType<unknown>(await getRes.json());

// Headers iterable
expectType<Headers>(getRes.headers);

// Post
try {
const request = new Request('http://byjka.com/buka');
expectType<string>(request.url);
expectType<Headers>(request.headers);
expectType<number>(request.timeout);

const headers = new Headers({byaka: 'buke'});
expectType<(a: string, b: string) => void>(headers.append);
expectType<(a: string) => string | null>(headers.get);
expectType<(name: string, value: string) => void>(headers.set);
expectType<(name: string) => void>(headers.delete);
expectType<() => IterableIterator<string>>(headers.keys);
expectType<() => IterableIterator<[string, string]>>(headers.entries);
expectType<() => IterableIterator<[string, string]>>(headers[Symbol.iterator]);

const postRes = await fetch(request, {method: 'POST', headers});
expectType<Blob>(await postRes.blob());
} catch (error) {
if (error instanceof FetchError) {
throw new TypeError(error.errno);
}

if (error instanceof AbortError) {
throw error;
}
}

const response = new Response();
expectType<string>(response.url);
}

run().finally(() => {
console.log('✅');
});
Loading

0 comments on commit 4824abe

Please sign in to comment.