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:
parent
8d281241a9
commit
67d9ba3b19
|
@ -16,90 +16,93 @@ type SelectorOptions = {
|
|||
pluralResourceName: string;
|
||||
};
|
||||
|
||||
export const getItemCreateError = (
|
||||
state: ResourceState,
|
||||
query: ItemQuery
|
||||
) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.CREATE_ITEM, query );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
export const getItemDeleteError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
export const getItem = ( state: ResourceState, id: IdType ) => {
|
||||
return state.data[ id ];
|
||||
};
|
||||
|
||||
export const getItemError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
export const getItems = createSelector(
|
||||
( state: ResourceState, query: ItemQuery ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||
|
||||
const ids = state.items[ itemQuery ]
|
||||
? state.items[ itemQuery ].data
|
||||
: undefined;
|
||||
|
||||
if ( ! ids ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( query._fields ) {
|
||||
return ids.map( ( id: IdType ) => {
|
||||
return query._fields.reduce(
|
||||
( item: Partial< Item >, field: string ) => {
|
||||
return {
|
||||
...item,
|
||||
[ field ]: state.data[ id ][ field ],
|
||||
};
|
||||
},
|
||||
{} as Partial< Item >
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
return ids.map( ( id: IdType ) => {
|
||||
return state.data[ id ];
|
||||
} );
|
||||
},
|
||||
( state, query ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||
const ids = state.items[ itemQuery ]
|
||||
? state.items[ itemQuery ].data
|
||||
: undefined;
|
||||
return [
|
||||
state.items[ itemQuery ],
|
||||
...( ids || [] ).map( ( id: string ) => {
|
||||
return state.data[ id ];
|
||||
} ),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
export const getItemsError = ( state: ResourceState, query: ItemQuery ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
export const getItemUpdateError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
export const createSelectors = ( {
|
||||
resourceName,
|
||||
pluralResourceName,
|
||||
}: SelectorOptions ) => {
|
||||
const getCreateItemError = ( state: ResourceState, query: ItemQuery ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.CREATE_ITEM, query );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
const getDeleteItemError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.DELETE_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
const getItem = ( state: ResourceState, id: IdType ) => {
|
||||
return state.data[ id ];
|
||||
};
|
||||
|
||||
const getItemError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
const getItems = createSelector(
|
||||
( state: ResourceState, query: ItemQuery ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||
|
||||
const ids = state.items[ itemQuery ]
|
||||
? state.items[ itemQuery ].data
|
||||
: undefined;
|
||||
|
||||
if ( ! ids ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( query._fields ) {
|
||||
return ids.map( ( id: IdType ) => {
|
||||
return query._fields.reduce(
|
||||
( item: Partial< Item >, field: string ) => {
|
||||
return {
|
||||
...item,
|
||||
[ field ]: state.data[ id ][ field ],
|
||||
};
|
||||
},
|
||||
{} as Partial< Item >
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
return ids.map( ( id: IdType ) => {
|
||||
return state.data[ id ];
|
||||
} );
|
||||
},
|
||||
( state, query ) => {
|
||||
const itemQuery = getResourceName( resourceName, query );
|
||||
const ids = state.items[ itemQuery ]
|
||||
? state.items[ itemQuery ].data
|
||||
: undefined;
|
||||
return [
|
||||
state.items[ itemQuery ],
|
||||
...( ids || [] ).map( ( id: string ) => {
|
||||
return state.data[ id ];
|
||||
} ),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
const getItemsError = ( state: ResourceState, query: ItemQuery ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.GET_ITEMS, query );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
const getUpdateItemError = ( state: ResourceState, id: IdType ) => {
|
||||
const itemQuery = getResourceName( CRUD_ACTIONS.UPDATE_ITEM, { id } );
|
||||
return state.errors[ itemQuery ];
|
||||
};
|
||||
|
||||
return {
|
||||
[ `get${ resourceName }` ]: getItem,
|
||||
[ `get${ resourceName }Error` ]: getItemError,
|
||||
[ `get${ pluralResourceName }` ]: getItems,
|
||||
[ `get${ pluralResourceName }Error` ]: getItemsError,
|
||||
[ `getCreate${ resourceName }Error` ]: getCreateItemError,
|
||||
[ `getDelete${ resourceName }Error` ]: getDeleteItemError,
|
||||
[ `getUpdate${ resourceName }Error` ]: getUpdateItemError,
|
||||
[ `get${ resourceName }CreateError` ]: getItemCreateError,
|
||||
[ `get${ resourceName }DeleteError` ]: getItemDeleteError,
|
||||
[ `get${ resourceName }UpdateError` ]: getItemUpdateError,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,8 +15,8 @@ describe( 'crud selectors', () => {
|
|||
expect( selectors ).toHaveProperty( 'getProducts' );
|
||||
expect( selectors ).toHaveProperty( 'getProductError' );
|
||||
expect( selectors ).toHaveProperty( 'getProductsError' );
|
||||
expect( selectors ).toHaveProperty( 'getCreateProductError' );
|
||||
expect( selectors ).toHaveProperty( 'getDeleteProductError' );
|
||||
expect( selectors ).toHaveProperty( 'getUpdateProductError' );
|
||||
expect( selectors ).toHaveProperty( 'getProductCreateError' );
|
||||
expect( selectors ).toHaveProperty( 'getProductDeleteError' );
|
||||
expect( selectors ).toHaveProperty( 'getProductUpdateError' );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
|
@ -13,3 +22,86 @@ export type Item = {
|
|||
export type ItemQuery = BaseQueryParams & {
|
||||
[ 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;
|
||||
};
|
||||
|
|
|
@ -119,6 +119,7 @@ import { OnboardingSelectors } from './onboarding/selectors';
|
|||
import { OptionsSelectors } from './options/types';
|
||||
import { ProductsSelectors } from './products/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
|
||||
// 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
|
||||
: T extends typeof PRODUCTS_STORE_NAME
|
||||
? ProductsSelectors
|
||||
: T extends typeof PRODUCT_ATTRIBUTES_STORE_NAME
|
||||
? ProductAttributeSelectors
|
||||
: T extends typeof ORDERS_STORE_NAME
|
||||
? OrdersSelectors
|
||||
: never;
|
||||
|
@ -158,4 +161,5 @@ export interface WCDataSelector {
|
|||
|
||||
// Other exports
|
||||
export { ActionDispatchers as PluginsStoreActions } from './plugins/actions';
|
||||
export { ActionDispatchers as ProductAttributesActions } from './product-attributes/types';
|
||||
export { ActionDispatchers as ProductsStoreActions } from './products/actions';
|
||||
|
|
|
@ -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 >;
|
Loading…
Reference in New Issue