typedux
Version:
Slightly adjusted Redux (awesome by default) for TS
252 lines (221 loc) • 5.78 kB
text/typescript
import type { ObservableStore } from "../store/ObservableStore"
import type { Dispatch } from "redux"
import type { State, RootState } from "../reducers"
import type {
ActionFactory,
ActionFactoryConstructor,
ActionFactoryDecorator,
ActionFn,
ActionMessage,
ActionOptions,
ActionRegistration,
DispatchState,
GetStoreState
} from "./ActionTypes"
import { Option } from "@3fv/prelude-ts"
import { getLogger } from "@3fv/logger-proxy"
import { makeId } from "../util/IdGenerator"
import { InternalState } from "../internal/InternalState"
import { INTERNAL_KEY } from "../constants"
// const
// _cloneDeep = require('lodash.cloneDeep')
const log = getLogger(__filename)
export interface ActionInterceptorNext {
(): any
}
export interface ActionInterceptor {
(reg: ActionRegistration, next: ActionInterceptorNext, ...args: any[]): any
}
/**
* Create a fully qualified action type
*
* @param leaf
* @param type
*
* @returns {string}
*/
export function createLeafActionType(leaf: string, type: string) {
return type.indexOf(".") > -1 ? type : `${leaf}.${type}`
}
export function createActionRegistration<
A extends ActionFactory,
F extends ActionFactoryConstructor<A> = ActionFactoryConstructor<A>,
K extends string | A["leaf"] = A["leaf"]
>(
actionFactoryCtor: F,
leaf: string | K,
type: string,
action: ActionFn,
options: ActionOptions<F> = {}
): ActionRegistration {
return {
type,
fullName: createLeafActionType(leaf as string, type),
leaf,
options,
actionFactoryCtor,
action: (decorator: ActionFactoryDecorator, ...args) => {
let actions = decorator ? decorator(actionFactoryCtor) : null
if (!actions) {
const newFactory = options.factoryCtor || actionFactoryCtor
actions = new newFactory()
}
return action.apply(actions, args)
}
}
}
let globalStore: ObservableStore<any>
// /**
// * Reference to a dispatcher
// */
// let dispatch:DispatchState
//
// /**
// * Reference to store state
// */
// let getStoreState:GetStoreState
export const getGlobalStore = () => globalStore
export const getGlobalStoreState = () => getGlobalStore()?.getState()
const globalDispatchProvider = (<A extends ActionMessage<any>>(action: A) =>
Option.ofNullable(globalStore)
.map(store => store.dispatch(action))
.getOrThrow(`Invalid store`)) as Dispatch<any>
/**
* Get the current store state get
* function - usually set when a new state is created
*
* @returns {GetStoreState}
*/
export function getGlobalStateProvider<S extends State = any>(): GetStoreState<
S
> {
return getGlobalStoreState
}
/**
* Get the current store
* dispatch function
*
* @returns {DispatchState}
*/
export function getGlobalDispatchProvider(): DispatchState {
return globalDispatchProvider
}
/**
* Get the stores internal state
*
* @returns {GetStoreState|any}
*/
export function getGlobalStoreInternalState(): InternalState {
return Option.ofNullable(getGlobalStoreState())
.map(state => state[INTERNAL_KEY])
.getOrUndefined()
}
/**
* Set the global store provider
*
* @param newStore
*/
export function setGlobalStore<Store extends ObservableStore>(newStore: Store) {
if (!newStore && process.env.NODE_ENV === "development") {
console.warn(`You are setting the global store to null`)
}
// Cast the guarded type
globalStore = newStore
}
export class ActionContainer {
registeredActions: { [actionType: string]: ActionRegistration } = {}
actionInterceptors: ActionInterceptor[] = []
constructor(public store: ObservableStore<any>) {}
/**
* Add an interceptor
*
* @param interceptor
* @returns {()=>undefined}
*/
addActionInterceptor(interceptor: ActionInterceptor) {
const { actionInterceptors } = this
actionInterceptors.push(interceptor)
return () => {
const index = actionInterceptors.findIndex(o => interceptor === o)
if (index > -1) actionInterceptors.splice(index, 1)
}
}
/**
* Execute an interceptor at a specific index
*
* @param index
* @param reg
* @param actionId
* @param action
* @param args
* @returns {any}
*/
executeActionInterceptor(
index: number,
reg: ActionRegistration,
actionId: string,
action: Function,
args: any[]
) {
const { actionInterceptors, store } = this
if (actionInterceptors.length > index) {
return actionInterceptors[index](
reg,
() => {
return this.executeActionInterceptor(
index + 1,
reg,
actionId,
action,
args
)
},
...args
)
} else {
return action(actionId, ...args)
}
}
/**
* Execute a given action chain
*
* @param reg
* @param actionFn
* @param args
* @returns {any|any}
*/
executeActionChain(
reg: ActionRegistration,
actionFn: Function,
...args: any[]
) {
return this.executeActionInterceptor(0, reg, makeId(), actionFn, args)
}
/**
* Register an action from a decoration usually
*
* @param reg
* @return {ActionRegistration}
*/
registerAction<
A extends ActionFactory,
F extends ActionFactoryConstructor<A> = ActionFactoryConstructor<A>,
K extends A["leaf"] = A["leaf"]
>(reg: ActionRegistration): ActionRegistration {
this.registeredActions[reg.fullName] = reg
return reg
}
/**
* Retrieve a registered leaf action
*
* @param leaf
* @param type
* @returns {ActionRegistration}
*/
getAction(leaf: string, type: string): ActionRegistration {
return this.registeredActions[createLeafActionType(leaf, type)]
}
getAllActions() {
return this.registeredActions // _cloneDeep(registeredActions)
}
}