@woocommerce/data
Version:
WooCommerce Admin data store and utilities
239 lines (238 loc) • 7.4 kB
JavaScript
/**
* External dependencies
*/
import { addQueryArgs } from '@wordpress/url';
/**
* Internal dependencies
*/
import CRUD_ACTIONS from './crud-actions';
import { getResourceName } from '../utils';
/**
* Get a REST path given a template path and URL params.
*
* @param templatePath Path with variable names.
* @param query Item query.
* @param parameters Array of items to replace in the templatePath.
* @return string REST path.
*/
export const getRestPath = (templatePath, query, parameters) => {
var _a;
let path = templatePath;
(_a = path.match(/{(.*?)}/g)) === null || _a === void 0 ? void 0 : _a.forEach((str, i) => {
path = path.replace(str, parameters[i].toString());
});
const regex = new RegExp(/{|}/);
if (regex.test(path.toString())) {
throw new Error('Not all URL parameters were replaced');
}
return addQueryArgs(path, query);
};
/**
* Get a key from an item ID and optional parent.
*
* @param query Item Query.
* @param urlParameters Parameters used for URL.
* @return string
*/
export const getKey = (query, urlParameters = []) => {
const id = typeof query === 'string' || typeof query === 'number'
? query
: query.id;
if (!urlParameters.length) {
return id;
}
return urlParameters.join('/') + '/' + id;
};
/**
* This function takes an array of items and reduces it into a single object,
* where each key is a unique identifier generated by
* combining the item ID and optional URL parameters.
* It also returns an array of these keys (`ids`).
*
* @param {Array<Item>} items - The items to process.
* @param {Array<IdType>} urlParameters - The URL parameters used to generate keys.
* @param {Record<string, Item>} currentState - The current state data to merge with.
* @return {organizeItemsByIdReturn} An object with two properties: `objItems` and `ids`.
*/
export const organizeItemsById = (items, urlParameters = [], currentState = {}) => {
const ids = [];
const objItems = {};
const hasUrlParams = urlParameters.length > 0;
items.forEach((item) => {
const key = hasUrlParams ? getKey(item.id, urlParameters) : item.id;
ids.push(key);
objItems[key] = {
...(currentState[key] || {}),
...item,
};
});
return { objItems, ids };
};
/**
* Filters the input data object, returning a new object that contains only the keys
* specified in the keys array.
*
* @param {Record<string, unknown>} data - The original data object to filter.
* @param {IdType[]} keys - An array of keys that should be included in the returned object.
* @return {Record<string, unknown>} A new object containing only the specified keys.
*/
export function filterDataByKeys(data, keys) {
return keys.reduce((acc, key) => {
if (data[key]) {
acc[key] = data[key];
}
return acc;
}, {});
}
/**
* Parse an ID query into a ID string.
*
* @param query Id Query
* @return string ID.
*/
export const parseId = (query, urlParameters = []) => {
if (typeof query === 'string' || typeof query === 'number') {
return {
id: query,
key: query,
};
}
return {
id: query.id,
key: getKey(query, urlParameters),
};
};
/**
* Create a new function that adds in the namespace.
*
* @param fn Function to wrap.
* @param namespace Namespace to pass to last argument of function.
* @return Wrapped function
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const applyNamespace = (fn, namespace,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultArgs = []) => {
return (...args) => {
defaultArgs.forEach((defaultArg, index) => {
// skip first item, as that is the state.
if (args[index + 1] === undefined) {
args[index + 1] = defaultArg;
}
});
return fn(...args, namespace);
};
};
/**
* Get the key names from a namespace string.
*
* @param namespace Namespace to get keys from.
* @return Array of keys.
*/
export const getNamespaceKeys = (namespace) => {
var _a;
const keys = [];
(_a = namespace.match(/{(.*?)}/g)) === null || _a === void 0 ? void 0 : _a.forEach((match) => {
const key = match.substr(1, match.length - 2);
keys.push(key);
});
return keys;
};
/**
* Get URL parameters from namespace and provided query.
*
* @param namespace Namespace string to replace params in.
* @param query Query object with key values.
* @return Array of URL parameter values.
*/
export const getUrlParameters = (namespace, query) => {
if (typeof query !== 'object') {
return [];
}
const params = [];
const keys = getNamespaceKeys(namespace);
keys.forEach((key) => {
if (query.hasOwnProperty(key)) {
params.push(query[key]);
}
});
return params;
};
/**
* Check to see if an argument is a valid type of ID query.
*
* @param arg Unknow argument to check.
* @param namespace The namespace string
* @return boolean
*/
export const isValidIdQuery = (arg, namespace) => {
if (typeof arg === 'string' || typeof arg === 'number') {
return true;
}
const validKeys = ['id', ...getNamespaceKeys(namespace)];
if (arg &&
typeof arg === 'object' &&
arg.hasOwnProperty('id') &&
JSON.stringify(validKeys.sort()) ===
JSON.stringify(Object.keys(arg).sort())) {
return true;
}
return false;
};
/**
* Replace the initial argument with a key if it's a valid ID query.
*
* @param args Args to check.
* @param namespace Namespace.
* @return Sanitized arguments.
*/
export const maybeReplaceIdQuery = (args, namespace) => {
const [firstArgument, ...rest] = args;
if (!firstArgument || !isValidIdQuery(firstArgument, namespace)) {
return args;
}
const urlParameters = getUrlParameters(namespace, firstArgument);
const { key } = parseId(firstArgument, urlParameters);
return [key, ...rest];
};
/**
* Clean a query of all namespaced params.
*
* @param query Query to clean.
* @param namespace
* @return Cleaned query object.
*/
export const cleanQuery = (query, namespace) => {
const cleaned = { ...query };
const keys = getNamespaceKeys(namespace);
keys.forEach((key) => {
delete cleaned[key];
});
return cleaned;
};
/**
* Get the identifier for a request provided its arguments.
*
* @param name Name of action or selector.
* @param args Arguments for the request.
* @return Key to identify the request.
*/
export const getRequestIdentifier = getResourceName;
/**
* Get a generic action name from a resource action name if one exists.
*
* @param action Action name to check.
* @param resourceName Resurce name.
* @return Generic action name if one exists, otherwise the passed action name.
*/
export const getGenericActionName = (action, resourceName) => {
switch (action) {
case `create${resourceName}`:
return CRUD_ACTIONS.CREATE_ITEM;
case `delete${resourceName}`:
return CRUD_ACTIONS.DELETE_ITEM;
case `update${resourceName}`:
return CRUD_ACTIONS.UPDATE_ITEM;
}
return action;
};