Resource
import * as RES from 'mutoid/http/Resource'
import * as OR from 'mutoid/http/ObservableResource'
import * as ROR from 'mutoid/http/ReaderObservableResource'
ReaderObservableResource, ObservableResource, Resource are three data structures that implement an instance of Functor, Apply, Bifunctor, Applicative, Monad (and MonadObservable for ObservableResource and ReaderObservableResource).
All modules have the same structure (more or less) as a fp-ts
module:
- constructor
- destructors
- combinators
- type class members
- instances
Resource
Resource, that is the basic data structures, is a sum type that represents all possible cases of async provisioning of data.
- ResourceInit: nothing happened
- ResourceSubmitted: the asynchronous request has started
- ResourceDone: the asynchronous request has terminated in an expected state
- ResourceFail: the asynchronous request hasn't terminated in an expected state
Constructors
There are some classic constructors and some helper constructors from data structure IO
, Task
, Either
, etc..
fromAjax
One of the most important and (hopefully) useful constructor implemented in ReaderObservableResource and ObservableResource.
The inputs are ObservableAjax and ResourceDecoders.
>> ObservableAjax
import * as RES from 'mutoid/http/Resource'
import { AjaxError } from 'rxjs/ajax'
import { Observable } from 'rxjs'
type ObservableAjax<AE = never> = Observable<AjaxResponse | RES.ResourceAjaxFail<AE>>
Where:
AjaxResponse
is a normalized response from an AJAX request defined in rxjsResourceAjaxFail
is a sum type defined with two casesunknownError
andappError
(you can find a constructor in Resource)
>> ResourceDecoders
import { Either } from 'fp-ts/Either'
import { StatusCode } from 'mutoid/http/statusCode'
type ResourceDecoders = { [k in StatusCode]?: (i: unknown) => Either<unknown, unknown> }
You can use io-ts
to easily build the decoders.
Example
import * as t from 'io-ts'
import { ajax } from 'rxjs/ajax'
import * as ROR from 'mutoid/http/ReaderObservableResource'
import * as OR from 'mutoid/http/ObservableResource'
import * as RES from 'mutoid/http/Resource'
import { pipe } from 'fp-ts/function'
export interface Deps {
ajax: typeof ajax
}
export const somethingDecoders = {
200: t.array(t.string).decode,
400: t.string.decode,
}
type somethingResource = RES.ResourceTypeOf<typeof somethingDecoders>
const fetchSomething = (id: number, from: string) => (deps: Deps) =>
OR.fromAjax(deps.ajax(`https://api.io?id=${id}&from=${from}`), somethingDecoders)
// or
export const fetchSomething = (id: number, from: string) =>
pipe(
ROR.askTypeOf<Deps, typeof somethingDecoders>(),
ROR.chainW(deps =>
ROR.fromAjax(
deps.ajax(`https://ron-swanson-quotes.herokuapp.com/v2/quotes?id=${id}&from=${from}`),
somethingDecoders
)
)
)
When you use fromAjax
, you consider every status code in the decoder dictionary as a Done
case.
In that case, the Done
resource data is described by a sum type:
interface ResourceData<S, P> {
readonly status: S
readonly payload: P
}
where status and payload are inferred from your decoder dictionary.
The fail resource data, instead, is described by a sum type that has (4+1) cases:
unknownError
decodeError
errors are inferred from your decoder dictionarynetworkError
unexpectedResponse
all status code that you don't specify in decoder dictionaryappError
inferred if you specify that
Here are some examples:
1 - simple fetch with token in store: example
2 - fetch in series: example
3 - fetch in parallel: example
Destructors
match (Resource)
This destructor takes 4 functions for each case: onInit
, onSubmitted
, onDone
, onFail
.
matchD (Resource)
This destructor is very similar to match
, but it works with different inputs:
{
onInit: () => R
onSubmitted: () => R
onDone: (r: A) => R
onFail: (r: E) => R
}
// or
{
onPending: () => R
onDone: (r: A) => R
onFail: (r: E) => R
}
toMutationEffect (ReaderObservableResource, ObservableResource)
This destructor is useful when you want to update a store after an asynchronous request. It can be implemented in ReaderObservableResource and ObservableResource.
In ObservableResource:
import { map } from 'rxjs/operators'
import * as OR from 'mutoid/http/ObservableResource'
import * as MS from 'mutoid/state'
type fetchQuoteMutationWithParams = () => MS.Mutation<
'fetchSomethingMutation',
[id: number, from: string],
QuoteState,
QuoteState
>
export const fetchQuoteMutationWithParams = pipe(
fetchSomething, // (id: number, from: string) => OR.ObservableResource<E, A>
OR.fetchToMutationEffect((s: QuoteState) => (quote): QuoteState => ({ ...s, something: c })),
MS.ctorMutationC('fetchSomethingMutation')
)
In ReaderObservableResource:
import { map } from 'rxjs/operators'
import * as ROR from 'mutoid/http/ReaderObservableResource'
import * as MS from 'mutoid/state'
type fetchQuoteMutationWithParams = (
d: Deps
) => MS.Mutation<'fetchSomethingMutation', [id: number, from: string], QuoteState, QuoteState>
export const fetchQuoteMutationWithParams = pipe(
fetchSomething, // (id: number, from: string) => ROR.ReaderObservableResource<R, E, A>
ROR.fetchToMutationEffectR((s: QuoteState) => (quote): QuoteState => ({ ...s, something: c })),
MS.ctorMutationCR('fetchSomethingMutation')
)