Skip to content
This repository was archived by the owner on Mar 30, 2022. It is now read-only.

Commit

Permalink
feat: Sync with @mutant-ws/fetch-node
Browse files Browse the repository at this point in the history
  • Loading branch information
andreidmt committed May 27, 2020
1 parent ac2028d commit 0c5cd95
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 81 deletions.
83 changes: 62 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,69 @@

# fetch-browser

Thin wrapper over `window.fetch`. Sister libray of [`@mutant-ws/fetch-node`](https://github.com/mutant-ws/fetch-node).
Thin wrapper over [`window.fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Sister library of [`@mutant-ws/fetch-node`](https://github.com/mutant-ws/fetch-node).

## `set`
<!-- vim-markdown-toc GFM -->

* [Install](#install)
* [Initialize](#initialize)
* [Default headers](#default-headers)
* [Query string parameters](#query-string-parameters)
* [`GET`](#get)
* [`PATCH`](#patch)
* [`POST`](#post)
* [`DELETE`](#delete)
* [`MULTIPART`](#multipart)
* [Changelog](#changelog)

<!-- vim-markdown-toc -->

## Install

```bash
npm i @mutant-ws/fetch-browser
```

## Initialize

```javascript
import { set } from "@mutant-ws/fetch-browser"

// third party library for turning objects into query strings
import { stringify } from "qs"

set({
// Throws if not set and using relative paths
baseURL: "http://localhost",
})
```

### Default headers

```javascript
import { set } from "@mutant-ws/fetch-browser"

// Default headers sent with every request
set({
// Persistent headers
headers: {
// these are already the default
"Accept": "application/json",
"Content-Type": "application/json",
// Library defaults
"accept": "application/json",
"content-type": "application/json",

// Set JWT for authorized requests
authorization: "signed-payload-with-base64-over",
},
})
```

// Stringify for query params. No default provided. Throws if query params
// passed and no stringify function defined.
### Query string parameters

There is no built-in way to handle query params but you can set a custom
transform function.

```javascript
import { set } from "@mutant-ws/fetch-browser"
import { stringify } from "qs"

set({
// Throws if query params passed and no stringify function defined
queryStringifyFn: source =>
stringify(source, {
allowDots: true,
Expand All @@ -33,11 +73,21 @@ set({
strictNullHandling: true,
})
})

```

## `GET`

```javascript
import { GET } from "@mutant-ws/fetch-browser"

const myIP = await GET("https://api.ipify.org", {
query: {
format: "json"
}
})
// => {"ip":"213.127.80.141"}
```

## `PATCH`

## `POST`
Expand All @@ -46,15 +96,6 @@ set({

## `MULTIPART`

<!-- vim-markdown-toc GFM -->

* [About](#about)
* [Changelog](#changelog)

<!-- vim-markdown-toc -->

## About

## Changelog

See the [releases section](https://github.com/mutant-ws/fetch-browser/releases) for details.
17 changes: 0 additions & 17 deletions src/fn.filter-by-value.js

This file was deleted.

6 changes: 3 additions & 3 deletions src/fn.request-error.js → src/fn.http-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
* @param {Number} opt.status Response status
* @param {String|Object} opt.body Response body
*/
export function RequestError(message, { url, status, body }) {
export function HTTPError(message, { url, status, body }) {
this.message = `${status} Server error: ${message}`
this.name = "RequestError"
this.name = "HTTPError"
this.body = body
this.status = status
this.url = url
this.stack = new Error().stack
}

RequestError.prototype = new Error()
HTTPError.prototype = new Error()
2 changes: 1 addition & 1 deletion src/fn.set-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const setProps = props => ({ baseURL, headers, queryStringifyFn }) => {
props.baseURL = trim("/")(baseURL)
} else {
throw new TypeError(
`@mutant-ws/fetch-node: "baseURL" should be a string, received ${JSON.stringify(
`@mutant-ws/fetch-browser: "baseURL" should be a string, received ${JSON.stringify(
baseURL
)}`
)
Expand Down
103 changes: 64 additions & 39 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
/* eslint-disable import/exports-last */

import RFC3986 from "rfc-3986"
import {
get,
pipe,
reduce,
startsWith,
trim,
when,
same,
is,
toLower,
isEmpty,
} from "@mutant-ws/m"

import { setProps } from "./fn.set-props"
import { filterByValue } from "./fn.filter-by-value"
import { RequestError } from "./fn.request-error"
import { HTTPError } from "./fn.http-error"

/**
* Library options
* Config
*/
const props = {
baseURL: "",
Expand All @@ -28,7 +29,7 @@ const props = {
* `window.fetch` with qs support, default headers and rejects on status
* outside 200
*
* @param {String} url API endpoint
* @param {String} path API endpoint
* @param {String} opt.method HTTP Method
* @param {Object} opt.headers HTTP Headers
* @param {Object} opt.body HTTP Body
Expand All @@ -38,49 +39,77 @@ const props = {
* Reject all other response codes.
*/
const request = (
url,
{ method, body = {}, headers = {}, query = {}, isFile = false } = {}
path,
{ method, body = {}, headers = {}, query = {} } = {}
) => {
if (!isEmpty(query) && isEmpty(props.queryStringifyFn)) {
throw new TypeError(
`@mutant-ws/fetch-browser: ${method}:${url} - Trying to send query params but no "queryStringifyFn" provided.`
`@mutant-ws/fetch-browser: ${method}:${path} - Cannot send query params without providing "queryStringifyFn"`
)
}

const reqContent = {
method,
headers: filterByValue(is)({
Accept: "application/json",
"Content-Type": "application/json",
const isPathURI = new RegExp(RFC3986.uri).test(path)

if (isEmpty(props.baseURL) && !isPathURI) {
throw new TypeError(
`@mutant-ws/fetch-browser: ${method}:${path} - Cannot make request with non-absolute path and no "baseURL"`
)
}

// - Remove all undefined values
// - toLower all keys
const HEADERS = reduce(
(acc, [key, value]) =>
value === undefined
? acc
: {
...acc,
[toLower(key)]: value,
},
{}
)(
Object.entries({
accept: "application/json",
"content-type": "application/json",
...props.headers,
...headers,
}),
}
})
)

// Avoid "HEAD or GET Request cannot have a body"
if (method !== "GET") {
reqContent.body = isFile ? body : JSON.stringify(body)
}
const isReqJSON = pipe(
get("content-type"),
startsWith("application/json")
)(HEADERS)

const reqURL = pipe(
const URI = pipe(
when(
isEmpty,
same(url),
source => `${url}?${props.queryStringifyFn(source)}`
same(path),
source => `${path}?${props.queryStringifyFn(source)}`
),
trim("/"),
source => `${props.baseURL}/${source}`
source => (isPathURI ? source : `${props.baseURL}/${source}`)
)(query)

return window
.fetch(reqURL, reqContent)
.fetch(URI, {
method,
headers: HEADERS,

// Avoid "HEAD or GET Request cannot have a body"
...(method === "GET"
? {}
: { body: isReqJSON ? JSON.stringify(body) : body }),
})
.then(response => {
const isJSON = startsWith(
"application/json",
const isResJSON = startsWith("application/json")(
response.headers.get("Content-Type")
)

return Promise.all([response, isJSON ? response.json() : response.text()])
return Promise.all([
response,
isResJSON ? response.json() : response.text(),
])
})
.then(([response, data]) => {
/*
Expand All @@ -93,15 +122,15 @@ const request = (
return data
}

throw new RequestError(response.statusText, {
throw new HTTPError(response.statusText, {
status: response.status,
body: data,
url: reqURL,
path: URI,
})
})
}

export const set = () => setProps(props)
export const set = setProps(props)

export const GET = (url, { query, headers } = {}) =>
request(url, { method: "GET", query, headers })
Expand All @@ -120,20 +149,16 @@ export const MULTIPART = (url, { body = {}, headers } = {}) => {

return request(url, {
method: "POST",
body: pipe(
Object.entries,
reduce((acc, [key, value]) => {
acc.append(key, value)

return acc
}, form)
)(body),
body: reduce((acc, [key, value]) => {
acc.append(key, value)

return acc
}, form)(Object.entries(body)),
headers: {
...headers,

// remove content-type header or browser boundery wont get set
"Content-Type": null,
},
isFile: true,
})
}

0 comments on commit 0c5cd95

Please sign in to comment.