UNPKG

@trpc/tanstack-react-query

Version:

TanStack React Query Integration for tRPC

358 lines (357 loc) • 14.9 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_utils = require("../utils-Dc6cVb7T.cjs"); let _trpc_client = require("@trpc/client"); let _tanstack_react_query = require("@tanstack/react-query"); let _trpc_server_unstable_core_do_not_import = require("@trpc/server/unstable-core-do-not-import"); //#region src/unstable-handles/entryError.ts /** * What `loadHandles()` rejects with when a fetch or ensure entry fails. It * names the entry, and keeps the request's error as `cause`. * @see https://trpc.io/docs/client/tanstack-react-query/handles#errors-ensure */ var TRPCEntryError = class extends Error { constructor(opts) { super(`Entry "${opts.key}" (${opts.path}) failed: ${opts.cause.message}`); require_utils._defineProperty(this, "key", void 0); require_utils._defineProperty(this, "path", void 0); require_utils._defineProperty(this, "code", void 0); require_utils._defineProperty(this, "cause", void 0); this.name = "TRPCEntryError"; this.cause = opts.cause; this.key = opts.key; this.path = opts.path; this.code = opts.code; } }; function codeOfShape(data) { const code = data === null || data === void 0 ? void 0 : data.code; return typeof code === "string" ? code : void 0; } function entryCause(error) { if ((0, _trpc_client.isTRPCClientError)(error)) return { cause: error, code: codeOfShape(error.data) }; const cause = (0, _trpc_server_unstable_core_do_not_import.getTRPCErrorFromUnknown)(error); return { cause, code: cause.code }; } //#endregion //#region src/unstable-handles/handles.ts const notAHandle = "Expected a tRPC handle from `loadHandles()`"; function readHandle(handle, prefix) { var _data$args, _data$prefix$, _data$prefix, _data$args2; const queryKey = handle === null || handle === void 0 ? void 0 : handle.queryKey; if (!Array.isArray(queryKey)) throw new Error(`${notAHandle}, got ${JSON.stringify(handle)}`); const data = require_utils.readQueryKey(queryKey); const type = (_data$args = data.args) === null || _data$args === void 0 ? void 0 : _data$args.type; if (type !== "query" && type !== "infinite") throw new Error(`${notAHandle}, got a key of type ${String(type)} for "${data.path.join(".")}"`); if (((_data$prefix$ = (_data$prefix = data.prefix) === null || _data$prefix === void 0 ? void 0 : _data$prefix[0]) !== null && _data$prefix$ !== void 0 ? _data$prefix$ : void 0) !== prefix) { var _data$prefix2; throw new Error(`The handle for "${data.path.join(".")}" was created with keyPrefix ${JSON.stringify((_data$prefix2 = data.prefix) === null || _data$prefix2 === void 0 ? void 0 : _data$prefix2[0])}, but this proxy uses ${JSON.stringify(prefix)}`); } return { type, path: data.path, input: (_data$args2 = data.args) === null || _data$args2 === void 0 ? void 0 : _data$args2.input, queryKey }; } function toHandle(options, type) { var _readQueryKey$args; const input = (_readQueryKey$args = require_utils.readQueryKey(options.queryKey).args) === null || _readQueryKey$args === void 0 ? void 0 : _readQueryKey$args.input; const handle = require_utils._objectSpread2({ queryKey: options.queryKey }, input !== void 0 && { input }); if (type === "infinite" && options.initialPageParam != null) handle["initialCursor"] = options.initialPageParam; return handle; } const entryState = Symbol("trpc.handleEntry"); function createEntry(state) { return { [entryState]: state }; } function readEntry(key, entry) { const state = entry === null || entry === void 0 ? void 0 : entry[entryState]; if (!state) throw new Error(`Expected an entry from \`prefetchEntry()\`, \`fetchEntry()\` or \`ensureEntry()\` for "${key}", got ${JSON.stringify(entry)}`); return state; } function startEntry(queryClient, state) { const { options } = state; if (state.type === "infinite") switch (state.kind) { case "prefetch": return queryClient.prefetchInfiniteQuery(options); case "ensure": return queryClient.ensureInfiniteQueryData(require_utils._objectSpread2(require_utils._objectSpread2({}, options), {}, { revalidateIfStale: true })); case "fetch": return queryClient.fetchInfiniteQuery(options); } switch (state.kind) { case "prefetch": return queryClient.prefetchQuery(options); case "ensure": return queryClient.ensureQueryData(require_utils._objectSpread2(require_utils._objectSpread2({}, options), {}, { revalidateIfStale: true })); case "fetch": return queryClient.fetchQuery(options); } } function toEntryError(key, state, error) { if ((0, _tanstack_react_query.isCancelledError)(error)) return error; return new TRPCEntryError(require_utils._objectSpread2({ key, path: require_utils.readQueryKey(state.options.queryKey).path.join(".") }, entryCause(error))); } function settleLoad(promise) { return promise.then((data) => ({ status: "success", data }), (error) => { if (error instanceof TRPCEntryError) return { status: "error", error }; throw error; }); } async function startEntries(opts) { const { queryClient, entries } = opts; const loads = Object.entries(entries).map(([key, entry]) => { const state = readEntry(key, entry); if (state.queryClient !== queryClient) throw new Error(`The entry for "${key}" was built with a proxy for another QueryClient than the root proxy that loads it`); return [key, state]; }).map(([key, state]) => ({ key, state, promise: startEntry(queryClient, state).catch((error) => { throw toEntryError(key, state, error); }) })); const waited = loads.filter((load) => load.state.kind !== "prefetch"); const results = await Promise.all(waited.map((load) => opts.settle ? settleLoad(load.promise) : load.promise)); return [Object.fromEntries(loads.map((load) => [load.key, toHandle(load.state.options, load.state.type)])), Object.fromEntries(waited.map((load, index) => [load.key, results[index]]))]; } function isServerEnvironment() { return typeof window === "undefined" || "Deno" in globalThis; } const warnedNotHydrated = /* @__PURE__ */ new Set(); function warnIfNotHydrated(path, queryFn) { return (context) => { if (isServerEnvironment()) { const queryHash = (0, _tanstack_react_query.hashKey)(context.queryKey); if (!warnedNotHydrated.has(queryHash)) { warnedNotHydrated.add(queryHash); console.warn(`[@trpc/tanstack-react-query] The handle for "${path.join(".")}" is being fetched again during server rendering. Unless its data is stale, the query it refers to isn't in this QueryClient. Render the reading component inside <HydrateClient> or <HydrationBoundary>, and don't return handles from server functions or server actions. See https://trpc.io/docs/client/tanstack-react-query/handles#where-handles-dont-work`); } } return queryFn(context); }; } const watchedHydrations = /* @__PURE__ */ new WeakSet(); /** * A query dehydrated as pending that later rejects reaches the browser as a * redacted error, before any `retry` from `fromHandle()` applies. Refetch such * a query once so error boundaries see a `TRPCClientError` from this client. */ function refetchRejectedHydration(queryClient, type, options) { const enabled = typeof options.enabled === "function" ? options.enabled : () => options.enabled; if (isServerEnvironment()) return; const query = queryClient.getQueryCache().find({ queryKey: options.queryKey, exact: true }); const promise = query === null || query === void 0 ? void 0 : query.promise; if (!query || !promise || query.state.data !== void 0 || query.state.fetchStatus === "idle" || enabled(query) === false || watchedHydrations.has(promise)) return; watchedHydrations.add(promise); promise.catch((error) => { var _defaulted$retry; if ((0, _trpc_client.isTRPCClientError)(error) || (0, _tanstack_react_query.isCancelledError)(error) || query.state.fetchStatus !== "idle" || enabled(query) === false) return; const defaulted = queryClient.defaultQueryOptions(options); (type === "infinite" ? queryClient.fetchInfiniteQuery(require_utils._objectSpread2(require_utils._objectSpread2({}, options), {}, { retry: (_defaulted$retry = defaulted.retry) !== null && _defaulted$retry !== void 0 ? _defaulted$retry : 3 })) : query.fetch(defaulted)).catch(() => {}); }); } function prepareHandleRead(opts) { var _globalThis$process; const { options } = opts; if (((_globalThis$process = globalThis.process) === null || _globalThis$process === void 0 || (_globalThis$process = _globalThis$process.env) === null || _globalThis$process === void 0 ? void 0 : _globalThis$process["NODE_ENV"]) !== "production" && typeof options.queryFn === "function") options.queryFn = warnIfNotHydrated(opts.handle.path, options.queryFn); refetchRejectedHydration(opts.queryClient, opts.handle.type, options); return options; } function readProxy(proxy, fn) { var _key$prefix; const queryKey = proxy === null || proxy === void 0 ? void 0 : proxy.queryKey; if (Array.isArray(queryKey)) throw new Error(`\`${fn}()\` expects a tRPC options proxy or one of its procedures, got a handle for \`${require_utils.readQueryKey(queryKey).path.join(".")}\``); if (typeof queryKey !== "function") throw new Error(`\`${fn}()\` expects a tRPC options proxy or one of its procedures, got ${String(proxy)}`); const key = require_utils.readQueryKey(queryKey()); return { proxy, path: key.path, prefix: (_key$prefix = key.prefix) === null || _key$prefix === void 0 ? void 0 : _key$prefix[0] }; } function queryClientOf(ctx) { return ctx.proxy["~queryClient"](); } function optionsOf(procedure, type) { return type === "infinite" ? procedure.infiniteQueryOptions : procedure.queryOptions; } function readRoot(trpc, fn) { const ctx = readProxy(trpc, fn); if (ctx.path.length > 0) throw new Error(`\`${fn}()\` takes the root proxy, \`trpc\`, got \`trpc.${ctx.path.join(".")}\``); return ctx; } function entry(opts) { const ctx = readProxy(opts.procedure, opts.fn); return createEntry({ type: opts.type, kind: opts.kind, options: optionsOf(ctx.proxy, opts.type)(opts.input, opts.opts), queryClient: queryClientOf(ctx) }); } function isTRPCEntryError(error, procedure) { if (!(error instanceof TRPCEntryError)) return false; return procedure === void 0 || error.path === readProxy(procedure, "isTRPCEntryError").path.join("."); } /** * Starts every entry before it waits for any, so the order of the entries doesn't * matter. Resolves to `[handles, data]` once every fetch and ensure entry has data: * a handle for every entry, and the data of the fetch and ensure entries. Rejects as * soon as a fetch or ensure entry fails. Prefetch entries never make it reject. * * Takes the root of an options proxy, from `createTRPCOptionsProxy` or `useTRPC()`. * Call it in a route loader, a server component or an event handler, not during * render: every render would start the entries again. * @example * ```ts * const [handles, data] = await loadHandles(trpc, { * comments: prefetchInfiniteEntry(trpc.post.comments, { postId: id }), * post: ensureEntry(trpc.post.byId, { id }), * }); * if (!data.post.published) throw notFound(); * return handles; * ``` * @see https://trpc.io/docs/client/tanstack-react-query/handles */ function loadHandles(trpc, entries) { return startEntries({ queryClient: queryClientOf(readRoot(trpc, "loadHandles")), entries, settle: false }); } /** * Like `loadHandles()`, but it waits for every fetch and ensure entry to * settle and never rejects because one failed. Resolves to `[handles, results]`: * `results` has `{ status: 'success', data }` or `{ status: 'error', error }`, * with a `TRPCEntryError`, for every fetch and ensure entry. * * A cancelled query still rejects, as it does with `loadHandles()`. * @example * ```ts * const [handles, results] = await loadHandlesSettled(trpc, { * post: ensureEntry(trpc.post.byId, { id }), * comments: prefetchInfiniteEntry(trpc.post.comments, { postId: id }), * }); * if (results.post.status === 'error') { * if (results.post.error.code === 'NOT_FOUND') throw notFound(); * throw results.post.error; * } * ``` * @see https://trpc.io/docs/client/tanstack-react-query/handles#errors-as-values */ function loadHandlesSettled(trpc, entries) { return startEntries({ queryClient: queryClientOf(readRoot(trpc, "loadHandlesSettled")), entries, settle: true }); } function prefetchEntry(procedure, input, opts) { return entry({ fn: "prefetchEntry", type: "query", kind: "prefetch", procedure, input, opts }); } function fetchEntry(procedure, input, opts) { return entry({ fn: "fetchEntry", type: "query", kind: "fetch", procedure, input, opts }); } function ensureEntry(procedure, input, opts) { return entry({ fn: "ensureEntry", type: "query", kind: "ensure", procedure, input, opts }); } /** * Like `prefetchEntry`, for an infinite query. Loads the first page unless `pages` is set. * @see https://trpc.io/docs/client/tanstack-react-query/handles#infinite-queries */ function prefetchInfiniteEntry(procedure, input, opts) { return entry({ fn: "prefetchInfiniteEntry", type: "infinite", kind: "prefetch", procedure, input, opts }); } /** * Like `fetchEntry`, for an infinite query. * @see https://trpc.io/docs/client/tanstack-react-query/handles#infinite-queries */ function fetchInfiniteEntry(procedure, input, opts) { return entry({ fn: "fetchInfiniteEntry", type: "infinite", kind: "fetch", procedure, input, opts }); } /** * Like `ensureEntry`, for an infinite query. * @see https://trpc.io/docs/client/tanstack-react-query/handles#infinite-queries */ function ensureInfiniteEntry(procedure, input, opts) { return entry({ fn: "ensureInfiniteEntry", type: "infinite", kind: "ensure", procedure, input, opts }); } function fromHandle(trpc, rawHandle, opts) { const ctx = readProxy(trpc, "fromHandle"); const handle = readHandle(rawHandle, ctx.prefix); if (!ctx.path.every((segment, index) => handle.path[index] === segment)) throw new Error(`\`fromHandle(trpc.${ctx.path.join(".")}, handle)\` got a handle for \`${handle.path.join(".")}\`, which isn't in \`${ctx.path.join(".")}\`. Pass the root proxy, \`trpc\``); const options = optionsOf(handle.path.slice(ctx.path.length).reduce((level, segment) => level[segment], ctx.proxy), handle.type)(handle.input, handle.type === "infinite" ? require_utils._objectSpread2({ initialCursor: rawHandle.initialCursor }, opts) : opts); return prepareHandleRead({ queryClient: queryClientOf(ctx), handle, options }); } //#endregion exports.TRPCEntryError = TRPCEntryError; exports.ensureEntry = ensureEntry; exports.ensureInfiniteEntry = ensureInfiniteEntry; exports.fetchEntry = fetchEntry; exports.fetchInfiniteEntry = fetchInfiniteEntry; exports.fromHandle = fromHandle; exports.isTRPCEntryError = isTRPCEntryError; exports.loadHandles = loadHandles; exports.loadHandlesSettled = loadHandlesSettled; exports.prefetchEntry = prefetchEntry; exports.prefetchInfiniteEntry = prefetchInfiniteEntry;