@metamask-previews/react-data-query 0.2.2-preview-d77c814bb → 0.2.2-preview-48f22dbe8

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
@@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
12
  - **BREAKING:** Upgrade `@tanstack/query-core` and `@tanstack/react-query` from `^4.43.0` to `^5.62.16` ([#9563](https://github.com/MetaMask/core/pull/9563))
13
13
  - `createUIQueryClient`'s `invalidateQueries` override now matches the TanStack Query v5 signature (`filters`, `options`) instead of the v4 overload style that relied on `parseFilterArgs`.
14
14
  - Consumers must migrate to TanStack Query v5 APIs (for example `gcTime` instead of `cacheTime`, and `initialPageParam` for infinite queries).
15
+ - **BREAKING:** Constrain `createUIQueryClient`'s messenger-like type to match the given data services ([#9475](https://github.com/MetaMask/core/pull/9475))
16
+ - The messenger-like object that `createUIQueryClient` takes must minimally support actions or `:cacheUpdated:${hash}` events which are namespaced by the provided data service names.
17
+ - If you're passing a messenger, it should "just work" as long as your messenger supports the right actions and events.
18
+ - If you're passing a messenger adapter defined in its own variable, you may need to update its type. See `MessengerAdapter` in `packages/react-data-query/src/createUIQueryClient.ts` for an example.
19
+ - If you're passing a messenger adapter directly (it is not defined in its own variable), then it should also "just work".
20
+ - The types for `createUIQueryClient` no longer check that the provided messenger's actions are JSON-compatible ([#9475](https://github.com/MetaMask/core/pull/9475))
21
+ - If you are experiencing any errors calling actions through the query client, check to make sure their parameters are JSON-compatible.
15
22
 
16
23
  ## [0.2.2]
17
24
 
@@ -4,7 +4,16 @@ exports.createUIQueryClient = void 0;
4
4
  const utils_1 = require("@metamask/utils");
5
5
  const query_core_1 = require("@tanstack/query-core");
6
6
  /**
7
- * Create a QueryClient queries and subscribes to data services using the messenger.
7
+ * Create a QueryClient that queries and subscribes to data services using a
8
+ * messenger adapter. This is a messenger-like object that carries some
9
+ * constraints:
10
+ *
11
+ * 1. The messenger must support the `call`, `subscribe` and
12
+ * `unsubscribe` methods.
13
+ * 2. All action handler arguments and event payloads must be JSON-compatible.
14
+ * 3. The messenger must minimally support actions that are scoped to the
15
+ * designated data services and must minimally support the
16
+ * `:cacheUpdated:${hash}` event scoped to the designated data services.
8
17
  *
9
18
  * @param dataServices - A list of data services.
10
19
  * @param messenger - A messenger adapter.
@@ -13,6 +22,24 @@ const query_core_1 = require("@tanstack/query-core");
13
22
  */
14
23
  function createUIQueryClient(dataServices, messenger, config = {}) {
15
24
  const subscriptions = new Map();
25
+ /**
26
+ * Check whether a name is one of the provided data service names.
27
+ *
28
+ * @param service - The service name to check.
29
+ * @returns Whether the service name is configured.
30
+ */
31
+ function isRecognizedDataService(service) {
32
+ return dataServices.some((dataService) => dataService === service);
33
+ }
34
+ /**
35
+ * Check whether an action belongs to one of the provided data services.
36
+ *
37
+ * @param action - The action name to check.
38
+ * @returns Whether the action belongs to a configured data service.
39
+ */
40
+ function isRecognizedDataServiceAction(action) {
41
+ return isRecognizedDataService(action.split(':')[0]);
42
+ }
16
43
  /**
17
44
  * Parse a query key to detect a service name.
18
45
  *
@@ -25,7 +52,7 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
25
52
  return null;
26
53
  }
27
54
  const service = action.split(':')[0];
28
- if (!dataServices.includes(service)) {
55
+ if (!isRecognizedDataService(service)) {
29
56
  return null;
30
57
  }
31
58
  return service;
@@ -38,9 +65,12 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
38
65
  queryFn: async (options) => {
39
66
  const { queryKey } = options;
40
67
  const action = queryKey[0];
41
- (0, utils_1.assert)(typeof action === 'string' &&
42
- dataServices.includes(action.split(':')?.[0]), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
43
- return await messenger.call(action, ...options.queryKey.slice(1), options.pageParam);
68
+ (0, utils_1.assert)(typeof action === 'string' && isRecognizedDataServiceAction(action), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
69
+ const params = options.queryKey.slice(1);
70
+ if (options.pageParam !== undefined) {
71
+ params.push(options.pageParam);
72
+ }
73
+ return await messenger.call(action, ...params);
44
74
  },
45
75
  },
46
76
  mutations: config.defaultOptions?.mutations,
@@ -72,7 +102,12 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
72
102
  observerCount === 0 &&
73
103
  hasSubscription) {
74
104
  const subscriptionListener = subscriptions.get(hash);
75
- messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
105
+ // We can't write a test for this, as it's unrealistic
106
+ // (we just need a check to appease TypeScript).
107
+ // istanbul ignore next
108
+ if (subscriptionListener) {
109
+ messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
110
+ }
76
111
  subscriptions.delete(hash);
77
112
  }
78
113
  });
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.cjs","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { DataServiceGranularCacheUpdatedPayload } from '@metamask/base-data-service';\nimport { assert, Json } from '@metamask/utils';\nimport {\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n QueryKey,\n QueryClientConfig,\n} from '@tanstack/query-core';\n\ntype SubscriptionCallback = (\n payload: DataServiceGranularCacheUpdatedPayload,\n) => void;\n\ntype JsonSubscriptionCallback = (data: Json) => void;\n\n// TODO: Figure out if we can replace with a better Messenger type\ntype MessengerAdapter = {\n call: (method: string, ...params: Json[]) => Promise<Json | void>;\n subscribe: (method: string, callback: JsonSubscriptionCallback) => void;\n unsubscribe: (method: string, callback: JsonSubscriptionCallback) => void;\n};\n\n/**\n * Create a QueryClient queries and subscribes to data services using the messenger.\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(\n dataServices: string[],\n messenger: MessengerAdapter,\n config: QueryClientConfig = {},\n): QueryClient {\n const subscriptions = new Map<string, SubscriptionCallback>();\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): string | 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 (!dataServices.includes(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' &&\n dataServices.includes(action.split(':')?.[0]),\n \"Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.\",\n );\n\n return await messenger.call(\n action,\n ...(options.queryKey.slice(1) as Json[]),\n options.pageParam as Json,\n );\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 = (\n payload: DataServiceGranularCacheUpdatedPayload,\n ): void => {\n if (payload.type === 'removed') {\n return;\n }\n\n hydrate(client, payload.state);\n };\n\n subscriptions.set(hash, cacheListener);\n messenger.subscribe(\n `${service}:cacheUpdated:${hash}`,\n cacheListener as JsonSubscriptionCallback,\n );\n } else if (\n event.type === 'observerRemoved' &&\n observerCount === 0 &&\n hasSubscription\n ) {\n const subscriptionListener = subscriptions.get(hash);\n\n messenger.unsubscribe(\n `${service}:cacheUpdated:${hash}`,\n subscriptionListener as JsonSubscriptionCallback,\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(\n `${service}:invalidateQueries`,\n filters as Json,\n options as Json,\n );\n }),\n );\n\n return originalInvalidate(filters, options);\n };\n\n return client;\n}\n"]}
1
+ {"version":3,"file":"createUIQueryClient.cjs","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"]}
@@ -1,19 +1,51 @@
1
- import { Json } from "@metamask/utils";
1
+ import type { DataServiceGranularCacheUpdatedEvent, DataServiceGranularCacheUpdatedPayload } from "@metamask/base-data-service";
2
2
  import { QueryClient, QueryClientConfig } from "@tanstack/query-core";
3
- type JsonSubscriptionCallback = (data: Json) => void;
4
- type MessengerAdapter = {
5
- call: (method: string, ...params: Json[]) => Promise<Json | void>;
6
- subscribe: (method: string, callback: JsonSubscriptionCallback) => void;
7
- unsubscribe: (method: string, callback: JsonSubscriptionCallback) => void;
3
+ /**
4
+ * Handles granular cache update events emitted by data services.
5
+ */
6
+ type DataServiceGranularCacheUpdatedHandler = (payload: DataServiceGranularCacheUpdatedPayload) => void;
7
+ /**
8
+ * A narrower subset of the `Messenger` type, tailored to the messenger
9
+ * that `createUIQueryClient` interacts with.
10
+ */
11
+ type MessengerAdapter<DataServiceName extends string> = {
12
+ /**
13
+ * Call an action on one of the configured data services.
14
+ *
15
+ * Note: The parameters are typed as `unknown[]` rather than `Json[]`. For
16
+ * concrete messengers, each action's parameters exist as fixed-length tuple,
17
+ * and a variadic `Json[]` is not assignable to a fixed-length tuple, so using
18
+ * `Json[]` here would reject otherwise valid messengers.
19
+ */
20
+ call(actionType: `${DataServiceName}:${string}`, ...params: unknown[]): unknown;
21
+ /**
22
+ * Subscribe to a granular cache update event on one of the configured data
23
+ * services.
24
+ */
25
+ subscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
26
+ /**
27
+ * Unsubscribe from a granular cache update event on one of the configured
28
+ * data services.
29
+ */
30
+ unsubscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
8
31
  };
9
32
  /**
10
- * Create a QueryClient queries and subscribes to data services using the messenger.
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:
36
+ *
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.
11
43
  *
12
44
  * @param dataServices - A list of data services.
13
45
  * @param messenger - A messenger adapter.
14
46
  * @param config - Optional query client configuration options.
15
47
  * @returns The QueryClient.
16
48
  */
17
- export declare function createUIQueryClient(dataServices: string[], messenger: MessengerAdapter, config?: QueryClientConfig): QueryClient;
49
+ export declare function createUIQueryClient<DataServiceNames extends readonly string[]>(dataServices: DataServiceNames, messenger: MessengerAdapter<DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
18
50
  export {};
19
51
  //# sourceMappingURL=createUIQueryClient.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.d.cts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,IAAI,EAAE,wBAAwB;AAC/C,OAAO,EAEL,WAAW,EAIX,iBAAiB,EAClB,6BAA6B;AAM9B,KAAK,wBAAwB,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrD,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAClE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACxE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;CAC3E,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,gBAAgB,EAC3B,MAAM,GAAE,iBAAsB,GAC7B,WAAW,CAqIb"}
1
+ {"version":3,"file":"createUIQueryClient.d.cts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,oCAAoC;AAErC,OAAO,EAEL,WAAW,EAIX,iBAAiB,EAClB,6BAA6B;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,19 +1,51 @@
1
- import { Json } from "@metamask/utils";
1
+ import type { DataServiceGranularCacheUpdatedEvent, DataServiceGranularCacheUpdatedPayload } from "@metamask/base-data-service";
2
2
  import { QueryClient, QueryClientConfig } from "@tanstack/query-core";
3
- type JsonSubscriptionCallback = (data: Json) => void;
4
- type MessengerAdapter = {
5
- call: (method: string, ...params: Json[]) => Promise<Json | void>;
6
- subscribe: (method: string, callback: JsonSubscriptionCallback) => void;
7
- unsubscribe: (method: string, callback: JsonSubscriptionCallback) => void;
3
+ /**
4
+ * Handles granular cache update events emitted by data services.
5
+ */
6
+ type DataServiceGranularCacheUpdatedHandler = (payload: DataServiceGranularCacheUpdatedPayload) => void;
7
+ /**
8
+ * A narrower subset of the `Messenger` type, tailored to the messenger
9
+ * that `createUIQueryClient` interacts with.
10
+ */
11
+ type MessengerAdapter<DataServiceName extends string> = {
12
+ /**
13
+ * Call an action on one of the configured data services.
14
+ *
15
+ * Note: The parameters are typed as `unknown[]` rather than `Json[]`. For
16
+ * concrete messengers, each action's parameters exist as fixed-length tuple,
17
+ * and a variadic `Json[]` is not assignable to a fixed-length tuple, so using
18
+ * `Json[]` here would reject otherwise valid messengers.
19
+ */
20
+ call(actionType: `${DataServiceName}:${string}`, ...params: unknown[]): unknown;
21
+ /**
22
+ * Subscribe to a granular cache update event on one of the configured data
23
+ * services.
24
+ */
25
+ subscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
26
+ /**
27
+ * Unsubscribe from a granular cache update event on one of the configured
28
+ * data services.
29
+ */
30
+ unsubscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
8
31
  };
9
32
  /**
10
- * Create a QueryClient queries and subscribes to data services using the messenger.
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:
36
+ *
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.
11
43
  *
12
44
  * @param dataServices - A list of data services.
13
45
  * @param messenger - A messenger adapter.
14
46
  * @param config - Optional query client configuration options.
15
47
  * @returns The QueryClient.
16
48
  */
17
- export declare function createUIQueryClient(dataServices: string[], messenger: MessengerAdapter, config?: QueryClientConfig): QueryClient;
49
+ export declare function createUIQueryClient<DataServiceNames extends readonly string[]>(dataServices: DataServiceNames, messenger: MessengerAdapter<DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
18
50
  export {};
19
51
  //# sourceMappingURL=createUIQueryClient.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.d.mts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,IAAI,EAAE,wBAAwB;AAC/C,OAAO,EAEL,WAAW,EAIX,iBAAiB,EAClB,6BAA6B;AAM9B,KAAK,wBAAwB,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrD,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAClE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACxE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;CAC3E,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,gBAAgB,EAC3B,MAAM,GAAE,iBAAsB,GAC7B,WAAW,CAqIb"}
1
+ {"version":3,"file":"createUIQueryClient.d.mts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,oCAAoC;AAErC,OAAO,EAEL,WAAW,EAIX,iBAAiB,EAClB,6BAA6B;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,7 +1,16 @@
1
1
  import { assert } from "@metamask/utils";
2
2
  import { hydrate, QueryClient } from "@tanstack/query-core";
3
3
  /**
4
- * Create a QueryClient queries and subscribes to data services using the messenger.
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:
7
+ *
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.
5
14
  *
6
15
  * @param dataServices - A list of data services.
7
16
  * @param messenger - A messenger adapter.
@@ -10,6 +19,24 @@ import { hydrate, QueryClient } from "@tanstack/query-core";
10
19
  */
11
20
  export function createUIQueryClient(dataServices, messenger, config = {}) {
12
21
  const subscriptions = new Map();
22
+ /**
23
+ * Check whether a name is one of the provided data service names.
24
+ *
25
+ * @param service - The service name to check.
26
+ * @returns Whether the service name is configured.
27
+ */
28
+ function isRecognizedDataService(service) {
29
+ return dataServices.some((dataService) => dataService === service);
30
+ }
31
+ /**
32
+ * Check whether an action belongs to one of the provided data services.
33
+ *
34
+ * @param action - The action name to check.
35
+ * @returns Whether the action belongs to a configured data service.
36
+ */
37
+ function isRecognizedDataServiceAction(action) {
38
+ return isRecognizedDataService(action.split(':')[0]);
39
+ }
13
40
  /**
14
41
  * Parse a query key to detect a service name.
15
42
  *
@@ -22,7 +49,7 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
22
49
  return null;
23
50
  }
24
51
  const service = action.split(':')[0];
25
- if (!dataServices.includes(service)) {
52
+ if (!isRecognizedDataService(service)) {
26
53
  return null;
27
54
  }
28
55
  return service;
@@ -35,9 +62,12 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
35
62
  queryFn: async (options) => {
36
63
  const { queryKey } = options;
37
64
  const action = queryKey[0];
38
- assert(typeof action === 'string' &&
39
- dataServices.includes(action.split(':')?.[0]), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
40
- return await messenger.call(action, ...options.queryKey.slice(1), options.pageParam);
65
+ assert(typeof action === 'string' && isRecognizedDataServiceAction(action), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
66
+ const params = options.queryKey.slice(1);
67
+ if (options.pageParam !== undefined) {
68
+ params.push(options.pageParam);
69
+ }
70
+ return await messenger.call(action, ...params);
41
71
  },
42
72
  },
43
73
  mutations: config.defaultOptions?.mutations,
@@ -69,7 +99,12 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
69
99
  observerCount === 0 &&
70
100
  hasSubscription) {
71
101
  const subscriptionListener = subscriptions.get(hash);
72
- messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
102
+ // We can't write a test for this, as it's unrealistic
103
+ // (we just need a check to appease TypeScript).
104
+ // istanbul ignore next
105
+ if (subscriptionListener) {
106
+ messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
107
+ }
73
108
  subscriptions.delete(hash);
74
109
  }
75
110
  });
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.mjs","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { DataServiceGranularCacheUpdatedPayload } from '@metamask/base-data-service';\nimport { assert, Json } from '@metamask/utils';\nimport {\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n QueryKey,\n QueryClientConfig,\n} from '@tanstack/query-core';\n\ntype SubscriptionCallback = (\n payload: DataServiceGranularCacheUpdatedPayload,\n) => void;\n\ntype JsonSubscriptionCallback = (data: Json) => void;\n\n// TODO: Figure out if we can replace with a better Messenger type\ntype MessengerAdapter = {\n call: (method: string, ...params: Json[]) => Promise<Json | void>;\n subscribe: (method: string, callback: JsonSubscriptionCallback) => void;\n unsubscribe: (method: string, callback: JsonSubscriptionCallback) => void;\n};\n\n/**\n * Create a QueryClient queries and subscribes to data services using the messenger.\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(\n dataServices: string[],\n messenger: MessengerAdapter,\n config: QueryClientConfig = {},\n): QueryClient {\n const subscriptions = new Map<string, SubscriptionCallback>();\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): string | 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 (!dataServices.includes(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' &&\n dataServices.includes(action.split(':')?.[0]),\n \"Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.\",\n );\n\n return await messenger.call(\n action,\n ...(options.queryKey.slice(1) as Json[]),\n options.pageParam as Json,\n );\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 = (\n payload: DataServiceGranularCacheUpdatedPayload,\n ): void => {\n if (payload.type === 'removed') {\n return;\n }\n\n hydrate(client, payload.state);\n };\n\n subscriptions.set(hash, cacheListener);\n messenger.subscribe(\n `${service}:cacheUpdated:${hash}`,\n cacheListener as JsonSubscriptionCallback,\n );\n } else if (\n event.type === 'observerRemoved' &&\n observerCount === 0 &&\n hasSubscription\n ) {\n const subscriptionListener = subscriptions.get(hash);\n\n messenger.unsubscribe(\n `${service}:cacheUpdated:${hash}`,\n subscriptionListener as JsonSubscriptionCallback,\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(\n `${service}:invalidateQueries`,\n filters as Json,\n options as Json,\n );\n }),\n );\n\n return originalInvalidate(filters, options);\n };\n\n return client;\n}\n"]}
1
+ {"version":3,"file":"createUIQueryClient.mjs","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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/react-data-query",
3
- "version": "0.2.2-preview-d77c814bb",
3
+ "version": "0.2.2-preview-48f22dbe8",
4
4
  "description": "Provides React utilities for consuming data services",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -47,9 +47,13 @@
47
47
  "lint:tsconfigs": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts",
48
48
  "lint:tsconfigs:fix": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts --fix",
49
49
  "since-latest-release": "../../scripts/since-latest-release.sh",
50
- "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
51
- "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
52
- "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
50
+ "test": "yarn test:unit && yarn test:types",
51
+ "test:clean": "yarn test:unit:clean && yarn test:types",
52
+ "test:types": "tstyche",
53
+ "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
54
+ "test:unit:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
55
+ "test:unit:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
56
+ "test:verbose": "yarn test:unit:verbose && yarn test:types",
53
57
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
54
58
  },
55
59
  "dependencies": {
@@ -66,6 +70,7 @@
66
70
  "deepmerge": "^4.2.2",
67
71
  "jest": "^30.4.2",
68
72
  "ts-jest": "^29.4.11",
73
+ "tstyche": "^5.0.2",
69
74
  "tsx": "^4.20.5",
70
75
  "typedoc": "^0.25.13",
71
76
  "typedoc-plugin-missing-exports": "^2.0.0",