feat: Implement BaseDataService#8039
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
1b629b1 to
c19b3d7
Compare
|
@metamaskbot publish-previews |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
86eff63 to
5111712
Compare
|
@metamaskbot publish-previews |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
|
@metamaskbot publish-previews |
1afe924 to
c36e274
Compare
|
@metamaskbot publish-previews |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
| */ | ||
| const ALLOWED_INCONSISTENT_DEPENDENCIES = { | ||
| // '@metamask/json-rpc-engine': ['^9.0.3'], | ||
| '@tanstack/query-core': ['^4.43.0'], |
There was a problem hiding this comment.
core-backend will need to downgrade to use BaseDataService or wait until we are able to bump everywhere (blocked by extension not supporting React 18).
|
I know that TanStack Query is the motivation for this PR, but before we merge this I want to make sure that we've (at least briefly) considered how this new package would overlap with the other "data service layer" features which are already implemented and which we've discussed adding in the future. Namely:
I guess the theme of these questions is that I want to understand what the intended domain of this package is. Should it be only restricted to TanStack Query integration in the future — meaning that we may create other packages to solve other problems later — or should it be designed to encompass other things that are data-service-related in the future? |
|
I was expecting that we'd continue to use Cockatiel. TanStack does have basic built-in retry functionality, but nothing remotely similar to what our current retry/circuit break policies do. |
|
Related question: Are there any data services where this query-related functionality would not be useful? i.e. where extending this base class would be unwanted. If so, we could rename this to I think the answer here is "no" though. When wouldn't we want request deduplication, and easier-to-use caching options? |
mcmire
left a comment
There was a problem hiding this comment.
Still working through this PR, but made some more suggestions.
019cd62 to
0d3c01d
Compare
mcmire
left a comment
There was a problem hiding this comment.
Getting closer on this. I admit I'm not following the code in createUIQueryClient. The tests do help in demonstrating the behavior, but I feel like I need to read about observers to fully grasp it. That said, overall, I don't have any major concerns, just minor things. I'll try to do a final pass next week.
| if ( | ||
| !hasSubscription && | ||
| event.type === 'observerAdded' && | ||
| observerCount === 1 |
There was a problem hiding this comment.
Is this similar-ish to what we are doing in MessengerSubscriptions in the extension?
There was a problem hiding this comment.
Yes. Whenever the first consumer of a query is added, we add our subscription, when no consumers are left we can unsubscribe again.
| ) => void; | ||
| type JsonSubscriptionCallback = (data: Json) => void; | ||
|
|
||
| // TODO: Figure out if we can replace with a better Messenger type |
There was a problem hiding this comment.
It seems like after the UI messenger integration is introduced, this could either be the UI messenger or a route messenger. Would that make sense or would we still need an abstract type here?
There was a problem hiding this comment.
Potentially. We would need the type to be generic enough that it can support any data service.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
mcmire
left a comment
There was a problem hiding this comment.
I'll still have to do more research on TanStack Query works but I'm caught up enough to have a rough understanding. I like how simple this is. LGTM.
## Explanation This PR implements `BaseDataService` and a function to wrap `QueryClient` to proxy requests accordingly. The `BaseDataService`, similarly to the `BaseController` provides the framework for building a service that can be registered and accessed via the messenger system, but also provides guarantees about per-request deduping, retries, caching, invalidation, state-while-revalidate etc via `@tanstack/query-core`. The `BaseDataService` provides two utilities for this: `fetchQuery` and `fetchInfiniteQuery`, which is similar but one is separated for special pagination behaviour. Each service has its own cache for the APIs that it exposes that must also be synchronized with the UI processes. To facilitate this synchronization, the `BaseDataService` also automatically provides a `cacheUpdate` event. The overall goal of the PR is to provide a base layer that can keep as much compatibility as possible with native TanStack Query while also simultaneously allowing us to have one source of truth per data service. The synchronization is achieved via a special `QueryClient` created by `createUIQueryClient`, which wraps functionality such as cache invalidation, provides the default proxied fetch behaviour and subscribes to cache updates from data services that it is observing (e.g. has active queries for). ## References https://consensyssoftware.atlassian.net/browse/WPC-445 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces new shared data-service/query infrastructure (caching, retries/circuit breaking, invalidation, cross-process cache hydration) that can affect data freshness and event synchronization if misused, though changes are isolated to new packages with test coverage. > > **Overview** > Adds an initial implementation of `@metamask/base-data-service`, introducing `BaseDataService` built on `@tanstack/query-core` with `fetchQuery`/`fetchInfiniteQuery`, service-policy-wrapped execution (retries/circuit breaking), a registered `:invalidateQueries` action, and automatic `:cacheUpdated`/granular `:cacheUpdated:${hash}` events with dehydrated cache state. > > Adds `@metamask/react-data-query` utilities to consume these services from UI code: `createUIQueryClient` proxies TanStack queries through a messenger, subscribes/unsubscribes to per-query cache update events to hydrate/remove cached entries, and forwards `invalidateQueries` to the underlying service; also adds typed `useQuery`/`useInfiniteQuery` wrappers. > > Updates package metadata/build references, adds dependencies (TanStack v4, controller-utils, messenger, nock, etc.), and adjusts Yarn constraints to allow the TanStack v4 range and React peer deps without requiring devDependency installs. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0ae98c7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Explanation
This PR implements
BaseDataServiceand a function to wrapQueryClientto proxy requests accordingly.The
BaseDataService, similarly to theBaseControllerprovides the framework for building a service that can be registered and accessed via the messenger system, but also provides guarantees about per-request deduping, retries, caching, invalidation, state-while-revalidate etc via@tanstack/query-core.The
BaseDataServiceprovides two utilities for this:fetchQueryandfetchInfiniteQuery, which is similar but one is separated for special pagination behaviour. Each service has its own cache for the APIs that it exposes that must also be synchronized with the UI processes. To facilitate this synchronization, theBaseDataServicealso automatically provides acacheUpdateevent.The overall goal of the PR is to provide a base layer that can keep as much compatibility as possible with native TanStack Query while also simultaneously allowing us to have one source of truth per data service.
The synchronization is achieved via a special
QueryClientcreated bycreateUIQueryClient, which wraps functionality such as cache invalidation, provides the default proxied fetch behaviour and subscribes to cache updates from data services that it is observing (e.g. has active queries for).References
https://consensyssoftware.atlassian.net/browse/WPC-445
Checklist
Note
Medium Risk
Introduces new shared data-service/query infrastructure (caching, retries/circuit breaking, invalidation, cross-process cache hydration) that can affect data freshness and event synchronization if misused, though changes are isolated to new packages with test coverage.
Overview
Adds an initial implementation of
@metamask/base-data-service, introducingBaseDataServicebuilt on@tanstack/query-corewithfetchQuery/fetchInfiniteQuery, service-policy-wrapped execution (retries/circuit breaking), a registered:invalidateQueriesaction, and automatic:cacheUpdated/granular:cacheUpdated:${hash}events with dehydrated cache state.Adds
@metamask/react-data-queryutilities to consume these services from UI code:createUIQueryClientproxies TanStack queries through a messenger, subscribes/unsubscribes to per-query cache update events to hydrate/remove cached entries, and forwardsinvalidateQueriesto the underlying service; also adds typeduseQuery/useInfiniteQuerywrappers.Updates package metadata/build references, adds dependencies (TanStack v4, controller-utils, messenger, nock, etc.), and adjusts Yarn constraints to allow the TanStack v4 range and React peer deps without requiring devDependency installs.
Written by Cursor Bugbot for commit 0ae98c7. This will update automatically on new commits. Configure here.