@metamask-previews/react-data-query 0.2.1-preview-75ce9748b → 0.2.2-preview-8b1e39b

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,11 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2]
11
+
10
12
  ### Changed
11
13
 
12
- - **BREAKING:** Constrain `createUIQueryClient`'s messenger-like type to match the given data services ([#9475](https://github.com/MetaMask/core/pull/9475))
13
- - The messenger-like object that `createUIQueryClient` takes must minimally support actions or `:cacheUpdated:${hash}` events which are namespaced by the provided data service names.
14
- - You will likely need to update the type of your messenger. See `MessengerAdapter` in `packages/react-data-query/src/createUIQueryClient.ts` for an example.
15
14
  - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074))
16
15
  - Make `react-dom` and `react-native` peer dependencies optional ([#9295](https://github.com/MetaMask/core/pull/9295))
17
16
 
@@ -38,7 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
38
37
 
39
38
  - Initial release ([#8039](https://github.com/MetaMask/core/pull/8039), [#8292](https://github.com/MetaMask/core/pull/8292))
40
39
 
41
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/react-data-query@0.2.1...HEAD
40
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/react-data-query@0.2.2...HEAD
41
+ [0.2.2]: https://github.com/MetaMask/core/compare/@metamask/react-data-query@0.2.1...@metamask/react-data-query@0.2.2
42
42
  [0.2.1]: https://github.com/MetaMask/core/compare/@metamask/react-data-query@0.2.0...@metamask/react-data-query@0.2.1
43
43
  [0.2.0]: https://github.com/MetaMask/core/compare/@metamask/react-data-query@0.1.0...@metamask/react-data-query@0.2.0
44
44
  [0.1.0]: https://github.com/MetaMask/core/releases/tag/@metamask/react-data-query@0.1.0
@@ -3,26 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createUIQueryClient = void 0;
4
4
  const utils_1 = require("@metamask/utils");
5
5
  const query_core_1 = require("@tanstack/query-core");
6
+ /**
7
+ * Create a QueryClient queries and subscribes to data services using the messenger.
8
+ *
9
+ * @param dataServices - A list of data services.
10
+ * @param messenger - A messenger adapter.
11
+ * @param config - Optional query client configuration options.
12
+ * @returns The QueryClient.
13
+ */
6
14
  function createUIQueryClient(dataServices, messenger, config = {}) {
7
15
  const subscriptions = new Map();
8
- /**
9
- * Check whether a name is one of the provided data service names.
10
- *
11
- * @param service - The service name to check.
12
- * @returns Whether the service name is configured.
13
- */
14
- function isRecognizedDataService(service) {
15
- return dataServices.some((dataService) => dataService === service);
16
- }
17
- /**
18
- * Check whether an action belongs to one of the provided data services.
19
- *
20
- * @param action - The action name to check.
21
- * @returns Whether the action belongs to a configured data service.
22
- */
23
- function isRecognizedDataServiceAction(action) {
24
- return isRecognizedDataService(action.split(':')[0]);
25
- }
26
16
  /**
27
17
  * Parse a query key to detect a service name.
28
18
  *
@@ -35,7 +25,7 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
35
25
  return null;
36
26
  }
37
27
  const service = action.split(':')[0];
38
- if (!isRecognizedDataService(service)) {
28
+ if (!dataServices.includes(service)) {
39
29
  return null;
40
30
  }
41
31
  return service;
@@ -48,15 +38,9 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
48
38
  queryFn: async (options) => {
49
39
  const { queryKey } = options;
50
40
  const action = queryKey[0];
51
- (0, utils_1.assert)(typeof action === 'string' && isRecognizedDataServiceAction(action), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
52
- const params = options.queryKey.slice(1);
53
- // `pageParam` is only present for infinite (paginated) queries. For
54
- // regular queries it is `undefined`, so we omit it to avoid passing a
55
- // trailing `undefined` argument to the action handler.
56
- if (options.pageParam !== undefined) {
57
- params.push(options.pageParam);
58
- }
59
- return await messenger.call(action, ...params);
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);
60
44
  },
61
45
  },
62
46
  mutations: config.defaultOptions?.mutations,
@@ -88,9 +72,7 @@ function createUIQueryClient(dataServices, messenger, config = {}) {
88
72
  observerCount === 0 &&
89
73
  hasSubscription) {
90
74
  const subscriptionListener = subscriptions.get(hash);
91
- if (subscriptionListener) {
92
- messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
93
- }
75
+ messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
94
76
  subscriptions.delete(hash);
95
77
  }
96
78
  });
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.cjs","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataServiceActions,\n DataServiceGranularCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedPayload,\n} from '@metamask/base-data-service';\nimport type {\n ActionConstraint,\n EventConstraint,\n Messenger,\n MessengerActions,\n MessengerEvents,\n} from '@metamask/messenger';\nimport { assert } from '@metamask/utils';\nimport {\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n OmitKeyof,\n parseFilterArgs,\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 * The supertype of all messengers.\n *\n * Used as the upper bound for the messenger type variable so that any concrete\n * {@link Messenger} can be passed, with {@link SupportsDataServices} layered on\n * top to verify the required data service capabilities are present. We cannot\n * instead require a fixed structural shape whose `call` accepts an open-ended\n * `${DataServiceName}:${string}` template literal: a concrete messenger's own\n * generic `call` is bounded by its declared action union, so it cannot accept\n * action types it does not declare, and such a shape would reject any messenger\n * that declares actions from additional namespaces.\n */\ntype GenericMessenger = Messenger<\n string,\n ActionConstraint,\n EventConstraint,\n // Use `any` for the parent so any messenger, delegated or not, is accepted.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>;\n\n/**\n * A messenger that minimally supports a subset of capabilities that data\n * services with the given namespaces would provide. Specifically, of these\n * namespaces, it must at least allow all data service actions to be called, and\n * it must at least allow the `:cacheUpdated:${hash}` event to be subscribed to.\n */\ntype SupportsDataServices<\n MessengerInstance extends GenericMessenger,\n DataServiceName extends string,\n> = DataServiceActions<DataServiceName>['type'] extends MessengerActions<MessengerInstance>['type']\n ? DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'] extends MessengerEvents<MessengerInstance>['type']\n ? MessengerInstance\n : never\n : never;\n\n/**\n * A messenger adapter: the narrow, messenger-like shape that\n * `createUIQueryClient` interacts with. This is what the function uses\n * internally, and it may also be passed in directly by callers that do not have\n * a full {@link Messenger} instance (e.g. a hand-rolled object that forwards to\n * some other transport).\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[]`.\n * Concrete messengers declare each action's parameters as a fixed-length\n * tuple, and a variadic `Json[]` is not assignable to a fixed-length tuple,\n * so using `Json[]` here would reject otherwise valid messengers.\n */\n call(\n actionType: `${DataServiceName}:${string}`,\n ...params: unknown[]\n ): Promise<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.\n *\n * This overload accepts a messenger adapter (see {@link MessengerAdapter}) —\n * any object that exposes the `call`, `subscribe`, and `unsubscribe` methods\n * for the designated data services. Use this when you do not have a full\n * {@link Messenger} instance on hand.\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\n/**\n * Create a QueryClient that queries and subscribes to data services using a\n * messenger.\n *\n * This overload accepts a full {@link Messenger} instance, with some\n * constraints:\n * 1. The messenger must minimally 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 capabilities that belong to the\n * designated data services and must minimally support the\n * `:cacheUpdated:${hash}` event of the the designated data services.\n *\n * @param dataServices - A list of data services.\n * @param messenger - A messenger.\n * @param config - Optional query client configuration options.\n * @returns The QueryClient.\n */\nexport function createUIQueryClient<\n DataServiceNames extends readonly string[],\n MessengerInstance extends GenericMessenger,\n>(\n dataServices: DataServiceNames,\n messenger: SupportsDataServices<MessengerInstance, DataServiceNames[number]>,\n config?: QueryClientConfig,\n): 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\n // `pageParam` is only present for infinite (paginated) queries. For\n // regular queries it is `undefined`, so we omit it to avoid passing a\n // trailing `undefined` argument to the action handler.\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 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 // This function is defined in this way to have full support for all function overloads.\n client.invalidateQueries = async (\n arg1?: QueryKey | InvalidateQueryFilters,\n arg2?: OmitKeyof<InvalidateQueryFilters, 'queryKey'> | InvalidateOptions,\n arg3?: InvalidateOptions,\n ): Promise<void> => {\n const [filters, options] = parseFilterArgs(arg1, arg2, arg3);\n\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
+ {"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 OmitKeyof,\n parseFilterArgs,\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,\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 // This function is defined in this way to have full support for all function overloads.\n client.invalidateQueries = async (\n arg1?: QueryKey | InvalidateQueryFilters,\n arg2?: OmitKeyof<InvalidateQueryFilters, 'queryKey'> | InvalidateOptions,\n arg3?: InvalidateOptions,\n ): Promise<void> => {\n const [filters, options] = parseFilterArgs(arg1, arg2, arg3);\n\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,91 +1,19 @@
1
- import type { DataServiceActions, DataServiceGranularCacheUpdatedEvent, DataServiceGranularCacheUpdatedPayload } from "@metamask/base-data-service";
2
- import type { ActionConstraint, EventConstraint, Messenger, MessengerActions, MessengerEvents } from "@metamask/messenger";
1
+ import { Json } from "@metamask/utils";
3
2
  import { QueryClient, QueryClientConfig } from "@tanstack/query-core";
4
- /**
5
- * Handles granular cache update events emitted by data services.
6
- */
7
- type DataServiceGranularCacheUpdatedHandler = (payload: DataServiceGranularCacheUpdatedPayload) => void;
8
- /**
9
- * The supertype of all messengers.
10
- *
11
- * Used as the upper bound for the messenger type variable so that any concrete
12
- * {@link Messenger} can be passed, with {@link SupportsDataServices} layered on
13
- * top to verify the required data service capabilities are present. We cannot
14
- * instead require a fixed structural shape whose `call` accepts an open-ended
15
- * `${DataServiceName}:${string}` template literal: a concrete messenger's own
16
- * generic `call` is bounded by its declared action union, so it cannot accept
17
- * action types it does not declare, and such a shape would reject any messenger
18
- * that declares actions from additional namespaces.
19
- */
20
- type GenericMessenger = Messenger<string, ActionConstraint, EventConstraint, any>;
21
- /**
22
- * A messenger that minimally supports a subset of capabilities that data
23
- * services with the given namespaces would provide. Specifically, of these
24
- * namespaces, it must at least allow all data service actions to be called, and
25
- * it must at least allow the `:cacheUpdated:${hash}` event to be subscribed to.
26
- */
27
- type SupportsDataServices<MessengerInstance extends GenericMessenger, DataServiceName extends string> = DataServiceActions<DataServiceName>['type'] extends MessengerActions<MessengerInstance>['type'] ? DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'] extends MessengerEvents<MessengerInstance>['type'] ? MessengerInstance : never : never;
28
- /**
29
- * A messenger adapter: the narrow, messenger-like shape that
30
- * `createUIQueryClient` interacts with. This is what the function uses
31
- * internally, and it may also be passed in directly by callers that do not have
32
- * a full {@link Messenger} instance (e.g. a hand-rolled object that forwards to
33
- * some other transport).
34
- */
35
- type MessengerAdapter<DataServiceName extends string> = {
36
- /**
37
- * Call an action on one of the configured data services.
38
- *
39
- * Note: The parameters are typed as `unknown[]` rather than `Json[]`.
40
- * Concrete messengers declare each action's parameters as a fixed-length
41
- * tuple, and a variadic `Json[]` is not assignable to a fixed-length tuple,
42
- * so using `Json[]` here would reject otherwise valid messengers.
43
- */
44
- call(actionType: `${DataServiceName}:${string}`, ...params: unknown[]): Promise<unknown>;
45
- /**
46
- * Subscribe to a granular cache update event on one of the configured data
47
- * services.
48
- */
49
- subscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
50
- /**
51
- * Unsubscribe from a granular cache update event on one of the configured
52
- * data services.
53
- */
54
- unsubscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
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;
55
8
  };
56
9
  /**
57
- * Create a QueryClient that queries and subscribes to data services using a
58
- * messenger adapter.
59
- *
60
- * This overload accepts a messenger adapter (see {@link MessengerAdapter}) —
61
- * any object that exposes the `call`, `subscribe`, and `unsubscribe` methods
62
- * for the designated data services. Use this when you do not have a full
63
- * {@link Messenger} instance on hand.
10
+ * Create a QueryClient queries and subscribes to data services using the messenger.
64
11
  *
65
12
  * @param dataServices - A list of data services.
66
13
  * @param messenger - A messenger adapter.
67
14
  * @param config - Optional query client configuration options.
68
15
  * @returns The QueryClient.
69
16
  */
70
- export declare function createUIQueryClient<DataServiceNames extends readonly string[]>(dataServices: DataServiceNames, messenger: MessengerAdapter<DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
71
- /**
72
- * Create a QueryClient that queries and subscribes to data services using a
73
- * messenger.
74
- *
75
- * This overload accepts a full {@link Messenger} instance, with some
76
- * constraints:
77
- * 1. The messenger must minimally support the `call`, `subscribe` and
78
- * `unsubscribe` methods.
79
- * 2. All action handler arguments and event payloads must be JSON-compatible.
80
- * 3. The messenger must minimally support capabilities that belong to the
81
- * designated data services and must minimally support the
82
- * `:cacheUpdated:${hash}` event of the the designated data services.
83
- *
84
- * @param dataServices - A list of data services.
85
- * @param messenger - A messenger.
86
- * @param config - Optional query client configuration options.
87
- * @returns The QueryClient.
88
- */
89
- export declare function createUIQueryClient<DataServiceNames extends readonly string[], MessengerInstance extends GenericMessenger>(dataServices: DataServiceNames, messenger: SupportsDataServices<MessengerInstance, DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
17
+ export declare function createUIQueryClient(dataServices: string[], messenger: MessengerAdapter, config?: QueryClientConfig): QueryClient;
90
18
  export {};
91
19
  //# sourceMappingURL=createUIQueryClient.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.d.cts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,oCAAoC,EACpC,sCAAsC,EACvC,oCAAoC;AACrC,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,4BAA4B;AAE7B,OAAO,EAEL,WAAW,EAMX,iBAAiB,EAClB,6BAA6B;AAE9B;;GAEG;AACH,KAAK,sCAAsC,GAAG,CAC5C,OAAO,EAAE,sCAAsC,KAC5C,IAAI,CAAC;AAEV;;;;;;;;;;;GAWG;AACH,KAAK,gBAAgB,GAAG,SAAS,CAC/B,MAAM,EACN,gBAAgB,EAChB,eAAe,EAGf,GAAG,CACJ,CAAC;AAEF;;;;;GAKG;AACH,KAAK,oBAAoB,CACvB,iBAAiB,SAAS,gBAAgB,EAC1C,eAAe,SAAS,MAAM,IAC5B,kBAAkB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,SAAS,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,GAC/F,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,SAAS,eAAe,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,GAC9G,iBAAiB,GACjB,KAAK,GACP,KAAK,CAAC;AAEV;;;;;;GAMG;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,OAAO,CAAC,CAAC;IAEpB;;;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;;;;;;;;;;;;;GAaG;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,CAAC,EAAE,iBAAiB,GACzB,WAAW,CAAC;AAEf;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,SAAS,SAAS,MAAM,EAAE,EAC1C,iBAAiB,SAAS,gBAAgB,EAE1C,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,oBAAoB,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAC5E,MAAM,CAAC,EAAE,iBAAiB,GACzB,WAAW,CAAC"}
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,EAMX,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,CAyIb"}
@@ -1,91 +1,19 @@
1
- import type { DataServiceActions, DataServiceGranularCacheUpdatedEvent, DataServiceGranularCacheUpdatedPayload } from "@metamask/base-data-service";
2
- import type { ActionConstraint, EventConstraint, Messenger, MessengerActions, MessengerEvents } from "@metamask/messenger";
1
+ import { Json } from "@metamask/utils";
3
2
  import { QueryClient, QueryClientConfig } from "@tanstack/query-core";
4
- /**
5
- * Handles granular cache update events emitted by data services.
6
- */
7
- type DataServiceGranularCacheUpdatedHandler = (payload: DataServiceGranularCacheUpdatedPayload) => void;
8
- /**
9
- * The supertype of all messengers.
10
- *
11
- * Used as the upper bound for the messenger type variable so that any concrete
12
- * {@link Messenger} can be passed, with {@link SupportsDataServices} layered on
13
- * top to verify the required data service capabilities are present. We cannot
14
- * instead require a fixed structural shape whose `call` accepts an open-ended
15
- * `${DataServiceName}:${string}` template literal: a concrete messenger's own
16
- * generic `call` is bounded by its declared action union, so it cannot accept
17
- * action types it does not declare, and such a shape would reject any messenger
18
- * that declares actions from additional namespaces.
19
- */
20
- type GenericMessenger = Messenger<string, ActionConstraint, EventConstraint, any>;
21
- /**
22
- * A messenger that minimally supports a subset of capabilities that data
23
- * services with the given namespaces would provide. Specifically, of these
24
- * namespaces, it must at least allow all data service actions to be called, and
25
- * it must at least allow the `:cacheUpdated:${hash}` event to be subscribed to.
26
- */
27
- type SupportsDataServices<MessengerInstance extends GenericMessenger, DataServiceName extends string> = DataServiceActions<DataServiceName>['type'] extends MessengerActions<MessengerInstance>['type'] ? DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'] extends MessengerEvents<MessengerInstance>['type'] ? MessengerInstance : never : never;
28
- /**
29
- * A messenger adapter: the narrow, messenger-like shape that
30
- * `createUIQueryClient` interacts with. This is what the function uses
31
- * internally, and it may also be passed in directly by callers that do not have
32
- * a full {@link Messenger} instance (e.g. a hand-rolled object that forwards to
33
- * some other transport).
34
- */
35
- type MessengerAdapter<DataServiceName extends string> = {
36
- /**
37
- * Call an action on one of the configured data services.
38
- *
39
- * Note: The parameters are typed as `unknown[]` rather than `Json[]`.
40
- * Concrete messengers declare each action's parameters as a fixed-length
41
- * tuple, and a variadic `Json[]` is not assignable to a fixed-length tuple,
42
- * so using `Json[]` here would reject otherwise valid messengers.
43
- */
44
- call(actionType: `${DataServiceName}:${string}`, ...params: unknown[]): Promise<unknown>;
45
- /**
46
- * Subscribe to a granular cache update event on one of the configured data
47
- * services.
48
- */
49
- subscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
50
- /**
51
- * Unsubscribe from a granular cache update event on one of the configured
52
- * data services.
53
- */
54
- unsubscribe(eventType: DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'], handler: DataServiceGranularCacheUpdatedHandler): void;
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;
55
8
  };
56
9
  /**
57
- * Create a QueryClient that queries and subscribes to data services using a
58
- * messenger adapter.
59
- *
60
- * This overload accepts a messenger adapter (see {@link MessengerAdapter}) —
61
- * any object that exposes the `call`, `subscribe`, and `unsubscribe` methods
62
- * for the designated data services. Use this when you do not have a full
63
- * {@link Messenger} instance on hand.
10
+ * Create a QueryClient queries and subscribes to data services using the messenger.
64
11
  *
65
12
  * @param dataServices - A list of data services.
66
13
  * @param messenger - A messenger adapter.
67
14
  * @param config - Optional query client configuration options.
68
15
  * @returns The QueryClient.
69
16
  */
70
- export declare function createUIQueryClient<DataServiceNames extends readonly string[]>(dataServices: DataServiceNames, messenger: MessengerAdapter<DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
71
- /**
72
- * Create a QueryClient that queries and subscribes to data services using a
73
- * messenger.
74
- *
75
- * This overload accepts a full {@link Messenger} instance, with some
76
- * constraints:
77
- * 1. The messenger must minimally support the `call`, `subscribe` and
78
- * `unsubscribe` methods.
79
- * 2. All action handler arguments and event payloads must be JSON-compatible.
80
- * 3. The messenger must minimally support capabilities that belong to the
81
- * designated data services and must minimally support the
82
- * `:cacheUpdated:${hash}` event of the the designated data services.
83
- *
84
- * @param dataServices - A list of data services.
85
- * @param messenger - A messenger.
86
- * @param config - Optional query client configuration options.
87
- * @returns The QueryClient.
88
- */
89
- export declare function createUIQueryClient<DataServiceNames extends readonly string[], MessengerInstance extends GenericMessenger>(dataServices: DataServiceNames, messenger: SupportsDataServices<MessengerInstance, DataServiceNames[number]>, config?: QueryClientConfig): QueryClient;
17
+ export declare function createUIQueryClient(dataServices: string[], messenger: MessengerAdapter, config?: QueryClientConfig): QueryClient;
90
18
  export {};
91
19
  //# sourceMappingURL=createUIQueryClient.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.d.mts","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,oCAAoC,EACpC,sCAAsC,EACvC,oCAAoC;AACrC,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,4BAA4B;AAE7B,OAAO,EAEL,WAAW,EAMX,iBAAiB,EAClB,6BAA6B;AAE9B;;GAEG;AACH,KAAK,sCAAsC,GAAG,CAC5C,OAAO,EAAE,sCAAsC,KAC5C,IAAI,CAAC;AAEV;;;;;;;;;;;GAWG;AACH,KAAK,gBAAgB,GAAG,SAAS,CAC/B,MAAM,EACN,gBAAgB,EAChB,eAAe,EAGf,GAAG,CACJ,CAAC;AAEF;;;;;GAKG;AACH,KAAK,oBAAoB,CACvB,iBAAiB,SAAS,gBAAgB,EAC1C,eAAe,SAAS,MAAM,IAC5B,kBAAkB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,SAAS,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,GAC/F,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,SAAS,eAAe,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,GAC9G,iBAAiB,GACjB,KAAK,GACP,KAAK,CAAC;AAEV;;;;;;GAMG;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,OAAO,CAAC,CAAC;IAEpB;;;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;;;;;;;;;;;;;GAaG;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,CAAC,EAAE,iBAAiB,GACzB,WAAW,CAAC;AAEf;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,SAAS,SAAS,MAAM,EAAE,EAC1C,iBAAiB,SAAS,gBAAgB,EAE1C,YAAY,EAAE,gBAAgB,EAC9B,SAAS,EAAE,oBAAoB,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAC5E,MAAM,CAAC,EAAE,iBAAiB,GACzB,WAAW,CAAC"}
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,EAMX,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,CAyIb"}
@@ -1,25 +1,15 @@
1
1
  import { assert } from "@metamask/utils";
2
2
  import { hydrate, QueryClient, parseFilterArgs } from "@tanstack/query-core";
3
+ /**
4
+ * Create a QueryClient queries and subscribes to data services using the messenger.
5
+ *
6
+ * @param dataServices - A list of data services.
7
+ * @param messenger - A messenger adapter.
8
+ * @param config - Optional query client configuration options.
9
+ * @returns The QueryClient.
10
+ */
3
11
  export function createUIQueryClient(dataServices, messenger, config = {}) {
4
12
  const subscriptions = new Map();
5
- /**
6
- * Check whether a name is one of the provided data service names.
7
- *
8
- * @param service - The service name to check.
9
- * @returns Whether the service name is configured.
10
- */
11
- function isRecognizedDataService(service) {
12
- return dataServices.some((dataService) => dataService === service);
13
- }
14
- /**
15
- * Check whether an action belongs to one of the provided data services.
16
- *
17
- * @param action - The action name to check.
18
- * @returns Whether the action belongs to a configured data service.
19
- */
20
- function isRecognizedDataServiceAction(action) {
21
- return isRecognizedDataService(action.split(':')[0]);
22
- }
23
13
  /**
24
14
  * Parse a query key to detect a service name.
25
15
  *
@@ -32,7 +22,7 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
32
22
  return null;
33
23
  }
34
24
  const service = action.split(':')[0];
35
- if (!isRecognizedDataService(service)) {
25
+ if (!dataServices.includes(service)) {
36
26
  return null;
37
27
  }
38
28
  return service;
@@ -45,15 +35,9 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
45
35
  queryFn: async (options) => {
46
36
  const { queryKey } = options;
47
37
  const action = queryKey[0];
48
- assert(typeof action === 'string' && isRecognizedDataServiceAction(action), "Queries must call actions on the messenger provided to createUIQueryClient, e.g. `queryKey: ['ExampleDataService:getAssets', ...]`.");
49
- const params = options.queryKey.slice(1);
50
- // `pageParam` is only present for infinite (paginated) queries. For
51
- // regular queries it is `undefined`, so we omit it to avoid passing a
52
- // trailing `undefined` argument to the action handler.
53
- if (options.pageParam !== undefined) {
54
- params.push(options.pageParam);
55
- }
56
- return await messenger.call(action, ...params);
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);
57
41
  },
58
42
  },
59
43
  mutations: config.defaultOptions?.mutations,
@@ -85,9 +69,7 @@ export function createUIQueryClient(dataServices, messenger, config = {}) {
85
69
  observerCount === 0 &&
86
70
  hasSubscription) {
87
71
  const subscriptionListener = subscriptions.get(hash);
88
- if (subscriptionListener) {
89
- messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
90
- }
72
+ messenger.unsubscribe(`${service}:cacheUpdated:${hash}`, subscriptionListener);
91
73
  subscriptions.delete(hash);
92
74
  }
93
75
  });
@@ -1 +1 @@
1
- {"version":3,"file":"createUIQueryClient.mjs","sourceRoot":"","sources":["../src/createUIQueryClient.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,MAAM,EAAE,wBAAwB;AACzC,OAAO,EACL,OAAO,EACP,WAAW,EAIX,eAAe,EAGhB,6BAA6B;AAoI9B,MAAM,UAAU,mBAAmB,CACjC,YAA8B,EAC9B,SAAqD,EACrD,SAA4B,EAAE;IAE9B,MAAM,aAAa,GAAG,IAAI,GAAG,EAG1B,CAAC;IAEJ;;;;;OAKG;IACH,SAAS,uBAAuB,CAC9B,OAAe;QAEf,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,KAAK,OAAO,CAAC,CAAC;IACrE,CAAC;IAED;;;;;OAKG;IACH,SAAS,6BAA6B,CACpC,MAAc;QAEd,OAAO,uBAAuB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IAED;;;;;OAKG;IACH,SAAS,aAAa,CAAC,QAAkB;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAE3B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAErC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAgB,IAAI,WAAW,CAAC;QAC1C,GAAG,MAAM;QACT,cAAc,EAAE;YACd,OAAO,EAAE;gBACP,GAAG,MAAM,CAAC,cAAc,EAAE,OAAO;gBACjC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAoB,EAAE;oBAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;oBAE7B,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;oBAE3B,MAAM,CACJ,OAAO,MAAM,KAAK,QAAQ,IAAI,6BAA6B,CAAC,MAAM,CAAC,EACnE,qIAAqI,CACtI,CAAC;oBAEF,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAEzC,oEAAoE;oBACpE,sEAAsE;oBACtE,uDAAuD;oBACvD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;wBACpC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBACjC,CAAC;oBAED,OAAO,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;gBACjD,CAAC;aACF;YACD,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE,SAAS;SAC5C;KACF,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC;IAErC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACxB,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;QAExB,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC;QAC7B,MAAM,eAAe,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAEhD,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAE9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,IACE,CAAC,eAAe;YAChB,KAAK,CAAC,IAAI,KAAK,eAAe;YAC9B,aAAa,KAAK,CAAC,EACnB,CAAC;YACD,MAAM,aAAa,GAA2C,CAC5D,OAAO,EACP,EAAE;gBACF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBAC/B,OAAO;gBACT,CAAC;gBAED,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC,CAAC;YAEF,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;YACvC,SAAS,CAAC,SAAS,CAAC,GAAG,OAAO,iBAAiB,IAAI,EAAE,EAAE,aAAa,CAAC,CAAC;QACxE,CAAC;aAAM,IACL,KAAK,CAAC,IAAI,KAAK,iBAAiB;YAChC,aAAa,KAAK,CAAC;YACnB,eAAe,EACf,CAAC;YACD,MAAM,oBAAoB,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAErD,IAAI,oBAAoB,EAAE,CAAC;gBACzB,SAAS,CAAC,WAAW,CACnB,GAAG,OAAO,iBAAiB,IAAI,EAAE,EACjC,oBAAoB,CACrB,CAAC;YACJ,CAAC;YACD,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,gFAAgF;IAChF,MAAM,kBAAkB,GAAG,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEjE,wFAAwF;IACxF,MAAM,CAAC,iBAAiB,GAAG,KAAK,EAC9B,IAAwC,EACxC,IAAwE,EACxE,IAAwB,EACT,EAAE;QACjB,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAE7D,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAExD,MAAM,QAAQ,GAAG;YACf,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;SAClE,CAAC;QAEF,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,OAAO,oBAAoB,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1E,CAAC,CAAC,CACH,CAAC;QAEF,OAAO,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type {\n DataServiceActions,\n DataServiceGranularCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedPayload,\n} from '@metamask/base-data-service';\nimport type {\n ActionConstraint,\n EventConstraint,\n Messenger,\n MessengerActions,\n MessengerEvents,\n} from '@metamask/messenger';\nimport { assert } from '@metamask/utils';\nimport {\n hydrate,\n QueryClient,\n InvalidateQueryFilters,\n InvalidateOptions,\n OmitKeyof,\n parseFilterArgs,\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 * The supertype of all messengers.\n *\n * Used as the upper bound for the messenger type variable so that any concrete\n * {@link Messenger} can be passed, with {@link SupportsDataServices} layered on\n * top to verify the required data service capabilities are present. We cannot\n * instead require a fixed structural shape whose `call` accepts an open-ended\n * `${DataServiceName}:${string}` template literal: a concrete messenger's own\n * generic `call` is bounded by its declared action union, so it cannot accept\n * action types it does not declare, and such a shape would reject any messenger\n * that declares actions from additional namespaces.\n */\ntype GenericMessenger = Messenger<\n string,\n ActionConstraint,\n EventConstraint,\n // Use `any` for the parent so any messenger, delegated or not, is accepted.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>;\n\n/**\n * A messenger that minimally supports a subset of capabilities that data\n * services with the given namespaces would provide. Specifically, of these\n * namespaces, it must at least allow all data service actions to be called, and\n * it must at least allow the `:cacheUpdated:${hash}` event to be subscribed to.\n */\ntype SupportsDataServices<\n MessengerInstance extends GenericMessenger,\n DataServiceName extends string,\n> = DataServiceActions<DataServiceName>['type'] extends MessengerActions<MessengerInstance>['type']\n ? DataServiceGranularCacheUpdatedEvent<DataServiceName>['type'] extends MessengerEvents<MessengerInstance>['type']\n ? MessengerInstance\n : never\n : never;\n\n/**\n * A messenger adapter: the narrow, messenger-like shape that\n * `createUIQueryClient` interacts with. This is what the function uses\n * internally, and it may also be passed in directly by callers that do not have\n * a full {@link Messenger} instance (e.g. a hand-rolled object that forwards to\n * some other transport).\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[]`.\n * Concrete messengers declare each action's parameters as a fixed-length\n * tuple, and a variadic `Json[]` is not assignable to a fixed-length tuple,\n * so using `Json[]` here would reject otherwise valid messengers.\n */\n call(\n actionType: `${DataServiceName}:${string}`,\n ...params: unknown[]\n ): Promise<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.\n *\n * This overload accepts a messenger adapter (see {@link MessengerAdapter}) —\n * any object that exposes the `call`, `subscribe`, and `unsubscribe` methods\n * for the designated data services. Use this when you do not have a full\n * {@link Messenger} instance on hand.\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\n/**\n * Create a QueryClient that queries and subscribes to data services using a\n * messenger.\n *\n * This overload accepts a full {@link Messenger} instance, with some\n * constraints:\n * 1. The messenger must minimally 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 capabilities that belong to the\n * designated data services and must minimally support the\n * `:cacheUpdated:${hash}` event of the the designated data services.\n *\n * @param dataServices - A list of data services.\n * @param messenger - A messenger.\n * @param config - Optional query client configuration options.\n * @returns The QueryClient.\n */\nexport function createUIQueryClient<\n DataServiceNames extends readonly string[],\n MessengerInstance extends GenericMessenger,\n>(\n dataServices: DataServiceNames,\n messenger: SupportsDataServices<MessengerInstance, DataServiceNames[number]>,\n config?: QueryClientConfig,\n): 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\n // `pageParam` is only present for infinite (paginated) queries. For\n // regular queries it is `undefined`, so we omit it to avoid passing a\n // trailing `undefined` argument to the action handler.\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 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 // This function is defined in this way to have full support for all function overloads.\n client.invalidateQueries = async (\n arg1?: QueryKey | InvalidateQueryFilters,\n arg2?: OmitKeyof<InvalidateQueryFilters, 'queryKey'> | InvalidateOptions,\n arg3?: InvalidateOptions,\n ): Promise<void> => {\n const [filters, options] = parseFilterArgs(arg1, arg2, arg3);\n\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
+ {"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 OmitKeyof,\n parseFilterArgs,\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,\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 // This function is defined in this way to have full support for all function overloads.\n client.invalidateQueries = async (\n arg1?: QueryKey | InvalidateQueryFilters,\n arg2?: OmitKeyof<InvalidateQueryFilters, 'queryKey'> | InvalidateOptions,\n arg3?: InvalidateOptions,\n ): Promise<void> => {\n const [filters, options] = parseFilterArgs(arg1, arg2, arg3);\n\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/react-data-query",
3
- "version": "0.2.1-preview-75ce9748b",
3
+ "version": "0.2.2-preview-8b1e39b",
4
4
  "description": "Provides React utilities for consuming data services",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -47,13 +47,9 @@
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": "yarn test:jest && yarn test:types",
51
- "test:clean": "yarn test:jest:clean && yarn test:types",
52
- "test:jest": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
53
- "test:jest:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
54
- "test:jest:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
55
- "test:types": "tsc --noEmit --project tsconfig.type-tests.json && echo \"Type tests pass\"",
56
- "test:verbose": "yarn test:jest:verbose && yarn test:types",
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",
57
53
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
58
54
  },
59
55
  "dependencies": {