https://github.com/contactlab/appy
A functional wrapper around Fetch API
https://github.com/contactlab/appy
appy contactlab fetch fp fp-ts typescript
Last synced: 5 months ago
JSON representation
A functional wrapper around Fetch API
- Host: GitHub
- URL: https://github.com/contactlab/appy
- Owner: contactlab
- License: apache-2.0
- Created: 2018-01-19T14:23:47.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2024-04-15T05:27:00.000Z (about 2 years ago)
- Last Synced: 2025-10-24T20:10:26.793Z (8 months ago)
- Topics: appy, contactlab, fetch, fp, fp-ts, typescript
- Language: TypeScript
- Homepage: https://contactlab.github.io/appy
- Size: 2.59 MB
- Stars: 64
- Watchers: 6
- Forks: 4
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# @contactlab/appy
     
A functional wrapper around Fetch API.
## Install
```sh
$ npm install @contactlab/appy fp-ts
# --- or ---
$ yarn add @contactlab/appy fp-ts
```
## Motivation
`appy` tries to offer a better model for fetching resources, using the standard global `fetch()` function as a "backbone" and some principles from Functional Programming paradigm.
The model is built around the concepts of:
- a function with some configurable options (`Reader`)
- that runs asynchronous operations (`Task`)
- which can fail for some reason (`Either`)
In order to achieve this, `appy` intensely uses:
- [Typescript](https://www.typescriptlang.org) >= v3.2.2
- [`fp-ts`](https://github.com/gcanti/fp-ts)
## API
`appy` exposes a simple core API that can be extended with ["combinators"](#combinators).
It encodes through the `Req` type a resource's request, or rather, an async operation that can fail or return a `Resp`.
For better composability, the request is expressed in terms of `ReaderTaskEither` - a function that takes a `ReqInput` as parameter and returns a `TaskEither`: we can act on both side of operation (input and output) with the tools provided by `fp-ts`.
```ts
interface Req extends ReaderTaskEither> {}
```
`ReqInput` encodes the `fetch()` parameters: a single [`RequestInfo`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) (simple string or [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object) or a tuple of `RequestInfo` and [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) (the object containing request's options, that it's optional in the original `fetch()` API).
```ts
type ReqInput = RequestInfo | RequestInfoInit;
// Just an alias for a tuple of `RequesInfo` and `RequestInit` (namely the `fetch()` parameters)
type RequestInfoInit = [RequestInfo, RequestInit];
```
```ts
interface Resp {
response: Response;
data: A;
input?: RequestInfoInit;
}
```
`Err` encodes (as tagged union) the two kind of error that can be generated by `Req`: a `RequestError` or a `ResponseError`.
```ts
type Err = RequestError | ResponseError;
```
`RequestError` represents a request error. It carries the generated `Error` and the input of the request (`RequestInfoInit` tuple).
```ts
interface RequestError {
type: 'RequestError';
error: Error;
input: RequestInfoInit;
}
```
`ResponseError` represents a response error. It carries the generated `Error`, the original `Response` object and the request's input (optional).
```ts
interface ResponseError {
type: 'ResponseError';
error: Error;
response: Response;
input?: RequestInfoInit;
}
```
## Examples
```ts
import {get} from '@contactlab/appy';
import {fold} from 'fp-ts/Either';
const users = get('https://reqres.in/api/users');
users().then(
fold(
err => console.error(err),
resp => console.log(resp.data)
)
);
```
You can find other examples [here](https://github.com/contactlab/appy/tree/master/examples).
## Combinators
To make easier extending the library functionalities, any other feature should then be expressed as a simple combinator `Req => Req`.
So, for example, decoding the response body as JSON:
```ts
import {get} from '@contactlab/appy';
import {withDecoder, Decoder} from '@contactlab/appy/combinators/decoder';
import {pipe} from 'fp-ts/function';
interface User {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string;
}
declare const userDec: Decoder;
const getUser = pipe(get, withDecoder(userDec));
const singleUser = getUser('https://reqres.in/api/users/1');
```
or adding headers to the request:
```ts
import {get} from '@contactlab/appy';
import {withHeaders} from '@contactlab/appy/combinators/headers';
const asJson = pipe(get, withHeaders({'Content-Type': 'application/json'}));
const users = asJson('https://reqres.in/api/users');
```
or setting request's body (for `POST`s or `PUT`s):
```ts
import {post} from '@contactlab/appy';
import {withBody} from '@contactlab/appy/combinators/body';
import {pipe} from 'fp-ts/function';
const send = pipe(
post,
withBody({email: 'foo.bar@mail.com', first_name: 'Foo', last_name: 'Bar'})
);
const addUser = send('https://reqres.in/api/users');
```
### `io-ts` integration
[`io-ts`](https://github.com/gcanti/io-ts) is recommended but not automatically installed as dependency.
In order to use it with the `Decoder` combinator you can write a simple helper like:
```ts
import * as t from 'io-ts';
import {failure} from 'io-ts/PathReporter';
import {Decoder, toDecoder} from '@contactlab/appy/combinators/decoder';
export const fromIots = (d: t.Decoder): Decoder =>
toDecoder(d.decode, e => new Error(failure(e).join('\n')));
```
Or, with the [Decoder](https://gcanti.github.io/io-ts/modules/Decoder.ts.html) module:
```ts
import * as D from 'io-ts/Decoder';
import {Decoder, toDecoder} from '@contactlab/appy/combinators/decoder';
export const fromIots = (d: D.Decoder): Decoder =>
toDecoder(d.decode, e => new Error(D.draw(e)));
```
## About `fetch()` compatibility
The Fetch API is available only on "modern" browsers: if you need to support legacy browsers (e.g. **Internet Explorer 11** or older) or you want to use it in a Nodejs script we recommend you the excellent [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) package.
**Be aware that Nodejs lacks of some classes and directives which have to be exposed to the global scope (check out the [tests setup file](https://github.com/contactlab/appy/blob/master/test/_setup.ts)).**
### Publish a new version
In order to keep the package's file structure as flat as possible, the "usual" npm `publish` command was disabled (via a `prepublishOnly` script) in favour of a `release` script:
```sh
$ npm run release
```
This command will execute `npm publish` directly in the `/dist` folder, where the `postbuild` script previously copied the `package.json` and other usefull files (`LICENSE`, `CHANGELOG.md`, etc...).
## License
Released under the [Apache 2.0](https://github.com/contactlab/appy/blob/master/LICENSE) license.