diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 1ff1faed5695b2..cdbc5ec59468a9 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -111,6 +111,16 @@ function createResolversCache() { export default function createReduxStore( key, options ) { const privateActions = {}; const privateSelectors = {}; + const privateRegistrationFunctions = { + privateActions, + registerPrivateActions: ( actions ) => { + Object.assign( privateActions, actions ); + }, + privateSelectors, + registerPrivateSelectors: ( selectors ) => { + Object.assign( privateSelectors, selectors ); + }, + }; const storeDescriptor = { name: key, instantiate: ( registry ) => { @@ -141,6 +151,9 @@ export default function createReduxStore( key, options ) { registry, thunkArgs ); + // Expose the private registration functions on the store + // so they can be copied to a sub registry in registry.js. + lock( store, privateRegistrationFunctions ); const resolversCache = createResolversCache(); let resolvers; @@ -260,14 +273,10 @@ export default function createReduxStore( key, options ) { }, }; - lock( storeDescriptor, { - registerPrivateActions: ( actions ) => { - Object.assign( privateActions, actions ); - }, - registerPrivateSelectors: ( selectors ) => { - Object.assign( privateSelectors, selectors ); - }, - } ); + // Expose the private registration functions on the store + // descriptor. That's a natural choice since that's where the + // public actions and selectors are stored . + lock( storeDescriptor, privateRegistrationFunctions ); return storeDescriptor; } diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index df46227257f363..7a5668505b7127 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -14,6 +14,7 @@ import deprecated from '@wordpress/deprecated'; import createReduxStore from './redux-store'; import coreDataStore from './store'; import { createEmitter } from './utils/emitter'; +import { lock, unlock } from './experiments'; /** @typedef {import('./types').StoreDescriptor} StoreDescriptor */ @@ -245,6 +246,22 @@ export function createRegistry( storeConfigs = {}, parent = null ) { }; stores[ name ] = store; store.subscribe( globalListener ); + + // Copy private actions and selectors from the parent store. + if ( parent ) { + try { + unlock( store.store ).registerPrivateActions( + unlock( parent ).privateActionsOf( name ) + ); + unlock( store.store ).registerPrivateSelectors( + unlock( parent ).privateSelectorsOf( name ) + ); + } catch ( e ) { + // unlock() throws if store.store was not locked. + // The error indicates there's nothing to do here so let's + // ignore it. + } + } } /** @@ -334,5 +351,24 @@ export function createRegistry( storeConfigs = {}, parent = null ) { parent.subscribe( globalListener ); } - return withPlugins( registry ); + const registryWithPlugins = withPlugins( registry ); + lock( registryWithPlugins, { + privateActionsOf: ( name ) => { + try { + return unlock( stores[ name ].store ).privateActions; + } catch ( e ) { + // unlock() throws an error the store was not locked – this means + // there no private actions are available + return {}; + } + }, + privateSelectorsOf: ( name ) => { + try { + return unlock( stores[ name ].store ).privateSelectors; + } catch ( e ) { + return {}; + } + }, + } ); + return registryWithPlugins; } diff --git a/packages/data/src/test/privateAPIs.js b/packages/data/src/test/privateAPIs.js index 00b9e8d866c213..ea6eb53eda3c87 100644 --- a/packages/data/src/test/privateAPIs.js +++ b/packages/data/src/test/privateAPIs.js @@ -38,32 +38,34 @@ describe( 'Private data APIs', () => { function setPublicPrice( price ) { return { type: 'SET_PUBLIC_PRICE', price }; } - function createStore() { - const groceryStore = createReduxStore( 'grocer', { - selectors: { - getPublicPrice, - getState: ( state ) => state, - }, - actions: { setPublicPrice }, - reducer: ( state, action ) => { - if ( action?.type === 'SET_PRIVATE_PRICE' ) { - return { - ...state, - secretDiscount: action?.price, - }; - } else if ( action?.type === 'SET_PUBLIC_PRICE' ) { - return { - ...state, - price: action?.price, - }; - } + const storeName = 'grocer'; + const storeDescriptor = { + selectors: { + getPublicPrice, + getState: ( state ) => state, + }, + actions: { setPublicPrice }, + reducer: ( state, action ) => { + if ( action?.type === 'SET_PRIVATE_PRICE' ) { return { - price: 1000, - secretDiscount: 800, - ...( state || {} ), + ...state, + secretDiscount: action?.price, }; - }, - } ); + } else if ( action?.type === 'SET_PUBLIC_PRICE' ) { + return { + ...state, + price: action?.price, + }; + } + return { + price: 1000, + secretDiscount: 800, + ...( state || {} ), + }; + }, + }; + function createStore() { + const groceryStore = createReduxStore( storeName, storeDescriptor ); registry.register( groceryStore ); return groceryStore; } @@ -126,6 +128,28 @@ describe( 'Private data APIs', () => { const unlockedSelectors = unlock( registry.select( groceryStore ) ); expect( unlockedSelectors.getPublicPrice() ).toEqual( 1000 ); } ); + + it( 'should support sub registries', () => { + const groceryStore = registry.registerStore( + storeName, + storeDescriptor + ); + unlock( groceryStore ).registerPrivateSelectors( { + getSecretDiscount, + } ); + const subRegistry = createRegistry( {}, registry ); + subRegistry.registerStore( storeName, storeDescriptor ); + + const parentPrivateSelectors = unlock( + registry.select( storeName ) + ); + expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 800 ); + + const subPrivateSelectors = unlock( + subRegistry.select( storeName ) + ); + expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 800 ); + } ); } ); describe( 'private actions', () => { @@ -224,5 +248,37 @@ describe( 'Private data APIs', () => { unlock( registry.select( groceryStore ) ).getSecretDiscount() ).toEqual( 100 ); } ); + + it( 'should support sub registries', () => { + const groceryStore = createStore(); + unlock( groceryStore ).registerPrivateSelectors( { + getSecretDiscount, + } ); + unlock( groceryStore ).registerPrivateActions( { + setSecretDiscount, + } ); + const subRegistry = createRegistry( {}, registry ); + subRegistry.registerStore( storeName, storeDescriptor ); + + const parentPrivateActions = unlock( + registry.dispatch( storeName ) + ); + const parentPrivateSelectors = unlock( + registry.select( storeName ) + ); + + const subPrivateActions = unlock( + subRegistry.dispatch( storeName ) + ); + const subPrivateSelectors = unlock( + subRegistry.select( storeName ) + ); + + parentPrivateActions.setSecretDiscount( 400 ); + subPrivateActions.setSecretDiscount( 478 ); + + expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 400 ); + expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 478 ); + } ); } ); } );