diff --git a/@example/src/lib/trpc/router.ts b/@example/src/lib/trpc/router.ts index 0d5861b..2898b55 100644 --- a/@example/src/lib/trpc/router.ts +++ b/@example/src/lib/trpc/router.ts @@ -20,7 +20,15 @@ export const router = t.router({ .then((r) => r[0]); }), - get: t.procedure.query(({ ctx: { db } }) => db.query.todo.findMany()), + get: t.procedure + .input(z.string().optional()) + .query(({ input: filter, ctx: { db } }) => + db.query.todo.findMany({ + where: filter + ? (todo, { like }) => like(todo.text, `%${filter}%`) + : undefined, + }) + ), getPopular: t.procedure .input( diff --git a/@example/src/lib/utils.ts b/@example/src/lib/utils.ts new file mode 100644 index 0000000..68bf41b --- /dev/null +++ b/@example/src/lib/utils.ts @@ -0,0 +1,7 @@ +export function debounce(cb: (v: T) => void, durationMs: number) { + let timer: ReturnType; + return (v: T) => { + clearTimeout(timer); + timer = setTimeout(() => cb(v), durationMs); + }; +} diff --git a/@example/src/routes/(app)/client-only/+page.svelte b/@example/src/routes/(app)/client-only/+page.svelte index 4ac5b76..08a91a2 100644 --- a/@example/src/routes/(app)/client-only/+page.svelte +++ b/@example/src/routes/(app)/client-only/+page.svelte @@ -3,20 +3,28 @@ import { X, Plus } from 'phosphor-svelte'; + import { debounce } from '$lib/utils'; import { page } from '$app/stores'; import { trpc } from '$lib/trpc/client'; + import { writable } from 'svelte/store'; + import type { InferProcedureOpts } from 'trpc-svelte-query-adapter'; const api = trpc($page); const utils = api.createUtils(); let todoInput: HTMLInputElement; - const todos = api.todos.get.createQuery(); + const filter = writable(); + const opts = writable({ + refetchInterval: Infinity, + } satisfies InferProcedureOpts); + + const todos = api.todos.get.createQuery(filter, opts); const popularTodos = api.todos.getPopular.createInfiniteQuery( {}, { - getNextPageParam: (data) => data.nextCursor, + getNextPageParam: (lastPage) => lastPage.nextCursor, } ); @@ -81,6 +89,33 @@ {/if} +
+ + +
+ { + if (!(e.target instanceof HTMLInputElement)) return; + $filter = e.target.value || undefined; + }, 500)} + /> + { + if (!(e.target instanceof HTMLInputElement)) return; + $opts.refetchInterval = e.target.value ? +e.target.value : Infinity; + }, 500)} + /> +
+
+
{#if $todos.isPending} diff --git a/@example/src/routes/(app)/ssr/+page.svelte b/@example/src/routes/(app)/ssr/+page.svelte index bf4c42a..0b24fb6 100644 --- a/@example/src/routes/(app)/ssr/+page.svelte +++ b/@example/src/routes/(app)/ssr/+page.svelte @@ -2,6 +2,9 @@ import Heading from '$lib/components/Heading.svelte'; import { X, Plus } from 'phosphor-svelte'; + import { writable } from 'svelte/store'; + import { debounce } from '$lib/utils'; + import type { InferProcedureOpts } from 'trpc-svelte-query-adapter'; export let data; @@ -10,7 +13,13 @@ let todoInput: HTMLInputElement; - const todos = data.todos(); + const filter = writable(); + const opts = writable({ + refetchInterval: Infinity, + } satisfies InferProcedureOpts); + + const todos = data.todos(filter, opts); + const popularTodos = data.popularTodos(); const createTodo = api.todos.create.createMutation({ @@ -31,7 +40,7 @@ }); -Client-only +SSR
@@ -74,6 +83,33 @@ {/if} +
+ + +
+ { + if (!(e.target instanceof HTMLInputElement)) return; + $filter = e.target.value || undefined; + }, 500)} + /> + { + if (!(e.target instanceof HTMLInputElement)) return; + $opts.refetchInterval = e.target.value ? +e.target.value : Infinity; + }, 500)} + /> +
+
+
{#if $todos.isPending} diff --git a/@example/src/routes/(app)/ssr/+page.ts b/@example/src/routes/(app)/ssr/+page.ts index 0325c41..9ca3535 100644 --- a/@example/src/routes/(app)/ssr/+page.ts +++ b/@example/src/routes/(app)/ssr/+page.ts @@ -11,7 +11,7 @@ export async function load(event) { popularTodos: await api.todos.getPopular.createServerInfiniteQuery( {}, { - getNextPageParam: (data) => data.nextCursor, + getNextPageParam: (lastPage) => lastPage.nextCursor, } ), }; diff --git a/@lib/README.md b/@lib/README.md index e052dc7..2886952 100644 --- a/@lib/README.md +++ b/@lib/README.md @@ -4,13 +4,15 @@ [![License][license-image]][license-url] [![Last commit][last-commit-image]][repo-url] -> **NOTE:** The README on [npmjs](https://npmjs.com/trpc-svelte-query-adapter) might not be fully up to date. Please refer to the [README on the Github Repo](https://github.com/vishalbalaji/trpc-svelte-query-adapter/#readme) for the latest setup instructions. +> [!NOTE] +> The README on [npmjs](https://npmjs.com/trpc-svelte-query-adapter) might not be fully up to date. Please refer to +> the [README on the Github Repo](https://github.com/vishalbalaji/trpc-svelte-query-adapter/#readme) for the latest setup instructions. -This package provides an adapter to call `tRPC` procedures wrapped with [@tanstack/svelte-query](https://tanstack.com/query/latest/docs/svelte/overview), similar to [@trpc/react-query](https://trpc.io/docs/react-query). This is made possible using [proxy-deep](https://www.npmjs.com/package/proxy-deep). +An adapter to call `tRPC` procedures wrapped with [@tanstack/svelte-query](https://tanstack.com/query/latest/docs/svelte/overview), similar to [@trpc/react-query](https://trpc.io/docs/react-query). This is made possible using [proxy-deep](https://www.npmjs.com/package/proxy-deep). ## Installation -```console +```sh # npm npm install trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query @@ -202,7 +204,8 @@ By default, these 3 procedures will pre-fetch the data required to pre-render th These procedures can be used as such: -> **NOTE:** [Gotta await top-level promises to pre-fetch data from SvelteKit v2](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited). +> [!NOTE] +> [Gotta await top-level promises to pre-fetch data from SvelteKit v2](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited). ```typescript // +page.ts @@ -258,58 +261,62 @@ Then, in the component: {/each} ``` -You can also optionally pass new inputs to the queries and infinite queries from the client side(see [#34](/../../issues/34)) like so: +You can also optionally pass new inputs to the queries and infinite queries from the client side(see [#34](/../../issues/34), [#47]( /../../issues/47 )) like so: ```svelte -{#if $foo.isPending} - Loading... -{:else if $foo.isError} - {$foo.error} -{:else if $foo.data} - {$foo.data} -{/if} - -
- - -

- -{#each $queries as query} - {#if query.isPending} +
+ {#if $foo.isPending} Loading... - {:else if query.isError} - {query.error.message} - {:else if query.data} - {query.data} + {:else if $foo.isError} + {$foo.error} + {:else if $foo.data} + {$foo.data} {/if} -
-{/each} + +
+ +
-
{ - const data = new FormData(e.currentTarget).get('name'); - if (typeof data === 'string') newNames.push(data); - newNames = newNames; -}}> - - -
+
+ {#each $queries as query} + {#if query.isPending} + Loading... + {:else if query.isError} + {query.error.message} + {:else if query.data} + {query.data} + {/if} +
+ {/each} + +
{ + const data = new FormData(e.currentTarget).get('name'); + if (typeof data === 'string') $newNames.push(data); + $newNames = $newNames; + }}> + + +
+
``` [npm-url]: https://npmjs.org/package/trpc-svelte-query-adapter diff --git a/@lib/package.json b/@lib/package.json index df50535..3d24023 100644 --- a/@lib/package.json +++ b/@lib/package.json @@ -1,6 +1,6 @@ { "name": "trpc-svelte-query-adapter", - "version": "2.3.7", + "version": "2.3.8", "description": "A simple adapter to use `@tanstack/svelte-query` with trpc, similar to `@trpc/react-query`.", "keywords": [ "trpc", @@ -34,7 +34,7 @@ "import": "./src/index.ts" }, "dependencies": { - "proxy-deep": "^3.1.1" + "proxy-deep": "^4.0.1" }, "peerDependencies": { "@tanstack/svelte-query": "^5.8.2", diff --git a/@lib/src/index.ts b/@lib/src/index.ts index 244ff24..e7bbd9d 100644 --- a/@lib/src/index.ts +++ b/@lib/src/index.ts @@ -1,8 +1,8 @@ import DeepProxy from 'proxy-deep'; -import { - type TRPCClientErrorLike, - type CreateTRPCProxyClient, +import type { + TRPCClientErrorLike, + CreateTRPCProxyClient, TRPCUntypedClient, } from '@trpc/client'; import type { AnyRouter } from '@trpc/server'; @@ -32,13 +32,46 @@ import { type CreateQueryResult, type CreateInfiniteQueryResult, type CreateMutationResult, - QueriesResults, + type StoreOrVal as _StoreOrVal, + type QueryObserverResult, + type QueryObserverOptions, + type DefaultError, + type OmitKeyof, + type QueriesPlaceholderDataFunction, } from '@tanstack/svelte-query'; -import { onDestroy } from 'svelte'; +import { onDestroy, onMount } from 'svelte'; +import { + derived, + get, + writable, + type Readable, + type Writable, +} from 'svelte/store'; type InnerClient = TRPCUntypedClient; +type StoreOrVal = _StoreOrVal | Writable; + +export function isSvelteStore( + obj: StoreOrVal +): obj is Readable { + return ( + typeof obj === 'object' && + 'subscribe' in obj && + typeof obj.subscribe === 'function' + ); +} + +const blank = Symbol('blank'); +export const isBlank = (val: unknown): val is typeof blank => val === blank; +export const blankStore: Readable = { + subscribe(run) { + run(blank); + return () => {}; + }, +}; + // CREDIT: https://stackoverflow.com/a/63448246 type WithNevers = { [K in keyof T]: Exclude extends V @@ -58,7 +91,20 @@ type HasMutate = { mutate: (...args: any[]) => any }; type HasSubscribe = { subscribe: (...args: any[]) => any }; type OnlyQueries = Without; -// createUtils +const Procedure = { + query: 'createQuery', + serverQuery: 'createServerQuery', + infiniteQuery: 'createInfiniteQuery', + serverInfiniteQuery: 'createServerInfiniteQuery', + mutate: 'createMutation', + subscribe: 'createSubscription', + queryKey: 'getQueryKey', + context: 'createContext', + utils: 'createUtils', + queries: 'createQueries', + serverQueries: 'createServerQueries', +} as const; + const UtilsProcedure = { client: 'client', fetch: 'fetch', @@ -76,6 +122,7 @@ const UtilsProcedure = { getInfiniteData: 'getInfiniteData', } as const; +// createUtils type UtilsProcedures< TInput = undefined, TOutput = undefined, @@ -202,27 +249,68 @@ type AddUtilsPropTypes = { Pick; }; -type CreateUtils = AddUtilsPropTypes< - OnlyQueries, - TError -> & - Pick & { - [UtilsProcedure.client]: TClient; - }; +type CreateUtilsProcedure = { + /** + * @see https://trpc.io/docs/client/react/useUtils + */ + [Procedure.utils](): AddUtilsPropTypes, TError> & + Pick & { + [UtilsProcedure.client]: TClient; + }; + + /** + * @deprecated renamed to `createUtils` and will be removed in a future tRPC version + * + * @see https://trpc.io/docs/client/react/useUtils + */ + [Procedure.context](): AddUtilsPropTypes, TError> & + Pick & { + [UtilsProcedure.client]: TClient; + }; +} & {}; // createQueries -type CreateQueriesResult = ReturnType< - typeof createQueries ->; -type CreateQueriesOpts = Omit< - Parameters>[0], - 'queries' ->; +// REFER: https://github.com/trpc/trpc/blob/936db6dd2598337758e29c843ff66984ed54faaf/packages/react-query/src/internals/useQueries.ts#L33 +type QueriesResults< + TQueriesOptions extends CreateQueryOptionsForCreateQueries< + any, + any, + any, + any + >[], +> = { + [TKey in keyof TQueriesOptions]: TQueriesOptions[TKey] extends CreateQueryOptionsForCreateQueries< + infer TQueryFnData, + infer TError, + infer TData, + any + > + ? QueryObserverResult + : never; +}; -type CreateQueryOptionsForCreateQueries = Omit< - CreateQueryOptions, +type QueryObserverOptionsForCreateQueries< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = OmitKeyof< + QueryObserverOptions, + 'placeholderData' +> & { + placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction; +}; + +type CreateQueryOptionsForCreateQueries< + TOutput = unknown, + TError = unknown, + TData = unknown, + TQueryKey extends QueryKey = QueryKey, +> = Omit< + QueryObserverOptionsForCreateQueries, 'context' | 'queryKey' | 'queryFn' ->; +> & + TRPCQueryOpts; type CreateQueriesRecord = { [K in keyof TClient]: TClient[K] extends HasQuery @@ -233,21 +321,22 @@ type CreateQueriesRecord = { : CreateQueriesRecord; }; -type CreateQueries = < - TOpts extends CreateQueryOptionsForCreateQueries[], - TCombinedResult = QueriesResults, ->( - queriesCallback: ( - t: CreateQueriesRecord, TError> - ) => readonly [...TOpts], - opts?: CreateQueriesOpts -) => CreateQueriesResult; +type CreateQueriesOpts< + TOpts extends CreateQueryOptionsForCreateQueries[], + TCombinedResult, +> = { + combine?: (result: QueriesResults) => TCombinedResult; +}; // createServerQueries -type CreateQueryOptionsForCreateServerQueries = - CreateQueryOptionsForCreateQueries & { - ssr?: boolean; - }; +type CreateQueryOptionsForCreateServerQueries< + TOutput = unknown, + TError = unknown, + TData = unknown, + TQueryKey extends QueryKey = QueryKey, +> = CreateQueryOptionsForCreateQueries & { + ssr?: boolean; +}; type CreateServerQueriesRecord = { [K in keyof TClient]: TClient[K] extends HasQuery @@ -255,45 +344,50 @@ type CreateServerQueriesRecord = { input: Parameters[0], opts?: CreateQueryOptionsForCreateServerQueries ) => CreateQueryOptionsForCreateServerQueries - : CreateQueriesRecord; + : CreateServerQueriesRecord; }; -type CreateServerQueries = < - TOpts extends CreateQueryOptionsForCreateQueries[], - TCombinedResult = QueriesResults, ->( - queriesCallback: ( - t: CreateServerQueriesRecord, TError> - ) => readonly [...TOpts], - opts?: CreateQueriesOpts -) => Promise< - ( - queriesCallback?: ( - t: CreateQueriesRecord, TError>, - old: readonly [...TOpts] - ) => readonly [...TOpts] - ) => CreateQueriesResult ->; +type CreateQueriesProcedure = { + [Procedure.queries]: < + TOpts extends CreateQueryOptionsForCreateQueries[], + TCombinedResult = QueriesResults, + >( + queriesCallback: ( + t: CreateQueriesRecord, TError> + ) => StoreOrVal, + opts?: CreateQueriesOpts + ) => Readable; + + [Procedure.serverQueries]: < + TOpts extends CreateQueryOptionsForCreateServerQueries< + any, + any, + any, + any + >[], + TCombinedResult = QueriesResults, + >( + queriesCallback: ( + t: CreateServerQueriesRecord, TError> + ) => readonly [...TOpts], + opts?: CreateQueriesOpts + ) => Promise< + ( + queriesCallback?: ( + t: CreateServerQueriesRecord, TError>, + old: readonly [...TOpts] + ) => StoreOrVal, + opts?: CreateQueriesOpts + ) => Readable + >; +} & {}; // Procedures -const Procedure = { - query: 'createQuery', - serverQuery: 'createServerQuery', - infiniteQuery: 'createInfiniteQuery', - serverInfiniteQuery: 'createServerInfiniteQuery', - mutate: 'createMutation', - subscribe: 'createSubscription', - queryKey: 'getQueryKey', - context: 'createContext', - utils: 'createUtils', - queries: 'createQueries', - serverQueries: 'createServerQueries', -} as const; - -type CreateTRPCQueryOptions = Omit< +type CreateTRPCQueryOptions = Omit< CreateQueryOptions, 'queryKey' | 'queryFn' >; + type CreateTRPCServerQueryOptions = CreateTRPCQueryOptions & { ssr?: boolean; @@ -305,18 +399,23 @@ type TRPCQueryOpts = { }; }; -type CreateQueryProcedure = { +type CreateQueryProcedure = { [Procedure.query]: ( - input: TInput, - opts?: CreateTRPCQueryOptions & TRPCQueryOpts + input: StoreOrVal, + opts?: StoreOrVal< + CreateTRPCQueryOptions & TRPCQueryOpts + > ) => CreateQueryResult; -} & { + [Procedure.serverQuery]: ( input: TInput, opts?: CreateTRPCServerQueryOptions & TRPCQueryOpts ) => Promise< - ( - ...args: [TInput | ((old: TInput) => TInput)] | [] + ( + input?: StoreOrVal | ((old: TInput) => StoreOrVal), + opts?: StoreOrVal< + CreateTRPCServerQueryOptions & TRPCQueryOpts + > ) => CreateQueryResult >; } & {}; @@ -332,6 +431,7 @@ type CreateTRPCInfiniteQueryOptions = Omit< >, 'queryKey' | 'queryFn' | 'initialPageParam' >; + type CreateTRPCServerInfiniteQueryOptions = CreateTRPCInfiniteQueryOptions & { ssr?: boolean; @@ -345,45 +445,54 @@ type InfiniteQueryOpts = { initialCursor?: ExtractCursorType; }; -type CreateInfiniteQueryProcedure = (TInput extends { - cursor?: any; -} - ? { - [Procedure.infiniteQuery]: ( - input: Omit, - opts: CreateTRPCInfiniteQueryOptions & - InfiniteQueryOpts & - TRPCQueryOpts - ) => CreateInfiniteQueryResult< - InfiniteData> | null>, - TError - >; - [Procedure.serverInfiniteQuery]: ( - input: Omit, - opts: CreateTRPCServerInfiniteQueryOptions< - TInput, - TOutput, - TError, - TData - > & +type CreateInfiniteQueryProcedure = { + [Procedure.infiniteQuery]: ( + input: StoreOrVal>, + opts: StoreOrVal< + CreateTRPCInfiniteQueryOptions & + InfiniteQueryOpts & + TRPCQueryOpts + > + ) => CreateInfiniteQueryResult< + InfiniteData> | null>, + TError + >; + + [Procedure.serverInfiniteQuery]: ( + input: Omit, + opts: CreateTRPCServerInfiniteQueryOptions & + InfiniteQueryOpts & + TRPCQueryOpts + ) => Promise< + ( + input?: + | StoreOrVal> + | ((old: Omit) => StoreOrVal>), + opts?: StoreOrVal< + CreateTRPCServerInfiniteQueryOptions & InfiniteQueryOpts & TRPCQueryOpts - ) => Promise< - ( - ...args: [TInput | ((old: TInput) => TInput)] | [] - ) => CreateInfiniteQueryResult< - InfiniteData> | null>, - TError - > - >; - } - : {}) & {}; + > + // ...args: [TInput | ((old: TInput) => TInput)] | [] + ) => CreateInfiniteQueryResult< + InfiniteData> | null>, + TError + > + >; +}; type QueryProcedures = GetQueryKey & CreateQueryProcedure & - CreateInfiniteQueryProcedure; - -type CreateMutationProcedure = { + (TInput extends { cursor?: any } + ? CreateInfiniteQueryProcedure + : {}); + +type CreateMutationProcedure< + TInput = any, + TOutput = any, + TError = any, + TContext = unknown, +> = { [Procedure.mutate]: ( opts?: CreateMutationOptions ) => CreateMutationResult; @@ -402,7 +511,7 @@ type GetSubscriptionOutput = TOpts extends unknown & Partial : never : never; -type CreateSubscriptionProcedure = { +type CreateSubscriptionProcedure = { [Procedure.subscribe]: ( input: TInput, opts?: CreateSubscriptionOptions @@ -625,22 +734,53 @@ const procedures: Record any> = { }, [Procedure.query]: ({ path, client, abortOnUnmount }) => { return (input: any, opts?: any) => { - const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; - return createQuery({ - ...opts, - queryKey: getArrayQueryKey(path, input, 'query'), - queryFn: ({ signal }) => - client.query(path.join('.'), input, { - ...(shouldAbortOnUnmount && { signal }), - }), - }); + const isOptsStore = isSvelteStore(opts); + const isInputStore = isSvelteStore(input); + const currentOpts = isOptsStore ? get(opts) : opts; + + if (!isInputStore && !isOptsStore) { + const shouldAbortOnUnmount = + opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + return createQuery({ + ...opts, + queryKey: getArrayQueryKey(path, input, 'query'), + queryFn: ({ signal }) => + client.query(path.join('.'), input, { + ...(shouldAbortOnUnmount && { signal }), + }), + }); + } + + const shouldAbortOnUnmount = + currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount; + + return createQuery( + derived( + [isInputStore ? input : blankStore, isOptsStore ? opts : blankStore], + ([$input, $opts]) => { + const newInput = !isBlank($input) ? $input : input; + const newOpts = !isBlank($opts) ? $opts : opts; + return { + ...newOpts, + queryKey: getArrayQueryKey(path, newInput, 'query'), + queryFn: ({ signal }) => + client.query(path.join('.'), newInput, { + ...(shouldAbortOnUnmount && { signal }), + }), + } satisfies CreateQueryOptions; + } + ) + ); }; }, [Procedure.serverQuery]: ({ path, client, queryClient, abortOnUnmount }) => { const pathString = path.join('.'); - return async (input: any, opts?: any) => { - const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + return async (_input: any, _opts?: any) => { + let input = _input; + let opts = _opts; + let shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + const query: FetchQueryOptions = { queryKey: getArrayQueryKey(path, input, 'query'), queryFn: ({ signal }) => @@ -657,53 +797,89 @@ const procedures: Record any> = { await queryClient.prefetchQuery(query); } - const defaultOptions = queryClient.getDefaultOptions(); - return (...args: any[]) => { - let newQuery = query; - - if (args.length > 0) { - const newInput = args[0]; - let i = newInput; - if (typeof newInput === 'function') i = newInput(input); - newQuery = { - queryKey: getArrayQueryKey(path, i, 'query'), - queryFn: ({ signal }) => - client.query(pathString, i, { - ...(shouldAbortOnUnmount && { signal }), - }), - }; - } - - return createQuery({ - ...opts, - ...newQuery, - ...(cacheNotFound - ? { - refetchOnMount: - opts?.refetchOnMount ?? - defaultOptions.queries?.refetchOnMount ?? - false, - } - : {}), - }); + if (args.length > 0) input = args.shift(); + if (args.length > 0) opts = args.shift(); + + const isOptsStore = isSvelteStore(opts); + const currentOpts = isOptsStore ? get(opts) : opts; + shouldAbortOnUnmount = + currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount; + + const staleTime = writable(Infinity); + onMount(() => { staleTime.set(null); }); // prettier-ignore + + return createQuery( + derived( + [ + isSvelteStore(input) ? input : blankStore, + isOptsStore ? opts : blankStore, + staleTime, + ], + ([$input, $opts, $staleTime]) => { + const newInput = !isBlank($input) ? $input : input; + const newOpts = !isBlank($opts) ? $opts : opts; + return { + ...newOpts, + queryKey: getArrayQueryKey(path, newInput, 'query'), + queryFn: ({ signal }) => + client.query(pathString, newInput, { + ...(shouldAbortOnUnmount && { signal }), + }), + ...($staleTime && { staleTime: $staleTime }), + } satisfies CreateQueryOptions; + } + ) + ); }; }; }, [Procedure.infiniteQuery]: ({ path, client, abortOnUnmount }) => { return (input: any, opts?: any) => { - const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; - return createInfiniteQuery({ - ...opts, - initialPageParam: opts.initialCursor ?? null, - queryKey: getArrayQueryKey(path, input, 'infinite'), - queryFn: ({ pageParam, signal }) => - client.query( - path.join('.'), - { ...input, cursor: pageParam ?? opts?.initialCursor }, - { ...(shouldAbortOnUnmount && { signal }) } - ), - }); + const isOptsStore = isSvelteStore(opts); + const isInputStore = isSvelteStore(input); + const currentOpts = isOptsStore ? get(opts) : opts; + + if (!isInputStore && !isOptsStore) { + const shouldAbortOnUnmount = + opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + + return createInfiniteQuery({ + ...opts, + initialPageParam: opts?.initialCursor ?? null, + queryKey: getArrayQueryKey(path, input, 'infinite'), + queryFn: ({ pageParam, signal }) => + client.query( + path.join('.'), + { ...input, cursor: pageParam ?? opts?.initialCursor }, + { ...(shouldAbortOnUnmount && { signal }) } + ), + } satisfies CreateInfiniteQueryOptions); + } + + const shouldAbortOnUnmount = + currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount; + + return createInfiniteQuery( + derived( + [isInputStore ? input : blankStore, isOptsStore ? opts : blankStore], + ([$input, $opts]) => { + const newInput = !isBlank($input) ? $input : input; + const newOpts = !isBlank($opts) ? $opts : opts; + + return { + ...newOpts, + queryKey: getArrayQueryKey(path, newInput, 'infinite'), + queryFn: ({ pageParam, signal }) => + client.query( + path.join('.'), + { ...newInput, cursor: pageParam ?? newOpts?.initialCursor }, + { ...(shouldAbortOnUnmount && { signal }) } + ), + } satisfies CreateInfiniteQueryOptions; + } + ) + ); }; }, [Procedure.serverInfiniteQuery]: ({ @@ -714,8 +890,11 @@ const procedures: Record any> = { }) => { const pathString = path.join('.'); - return async (input: any, opts?: any) => { - const shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + return async (_input: any, _opts?: any) => { + let input = _input; + let opts = _opts; + let shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount; + const query: Omit = { queryKey: getArrayQueryKey(path, input, 'infinite'), queryFn: ({ pageParam, signal }) => @@ -735,38 +914,46 @@ const procedures: Record any> = { } return (...args: any[]) => { - let newQuery = query; - - if (args.length > 0) { - const newInput = args[0]; - let i = newInput; - if (typeof newInput === 'function') i = newInput(input); - newQuery = { - queryKey: getArrayQueryKey(path, i, 'infinite'), - queryFn: ({ pageParam, signal }) => - client.query( - pathString, - { ...i, cursor: pageParam ?? opts?.initialCursor }, - { ...(shouldAbortOnUnmount && { signal }) } - ), - }; - } - - const defaultOptions = queryClient.getDefaultOptions(); - - return createInfiniteQuery({ - ...opts, - initialPageParam: opts.initialCursor ?? null, - ...newQuery, - ...(cacheNotFound - ? { - refetchOnMount: - opts?.refetchOnMount ?? - defaultOptions.queries?.refetchOnMount ?? - false, - } - : {}), - }); + if (args.length > 0) input = args.shift(); + if (args.length > 0) opts = args.shift(); + + const isOptsStore = isSvelteStore(opts); + const currentOpts = isOptsStore ? get(opts) : opts; + shouldAbortOnUnmount = + currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount; + + const staleTime = writable(Infinity); + onMount(() => { staleTime.set(null); }); // prettier-ignore + + return createInfiniteQuery( + derived( + [ + isSvelteStore(input) ? input : blankStore, + isOptsStore ? opts : blankStore, + staleTime, + ], + ([$input, $opts, $staleTime]) => { + const newInput = !isBlank($input) ? $input : input; + const newOpts = !isBlank($opts) ? $opts : opts; + + return { + ...newOpts, + initialPageParam: newOpts?.initialCursor, + queryKey: getArrayQueryKey(path, newInput, 'infinite'), + queryFn: ({ pageParam, signal }) => + client.query( + pathString, + { + ...newInput, + cursor: pageParam ?? newOpts?.initialCursor, + }, + { ...(shouldAbortOnUnmount && { signal }) } + ), + ...($staleTime && { staleTime: $staleTime }), + } satisfies CreateInfiniteQueryOptions; + } + ) + ); }; }; }, @@ -817,11 +1004,15 @@ const procedures: Record any> = { if (path.length !== 0) return; const proxy = createQueriesProxy(ctx); - const defaultOptions = queryClient.getDefaultOptions(); + return async ( + input: (...args: any[]) => QueryObserverOptionsForCreateQueries[], + _opts?: any + ) => { + let opts = _opts; - return async (input: (...args: any[]) => any, opts?: any) => { - const queries = await Promise.all( - input(proxy).map(async (query: any) => { + let queries = input(proxy); + await Promise.all( + queries.map(async (query: any) => { const cache = queryClient .getQueryCache() .find({ queryKey: query.queryKey }); @@ -830,27 +1021,29 @@ const procedures: Record any> = { if (query.ssr !== false && cacheNotFound) { await queryClient.prefetchQuery(query); } - - return { - ...query, - ...(cacheNotFound - ? { - refetchOnMount: - query.refetchOnMount ?? - defaultOptions.queries?.refetchOnMount ?? - false, - } - : {}), - }; }) ); - return (newInput?: (...args: any[]) => any) => { - let newQueries = queries; - if (newInput) newQueries = newInput(proxy, queries); + return (...args: any[]) => { + if (args.length > 0) queries = args.shift()!(proxy, queries); + if (args.length > 0) opts = args.shift(); + + const staleTime = writable(Infinity); + onMount(() => { staleTime.set(null); }); // prettier-ignore + return createQueries({ ...opts, - queries: newQueries, + queries: derived( + [isSvelteStore(queries) ? queries : blankStore, staleTime], + ([$queries, $staleTime]) => { + const newQueries = !isBlank($queries) ? $queries : queries; + if (!staleTime) return newQueries; + return newQueries.map((query) => ({ + ...query, + ...($staleTime && { staleTime: $staleTime }), + })); + } + ), }); }; }; @@ -912,6 +1105,23 @@ type GetQueryKey = [TInput] extends [undefined | void] [Procedure.queryKey]: (input: TInput, type?: QueryType) => QueryKey; } & {}; +type ValFromStore = T extends Readable ? U : T; + +// prettier-ignore +export type InferProcedureOpts< + T extends + | CreateQueryProcedure[typeof Procedure.query] + | CreateInfiniteQueryProcedure[typeof Procedure.infiniteQuery] + | CreateMutationProcedure[typeof Procedure.mutate] + | CreateSubscriptionProcedure[typeof Procedure.subscribe], +> = NonNullable<( + T extends CreateQueryProcedure[typeof Procedure.query] ? ValFromStore[1]> + : T extends CreateInfiniteQueryProcedure[typeof Procedure.infiniteQuery] ? ValFromStore[1]> + : T extends CreateMutationProcedure[typeof Procedure.mutate] ? ValFromStore[0]> + : T extends CreateSubscriptionProcedure[typeof Procedure.subscribe] ? ValFromStore[1]> + : never +)>; + export function svelteQueryWrapper({ client, queryClient: _queryClient, @@ -931,22 +1141,11 @@ export function svelteQueryWrapper({ const innerClient = client.__untypedClient as InnerClient; return new DeepProxy( + // prettier-ignore {} as ClientWithQuery & (ClientWithQuery extends Record - ? { - /** - * @deprecated renamed to `createUtils` and will be removed in a future tRPC version - * - * @see https://trpc.io/docs/client/react/useUtils - */ - [Procedure.context](): CreateUtils; - /** - * @see https://trpc.io/docs/client/react/useUtils - */ - [Procedure.utils](): CreateUtils; - [Procedure.queries]: CreateQueries; - [Procedure.serverQueries]: CreateServerQueries; - } + ? CreateUtilsProcedure + & CreateQueriesProcedure : {}), { get(_, key) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af56458..0837eed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,76 +16,6 @@ importers: specifier: ^5.0.0 version: 5.5.3 - '@app': - dependencies: - '@tanstack/svelte-query': - specifier: ^5.50.3 - version: 5.50.3(svelte@4.2.18) - '@trpc/client': - specifier: ^10.45.2 - version: 10.45.2(@trpc/server@10.45.2) - '@trpc/server': - specifier: ^10.45.2 - version: 10.45.2 - trpc-svelte-query-adapter: - specifier: workspace:^ - version: link:../@lib - trpc-sveltekit: - specifier: ^3.6.2 - version: 3.6.2(@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(ws@8.18.0) - zod: - specifier: ^3.23.8 - version: 3.23.8 - devDependencies: - '@sveltejs/adapter-auto': - specifier: ^3.0.0 - version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8))) - '@sveltejs/kit': - specifier: ^2.0.0 - version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)))(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) - '@sveltejs/vite-plugin-svelte': - specifier: ^3.0.0 - version: 3.1.1(svelte@4.2.18)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)) - '@types/eslint': - specifier: ^8.56.7 - version: 8.56.10 - eslint: - specifier: ^9.0.0 - version: 9.6.0 - eslint-plugin-svelte: - specifier: ^2.36.0 - version: 2.41.0(eslint@9.6.0)(svelte@4.2.18)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) - globals: - specifier: ^15.0.0 - version: 15.8.0 - prettier: - specifier: ^3.1.1 - version: 3.3.2 - prettier-plugin-svelte: - specifier: ^3.1.2 - version: 3.2.5(prettier@3.3.2)(svelte@4.2.18) - svelte: - specifier: ^4.2.7 - version: 4.2.18 - svelte-check: - specifier: ^3.6.0 - version: 3.8.4(postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))(postcss@8.4.39)(sass@1.77.8)(svelte@4.2.18) - tslib: - specifier: ^2.4.1 - version: 2.6.3 - typescript: - specifier: ^5.0.0 - version: 5.5.3 - typescript-eslint: - specifier: ^8.0.0-alpha.20 - version: 8.0.0-alpha.41(eslint@9.6.0)(typescript@5.5.3) - typescript-svelte-plugin: - specifier: ^0.3.39 - version: 0.3.39(svelte@4.2.18)(typescript@5.5.3) - vite: - specifier: ^5.0.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.77.8) - '@example': dependencies: '@picocss/pico': @@ -189,8 +119,8 @@ importers: specifier: ^10.43.3 version: 10.45.2 proxy-deep: - specifier: ^3.1.1 - version: 3.1.1 + specifier: ^4.0.1 + version: 4.0.1 svelte: specifier: ^4.2.3 version: 4.2.18 @@ -220,36 +150,6 @@ importers: specifier: ^8.0.0-alpha.20 version: 8.0.0-alpha.41(eslint@9.6.0)(typescript@5.5.3) - '@shared/eslint': - devDependencies: - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.6.0) - globals: - specifier: ^15.0.0 - version: 15.8.0 - - '@shared/prettier': - devDependencies: - prettier: - specifier: ^3.1.1 - version: 3.3.2 - - '@utils/eslint-shared': - devDependencies: - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@9.6.0) - globals: - specifier: ^15.0.0 - version: 15.8.0 - - '@utils/prettier-shared': - devDependencies: - prettier: - specifier: ^3.1.1 - version: 3.3.2 - packages: '@ampproject/remapping@2.3.0': @@ -1873,8 +1773,8 @@ packages: engines: {node: '>=14'} hasBin: true - proxy-deep@3.1.1: - resolution: {integrity: sha512-kppbvLUNJ4IOMZds9/4gz/rtT5OFiesy3XosLsgMKlF3vb6GA5Y3ptyDlzKLcOcUBW+zaY+RiMINTsgE+O6e+Q==} + proxy-deep@4.0.1: + resolution: {integrity: sha512-7Ojq0fbemOGGpRBbExicriLljomgJ32t9jL8HV+FvpqU/UYzowLKVhJyRYcCMBeAGCz++rK2maNzuZYbqjAVPw==} pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -3667,7 +3567,7 @@ snapshots: prettier@3.3.2: {} - proxy-deep@3.1.1: {} + proxy-deep@4.0.1: {} pump@3.0.0: dependencies: