@contactlab/appy v5.2.0
@contactlab/appy
A functional wrapper around Fetch API.
Install
$ npm install @contactlab/appy fp-ts
# --- or ---
$ yarn add @contactlab/appy fp-tsMotivation
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 >= v3.2.2
fp-ts
API
appy exposes a simple core API that can be extended with "combinators".
It encodes through the Req<A> type a resource's request, or rather, an async operation that can fail or return a Resp<A>.
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.
interface Req<A> extends ReaderTaskEither<ReqInput, Err, Resp<A>> {}ReqInput encodes the fetch() parameters: a single RequestInfo (simple string or Request object) or a tuple of RequestInfo and RequestInit (the object containing request's options, that it's optional in the original fetch() API).
type ReqInput = RequestInfo | RequestInfoInit;
// Just an alias for a tuple of `RequesInfo` and `RequestInit` (namely the `fetch()` parameters)
type RequestInfoInit = [RequestInfo, RequestInit];Resp<A> is an object that carries the original Response from a fetch() call, the actual retrieved data (of type A) and the request's input (optional).
interface Resp<A> {
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.
type Err = RequestError | ResponseError;RequestError represents a request error. It carries the generated Error and the input of the request (RequestInfoInit tuple).
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).
interface ResponseError {
type: 'ResponseError';
error: Error;
response: Response;
input?: RequestInfoInit;
}Examples
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.
Combinators
To make easier extending the library functionalities, any other feature should then be expressed as a simple combinator Req<A> => Req<A>.
So, for example, decoding the response body as JSON:
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<User>;
const getUser = pipe(get, withDecoder(userDec));
const singleUser = getUser('https://reqres.in/api/users/1');or adding headers to the request:
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 POSTs or PUTs):
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 is recommended but not automatically installed as dependency.
In order to use it with the Decoder combinator you can write a simple helper like:
import * as t from 'io-ts';
import {failure} from 'io-ts/PathReporter';
import {Decoder, toDecoder} from '@contactlab/appy/combinators/decoder';
export const fromIots = <A>(d: t.Decoder<unknown, A>): Decoder<A> =>
toDecoder(d.decode, e => new Error(failure(e).join('\n')));Or, with the Decoder module:
import * as D from 'io-ts/Decoder';
import {Decoder, toDecoder} from '@contactlab/appy/combinators/decoder';
export const fromIots = <A>(d: D.Decoder<unknown, A>): Decoder<A> =>
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 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).
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:
$ npm run releaseThis 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 license.
2 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago