@trpc/tanstack-react-query
Version:
TanStack React Query Integration for tRPC
204 lines (181 loc) • 4.66 kB
text/typescript
import { skipToken, type QueryClient } from '@tanstack/react-query';
import {
isFunction,
isObject,
run,
} from '@trpc/server/unstable-core-do-not-import';
import type {
AnyTRPCMutationKey,
AnyTRPCQueryKey,
FeatureFlags,
QueryType,
TRPCMutationKeyWithoutPrefix,
TRPCQueryKey,
TRPCQueryKeyWithoutPrefix,
TRPCQueryKeyWithPrefix,
TRPCQueryOptionsResult,
} from './types';
/**
* @internal
*/
export function createTRPCOptionsResult(value: {
path: string[];
}): TRPCQueryOptionsResult['trpc'] {
const path = value.path.join('.');
return {
path,
};
}
export function isPrefixedQueryKey(
queryKey: TRPCQueryKey<any>,
): queryKey is TRPCQueryKeyWithPrefix {
return queryKey.length >= 3;
}
export function readQueryKey(queryKey: AnyTRPCQueryKey) {
if (isPrefixedQueryKey(queryKey)) {
return {
type: 'prefixed' as const,
prefix: queryKey[0],
path: queryKey[1],
args: queryKey[2],
};
} else {
return {
type: 'unprefixed' as const,
prefix: undefined,
path: queryKey[0],
args: queryKey[1],
};
}
}
/**
* @internal
*/
export function getClientArgs<TOptions, TFeatureFlags extends FeatureFlags>(
queryKey: TRPCQueryKey<TFeatureFlags['keyPrefix']>,
opts: TOptions,
infiniteParams?: {
pageParam: any;
direction: 'forward' | 'backward';
},
) {
const queryKeyData = readQueryKey(queryKey);
let input = queryKeyData.args?.input;
if (infiniteParams) {
input = {
...(queryKeyData.args?.input ?? {}),
...(infiniteParams.pageParam !== undefined
? { cursor: infiniteParams.pageParam }
: {}),
direction: infiniteParams.direction,
};
}
return [queryKeyData.path.join('.'), input, (opts as any)?.trpc] as const;
}
/**
* @internal
*/
export async function buildQueryFromAsyncIterable<
TQueryKey extends TRPCQueryKey<any>,
>(
asyncIterable: AsyncIterable<unknown>,
queryClient: QueryClient,
queryKey: TQueryKey,
) {
const queryCache = queryClient.getQueryCache();
const query = queryCache.build(queryClient, {
queryKey,
});
query.setState({
data: [],
status: 'success',
});
const aggregate: unknown[] = [];
for await (const value of asyncIterable) {
aggregate.push(value);
query.setState({
data: [...aggregate],
});
}
return aggregate;
}
/**
* To allow easy interactions with groups of related queries, such as
* invalidating all queries of a router, we use an array as the path when
* storing in tanstack query.
*
* @internal
*/
export function getQueryKeyInternal(opts: {
path: string[];
input?: unknown;
type: QueryType;
prefix: string | undefined;
}): AnyTRPCQueryKey {
const key = run((): TRPCQueryKeyWithoutPrefix => {
const { input, type } = opts;
// Construct a query key that is easy to destructure and flexible for
// partial selecting etc.
// https://github.com/trpc/trpc/issues/3128
// some parts of the path may be dot-separated, split them up
const splitPath = opts.path.flatMap((part) => part.split('.'));
if (!input && type === 'any') {
// this matches also all mutations (see `getMutationKeyInternal`)
// for `utils.invalidate()` to match all queries (including vanilla react-query)
// we don't want nested array if path is empty, i.e. `[]` instead of `[[]]`
return splitPath.length ? [splitPath] : ([] as unknown as TRPCQueryKey);
}
if (
type === 'infinite' &&
isObject(input) &&
('direction' in input || 'cursor' in input)
) {
const {
cursor: _,
direction: __,
...inputWithoutCursorAndDirection
} = input;
return [
splitPath,
{
input: inputWithoutCursorAndDirection,
type: 'infinite',
},
];
}
return [
splitPath,
{
...(typeof input !== 'undefined' &&
input !== skipToken && { input: input }),
...(type && type !== 'any' && { type: type }),
},
];
});
if (opts.prefix) {
key.unshift([opts.prefix]);
}
return key;
}
/**
* @internal
*/
export function getMutationKeyInternal(opts: {
prefix: string | undefined;
path: string[];
}): AnyTRPCMutationKey {
// some parts of the path may be dot-separated, split them up
const key: TRPCMutationKeyWithoutPrefix = [
opts.path.flatMap((part) => part.split('.')),
];
if (opts.prefix) {
key.unshift([opts.prefix]);
}
return key;
}
/**
* @internal
*/
export function unwrapLazyArg<T>(valueOrLazy: T | (() => T)): T {
return (isFunction(valueOrLazy) ? valueOrLazy() : valueOrLazy) as T;
}