Skip to content

Commit

Permalink
First stab at the FAIL_RESOLUTION meta action
Browse files Browse the repository at this point in the history
  • Loading branch information
adamziel committed Feb 10, 2022
1 parent e04ec56 commit 71678cc
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 43 deletions.
31 changes: 22 additions & 9 deletions packages/data/src/redux-store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,15 +416,28 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
store.dispatch(
metadataActions.startResolution( selectorName, args )
);
await fulfillResolver(
store,
mappedResolvers,
selectorName,
...args
);
store.dispatch(
metadataActions.finishResolution( selectorName, args )
);
try {
await fulfillResolver(
store,
mappedResolvers,
selectorName,
...args
);
store.dispatch(
metadataActions.finishResolution(
selectorName,
args
)
);
} catch ( e ) {
store.dispatch(
metadataActions.failResolution(
selectorName,
args,
e
)
);
}
} );
}

Expand Down
39 changes: 39 additions & 0 deletions packages/data/src/redux-store/metadata/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ export function finishResolution( selectorName, args ) {
};
}

/**
* Returns an action object used in signalling that selector resolution has
* failed.
*
* @param {string} selectorName Name of selector for which resolver triggered.
* @param {unknown[]} args Arguments to associate for uniqueness.
* @param {Error} error The error that caused the failure
*
* @return {{ type: 'FAIL_RESOLUTION', selectorName: string, args: unknown[], error: Error }} Action object.
*/
export function failResolution( selectorName, args, error ) {
return {
type: 'FAIL_RESOLUTION',
selectorName,
args,
error,
};
}

/**
* Returns an action object used in signalling that a batch of selector resolutions has
* started.
Expand Down Expand Up @@ -68,6 +87,26 @@ export function finishResolutions( selectorName, args ) {
};
}

/**
* Returns an action object used in signalling that a batch of selector resolutions has
* completed and at least one of them has failed.
*
* @param {string} selectorName Name of selector for which resolver triggered.
* @param {unknown[]} args Array of arguments to associate for uniqueness, each item
* is associated to a resolution.
* @param {(Error|null)[]} errors Array of errors to associate for uniqueness, each item
* is associated to a resolution.
* @return {{ type: 'FAIL_RESOLUTIONS', selectorName: string, args: unknown[], errors: Array<Error|null> }} Action object.
*/
export function failResolutions( selectorName, args, errors ) {
return {
type: 'FAIL_RESOLUTIONS',
selectorName,
args,
errors,
};
}

/**
* Returns an action object used in signalling that we should invalidate the resolution cache.
*
Expand Down
39 changes: 34 additions & 5 deletions packages/data/src/redux-store/metadata/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ import { onSubKey } from './utils';
type Action =
| ReturnType< typeof import('./actions').startResolution >
| ReturnType< typeof import('./actions').finishResolution >
| ReturnType< typeof import('./actions').failResolution >
| ReturnType< typeof import('./actions').startResolutions >
| ReturnType< typeof import('./actions').finishResolutions >
| ReturnType< typeof import('./actions').failResolutions >
| ReturnType< typeof import('./actions').invalidateResolution >
| ReturnType< typeof import('./actions').invalidateResolutionForStore >
| ReturnType<
typeof import('./actions').invalidateResolutionForStoreSelector
>;

export type State = EquivalentKeyMap< unknown[] | unknown, boolean >;
type StateKey = unknown[] | unknown;
export type StateValue = { isResolving: boolean; error?: Error };
export type State = EquivalentKeyMap< StateKey, StateValue >;

/**
* Reducer function returning next state for selector resolution of
Expand All @@ -36,17 +40,40 @@ const subKeysIsResolved: Reducer< Record< string, State >, Action > = onSubKey<
switch ( action.type ) {
case 'START_RESOLUTION':
case 'FINISH_RESOLUTION': {
const isStarting = action.type === 'START_RESOLUTION';
const isResolving = action.type === 'START_RESOLUTION';
const nextState = new EquivalentKeyMap( state );
nextState.set( action.args, isStarting );
nextState.set( action.args, { isResolving } );
return nextState;
}
case 'FAIL_RESOLUTION': {
const nextState = new EquivalentKeyMap( state );
nextState.set( action.args, {
isResolving: false,
error: action.error,
} );
return nextState;
}
case 'START_RESOLUTIONS':
case 'FINISH_RESOLUTIONS': {
const isStarting = action.type === 'START_RESOLUTIONS';
const isResolving = action.type === 'START_RESOLUTIONS';
const nextState = new EquivalentKeyMap( state );
for ( const resolutionArgs of action.args ) {
nextState.set( resolutionArgs, isStarting );
nextState.set( resolutionArgs, { isResolving } );
}
return nextState;
}
case 'FAIL_RESOLUTIONS': {
const nextState = new EquivalentKeyMap( state );
for ( const idx in action.args ) {
const resolutionArgs = action.args[ idx ];
const resolutionState: StateValue = { isResolving: false };

const error = action.errors[ idx ];
if ( error ) {
resolutionState.error = error;
}

nextState.set( resolutionArgs, resolutionState );
}
return nextState;
}
Expand Down Expand Up @@ -79,8 +106,10 @@ const isResolved = ( state: Record< string, State > = {}, action: Action ) => {
: state;
case 'START_RESOLUTION':
case 'FINISH_RESOLUTION':
case 'FAIL_RESOLUTION':
case 'START_RESOLUTIONS':
case 'FINISH_RESOLUTIONS':
case 'FAIL_RESOLUTIONS':
case 'INVALIDATE_RESOLUTION':
return subKeysIsResolved( state, action );
}
Expand Down
55 changes: 51 additions & 4 deletions packages/data/src/redux-store/metadata/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import { get } from 'lodash';

/** @typedef {Record<string, import('./reducer').State>} State */
/** @typedef {import('./reducer').StateValue} StateValue */

/**
* Returns the raw `isResolving` value for a given selector name,
* Returns the raw resolution state value for a given selector name,
* and arguments set. May be undefined if the selector has never been resolved
* or not resolved for the given set of arguments, otherwise true or false for
* resolution started and completed respectively.
Expand All @@ -15,9 +16,9 @@ import { get } from 'lodash';
* @param {string} selectorName Selector name.
* @param {unknown[]} args Arguments passed to selector.
*
* @return {boolean | undefined} isResolving value.
* @return {StateValue | undefined} isResolving value.
*/
export function getIsResolving( state, selectorName, args ) {
export function getResolutionState( state, selectorName, args ) {
const map = get( state, [ selectorName ] );
if ( ! map ) {
return undefined;
Expand All @@ -26,6 +27,22 @@ export function getIsResolving( state, selectorName, args ) {
return map.get( args );
}

/**
* Returns the raw `isResolving` value for a given selector name,
* and arguments set. May be undefined if the selector has never been resolved
* or not resolved for the given set of arguments, otherwise true or false for
* resolution started and completed respectively.
*
* @param {State} state Data state.
* @param {string} selectorName Selector name.
* @param {unknown[]} args Arguments passed to selector.
*
* @return {boolean | undefined} isResolving value.
*/
export function getIsResolving( state, selectorName, args ) {
return getResolutionState( state, selectorName, args )?.isResolving;
}

/**
* Returns true if resolution has already been triggered for a given
* selector name, and arguments set.
Expand All @@ -37,7 +54,7 @@ export function getIsResolving( state, selectorName, args ) {
* @return {boolean} Whether resolution has been triggered.
*/
export function hasStartedResolution( state, selectorName, args = [] ) {
return getIsResolving( state, selectorName, args ) !== undefined;
return getResolutionState( state, selectorName, args ) !== undefined;
}

/**
Expand All @@ -54,6 +71,36 @@ export function hasFinishedResolution( state, selectorName, args = [] ) {
return getIsResolving( state, selectorName, args ) === false;
}

/**
* Returns true if resolution has failed for a given selector
* name, and arguments set.
*
* @param {State} state Data state.
* @param {string} selectorName Selector name.
* @param {unknown[]} [args] Arguments passed to selector.
*
* @return {boolean} Has resolution failed
*/
export function hasLastResolutionFailed( state, selectorName, args = [] ) {
const resolutionState = getResolutionState( state, selectorName, args );
const hasFailed = resolutionState && 'error' in resolutionState;
return !! hasFailed;
}

/**
* Returns the resolution error for a given selector
* name, and arguments set.
*
* @param {State} state Data state.
* @param {string} selectorName Selector name.
* @param {unknown[]} [args] Arguments passed to selector.
*
* @return {Error|undefined} Last resolution error
*/
export function getLastResolutionFailure( state, selectorName, args = [] ) {
return getResolutionState( state, selectorName, args )?.error;
}

/**
* Returns true if resolution has been triggered but has not yet completed for
* a given selector name, and arguments set.
Expand Down
60 changes: 42 additions & 18 deletions packages/data/src/redux-store/metadata/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ describe( 'reducer', () => {
args: [],
} );

// { test: { getFoo: EquivalentKeyMap( [] => true ) } }
expect( state.getFoo.get( [] ) ).toBe( true );
// { test: { getFoo: EquivalentKeyMap( [] => isResolving: true } ) } }
expect( state.getFoo.get( [] ) ).toEqual( { isResolving: true } );
} );

it( 'should return with finished resolution', () => {
Expand All @@ -39,8 +39,8 @@ describe( 'reducer', () => {
args: [],
} );

// { test: { getFoo: EquivalentKeyMap( [] => false ) } }
expect( state.getFoo.get( [] ) ).toBe( false );
// { test: { getFoo: EquivalentKeyMap( [] => { isResolving: false } ) } }
expect( state.getFoo.get( [] ) ).toEqual( { isResolving: false } );
} );

it( 'should remove invalidations', () => {
Expand Down Expand Up @@ -81,9 +81,13 @@ describe( 'reducer', () => {
args: [ 'block' ],
} );

// { getFoo: EquivalentKeyMap( [] => false ) }
expect( state.getFoo.get( [ 'post' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( true );
// { getFoo: EquivalentKeyMap( [] => { isResolving: false } ) }
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: false,
} );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: true,
} );
} );

it(
Expand Down Expand Up @@ -123,8 +127,10 @@ describe( 'reducer', () => {
} );

expect( state.getBar ).toBeUndefined();
// { getFoo: EquivalentKeyMap( [] => false ) }
expect( state.getFoo.get( [ 'post' ] ) ).toBe( false );
// { getFoo: EquivalentKeyMap( [] => { isResolving: false } ) }
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: false,
} );
}
);
} );
Expand All @@ -137,8 +143,12 @@ describe( 'reducer', () => {
args: [ [ 'post' ], [ 'block' ] ],
} );

expect( state.getFoo.get( [ 'post' ] ) ).toBe( true );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( true );
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: true,
} );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: true,
} );
} );

it( 'should return with finished resolutions', () => {
Expand All @@ -153,8 +163,12 @@ describe( 'reducer', () => {
args: [ [ 'post' ], [ 'block' ] ],
} );

expect( state.getFoo.get( [ 'post' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: false,
} );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: false,
} );
} );

it( 'should remove invalidations', () => {
Expand All @@ -175,7 +189,9 @@ describe( 'reducer', () => {
} );

expect( state.getFoo.get( [ 'post' ] ) ).toBe( undefined );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: false,
} );
} );

it( 'different arguments should not conflict', () => {
Expand All @@ -195,8 +211,12 @@ describe( 'reducer', () => {
args: [ [ 'block' ] ],
} );

expect( state.getFoo.get( [ 'post' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( true );
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: false,
} );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: true,
} );
} );

it(
Expand Down Expand Up @@ -236,8 +256,12 @@ describe( 'reducer', () => {
} );

expect( state.getBar ).toBeUndefined();
expect( state.getFoo.get( [ 'post' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'block' ] ) ).toBe( false );
expect( state.getFoo.get( [ 'post' ] ) ).toEqual( {
isResolving: false,
} );
expect( state.getFoo.get( [ 'block' ] ) ).toEqual( {
isResolving: false,
} );
}
);
} );
Expand Down
Loading

0 comments on commit 71678cc

Please sign in to comment.