Skip to content

Commit

Permalink
Data Module: Support controls in resolvers (#9507)
Browse files Browse the repository at this point in the history
* Redux Routine: Make it more flexible allowing runtimes without middlewares

* Use rungen to implement the co-routine middleware

* Refactor resolvers to allow extending fullfillement in plugins

* Simplify the signature of the fulfill function

* Fallback to regular resolution if controls are not supported

* Treat async generators in resolvers as a plugin

* Fix async generator middleware

* Don't proxy the namespaces registry property

* Avoid useless middleware file

* Fix proxifying the registry attributes by auto-decting functions

* Performance: Bind resolvers to selectors at registration time

* Remove useless alignment change

* Avoid a middlewares folder and move middlewares to plugins and core

* Make the fulfill registry method experimental

* fix wording and typos
  • Loading branch information
youknowriad authored Sep 17, 2018
1 parent 41cb78b commit cbee072
Show file tree
Hide file tree
Showing 16 changed files with 442 additions and 443 deletions.
1 change: 1 addition & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ function gutenberg_register_scripts_and_styles() {
' var storageKey = "WP_DATA_USER_" + userId;',
' wp.data',
' .use( wp.data.plugins.persistence, { storageKey: storageKey } )',
' .use( wp.data.plugins.asyncGenerator )',
' .use( wp.data.plugins.controls );',
'} )()',
)
Expand Down
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions packages/data/src/plugins/async-generator/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* External dependencies
*/
import { applyMiddleware } from 'redux';
import { get } from 'lodash';

/**
* Internal dependencies
*/
import asyncGeneratorMiddleware, { toAsyncIterable } from './middleware';

export default function( registry ) {
return {
registerStore( reducerKey, options ) {
const store = registry.registerStore( reducerKey, options );
const enhancer = applyMiddleware( asyncGeneratorMiddleware );
const createStore = () => store;
Object.assign(
store,
enhancer( createStore )( options.reducer )
);
return store;
},

async __experimentalFulfill( reducerKey, selectorName, ...args ) {
const resolver = get( registry.namespaces, [ reducerKey, 'resolvers', selectorName ] );
if ( ! resolver ) {
return;
}
const store = registry.namespaces[ reducerKey ].store;
const state = store.getState();
const action = resolver.fulfill( state, ...args );
if ( action ) {
await store.dispatch( toAsyncIterable( action ) );
}
},
};
}
92 changes: 92 additions & 0 deletions packages/data/src/plugins/async-generator/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Returns true if the given argument appears to be a dispatchable action.
*
* @param {*} action Object to test.
*
* @return {boolean} Whether object is action-like.
*/
function isActionLike( action ) {
return (
!! action &&
typeof action.type === 'string'
);
}

/**
* Returns true if the given object is an async iterable, or false otherwise.
*
* @param {*} object Object to test.
*
* @return {boolean} Whether object is an async iterable.
*/
function isAsyncIterable( object ) {
return (
!! object &&
typeof object[ Symbol.asyncIterator ] === 'function'
);
}

/**
* Returns true if the given object is iterable, or false otherwise.
*
* @param {*} object Object to test.
*
* @return {boolean} Whether object is iterable.
*/
function isIterable( object ) {
return (
!! object &&
typeof object[ Symbol.iterator ] === 'function'
);
}

/**
* Normalizes the given object argument to an async iterable, asynchronously
* yielding on a singular or array of generator yields or promise resolution.
*
* @param {*} object Object to normalize.
*
* @return {AsyncGenerator} Async iterable actions.
*/
export function toAsyncIterable( object ) {
if ( isAsyncIterable( object ) ) {
return object;
}

return ( async function* () {
// Normalize as iterable...
if ( ! isIterable( object ) ) {
object = [ object ];
}

for ( const maybeAction of object ) {
yield maybeAction;
}
}() );
}

/**
* Simplest possible promise redux middleware.
*
* @param {Object} store Redux store.
*
* @return {function} middleware.
*/
const asyncGeneratorMiddleware = ( store ) => ( next ) => ( action ) => {
if ( ! isAsyncIterable( action ) ) {
return next( action );
}

const runtime = async ( fulfillment ) => {
for await ( const maybeAction of fulfillment ) {
// Dispatch if it quacks like an action.
if ( isActionLike( maybeAction ) ) {
store.dispatch( maybeAction );
}
}
};

return runtime( action );
};

export default asyncGeneratorMiddleware;
16 changes: 16 additions & 0 deletions packages/data/src/plugins/controls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { applyMiddleware } from 'redux';
import { get } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -22,9 +23,24 @@ export default function( registry ) {
store,
enhancer( createStore )( options.reducer )
);

registry.namespaces[ reducerKey ].supportControls = true;
}

return store;
},

async __experimentalFulfill( reducerKey, selectorName, ...args ) {
if ( ! registry.namespaces[ reducerKey ].supportControls ) {
registry.__experimentalFulfill( reducerKey, selectorName, ...args );
return;
}

const resolver = get( registry.namespaces, [ reducerKey, 'resolvers', selectorName ] );
if ( ! resolver ) {
return;
}
await registry.namespaces[ reducerKey ].store.dispatch( resolver.fulfill( ...args ) );
},
};
}
1 change: 1 addition & 0 deletions packages/data/src/plugins/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as controls } from './controls';
export { default as persistence } from './persistence';
export { default as asyncGenerator } from './async-generator';
14 changes: 14 additions & 0 deletions packages/data/src/promise-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Simplest possible promise redux middleware.
*
* @return {function} middleware.
*/
const promiseMiddleware = () => ( next ) => ( action ) => {
if ( action instanceof Promise ) {
return action.then( next );
}

return next( action );
};

export default promiseMiddleware;
Loading

0 comments on commit cbee072

Please sign in to comment.