diff --git a/packages/toolkit/src/query/react/HydrateEndpoints.cc.tsx b/packages/toolkit/src/query/react/HydrateEndpoints.cc.tsx new file mode 100644 index 0000000000..68ed2ebd39 --- /dev/null +++ b/packages/toolkit/src/query/react/HydrateEndpoints.cc.tsx @@ -0,0 +1,75 @@ +import { useStore } from 'react-redux' + +export interface EndpointRequest { + apiPath: string + serializedQueryArgs: string + resolvedAndTransformedData: Promise +} + +interface HydrateEndpointsProps { + immediateRequests: Array + lateRequests: AsyncGenerator + children?: any +} + +const seen = new WeakSet< + Array | AsyncGenerator +>() + +export function HydrateEndpoints({ + immediateRequests, + lateRequests, + children, +}: HydrateEndpointsProps) { + if (!seen.has(immediateRequests)) { + seen.add(immediateRequests) + for (const request of immediateRequests) { + handleRequest(request) + } + } + if (!seen.has(lateRequests)) { + seen.add(lateRequests) + handleLateRequests() + async function handleLateRequests() { + for await (const request of lateRequests) { + for (const request of immediateRequests) { + handleRequest(request) + } + } + } + } + const store = useStore() + return children + + async function handleRequest(request: EndpointRequest) { + store.dispatch({ + type: 'simulate-endpoint-start', + payload: { + serializedQueryArgs: request.serializedQueryArgs, + apiPath: request.apiPath, + }, + }) + try { + const data = await request.resolvedAndTransformedData + store.dispatch({ + type: 'simulate-endpoint-success', + payload: { + data, + serializedQueryArgs: request.serializedQueryArgs, + apiPath: request.apiPath, + }, + }) + } catch (error) { + store.dispatch({ + type: 'simulate-endpoint-error', + payload: { + serializedQueryArgs: request.serializedQueryArgs, + apiPath: request.apiPath, + // no error details here as it won't be transported over by React + // to not leak sensitive information from the server + // that's a good thing + }, + }) + } + } +} diff --git a/packages/toolkit/src/query/react/PrefetchEndpoints.tsx b/packages/toolkit/src/query/react/PrefetchEndpoints.tsx new file mode 100644 index 0000000000..c0164f34a4 --- /dev/null +++ b/packages/toolkit/src/query/react/PrefetchEndpoints.tsx @@ -0,0 +1,103 @@ +import { createApi } from '.' +import type { BaseQueryFn } from '../baseQueryTypes' +import type { ApiEndpointQuery } from '../core' +import type { QueryDefinition } from '../endpointDefinitions' +import { fetchBaseQuery } from '../fetchBaseQuery' +import type { EndpointRequest } from './HydrateEndpoints.cc' +// this needs to be a separately bundled entry point prefixed with "use client" +import { HydrateEndpoints } from './HydrateEndpoints.cc' + +interface PrefetchEndpointsProps { + baseQuery: BaseQueryFn + run: ( + prefetchEndpoint: ( + endpoint: ApiEndpointQuery< + QueryDefinition, + any + >, + arg: QueryArg, + ) => Promise, + ) => Promise | undefined + children?: any +} + +export function PrefetchEndpoints({ + baseQuery, + run, + children, +}: PrefetchEndpointsProps) { + const immediateRequests: Array = [] + const lateRequests = generateRequests() + async function* generateRequests(): AsyncGenerator { + let resolveNext: undefined | PromiseWithResolvers + const running = run((endpoint, arg) => { + // something something magic + const request = { + serializedQueryArgs: '...', + resolvedAndTransformedData: {}, // ... + } as any as EndpointRequest + if (!resolveNext) { + immediateRequests.push(request) + } else { + const oldResolveNext = resolveNext + resolveNext = Promise.withResolvers() + oldResolveNext.resolve(request) + } + return request.resolvedAndTransformedData + }) + + // not an async function, no need to wait for late requests + if (!running) return + + let runningResolved = false + running.then(() => { + runningResolved = true + }) + + resolveNext = Promise.withResolvers() + while (!runningResolved) { + yield await resolveNext.promise + } + } + return ( + + {children} + + ) +} + +// usage: + +const baseQuery = fetchBaseQuery() +const api = createApi({ + baseQuery, + endpoints: (build) => ({ + foo: build.query({ + query(arg) { + return { url: '/foo' + arg } + }, + }), + }), +}) + +function Page() { + return ( + { + // immediate prefetching + const promise1 = prefetch(api.endpoints.foo, 'bar') + const promise2 = prefetch(api.endpoints.foo, 'baz') + // and a "dependent endpoint" that can only be prefetched with the result of the first two + const result1 = await promise1 + const result2 = await promise2 + prefetch(api.endpoints.foo, result1 + result2) + }} + > + foo + + ) +} diff --git a/packages/toolkit/src/query/react/cc-entry-point.ts b/packages/toolkit/src/query/react/cc-entry-point.ts new file mode 100644 index 0000000000..8ab2d1c778 --- /dev/null +++ b/packages/toolkit/src/query/react/cc-entry-point.ts @@ -0,0 +1,3 @@ +'use client' + +export { HydrateEndpoints } from './HydrateEndpoints.cc.jsx'