@typed/fp
Version:
Data Structures and Resources for fp-ts
165 lines • 6.52 kB
JavaScript
/**
* @typed/fp/Use is the only non-referentially transparent module in @typed/fp. It is built atop
* of [Ref](./Ref.ts.md) to enable many common workflows. If you're coming from a React background, it is
* pretty similar to hooks, but the only constraint is that is should be declared once at the top of the scope of your module.
* @since 0.11.0
*/
import { disposeBoth, disposeNone } from '@most/disposable';
import { flow, pipe } from 'fp-ts/function';
import * as O from 'fp-ts/Option';
import { not } from 'fp-ts/Predicate';
import * as E from './Env';
import * as EO from './EnvOption';
import { alwaysEqualsEq, deepEqualsEq, EqStrict } from './Eq';
import * as KV from './KV';
import * as RS from './ReaderStream';
import * as Ref from './Ref';
import * as RefDisposable from './RefDisposable';
import * as R from './Resume';
import { delay } from './Scheduler';
import * as S from './Stream';
/**
* Use Refs to check if a value has changed between invocations
* @since 0.11.0
* @category Constructor
*/
export const defaultOptionRef = () => Ref.kv(E.of(O.none), alwaysEqualsEq);
/**
* Use Refs to check if a value has changed between invocations
* @since 0.11.0
* @category Combinator
*/
export function useEqWith(ref) {
return (Eq = deepEqualsEq, initial = true) => (value) => pipe(E.Do, E.bindW('previous', () => ref.get), E.bindW('changed', ({ previous }) => pipe(previous, O.matchW(() => initial, not(Eq.equals(value))), E.of)), E.chainW(({ previous, changed }) => pipe(previous, O.matchW(() => E.of(changed), () => pipe(value, O.some, ref.set, E.constant(changed))))));
}
/**
* Use Refs to check if a value has changed between invocations
* @since 0.11.0
* @category Combinator
*/
export const useEq = (Eq = deepEqualsEq, initial = true) => useEqWith(defaultOptionRef())(Eq, initial);
/**
* @since 0.11.0
* @category Use
*/
export const useMemoWith = (options) => (env, Eq = deepEqualsEq) => {
const changed = pipe(Eq, useEqWith(options.changed));
const updateRef = options.currentValue.update(() => EO.fromEnv(env));
return flow(changed, E.chainFirstW((changed) => (changed ? updateRef : E.of(null))), E.chainW(() => options.currentValue.get), EO.getOrElseEW(() => pipe(env, E.chainFirstW(flow(O.some, options.currentValue.set)))));
};
const defaultUseMemoRefs = () => ({
currentValue: defaultOptionRef(),
changed: defaultOptionRef(),
});
/**
* @since 0.11.0
* @category Use
*/
export const useMemo = (env, Eq = deepEqualsEq) => useMemoWith(defaultUseMemoRefs())(env, Eq);
/**
* @since 0.11.0
* @category Use
*/
export const useDisposableWith = (options) => (Eq = deepEqualsEq, switchLatest = false) => {
const changed = useEqWith(options.changed)(Eq);
return (f, value) => pipe(E.Do, E.bindW('changed', () => changed(value)), E.bindW('current', () => options.disposable.get), E.chainW(({ changed, current }) => changed
? pipe(E.fromIO(() => (switchLatest ? current.dispose() : null)), E.chainW(() => E.fromIO(f)), E.chainW((next) => pipe(next, RefDisposable.add, E.map((d) => disposeBoth(d, next)), E.chainW((a) => options.disposable.set(a)))))
: E.of(current)));
};
const defaultDisposableRefs = () => ({
disposable: Ref.kv(E.fromIO(disposeNone)),
changed: defaultOptionRef(),
});
/**
* @since 0.11.0
* @category Use
*/
export const useDisposable = (Eq = deepEqualsEq, switchLatest = false) => useDisposableWith(defaultDisposableRefs())(Eq, switchLatest);
/**
* @since 0.11.0
* @category Use
*/
export const useEffectWith = (options) => {
const useD = useDisposableWith(options);
return (Eq = deepEqualsEq, switchLatest = false) => {
const use = useD(Eq, switchLatest);
return (env, value) => pipe(E.ask(), E.chainW((r) => use(() => pipe(r, pipe(delay(0), E.chainW(() => env)), R.exec), value)));
};
};
/**
* @since 0.11.0
* @category Use
*/
export const useWithPrevious = (ref) => {
return (f, value) => pipe(ref.get, E.map((previous) => f(previous, value)), E.chainFirstW(() => pipe(value, O.some, ref.set)));
};
/**
* Helps you to convert a Kliesli arrow of an Env into a function to
* a Disposable. Useful for UIs where you need to provide onClick={fn}
* style handlers.
* @since 0.11.0
* @category Use
*/
export function useEnvK(f, onValue = E.of) {
return pipe(E.Do, E.apSW('refDisposable', RefDisposable.get), E.apSW('resumeF', E.toResumeK(f)), E.apSW('resumeV', E.toResumeK(onValue)), E.map(({ resumeF, resumeV, refDisposable }) => (...args) => {
const d1 = pipe(resumeF(...args), R.chain(resumeV), R.exec);
const d2 = refDisposable.addDisposable(d1);
return disposeBoth(d1, d2);
}));
}
/**
* @since 0.11.0
* @category Use
*/
export const bindEnvK = (name, f, onValue) => (ma) => E.bindW(name, () => useEnvK(f, onValue))(ma);
/**
* @since 0.11.0
* @category Use
*/
export const useReaderStreamWith = (options) => (Eq = deepEqualsEq) => {
const use = useDisposableWith(options)(Eq);
return (rs, dep) => pipe(E.asksE((r) => use(() => rs(r).run(S.createSink({
event: (_, value) => pipe(value, O.some, options.value.set, E.execWith(r)),
}), r.scheduler), dep)), E.chainW(() => options.value.get));
};
const defaultUserReaderStreamRefs = () => ({
disposable: Ref.kv(E.fromIO(disposeNone)),
changed: defaultOptionRef(),
value: defaultOptionRef(),
});
/**
* @since 0.11.0
* @category Use
*/
export const useReaderStream = (Eq = deepEqualsEq) => useReaderStreamWith(defaultUserReaderStreamRefs())(Eq);
/**
* @since 0.11.0
* @category Use
*/
export const useStreamWith = (options) => (Eq = deepEqualsEq) => {
const useRS = pipe(Eq, useReaderStreamWith(options));
return (stream, dep) => useRS(() => stream, dep);
};
/**
* @since 0.11.0
* @category Use
*/
export const useStream = (Eq = deepEqualsEq) => {
const use = useStreamWith(defaultUserReaderStreamRefs())(Eq);
return (stream, dep) => use(stream, dep);
};
/**
* @since 0.11.0
* @category Use
*/
export const useKVStream = (f, Eq) => {
const use = RS.fromEnv(KV.useKeyedEnvs(EqStrict));
const mergeMap = RS.mergeMapWhen(EqStrict);
return (stream) => pipe(use, RS.switchMapW(({ findRefs, deleteRefs }) => pipe(stream, RS.keyed(Eq), mergeMap((s) => pipe(s, RS.fromStream, RS.switchMapW(f), RS.onDispose(deleteRefs(s)), RS.useSomeWith(RS.fromEnv(findRefs(s))))))));
};
/**
* @since 0.11.0
* @category Use
*/
export const useKVs = (f, Eq) => useKVStream(flow(f, KV.sample), Eq);
//# sourceMappingURL=Use.js.map