thenavisapp
Version:
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
256 lines (199 loc) • 6.63 kB
text/typescript
import is, {assert} from '@sindresorhus/is';
import asPromise from './as-promise/index.js';
import type {
GotReturn,
ExtendOptions,
Got,
HTTPAlias,
InstanceDefaults,
GotPaginate,
GotStream,
GotRequestFunction,
OptionsWithPagination,
StreamOptions,
} from './types.js';
import Request from './core/index.js';
import type {Response} from './core/response.js';
import Options, {type OptionsInit} from './core/options.js';
import type {CancelableRequest} from './as-promise/types.js';
// The `delay` package weighs 10KB (!)
const delay = async (ms: number) => new Promise(resolve => {
setTimeout(resolve, ms);
});
const isGotInstance = (value: Got | ExtendOptions): value is Got => is.function_(value);
const aliases: readonly HTTPAlias[] = [
'get',
'post',
'put',
'patch',
'head',
'delete',
];
const create = (defaults: InstanceDefaults): Got => {
defaults = {
options: new Options(undefined, undefined, defaults.options),
handlers: [...defaults.handlers],
mutableDefaults: defaults.mutableDefaults,
};
Object.defineProperty(defaults, 'mutableDefaults', {
enumerable: true,
configurable: false,
writable: false,
});
// Got interface
const got: Got = ((url: string | URL | OptionsInit | undefined, options?: OptionsInit, defaultOptions: Options = defaults.options): GotReturn => {
const request = new Request(url, options, defaultOptions);
let promise: CancelableRequest | undefined;
const lastHandler = (normalized: Options): GotReturn => {
// Note: `options` is `undefined` when `new Options(...)` fails
request.options = normalized;
request._noPipe = !normalized.isStream;
void request.flush();
if (normalized.isStream) {
return request;
}
if (!promise) {
promise = asPromise(request);
}
return promise;
};
let iteration = 0;
const iterateHandlers = (newOptions: Options): GotReturn => {
const handler = defaults.handlers[iteration++] ?? lastHandler;
const result = handler(newOptions, iterateHandlers) as GotReturn;
if (is.promise(result) && !request.options.isStream) {
if (!promise) {
promise = asPromise(request);
}
if (result !== promise) {
const descriptors = Object.getOwnPropertyDescriptors(promise);
for (const key in descriptors) {
if (key in result) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete descriptors[key];
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Object.defineProperties(result, descriptors);
result.cancel = promise.cancel;
}
}
return result;
};
return iterateHandlers(request.options);
}) as Got;
got.extend = (...instancesOrOptions) => {
const options = new Options(undefined, undefined, defaults.options);
const handlers = [...defaults.handlers];
let mutableDefaults: boolean | undefined;
for (const value of instancesOrOptions) {
if (isGotInstance(value)) {
options.merge(value.defaults.options);
handlers.push(...value.defaults.handlers);
mutableDefaults = value.defaults.mutableDefaults;
} else {
options.merge(value);
if (value.handlers) {
handlers.push(...value.handlers);
}
mutableDefaults = value.mutableDefaults;
}
}
return create({
options,
handlers,
mutableDefaults: Boolean(mutableDefaults),
});
};
// Pagination
const paginateEach = (async function * <T, R>(url: string | URL, options?: OptionsWithPagination<T, R>): AsyncIterableIterator<T> {
let normalizedOptions = new Options(url, options as OptionsInit, defaults.options);
normalizedOptions.resolveBodyOnly = false;
const {pagination} = normalizedOptions;
assert.function_(pagination.transform);
assert.function_(pagination.shouldContinue);
assert.function_(pagination.filter);
assert.function_(pagination.paginate);
assert.number(pagination.countLimit);
assert.number(pagination.requestLimit);
assert.number(pagination.backoff);
const allItems: T[] = [];
let {countLimit} = pagination;
let numberOfRequests = 0;
while (numberOfRequests < pagination.requestLimit) {
if (numberOfRequests !== 0) {
// eslint-disable-next-line no-await-in-loop
await delay(pagination.backoff);
}
// eslint-disable-next-line no-await-in-loop
const response = (await got(undefined, undefined, normalizedOptions)) as Response;
// eslint-disable-next-line no-await-in-loop
const parsed: unknown[] = await pagination.transform(response);
const currentItems: T[] = [];
assert.array(parsed);
for (const item of parsed) {
if (pagination.filter({item, currentItems, allItems})) {
if (!pagination.shouldContinue({item, currentItems, allItems})) {
return;
}
yield item as T;
if (pagination.stackAllItems) {
allItems.push(item as T);
}
currentItems.push(item as T);
if (--countLimit <= 0) {
return;
}
}
}
const optionsToMerge = pagination.paginate({
response,
currentItems,
allItems,
});
if (optionsToMerge === false) {
return;
}
if (optionsToMerge === response.request.options) {
normalizedOptions = response.request.options;
} else {
normalizedOptions.merge(optionsToMerge);
assert.any([is.urlInstance, is.undefined], optionsToMerge.url);
if (optionsToMerge.url !== undefined) {
normalizedOptions.prefixUrl = '';
normalizedOptions.url = optionsToMerge.url;
}
}
numberOfRequests++;
}
});
got.paginate = paginateEach as GotPaginate;
got.paginate.all = (async <T, R>(url: string | URL, options?: OptionsWithPagination<T, R>) => {
const results: T[] = [];
for await (const item of paginateEach<T, R>(url, options)) {
results.push(item);
}
return results;
}) as GotPaginate['all'];
// For those who like very descriptive names
got.paginate.each = paginateEach as GotPaginate['each'];
// Stream API
got.stream = ((url: string | URL, options?: StreamOptions) => got(url, {...options, isStream: true})) as GotStream;
// Shortcuts
for (const method of aliases) {
got[method] = ((url: string | URL, options?: Options): GotReturn => got(url, {...options, method})) as GotRequestFunction;
got.stream[method] = ((url: string | URL, options?: StreamOptions) => got(url, {...options, method, isStream: true})) as GotStream;
}
if (!defaults.mutableDefaults) {
Object.freeze(defaults.handlers);
defaults.options.freeze();
}
Object.defineProperty(got, 'defaults', {
value: defaults,
writable: false,
configurable: false,
enumerable: true,
});
return got;
};
export default create;