Skip to content

Commit

Permalink
Refactor the core/data store to be independent from the registry
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Mar 26, 2019
1 parent 2e6199d commit 9044dc5
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 157 deletions.
1 change: 1 addition & 0 deletions lib/packages-dependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
'wp-data' => array(
'lodash',
'wp-compose',
'wp-deprecated',
'wp-element',
'wp-is-shallow-equal',
'wp-priority-queue',
Expand Down
1 change: 1 addition & 0 deletions packages/data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@babel/runtime": "^7.3.1",
"@wordpress/compose": "file:../compose",
"@wordpress/deprecated": "file:../deprecated",
"@wordpress/element": "file:../element",
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/priority-queue": "file:../priority-queue",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@ import {
get,
mapValues,
} from 'lodash';
import combineReducers from 'turbo-combine-reducers';

/**
* WordPress dependencies
*/
import createReduxRoutineMiddleware from '@wordpress/redux-routine';

/**
* Internal dependencies
*/
import promise from './promise-middleware';
import createResolversCacheMiddleware from './resolvers-cache-middleware';
import promise from '../promise-middleware';
import createResolversCacheMiddleware from '../resolvers-cache-middleware';
import metadataReducer from './metadata/reducer';
import * as metadataSelectors from './metadata/selectors';
import * as metadataActions from './metadata/actions';

/**
* Creates a namespace object with a store derived from the reducer given.
Expand All @@ -27,16 +36,17 @@ export default function createNamespace( key, options, registry ) {
const reducer = options.reducer;
const store = createReduxStore( key, options, registry );

let selectors, actions, resolvers;
if ( options.actions ) {
actions = mapActions( options.actions, store );
}
if ( options.selectors ) {
selectors = mapSelectors( options.selectors, store, registry );
}
let resolvers;
const actions = mapActions( {
...metadataActions,
...options.actions,
}, store );
let selectors = mapSelectors( {
...mapValues( metadataSelectors, ( selector ) => ( state, ...args ) => selector( state.metadata, ...args ) ),
...mapValues( options.selectors, ( selector ) => ( state, ...args ) => selector( state.root, ...args ) ),
}, store, registry );
if ( options.resolvers ) {
const fulfillment = getCoreDataFulfillment( registry, key );
const result = mapResolvers( options.resolvers, selectors, fulfillment, store );
const result = mapResolvers( options.resolvers, selectors, store );
resolvers = result.resolvers;
selectors = result.selectors;
}
Expand Down Expand Up @@ -84,15 +94,32 @@ export default function createNamespace( key, options, registry ) {
* @return {Object} Newly created redux store.
*/
function createReduxStore( key, options, registry ) {
const middlewares = [
createResolversCacheMiddleware( registry, key ),
promise,
];

if ( options.controls ) {
const normalizedControls = mapValues( options.controls, ( control ) => {
return control.isRegistryControl ? control( registry ) : control;
} );
middlewares.push( createReduxRoutineMiddleware( normalizedControls ) );
}

const enhancers = [
applyMiddleware( createResolversCacheMiddleware( registry, key ), promise ),
applyMiddleware( ...middlewares ),
];
if ( typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__ ) {
enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__( { name: key, instanceId: key } ) );
}

const { reducer, initialState } = options;
return createStore( reducer, initialState, flowRight( enhancers ) );
const enhancedReducer = combineReducers( {
metadata: metadataReducer,
root: reducer,
} );

return createStore( enhancedReducer, initialState, flowRight( enhancers ) );
}

/**
Expand Down Expand Up @@ -151,11 +178,15 @@ function mapActions( actions, store ) {
*
* @param {Object} resolvers Resolvers to register.
* @param {Object} selectors The current selectors to be modified.
* @param {Object} fulfillment Fulfillment implementation functions.
* @param {Object} store The redux store to which the resolvers should be mapped.
* @return {Object} An object containing updated selectors and resolvers.
*/
function mapResolvers( resolvers, selectors, fulfillment, store ) {
function mapResolvers( resolvers, selectors, store ) {
const mappedResolvers = mapValues( resolvers, ( resolver ) => {
const { fulfill: resolverFulfill = resolver } = resolver;
return { ...resolver, fulfill: resolverFulfill };
} );

const mapSelector = ( selector, selectorName ) => {
const resolver = resolvers[ selectorName ];
if ( ! resolver ) {
Expand All @@ -169,68 +200,42 @@ function mapResolvers( resolvers, selectors, fulfillment, store ) {
return;
}

if ( fulfillment.hasStarted( selectorName, args ) ) {
if ( metadataSelectors.hasStartedResolution( store.getState().metadata, selectorName, args ) ) {
return;
}

fulfillment.start( selectorName, args );
await fulfillment.fulfill( selectorName, ...args );
fulfillment.finish( selectorName, args );
store.dispatch( metadataActions.startResolution( selectorName, args ) );
await fulfillResolver( store, mappedResolvers, selectorName, ...args );
store.dispatch( metadataActions.finishResolution( selectorName, args ) );
}

fulfillSelector( ...args );
return selector( ...args );
};
};

const mappedResolvers = mapValues( resolvers, ( resolver ) => {
const { fulfill: resolverFulfill = resolver } = resolver;
return { ...resolver, fulfill: resolverFulfill };
} );

return {
resolvers: mappedResolvers,
selectors: mapValues( selectors, mapSelector ),
};
}

/**
* Bundles up fulfillment functions for resolvers.
* @param {Object} registry Registry reference, for fulfilling via resolvers
* @param {string} key Part of the state shape to register the
* selectors for.
* @return {Object} An object providing fulfillment functions.
*/
function getCoreDataFulfillment( registry, key ) {
const { hasStartedResolution } = registry.select( 'core/data' );
const { startResolution, finishResolution } = registry.dispatch( 'core/data' );

return {
hasStarted: ( ...args ) => hasStartedResolution( key, ...args ),
start: ( ...args ) => startResolution( key, ...args ),
finish: ( ...args ) => finishResolution( key, ...args ),
fulfill: ( ...args ) => fulfillWithRegistry( registry, key, ...args ),
};
}

/**
* Calls a resolver given arguments
*
* @param {Object} registry Registry reference, for fulfilling via resolvers
* @param {string} key Part of the state shape to register the
* selectors for.
* @param {Object} store Store reference, for fulfilling via resolvers
* @param {Object} resolvers Store Resolvers
* @param {string} selectorName Selector name to fulfill.
* @param {Array} args Selector Arguments.
* @param {Array} args Selector Arguments.
*/
async function fulfillWithRegistry( registry, key, selectorName, ...args ) {
const namespace = registry.stores[ key ];
const resolver = get( namespace, [ 'resolvers', selectorName ] );
async function fulfillResolver( store, resolvers, selectorName, ...args ) {
const resolver = get( resolvers, [ selectorName ] );
if ( ! resolver ) {
return;
}

const action = resolver.fulfill( ...args );
if ( action ) {
await namespace.store.dispatch( action );
await store.dispatch( action );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
* Returns an action object used in signalling that selector resolution has
* started.
*
* @param {string} reducerKey Registered store reducer key.
* @param {string} selectorName Name of selector for which resolver triggered.
* @param {...*} args Arguments to associate for uniqueness.
*
* @return {Object} Action object.
*/
export function startResolution( reducerKey, selectorName, args ) {
export function startResolution( selectorName, args ) {
return {
type: 'START_RESOLUTION',
reducerKey,
selectorName,
args,
};
Expand All @@ -21,16 +19,14 @@ export function startResolution( reducerKey, selectorName, args ) {
* Returns an action object used in signalling that selector resolution has
* completed.
*
* @param {string} reducerKey Registered store reducer key.
* @param {string} selectorName Name of selector for which resolver triggered.
* @param {...*} args Arguments to associate for uniqueness.
*
* @return {Object} Action object.
*/
export function finishResolution( reducerKey, selectorName, args ) {
export function finishResolution( selectorName, args ) {
return {
type: 'FINISH_RESOLUTION',
reducerKey,
selectorName,
args,
};
Expand All @@ -39,53 +35,43 @@ export function finishResolution( reducerKey, selectorName, args ) {
/**
* Returns an action object used in signalling that we should invalidate the resolution cache.
*
* @param {string} reducerKey Registered store reducer key.
* @param {string} selectorName Name of selector for which resolver should be invalidated.
* @param {Array} args Arguments to associate for uniqueness.
*
* @return {Object} Action object.
*/
export function invalidateResolution( reducerKey, selectorName, args ) {
export function invalidateResolution( selectorName, args ) {
return {
type: 'INVALIDATE_RESOLUTION',
reducerKey,
selectorName,
args,
};
}

/**
* Returns an action object used in signalling that the resolution cache for a
* given reducerKey should be invalidated.
*
* @param {string} reducerKey Registered store reducer key.
* Returns an action object used in signalling that the resolution
* should be invalidated.
*
* @return {Object} Action object.
*/
export function invalidateResolutionForStore( reducerKey ) {
export function invalidateResolutionForStore() {
return {
type: 'INVALIDATE_RESOLUTION_FOR_STORE',
reducerKey,
};
}

/**
* Returns an action object used in signalling that the resolution cache for a
* given reducerKey and selectorName should be invalidated.
* given selectorName should be invalidated.
*
* @param {string} reducerKey Registered store reducer key.
* @param {string} selectorName Name of selector for which all resolvers should
* be invalidated.
*
* @return {Object} Action object.
*/
export function invalidateResolutionForStoreSelector(
reducerKey,
selectorName
) {
export function invalidateResolutionForStoreSelector( selectorName ) {
return {
type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR',
reducerKey,
selectorName,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ import { onSubKey } from './utils';
* Reducer function returning next state for selector resolution of
* subkeys, object form:
*
* reducerKey -> selectorName -> EquivalentKeyMap<Array,boolean>
* selectorName -> EquivalentKeyMap<Array,boolean>
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @returns {Object} Next state.
*/
const subKeysIsResolved = flowRight( [
onSubKey( 'reducerKey' ),
onSubKey( 'selectorName' ),
] )( ( state = new EquivalentKeyMap(), action ) => {
switch ( action.type ) {
Expand All @@ -44,7 +43,7 @@ const subKeysIsResolved = flowRight( [
/**
* Reducer function returning next state for selector resolution, object form:
*
* reducerKey -> selectorName -> EquivalentKeyMap<Array, boolean>
* selectorName -> EquivalentKeyMap<Array, boolean>
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
Expand All @@ -54,18 +53,10 @@ const subKeysIsResolved = flowRight( [
const isResolved = ( state = {}, action ) => {
switch ( action.type ) {
case 'INVALIDATE_RESOLUTION_FOR_STORE':
return has( state, action.reducerKey ) ?
omit( state, [ action.reducerKey ] ) :
state;
return {};
case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR':
return has( state, [ action.reducerKey, action.selectorName ] ) ?
{
...state,
[ action.reducerKey ]: omit(
state[ action.reducerKey ],
[ action.selectorName ]
),
} :
return has( state, [ action.selectorName ] ) ?
omit( state, [ action.selectorName ] ) :
state;
case 'START_RESOLUTION':
case 'FINISH_RESOLUTION':
Expand Down
Loading

0 comments on commit 9044dc5

Please sign in to comment.