Skip to content

Commit

Permalink
api/test improvements, package updates
Browse files Browse the repository at this point in the history
  • Loading branch information
sampullman committed Nov 21, 2021
1 parent 36cb239 commit 6258f6f
Show file tree
Hide file tree
Showing 20 changed files with 1,054 additions and 876 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Publish
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: 'https://registry.npmjs.org'
- name: Cache pnpm modules
uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- uses: pnpm/[email protected]
with:
version: 6.0.2
run_install: true
- run: npm run build
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npx conventional-github-releaser -p angular
env:
CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"printWidth": 90
}
6 changes: 4 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"editor.tabSize": 2
}
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
4 changes: 0 additions & 4 deletions jest.config.js

This file was deleted.

10 changes: 10 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
preset: 'ts-jest/presets/js-with-ts-esm',
globals: {
'ts-jest': {
tsconfig: './tsconfig.spec.json',
},
},
transformIgnorePatterns: [],
testEnvironment: 'node',
};
43 changes: 24 additions & 19 deletions lib/fetchApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@ import {
FetchRequestConfig,
} from './types';

import { prepareJson, basicAuth, encodeParams, toArray, filterUndefined } from './utils';
import {
prepareJson,
basicAuth,
encodeParams,
resolveSearchParams,
toArray,
} from './utils';

export class FetchApi {
export class FetchApi<ResponseType = Response> {
readonly baseUrl: string;
readonly timeout: number;
requestInterceptors: RequestInterceptor[];
responseInterceptors: ResponseInterceptor[];
constructor(options: FetchApiOptions) {
responseInterceptors: ResponseInterceptor<ResponseType>[];

constructor(options: FetchApiOptions<ResponseType>) {
const { timeout, baseUrl } = options;
this.responseInterceptors = toArray(options.responseInterceptors);
this.requestInterceptors = toArray(options.requestInterceptors);

this.baseUrl = baseUrl || '';
this.timeout = (timeout === undefined) ? 10000 : timeout;
this.timeout = timeout === undefined ? 10000 : timeout;
}

async request(config: FetchRequestConfig) {

async request(config: FetchRequestConfig): Promise<ResponseType> {
// Allow user to transform request config before we do anything
for(const reqInt of this.requestInterceptors) {
for (const reqInt of this.requestInterceptors) {
config = await reqInt(config);
}

Expand All @@ -40,42 +45,42 @@ export class FetchApi {
...finalConfig
}: FetchApiConfig & RequestInit = config;

if(data) {
if (data) {
finalConfig = prepareJson(data, finalConfig);
}
if(auth) {
if (auth) {
finalConfig = basicAuth(auth, finalConfig);
}
timeout = timeout || this.timeout;

let aborter: AbortController | null = null;
if(timeout) {
if (timeout) {
aborter = new AbortController();
config.signal = aborter.signal;
}
params = filterUndefined(params);
const resolvedParams = resolveSearchParams(params);

// Make `fetch` request, without timeout if configured
url = encodeParams(`${this.baseUrl}${config.url}`, params).toString();
url = encodeParams(`${this.baseUrl}${config.url}`, resolvedParams).toString();
let request = fetch(url, finalConfig);

let timeoutId = null;
if(aborter) {
if (aborter) {
timeoutId = setTimeout(() => (aborter as AbortController).abort(), timeout);
}
let response = await request;
if(timeoutId !== null) {
let response = (await request) as unknown as ResponseType;
if (timeoutId !== null) {
clearTimeout(timeoutId);
}

// Allow user to transform response
for(const rspInt of this.responseInterceptors) {
for (const rspInt of this.responseInterceptors) {
response = await rspInt(response);
}
return response;
}

interceptResponse(interceptor: ResponseInterceptor) {
interceptResponse(interceptor: ResponseInterceptor<ResponseType>) {
this.responseInterceptors.push(interceptor);
}

Expand Down
46 changes: 29 additions & 17 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,93 @@


export type RequestInterceptor = (config: Object) => Promise<Object>;
export type ResponseInterceptor = (response: Response) => Promise<Response>;
export type ResponseInterceptor<T> = (response: T) => Promise<T>;

export type RequestParams =
| string
| URLSearchParams
| string[][]
| Record<string, string | number | undefined | null>
| undefined;

export type RequestParams = string | URLSearchParams | string[][] | Record<string, string> | undefined;
export type ResolvedRequestParams =
| string
| URLSearchParams
| string[][]
| Record<string, string>
| undefined;

export interface FetchApiOptions {
export interface FetchApiOptions<ResponseType = Response> {
/**
* API base URL prepended to requests
* @default ''
*/
baseUrl?: string
baseUrl?: string;

/**
* Default request timeout
* @default 10000
*/
timeout?: number
timeout?: number;

/**
* Request interceptors
*/
requestInterceptors?: RequestInterceptor | RequestInterceptor[]
requestInterceptors?: RequestInterceptor | RequestInterceptor[];

/**
* Response interceptors
*/
responseInterceptors?: ResponseInterceptor | ResponseInterceptor[]
responseInterceptors?:
| ResponseInterceptor<ResponseType>
| ResponseInterceptor<ResponseType>[];
}

export interface BasicAuth {
/**
* Basic Auth username
*/
username: string
username: string;

/**
* Basic Auth password
*/
password: string
password: string;
}

export interface FetchApiConfig {
/**
* The URL passed to `fetch`
* @default ''
*/
url?: string
url?: string;

/**
* If present, `data` is stringified and used as `body`
* Headers for JSON data are automatically included:
* { Accept: 'application/json', 'Content-Type': 'application/json' }
*
*
* WARNING - if preset, `body`, `headers['Accept']`, and `headers['Content-Type']` are overridden
*/
data?: Object
data?: Object;

/**
* URL parameters appended to `url` using URLSearchParams
*/
params?: RequestParams,
params?: RequestParams;

/**
* Convenience option for providing Basic Auth headers
* Overrides headers:
* `headers['Authorization'] = \`Basic ${btoa(\`${auth.username}:${auth.password}\`)}\``
* `
*/
auth?: BasicAuth
auth?: BasicAuth;

/**
* Timeout in milliseconds. Falls back to global config timeout
* Overrides `signal`
* @default 10000
*/
timeout?: number
timeout?: number;
}

export type FetchRequestConfig = FetchApiConfig & RequestInit;
29 changes: 15 additions & 14 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ResolvedRequestParams } from '.';
import { FetchRequestConfig, BasicAuth, RequestParams } from './types';

type Filterable = {
[key: string]: any
}
[key: string]: any;
};

export function filterUndefined(obj: RequestParams) {
if(typeof obj === 'object') {
export function resolveSearchParams(obj: RequestParams): ResolvedRequestParams {
if (typeof obj === 'object') {
const filteredObj: Filterable = {};
Object.entries(obj).forEach(([key, val]) => {
if(val !== undefined) {
filteredObj[key] = val;
if (val !== undefined) {
filteredObj[key] = val.toString();
}
});
return filteredObj;
Expand All @@ -18,10 +19,10 @@ export function filterUndefined(obj: RequestParams) {
}

export function toArray<T>(arr: void | T | T[]): any[] {
if(!arr) {
if (!arr) {
return [];
}
if(Array.isArray(arr)) {
if (Array.isArray(arr)) {
return arr;
}
return [arr];
Expand All @@ -47,9 +48,9 @@ export function prepareJson(data: any, config: RequestInit): RequestInit {
};
}

export function encodeParams(url: string, params: RequestParams) {
export function encodeParams(url: string, params: ResolvedRequestParams) {
const encodedUrl = new URL(url);
if(params) {
if (params) {
encodedUrl.search = new URLSearchParams(params).toString();
}
return encodedUrl;
Expand All @@ -62,8 +63,8 @@ export function basicAuth(auth: BasicAuth, config: RequestInit): RequestInit {
...rest,
headers: {
...headers,
'Authorization': `Basic ${authData}`,
Authorization: `Basic ${authData}`,
'Content-Type': 'application/x-www-form-urlencoded',
}
}
}
},
};
}
31 changes: 16 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"name": "@sampullman/fetch-api",
"version": "0.5.0",
"description": "Small fetch wrapper for quickly prototyping API clients",
"type": "module",
"main": "dist/fetch-api.umd",
"module": "dist/fetch-api.mjs",
"types": "dist/fetch-api.d.ts",
"module": "dist/fetch-api.js",
"types": "dist/lib/index.d.ts",
"files": [
"dist",
"lib"
Expand Down Expand Up @@ -35,23 +36,23 @@
},
"homepage": "https://github.com/sampullman/fetch-api#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@types/jest": "^27.0.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.0.6",
"@types/btoa": "^1.2.3",
"@types/jest": "^27.0.3",
"abort-controller": "^3.0.0",
"btoa": "^1.2.1",
"fetch-mock-jest": "^1.5.1",
"jest": "^27.0.6",
"node-fetch": "^2.6.1",
"jest": "^27.3.1",
"node-fetch": "^3.1.0",
"rimraf": "^3.0.2",
"rollup": "^2.52.6",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-dts": "^3.0.2",
"rollup": "^2.60.0",
"rollup-plugin-dts": "^4.0.1",
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
"ts-jest": "^27.0.4",
"tslib": "^2.3.0",
"typescript": "^4.3.5"
"rollup-plugin-typescript2": "^0.31.0",
"ts-jest": "^27.0.7",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
"typescript": "^4.5.2"
}
}
Loading

0 comments on commit 6258f6f

Please sign in to comment.