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(() => { });