Add CRUD data store typing (#33680)

* Rename and export selectors for easier typing

* Add CRUD action and selector type mapping

* Add product attribute types

* Separate getItem to provide correct return type

* Rename config to type

* Add generator return types
This commit is contained in:
Joshua T Flowers 2022-07-06 12:04:16 -04:00 committed by GitHub
parent 8d281241a9
commit 67d9ba3b19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 223 additions and 80 deletions

View File

@ -16,30 +16,29 @@ type SelectorOptions = {
pluralResourceName: string; pluralResourceName: string;
}; };
export const createSelectors = ( { export const getItemCreateError = (
resourceName, state: ResourceState,
pluralResourceName, query: ItemQuery
}: SelectorOptions ) => { ) => {
const getCreateItemError = ( state: ResourceState, query: ItemQuery ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.CREATE_ITEM, query ); const itemQuery = getResourceName( CRUD_ACTIONS.CREATE_ITEM, query );
return state.errors[ itemQuery ]; return state.errors[ itemQuery ];
}; };
const getDeleteItemError = ( state: ResourceState, id: IdType ) => { export const getItemDeleteError = ( state: ResourceState, id: IdType ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { id } ); const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { id } );
return state.errors[ itemQuery ]; return state.errors[ itemQuery ];
}; };
const getItem = ( state: ResourceState, id: IdType ) => { export const getItem = ( state: ResourceState, id: IdType ) => {
return state.data[ id ]; return state.data[ id ];
}; };
const getItemError = ( state: ResourceState, id: IdType ) => { export const getItemError = ( state: ResourceState, id: IdType ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } ); const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
return state.errors[ itemQuery ]; return state.errors[ itemQuery ];
}; };
const getItems = createSelector( export const getItems = createSelector(
( state: ResourceState, query: ItemQuery ) => { ( state: ResourceState, query: ItemQuery ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query ); const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
@ -70,7 +69,7 @@ export const createSelectors = ( {
} ); } );
}, },
( state, query ) => { ( state, query ) => {
const itemQuery = getResourceName( resourceName, query ); const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
const ids = state.items[ itemQuery ] const ids = state.items[ itemQuery ]
? state.items[ itemQuery ].data ? state.items[ itemQuery ].data
: undefined; : undefined;
@ -81,25 +80,29 @@ export const createSelectors = ( {
} ), } ),
]; ];
} }
); );
const getItemsError = ( state: ResourceState, query: ItemQuery ) => { export const getItemsError = ( state: ResourceState, query: ItemQuery ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query ); const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
return state.errors[ itemQuery ]; return state.errors[ itemQuery ];
}; };
const getUpdateItemError = ( state: ResourceState, id: IdType ) => { export const getItemUpdateError = ( state: ResourceState, id: IdType ) => {
const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, { id } ); const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, { id } );
return state.errors[ itemQuery ]; return state.errors[ itemQuery ];
}; };
export const createSelectors = ( {
resourceName,
pluralResourceName,
}: SelectorOptions ) => {
return { return {
[ `get${ resourceName }` ]: getItem, [ `get${ resourceName }` ]: getItem,
[ `get${ resourceName }Error` ]: getItemError, [ `get${ resourceName }Error` ]: getItemError,
[ `get${ pluralResourceName }` ]: getItems, [ `get${ pluralResourceName }` ]: getItems,
[ `get${ pluralResourceName }Error` ]: getItemsError, [ `get${ pluralResourceName }Error` ]: getItemsError,
[ `getCreate${ resourceName }Error` ]: getCreateItemError, [ `get${ resourceName }CreateError` ]: getItemCreateError,
[ `getDelete${ resourceName }Error` ]: getDeleteItemError, [ `get${ resourceName }DeleteError` ]: getItemDeleteError,
[ `getUpdate${ resourceName }Error` ]: getUpdateItemError, [ `get${ resourceName }UpdateError` ]: getItemUpdateError,
}; };
}; };

View File

@ -15,8 +15,8 @@ describe( 'crud selectors', () => {
expect( selectors ).toHaveProperty( 'getProducts' ); expect( selectors ).toHaveProperty( 'getProducts' );
expect( selectors ).toHaveProperty( 'getProductError' ); expect( selectors ).toHaveProperty( 'getProductError' );
expect( selectors ).toHaveProperty( 'getProductsError' ); expect( selectors ).toHaveProperty( 'getProductsError' );
expect( selectors ).toHaveProperty( 'getCreateProductError' ); expect( selectors ).toHaveProperty( 'getProductCreateError' );
expect( selectors ).toHaveProperty( 'getDeleteProductError' ); expect( selectors ).toHaveProperty( 'getProductDeleteError' );
expect( selectors ).toHaveProperty( 'getUpdateProductError' ); expect( selectors ).toHaveProperty( 'getProductUpdateError' );
} ); } );
} ); } );

View File

@ -1,7 +1,16 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { BaseQueryParams } from '../types'; import { BaseQueryParams, WPDataSelector, WPDataSelectors } from '../types';
import {
getItem,
getItemError,
getItems,
getItemsError,
getItemCreateError,
getItemDeleteError,
getItemUpdateError,
} from './selectors';
export type IdType = number | string; export type IdType = number | string;
@ -13,3 +22,86 @@ export type Item = {
export type ItemQuery = BaseQueryParams & { export type ItemQuery = BaseQueryParams & {
[ key: string ]: unknown; [ key: string ]: unknown;
}; };
export type CrudActions< ResourceName, ItemType, MutableProperties > =
MapActions<
{
create: ( query: ItemQuery ) => Item;
update: ( query: ItemQuery ) => Item;
},
ResourceName,
MutableProperties,
Generator< unknown, ItemType >
> &
MapActions<
{
delete: ( id: IdType ) => Item;
},
ResourceName,
IdType,
Generator< unknown, ItemType >
>;
export type CrudSelectors<
ResourceName,
PluralResourceName,
ItemType,
ItemQueryType,
MutableProperties
> = MapSelectors<
{
'': WPDataSelector< typeof getItem >;
},
ResourceName,
IdType,
ItemType
> &
MapSelectors<
{
Error: WPDataSelector< typeof getItemError >;
DeleteError: WPDataSelector< typeof getItemDeleteError >;
UpdateError: WPDataSelector< typeof getItemUpdateError >;
},
ResourceName,
IdType,
unknown
> &
MapSelectors<
{
'': WPDataSelector< typeof getItems >;
},
PluralResourceName,
ItemQueryType,
ItemType[]
> &
MapSelectors<
{
Error: WPDataSelector< typeof getItemsError >;
},
PluralResourceName,
ItemQueryType,
unknown
> &
MapSelectors<
{
CreateError: WPDataSelector< typeof getItemCreateError >;
},
PluralResourceName,
MutableProperties,
unknown
> &
WPDataSelectors;
export type MapSelectors< Type, ResourceName, ParamType, ReturnType > = {
[ Property in keyof Type as `get${ Capitalize<
string & ResourceName
> }${ Capitalize< string & Property > }` ]: ( x: ParamType ) => ReturnType;
};
export type MapActions< Type, ResourceName, ParamType, ReturnType > = {
[ Property in keyof Type as `${ Lowercase<
string & Property
> }${ Capitalize< string & ResourceName > }` ]: (
x: ParamType
) => ReturnType;
};

View File

@ -119,6 +119,7 @@ import { OnboardingSelectors } from './onboarding/selectors';
import { OptionsSelectors } from './options/types'; import { OptionsSelectors } from './options/types';
import { ProductsSelectors } from './products/selectors'; import { ProductsSelectors } from './products/selectors';
import { OrdersSelectors } from './orders/selectors'; import { OrdersSelectors } from './orders/selectors';
import { ProductAttributeSelectors } from './product-attributes/types';
// As we add types to all the package selectors we can fill out these unknown types with real ones. See one // As we add types to all the package selectors we can fill out these unknown types with real ones. See one
// of the already typed selectors for an example of how you can do this. // of the already typed selectors for an example of how you can do this.
@ -148,6 +149,8 @@ export type WCSelectorType< T > = T extends typeof REVIEWS_STORE_NAME
? WPDataSelectors ? WPDataSelectors
: T extends typeof PRODUCTS_STORE_NAME : T extends typeof PRODUCTS_STORE_NAME
? ProductsSelectors ? ProductsSelectors
: T extends typeof PRODUCT_ATTRIBUTES_STORE_NAME
? ProductAttributeSelectors
: T extends typeof ORDERS_STORE_NAME : T extends typeof ORDERS_STORE_NAME
? OrdersSelectors ? OrdersSelectors
: never; : never;
@ -158,4 +161,5 @@ export interface WCDataSelector {
// Other exports // Other exports
export { ActionDispatchers as PluginsStoreActions } from './plugins/actions'; export { ActionDispatchers as PluginsStoreActions } from './plugins/actions';
export { ActionDispatchers as ProductAttributesActions } from './product-attributes/types';
export { ActionDispatchers as ProductsStoreActions } from './products/actions'; export { ActionDispatchers as ProductsStoreActions } from './products/actions';

View File

@ -0,0 +1,44 @@
/**
* External dependencies
*/
import { DispatchFromMap } from '@automattic/data-stores';
/**
* Internal dependencies
*/
import { CrudActions, CrudSelectors } from '../crud/types';
type ProductAttribute = {
id: number;
slug: string;
name: string;
type: string;
order_by: string;
has_archives: boolean;
};
type Query = {
context?: string;
};
type ReadOnlyProperties = 'id';
type MutableProperties = Partial<
Omit< ProductAttribute, ReadOnlyProperties >
>;
type ProductAttributeActions = CrudActions<
'ProductAttribute',
ProductAttribute,
MutableProperties
>;
export type ProductAttributeSelectors = CrudSelectors<
'ProductAttribute',
'ProductAttributes',
ProductAttribute,
Query,
MutableProperties
>;
export type ActionDispatchers = DispatchFromMap< ProductAttributeActions >;