@wordpress/data
Version:
Data module for WordPress.
458 lines (457 loc) • 13.7 kB
JavaScript
// 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