Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New package: fetch, enhanced fetch api with timeout, promise and types. #37

Merged
merged 13 commits into from
Mar 3, 2022
11 changes: 11 additions & 0 deletions package/fetch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @vatr/fetch

Enhanced fetch api with timeout, promise and types.

## Example usage

```js
import { ... } from 'https://esm.run/@vatr/fetch';

...
```
41 changes: 41 additions & 0 deletions package/fetch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@vatr/fetch",
"version": "0.1.2",
"description": "Enhanced fetch api with timeout, promise and types.",
"keywords": [
"vatr",
"alvatr",
"fetch",
"request",
"api",
"timeout"
],
"main": "fetch.js",
"type": "module",
"types": "fetch.d.ts",
"author": "S. Ali Mihandoost <[email protected]> (https://ali.mihandoost.com)",
"license": "MIT",
"files": [
"**/*.js",
"**/*.d.ts",
"**/*.map",
"**/*.html",
"**/*.md"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/AliMD/vatr",
"directory": "package/fetch"
},
"homepage": "https://github.com/AliMD/vatr/tree/main/package/fetch#readme",
"bugs": {
"url": "https://github.com/AliMD/vatr/issues"
},
"dependencies": {
"@vatr/logger": "^0.1.2",
"tslib": "^2.3.1"
}
}
139 changes: 139 additions & 0 deletions package/fetch/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {createLogger, vatrRegisteredList} from '@vatr/logger';

const log = createLogger('vatr/fetch');
// const error = createLogger('vatr/fetch', 'error', true);

vatrRegisteredList.push({
name: '@vatr/fetch',
version: '{{VATR_VERSION}}',
});

declare global {
// Patch typescript's global types
interface AbortController {
abort(reason?: string): void;
}
interface AbortSignal {
reason?: string;
}
}

// @TODO: docs for all options
export interface FetchOptions extends RequestInit
{
/**
* @default 10_000 ms
*/
timeout?: number;
bodyObject?: Record<string | number, unknown>;
alimd marked this conversation as resolved.
Show resolved Hide resolved
queryParameters?: Record<string, string | number | boolean>;
alimd marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Enhanced base fetch API.
* @example const response = await fetch(url, {jsonResponse: false});
*/
export function fetch(url: string, options?: FetchOptions): Promise<Response> {
log('fetch', url, options);

if (!navigator.onLine) {
throw new Error('vatr_fetch_offline');
}

options = {
method: 'GET',
timeout: 15_000,
window: null,
...options,
};

if (options.queryParameters != null) {
const queryArray = Object
.keys(options.queryParameters)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map((key) => `${key}=${String(options!.queryParameters![key])}`)
;
if (queryArray.length > 0) {
url += '?' + queryArray.join('&');
}
}

if (options.bodyObject != null) {
options.body = JSON.stringify(options.bodyObject);
options.headers ??= {};
options.headers['Content-Type'] = 'application/json';
}

// @TODO: AbortController polyfill
const abortController = new AbortController();
const externalAbortSignal = options.signal;
if (externalAbortSignal != null) {
// Respect external abort signal
externalAbortSignal.addEventListener('abort', () => {
abortController.abort(`external abort signal: ${externalAbortSignal.reason}`);
});
}
abortController.signal.addEventListener('abort', () => {
log('fetch: aborted %s', abortController.signal.reason);
});
options.signal = abortController.signal;

const timeoutId = setTimeout(() => abortController.abort(), options.timeout);

// @TODO: browser fetch polyfill
const response = window.fetch(url, options);
alimd marked this conversation as resolved.
Show resolved Hide resolved
response.then(() => clearTimeout(timeoutId));
return response;
}

/**
* Enhanced get data.
* @example
* const response = await postData('/api/products', {limit: 10}, {timeout: 5_000});
*/
export function getData(
url: string,
queryParameters?: Record<string | number, string | number | boolean>,
options?: FetchOptions,
): Promise<Response> {
return fetch(url, {
queryParameters,
...options,
});
}

/**
* Enhanced fetch JSON.
* @example
* const productList = await getJson('/api/products', {limit: 10}, {timeout: 5_000});
*/
export async function getJson<ResponseType extends Record<string | number, unknown>>(
url: string,
queryParameters?: Record<string | number, string | number | boolean>,
options?: FetchOptions,
): Promise<ResponseType> {
const response = await getData(url, queryParameters, options);

if (!response.ok) {
throw new Error('vatr_fetch_nok');
}

return response.json() as Promise<ResponseType>;
}

/**
* Enhanced post json data.
* @example
* const response = await postData('/api/product/new', {name: 'foo', ...});
*/
export function postData(
url: string,
body: Record<string | number, unknown>,
options?: FetchOptions,
): Promise<Response> {
return fetch(url, {
method: 'POST',
bodyObject: body,
...options,
});
}
17 changes: 17 additions & 0 deletions package/fetch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": ".tsbuildinfo",
"rootDir": "src",
"outDir": ".",
},
// files, include and exclude from the inheriting config are always overwritten.
"include": [
"src/**/*.ts"
],
"exclude": [],
"references": [
{ "path": "../logger" },
]
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"references": [
{"path": "./package/signal"},
{"path": "./package/logger"},
{"path": "./package/fetch"},
{"path": "./package/util"},
{"path": "./demo"},
]
Expand Down