@typed/fp
Version:
Data Structures and Resources for fp-ts
302 lines • 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useKeyedEnvs = exports.Disposable = exports.sample = exports.env = exports.match = exports.matchW = exports.isRemoved = exports.isUpdated = exports.isCreated = exports.getEnv = exports.withProviderStream = exports.withProvider = exports.findKVProvider = exports.getParentEnv = exports.listenToValues = exports.listenTo = exports.getKVEvents = exports.sendEvent = exports.getSendEvent = exports.getAdapter = exports.remove = exports.update = exports.set = exports.has = exports.get = exports.make = void 0;
const tslib_1 = require("tslib");
/**
* `KV` is an abstraction for managing state-based applications using [Env](./Env.ts.md). It exposes an extensible
* get/set/delete API for managing keys to values. Every `KV` is connected to an `Env` that will
* provide the default value lazily when first asked for or after being deleted previously.
*
* The provided implementation will also send events containing all of the creations/updates/deletes
* occurring in real-time.
* @since 0.11.0
*/
const B = (0, tslib_1.__importStar)(require("fp-ts/boolean"));
const Eq_1 = require("fp-ts/Eq");
const function_1 = require("fp-ts/function");
const RM = (0, tslib_1.__importStar)(require("fp-ts/ReadonlyMap"));
const Refinement_1 = require("fp-ts/Refinement");
const Tuple2_1 = require("fp-ts/Tuple2");
const A = (0, tslib_1.__importStar)(require("./Adapter"));
const D = (0, tslib_1.__importStar)(require("./Disposable"));
const E = (0, tslib_1.__importStar)(require("./Env"));
const EE = (0, tslib_1.__importStar)(require("./EnvEither"));
const Eq_2 = require("./Eq");
const O = (0, tslib_1.__importStar)(require("./Option"));
const RS = (0, tslib_1.__importStar)(require("./ReaderStream"));
const R = (0, tslib_1.__importStar)(require("./Resume"));
/**
* Note that by default an incrementing index is utilized to generate a key if one is not
* provided. In other words, by default, this is not referentially transparent for
* your own convenience
*
* @since 0.11.0
* @category Constructor
*/
function make(initial, options = {}) {
const { equals = Eq_2.deepEqualsEq.equals, key = Symbol() } = options;
return {
key,
initial,
equals,
};
}
exports.make = make;
/**
* @since 0.11.0
* @category Combinator
*/
const get = (kv) => E.asksE((e) => e.getKV(kv));
exports.get = get;
/**
* @since 0.11.0
* @category Combinator
*/
const has = (kv) => E.asksE((e) => e.hasKV(kv));
exports.has = has;
/**
* @since 0.11.0
* @category Combinator
*/
const set = (kv) => (value) => E.asksE((e) => e.setKV(kv, value));
exports.set = set;
/**
* @since 0.11.0
* @category Combinator
*/
const update = (kv) => (f) => (0, function_1.pipe)(kv, exports.get, E.chainW(f), E.chainW((0, exports.set)(kv)));
exports.update = update;
/**
* @since 0.11.0
* @category Combinator
*/
const remove = (kv) => E.asksE((e) => e.removeKV(kv));
exports.remove = remove;
/**
* @since 0.12.0
* @category Combinator
*/
exports.getAdapter = E.asks((e) => e.kvEvents);
/**
* @since 0.11.0
* @category Combinator
*/
exports.getSendEvent = (0, function_1.pipe)(exports.getAdapter, E.map(Tuple2_1.fst));
/**
* @since 0.11.0
* @category Combinator
*/
const sendEvent = (event) => (0, function_1.pipe)(exports.getSendEvent, E.apW(E.of(event)));
exports.sendEvent = sendEvent;
/**
* @since 0.12.0
* @category Combinator
*/
const getKVEvents = (e) => (0, Tuple2_1.snd)(e.kvEvents);
exports.getKVEvents = getKVEvents;
/**
* @since 0.11.0
* @category Combinator
*/
const listenTo = (kv) => (0, function_1.pipe)(exports.getKVEvents, RS.filter((x) => x.key === kv.key));
exports.listenTo = listenTo;
/**
* @since 0.11.0
* @category Combinator
*/
const listenToValues = (kv) => (0, function_1.pipe)(kv, exports.listenTo, RS.map((e) => ((0, exports.isRemoved)(e) ? O.none : O.some(e.value))), RS.startWith(O.none));
exports.listenToValues = listenToValues;
/**
* @since 0.11.0
* @category Combinator
*/
exports.getParentEnv = E.asks((e) => e.parentKVEnv);
/**
* Traverse up the tree of KVEnv and parent KVEnv to find the closest KVEnv that
* has reference for a given KV. This is useful for providing a React-like Context
* API atop of KV.
* @since 0.11.0
* @category Combinator
*/
const findKVProvider = (ref) => {
const check = (0, function_1.pipe)(E.Do, E.bindW('hasRef', () => (0, exports.has)(ref)), E.bindW('env', () => exports.getEnv));
return (0, function_1.pipe)(check, E.chainW(E.chainRec(({ hasRef, env }) => {
if (hasRef || O.isNone(env.parentKVEnv)) {
return (0, function_1.pipe)(env, EE.of);
}
return (0, function_1.pipe)(check, E.useSome(env.parentKVEnv.value), EE.fromEnvL);
})));
};
exports.findKVProvider = findKVProvider;
/**
* @since 0.11.0
* @category Combinator
*/
const withProvider = (kv) => (env) => (0, function_1.pipe)(kv, exports.findKVProvider, E.chainW((refs) => (0, function_1.pipe)(env, E.useSome(refs))));
exports.withProvider = withProvider;
/**
* @since 0.11.0
* @category Combinator
*/
const withProviderStream = (kv) => (rs) => (0, function_1.pipe)(kv, exports.findKVProvider, RS.fromEnv, RS.switchMapW((refs) => (0, function_1.pipe)(rs, RS.useSome(refs))));
exports.withProviderStream = withProviderStream;
/**
* @since 0.12.0
* @category Combinator
*/
exports.getEnv = E.asks(({ getKV, hasKV, setKV, removeKV, kvEvents, parentKVEnv }) => ({
getKV,
hasKV,
setKV,
removeKV,
kvEvents,
parentKVEnv,
}));
/**
* @since 0.11.0
* @category Refinement
*/
const isCreated = (event) => event._tag === 'Created';
exports.isCreated = isCreated;
/**
* @since 0.11.0
* @category Refinement
*/
const isUpdated = (event) => event._tag === 'Updated';
exports.isUpdated = isUpdated;
/**
* @since 0.11.0
* @category Refinement
*/
const isRemoved = (event) => event._tag === 'Removed';
exports.isRemoved = isRemoved;
/**
* @since 0.12.0
* @category Deconstructor
*/
const matchW = (onCreated, onUpdated, onDeleted) => (event) => {
if (event._tag === 'Updated') {
return onUpdated(event.previousValue, event.value, event.key);
}
if (event._tag === 'Created') {
return onCreated(event.value, event.key);
}
return onDeleted(event.key);
};
exports.matchW = matchW;
/**
* @since 0.12.0
* @category Deconstructor
*/
exports.match = exports.matchW;
/**
* @since 0.12.0
* @category Environment Constructor
*/
function env(options = {}) {
const { initial = [], kvEvents = A.create() } = options;
const references = new Map(initial);
const sendEvent = createSendEvent(references, kvEvents);
return {
...makeGetKV(references, sendEvent),
...makeHasKV(references),
...makeSetKV(references, sendEvent),
...makeDeleteKV(references, sendEvent),
parentKVEnv: O.fromNullable(options.parentEnv),
kvEvents: [sendEvent, kvEvents[1]],
};
}
exports.env = env;
function createSendEvent(references, [push]) {
return (event) => (0, function_1.pipe)(event.fromAncestor, B.matchW(
// Only update our local references when event.fromAncestor is false
// as this indicates the event originates from within our current environment.
() => {
if (event._tag === 'Created' || event._tag === 'Updated') {
references.set(event.key, event.value);
}
else {
references.delete(event.key);
}
push(event);
},
// When event.fromAncestor is true, the event originated from another environment.
// We only replicate the event such that a descendant KVEnv can be re-sampled when it subscribes to
// a Ref from an Ancestor's environment.
() => push(event)));
}
function makeGetKV(references, sendEvent) {
return {
getKV(kv) {
if (references.has(kv.key)) {
return E.of(references.get(kv.key));
}
return (0, function_1.pipe)(kv.initial, E.chainFirstIOK((value) => () => sendEvent({ _tag: 'Created', key: kv.key, value, fromAncestor: false })));
},
};
}
function makeHasKV(references) {
return {
hasKV(kv) {
return E.fromIO(() => references.has(kv.key));
},
};
}
function makeSetKV(references, sendEvent) {
const { getKV } = makeGetKV(references, sendEvent);
return {
setKV(kv, value) {
return (0, function_1.pipe)(kv, getKV, E.map((previousValue) => [previousValue, !(0, function_1.pipe)(value, kv.equals(previousValue))]), E.chainFirstIOK(([previousValue, changed]) => () =>
// Only send event when things changed
changed &&
sendEvent({
_tag: 'Updated',
key: kv.key,
previousValue,
value,
fromAncestor: false,
})), E.map(([previousValue, changed]) => (changed ? value : previousValue)));
},
};
}
function makeDeleteKV(references, sendEvent) {
return {
removeKV(kv) {
return (0, function_1.pipe)(E.fromIO(() => (references.has(kv.key) ? O.some(references.get(kv.key)) : O.none)), E.chainFirstIOK(() => () => sendEvent({ _tag: 'Removed', key: kv.key, fromAncestor: false })));
},
};
}
/**
* Sample an Env with the latest references when updates have occured.
* @since 0.11.0
* @category Combinator
*/
const sample = (env) => (0, function_1.pipe)(exports.getKVEvents, RS.filter((0, Refinement_1.not)(exports.isCreated)), RS.startWith(null), RS.exhaustMapLatestEnv(() => env));
exports.sample = sample;
/**
* A shared KV for keeping track of a context's disposable resources.
* @since 0.11.0
* @category KV
*/
exports.Disposable = make(E.fromIO(D.settable), {
...Eq_1.EqStrict,
key: Symbol.for('@typed/fp/KV.Disposable'),
});
/**
* @since 0.11.0
* @category Use
*/
const useKeyedEnvs = (Eq) => {
const refs = make(E.fromIO(() => new Map()), Eq_2.alwaysEqualsEq);
const lookup = RM.lookup(Eq);
const getOrCreate = (key, value) => (0, function_1.pipe)(refs, exports.get, E.chainW((m) => (0, function_1.pipe)(m, lookup(key), O.matchW(() => (0, function_1.pipe)(value, E.tap((x) => m.set(key, x))), E.of))));
const dispose = (0, function_1.pipe)(exports.Disposable, exports.get, E.tap((d) => d.dispose()), E.chainFirstW(() => (0, exports.remove)(exports.Disposable)));
return (0, function_1.pipe)(E.Do, E.apSW('parentEnv', exports.getEnv), E.bindW('createRefs', ({ parentEnv }) => E.of((key) => {
const r = env({ parentEnv });
return (0, function_1.pipe)(refs, exports.get, E.map((m) => m.set(key, r)), E.constant(r), E.useSome(parentEnv));
})), E.bindW('findRefs', ({ createRefs, parentEnv }) => E.of((key) => (0, function_1.pipe)(getOrCreate(key, createRefs(key)), E.useSome(parentEnv)))), E.bindW('deleteRefs', ({ parentEnv }) => E.of((key) => ({
dispose: () => (0, function_1.pipe)(parentEnv, (0, exports.get)(refs), R.map((refs) => refs.get(key)), R.chainFirst(() => (0, function_1.pipe)(refs, exports.get, E.tap((m) => m.delete(key)))(parentEnv)), R.chain((refs) => (refs ? dispose(refs) : R.of(null))), R.exec),
}))), E.map(({ findRefs, deleteRefs }) => ({ findRefs, deleteRefs })));
};
exports.useKeyedEnvs = useKeyedEnvs;
//# sourceMappingURL=KV.js.map