/** * `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 { readonly get: E.Env readonly has: E.Env readonly set: (input: I) => E.Env readonly update: (f: (value: O) => E.Env) => E.Env readonly remove: E.Env> readonly values: RS.ReaderStream> } /** * @since 0.11.0 * @category Type-level */ export type RequirementsOf = [A] extends [Ref] ? R : never /** * @since 0.11.0 * @category Type-level */ export type InputOf = [A] extends [Ref] ? R : never /** * @since 0.11.0 * @category Type-level */ export type OutputOf = [A] extends [Ref] ? 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 { [URI]: Ref } export interface URItoKind3 { [URI]: Ref } } declare module './HKT' { export interface URItoVariance { [URI]: V } } /** * @since 0.11.0 * @category Constructor */ export const fromKV = (kv: KV.KV): Ref & KV.KV => ({ ...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 = { fromKV, } /** * @since 0.11.0 * @category Combinator */ export const map = (f: (value: A) => B) => (ref: Ref): Ref => ({ 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 = { map, } /** * @since 0.11.0 * @category Combinator */ export const local = (f: (value: A) => B) => (ref: Ref): Ref => ({ 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 = (f: (value: A) => E.Env) => (ref: Ref): Ref => ({ 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 = (f: (value: B) => A, g: (value: C) => D) => (ref: Ref): Ref => pipe(ref, local(f), map(g)) /** * @since 0.12.2 * @category Combinator */ export const promapE = (f: (value: B) => E.Env, g: (value: C) => E.Env) => (ref: Ref): Ref => pipe(ref, localE(f), chainEnvK(g)) /** * @since 0.11.0 * @category Instance */ export const Profunctor: Profunctor3 = { map, promap, } /** * @since 0.11.0 * @category Combinator */ export const ap = (fa: Ref) => (fab: Ref B>): Ref => { 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 = { 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 ( second: Ref, ) => (first: Ref) => Ref /** * @since 0.11.0 * @category Combinator */ export const apS = Ap.apS(Apply) /** * @since 0.11.0 * @category Combinator */ export const apSW = apS as ( name: Exclude, fb: Ref, ) => ( fa: Ref, ) => Ref /** * @since 0.11.0 * @category Combinator */ export const apSecond = Ap.apSecond(Apply) /** * @since 0.11.0 * @category Combinator */ export const apSecondW = apSecond as ( second: Ref, ) => (first: Ref) => Ref /** * @since 0.11.0 * @category Combinator */ export const apT = Ap.apT(Apply) /** * @since 0.11.0 * @category Combinator */ export const apTW = apT as ( fb: Ref, ) => (fas: Ref) => Ref /** * @since 0.11.0 * @category Typeclass Constructor */ export const getApplySemigroup = Ap.getApplySemigroup(Apply) /** * @since 0.11.0 * @category Combinator */ export const compose = (second: Ref) => (first: Ref): Ref => ({ 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 = { compose, } /** * @since 0.11.0 * @category Combinator */ export const provideSome = (provided: E1) => (ref: Ref): Ref => { 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: (provided: E) => (ref: Ref) => Ref = provideSome /** * @since 0.11.0 * @category Combinator */ export const useSome = (provided: E1) => (ref: Ref): Ref => { 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: (provided: E1) => (ref: Ref) => Ref = useSome /** * @since 0.11.0 * @category Instance */ export const UseSome: P.UseSome3 = { useSome, } /** * @since 0.11.0 * @category Instance */ export const UseAll: P.UseAll3 = { useAll, } /** * @since 0.11.0 * @category Instance */ export const ProvideSome: P.ProvideSome3 = { provideSome, } /** * @since 0.11.0 * @category Instance */ export const ProvideAll: P.ProvideAll3 = { provideAll, } /** * @since 0.11.0 * @category Instance */ export const Provide: P.Provide3 = { useSome, useAll, provideSome, provideAll, } /** * @since 0.11.0 * @category Combinator */ export function chainEnvK(f: (value: A) => E.Env) { return (ref: Ref): Ref => ({ 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(f: (value: A) => E.Env) { return (ref: Ref): Ref => ({ 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(f: (value: A) => IO) { return pipe(f, E.fromIOK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstIOK(f: (value: A) => IO) { return pipe(f, E.fromIOK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainReaderK(f: (value: A) => Reader) { return pipe(f, E.fromReaderK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstReaderK(f: (value: A) => Reader) { return pipe(f, E.fromReaderK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainResumeK(f: (value: A) => Resume) { return pipe(f, E.fromResumeK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstResumeK(f: (value: A) => Resume) { return pipe(f, E.fromResumeK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainTaskK(f: (value: A) => Task) { return pipe(f, E.fromTaskK, chainEnvK) } /** * @since 0.11.0 * @category Combinator */ export function chainFirstTaskK(f: (value: A) => Task) { return pipe(f, E.fromTaskK, chainFirstEnvK) } /** * @since 0.11.0 * @category Combinator */ export function struct( properties: S, ): Ref, RefStructInput, RefStructOutput> { const entries = Object.entries(properties) const zipAssign = flow( E.zipW, E.map((props) => Object.assign({}, ...props) as RefStructOutput), ) 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) => 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), ), ), ) 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), ), ), ) return { get, has, set, update: (f) => pipe(get, E.chainW(f), E.chainW(set)), remove, values, } } type AnyRefStruct = Readonly>> type RefStructEnv = Intersect>> type RefStructInput = { readonly [K in keyof S]: InputOf } type RefStructOutput = { readonly [K in keyof S]: OutputOf }