@tanstack/router-ssr-query-core
Advanced tools
+40
-3
@@ -13,5 +13,35 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| let unsubscribe = void 0; | ||
| let cleanupRegistered = false; | ||
| let tornDown = false; | ||
| const teardown = () => { | ||
| if (tornDown) return; | ||
| tornDown = true; | ||
| try { | ||
| unsubscribe?.(); | ||
| } catch {} | ||
| unsubscribe = void 0; | ||
| try { | ||
| if (!queryStream.isClosed()) queryStream.close(); | ||
| } catch {} | ||
| try { | ||
| queryClient.cancelQueries(); | ||
| } catch {} | ||
| try { | ||
| queryClient.clear(); | ||
| } catch {} | ||
| sentQueries.clear(); | ||
| }; | ||
| const registerCleanup = (serverSsr = router.serverSsr) => { | ||
| if (cleanupRegistered) return; | ||
| if (!serverSsr) return; | ||
| serverSsr.onCleanup(teardown); | ||
| cleanupRegistered = true; | ||
| }; | ||
| router.serverSsrLifecycle = { | ||
| ...router.serverSsrLifecycle, | ||
| onServerSsrAttach: [...router.serverSsrLifecycle?.onServerSsrAttach ?? [], registerCleanup] | ||
| }; | ||
| router.options.dehydrate = async () => { | ||
| router.serverSsr.onRenderFinished(() => { | ||
| queryStream.close(); | ||
| if (!queryStream.isClosed()) queryStream.close(); | ||
| unsubscribe?.(); | ||
@@ -107,4 +137,7 @@ unsubscribe = void 0; | ||
| stream, | ||
| enqueue: (chunk) => controllerRef.enqueue(chunk), | ||
| enqueue: (chunk) => { | ||
| if (!_isClosed) controllerRef.enqueue(chunk); | ||
| }, | ||
| close: () => { | ||
| if (_isClosed) return; | ||
| controllerRef.close(); | ||
@@ -114,3 +147,7 @@ _isClosed = true; | ||
| isClosed: () => _isClosed, | ||
| error: (err) => controllerRef.error(err) | ||
| error: (err) => { | ||
| if (_isClosed) return; | ||
| _isClosed = true; | ||
| controllerRef.error(err); | ||
| } | ||
| }; | ||
@@ -117,0 +154,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n dehydrate as queryDehydrate,\n hydrate as queryHydrate,\n} from '@tanstack/query-core'\nimport { isRedirect } from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type { AnyRouter } from '@tanstack/router-core'\nimport type {\n DehydrateOptions,\n HydrateOptions,\n QueryClient,\n DehydratedState as QueryDehydratedState,\n} from '@tanstack/query-core'\n\nexport type RouterSsrQueryOptions<TRouter extends AnyRouter> = {\n router: TRouter\n queryClient: QueryClient\n dehydrateOptions?: DehydrateOptions\n hydrateOptions?: HydrateOptions\n\n /**\n * If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries.\n *\n * @default true\n * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction)\n */\n handleRedirects?: boolean\n}\n\ntype DehydratedRouterQueryState = {\n dehydratedQueryClient?: QueryDehydratedState\n queryStream: ReadableStream<QueryDehydratedState>\n}\n\nexport function setupCoreRouterSsrQueryIntegration<TRouter extends AnyRouter>({\n router,\n queryClient,\n dehydrateOptions,\n hydrateOptions,\n handleRedirects = true,\n}: RouterSsrQueryOptions<TRouter>) {\n const ogHydrate = router.options.hydrate\n const ogDehydrate = router.options.dehydrate\n\n if (isServer ?? router.isServer) {\n const sentQueries = new Set<string>()\n const queryStream = createPushableStream()\n let unsubscribe: (() => void) | undefined = undefined\n router.options.dehydrate =\n async (): Promise<DehydratedRouterQueryState> => {\n router.serverSsr!.onRenderFinished(() => {\n queryStream.close()\n unsubscribe?.()\n unsubscribe = undefined\n })\n const ogDehydrated = await ogDehydrate?.()\n\n const dehydratedRouter = {\n ...ogDehydrated,\n // prepare the stream for queries coming up during rendering\n queryStream: queryStream.stream,\n }\n\n const dehydratedQueryClient = queryDehydrate(\n queryClient,\n dehydrateOptions,\n )\n if (dehydratedQueryClient.queries.length > 0) {\n dehydratedQueryClient.queries.forEach((query) => {\n sentQueries.add(query.queryHash)\n })\n dehydratedRouter.dehydratedQueryClient = dehydratedQueryClient\n }\n\n return dehydratedRouter\n }\n\n const ogClientOptions = queryClient.getDefaultOptions()\n queryClient.setDefaultOptions({\n ...ogClientOptions,\n dehydrate: {\n shouldDehydrateQuery: () => true,\n ...ogClientOptions.dehydrate,\n },\n })\n\n unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n // before rendering starts, we do not stream individual queries\n // instead we dehydrate the entire query client in router's dehydrate()\n // if attachRouterServerSsrUtils() has not been called yet, `router.serverSsr` will be undefined and we also do not stream\n if (!router.serverSsr?.isDehydrated()) {\n return\n }\n if (sentQueries.has(event.query.queryHash)) {\n return\n }\n // promise not yet set on the query, so we cannot stream it yet\n if (!event.query.promise) {\n return\n }\n if (queryStream.isClosed()) {\n console.warn(\n `tried to stream query ${event.query.queryHash} after stream was already closed`,\n )\n return\n }\n const dehydratedQuery = queryDehydrate(queryClient, {\n ...dehydrateOptions,\n shouldDehydrateQuery: (query) => {\n if (query.queryHash !== event.query.queryHash) {\n return false\n }\n\n return (\n (ogClientOptions.dehydrate?.shouldDehydrateQuery?.(query) ??\n true) &&\n (dehydrateOptions?.shouldDehydrateQuery?.(query) ?? true)\n )\n },\n })\n\n if (dehydratedQuery.queries.length === 0) {\n return\n }\n\n sentQueries.add(event.query.queryHash)\n queryStream.enqueue(dehydratedQuery)\n })\n // on the client\n } else {\n router.options.hydrate = async (dehydrated: DehydratedRouterQueryState) => {\n await ogHydrate?.(dehydrated)\n // hydrate the query client with the dehydrated data (if it was dehydrated on the server)\n if (dehydrated.dehydratedQueryClient) {\n queryHydrate(\n queryClient,\n dehydrated.dehydratedQueryClient,\n hydrateOptions,\n )\n }\n\n // read the query stream and hydrate the queries as they come in\n const reader = dehydrated.queryStream.getReader()\n reader\n .read()\n .then(async function handle({ done, value }) {\n queryHydrate(queryClient, value, hydrateOptions)\n if (done) {\n return\n }\n const result = await reader.read()\n return handle(result)\n })\n .catch((err) => {\n console.error('Error reading query stream:', err)\n })\n }\n if (handleRedirects) {\n const ogMutationCacheConfig = queryClient.getMutationCache().config\n queryClient.getMutationCache().config = {\n ...ogMutationCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogMutationCacheConfig.onError?.(error, ...rest)\n },\n }\n\n const ogQueryCacheConfig = queryClient.getQueryCache().config\n queryClient.getQueryCache().config = {\n ...ogQueryCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogQueryCacheConfig.onError?.(error, ...rest)\n },\n }\n }\n }\n}\n\ntype PushableStream = {\n stream: ReadableStream\n enqueue: (chunk: unknown) => void\n close: () => void\n isClosed: () => boolean\n error: (err: unknown) => void\n}\n\nfunction createPushableStream(): PushableStream {\n let controllerRef: ReadableStreamDefaultController\n const stream = new ReadableStream({\n start(controller) {\n controllerRef = controller\n },\n })\n let _isClosed = false\n\n return {\n stream,\n enqueue: (chunk) => controllerRef.enqueue(chunk),\n close: () => {\n controllerRef.close()\n _isClosed = true\n },\n isClosed: () => _isClosed,\n error: (err: unknown) => controllerRef.error(err),\n }\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n dehydrate as queryDehydrate,\n hydrate as queryHydrate,\n} from '@tanstack/query-core'\nimport { isRedirect } from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type { AnyRouter } from '@tanstack/router-core'\nimport type {\n DehydrateOptions,\n HydrateOptions,\n QueryClient,\n DehydratedState as QueryDehydratedState,\n} from '@tanstack/query-core'\n\nexport type RouterSsrQueryOptions<TRouter extends AnyRouter> = {\n router: TRouter\n queryClient: QueryClient\n dehydrateOptions?: DehydrateOptions\n hydrateOptions?: HydrateOptions\n\n /**\n * If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries.\n *\n * @default true\n * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction)\n */\n handleRedirects?: boolean\n}\n\ntype DehydratedRouterQueryState = {\n dehydratedQueryClient?: QueryDehydratedState\n queryStream: ReadableStream<QueryDehydratedState>\n}\n\nexport function setupCoreRouterSsrQueryIntegration<TRouter extends AnyRouter>({\n router,\n queryClient,\n dehydrateOptions,\n hydrateOptions,\n handleRedirects = true,\n}: RouterSsrQueryOptions<TRouter>) {\n const ogHydrate = router.options.hydrate\n const ogDehydrate = router.options.dehydrate\n\n if (isServer ?? router.isServer) {\n const sentQueries = new Set<string>()\n const queryStream = createPushableStream()\n let unsubscribe: (() => void) | undefined = undefined\n let cleanupRegistered = false\n let tornDown = false\n\n const teardown = () => {\n if (tornDown) return\n tornDown = true\n try {\n unsubscribe?.()\n } catch {\n // ignore\n }\n unsubscribe = undefined\n try {\n if (!queryStream.isClosed()) queryStream.close()\n } catch {\n // ignore\n }\n // Cancel any in-flight queries and clear the cache. Removing queries\n // cancels their gcTime setTimeout handles which would otherwise pin\n // the queryClient (and transitively the router via router.context)\n // alive for the full gcTime window (default 5min) per SSR request.\n try {\n queryClient.cancelQueries()\n } catch {\n // ignore\n }\n try {\n queryClient.clear()\n } catch {\n // ignore\n }\n sentQueries.clear()\n }\n\n // Register teardown as soon as SSR attaches. attachRouterServerSsrUtils()\n // runs before router.load(), so this covers redirects/errors thrown before\n // router.options.dehydrate() can run.\n const registerCleanup = (serverSsr = router.serverSsr) => {\n if (cleanupRegistered) return\n if (!serverSsr) return\n serverSsr.onCleanup(teardown)\n cleanupRegistered = true\n }\n router.serverSsrLifecycle = {\n ...router.serverSsrLifecycle,\n onServerSsrAttach: [\n ...(router.serverSsrLifecycle?.onServerSsrAttach ?? []),\n registerCleanup,\n ],\n }\n\n router.options.dehydrate =\n async (): Promise<DehydratedRouterQueryState> => {\n router.serverSsr!.onRenderFinished(() => {\n if (!queryStream.isClosed()) queryStream.close()\n unsubscribe?.()\n unsubscribe = undefined\n })\n const ogDehydrated = await ogDehydrate?.()\n\n const dehydratedRouter = {\n ...ogDehydrated,\n // prepare the stream for queries coming up during rendering\n queryStream: queryStream.stream,\n }\n\n const dehydratedQueryClient = queryDehydrate(\n queryClient,\n dehydrateOptions,\n )\n if (dehydratedQueryClient.queries.length > 0) {\n dehydratedQueryClient.queries.forEach((query) => {\n sentQueries.add(query.queryHash)\n })\n dehydratedRouter.dehydratedQueryClient = dehydratedQueryClient\n }\n\n return dehydratedRouter\n }\n\n const ogClientOptions = queryClient.getDefaultOptions()\n queryClient.setDefaultOptions({\n ...ogClientOptions,\n dehydrate: {\n shouldDehydrateQuery: () => true,\n ...ogClientOptions.dehydrate,\n },\n })\n\n unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n // before rendering starts, we do not stream individual queries\n // instead we dehydrate the entire query client in router's dehydrate()\n // if attachRouterServerSsrUtils() has not been called yet, `router.serverSsr` will be undefined and we also do not stream\n if (!router.serverSsr?.isDehydrated()) {\n return\n }\n if (sentQueries.has(event.query.queryHash)) {\n return\n }\n // promise not yet set on the query, so we cannot stream it yet\n if (!event.query.promise) {\n return\n }\n if (queryStream.isClosed()) {\n console.warn(\n `tried to stream query ${event.query.queryHash} after stream was already closed`,\n )\n return\n }\n const dehydratedQuery = queryDehydrate(queryClient, {\n ...dehydrateOptions,\n shouldDehydrateQuery: (query) => {\n if (query.queryHash !== event.query.queryHash) {\n return false\n }\n\n return (\n (ogClientOptions.dehydrate?.shouldDehydrateQuery?.(query) ??\n true) &&\n (dehydrateOptions?.shouldDehydrateQuery?.(query) ?? true)\n )\n },\n })\n\n if (dehydratedQuery.queries.length === 0) {\n return\n }\n\n sentQueries.add(event.query.queryHash)\n queryStream.enqueue(dehydratedQuery)\n })\n // on the client\n } else {\n router.options.hydrate = async (dehydrated: DehydratedRouterQueryState) => {\n await ogHydrate?.(dehydrated)\n // hydrate the query client with the dehydrated data (if it was dehydrated on the server)\n if (dehydrated.dehydratedQueryClient) {\n queryHydrate(\n queryClient,\n dehydrated.dehydratedQueryClient,\n hydrateOptions,\n )\n }\n\n // read the query stream and hydrate the queries as they come in\n const reader = dehydrated.queryStream.getReader()\n reader\n .read()\n .then(async function handle({ done, value }) {\n queryHydrate(queryClient, value, hydrateOptions)\n if (done) {\n return\n }\n const result = await reader.read()\n return handle(result)\n })\n .catch((err) => {\n console.error('Error reading query stream:', err)\n })\n }\n if (handleRedirects) {\n const ogMutationCacheConfig = queryClient.getMutationCache().config\n queryClient.getMutationCache().config = {\n ...ogMutationCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogMutationCacheConfig.onError?.(error, ...rest)\n },\n }\n\n const ogQueryCacheConfig = queryClient.getQueryCache().config\n queryClient.getQueryCache().config = {\n ...ogQueryCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogQueryCacheConfig.onError?.(error, ...rest)\n },\n }\n }\n }\n}\n\ntype PushableStream = {\n stream: ReadableStream\n enqueue: (chunk: unknown) => void\n close: () => void\n isClosed: () => boolean\n error: (err: unknown) => void\n}\n\nfunction createPushableStream(): PushableStream {\n let controllerRef: ReadableStreamDefaultController\n const stream = new ReadableStream({\n start(controller) {\n controllerRef = controller\n },\n })\n let _isClosed = false\n\n return {\n stream,\n enqueue: (chunk) => {\n if (!_isClosed) controllerRef.enqueue(chunk)\n },\n close: () => {\n if (_isClosed) return\n controllerRef.close()\n _isClosed = true\n },\n isClosed: () => _isClosed,\n error: (err: unknown) => {\n if (_isClosed) return\n _isClosed = true\n controllerRef.error(err)\n },\n }\n}\n"],"mappings":"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"} |
+40
-3
@@ -12,5 +12,35 @@ import { dehydrate, hydrate } from "@tanstack/query-core"; | ||
| let unsubscribe = void 0; | ||
| let cleanupRegistered = false; | ||
| let tornDown = false; | ||
| const teardown = () => { | ||
| if (tornDown) return; | ||
| tornDown = true; | ||
| try { | ||
| unsubscribe?.(); | ||
| } catch {} | ||
| unsubscribe = void 0; | ||
| try { | ||
| if (!queryStream.isClosed()) queryStream.close(); | ||
| } catch {} | ||
| try { | ||
| queryClient.cancelQueries(); | ||
| } catch {} | ||
| try { | ||
| queryClient.clear(); | ||
| } catch {} | ||
| sentQueries.clear(); | ||
| }; | ||
| const registerCleanup = (serverSsr = router.serverSsr) => { | ||
| if (cleanupRegistered) return; | ||
| if (!serverSsr) return; | ||
| serverSsr.onCleanup(teardown); | ||
| cleanupRegistered = true; | ||
| }; | ||
| router.serverSsrLifecycle = { | ||
| ...router.serverSsrLifecycle, | ||
| onServerSsrAttach: [...router.serverSsrLifecycle?.onServerSsrAttach ?? [], registerCleanup] | ||
| }; | ||
| router.options.dehydrate = async () => { | ||
| router.serverSsr.onRenderFinished(() => { | ||
| queryStream.close(); | ||
| if (!queryStream.isClosed()) queryStream.close(); | ||
| unsubscribe?.(); | ||
@@ -106,4 +136,7 @@ unsubscribe = void 0; | ||
| stream, | ||
| enqueue: (chunk) => controllerRef.enqueue(chunk), | ||
| enqueue: (chunk) => { | ||
| if (!_isClosed) controllerRef.enqueue(chunk); | ||
| }, | ||
| close: () => { | ||
| if (_isClosed) return; | ||
| controllerRef.close(); | ||
@@ -113,3 +146,7 @@ _isClosed = true; | ||
| isClosed: () => _isClosed, | ||
| error: (err) => controllerRef.error(err) | ||
| error: (err) => { | ||
| if (_isClosed) return; | ||
| _isClosed = true; | ||
| controllerRef.error(err); | ||
| } | ||
| }; | ||
@@ -116,0 +153,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n dehydrate as queryDehydrate,\n hydrate as queryHydrate,\n} from '@tanstack/query-core'\nimport { isRedirect } from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type { AnyRouter } from '@tanstack/router-core'\nimport type {\n DehydrateOptions,\n HydrateOptions,\n QueryClient,\n DehydratedState as QueryDehydratedState,\n} from '@tanstack/query-core'\n\nexport type RouterSsrQueryOptions<TRouter extends AnyRouter> = {\n router: TRouter\n queryClient: QueryClient\n dehydrateOptions?: DehydrateOptions\n hydrateOptions?: HydrateOptions\n\n /**\n * If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries.\n *\n * @default true\n * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction)\n */\n handleRedirects?: boolean\n}\n\ntype DehydratedRouterQueryState = {\n dehydratedQueryClient?: QueryDehydratedState\n queryStream: ReadableStream<QueryDehydratedState>\n}\n\nexport function setupCoreRouterSsrQueryIntegration<TRouter extends AnyRouter>({\n router,\n queryClient,\n dehydrateOptions,\n hydrateOptions,\n handleRedirects = true,\n}: RouterSsrQueryOptions<TRouter>) {\n const ogHydrate = router.options.hydrate\n const ogDehydrate = router.options.dehydrate\n\n if (isServer ?? router.isServer) {\n const sentQueries = new Set<string>()\n const queryStream = createPushableStream()\n let unsubscribe: (() => void) | undefined = undefined\n router.options.dehydrate =\n async (): Promise<DehydratedRouterQueryState> => {\n router.serverSsr!.onRenderFinished(() => {\n queryStream.close()\n unsubscribe?.()\n unsubscribe = undefined\n })\n const ogDehydrated = await ogDehydrate?.()\n\n const dehydratedRouter = {\n ...ogDehydrated,\n // prepare the stream for queries coming up during rendering\n queryStream: queryStream.stream,\n }\n\n const dehydratedQueryClient = queryDehydrate(\n queryClient,\n dehydrateOptions,\n )\n if (dehydratedQueryClient.queries.length > 0) {\n dehydratedQueryClient.queries.forEach((query) => {\n sentQueries.add(query.queryHash)\n })\n dehydratedRouter.dehydratedQueryClient = dehydratedQueryClient\n }\n\n return dehydratedRouter\n }\n\n const ogClientOptions = queryClient.getDefaultOptions()\n queryClient.setDefaultOptions({\n ...ogClientOptions,\n dehydrate: {\n shouldDehydrateQuery: () => true,\n ...ogClientOptions.dehydrate,\n },\n })\n\n unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n // before rendering starts, we do not stream individual queries\n // instead we dehydrate the entire query client in router's dehydrate()\n // if attachRouterServerSsrUtils() has not been called yet, `router.serverSsr` will be undefined and we also do not stream\n if (!router.serverSsr?.isDehydrated()) {\n return\n }\n if (sentQueries.has(event.query.queryHash)) {\n return\n }\n // promise not yet set on the query, so we cannot stream it yet\n if (!event.query.promise) {\n return\n }\n if (queryStream.isClosed()) {\n console.warn(\n `tried to stream query ${event.query.queryHash} after stream was already closed`,\n )\n return\n }\n const dehydratedQuery = queryDehydrate(queryClient, {\n ...dehydrateOptions,\n shouldDehydrateQuery: (query) => {\n if (query.queryHash !== event.query.queryHash) {\n return false\n }\n\n return (\n (ogClientOptions.dehydrate?.shouldDehydrateQuery?.(query) ??\n true) &&\n (dehydrateOptions?.shouldDehydrateQuery?.(query) ?? true)\n )\n },\n })\n\n if (dehydratedQuery.queries.length === 0) {\n return\n }\n\n sentQueries.add(event.query.queryHash)\n queryStream.enqueue(dehydratedQuery)\n })\n // on the client\n } else {\n router.options.hydrate = async (dehydrated: DehydratedRouterQueryState) => {\n await ogHydrate?.(dehydrated)\n // hydrate the query client with the dehydrated data (if it was dehydrated on the server)\n if (dehydrated.dehydratedQueryClient) {\n queryHydrate(\n queryClient,\n dehydrated.dehydratedQueryClient,\n hydrateOptions,\n )\n }\n\n // read the query stream and hydrate the queries as they come in\n const reader = dehydrated.queryStream.getReader()\n reader\n .read()\n .then(async function handle({ done, value }) {\n queryHydrate(queryClient, value, hydrateOptions)\n if (done) {\n return\n }\n const result = await reader.read()\n return handle(result)\n })\n .catch((err) => {\n console.error('Error reading query stream:', err)\n })\n }\n if (handleRedirects) {\n const ogMutationCacheConfig = queryClient.getMutationCache().config\n queryClient.getMutationCache().config = {\n ...ogMutationCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogMutationCacheConfig.onError?.(error, ...rest)\n },\n }\n\n const ogQueryCacheConfig = queryClient.getQueryCache().config\n queryClient.getQueryCache().config = {\n ...ogQueryCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogQueryCacheConfig.onError?.(error, ...rest)\n },\n }\n }\n }\n}\n\ntype PushableStream = {\n stream: ReadableStream\n enqueue: (chunk: unknown) => void\n close: () => void\n isClosed: () => boolean\n error: (err: unknown) => void\n}\n\nfunction createPushableStream(): PushableStream {\n let controllerRef: ReadableStreamDefaultController\n const stream = new ReadableStream({\n start(controller) {\n controllerRef = controller\n },\n })\n let _isClosed = false\n\n return {\n stream,\n enqueue: (chunk) => controllerRef.enqueue(chunk),\n close: () => {\n controllerRef.close()\n _isClosed = true\n },\n isClosed: () => _isClosed,\n error: (err: unknown) => controllerRef.error(err),\n }\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n dehydrate as queryDehydrate,\n hydrate as queryHydrate,\n} from '@tanstack/query-core'\nimport { isRedirect } from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type { AnyRouter } from '@tanstack/router-core'\nimport type {\n DehydrateOptions,\n HydrateOptions,\n QueryClient,\n DehydratedState as QueryDehydratedState,\n} from '@tanstack/query-core'\n\nexport type RouterSsrQueryOptions<TRouter extends AnyRouter> = {\n router: TRouter\n queryClient: QueryClient\n dehydrateOptions?: DehydrateOptions\n hydrateOptions?: HydrateOptions\n\n /**\n * If `true`, the QueryClient will handle errors thrown by `redirect()` inside of mutations and queries.\n *\n * @default true\n * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction)\n */\n handleRedirects?: boolean\n}\n\ntype DehydratedRouterQueryState = {\n dehydratedQueryClient?: QueryDehydratedState\n queryStream: ReadableStream<QueryDehydratedState>\n}\n\nexport function setupCoreRouterSsrQueryIntegration<TRouter extends AnyRouter>({\n router,\n queryClient,\n dehydrateOptions,\n hydrateOptions,\n handleRedirects = true,\n}: RouterSsrQueryOptions<TRouter>) {\n const ogHydrate = router.options.hydrate\n const ogDehydrate = router.options.dehydrate\n\n if (isServer ?? router.isServer) {\n const sentQueries = new Set<string>()\n const queryStream = createPushableStream()\n let unsubscribe: (() => void) | undefined = undefined\n let cleanupRegistered = false\n let tornDown = false\n\n const teardown = () => {\n if (tornDown) return\n tornDown = true\n try {\n unsubscribe?.()\n } catch {\n // ignore\n }\n unsubscribe = undefined\n try {\n if (!queryStream.isClosed()) queryStream.close()\n } catch {\n // ignore\n }\n // Cancel any in-flight queries and clear the cache. Removing queries\n // cancels their gcTime setTimeout handles which would otherwise pin\n // the queryClient (and transitively the router via router.context)\n // alive for the full gcTime window (default 5min) per SSR request.\n try {\n queryClient.cancelQueries()\n } catch {\n // ignore\n }\n try {\n queryClient.clear()\n } catch {\n // ignore\n }\n sentQueries.clear()\n }\n\n // Register teardown as soon as SSR attaches. attachRouterServerSsrUtils()\n // runs before router.load(), so this covers redirects/errors thrown before\n // router.options.dehydrate() can run.\n const registerCleanup = (serverSsr = router.serverSsr) => {\n if (cleanupRegistered) return\n if (!serverSsr) return\n serverSsr.onCleanup(teardown)\n cleanupRegistered = true\n }\n router.serverSsrLifecycle = {\n ...router.serverSsrLifecycle,\n onServerSsrAttach: [\n ...(router.serverSsrLifecycle?.onServerSsrAttach ?? []),\n registerCleanup,\n ],\n }\n\n router.options.dehydrate =\n async (): Promise<DehydratedRouterQueryState> => {\n router.serverSsr!.onRenderFinished(() => {\n if (!queryStream.isClosed()) queryStream.close()\n unsubscribe?.()\n unsubscribe = undefined\n })\n const ogDehydrated = await ogDehydrate?.()\n\n const dehydratedRouter = {\n ...ogDehydrated,\n // prepare the stream for queries coming up during rendering\n queryStream: queryStream.stream,\n }\n\n const dehydratedQueryClient = queryDehydrate(\n queryClient,\n dehydrateOptions,\n )\n if (dehydratedQueryClient.queries.length > 0) {\n dehydratedQueryClient.queries.forEach((query) => {\n sentQueries.add(query.queryHash)\n })\n dehydratedRouter.dehydratedQueryClient = dehydratedQueryClient\n }\n\n return dehydratedRouter\n }\n\n const ogClientOptions = queryClient.getDefaultOptions()\n queryClient.setDefaultOptions({\n ...ogClientOptions,\n dehydrate: {\n shouldDehydrateQuery: () => true,\n ...ogClientOptions.dehydrate,\n },\n })\n\n unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n // before rendering starts, we do not stream individual queries\n // instead we dehydrate the entire query client in router's dehydrate()\n // if attachRouterServerSsrUtils() has not been called yet, `router.serverSsr` will be undefined and we also do not stream\n if (!router.serverSsr?.isDehydrated()) {\n return\n }\n if (sentQueries.has(event.query.queryHash)) {\n return\n }\n // promise not yet set on the query, so we cannot stream it yet\n if (!event.query.promise) {\n return\n }\n if (queryStream.isClosed()) {\n console.warn(\n `tried to stream query ${event.query.queryHash} after stream was already closed`,\n )\n return\n }\n const dehydratedQuery = queryDehydrate(queryClient, {\n ...dehydrateOptions,\n shouldDehydrateQuery: (query) => {\n if (query.queryHash !== event.query.queryHash) {\n return false\n }\n\n return (\n (ogClientOptions.dehydrate?.shouldDehydrateQuery?.(query) ??\n true) &&\n (dehydrateOptions?.shouldDehydrateQuery?.(query) ?? true)\n )\n },\n })\n\n if (dehydratedQuery.queries.length === 0) {\n return\n }\n\n sentQueries.add(event.query.queryHash)\n queryStream.enqueue(dehydratedQuery)\n })\n // on the client\n } else {\n router.options.hydrate = async (dehydrated: DehydratedRouterQueryState) => {\n await ogHydrate?.(dehydrated)\n // hydrate the query client with the dehydrated data (if it was dehydrated on the server)\n if (dehydrated.dehydratedQueryClient) {\n queryHydrate(\n queryClient,\n dehydrated.dehydratedQueryClient,\n hydrateOptions,\n )\n }\n\n // read the query stream and hydrate the queries as they come in\n const reader = dehydrated.queryStream.getReader()\n reader\n .read()\n .then(async function handle({ done, value }) {\n queryHydrate(queryClient, value, hydrateOptions)\n if (done) {\n return\n }\n const result = await reader.read()\n return handle(result)\n })\n .catch((err) => {\n console.error('Error reading query stream:', err)\n })\n }\n if (handleRedirects) {\n const ogMutationCacheConfig = queryClient.getMutationCache().config\n queryClient.getMutationCache().config = {\n ...ogMutationCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogMutationCacheConfig.onError?.(error, ...rest)\n },\n }\n\n const ogQueryCacheConfig = queryClient.getQueryCache().config\n queryClient.getQueryCache().config = {\n ...ogQueryCacheConfig,\n onError: (error, ...rest) => {\n if (isRedirect(error)) {\n error.options._fromLocation = router.stores.location.get()\n return router.navigate(router.resolveRedirect(error).options)\n }\n\n return ogQueryCacheConfig.onError?.(error, ...rest)\n },\n }\n }\n }\n}\n\ntype PushableStream = {\n stream: ReadableStream\n enqueue: (chunk: unknown) => void\n close: () => void\n isClosed: () => boolean\n error: (err: unknown) => void\n}\n\nfunction createPushableStream(): PushableStream {\n let controllerRef: ReadableStreamDefaultController\n const stream = new ReadableStream({\n start(controller) {\n controllerRef = controller\n },\n })\n let _isClosed = false\n\n return {\n stream,\n enqueue: (chunk) => {\n if (!_isClosed) controllerRef.enqueue(chunk)\n },\n close: () => {\n if (_isClosed) return\n controllerRef.close()\n _isClosed = true\n },\n isClosed: () => _isClosed,\n error: (err: unknown) => {\n if (_isClosed) return\n _isClosed = true\n controllerRef.error(err)\n },\n }\n}\n"],"mappings":"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"} |
+3
-3
| { | ||
| "name": "@tanstack/router-ssr-query-core", | ||
| "version": "1.169.0", | ||
| "version": "1.169.1", | ||
| "description": "Modern and scalable routing for React applications", | ||
@@ -52,3 +52,3 @@ "author": "Tanner Linsley", | ||
| "devDependencies": { | ||
| "@tanstack/router-core": ">=1.170.0", | ||
| "@tanstack/router-core": ">=1.171.7", | ||
| "@tanstack/query-core": ">=5.90.0", | ||
@@ -71,3 +71,3 @@ "vite": "*" | ||
| "test:types:ts60": "tsc", | ||
| "test:unit": "exit 0; vitest", | ||
| "test:unit": "vitest", | ||
| "test:unit:dev": "pnpm run test:unit --watch", | ||
@@ -74,0 +74,0 @@ "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", |
+61
-3
@@ -49,6 +49,57 @@ import { | ||
| let unsubscribe: (() => void) | undefined = undefined | ||
| let cleanupRegistered = false | ||
| let tornDown = false | ||
| const teardown = () => { | ||
| if (tornDown) return | ||
| tornDown = true | ||
| try { | ||
| unsubscribe?.() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| unsubscribe = undefined | ||
| try { | ||
| if (!queryStream.isClosed()) queryStream.close() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| // Cancel any in-flight queries and clear the cache. Removing queries | ||
| // cancels their gcTime setTimeout handles which would otherwise pin | ||
| // the queryClient (and transitively the router via router.context) | ||
| // alive for the full gcTime window (default 5min) per SSR request. | ||
| try { | ||
| queryClient.cancelQueries() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| try { | ||
| queryClient.clear() | ||
| } catch { | ||
| // ignore | ||
| } | ||
| sentQueries.clear() | ||
| } | ||
| // Register teardown as soon as SSR attaches. attachRouterServerSsrUtils() | ||
| // runs before router.load(), so this covers redirects/errors thrown before | ||
| // router.options.dehydrate() can run. | ||
| const registerCleanup = (serverSsr = router.serverSsr) => { | ||
| if (cleanupRegistered) return | ||
| if (!serverSsr) return | ||
| serverSsr.onCleanup(teardown) | ||
| cleanupRegistered = true | ||
| } | ||
| router.serverSsrLifecycle = { | ||
| ...router.serverSsrLifecycle, | ||
| onServerSsrAttach: [ | ||
| ...(router.serverSsrLifecycle?.onServerSsrAttach ?? []), | ||
| registerCleanup, | ||
| ], | ||
| } | ||
| router.options.dehydrate = | ||
| async (): Promise<DehydratedRouterQueryState> => { | ||
| router.serverSsr!.onRenderFinished(() => { | ||
| queryStream.close() | ||
| if (!queryStream.isClosed()) queryStream.close() | ||
| unsubscribe?.() | ||
@@ -208,4 +259,7 @@ unsubscribe = undefined | ||
| stream, | ||
| enqueue: (chunk) => controllerRef.enqueue(chunk), | ||
| enqueue: (chunk) => { | ||
| if (!_isClosed) controllerRef.enqueue(chunk) | ||
| }, | ||
| close: () => { | ||
| if (_isClosed) return | ||
| controllerRef.close() | ||
@@ -215,4 +269,8 @@ _isClosed = true | ||
| isClosed: () => _isClosed, | ||
| error: (err: unknown) => controllerRef.error(err), | ||
| error: (err: unknown) => { | ||
| if (_isClosed) return | ||
| _isClosed = true | ||
| controllerRef.error(err) | ||
| }, | ||
| } | ||
| } |
46728
21.13%571
29.19%