Skip to content

Commit c537dee

Browse files
pmmmwhsindresorhus
andcommitted
Make TypeScript types conforms to strict mode (sindresorhus#928)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent d9a3273 commit c537dee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+580
-466
lines changed

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"@sindresorhus/is": "^1.0.0",
4242
"@szmarczak/http-timer": "^3.1.0",
4343
"@types/cacheable-request": "^6.0.1",
44-
"@types/tough-cookie": "^2.3.5",
4544
"cacheable-lookup": "^0.2.1",
4645
"cacheable-request": "^7.0.0",
4746
"decompress-response": "^5.0.0",
@@ -55,10 +54,14 @@
5554
"type-fest": "^0.8.0"
5655
},
5756
"devDependencies": {
58-
"@sindresorhus/tsconfig": "^0.5.0",
57+
"@sindresorhus/tsconfig": "^0.6.0",
5958
"@types/duplexer3": "^0.1.0",
59+
"@types/express": "^4.17.2",
60+
"@types/lolex": "^3.1.1",
6061
"@types/node": "^12.12.8",
62+
"@types/proxyquire": "^1.3.28",
6163
"@types/sinon": "^7.0.13",
64+
"@types/tough-cookie": "^2.3.5",
6265
"@typescript-eslint/eslint-plugin": "^2.7.0",
6366
"@typescript-eslint/parser": "^2.7.0",
6467
"ava": "^2.4.0",
@@ -67,6 +70,7 @@
6770
"del-cli": "^3.0.0",
6871
"delay": "^4.3.0",
6972
"eslint-config-xo-typescript": "^0.21.0",
73+
"express": "^4.17.1",
7074
"form-data": "^3.0.0",
7175
"get-port": "^5.0.0",
7276
"keyv": "^4.0.0",

source/as-promise.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import {IncomingMessage} from 'http';
21
import EventEmitter = require('events');
32
import getStream = require('get-stream');
4-
import is from '@sindresorhus/is';
53
import PCancelable = require('p-cancelable');
6-
import {NormalizedOptions, Response, CancelableRequest} from './utils/types';
4+
import is from '@sindresorhus/is';
75
import {ParseError, ReadError, HTTPError} from './errors';
8-
import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter';
96
import {normalizeArguments, mergeOptions} from './normalize-arguments';
7+
import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter';
8+
import {CancelableRequest, GeneralError, NormalizedOptions, Response} from './utils/types';
109

1110
const parseBody = (body: Response['body'], responseType: NormalizedOptions['responseType'], statusCode: Response['statusCode']) => {
1211
if (responseType === 'json' && is.string(body)) {
@@ -28,16 +27,16 @@ const parseBody = (body: Response['body'], responseType: NormalizedOptions['resp
2827
throw new Error(`Failed to parse body of type '${typeof body}' as '${responseType}'`);
2928
};
3029

31-
export default function asPromise(options: NormalizedOptions) {
30+
export default function asPromise<T>(options: NormalizedOptions): CancelableRequest<T> {
3231
const proxy = new EventEmitter();
3332
let finalResponse: Pick<Response, 'body' | 'statusCode'>;
3433

3534
// @ts-ignore `.json()`, `.buffer()` and `.text()` are added later
36-
const promise = new PCancelable<IncomingMessage | Response['body']>((resolve, reject, onCancel) => {
35+
const promise = new PCancelable<Response | Response['body']>((resolve, reject, onCancel) => {
3736
const emitter = requestAsEventEmitter(options);
3837
onCancel(emitter.abort);
3938

40-
const emitError = async (error: Error): Promise<void> => {
39+
const emitError = async (error: GeneralError): Promise<void> => {
4140
try {
4241
for (const hook of options.hooks.beforeError) {
4342
// eslint-disable-next-line no-await-in-loop
@@ -80,7 +79,7 @@ export default function asPromise(options: NormalizedOptions) {
8079
resolveBodyOnly: false
8180
}));
8281

83-
// Remove any further hooks for that request, because we we'll call them anyway.
82+
// Remove any further hooks for that request, because we'll call them anyway.
8483
// The loop continues. We don't want duplicates (asPromise recursion).
8584
updatedOptions.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
8685

@@ -124,7 +123,7 @@ export default function asPromise(options: NormalizedOptions) {
124123
const limitStatusCode = options.followRedirect ? 299 : 399;
125124
if (statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
126125
const error = new HTTPError(response, options);
127-
if (emitter.retry(error) === false) {
126+
if (!emitter.retry(error)) {
128127
if (options.throwHttpErrors) {
129128
emitError(error);
130129
return;
@@ -149,7 +148,7 @@ export default function asPromise(options: NormalizedOptions) {
149148
return promise;
150149
};
151150

152-
const shortcut = (responseType: NormalizedOptions['responseType']): CancelableRequest<any> => {
151+
const shortcut = <T>(responseType: NormalizedOptions['responseType']): CancelableRequest<T> => {
153152
// eslint-disable-next-line promise/prefer-await-to-then
154153
const newPromise = promise.then(() => parseBody(finalResponse.body, responseType, finalResponse.statusCode));
155154

source/as-stream.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import {PassThrough as PassThroughStream, Duplex as DuplexStream} from 'stream';
1+
import duplexer3 = require('duplexer3');
22
import stream = require('stream');
33
import {IncomingMessage} from 'http';
4-
import duplexer3 = require('duplexer3');
5-
import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter';
4+
import {Duplex as DuplexStream, PassThrough as PassThroughStream} from 'stream';
65
import {HTTPError, ReadError} from './errors';
7-
import {NormalizedOptions, Response, GotEvents} from './utils/types';
6+
import requestAsEventEmitter, {proxyEvents} from './request-as-event-emitter';
7+
import {GeneralError, GotEvents, NormalizedOptions, Response} from './utils/types';
88

9-
export class ProxyStream extends DuplexStream implements GotEvents<ProxyStream> {
9+
export class ProxyStream<T = unknown> extends DuplexStream implements GotEvents<ProxyStream<T>> {
1010
isFromCache?: boolean;
1111
}
1212

13-
export default function asStream(options: NormalizedOptions): ProxyStream {
13+
export default function asStream<T>(options: NormalizedOptions): ProxyStream<T> {
1414
const input = new PassThroughStream();
1515
const output = new PassThroughStream();
1616
const proxy = duplexer3(input, output) as ProxyStream;
@@ -35,7 +35,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream {
3535

3636
const emitter = requestAsEventEmitter(options);
3737

38-
const emitError = async (error: Error): Promise<void> => {
38+
const emitError = async (error: GeneralError): Promise<void> => {
3939
try {
4040
for (const hook of options.hooks.beforeError) {
4141
// eslint-disable-next-line no-await-in-loop
@@ -102,7 +102,7 @@ export default function asStream(options: NormalizedOptions): ProxyStream {
102102
});
103103

104104
proxyEvents(proxy, emitter);
105-
emitter.on('error', (error: Error) => proxy.emit('error', error));
105+
emitter.on('error', (error: GeneralError) => proxy.emit('error', error));
106106

107107
const pipe = proxy.pipe.bind(proxy);
108108
const unpipe = proxy.unpipe.bind(proxy);

source/calculate-retry-delay.ts

+25-19
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
11
import is from '@sindresorhus/is';
2-
import {HTTPError, ParseError, MaxRedirectsError, GotError} from './errors';
3-
import {RetryFunction} from './utils/types';
2+
import {HTTPError, ParseError, MaxRedirectsError} from './errors';
3+
import {RetryFunction, RetryObject} from './utils/types';
44

55
const retryAfterStatusCodes: ReadonlySet<number> = new Set([413, 429, 503]);
66

7+
const isErrorWithResponse = (error: RetryObject['error']): error is HTTPError | ParseError | MaxRedirectsError => (
8+
error instanceof HTTPError || error instanceof ParseError || error instanceof MaxRedirectsError
9+
);
10+
711
const calculateRetryDelay: RetryFunction = ({attemptCount, retryOptions, error}) => {
812
if (attemptCount > retryOptions.limit) {
913
return 0;
1014
}
1115

12-
const hasMethod = retryOptions.methods.includes((error as GotError).options.method);
13-
const hasErrorCode = Reflect.has(error, 'code') && retryOptions.errorCodes.includes((error as GotError).code);
14-
const hasStatusCode = Reflect.has(error, 'response') && retryOptions.statusCodes.includes((error as HTTPError | ParseError | MaxRedirectsError).response.statusCode);
16+
const hasMethod = retryOptions.methods.includes(error.options.method);
17+
const hasErrorCode = Reflect.has(error, 'code') && retryOptions.errorCodes.includes(error.code);
18+
const hasStatusCode = isErrorWithResponse(error) && Reflect.has(error, 'response') && retryOptions.statusCodes.includes(error.response?.statusCode);
1519
if (!hasMethod || (!hasErrorCode && !hasStatusCode)) {
1620
return 0;
1721
}
1822

19-
const {response} = error as HTTPError | ParseError | MaxRedirectsError | undefined;
20-
if (response && Reflect.has(response.headers, 'retry-after') && retryAfterStatusCodes.has(response.statusCode)) {
21-
let after = Number(response.headers['retry-after']);
22-
if (is.nan(after)) {
23-
after = Date.parse(response.headers['retry-after']) - Date.now();
24-
} else {
25-
after *= 1000;
23+
if (isErrorWithResponse(error)) {
24+
const {response} = error;
25+
if (response && Reflect.has(response.headers, 'retry-after') && retryAfterStatusCodes.has(response.statusCode)) {
26+
let after = Number(response.headers['retry-after']);
27+
if (is.nan(after)) {
28+
after = Date.parse(response.headers['retry-after']!) - Date.now();
29+
} else {
30+
after *= 1000;
31+
}
32+
33+
if (after > retryOptions.maxRetryAfter) {
34+
return 0;
35+
}
36+
37+
return after;
2638
}
2739

28-
if (after > retryOptions.maxRetryAfter) {
40+
if (response?.statusCode === 413) {
2941
return 0;
3042
}
31-
32-
return after;
33-
}
34-
35-
if (response?.statusCode === 413) {
36-
return 0;
3743
}
3844

3945
const noise = Math.random() * 100;

source/create.ts

+47-47
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import {Merge} from 'type-fest';
1+
import {Merge, PartialDeep} from 'type-fest';
2+
import asPromise from './as-promise';
3+
import asStream, {ProxyStream} from './as-stream';
24
import * as errors from './errors';
5+
import {normalizeArguments, mergeOptions} from './normalize-arguments';
6+
import deepFreeze from './utils/deep-freeze';
37
import {
4-
Options,
5-
Defaults,
6-
NormalizedOptions,
7-
Response,
88
CancelableRequest,
9-
URLOrOptions,
9+
ExtendOptions,
1010
HandlerFunction,
11-
ExtendedOptions
11+
NormalizedDefaults,
12+
NormalizedOptions,
13+
Options,
14+
Response,
15+
URLOrOptions
1216
} from './utils/types';
13-
import deepFreeze from './utils/deep-freeze';
14-
import asPromise from './as-promise';
15-
import asStream, {ProxyStream} from './as-stream';
16-
import {normalizeArguments, mergeOptions} from './normalize-arguments';
17-
import {Hooks} from './known-hook-events';
1817

1918
export type HTTPAlias =
2019
| 'get'
@@ -24,36 +23,39 @@ export type HTTPAlias =
2423
| 'head'
2524
| 'delete';
2625

27-
export type ReturnStream = (url: string | Options & {isStream: true}, options?: Options & {isStream: true}) => ProxyStream;
28-
export type GotReturn = ProxyStream | CancelableRequest<Response>;
26+
export type ReturnStream = <T>(url: string | Merge<Options, {isStream?: true}>, options?: Merge<Options, {isStream?: true}>) => ProxyStream<T>;
27+
export type GotReturn<T = unknown> = CancelableRequest<T> | ProxyStream<T>;
2928

3029
const getPromiseOrStream = (options: NormalizedOptions): GotReturn => options.isStream ? asStream(options) : asPromise(options);
3130

32-
type OptionsOfDefaultResponseBody = Options & {isStream?: false; resolveBodyOnly?: false; responseType?: 'default'};
33-
type OptionsOfTextResponseBody = Options & {isStream?: false; resolveBodyOnly?: false; responseType: 'text'};
34-
type OptionsOfJSONResponseBody = Options & {isStream?: false; resolveBodyOnly?: false; responseType: 'json'};
35-
type OptionsOfBufferResponseBody = Options & {isStream?: false; resolveBodyOnly?: false; responseType: 'buffer'};
31+
const isGotInstance = (value: any): value is Got => (
32+
Reflect.has(value, 'defaults') && Reflect.has(value.defaults, 'options')
33+
);
34+
35+
export type OptionsOfDefaultResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType?: 'default'}>;
36+
type OptionsOfTextResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'text'}>;
37+
type OptionsOfJSONResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'json'}>;
38+
type OptionsOfBufferResponseBody = Merge<Options, {isStream?: false; resolveBodyOnly?: false; responseType: 'buffer'}>;
3639
type ResponseBodyOnly = {resolveBodyOnly: true};
3740

3841
interface GotFunctions {
3942
// `asPromise` usage
40-
(url: string | OptionsOfDefaultResponseBody, options?: OptionsOfDefaultResponseBody): CancelableRequest<Response>;
43+
<T = string>(url: string | OptionsOfDefaultResponseBody, options?: OptionsOfDefaultResponseBody): CancelableRequest<Response<T>>;
4144
(url: string | OptionsOfTextResponseBody, options?: OptionsOfTextResponseBody): CancelableRequest<Response<string>>;
42-
(url: string | OptionsOfJSONResponseBody, options?: OptionsOfJSONResponseBody): CancelableRequest<Response<object>>;
45+
<T>(url: string | OptionsOfJSONResponseBody, options?: OptionsOfJSONResponseBody): CancelableRequest<Response<T>>;
4346
(url: string | OptionsOfBufferResponseBody, options?: OptionsOfBufferResponseBody): CancelableRequest<Response<Buffer>>;
44-
45-
(url: string | OptionsOfDefaultResponseBody & ResponseBodyOnly, options?: OptionsOfDefaultResponseBody & ResponseBodyOnly): CancelableRequest<any>;
46-
(url: string | OptionsOfTextResponseBody & ResponseBodyOnly, options?: OptionsOfTextResponseBody & ResponseBodyOnly): CancelableRequest<string>;
47-
(url: string | OptionsOfJSONResponseBody & ResponseBodyOnly, options?: OptionsOfJSONResponseBody & ResponseBodyOnly): CancelableRequest<object>;
48-
(url: string | OptionsOfBufferResponseBody & ResponseBodyOnly, options?: OptionsOfBufferResponseBody & ResponseBodyOnly): CancelableRequest<Buffer>;
49-
47+
// `resolveBodyOnly` usage
48+
<T = string>(url: string | Merge<OptionsOfDefaultResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfDefaultResponseBody, ResponseBodyOnly>): CancelableRequest<T>;
49+
(url: string | Merge<OptionsOfTextResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfTextResponseBody, ResponseBodyOnly>): CancelableRequest<string>;
50+
<T>(url: string | Merge<OptionsOfJSONResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfJSONResponseBody, ResponseBodyOnly>): CancelableRequest<T>;
51+
(url: string | Merge<OptionsOfBufferResponseBody, ResponseBodyOnly>, options?: Merge<OptionsOfBufferResponseBody, ResponseBodyOnly>): CancelableRequest<Buffer>;
5052
// `asStream` usage
51-
(url: string | Options & {isStream: true}, options?: Options & {isStream: true}): ProxyStream;
53+
<T>(url: string | Merge<Options, {isStream: true}>, options?: Merge<Options, {isStream: true}>): ProxyStream<T>;
5254
}
5355

54-
export interface Got extends Merge<Record<HTTPAlias, GotFunctions>, GotFunctions> {
56+
export interface Got extends Record<HTTPAlias, GotFunctions>, GotFunctions {
5557
stream: GotStream;
56-
defaults: Defaults | Readonly<Defaults>;
58+
defaults: NormalizedDefaults | Readonly<NormalizedDefaults>;
5759
GotError: typeof errors.GotError;
5860
CacheError: typeof errors.CacheError;
5961
RequestError: typeof errors.RequestError;
@@ -65,9 +67,9 @@ export interface Got extends Merge<Record<HTTPAlias, GotFunctions>, GotFunctions
6567
TimeoutError: typeof errors.TimeoutError;
6668
CancelError: typeof errors.CancelError;
6769

68-
extend(...instancesOrOptions: Array<Got | ExtendedOptions>): Got;
70+
extend(...instancesOrOptions: Array<Got | ExtendOptions>): Got;
6971
mergeInstances(parent: Got, ...instances: Got[]): Got;
70-
mergeOptions<T extends Options>(...sources: T[]): T & {hooks: Partial<Hooks>};
72+
mergeOptions<T extends PartialDeep<Options>>(...sources: T[]): T;
7173
}
7274

7375
export interface GotStream extends Record<HTTPAlias, ReturnStream> {
@@ -85,11 +87,12 @@ const aliases: readonly HTTPAlias[] = [
8587

8688
export const defaultHandler: HandlerFunction = (options, next) => next(options);
8789

88-
const create = (defaults: Defaults): Got => {
90+
const create = (defaults: NormalizedDefaults & {_rawHandlers?: HandlerFunction[]}): Got => {
8991
// Proxy properties from next handlers
9092
defaults._rawHandlers = defaults.handlers;
9193
defaults.handlers = defaults.handlers.map(fn => ((options, next) => {
92-
let root: GotReturn;
94+
// This will be assigned by assigning result
95+
let root!: GotReturn;
9396

9497
const result = fn(options, newOptions => {
9598
root = next(newOptions);
@@ -107,7 +110,7 @@ const create = (defaults: Defaults): Got => {
107110
// @ts-ignore Because the for loop handles it for us, as well as the other Object.defines
108111
const got: Got = (url: URLOrOptions, options?: Options): GotReturn => {
109112
let iteration = 0;
110-
const iterateHandlers: HandlerFunction = newOptions => {
113+
const iterateHandlers = (newOptions: Parameters<HandlerFunction>[0]): ReturnType<HandlerFunction> => {
111114
return defaults.handlers[iteration++](
112115
newOptions,
113116
// @ts-ignore TS doesn't know that it calls `getPromiseOrStream` at the end
@@ -116,7 +119,6 @@ const create = (defaults: Defaults): Got => {
116119
};
117120

118121
try {
119-
// @ts-ignore This handler takes only one parameter.
120122
return iterateHandlers(normalizeArguments(url, options, defaults));
121123
} catch (error) {
122124
if (options?.isStream) {
@@ -130,24 +132,22 @@ const create = (defaults: Defaults): Got => {
130132

131133
got.extend = (...instancesOrOptions) => {
132134
const optionsArray: Options[] = [defaults.options];
133-
let handlers: HandlerFunction[] = [...defaults._rawHandlers];
134-
let mutableDefaults: boolean;
135+
let handlers: HandlerFunction[] = [...defaults._rawHandlers!];
136+
let mutableDefaults: boolean | undefined;
135137

136138
for (const value of instancesOrOptions) {
137-
if (Reflect.has(value, 'defaults')) {
138-
optionsArray.push((value as Got).defaults.options);
139-
140-
handlers.push(...(value as Got).defaults._rawHandlers);
141-
142-
mutableDefaults = (value as Got).defaults.mutableDefaults;
139+
if (isGotInstance(value)) {
140+
optionsArray.push(value.defaults.options);
141+
handlers.push(...value.defaults._rawHandlers!);
142+
mutableDefaults = value.defaults.mutableDefaults;
143143
} else {
144-
optionsArray.push(value as ExtendedOptions);
144+
optionsArray.push(value);
145145

146146
if (Reflect.has(value, 'handlers')) {
147-
handlers.push(...(value as ExtendedOptions).handlers);
147+
handlers.push(...value.handlers);
148148
}
149149

150-
mutableDefaults = (value as ExtendedOptions).mutableDefaults;
150+
mutableDefaults = value.mutableDefaults;
151151
}
152152
}
153153

@@ -160,7 +160,7 @@ const create = (defaults: Defaults): Got => {
160160
return create({
161161
options: mergeOptions(...optionsArray),
162162
handlers,
163-
mutableDefaults
163+
mutableDefaults: Boolean(mutableDefaults)
164164
});
165165
};
166166

0 commit comments

Comments
 (0)