@metamask-previews/react-data-query 2.0.0-preview-acac4ba67 → 2.0.0-preview-8f81c90d5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,19 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Added
11
-
12
- - Add support for mutations ([#9324](https://github.com/MetaMask/core/pull/9324))
13
- - You can now use `useMutation` in your UI files via the UI query client, passing a reference to the action and its called arguments through the `mutationKey` option (e.g. `['SocialService:follow', '0xaaaa', '0xbbbb']`).
14
- - This assumes that the data service method you want to call uses `BaseDataService.executeMutation` to make the request instead of `fetchQuery`, but should also take a trailing `globalId` argument.
15
- - Retries are disabled by default.
16
- - Also, the query client returned by `createUIQueryClient` is now aware of mutations and will ensure that they are copied from data service query clients properly.
17
-
18
10
  ### Changed
19
11
 
20
12
  - Bump `@metamask/utils` from `^11.12.0` to `^12.0.0` ([#10192](https://github.com/MetaMask/core/pull/10192))
21
- - Add `uuid` `^11.1.1` as a dependency ([#9324](https://github.com/MetaMask/core/pull/9324))
22
- - Bump `@tanstack/query-core` and `@tanstack/react-query` from `^5.62.16` to `^5.89.0` ([#9324](https://github.com/MetaMask/core/pull/9324))
23
13
 
24
14
  ## [2.0.0]
25
15
 
@@ -30,14 +30,21 @@ type MessengerAdapter<DataServiceName extends string> = {
30
30
  unsubscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
31
31
  };
32
32
  /**
33
- * Create a QueryClient that enables data services to power queries and mutations via a messenger adapter.
33
+ * Create a QueryClient that queries and subscribes to data services using a
34
+ * messenger adapter. This is a messenger-like object that carries some
35
+ * constraints:
34
36
  *
35
- * This returns a wrapped version of TanStack Query's QueryClient interface. Note that while some methods such as `invalidateQueries` have support for data services, some such as `setQueryData` do not.
37
+ * 1. The messenger must support the `call`, `subscribe` and
38
+ * `unsubscribe` methods.
39
+ * 2. All action handler arguments and event payloads must be JSON-compatible.
40
+ * 3. The messenger must minimally support actions that are scoped to the
41
+ * designated data services and must minimally support the
42
+ * `:cacheUpdated:${hash}` event scoped to the designated data services.
36
43
  *
37
44
  * @param dataServices - A list of data services.
38
- * @param messenger - A messenger-like object with the following constraints: 1) the messenger must support the `call`, `subscribe` and `unsubscribe` methods; 2) all action handler arguments and event payloads must be JSON-compatible; 3) the messenger must minimally support actions that are scoped to the designated data services and must minimally support the `:cacheUpdated:${hash}` event scoped to the designated data services.
45
+ * @param messenger - A messenger adapter.
39
46
  * @param config - Optional query client configuration options.
40
- * @returns The created QueryClient.
47
+ * @returns The QueryClient.
41
48
  */
42
49
  export declare function createUIQueryClient<DataServiceNames extends readonly string[]>(dataServices: DataServiceNames, messenger: MessengerAdapter<DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
43
50
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.d.ts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAGL,WAAW,EAIX,iBAAiB,EAKlB,MAAM,sBAAsB,CAAC;AAQ9B;;GAEG;AACH,KAAK,sCAAsC,GAAG,CAC5C,OAAO,EAAE,sCAAsC,KAC5C,IAAI,CAAC;AAEV;;;GAGG;AACH,KAAK,gBAAgB,CAAC,eAAe,SAAS,MAAM,IAAI;IACtD;;;;;;;OAOG;IACH,IAAI,CACF,UAAU,EAAE,GAAG,eAAe,IAAI,MAAM,EAAE,EAC1C,GAAG,MAAM,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC;IAEX;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EACxE,OAAO,EAAE,sCAAsC,GAC9C,IAAI,CAAC;IAER;;;OAGG;IACH,WAAW,CACT,SAAS,EAAE,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EACxE,OAAO,EAAE,sCAAsC,GAC9C,IAAI,CAAC;CACT,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,gBAAgB,SAAS,SAAS,MAAM,EAAE,EAC5E,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,EACrD,MAAM,GAAE,iBAAsB,GAC7B,WAAW,CA4Vb"}
1
+ {"version":3,"file":"createUIQueryClient.d.ts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAEL,WAAW,EAIX,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,KAAK,sCAAsC,GAAG,CAC5C,OAAO,EAAE,sCAAsC,KAC5C,IAAI,CAAC;AAEV;;;GAGG;AACH,KAAK,gBAAgB,CAAC,eAAe,SAAS,MAAM,IAAI;IACtD;;;;;;;OAOG;IACH,IAAI,CACF,UAAU,EAAE,GAAG,eAAe,IAAI,MAAM,EAAE,EAC1C,GAAG,MAAM,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC;IAEX;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EACxE,OAAO,EAAE,sCAAsC,GAC9C,IAAI,CAAC;IAER;;;OAGG;IACH,WAAW,CACT,SAAS,EAAE,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EACxE,OAAO,EAAE,sCAAsC,GAC9C,IAAI,CAAC;CACT,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,gBAAgB,SAAS,SAAS,MAAM,EAAE,EAC5E,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,EACrD,MAAM,GAAE,iBAAsB,GAC7B,WAAW,CA8Jb"}
@@ -1,26 +1,24 @@
1
1
  import { assert } from '@metamask/utils';
2
- import { hashKey, hydrate, QueryClient, } from '@tanstack/query-core';
3
- import { v4 as uuidV4 } from 'uuid';
4
- import { hydrateMutations, readGlobalId } from './hydrateMutations.js';
5
- import { createModuleLogger, projectLogger } from './loggers.js';
6
- const log = createModuleLogger(projectLogger, 'createUIQueryClient');
2
+ import { hydrate, QueryClient, } from '@tanstack/query-core';
7
3
  /**
8
- * Create a QueryClient that enables data services to power queries and mutations via a messenger adapter.
4
+ * Create a QueryClient that queries and subscribes to data services using a
5
+ * messenger adapter. This is a messenger-like object that carries some
6
+ * constraints:
9
7
  *
10
- * This returns a wrapped version of TanStack Query's QueryClient interface. Note that while some methods such as `invalidateQueries` have support for data services, some such as `setQueryData` do not.
8
+ * 1. The messenger must support the `call`, `subscribe` and
9
+ * `unsubscribe` methods.
10
+ * 2. All action handler arguments and event payloads must be JSON-compatible.
11
+ * 3. The messenger must minimally support actions that are scoped to the
12
+ * designated data services and must minimally support the
13
+ * `:cacheUpdated:${hash}` event scoped to the designated data services.
11
14
  *
12
15
  * @param dataServices - A list of data services.
13
- * @param messenger - A messenger-like object with the following constraints: 1) the messenger must support the `call`, `subscribe` and `unsubscribe` methods; 2) all action handler arguments and event payloads must be JSON-compatible; 3) the messenger must minimally support actions that are scoped to the designated data services and must minimally support the `:cacheUpdated:${hash}` event scoped to the designated data services.
16
+ * @param messenger - A messenger adapter.
14
17
  * @param config - Optional query client configuration options.
15
- * @returns The created QueryClient.
18
+ * @returns The QueryClient.
16
19
  */
17
20
  export function createUIQueryClient(dataServices, messenger, config = {}) {
18
21
  const subscriptions = new Map();
19
- // Tracks how many mutation observers are currently relying on each mutation
20
- // key's cache subscription. Unlike queries, a `Mutation` does not expose its
21
- // observer count publicly, so we count observers ourselves and only tear down
22
- // the messenger subscription once the last observer for a key is removed.
23
- const mutationObserverCounts = new Map();
24
22
  /**
25
23
  * Check whether a name is one of the provided data service names.
26
24
  *
@@ -56,34 +54,6 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
56
54
  }
57
55
  return service;
58
56
  }
59
- /**
60
- * The default `mutationFn` for data-service mutations. It uses the
61
- * `mutationKey` to call an action on a data service through the messenger.
62
- *
63
- * This is a separate function so that we can use it to determine whether a
64
- * custom `mutationFn` was used when the mutation was executed.
65
- *
66
- * @param _variables - The variables with which the mutation is being
67
- * executed. Unused.
68
- * @param context - The context associated with the mutation. Holds the
69
- * `mutationKey`.
70
- * @returns The result of the data service action.
71
- */
72
- const defaultDataServiceMutationFn = async (_variables, context) => {
73
- const { mutationKey } = context;
74
- assert(mutationKey !== undefined, "You must pass a `mutationKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `mutationKey: ['ExampleDataService:createOrder', ...]`.");
75
- const [action, ...params] = mutationKey;
76
- assert(typeof action === 'string' && isRecognizedDataServiceAction(action), "You must pass a `mutationKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `mutationKey: ['ExampleDataService:createOrder', ...]`.");
77
- log(`Detected mutation request, calling action: "${action}"`);
78
- // We assume that this mutation already has a global ID. (It was
79
- // added by our `MutationCache.build` override below.)
80
- const globalId = readGlobalId(context.meta);
81
- // We can't realistically test that it doesn't have a global ID, however,
82
- // since `MutationCache.build` always runs first.
83
- // istanbul ignore next
84
- assert(globalId !== undefined, 'Expected mutation to have a `globalId` in its `meta` by the time its `mutationFn` runs, but none was found. This is a bug in `createUIQueryClient`.');
85
- return await messenger.call(action, ...params, globalId);
86
- };
87
57
  const client = new QueryClient({
88
58
  ...config,
89
59
  defaultOptions: {
@@ -92,7 +62,7 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
92
62
  queryFn: async (options) => {
93
63
  const { queryKey } = options;
94
64
  const action = queryKey[0];
95
- assert(typeof action === 'string' && isRecognizedDataServiceAction(action), "You must pass a `queryKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
65
+ assert(typeof action === 'string' && isRecognizedDataServiceAction(action), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
96
66
  const params = options.queryKey.slice(1);
97
67
  if (options.pageParam !== undefined) {
98
68
  params.push(options.pageParam);
@@ -100,14 +70,11 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
100
70
  return await messenger.call(action, ...params);
101
71
  },
102
72
  },
103
- mutations: {
104
- ...config.defaultOptions?.mutations,
105
- mutationFn: defaultDataServiceMutationFn,
106
- },
73
+ mutations: config.defaultOptions?.mutations,
107
74
  },
108
75
  });
109
- const queryCache = client.getQueryCache();
110
- queryCache.subscribe((event) => {
76
+ const cache = client.getQueryCache();
77
+ cache.subscribe((event) => {
111
78
  const { query } = event;
112
79
  const hash = query.queryHash;
113
80
  const hasSubscription = subscriptions.has(hash);
@@ -123,7 +90,6 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
123
90
  if (payload.type === 'removed') {
124
91
  return;
125
92
  }
126
- log('Hydrating with', payload.state);
127
93
  hydrate(client, payload.state);
128
94
  };
129
95
  subscriptions.set(hash, cacheListener);
@@ -142,60 +108,6 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
142
108
  subscriptions.delete(hash);
143
109
  }
144
110
  });
145
- const mutationCache = client.getMutationCache();
146
- mutationCache.subscribe((event) => {
147
- const { mutation } = event;
148
- if (!mutation?.options.mutationKey) {
149
- return;
150
- }
151
- const service = parseQueryKey(mutation.options.mutationKey);
152
- if (!service) {
153
- return;
154
- }
155
- const hash = hashKey(mutation.options.mutationKey);
156
- const hasSubscription = subscriptions.has(hash);
157
- log(`[mutationCache subscription] Received event "${event.type}". Details:`, event.mutation);
158
- if (event.type === 'observerAdded') {
159
- mutationObserverCounts.set(hash, (mutationObserverCounts.get(hash) ?? 0) + 1);
160
- log('[mutationCache subscription] hasSubscription =', hasSubscription);
161
- if (!hasSubscription) {
162
- const cacheListener = (payload) => {
163
- log(`[mutationCache subscription] cacheUpdated:${hash} emitted`, payload);
164
- // Only `updated` events have meaningful mutation state to sync.
165
- // When a mutation is first created, an `added` event is fired and the
166
- // state is `idle` with no result; hydrating that would clobber a UI
167
- // mutation that has already moved to `pending`, `success`, or
168
- // `error`. (And a `removed` event has no state at all.)
169
- if (payload.type !== 'updated') {
170
- return;
171
- }
172
- hydrateMutations(client, payload.state);
173
- };
174
- subscriptions.set(hash, cacheListener);
175
- messenger.subscribe(`${service}:cacheUpdated:${hash}`, cacheListener);
176
- }
177
- }
178
- else if (event.type === 'observerRemoved' && hasSubscription) {
179
- // We can assume that if an observed mutation is removed, it must have
180
- // first been added; and that when it was added, the observer count was
181
- // initialized. (There's no real way to test the alternative, anyway.)
182
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183
- const remainingObservers = mutationObserverCounts.get(hash) - 1;
184
- if (remainingObservers > 0) {
185
- mutationObserverCounts.set(hash, remainingObservers);
186
- return;
187
- }
188
- mutationObserverCounts.delete(hash);
189
- const subscriptionListener = subscriptions.get(hash);
190
- // A subscription always has a listener, since both are set together when
191
- // the first observer is added.
192
- // istanbul ignore next
193
- if (subscriptionListener) {
194
- messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
195
- }
196
- subscriptions.delete(hash);
197
- }
198
- });
199
111
  // Override invalidateQueries to ensure the data service is invalidated as well.
200
112
  const originalInvalidate = client.invalidateQueries.bind(client);
201
113
  client.invalidateQueries = async (filters, options) => {
@@ -211,43 +123,6 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
211
123
  }));
212
124
  return originalInvalidate(filters, options);
213
125
  };
214
- // Override `build` to ensure that any data-service mutation created via
215
- // `executeMutation` or manually has a `globalId`.
216
- const originalBuildMutation = mutationCache.build.bind(mutationCache);
217
- mutationCache.build = function (buildClient, options, state) {
218
- const mutation = originalBuildMutation(buildClient, options, state);
219
- const { mutationFn } = mutation.options;
220
- if (
221
- // We know that a custom `mutationFn` was provided because it
222
- // is not the default (which is defined above).
223
- mutationFn !== defaultDataServiceMutationFn ||
224
- // No need to add a global ID if there's already one.
225
- readGlobalId(mutation.options.meta) !== undefined) {
226
- return mutation;
227
- }
228
- const globalId = uuidV4();
229
- // Give the mutation its own `options`/`meta` objects rather than mutating
230
- // in place. When options are already defaulted, TanStack returns the shared
231
- // observer options as-is, so mutating them would leak this mutation's
232
- // `globalId` back onto the observer and cause the next mutation built from
233
- // that observer to reuse the same id.
234
- const originalSetMutationOptions = mutation.setOptions.bind(mutation);
235
- originalSetMutationOptions({
236
- ...mutation.options,
237
- meta: { ...mutation.options.meta, globalId },
238
- });
239
- // `MutationObserver.setOptions` (triggered on every re-render) replaces a
240
- // pending mutation's options wholesale, which would otherwise strip the
241
- // `globalId` and stop the mutation from matching its service-side cache
242
- // updates. Preserve the established `globalId` across such updates.
243
- mutation.setOptions = (nextOptions) => {
244
- originalSetMutationOptions({
245
- ...nextOptions,
246
- meta: { ...nextOptions.meta, globalId },
247
- });
248
- };
249
- return mutation;
250
- };
251
126
  return client;
252
127
  }
253
128
  //# sourceMappingURL=createUIQueryClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.js","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataServiceGranularCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedPayload,\n} from '@metamask/base-data-service';\nimport { Json, assert } from '@metamask/utils';\nimport {\n hashKey,\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n QueryKey,\n QueryClientConfig,\n Mutation,\n MutationOptions,\n MutationState,\n MutationFunction,\n} from '@tanstack/query-core';\nimport { v4 as uuidV4 } from 'uuid';\n\nimport { hydrateMutations, readGlobalId } from './hydrateMutations.js';\nimport { createModuleLogger, projectLogger } from './loggers.js';\n\nconst log = createModuleLogger(projectLogger, 'createUIQueryClient');\n\n/**\n * Handles granular cache update events emitted by data services.\n */\ntype DataServiceGranularCacheUpdatedHandler = (\n payload: DataServiceGranularCacheUpdatedPayload,\n) => void;\n\n/**\n * A narrower subset of the `Messenger` type, tailored to the messenger\n * that `createUIQueryClient` interacts with.\n */\ntype MessengerAdapter<DataServiceName extends string> = {\n /**\n * Call an action on one of the configured data services.\n *\n * Note: The parameters are typed as `unknown[]` rather than `Json[]`. For\n * concrete messengers, each action's parameters exist as fixed-length tuple,\n * and a variadic `Json[]` is not assignable to a fixed-length tuple, so using\n * `Json[]` here would reject otherwise valid messengers.\n */\n call(\n actionType: `${DataServiceName}:${string}`,\n ...params: unknown[]\n ): unknown;\n\n /**\n * Subscribe to a granular cache update event on one of the configured data\n * services.\n */\n subscribe(\n eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'],\n handler: DataServiceGranularCacheUpdatedHandler,\n ): void;\n\n /**\n * Unsubscribe from a granular cache update event on one of the configured\n * data services.\n */\n unsubscribe(\n eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'],\n handler: DataServiceGranularCacheUpdatedHandler,\n ): void;\n};\n\n/**\n * Create a QueryClient that enables data services to power queries and mutations via a messenger adapter.\n *\n * This returns a wrapped version of TanStack Query's QueryClient interface. Note that while some methods such as `invalidateQueries` have support for data services, some such as `setQueryData` do not.\n *\n * @param dataServices - A list of data services.\n * @param messenger - A messenger-like object with the following constraints: 1) the messenger must support the `call`, `subscribe` and `unsubscribe` methods; 2) all action handler arguments and event payloads must be JSON-compatible; 3) the messenger must minimally support actions that are scoped to the designated data services and must minimally support the `:cacheUpdated:${hash}` event scoped to the designated data services.\n * @param config - Optional query client configuration options.\n * @returns The created QueryClient.\n */\nexport function createUIQueryClient<DataServiceNames extends readonly string[]>(\n dataServices: DataServiceNames,\n messenger: MessengerAdapter<DataServiceNames[number]>,\n config: QueryClientConfig = {},\n): QueryClient {\n const subscriptions = new Map<\n string,\n DataServiceGranularCacheUpdatedHandler\n >();\n\n // Tracks how many mutation observers are currently relying on each mutation\n // key's cache subscription. Unlike queries, a `Mutation` does not expose its\n // observer count publicly, so we count observers ourselves and only tear down\n // the messenger subscription once the last observer for a key is removed.\n const mutationObserverCounts = new Map<string, number>();\n\n /**\n * Check whether a name is one of the provided data service names.\n *\n * @param service - The service name to check.\n * @returns Whether the service name is configured.\n */\n function isRecognizedDataService(\n service: string,\n ): service is DataServiceNames[number] {\n return dataServices.some((dataService) => dataService === service);\n }\n\n /**\n * Check whether an action belongs to one of the provided data services.\n *\n * @param action - The action name to check.\n * @returns Whether the action belongs to a configured data service.\n */\n function isRecognizedDataServiceAction(\n action: string,\n ): action is `${DataServiceNames[number]}:${string}` {\n return isRecognizedDataService(action.split(':')[0]);\n }\n\n /**\n * Parse a query key to detect a service name.\n *\n * @param queryKey - The query key.\n * @returns The service name if it parsing succeeded, otherwise null.\n */\n function parseQueryKey(queryKey: QueryKey): DataServiceNames[number] | null {\n const action = queryKey[0];\n\n if (typeof action !== 'string') {\n return null;\n }\n\n const service = action.split(':')[0];\n\n if (!isRecognizedDataService(service)) {\n return null;\n }\n\n return service;\n }\n\n /**\n * The default `mutationFn` for data-service mutations. It uses the\n * `mutationKey` to call an action on a data service through the messenger.\n *\n * This is a separate function so that we can use it to determine whether a\n * custom `mutationFn` was used when the mutation was executed.\n *\n * @param _variables - The variables with which the mutation is being\n * executed. Unused.\n * @param context - The context associated with the mutation. Holds the\n * `mutationKey`.\n * @returns The result of the data service action.\n */\n const defaultDataServiceMutationFn: MutationFunction = async (\n _variables,\n context,\n ): Promise<unknown> => {\n const { mutationKey } = context;\n\n assert(\n mutationKey !== undefined,\n \"You must pass a `mutationKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `mutationKey: ['ExampleDataService:createOrder', ...]`.\",\n );\n\n const [action, ...params] = mutationKey;\n\n assert(\n typeof action === 'string' && isRecognizedDataServiceAction(action),\n \"You must pass a `mutationKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `mutationKey: ['ExampleDataService:createOrder', ...]`.\",\n );\n\n log(`Detected mutation request, calling action: \"${action}\"`);\n\n // We assume that this mutation already has a global ID. (It was\n // added by our `MutationCache.build` override below.)\n const globalId = readGlobalId(context.meta);\n // We can't realistically test that it doesn't have a global ID, however,\n // since `MutationCache.build` always runs first.\n // istanbul ignore next\n assert(\n globalId !== undefined,\n 'Expected mutation to have a `globalId` in its `meta` by the time its `mutationFn` runs, but none was found. This is a bug in `createUIQueryClient`.',\n );\n\n return await messenger.call(action, ...(params as Json[]), globalId);\n };\n\n const client: QueryClient = new QueryClient({\n ...config,\n defaultOptions: {\n queries: {\n ...config.defaultOptions?.queries,\n queryFn: async (options): Promise<unknown> => {\n const { queryKey } = options;\n\n const action = queryKey[0];\n\n assert(\n typeof action === 'string' && isRecognizedDataServiceAction(action),\n \"You must pass a `queryKey` that calls an action on the messenger provided to `createUIQueryClient`, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.\",\n );\n\n const params = options.queryKey.slice(1);\n if (options.pageParam !== undefined) {\n params.push(options.pageParam);\n }\n\n return await messenger.call(action, ...params);\n },\n },\n mutations: {\n ...config.defaultOptions?.mutations,\n mutationFn: defaultDataServiceMutationFn,\n },\n },\n });\n\n const queryCache = client.getQueryCache();\n queryCache.subscribe((event) => {\n const { query } = event;\n\n const hash = query.queryHash;\n const hasSubscription = subscriptions.has(hash);\n const observerCount = query.getObserversCount();\n\n const service = parseQueryKey(query.queryKey);\n\n if (!service) {\n return;\n }\n\n if (\n !hasSubscription &&\n event.type === 'observerAdded' &&\n observerCount === 1\n ) {\n const cacheListener: DataServiceGranularCacheUpdatedHandler = (\n payload,\n ) => {\n if (payload.type === 'removed') {\n return;\n }\n\n log('Hydrating with', payload.state);\n hydrate(client, payload.state);\n };\n\n subscriptions.set(hash, cacheListener);\n messenger.subscribe(`${service}:cacheUpdated:${hash}`, cacheListener);\n } else if (\n event.type === 'observerRemoved' &&\n observerCount === 0 &&\n hasSubscription\n ) {\n const subscriptionListener = subscriptions.get(hash);\n\n // We can't write a test for this, as it's unrealistic\n // (we just need a check to appease TypeScript).\n // istanbul ignore next\n if (subscriptionListener) {\n messenger.unsubscribe(\n `${service}:cacheUpdated:${hash}`,\n subscriptionListener,\n );\n }\n subscriptions.delete(hash);\n }\n });\n\n const mutationCache = client.getMutationCache();\n mutationCache.subscribe((event) => {\n const { mutation } = event;\n\n if (!mutation?.options.mutationKey) {\n return;\n }\n\n const service = parseQueryKey(mutation.options.mutationKey);\n\n if (!service) {\n return;\n }\n\n const hash = hashKey(mutation.options.mutationKey);\n const hasSubscription = subscriptions.has(hash);\n\n log(\n `[mutationCache subscription] Received event \"${event.type}\". Details:`,\n event.mutation,\n );\n\n if (event.type === 'observerAdded') {\n mutationObserverCounts.set(\n hash,\n (mutationObserverCounts.get(hash) ?? 0) + 1,\n );\n\n log('[mutationCache subscription] hasSubscription =', hasSubscription);\n\n if (!hasSubscription) {\n const cacheListener = (\n payload: DataServiceGranularCacheUpdatedPayload,\n ): void => {\n log(\n `[mutationCache subscription] cacheUpdated:${hash} emitted`,\n payload,\n );\n\n // Only `updated` events have meaningful mutation state to sync.\n // When a mutation is first created, an `added` event is fired and the\n // state is `idle` with no result; hydrating that would clobber a UI\n // mutation that has already moved to `pending`, `success`, or\n // `error`. (And a `removed` event has no state at all.)\n if (payload.type !== 'updated') {\n return;\n }\n\n hydrateMutations(client, payload.state);\n };\n\n subscriptions.set(hash, cacheListener);\n messenger.subscribe(`${service}:cacheUpdated:${hash}`, cacheListener);\n }\n } else if (event.type === 'observerRemoved' && hasSubscription) {\n // We can assume that if an observed mutation is removed, it must have\n // first been added; and that when it was added, the observer count was\n // initialized. (There's no real way to test the alternative, anyway.)\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const remainingObservers = mutationObserverCounts.get(hash)! - 1;\n\n if (remainingObservers > 0) {\n mutationObserverCounts.set(hash, remainingObservers);\n return;\n }\n\n mutationObserverCounts.delete(hash);\n\n const subscriptionListener = subscriptions.get(hash);\n\n // A subscription always has a listener, since both are set together when\n // the first observer is added.\n // istanbul ignore next\n if (subscriptionListener) {\n messenger.unsubscribe(\n `${service}:cacheUpdated:${hash}`,\n subscriptionListener,\n );\n }\n subscriptions.delete(hash);\n }\n });\n\n // Override invalidateQueries to ensure the data service is invalidated as well.\n\n const originalInvalidate = client.invalidateQueries.bind(client);\n\n client.invalidateQueries = async (\n filters?: InvalidateQueryFilters,\n options?: InvalidateOptions,\n ): Promise<void> => {\n const queries = client.getQueryCache().findAll(filters);\n\n const services = [\n ...new Set(queries.map((query) => parseQueryKey(query.queryKey))),\n ];\n\n await Promise.all(\n services.map(async (service) => {\n if (!service) {\n return null;\n }\n\n return messenger.call(`${service}:invalidateQueries`, filters, options);\n }),\n );\n\n return originalInvalidate(filters, options);\n };\n\n // Override `build` to ensure that any data-service mutation created via\n // `executeMutation` or manually has a `globalId`.\n\n const originalBuildMutation = mutationCache.build.bind(mutationCache);\n\n mutationCache.build = function <TData, TError, TVariables, TOnMutateResult>(\n buildClient: QueryClient,\n options: MutationOptions<TData, TError, TVariables, TOnMutateResult>,\n state?: MutationState<TData, TError, TVariables, TOnMutateResult>,\n ): Mutation<TData, TError, TVariables, TOnMutateResult> {\n const mutation = originalBuildMutation(buildClient, options, state);\n const { mutationFn } = mutation.options;\n\n if (\n // We know that a custom `mutationFn` was provided because it\n // is not the default (which is defined above).\n mutationFn !== defaultDataServiceMutationFn ||\n // No need to add a global ID if there's already one.\n readGlobalId(mutation.options.meta) !== undefined\n ) {\n return mutation;\n }\n\n const globalId = uuidV4();\n\n // Give the mutation its own `options`/`meta` objects rather than mutating\n // in place. When options are already defaulted, TanStack returns the shared\n // observer options as-is, so mutating them would leak this mutation's\n // `globalId` back onto the observer and cause the next mutation built from\n // that observer to reuse the same id.\n const originalSetMutationOptions = mutation.setOptions.bind(mutation);\n originalSetMutationOptions({\n ...mutation.options,\n meta: { ...mutation.options.meta, globalId },\n });\n\n // `MutationObserver.setOptions` (triggered on every re-render) replaces a\n // pending mutation's options wholesale, which would otherwise strip the\n // `globalId` and stop the mutation from matching its service-side cache\n // updates. Preserve the established `globalId` across such updates.\n mutation.setOptions = (nextOptions): void => {\n originalSetMutationOptions({\n ...nextOptions,\n meta: { ...nextOptions.meta, globalId },\n });\n };\n\n return mutation;\n };\n\n return client;\n}\n"]}
1
+ {"version":3,"file":"createUIQueryClient.js","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataServiceGranularCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedPayload,\n} from '@metamask/base-data-service';\nimport { assert } from '@metamask/utils';\nimport {\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n QueryKey,\n QueryClientConfig,\n} from '@tanstack/query-core';\n\n/**\n * Handles granular cache update events emitted by data services.\n */\ntype DataServiceGranularCacheUpdatedHandler = (\n payload: DataServiceGranularCacheUpdatedPayload,\n) => void;\n\n/**\n * A narrower subset of the `Messenger` type, tailored to the messenger\n * that `createUIQueryClient` interacts with.\n */\ntype MessengerAdapter<DataServiceName extends string> = {\n /**\n * Call an action on one of the configured data services.\n *\n * Note: The parameters are typed as `unknown[]` rather than `Json[]`. For\n * concrete messengers, each action's parameters exist as fixed-length tuple,\n * and a variadic `Json[]` is not assignable to a fixed-length tuple, so using\n * `Json[]` here would reject otherwise valid messengers.\n */\n call(\n actionType: `${DataServiceName}:${string}`,\n ...params: unknown[]\n ): unknown;\n\n /**\n * Subscribe to a granular cache update event on one of the configured data\n * services.\n */\n subscribe(\n eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'],\n handler: DataServiceGranularCacheUpdatedHandler,\n ): void;\n\n /**\n * Unsubscribe from a granular cache update event on one of the configured\n * data services.\n */\n unsubscribe(\n eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'],\n handler: DataServiceGranularCacheUpdatedHandler,\n ): void;\n};\n\n/**\n * Create a QueryClient that queries and subscribes to data services using a\n * messenger adapter. This is a messenger-like object that carries some\n * constraints:\n *\n * 1. The messenger must support the `call`, `subscribe` and\n * `unsubscribe` methods.\n * 2. All action handler arguments and event payloads must be JSON-compatible.\n * 3. The messenger must minimally support actions that are scoped to the\n * designated data services and must minimally support the\n * `:cacheUpdated:${hash}` event scoped to the designated data services.\n *\n * @param dataServices - A list of data services.\n * @param messenger - A messenger adapter.\n * @param config - Optional query client configuration options.\n * @returns The QueryClient.\n */\nexport function createUIQueryClient<DataServiceNames extends readonly string[]>(\n dataServices: DataServiceNames,\n messenger: MessengerAdapter<DataServiceNames[number]>,\n config: QueryClientConfig = {},\n): QueryClient {\n const subscriptions = new Map<\n string,\n DataServiceGranularCacheUpdatedHandler\n >();\n\n /**\n * Check whether a name is one of the provided data service names.\n *\n * @param service - The service name to check.\n * @returns Whether the service name is configured.\n */\n function isRecognizedDataService(\n service: string,\n ): service is DataServiceNames[number] {\n return dataServices.some((dataService) => dataService === service);\n }\n\n /**\n * Check whether an action belongs to one of the provided data services.\n *\n * @param action - The action name to check.\n * @returns Whether the action belongs to a configured data service.\n */\n function isRecognizedDataServiceAction(\n action: string,\n ): action is `${DataServiceNames[number]}:${string}` {\n return isRecognizedDataService(action.split(':')[0]);\n }\n\n /**\n * Parse a query key to detect a service name.\n *\n * @param queryKey - The query key.\n * @returns The service name if it parsing succeeded, otherwise null.\n */\n function parseQueryKey(queryKey: QueryKey): DataServiceNames[number] | null {\n const action = queryKey[0];\n\n if (typeof action !== 'string') {\n return null;\n }\n\n const service = action.split(':')[0];\n\n if (!isRecognizedDataService(service)) {\n return null;\n }\n\n return service;\n }\n\n const client: QueryClient = new QueryClient({\n ...config,\n defaultOptions: {\n queries: {\n ...config.defaultOptions?.queries,\n queryFn: async (options): Promise<unknown> => {\n const { queryKey } = options;\n\n const action = queryKey[0];\n\n assert(\n typeof action === 'string' && isRecognizedDataServiceAction(action),\n \"Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.\",\n );\n\n const params = options.queryKey.slice(1);\n if (options.pageParam !== undefined) {\n params.push(options.pageParam);\n }\n\n return await messenger.call(action, ...params);\n },\n },\n mutations: config.defaultOptions?.mutations,\n },\n });\n\n const cache = client.getQueryCache();\n\n cache.subscribe((event) => {\n const { query } = event;\n\n const hash = query.queryHash;\n const hasSubscription = subscriptions.has(hash);\n const observerCount = query.getObserversCount();\n\n const service = parseQueryKey(query.queryKey);\n\n if (!service) {\n return;\n }\n\n if (\n !hasSubscription &&\n event.type === 'observerAdded' &&\n observerCount === 1\n ) {\n const cacheListener: DataServiceGranularCacheUpdatedHandler = (\n payload,\n ) => {\n if (payload.type === 'removed') {\n return;\n }\n\n hydrate(client, payload.state);\n };\n\n subscriptions.set(hash, cacheListener);\n messenger.subscribe(`${service}:cacheUpdated:${hash}`, cacheListener);\n } else if (\n event.type === 'observerRemoved' &&\n observerCount === 0 &&\n hasSubscription\n ) {\n const subscriptionListener = subscriptions.get(hash);\n\n // We can't write a test for this, as it's unrealistic\n // (we just need a check to appease TypeScript).\n // istanbul ignore next\n if (subscriptionListener) {\n messenger.unsubscribe(\n `${service}:cacheUpdated:${hash}`,\n subscriptionListener,\n );\n }\n subscriptions.delete(hash);\n }\n });\n\n // Override invalidateQueries to ensure the data service is invalidated as well.\n const originalInvalidate = client.invalidateQueries.bind(client);\n\n client.invalidateQueries = async (\n filters?: InvalidateQueryFilters,\n options?: InvalidateOptions,\n ): Promise<void> => {\n const queries = client.getQueryCache().findAll(filters);\n\n const services = [\n ...new Set(queries.map((query) => parseQueryKey(query.queryKey))),\n ];\n\n await Promise.all(\n services.map(async (service) => {\n if (!service) {\n return null;\n }\n\n return messenger.call(`${service}:invalidateQueries`, filters, options);\n }),\n );\n\n return originalInvalidate(filters, options);\n };\n\n return client;\n}\n"]}
package/dist/hooks.d.ts CHANGED
@@ -1,10 +1,5 @@
1
- /**
2
- * @file
3
- * We provide re-exports of the underlying TanStack Query hooks with narrower types,
4
- * removing `staleTime` and `queryFn` which aren't useful when using data services.
5
- */
6
- import { QueryKey, MutationKey } from '@metamask/base-data-service';
7
- import { OmitKeyof, UseQueryOptions, InitialDataFunction, NonUndefinedGuard, UseInfiniteQueryOptions, UseQueryResult, UseInfiniteQueryResult, DefaultError, InfiniteData, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
1
+ import { QueryKey } from '@metamask/base-data-service';
2
+ import { OmitKeyof, UseQueryOptions, InitialDataFunction, NonUndefinedGuard, UseInfiniteQueryOptions, UseQueryResult, UseInfiniteQueryResult, DefaultError, InfiniteData } from '@tanstack/react-query';
8
3
  /**
9
4
  * Consume a query from a data service.
10
5
  *
@@ -23,22 +18,4 @@ export declare function useQuery<TQueryFnData = unknown, TError = DefaultError,
23
18
  * @returns The paginated query results.
24
19
  */
25
20
  export declare function useInfiniteQuery<TQueryFnData = unknown, TError = DefaultError, TData = InfiniteData<TQueryFnData>, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown>(options: OmitKeyof<UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>, 'staleTime' | 'queryFn'>): UseInfiniteQueryResult<TData, TError>;
26
- /**
27
- * Execute a mutation through a data service.
28
- *
29
- * This is a version of `useMutation` from TanStack Query which is intended to be used with a {@link createUIQueryClient|UI query client}. Note the following constraints:
30
- *
31
- * - The `mutationKey` must refer to a method within a data service by matching the following format: ``[`${ServiceName}:${actionName}`, ...arguments]``.
32
- * - Providing a custom `mutationFn` is not supported.
33
- * - Updating a connected query's cache data [before][1] or [after][2] a mutation via `setQueryData` is not supported.
34
- *
35
- * [1]: https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates#via-the-cache
36
- * [2]: https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses
37
- *
38
- * @param options - The mutation options.
39
- * @returns The result of the mutation.
40
- */
41
- export declare function useMutation<TData = unknown, TError = DefaultError, TVariables = void, TOnMutateResult = unknown, TMutationKey extends MutationKey = MutationKey>(options: OmitKeyof<UseMutationOptions<TData, TError, TVariables, TOnMutateResult>, 'mutationKey' | 'mutationFn'> & {
42
- mutationKey: TMutationKey;
43
- }): UseMutationResult<TData, TError, TVariables, TOnMutateResult>;
44
21
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAIL,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAe/B;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,YAAY,EACrB,KAAK,GAAG,YAAY,EACpB,SAAS,SAAS,QAAQ,GAAG,QAAQ,EAErC,OAAO,EAAE,SAAS,CAChB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EACvD,aAAa,GAAG,WAAW,GAAG,SAAS,CACxC,GAAG;IACF,WAAW,CAAC,EACR,SAAS,GACT,mBAAmB,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,GACpD,iBAAiB,CAAC,YAAY,CAAC,CAAC;CACrC,GACA,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,YAAY,EACrB,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,EAClC,SAAS,SAAS,QAAQ,GAAG,QAAQ,EACrC,UAAU,GAAG,OAAO,EAEpB,OAAO,EAAE,SAAS,CAChB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,EAC3E,WAAW,GAAG,SAAS,CACxB,GACA,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAKvC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CACzB,KAAK,GAAG,OAAO,EACf,MAAM,GAAG,YAAY,EACrB,UAAU,GAAG,IAAI,EACjB,eAAe,GAAG,OAAO,EACzB,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,EAAE,SAAS,CAChB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,CAAC,EAC9D,aAAa,GAAG,YAAY,CAC7B,GAAG;IACF,WAAW,EAAE,YAAY,CAAC;CAC3B,GACA,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,CAAC,CAK/D"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAGL,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,EACd,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACb,MAAM,uBAAuB,CAAC;AAY/B;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,YAAY,EACrB,KAAK,GAAG,YAAY,EACpB,SAAS,SAAS,QAAQ,GAAG,QAAQ,EAErC,OAAO,EAAE,SAAS,CAChB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EACvD,aAAa,GAAG,WAAW,GAAG,SAAS,CACxC,GAAG;IACF,WAAW,CAAC,EACR,SAAS,GACT,mBAAmB,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,GACpD,iBAAiB,CAAC,YAAY,CAAC,CAAC;CACrC,GACA,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,GAAG,OAAO,EACtB,MAAM,GAAG,YAAY,EACrB,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,EAClC,SAAS,SAAS,QAAQ,GAAG,QAAQ,EACrC,UAAU,GAAG,OAAO,EAEpB,OAAO,EAAE,SAAS,CAChB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,EAC3E,WAAW,GAAG,SAAS,CACxB,GACA,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAKvC"}
package/dist/hooks.js CHANGED
@@ -1,16 +1,12 @@
1
+ import { useQuery as useQueryTanStack, useInfiniteQuery as useInfiniteQueryTanStack, } from '@tanstack/react-query';
1
2
  /**
2
- * @file
3
3
  * We provide re-exports of the underlying TanStack Query hooks with narrower types,
4
4
  * removing `staleTime` and `queryFn` which aren't useful when using data services.
5
5
  */
6
- import { useQuery as useQueryFromTanStack, useInfiniteQuery as useInfiniteQueryFromTanStack, useMutation as useMutationFromTanStack, } from '@tanstack/react-query';
7
6
  const DATA_SERVICE_QUERY_DEFAULTS = {
8
7
  staleTime: 0,
9
8
  retry: false,
10
9
  };
11
- const DATA_SERVICE_MUTATION_DEFAULTS = {
12
- retry: false,
13
- };
14
10
  /**
15
11
  * Consume a query from a data service.
16
12
  *
@@ -19,7 +15,7 @@ const DATA_SERVICE_MUTATION_DEFAULTS = {
19
15
  * @returns The query results.
20
16
  */
21
17
  export function useQuery(options) {
22
- return useQueryFromTanStack({ ...DATA_SERVICE_QUERY_DEFAULTS, ...options });
18
+ return useQueryTanStack({ ...DATA_SERVICE_QUERY_DEFAULTS, ...options });
23
19
  }
24
20
  /**
25
21
  * Consume a paginated query from a data service.
@@ -29,30 +25,9 @@ export function useQuery(options) {
29
25
  * @returns The paginated query results.
30
26
  */
31
27
  export function useInfiniteQuery(options) {
32
- return useInfiniteQueryFromTanStack({
28
+ return useInfiniteQueryTanStack({
33
29
  ...DATA_SERVICE_QUERY_DEFAULTS,
34
30
  ...options,
35
31
  });
36
32
  }
37
- /**
38
- * Execute a mutation through a data service.
39
- *
40
- * This is a version of `useMutation` from TanStack Query which is intended to be used with a {@link createUIQueryClient|UI query client}. Note the following constraints:
41
- *
42
- * - The `mutationKey` must refer to a method within a data service by matching the following format: ``[`${ServiceName}:${actionName}`, ...arguments]``.
43
- * - Providing a custom `mutationFn` is not supported.
44
- * - Updating a connected query's cache data [before][1] or [after][2] a mutation via `setQueryData` is not supported.
45
- *
46
- * [1]: https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates#via-the-cache
47
- * [2]: https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses
48
- *
49
- * @param options - The mutation options.
50
- * @returns The result of the mutation.
51
- */
52
- export function useMutation(options) {
53
- return useMutationFromTanStack({
54
- ...DATA_SERVICE_MUTATION_DEFAULTS,
55
- ...options,
56
- });
57
- }
58
33
  //# sourceMappingURL=hooks.js.map
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,QAAQ,IAAI,oBAAoB,EAChC,gBAAgB,IAAI,4BAA4B,EAChD,WAAW,IAAI,uBAAuB,GAYvC,MAAM,uBAAuB,CAAC;AAM/B,MAAM,2BAA2B,GAAG;IAClC,SAAS,EAAE,CAAC;IACZ,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,MAAM,8BAA8B,GAAG;IACrC,KAAK,EAAE,KAAK;CACb,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAMtB,OAQC;IAED,OAAO,oBAAoB,CAAC,EAAE,GAAG,2BAA2B,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAO9B,OAGC;IAED,OAAO,4BAA4B,CAAC;QAClC,GAAG,2BAA2B;QAC9B,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAOzB,OAKC;IAED,OAAO,uBAAuB,CAAC;QAC7B,GAAG,8BAA8B;QACjC,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * @file\n * We provide re-exports of the underlying TanStack Query hooks with narrower types,\n * removing `staleTime` and `queryFn` which aren't useful when using data services.\n */\n\nimport { QueryKey, MutationKey } from '@metamask/base-data-service';\nimport {\n useQuery as useQueryFromTanStack,\n useInfiniteQuery as useInfiniteQueryFromTanStack,\n useMutation as useMutationFromTanStack,\n OmitKeyof,\n UseQueryOptions,\n InitialDataFunction,\n NonUndefinedGuard,\n UseInfiniteQueryOptions,\n UseQueryResult,\n UseInfiniteQueryResult,\n DefaultError,\n InfiniteData,\n UseMutationOptions,\n UseMutationResult,\n} from '@tanstack/react-query';\n\n// This is referenced in JSDoc below.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { createUIQueryClient } from './createUIQueryClient.js';\n\nconst DATA_SERVICE_QUERY_DEFAULTS = {\n staleTime: 0,\n retry: false,\n};\n\nconst DATA_SERVICE_MUTATION_DEFAULTS = {\n retry: false,\n};\n\n/**\n * Consume a query from a data service.\n *\n * @param options - The query options. Keep in mind that `staleTime` and `queryFn` are not supported\n * when querying data services.\n * @returns The query results.\n */\nexport function useQuery<\n TQueryFnData = unknown,\n TError = DefaultError,\n TData = TQueryFnData,\n TQueryKey extends QueryKey = QueryKey,\n>(\n options: OmitKeyof<\n UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n 'initialData' | 'staleTime' | 'queryFn'\n > & {\n initialData?:\n | undefined\n | InitialDataFunction<NonUndefinedGuard<TQueryFnData>>\n | NonUndefinedGuard<TQueryFnData>;\n },\n): UseQueryResult<TData, TError> {\n return useQueryFromTanStack({ ...DATA_SERVICE_QUERY_DEFAULTS, ...options });\n}\n\n/**\n * Consume a paginated query from a data service.\n *\n * @param options - The query options. Keep in mind that `staleTime` and `queryFn` are not supported\n * when querying data services.\n * @returns The paginated query results.\n */\nexport function useInfiniteQuery<\n TQueryFnData = unknown,\n TError = DefaultError,\n TData = InfiniteData<TQueryFnData>,\n TQueryKey extends QueryKey = QueryKey,\n TPageParam = unknown,\n>(\n options: OmitKeyof<\n UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,\n 'staleTime' | 'queryFn'\n >,\n): UseInfiniteQueryResult<TData, TError> {\n return useInfiniteQueryFromTanStack({\n ...DATA_SERVICE_QUERY_DEFAULTS,\n ...options,\n });\n}\n\n/**\n * Execute a mutation through a data service.\n *\n * This is a version of `useMutation` from TanStack Query which is intended to be used with a {@link createUIQueryClient|UI query client}. Note the following constraints:\n *\n * - The `mutationKey` must refer to a method within a data service by matching the following format: ``[`${ServiceName}:${actionName}`, ...arguments]``.\n * - Providing a custom `mutationFn` is not supported.\n * - Updating a connected query's cache data [before][1] or [after][2] a mutation via `setQueryData` is not supported.\n *\n * [1]: https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates#via-the-cache\n * [2]: https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses\n *\n * @param options - The mutation options.\n * @returns The result of the mutation.\n */\nexport function useMutation<\n TData = unknown,\n TError = DefaultError,\n TVariables = void,\n TOnMutateResult = unknown,\n TMutationKey extends MutationKey = MutationKey,\n>(\n options: OmitKeyof<\n UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,\n 'mutationKey' | 'mutationFn'\n > & {\n mutationKey: TMutationKey;\n },\n): UseMutationResult<TData, TError, TVariables, TOnMutateResult> {\n return useMutationFromTanStack({\n ...DATA_SERVICE_MUTATION_DEFAULTS,\n ...options,\n });\n}\n"]}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,IAAI,gBAAgB,EAC5B,gBAAgB,IAAI,wBAAwB,GAU7C,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AAEH,MAAM,2BAA2B,GAAG;IAClC,SAAS,EAAE,CAAC;IACZ,KAAK,EAAE,KAAK;CACb,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAMtB,OAQC;IAED,OAAO,gBAAgB,CAAC,EAAE,GAAG,2BAA2B,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAO9B,OAGC;IAED,OAAO,wBAAwB,CAAC;QAC9B,GAAG,2BAA2B;QAC9B,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { QueryKey } from '@metamask/base-data-service';\nimport {\n useQuery as useQueryTanStack,\n useInfiniteQuery as useInfiniteQueryTanStack,\n OmitKeyof,\n UseQueryOptions,\n InitialDataFunction,\n NonUndefinedGuard,\n UseInfiniteQueryOptions,\n UseQueryResult,\n UseInfiniteQueryResult,\n DefaultError,\n InfiniteData,\n} from '@tanstack/react-query';\n\n/**\n * We provide re-exports of the underlying TanStack Query hooks with narrower types,\n * removing `staleTime` and `queryFn` which aren't useful when using data services.\n */\n\nconst DATA_SERVICE_QUERY_DEFAULTS = {\n staleTime: 0,\n retry: false,\n};\n\n/**\n * Consume a query from a data service.\n *\n * @param options - The query options. Keep in mind that `staleTime` and `queryFn` are not supported\n * when querying data services.\n * @returns The query results.\n */\nexport function useQuery<\n TQueryFnData = unknown,\n TError = DefaultError,\n TData = TQueryFnData,\n TQueryKey extends QueryKey = QueryKey,\n>(\n options: OmitKeyof<\n UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,\n 'initialData' | 'staleTime' | 'queryFn'\n > & {\n initialData?:\n | undefined\n | InitialDataFunction<NonUndefinedGuard<TQueryFnData>>\n | NonUndefinedGuard<TQueryFnData>;\n },\n): UseQueryResult<TData, TError> {\n return useQueryTanStack({ ...DATA_SERVICE_QUERY_DEFAULTS, ...options });\n}\n\n/**\n * Consume a paginated query from a data service.\n *\n * @param options - The query options. Keep in mind that `staleTime` and `queryFn` are not supported\n * when querying data services.\n * @returns The paginated query results.\n */\nexport function useInfiniteQuery<\n TQueryFnData = unknown,\n TError = DefaultError,\n TData = InfiniteData<TQueryFnData>,\n TQueryKey extends QueryKey = QueryKey,\n TPageParam = unknown,\n>(\n options: OmitKeyof<\n UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,\n 'staleTime' | 'queryFn'\n >,\n): UseInfiniteQueryResult<TData, TError> {\n return useInfiniteQueryTanStack({\n ...DATA_SERVICE_QUERY_DEFAULTS,\n ...options,\n });\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createUIQueryClient } from './createUIQueryClient.js';
2
- export { useQuery, useInfiniteQuery, useMutation } from './hooks.js';
2
+ export { useQuery, useInfiniteQuery } from './hooks.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createUIQueryClient } from './createUIQueryClient.js';
2
- export { useQuery, useInfiniteQuery, useMutation } from './hooks.js';
2
+ export { useQuery, useInfiniteQuery } from './hooks.js';
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { createUIQueryClient } from './createUIQueryClient.js';\nexport { useQuery, useInfiniteQuery, useMutation } from './hooks.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { createUIQueryClient } from './createUIQueryClient.js';\nexport { useQuery, useInfiniteQuery } from './hooks.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/react-data-query",
3
- "version": "2.0.0-preview-acac4ba67",
3
+ "version": "2.0.0-preview-8f81c90d5",
4
4
  "description": "Provides React utilities for consuming data services",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -50,21 +50,19 @@
50
50
  "dependencies": {
51
51
  "@metamask/base-data-service": "^2.0.0",
52
52
  "@metamask/utils": "^12.0.0",
53
- "@tanstack/query-core": "^5.89.0",
54
- "@tanstack/react-query": "^5.89.0",
55
- "uuid": "^11.1.1"
53
+ "@tanstack/query-core": "^5.62.16",
54
+ "@tanstack/react-query": "^5.62.16"
56
55
  },
57
56
  "devDependencies": {
58
- "@metamask/auto-changelog": "^6.2.1",
57
+ "@metamask/auto-changelog": "^6.1.0",
59
58
  "@metamask/messenger": "^3.0.0",
60
59
  "@types/jest": "^30.0.0",
61
60
  "@typescript/native": "npm:typescript@^7.0.2",
62
61
  "deepmerge": "^4.2.2",
63
62
  "jest": "^30.4.2",
64
- "nock": "^13.5.6",
65
- "rimraf": "^6.1.3",
66
- "ts-jest": "^29.4.12",
67
- "tsx": "^4.23.13",
63
+ "rimraf": "^5.0.5",
64
+ "ts-jest": "^29.4.11",
65
+ "tsx": "^4.20.5",
68
66
  "typedoc": "^0.25.13",
69
67
  "typedoc-plugin-missing-exports": "^2.0.0",
70
68
  "typescript": "npm:@typescript/typescript6@^6.0.2"
@@ -1,34 +0,0 @@
1
- import { QueryClient, DehydratedState } from '@tanstack/query-core';
2
- /**
3
- * Get the `globalId` of a mutation by reading its `meta` data.
4
- *
5
- * Each mutation that is routed from the UI query client to a data service is
6
- * assigned a `globalId` through the mutation's `meta` property. However, the
7
- * `meta` property is optional and untyped, so reading this property back
8
- * requires some validation.
9
- *
10
- * @param meta - The mutation `meta`, if any.
11
- * @returns The `globalId` if present and a string, otherwise undefined.
12
- */
13
- export declare function readGlobalId(meta: Record<string, unknown> | undefined): string | undefined;
14
- /**
15
- * Load dehydrated mutations into the given query client.
16
- *
17
- * TanStack Query's `hydrate` function works well for queries: it ensures that
18
- * incoming queries are deduplicated (using the computed query key hash). But
19
- * mutations don't need to be deduplicated, and so `hydrate` follows a different
20
- * process, opting to register incoming mutations as new entries.
21
- *
22
- * This does not work well for what we want to achieve, which is to be able to
23
- * synchronize queries and mutations between a data service query client and a
24
- * UI query client. To accomplish this, we assume that mutations which
25
- * originated on the UI side have been tagged with a custom UUID (stored as
26
- * `globalId` in its `meta`). This allows us to keep mutations with the same
27
- * UUID on both sides and thus sychronize them effectively.
28
- *
29
- * @param client - The UI query client whose mutation cache should be hydrated.
30
- * @param dehydratedState - The dehydrated state emitted by a data service's
31
- * `:cacheUpdated` event.
32
- */
33
- export declare function hydrateMutations(client: QueryClient, dehydratedState: DehydratedState): void;
34
- //# sourceMappingURL=hydrateMutations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateMutations.d.ts","sourceRoot":"","sources":["../src/hydrateMutations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACxC,MAAM,GAAG,SAAS,CAGpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,eAAe,GAC/B,IAAI,CAkCN"}
@@ -1,92 +0,0 @@
1
- /**
2
- * Get the `globalId` of a mutation by reading its `meta` data.
3
- *
4
- * Each mutation that is routed from the UI query client to a data service is
5
- * assigned a `globalId` through the mutation's `meta` property. However, the
6
- * `meta` property is optional and untyped, so reading this property back
7
- * requires some validation.
8
- *
9
- * @param meta - The mutation `meta`, if any.
10
- * @returns The `globalId` if present and a string, otherwise undefined.
11
- */
12
- export function readGlobalId(meta) {
13
- const globalId = meta?.globalId;
14
- return typeof globalId === 'string' ? globalId : undefined;
15
- }
16
- /**
17
- * Load dehydrated mutations into the given query client.
18
- *
19
- * TanStack Query's `hydrate` function works well for queries: it ensures that
20
- * incoming queries are deduplicated (using the computed query key hash). But
21
- * mutations don't need to be deduplicated, and so `hydrate` follows a different
22
- * process, opting to register incoming mutations as new entries.
23
- *
24
- * This does not work well for what we want to achieve, which is to be able to
25
- * synchronize queries and mutations between a data service query client and a
26
- * UI query client. To accomplish this, we assume that mutations which
27
- * originated on the UI side have been tagged with a custom UUID (stored as
28
- * `globalId` in its `meta`). This allows us to keep mutations with the same
29
- * UUID on both sides and thus sychronize them effectively.
30
- *
31
- * @param client - The UI query client whose mutation cache should be hydrated.
32
- * @param dehydratedState - The dehydrated state emitted by a data service's
33
- * `:cacheUpdated` event.
34
- */
35
- export function hydrateMutations(client, dehydratedState) {
36
- const mutationCache = client.getMutationCache();
37
- for (const dehydratedMutation of dehydratedState.mutations) {
38
- const { mutationKey, state, meta } = dehydratedMutation;
39
- const globalId = readGlobalId(meta);
40
- // Although TanStack Query does not require mutations to have mutation keys,
41
- // all mutations created through data services or the UI query client *must*
42
- // have one. They must also have a global ID.
43
- if (!mutationKey || !globalId) {
44
- continue;
45
- }
46
- const existingMutation = mutationCache.find({
47
- mutationKey,
48
- predicate: (mutation) => readGlobalId(mutation.meta) === globalId,
49
- });
50
- // A UI query client only subscribes to a mutation key's cache updates after
51
- // it has built a mutation for that key, so there is always a matching
52
- // mutation to update in place, and we can disregard the case in which there
53
- // is not.
54
- // istanbul ignore else
55
- if (existingMutation) {
56
- existingMutation.state = state;
57
- mutationCache.notify({
58
- type: 'updated',
59
- mutation: existingMutation,
60
- action: deriveMutationAction(state),
61
- });
62
- }
63
- }
64
- }
65
- /**
66
- * When publishing an `update` event through the mutation cache we must supply
67
- * an action. This function derives an appropriate action from a dehydrated
68
- * mutation's state.
69
- *
70
- * @param state - The state of a dehydrated mutation.
71
- * @returns The mutation cache action describing the state.
72
- */
73
- function deriveMutationAction(state) {
74
- switch (state.status) {
75
- case 'success':
76
- return { type: 'success', data: state.data };
77
- case 'error':
78
- return { type: 'error', error: state.error };
79
- case 'pending':
80
- return {
81
- type: 'pending',
82
- variables: state.variables,
83
- context: state.context,
84
- isPaused: state.isPaused,
85
- };
86
- // The `idle` status (the default) has no data, error, or variables, and
87
- // thus no result.
88
- default:
89
- return { type: 'continue' };
90
- }
91
- }
92
- //# sourceMappingURL=hydrateMutations.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateMutations.js","sourceRoot":"","sources":["../src/hydrateMutations.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAyC;IAEzC,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;IAChC,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAmB,EACnB,eAAgC;IAEhC,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;IAEhD,KAAK,MAAM,kBAAkB,IAAI,eAAe,CAAC,SAAS,EAAE,CAAC;QAC3D,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC;QAExD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAEpC,4EAA4E;QAC5E,4EAA4E;QAC5E,6CAA6C;QAC7C,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,MAAM,gBAAgB,GAAG,aAAa,CAAC,IAAI,CAAC;YAC1C,WAAW;YACX,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,QAAQ;SAClE,CAAC,CAAC;QAEH,4EAA4E;QAC5E,sEAAsE;QACtE,4EAA4E;QAC5E,UAAU;QACV,uBAAuB;QACvB,IAAI,gBAAgB,EAAE,CAAC;YACrB,gBAAgB,CAAC,KAAK,GAAG,KAAK,CAAC;YAC/B,aAAa,CAAC,MAAM,CAAC;gBACnB,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,gBAAgB;gBAC1B,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC;aACpC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAC3B,KAAoB;IAMpB,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC/C,KAAK,OAAO;YACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/C,KAAK,SAAS;YACZ,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC;QACJ,wEAAwE;QACxE,kBAAkB;QAClB;YACE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAChC,CAAC;AACH,CAAC","sourcesContent":["import {\n QueryClient,\n DehydratedState,\n MutationState,\n} from '@tanstack/query-core';\n\n/**\n * Get the `globalId` of a mutation by reading its `meta` data.\n *\n * Each mutation that is routed from the UI query client to a data service is\n * assigned a `globalId` through the mutation's `meta` property. However, the\n * `meta` property is optional and untyped, so reading this property back\n * requires some validation.\n *\n * @param meta - The mutation `meta`, if any.\n * @returns The `globalId` if present and a string, otherwise undefined.\n */\nexport function readGlobalId(\n meta: Record<string, unknown> | undefined,\n): string | undefined {\n const globalId = meta?.globalId;\n return typeof globalId === 'string' ? globalId : undefined;\n}\n\n/**\n * Load dehydrated mutations into the given query client.\n *\n * TanStack Query's `hydrate` function works well for queries: it ensures that\n * incoming queries are deduplicated (using the computed query key hash). But\n * mutations don't need to be deduplicated, and so `hydrate` follows a different\n * process, opting to register incoming mutations as new entries.\n *\n * This does not work well for what we want to achieve, which is to be able to\n * synchronize queries and mutations between a data service query client and a\n * UI query client. To accomplish this, we assume that mutations which\n * originated on the UI side have been tagged with a custom UUID (stored as\n * `globalId` in its `meta`). This allows us to keep mutations with the same\n * UUID on both sides and thus sychronize them effectively.\n *\n * @param client - The UI query client whose mutation cache should be hydrated.\n * @param dehydratedState - The dehydrated state emitted by a data service's\n * `:cacheUpdated` event.\n */\nexport function hydrateMutations(\n client: QueryClient,\n dehydratedState: DehydratedState,\n): void {\n const mutationCache = client.getMutationCache();\n\n for (const dehydratedMutation of dehydratedState.mutations) {\n const { mutationKey, state, meta } = dehydratedMutation;\n\n const globalId = readGlobalId(meta);\n\n // Although TanStack Query does not require mutations to have mutation keys,\n // all mutations created through data services or the UI query client *must*\n // have one. They must also have a global ID.\n if (!mutationKey || !globalId) {\n continue;\n }\n\n const existingMutation = mutationCache.find({\n mutationKey,\n predicate: (mutation) => readGlobalId(mutation.meta) === globalId,\n });\n\n // A UI query client only subscribes to a mutation key's cache updates after\n // it has built a mutation for that key, so there is always a matching\n // mutation to update in place, and we can disregard the case in which there\n // is not.\n // istanbul ignore else\n if (existingMutation) {\n existingMutation.state = state;\n mutationCache.notify({\n type: 'updated',\n mutation: existingMutation,\n action: deriveMutationAction(state),\n });\n }\n }\n}\n\n/**\n * When publishing an `update` event through the mutation cache we must supply\n * an action. This function derives an appropriate action from a dehydrated\n * mutation's state.\n *\n * @param state - The state of a dehydrated mutation.\n * @returns The mutation cache action describing the state.\n */\nfunction deriveMutationAction(\n state: MutationState,\n):\n | { type: 'success'; data: unknown }\n | { type: 'error'; error: unknown }\n | { type: 'pending'; variables: unknown; context: unknown; isPaused: boolean }\n | { type: 'continue' } {\n switch (state.status) {\n case 'success':\n return { type: 'success', data: state.data };\n case 'error':\n return { type: 'error', error: state.error };\n case 'pending':\n return {\n type: 'pending',\n variables: state.variables,\n context: state.context,\n isPaused: state.isPaused,\n };\n // The `idle` status (the default) has no data, error, or variables, and\n // thus no result.\n default:\n return { type: 'continue' };\n }\n}\n"]}
package/dist/loggers.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { createModuleLogger } from '@metamask/utils';
2
- export declare const projectLogger: import("debug").Debugger;
3
- export { createModuleLogger };
4
- //# sourceMappingURL=loggers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loggers.d.ts","sourceRoot":"","sources":["../src/loggers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,eAAO,MAAM,aAAa,0BAA0C,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
package/dist/loggers.js DELETED
@@ -1,4 +0,0 @@
1
- import { createProjectLogger, createModuleLogger } from '@metamask/utils';
2
- export const projectLogger = createProjectLogger('react-data-query');
3
- export { createModuleLogger };
4
- //# sourceMappingURL=loggers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loggers.js","sourceRoot":"","sources":["../src/loggers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import { createProjectLogger, createModuleLogger } from '@metamask/utils';\n\nexport const projectLogger = createProjectLogger('react-data-query');\n\nexport { createModuleLogger };\n"]}