@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
880 lines (844 loc) • 31.8 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getUserPatternCategories = exports.getThemeSupports = exports.getTemplateAutoDraftId = exports.getRevisions = exports.getRevision = exports.getRegisteredPostMeta = exports.getRawEntityRecord = exports.getNavigationFallbackId = exports.getEntityRecordsTotalPages = exports.getEntityRecordsTotalItems = exports.getEntityRecords = exports.getEntityRecord = exports.getEntitiesConfig = exports.getEmbedPreview = exports.getEditedEntityRecord = exports.getDefaultTemplateId = exports.getCurrentUser = exports.getCurrentThemeGlobalStylesRevisions = exports.getCurrentTheme = exports.getBlockPatterns = exports.getBlockPatternCategories = exports.getAutosaves = exports.getAutosave = exports.getAuthors = exports.canUserEditEntityRecord = exports.canUser = exports.__experimentalGetCurrentThemeGlobalStylesVariations = exports.__experimentalGetCurrentThemeBaseGlobalStyles = exports.__experimentalGetCurrentGlobalStylesId = void 0;
var _changeCase = require("change-case");
var _url = require("@wordpress/url");
var _htmlEntities = require("@wordpress/html-entities");
var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch"));
var _name = require("./name");
var _entities = require("./entities");
var _utils = require("./utils");
var _sync = require("./sync");
var _fetch = require("./fetch");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
/**
* Requests authors from the REST API.
*
* @param {Object|undefined} query Optional object of query parameters to
* include with request.
*/
const getAuthors = query => async ({
dispatch
}) => {
const path = (0, _url.addQueryArgs)('/wp/v2/users/?who=authors&per_page=100', query);
const users = await (0, _apiFetch.default)({
path
});
dispatch.receiveUserQuery(path, users);
};
/**
* Requests the current user from the REST API.
*/
exports.getAuthors = getAuthors;
const getCurrentUser = () => async ({
dispatch
}) => {
const currentUser = await (0, _apiFetch.default)({
path: '/wp/v2/users/me'
});
dispatch.receiveCurrentUser(currentUser);
};
/**
* Requests an entity's record from the REST API.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number|string} key Record's key
* @param {Object|undefined} query Optional object of query parameters to
* include with request. If requesting specific
* fields, fields must always include the ID.
*/
exports.getCurrentUser = getCurrentUser;
const getEntityRecord = (kind, name, key = '', query) => async ({
select,
dispatch,
registry,
resolveSelect
}) => {
// For back-compat, we allow querying for static templates through
// wp_template.
if (kind === 'postType' && name === 'wp_template' && typeof key === 'string' &&
// __experimentalGetDirtyEntityRecords always calls getEntityRecord
// with a string key, so we need that it's not a numeric ID.
!/^\d+$/.test(key)) {
name = 'wp_registered_template';
}
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(config => config.name === name && config.kind === kind);
if (!entityConfig) {
return;
}
const lock = await dispatch.__unstableAcquireStoreLock(_name.STORE_NAME, ['entities', 'records', kind, name, key], {
exclusive: false
});
try {
// Entity supports configs,
// use the sync algorithm instead of the old fetch behavior.
if (window.__experimentalEnableSync && entityConfig.syncConfig && !query) {
if (globalThis.IS_GUTENBERG_PLUGIN) {
const objectId = entityConfig.getSyncObjectId(key);
// Loads the persisted document.
await (0, _sync.getSyncProvider)().bootstrap(entityConfig.syncObjectType, objectId, record => {
dispatch.receiveEntityRecords(kind, name, record, query);
});
// Bootstraps the edited document as well (and load from peers).
await (0, _sync.getSyncProvider)().bootstrap(entityConfig.syncObjectType + '--edit', objectId, record => {
dispatch({
type: 'EDIT_ENTITY_RECORD',
kind,
name,
recordId: key,
edits: record,
meta: {
undo: undefined
}
});
});
}
} else {
if (query !== undefined && query._fields) {
// If requesting specific fields, items and query association to said
// records are stored by ID reference. Thus, fields must always include
// the ID.
query = {
...query,
_fields: [...new Set([...((0, _utils.getNormalizedCommaSeparable)(query._fields) || []), entityConfig.key || _entities.DEFAULT_ENTITY_KEY])].join()
};
}
if (query !== undefined && query._fields) {
// The resolution cache won't consider query as reusable based on the
// fields, so it's tested here, prior to initiating the REST request,
// and without causing `getEntityRecord` resolution to occur.
const hasRecord = select.hasEntityRecord(kind, name, key, query);
if (hasRecord) {
return;
}
}
const path = (0, _url.addQueryArgs)(entityConfig.baseURL + (key ? '/' + key : ''), {
...entityConfig.baseURLParams,
...query
});
const response = await (0, _apiFetch.default)({
path,
parse: false
});
const record = await response.json();
const permissions = (0, _utils.getUserPermissionsFromAllowHeader)(response.headers?.get('allow'));
const canUserResolutionsArgs = [];
const receiveUserPermissionArgs = {};
for (const action of _utils.ALLOWED_RESOURCE_ACTIONS) {
receiveUserPermissionArgs[(0, _utils.getUserPermissionCacheKey)(action, {
kind,
name,
id: key
})] = permissions[action];
canUserResolutionsArgs.push([action, {
kind,
name,
id: key
}]);
}
registry.batch(() => {
dispatch.receiveEntityRecords(kind, name, record, query);
dispatch.receiveUserPermissions(receiveUserPermissionArgs);
dispatch.finishResolutions('canUser', canUserResolutionsArgs);
});
}
} finally {
dispatch.__unstableReleaseStoreLock(lock);
}
};
exports.getEntityRecord = getEntityRecord;
const getTemplateAutoDraftId = staticTemplateId => async ({
resolveSelect,
dispatch
}) => {
const record = await resolveSelect.getEntityRecord('postType', 'wp_registered_template', staticTemplateId);
const autoDraft = await dispatch.saveEntityRecord('postType', 'wp_template', {
...record,
id: undefined,
type: 'wp_template',
status: 'auto-draft'
});
await dispatch.receiveTemplateAutoDraftId(staticTemplateId, autoDraft.id);
};
/**
* Requests an entity's record from the REST API.
*/
exports.getTemplateAutoDraftId = getTemplateAutoDraftId;
const getRawEntityRecord = exports.getRawEntityRecord = (0, _utils.forwardResolver)('getEntityRecord');
/**
* Requests an entity's record from the REST API.
*/
const getEditedEntityRecord = exports.getEditedEntityRecord = (0, _utils.forwardResolver)('getEntityRecord');
/**
* Requests the entity's records from the REST API.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {?Object} query Query Object. If requesting specific fields, fields
* must always include the ID.
*/
const getEntityRecords = (kind, name, query = {}) => async ({
dispatch,
registry,
resolveSelect
}) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(config => config.name === name && config.kind === kind);
if (!entityConfig) {
return;
}
const lock = await dispatch.__unstableAcquireStoreLock(_name.STORE_NAME, ['entities', 'records', kind, name], {
exclusive: false
});
// Keep a copy of the original query for later use in getResolutionsArgs.
// The query object may be modified below (for example, when _fields is
// specified), but we want to use the original query when marking
// resolutions as finished.
const rawQuery = {
...query
};
const key = entityConfig.key || _entities.DEFAULT_ENTITY_KEY;
function getResolutionsArgs(records, recordsQuery) {
const queryArgs = Object.fromEntries(Object.entries(recordsQuery).filter(([k, v]) => {
return ['context', '_fields'].includes(k) && !!v;
}));
return records.filter(record => record?.[key]).map(record => [kind, name, record[key], Object.keys(queryArgs).length > 0 ? queryArgs : undefined]);
}
try {
if (query._fields) {
// If requesting specific fields, items and query association to said
// records are stored by ID reference. Thus, fields must always include
// the ID.
query = {
...query,
_fields: [...new Set([...((0, _utils.getNormalizedCommaSeparable)(query._fields) || []), key])].join()
};
}
const path = (0, _url.addQueryArgs)(entityConfig.baseURL, {
...entityConfig.baseURLParams,
...query
});
let records = [],
meta;
if (entityConfig.supportsPagination && query.per_page !== -1) {
const response = await (0, _apiFetch.default)({
path,
parse: false
});
records = Object.values(await response.json());
meta = {
totalItems: parseInt(response.headers.get('X-WP-Total')),
totalPages: parseInt(response.headers.get('X-WP-TotalPages'))
};
} else if (query.per_page === -1 && query[_utils.RECEIVE_INTERMEDIATE_RESULTS] === true) {
let page = 1;
let totalPages;
do {
const response = await (0, _apiFetch.default)({
path: (0, _url.addQueryArgs)(path, {
page,
per_page: 100
}),
parse: false
});
const pageRecords = Object.values(await response.json());
totalPages = parseInt(response.headers.get('X-WP-TotalPages'));
if (!meta) {
meta = {
totalItems: parseInt(response.headers.get('X-WP-Total')),
totalPages: 1
};
}
records.push(...pageRecords);
registry.batch(() => {
dispatch.receiveEntityRecords(kind, name, records, query, false, undefined, meta);
dispatch.finishResolutions('getEntityRecord', getResolutionsArgs(pageRecords, rawQuery));
});
page++;
} while (page <= totalPages);
} else {
records = Object.values(await (0, _apiFetch.default)({
path
}));
meta = {
totalItems: records.length,
totalPages: 1
};
}
// If we request fields but the result doesn't contain the fields,
// explicitly set these fields as "undefined"
// that way we consider the query "fulfilled".
if (query._fields) {
records = records.map(record => {
query._fields.split(',').forEach(field => {
if (!record.hasOwnProperty(field)) {
record[field] = undefined;
}
});
return record;
});
}
registry.batch(() => {
dispatch.receiveEntityRecords(kind, name, records, query, false, undefined, meta);
const targetHints = records.filter(record => !!record?.[key] && !!record?._links?.self?.[0]?.targetHints?.allow).map(record => ({
id: record[key],
permissions: (0, _utils.getUserPermissionsFromAllowHeader)(record._links.self[0].targetHints.allow)
}));
const canUserResolutionsArgs = [];
const receiveUserPermissionArgs = {};
for (const targetHint of targetHints) {
for (const action of _utils.ALLOWED_RESOURCE_ACTIONS) {
canUserResolutionsArgs.push([action, {
kind,
name,
id: targetHint.id
}]);
receiveUserPermissionArgs[(0, _utils.getUserPermissionCacheKey)(action, {
kind,
name,
id: targetHint.id
})] = targetHint.permissions[action];
}
}
if (targetHints.length > 0) {
dispatch.receiveUserPermissions(receiveUserPermissionArgs);
dispatch.finishResolutions('canUser', canUserResolutionsArgs);
}
dispatch.finishResolutions('getEntityRecord', getResolutionsArgs(records, rawQuery));
dispatch.__unstableReleaseStoreLock(lock);
});
} catch (e) {
dispatch.__unstableReleaseStoreLock(lock);
}
};
exports.getEntityRecords = getEntityRecords;
getEntityRecords.shouldInvalidate = (action, kind, name) => {
return (action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS') && action.invalidateCache && kind === action.kind && name === action.name;
};
/**
* Requests the total number of entity records.
*/
const getEntityRecordsTotalItems = exports.getEntityRecordsTotalItems = (0, _utils.forwardResolver)('getEntityRecords');
/**
* Requests the number of available pages for the given query.
*/
const getEntityRecordsTotalPages = exports.getEntityRecordsTotalPages = (0, _utils.forwardResolver)('getEntityRecords');
/**
* Requests the current theme.
*/
const getCurrentTheme = () => async ({
dispatch,
resolveSelect
}) => {
const activeThemes = await resolveSelect.getEntityRecords('root', 'theme', {
status: 'active'
});
dispatch.receiveCurrentTheme(activeThemes[0]);
};
/**
* Requests theme supports data from the index.
*/
exports.getCurrentTheme = getCurrentTheme;
const getThemeSupports = exports.getThemeSupports = (0, _utils.forwardResolver)('getCurrentTheme');
/**
* Requests a preview from the Embed API.
*
* @param {string} url URL to get the preview for.
*/
const getEmbedPreview = url => async ({
dispatch
}) => {
try {
const embedProxyResponse = await (0, _apiFetch.default)({
path: (0, _url.addQueryArgs)('/oembed/1.0/proxy', {
url
})
});
dispatch.receiveEmbedPreview(url, embedProxyResponse);
} catch (error) {
// Embed API 404s if the URL cannot be embedded, so we have to catch the error from the apiRequest here.
dispatch.receiveEmbedPreview(url, false);
}
};
/**
* Checks whether the current user can perform the given action on the given
* REST resource.
*
* @param {string} requestedAction Action to check. One of: 'create', 'read', 'update',
* 'delete'.
* @param {string|Object} resource Entity resource to check. Accepts entity object `{ kind: 'postType', name: 'attachment', id: 1 }`
* or REST base as a string - `media`.
* @param {?string} id ID of the rest resource to check.
*/
exports.getEmbedPreview = getEmbedPreview;
const canUser = (requestedAction, resource, id) => async ({
dispatch,
registry,
resolveSelect
}) => {
if (!_utils.ALLOWED_RESOURCE_ACTIONS.includes(requestedAction)) {
throw new Error(`'${requestedAction}' is not a valid action.`);
}
const {
hasStartedResolution
} = registry.select(_name.STORE_NAME);
// Prevent resolving the same resource twice.
for (const relatedAction of _utils.ALLOWED_RESOURCE_ACTIONS) {
if (relatedAction === requestedAction) {
continue;
}
const isAlreadyResolving = hasStartedResolution('canUser', [relatedAction, resource, id]);
if (isAlreadyResolving) {
return;
}
}
let resourcePath = null;
if (typeof resource === 'object') {
if (!resource.kind || !resource.name) {
throw new Error('The entity resource object is not valid.');
}
const configs = await resolveSelect.getEntitiesConfig(resource.kind);
const entityConfig = configs.find(config => config.name === resource.name && config.kind === resource.kind);
if (!entityConfig) {
return;
}
resourcePath = entityConfig.baseURL + (resource.id ? '/' + resource.id : '');
} else {
resourcePath = `/wp/v2/${resource}` + (id ? '/' + id : '');
}
let response;
try {
response = await (0, _apiFetch.default)({
path: resourcePath,
method: 'OPTIONS',
parse: false
});
} catch (error) {
// Do nothing if our OPTIONS request comes back with an API error (4xx or
// 5xx). The previously determined isAllowed value will remain in the store.
return;
}
// Optional chaining operator is used here because the API requests don't
// return the expected result in the React native version. Instead, API requests
// only return the result, without including response properties like the headers.
const permissions = (0, _utils.getUserPermissionsFromAllowHeader)(response.headers?.get('allow'));
registry.batch(() => {
for (const action of _utils.ALLOWED_RESOURCE_ACTIONS) {
const key = (0, _utils.getUserPermissionCacheKey)(action, resource, id);
dispatch.receiveUserPermission(key, permissions[action]);
// Mark related action resolutions as finished.
if (action !== requestedAction) {
dispatch.finishResolution('canUser', [action, resource, id]);
}
}
});
};
/**
* Checks whether the current user can perform the given action on the given
* REST resource.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number|string} recordId Record's id.
*/
exports.canUser = canUser;
const canUserEditEntityRecord = (kind, name, recordId) => async ({
dispatch
}) => {
await dispatch(canUser('update', {
kind,
name,
id: recordId
}));
};
/**
* Request autosave data from the REST API.
*
* @param {string} postType The type of the parent post.
* @param {number} postId The id of the parent post.
*/
exports.canUserEditEntityRecord = canUserEditEntityRecord;
const getAutosaves = (postType, postId) => async ({
dispatch,
resolveSelect
}) => {
const {
rest_base: restBase,
rest_namespace: restNamespace = 'wp/v2',
supports
} = await resolveSelect.getPostType(postType);
if (!supports?.autosave) {
return;
}
const autosaves = await (0, _apiFetch.default)({
path: `/${restNamespace}/${restBase}/${postId}/autosaves?context=edit`
});
if (autosaves && autosaves.length) {
dispatch.receiveAutosaves(postId, autosaves);
}
};
/**
* Request autosave data from the REST API.
*
* This resolver exists to ensure the underlying autosaves are fetched via
* `getAutosaves` when a call to the `getAutosave` selector is made.
*
* @param {string} postType The type of the parent post.
* @param {number} postId The id of the parent post.
*/
exports.getAutosaves = getAutosaves;
const getAutosave = (postType, postId) => async ({
resolveSelect
}) => {
await resolveSelect.getAutosaves(postType, postId);
};
exports.getAutosave = getAutosave;
const __experimentalGetCurrentGlobalStylesId = () => async ({
dispatch,
resolveSelect
}) => {
const activeThemes = await resolveSelect.getEntityRecords('root', 'theme', {
status: 'active'
});
const globalStylesURL = activeThemes?.[0]?._links?.['wp:user-global-styles']?.[0]?.href;
if (!globalStylesURL) {
return;
}
// Regex matches the ID at the end of a URL or immediately before
// the query string.
const matches = globalStylesURL.match(/\/(\d+)(?:\?|$)/);
const id = matches ? Number(matches[1]) : null;
if (id) {
dispatch.__experimentalReceiveCurrentGlobalStylesId(id);
}
};
exports.__experimentalGetCurrentGlobalStylesId = __experimentalGetCurrentGlobalStylesId;
const __experimentalGetCurrentThemeBaseGlobalStyles = () => async ({
resolveSelect,
dispatch
}) => {
const currentTheme = await resolveSelect.getCurrentTheme();
// Please adjust the preloaded requests if this changes!
const themeGlobalStyles = await (0, _apiFetch.default)({
path: `/wp/v2/global-styles/themes/${currentTheme.stylesheet}?context=view`
});
dispatch.__experimentalReceiveThemeBaseGlobalStyles(currentTheme.stylesheet, themeGlobalStyles);
};
exports.__experimentalGetCurrentThemeBaseGlobalStyles = __experimentalGetCurrentThemeBaseGlobalStyles;
const __experimentalGetCurrentThemeGlobalStylesVariations = () => async ({
resolveSelect,
dispatch
}) => {
const currentTheme = await resolveSelect.getCurrentTheme();
// Please adjust the preloaded requests if this changes!
const variations = await (0, _apiFetch.default)({
path: `/wp/v2/global-styles/themes/${currentTheme.stylesheet}/variations?context=view`
});
dispatch.__experimentalReceiveThemeGlobalStyleVariations(currentTheme.stylesheet, variations);
};
/**
* Fetches and returns the revisions of the current global styles theme.
*/
exports.__experimentalGetCurrentThemeGlobalStylesVariations = __experimentalGetCurrentThemeGlobalStylesVariations;
const getCurrentThemeGlobalStylesRevisions = () => async ({
resolveSelect,
dispatch
}) => {
const globalStylesId = await resolveSelect.__experimentalGetCurrentGlobalStylesId();
const record = globalStylesId ? await resolveSelect.getEntityRecord('root', 'globalStyles', globalStylesId) : undefined;
const revisionsURL = record?._links?.['version-history']?.[0]?.href;
if (revisionsURL) {
const resetRevisions = await (0, _apiFetch.default)({
url: revisionsURL
});
const revisions = resetRevisions?.map(revision => Object.fromEntries(Object.entries(revision).map(([key, value]) => [(0, _changeCase.camelCase)(key), value])));
dispatch.receiveThemeGlobalStyleRevisions(globalStylesId, revisions);
}
};
exports.getCurrentThemeGlobalStylesRevisions = getCurrentThemeGlobalStylesRevisions;
getCurrentThemeGlobalStylesRevisions.shouldInvalidate = action => {
return action.type === 'SAVE_ENTITY_RECORD_FINISH' && action.kind === 'root' && !action.error && action.name === 'globalStyles';
};
const getBlockPatterns = () => async ({
dispatch
}) => {
const patterns = await (0, _fetch.fetchBlockPatterns)();
dispatch({
type: 'RECEIVE_BLOCK_PATTERNS',
patterns
});
};
exports.getBlockPatterns = getBlockPatterns;
const getBlockPatternCategories = () => async ({
dispatch
}) => {
const categories = await (0, _apiFetch.default)({
path: '/wp/v2/block-patterns/categories'
});
dispatch({
type: 'RECEIVE_BLOCK_PATTERN_CATEGORIES',
categories
});
};
exports.getBlockPatternCategories = getBlockPatternCategories;
const getUserPatternCategories = () => async ({
dispatch,
resolveSelect
}) => {
const patternCategories = await resolveSelect.getEntityRecords('taxonomy', 'wp_pattern_category', {
per_page: -1,
_fields: 'id,name,description,slug',
context: 'view'
});
const mappedPatternCategories = patternCategories?.map(userCategory => ({
...userCategory,
label: (0, _htmlEntities.decodeEntities)(userCategory.name),
name: userCategory.slug
})) || [];
dispatch({
type: 'RECEIVE_USER_PATTERN_CATEGORIES',
patternCategories: mappedPatternCategories
});
};
exports.getUserPatternCategories = getUserPatternCategories;
const getNavigationFallbackId = () => async ({
dispatch,
select,
registry
}) => {
const fallback = await (0, _apiFetch.default)({
path: (0, _url.addQueryArgs)('/wp-block-editor/v1/navigation-fallback', {
_embed: true
})
});
const record = fallback?._embedded?.self;
registry.batch(() => {
dispatch.receiveNavigationFallbackId(fallback?.id);
if (!record) {
return;
}
// If the fallback is already in the store, don't invalidate navigation queries.
// Otherwise, invalidate the cache for the scenario where there were no Navigation
// posts in the state and the fallback created one.
const existingFallbackEntityRecord = select.getEntityRecord('postType', 'wp_navigation', fallback.id);
const invalidateNavigationQueries = !existingFallbackEntityRecord;
dispatch.receiveEntityRecords('postType', 'wp_navigation', record, undefined, invalidateNavigationQueries);
// Resolve to avoid further network requests.
dispatch.finishResolution('getEntityRecord', ['postType', 'wp_navigation', fallback.id]);
});
};
exports.getNavigationFallbackId = getNavigationFallbackId;
const getDefaultTemplateId = query => async ({
dispatch,
registry,
resolveSelect
}) => {
const template = await (0, _apiFetch.default)({
path: (0, _url.addQueryArgs)('/wp/v2/templates/lookup', query)
});
// Wait for the the entities config to be loaded, otherwise receiving
// the template as an entity will not work.
await resolveSelect.getEntitiesConfig('postType');
const id = template?.wp_id || template?.id;
// Endpoint may return an empty object if no template is found.
if (id) {
template.id = id;
template.type = typeof id === 'string' ? 'wp_registered_template' : 'wp_template';
registry.batch(() => {
dispatch.receiveDefaultTemplateId(query, id);
dispatch.receiveEntityRecords('postType', template.type, [template]);
// Avoid further network requests.
dispatch.finishResolution('getEntityRecord', ['postType', template.type, id]);
});
}
};
exports.getDefaultTemplateId = getDefaultTemplateId;
getDefaultTemplateId.shouldInvalidate = action => {
return action.type === 'EDIT_ENTITY_RECORD' && action.kind === 'root' && action.name === 'site';
};
/**
* Requests an entity's revisions from the REST API.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number|string} recordKey The key of the entity record whose revisions you want to fetch.
* @param {Object|undefined} query Optional object of query parameters to
* include with request. If requesting specific
* fields, fields must always include the ID.
*/
const getRevisions = (kind, name, recordKey, query = {}) => async ({
dispatch,
registry,
resolveSelect
}) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(config => config.name === name && config.kind === kind);
if (!entityConfig) {
return;
}
if (query._fields) {
// If requesting specific fields, items and query association to said
// records are stored by ID reference. Thus, fields must always include
// the ID.
query = {
...query,
_fields: [...new Set([...((0, _utils.getNormalizedCommaSeparable)(query._fields) || []), entityConfig.revisionKey || _entities.DEFAULT_ENTITY_KEY])].join()
};
}
const path = (0, _url.addQueryArgs)(entityConfig.getRevisionsUrl(recordKey), query);
let records, response;
const meta = {};
const isPaginated = entityConfig.supportsPagination && query.per_page !== -1;
try {
response = await (0, _apiFetch.default)({
path,
parse: !isPaginated
});
} catch (error) {
// Do nothing if our request comes back with an API error.
return;
}
if (response) {
if (isPaginated) {
records = Object.values(await response.json());
meta.totalItems = parseInt(response.headers.get('X-WP-Total'));
} else {
records = Object.values(response);
}
// If we request fields but the result doesn't contain the fields,
// explicitly set these fields as "undefined"
// that way we consider the query "fulfilled".
if (query._fields) {
records = records.map(record => {
query._fields.split(',').forEach(field => {
if (!record.hasOwnProperty(field)) {
record[field] = undefined;
}
});
return record;
});
}
registry.batch(() => {
dispatch.receiveRevisions(kind, name, recordKey, records, query, false, meta);
// When requesting all fields, the list of results can be used to
// resolve the `getRevision` selector in addition to `getRevisions`.
if (!query?._fields && !query.context) {
const key = entityConfig.key || _entities.DEFAULT_ENTITY_KEY;
const resolutionsArgs = records.filter(record => record[key]).map(record => [kind, name, recordKey, record[key]]);
dispatch.finishResolutions('getRevision', resolutionsArgs);
}
});
}
};
// Invalidate cache when a new revision is created.
exports.getRevisions = getRevisions;
getRevisions.shouldInvalidate = (action, kind, name, recordKey) => action.type === 'SAVE_ENTITY_RECORD_FINISH' && name === action.name && kind === action.kind && !action.error && recordKey === action.recordId;
/**
* Requests a specific Entity revision from the REST API.
*
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number|string} recordKey The key of the entity record whose revisions you want to fetch.
* @param {number|string} revisionKey The revision's key.
* @param {Object|undefined} query Optional object of query parameters to
* include with request. If requesting specific
* fields, fields must always include the ID.
*/
const getRevision = (kind, name, recordKey, revisionKey, query) => async ({
dispatch,
resolveSelect
}) => {
const configs = await resolveSelect.getEntitiesConfig(kind);
const entityConfig = configs.find(config => config.name === name && config.kind === kind);
if (!entityConfig) {
return;
}
if (query !== undefined && query._fields) {
// If requesting specific fields, items and query association to said
// records are stored by ID reference. Thus, fields must always include
// the ID.
query = {
...query,
_fields: [...new Set([...((0, _utils.getNormalizedCommaSeparable)(query._fields) || []), entityConfig.revisionKey || _entities.DEFAULT_ENTITY_KEY])].join()
};
}
const path = (0, _url.addQueryArgs)(entityConfig.getRevisionsUrl(recordKey, revisionKey), query);
let record;
try {
record = await (0, _apiFetch.default)({
path
});
} catch (error) {
// Do nothing if our request comes back with an API error.
return;
}
if (record) {
dispatch.receiveRevisions(kind, name, recordKey, record, query);
}
};
/**
* Requests a specific post type options from the REST API.
*
* @param {string} postType Post type slug.
*/
exports.getRevision = getRevision;
const getRegisteredPostMeta = postType => async ({
dispatch,
resolveSelect
}) => {
let options;
try {
const {
rest_namespace: restNamespace = 'wp/v2',
rest_base: restBase
} = (await resolveSelect.getPostType(postType)) || {};
options = await (0, _apiFetch.default)({
path: `${restNamespace}/${restBase}/?context=edit`,
method: 'OPTIONS'
});
} catch (error) {
// Do nothing if the request comes back with an API error.
return;
}
if (options) {
dispatch.receiveRegisteredPostMeta(postType, options?.schema?.properties?.meta?.properties);
}
};
/**
* Requests entity configs for the given kind from the REST API.
*
* @param {string} kind Entity kind.
*/
exports.getRegisteredPostMeta = getRegisteredPostMeta;
const getEntitiesConfig = kind => async ({
dispatch
}) => {
const loader = _entities.additionalEntityConfigLoaders.find(l => l.kind === kind);
if (!loader) {
return;
}
try {
const configs = await loader.loadEntities();
if (!configs.length) {
return;
}
dispatch.addEntities(configs);
} catch {
// Do nothing if the request comes back with an API error.
}
};
exports.getEntitiesConfig = getEntitiesConfig;
//# sourceMappingURL=resolvers.js.map