@typed/fp
Version:
Data Structures and Resources for fp-ts
616 lines (549 loc) • 13.6 kB
text/typescript
/**
* `Ref` is an abstraction for managing data-driven applications using [Env](./Env.ts.md).
* @since 0.11.0
*/
import * as Ap from 'fp-ts/Apply'
import * as B from 'fp-ts/boolean'
import { EqStrict } from 'fp-ts/Eq'
import { flow, pipe } from 'fp-ts/function'
import { Functor3 } from 'fp-ts/Functor'
import { IO } from 'fp-ts/IO'
import { concatAll } from 'fp-ts/Monoid'
import { Profunctor3 } from 'fp-ts/Profunctor'
import { Reader } from 'fp-ts/Reader'
import * as RA from 'fp-ts/ReadonlyArray'
import { Semigroupoid3 } from 'fp-ts/Semigroupoid'
import { Task } from 'fp-ts/Task'
import { U } from 'ts-toolbelt'
import * as E from './Env'
import * as EO from './EnvOption'
import * as FKV from './FromKV'
import { Intersect } from './HKT'
import * as KV from './KV'
import * as O from './Option'
import * as P from './Provide'
import * as RS from './ReaderStream'
import * as RSO from './ReaderStreamOption'
import { Resume } from './Resume'
import * as S from './struct'
const allBooleans = E.map(concatAll(B.MonoidAll))
/**
* @since 0.11.0
* @category Model
*/
export interface Ref<E, I, O = I> {
readonly get: E.Env<E, O>
readonly has: E.Env<E, boolean>
readonly set: (input: I) => E.Env<E, O>
readonly update: <E2>(f: (value: O) => E.Env<E2, I>) => E.Env<E & E2, O>
readonly remove: E.Env<E, O.Option<O>>
readonly values: RS.ReaderStream<E, O.Option<O>>
}
/**
* @since 0.11.0
* @category Type-level
*/
export type RequirementsOf<A> = [A] extends [Ref<infer R, any, any>] ? R : never
/**
* @since 0.11.0
* @category Type-level
*/
export type InputOf<A> = [A] extends [Ref<any, infer R, any>] ? R : never
/**
* @since 0.11.0
* @category Type-level
*/
export type OutputOf<A> = [A] extends [Ref<any, any, infer R>] ? R : never
/**
* @since 0.11.0
* @category URI
*/
export const URI = '@typed/fp/Ref'
/**
* @since 0.11.0
* @category URI
*/
export type URI = typeof URI
declare module 'fp-ts/HKT' {
export interface URItoKind2<E, A> {
[URI]: Ref<E, A>
}
export interface URItoKind3<R, E, A> {
[URI]: Ref<R, E, A>
}
}
declare module './HKT' {
export interface URItoVariance {
[URI]: V<R, Contravariant>
}
}
/**
* @since 0.11.0
* @category Constructor
*/
export const fromKV = <K, E, A>(kv: KV.KV<K, E, A>): Ref<E & KV.Env, A> & KV.KV<K, E, A> => ({
...kv,
get: KV.get(kv),
has: KV.has(kv),
set: KV.set(kv),
update: KV.update(kv),
remove: KV.remove(kv),
values: KV.listenToValues(kv),
})
/**
* @since 0.11.0
* @category Constructor
*/
export const kv = flow(KV.make, fromKV)
/**
* @since 0.12.0
* @category Instance Constructor
*/
export const FromKV: FKV.FromKV2<URI, KV.Env> = {
fromKV,
}
/**
* @since 0.11.0
* @category Combinator
*/
export const map =
<A, B>(f: (value: A) => B) =>
<E, I>(ref: Ref<E, I, A>): Ref<E, I, B> => ({
get: pipe(ref.get, E.map(f)),
has: ref.has,
set: flow(ref.set, E.map(f)),
update: (g) => pipe(ref.get, E.map(f), E.chainW(g), E.chainW(ref.set), E.map(f)),
remove: pipe(ref.remove, EO.map(f)),
values: pipe(ref.values, RSO.map(f)),
})
/**
* @since 0.11.0
* @category Instance
*/
export const Functor: Functor3<URI> = {
map,
}
/**
* @since 0.11.0
* @category Combinator
*/
export const local =
<A, B>(f: (value: A) => B) =>
<E, C>(ref: Ref<E, B, C>): Ref<E, A, C> => ({
get: ref.get,
has: ref.has,
set: flow(f, ref.set),
update: (g) => ref.update(flow(g, E.map(f))),
remove: ref.remove,
values: ref.values,
})
/**
* @since 0.12.2
* @category Combinator
*/
export const localE =
<A, E1, B>(f: (value: A) => E.Env<E1, B>) =>
<E2, C>(ref: Ref<E2, B, C>): Ref<E1 & E2, A, C> => ({
get: ref.get,
has: ref.has,
set: flow(f, E.chainW(ref.set)),
update: (g) => ref.update(flow(g, E.chainW(f))),
remove: ref.remove,
values: ref.values,
})
/**
* @since 0.11.0
* @category Combinator
*/
export const promap =
<B, A, C, D>(f: (value: B) => A, g: (value: C) => D) =>
<E>(ref: Ref<E, A, C>): Ref<E, B, D> =>
pipe(ref, local(f), map(g))
/**
* @since 0.12.2
* @category Combinator
*/
export const promapE =
<B, E1, A, C, E2, D>(f: (value: B) => E.Env<E1, A>, g: (value: C) => E.Env<E2, D>) =>
<E3>(ref: Ref<E3, A, C>): Ref<E1 & E2 & E3, B, D> =>
pipe(ref, localE(f), chainEnvK(g))
/**
* @since 0.11.0
* @category Instance
*/
export const Profunctor: Profunctor3<URI> = {
map,
promap,
}
/**
* @since 0.11.0
* @category Combinator
*/
export const ap =
<E1, I, A>(fa: Ref<E1, I, A>) =>
<E2, B>(fab: Ref<E2, I, (value: A) => B>): Ref<E1 & E2, I, B> => {
const get = pipe(fab.get, E.apW(fa.get))
const set = (i: I) => pipe(i, fab.set, E.apW(fa.set(i)))
return {
get,
has: pipe([], E.of, E.apTW(fa.has), E.apTW(fab.has), allBooleans),
set: (i) => pipe(i, fab.set, E.apW(fa.set(i))),
update: (f) => pipe(get, E.chainW(f), E.chainW(set)),
remove: pipe(fab.remove, EO.apW(fa.remove)),
values: pipe(fab.values, RSO.apW(fa.values)),
}
}
/**
* @since 0.11.0
* @category Instance
*/
export const Apply: Ap.Apply3<URI> = {
map,
ap,
}
/**
* @since 0.11.0
* @category Combinator
*/
export const apFirst = Ap.apFirst(Apply)
/**
* @since 0.11.0
* @category Combinator
*/
export const apFirstW = apFirst as <R1, E, B>(
second: Ref<R1, E, B>,
) => <R2, A>(first: Ref<R2, E, A>) => Ref<R1 & R2, E, A>
/**
* @since 0.11.0
* @category Combinator
*/
export const apS = Ap.apS(Apply)
/**
* @since 0.11.0
* @category Combinator
*/
export const apSW = apS as <N extends string, A, R1, E, B>(
name: Exclude<N, keyof A>,
fb: Ref<R1, E, B>,
) => <R2>(
fa: Ref<R2, E, A>,
) => Ref<R1 & R2, E, { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }>
/**
* @since 0.11.0
* @category Combinator
*/
export const apSecond = Ap.apSecond(Apply)
/**
* @since 0.11.0
* @category Combinator
*/
export const apSecondW = apSecond as <R1, E, B>(
second: Ref<R1, E, B>,
) => <R2, A>(first: Ref<R2, E, A>) => Ref<R1 & R2, E, B>
/**
* @since 0.11.0
* @category Combinator
*/
export const apT = Ap.apT(Apply)
/**
* @since 0.11.0
* @category Combinator
*/
export const apTW = apT as <R1, E, B>(
fb: Ref<R1, E, B>,
) => <R2, A extends readonly unknown[]>(fas: Ref<R2, E, A>) => Ref<R1 & R2, E, readonly [...A, B]>
/**
* @since 0.11.0
* @category Typeclass Constructor
*/
export const getApplySemigroup = Ap.getApplySemigroup(Apply)
/**
* @since 0.11.0
* @category Combinator
*/
export const compose =
<E2, A, O>(second: Ref<E2, A, O>) =>
<E1, I>(first: Ref<E1, I, A>): Ref<E1 & E2, I, O> => ({
get: pipe(second.get, E.apFirstW(first.get)), // Ensure both exist
has: pipe(first.has, E.tupled, E.apTW(second.has), allBooleans),
set: flow(first.set, E.chainW(second.set)),
update: (f) => pipe(second.get, E.chainW(f), E.chainW(first.set), E.chainW(second.set)),
remove: pipe(second.remove, E.apFirstW(first.remove)), // Remove both in parallel
values: pipe(
second.values,
RS.mergeFirst(
// Replicate all values into the second
pipe(
first.values,
RS.chainFirstEnvK(O.matchW(() => second.remove, flow(second.set, EO.fromEnv))),
),
),
RS.skipRepeatsWith(O.getEq(EqStrict)), // Avoid sending duplicates
),
})
/**
* @since 0.11.0
* @category Instance
*/
export const Semigroupoid: Semigroupoid3<URI> = {
compose,
}
/**
* @since 0.11.0
* @category Combinator
*/
export const provideSome =
<E1>(provided: E1) =>
<E2, A, B>(ref: Ref<E1 & E2, A, B>): Ref<E2, A, B> => {
return {
get: pipe(ref.get, E.provideSome(provided)),
has: pipe(ref.has, E.provideSome(provided)),
set: flow(ref.set, E.provideSome(provided)),
update: flow(ref.update, E.provideSome(provided)),
remove: pipe(ref.remove, E.provideSome(provided)),
values: pipe(ref.values, RS.provideSome(provided)),
}
}
/**
* @since 0.11.0
* @category Combinator
*/
export const provideAll: <E>(provided: E) => <A, B>(ref: Ref<E, A, B>) => Ref<unknown, A, B> =
provideSome
/**
* @since 0.11.0
* @category Combinator
*/
export const useSome =
<E1>(provided: E1) =>
<E2, A, B>(ref: Ref<E1 & E2, A, B>): Ref<E2, A, B> => {
return {
get: pipe(ref.get, E.useSome(provided)),
has: pipe(ref.has, E.useSome(provided)),
set: flow(ref.set, E.useSome(provided)),
update: flow(ref.update, E.useSome(provided)),
remove: pipe(ref.remove, E.useSome(provided)),
values: pipe(ref.values, RS.useSome(provided)),
}
}
/**
* @since 0.11.0
* @category Combinator
*/
export const useAll: <E1>(provided: E1) => <A, B>(ref: Ref<E1, A, B>) => Ref<unknown, A, B> =
useSome
/**
* @since 0.11.0
* @category Instance
*/
export const UseSome: P.UseSome3<URI> = {
useSome,
}
/**
* @since 0.11.0
* @category Instance
*/
export const UseAll: P.UseAll3<URI> = {
useAll,
}
/**
* @since 0.11.0
* @category Instance
*/
export const ProvideSome: P.ProvideSome3<URI> = {
provideSome,
}
/**
* @since 0.11.0
* @category Instance
*/
export const ProvideAll: P.ProvideAll3<URI> = {
provideAll,
}
/**
* @since 0.11.0
* @category Instance
*/
export const Provide: P.Provide3<URI> = {
useSome,
useAll,
provideSome,
provideAll,
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainEnvK<A, E1, B>(f: (value: A) => E.Env<E1, B>) {
return <E2, I>(ref: Ref<E2, I, A>): Ref<E1 & E2, I, B> => ({
get: pipe(ref.get, E.chainW(f)),
has: ref.has,
set: flow(ref.set, E.chainW(f)),
update: (g) => pipe(flow(f, E.chainW(g)), ref.update, E.chainW(f)),
remove: pipe(ref.remove, EO.chainEnvK(f)),
values: pipe(ref.values, RSO.chainEnvK(f)),
})
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainFirstEnvK<A, E1, B>(f: (value: A) => E.Env<E1, B>) {
return <E2, I>(ref: Ref<E2, I, A>): Ref<E1 & E2, I, A> => ({
get: pipe(ref.get, E.chainFirstW(f)),
has: ref.has,
set: flow(ref.set, E.chainFirstW(f)),
update: flow(ref.update, E.chainFirstW(f)),
remove: pipe(ref.remove, EO.chainFirstEnvK(f)),
values: pipe(ref.values, RSO.chainFirstEnvK(f)),
})
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainIOK<A, B>(f: (value: A) => IO<B>) {
return pipe(f, E.fromIOK, chainEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainFirstIOK<A, B>(f: (value: A) => IO<B>) {
return pipe(f, E.fromIOK, chainFirstEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainReaderK<A, E1, B>(f: (value: A) => Reader<E1, B>) {
return pipe(f, E.fromReaderK, chainEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainFirstReaderK<A, E1, B>(f: (value: A) => Reader<E1, B>) {
return pipe(f, E.fromReaderK, chainFirstEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainResumeK<A, B>(f: (value: A) => Resume<B>) {
return pipe(f, E.fromResumeK, chainEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainFirstResumeK<A, B>(f: (value: A) => Resume<B>) {
return pipe(f, E.fromResumeK, chainFirstEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainTaskK<A, B>(f: (value: A) => Task<B>) {
return pipe(f, E.fromTaskK, chainEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function chainFirstTaskK<A, B>(f: (value: A) => Task<B>) {
return pipe(f, E.fromTaskK, chainFirstEnvK)
}
/**
* @since 0.11.0
* @category Combinator
*/
export function struct<S extends AnyRefStruct>(
properties: S,
): Ref<RefStructEnv<S>, RefStructInput<S>, RefStructOutput<S>> {
const entries = Object.entries(properties)
const zipAssign = flow(
E.zipW,
E.map((props) => Object.assign({}, ...props) as RefStructOutput<S>),
)
const get = pipe(
entries,
RA.map(([k, ref]) =>
pipe(
ref.get,
E.map((v) => S.make(k, v)),
),
),
zipAssign,
)
const has = pipe(
Object.values(properties),
RA.map((ref) => ref.has),
E.zipW,
allBooleans,
)
const set = (i: RefStructInput<S>) =>
pipe(
i,
Object.entries,
RA.map(([k, v]) =>
pipe(
v,
properties[k].set,
E.map((v) => S.make(k, v)),
),
),
zipAssign,
)
const remove = pipe(
entries,
RA.map(([k, ref]) =>
pipe(
ref.remove,
EO.map((v) => S.make(k, v)),
),
),
E.zipW,
E.map(
flow(
O.traverseReadonlyArrayWithIndex((_, a) => a),
O.map((xs) => Object.assign({}, ...xs) as RefStructOutput<S>),
),
),
)
const values = pipe(
RS.combineAll(
...pipe(
entries,
RA.map(([k, ref]) =>
pipe(
ref.values,
RSO.map((v) => S.make(k, v)),
),
),
),
),
RS.map(
flow(
O.traverseReadonlyArrayWithIndex((_, a) => a),
O.map((xs) => Object.assign({}, ...xs) as RefStructOutput<S>),
),
),
)
return {
get,
has,
set,
update: (f) => pipe(get, E.chainW(f), E.chainW(set)),
remove,
values,
}
}
type AnyRefStruct = Readonly<Record<string, Ref<any, any, any>>>
type RefStructEnv<A extends AnyRefStruct> = Intersect<U.ListOf<RequirementsOf<A[keyof A]>>>
type RefStructInput<S extends AnyRefStruct> = {
readonly [K in keyof S]: InputOf<S[K]>
}
type RefStructOutput<S extends AnyRefStruct> = {
readonly [K in keyof S]: OutputOf<S[K]>
}