diff --git a/.gitignore b/.gitignore index 1514fce5..4291a7b6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules /packages/*/dist /packages/*/coverage *.tsbuildinfo -.turbo \ No newline at end of file +.turbo +.vscode \ No newline at end of file diff --git a/packages/connect-query-core/src/query-client.test.ts b/packages/connect-query-core/src/query-client.test.ts new file mode 100644 index 00000000..73903eb4 --- /dev/null +++ b/packages/connect-query-core/src/query-client.test.ts @@ -0,0 +1,395 @@ +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import type { MessageShape } from "@bufbuild/protobuf"; +import type { Query } from "@tanstack/query-core"; +import { mockEliza, mockPaginatedTransport } from "test-utils"; +import type { SayResponseSchema } from "test-utils/gen/eliza_pb.js"; +import { ElizaService } from "test-utils/gen/eliza_pb.js"; +import { ListService } from "test-utils/gen/list_pb.js"; +import { describe, expect, it } from "vitest"; + +import { createConnectQueryKey } from "./connect-query-key.js"; +import { QueryClient } from "./query-client.js"; + +// TODO: maybe create a helper to take a service and method and generate this. +const sayMethodDescriptor = ElizaService.method.say; + +const mockedElizaTransport = mockEliza(); + +const paginatedTransport = mockPaginatedTransport(); + +const queryDetails = { + schema: sayMethodDescriptor, + input: { + sentence: "Pablo", + }, + transport: mockedElizaTransport, +}; + +describe("prefetch APIs", () => { + it("populates a single query cache", async () => { + const queryClient = new QueryClient(); + const currentCacheItems = queryClient.getQueryCache().findAll(); + expect(currentCacheItems).toHaveLength(0); + + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + const item = queryClient.getConnectQueryData({ + ...queryDetails, + cardinality: "finite", + }); + expect(item.sentence).toBe("Hello Pablo"); + + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.status).toBe("success"); + expect(queryState.fetchStatus).toBe("idle"); + expect(queryState.dataUpdateCount).toBe(1); + }); + + it("populates an infinite query cache", async () => { + const queryClient = new QueryClient(); + const currentCacheItems = queryClient.getQueryCache().findAll(); + expect(currentCacheItems).toHaveLength(0); + + await queryClient.prefetchConnectInfiniteQuery( + ListService.method.list, + { + preview: true, + page: 0n, + }, + { + transport: paginatedTransport, + pageParamKey: "page", + getNextPageParam: (data) => data.page, + }, + ); + + const details = { + schema: ListService.method.list, + transport: paginatedTransport, + input: { + preview: true, + }, + cardinality: "infinite" as const, + }; + + const item = queryClient.getConnectQueryData(details); + + const nextItems = queryClient.getQueryCache().findAll(); + expect(nextItems).toHaveLength(1); + expect(item.pages[0].items).toHaveLength(3); + + const queryState = queryClient.getConnectQueryState(details); + expect(queryState.status).toBe("success"); + expect(queryState.fetchStatus).toBe("idle"); + expect(queryState.dataUpdateCount).toBe(1); + }); +}); + +describe("invalidateConnectQueries", () => { + it("invalidates a specific query", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + await queryClient.invalidateConnectQueries(queryDetails); + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.isInvalidated).toBe(true); + }); + + it("invalidate all methods for a given service", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + + await queryClient.invalidateConnectQueries({ + schema: ElizaService, + transport: mockedElizaTransport, + }); + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.isInvalidated).toBe(true); + }); +}); + +describe("refetchConnectQueries", () => { + it("refetch a specific query", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + await queryClient.refetchConnectQueries(queryDetails); + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.dataUpdateCount).toBe(2); + }); + + it("refetch all methods for a given service", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + + await queryClient.refetchConnectQueries({ + schema: ElizaService, + transport: mockedElizaTransport, + }); + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.dataUpdateCount).toBe(2); + }); +}); + +describe("setConnectQueryData", () => { + it("updates locally fetched data", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.dataUpdateCount).toBe(1); + expect(queryState.data?.sentence).toBe("Hello Pablo"); + + queryClient.setConnectQueryData( + { + ...queryDetails, + cardinality: "finite", + }, + { + sentence: "Hello Stu", + }, + ); + + const newQueryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + + expect(newQueryState.dataUpdateCount).toBe(2); + expect(newQueryState.data?.sentence).toBe("Hello Stu"); + }); + + it("updates locally fetched data with a callback", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + + const queryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + expect(queryState.dataUpdateCount).toBe(1); + expect(queryState.data?.sentence).toBe("Hello Pablo"); + + queryClient.setConnectQueryData( + { + ...queryDetails, + cardinality: "finite", + }, + (prev) => { + if (prev === undefined) { + return undefined; + } + expect(prev.sentence).toBe("Hello Pablo"); + return { + ...prev, + sentence: "Hello Stu", + }; + }, + ); + + const newQueryState = queryClient.getConnectQueryState({ + ...queryDetails, + cardinality: "finite", + }); + + expect(newQueryState.dataUpdateCount).toBe(2); + expect(newQueryState.data?.sentence).toBe("Hello Stu"); + }); +}); + +describe("setConnectQueriesData", () => { + it("update locally fetched data across multiple queries", async () => { + const queryClient = new QueryClient(); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + queryDetails.input, + { transport: queryDetails.transport }, + ); + await queryClient.prefetchConnectQuery( + queryDetails.schema, + { + sentence: "Stu", + }, + { transport: queryDetails.transport }, + ); + + const cachedItems = queryClient.getQueryCache().findAll({ + queryKey: createConnectQueryKey({ + ...queryDetails, + input: {}, + cardinality: "finite", + }), + }); + expect(cachedItems).toHaveLength(2); + + queryClient.setConnectQueriesData( + { + schema: sayMethodDescriptor, + }, + (prev) => { + if (prev === undefined) { + return undefined; + } + return { + ...prev, + sentence: prev.sentence + "!", + }; + }, + ); + + const newCachedItems = queryClient.getQueryCache().findAll() as Query< + MessageShape + >[]; + expect(newCachedItems).toHaveLength(2); + expect(newCachedItems[0].state.data?.sentence).toBe("Hello Pablo!"); + expect(newCachedItems[1].state.data?.sentence).toBe("Hello Stu!"); + }); +}); + +describe("fetchConnectInfiniteQuery", () => { + it("fetches infinite data", async () => { + const queryClient = new QueryClient(); + const result = await queryClient.fetchConnectInfiniteQuery( + ListService.method.list, + { + preview: true, + page: 0n, + }, + { + transport: paginatedTransport, + getNextPageParam: (data) => { + return data.page + 1n; + }, + pageParamKey: "page", + }, + ); + + expect(result).toBeDefined(); + expect(result.pages).toHaveLength(1); + expect(result.pages[0].$typeName).toBe("ListResponse"); + expect(result.pages[0].items).toHaveLength(3); + }); +}); + +describe("getConnectQueryState", () => { + it("can get state for infinite queries", async () => { + const queryClient = new QueryClient(); + await queryClient.fetchConnectInfiniteQuery( + ListService.method.list, + { + preview: true, + page: 0n, + }, + { + transport: paginatedTransport, + getNextPageParam: (data) => { + return data.page + 1n; + }, + pageParamKey: "page", + }, + ); + + const state = queryClient.getConnectQueryState({ + schema: ListService.method.list, + transport: paginatedTransport, + input: { + preview: true, + }, + cardinality: "infinite", + }); + + expect(state.status).toBe("success"); + expect(state.fetchStatus).toBe("idle"); + expect(state.dataUpdateCount).toBe(1); + }); +}); + +describe("ensure APIs", () => { + it("ensure data exists for infinite queries", async () => { + const queryClient = new QueryClient(); + const data = await queryClient.ensureConnectInfiniteQueryData( + ListService.method.list, + { + preview: true, + page: 0n, + }, + { + transport: paginatedTransport, + getNextPageParam: (localData) => { + return localData.page + 1n; + }, + pageParamKey: "page", + staleTime: 1000, + }, + ); + + const state = queryClient.getConnectQueryState({ + schema: ListService.method.list, + transport: paginatedTransport, + input: { + preview: true, + }, + cardinality: "infinite", + }); + expect(state.status).toBe("success"); + expect(state.fetchStatus).toBe("idle"); + expect(state.dataUpdateCount).toBe(1); + expect(data.pages[0]).toBe(state.data?.pages[0]); + }); +}); diff --git a/packages/connect-query-core/src/query-client.ts b/packages/connect-query-core/src/query-client.ts new file mode 100644 index 00000000..1e3637f2 --- /dev/null +++ b/packages/connect-query-core/src/query-client.ts @@ -0,0 +1,574 @@ +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import type { + DescMessage, + DescMethod, + DescMethodUnary, + DescService, + MessageInitShape, + MessageShape, +} from "@bufbuild/protobuf"; +import type { ConnectError, Transport } from "@connectrpc/connect"; +import type { + FetchInfiniteQueryOptions as TanstackFetchInfiniteQueryOptions, + FetchQueryOptions as TanstackFetchQueryOptions, + InfiniteData, + InvalidateOptions, + InvalidateQueryFilters, + QueryFilters, + QueryState, + RefetchOptions, + RefetchQueryFilters, + ResetOptions, + SetDataOptions, +} from "@tanstack/query-core"; +import { QueryClient as TSQueryClient } from "@tanstack/query-core"; + +import type { ConnectQueryKey } from "./connect-query-key.js"; +import { createConnectQueryKey } from "./connect-query-key.js"; +import type { ConnectInfiniteQueryOptions } from "./create-infinite-query-options.js"; +import { createInfiniteQueryOptions } from "./create-infinite-query-options.js"; +import { createQueryOptions } from "./create-query-options.js"; +import type { ConnectUpdater } from "./utils.js"; +import { createProtobufSafeUpdater } from "./utils.js"; + +type FetchQueryOptions< + O extends DescMessage, + SelectOutData = MessageShape, +> = Omit< + TanstackFetchQueryOptions< + MessageShape, + ConnectError, + SelectOutData, + ConnectQueryKey + >, + "queryFn" | "queryKey" +> & { + /** The transport to be used for the fetching. */ + transport?: Transport; +}; + +type FetchInfiniteQueryOptions< + I extends DescMessage, + O extends DescMessage, + ParamKey extends keyof MessageInitShape, +> = Omit< + TanstackFetchInfiniteQueryOptions< + MessageShape, + ConnectError, + MessageShape, + ConnectQueryKey, + MessageInitShape[ParamKey] + >, + "getNextPageParam" | "initialPageParam" | "queryFn" | "queryKey" +> & + ConnectInfiniteQueryOptions & { + transport: Transport; + }; + +type KeyParams = + Desc extends DescMethodUnary + ? { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality?: "finite" | "infinite" | undefined; + pageParamKey?: keyof MessageInitShape; + } + : { + schema: Desc; + transport: Transport; + cardinality?: "finite" | "infinite" | undefined; + }; + +/** + * A custom query client that adds some useful methods to access typesafe query data and other shortcuts. + */ +export class QueryClient extends TSQueryClient { + /** + * Invalidate and refetch all queries that match the given schema. This + * can include all queries for a service (and sub methods), or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientinvalidatequeries} + */ + public async invalidateConnectQueries< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >( + params: Desc | Params, + filterOptions?: Omit, + options?: InvalidateOptions, + ) { + if ("schema" in params) { + return this.invalidateQueries( + { + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }, + options, + ); + } + return this.invalidateQueries( + { + ...filterOptions, + queryKey: createConnectQueryKey({ + schema: params as DescMethod, + cardinality: undefined, + }), + }, + options, + ); + } + + /** + * Refetches all queries that match the given schema. This can include all queries for a service (and sub methods), + * or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientrefetchqueries} + */ + public async refetchConnectQueries< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >( + params: Params, + filterOptions?: Omit, + options?: RefetchOptions, + ) { + return this.refetchQueries( + { + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }, + options, + ); + } + + /** + * Set the data for a single query. The query must match exactly the input provided, as well + * as the transport and cardinality (whether it was a finite or infinite query). + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientsetquerydata} + */ + public setConnectQueryData( + keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "finite" | "infinite"; + }, + updater: ConnectUpdater, + + options?: SetDataOptions | undefined, + ) { + return this.setQueryData( + createConnectQueryKey({ + ...keyDescriptor, + // Since we are matching on the exact input, we match what connect-query does in createQueryOptions + input: keyDescriptor.input ?? {}, + }), + createProtobufSafeUpdater(keyDescriptor.schema, updater), + options, + ); + } + + /** + * Get the data for a single query. The query must match exactly the input provided, as well + * as the transport and cardinality (whether it was a finite or infinite query). + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientgetquerydata} + */ + public getConnectQueryData(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "finite"; + }): MessageShape; + public getConnectQueryData(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "infinite"; + }): InfiniteData>; + public getConnectQueryData(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "finite" | "infinite"; + }): + | MessageShape + | InfiniteData> + | undefined { + const key = createConnectQueryKey({ + ...keyDescriptor, + // Since we are matching on the exact input, we match what connect-query does in createQueryOptions + input: keyDescriptor.input ?? {}, + }); + return this.getQueryData(key); + } + + /** + * Sets the data for any matching queries for a given method. The input is optional, and anything left + * as undefined will greedily match queries. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientsetqueriesdata} + */ + public setConnectQueriesData( + keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport?: Transport; + cardinality?: "finite" | "infinite" | undefined; + }, + updater: ConnectUpdater, + options?: + | (SetDataOptions & { + exact?: boolean; + }) + | undefined, + ) { + return this.setQueriesData( + { + queryKey: createConnectQueryKey({ + ...keyDescriptor, + cardinality: keyDescriptor.cardinality, + }), + exact: options?.exact ?? false, + }, + createProtobufSafeUpdater(keyDescriptor.schema, updater), + options, + ); + } + + /** + * Fetch a single query and return the result. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientfetchquery} + */ + public async fetchConnectQuery< + I extends DescMessage, + O extends DescMessage, + SelectOutData = MessageShape, + >( + schema: DescMethodUnary, + input: MessageInitShape | undefined, + { + transport, + ...queryOptions + }: { + transport: Transport; + } & FetchQueryOptions, + ) { + return this.fetchQuery({ + ...createQueryOptions(schema, input, { transport }), + ...queryOptions, + }); + } + + /** + * Fetch a single infinite query and return the result. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientfetchinfinitequery} + */ + public async fetchConnectInfiniteQuery< + I extends DescMessage, + O extends DescMessage, + ParamKey extends keyof MessageInitShape, + >( + schema: DescMethodUnary, + input: MessageInitShape & Required, ParamKey>>, + { + transport, + getNextPageParam, + pageParamKey, + ...queryOptions + }: FetchInfiniteQueryOptions, + ) { + return this.fetchInfiniteQuery({ + ...createInfiniteQueryOptions(schema, input, { + transport, + pageParamKey, + getNextPageParam, + }), + ...queryOptions, + }); + } + + /** + * Prefetch a single query and discard the result. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientprefetchquery} + */ + public async prefetchConnectQuery< + I extends DescMessage, + O extends DescMessage, + SelectOutData = MessageShape, + >( + schema: DescMethodUnary, + input: MessageInitShape | undefined, + { + transport, + ...queryOptions + }: { + transport: Transport; + } & FetchQueryOptions, + ) { + return this.prefetchQuery({ + ...createQueryOptions(schema, input, { transport }), + ...queryOptions, + }); + } + + /** + * Prefetch a single infinite query and discard the result. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientprefetchinfinitequery} + */ + public async prefetchConnectInfiniteQuery< + I extends DescMessage, + O extends DescMessage, + ParamKey extends keyof MessageInitShape, + >( + schema: DescMethodUnary, + input: MessageInitShape & Required, ParamKey>>, + { + transport, + getNextPageParam, + pageParamKey, + ...queryOptions + }: FetchInfiniteQueryOptions, + ) { + return this.prefetchInfiniteQuery({ + ...createInfiniteQueryOptions(schema, input, { + transport, + pageParamKey, + getNextPageParam, + }), + ...queryOptions, + }); + } + + /** + * Get the query state for a single query. The query must match exactly the input provided, as well + * as the transport and cardinality (whether it was a finite or infinite query). + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientgetquerystate} + */ + public getConnectQueryState(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "finite"; + }): QueryState, ConnectError>; + public getConnectQueryState(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "infinite"; + }): QueryState>, ConnectError>; + public getConnectQueryState(keyDescriptor: { + schema: Desc; + input?: MessageInitShape | undefined; + transport: Transport; + cardinality: "finite" | "infinite"; + }) { + return this.getQueryState(createConnectQueryKey(keyDescriptor)); + } + + /** + * Ensure the query data for a single query. The query must match exactly the input provided, as well + * as the transport and cardinality (whether it was a finite or infinite query). + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientensurequerydata} + */ + public async ensureConnectQueryData< + I extends DescMessage, + O extends DescMessage, + SelectOutData = MessageShape, + >( + schema: DescMethodUnary, + input: MessageInitShape | undefined, + { + transport, + ...queryOptions + }: { + transport: Transport; + } & FetchQueryOptions, + ) { + return this.ensureQueryData({ + ...createQueryOptions(schema, input, { transport }), + ...queryOptions, + }); + } + + /** + * Ensure the query data for a single infinite query. The query must match exactly the input provided, as well + * as the transport and cardinality (whether it was a finite or infinite query). + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientensureinfinitequerydata} + */ + public async ensureConnectInfiniteQueryData< + I extends DescMessage, + O extends DescMessage, + ParamKey extends keyof MessageInitShape, + >( + schema: DescMethodUnary, + input: MessageInitShape & Required, ParamKey>>, + { + transport, + getNextPageParam, + pageParamKey, + ...queryOptions + }: FetchInfiniteQueryOptions, + ) { + return this.ensureInfiniteQueryData({ + ...createInfiniteQueryOptions(schema, input, { + transport, + pageParamKey, + getNextPageParam, + }), + ...queryOptions, + }); + } + + /** + * Get all data entries that match the given schema. This + * can include all queries for a service (and sub methods), or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientgetqueriesdata} + */ + public getConnectQueriesData< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >(params: Desc | Params, filterOptions?: Omit) { + if ("schema" in params) { + return this.getQueriesData({ + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }); + } + return this.getQueriesData({ + ...filterOptions, + queryKey: createConnectQueryKey({ + schema: params as DescMethod, + cardinality: undefined, + }), + }); + } + + /** + * Cancels any outgoing queries that match the given schema. This + * can include all queries for a service (and sub methods), or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientcancelqueries} + */ + public async cancelConnectQueries< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >(params: Desc | Params, filterOptions?: Omit) { + if ("schema" in params) { + return this.cancelQueries({ + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }); + } + return this.cancelQueries({ + ...filterOptions, + queryKey: createConnectQueryKey({ + schema: params as DescMethod, + cardinality: undefined, + }), + }); + } + + /** + * Removes any queries from the cache that match the given schema. This + * can include all queries for a service (and sub methods), or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientremovequeries} + */ + public removeConnectQueries< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >(params: Desc | Params, filterOptions?: Omit) { + if ("schema" in params) { + this.removeQueries({ + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }); + return; + } + this.removeQueries({ + ...filterOptions, + queryKey: createConnectQueryKey({ + schema: params as DescMethod, + cardinality: undefined, + }), + }); + return; + } + + /** + * Resets any queries that match the given schema. This + * can include all queries for a service (and sub methods), or all queries for a method. + * + * @see {@link https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientresetqueries} + */ + public async resetConnectQueries< + Desc extends DescMethod | DescService, + Params extends KeyParams, + >( + params: Desc | Params, + filterOptions?: Omit, + options?: ResetOptions, + ) { + if ("schema" in params) { + return this.resetQueries( + { + ...filterOptions, + queryKey: createConnectQueryKey({ + ...params, + cardinality: params.cardinality, + }), + }, + options, + ); + } + return this.resetQueries( + { + ...filterOptions, + queryKey: createConnectQueryKey({ + schema: params as DescMethod, + cardinality: undefined, + }), + }, + options, + ); + } +} diff --git a/packages/test-utils/src/index.tsx b/packages/test-utils/src/index.tsx index 10fca078..a577f9ef 100644 --- a/packages/test-utils/src/index.tsx +++ b/packages/test-utils/src/index.tsx @@ -41,7 +41,7 @@ export const sleep = async (timeout: number) => */ export const mockEliza = ( override?: MessageInitShape, - addDelay = false + addDelay = false, ) => createRouterTransport(({ service }) => { service(ElizaService, { @@ -51,7 +51,7 @@ export const mockEliza = ( } return create( SayResponseSchema, - override ?? { sentence: `Hello ${input.sentence}` } + override ?? { sentence: `Hello ${input.sentence}` }, ); }, }); @@ -92,7 +92,7 @@ export const mockStatefulBigIntTransport = (addDelay = false) => */ export const mockPaginatedTransport = ( override?: MessageInitShape, - addDelay = false + addDelay = false, ) => createRouterTransport(({ service }) => { service(ListService, {