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

[core-data] Document and add types for dynamic actions and selectors. #67668

Open
wants to merge 23 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d06bc58
[core-data] Document and add types for dynamic actions and selectors.
manzoorwanijk Nov 14, 2024
b3778a3
Now that things are typed, we don't expect an error
manzoorwanijk Nov 14, 2024
0b72ca3
Put definitions first to allow it be overridden
manzoorwanijk Nov 14, 2024
3928817
Use namespaces to avoid direct imports
manzoorwanijk Nov 14, 2024
9280cce
Merge remote-tracking branch 'upstream/trunk' into update/document-dy…
manzoorwanijk Nov 15, 2024
9f6b4e3
Merge branch 'trunk' into update/document-dynamic-actions-selectors-f…
manzoorwanijk Dec 11, 2024
c583805
Remove unnecessary `ts-expect-error`
manzoorwanijk Dec 11, 2024
8bf0ea5
Add notice for new entities
manzoorwanijk Dec 12, 2024
0de4023
Merge branch 'trunk' into update/document-dynamic-actions-selectors-f…
manzoorwanijk Dec 12, 2024
0096e00
Use existing Type instead of new PostType
manzoorwanijk Dec 18, 2024
a6612e6
Dynamically create entity selectors and actions
manzoorwanijk Dec 18, 2024
bea1374
Remove unnecessary comment
manzoorwanijk Dec 18, 2024
5293486
Export base type as UnstableBase
manzoorwanijk Dec 18, 2024
105423a
Add template related types to base
manzoorwanijk Dec 18, 2024
1a45990
Get rid of one more @ts-expect-error
manzoorwanijk Dec 18, 2024
fa2a98b
Fix Site, Status and Revision types
manzoorwanijk Dec 18, 2024
5a09cec
Merge branch 'trunk' into update/document-dynamic-actions-selectors-f…
manzoorwanijk Dec 27, 2024
1f5bd67
Add GlobalStyles
manzoorwanijk Jan 3, 2025
ca6550a
Merge branch 'trunk' into update/document-dynamic-actions-selectors-f…
manzoorwanijk Jan 3, 2025
7b23643
Disable plural for global styles
manzoorwanijk Jan 3, 2025
465521b
Add a note about "GlobalStyles"
manzoorwanijk Jan 3, 2025
a4fea0b
Fix type for gmt_offset
manzoorwanijk Jan 3, 2025
70f681e
Export and use TemplatePartArea
manzoorwanijk Jan 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions packages/core-data/src/dynamic-entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Internal dependencies
*/
import type { GetRecordsHttpQuery, State } from './selectors';
import type * as ET from './entity-types';

export type WPEntityTypes< C extends ET.Context = 'edit' > = {
Comment: ET.Comment< C >;
GlobalStyles: ET.GlobalStylesRevision< C >;
Media: ET.Attachment< C >;
Menu: ET.NavMenu< C >;
MenuItem: ET.NavMenuItem< C >;
MenuLocation: ET.MenuLocation< C >;
Plugin: ET.Plugin< C >;
PostType: ET.Type< C >;
Revision: ET.PostRevision< C >;
Sidebar: ET.Sidebar< C >;
Site: ET.Settings< C >;
Status: ET.PostStatusObject< C >;
Taxonomy: ET.Taxonomy< C >;
Theme: ET.Theme< C >;
UnstableBase: ET.UnstableBase< C >;
User: ET.User< C >;
Widget: ET.Widget< C >;
WidgetType: ET.WidgetType< C >;
};

/**
* A simple utility that pluralizes a string.
* Converts:
* - "post" to "posts"
* - "taxonomy" to "taxonomies"
* - "media" to "mediaItems"
* - "status" to "statuses"
*
* It does not pluralize "GlobalStyles" due to lack of clarity about it at time of writing.
*/
type PluralizeEntity< T extends string > = T extends 'GlobalStyles'
? never
: T extends 'Media'
? 'MediaItems'
: T extends 'Status'
? 'Statuses'
: T extends `${ infer U }y`
? `${ U }ies`
: `${ T }s`;

/**
* A simple utility that singularizes a string.
*
* Converts:
* - "posts" to "post"
* - "taxonomies" to "taxonomy"
* - "mediaItems" to "media"
* - "statuses" to "status"
*/
type SingularizeEntity< T extends string > = T extends 'MediaItems'
? 'Media'
: T extends 'Statuses'
? 'Status'
: T extends `${ infer U }ies`
? `${ U }y`
: T extends `${ infer U }s`
? U
: T;

export type SingularGetters = {
[ Key in `get${ keyof WPEntityTypes }` ]: (
state: State,
id: number | string,
query?: GetRecordsHttpQuery
) => WPEntityTypes[ Key extends `get${ infer E }` ? E : never ] | undefined;
};

export type PluralGetters = {
[ Key in `get${ PluralizeEntity< keyof WPEntityTypes > }` ]: (
state: State,
query?: GetRecordsHttpQuery
) => Array<
WPEntityTypes[ Key extends `get${ infer E }`
? SingularizeEntity< E >
: never ]
> | null;
};

type ActionOptions = {
throwOnError?: boolean;
};

type DeleteRecordsHttpQuery = Record< string, any >;

export type SaveActions = {
[ Key in `save${ keyof WPEntityTypes }` ]: (
manzoorwanijk marked this conversation as resolved.
Show resolved Hide resolved
data: Partial<
WPEntityTypes[ Key extends `save${ infer E }` ? E : never ]
>,
options?: ActionOptions
) => Promise< void >;
};

export type DeleteActions = {
[ Key in `delete${ keyof WPEntityTypes }` ]: (
id: number | string,
query?: DeleteRecordsHttpQuery,
options?: ActionOptions
) => Promise< void >;
};

export let dynamicActions: SaveActions & DeleteActions;

export let dynamicSelectors: SingularGetters & PluralGetters;
Comment on lines +109 to +111
Copy link
Member

Choose a reason for hiding this comment

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

Is a let necessary for some reason? Can't we just use const?

Copy link
Member Author

Choose a reason for hiding this comment

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

For const, you need to assign the value. Since the value is actually undefined and only the type is set, it's fine.

84 changes: 84 additions & 0 deletions packages/core-data/src/entity-types/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Internal dependencies
*/
import type { Context, OmitNevers } from './helpers';
import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records';

export type TemplatePartArea = {
area: string;
label: string;
icon: string;
description: string;
};

export type TemplateType = {
title: string;
description: string;
slug: string;
};

declare module './base-entity-records' {
export namespace BaseEntityRecords {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
export interface Base< C extends Context > {
/**
* Site description.
*/
description: string;

/**
* GMT offset for the site.
*/
gmt_offset: string;

/**
* Home URL.
*/
home: string;

/**
* Site title
*/
name: string;

/**
* Site icon ID.
*/
site_icon?: number;
Copy link
Member

Choose a reason for hiding this comment

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

Confirming that regardless of its wp_options origin, this one is actually a number because it gets additional treatment.


/**
* Site icon URL.
*/
site_icon_url: string;

/**
* Site logo ID.
*/
site_logo?: number;
Copy link
Member

Choose a reason for hiding this comment

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

Confirming that regardless of its wp_options origin, this one is actually a number because it gets additional treatment.


/**
* Site timezone string.
*/
timezone_string: string;

/**
* Site URL.
*/
url: string;

/**
* Default template part areas.
*/
default_template_part_areas?: Array< TemplatePartArea >;

/**
* Default template types
*/
default_template_types?: Array< TemplateType >;
}
}
}

export type Base< C extends Context = 'edit' > = OmitNevers<
_BaseEntityRecords.Base< C >
>;
10 changes: 9 additions & 1 deletion packages/core-data/src/entity-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import type { Context, Updatable } from './helpers';
import type { Attachment } from './attachment';
import type { Base, TemplatePartArea, TemplateType } from './base';
import type { Comment } from './comment';
import type { GlobalStylesRevision } from './global-styles-revision';
import type { MenuLocation } from './menu-location';
Expand All @@ -11,6 +12,7 @@ import type { NavMenuItem } from './nav-menu-item';
import type { Page } from './page';
import type { Plugin } from './plugin';
import type { Post } from './post';
import type { PostStatusObject } from './post-status';
import type { PostRevision } from './post-revision';
import type { Settings } from './settings';
import type { Sidebar } from './sidebar';
Expand All @@ -27,6 +29,7 @@ export type { BaseEntityRecords } from './base-entity-records';

export type {
Attachment,
Base as UnstableBase,
Comment,
Context,
GlobalStylesRevision,
Expand All @@ -37,13 +40,16 @@ export type {
Plugin,
Post,
PostRevision,
PostStatusObject,
Settings,
Sidebar,
Taxonomy,
TemplatePartArea,
TemplateType,
Theme,
Type,
Updatable,
User,
Type,
Widget,
WidgetType,
WpTemplate,
Expand Down Expand Up @@ -84,6 +90,7 @@ export type {
*/
export interface PerPackageEntityRecords< C extends Context > {
core:
| Base< C >
| Attachment< C >
| Comment< C >
| GlobalStylesRevision< C >
Expand All @@ -93,6 +100,7 @@ export interface PerPackageEntityRecords< C extends Context > {
| Page< C >
| Plugin< C >
| Post< C >
| PostStatusObject< C >
| PostRevision< C >
Comment on lines +103 to 104
Copy link
Member

Choose a reason for hiding this comment

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

Same as above, with regards to keeping the alphabetic order

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Thanks

| Settings< C >
| Sidebar< C >
Expand Down
56 changes: 56 additions & 0 deletions packages/core-data/src/entity-types/post-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Internal dependencies
*/
import type { Context, OmitNevers } from './helpers';
import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records';

declare module './base-entity-records' {
export namespace BaseEntityRecords {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
export interface PostStatusObject< C extends Context > {
/**
* The title for the status.
*/
name: string;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

It's the same for user object but those fields are actually present because those are actually present in the item schema.

https://github.com/WordPress/wordpress-develop/blob/016bbeca9bfe96b3e024441059b76a3ad7d31df9/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php#L998-L1076


/**
* Whether posts with this status should be private.
*/
private: boolean;

/**
* Whether posts with this status should be protected.
*/
protected: boolean;

/**
* Whether posts of this status should be shown in the front end of the site.
*/
public: boolean;

/**
* Whether posts with this status should be publicly-queryable.
*/
queryable: boolean;

/**
* Whether to include posts in the edit listing for their post type.
*/
show_in_list: boolean;

/**
* An alphanumeric identifier for the status.
*/
slug: string;

/**
* Whether posts of this status may have floating published dates.
*/
date_floating: boolean;
}
Copy link
Member

Choose a reason for hiding this comment

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

Statuses can also have _links, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't have links defined in any of the objects here.

}
}

export type PostStatusObject< C extends Context = 'edit' > = OmitNevers<
_BaseEntityRecords.Type< C >
>;
14 changes: 12 additions & 2 deletions packages/core-data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from './entities';
import { STORE_NAME } from './name';
import { unlock } from './lock-unlock';
import { dynamicActions, dynamicSelectors } from './dynamic-entities';

// The entity selectors/resolvers and actions are shortcuts to their generic equivalents
// (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords)
Expand Down Expand Up @@ -68,8 +69,17 @@ const entityActions = entitiesConfig.reduce( ( result, entity ) => {

const storeConfig = () => ( {
reducer,
actions: { ...actions, ...entityActions, ...createLocksActions() },
selectors: { ...selectors, ...entitySelectors },
actions: {
...dynamicActions,
...actions,
...entityActions,
...createLocksActions(),
},
selectors: {
...dynamicSelectors,
...selectors,
...entitySelectors,
},
resolvers: { ...resolvers, ...entityResolvers },
} );

Expand Down
2 changes: 1 addition & 1 deletion packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ type Optional< T > = T | undefined;
/**
* HTTP Query parameters sent with the API request to fetch the entity records.
*/
type GetRecordsHttpQuery = Record< string, any >;
export type GetRecordsHttpQuery = Record< string, any >;

/**
* Arguments for EntityRecord selectors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import type { TemplatePartArea } from '@wordpress/core-data';
import { store as coreStore } from '@wordpress/core-data';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
Expand Down Expand Up @@ -52,13 +53,6 @@ type CreateTemplatePartModalContentsProps = {
defaultTitle?: string;
};

type TemplatePartArea = {
area: string;
label: string;
icon: string;
description: string;
};

/**
* A React component that renders a modal for creating a template part. The modal displays a title and the contents for creating the template part.
* This component should not live in this package, it should be moved to a dedicated package responsible for managing template.
Expand All @@ -73,7 +67,6 @@ export default function CreateTemplatePartModal( {
} & CreateTemplatePartModalContentsProps ) {
const defaultModalTitle = useSelect(
( select ) =>
// @ts-expect-error getPostType is not typed with 'wp_template_part' as argument.
select( coreStore ).getPostType( 'wp_template_part' )?.labels
?.add_new_item,
[]
Expand Down Expand Up @@ -135,7 +128,6 @@ export function CreateTemplatePartModalContents( {

const defaultTemplatePartAreas = useSelect(
( select ) =>
// @ts-expect-error getEntityRecord is not typed with unstableBase as argument.
select( coreStore ).getEntityRecord< {
default_template_part_areas: Array< TemplatePartArea >;
} >( 'root', '__unstableBase' )?.default_template_part_areas,
Expand Down
Loading
Loading