UNPKG

@wordpress/data

Version:
352 lines (294 loc) 10.8 kB
/** * External dependencies */ import { mapValues, isObject, forEach } from 'lodash'; /** * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ import createReduxStore from './redux-store'; import coreDataStore from './store'; import { createEmitter } from './utils/emitter'; /** @typedef {import('./types').StoreDescriptor} StoreDescriptor */ /** * @typedef {Object} WPDataRegistry An isolated orchestrator of store registrations. * * @property {Function} registerGenericStore Given a namespace key and settings * object, registers a new generic * store. * @property {Function} registerStore Given a namespace key and settings * object, registers a new namespace * store. * @property {Function} subscribe Given a function callback, invokes * the callback on any change to state * within any registered store. * @property {Function} select Given a namespace key, returns an * object of the store's registered * selectors. * @property {Function} dispatch Given a namespace key, returns an * object of the store's registered * action dispatchers. */ /** * @typedef {Object} WPDataPlugin An object of registry function overrides. * * @property {Function} registerStore registers store. */ /** * Creates a new store registry, given an optional object of initial store * configurations. * * @param {Object} storeConfigs Initial store configurations. * @param {Object?} parent Parent registry. * * @return {WPDataRegistry} Data registry. */ export function createRegistry() { let storeConfigs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; let parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; const stores = {}; const emitter = createEmitter(); const listeningStores = new Set(); /** * Global listener called for each store's update. */ function globalListener() { emitter.emit(); } /** * Subscribe to changes to any data. * * @param {Function} listener Listener function. * * @return {Function} Unsubscribe function. */ const subscribe = listener => { return emitter.subscribe(listener); }; /** * Calls a selector given the current state and extra arguments. * * @param {string|StoreDescriptor} storeNameOrDescriptor Unique namespace identifier for the store * or the store descriptor. * * @return {*} The selector's returned value. */ function select(storeNameOrDescriptor) { const storeName = isObject(storeNameOrDescriptor) ? storeNameOrDescriptor.name : storeNameOrDescriptor; listeningStores.add(storeName); const store = stores[storeName]; if (store) { return store.getSelectors(); } return parent === null || parent === void 0 ? void 0 : parent.select(storeName); } function __unstableMarkListeningStores(callback, ref) { listeningStores.clear(); try { return callback.call(this); } finally { ref.current = Array.from(listeningStores); } } /** * Given the name of a registered store, returns an object containing the store's * selectors pre-bound to state so that you only need to supply additional arguments, * and modified so that they return promises that resolve to their eventual values, * after any resolvers have ran. * * @param {string|StoreDescriptor} storeNameOrDescriptor Unique namespace identifier for the store * or the store descriptor. * * @return {Object} Each key of the object matches the name of a selector. */ function resolveSelect(storeNameOrDescriptor) { const storeName = isObject(storeNameOrDescriptor) ? storeNameOrDescriptor.name : storeNameOrDescriptor; listeningStores.add(storeName); const store = stores[storeName]; if (store) { return store.getResolveSelectors(); } return parent && parent.resolveSelect(storeName); } /** * Given the name of a registered store, returns an object containing the store's * selectors pre-bound to state so that you only need to supply additional arguments, * and modified so that they throw promises in case the selector is not resolved yet. * * @param {string|StoreDescriptor} storeNameOrDescriptor Unique namespace identifier for the store * or the store descriptor. * * @return {Object} Object containing the store's suspense-wrapped selectors. */ function suspendSelect(storeNameOrDescriptor) { const storeName = isObject(storeNameOrDescriptor) ? storeNameOrDescriptor.name : storeNameOrDescriptor; listeningStores.add(storeName); const store = stores[storeName]; if (store) { return store.getSuspendSelectors(); } return parent && parent.suspendSelect(storeName); } /** * Returns the available actions for a part of the state. * * @param {string|StoreDescriptor} storeNameOrDescriptor Unique namespace identifier for the store * or the store descriptor. * * @return {*} The action's returned value. */ function dispatch(storeNameOrDescriptor) { const storeName = isObject(storeNameOrDescriptor) ? storeNameOrDescriptor.name : storeNameOrDescriptor; const store = stores[storeName]; if (store) { return store.getActions(); } return parent && parent.dispatch(storeName); } // // Deprecated // TODO: Remove this after `use()` is removed. function withPlugins(attributes) { return mapValues(attributes, (attribute, key) => { if (typeof attribute !== 'function') { return attribute; } return function () { return registry[key].apply(null, arguments); }; }); } /** * Registers a store instance. * * @param {string} name Store registry name. * @param {Object} store Store instance object (getSelectors, getActions, subscribe). */ function registerStoreInstance(name, store) { if (typeof store.getSelectors !== 'function') { throw new TypeError('store.getSelectors must be a function'); } if (typeof store.getActions !== 'function') { throw new TypeError('store.getActions must be a function'); } if (typeof store.subscribe !== 'function') { throw new TypeError('store.subscribe must be a function'); } // The emitter is used to keep track of active listeners when the registry // get paused, that way, when resumed we should be able to call all these // pending listeners. store.emitter = createEmitter(); const currentSubscribe = store.subscribe; store.subscribe = listener => { const unsubscribeFromEmitter = store.emitter.subscribe(listener); const unsubscribeFromStore = currentSubscribe(() => { if (store.emitter.isPaused) { store.emitter.emit(); return; } listener(); }); return () => { unsubscribeFromStore === null || unsubscribeFromStore === void 0 ? void 0 : unsubscribeFromStore(); unsubscribeFromEmitter === null || unsubscribeFromEmitter === void 0 ? void 0 : unsubscribeFromEmitter(); }; }; stores[name] = store; store.subscribe(globalListener); } /** * Registers a new store given a store descriptor. * * @param {StoreDescriptor} store Store descriptor. */ function register(store) { registerStoreInstance(store.name, store.instantiate(registry)); } function registerGenericStore(name, store) { deprecated('wp.data.registerGenericStore', { since: '5.9', alternative: 'wp.data.register( storeDescriptor )' }); registerStoreInstance(name, store); } /** * Registers a standard `@wordpress/data` store. * * @param {string} storeName Unique namespace identifier. * @param {Object} options Store description (reducer, actions, selectors, resolvers). * * @return {Object} Registered store object. */ function registerStore(storeName, options) { if (!options.reducer) { throw new TypeError('Must specify store reducer'); } const store = createReduxStore(storeName, options).instantiate(registry); registerStoreInstance(storeName, store); return store.store; } /** * Subscribe handler to a store. * * @param {string[]} storeName The store name. * @param {Function} handler The function subscribed to the store. * @return {Function} A function to unsubscribe the handler. */ function __unstableSubscribeStore(storeName, handler) { if (storeName in stores) { return stores[storeName].subscribe(handler); } // Trying to access a store that hasn't been registered, // this is a pattern rarely used but seen in some places. // We fallback to regular `subscribe` here for backward-compatibility for now. // See https://github.com/WordPress/gutenberg/pull/27466 for more info. if (!parent) { return subscribe(handler); } return parent.__unstableSubscribeStore(storeName, handler); } function batch(callback) { emitter.pause(); forEach(stores, store => store.emitter.pause()); callback(); emitter.resume(); forEach(stores, store => store.emitter.resume()); } let registry = { batch, stores, namespaces: stores, // TODO: Deprecate/remove this. subscribe, select, resolveSelect, suspendSelect, dispatch, use, register, registerGenericStore, registerStore, __unstableMarkListeningStores, __unstableSubscribeStore }; // // TODO: // This function will be deprecated as soon as it is no longer internally referenced. function use(plugin, options) { if (!plugin) { return; } registry = { ...registry, ...plugin(registry, options) }; return registry; } registry.register(coreDataStore); for (const [name, config] of Object.entries(storeConfigs)) { registry.register(createReduxStore(name, config)); } if (parent) { parent.subscribe(globalListener); } return withPlugins(registry); } //# sourceMappingURL=registry.js.map