From 481aa9bb894542980c4101a0dbbeb71295f7b111 Mon Sep 17 00:00:00 2001 From: vishalbalaji <36506250+vishalbalaji@users.noreply.github.com> Date: Sun, 17 Dec 2023 00:04:30 +0530 Subject: [PATCH] Implement query cancellation (#18) * feat: add query cancellation * feat: add initialCursor to infinite queries * docs: update README * chore: bump version --- README.md | 22 +++++++---------- package.json | 2 +- src/index.ts | 68 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index dc75e71..e86e736 100644 --- a/README.md +++ b/README.md @@ -144,16 +144,16 @@ Which can then be used in a component as such: import { trpc } from "$lib/trpc/client"; const client = trpc(); - const hello = client.greeting.createQuery("foo", { retry: false }); + const foo = client.greeting.createQuery("foo", { retry: false });

- {#if $hello.isLoading} + {#if $foo.isLoading} Loading... - {:else if $hello.isError} - Error: {$hello.error.message} + {:else if $foo.isError} + Error: {$foo.error.message} {:else} - {$hello.data} + {$foo.data} {/if}

``` @@ -202,7 +202,7 @@ By default, these 3 procedures will pre-fetch the data required to pre-render th These procedures can be used as such: -> **NOTE:** You can await the procedures first, but it is better to pass the promises directly as SvelteKit automatically resolves all these promises at the same time. [This excellent video by **Huntabyte**](https://www.youtube.com/watch?v=Ymk22rD8Lb4) explains this in detail. +> **NOTE:** [Gotta await top-level promises from SvelteKit v2](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited). ```typescript // +page.ts @@ -215,8 +215,8 @@ export const load = (async (event) => { const client = trpc(event, queryClient); return { - foo: client.greeting.createServerQuery('foo'), - queries: client.createServerQueries((t) => + foo: await client.greeting.createServerQuery('foo'), + queries: await client.createServerQueries((t) => ['bar', 'baz'].map((name) => t.greeting(name, { ssr: name !== 'baz' })), // pre-fetching disabled for the `baz` query. ), }; @@ -257,12 +257,6 @@ Then, in the component:
{/each} ``` - -## Some Notes - -* This wrapper only supports `tRPC v10` onward. -* This project was made purely for fun and not linked to official `tRPC` or `tanstack-query` development in any way. If any official adapters of this sort were to be released, this project would most likely be discontinued. - [npm-url]: https://npmjs.org/package/trpc-svelte-query-adapter [npm-image]: https://img.shields.io/npm/v/trpc-svelte-query-adapter.svg [license-url]: LICENSE diff --git a/package.json b/package.json index 0b1370c..9247bd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trpc-svelte-query-adapter", - "version": "2.2.0", + "version": "2.2.1", "description": "A simple adapter to use `@tanstack/svelte-query` with trpc, similar to `@trpc/react-query`.", "keywords": [ "trpc", diff --git a/src/index.ts b/src/index.ts index 3820405..c237803 100644 --- a/src/index.ts +++ b/src/index.ts @@ -197,10 +197,16 @@ interface CreateServerQueryOptions ssr?: boolean } +type TRPCQueryOpts = { + trpc: { + abortOnUnmount: boolean; + }, +}; + type CreateQueryProcedure = { - [ProcedureNames.query]: (input: TInput, opts?: CreateQueryOptions) + [ProcedureNames.query]: (input: TInput, opts?: CreateQueryOptions & TRPCQueryOpts) => CreateQueryResult, - [ProcedureNames.serverQuery]: (input: TInput, opts?: CreateServerQueryOptions) + [ProcedureNames.serverQuery]: (input: TInput, opts?: CreateServerQueryOptions & TRPCQueryOpts) => Promise<() => CreateQueryResult>, } & {} @@ -209,11 +215,19 @@ interface CreateServerInfiniteQueryOptions ssr?: boolean } +export type ExtractCursorType = TInput extends { cursor: any } + ? TInput['cursor'] + : unknown; + +type InfiniteQueryOpts = { + initialCursor?: ExtractCursorType; +} + type CreateInfiniteQueryProcedure = (TInput extends { cursor?: any } ? { - [ProcedureNames.infiniteQuery]: (input: Omit, opts?: CreateInfiniteQueryOptions) + [ProcedureNames.infiniteQuery]: (input: Omit, opts?: CreateInfiniteQueryOptions & InfiniteQueryOpts & TRPCQueryOpts) => CreateInfiniteQueryResult, - [ProcedureNames.serverInfiniteQuery]: (input: Omit, opts?: CreateServerInfiniteQueryOptions) + [ProcedureNames.serverInfiniteQuery]: (input: Omit, opts?: CreateServerInfiniteQueryOptions & InfiniteQueryOpts & TRPCQueryOpts) => Promise<() => CreateInfiniteQueryResult>, } : {}) & {} @@ -306,7 +320,7 @@ const utilsProcedures: Record target.query({ ...input, cursor: pageParam }), + queryFn: ({ pageParam }: { pageParam: number }) => target.query({ ...input, cursor: pageParam }), }); }; }, @@ -377,7 +391,7 @@ const utilsProcedures: Record { return (input?: any, filters?: any) => { - return queryClient.getQueryData( { + return queryClient.getQueryData({ ...filters, queryKey: getArrayQueryKey(path, input, 'query'), }); @@ -415,29 +429,36 @@ const procedures: Record any, utilsProxy: () => any + abortOnUnmount?: boolean, }) => any> = { [ProcedureNames.queryKey]: ({ path }) => { return (input: any, opts?: any) => getArrayQueryKey(path, input, opts); }, - [ProcedureNames.query]: ({ path, target }) => { + [ProcedureNames.query]: ({ path, target, abortOnUnmount }) => { const targetFn = target.query; return (input: any, opts?: any) => { + const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; return createQuery({ ...opts, queryKey: getArrayQueryKey(path, input, 'query'), - queryFn: () => targetFn(input), + queryFn: ({ signal }) => targetFn(input, { + ...(shouldAbortOnUnmount && { signal }), + }), }); }; }, - [ProcedureNames.serverQuery]: ({ path, target, queryClient }) => { + [ProcedureNames.serverQuery]: ({ path, target, queryClient, abortOnUnmount }) => { const targetFn = target.query; return async (input: any, opts?: any) => { - const query = { + const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + const query: FetchQueryOptions = { queryKey: getArrayQueryKey(path, input, 'query'), - queryFn: () => targetFn(input), + queryFn: ({ signal }) => targetFn(input, { + ...(shouldAbortOnUnmount && { signal }), + }), }; const cache = queryClient.getQueryCache().find({ queryKey: query.queryKey }); @@ -455,22 +476,33 @@ const procedures: Record { + [ProcedureNames.infiniteQuery]: ({ path, target, abortOnUnmount }) => { return (input: any, opts?: any) => { + const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; return createInfiniteQuery({ ...opts, queryKey: getArrayQueryKey(path, input, 'infinite'), - queryFn: ({ pageParam }: { pageParam: number }) => target.query({ ...input, cursor: pageParam }), + queryFn: ({ pageParam, signal }) => target.query({ + ...input, + cursor: pageParam ?? opts?.initialCursor, + ...(shouldAbortOnUnmount && { signal }), + + }), }); }; }, - [ProcedureNames.serverInfiniteQuery]: ({ path, target, queryClient }) => { + [ProcedureNames.serverInfiniteQuery]: ({ path, target, queryClient, abortOnUnmount }) => { const targetFn = target.query; return async (input: any, opts?: any) => { - const query = { + const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + const query: Omit = { queryKey: getArrayQueryKey(path, input, 'infinite'), - queryFn: ({ pageParam }: { pageParam: number }) => targetFn({ ...input, cursor: pageParam }), + queryFn: ({ pageParam, signal }) => targetFn({ + ...input, + cursor: pageParam ?? opts?.initialCursor, + ...(shouldAbortOnUnmount && { signal }), + }), }; const cache = queryClient.getQueryCache().find({ queryKey: query.queryKey }); @@ -611,7 +643,8 @@ type GetQueryKey = TInput extends undefined export function svelteQueryWrapper({ client, queryClient, -}: { client: CreateTRPCProxyClient, queryClient?: QueryClient }) { + abortOnUnmount, +}: { client: CreateTRPCProxyClient, queryClient?: QueryClient, abortOnUnmount?: boolean }) { type Client = typeof client; type RouterError = TRPCClientErrorLike; @@ -645,6 +678,7 @@ export function svelteQueryWrapper({ queryClient: qc, queriesProxy: () => createQueriesProxy(client), utilsProxy: () => createUtilsProxy(client, qc), + abortOnUnmount, }); } return this.nest(() => { });