diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 61d0f8bd30f264..ba500d10c0038f 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -21,7 +21,7 @@ _Parameters_ - _state_ `State`: Data state. - _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. - _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. -- _id_ `GenericRecordKey`: Optional ID of the rest resource to check. +- _id_ `EntityRecordKey`: Optional ID of the rest resource to check. _Returns_ @@ -39,9 +39,9 @@ Calling this may trigger an OPTIONS request to the REST API via the _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record's id. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record's id. _Returns_ @@ -56,11 +56,11 @@ Returns all available authors. _Parameters_ - _state_ `State`: Data state. -- _query_ `EntityQuery< any >`: Optional object of query parameters to include with request. +- _query_ `GetRecordsHttpQuery`: Optional object of query parameters to include with request. _Returns_ -- `User< 'edit' >[]`: Authors list. +- `ET.User[]`: Authors list. ### getAutosave @@ -70,8 +70,8 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. -- _authorId_ `GenericRecordKey`: The id of the author. +- _postId_ `EntityRecordKey`: The id of the parent post. +- _authorId_ `EntityRecordKey`: The id of the author. _Returns_ @@ -88,7 +88,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -140,7 +140,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >`: Current user object. +- `undefined< 'edit' >`: Current user object. ### getEditedEntityRecord @@ -149,13 +149,13 @@ Returns the specified entity record, merged with its edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ -- `EntityRecord | undefined`: The entity record, merged with its edits. +- `undefined< EntityRecord > | undefined`: The entity record, merged with its edits. ### getEmbedPreview @@ -179,7 +179,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -192,7 +192,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -207,8 +207,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -221,8 +221,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -237,14 +237,14 @@ entity object if it exists and is received. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< R >`: Record's key -- _query_ Optional query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. _Returns_ -- Record. +- `EntityRecord | undefined`: Record. ### getEntityRecordEdits @@ -253,9 +253,9 @@ Returns the specified entity record's edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -272,9 +272,9 @@ They are defined in the entity's config. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -287,13 +287,13 @@ Returns the Entity's records. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ Optional terms query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. If requesting specific fields, fields must always include the ID. _Returns_ -- Records. +- `EntityRecord[] | null`: Records. ### getLastEntityDeleteError @@ -302,9 +302,9 @@ Returns the specified entity record's last delete error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -317,9 +317,9 @@ Returns the specified entity record's last save error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -333,9 +333,9 @@ with its attributes mapped to their raw values. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< K, N >`: Record's key. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key. _Returns_ @@ -411,7 +411,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >[]`: Users list. +- `undefined< 'edit' >[]`: Users list. ### hasEditsForEntityRecord @@ -421,9 +421,9 @@ and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -437,9 +437,9 @@ or false otherwise. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ `EntityQuery< C >`: Optional terms query. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. _Returns_ @@ -453,7 +453,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -492,9 +492,9 @@ Returns true if the specified entity record is autosaving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -507,9 +507,9 @@ Returns true if the specified entity record is deleting, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -553,9 +553,9 @@ Returns true if the specified entity record is saving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 6001ee7a6ba14c..b6e70412f530dd 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -189,7 +189,7 @@ _Parameters_ - _state_ `State`: Data state. - _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. - _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. -- _id_ `GenericRecordKey`: Optional ID of the rest resource to check. +- _id_ `EntityRecordKey`: Optional ID of the rest resource to check. _Returns_ @@ -207,9 +207,9 @@ Calling this may trigger an OPTIONS request to the REST API via the _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record's id. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record's id. _Returns_ @@ -224,11 +224,11 @@ Returns all available authors. _Parameters_ - _state_ `State`: Data state. -- _query_ `EntityQuery< any >`: Optional object of query parameters to include with request. +- _query_ `GetRecordsHttpQuery`: Optional object of query parameters to include with request. _Returns_ -- `User< 'edit' >[]`: Authors list. +- `ET.User[]`: Authors list. ### getAutosave @@ -238,8 +238,8 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. -- _authorId_ `GenericRecordKey`: The id of the author. +- _postId_ `EntityRecordKey`: The id of the parent post. +- _authorId_ `EntityRecordKey`: The id of the author. _Returns_ @@ -256,7 +256,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -308,7 +308,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >`: Current user object. +- `undefined< 'edit' >`: Current user object. ### getEditedEntityRecord @@ -317,13 +317,13 @@ Returns the specified entity record, merged with its edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ -- `EntityRecord | undefined`: The entity record, merged with its edits. +- `undefined< EntityRecord > | undefined`: The entity record, merged with its edits. ### getEmbedPreview @@ -347,7 +347,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -360,7 +360,7 @@ Returns the loaded entities for the given kind. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. +- _kind_ `string`: Entity kind. _Returns_ @@ -375,8 +375,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -389,8 +389,8 @@ Returns the entity config given its kind and name. _Parameters_ - _state_ `State`: Data state. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. _Returns_ @@ -405,14 +405,14 @@ entity object if it exists and is received. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< R >`: Record's key -- _query_ Optional query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key +- _query_ `GetRecordsHttpQuery`: Optional query. If requesting specific fields, fields must always include the ID. _Returns_ -- Record. +- `EntityRecord | undefined`: Record. ### getEntityRecordEdits @@ -421,9 +421,9 @@ Returns the specified entity record's edits. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -440,9 +440,9 @@ They are defined in the entity's config. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -455,13 +455,13 @@ Returns the Entity's records. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ Optional terms query. If requesting specific fields, fields must always include the ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. If requesting specific fields, fields must always include the ID. _Returns_ -- Records. +- `EntityRecord[] | null`: Records. ### getLastEntityDeleteError @@ -470,9 +470,9 @@ Returns the specified entity record's last delete error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -485,9 +485,9 @@ Returns the specified entity record's last save error. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -501,9 +501,9 @@ with its attributes mapped to their raw values. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _key_ `KeyOf< K, N >`: Record's key. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `EntityRecordKey`: Record's key. _Returns_ @@ -579,7 +579,7 @@ _Parameters_ _Returns_ -- `User< 'edit' >[]`: Users list. +- `undefined< 'edit' >[]`: Users list. ### hasEditsForEntityRecord @@ -589,9 +589,9 @@ and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -605,9 +605,9 @@ or false otherwise. _Parameters_ - _state_ `State`: State tree -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _query_ `EntityQuery< C >`: Optional terms query. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `GetRecordsHttpQuery`: Optional terms query. _Returns_ @@ -621,7 +621,7 @@ _Parameters_ - _state_ `State`: State tree. - _postType_ `string`: The type of the parent post. -- _postId_ `GenericRecordKey`: The id of the parent post. +- _postId_ `EntityRecordKey`: The id of the parent post. _Returns_ @@ -660,9 +660,9 @@ Returns true if the specified entity record is autosaving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -675,9 +675,9 @@ Returns true if the specified entity record is deleting, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `Kind`: Entity kind. -- _name_ `Name`: Entity name. -- _recordId_ `GenericRecordKey`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ @@ -721,9 +721,9 @@ Returns true if the specified entity record is saving, and false otherwise. _Parameters_ - _state_ `State`: State tree. -- _kind_ `K`: Entity kind. -- _name_ `N`: Entity name. -- _recordId_ `KeyOf< K, N >`: Record ID. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `EntityRecordKey`: Record ID. _Returns_ diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js new file mode 100644 index 00000000000000..0b64c3362e8955 --- /dev/null +++ b/packages/core-data/src/entities.js @@ -0,0 +1,325 @@ +/** + * External dependencies + */ +import { capitalCase, pascalCase } from 'change-case'; +import { map, find, get } from 'lodash'; + +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { addEntities } from './actions'; + +export const DEFAULT_ENTITY_KEY = 'id'; + +const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; + +export const rootEntitiesConfig = [ + { + label: __( 'Base' ), + kind: 'root', + name: '__unstableBase', + baseURL: '/', + baseURLParams: { + _fields: [ + 'description', + 'gmt_offset', + 'home', + 'name', + 'site_icon', + 'site_icon_url', + 'site_logo', + 'timezone_string', + 'url', + ].join( ',' ), + }, + }, + { + label: __( 'Site' ), + name: 'site', + kind: 'root', + baseURL: '/wp/v2/settings', + getTitle: ( record ) => { + return get( record, [ 'title' ], __( 'Site Title' ) ); + }, + }, + { + label: __( 'Post Type' ), + name: 'postType', + kind: 'root', + key: 'slug', + baseURL: '/wp/v2/types', + baseURLParams: { context: 'edit' }, + }, + { + name: 'media', + kind: 'root', + baseURL: '/wp/v2/media', + baseURLParams: { context: 'edit' }, + plural: 'mediaItems', + label: __( 'Media' ), + rawAttributes: [ 'caption', 'title', 'description' ], + }, + { + name: 'taxonomy', + kind: 'root', + key: 'slug', + baseURL: '/wp/v2/taxonomies', + baseURLParams: { context: 'edit' }, + plural: 'taxonomies', + label: __( 'Taxonomy' ), + }, + { + name: 'sidebar', + kind: 'root', + baseURL: '/wp/v2/sidebars', + baseURLParams: { context: 'edit' }, + plural: 'sidebars', + transientEdits: { blocks: true }, + label: __( 'Widget areas' ), + }, + { + name: 'widget', + kind: 'root', + baseURL: '/wp/v2/widgets', + baseURLParams: { context: 'edit' }, + plural: 'widgets', + transientEdits: { blocks: true }, + label: __( 'Widgets' ), + }, + { + name: 'widgetType', + kind: 'root', + baseURL: '/wp/v2/widget-types', + baseURLParams: { context: 'edit' }, + plural: 'widgetTypes', + label: __( 'Widget types' ), + }, + { + label: __( 'User' ), + name: 'user', + kind: 'root', + baseURL: '/wp/v2/users', + baseURLParams: { context: 'edit' }, + plural: 'users', + }, + { + name: 'comment', + kind: 'root', + baseURL: '/wp/v2/comments', + baseURLParams: { context: 'edit' }, + plural: 'comments', + label: __( 'Comment' ), + }, + { + name: 'menu', + kind: 'root', + baseURL: '/wp/v2/menus', + baseURLParams: { context: 'edit' }, + plural: 'menus', + label: __( 'Menu' ), + }, + { + name: 'menuItem', + kind: 'root', + baseURL: '/wp/v2/menu-items', + baseURLParams: { context: 'edit' }, + plural: 'menuItems', + label: __( 'Menu Item' ), + rawAttributes: [ 'title' ], + }, + { + name: 'menuLocation', + kind: 'root', + baseURL: '/wp/v2/menu-locations', + baseURLParams: { context: 'edit' }, + plural: 'menuLocations', + label: __( 'Menu Location' ), + key: 'name', + }, + { + label: __( 'Global Styles' ), + name: 'globalStyles', + kind: 'root', + baseURL: '/wp/v2/global-styles', + baseURLParams: { context: 'edit' }, + plural: 'globalStylesVariations', // Should be different than name. + getTitle: ( record ) => record?.title?.rendered || record?.title, + }, + { + label: __( 'Themes' ), + name: 'theme', + kind: 'root', + baseURL: '/wp/v2/themes', + baseURLParams: { context: 'edit' }, + key: 'stylesheet', + }, + { + label: __( 'Plugins' ), + name: 'plugin', + kind: 'root', + baseURL: '/wp/v2/plugins', + baseURLParams: { context: 'edit' }, + key: 'plugin', + }, +]; + +export const additionalEntityConfigLoaders = [ + { kind: 'postType', loadEntities: loadPostTypeEntities }, + { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, +]; + +/** + * Returns a function to be used to retrieve extra edits to apply before persisting a post type. + * + * @param {Object} persistedRecord Already persisted Post + * @param {Object} edits Edits. + * @return {Object} Updated edits. + */ +export const prePersistPostType = ( persistedRecord, edits ) => { + const newEdits = {}; + + if ( persistedRecord?.status === 'auto-draft' ) { + // Saving an auto-draft should create a draft by default. + if ( ! edits.status && ! newEdits.status ) { + newEdits.status = 'draft'; + } + + // Fix the auto-draft default title. + if ( + ( ! edits.title || edits.title === 'Auto Draft' ) && + ! newEdits.title && + ( ! persistedRecord?.title || + persistedRecord?.title === 'Auto Draft' ) + ) { + newEdits.title = ''; + } + } + + return newEdits; +}; + +/** + * Returns the list of post type entities. + * + * @return {Promise} Entities promise + */ +async function loadPostTypeEntities() { + const postTypes = await apiFetch( { + path: '/wp/v2/types?context=view', + } ); + return map( postTypes, ( postType, name ) => { + const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( + name + ); + const namespace = postType?.rest_namespace ?? 'wp/v2'; + return { + kind: 'postType', + baseURL: `/${ namespace }/${ postType.rest_base }`, + baseURLParams: { context: 'edit' }, + name, + label: postType.name, + transientEdits: { + blocks: true, + selection: true, + }, + mergedEdits: { meta: true }, + rawAttributes: POST_RAW_ATTRIBUTES, + getTitle: ( record ) => + record?.title?.rendered || + record?.title || + ( isTemplate + ? capitalCase( record.slug ?? '' ) + : String( record.id ) ), + __unstablePrePersist: isTemplate ? undefined : prePersistPostType, + __unstable_rest_base: postType.rest_base, + }; + } ); +} + +/** + * Returns the list of the taxonomies entities. + * + * @return {Promise} Entities promise + */ +async function loadTaxonomyEntities() { + const taxonomies = await apiFetch( { + path: '/wp/v2/taxonomies?context=view', + } ); + return map( taxonomies, ( taxonomy, name ) => { + const namespace = taxonomy?.rest_namespace ?? 'wp/v2'; + return { + kind: 'taxonomy', + baseURL: `/${ namespace }/${ taxonomy.rest_base }`, + baseURLParams: { context: 'edit' }, + name, + label: taxonomy.name, + }; + } ); +} + +/** + * Returns the entity's getter method name given its kind and name. + * + * @example + * ```js + * const nameSingular = getMethodName( 'root', 'theme', 'get' ); + * // nameSingular is getRootTheme + * + * const namePlural = getMethodName( 'root', 'theme', 'set' ); + * // namePlural is setRootThemes + * ``` + * + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {string} prefix Function prefix. + * @param {boolean} usePlural Whether to use the plural form or not. + * + * @return {string} Method name + */ +export const getMethodName = ( + kind, + name, + prefix = 'get', + usePlural = false +) => { + const entityConfig = find( rootEntitiesConfig, { kind, name } ); + const kindPrefix = kind === 'root' ? '' : pascalCase( kind ); + const nameSuffix = pascalCase( name ) + ( usePlural ? 's' : '' ); + const suffix = + usePlural && 'plural' in entityConfig && entityConfig?.plural + ? pascalCase( entityConfig.plural ) + : nameSuffix; + return `${ prefix }${ kindPrefix }${ suffix }`; +}; + +/** + * Loads the kind entities into the store. + * + * @param {string} kind Kind + * + * @return {(thunkArgs: object) => Promise} Entities + */ +export const getOrLoadEntitiesConfig = + ( kind ) => + async ( { select, dispatch } ) => { + let configs = select.getEntitiesConfig( kind ); + if ( configs && configs.length !== 0 ) { + return configs; + } + + const loader = find( additionalEntityConfigLoaders, { kind } ); + if ( ! loader ) { + return []; + } + + configs = await loader.loadEntities(); + dispatch( addEntities( configs ) ); + + return configs; + }; diff --git a/packages/core-data/src/entities.ts b/packages/core-data/src/entities.ts deleted file mode 100644 index 3637563adceaa5..00000000000000 --- a/packages/core-data/src/entities.ts +++ /dev/null @@ -1,550 +0,0 @@ -/** - * External dependencies - */ -import { capitalCase, pascalCase } from 'change-case'; -import { map, find, get } from 'lodash'; - -/** - * WordPress dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { addEntities } from './actions'; -import type * as Records from './entity-types'; -import type { - EntityType, - Context, - Post, - Taxonomy, - Type, - Updatable, -} from './entity-types'; - -export const DEFAULT_ENTITY_KEY = 'id'; - -const POST_RAW_ATTRIBUTES = [ 'title', 'excerpt', 'content' ]; - -type AttachmentEntity< C extends Context = Context > = EntityType< - { - name: 'media'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Attachment< C >, - C ->; - -const attachmentConfig: AttachmentEntity[ 'config' ] = { - name: 'media', - kind: 'root', - baseURL: '/wp/v2/media', - baseURLParams: { context: 'edit' }, - plural: 'mediaItems', - label: __( 'Media' ), - rawAttributes: [ 'caption', 'title', 'description' ], -}; - -type SiteEntity< C extends Context = Context > = EntityType< - { - name: 'site'; - kind: 'root'; - }, - Records.Settings< C >, - C ->; - -const siteConfig: SiteEntity[ 'config' ] = { - label: __( 'Site' ), - name: 'site', - kind: 'root', - baseURL: '/wp/v2/settings', - getTitle: ( record: Records.Settings< 'edit' > ) => { - return get( record, [ 'title' ], __( 'Site Title' ) ); - }, -}; - -type PostTypeEntity< C extends Context = Context > = EntityType< - { - name: 'postType'; - kind: 'root'; - key: 'slug'; - baseURLParams: { context: 'edit' }; - }, - Records.Type< C >, - C ->; - -const postTypeConfig: PostTypeEntity[ 'config' ] = { - label: __( 'Post Type' ), - name: 'postType', - kind: 'root', - key: 'slug', - baseURL: '/wp/v2/types', - baseURLParams: { context: 'edit' }, -}; - -type TaxonomyEntity< C extends Context = Context > = EntityType< - { - name: 'taxonomy'; - kind: 'root'; - key: 'slug'; - baseURLParams: { context: 'edit' }; - }, - Records.Taxonomy< C >, - C ->; - -const taxonomyConfig: TaxonomyEntity[ 'config' ] = { - name: 'taxonomy', - kind: 'root', - key: 'slug', - baseURL: '/wp/v2/taxonomies', - baseURLParams: { context: 'edit' }, - plural: 'taxonomies', - label: __( 'Taxonomy' ), -}; - -type SidebarEntity< C extends Context = Context > = EntityType< - { - name: 'sidebar'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Sidebar< C >, - C ->; - -const sidebarConfig: SidebarEntity[ 'config' ] = { - name: 'sidebar', - kind: 'root', - baseURL: '/wp/v2/sidebars', - baseURLParams: { context: 'edit' }, - plural: 'sidebars', - transientEdits: { blocks: true }, - label: __( 'Widget areas' ), -}; - -type WidgetEntity< C extends Context = Context > = EntityType< - { - name: 'widget'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Widget< C >, - C ->; -const widgetConfig: WidgetEntity[ 'config' ] = { - name: 'widget', - kind: 'root', - baseURL: '/wp/v2/widgets', - baseURLParams: { context: 'edit' }, - plural: 'widgets', - transientEdits: { blocks: true }, - label: __( 'Widgets' ), -}; - -type WidgetTypeEntity< C extends Context = Context > = EntityType< - { - name: 'widgetType'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.WidgetType< C >, - C ->; -const widgetTypeConfig: WidgetTypeEntity[ 'config' ] = { - name: 'widgetType', - kind: 'root', - baseURL: '/wp/v2/widget-types', - baseURLParams: { context: 'edit' }, - plural: 'widgetTypes', - label: __( 'Widget types' ), -}; - -type UserEntity< C extends Context = Context > = EntityType< - { - name: 'user'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.User< C >, - C ->; -const userConfig: UserEntity[ 'config' ] = { - label: __( 'User' ), - name: 'user', - kind: 'root', - baseURL: '/wp/v2/users', - baseURLParams: { context: 'edit' }, - plural: 'users', -}; - -type CommentEntity< C extends Context = Context > = EntityType< - { - name: 'comment'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.Comment< C >, - C ->; -const commentConfig: CommentEntity[ 'config' ] = { - name: 'comment', - kind: 'root', - baseURL: '/wp/v2/comments', - baseURLParams: { context: 'edit' }, - plural: 'comments', - label: __( 'Comment' ), -}; - -type NavMenuEntity< C extends Context = Context > = EntityType< - { - name: 'menu'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.NavMenu< C >, - C ->; - -const menuConfig: NavMenuEntity[ 'config' ] = { - name: 'menu', - kind: 'root', - baseURL: '/wp/v2/menus', - baseURLParams: { context: 'edit' }, - plural: 'menus', - label: __( 'Menu' ), -}; - -type NavMenuItemEntity< C extends Context = Context > = EntityType< - { - name: 'menuItem'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - }, - Records.NavMenuItem< C >, - C ->; - -const menuItemConfig: NavMenuItemEntity[ 'config' ] = { - name: 'menuItem', - kind: 'root', - baseURL: '/wp/v2/menu-items', - baseURLParams: { context: 'edit' }, - plural: 'menuItems', - label: __( 'Menu Item' ), - rawAttributes: [ 'title' ], -}; - -type MenuLocationEntity< C extends Context = Context > = EntityType< - { - name: 'menuLocation'; - kind: 'root'; - key: 'name'; - baseURLParams: { context: 'edit' }; - }, - Records.MenuLocation< C >, - C ->; - -const menuLocationConfig: MenuLocationEntity[ 'config' ] = { - name: 'menuLocation', - kind: 'root', - baseURL: '/wp/v2/menu-locations', - baseURLParams: { context: 'edit' }, - plural: 'menuLocations', - label: __( 'Menu Location' ), - key: 'name', -}; - -const globalStyleConfig = { - label: __( 'Global Styles' ), - name: 'globalStyles', - kind: 'root', - baseURL: '/wp/v2/global-styles', - baseURLParams: { context: 'edit' }, - plural: 'globalStylesVariations', // Should be different than name. - getTitle: ( record ) => record?.title?.rendered || record?.title, -}; - -type ThemeEntity< C extends Context = Context > = EntityType< - { - name: 'theme'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - key: 'stylesheet'; - }, - Records.Theme< C >, - C ->; - -const themeConfig: ThemeEntity[ 'config' ] = { - label: __( 'Themes' ), - name: 'theme', - kind: 'root', - baseURL: '/wp/v2/themes', - baseURLParams: { context: 'edit' }, - key: 'stylesheet', -}; - -type PluginEntity< C extends Context = Context > = EntityType< - { - name: 'plugin'; - kind: 'root'; - baseURLParams: { context: 'edit' }; - key: 'plugin'; - }, - Records.Plugin< C >, - C ->; -const pluginConfig: PluginEntity[ 'config' ] = { - label: __( 'Plugins' ), - name: 'plugin', - kind: 'root', - baseURL: '/wp/v2/plugins', - baseURLParams: { context: 'edit' }, - key: 'plugin', -}; - -export const rootEntitiesConfig = [ - { - label: __( 'Base' ), - kind: 'root', - name: '__unstableBase', - baseURL: '/', - baseURLParams: { - _fields: [ - 'description', - 'gmt_offset', - 'home', - 'name', - 'site_icon', - 'site_icon_url', - 'site_logo', - 'timezone_string', - 'url', - ].join( ',' ), - }, - }, - siteConfig, - postTypeConfig, - attachmentConfig, - taxonomyConfig, - sidebarConfig, - widgetConfig, - widgetTypeConfig, - userConfig, - commentConfig, - menuConfig, - menuItemConfig, - menuLocationConfig, - globalStyleConfig, - themeConfig, - pluginConfig, -]; - -type PostTypeConfig = { - kind: 'postType'; - key: 'id'; - defaultContext: 'edit'; -}; - -type PostEntity< C extends Context = Context > = EntityType< - PostTypeConfig & { name: 'post' }, - Records.Post< C >, - C ->; -type PageEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'page' }, - Records.Page< C >, - C ->; -type WpTemplateEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'wp_template' }, - Records.WpTemplate< C >, - C ->; -type WpTemplatePartEntity< C extends Context > = EntityType< - PostTypeConfig & { name: 'wp_template_part' }, - Records.WpTemplatePart< C >, - C ->; - -export type CoreEntities< C extends Context > = - | SiteEntity< C > - | PostTypeEntity< C > - | AttachmentEntity< C > - | TaxonomyEntity< C > - | SidebarEntity< C > - | WidgetEntity< C > - | WidgetTypeEntity< C > - | UserEntity< C > - | CommentEntity< C > - | NavMenuEntity< C > - | NavMenuItemEntity< C > - | MenuLocationEntity< C > - | ThemeEntity< C > - | PluginEntity< C > - | PostEntity< C > - | PageEntity< C > - | WpTemplateEntity< C > - | WpTemplatePartEntity< C >; - -export const additionalEntityConfigLoaders = [ - { kind: 'postType', loadEntities: loadPostTypeEntities }, - { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, -]; - -/** - * Returns a function to be used to retrieve extra edits to apply before persisting a post type. - * - * @param {Object} persistedRecord Already persisted Post - * @param {Object} edits Edits. - * @return {Object} Updated edits. - */ -export const prePersistPostType = ( persistedRecord, edits ) => { - const newEdits = {} as Partial< Updatable< Post< 'edit' > > >; - - if ( persistedRecord?.status === 'auto-draft' ) { - // Saving an auto-draft should create a draft by default. - if ( ! edits.status && ! newEdits.status ) { - newEdits.status = 'draft'; - } - - // Fix the auto-draft default title. - if ( - ( ! edits.title || edits.title === 'Auto Draft' ) && - ! newEdits.title && - ( ! persistedRecord?.title || - persistedRecord?.title === 'Auto Draft' ) - ) { - newEdits.title = ''; - } - } - - return newEdits; -}; - -/** - * Returns the list of post type entities. - * - * @return {Promise} Entities promise - */ -async function loadPostTypeEntities() { - const postTypes = ( await apiFetch( { - path: '/wp/v2/types?context=view', - } ) ) as Record< string, Type< 'view' > >; - return map( postTypes, ( postType, name ) => { - const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( - name - ); - const namespace = postType?.rest_namespace ?? 'wp/v2'; - return { - kind: 'postType', - baseURL: `/${ namespace }/${ postType.rest_base }`, - baseURLParams: { context: 'edit' }, - name, - label: postType.name, - transientEdits: { - blocks: true, - selection: true, - }, - mergedEdits: { meta: true }, - rawAttributes: POST_RAW_ATTRIBUTES, - getTitle: ( record ) => - record?.title?.rendered || - record?.title || - ( isTemplate - ? capitalCase( record.slug ?? '' ) - : String( record.id ) ), - __unstablePrePersist: isTemplate ? undefined : prePersistPostType, - __unstable_rest_base: postType.rest_base, - }; - } ); -} - -/** - * Returns the list of the taxonomies entities. - * - * @return {Promise} Entities promise - */ -async function loadTaxonomyEntities() { - const taxonomies = ( await apiFetch( { - path: '/wp/v2/taxonomies?context=view', - } ) ) as Record< string, Taxonomy< 'view' > >; - return map( taxonomies, ( taxonomy, name ) => { - const namespace = taxonomy?.rest_namespace ?? 'wp/v2'; - return { - kind: 'taxonomy', - baseURL: `/${ namespace }/${ taxonomy.rest_base }`, - baseURLParams: { context: 'edit' }, - name, - label: taxonomy.name, - }; - } ); -} - -/** - * Returns the entity's getter method name given its kind and name. - * - * @example - * ```js - * const nameSingular = getMethodName( 'root', 'theme', 'get' ); - * // nameSingular is getRootTheme - * - * const namePlural = getMethodName( 'root', 'theme', 'set' ); - * // namePlural is setRootThemes - * ``` - * - * @param {string} kind Entity kind. - * @param {string} name Entity name. - * @param {string} prefix Function prefix. - * @param {boolean} usePlural Whether to use the plural form or not. - * - * @return {string} Method name - */ -export const getMethodName = ( - kind, - name, - prefix = 'get', - usePlural = false -) => { - const entityConfig = find( rootEntitiesConfig, { kind, name } ); - const kindPrefix = kind === 'root' ? '' : pascalCase( kind ); - const nameSuffix = pascalCase( name ) + ( usePlural ? 's' : '' ); - const suffix = - usePlural && 'plural' in entityConfig! && entityConfig?.plural - ? pascalCase( entityConfig.plural ) - : nameSuffix; - return `${ prefix }${ kindPrefix }${ suffix }`; -}; - -/** - * Loads the kind entities into the store. - * - * @param {string} kind Kind - * - * @return {(thunkArgs: object) => Promise} Entities - */ -export const getOrLoadEntitiesConfig = - ( kind ) => - async ( { select, dispatch } ) => { - let configs = select.getEntitiesConfig( kind ); - if ( configs && configs.length !== 0 ) { - return configs; - } - - const loader = find( additionalEntityConfigLoaders, { kind } ); - if ( ! loader ) { - return []; - } - - configs = await loader.loadEntities(); - dispatch( addEntities( configs ) ); - - return configs; - }; diff --git a/packages/core-data/src/entity-types/attachment.ts b/packages/core-data/src/entity-types/attachment.ts index eb0280e88e9f1d..703a08611c0e2b 100644 --- a/packages/core-data/src/entity-types/attachment.ts +++ b/packages/core-data/src/entity-types/attachment.ts @@ -13,7 +13,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -142,6 +141,6 @@ declare module './base-entity-records' { } } -export type Attachment< - C extends Context = DefaultContextOf< 'root', 'media' > -> = OmitNevers< _BaseEntityRecords.Attachment< C > >; +export type Attachment< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Attachment< C > +>; diff --git a/packages/core-data/src/entity-types/comment.ts b/packages/core-data/src/entity-types/comment.ts index 9f3228261e7d36..fee7f1faec7840 100644 --- a/packages/core-data/src/entity-types/comment.ts +++ b/packages/core-data/src/entity-types/comment.ts @@ -9,7 +9,6 @@ import type { RenderedText, } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; export type CommentStatus = 'hold' | 'approve' | 'spam' | 'trash' | '1' | '0'; @@ -92,6 +91,6 @@ declare module './base-entity-records' { } } -export type Comment< - C extends Context = DefaultContextOf< 'root', 'comment' > -> = OmitNevers< _BaseEntityRecords.Comment< C > >; +export type Comment< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Comment< C > +>; diff --git a/packages/core-data/src/entity-types/entities.ts b/packages/core-data/src/entity-types/entities.ts deleted file mode 100644 index ccadeeda3f001a..00000000000000 --- a/packages/core-data/src/entity-types/entities.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Internal dependencies - */ -import type { Context } from './helpers'; -import type { EntityRecord } from './index'; - -/** - * HTTP Query parameters sent with the API request to fetch the entity records. - */ -export type EntityQuery< - C extends Context, - Fields extends string[] | undefined = undefined -> = Record< string, any > & { - context?: C; - /** - * The requested fields. If specified, the REST API will remove from the response - * any fields not on that list. - */ - _fields?: Fields; -}; - -interface Edit {} - -/** - * Helper type that transforms "raw" entity configuration from entities.ts - * into a format that makes searching by root and kind easy and extensible. - * - * This is the foundation of return type inference in calls such as: - * `getEntityRecord( "root", "comment", 15 )`. - * - * @see EntityRecordOf - * @see getEntityRecord - */ -export type EntityType< - Config extends Pick< EntityConfig< Record, Ctx >, RequiredConfigKeys >, - Record extends EntityRecord< Ctx >, - Ctx extends Context -> = { - record: Record; - config: Omit< EntityConfig< Record, Ctx >, RequiredConfigKeys > & Config; - key: Config[ 'key' ] extends string ? Config[ 'key' ] : 'id'; - defaultContext: Config[ 'baseURLParams' ] extends { - context: infer InferredContext; - } - ? InferredContext - : 'view'; -}; - -type RequiredConfigKeys = 'name' | 'kind' | 'key' | 'baseURLParams'; - -export interface EntityConfig< - R extends EntityRecord< C >, - C extends Context -> { - /** Path in WP REST API from which to request records of this entity. */ - baseURL: string; - - /** Arguments to supply by default to API requests for records of this entity. */ - baseURLParams?: EntityQuery< Context >; - - /** - * Returns the title for a given record of this entity. - * - * Some entities have an associated title, such as the name of a - * particular template part ("full width") or of a menu ("main nav"). - */ - getTitle?: ( record: R ) => string; - - /** - * Indicates an alternate field in record that can be used for identification. - * - * e.g. a post has an id but may also be uniquely identified by its `slug` - */ - key?: string; - - /** - * Collection in which to classify records of this entity. - * - * 'root' is a special name given to the core entities provided by the editor. - * - * It may be the case that we request an entity record for which we have no - * valid config in memory. In these cases the editor will look for a loader - * function to requests more entity configs from the server for the given - * "kind." This is how WordPress defers loading of template entity configs. - */ - kind: string; - - /** Translated form of human-recognizable name or reference to records of this entity. */ - label: string; - - mergedEdits?: { - meta?: boolean; - }; - - /** Name given to records of this entity, e.g. 'media', 'postType', 'widget' */ - name: string; - - /** - * Manually provided plural form of the entity name. - * - * When not supplied the editor will attempt to auto-generate a plural form. - */ - plural?: string; - - /** - * Fields in record of this entity which may appear as a compound object with - * a source value (`raw`) as well as a processed value (`rendered`). - * - * e.g. a post's `content` in the edit context contains the raw value stored - * in the database as well as the rendered version with shortcodes replaced, - * content texturized, blocks transformed, etc… - */ - rawAttributes?: ( keyof R )[]; - - /** - * Which transient edit operations records of this entity support. - */ - transientEdits?: { - blocks?: boolean; - selection?: boolean; - }; - - // Unstable properties - - /** Returns additional changes before applying edits to a record of this entity. */ - __unstablePrePersist?: ( record: R, edits: Edit[] ) => Edit[]; - - /** Used in `canEdit()` */ - __unstable_rest_base?: string; -} diff --git a/packages/core-data/src/entity-types/index.ts b/packages/core-data/src/entity-types/index.ts index fe167f1b2dcaf1..19d10a28ad698e 100644 --- a/packages/core-data/src/entity-types/index.ts +++ b/packages/core-data/src/entity-types/index.ts @@ -20,9 +20,7 @@ import type { Widget } from './widget'; import type { WidgetType } from './widget-type'; import type { WpTemplate } from './wp-template'; import type { WpTemplatePart } from './wp-template-part'; -import type { CoreEntities } from '../entities'; -export type { EntityType } from './entities'; export type { BaseEntityRecords } from './base-entity-records'; export type { @@ -48,8 +46,6 @@ export type { WpTemplatePart, }; -export type UpdatableEntityRecord = Updatable< EntityRecord< 'edit' > >; - /** * An interface that may be extended to add types for new entities. Each entry * must be a union of entity definitions adhering to the EntityInterface type. @@ -60,105 +56,52 @@ export type UpdatableEntityRecord = Updatable< EntityRecord< 'edit' > >; * import type { Context } from '@wordpress/core-data'; * // ... * - * interface Order { + * interface Client { * id: number; - * clientId: number; + * name: string; * // ... * } * - * type OrderEntity = { - * kind: 'myPlugin'; - * name: 'order'; - * recordType: Order; + * interface Order< C extends Context > { + * id: number; + * name: string; + * // ... * } * * declare module '@wordpress/core-data' { - * export interface PerPackageEntities< C extends Context > { - * myPlugin: OrderEntity | ClientEntity + * export interface PerPackageEntityRecords< C extends Context > { + * myPlugin: Client | Order> * } * } * - * const c = getEntityRecord( 'myPlugin', 'order', 15 ); + * const c = getEntityRecord( 'myPlugin', 'order', 15 ); * // c is of the type Order * ``` */ -export interface PerPackageEntityConfig< C extends Context > { - core: CoreEntities< C >; +export interface PerPackageEntityRecords< C extends Context > { + core: + | Attachment< C > + | Comment< C > + | MenuLocation< C > + | NavMenu< C > + | NavMenuItem< C > + | Page< C > + | Plugin< C > + | Post< C > + | Settings< C > + | Sidebar< C > + | Taxonomy< C > + | Theme< C > + | User< C > + | Type< C > + | Widget< C > + | WidgetType< C > + | WpTemplate< C > + | WpTemplatePart< C >; } -/** - * A union of all the registered entities. - */ -type EntityConfig< C extends Context = any > = - PerPackageEntityConfig< C >[ keyof PerPackageEntityConfig< C > ]; - /** * A union of all known record types. */ -export type EntityRecord< C extends Context = any > = - EntityConfig< C >[ 'record' ]; - -/** - * An entity corresponding to a specified record type. - */ -export type EntityConfigOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined -> = RecordOrKind extends EntityRecord - ? Extract< EntityConfig, { record: RecordOrKind } > - : Extract< EntityConfig, { config: { kind: RecordOrKind; name: N } } >; - -/** - * Name of the requested entity. - */ -export type NameOf< R extends EntityRecord > = - EntityConfigOf< R >[ 'config' ][ 'name' ]; - -/** - * Kind of the requested entity. - */ -export type KindOf< R extends EntityRecord > = - EntityConfigOf< R >[ 'config' ][ 'kind' ]; - -/** - * Primary key type of the requested entity, sourced from PerPackageEntities. - * - * For core entities, the key type is computed using the entity configuration in entities.js. - */ -export type KeyOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined, - E extends EntityConfig = EntityConfigOf< RecordOrKind, N > -> = ( E[ 'key' ] extends keyof E[ 'record' ] - ? E[ 'record' ][ E[ 'key' ] ] - : never ) & - ( number | string ); - -/** - * Default context of the requested entity, sourced from PerPackageEntities. - * - * For core entities, the default context is extracted from the entity configuration - * in entities.js. - */ -export type DefaultContextOf< - RecordOrKind extends EntityRecord | Kind, - N extends Name | undefined = undefined -> = EntityConfigOf< RecordOrKind, N >[ 'defaultContext' ]; - -/** - * An entity record type associated with specified kind and name, sourced from PerPackageEntities. - */ -export type EntityRecordOf< - K extends Kind, - N extends Name, - C extends Context = DefaultContextOf< K, N > -> = Extract< EntityConfig< C >, { config: { kind: K; name: N } } >[ 'record' ]; - -/** - * A union of all known entity kinds. - */ -export type Kind = EntityConfig[ 'config' ][ 'kind' ]; -/** - * A union of all known entity names. - */ -export type Name = EntityConfig[ 'config' ][ 'name' ]; +export type EntityRecord< C extends Context = 'edit' > = + PerPackageEntityRecords< C >[ keyof PerPackageEntityRecords< C > ]; diff --git a/packages/core-data/src/entity-types/menu-location.ts b/packages/core-data/src/entity-types/menu-location.ts index 8e15908eee7b4e..9bc3e5f7189b0e 100644 --- a/packages/core-data/src/entity-types/menu-location.ts +++ b/packages/core-data/src/entity-types/menu-location.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -25,6 +24,6 @@ declare module './base-entity-records' { } } -export type MenuLocation< - C extends Context = DefaultContextOf< 'root', 'menuLocation' > -> = OmitNevers< _BaseEntityRecords.MenuLocation< C > >; +export type MenuLocation< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.MenuLocation< C > +>; diff --git a/packages/core-data/src/entity-types/nav-menu-item.ts b/packages/core-data/src/entity-types/nav-menu-item.ts index 6d0c29a1f6b566..26a4d07d1e075b 100644 --- a/packages/core-data/src/entity-types/nav-menu-item.ts +++ b/packages/core-data/src/entity-types/nav-menu-item.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; export type NavMenuItemType = | 'taxonomy' @@ -107,6 +106,6 @@ declare module './base-entity-records' { } } -export type NavMenuItem< - C extends Context = DefaultContextOf< 'root', 'menuItem' > -> = OmitNevers< _BaseEntityRecords.NavMenuItem< C > >; +export type NavMenuItem< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.NavMenuItem< C > +>; diff --git a/packages/core-data/src/entity-types/nav-menu.ts b/packages/core-data/src/entity-types/nav-menu.ts index 857cf674c9f8a6..4626281ce1172e 100644 --- a/packages/core-data/src/entity-types/nav-menu.ts +++ b/packages/core-data/src/entity-types/nav-menu.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -49,5 +48,6 @@ declare module './base-entity-records' { } } -export type NavMenu< C extends Context = DefaultContextOf< 'root', 'menu' > > = - OmitNevers< _BaseEntityRecords.NavMenu< C > >; +export type NavMenu< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.NavMenu< C > +>; diff --git a/packages/core-data/src/entity-types/page.ts b/packages/core-data/src/entity-types/page.ts index cd2859a9bca720..4bdf583fe48800 100644 --- a/packages/core-data/src/entity-types/page.ts +++ b/packages/core-data/src/entity-types/page.ts @@ -12,7 +12,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -140,5 +139,6 @@ declare module './base-entity-records' { } } -export type Page< C extends Context = DefaultContextOf< 'postType', 'page' > > = - OmitNevers< _BaseEntityRecords.Page< C > >; +export type Page< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Page< C > +>; diff --git a/packages/core-data/src/entity-types/plugin.ts b/packages/core-data/src/entity-types/plugin.ts index 569316a48ef41a..61954ec8535310 100644 --- a/packages/core-data/src/entity-types/plugin.ts +++ b/packages/core-data/src/entity-types/plugin.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -75,5 +74,6 @@ declare module './base-entity-records' { } export type PluginStatus = 'active' | 'inactive'; -export type Plugin< C extends Context = DefaultContextOf< 'root', 'plugin' > > = - OmitNevers< _BaseEntityRecords.Plugin< C > >; +export type Plugin< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Plugin< C > +>; diff --git a/packages/core-data/src/entity-types/post.ts b/packages/core-data/src/entity-types/post.ts index ebfc4bbd1bf606..bcde222281cf09 100644 --- a/packages/core-data/src/entity-types/post.ts +++ b/packages/core-data/src/entity-types/post.ts @@ -13,7 +13,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -149,5 +148,6 @@ declare module './base-entity-records' { } } -export type Post< C extends Context = DefaultContextOf< 'postType', 'post' > > = - OmitNevers< _BaseEntityRecords.Post< C > >; +export type Post< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Post< C > +>; diff --git a/packages/core-data/src/entity-types/settings.ts b/packages/core-data/src/entity-types/settings.ts index 6181baa60a81fa..23bd223571d39e 100644 --- a/packages/core-data/src/entity-types/settings.ts +++ b/packages/core-data/src/entity-types/settings.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -94,5 +93,6 @@ declare module './base-entity-records' { } } -export type Settings< C extends Context = DefaultContextOf< 'root', 'site' > > = - OmitNevers< _BaseEntityRecords.Settings< C > >; +export type Settings< C extends Context = 'view' > = OmitNevers< + _BaseEntityRecords.Settings< C > +>; diff --git a/packages/core-data/src/entity-types/sidebar.ts b/packages/core-data/src/entity-types/sidebar.ts index 8cf1bd12642bb8..923e0279a9a3f7 100644 --- a/packages/core-data/src/entity-types/sidebar.ts +++ b/packages/core-data/src/entity-types/sidebar.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -55,6 +54,6 @@ declare module './base-entity-records' { type SidebarStatus = 'active' | 'inactive'; -export type Sidebar< - C extends Context = DefaultContextOf< 'root', 'sidebar' > -> = OmitNevers< _BaseEntityRecords.Sidebar< C > >; +export type Sidebar< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Sidebar< C > +>; diff --git a/packages/core-data/src/entity-types/taxonomy.ts b/packages/core-data/src/entity-types/taxonomy.ts index 267eb6fd43b59c..5dd76758bc68d5 100644 --- a/packages/core-data/src/entity-types/taxonomy.ts +++ b/packages/core-data/src/entity-types/taxonomy.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -88,6 +87,6 @@ declare module './base-entity-records' { } } -export type Taxonomy< - C extends Context = DefaultContextOf< 'postType', 'taxonomy' > -> = OmitNevers< _BaseEntityRecords.Taxonomy< C > >; +export type Taxonomy< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Taxonomy< C > +>; diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts index 26d70a92007d5e..04904ae2501f00 100644 --- a/packages/core-data/src/entity-types/theme.ts +++ b/packages/core-data/src/entity-types/theme.ts @@ -4,7 +4,6 @@ import type { Context, PostFormat, RenderedText, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -222,5 +221,6 @@ declare module './base-entity-records' { } } -export type Theme< C extends Context = DefaultContextOf< 'root', 'theme' > > = - OmitNevers< _BaseEntityRecords.Theme< C > >; +export type Theme< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Theme< C > +>; diff --git a/packages/core-data/src/entity-types/type.ts b/packages/core-data/src/entity-types/type.ts index b0df93cd06bc4d..e6c0cce30d6ed1 100644 --- a/packages/core-data/src/entity-types/type.ts +++ b/packages/core-data/src/entity-types/type.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -76,5 +75,6 @@ declare module './base-entity-records' { } } -export type Type< C extends Context = DefaultContextOf< 'root', 'postType' > > = - OmitNevers< _BaseEntityRecords.Type< C > >; +export type Type< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Type< C > +>; diff --git a/packages/core-data/src/entity-types/user.ts b/packages/core-data/src/entity-types/user.ts index 92bbd0a1344f69..9de22efb3d7940 100644 --- a/packages/core-data/src/entity-types/user.ts +++ b/packages/core-data/src/entity-types/user.ts @@ -9,7 +9,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -110,5 +109,6 @@ declare module './base-entity-records' { } } -export type User< C extends Context = DefaultContextOf< 'root', 'user' > > = - OmitNevers< _BaseEntityRecords.User< C > >; +export type User< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.User< C > +>; diff --git a/packages/core-data/src/entity-types/widget-type.ts b/packages/core-data/src/entity-types/widget-type.ts index 372a25d4cefcc1..29c47d5a9635c1 100644 --- a/packages/core-data/src/entity-types/widget-type.ts +++ b/packages/core-data/src/entity-types/widget-type.ts @@ -4,7 +4,6 @@ import type { Context, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -33,6 +32,6 @@ declare module './base-entity-records' { } } -export type WidgetType< - C extends Context = DefaultContextOf< 'root', 'widgetType' > -> = OmitNevers< _BaseEntityRecords.WidgetType< C > >; +export type WidgetType< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WidgetType< C > +>; diff --git a/packages/core-data/src/entity-types/widget.ts b/packages/core-data/src/entity-types/widget.ts index b3bec1d6a12bc2..8509914891281d 100644 --- a/packages/core-data/src/entity-types/widget.ts +++ b/packages/core-data/src/entity-types/widget.ts @@ -4,7 +4,6 @@ import type { Context, ContextualField, OmitNevers } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -60,5 +59,6 @@ declare module './base-entity-records' { } } -export type Widget< C extends Context = DefaultContextOf< 'root', 'widget' > > = - OmitNevers< _BaseEntityRecords.Widget< C > >; +export type Widget< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.Widget< C > +>; diff --git a/packages/core-data/src/entity-types/wp-template-part.ts b/packages/core-data/src/entity-types/wp-template-part.ts index bbe92b3a34a3e6..466da11b437ef0 100644 --- a/packages/core-data/src/entity-types/wp-template-part.ts +++ b/packages/core-data/src/entity-types/wp-template-part.ts @@ -10,7 +10,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -90,6 +89,6 @@ declare module './base-entity-records' { } } -export type WpTemplatePart< - C extends Context = DefaultContextOf< 'postType', 'wp_template_part' > -> = OmitNevers< _BaseEntityRecords.WpTemplatePart< C > >; +export type WpTemplatePart< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WpTemplatePart< C > +>; diff --git a/packages/core-data/src/entity-types/wp-template.ts b/packages/core-data/src/entity-types/wp-template.ts index c134d5b9f167e0..544476bbb7f36e 100644 --- a/packages/core-data/src/entity-types/wp-template.ts +++ b/packages/core-data/src/entity-types/wp-template.ts @@ -10,7 +10,6 @@ import type { } from './helpers'; import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records'; -import type { DefaultContextOf } from './index'; declare module './base-entity-records' { export namespace BaseEntityRecords { @@ -90,6 +89,6 @@ declare module './base-entity-records' { } } -export type WpTemplate< - C extends Context = DefaultContextOf< 'postType', 'wp_template' > -> = OmitNevers< _BaseEntityRecords.WpTemplate< C > >; +export type WpTemplate< C extends Context = 'edit' > = OmitNevers< + _BaseEntityRecords.WpTemplate< C > +>; diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 05cace07c992b4..43fa4a0b3cd074 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -60,8 +60,6 @@ const storeConfig = () => ( { * Store definition for the code data namespace. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore - * - * @type {Object} */ export const store = createReduxStore( STORE_NAME, storeConfig() ); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 6de78fc47ee8c0..c45bede2305747 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -18,18 +18,7 @@ import { STORE_NAME } from './name'; import { getQueriedItems } from './queried-data'; import { DEFAULT_ENTITY_KEY } from './entities'; import { getNormalizedCommaSeparable, isRawAttribute } from './utils'; -import type { - Context, - DefaultContextOf, - EntityRecordOf, - KeyOf, - Kind, - KindOf, - Name, - NameOf, - User, - WpTemplate, -} from './entity-types'; +import type * as ET from './entity-types'; // This is an incomplete, high-level approximation of the State type. // It makes the selectors slightly more safe, but is intended to evolve @@ -41,7 +30,7 @@ interface State { blockPatternCategories: Array< unknown >; currentGlobalStylesId: string; currentTheme: string; - currentUser: User< 'edit' >; + currentUser: ET.User< 'edit' >; embedPreviews: Record< string, { html: string } >; entities: EntitiesState; themeBaseGlobalStyles: Record< string, Object >; @@ -50,19 +39,21 @@ interface State { users: UserState; } +type EntityRecordKey = string | number; + interface EntitiesState { config: EntityConfig[]; - records: Record< Kind, Record< Name, EntityState< Kind, Name > > >; + records: Record< string, Record< string, EntityState< ET.EntityRecord > > >; } -interface EntityState< K extends Kind, N extends Name > { - edits: Record< KeyOf< K, N >, Partial< EntityRecordOf< K, N > > >; - saving: Record< KeyOf< K, N >, { pending: boolean } >; +interface EntityState< EntityRecord extends ET.EntityRecord > { + edits: Record< string, Partial< EntityRecord > >; + saving: Record< string, { pending: boolean } >; } interface EntityConfig { - name: Name; - kind: Kind; + name: string; + kind: string; } interface UndoState extends Array< Object > { @@ -71,31 +62,16 @@ interface UndoState extends Array< Object > { } interface UserState { - queries: Record< string, GenericRecordKey[] >; - byId: Record< GenericRecordKey, User< 'edit' > >; + queries: Record< string, EntityRecordKey[] >; + byId: Record< EntityRecordKey, ET.User< 'edit' > >; } -type GenericRecordKey = number | string; -type EntityRecord = any; type Optional< T > = T | undefined; /** * HTTP Query parameters sent with the API request to fetch the entity records. */ -export type EntityQuery< - C extends Context, - WithFields extends boolean = true -> = Omit< Record< string, any >, '_fields' > & { - context?: C; -} & ( WithFields extends true - ? { - /** - * The requested fields. If specified, the REST API will remove from the response - * any fields not on that list. - */ - _fields: string[]; - } - : {} ); +type GetRecordsHttpQuery = Record< string, any >; /** * Shared reference to an empty object for cases where it is important to avoid @@ -116,7 +92,7 @@ const EMPTY_OBJECT = {}; * @return Whether a request is in progress for an embed preview. */ export const isRequestingEmbedPreview = createRegistrySelector( - ( select ) => + ( select: any ) => ( state: State, url: string ): boolean => { return select( STORE_NAME ).isResolving( 'getEmbedPreview', [ url, @@ -136,8 +112,8 @@ export const isRequestingEmbedPreview = createRegistrySelector( */ export function getAuthors( state: State, - query?: EntityQuery< any > -): User< 'edit' >[] { + query?: GetRecordsHttpQuery +): ET.User[] { deprecated( "select( 'core' ).getAuthors()", { since: '5.9', alternative: "select( 'core' ).getUsers({ who: 'authors' })", @@ -157,7 +133,7 @@ export function getAuthors( * * @return Current user object. */ -export function getCurrentUser( state: State ): User< 'edit' > { +export function getCurrentUser( state: State ): ET.User< 'edit' > { return state.currentUser; } @@ -170,7 +146,7 @@ export function getCurrentUser( state: State ): User< 'edit' > { * @return Users list. */ export const getUserQueryResults = createSelector( - ( state: State, queryID: string ): User< 'edit' >[] => { + ( state: State, queryID: string ): ET.User< 'edit' >[] => { const queryResults = state.users.queries[ queryID ]; return map( queryResults, ( id ) => state.users.byId[ id ] ); @@ -190,7 +166,7 @@ export const getUserQueryResults = createSelector( * * @return Array of entities with config matching kind. */ -export function getEntitiesByKind( state: State, kind: Kind ): Array< any > { +export function getEntitiesByKind( state: State, kind: string ): Array< any > { deprecated( "wp.data.select( 'core' ).getEntitiesByKind()", { since: '6.0', alternative: "wp.data.select( 'core' ).getEntitiesConfig()", @@ -206,7 +182,7 @@ export function getEntitiesByKind( state: State, kind: Kind ): Array< any > { * * @return Array of entities with config matching kind. */ -export function getEntitiesConfig( state: State, kind: Kind ): Array< any > { +export function getEntitiesConfig( state: State, kind: string ): Array< any > { return filter( state.entities.config, { kind } ); } @@ -220,7 +196,7 @@ export function getEntitiesConfig( state: State, kind: Kind ): Array< any > { * * @return Entity config */ -export function getEntity( state: State, kind: Kind, name: Name ): any { +export function getEntity( state: State, kind: string, name: string ): any { deprecated( "wp.data.select( 'core' ).getEntity()", { since: '6.0', alternative: "wp.data.select( 'core' ).getEntityConfig()", @@ -237,56 +213,14 @@ export function getEntity( state: State, kind: Kind, name: Name ): any { * * @return Entity config */ -export function getEntityConfig( state: State, kind: Kind, name: Name ): any { +export function getEntityConfig( + state: State, + kind: string, + name: string +): any { return find( state.entities.config, { kind, name } ); } -/** - * GetEntityRecord is declared as an *interface*, but it actually describes - * the specifies the getEntityRecord *function* signature. It may seem unusual, - * but it's just how TypeScript implements function overloading. - * - * More accurately, GetEntityRecord distinguishes between two different signatures - * the getEntityRecord selector has: - * - * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C > - * 2. When query._fields is given, the returned type is Partial> - * - * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132) - * we can't use a single function signature with a return type such as: - * - * Fields extends undefined - * ? EntityRecordOf< K, N, C > - * : Partial< EntityRecordOf< K, N, C > > - */ -interface GetEntityRecord { - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - key: KeyOf< K, N >, - query: EntityQuery< C, true > - ): Partial< EntityRecordOf< K, N, C > > | null | undefined; - - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - key: KeyOf< K, N >, - query?: EntityQuery< C, false > - ): EntityRecordOf< K, N, C > | null | undefined; -} - /** * Returns the Entity's record object by key. Returns `null` if the value is not * yet received, undefined if the value entity is known to not exist, or the @@ -301,19 +235,18 @@ interface GetEntityRecord { * * @return Record. */ -export const getEntityRecord: GetEntityRecord = createSelector( +export const getEntityRecord = createSelector( < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > + EntityRecord extends + | ET.EntityRecord< any > + | Partial< ET.EntityRecord< any > > >( state: State, - kind: K, - name: N, - key: KeyOf< R >, - query - ) => { + kind: string, + name: string, + key: EntityRecordKey, + query?: GetRecordsHttpQuery + ): EntityRecord | undefined => { const queriedState = get( state.entities.records, [ kind, name, @@ -342,7 +275,7 @@ export const getEntityRecord: GetEntityRecord = createSelector( const value = get( item, field ); set( filteredItem, field, value ); } - return filteredItem; + return filteredItem as EntityRecord; } return item; @@ -381,10 +314,9 @@ export const getEntityRecord: GetEntityRecord = createSelector( * @return Record. */ export function __experimentalGetEntityRecordNoResolver< - K extends Kind, - N extends Name ->( state: State, kind: K, name: N, key: KeyOf< K, N > ) { - return getEntityRecord( state, kind, name, key ); + EntityRecord extends ET.EntityRecord< any > +>( state: State, kind: string, name: string, key: EntityRecordKey ) { + return getEntityRecord< EntityRecord >( state, kind, name, key ); } /** @@ -399,13 +331,18 @@ export function __experimentalGetEntityRecordNoResolver< * @return Object with the entity's raw attributes. */ export const getRawEntityRecord = createSelector( - < K extends Kind, N extends Name >( + < EntityRecord extends ET.EntityRecord< any > >( state: State, - kind: K, - name: N, - key: KeyOf< K, N > + kind: string, + name: string, + key: EntityRecordKey ): EntityRecord | undefined => { - const record = getEntityRecord( state, kind, name, key ); + const record = getEntityRecord< EntityRecord >( + state, + kind, + name, + key + ); return ( record && Object.keys( record ).reduce( ( accumulator, _key ) => { @@ -424,15 +361,15 @@ export const getRawEntityRecord = createSelector( accumulator[ _key ] = record[ _key ]; } return accumulator; - }, {} ) + }, {} as any ) ); }, ( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey, - query?: EntityQuery< any > + kind: string, + name: string, + recordId: EntityRecordKey, + query?: GetRecordsHttpQuery ) => { const context = query?.context ?? 'default'; return [ @@ -468,59 +405,15 @@ export const getRawEntityRecord = createSelector( * * @return Whether entity records have been received. */ -export function hasEntityRecords< - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > ->( state: State, kind: K, name: N, query?: EntityQuery< C > ): boolean { +export function hasEntityRecords( + state: State, + kind: string, + name: string, + query?: GetRecordsHttpQuery +): boolean { return Array.isArray( getEntityRecords( state, kind, name, query ) ); } -/** - * GetEntityRecord is declared as an *interface*, but it actually describes - * the specifies the getEntityRecord *function* signature. It may seem unusual, - * but it's just how TypeScript implements function overloading. - * - * More accurately, GetEntityRecord distinguishes between two different signatures - * the getEntityRecord selector has: - * - * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C >[] - * 2. When query._fields is given, the returned type is Partial>[] - * - * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132) - * we can't use a single function signature with a return type such as: - * - * Fields extends undefined - * ? EntityRecordOf< K, N, C >[] - * : Partial< EntityRecordOf< K, N, C > >[] - */ -interface GetEntityRecords { - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - query: EntityQuery< C, true > - ): Partial< EntityRecordOf< K, N, C > >[] | null | undefined; - - < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > - >( - state: State, - kind: K, - name: N, - query?: EntityQuery< C, false > - ): EntityRecordOf< K, N, C >[] | null | undefined; -} - /** * Returns the Entity's records. * @@ -532,17 +425,16 @@ interface GetEntityRecords { * * @return Records. */ -export const getEntityRecords: GetEntityRecords = < - R extends EntityRecordOf< K, N >, - C extends Context = DefaultContextOf< R >, - K extends Kind = KindOf< R >, - N extends Name = NameOf< R > +export const getEntityRecords = < + EntityRecord extends + | ET.EntityRecord< any > + | Partial< ET.EntityRecord< any > > >( state: State, - kind: K, - name: N, - query -) => { + kind: string, + name: string, + query?: GetRecordsHttpQuery +): EntityRecord[] | null => { // Queried data state is prepopulated for all known entities. If this is not // assigned for the given parameters, then it is known to not exist. const queriedState = get( state.entities.records, [ @@ -558,9 +450,9 @@ export const getEntityRecords: GetEntityRecords = < type DirtyEntityRecord = { title: string; - key: GenericRecordKey; - name: Name; - kind: Kind; + key: EntityRecordKey; + name: string; + kind: string; }; /** * Returns the list of dirty entity records. @@ -575,64 +467,44 @@ export const __experimentalGetDirtyEntityRecords = createSelector( entities: { records }, } = state; const dirtyRecords: DirtyEntityRecord[] = []; - ( Object.keys( records ) as Kind[] ).forEach( - < K extends Kind >( kind: K ) => { - ( Object.keys( records[ kind ] ) as Name[] ).forEach( - < N extends Name >( name: N ) => { - const primaryKeys = ( - Object.keys( - records[ kind ][ name ].edits - ) as KeyOf< K, N >[] - ).filter( - ( primaryKey ) => - // The entity record must exist (not be deleted), - // and it must have edits. - getEntityRecord( - state, - kind, - name, - primaryKey - ) && - hasEditsForEntityRecord( - state, - kind, - name, - primaryKey - ) - ); + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { + const primaryKeys = ( + Object.keys( records[ kind ][ name ].edits ) as string[] + ).filter( + ( primaryKey ) => + // The entity record must exist (not be deleted), + // and it must have edits. + getEntityRecord( state, kind, name, primaryKey ) && + hasEditsForEntityRecord( state, kind, name, primaryKey ) + ); - if ( primaryKeys.length ) { - const entityConfig = getEntityConfig( - state, - kind, - name - ); - primaryKeys.forEach( ( primaryKey ) => { - const entityRecord = getEditedEntityRecord( - state, - kind, - name, - primaryKey - ); - dirtyRecords.push( { - // We avoid using primaryKey because it's transformed into a string - // when it's used as an object key. - key: entityRecord[ + if ( primaryKeys.length ) { + const entityConfig = getEntityConfig( state, kind, name ); + primaryKeys.forEach( ( primaryKey ) => { + const entityRecord = getEditedEntityRecord( + state, + kind, + name, + primaryKey + ); + dirtyRecords.push( { + // We avoid using primaryKey because it's transformed into a string + // when it's used as an object key. + key: entityRecord + ? entityRecord[ entityConfig.key || DEFAULT_ENTITY_KEY - ], - title: - entityConfig?.getTitle?.( - entityRecord - ) || '', - name, - kind, - } ); - } ); - } - } - ); - } - ); + ] + : undefined, + title: + entityConfig?.getTitle?.( entityRecord ) || '', + name, + kind, + } ); + } ); + } + } ); + } ); return dirtyRecords; }, @@ -652,55 +524,40 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( entities: { records }, } = state; const recordsBeingSaved: DirtyEntityRecord[] = []; - ( Object.keys( records ) as Kind[] ).forEach( - < K extends Kind >( kind: K ) => { - ( Object.keys( records[ kind ] ) as Name[] ).forEach( - < N extends Name >( name: N ) => { - const primaryKeys = ( - Object.keys( - records[ kind ][ name ].saving - ) as KeyOf< K, N >[] - ).filter( ( primaryKey ) => - isSavingEntityRecord( - state, - kind, - name, - primaryKey - ) - ); + Object.keys( records ).forEach( ( kind ) => { + Object.keys( records[ kind ] ).forEach( ( name ) => { + const primaryKeys = ( + Object.keys( records[ kind ][ name ].saving ) as string[] + ).filter( ( primaryKey ) => + isSavingEntityRecord( state, kind, name, primaryKey ) + ); - if ( primaryKeys.length ) { - const entityConfig = getEntityConfig( - state, - kind, - name - ); - primaryKeys.forEach( ( primaryKey ) => { - const entityRecord = getEditedEntityRecord( - state, - kind, - name, - primaryKey - ); - recordsBeingSaved.push( { - // We avoid using primaryKey because it's transformed into a string - // when it's used as an object key. - key: entityRecord[ + if ( primaryKeys.length ) { + const entityConfig = getEntityConfig( state, kind, name ); + primaryKeys.forEach( ( primaryKey ) => { + const entityRecord = getEditedEntityRecord( + state, + kind, + name, + primaryKey + ); + recordsBeingSaved.push( { + // We avoid using primaryKey because it's transformed into a string + // when it's used as an object key. + key: entityRecord + ? entityRecord[ entityConfig.key || DEFAULT_ENTITY_KEY - ], - title: - entityConfig?.getTitle?.( - entityRecord - ) || '', - name, - kind, - } ); - } ); - } - } - ); - } - ); + ] + : undefined, + title: + entityConfig?.getTitle?.( entityRecord ) || '', + name, + kind, + } ); + } ); + } + } ); + } ); return recordsBeingSaved; }, ( state ) => [ state.entities.records ] @@ -716,11 +573,11 @@ export const __experimentalGetEntitiesBeingSaved = createSelector( * * @return The entity record's edits. */ -export function getEntityRecordEdits< K extends Kind, N extends Name >( +export function getEntityRecordEdits( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): Optional< any > { return get( state.entities.records, [ kind, @@ -745,11 +602,11 @@ export function getEntityRecordEdits< K extends Kind, N extends Name >( * @return The entity record's non transient edits. */ export const getEntityRecordNonTransientEdits = createSelector( - < K extends Kind, N extends Name >( + ( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): Optional< any > => { const { transientEdits } = getEntityConfig( state, kind, name ) || {}; const edits = getEntityRecordEdits( state, kind, name, recordId ) || {}; @@ -763,7 +620,7 @@ export const getEntityRecordNonTransientEdits = createSelector( return acc; }, {} ); }, - ( state: State, kind: Kind, name: Name, recordId: GenericRecordKey ) => [ + ( state: State, kind: string, name: string, recordId: EntityRecordKey ) => [ state.entities.config, get( state.entities.records, [ kind, name, 'edits', recordId ] ), ] @@ -780,11 +637,11 @@ export const getEntityRecordNonTransientEdits = createSelector( * * @return Whether the entity record has edits or not. */ -export function hasEditsForEntityRecord< K extends Kind, N extends Name >( +export function hasEditsForEntityRecord( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return ( isSavingEntityRecord( state, kind, name, recordId ) || @@ -805,21 +662,21 @@ export function hasEditsForEntityRecord< K extends Kind, N extends Name >( * @return The entity record, merged with its edits. */ export const getEditedEntityRecord = createSelector( - < K extends Kind, N extends Name >( + < EntityRecord extends ET.EntityRecord< any > >( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > - ): EntityRecord | undefined => ( { + kind: string, + name: string, + recordId: EntityRecordKey + ): ET.Updatable< EntityRecord > | undefined => ( { ...getRawEntityRecord( state, kind, name, recordId ), ...getEntityRecordEdits( state, kind, name, recordId ), } ), ( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey, - query?: EntityQuery< any > + kind: string, + name: string, + recordId: EntityRecordKey, + query?: GetRecordsHttpQuery ) => { const context = query?.context ?? 'default'; return [ @@ -857,9 +714,9 @@ export const getEditedEntityRecord = createSelector( */ export function isAutosavingEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { const { pending, isAutosave } = get( state.entities.records, @@ -879,15 +736,15 @@ export function isAutosavingEntityRecord( * * @return Whether the entity record is saving or not. */ -export function isSavingEntityRecord< K extends Kind, N extends Name >( +export function isSavingEntityRecord( state: State, - kind: K, - name: N, - recordId: KeyOf< K, N > + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return get( state.entities.records, - [ kind, name, 'saving', recordId as GenericRecordKey, 'pending' ], + [ kind, name, 'saving', recordId as EntityRecordKey, 'pending' ], false ); } @@ -904,9 +761,9 @@ export function isSavingEntityRecord< K extends Kind, N extends Name >( */ export function isDeletingEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean { return get( state.entities.records, @@ -927,9 +784,9 @@ export function isDeletingEntityRecord( */ export function getLastEntitySaveError( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): any { return get( state.entities.records, [ kind, @@ -952,9 +809,9 @@ export function getLastEntitySaveError( */ export function getLastEntityDeleteError( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): any { return get( state.entities.records, [ kind, @@ -1115,7 +972,7 @@ export function canUser( state: State, action: string, resource: string, - id?: GenericRecordKey + id?: EntityRecordKey ): boolean | undefined { const key = [ action, resource, id ].filter( Boolean ).join( '/' ); return get( state, [ 'userPermissions', key ] ); @@ -1138,9 +995,9 @@ export function canUser( */ export function canUserEditEntityRecord( state: State, - kind: Kind, - name: Name, - recordId: GenericRecordKey + kind: string, + name: string, + recordId: EntityRecordKey ): boolean | undefined { const entityConfig = getEntityConfig( state, kind, name ); if ( ! entityConfig ) { @@ -1166,7 +1023,7 @@ export function canUserEditEntityRecord( export function getAutosaves( state: State, postType: string, - postId: GenericRecordKey + postId: EntityRecordKey ): Array< any > | undefined { return state.autosaves[ postId ]; } @@ -1181,18 +1038,18 @@ export function getAutosaves( * * @return The autosave for the post and author. */ -export function getAutosave( +export function getAutosave< EntityRecord extends ET.EntityRecord< any > >( state: State, postType: string, - postId: GenericRecordKey, - authorId: GenericRecordKey + postId: EntityRecordKey, + authorId: EntityRecordKey ): EntityRecord | undefined { if ( authorId === undefined ) { return; } const autosaves = state.autosaves[ postId ]; - return find( autosaves, { author: authorId } ); + return find( autosaves, { author: authorId } ) as EntityRecord | undefined; } /** @@ -1209,7 +1066,7 @@ export const hasFetchedAutosaves = createRegistrySelector( ( state: State, postType: string, - postId: GenericRecordKey + postId: EntityRecordKey ): boolean => { return select( STORE_NAME ).hasFinishedResolution( 'getAutosaves', [ postType, @@ -1257,21 +1114,25 @@ export const getReferenceByDistinctEdits = createSelector( export function __experimentalGetTemplateForLink( state: State, link: string -): WpTemplate< 'edit' > | null { - const records = getEntityRecords( state, 'postType', 'wp_template', { - 'find-template': link, - } ); +): Optional< ET.Updatable< ET.WpTemplate > > | null { + const records = getEntityRecords< ET.WpTemplate >( + state, + 'postType', + 'wp_template', + { + 'find-template': link, + } + ); - const template = records?.length ? records[ 0 ] : null; - if ( template ) { - return getEditedEntityRecord( + if ( records?.length ) { + return getEditedEntityRecord< ET.WpTemplate >( state, 'postType', 'wp_template', - template.id + records[ 0 ].id ); } - return template; + return null; } /**