import { Subject, BehaviorSubject } from "rxjs" import { map } from "rxjs/operators" import assign from "lodash/assign" import clone from "lodash/clone" type dispatcherFunc = ( currentVal: StoreType, payload: any ) => Partial /** * Defines a dispatcher. * * This function exists to provide better typing for dispatch function. * As you can see, its pretty much an identity function. */ export const defineDispatchers = ( // eslint-disable-next-line no-unused-vars dispatchers: { [_ in keyof T]: dispatcherFunc } ) => dispatchers type Dispatch< StoreType, DispatchersType extends Record> > = { dispatcher: keyof DispatchersType payload: any } export default class DispatchingStore< StoreType, DispatchersType extends Record> > { #state$: BehaviorSubject #dispatchers: DispatchersType #dispatches$: Subject> = new Subject() constructor(initialValue: StoreType, dispatchers: DispatchersType) { this.#state$ = new BehaviorSubject(initialValue) this.#dispatchers = dispatchers this.#dispatches$ .pipe( map(({ dispatcher, payload }) => this.#dispatchers[dispatcher](this.value, payload) ) ) .subscribe((val) => { const data = clone(this.value) assign(data, val) this.#state$.next(data) }) } get subject$() { return this.#state$ } get value() { return this.subject$.value } get dispatches$() { return this.#dispatches$ } dispatch({ dispatcher, payload }: Dispatch) { if (!this.#dispatchers[dispatcher]) throw new Error(`Undefined dispatch type '${dispatcher}'`) this.#dispatches$.next({ dispatcher, payload }) } }