@trpc/tanstack-react-query
Version:
TanStack React Query Integration for tRPC
541 lines (540 loc) • 20.1 kB
JavaScript
import { a as getQueryKeyInternal, c as _objectSpread2, i as getMutationKeyInternal, n as createTRPCOptionsResult, r as getClientArgs, s as unwrapLazyArg, t as buildQueryFromAsyncIterable } from "./utils-DghvpG0v.mjs";
import * as React from "react";
import { TRPCUntypedClient, getUntypedClient } from "@trpc/client";
import { callTRPCProcedure, createTRPCRecursiveProxy } from "@trpc/server";
import { HydrationBoundary, defaultShouldDehydrateMutation, defaultShouldDehydrateQuery, dehydrate, hashKey, infiniteQueryOptions, queryOptions, skipToken } from "@tanstack/react-query";
import { isAsyncIterable } from "@trpc/server/unstable-core-do-not-import";
//#region src/internals/infiniteQueryOptions.ts
function trpcInfiniteQueryOptions(args) {
var _opts$initialCursor;
const { input, query, path, queryKey, opts } = args;
const inputIsSkipToken = input === skipToken;
const queryFn = async (queryFnContext) => {
var _opts$trpc;
const actualOpts = _objectSpread2(_objectSpread2({}, opts), {}, { trpc: _objectSpread2(_objectSpread2({}, opts === null || opts === void 0 ? void 0 : opts.trpc), (opts === null || opts === void 0 || (_opts$trpc = opts.trpc) === null || _opts$trpc === void 0 ? void 0 : _opts$trpc.abortOnUnmount) ? { signal: queryFnContext.signal } : { signal: null }) });
return await query(...getClientArgs(queryKey, actualOpts, {
direction: queryFnContext.direction,
pageParam: queryFnContext.pageParam
}));
};
return Object.assign(infiniteQueryOptions(_objectSpread2(_objectSpread2({}, opts !== null && opts !== void 0 ? opts : {}), {}, {
queryKey,
queryFn: inputIsSkipToken ? skipToken : queryFn,
initialPageParam: (_opts$initialCursor = opts === null || opts === void 0 ? void 0 : opts.initialCursor) !== null && _opts$initialCursor !== void 0 ? _opts$initialCursor : input === null || input === void 0 ? void 0 : input.cursor
})), { trpc: createTRPCOptionsResult({ path }) });
}
//#endregion
//#region src/internals/mutationOptions.ts
/**
* @internal
*/
function trpcMutationOptions(args) {
var _opts$keyPrefix, _overrides$onSuccess;
const { mutate, path, prefix, opts, overrides } = args;
const queryClient = unwrapLazyArg(args.queryClient);
const mutationKey = getMutationKeyInternal({
path,
prefix: (_opts$keyPrefix = opts === null || opts === void 0 ? void 0 : opts.keyPrefix) !== null && _opts$keyPrefix !== void 0 ? _opts$keyPrefix : prefix
});
const defaultOpts = queryClient.defaultMutationOptions(queryClient.getMutationDefaults(mutationKey));
const mutationSuccessOverride = (_overrides$onSuccess = overrides === null || overrides === void 0 ? void 0 : overrides.onSuccess) !== null && _overrides$onSuccess !== void 0 ? _overrides$onSuccess : ((options) => options.originalFn());
const mutationFn = async (input) => {
return await mutate(...getClientArgs([...mutationKey, { input }], opts));
};
return _objectSpread2(_objectSpread2({}, opts), {}, {
mutationKey,
mutationFn,
onSuccess(...args) {
var _ref, _opts$meta;
const originalFn = () => {
var _opts$onSuccess, _opts$onSuccess2, _defaultOpts$onSucces;
return (_opts$onSuccess = opts === null || opts === void 0 || (_opts$onSuccess2 = opts.onSuccess) === null || _opts$onSuccess2 === void 0 ? void 0 : _opts$onSuccess2.call(opts, ...args)) !== null && _opts$onSuccess !== void 0 ? _opts$onSuccess : defaultOpts === null || defaultOpts === void 0 || (_defaultOpts$onSucces = defaultOpts.onSuccess) === null || _defaultOpts$onSucces === void 0 ? void 0 : _defaultOpts$onSucces.call(defaultOpts, ...args);
};
return mutationSuccessOverride({
originalFn,
queryClient,
meta: (_ref = (_opts$meta = opts === null || opts === void 0 ? void 0 : opts.meta) !== null && _opts$meta !== void 0 ? _opts$meta : defaultOpts === null || defaultOpts === void 0 ? void 0 : defaultOpts.meta) !== null && _ref !== void 0 ? _ref : {}
});
},
trpc: createTRPCOptionsResult({ path })
});
}
//#endregion
//#region src/internals/queryOptions.ts
/**
* @internal
*/
function trpcQueryOptions(args) {
const { input, query, path, queryKey, opts } = args;
const queryClient = unwrapLazyArg(args.queryClient);
const inputIsSkipToken = input === skipToken;
const queryFn = async (queryFnContext) => {
var _opts$trpc;
const actualOpts = _objectSpread2(_objectSpread2({}, opts), {}, { trpc: _objectSpread2(_objectSpread2({}, opts === null || opts === void 0 ? void 0 : opts.trpc), (opts === null || opts === void 0 || (_opts$trpc = opts.trpc) === null || _opts$trpc === void 0 ? void 0 : _opts$trpc.abortOnUnmount) ? { signal: queryFnContext.signal } : { signal: null }) });
const queryKey = queryFnContext.queryKey;
const result = await query(...getClientArgs(queryKey, actualOpts));
if (isAsyncIterable(result)) return buildQueryFromAsyncIterable(result, queryClient, queryKey);
return result;
};
return Object.assign(queryOptions(_objectSpread2(_objectSpread2({}, opts), {}, {
queryKey,
queryFn: inputIsSkipToken ? skipToken : queryFn
})), { trpc: createTRPCOptionsResult({ path }) });
}
//#endregion
//#region src/internals/subscriptionOptions.ts
/**
* @internal
*/
const trpcSubscriptionOptions = (args) => {
const { subscribe, path, queryKey, input, opts = {} } = args;
const enabled = "enabled" in opts ? !!opts.enabled : input !== skipToken;
const _subscribe = (innerOpts) => {
return subscribe(path.join("."), input === skipToken ? void 0 : input, _objectSpread2(_objectSpread2({}, innerOpts), {}, { onComplete: () => {
var _innerOpts$onComplete;
return (_innerOpts$onComplete = innerOpts.onComplete) === null || _innerOpts$onComplete === void 0 ? void 0 : _innerOpts$onComplete.call(innerOpts);
} }));
};
return _objectSpread2(_objectSpread2({}, opts), {}, {
enabled,
subscribe: _subscribe,
queryKey,
trpc: createTRPCOptionsResult({ path })
});
};
/**
* Runs a subscription while the component is mounted.
*
* @example
* ```ts
* const trpc = useTRPC();
* const sub = useSubscription(
* trpc.chat.onMessage.subscriptionOptions({ roomId }),
* );
* ```
*
* @see https://trpc.io/docs/client/tanstack-react-query/usage#subscriptionOptions
*/
function useSubscription(opts) {
const optsRef = React.useRef(opts);
optsRef.current = opts;
const trackedProps = React.useRef(/* @__PURE__ */ new Set([]));
const addTrackedProp = React.useCallback((key) => {
trackedProps.current.add(key);
}, []);
const currentSubscriptionRef = React.useRef(() => {});
const reset = React.useCallback(() => {
var _currentSubscriptionR;
(_currentSubscriptionR = currentSubscriptionRef.current) === null || _currentSubscriptionR === void 0 || _currentSubscriptionR.call(currentSubscriptionRef);
updateState(getInitialState);
if (!opts.enabled) return;
let isUnsubscribed = false;
let isEnded = false;
const setEnded = () => {
if (isEnded) return;
isEnded = true;
updateState((prev) => _objectSpread2(_objectSpread2({}, prev), {}, {
status: "idle",
data: void 0,
error: null
}));
};
const subscription = opts.subscribe({
onStarted: () => {
var _optsRef$current$onSt, _optsRef$current;
(_optsRef$current$onSt = (_optsRef$current = optsRef.current).onStarted) === null || _optsRef$current$onSt === void 0 || _optsRef$current$onSt.call(_optsRef$current);
updateState((prev) => _objectSpread2(_objectSpread2({}, prev), {}, {
status: "pending",
error: null
}));
},
onData: (data) => {
var _optsRef$current$onDa, _optsRef$current2;
(_optsRef$current$onDa = (_optsRef$current2 = optsRef.current).onData) === null || _optsRef$current$onDa === void 0 || _optsRef$current$onDa.call(_optsRef$current2, data);
updateState((prev) => _objectSpread2(_objectSpread2({}, prev), {}, {
status: "pending",
data,
error: null
}));
},
onError: (error) => {
var _optsRef$current$onEr, _optsRef$current3;
(_optsRef$current$onEr = (_optsRef$current3 = optsRef.current).onError) === null || _optsRef$current$onEr === void 0 || _optsRef$current$onEr.call(_optsRef$current3, error);
updateState((prev) => _objectSpread2(_objectSpread2({}, prev), {}, {
status: "error",
error
}));
},
onStopped: () => {
var _optsRef$current$onSt2, _optsRef$current4;
if (isUnsubscribed) return;
setEnded();
(_optsRef$current$onSt2 = (_optsRef$current4 = optsRef.current).onStopped) === null || _optsRef$current$onSt2 === void 0 || _optsRef$current$onSt2.call(_optsRef$current4);
},
onComplete: () => {
var _optsRef$current$onCo, _optsRef$current5;
if (isUnsubscribed) return;
setEnded();
(_optsRef$current$onCo = (_optsRef$current5 = optsRef.current).onComplete) === null || _optsRef$current$onCo === void 0 || _optsRef$current$onCo.call(_optsRef$current5);
},
onConnectionStateChange: (result) => {
var _optsRef$current$onCo2, _optsRef$current6;
(_optsRef$current$onCo2 = (_optsRef$current6 = optsRef.current).onConnectionStateChange) === null || _optsRef$current$onCo2 === void 0 || _optsRef$current$onCo2.call(_optsRef$current6, result);
updateState((prev) => {
switch (result.state) {
case "connecting": return _objectSpread2(_objectSpread2({}, prev), {}, {
status: "connecting",
error: result.error
});
case "pending": return prev;
case "idle": return _objectSpread2(_objectSpread2({}, prev), {}, {
status: "idle",
data: void 0,
error: null
});
}
});
}
});
currentSubscriptionRef.current = () => {
isUnsubscribed = true;
subscription.unsubscribe();
};
}, [hashKey(opts.queryKey), opts.enabled]);
const getInitialState = React.useCallback(() => {
return opts.enabled ? {
data: void 0,
error: null,
status: "connecting",
reset
} : {
data: void 0,
error: null,
status: "idle",
reset
};
}, [opts.enabled, reset]);
const resultRef = React.useRef(getInitialState());
const [state, setState] = React.useState(trackResult(resultRef, addTrackedProp));
state.reset = reset;
const updateState = React.useCallback((callback) => {
const prev = resultRef.current;
const next = resultRef.current = callback(prev);
let shouldUpdate = false;
for (const key of trackedProps.current) if (prev[key] !== next[key]) {
shouldUpdate = true;
break;
}
if (shouldUpdate) setState(trackResult(resultRef, addTrackedProp));
}, [addTrackedProp]);
React.useEffect(() => {
if (!opts.enabled) return;
reset();
return () => {
var _currentSubscriptionR2;
(_currentSubscriptionR2 = currentSubscriptionRef.current) === null || _currentSubscriptionR2 === void 0 || _currentSubscriptionR2.call(currentSubscriptionRef);
};
}, [reset, opts.enabled]);
return state;
}
function trackResult(result, onTrackResult) {
return new Proxy(result.current, { get(_target, prop) {
onTrackResult(prop);
return result.current[prop];
} });
}
//#endregion
//#region src/internals/createOptionsProxy.ts
/**
* Creates the options proxy without React context. Works on the server too.
*
* @example
* ```ts
* export const trpc = createTRPCOptionsProxy<AppRouter>({
* client: trpcClient,
* queryClient,
* });
* ```
*
* @see https://trpc.io/docs/client/tanstack-react-query/setup#3c-set-up-without-react-context
* @see https://trpc.io/docs/client/tanstack-react-query/server-components#5-create-a-trpc-caller-for-server-components
*/
function createTRPCOptionsProxy(opts) {
const prefix = opts.keyPrefix;
const callIt = (type) => {
return (path, input, trpcOpts) => {
if ("router" in opts) return Promise.resolve(unwrapLazyArg(opts.ctx)).then((ctx) => callTRPCProcedure({
router: opts.router,
path,
getRawInput: async () => input,
ctx,
type,
signal: void 0,
batchIndex: 0
}));
return (opts.client instanceof TRPCUntypedClient ? opts.client : getUntypedClient(opts.client))[type](path, input, trpcOpts);
};
};
return createTRPCRecursiveProxy(({ args, path: _path }) => {
const path = [..._path];
const utilName = path.pop();
const [arg1, arg2] = args;
return {
"~types": void 0,
"~queryClient": () => unwrapLazyArg(opts.queryClient),
pathKey: () => {
return getQueryKeyInternal({
path,
type: "any",
prefix
});
},
pathFilter: () => {
return _objectSpread2(_objectSpread2({}, arg1), {}, { queryKey: getQueryKeyInternal({
path,
type: "any",
prefix
}) });
},
queryOptions: () => {
return trpcQueryOptions({
input: arg1,
opts: arg2,
path,
queryClient: opts.queryClient,
queryKey: getQueryKeyInternal({
path,
input: arg1,
type: "query",
prefix
}),
query: callIt("query")
});
},
queryKey: () => {
return getQueryKeyInternal({
path,
input: arg1,
type: "query",
prefix
});
},
queryFilter: () => {
return _objectSpread2(_objectSpread2({}, arg2), {}, { queryKey: getQueryKeyInternal({
path,
input: arg1,
type: "query",
prefix
}) });
},
infiniteQueryOptions: () => {
return trpcInfiniteQueryOptions({
input: arg1,
opts: arg2,
path,
queryClient: opts.queryClient,
queryKey: getQueryKeyInternal({
path,
input: arg1,
type: "infinite",
prefix
}),
query: callIt("query")
});
},
infiniteQueryKey: () => {
return getQueryKeyInternal({
path,
input: arg1,
type: "infinite",
prefix
});
},
infiniteQueryFilter: () => {
return _objectSpread2(_objectSpread2({}, arg2), {}, { queryKey: getQueryKeyInternal({
path,
input: arg1,
type: "infinite",
prefix
}) });
},
mutationOptions: () => {
var _opts$overrides;
return trpcMutationOptions({
opts: arg1,
path,
prefix,
queryClient: opts.queryClient,
mutate: callIt("mutation"),
overrides: (_opts$overrides = opts.overrides) === null || _opts$overrides === void 0 ? void 0 : _opts$overrides.mutations
});
},
mutationKey: () => {
return getMutationKeyInternal({
path,
prefix
});
},
subscriptionOptions: () => {
return trpcSubscriptionOptions({
opts: arg2,
path,
input: arg1,
queryKey: getQueryKeyInternal({
path,
input: arg1,
type: "any",
prefix
}),
subscribe: callIt("subscription")
});
}
}[utilName]();
});
}
//#endregion
//#region src/internals/Context.tsx
/**
* Creates a `TRPCProvider` and the `useTRPC` and `useTRPCClient` hooks for
* your router.
*
* @example
* ```ts
* export const { TRPCProvider, useTRPC, useTRPCClient } =
* createTRPCContext<AppRouter>();
* ```
*
* @see https://trpc.io/docs/client/tanstack-react-query/setup#3a-set-up-the-trpc-context-provider
*/
function createTRPCContext() {
const TRPCClientContext = React.createContext(null);
const TRPCContext = React.createContext(null);
const TRPCProvider = (props) => {
const value = React.useMemo(() => createTRPCOptionsProxy({
client: props.trpcClient,
queryClient: props.queryClient,
keyPrefix: props.keyPrefix
}), [
props.trpcClient,
props.queryClient,
props.keyPrefix
]);
return /* @__PURE__ */ React.createElement(TRPCClientContext.Provider, { value: props.trpcClient }, /* @__PURE__ */ React.createElement(TRPCContext.Provider, { value }, props.children));
};
TRPCProvider.displayName = "TRPCProvider";
function useTRPC() {
const utils = React.useContext(TRPCContext);
if (!utils) throw new Error("useTRPC() can only be used inside of a <TRPCProvider>");
return utils;
}
function useTRPCClient() {
const client = React.useContext(TRPCClientContext);
if (!client) throw new Error("useTRPCClient() can only be used inside of a <TRPCProvider>");
return client;
}
return {
TRPCProvider,
useTRPC,
useTRPCClient
};
}
//#endregion
//#region src/internals/hydration.ts
/**
* Keyed by the request's `QueryClient`, so one server render's boundaries
* share it and the next request starts over.
*/
const sentByClient = /* @__PURE__ */ new WeakMap();
function sentEntries(queryClient) {
let sent = sentByClient.get(queryClient);
if (!sent) {
sent = {
queries: /* @__PURE__ */ new Map(),
mutations: /* @__PURE__ */ new WeakSet(),
watchingPending: false
};
sentByClient.set(queryClient, sent);
}
return sent;
}
/**
* A query sent pending carries its request's promise, so the data that
* request writes is already on its way. Record it once it lands: the query
* drops its promise after settling, so it can't be compared later.
*/
function watchPendingSends(queryClient, sent) {
if (sent.watchingPending) return;
sent.watchingPending = true;
queryClient.getQueryCache().subscribe((event) => {
if (event.type !== "updated" || event.action.type !== "success" || event.action.manual) return;
const entry = sent.queries.get(event.query.queryHash);
if ((entry === null || entry === void 0 ? void 0 : entry.promise) && entry.promise === event.query.promise) {
entry.dataUpdatedAt = event.query.state.dataUpdatedAt;
entry.promise = void 0;
}
});
}
function isAlreadySent(query, sent) {
return sent !== void 0 && query.state.dataUpdatedAt <= sent.dataUpdatedAt;
}
/**
* Dehydrate what the client's `dehydrate` defaults select, minus entries an
* earlier call already sent for this client with the same or newer data.
* @internal
*/
function dehydrateUnsent(queryClient) {
var _defaults$shouldDehyd, _defaults$shouldDehyd2;
const defaults = queryClient.getDefaultOptions().dehydrate;
const shouldDehydrateQuery = (_defaults$shouldDehyd = defaults === null || defaults === void 0 ? void 0 : defaults.shouldDehydrateQuery) !== null && _defaults$shouldDehyd !== void 0 ? _defaults$shouldDehyd : defaultShouldDehydrateQuery;
const shouldDehydrateMutation = (_defaults$shouldDehyd2 = defaults === null || defaults === void 0 ? void 0 : defaults.shouldDehydrateMutation) !== null && _defaults$shouldDehyd2 !== void 0 ? _defaults$shouldDehyd2 : defaultShouldDehydrateMutation;
const sent = sentEntries(queryClient);
return dehydrate(queryClient, {
shouldDehydrateQuery: (query) => {
if (isAlreadySent(query, sent.queries.get(query.queryHash)) || !shouldDehydrateQuery(query)) return false;
const promise = query.state.status === "pending" ? query.promise : void 0;
if (promise) watchPendingSends(queryClient, sent);
sent.queries.set(query.queryHash, {
dataUpdatedAt: query.state.dataUpdatedAt,
promise
});
return true;
},
shouldDehydrateMutation: (mutation) => {
if (sent.mutations.has(mutation) || !shouldDehydrateMutation(mutation)) return false;
sent.mutations.add(mutation);
return true;
}
});
}
//#endregion
//#region src/internals/HydrateClient.tsx
/**
* Create a `HydrateClient` server component for the request's `QueryClient`.
* It sends the queries started so far in this request to the browser,
* pending ones as promises that stream in as they settle. A query an earlier
* `HydrateClient` already sent is skipped, so nesting them is fine.
*
* React Server Components only: the package resolves to the server build
* through the `react-server` export condition.
*
* @see https://trpc.io/docs/client/tanstack-react-query/handles#hydrate-client
*/
function unstable_createHydrateClient(_queryClient) {
function HydrateClient() {
throw new Error("`HydrateClient` from `unstable_createHydrateClient()` works only in React Server Components. Render it from a server component; in client components, use TanStack Query's `HydrationBoundary`.");
}
return HydrateClient;
}
/**
* `unstable_createHydrateClient` for the `react-server` export condition.
* @internal
*/
function createHydrateClientReactServer(queryClient) {
function HydrateClient(props) {
return /* @__PURE__ */ React.createElement(HydrationBoundary, { state: dehydrateUnsent(unwrapLazyArg(queryClient)) }, props.children);
}
return HydrateClient;
}
//#endregion
export { useSubscription as a, createTRPCOptionsProxy as i, unstable_createHydrateClient as n, createTRPCContext as r, createHydrateClientReactServer as t };
//# sourceMappingURL=src-CH8R2e0M.mjs.map