import * as R from 'ramda'; import { combineLatest } from 'rxjs/observable/combineLatest'; import { Action, ActionReducer } from '@ngrx/store'; import { trace } from '../js'; import { Obj } from '../models/models'; import { ActionPair, MyAction } from '../actions/actions'; import { Observable } from 'rxjs'; export type TypedReducer = (state: TState, action: MyAction) => TState; export type PayloadReducer = (state: TState, payload: T) => TState; export type ReducerMap = Obj>; export type ReducerTuple = [ActionPair, PayloadReducer]; export type State = any; export type PointFree = (state: T) => T; export type Reducer = (pl: T) => PointFree; // pick the right reducer by action type, or default to the current state export let reducerFn = (types: ReducerMap, initialState?: TState): ActionReducer => (state: TState = initialState, action: MyAction): TState => R.pipe( R.pathOr(() => trace(`reducer type defaulted: ${action.type}`, state), [action.type]), (f: PayloadReducer) => f(state, action.payload), // why was this failing to pass payload? )(types); export function reducerFnTuple(types: ReducerTuple[], initialState: TState): ActionReducer { let pairs = R.map(([{ type, action }, reducerFn]: [ActionPair, PayloadReducer]): [string, (s: TState, a:MyAction) => TState] => [type, (state: TState, act: MyAction) => reducerFn(state, act.payload)] , types); return reducerFn(R.fromPairs(pairs), initialState); } export type ReducerStructMap = { fixed?: Obj, // (_state, _pl) set?: Obj<(payload?: any) => State>, // (_state, pl) update?: Obj, // (state, _pl), point-free! edit?: { [k: string]: (payload?: T) => PointFree }, // (state, pl), point-free! misc?: { [k: string]: (state?: T, payload?: any) => T }, // (state, pl), not point-free, for backward compatibility }; // make a reducer with some optimizations based on what reducer parameters are actually used export let reducerStructFn = /**/(struct: ReducerStructMap, initialState: any): ActionReducer> => { // let { fixed = {}, set = {}, update = {}, edit = {}, misc = {} } = struct; let fixed = struct.fixed || {}; let set = struct.set || {}; let update = struct.update || {}; let edit = struct.edit || {}; let misc = struct.misc || {}; let reducer = >> R.mergeAll([ R.map((v: any) => (state: State, payload: any) => v)(fixed), R.map((fn: (payload: any) => State) => (state: State, payload: any) => fn(payload))(set), R.map((fn: PointFree) => (state: State, payload: any) => fn(state))(update), R.map((fn: (payload: any) => PointFree) => (state: State, payload: any) => fn(payload)(state))(update), misc, ]); return reducerFn(reducer, initialState); } let mapper = (fn: (types: ReducerMap, initialState: T) => ActionReducer) => R.mapObjIndexed(([types, initialState]: [ReducerMap, TState]) => fn(types, initialState)); // : Function // map [ReducerMap, State] tuples to `(State, Action) ~> State` reducers export let mapReducers = mapper(reducerFn); // map [ReducerStructMap, State] tuples to `(State, Action) ~> State` reducers export let mapStructReducers = mapper(reducerStructFn); export type Selector = (state$: Observable) => Observable; export let combineSelectors = (selectors: string/*Selector*/[] /* Types.map(Tp => Selector) */, fn: (params: Types) => TRes): Selector => (state$: Observable): Observable => combineLatest/*<...Types>*/ (...selectors.map((selector: string/*Selector*/) => state$.pluck(selector))).map(fn); // ^ could simplify from array with R.apply(fn) if using ...params wouldn't error with "A rest parameter must be of an array type"