import { Subject, BehaviorSubject } from "rxjs" import { map } from "rxjs/operators" import { assign, clone } from "lodash-es" type DispatcherFunc = ( currentVal: StoreType, payload: PayloadType ) => 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 = < StoreType, T extends { [x: string]: DispatcherFunc } >( // eslint-disable-next-line no-unused-vars dispatchers: T ) => dispatchers type Dispatch< StoreType, DispatchersType extends { [x: string]: DispatcherFunc } > = { [Dispatcher in keyof DispatchersType]: { dispatcher: Dispatcher payload: Parameters[1] } }[keyof DispatchersType] export default class DispatchingStore< StoreType, DispatchersType extends { [x: string]: DispatcherFunc } > { #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 '${String(dispatcher)}'`) this.#dispatches$.next({ dispatcher, payload }) } }