UNPKG

mobx-keystone

Version:

A MobX powered state management solution based on data trees with first class support for TypeScript, snapshots, patches and much more

112 lines (96 loc) 2.75 kB
import { ActionCall, applyAction } from "../action" import { getSnapshot } from "../snapshot/getSnapshot" import { onSnapshot, OnSnapshotDisposer, OnSnapshotListener } from "../snapshot/onSnapshot" import type { SnapshotOutOf } from "../snapshot/SnapshotOf" import { assertTweakedObject } from "../tweaker/core" import { failure } from "../utils" export const reduxActionType = "applyAction" /** * A redux action for mobx-keystone. */ export interface ReduxAction { readonly type: typeof reduxActionType readonly payload: ActionCall } /** * Transforms an action call into a redux action. * * @param actionCall Action call. * @returns A redux action. */ export function actionCallToReduxAction(actionCall: ActionCall): ReduxAction { return { type: reduxActionType, payload: actionCall, } } /** * A redux store for mobx-keystone. */ export interface ReduxStore<T> { getState(): SnapshotOutOf<T> dispatch(action: ReduxAction): ReduxAction subscribe(listener: OnSnapshotListener<T>): OnSnapshotDisposer } /** * A redux runner for mobx-keystone. */ export type ReduxRunner<T> = ( next: ReduxStore<T>["dispatch"] ) => (action: ReduxAction) => ReduxAction /** * A redux middleware for mobx-keystone. */ export type ReduxMiddleware<T> = (store: ReduxStore<T>) => ReduxRunner<T> /** * Generates a redux compatible store out of a mobx-keystone object. * * @template T Object type. * @param target Root object. * @param middlewares Optional list of redux middlewares. * @returns A redux compatible store. */ export function asReduxStore<T extends object>( target: T, ...middlewares: ReduxMiddleware<T>[] ): ReduxStore<T> { assertTweakedObject(target, "target") const defaultDispatch = (action: ReduxAction) => { if (action.type !== reduxActionType) { throw failure( `action type was expected to be '${reduxActionType}', but it was '${action.type}'` ) } applyAction(target, action.payload) return action } const store: ReduxStore<T> = { getState() { return getSnapshot(target) }, dispatch(action) { return runMiddlewares(action, runners, defaultDispatch) }, subscribe(listener) { return onSnapshot(target, listener) }, } const runners = middlewares.map((mw) => mw(store)) return store } function runMiddlewares<T>( initialAction: ReduxAction, runners: ReadonlyArray<ReduxRunner<T>>, next: ReduxStore<T>["dispatch"] ): ReduxAction { let i = 0 function runNextMiddleware(action: ReduxAction): ReduxAction { if (i < runners.length) { const runner = runners[i] i++ return runner(runNextMiddleware)(action) } return next(action) } return runNextMiddleware(initialAction) }