UNPKG

@trpc/tanstack-react-query

Version:

TanStack React Query Integration for tRPC

541 lines (540 loc) • 20.1 kB
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