DispatchingStore.ts 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import { Subject, BehaviorSubject } from "rxjs"
  2. import { map } from "rxjs/operators"
  3. import { assign, clone } from "lodash-es"
  4. type DispatcherFunc<StoreType, PayloadType> = (
  5. currentVal: StoreType,
  6. payload: PayloadType
  7. ) => Partial<StoreType>
  8. /**
  9. * Defines a dispatcher.
  10. *
  11. * This function exists to provide better typing for dispatch function.
  12. * As you can see, its pretty much an identity function.
  13. */
  14. export const defineDispatchers = <
  15. StoreType,
  16. T extends { [x: string]: DispatcherFunc<StoreType, any> }
  17. >(
  18. // eslint-disable-next-line no-unused-vars
  19. dispatchers: T
  20. ) => dispatchers
  21. type Dispatch<
  22. StoreType,
  23. DispatchersType extends { [x: string]: DispatcherFunc<StoreType, any> }
  24. > = {
  25. [Dispatcher in keyof DispatchersType]: {
  26. dispatcher: Dispatcher
  27. payload: Parameters<DispatchersType[Dispatcher]>[1]
  28. }
  29. }[keyof DispatchersType]
  30. export default class DispatchingStore<
  31. StoreType,
  32. DispatchersType extends { [x: string]: DispatcherFunc<StoreType, any> }
  33. > {
  34. #state$: BehaviorSubject<StoreType>
  35. #dispatchers: DispatchersType
  36. #dispatches$: Subject<Dispatch<StoreType, DispatchersType>> = new Subject()
  37. constructor(initialValue: StoreType, dispatchers: DispatchersType) {
  38. this.#state$ = new BehaviorSubject(initialValue)
  39. this.#dispatchers = dispatchers
  40. this.#dispatches$
  41. .pipe(
  42. map(({ dispatcher, payload }) =>
  43. this.#dispatchers[dispatcher](this.value, payload)
  44. )
  45. )
  46. .subscribe((val) => {
  47. const data = clone(this.value)
  48. assign(data, val)
  49. this.#state$.next(data)
  50. })
  51. }
  52. get subject$() {
  53. return this.#state$
  54. }
  55. get value() {
  56. return this.subject$.value
  57. }
  58. get dispatches$() {
  59. return this.#dispatches$
  60. }
  61. dispatch({ dispatcher, payload }: Dispatch<StoreType, DispatchersType>) {
  62. if (!this.#dispatchers[dispatcher])
  63. throw new Error(`Undefined dispatch type '${String(dispatcher)}'`)
  64. this.#dispatches$.next({ dispatcher, payload })
  65. }
  66. }