Skip to content

Commit

Permalink
Rename some internal types and upgrade dev dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Oct 13, 2021
1 parent ff1563a commit 2049aff
Show file tree
Hide file tree
Showing 23 changed files with 426 additions and 424 deletions.
28 changes: 16 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,28 @@
"node-fetch"
],
"devDependencies": {
"@sindresorhus/tsconfig": "^1.0.2",
"@sindresorhus/tsconfig": "^2.0.0",
"@type-challenges/utils": "^0.1.1",
"@types/body-parser": "^1.19.0",
"@types/busboy": "^0.2.3",
"@types/express": "^4.17.12",
"@types/body-parser": "^1.19.1",
"@types/busboy": "^0.2.4",
"@types/express": "^4.17.13",
"@types/node-fetch": "^2.5.10",
"@types/pify": "^5.0.0",
"@types/pify": "^5.0.1",
"abort-controller": "^3.0.0",
"ava": "^4.0.0-alpha.2",
"body-parser": "^1.19.0",
"busboy": "^0.3.1",
"del-cli": "^3.0.1",
"del-cli": "^4.0.1",
"delay": "^5.0.0",
"express": "^4.17.1",
"form-data": "^4.0.0",
"node-fetch": "^2.6.1",
"pify": "^5.0.0",
"playwright-chromium": "^1.11.1",
"playwright-chromium": "^1.15.2",
"raw-body": "^2.4.1",
"ts-node": "^10.0.0",
"typescript": "^4.4.3",
"xo": "^0.39.0"
"ts-node": "^10.3.0",
"typescript": "^4.4.4",
"xo": "^0.45.0"
},
"sideEffects": false,
"xo": {
Expand All @@ -83,8 +83,12 @@
],
"rules": {
"unicorn/filename-case": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unsafe-argument": "off"
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/naming-convention": "off"
}
},
"ava": {
Expand Down
180 changes: 89 additions & 91 deletions source/core/Ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,82 @@ import {ObjectEntries} from '../utils/types.js';
import {maxSafeTimeout, responseTypes, stop, supportsAbortController, supportsFormData, supportsStreams} from './constants.js';

export class Ky {
// eslint-disable-next-line @typescript-eslint/promise-function-async
static create(input: Input, options: Options): ResponsePromise {
const ky = new Ky(input, options);

const fn = async (): Promise<Response> => {
if (ky._options.timeout > maxSafeTimeout) {
throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
}

// Delay the fetch so that body method shortcuts can set the Accept header
await Promise.resolve();
let response = await ky._fetch();

for (const hook of ky._options.hooks.afterResponse) {
// eslint-disable-next-line no-await-in-loop
const modifiedResponse = await hook(
ky.request,
ky._options as NormalizedOptions,
ky._decorateResponse(response.clone()),
);

if (modifiedResponse instanceof globalThis.Response) {
response = modifiedResponse;
}
}

ky._decorateResponse(response);

if (!response.ok && ky._options.throwHttpErrors) {
throw new HTTPError(response, ky.request, (ky._options as unknown) as NormalizedOptions);
}

// If `onDownloadProgress` is passed, it uses the stream API internally
/* istanbul ignore next */
if (ky._options.onDownloadProgress) {
if (typeof ky._options.onDownloadProgress !== 'function') {
throw new TypeError('The `onDownloadProgress` option must be a function');
}

if (!supportsStreams) {
throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
}

return ky._stream(response.clone(), ky._options.onDownloadProgress);
}

return response;
};

const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
const result = (isRetriableMethod ? ky._retry(fn) : fn()) as ResponsePromise;

for (const [type, mimeType] of Object.entries(responseTypes) as ObjectEntries<typeof responseTypes>) {
result[type] = async () => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);

const response = (await result).clone();

if (type === 'json') {
if (response.status === 204) {
return '';
}

if (options.parseJson) {
return options.parseJson(await response.text());
}
}

return response[type]();
};
}

return result;
}

public request: Request;
protected abortController?: AbortController;
protected _retryCount = 0;
Expand All @@ -28,17 +104,17 @@ export class Ky {
{
beforeRequest: [],
beforeRetry: [],
afterResponse: []
afterResponse: [],
},
options.hooks
options.hooks,
),
method: normalizeRequestMethod(options.method ?? (this._input as Request).method),
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
prefixUrl: String(options.prefixUrl || ''),
retry: normalizeRetryOptions(options.retry),
throwHttpErrors: options.throwHttpErrors !== false,
timeout: typeof options.timeout === 'undefined' ? 10000 : options.timeout,
fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
timeout: typeof options.timeout === 'undefined' ? 10_000 : options.timeout,
fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
};

if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
Expand Down Expand Up @@ -72,17 +148,17 @@ export class Ky {

if (this._options.searchParams) {
// eslint-disable-next-line unicorn/prevent-abbreviations
const textSearchParams = typeof this._options.searchParams === 'string' ?
this._options.searchParams.replace(/^\?/, '') :
new URLSearchParams(this._options.searchParams as unknown as SearchParamsInit).toString();
const textSearchParams = typeof this._options.searchParams === 'string'
? this._options.searchParams.replace(/^\?/, '')
: new URLSearchParams(this._options.searchParams as unknown as SearchParamsInit).toString();
// eslint-disable-next-line unicorn/prevent-abbreviations
const searchParams = '?' + textSearchParams;
const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);

// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
if (
((supportsFormData && this._options.body instanceof globalThis.FormData) ||
this._options.body instanceof URLSearchParams) && !(this._options.headers && (this._options.headers as Record<string, string>)['content-type'])
((supportsFormData && this._options.body instanceof globalThis.FormData)
|| this._options.body instanceof URLSearchParams) && !(this._options.headers && (this._options.headers as Record<string, string>)['content-type'])
) {
this.request.headers.delete('content-type');
}
Expand All @@ -97,82 +173,6 @@ export class Ky {
}
}

// eslint-disable-next-line @typescript-eslint/promise-function-async
static create(input: Input, options: Options): ResponsePromise {
const ky = new Ky(input, options);

const fn = async (): Promise<Response> => {
if (ky._options.timeout > maxSafeTimeout) {
throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
}

// Delay the fetch so that body method shortcuts can set the Accept header
await Promise.resolve();
let response = await ky._fetch();

for (const hook of ky._options.hooks.afterResponse) {
// eslint-disable-next-line no-await-in-loop
const modifiedResponse = await hook(
ky.request,
ky._options as NormalizedOptions,
ky._decorateResponse(response.clone())
);

if (modifiedResponse instanceof globalThis.Response) {
response = modifiedResponse;
}
}

ky._decorateResponse(response);

if (!response.ok && ky._options.throwHttpErrors) {
throw new HTTPError(response, ky.request, (ky._options as unknown) as NormalizedOptions);
}

// If `onDownloadProgress` is passed, it uses the stream API internally
/* istanbul ignore next */
if (ky._options.onDownloadProgress) {
if (typeof ky._options.onDownloadProgress !== 'function') {
throw new TypeError('The `onDownloadProgress` option must be a function');
}

if (!supportsStreams) {
throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
}

return ky._stream(response.clone(), ky._options.onDownloadProgress);
}

return response;
};

const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
const result = (isRetriableMethod ? ky._retry(fn) : fn()) as ResponsePromise;

for (const [type, mimeType] of Object.entries(responseTypes) as ObjectEntries<typeof responseTypes>) {
result[type] = async () => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);

const response = (await result).clone();

if (type === 'json') {
if (response.status === 204) {
return '';
}

if (options.parseJson) {
return options.parseJson(await response.text());
}
}

return response[type]();
};
}

return result;
}

protected _calculateRetryDelay(error: unknown) {
this._retryCount++;

Expand Down Expand Up @@ -212,9 +212,7 @@ export class Ky {

protected _decorateResponse(response: Response): Response {
if (this._options.parseJson) {
response.json = async () => {
return this._options.parseJson!(await response.text());
};
response.json = async () => this._options.parseJson!(await response.text());
}

return response;
Expand All @@ -235,7 +233,7 @@ export class Ky {
request: this.request,
options: (this._options as unknown) as NormalizedOptions,
error: error as Error,
retryCount: this._retryCount
retryCount: this._retryCount,
});

// If `stop` is returned from the hook, the retry process is stopped
Expand Down Expand Up @@ -305,8 +303,8 @@ export class Ky {
}

await read();
}
})
},
}),
);
}
}
8 changes: 4 additions & 4 deletions source/core/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {Expect, Equal} from '@type-challenges/utils';
import {HTTPMethod} from '../types/options.js';
import {HttpMethod} from '../types/options.js';

export const supportsAbortController = typeof globalThis.AbortController === 'function';
export const supportsStreams = typeof globalThis.ReadableStream === 'function';
Expand All @@ -9,18 +9,18 @@ export const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete']

const validate = <T extends Array<true>>() => undefined as unknown as T;
validate<[
Expect<Equal<typeof requestMethods[number], HTTPMethod>>
Expect<Equal<typeof requestMethods[number], HttpMethod>>,
]>();

export const responseTypes = {
json: 'application/json',
text: 'text/*',
formData: 'multipart/form-data',
arrayBuffer: '*/*',
blob: '*/*'
blob: '*/*',
} as const;

// The maximum value of a 32bit int (see issue #117)
export const maxSafeTimeout = 2147483647;
export const maxSafeTimeout = 2_147_483_647;

export const stop = Symbol('stop');
1 change: 1 addition & 0 deletions source/errors/HTTPError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {NormalizedOptions} from '../types/options.js';

// eslint-lint-disable-next-line @typescript-eslint/naming-convention
export class HTTPError extends Error {
public response: Response;
public request: Request;
Expand Down
12 changes: 6 additions & 6 deletions source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import {Ky} from './core/Ky.js';
import {requestMethods, stop} from './core/constants.js';
import type {ky as KyInterface} from './types/ky.js';
import type {KyInstance} from './types/ky.js';
import type {Input, Options} from './types/options.js';
import {validateAndMerge} from './utils/merge.js';
import {Mutable} from './utils/types.js';

const createInstance = (defaults?: Partial<Options>): KyInterface => {
const createInstance = (defaults?: Partial<Options>): KyInstance => {
// eslint-disable-next-line @typescript-eslint/promise-function-async
const ky: Partial<Mutable<KyInterface>> = (input: Input, options?: Options) => Ky.create(input, validateAndMerge(defaults, options));
const ky: Partial<Mutable<KyInstance>> = (input: Input, options?: Options) => Ky.create(input, validateAndMerge(defaults, options));

for (const method of requestMethods) {
// eslint-disable-next-line @typescript-eslint/promise-function-async
Expand All @@ -20,7 +20,7 @@ const createInstance = (defaults?: Partial<Options>): KyInterface => {
ky.extend = (newDefaults?: Partial<Options>) => createInstance(validateAndMerge(defaults, newDefaults));
ky.stop = stop;

return ky as KyInterface;
return ky as KyInstance;
};

const ky = createInstance();
Expand All @@ -32,14 +32,14 @@ export {
NormalizedOptions,
RetryOptions,
SearchParamsOption,
DownloadProgress
DownloadProgress,
} from './types/options.js';

export {
Hooks,
BeforeRequestHook,
BeforeRetryHook,
AfterResponseHook
AfterResponseHook,
} from './types/hooks.js';

export {ResponsePromise} from './types/response.js';
Expand Down

0 comments on commit 2049aff

Please sign in to comment.