UNPKG

@wordpress/data

Version:
458 lines (457 loc) 13.7 kB
// packages/data/src/redux-store/index.js import { createStore, applyMiddleware } from "redux"; import EquivalentKeyMap from "equivalent-key-map"; import createReduxRoutineMiddleware from "@wordpress/redux-routine"; import { compose } from "@wordpress/compose"; import { combineReducers } from "./combine-reducers"; import { builtinControls } from "../controls"; import { lock } from "../lock-unlock"; import promise from "../promise-middleware"; import createResolversCacheMiddleware from "../resolvers-cache-middleware"; import createThunkMiddleware from "./thunk-middleware"; import metadataReducer from "./metadata/reducer"; import * as metadataSelectors from "./metadata/selectors"; import * as metadataActions from "./metadata/actions"; var trimUndefinedValues = (array) => { const result = [...array]; for (let i = result.length - 1; i >= 0; i--) { if (result[i] === void 0) { result.splice(i, 1); } } return result; }; var mapValues = (obj, callback) => Object.fromEntries( Object.entries(obj ?? {}).map(([key, value]) => [ key, callback(value, key) ]) ); var devToolsReplacer = (key, state) => { if (state instanceof Map) { return Object.fromEntries(state); } if (state instanceof window.HTMLElement) { return null; } return state; }; function createResolversCache() { const cache = {}; return { isRunning(selectorName, args) { return cache[selectorName] && cache[selectorName].get(trimUndefinedValues(args)); }, clear(selectorName, args) { if (cache[selectorName]) { cache[selectorName].delete(trimUndefinedValues(args)); } }, markAsRunning(selectorName, args) { if (!cache[selectorName]) { cache[selectorName] = new EquivalentKeyMap(); } cache[selectorName].set(trimUndefinedValues(args), true); } }; } function createBindingCache(getItem, bindItem) { const cache = /* @__PURE__ */ new WeakMap(); return { get(itemName) { const item = getItem(itemName); if (!item) { return null; } let boundItem = cache.get(item); if (!boundItem) { boundItem = bindItem(item, itemName); cache.set(item, boundItem); } return boundItem; } }; } function createPrivateProxy(publicItems, privateItems) { return new Proxy(publicItems, { get: (target, itemName) => privateItems.get(itemName) || Reflect.get(target, itemName) }); } 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) => { const listeners = /* @__PURE__ */ new Set(); const reducer = options.reducer; const thunkArgs = { registry, get dispatch() { return thunkDispatch; }, get select() { return thunkSelect; }, get resolveSelect() { return resolveSelectors; } }; const store = instantiateReduxStore( key, options, registry, thunkArgs ); lock(store, privateRegistrationFunctions); const resolversCache = createResolversCache(); function bindAction(action) { return (...args) => Promise.resolve(store.dispatch(action(...args))); } const actions = { ...mapValues(metadataActions, bindAction), ...mapValues(options.actions, bindAction) }; const allActions = createPrivateProxy( actions, createBindingCache( (name) => privateActions[name], bindAction ) ); const thunkDispatch = new Proxy( (action) => store.dispatch(action), { get: (target, name) => allActions[name] } ); lock(actions, allActions); const resolvers = options.resolvers ? mapValues(options.resolvers, mapResolver) : {}; function bindSelector(selector, selectorName) { if (selector.isRegistrySelector) { selector.registry = registry; } const boundSelector = (...args) => { args = normalize(selector, args); const state = store.__unstableOriginalGetState(); if (selector.isRegistrySelector) { selector.registry = registry; } return selector(state.root, ...args); }; boundSelector.__unstableNormalizeArgs = selector.__unstableNormalizeArgs; const resolver = resolvers[selectorName]; if (!resolver) { boundSelector.hasResolver = false; return boundSelector; } return mapSelectorWithResolver( boundSelector, selectorName, resolver, store, resolversCache, boundMetadataSelectors ); } function bindMetadataSelector(metaDataSelector) { const boundSelector = (selectorName, selectorArgs, ...args) => { if (selectorName) { const targetSelector = options.selectors?.[selectorName]; if (targetSelector) { selectorArgs = normalize( targetSelector, selectorArgs ); } } const state = store.__unstableOriginalGetState(); return metaDataSelector( state.metadata, selectorName, selectorArgs, ...args ); }; boundSelector.hasResolver = false; return boundSelector; } const boundMetadataSelectors = mapValues( metadataSelectors, bindMetadataSelector ); const boundSelectors = mapValues(options.selectors, bindSelector); const selectors = { ...boundMetadataSelectors, ...boundSelectors }; const boundPrivateSelectors = createBindingCache( (name) => privateSelectors[name], bindSelector ); const allSelectors = createPrivateProxy( selectors, boundPrivateSelectors ); for (const selectorName of Object.keys(privateSelectors)) { boundPrivateSelectors.get(selectorName); } const thunkSelect = new Proxy( (selector) => selector(store.__unstableOriginalGetState()), { get: (target, name) => allSelectors[name] } ); lock(selectors, allSelectors); const bindResolveSelector = mapResolveSelector( store, boundMetadataSelectors ); const resolveSelectors = mapValues( boundSelectors, bindResolveSelector ); const allResolveSelectors = createPrivateProxy( resolveSelectors, createBindingCache( (name) => boundPrivateSelectors.get(name), bindResolveSelector ) ); lock(resolveSelectors, allResolveSelectors); const bindSuspendSelector = mapSuspendSelector( store, boundMetadataSelectors ); const suspendSelectors = { ...boundMetadataSelectors, // no special suspense behavior ...mapValues(boundSelectors, bindSuspendSelector) }; const allSuspendSelectors = createPrivateProxy( suspendSelectors, createBindingCache( (name) => boundPrivateSelectors.get(name), bindSuspendSelector ) ); lock(suspendSelectors, allSuspendSelectors); const getSelectors = () => selectors; const getActions = () => actions; const getResolveSelectors = () => resolveSelectors; const getSuspendSelectors = () => suspendSelectors; store.__unstableOriginalGetState = store.getState; store.getState = () => store.__unstableOriginalGetState().root; const subscribe = store && ((listener) => { listeners.add(listener); return () => listeners.delete(listener); }); let lastState = store.__unstableOriginalGetState(); store.subscribe(() => { const state = store.__unstableOriginalGetState(); const hasChanged = state !== lastState; lastState = state; if (hasChanged) { for (const listener of listeners) { listener(); } } }); return { reducer, store, actions, selectors, resolvers, getSelectors, getResolveSelectors, getSuspendSelectors, getActions, subscribe }; } }; lock(storeDescriptor, privateRegistrationFunctions); return storeDescriptor; } function instantiateReduxStore(key, options, registry, thunkArgs) { const controls = { ...options.controls, ...builtinControls }; const normalizedControls = mapValues( controls, (control) => control.isRegistryControl ? control(registry) : control ); const middlewares = [ createResolversCacheMiddleware(registry, key), promise, createReduxRoutineMiddleware(normalizedControls), createThunkMiddleware(thunkArgs) ]; const enhancers = [applyMiddleware(...middlewares)]; if (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION__) { enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__({ name: key, instanceId: key, serialize: { replacer: devToolsReplacer } }) ); } const { reducer, initialState } = options; const enhancedReducer = combineReducers({ metadata: metadataReducer, root: reducer }); return createStore( enhancedReducer, { root: initialState }, compose(enhancers) ); } function mapResolveSelector(store, boundMetadataSelectors) { return (selector, selectorName) => { if (!selector.hasResolver) { return async (...args) => selector.apply(null, args); } return (...args) => new Promise((resolve, reject) => { const hasFinished = () => { return boundMetadataSelectors.hasFinishedResolution( selectorName, args ); }; const finalize = (result2) => { const hasFailed = boundMetadataSelectors.hasResolutionFailed( selectorName, args ); if (hasFailed) { const error = boundMetadataSelectors.getResolutionError( selectorName, args ); reject(error); } else { resolve(result2); } }; const getResult = () => selector.apply(null, args); const result = getResult(); if (hasFinished()) { return finalize(result); } const unsubscribe = store.subscribe(() => { if (hasFinished()) { unsubscribe(); finalize(getResult()); } }); }); }; } function mapSuspendSelector(store, boundMetadataSelectors) { return (selector, selectorName) => { if (!selector.hasResolver) { return selector; } return (...args) => { const result = selector.apply(null, args); if (boundMetadataSelectors.hasFinishedResolution( selectorName, args )) { if (boundMetadataSelectors.hasResolutionFailed( selectorName, args )) { throw boundMetadataSelectors.getResolutionError( selectorName, args ); } return result; } throw new Promise((resolve) => { const unsubscribe = store.subscribe(() => { if (boundMetadataSelectors.hasFinishedResolution( selectorName, args )) { resolve(); unsubscribe(); } }); }); }; }; } function mapResolver(resolver) { if (resolver.fulfill) { return resolver; } return { ...resolver, // Copy the enumerable properties of the resolver function. fulfill: resolver // Add the fulfill method. }; } function mapSelectorWithResolver(selector, selectorName, resolver, store, resolversCache, boundMetadataSelectors) { function fulfillSelector(args) { const state = store.getState(); if (resolversCache.isRunning(selectorName, args) || typeof resolver.isFulfilled === "function" && resolver.isFulfilled(state, ...args)) { return; } if (boundMetadataSelectors.hasStartedResolution(selectorName, args)) { return; } resolversCache.markAsRunning(selectorName, args); setTimeout(async () => { resolversCache.clear(selectorName, args); store.dispatch( metadataActions.startResolution(selectorName, args) ); try { const action = resolver.fulfill(...args); if (action) { await store.dispatch(action); } store.dispatch( metadataActions.finishResolution(selectorName, args) ); } catch (error) { store.dispatch( metadataActions.failResolution(selectorName, args, error) ); } }, 0); } const selectorResolver = (...args) => { args = normalize(selector, args); fulfillSelector(args); return selector(...args); }; selectorResolver.hasResolver = true; return selectorResolver; } function normalize(selector, args) { if (selector.__unstableNormalizeArgs && typeof selector.__unstableNormalizeArgs === "function" && args?.length) { return selector.__unstableNormalizeArgs(args); } return args; } export { combineReducers, createReduxStore as default }; //# sourceMappingURL=index.js.map