Skip to content

Commit

Permalink
Merge pull request #37 from AliMD/feat/fetch
Browse files Browse the repository at this point in the history
New package: fetch, enhanced fetch api with timeout, promise and types.
  • Loading branch information
alimd authored Mar 3, 2022
2 parents d7767a4 + 9a3303f commit 45d4922
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 0 deletions.
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>;
queryParameters?: Record<string, string | number | boolean>;
}

/**
* 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);
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

0 comments on commit 45d4922

Please sign in to comment.