Add Typed String-Based Data Store Access - #76323
Conversation
Replace single-signature store access functions with TypeScript overloads that provide specific return types for StoreDescriptor arguments and an untyped fallback for plain string store names. This eliminates several intermediate utility types (UseSelectReturn, UseDispatchReturn, DispatchReturn, DispatchFunction, SelectFunction) that were previously used to achieve similar type narrowing but couldn't express overload-level specificity. MapSelect now references DataRegistry['select'] directly instead of the removed SelectFunction. Also extracts resolveSelect and suspendSelect into dedicated modules (previously inlined in index.ts) and separates useSelect/useDispatch declarations from their default exports to work around a docgen limitation with export default function overloads.
Add an augmentable StoreRegistry interface that maps store name
strings to their store descriptors. Packages can declare their
stores via module augmentation so that select('core'), dispatch('core'),
etc. return fully typed selectors/actions without importing the
store descriptor.
Each store access function (select, dispatch, resolveSelect,
suspendSelect) and hook (useSelect, useDispatch) gains a
keyof StoreRegistry overload that returns typed results through
StoreRegistryResult — a guard type that falls back to any when
the key is unresolved, preserving backward compatibility.
This adds StoreRegistry entries for: - abilities - boot - core-data - editor - notices - preferences - upload-media
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @Copilot, @gutenbergplugin. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Pull request overview
Adds a TypeScript-level “store name → store descriptor” registry to @wordpress/data so string-based store access (e.g. select( 'core' )) can be typed via module augmentation, while preserving a fallback for unregistered/dynamic names.
Changes:
- Introduces an augmentable
StoreRegistry+StoreRegistryResulttype guard in@wordpress/dataand adds overloads forselect,dispatch,resolveSelect,suspendSelect,useSelect, anduseDispatch. - Adds store-to-name bindings (module augmentations) across several packages so common store names are typed.
- Refactors some package entrypoints / docs to ensure the new typings are surfaced to consumers.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/data/src/types.ts | Adds StoreRegistry/StoreRegistryResult and updates DataRegistry/MapSelect typing to enable string-name overloads. |
| packages/data/src/select.ts | Converts select to overload-based function declaration to support typed string store names. |
| packages/data/src/dispatch.ts | Converts dispatch to overload-based function declaration to support typed string store names. |
| packages/data/src/resolve-select.ts | Adds overload-based resolveSelect wrapper to support typed string store names. |
| packages/data/src/suspend-select.ts | Adds overload-based suspendSelect wrapper to support typed string store names. |
| packages/data/src/index.ts | Re-exports resolveSelect/suspendSelect from new modules (removing inline definitions). |
| packages/data/src/controls.ts | Updates control helpers to use the shared StoreNameOrDescriptor type. |
| packages/data/src/components/with-select/index.tsx | Updates withSelect callback typing to use DataRegistry['select']. |
| packages/data/src/components/use-select/index.ts | Adds overloads for useSelect to support typed string store names and simplifies internal typing. |
| packages/data/src/components/use-dispatch/use-dispatch.ts | Adds overloads for useDispatch to support typed string store names. |
| packages/data/README.md | Updates parameter/return documentation to reflect the new API typing surface. |
| packages/core-data/src/store.js | Extracts core-data store creation/registration into its own module. |
| packages/core-data/src/store-registry.ts | Adds StoreRegistry augmentation binding 'core' to core-data’s store descriptor type. |
| packages/core-data/src/index.js | Attempts to ensure store registry augmentation is included in emitted declarations. |
| packages/core-data/CHANGELOG.md | Adds an “Unreleased” note about StoreRegistry. |
| packages/editor/src/store-registry.ts | Adds StoreRegistry augmentation binding 'core/editor' to the editor store descriptor type. |
| packages/editor/src/index.js | Attempts to ensure editor store registry augmentation is included in emitted declarations. |
| packages/notices/src/store/index.ts | Adds StoreRegistry augmentation binding 'core/notices' to notices store. |
| packages/preferences/src/store/index.ts | Adds StoreRegistry augmentation binding 'core/preferences' to preferences store. |
| packages/upload-media/src/store/index.ts | Adds StoreRegistry augmentation binding 'core/upload-media' to upload-media store. |
| packages/abilities/src/store/index.ts | Adds StoreRegistry augmentation binding 'core/abilities' to abilities store. |
| packages/boot/src/store/index.ts | Adds StoreRegistry augmentation binding 'wordpress/boot' to boot store. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
This reverts commit 9c4fe3b. The StoreRegistry augmentations in individual store packages are contentious; keep only the central StoreRegistry infrastructure in @wordpress/data for now.
|
Size Change: -7 B (0%) Total Size: 7.73 MB
ℹ️ View Unchanged
|
|
Flaky tests detected in aba0d66. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/23888562724
|
- Add string index signature to StoreRegistry so unregistered store names fall back to StoreDescriptor<AnyConfig> automatically - Remove StoreRegistryResult guard type (no longer needed) - Reorder overloads: StoreDescriptor first, keyof StoreRegistry second, StoreNameOrDescriptor union third - Remove intermediate utility types (UseSelectReturn, UseDispatchReturn, DispatchReturn, SelectFunction) in favor of explicit overloads - Fix resolvers-cache-middleware types previously hidden by any
The `[key: string]: StoreDescriptor<AnyConfig>` index signature was added in f27fb0c as a simplification when the `StoreRegistryResult` guard type was removed. However, the guard's purpose — preventing `any`-typed store descriptors (from `@ts-expect-error` imports) from matching the `keyof StoreRegistry` overload — was already solved by the overload reordering done in the same commit, which placed the `StoreDescriptor` overload above the `StoreRegistry` one. The index signature is therefore redundant and can cause issues in consuming projects by making `keyof StoreRegistry` resolve to `string`, which causes the StoreRegistry overload to greedily match all string arguments (including `any`-typed values). With a blank interface, `keyof StoreRegistry` only contains keys from explicit augmentations (e.g. `'core'`), and unregistered store names correctly fall through to the `StoreNameOrDescriptor` overload.
The deprecated types (SelectFunction, DispatchFunction, DispatchReturn, UseSelectReturn, UseDispatchReturn) were moved from types.ts to a separate deprecated.ts file. This broke downstream consumers that deep-import from @wordpress/data/build-types/types. Move them back to types.ts to preserve the deep import paths. The types are marked with @deprecated JSDoc tags to guide consumers toward their replacements.
|
Thanks for working on this PR! 🙇
I don’t think we should move forward with this PR. The approach is already deprecated, so plugins or external code should be updated if they want to have proper TypeScript checks. I’m also concerned that introducing this change could increase our maintenance burden. |
|
Thanks for the feedback @gigitux!
While I agree that people should use descriptors, in practice, I don't think store names can be justifiably called "deprecated".
To be honest, the feature pretty much just boils down to an empty Aside from that, this pull request better expresses functions using overloads, removes the need for types like |
It is not completely true. This way to access the store is defined as legacy in the documentation
I haven’t verified this yet, but does it mean that if I write In my opinion, this kind of work might have been useful when we were starting to migrate stores. At this point, though, I’m not sure it adds much value. If plugins need typed selectors and dispatchers, they should rely on the store description instead. |
|
I like the refactoring where we split the The string store names are still widely used, no matter how deprecated they officially are. Looking at sources of major plugins like Jetpack, WooCommerce or Yoast, you'll see many usages. But providing a good type support was exactly the kind of thing that motivated us to switch away from strings to "descriptors" many years ago. A descriptor is an opaque object that you need to import from another module, you can't just make it up (well you can, but don't tell anyone). That has a few benefits:
The descriptor lets us having the type directly attached to it, we don't need to rely on the shared I think the existing state is a good tradeoff:
|
Can we please do that? |
|
As I've departed from Automattic, I'm going to go ahead and close this pull request, feel free to take the commits you'd like to keep! |
What?
Adds TypeScript support for typed string-based store access (e.g.,
select( 'core' )) via an augmentableStoreRegistryinterface.Why?
While Gutenberg has migrated to store descriptors, much external code still uses string-based store names. This enables that code to get the same type safety as descriptor-based access without requiring migration.
How?
StoreRegistryinterface that packages can augment viadeclare module '@wordpress/data'to map store names to their descriptors.select,dispatch,resolveSelect,suspendSelect) and hooks (useSelect,useDispatch) from arrow functions to function declarations with overloads, enabling type narrowing based on argument type.UseSelectReturn,UseDispatchReturn, etc.) made redundant by overloads.Testing Instructions
1. Build and pack all public packages
2. Create the test harness
Test harness files
package.json
{ "name": "wp-data-test", "version": "1.0.0", "private": true }tsconfig.json
{ "compilerOptions": { "strict": true, "skipLibCheck": true, "moduleResolution": "bundler", "esModuleInterop": true, "target": "ES2020", "module": "ES2020" }, "include": ["src"] }src/augmentation.d.ts
src/test.ts
3. Install and test