rc inital
This commit is contained in:
@@ -0,0 +1,653 @@
|
||||
import {
|
||||
DehydratedState,
|
||||
QueryClient,
|
||||
CreateInfiniteQueryOptions,
|
||||
CreateInfiniteQueryResult,
|
||||
CreateMutationOptions,
|
||||
CreateMutationResult,
|
||||
CreateQueryOptions,
|
||||
CreateQueryResult,
|
||||
createInfiniteQuery as __useInfiniteQuery,
|
||||
createMutation as __useMutation,
|
||||
createQuery as __useQuery,
|
||||
hashQueryKey,
|
||||
useQueryClient,
|
||||
QueryClientProviderProps,
|
||||
QueryClientProvider,
|
||||
} from "@tanstack/solid-query";
|
||||
import {
|
||||
CreateTRPCClientOptions,
|
||||
TRPCClient,
|
||||
TRPCClientErrorLike,
|
||||
TRPCRequestOptions,
|
||||
createTRPCClient,
|
||||
} from "@trpc/client";
|
||||
import type {
|
||||
AnyRouter,
|
||||
ProcedureRecord,
|
||||
inferHandlerInput,
|
||||
inferProcedureClientError,
|
||||
inferProcedureInput,
|
||||
inferProcedureOutput,
|
||||
inferSubscriptionOutput,
|
||||
} from "@trpc/server";
|
||||
import { inferObservableValue } from "@trpc/server/observable";
|
||||
import {
|
||||
Accessor,
|
||||
Context,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
JSX,
|
||||
onCleanup,
|
||||
onMount,
|
||||
useContext as __useContext,
|
||||
} from "solid-js";
|
||||
import {
|
||||
SSRState,
|
||||
TRPCContext,
|
||||
TRPCContextProps,
|
||||
TRPCContextState,
|
||||
} from "../../internals/context";
|
||||
import { getArrayQueryKey } from "../../internals/getArrayQueryKey";
|
||||
import { CreateTRPCSolidOptions, UseMutationOverride } from "../types";
|
||||
|
||||
export type OutputWithCursor<TData, TCursor = any> = {
|
||||
cursor: TCursor | null;
|
||||
data: TData;
|
||||
};
|
||||
|
||||
export interface TRPCReactRequestOptions
|
||||
// For RQ, we use their internal AbortSignals instead of letting the user pass their own
|
||||
extends Omit<TRPCRequestOptions, "signal"> {
|
||||
/**
|
||||
* Opt out of SSR for this query by passing `ssr: false`
|
||||
*/
|
||||
ssr?: boolean;
|
||||
/**
|
||||
* Opt out or into aborting request on unmount
|
||||
*/
|
||||
abortOnUnmount?: boolean;
|
||||
}
|
||||
|
||||
export interface TRPCUseQueryBaseOptions {
|
||||
/**
|
||||
* tRPC-related options
|
||||
*/
|
||||
trpc?: TRPCReactRequestOptions;
|
||||
}
|
||||
|
||||
export type { TRPCContext, TRPCContextState } from "../../internals/context";
|
||||
|
||||
export interface UseTRPCQueryOptions<TPath, TInput, TOutput, TData, TError>
|
||||
extends CreateQueryOptions<TOutput, TError, TData, () => [TPath, TInput]>,
|
||||
TRPCUseQueryBaseOptions {}
|
||||
|
||||
export interface UseTRPCInfiniteQueryOptions<TPath, TInput, TOutput, TError>
|
||||
extends CreateInfiniteQueryOptions<
|
||||
TOutput,
|
||||
TError,
|
||||
TOutput,
|
||||
TOutput,
|
||||
() => [TPath, TInput]
|
||||
>,
|
||||
TRPCUseQueryBaseOptions {}
|
||||
|
||||
export interface UseTRPCMutationOptions<
|
||||
TInput,
|
||||
TError,
|
||||
TOutput,
|
||||
TContext = unknown
|
||||
> extends CreateMutationOptions<TOutput, TError, TInput, TContext>,
|
||||
TRPCUseQueryBaseOptions {}
|
||||
|
||||
export interface UseTRPCSubscriptionOptions<TOutput, TError> {
|
||||
enabled?: boolean;
|
||||
onStarted?: () => void;
|
||||
onData: (data: TOutput) => void;
|
||||
onError?: (err: TError) => void;
|
||||
}
|
||||
|
||||
function getClientArgs<TPathAndInput extends unknown[], TOptions>(
|
||||
pathAndInput: TPathAndInput,
|
||||
opts: TOptions
|
||||
) {
|
||||
const [path, input] = pathAndInput;
|
||||
return [path, input, (opts as any)?.trpc] as const;
|
||||
}
|
||||
|
||||
type inferInfiniteQueryNames<TObj extends ProcedureRecord> = {
|
||||
[TPath in keyof TObj]: inferProcedureInput<TObj[TPath]> extends {
|
||||
cursor?: any;
|
||||
}
|
||||
? TPath
|
||||
: never;
|
||||
}[keyof TObj];
|
||||
|
||||
type inferProcedures<TObj extends ProcedureRecord> = {
|
||||
[TPath in keyof TObj]: {
|
||||
input: inferProcedureInput<TObj[TPath]>;
|
||||
output: inferProcedureOutput<TObj[TPath]>;
|
||||
};
|
||||
};
|
||||
|
||||
export interface TRPCProviderProps<TRouter extends AnyRouter, TSSRContext>
|
||||
extends TRPCContextProps<TRouter, TSSRContext> {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export type TRPCProvider<TRouter extends AnyRouter, TSSRContext> = (
|
||||
props: TRPCProviderProps<TRouter, TSSRContext> & {
|
||||
queryClientOpts?: Omit<QueryClientProviderProps, "client">;
|
||||
}
|
||||
) => JSX.Element;
|
||||
|
||||
export type UseDehydratedState<TRouter extends AnyRouter> = (
|
||||
client: TRPCClient<TRouter>,
|
||||
trpcState: DehydratedState | undefined
|
||||
) => Accessor<DehydratedState | undefined>;
|
||||
|
||||
export type CreateClient<TRouter extends AnyRouter> = (
|
||||
opts: CreateTRPCClientOptions<TRouter>
|
||||
) => TRPCClient<TRouter>;
|
||||
|
||||
interface TRPCHookResult {
|
||||
trpc: {
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type UseTRPCQueryResult<TData, TError> = CreateQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> &
|
||||
TRPCHookResult;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type UseTRPCInfiniteQueryResult<TData, TError> =
|
||||
CreateInfiniteQueryResult<TData, TError> & TRPCHookResult;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type UseTRPCMutationResult<TData, TError, TVariables, TContext> =
|
||||
CreateMutationResult<TData, TError, TVariables, TContext> & TRPCHookResult;
|
||||
|
||||
/**
|
||||
* Makes a stable reference of the `trpc` prop
|
||||
*/
|
||||
function useHookResult(value: TRPCHookResult["trpc"]): TRPCHookResult["trpc"] {
|
||||
const ref = { current: value };
|
||||
ref.current.path = value.path;
|
||||
return ref.current;
|
||||
}
|
||||
/**
|
||||
* Create strongly typed react hooks
|
||||
* @internal
|
||||
*/
|
||||
export function createHooksInternal<
|
||||
TRouter extends AnyRouter,
|
||||
TSSRContext = unknown
|
||||
>(config?: CreateTRPCSolidOptions<TRouter>) {
|
||||
const mutationSuccessOverride: UseMutationOverride["onSuccess"] =
|
||||
config?.unstable_overrides?.useMutation?.onSuccess ??
|
||||
((options) => options.originalFn());
|
||||
|
||||
type TQueries = TRouter["_def"]["queries"];
|
||||
type TSubscriptions = TRouter["_def"]["subscriptions"];
|
||||
type TMutations = TRouter["_def"]["mutations"];
|
||||
|
||||
type TError = TRPCClientErrorLike<TRouter>;
|
||||
type TInfiniteQueryNames = inferInfiniteQueryNames<TQueries>;
|
||||
|
||||
type TQueryValues = inferProcedures<TQueries>;
|
||||
type TMutationValues = inferProcedures<TMutations>;
|
||||
|
||||
type ProviderContext = Omit<
|
||||
TRPCContextState<TRouter, TSSRContext>,
|
||||
"ssrState"
|
||||
> & {
|
||||
ssrState: Accessor<TRPCContextState<TRouter, TSSRContext>["ssrState"]>;
|
||||
};
|
||||
|
||||
const Context = (config?.context ?? TRPCContext) as Context<ProviderContext>;
|
||||
const SolidQueryContext = config?.solidQueryContext as Context<
|
||||
QueryClient | undefined
|
||||
>;
|
||||
|
||||
const createClient: CreateClient<TRouter> = (opts) => {
|
||||
return createTRPCClient(opts);
|
||||
};
|
||||
|
||||
const TRPCProvider: TRPCProvider<TRouter, TSSRContext> = (props) => {
|
||||
const { abortOnUnmount = false, client, queryClient, ssrContext } = props;
|
||||
const [ssrState, setSSRState] = createSignal<SSRState>(
|
||||
props.ssrState ?? false
|
||||
);
|
||||
onMount(() => {
|
||||
// Only updating state to `mounted` if we are using SSR.
|
||||
// This makes it so we don't have an unnecessary re-render when opting out of SSR.
|
||||
setSSRState((state) => (state ? "mounted" : false));
|
||||
});
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
abortOnUnmount,
|
||||
queryClient,
|
||||
client,
|
||||
ssrContext: ssrContext || null,
|
||||
ssrState,
|
||||
fetchQuery: (pathAndInput, opts) => {
|
||||
return queryClient.fetchQuery(
|
||||
getArrayQueryKey(pathAndInput),
|
||||
() => (client as any).query(...getClientArgs(pathAndInput, opts)),
|
||||
opts
|
||||
);
|
||||
},
|
||||
fetchInfiniteQuery: (pathAndInput, opts) => {
|
||||
return queryClient.fetchInfiniteQuery(
|
||||
getArrayQueryKey(pathAndInput),
|
||||
({ pageParam }) => {
|
||||
const [path, input] = pathAndInput;
|
||||
const actualInput = { ...(input as any), cursor: pageParam };
|
||||
return (client as any).query(
|
||||
...getClientArgs([path, actualInput], opts)
|
||||
);
|
||||
},
|
||||
opts
|
||||
);
|
||||
},
|
||||
|
||||
prefetchQuery: (pathAndInput, opts) => {
|
||||
return queryClient.prefetchQuery(
|
||||
getArrayQueryKey(pathAndInput),
|
||||
() => (client as any).query(...getClientArgs(pathAndInput, opts)),
|
||||
opts
|
||||
);
|
||||
},
|
||||
prefetchInfiniteQuery: (pathAndInput, opts) => {
|
||||
return queryClient.prefetchInfiniteQuery(
|
||||
getArrayQueryKey(pathAndInput),
|
||||
({ pageParam }) => {
|
||||
const [path, input] = pathAndInput;
|
||||
const actualInput = { ...(input as any), cursor: pageParam };
|
||||
return (client as any).query(
|
||||
...getClientArgs([path, actualInput], opts)
|
||||
);
|
||||
},
|
||||
opts
|
||||
);
|
||||
},
|
||||
invalidateQueries: (...args: any[]) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
return queryClient.invalidateQueries(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
refetchQueries: (...args: any[]) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
|
||||
return queryClient.refetchQueries(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
cancelQuery: (pathAndInput) => {
|
||||
return queryClient.cancelQueries(getArrayQueryKey(pathAndInput));
|
||||
},
|
||||
setQueryData: (...args) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
return queryClient.setQueryData(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
getQueryData: (...args) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
|
||||
return queryClient.getQueryData(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
setInfiniteQueryData: (...args) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
|
||||
return queryClient.setQueryData(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
getInfiniteQueryData: (...args) => {
|
||||
const [queryKey, ...rest] = args;
|
||||
|
||||
return queryClient.getQueryData(
|
||||
getArrayQueryKey(queryKey),
|
||||
...rest
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<QueryClientProvider
|
||||
client={queryClient}
|
||||
{...((props.queryClientOpts ?? {}) as any)}
|
||||
>
|
||||
{props.children}
|
||||
</QueryClientProvider>
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
function useContext() {
|
||||
return __useContext(Context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to make sure errors return `status`='error` when doing SSR
|
||||
* @link https://github.com/trpc/trpc/pull/1645
|
||||
*/
|
||||
function useSSRQueryOptionsIfNeeded<
|
||||
TOptions extends { retryOnMount?: boolean } | undefined
|
||||
>(pathAndInput: unknown[], opts: TOptions): TOptions {
|
||||
const { queryClient, ssrState } = useContext();
|
||||
return ssrState() &&
|
||||
ssrState() !== "mounted" &&
|
||||
queryClient.getQueryCache().find(getArrayQueryKey(pathAndInput))?.state
|
||||
.status === "error"
|
||||
? {
|
||||
retryOnMount: false,
|
||||
...opts,
|
||||
}
|
||||
: opts;
|
||||
}
|
||||
|
||||
function useQuery<
|
||||
TPath extends keyof TQueryValues & string,
|
||||
TQueryFnData = TQueryValues[TPath]["output"],
|
||||
TData = TQueryValues[TPath]["output"]
|
||||
>(
|
||||
pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>],
|
||||
opts?: UseTRPCQueryOptions<
|
||||
TPath,
|
||||
TQueryValues[TPath]["input"],
|
||||
TQueryFnData,
|
||||
TData,
|
||||
TError
|
||||
>
|
||||
): UseTRPCQueryResult<TData, TError> {
|
||||
const ctx = useContext();
|
||||
|
||||
if (
|
||||
typeof window === "undefined" &&
|
||||
ctx.ssrState() === "prepass" &&
|
||||
opts?.trpc?.ssr !== false &&
|
||||
opts?.enabled !== false &&
|
||||
!ctx.queryClient.getQueryCache().find(getArrayQueryKey(pathAndInput))
|
||||
) {
|
||||
void ctx.prefetchQuery(pathAndInput as any, opts as any);
|
||||
}
|
||||
const ssrOpts = useSSRQueryOptionsIfNeeded(pathAndInput, opts);
|
||||
// request option should take priority over global
|
||||
const shouldAbortOnUnmount =
|
||||
opts?.trpc?.abortOnUnmount ?? ctx?.abortOnUnmount ?? false;
|
||||
|
||||
const hook = __useQuery(
|
||||
() => getArrayQueryKey(pathAndInput),
|
||||
(queryFunctionContext) => {
|
||||
const actualOpts = {
|
||||
...ssrOpts,
|
||||
trpc: {
|
||||
...ssrOpts?.trpc,
|
||||
...(shouldAbortOnUnmount
|
||||
? { signal: queryFunctionContext.signal }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
|
||||
return (ctx.client as any).query(
|
||||
...getClientArgs(pathAndInput, actualOpts)
|
||||
);
|
||||
},
|
||||
{ context: SolidQueryContext, ...ssrOpts } as any
|
||||
) as UseTRPCQueryResult<TData, TError>;
|
||||
hook.trpc = useHookResult({
|
||||
path: pathAndInput[0],
|
||||
});
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
function useMutation<
|
||||
TPath extends keyof TMutationValues & string,
|
||||
TContext = unknown
|
||||
>(
|
||||
path: TPath | [TPath],
|
||||
opts?: UseTRPCMutationOptions<
|
||||
TMutationValues[TPath]["input"],
|
||||
TError,
|
||||
TMutationValues[TPath]["output"],
|
||||
TContext
|
||||
>
|
||||
): UseTRPCMutationResult<
|
||||
TMutationValues[TPath]["output"],
|
||||
TError,
|
||||
TMutationValues[TPath]["input"],
|
||||
TContext
|
||||
> {
|
||||
const ctx = useContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const hook = __useMutation(
|
||||
(input) => {
|
||||
const actualPath = Array.isArray(path) ? path[0] : path;
|
||||
|
||||
return (ctx.client.mutation as any)(
|
||||
...getClientArgs([actualPath, input], opts)
|
||||
);
|
||||
},
|
||||
{
|
||||
context: SolidQueryContext,
|
||||
...opts,
|
||||
onSuccess(...args) {
|
||||
const originalFn = () => opts?.onSuccess?.(...args);
|
||||
return mutationSuccessOverride({ originalFn, queryClient });
|
||||
},
|
||||
}
|
||||
) as UseTRPCMutationResult<
|
||||
TMutationValues[TPath]["output"],
|
||||
TError,
|
||||
TMutationValues[TPath]["input"],
|
||||
TContext
|
||||
>;
|
||||
|
||||
hook.trpc = useHookResult({
|
||||
path: Array.isArray(path) ? path[0] : path,
|
||||
});
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
/**
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
|
||||
* **Experimental.** API might change without major version bump
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠
|
||||
*/
|
||||
function useSubscription<
|
||||
TPath extends keyof TSubscriptions & string,
|
||||
TOutput extends inferSubscriptionOutput<TRouter, TPath>
|
||||
>(
|
||||
pathAndInput: [
|
||||
path: TPath,
|
||||
...args: inferHandlerInput<TSubscriptions[TPath]>
|
||||
],
|
||||
opts: UseTRPCSubscriptionOptions<
|
||||
inferObservableValue<inferProcedureOutput<TSubscriptions[TPath]>>,
|
||||
inferProcedureClientError<TSubscriptions[TPath]>
|
||||
>
|
||||
) {
|
||||
const enabled = opts?.enabled ?? true;
|
||||
const ctx = useContext();
|
||||
|
||||
return createEffect(() => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
// noop
|
||||
(() => {
|
||||
return hashQueryKey(pathAndInput);
|
||||
})();
|
||||
const [path, input] = pathAndInput;
|
||||
let isStopped = false;
|
||||
const subscription = ctx.client.subscription<
|
||||
TRouter["_def"]["subscriptions"],
|
||||
TPath,
|
||||
TOutput,
|
||||
inferProcedureInput<TRouter["_def"]["subscriptions"][TPath]>
|
||||
>(path, (input ?? undefined) as any, {
|
||||
onStarted: () => {
|
||||
if (!isStopped) {
|
||||
opts.onStarted?.();
|
||||
}
|
||||
},
|
||||
onData: (data) => {
|
||||
if (!isStopped) {
|
||||
opts.onData(data);
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
if (!isStopped) {
|
||||
opts.onError?.(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
onCleanup(() => {
|
||||
isStopped = true;
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function useInfiniteQuery<TPath extends TInfiniteQueryNames & string>(
|
||||
pathAndInput: [
|
||||
path: TPath,
|
||||
input: Omit<TQueryValues[TPath]["input"], "cursor">
|
||||
],
|
||||
opts?: UseTRPCInfiniteQueryOptions<
|
||||
TPath,
|
||||
Omit<TQueryValues[TPath]["input"], "cursor">,
|
||||
TQueryValues[TPath]["output"],
|
||||
TError
|
||||
>
|
||||
): UseTRPCInfiniteQueryResult<TQueryValues[TPath]["output"], TError> {
|
||||
const [path, input] = pathAndInput;
|
||||
const ctx = useContext();
|
||||
|
||||
if (
|
||||
typeof window === "undefined" &&
|
||||
ctx.ssrState() === "prepass" &&
|
||||
opts?.trpc?.ssr !== false &&
|
||||
opts?.enabled !== false &&
|
||||
!ctx.queryClient.getQueryCache().find(getArrayQueryKey(pathAndInput))
|
||||
) {
|
||||
void ctx.prefetchInfiniteQuery(pathAndInput as any, opts as any);
|
||||
}
|
||||
|
||||
const ssrOpts = useSSRQueryOptionsIfNeeded(pathAndInput, opts);
|
||||
|
||||
// request option should take priority over global
|
||||
const shouldAbortOnUnmount =
|
||||
opts?.trpc?.abortOnUnmount ?? ctx?.abortOnUnmount ?? false;
|
||||
|
||||
const hook = __useInfiniteQuery(
|
||||
() => getArrayQueryKey(pathAndInput),
|
||||
(queryFunctionContext) => {
|
||||
const actualOpts = {
|
||||
...ssrOpts,
|
||||
trpc: {
|
||||
...ssrOpts?.trpc,
|
||||
...(shouldAbortOnUnmount
|
||||
? { signal: queryFunctionContext.signal }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
|
||||
const actualInput = {
|
||||
...((input as any) ?? {}),
|
||||
cursor: queryFunctionContext.pageParam,
|
||||
};
|
||||
|
||||
return (ctx.client as any).query(
|
||||
...getClientArgs([path, actualInput], actualOpts)
|
||||
);
|
||||
},
|
||||
{ context: SolidQueryContext, ...ssrOpts } as any
|
||||
) as UseTRPCInfiniteQueryResult<TQueryValues[TPath]["output"], TError>;
|
||||
|
||||
hook.trpc = useHookResult({
|
||||
path,
|
||||
});
|
||||
return hook;
|
||||
}
|
||||
const useDehydratedState: UseDehydratedState<TRouter> = (
|
||||
client,
|
||||
trpcState
|
||||
) => {
|
||||
const transformed: Accessor<DehydratedState | undefined> = createMemo(
|
||||
() => {
|
||||
if (!trpcState) {
|
||||
return trpcState;
|
||||
}
|
||||
|
||||
return client.runtime.transformer.deserialize(trpcState);
|
||||
}
|
||||
);
|
||||
return transformed;
|
||||
};
|
||||
|
||||
return {
|
||||
Provider: TRPCProvider,
|
||||
createClient,
|
||||
useContext,
|
||||
useQuery,
|
||||
useMutation,
|
||||
useSubscription,
|
||||
useDehydratedState,
|
||||
useInfiniteQuery,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to infer the type of `createReactQueryHooks`
|
||||
* @link https://stackoverflow.com/a/59072991
|
||||
*/
|
||||
class GnClass<TRouter extends AnyRouter, TSSRContext = unknown> {
|
||||
fn() {
|
||||
return createHooksInternal<TRouter, TSSRContext>();
|
||||
}
|
||||
}
|
||||
|
||||
type returnTypeInferer<TType> = TType extends (
|
||||
a: Record<string, string>
|
||||
) => infer U
|
||||
? U
|
||||
: never;
|
||||
type fooType<TRouter extends AnyRouter, TSSRContext = unknown> = GnClass<
|
||||
TRouter,
|
||||
TSSRContext
|
||||
>["fn"];
|
||||
|
||||
/**
|
||||
* Infer the type of a `createReactQueryHooks` function
|
||||
* @internal
|
||||
*/
|
||||
export type CreateReactQueryHooks<
|
||||
TRouter extends AnyRouter,
|
||||
TSSRContext = unknown
|
||||
> = returnTypeInferer<fooType<TRouter, TSSRContext>>;
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from "./proxy/decorationProxy";
|
||||
export * from "./proxy/utilsProxy";
|
||||
export type {
|
||||
DecoratedProcedureRecord,
|
||||
DecorateProcedure,
|
||||
} from "../createTRPCSolid";
|
||||
export * from "./hooks/createHooksInternal";
|
||||
export * from "./queryClient";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,33 @@
|
||||
import { AnyRouter } from "@trpc/server";
|
||||
import { createRecursiveProxy } from "@trpc/server/shared";
|
||||
import { getQueryKey } from "../../internals/getQueryKey";
|
||||
import { CreateReactQueryHooks } from "../hooks/createHooksInternal";
|
||||
|
||||
/**
|
||||
* Create proxy for decorating procedures
|
||||
* @internal
|
||||
*/
|
||||
export function createReactProxyDecoration<
|
||||
TRouter extends AnyRouter,
|
||||
TSSRContext = unknown
|
||||
>(name: string, hooks: CreateReactQueryHooks<TRouter, TSSRContext>) {
|
||||
return createRecursiveProxy((opts) => {
|
||||
const args = opts.args;
|
||||
|
||||
const pathCopy = [name, ...opts.path];
|
||||
|
||||
// The last arg is for instance `.useMutation` or `.useQuery()`
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const lastArg = pathCopy.pop()!;
|
||||
|
||||
// The `path` ends up being something like `post.byId`
|
||||
const path = pathCopy.join(".");
|
||||
if (lastArg === "useMutation") {
|
||||
return (hooks as any)[lastArg](path, ...args);
|
||||
}
|
||||
const [input, ...rest] = args;
|
||||
|
||||
const queryKey = getQueryKey(path, input);
|
||||
return (hooks as any)[lastArg](queryKey, ...rest);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import {
|
||||
CancelOptions,
|
||||
InfiniteData,
|
||||
InvalidateOptions,
|
||||
InvalidateQueryFilters,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
SetDataOptions,
|
||||
Updater,
|
||||
} from "@tanstack/solid-query";
|
||||
import { TRPCClientError } from "@trpc/client";
|
||||
import {
|
||||
AnyQueryProcedure,
|
||||
AnyRouter,
|
||||
Filter,
|
||||
ProcedureOptions,
|
||||
inferProcedureInput,
|
||||
inferProcedureOutput,
|
||||
} from "@trpc/server";
|
||||
import { createFlatProxy, createRecursiveProxy } from "@trpc/server/shared";
|
||||
import {
|
||||
ProxyTRPCContextProps,
|
||||
TRPCContextState,
|
||||
TRPCFetchInfiniteQueryOptions,
|
||||
TRPCFetchQueryOptions,
|
||||
contextProps,
|
||||
} from "../../internals/context";
|
||||
import { getQueryKey } from "../../internals/getQueryKey";
|
||||
|
||||
type DecorateProcedure<
|
||||
TRouter extends AnyRouter,
|
||||
TProcedure extends AnyQueryProcedure
|
||||
> = {
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/prefetching
|
||||
*/
|
||||
fetch(
|
||||
input: inferProcedureInput<TProcedure>,
|
||||
opts?: TRPCFetchQueryOptions<
|
||||
inferProcedureInput<TProcedure>,
|
||||
TRPCClientError<TRouter>,
|
||||
inferProcedureOutput<TProcedure>
|
||||
>
|
||||
): Promise<inferProcedureOutput<TProcedure>>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/prefetching
|
||||
*/
|
||||
fetchInfinite(
|
||||
input: inferProcedureInput<TProcedure>,
|
||||
opts?: TRPCFetchInfiniteQueryOptions<
|
||||
inferProcedureInput<TProcedure>,
|
||||
TRPCClientError<TRouter>,
|
||||
inferProcedureOutput<TProcedure>
|
||||
>
|
||||
): Promise<InfiniteData<inferProcedureOutput<TProcedure>>>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/prefetching
|
||||
*/
|
||||
prefetch(
|
||||
input: inferProcedureInput<TProcedure>,
|
||||
opts?: TRPCFetchQueryOptions<
|
||||
inferProcedureInput<TProcedure>,
|
||||
TRPCClientError<TRouter>,
|
||||
inferProcedureOutput<TProcedure>
|
||||
>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/prefetching
|
||||
*/
|
||||
prefetchInfinite(
|
||||
input: inferProcedureInput<TProcedure>,
|
||||
procedureOpts?: ProcedureOptions,
|
||||
opts?: TRPCFetchInfiniteQueryOptions<
|
||||
inferProcedureInput<TProcedure>,
|
||||
TRPCClientError<TRouter>,
|
||||
inferProcedureOutput<TProcedure>
|
||||
>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/query-invalidation
|
||||
*/
|
||||
invalidate(
|
||||
input?: inferProcedureInput<TProcedure>,
|
||||
filters?: InvalidateQueryFilters,
|
||||
options?: InvalidateOptions
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientrefetchqueries
|
||||
*/
|
||||
refetch(
|
||||
input?: inferProcedureInput<TProcedure>,
|
||||
filters?: RefetchQueryFilters,
|
||||
options?: RefetchOptions
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/query-cancellation
|
||||
*/
|
||||
cancel(
|
||||
input?: inferProcedureInput<TProcedure>,
|
||||
options?: CancelOptions
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientsetquerydata
|
||||
*/
|
||||
setData(
|
||||
updater: Updater<
|
||||
inferProcedureOutput<TProcedure> | undefined,
|
||||
inferProcedureOutput<TProcedure> | undefined
|
||||
>,
|
||||
input?: inferProcedureInput<TProcedure>,
|
||||
options?: SetDataOptions
|
||||
): void;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
|
||||
*/
|
||||
setInfiniteData(
|
||||
updater: Updater<
|
||||
InfiniteData<inferProcedureOutput<TProcedure>> | undefined,
|
||||
InfiniteData<inferProcedureOutput<TProcedure>> | undefined
|
||||
>,
|
||||
input?: inferProcedureInput<TProcedure>,
|
||||
options?: SetDataOptions
|
||||
): void;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
|
||||
*/
|
||||
getData(
|
||||
input?: inferProcedureInput<TProcedure>
|
||||
): inferProcedureOutput<TProcedure> | undefined;
|
||||
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
|
||||
*/
|
||||
getInfiniteData(
|
||||
input?: inferProcedureInput<TProcedure>
|
||||
): InfiniteData<inferProcedureOutput<TProcedure>> | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* A type that will traverse all procedures and sub routers of a given router to create a union of
|
||||
* their possible input types
|
||||
*/
|
||||
type InferAllRouterQueryInputTypes<TRouter extends AnyRouter> = {
|
||||
[TKey in keyof Filter<
|
||||
TRouter["_def"]["record"],
|
||||
AnyRouter | AnyQueryProcedure
|
||||
>]: TRouter["_def"]["record"][TKey] extends AnyQueryProcedure
|
||||
? inferProcedureInput<TRouter["_def"]["record"][TKey]>
|
||||
: InferAllRouterQueryInputTypes<TRouter["_def"]["record"][TKey]>; // Recurse as we have a sub router!
|
||||
}[keyof Filter<TRouter["_def"]["record"], AnyRouter | AnyQueryProcedure>]; // This flattens results into a big union
|
||||
|
||||
/**
|
||||
* this is the type that is used to add in procedures that can be used on
|
||||
* an entire router
|
||||
*/
|
||||
type DecorateRouterProcedure<TRouter extends AnyRouter> = {
|
||||
/**
|
||||
* @link https://react-query.tanstack.com/guides/query-invalidation
|
||||
*/
|
||||
invalidate(
|
||||
input?: Partial<InferAllRouterQueryInputTypes<TRouter>>,
|
||||
filters?: InvalidateQueryFilters,
|
||||
options?: InvalidateOptions
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type DecoratedProcedureUtilsRecord<TRouter extends AnyRouter> = {
|
||||
[TKey in keyof Filter<
|
||||
TRouter["_def"]["record"],
|
||||
AnyRouter | AnyQueryProcedure
|
||||
>]: TRouter["_def"]["record"][TKey] extends AnyRouter
|
||||
? DecoratedProcedureUtilsRecord<TRouter["_def"]["record"][TKey]> &
|
||||
DecorateRouterProcedure<TRouter["_def"]["record"][TKey]>
|
||||
: // utils only apply to queries
|
||||
DecorateProcedure<TRouter, TRouter["_def"]["record"][TKey]>;
|
||||
} & DecorateRouterProcedure<TRouter>; // Add functions that should be available at utils root
|
||||
|
||||
type AnyDecoratedProcedure = DecorateProcedure<any, any>;
|
||||
|
||||
export type CreateReactUtilsProxy<
|
||||
TRouter extends AnyRouter,
|
||||
TSSRContext
|
||||
> = DecoratedProcedureUtilsRecord<TRouter> &
|
||||
ProxyTRPCContextProps<TRouter, TSSRContext>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function createReactQueryUtilsProxy<
|
||||
TRouter extends AnyRouter,
|
||||
TSSRContext
|
||||
>(context: TRPCContextState<AnyRouter, unknown>) {
|
||||
type CreateReactUtilsProxyReturnType = CreateReactUtilsProxy<
|
||||
TRouter,
|
||||
TSSRContext
|
||||
>;
|
||||
|
||||
return createFlatProxy<CreateReactUtilsProxyReturnType>((key) => {
|
||||
const contextName = key as typeof contextProps[number];
|
||||
if (contextProps.includes(contextName)) {
|
||||
return context[contextName];
|
||||
}
|
||||
|
||||
return createRecursiveProxy(({ path, args }) => {
|
||||
const pathCopy = [key, ...path];
|
||||
const utilName = pathCopy.pop() as keyof AnyDecoratedProcedure;
|
||||
|
||||
const fullPath = pathCopy.join(".");
|
||||
|
||||
const getOpts = (name: typeof utilName) => {
|
||||
if (["setData", "setInfiniteData"].includes(name)) {
|
||||
const [updater, input, ...rest] = args as Parameters<
|
||||
AnyDecoratedProcedure[typeof utilName]
|
||||
>;
|
||||
const queryKey = getQueryKey(fullPath, input);
|
||||
return {
|
||||
queryKey,
|
||||
updater,
|
||||
rest,
|
||||
};
|
||||
}
|
||||
|
||||
const [input, ...rest] = args as Parameters<
|
||||
AnyDecoratedProcedure[typeof utilName]
|
||||
>;
|
||||
const queryKey = getQueryKey(fullPath, input);
|
||||
return {
|
||||
queryKey,
|
||||
rest,
|
||||
};
|
||||
};
|
||||
|
||||
const { queryKey, rest, updater } = getOpts(utilName);
|
||||
|
||||
const contextMap: Record<keyof AnyDecoratedProcedure, () => unknown> = {
|
||||
fetch: () => context.fetchQuery(queryKey, ...rest),
|
||||
fetchInfinite: () => context.fetchInfiniteQuery(queryKey, ...rest),
|
||||
prefetch: () => context.prefetchQuery(queryKey, ...rest),
|
||||
prefetchInfinite: () =>
|
||||
context.prefetchInfiniteQuery(queryKey, ...rest),
|
||||
invalidate: () => context.invalidateQueries(queryKey, ...rest),
|
||||
refetch: () => context.refetchQueries(queryKey, ...rest),
|
||||
cancel: () => context.cancelQuery(queryKey, ...rest),
|
||||
setData: () => context.setQueryData(queryKey, updater, ...rest),
|
||||
setInfiniteData: () =>
|
||||
context.setInfiniteQueryData(queryKey, updater, ...rest),
|
||||
getData: () => context.getQueryData(queryKey),
|
||||
getInfiniteData: () => context.getInfiniteQueryData(queryKey),
|
||||
};
|
||||
|
||||
return contextMap[utilName]();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { QueryClient, QueryClientConfig } from "@tanstack/solid-query";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type CreateTRPCReactQueryClientConfig =
|
||||
| {
|
||||
queryClient?: QueryClient;
|
||||
queryClientConfig?: never;
|
||||
}
|
||||
| {
|
||||
queryClientConfig?: QueryClientConfig;
|
||||
queryClient?: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const getQueryClient = (config: CreateTRPCReactQueryClientConfig) =>
|
||||
config.queryClient ?? new QueryClient(config.queryClientConfig);
|
||||
@@ -0,0 +1,39 @@
|
||||
import { QueryClient } from "@tanstack/solid-query";
|
||||
import { AnyRouter, MaybePromise } from "@trpc/server";
|
||||
import Solid from "solid-js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface UseMutationOverride {
|
||||
onSuccess: (opts: {
|
||||
/**
|
||||
* Calls the original function that was defined in the query's `onSuccess` option
|
||||
*/
|
||||
originalFn: () => MaybePromise<unknown>;
|
||||
queryClient: QueryClient;
|
||||
}) => MaybePromise<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CreateTRPCSolidOptions<_TRouter extends AnyRouter> {
|
||||
/**
|
||||
* Override behaviors of the built-in hooks
|
||||
*/
|
||||
unstable_overrides?: {
|
||||
useMutation?: Partial<UseMutationOverride>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the default context provider
|
||||
* @default undefined
|
||||
*/
|
||||
context?: Solid.Context<any>;
|
||||
/**
|
||||
* Override the default React Query context
|
||||
* @default undefined
|
||||
*/
|
||||
solidQueryContext?: Solid.Context<QueryClient | undefined>;
|
||||
}
|
||||
Reference in New Issue
Block a user