From 5cc660ff7c203613d75672921bec22ad9d750540 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 17 May 2020 00:34:49 -0400 Subject: [PATCH] Update --- .../src/instrument.ts | 247 +++++++++++++----- .../test/instrument.spec.ts | 40 +-- 2 files changed, 200 insertions(+), 87 deletions(-) diff --git a/packages/redux-devtools-instrument/src/instrument.ts b/packages/redux-devtools-instrument/src/instrument.ts index 6ba337a2..b6ac875d 100644 --- a/packages/redux-devtools-instrument/src/instrument.ts +++ b/packages/redux-devtools-instrument/src/instrument.ts @@ -96,9 +96,9 @@ interface JumpToActionAction { actionId: number; } -interface ImportStateAction> { +interface ImportStateAction, MonitorState> { type: typeof ActionTypes.IMPORT_STATE; - nextLiftedState: LiftedState | readonly A[]; + nextLiftedState: LiftedState | readonly A[]; preloadedState?: S; noRecompute: boolean | undefined; } @@ -113,7 +113,12 @@ interface PauseRecordingAction { status: boolean; } -export type LiftedAction> = +export type LiftedAction< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> = | PerformAction | ResetAction | RollbackAction @@ -124,7 +129,7 @@ export type LiftedAction> = | ReorderAction | JumpToStateAction | JumpToActionAction - | ImportStateAction + | ImportStateAction | LockChangesAction | PauseRecordingAction; @@ -242,10 +247,10 @@ export const ActionCreators = { return { type: ActionTypes.JUMP_TO_ACTION, actionId }; }, - importState>( - nextLiftedState: LiftedState | readonly A[], + importState, MonitorState>( + nextLiftedState: LiftedState | readonly A[], noRecompute?: boolean - ): ImportStateAction { + ): ImportStateAction { return { type: ActionTypes.IMPORT_STATE, nextLiftedState, noRecompute }; }, @@ -371,7 +376,7 @@ export function liftAction>( traceLimit?: number, toExcludeFromTrace?: Function ): PerformAction { - return ActionCreators.performAction( + return ActionCreators.performAction( action, trace, traceLimit, @@ -379,8 +384,14 @@ export function liftAction>( ); } -export interface LiftedState> { - monitorState: unknown; +function isArray, MonitorState>( + nextLiftedState: LiftedState | readonly A[] +): nextLiftedState is readonly A[] { + return Array.isArray(nextLiftedState); +} + +export interface LiftedState, MonitorState> { + monitorState: MonitorState; nextActionId: number; actionsById: { [actionId: number]: PerformAction }; stagedActionIds: number[]; @@ -392,25 +403,27 @@ export interface LiftedState> { isPaused: boolean; } -function isArray>( - nextLiftedState: LiftedState | readonly A[] -): nextLiftedState is readonly A[] { - return Array.isArray(nextLiftedState); -} - /** * Creates a history state reducer from an app's reducer. */ -export function liftReducerWith>( +export function liftReducerWith< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +>( reducer: Reducer, initialCommittedState: PreloadedState | undefined, - monitorReducer: Reducer>, - options: Options -): Reducer, LiftedAction> { - const initialLiftedState: LiftedState = { - monitorState: monitorReducer(undefined, {} as Action), + monitorReducer: Reducer, + options: Options +): Reducer< + LiftedState, + LiftedAction +> { + const initialLiftedState: LiftedState = { + monitorState: monitorReducer(undefined, {} as MonitorAction), nextActionId: 1, - actionsById: { 0: liftAction(INIT_ACTION as A) }, + actionsById: { 0: liftAction(INIT_ACTION as A) }, stagedActionIds: [0], skippedActionIds: [], committedState: initialCommittedState as S, @@ -424,9 +437,9 @@ export function liftReducerWith>( * Manages how the history actions modify the history state. */ return ( - liftedState: LiftedState | undefined, - liftedAction: LiftedAction - ): LiftedState => { + liftedState: LiftedState | undefined, + liftedAction: LiftedAction + ): LiftedState => { let { monitorState, actionsById, @@ -471,11 +484,16 @@ export function liftReducerWith>( currentStateIndex > excess ? currentStateIndex - excess : 0; } - function computePausedAction(shouldInit?: boolean): LiftedState { + function computePausedAction( + shouldInit?: boolean + ): LiftedState { let computedState; if (shouldInit) { computedState = computedStates[currentStateIndex]; - monitorState = monitorReducer(monitorState, liftedAction); + monitorState = monitorReducer( + monitorState, + liftedAction as MonitorAction + ); } else { computedState = computeNextEntry( reducer, @@ -487,7 +505,7 @@ export function liftReducerWith>( if (!options.pauseActionType || nextActionId === 1) { return { monitorState, - actionsById: { 0: liftAction(INIT_ACTION as A) }, + actionsById: { 0: liftAction(INIT_ACTION as A) }, nextActionId: 1, stagedActionIds: [0], skippedActionIds: [], @@ -509,7 +527,7 @@ export function liftReducerWith>( monitorState, actionsById: { ...actionsById, - [nextActionId - 1]: liftAction({ + [nextActionId - 1]: liftAction({ type: options.pauseActionType } as A) }, @@ -539,7 +557,7 @@ export function liftReducerWith>( if (/^@@redux\/(INIT|REPLACE)/.test(liftedAction.type)) { if (options.shouldHotReload === false) { - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -597,7 +615,7 @@ export function liftReducerWith>( } case ActionTypes.RESET: { // Get back to the state the store was created with. - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -609,7 +627,7 @@ export function liftReducerWith>( case ActionTypes.COMMIT: { // Consider the last committed state the new starting point. // Squash any staged actions into a single committed state. - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -621,7 +639,7 @@ export function liftReducerWith>( case ActionTypes.ROLLBACK: { // Forget about any staged actions. // Start again from the last committed state. - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -724,7 +742,7 @@ export function liftReducerWith>( case ActionTypes.IMPORT_STATE: { if (isArray(liftedAction.nextLiftedState)) { // recompute array of actions - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -772,7 +790,7 @@ export function liftReducerWith>( return computePausedAction(true); } // Commit when unpausing - actionsById = { 0: liftAction(INIT_ACTION as A) }; + actionsById = { 0: liftAction(INIT_ACTION as A) }; nextActionId = 1; stagedActionIds = [0]; skippedActionIds = []; @@ -800,7 +818,7 @@ export function liftReducerWith>( skippedActionIds, options.shouldCatchErrors ); - monitorState = monitorReducer(monitorState, liftedAction); + monitorState = monitorReducer(monitorState, liftedAction as MonitorAction); return { monitorState, actionsById, @@ -819,25 +837,55 @@ export function liftReducerWith>( /** * Provides an app's view into the state of the lifted store. */ -export function unliftState, NextStateExt>( - liftedState: LiftedState & NextStateExt -) { +export function unliftState< + S, + A extends Action, + MonitorState, + MonitorAction extends Action, + NextStateExt +>( + liftedState: LiftedState & NextStateExt +): S & NextStateExt { const { computedStates, currentStateIndex } = liftedState; const { state } = computedStates[currentStateIndex]; - return state; + return state as S & NextStateExt; } -export type LiftedStore> = Store< - LiftedState, - LiftedAction +export type LiftedReducer< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> = Reducer< + LiftedState, + LiftedAction >; -export type InstrumentExt> = { - liftedStore: LiftedStore; +export type LiftedStore< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> = Store< + LiftedState, + LiftedAction +>; + +export type InstrumentExt< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> = { + liftedStore: LiftedStore; }; -export type EnhancedStore> = Store & - InstrumentExt; +export type EnhancedStore< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> = Store & InstrumentExt; /** * Provides an app's view into the lifted store. @@ -845,26 +893,35 @@ export type EnhancedStore> = Store & export function unliftStore< S, A extends Action, + MonitorState, + MonitorAction extends Action, NextExt, NextStateExt >( - liftedStore: Store & NextStateExt, LiftedAction> & + liftedStore: Store< + LiftedState & NextStateExt, + LiftedAction + > & NextExt, liftReducer: ( r: Reducer - ) => Reducer, LiftedAction>, - options: Options + ) => LiftedReducer, + options: Options ): Store & NextExt & { - liftedStore: Store & NextStateExt, LiftedAction>; + liftedStore: Store< + LiftedState & NextStateExt, + LiftedAction + >; } { let lastDefinedState: S & NextStateExt; const trace = options.trace || options.shouldIncludeCallstack; const traceLimit = options.traceLimit || 10; function getState(): S & NextStateExt { - const state = unliftState(liftedStore.getState()) as S & - NextStateExt; + const state = unliftState( + liftedStore.getState() + ); if (state !== undefined) { lastDefinedState = state; } @@ -890,8 +947,8 @@ export function unliftStore< (liftReducer( (nextReducer as unknown) as Reducer ) as unknown) as Reducer< - LiftedState & NextStateExt, - LiftedAction + LiftedState & NextStateExt, + LiftedAction > ); }, @@ -922,16 +979,24 @@ export function unliftStore< } } as unknown) as Store & NextExt & { - liftedStore: Store & NextStateExt, LiftedAction>; + liftedStore: Store< + LiftedState & NextStateExt, + LiftedAction + >; }; } -export interface Options> { +export interface Options< + S, + A extends Action, + MonitorState, + MonitorAction extends Action +> { maxAge?: | number | (( - currentLiftedAction: LiftedAction, - previousLiftedState: LiftedState | undefined + currentLiftedAction: LiftedAction, + previousLiftedState: LiftedState | undefined ) => number); shouldCatchErrors?: boolean; shouldRecordChanges?: boolean; @@ -946,10 +1011,40 @@ export interface Options> { /** * Redux instrumentation store enhancer. */ -export default function instrument>( - monitorReducer: Reducer> = () => null, - options: Options = {} -): StoreEnhancer> { +export default function instrument< + no_options_state = never, + no_options_action = never, + no_monitor_state = null, + no_monitor_action = never +>(): StoreEnhancer>; +export default function instrument< + OptionsS, + OptionsA extends Action, + no_monitor_state = null, + no_monitor_action = never +>( + monitorReducer: undefined, + options: Options +): StoreEnhancer>; +export default function instrument< + OptionsS, + OptionsA extends Action, + MonitorState, + MonitorAction extends Action +>( + monitorReducer: Reducer, + options: Options +): StoreEnhancer>; +export default function instrument< + OptionsS, + OptionsA extends Action, + MonitorState, + MonitorAction extends Action +>( + monitorReducer: Reducer = ((() => + null) as unknown) as Reducer, + options: Options = {} +): StoreEnhancer> { if (typeof options.maxAge === 'number' && options.maxAge < 2) { throw new Error( 'DevTools.instrument({ maxAge }) option, if specified, ' + @@ -975,22 +1070,25 @@ export default function instrument>( } throw new Error('Expected the reducer to be a function.'); } - return liftReducerWith( + return liftReducerWith( r, initialState, monitorReducer, - (options as unknown) as Options + (options as unknown) as Options ); } const liftedStore = createStore(liftReducer(reducer)); if ( (liftedStore as Store< - LiftedState & NextStateExt, - LiftedAction + LiftedState & NextStateExt, + LiftedAction > & NextExt & { - liftedStore: Store, LiftedAction>; + liftedStore: Store< + LiftedState, + LiftedAction + >; }).liftedStore ) { throw new Error( @@ -999,10 +1097,17 @@ export default function instrument>( ); } - return unliftStore( + return unliftStore< + S, + A, + MonitorState, + MonitorAction, + NextExt, + NextStateExt + >( liftedStore, liftReducer, - (options as unknown) as Options + (options as unknown) as Options ); }; } diff --git a/packages/redux-devtools-instrument/test/instrument.spec.ts b/packages/redux-devtools-instrument/test/instrument.spec.ts index 648a50f7..bb72e3ab 100644 --- a/packages/redux-devtools-instrument/test/instrument.spec.ts +++ b/packages/redux-devtools-instrument/test/instrument.spec.ts @@ -90,8 +90,8 @@ function counterWithMultiply(state = 0, action: CounterWithMultiplyAction) { } describe('instrument', () => { - let store: EnhancedStore; - let liftedStore: LiftedStore; + let store: EnhancedStore; + let liftedStore: LiftedStore; beforeEach(() => { store = createStore(counter, instrument()); @@ -534,8 +534,8 @@ describe('instrument', () => { }); describe('maxAge option', () => { - let configuredStore: EnhancedStore; - let configuredLiftedStore: LiftedStore; + let configuredStore: EnhancedStore; + let configuredLiftedStore: LiftedStore; beforeEach(() => { configuredStore = createStore( @@ -618,7 +618,9 @@ describe('instrument', () => { ); const liftedStoreWithAnotherBug = (liftedStoreWithBug as unknown) as LiftedStore< number, - CounterWithAnotherBugAction + CounterWithAnotherBugAction, + null, + never >; expect(liftedStoreWithAnotherBug.getState().stagedActionIds).toHaveLength( 5 @@ -630,7 +632,9 @@ describe('instrument', () => { ); const liftedStore = liftedStoreWithBug as LiftedStore< number, - CounterAction + CounterAction, + null, + never >; expect(liftedStore.getState().stagedActionIds).toHaveLength(3); @@ -698,7 +702,9 @@ describe('instrument', () => { ); const liftedStore = liftedStoreWithBug as LiftedStore< number, - CounterAction + CounterAction, + null, + never >; const liftedStoreState = liftedStore.getState(); const currentComputedState = @@ -730,7 +736,9 @@ describe('instrument', () => { ); const liftedStore = liftedStoreWithBug as LiftedStore< number, - CounterAction + CounterAction, + null, + never >; const liftedStoreState = liftedStore.getState(); const currentComputedState = @@ -1081,9 +1089,9 @@ describe('instrument', () => { }); describe('Import State', () => { - let monitoredStore: EnhancedStore; - let monitoredLiftedStore: LiftedStore; - let exportedState: LiftedState; + let monitoredStore: EnhancedStore; + let monitoredLiftedStore: LiftedStore; + let exportedState: LiftedState; beforeEach(() => { monitoredStore = createStore(counter, instrument()); @@ -1164,7 +1172,7 @@ describe('instrument', () => { }); function filterStackAndTimestamps>( - state: LiftedState + state: LiftedState ) { state.actionsById = _.mapValues(state.actionsById, action => { delete action.timestamp; @@ -1175,9 +1183,9 @@ describe('instrument', () => { } describe('Import Actions', () => { - let monitoredStore: EnhancedStore; - let monitoredLiftedStore: LiftedStore; - let exportedState: LiftedState; + let monitoredStore: EnhancedStore; + let monitoredLiftedStore: LiftedStore; + let exportedState: LiftedState; const savedActions = [ { type: 'INCREMENT' }, { type: 'INCREMENT' }, @@ -1285,7 +1293,7 @@ describe('instrument', () => { { type: 'INCREMENT' } ] as const; store.liftedStore.dispatch( - ActionCreators.importState(savedActions) + ActionCreators.importState(savedActions) ); expect(store.liftedStore.getState().nextActionId).toBe(3); expect(store.getState()).toBe(2);