-
Notifications
You must be signed in to change notification settings - Fork 4.7k
wp.data: Split store implementation out from registry. #10289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
aduth
merged 14 commits into
WordPress:master
from
coderkevin:update/data-registry-split-implementation
Nov 2, 2018
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
fc906f1
data: Start split of registry with reducer
coderkevin 5e88f30
data: Move registerSelectors and registerActions
coderkevin 534e09a
data: Untangle registerResolvers
coderkevin df3e25a
data: Centralize namespace registration
coderkevin 1063e3d
data: Add registerGenericStore
coderkevin 017ae80
data: Use store.subscribe() function
coderkevin 02652a1
data: Make registerGenericStore public + add tests
coderkevin e2683eb
data: Add registerGenericStore to readme
coderkevin 71c00ae
data: Export registerGenericStore from index
coderkevin 2b4235b
data: Fixes to readme
coderkevin 92803e3
data: Deprecate registerX and use functions
coderkevin 407d503
data: docs/changelog updates for deprecations
coderkevin f95b7bc
data: un-deprecate `registry.use`
coderkevin d0c6dba
data: Add deprected as script dependency to data
coderkevin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import { createStore, applyMiddleware } from 'redux'; | ||
| import { | ||
| flowRight, | ||
| get, | ||
| mapValues, | ||
| } from 'lodash'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import promise from './promise-middleware'; | ||
| import createResolversCacheMiddleware from './resolvers-cache-middleware'; | ||
|
|
||
| /** | ||
| * Creates a namespace object with a store derived from the reducer given. | ||
| * | ||
| * @param {string} key Identifying string used for namespace and redex dev tools. | ||
| * @param {Object} options Contains reducer, actions, selectors, and resolvers. | ||
| * @param {Object} registry Temporary registry reference, required for namespace updates. | ||
| * | ||
| * @return {Object} Store Object. | ||
| */ | ||
| export default function createNamespace( key, options, registry ) { | ||
| // TODO: After register[Reducer|Actions|Selectors|Resolvers] are deprecated and removed, | ||
| // this function can be greatly simplified because it should no longer be called to modify | ||
| // a namespace, but only to create one, and only once for each namespace. | ||
|
|
||
| // TODO: After removing `registry.namespaces`and making stores immutable after create, | ||
| // reducer, store, actinos, selectors, and resolvers can all be removed from here. | ||
| let { | ||
| reducer, | ||
| store, | ||
| actions, | ||
| selectors, | ||
| resolvers, | ||
| } = registry.namespaces[ key ] || {}; | ||
|
|
||
| if ( options.reducer ) { | ||
| reducer = options.reducer; | ||
| store = createReduxStore( reducer, key, registry ); | ||
| } | ||
| if ( options.actions ) { | ||
| if ( ! store ) { | ||
| throw new TypeError( 'Cannot specify actions when no reducer is present' ); | ||
| } | ||
| actions = mapActions( options.actions, store ); | ||
| } | ||
| if ( options.selectors ) { | ||
| if ( ! store ) { | ||
| throw new TypeError( 'Cannot specify selectors when no reducer is present' ); | ||
| } | ||
| selectors = mapSelectors( options.selectors, store ); | ||
| } | ||
| if ( options.resolvers ) { | ||
| const fulfillment = getCoreDataFulfillment( registry, key ); | ||
| const result = mapResolvers( options.resolvers, selectors, fulfillment, store ); | ||
| resolvers = result.resolvers; | ||
| selectors = result.selectors; | ||
| } | ||
|
|
||
| const getSelectors = () => selectors; | ||
| const getActions = () => actions; | ||
|
|
||
| // Customize subscribe behavior to call listeners only on effective change, | ||
| // not on every dispatch. | ||
| const subscribe = store && function( listener ) { | ||
| let lastState = store.getState(); | ||
| store.subscribe( () => { | ||
| const state = store.getState(); | ||
| const hasChanged = state !== lastState; | ||
| lastState = state; | ||
|
|
||
| if ( hasChanged ) { | ||
| listener(); | ||
| } | ||
| } ); | ||
| }; | ||
|
|
||
| return { | ||
| reducer, | ||
| store, | ||
| actions, | ||
| selectors, | ||
| resolvers, | ||
| getSelectors, | ||
| getActions, | ||
| subscribe, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a redux store for a namespace. | ||
| * | ||
| * @param {Function} reducer Root reducer for redux store. | ||
| * @param {string} key Part of the state shape to register the | ||
| * selectors for. | ||
| * @param {Object} registry Registry reference, for resolver enhancer support. | ||
| * @return {Object} Newly created redux store. | ||
| */ | ||
| function createReduxStore( reducer, key, registry ) { | ||
| const enhancers = [ | ||
| applyMiddleware( createResolversCacheMiddleware( registry, key ), promise ), | ||
| ]; | ||
| if ( typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__ ) { | ||
| enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__( { name: key, instanceId: key } ) ); | ||
| } | ||
|
|
||
| return createStore( reducer, flowRight( enhancers ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Maps selectors to a redux store. | ||
| * | ||
| * @param {Object} selectors Selectors to register. Keys will be used as the | ||
| * public facing API. Selectors will get passed the | ||
| * state as first argument. | ||
| * @param {Object} store The redux store to which the selectors should be mapped. | ||
| * @return {Object} Selectors mapped to the redux store provided. | ||
| */ | ||
| function mapSelectors( selectors, store ) { | ||
| const createStateSelector = ( selector ) => ( ...args ) => selector( store.getState(), ...args ); | ||
| return mapValues( selectors, createStateSelector ); | ||
| } | ||
|
|
||
| /** | ||
| * Maps actions to dispatch from a given store. | ||
| * | ||
| * @param {Object} actions Actions to register. | ||
| * @param {Object} store The redux store to which the actions should be mapped. | ||
| * @return {Object} Actions mapped to the redux store provided. | ||
| */ | ||
| function mapActions( actions, store ) { | ||
| const createBoundAction = ( action ) => ( ...args ) => store.dispatch( action( ...args ) ); | ||
| return mapValues( actions, createBoundAction ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns resolvers with matched selectors for a given namespace. | ||
| * Resolvers are side effects invoked once per argument set of a given selector call, | ||
| * used in ensuring that the data needs for the selector are satisfied. | ||
| * | ||
| * @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 ) { | ||
| const mapSelector = ( selector, selectorName ) => { | ||
| const resolver = resolvers[ selectorName ]; | ||
| if ( ! resolver ) { | ||
| return selector; | ||
| } | ||
|
|
||
| return ( ...args ) => { | ||
| async function fulfillSelector() { | ||
| const state = store.getState(); | ||
| if ( typeof resolver.isFulfilled === 'function' && resolver.isFulfilled( state, ...args ) ) { | ||
| return; | ||
| } | ||
|
|
||
| if ( fulfillment.hasStarted( selectorName, args ) ) { | ||
| return; | ||
| } | ||
|
|
||
| fulfillment.start( selectorName, args ); | ||
| await fulfillment.fulfill( selectorName, ...args ); | ||
| fulfillment.finish( 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 {string} selectorName Selector name to fulfill. | ||
| * @param {Array} args Selector Arguments. | ||
| */ | ||
| async function fulfillWithRegistry( registry, key, selectorName, ...args ) { | ||
| const namespace = registry.stores[ key ]; | ||
| const resolver = get( namespace, [ 'resolvers', selectorName ] ); | ||
| if ( ! resolver ) { | ||
| return; | ||
| } | ||
|
|
||
| const action = resolver.fulfill( ...args ); | ||
| if ( action ) { | ||
| await namespace.store.dispatch( action ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.