From 72155375e24cbd05540025ab182580284162ba7c Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Wed, 16 Mar 2022 19:33:31 -0700 Subject: [PATCH] Core Data: Fixes necessary for converting to TypeScript --- docs/reference-guides/data/data-core.md | 2 +- packages/core-data/README.md | 2 +- packages/core-data/package.json | 1 + .../core-data/src/batch/default-processor.js | 5 +- packages/core-data/src/entities.js | 1 - packages/core-data/src/entity-provider.js | 2 + .../core-data/src/hooks/use-entity-records.ts | 8 +-- .../core-data/src/hooks/use-query-select.ts | 23 ++++--- .../core-data/src/{reducer.js => reducer.ts} | 69 ++++++++++++++----- packages/core-data/src/selectors.js | 9 +-- .../core-data/src/utils/if-matching-action.js | 18 ----- .../core-data/src/utils/if-matching-action.ts | 28 ++++++++ packages/core-data/src/utils/on-sub-key.js | 2 +- .../{replace-action.js => replace-action.ts} | 5 +- packages/core-data/tsconfig.json | 20 ++++++ tsconfig.json | 1 + 16 files changed, 136 insertions(+), 60 deletions(-) rename packages/core-data/src/{reducer.js => reducer.ts} (91%) delete mode 100644 packages/core-data/src/utils/if-matching-action.js create mode 100644 packages/core-data/src/utils/if-matching-action.ts rename packages/core-data/src/utils/{replace-action.js => replace-action.ts} (68%) create mode 100644 packages/core-data/tsconfig.json diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 3353b1dd41c248..0a241f9be62da4 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -399,7 +399,7 @@ _Parameters_ - _state_ `Object`: State tree. - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _recordId_ `number`: Record ID. +- _recordId_ `number|string`: Record ID. _Returns_ diff --git a/packages/core-data/README.md b/packages/core-data/README.md index aa26746a4df95f..32201f32a2af6e 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -646,7 +646,7 @@ _Parameters_ - _state_ `Object`: State tree. - _kind_ `string`: Entity kind. - _name_ `string`: Entity name. -- _recordId_ `number`: Record ID. +- _recordId_ `number|string`: Record ID. _Returns_ diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 146eb76d5bcc26..dd48f30af228b1 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "sideEffects": [ "{src,build,build-module}/index.js" ], diff --git a/packages/core-data/src/batch/default-processor.js b/packages/core-data/src/batch/default-processor.js index 34f38ff5c3d0da..477f67e78106e1 100644 --- a/packages/core-data/src/batch/default-processor.js +++ b/packages/core-data/src/batch/default-processor.js @@ -12,7 +12,8 @@ import apiFetch from '@wordpress/api-fetch'; * Maximum number of requests to place in a single batch request. Obtained by * sending a preflight OPTIONS request to /batch/v1/. * - * @type {number?} + * @type {number|null} + * @default 25 (set by Core) */ let maxItems = null; @@ -36,7 +37,7 @@ export default async function defaultProcessor( requests ) { const results = []; - for ( const batchRequests of chunk( requests, maxItems ) ) { + for ( const batchRequests of chunk( requests, maxItems ?? 25 ) ) { const batchResponse = await apiFetch( { path: '/batch/v1', method: 'POST', diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 09731d54841fc2..e7a5a43347a868 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -307,7 +307,6 @@ export const getMethodName = ( * Loads the kind entities into the store. * * @param {string} kind Kind - * * @return {(thunkArgs: object) => Promise} Entities */ export const getOrLoadEntitiesConfig = ( kind ) => async ( { diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 55bceea43ee446..c1e7a755fe6e17 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -24,6 +24,8 @@ const EMPTY_ARRAY = []; */ import { rootEntitiesConfig, additionalEntityConfigLoaders } from './entities'; +/** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */ + const entityContexts = { ...rootEntitiesConfig.reduce( ( acc, loader ) => { if ( ! acc[ loader.kind ] ) { diff --git a/packages/core-data/src/hooks/use-entity-records.ts b/packages/core-data/src/hooks/use-entity-records.ts index 8b88fb2f662f7a..b08e78f16a4ac4 100644 --- a/packages/core-data/src/hooks/use-entity-records.ts +++ b/packages/core-data/src/hooks/use-entity-records.ts @@ -40,10 +40,10 @@ interface Options { /** * Resolves the specified entity records. * - * @param kind Kind of the requested entities. - * @param name Name of the requested entities. - * @param queryArgs HTTP query for the requested entities. - * @param options Hook options. + * @param kind Kind of the requested entities. + * @param name Name of the requested entities. + * @param queryArgs HTTP query for the requested entities. + * @param options Hook options. * @example * ```js * import { useEntityRecord } from '@wordpress/core-data'; diff --git a/packages/core-data/src/hooks/use-query-select.ts b/packages/core-data/src/hooks/use-query-select.ts index 4de52a28d8ba15..cadcd114258c1b 100644 --- a/packages/core-data/src/hooks/use-query-select.ts +++ b/packages/core-data/src/hooks/use-query-select.ts @@ -17,9 +17,9 @@ export const META_SELECTORS = [ 'getCachedResolvers', ]; -interface QuerySelectResponse { +interface QuerySelectResponse< Data > { /** the requested selector return value */ - data: Object; + data: Data; /** is the record still being resolved? Via the `getIsResolving` meta-selector */ isResolving: boolean; @@ -78,19 +78,24 @@ export default function __experimentalUseQuerySelect( mapQuerySelect, deps ) { }, deps ); } -type QuerySelector = ( ...args ) => QuerySelectResponse; -interface EnrichedSelectors { - [ key: string ]: QuerySelector; +interface EnrichSelectors { + < Selectors extends Record< string, ( ...args: any ) => any > >( + selectors: Selectors + ): { + [ Selector in keyof Selectors ]: ( + ...args: Parameters< Selectors[ Selector ] > + ) => QuerySelectResponse< ReturnType< Selectors[ Selector ] > >; + }; } /** * Transform simple selectors into ones that return an object with the * original return value AND the resolution info. * - * @param {Object} selectors Selectors to enrich - * @return {EnrichedSelectors} Enriched selectors + * @param selectors Selectors to enrich + * @return Enriched selectors */ -const enrichSelectors = memoize( ( selectors ) => { +const enrichSelectors = memoize( ( ( selectors ) => { const resolvers = {}; for ( const selectorName in selectors ) { if ( META_SELECTORS.includes( selectorName ) ) { @@ -128,4 +133,4 @@ const enrichSelectors = memoize( ( selectors ) => { } ); } return resolvers; -} ); +} ) as EnrichSelectors ); diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.ts similarity index 91% rename from packages/core-data/src/reducer.js rename to packages/core-data/src/reducer.ts index 0c901c8ab7a2fc..ca2795b83f6849 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.ts @@ -330,12 +330,15 @@ function entity( entityConfig ) { /** * Reducer keeping track of the registered entities. * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param state Current state. + * @param action Dispatched action. * - * @return {Object} Updated state. + * @return Updated state. */ -export function entitiesConfig( state = rootEntitiesConfig, action ) { +export function entitiesConfig( + state = rootEntitiesConfig as EntityConfig[], + action +): EntityConfig[] { switch ( action.type ) { case 'ADD_ENTITIES': return [ ...state, ...action.entities ]; @@ -345,14 +348,40 @@ export function entitiesConfig( state = rootEntitiesConfig, action ) { } /** - * Reducer keeping track of the registered entities config and data. + * Placeholder for an entity config record. * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * TODO: Replace once we have an official type for this. * - * @return {Object} Updated state. + * @see #39481 */ -export const entities = ( state = {}, action ) => { +interface EntityConfig extends Record< string, any > { + baseURL?: string; + baseURLParams?: Record< string, string | number >; + getTitle?(): ( record: unknown ) => string; + key?: string; + kind: string; + label?: string; + name: string; + plural?: string; + rawAttributes?: string[]; + title?: string; + transientEdits?: { blocks: boolean }; +} + +interface EntityState { + config: EntityConfig[]; + records: Record< string, any >; + reducer< State >( state: State, action: Record< string, any > ): State; +} + +/** + * Reducer keeping track of the registered entities config and data. + * + * @param state Current state. + * @param action Dispatched action. + * @return Updated state. + */ +export const entities = ( state = {} as EntityState, action ): EntityState => { const newConfig = entitiesConfig( state.config, action ); // Generates a dynamic reducer for the entities. @@ -397,18 +426,26 @@ export const entities = ( state = {}, action ) => { }; }; +interface UndoState extends Array< object > { + offset: number; + flattenedUndo?: object; +} + +const UNDO_INITIAL_STATE: UndoState = Object.assign( [], { offset: 0 } ); +let lastEditAction; + /** * Reducer keeping track of entity edit undo history. * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. + * @param state Current state. + * @param action Dispatched action. * - * @return {Object} Updated state. + * @return Updated state. */ -const UNDO_INITIAL_STATE = []; -UNDO_INITIAL_STATE.offset = 0; -let lastEditAction; -export function undo( state = UNDO_INITIAL_STATE, action ) { +export function undo( + state = UNDO_INITIAL_STATE, + action: Record< string, any > +): UndoState { switch ( action.type ) { case 'EDIT_ENTITY_RECORD': case 'CREATE_UNDO_LEVEL': diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 01dc3024152afd..e5394e7dc1c4d2 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -192,6 +192,7 @@ export const getEntityRecord = createSelector( if ( item && query._fields ) { const filteredItem = {}; const fields = getNormalizedCommaSeparable( query._fields ) ?? []; + for ( let f = 0; f < fields.length; f++ ) { const field = fields[ f ].split( '.' ); const value = get( item, field ); @@ -501,10 +502,10 @@ export const getEntityRecordNonTransientEdits = createSelector( * Returns true if the specified entity record has edits, * and false otherwise. * - * @param {Object} state State tree. - * @param {string} kind Entity kind. - * @param {string} name Entity name. - * @param {number} recordId Record ID. + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number|string} recordId Record ID. * * @return {boolean} Whether the entity record has edits or not. */ diff --git a/packages/core-data/src/utils/if-matching-action.js b/packages/core-data/src/utils/if-matching-action.js deleted file mode 100644 index bbbe35800187bd..00000000000000 --- a/packages/core-data/src/utils/if-matching-action.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * A higher-order reducer creator which invokes the original reducer only if - * the dispatching action matches the given predicate, **OR** if state is - * initializing (undefined). - * - * @param {Function} isMatch Function predicate for allowing reducer call. - * - * @return {Function} Higher-order reducer. - */ -const ifMatchingAction = ( isMatch ) => ( reducer ) => ( state, action ) => { - if ( state === undefined || isMatch( action ) ) { - return reducer( state, action ); - } - - return state; -}; - -export default ifMatchingAction; diff --git a/packages/core-data/src/utils/if-matching-action.ts b/packages/core-data/src/utils/if-matching-action.ts new file mode 100644 index 00000000000000..2c6a99919d7d50 --- /dev/null +++ b/packages/core-data/src/utils/if-matching-action.ts @@ -0,0 +1,28 @@ +interface Action extends Record< string, any > { + type: string; +} + +/** + * A higher-order reducer creator which invokes the original reducer only if + * the dispatching action matches the given predicate, **OR** if state is + * initializing (undefined). + * + * @param isMatch Function predicate for allowing reducer call. + * @return Higher-order reducer. + */ +const ifMatchingAction = < + Matcher extends ( action: Action ) => boolean, + State, + Reducer extends ( state: State, action: Action ) => State +>( + isMatch: Matcher +) => ( reducer: Reducer ) => + ( ( state, action ) => { + if ( state === undefined || isMatch( action ) ) { + return reducer( state, action ); + } + + return state; + } ) as Reducer; + +export default ifMatchingAction; diff --git a/packages/core-data/src/utils/on-sub-key.js b/packages/core-data/src/utils/on-sub-key.js index 24adf06b773ebc..1259b9b9cf0588 100644 --- a/packages/core-data/src/utils/on-sub-key.js +++ b/packages/core-data/src/utils/on-sub-key.js @@ -4,7 +4,7 @@ * * @param {string} actionProperty Action property by which to key object. * - * @return {Function} Higher-order reducer. + * @return {(...args: any[]) => any} Higher-order reducer. */ export const onSubKey = ( actionProperty ) => ( reducer ) => ( state = {}, diff --git a/packages/core-data/src/utils/replace-action.js b/packages/core-data/src/utils/replace-action.ts similarity index 68% rename from packages/core-data/src/utils/replace-action.js rename to packages/core-data/src/utils/replace-action.ts index 91cecb0e391513..75a5e84fa190b4 100644 --- a/packages/core-data/src/utils/replace-action.js +++ b/packages/core-data/src/utils/replace-action.ts @@ -2,9 +2,8 @@ * Higher-order reducer creator which substitutes the action object before * passing to the original reducer. * - * @param {Function} replacer Function mapping original action to replacement. - * - * @return {Function} Higher-order reducer. + * @param replacer Function mapping original action to replacement. + * @return Higher-order reducer. */ const replaceAction = ( replacer ) => ( reducer ) => ( state, action ) => { return reducer( state, replacer( action ) ); diff --git a/packages/core-data/tsconfig.json b/packages/core-data/tsconfig.json new file mode 100644 index 00000000000000..4d05815f9509c3 --- /dev/null +++ b/packages/core-data/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "build-types", + "noUnusedParameters": false, + "noImplicitAny": false + }, + "references": [ + { "path": "../compose" }, + { "path": "../deprecated" }, + { "path": "../data" }, + { "path": "../element" }, + { "path": "../is-shallow-equal" }, + { "path": "../priority-queue" }, + { "path": "../redux-routine" }, + { "path": "../url" } + ], + "include": [ "src/**/*.js", "src/**/*.tsx", "src/**/*.ts" ] +} diff --git a/tsconfig.json b/tsconfig.json index 53ab6c77aa41b5..ef5cb566a910fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ { "path": "packages/block-editor" }, { "path": "packages/components" }, { "path": "packages/compose" }, + { "path": "packages/core-data" }, { "path": "packages/data" }, { "path": "packages/date" }, { "path": "packages/deprecated" },