Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity configuration types #40995

Merged
merged 1 commit into from
May 23, 2022
Merged

Entity configuration types #40995

merged 1 commit into from
May 23, 2022

Conversation

adamziel
Copy link
Contributor

@adamziel adamziel commented May 11, 2022

What?

This PR adds Entity configuration types to core-data/src/entities.ts.

It is an alternative to #40024. See also #39025, a mega branch that proposes TypeScript signatures for all @wordpress/core-data selectors.

Why?

Consider the getEntityRecord selector:

/**
* 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
* entity object if it exists and is received.
*
* @param {Object} state State tree
* @param {string} kind Entity kind.
* @param {string} name Entity name.
* @param {number} key Record's key
* @param {?Object} query Optional query.
*
* @return {Object|undefined} Record.
*/
export const getEntityRecord = createSelector(
( state, kind, name, key, query ) => {

Different entity kinds, like root, postType, taxonomy, are associated with different entity names. For example, kind: root, name: plugin is a valid combination, but kind: postType, name: plugin is not. Other valid combinations are configured in the entities.ts file via a JavaScript object.

An ideal getEntityRecord function signature would only accept valid combinations, then require the key to be either number or a string, and return the list of corresponding entity records.

Again, ideally, there would only be a single source of truth for all the information. That's exactly what this PR enables by introducing the Entity types:

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',
};

// PluginEntity['config']['kind'] is "root"
// PluginEntity['defaultContext'] is "edit"

That PluginEntity type can then be reused to build the getEntityRecord signature.

Why write the kind and name twice?

This is a trade-off. I've spent hours exploring the available options with @dmsnell and we concluded that it's only possibl to either:

  1. Infer it from const config = and miss out on autocompletion, config type validation, and require using as const. Reuse the JS entities configuration in the TypeScript type system #40024 explored that
  2. Infer it from const config = and have all of the above, but at the cost of using super complex type plumbing. This TS playground explores that
  3. Type it explicitly, have autocompletion and type validation without complex types, but duplicate a few lines of code.

This PR implements the latter approach.

Why have a new config type?

Good question! It's needed because entity configuration comes from three different sources:

  • Hardcoded types in entities.ts
  • Implicit knowledge about the entities loaded using the entity loaders, e.g. that the default context of all kind=postType is edit as seen in loadPostTypeEntities
  • Any additional configuration provided by Gutenberg extenders.

A somewhat eccentric metaphor would be picturing it as a MySQL query that joins three tables:

SELECT
	kind,
	name,
	recordType,
	DEFAULT(key, 'id') as key,
	DEFAULT(baseURLParams['context'], 'view') as defaultContext
FROM rootEntitiesConfig_declared_in_entities_js d
INNER JOIN record_types r ON d.kind = r.kind AND d.name = r.name
UNION
SELECT VALUES
	ROW ( 'postType', 'post', 'id', 'Post', 'edit' ),
	ROW ( 'postType', 'page', 'id', 'Page', 'edit' ),
	ROW ( 'postType', 'wp_template', 'WpTemplate', 'id', 'edit' ),
	ROW ( 'postType', 'wp_template_part', 'WpTemplatePart', 'id', 'edit' ),
AS what_we_intrinsically_know_from_additionalEntityConfigLoaders
UNION
SELECT
	kind,
	name,
	recordType,
	key,
	defaultContext
FROM additional_configuration_provided_by_extenders

A common format like the PluginEntity makes reasoning about all these data sources much easier down the road, e.g. it enables the following succinct formulation of the Kind type:

export type KindOf< R extends EntityRecord > = EntityConfigOf< R >[ 'kind' ];

Test plan

Confirm the checks are green and that no entity configuration got changed when I split the large array into atomic declarations. The changes here should only affect the type system so there is nothing to test in the browser.

cc @dmsnell @jsnajdr @youknowriad @sarayourfriend @getdave @draganescu @scruffian

@adamziel adamziel added [Type] Code Quality Issues or PRs that relate to code quality [Package] Core data /packages/core-data Developer Experience Ideas about improving block and theme developer experience labels May 11, 2022
@adamziel adamziel force-pushed the ts/core-data-config-entity-types branch from 8cb32c0 to 9ab0989 Compare May 11, 2022 11:20
@adamziel adamziel force-pushed the ts/core-data-config-entity-types branch from 9ab0989 to a9d9b7d Compare May 11, 2022 11:24
@adamziel adamziel marked this pull request as ready for review May 11, 2022 11:34
@adamziel adamziel requested a review from nerrad as a code owner May 11, 2022 11:34
@adamziel adamziel changed the title Add types for the entity configuration Entity configuration types May 11, 2022
Copy link
Member

@dmsnell dmsnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to hold this up, but I'd like to see some further exploration after this merges to move the individual configs into their entity type files rather than having them all here (as we did with entity-types).

Thanks for working so hard on this.

@adamziel adamziel merged commit f31e8f0 into trunk May 23, 2022
@adamziel adamziel deleted the ts/core-data-config-entity-types branch May 23, 2022 09:42
@github-actions github-actions bot added this to the Gutenberg 13.4 milestone May 23, 2022
westonruter added a commit that referenced this pull request May 23, 2022
…p-tests-config

* 'trunk' of github.com:WordPress/gutenberg: (88 commits)
  Components: refactor `AlignmentMatrixControl` to pass `exhaustive-deps` (#41167)
  [RNMobile] Add 'Insert from URL' option to Image block (#40334)
  [RNMobile] Improvements to Getting Started Guides (#40964)
  Post Author Name: Add to and from Post Author transformations (#41151)
  CheckboxControl: Add unit tests (#41165)
  Improve inline documentation (#41209)
  Mobile Release v1.76.1 (#41196)
  Use explicit type definitions for entity configuration (#40995)
  Scripts: Convert file extension to js in `block.json` during build (#41068)
  Reflects revert in 6446878 (#41221)
  get_style_nodes should be compatible with parent method. (#41217)
  Gallery: Opt-in to axial (column/row) block spacing controls (#41175)
  Table of Contents block: convert line breaks to spaces in headings. (#41206)
  Add support for button elements to theme.json (#40260)
  Global Styles: Load block CSS conditionally (#41160)
  Update URL (#41188)
  Improve autocompleter performance (#41197)
  Site Editor: Set min-width for styles preview (#41198)
  Remove Navigation Editor screen from experiments page (#40878)
  Fix broken Page title for pages created inline within in Nav block (#41063)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Developer Experience Ideas about improving block and theme developer experience [Package] Core data /packages/core-data [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants