diff --git a/docs/framework/react/api/router/RouterOptionsType.md b/docs/framework/react/api/router/RouterOptionsType.md index d3c6f4ad96..5de8c3bdaa 100644 --- a/docs/framework/react/api/router/RouterOptionsType.md +++ b/docs/framework/react/api/router/RouterOptionsType.md @@ -276,23 +276,6 @@ const router = createRouter({ - Type: `(err: TSerializedError) => unknown` - This method is called to define how errors are deserialized from the router's dehydrated state. -### `transformer` property - -- Type: `RouterTransformer` -- Optional -- The transformer that will be used when sending data between the server and the client during SSR. -- Defaults to a very lightweight transformer that supports a few basic types. See the [SSR guide](../../guide/ssr.md) for more information. - -#### `transformer.stringify` method - -- Type: `(obj: unknown) => string` -- This method is called when stringifying data to be sent to the client. - -#### `transformer.parse` method - -- Type: `(str: string) => unknown` -- This method is called when parsing the string encoded by the server. - ### `trailingSlash` property - Type: `'always' | 'never' | 'preserve'` diff --git a/docs/framework/react/guide/ssr.md b/docs/framework/react/guide/ssr.md index 8959423a8b..997d918c7d 100644 --- a/docs/framework/react/guide/ssr.md +++ b/docs/framework/react/guide/ssr.md @@ -201,28 +201,32 @@ This pattern can be useful for pages that have slow or high-latency data fetchin Streaming dehydration/hydration is an advanced pattern that goes beyond markup and allows you to dehydrate and stream any supporting data from the server to the client and rehydrate it on arrival. This is useful for applications that may need to further use/manage the underlying data that was used to render the initial markup on the server. -## Data Transformers +## Data Serialization -When using SSR, data passed between the server and the client must be serialized before it is sent across network-boundaries. By default, TanStack Router will serialize data using a very lightweight serializer that supports a few basic types beyond JSON.stringify/JSON.parse. +When using SSR, data passed between the server and the client must be serialized before it is sent across network-boundaries. TanStack Router handles this serialization using a very lightweight serializer that supports common data types beyond JSON.stringify/JSON.parse. Out of the box, the following types are supported: -- `Date` - `undefined` +- `Date` +- `Error` +- `FormData` If you feel that there are other types that should be supported by default, please open an issue on the TanStack Router repository. -If you are using more complex data types like `Map`, `Set`, `BigInt`, etc, you may need to use a custom serializer to ensure that your type-definitions are accurate and your data is correctly serialized and deserialized. This is where the `transformer` option on `createRouter` comes in. +If you are using more complex data types like `Map`, `Set`, `BigInt`, etc, you may need to use a custom serializer to ensure that your type-definitions are accurate and your data is correctly serialized and deserialized. We are currently working on both a more robust serializer and a way to customize the serializer for your application. Open an issue if you are interested in helping out! + + -The Data Transformer API allows the usage of a custom serializer that can allow us to transparently use these data types when communicating across the network. +The Data Serialization API allows the usage of a custom serializer that can allow us to transparently use these data types when communicating across the network. -The following example shows usage with [SuperJSON](https://github.com/blitz-js/superjson), however, anything that implements [`Router Transformer`](../api/router/RouterOptionsType.md#transformer-property) can be used. + ```tsx import { SuperJSON } from 'superjson' const router = createRouter({ - transformer: SuperJSON, + serializer: SuperJSON, }) ``` diff --git a/e2e/start/basic/app/routes/stream.tsx b/e2e/start/basic/app/routes/stream.tsx index 7a6401ac3d..49383bdd55 100644 --- a/e2e/start/basic/app/routes/stream.tsx +++ b/e2e/start/basic/app/routes/stream.tsx @@ -1,5 +1,5 @@ import { Await, createFileRoute } from '@tanstack/react-router' -import React, { useEffect, useState } from 'react' +import { useEffect, useState } from 'react' export const Route = createFileRoute('/stream')({ component: Home, @@ -9,8 +9,11 @@ export const Route = createFileRoute('/stream')({ setTimeout(() => resolve('promise-data'), 150), ), stream: new ReadableStream({ - start(controller) { - controller.enqueue('stream-data') + async start(controller) { + for (let i = 0; i < 5; i++) { + await new Promise((resolve) => setTimeout(resolve, 200)) + controller.enqueue(`stream-data-${i} `) + } controller.close() }, }), @@ -18,40 +21,41 @@ export const Route = createFileRoute('/stream')({ }, }) +const decoder = new TextDecoder('utf-8') + function Home() { const { promise, stream } = Route.useLoaderData() - - const [streamData, setStreamData] = useState('') + const [streamData, setStreamData] = useState([]) useEffect(() => { async function fetchStream() { const reader = stream.getReader() - const decoder = new TextDecoder('utf-8') - let result = '' - let done = false + let chunk - while (!done) { - const { value, done: readerDone } = await reader.read() - done = readerDone - if (value) { - result += decoder.decode(value, { stream: !done }) - } + while (!(chunk = await reader.read()).done) { + const decoded = decoder.decode(chunk.value, { stream: !chunk.done }) + setStreamData((prev) => [...prev, decoded]) } - setStreamData(result) } fetchStream() }, []) return ( - ( -
-

{promiseData}

-

{streamData}

-
- )} - >
+ <> + ( +
+ {promiseData} +
+ {streamData.map((d) => ( +
{d}
+ ))} +
+
+ )} + /> + ) } diff --git a/examples/react/basic-ssr-streaming-file-based/src/router.tsx b/examples/react/basic-ssr-streaming-file-based/src/router.tsx index 07c33c6d9f..5fef76e635 100644 --- a/examples/react/basic-ssr-streaming-file-based/src/router.tsx +++ b/examples/react/basic-ssr-streaming-file-based/src/router.tsx @@ -1,7 +1,7 @@ import { createRouter as createReactRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' -import SuperJSON from 'superjson' +// import SuperJSON from 'superjson' export function createRouter() { return createReactRouter({ @@ -10,7 +10,7 @@ export function createRouter() { head: '', }, defaultPreload: 'intent', - transformer: SuperJSON, + // serializer: SuperJSON, }) } diff --git a/package.json b/package.json index 390493a1fa..e255fbb52b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "eslint-plugin-unused-imports": "^4.1.4", "tinyglobby": "^0.2.10", "jsdom": "^25.0.1", - "nx": "^20.3.0", + "nx": "^20.3.3", "prettier": "^3.4.2", "publint": "^0.2.12", "react": "^18.3.1", @@ -65,6 +65,8 @@ }, "pnpm": { "overrides": { + "react": "$react", + "react-dom": "$react-dom", "eslint": "$eslint", "vite": "$vite", "@tanstack/react-query": "5.62.3", diff --git a/packages/react-router-with-query/src/index.tsx b/packages/react-router-with-query/src/index.tsx index 255cb0d536..4291c2ee98 100644 --- a/packages/react-router-with-query/src/index.tsx +++ b/packages/react-router-with-query/src/index.tsx @@ -49,7 +49,7 @@ export function routerWithQueryClient( } } else { // On the client, pick up the deferred data from the stream - const dehydratedClient = router.getStreamedValue( + const dehydratedClient = router.clientSsr!.getStreamedValue( '__QueryClient__' + hash(options.queryKey), ) @@ -75,7 +75,7 @@ export function routerWithQueryClient( ) { streamedQueryKeys.add(hash(options.queryKey)) - router.streamValue( + router.serverSsr!.streamValue( '__QueryClient__' + hash(options.queryKey), dehydrate(queryClient, { shouldDehydrateMutation: () => false, diff --git a/packages/react-router/src/Match.tsx b/packages/react-router/src/Match.tsx index b0602e646a..dd5e4f0ddd 100644 --- a/packages/react-router/src/Match.tsx +++ b/packages/react-router/src/Match.tsx @@ -10,7 +10,6 @@ import { createControlledPromise, pick } from './utils' import { CatchNotFound, isNotFound } from './not-found' import { isRedirect } from './redirects' import { matchContext } from './matchContext' -import { defaultDeserializeError, isServerSideError } from './isServerSideError' import { SafeFragment } from './SafeFragment' import { renderRouteNotFound } from './renderRouteNotFound' import { rootRouteId } from './root' @@ -157,19 +156,8 @@ export const MatchInner = React.memo(function MatchInnerImpl({ ErrorComponent if (match.status === 'notFound') { - let error: unknown - if (isServerSideError(match.error)) { - const deserializeError = - router.options.errorSerializer?.deserialize ?? defaultDeserializeError - - error = deserializeError(match.error.data) - } else { - error = match.error - } - - invariant(isNotFound(error), 'Expected a notFound error') - - return renderRouteNotFound(router, route, error) + invariant(isNotFound(match.error), 'Expected a notFound error') + return renderRouteNotFound(router, route, match.error) } if (match.status === 'redirected') { @@ -201,13 +189,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({ ) } - if (isServerSideError(match.error)) { - const deserializeError = - router.options.errorSerializer?.deserialize ?? defaultDeserializeError - throw deserializeError(match.error.data) - } else { - throw match.error - } + throw match.error } if (match.status === 'pending') { @@ -241,14 +223,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({ throw router.getMatch(match.id)?.loadPromise } - return ( - <> - {out} - {router.AfterEachMatch ? ( - - ) : null} - - ) + return out }) export const Outlet = React.memo(function OutletImpl() { diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx index 324ac95686..a70ed9e9a8 100644 --- a/packages/react-router/src/Matches.tsx +++ b/packages/react-router/src/Matches.tsx @@ -219,7 +219,7 @@ export function Matches() { // Do not render a root Suspense during SSR or hydrating from SSR const ResolvedSuspense = - router.isServer || (typeof document !== 'undefined' && window.__TSR__) + router.isServer || (typeof document !== 'undefined' && router.clientSsr) ? SafeFragment : React.Suspense diff --git a/packages/react-router/src/RouterProvider.tsx b/packages/react-router/src/RouterProvider.tsx index e5b40ebaa2..59f8e53347 100644 --- a/packages/react-router/src/RouterProvider.tsx +++ b/packages/react-router/src/RouterProvider.tsx @@ -54,8 +54,6 @@ export type BuildLocationFn = < }, ) => ParsedLocation -export type InjectedHtmlEntry = string | (() => Promise | string) - export function RouterContextProvider< TRouter extends AnyRouter = RegisteredRouter, TDehydrated extends Record = Record, @@ -79,7 +77,9 @@ export function RouterContextProvider< const routerContext = getRouterContext() const provider = ( - {children} + + {children} + ) if (router.options.Wrap) { diff --git a/packages/react-router/src/ScriptOnce.tsx b/packages/react-router/src/ScriptOnce.tsx index 4a851a7e6a..2ad9ea23b2 100644 --- a/packages/react-router/src/ScriptOnce.tsx +++ b/packages/react-router/src/ScriptOnce.tsx @@ -1,25 +1,17 @@ import jsesc from 'jsesc' -import { useRouter } from './useRouter' export function ScriptOnce({ children, log, - sync, }: { children: string log?: boolean sync?: boolean }) { - const router = useRouter() if (typeof document !== 'undefined') { return null } - if (!sync) { - router.injectScript(children, { logScript: log }) - return null - } - return ( `, - ) - } - - streamedKeys: Set = new Set() - - getStreamedValue = (key: string): T | undefined => { - if (this.isServer) { - return undefined - } - - const streamedValue = window.__TSR__?.streamedValues[key] - - if (!streamedValue) { - return - } - - if (!streamedValue.parsed) { - streamedValue.parsed = this.options.transformer.parse(streamedValue.value) - } - - return streamedValue.parsed - } - - streamValue = (key: string, value: any) => { - warning( - !this.streamedKeys.has(key), - 'Key has already been streamed: ' + key, - ) - - this.streamedKeys.add(key) - this.injectScript( - `__TSR__.streamedValues['${key}'] = { value: ${this.serializer?.(this.options.transformer.stringify(value))}}`, - ) + clientSsr?: { + getStreamedValue: (key: string) => T | undefined } _handleNotFound = ( diff --git a/packages/react-router/src/routerContext.tsx b/packages/react-router/src/routerContext.tsx index 1e5ed33540..8c54b1dc44 100644 --- a/packages/react-router/src/routerContext.tsx +++ b/packages/react-router/src/routerContext.tsx @@ -1,5 +1,11 @@ import * as React from 'react' -import type { Router } from './router' +import type { AnyRouter, Router } from './router' + +declare global { + interface Window { + __TSR_ROUTER_CONTEXT__?: React.Context + } +} const routerContext = React.createContext>(null!) diff --git a/packages/react-router/src/serializer.ts b/packages/react-router/src/serializer.ts new file mode 100644 index 0000000000..f315cdec33 --- /dev/null +++ b/packages/react-router/src/serializer.ts @@ -0,0 +1,24 @@ +export interface StartSerializer { + stringify: (obj: unknown) => string + parse: (str: string) => unknown + encode: (value: T) => T + decode: (value: T) => T +} + +export type SerializerStringifyBy = T extends TSerializable + ? T + : T extends (...args: Array) => any + ? 'Function is not serializable' + : { [K in keyof T]: SerializerStringifyBy } + +export type SerializerParseBy = T extends TSerializable + ? T + : T extends React.JSX.Element + ? ReadableStream + : { [K in keyof T]: SerializerParseBy } + +export type Serializable = Date | undefined | Error | FormData + +export type SerializerStringify = SerializerStringifyBy + +export type SerializerParse = SerializerParseBy diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx index 8f6ffcb67a..c14da8e9e0 100644 --- a/packages/react-router/tests/router.test.tsx +++ b/packages/react-router/tests/router.test.tsx @@ -743,17 +743,6 @@ describe('router rendering stability', () => { }) }) -describe('transformer functions are defined', () => { - it('should have default transformer functions', () => { - const rootRoute = createRootRoute({}) - const routeTree = rootRoute.addChildren([]) - const router = createRouter({ routeTree }) - - expect(router.options.transformer.parse).toBeInstanceOf(Function) - expect(router.options.transformer.stringify).toBeInstanceOf(Function) - }) -}) - describe('router matches URLs to route definitions', () => { it('solo splat route matches index route', async () => { const { router } = createTestRouter({ diff --git a/packages/react-router/tsconfig.json b/packages/react-router/tsconfig.json index 2c71ee70f6..372bb7b491 100644 --- a/packages/react-router/tsconfig.json +++ b/packages/react-router/tsconfig.json @@ -4,11 +4,5 @@ "jsx": "react-jsx", "jsxImportSource": "react" }, - "include": [ - "src", - "tests", - "vite.config.ts", - "eslint.config.ts", - "../start/src/client/DehydrateRouter.tsx" - ] + "include": ["src", "tests", "vite.config.ts", "eslint.config.ts"] } diff --git a/packages/start-client/package.json b/packages/start-client/package.json index b82523dada..c4adbc4930 100644 --- a/packages/start-client/package.json +++ b/packages/start-client/package.json @@ -61,17 +61,16 @@ "node": ">=12" }, "dependencies": { - "@tanstack/react-router": "workspace:^", "@tanstack/react-cross-context": "workspace:^", - "tiny-invariant": "^1.3.3", + "@tanstack/react-router": "workspace:^", "jsesc": "^3.0.2", + "tiny-invariant": "^1.3.3", "vinxi": "^0.5.1" }, "devDependencies": { + "@testing-library/react": "^16.1.0", "@types/jsesc": "^3.0.3", - "@vitejs/plugin-react": "^4.3.4", - "esbuild": "^0.24.2", - "@testing-library/react": "^16.1.0" + "@vitejs/plugin-react": "^4.3.4" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", diff --git a/packages/start-client/src/DehydrateRouter.tsx b/packages/start-client/src/DehydrateRouter.tsx deleted file mode 100644 index 4d227858e8..0000000000 --- a/packages/start-client/src/DehydrateRouter.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function DehydrateRouter() { - return null -} diff --git a/packages/start-client/src/Meta.tsx b/packages/start-client/src/Meta.tsx index 54e1bdb72e..ebfa96b811 100644 --- a/packages/start-client/src/Meta.tsx +++ b/packages/start-client/src/Meta.tsx @@ -1,9 +1,6 @@ -import { ScriptOnce, useRouter, useRouterState } from '@tanstack/react-router' +import { useRouter, useRouterState } from '@tanstack/react-router' import * as React from 'react' -import jsesc from 'jsesc' -import { Context } from '@tanstack/react-cross-context' import { Asset } from './Asset' -import minifiedScript from './tsrScript?script-string' import type { RouterManagedTag } from '@tanstack/react-router' export const useMeta = () => { @@ -81,7 +78,7 @@ export const useMeta = () => { state.matches .map((match) => router.looseRoutesById[match.routeId]!) .forEach((route) => - router.manifest?.routes[route.id]?.preloads + router.ssr?.manifest?.routes[route.id]?.preloads ?.filter(Boolean) .forEach((preload) => { preloadMeta.push({ @@ -108,32 +105,13 @@ export const useMeta = () => { } export const useMetaElements = () => { - const router = useRouter() const meta = useMeta() - const dehydratedCtx = React.useContext( - Context.get('TanStackRouterHydrationContext', {}), - ) - return ( <> - {meta.map((asset, i) => ( + {meta.map((asset) => ( ))} - <> - - - ) } diff --git a/packages/start-client/src/Scripts.tsx b/packages/start-client/src/Scripts.tsx index 19d6b305d3..9865f1db2b 100644 --- a/packages/start-client/src/Scripts.tsx +++ b/packages/start-client/src/Scripts.tsx @@ -1,4 +1,4 @@ -import { useRouter, useRouterState } from '@tanstack/react-router' +import { useRouter, useRouterState, warning } from '@tanstack/react-router' import { Asset } from './Asset' import type { RouterManagedTag } from '@tanstack/react-router' @@ -8,11 +8,17 @@ export const Scripts = () => { const assetScripts = useRouterState({ select: (state) => { const assetScripts: Array = [] + const manifest = router.ssr?.manifest + + if (!manifest) { + warning(false, ' found no manifest') + return [] + } state.matches .map((match) => router.looseRoutesById[match.routeId]!) .forEach((route) => - router.manifest?.routes[route.id]?.assets + manifest.routes[route.id]?.assets ?.filter((d) => d.tag === 'script') .forEach((asset) => { assetScripts.push({ diff --git a/packages/start-client/src/StartClient.tsx b/packages/start-client/src/StartClient.tsx index 499d81d161..8cf0378cb7 100644 --- a/packages/start-client/src/StartClient.tsx +++ b/packages/start-client/src/StartClient.tsx @@ -1,11 +1,10 @@ import { RouterProvider } from '@tanstack/react-router' -import { afterHydrate } from './serialization' +import { hydrate } from './ssr-client' import type { AnyRouter } from '@tanstack/react-router' export function StartClient(props: { router: AnyRouter }) { if (!props.router.state.matches.length) { - props.router.hydrate() - afterHydrate({ router: props.router }) + hydrate(props.router) } return diff --git a/packages/start-client/src/createMiddleware.ts b/packages/start-client/src/createMiddleware.ts index cdc4a09804..566a082b2c 100644 --- a/packages/start-client/src/createMiddleware.ts +++ b/packages/start-client/src/createMiddleware.ts @@ -2,10 +2,10 @@ import type { ConstrainValidator, Method } from './createServerFn' import type { Assign, Constrain, - DefaultTransformerStringify, Expand, ResolveValidatorInput, ResolveValidatorOutput, + SerializerStringify, } from '@tanstack/react-router' export type MergeAllMiddleware< @@ -111,7 +111,7 @@ export type MiddlewareServerNextFn = < TNewClientAfterContext = undefined, >(ctx?: { context?: TNewServerContext - sendContext?: DefaultTransformerStringify + sendContext?: SerializerStringify }) => Promise< ServerResultWithContext > @@ -148,7 +148,7 @@ export type MiddlewareClientNextFn = < TNewClientContext = undefined, >(ctx?: { context?: TNewClientContext - sendContext?: DefaultTransformerStringify + sendContext?: SerializerStringify headers?: HeadersInit }) => Promise> diff --git a/packages/start-client/src/createServerFn.ts b/packages/start-client/src/createServerFn.ts index 45f3e2a9ed..fb3723a51b 100644 --- a/packages/start-client/src/createServerFn.ts +++ b/packages/start-client/src/createServerFn.ts @@ -1,19 +1,15 @@ -import { - defaultTransformer, - isNotFound, - isRedirect, - warning, -} from '@tanstack/react-router' +import { isNotFound, isRedirect, warning } from '@tanstack/react-router' import { mergeHeaders } from './headers' import { globalMiddleware } from './registerGlobalMiddleware' +import { startSerializer } from './serializer' import type { AnyValidator, Constrain, - DefaultTransformerParse, - DefaultTransformerStringify, Expand, ResolveValidatorInput, - TransformerStringify, + SerializerParse, + SerializerStringify, + SerializerStringifyBy, Validator, } from '@tanstack/react-router' import type { @@ -81,8 +77,8 @@ export interface OptionalFetcherDataOptions export type FetcherData = TResponse extends JsonResponse - ? DefaultTransformerParse> - : DefaultTransformerParse + ? SerializerParse> + : SerializerParse export type RscStream = { __cacheState: T @@ -92,9 +88,7 @@ export type Method = 'GET' | 'POST' export type ServerFn = ( ctx: ServerFnCtx, -) => - | Promise> - | DefaultTransformerStringify +) => Promise> | SerializerStringify export interface ServerFnCtx { method: TMethod @@ -125,8 +119,8 @@ type ServerFnBaseOptions< functionId: string } -export type ValidatorTransformerStringify = Validator< - TransformerStringify< +export type ValidatorSerializerStringify = Validator< + SerializerStringifyBy< ResolveValidatorInput, Date | undefined | FormData >, @@ -135,7 +129,7 @@ export type ValidatorTransformerStringify = Validator< export type ConstrainValidator = unknown extends TValidator ? TValidator - : Constrain> + : Constrain> export interface ServerFnMiddleware { middleware: ( @@ -307,12 +301,12 @@ function extractFormDataContext(formData: FormData) { } try { - const context = defaultTransformer.parse(serializedContext) + const context = startSerializer.parse(serializedContext) return { context, data: formData, } - } catch (e) { + } catch { return { data: formData, } diff --git a/packages/start-client/src/index.tsx b/packages/start-client/src/index.tsx index a55f07395d..bbdb5c0dfe 100644 --- a/packages/start-client/src/index.tsx +++ b/packages/start-client/src/index.tsx @@ -62,7 +62,6 @@ export { globalMiddleware, } from './registerGlobalMiddleware' export { serverOnly, clientOnly } from './envOnly' -export { DehydrateRouter } from './DehydrateRouter' export { json } from './json' export { Meta } from './Meta' export { Scripts } from './Scripts' @@ -71,4 +70,16 @@ export { mergeHeaders } from './headers' export { renderRsc } from './renderRSC' export { useServerFn } from './useServerFn' export { serverFnFetcher } from './serverFnFetcher' -export { serializeLoaderData, AfterEachMatch } from './serialization' +export { + type DehydratedRouterState, + type DehydratedRouteMatch, + type DehydratedRouter, + type ClientExtractedBaseEntry, + type StartSsrGlobal, + type ClientExtractedEntry, + type SsrMatch, + type ClientExtractedPromise, + type ClientExtractedStream, + type ResolvePromiseState, +} from './ssr-client' +export { startSerializer } from './serializer' diff --git a/packages/start-client/src/serialization.tsx b/packages/start-client/src/serialization.tsx deleted file mode 100644 index 173329c1d9..0000000000 --- a/packages/start-client/src/serialization.tsx +++ /dev/null @@ -1,444 +0,0 @@ -import * as React from 'react' -import { - ScriptOnce, - TSR_DEFERRED_PROMISE, - createControlledPromise, - defer, - isPlainArray, - isPlainObject, - pick, - useRouter, -} from '@tanstack/react-router' -import jsesc from 'jsesc' -import invariant from 'tiny-invariant' -import type { - AnyRouteMatch, - AnyRouter, - ClientExtractedBaseEntry, - DeferredPromise, - StreamState, - TSRGlobalMatch, -} from '@tanstack/react-router' -import type { ResolvePromiseState } from './tsrScript' - -export interface ServerExtractedBaseEntry extends ClientExtractedBaseEntry { - dataType: '__beforeLoadContext' | 'loaderData' - id: number - matchIndex: number -} - -export interface ServerExtractedStream extends ServerExtractedBaseEntry { - type: 'stream' - streamState: StreamState -} - -export type ServerExtractedEntry = - | ServerExtractedStream - | ServerExtractedPromise -export interface ServerExtractedPromise extends ServerExtractedBaseEntry { - type: 'promise' - promise: DeferredPromise -} - -export function serializeLoaderData( - dataType: '__beforeLoadContext' | 'loaderData', - data: any, - ctx: { - match: AnyRouteMatch - router: AnyRouter - }, -) { - if (!ctx.router.isServer) { - return data - } - - ;(ctx.match as any).extracted = (ctx.match as any).extracted || [] - - const extracted = (ctx.match as any).extracted - - const replacedLoaderData = replaceBy(data, (value, path) => { - const type = - value instanceof ReadableStream - ? 'stream' - : value instanceof Promise - ? 'promise' - : undefined - - // If it's a stream, we need to tee it so we can read it multiple times - if (type === 'stream') { - const [copy1, copy2] = value.tee() - const entry: ServerExtractedStream = { - dataType, - type, - path, - id: extracted.length, - matchIndex: ctx.match.index, - streamState: createStreamState({ stream: copy2 }), - } - - extracted.push(entry) - return copy1 - } else if (type === 'promise') { - const deferredPromise = defer(value) - const entry: ServerExtractedPromise = { - dataType, - type, - path, - id: extracted.length, - matchIndex: ctx.match.index, - promise: deferredPromise, - } - extracted.push(entry) - } - - return value - }) - - return replacedLoaderData -} - -// Right after hydration and before the first render, we need to rehydrate each match -// This includes rehydrating the loaderData and also using the beforeLoadContext -// to reconstruct any context that was serialized on the server -export function afterHydrate({ router }: { router: AnyRouter }) { - router.state.matches.forEach((match) => { - const route = router.looseRoutesById[match.routeId]! - const dMatch = window.__TSR__?.matches[match.index] - if (dMatch) { - const parentMatch = router.state.matches[match.index - 1] - const parentContext = parentMatch?.context ?? router.options.context ?? {} - if (dMatch.__beforeLoadContext) { - match.__beforeLoadContext = router.options.transformer.parse( - dMatch.__beforeLoadContext, - ) as any - - match.context = { - ...parentContext, - ...match.context, - ...match.__beforeLoadContext, - } - } - - if (dMatch.loaderData) { - match.loaderData = router.options.transformer.parse(dMatch.loaderData) - } - - const extracted = dMatch.extracted - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (extracted) { - Object.entries(extracted).forEach(([_, ex]: any) => { - deepMutableSetByPath(match, ['loaderData', ...ex.path], ex.value) - }) - } - } - - const headFnContent = route.options.head?.({ - matches: router.state.matches, - match, - params: match.params, - loaderData: match.loaderData, - }) - - Object.assign(match, { - meta: headFnContent?.meta, - links: headFnContent?.links, - scripts: headFnContent?.scripts, - }) - }) -} - -export function AfterEachMatch(props: { match: any; matchIndex: number }) { - const router = useRouter() - - const fullMatch = router.state.matches[props.matchIndex]! - - if (!router.isServer) { - return null - } - - const extracted = (fullMatch as any).extracted as - | undefined - | Array - - const [serializedBeforeLoadData, serializedLoaderData] = ( - ['__beforeLoadContext', 'loaderData'] as const - ).map((dataType) => { - return extracted - ? extracted.reduce( - (acc: any, entry: ServerExtractedEntry) => { - if (entry.dataType !== dataType) { - return deepImmutableSetByPath( - acc, - ['temp', ...entry.path], - undefined, - ) - } - return acc - }, - { temp: fullMatch[dataType] }, - ).temp - : fullMatch[dataType] - }) - - if ( - serializedBeforeLoadData !== undefined || - serializedLoaderData !== undefined || - extracted?.length - ) { - const initCode = `__TSR__.initMatch(${jsesc( - { - index: props.matchIndex, - __beforeLoadContext: router.options.transformer.stringify( - serializedBeforeLoadData, - ), - loaderData: router.options.transformer.stringify(serializedLoaderData), - extracted: extracted - ? Object.fromEntries( - extracted.map((entry) => { - return [entry.id, pick(entry, ['type', 'path'])] - }), - ) - : {}, - } satisfies TSRGlobalMatch, - { - isScriptContext: true, - wrap: true, - json: true, - }, - )})` - - return ( - <> - - {extracted - ? extracted.map((d) => { - if (d.type === 'stream') { - return - } - - return - }) - : null} - - ) - } - - return null -} - -export function replaceBy( - obj: T, - cb: (value: any, path: Array) => any, - path: Array = [], -): T { - if (isPlainArray(obj)) { - return obj.map((value, i) => replaceBy(value, cb, [...path, `${i}`])) as any - } - - if (isPlainObject(obj)) { - // Do not allow objects with illegal - const newObj: any = {} - - for (const key in obj) { - newObj[key] = replaceBy(obj[key], cb, [...path, key]) - } - - return newObj - } - - // // Detect classes, functions, and other non-serializable objects - // // and return undefined. Exclude some known types that are serializable - // if ( - // typeof obj === 'function' || - // (typeof obj === 'object' && - // ![Object, Promise, ReadableStream].includes((obj as any)?.constructor)) - // ) { - // console.info(obj) - // warning(false, `Non-serializable value ☝️ found at ${path.join('.')}`) - // return undefined as any - // } - - const newObj = cb(obj, path) - - if (newObj !== obj) { - return newObj - } - - return obj -} - -function DehydratePromise({ entry }: { entry: ServerExtractedPromise }) { - return ( -
- - - -
- ) -} - -function InnerDehydratePromise({ entry }: { entry: ServerExtractedPromise }) { - const router = useRouter() - if (entry.promise[TSR_DEFERRED_PROMISE].status === 'pending') { - throw entry.promise - } - - const code = `__TSR__.resolvePromise(${jsesc( - { - id: entry.id, - matchIndex: entry.matchIndex, - promiseState: entry.promise[TSR_DEFERRED_PROMISE], - } satisfies ResolvePromiseState, - { - isScriptContext: true, - wrap: true, - json: true, - }, - )})` - - router.injectScript(code) - - return <> -} - -function DehydrateStream({ entry }: { entry: ServerExtractedStream }) { - invariant(entry.streamState, 'StreamState should be defined') - const router = useRouter() - - return ( - { - const code = chunk - ? `__TSR__.matches[${entry.matchIndex}].extracted[${entry.id}].value.controller.enqueue(new TextEncoder().encode(${jsesc( - chunk.toString(), - { - isScriptContext: true, - wrap: true, - json: true, - }, - )}))` - : `__TSR__.matches[${entry.matchIndex}].extracted[${entry.id}].value.controller.close()` - - router.injectScript(code) - - return <> - }} - /> - ) -} - -// Readable stream with state is a stream that has a promise that resolves to the next chunk -function createStreamState({ - stream, -}: { - stream: ReadableStream -}): StreamState { - const streamState: StreamState = { - promises: [], - } - - const reader = stream.getReader() - - const read = (index: number): any => { - streamState.promises[index] = createControlledPromise() - - return reader.read().then(({ done, value }) => { - if (done) { - streamState.promises[index]!.resolve(null) - reader.releaseLock() - return - } - - streamState.promises[index]!.resolve(value) - - return read(index + 1) - }) - } - - read(0).catch((err: any) => { - console.error('stream read error', err) - }) - - return streamState -} - -function StreamChunks({ - streamState, - children, - __index = 0, -}: { - streamState: StreamState - children: (chunk: string | null) => React.JSX.Element - __index?: number -}) { - const promise = streamState.promises[__index] - - if (!promise) { - return null - } - - if (promise.status === 'pending') { - throw promise - } - - const chunk = promise.value! - - return ( - <> - {children(chunk)} -
- - - -
- - ) -} - -function deepImmutableSetByPath(obj: T, path: Array, value: any): T { - // immutable set by path retaining array and object references - if (path.length === 0) { - return value - } - - const [key, ...rest] = path - - if (Array.isArray(obj)) { - return obj.map((item, i) => { - if (i === Number(key)) { - return deepImmutableSetByPath(item, rest, value) - } - return item - }) as T - } - - if (isPlainObject(obj)) { - return { - ...obj, - [key!]: deepImmutableSetByPath((obj as any)[key!], rest, value), - } - } - - return obj -} - -function deepMutableSetByPath(obj: T, path: Array, value: any) { - // mutable set by path retaining array and object references - if (path.length === 1) { - ;(obj as any)[path[0]!] = value - } - - const [key, ...rest] = path - - if (Array.isArray(obj)) { - deepMutableSetByPath(obj[Number(key)], rest, value) - } else if (isPlainObject(obj)) { - deepMutableSetByPath((obj as any)[key!], rest, value) - } -} diff --git a/packages/react-router/src/transformer.ts b/packages/start-client/src/serializer.ts similarity index 56% rename from packages/react-router/src/transformer.ts rename to packages/start-client/src/serializer.ts index fad1fb2f89..0754b2b9f5 100644 --- a/packages/react-router/src/transformer.ts +++ b/packages/start-client/src/serializer.ts @@ -1,20 +1,14 @@ -import { isPlainObject } from './utils' +import { isPlainObject } from '@tanstack/react-router' +import type { StartSerializer } from '@tanstack/react-router' -export interface RouterTransformer { - stringify: (obj: unknown) => string - parse: (str: string) => unknown - encode: (value: T) => T - decode: (value: T) => T -} - -export const defaultTransformer: RouterTransformer = { +export const startSerializer: StartSerializer = { stringify: (value: any) => JSON.stringify(value, function replacer(key, val) { const ogVal = this[key] - const transformer = transformers.find((t) => t.stringifyCondition(ogVal)) + const serializer = serializers.find((t) => t.stringifyCondition(ogVal)) - if (transformer) { - return transformer.stringify(ogVal) + if (serializer) { + return serializer.stringify(ogVal) } return val @@ -23,10 +17,10 @@ export const defaultTransformer: RouterTransformer = { JSON.parse(value, function parser(key, val) { const ogVal = this[key] if (isPlainObject(ogVal)) { - const transformer = transformers.find((t) => t.parseCondition(ogVal)) + const serializer = serializers.find((t) => t.parseCondition(ogVal)) - if (transformer) { - return transformer.parse(ogVal) + if (serializer) { + return serializer.parse(ogVal) } } @@ -35,21 +29,21 @@ export const defaultTransformer: RouterTransformer = { encode: (value: any) => { // When encoding, dive first if (Array.isArray(value)) { - return value.map((v) => defaultTransformer.encode(v)) + return value.map((v) => startSerializer.encode(v)) } if (isPlainObject(value)) { return Object.fromEntries( Object.entries(value).map(([key, v]) => [ key, - defaultTransformer.encode(v), + startSerializer.encode(v), ]), ) } - const transformer = transformers.find((t) => t.stringifyCondition(value)) - if (transformer) { - return transformer.stringify(value) + const serializer = serializers.find((t) => t.stringifyCondition(value)) + if (serializer) { + return serializer.stringify(value) } return value @@ -57,21 +51,21 @@ export const defaultTransformer: RouterTransformer = { decode: (value: any) => { // Attempt transform first if (isPlainObject(value)) { - const transformer = transformers.find((t) => t.parseCondition(value)) - if (transformer) { - return transformer.parse(value) + const serializer = serializers.find((t) => t.parseCondition(value)) + if (serializer) { + return serializer.parse(value) } } if (Array.isArray(value)) { - return value.map((v) => defaultTransformer.decode(v)) + return value.map((v) => startSerializer.decode(v)) } if (isPlainObject(value)) { return Object.fromEntries( Object.entries(value).map(([key, v]) => [ key, - defaultTransformer.decode(v), + startSerializer.decode(v), ]), ) } @@ -80,7 +74,7 @@ export const defaultTransformer: RouterTransformer = { }, } -const createTransformer = ( +const createSerializer = ( key: TKey, check: (value: any) => value is TInput, toValue: (value: TInput) => TSerialized, @@ -94,10 +88,10 @@ const createTransformer = ( }) // Keep these ordered by predicted frequency -// Make sure to keep DefaultSerializeable in sync with these transformers -// Also, make sure that they are unit tested in transformer.test.tsx -const transformers = [ - createTransformer( +// Make sure to keep DefaultSerializeable in sync with these serializers +// Also, make sure that they are unit tested in serializer.test.tsx +const serializers = [ + createSerializer( // Key 'undefined', // Check @@ -107,7 +101,7 @@ const transformers = [ // From () => undefined, ), - createTransformer( + createSerializer( // Key 'date', // Check @@ -117,17 +111,22 @@ const transformers = [ // From (v) => new Date(v), ), - createTransformer( + createSerializer( // Key 'error', // Check (v): v is Error => v instanceof Error, // To - (v) => ({ ...v, message: v.message, stack: v.stack, cause: v.cause }), + (v) => ({ + ...v, + message: v.message, + stack: process.env.NODE_ENV === 'development' ? v.stack : undefined, + cause: v.cause, + }), // From (v) => Object.assign(new Error(v.message), v), ), - createTransformer( + createSerializer( // Key 'formData', // Check @@ -166,27 +165,3 @@ const transformers = [ }, ), ] as const - -export type TransformerStringify = T extends TSerializable - ? T - : T extends (...args: Array) => any - ? 'Function is not serializable' - : { [K in keyof T]: TransformerStringify } - -export type TransformerParse = T extends TSerializable - ? T - : T extends React.JSX.Element - ? ReadableStream - : { [K in keyof T]: TransformerParse } - -export type DefaultSerializable = Date | undefined | Error | FormData - -export type DefaultTransformerStringify = TransformerStringify< - T, - DefaultSerializable -> - -export type DefaultTransformerParse = TransformerParse< - T, - DefaultSerializable -> diff --git a/packages/start-client/src/serverFnFetcher.tsx b/packages/start-client/src/serverFnFetcher.tsx index e5de59b6b3..940f46fda0 100644 --- a/packages/start-client/src/serverFnFetcher.tsx +++ b/packages/start-client/src/serverFnFetcher.tsx @@ -1,10 +1,10 @@ import { - defaultTransformer, encode, isNotFound, isPlainObject, isRedirect, } from '@tanstack/react-router' +import { startSerializer } from './serializer' import type { MiddlewareOptions } from './createServerFn' export async function serverFnFetcher( @@ -37,7 +37,7 @@ export async function serverFnFetcher( if (first.method === 'GET') { // If the method is GET, we need to move the payload to the query string const encodedPayload = encode({ - payload: defaultTransformer.stringify({ + payload: startSerializer.stringify({ data: first.data, context: first.context, }), @@ -64,7 +64,7 @@ export async function serverFnFetcher( if (response.headers.get('content-type')?.includes('application/json')) { // Even though the response is JSON, we need to decode it // because the server may have transformed it - const json = defaultTransformer.decode(await response.json()) + const json = startSerializer.decode(await response.json()) // If the response is a redirect or not found, throw it // for the router to handle @@ -95,7 +95,7 @@ export async function serverFnFetcher( // If the response is JSON, return it parsed const contentType = response.headers.get('content-type') if (contentType && contentType.includes('application/json')) { - return defaultTransformer.decode(await response.json()) + return startSerializer.decode(await response.json()) } else { // Otherwise, return the text as a fallback // If the user wants more than this, they can pass a @@ -107,14 +107,14 @@ export async function serverFnFetcher( function getFetcherRequestOptions(opts: MiddlewareOptions) { if (opts.method === 'POST') { if (opts.data instanceof FormData) { - opts.data.set('__TSR_CONTEXT', defaultTransformer.stringify(opts.context)) + opts.data.set('__TSR_CONTEXT', startSerializer.stringify(opts.context)) return { body: opts.data, } } return { - body: defaultTransformer.stringify({ + body: startSerializer.stringify({ data: opts.data ?? null, context: opts.context, }), @@ -130,7 +130,7 @@ async function handleResponseErrors(response: Response) { const isJson = contentType && contentType.includes('application/json') if (isJson) { - throw defaultTransformer.decode(await response.json()) + throw startSerializer.decode(await response.json()) } throw new Error(await response.text()) diff --git a/packages/start-client/src/ssr-client.tsx b/packages/start-client/src/ssr-client.tsx new file mode 100644 index 0000000000..6a0a68f69a --- /dev/null +++ b/packages/start-client/src/ssr-client.tsx @@ -0,0 +1,219 @@ +import { isPlainObject } from '@tanstack/react-router' + +import invariant from 'tiny-invariant' + +import { startSerializer } from './serializer' +import type { + AnyRouter, + ControllablePromise, + DeferredPromiseState, + MakeRouteMatch, + Manifest, +} from '@tanstack/react-router' + +declare global { + interface Window { + __TSR_SSR__?: StartSsrGlobal + } +} + +export interface StartSsrGlobal { + matches: Array + streamedValues: Record< + string, + { + value: any + parsed: any + } + > + cleanScripts: () => void + dehydrated?: any + initMatch: (match: SsrMatch) => void + resolvePromise: (opts: { + matchId: string + id: string + promiseState: DeferredPromiseState + }) => void + injectChunk: (opts: { matchId: string; id: string; chunk: string }) => void + closeStream: (opts: { matchId: string; id: string }) => void +} + +export interface SsrMatch { + id: string + __beforeLoadContext?: string + loaderData?: string + error?: string + extracted: Record + updatedAt: MakeRouteMatch['updatedAt'] + status: MakeRouteMatch['status'] +} + +export type ClientExtractedEntry = + | ClientExtractedStream + | ClientExtractedPromise + +export interface ClientExtractedPromise extends ClientExtractedBaseEntry { + type: 'promise' + value?: ControllablePromise +} + +export interface ClientExtractedStream extends ClientExtractedBaseEntry { + type: 'stream' + value?: ReadableStream & { controller?: ReadableStreamDefaultController } +} + +export interface ClientExtractedBaseEntry { + type: string + path: Array +} + +export interface ResolvePromiseState { + matchId: string + id: number + promiseState: DeferredPromiseState +} + +export interface DehydratedRouterState { + dehydratedMatches: Array +} + +export type DehydratedRouteMatch = Pick< + MakeRouteMatch, + 'id' | 'status' | 'updatedAt' | 'loaderData' +> + +export interface DehydratedRouter { + manifest: Manifest | undefined + dehydratedData: any +} + +export function hydrate(router: AnyRouter) { + invariant( + window.__TSR_SSR__?.dehydrated, + 'Expected to find a dehydrated data on window.__TSR_SSR__.dehydrated... but we did not. Please file an issue!', + ) + + const { manifest, dehydratedData } = startSerializer.parse( + window.__TSR_SSR__.dehydrated, + ) as DehydratedRouter + + router.ssr = { + manifest, + serializer: startSerializer, + } + + router.clientSsr = { + getStreamedValue: (key: string): T | undefined => { + if (router.isServer) { + return undefined + } + + const streamedValue = window.__TSR_SSR__?.streamedValues[key] + + if (!streamedValue) { + return + } + + if (!streamedValue.parsed) { + streamedValue.parsed = router.ssr!.serializer.parse(streamedValue.value) + } + + return streamedValue.parsed + }, + } + + // Allow the user to handle custom hydration data + router.options.hydrate?.(dehydratedData) + + // Hydrate the router state + const matches = router.matchRoutes(router.state.location).map((match) => { + const route = router.looseRoutesById[match.routeId]! + + // Right after hydration and before the first render, we need to rehydrate each match + // This includes rehydrating the loaderData and also using the beforeLoadContext + // to reconstruct any context that was serialized on the server + + const dehydratedMatch = window.__TSR_SSR__!.matches.find( + (d) => d.id === match.id, + ) + + if (dehydratedMatch) { + Object.assign(match, dehydratedMatch) + + const parentMatch = router.state.matches[match.index - 1] + const parentContext = parentMatch?.context ?? router.options.context ?? {} + + // Handle beforeLoadContext + if (dehydratedMatch.__beforeLoadContext) { + match.__beforeLoadContext = router.ssr!.serializer.parse( + dehydratedMatch.__beforeLoadContext, + ) as any + + match.context = { + ...parentContext, + ...match.context, + ...match.__beforeLoadContext, + } + } + + // Handle loaderData + if (dehydratedMatch.loaderData) { + match.loaderData = router.ssr!.serializer.parse( + dehydratedMatch.loaderData, + ) + } + + // Handle error + if (dehydratedMatch.error) { + match.error = router.ssr!.serializer.parse(dehydratedMatch.error) + } + + // Handle extracted + Object.entries((match as any).extracted).forEach(([_, ex]: any) => { + deepMutableSetByPath(match, ['loaderData', ...ex.path], ex.value) + }) + } else { + Object.assign(match, { + status: 'success', + updatedAt: Date.now(), + }) + } + + const headFnContent = route.options.head?.({ + matches: router.state.matches, + match, + params: match.params, + loaderData: match.loaderData, + }) + + Object.assign(match, { + meta: headFnContent?.meta, + links: headFnContent?.links, + scripts: headFnContent?.scripts, + }) + + return match + }) + + router.__store.setState((s) => { + return { + ...s, + matches: matches, + } + }) +} + +function deepMutableSetByPath(obj: T, path: Array, value: any) { + // mutable set by path retaining array and object references + if (path.length === 1) { + ;(obj as any)[path[0]!] = value + } + + const [key, ...rest] = path + + if (Array.isArray(obj)) { + deepMutableSetByPath(obj[Number(key)], rest, value) + } else if (isPlainObject(obj)) { + deepMutableSetByPath((obj as any)[key!], rest, value) + } +} diff --git a/packages/react-router/tests/transformer.test.tsx b/packages/start-client/src/tests/transformer.test.tsx similarity index 98% rename from packages/react-router/tests/transformer.test.tsx rename to packages/start-client/src/tests/transformer.test.tsx index e4cf06110f..4e0f55694e 100644 --- a/packages/react-router/tests/transformer.test.tsx +++ b/packages/start-client/src/tests/transformer.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { defaultTransformer as tf } from '../src/transformer' +import { startSerializer as tf } from '../serializer' describe('transformer.stringify', () => { it('should stringify dates', () => { diff --git a/packages/start-client/src/tsrScript.ts b/packages/start-client/src/tsrScript.ts deleted file mode 100644 index dbaacb1244..0000000000 --- a/packages/start-client/src/tsrScript.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { - ControllablePromise, - DeferredPromiseState, - TSRGlobal, - TSRGlobalMatch, -} from '@tanstack/react-router' - -export interface ResolvePromiseState { - id: number - matchIndex: number - promiseState: DeferredPromiseState -} -interface StartTSRGlobal extends TSRGlobal { - queue: Array<() => boolean> - runQueue: () => void - initMatch: (match: TSRGlobalMatch) => void - resolvePromise: (p: ResolvePromiseState) => void -} - -declare module '@tanstack/react-router' { - interface Register { - __TSR__: StartTSRGlobal - } -} - -const __TSR__: StartTSRGlobal = { - matches: [], - streamedValues: {}, - queue: [], - runQueue: () => { - let changed = false as boolean - __TSR__.queue = __TSR__.queue.filter((fn) => { - if (fn()) { - changed = true - return false - } - return true - }) - if (changed) { - __TSR__.runQueue() - } - }, - initMatch: (match) => { - __TSR__.queue.push(() => { - if (!__TSR__.matches[match.index]) { - __TSR__.matches[match.index] = match - Object.entries(match.extracted).forEach(([id, ex]) => { - if (ex.type === 'stream') { - let controller - ex.value = new ReadableStream({ - start(c) { - controller = { - enqueue: (chunk: unknown) => { - try { - c.enqueue(chunk) - } catch {} - }, - close: () => { - try { - c.close() - } catch {} - }, - } - }, - }) - ex.value.controller = controller - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if (ex.type === 'promise') { - let resolve: ControllablePromise['reject'] | undefined - let reject: ControllablePromise['reject'] | undefined - - ex.value = new Promise((_resolve, _reject) => { - reject = _reject - resolve = _resolve - }) as ControllablePromise - ex.value.reject = reject! - ex.value.resolve = resolve! - } - }) - } - - return true - }) - - __TSR__.runQueue() - }, - resolvePromise: (p) => { - __TSR__.queue.push(() => { - const match = __TSR__.matches[p.matchIndex] - if (match) { - const ex = match.extracted[p.id] - if ( - ex && - ex.type === 'promise' && - ex.value && - p.promiseState.status === 'success' - ) { - ex.value.resolve(p.promiseState.data) - return true - } - } - return false - }) - - __TSR__.runQueue() - }, - cleanScripts: () => { - document.querySelectorAll('.tsr-once').forEach((el) => { - el.remove() - }) - }, -} - -window.__TSR__ = __TSR__ diff --git a/packages/start-client/tsconfig.json b/packages/start-client/tsconfig.json index 51dda9abf2..5f141a9ce5 100644 --- a/packages/start-client/tsconfig.json +++ b/packages/start-client/tsconfig.json @@ -4,5 +4,11 @@ "jsx": "react-jsx", "module": "esnext" }, - "include": ["src", "vite.config.ts"] + "include": [ + "src", + "vite.config.ts", + "src/tsrScript.ts", + "../start-server/src/ssr-server.ts", + "../start-server/src/vite-env.d.ts" + ] } diff --git a/packages/start-client/vite.config.ts b/packages/start-client/vite.config.ts index e05e5cc394..4691019d91 100644 --- a/packages/start-client/vite.config.ts +++ b/packages/start-client/vite.config.ts @@ -2,11 +2,10 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackViteConfig } from '@tanstack/config/vite' import react from '@vitejs/plugin-react' import packageJson from './package.json' -import minifyScriptPlugin from './vite-minify-plugin' import type { ViteUserConfig } from 'vitest/config' const config = defineConfig({ - plugins: [minifyScriptPlugin(), react()] as ViteUserConfig['plugins'], + plugins: [react()] as ViteUserConfig['plugins'], test: { name: packageJson.name, watch: false, diff --git a/packages/start-router-manifest/src/index.ts b/packages/start-router-manifest/src/index.ts index 028164eeb4..b907916c36 100644 --- a/packages/start-router-manifest/src/index.ts +++ b/packages/start-router-manifest/src/index.ts @@ -33,6 +33,7 @@ export function getFullRouterManifest() { src: vinxiClientManifest.inputs[vinxiClientManifest.handler]?.output.path, type: 'module', suppressHydrationWarning: true, + async: true, }, }) diff --git a/packages/start-server-functions-fetcher/src/index.tsx b/packages/start-server-functions-fetcher/src/index.tsx index 7a81e62b9f..98b44134c8 100644 --- a/packages/start-server-functions-fetcher/src/index.tsx +++ b/packages/start-server-functions-fetcher/src/index.tsx @@ -1,10 +1,10 @@ import { - defaultTransformer, encode, isNotFound, isPlainObject, isRedirect, } from '@tanstack/react-router' +import { startSerializer } from '@tanstack/start-client' import type { MiddlewareClientFnOptions } from '@tanstack/start-client' export async function serverFnFetcher( @@ -39,7 +39,7 @@ export async function serverFnFetcher( if (first.method === 'GET') { // If the method is GET, we need to move the payload to the query string const encodedPayload = encode({ - payload: defaultTransformer.stringify({ + payload: startSerializer.stringify({ data: first.data, context: first.context, }), @@ -66,7 +66,7 @@ export async function serverFnFetcher( if (response.headers.get('content-type')?.includes('application/json')) { // Even though the response is JSON, we need to decode it // because the server may have transformed it - const json = defaultTransformer.decode(await response.json()) + const json = startSerializer.decode(await response.json()) // If the response is a redirect or not found, throw it // for the router to handle @@ -97,7 +97,7 @@ export async function serverFnFetcher( // If the response is JSON, return it parsed const contentType = response.headers.get('content-type') if (contentType && contentType.includes('application/json')) { - return defaultTransformer.decode(await response.json()) + return startSerializer.decode(await response.json()) } else { // Otherwise, return the text as a fallback // If the user wants more than this, they can pass a @@ -109,14 +109,14 @@ export async function serverFnFetcher( function getFetcherRequestOptions(opts: MiddlewareClientFnOptions) { if (opts.method === 'POST') { if (opts.data instanceof FormData) { - opts.data.set('__TSR_CONTEXT', defaultTransformer.stringify(opts.context)) + opts.data.set('__TSR_CONTEXT', startSerializer.stringify(opts.context)) return { body: opts.data, } } return { - body: defaultTransformer.stringify({ + body: startSerializer.stringify({ data: opts.data ?? null, context: opts.context, }), @@ -132,7 +132,7 @@ async function handleResponseErrors(response: Response) { const isJson = contentType && contentType.includes('application/json') if (isJson) { - throw defaultTransformer.decode(await response.json()) + throw startSerializer.decode(await response.json()) } throw new Error(await response.text()) diff --git a/packages/start-server-functions-handler/package.json b/packages/start-server-functions-handler/package.json index 7d0f867c9f..06c3e0566e 100644 --- a/packages/start-server-functions-handler/package.json +++ b/packages/start-server-functions-handler/package.json @@ -62,6 +62,7 @@ }, "dependencies": { "@tanstack/react-router": "workspace:^", + "@tanstack/start-client": "workspace:^", "@tanstack/start-server": "workspace:^", "@vitejs/plugin-react": "^4.3.4", "tiny-invariant": "^1.3.3" diff --git a/packages/start-server-functions-handler/src/index.tsx b/packages/start-server-functions-handler/src/index.tsx index 9a5b962a42..7f96efd1b3 100644 --- a/packages/start-server-functions-handler/src/index.tsx +++ b/packages/start-server-functions-handler/src/index.tsx @@ -1,9 +1,4 @@ -import { - defaultTransformer, - isNotFound, - isPlainObject, - isRedirect, -} from '@tanstack/react-router' +import { isNotFound, isPlainObject, isRedirect } from '@tanstack/react-router' import invariant from 'tiny-invariant' import { eventHandler, @@ -11,6 +6,7 @@ import { getResponseStatus, toWebRequest, } from '@tanstack/start-server' +import { startSerializer } from '@tanstack/start-client' // @ts-expect-error import _serverFnManifest from 'tsr:server-fn-manifest' import type { H3Event } from '@tanstack/start-server' @@ -139,12 +135,12 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { } // If there's a payload, we need to parse it - return defaultTransformer.parse(search.payload) + return startSerializer.parse(search.payload) } // For non-form, non-get const jsonPayloadAsString = await request.text() - return defaultTransformer.parse(jsonPayloadAsString) + return startSerializer.parse(jsonPayloadAsString) })() const result = await action(arg) @@ -183,7 +179,7 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { } return new Response( - result !== undefined ? defaultTransformer.stringify(result) : undefined, + result !== undefined ? startSerializer.stringify(result) : undefined, { status: getResponseStatus(getEvent()), headers: { @@ -215,7 +211,7 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { console.error(error) console.info() - return new Response(defaultTransformer.stringify(error), { + return new Response(startSerializer.stringify(error), { status: 500, headers: { 'Content-Type': 'application/json', diff --git a/packages/start-server/package.json b/packages/start-server/package.json index 73385040a9..502bfecec1 100644 --- a/packages/start-server/package.json +++ b/packages/start-server/package.json @@ -72,6 +72,7 @@ }, "devDependencies": { "@types/jsesc": "^3.0.3", + "esbuild": "^0.19.12", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.7.2" diff --git a/packages/start-server/src/StartServer.tsx b/packages/start-server/src/StartServer.tsx index 47498313da..646ee5e450 100644 --- a/packages/start-server/src/StartServer.tsx +++ b/packages/start-server/src/StartServer.tsx @@ -1,34 +1,9 @@ import * as React from 'react' -import { Context } from '@tanstack/react-cross-context' import { RouterProvider } from '@tanstack/react-router' -import jsesc from 'jsesc' -import { AfterEachMatch } from '@tanstack/start-client' import type { AnyRouter } from '@tanstack/react-router' export function StartServer(props: { router: TRouter }) { - props.router.AfterEachMatch = AfterEachMatch - props.router.serializer = (value) => - jsesc(value, { - isScriptContext: true, - wrap: true, - json: true, - }) - - const hydrationContext = Context.get('TanStackRouterHydrationContext', {}) - - const hydrationCtxValue = React.useMemo( - () => ({ - router: props.router.dehydrate(), - payload: props.router.options.dehydrate?.(), - }), - [props.router], - ) - - return ( - - - - ) + return } diff --git a/packages/start-server/src/createRequestHandler.ts b/packages/start-server/src/createRequestHandler.ts index 3973c0b89e..77418f1a44 100644 --- a/packages/start-server/src/createRequestHandler.ts +++ b/packages/start-server/src/createRequestHandler.ts @@ -1,5 +1,6 @@ import { createMemoryHistory } from '@tanstack/react-router' -import { mergeHeaders, serializeLoaderData } from '@tanstack/start-client' +import { mergeHeaders } from '@tanstack/start-client' +import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server' import type { AnyRouter, Manifest } from '@tanstack/react-router' import type { HandlerCallback } from './defaultStreamHandler' @@ -19,12 +20,7 @@ export function createRequestHandler({ return async (cb) => { const router = createRouter() - // Inject a few of the SSR helpers and defaults - router.serializeLoaderData = serializeLoaderData - - if (getRouterManifest) { - router.manifest = getRouterManifest() - } + attachRouterServerSsrUtils(router, getRouterManifest?.()) const url = new URL(request.url, 'http://localhost') @@ -42,6 +38,8 @@ export function createRequestHandler({ await router.load() + dehydrateRouter(router) + const responseHeaders = getRequestHeaders({ router, }) diff --git a/packages/start-server/src/createStartHandler.ts b/packages/start-server/src/createStartHandler.ts index a0001951d5..606ca46ffb 100644 --- a/packages/start-server/src/createStartHandler.ts +++ b/packages/start-server/src/createStartHandler.ts @@ -1,6 +1,7 @@ import { createMemoryHistory } from '@tanstack/react-router' -import { mergeHeaders, serializeLoaderData } from '@tanstack/start-client' +import { mergeHeaders } from '@tanstack/start-client' import { eventHandler, getResponseHeaders, toWebRequest } from 'h3' +import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server' import type { H3Event } from 'h3' import type { AnyRouter, Manifest } from '@tanstack/react-router' import type { HandlerCallback } from './defaultStreamHandler' @@ -30,12 +31,7 @@ export function createStartHandler({ const router = createRouter() - // Inject a few of the SSR helpers and defaults - router.serializeLoaderData = serializeLoaderData - - if (getRouterManifest) { - router.manifest = getRouterManifest() - } + attachRouterServerSsrUtils(router, getRouterManifest?.()) // Update the router with the history and context router.update({ @@ -44,6 +40,8 @@ export function createStartHandler({ await router.load() + dehydrateRouter(router) + const responseHeaders = getRequestHeaders({ event, router, diff --git a/packages/start-server/src/defaultRenderHandler.tsx b/packages/start-server/src/defaultRenderHandler.tsx index 8e866e16b5..2ff85974f6 100644 --- a/packages/start-server/src/defaultRenderHandler.tsx +++ b/packages/start-server/src/defaultRenderHandler.tsx @@ -3,16 +3,16 @@ import { StartServer } from './StartServer' import type { HandlerCallback } from './defaultStreamHandler' import type { AnyRouter } from '@tanstack/react-router' -export const defaultRenderHandler: HandlerCallback = ({ +export const defaultRenderHandler: HandlerCallback = async ({ router, responseHeaders, }) => { try { let html = ReactDOMServer.renderToString() - html = html.replace( - ``, - `${router.injectedHtml.map((d) => d()).join('')}`, + const injectedHtml = await Promise.all(router.serverSsr!.injectedHtml).then( + (htmls) => htmls.join(''), ) + html = html.replace(``, `${injectedHtml}`) return new Response(`${html}`, { status: router.state.statusCode, headers: responseHeaders, diff --git a/packages/start-server/src/defaultStreamHandler.tsx b/packages/start-server/src/defaultStreamHandler.tsx index 3a8707017a..46a503c9d0 100644 --- a/packages/start-server/src/defaultStreamHandler.tsx +++ b/packages/start-server/src/defaultStreamHandler.tsx @@ -2,10 +2,12 @@ import { PassThrough } from 'node:stream' import { isbot } from 'isbot' import ReactDOMServer from 'react-dom/server' import { StartServer } from './StartServer' + import { + transformPipeableStreamWithRouter, transformReadableStreamWithRouter, - transformStreamWithRouter, } from './transformStreamWithRouter' +import type { ReadableStream } from 'node:stream/web' import type { AnyRouter } from '@tanstack/react-router' export type HandlerCallback = (ctx: { @@ -31,21 +33,19 @@ export const defaultStreamHandler: HandlerCallback = async ({ await stream.allReady } - const transforms = [transformReadableStreamWithRouter(router)] - - const transformedStream = transforms.reduce( - (stream, transform) => stream.pipeThrough(transform), - stream as ReadableStream, + const responseStream = transformReadableStreamWithRouter( + router, + stream as unknown as ReadableStream, ) - return new Response(transformedStream, { + return new Response(responseStream as any, { status: router.state.statusCode, headers: responseHeaders, }) } if (typeof ReactDOMServer.renderToPipeableStream === 'function') { - const passthrough = new PassThrough() + const reactAppPassthrough = new PassThrough() try { const pipeable = ReactDOMServer.renderToPipeableStream( @@ -54,12 +54,12 @@ export const defaultStreamHandler: HandlerCallback = async ({ ...(isbot(request.headers.get('User-Agent')) ? { onAllReady() { - pipeable.pipe(passthrough) + pipeable.pipe(reactAppPassthrough) }, } : { onShellReady() { - pipeable.pipe(passthrough) + pipeable.pipe(reactAppPassthrough) }, }), onError: (error, info) => { @@ -71,14 +71,12 @@ export const defaultStreamHandler: HandlerCallback = async ({ console.error('Error in renderToPipeableStream:', e) } - const transforms = [transformStreamWithRouter(router)] - - const transformedStream = transforms.reduce( - (stream, transform) => (stream as any).pipe(transform), - passthrough, + const responseStream = transformPipeableStreamWithRouter( + router, + reactAppPassthrough, ) - return new Response(transformedStream as any, { + return new Response(responseStream as any, { status: router.state.statusCode, headers: responseHeaders, }) diff --git a/packages/start-server/src/ssr-server.ts b/packages/start-server/src/ssr-server.ts new file mode 100644 index 0000000000..def102f25d --- /dev/null +++ b/packages/start-server/src/ssr-server.ts @@ -0,0 +1,393 @@ +import { + TSR_DEFERRED_PROMISE, + defer, + isPlainArray, + isPlainObject, + pick, + warning, +} from '@tanstack/react-router' +import jsesc from 'jsesc' +import { startSerializer } from '@tanstack/start-client' +import minifiedTsrBootStrapScript from './tsrScript?script-string' +import type { + ClientExtractedBaseEntry, + DehydratedRouter, + ResolvePromiseState, + SsrMatch, +} from '@tanstack/start-client' +import type { + AnyRouteMatch, + AnyRouter, + DeferredPromise, + Manifest, +} from '@tanstack/react-router' + +export type ServerExtractedEntry = + | ServerExtractedStream + | ServerExtractedPromise + +export interface ServerExtractedBaseEntry extends ClientExtractedBaseEntry { + dataType: '__beforeLoadContext' | 'loaderData' + id: number + matchIndex: number +} + +export interface ServerExtractedStream extends ServerExtractedBaseEntry { + type: 'stream' + stream: ReadableStream +} + +export interface ServerExtractedPromise extends ServerExtractedBaseEntry { + type: 'promise' + promise: DeferredPromise +} + +export function attachRouterServerSsrUtils( + router: AnyRouter, + manifest: Manifest | undefined, +) { + router.ssr = { + manifest, + serializer: startSerializer, + } + + router.serverSsr = { + injectedHtml: [], + streamedKeys: new Set(), + injectHtml: (getHtml) => { + const promise = Promise.resolve().then(getHtml) + router.serverSsr!.injectedHtml.push(promise) + router.emit({ + type: 'onInjectedHtml', + promise, + }) + + return promise.then(() => {}) + }, + injectScript: (getScript, opts) => { + return router.serverSsr!.injectHtml(async () => { + const script = await getScript() + return `` + }) + }, + streamValue: (key, value) => { + warning( + !router.serverSsr!.streamedKeys.has(key), + 'Key has already been streamed: ' + key, + ) + + router.serverSsr!.streamedKeys.add(key) + router.serverSsr!.injectScript( + () => + `__TSR_SSR__.streamedValues['${key}'] = { value: ${jsesc( + router.ssr!.serializer.stringify(value), + { + isScriptContext: true, + wrap: true, + json: true, + }, + )}}`, + ) + }, + onMatchSettled, + } + + router.serverSsr.injectScript(() => minifiedTsrBootStrapScript, { + logScript: false, + }) +} + +export function dehydrateRouter(router: AnyRouter) { + const dehydratedRouter: DehydratedRouter = { + manifest: router.ssr!.manifest, + dehydratedData: router.options.dehydrate?.(), + } + + router.serverSsr!.injectScript( + () => + `__TSR_SSR__.dehydrated = ${jsesc( + router.ssr!.serializer.stringify(dehydratedRouter), + { + isScriptContext: true, + wrap: true, + json: true, + }, + )}`, + ) +} + +export function extractAsyncDataToMatch( + dataType: '__beforeLoadContext' | 'loaderData', + data: any, + ctx: { + match: AnyRouteMatch + router: AnyRouter + }, +) { + ;(ctx.match as any).extracted = (ctx.match as any).extracted || [] + + const extracted = (ctx.match as any).extracted + + const replacedLoaderData = replaceBy(data, (value, path) => { + const type = + value instanceof ReadableStream + ? 'stream' + : value instanceof Promise + ? 'promise' + : undefined + + // If it's a stream, we need to tee it so we can read it multiple times + if (type === 'stream') { + const [copy1, copy2] = value.tee() + const entry: ServerExtractedStream = { + dataType, + type, + path, + id: extracted.length, + matchIndex: ctx.match.index, + stream: copy2, + } + + extracted.push(entry) + return copy1 + } else if (type === 'promise') { + const deferredPromise = defer(value) + const entry: ServerExtractedPromise = { + dataType, + type, + path, + id: extracted.length, + matchIndex: ctx.match.index, + promise: deferredPromise, + } + extracted.push(entry) + } + + return value + }) + + return replacedLoaderData +} + +export function onMatchSettled(opts: { + router: AnyRouter + match: AnyRouteMatch +}) { + const { router, match } = opts + + const [serializedBeforeLoadData, serializedLoaderData] = ( + ['__beforeLoadContext', 'loaderData'] as const + ).map((dataType) => { + const data = extractAsyncDataToMatch(dataType, match[dataType], { + router: router, + match, + }) + + const extracted = (match as any).extracted as + | undefined + | Array + + return extracted + ? extracted.reduce( + (acc: any, entry: ServerExtractedEntry) => { + if (entry.dataType !== dataType) { + return deepImmutableSetByPath( + acc, + ['temp', ...entry.path], + undefined, + ) + } + return acc + }, + { temp: data }, + ).temp + : data + }) + + const extracted = (match as any).extracted as + | undefined + | Array + + if ( + serializedBeforeLoadData !== undefined || + serializedLoaderData !== undefined || + extracted?.length + ) { + const initCode = `__TSR_SSR__.initMatch(${jsesc( + { + id: match.id, + __beforeLoadContext: router.ssr!.serializer.stringify( + serializedBeforeLoadData, + ), + loaderData: router.ssr!.serializer.stringify(serializedLoaderData), + error: router.ssr!.serializer.stringify(match.error), + extracted: extracted + ? Object.fromEntries( + extracted.map((entry) => { + return [entry.id, pick(entry, ['type', 'path'])] + }), + ) + : {}, + updatedAt: match.updatedAt, + status: match.status, + } satisfies SsrMatch, + { + isScriptContext: true, + wrap: true, + json: true, + }, + )})` + + router.serverSsr!.injectScript(() => initCode) + + if (extracted) { + extracted.forEach((entry) => { + if (entry.type === 'promise') return injectPromise(entry) + return injectStream(entry) + }) + } + + function injectPromise(entry: ServerExtractedPromise) { + router.serverSsr!.injectScript(async () => { + await entry.promise + + return `__TSR_SSR__.resolvePromise(${jsesc( + { + matchId: match.id, + id: entry.id, + promiseState: entry.promise[TSR_DEFERRED_PROMISE], + } satisfies ResolvePromiseState, + { + isScriptContext: true, + wrap: true, + json: true, + }, + )})` + }) + } + + function injectStream(entry: ServerExtractedStream) { + // Inject a promise that resolves when the stream is done + // We do this to keep the stream open until we're done + router.serverSsr!.injectHtml(async () => { + // + try { + const reader = entry.stream.getReader() + let chunk: ReadableStreamReadResult | null = null + while (!(chunk = await reader.read()).done) { + if (chunk.value) { + const code = `__TSR_SSR__.injectChunk(${jsesc( + { + matchId: match.id, + id: entry.id, + chunk: chunk.value, + }, + { + isScriptContext: true, + wrap: true, + json: true, + }, + )})` + + router.serverSsr!.injectScript(() => code) + } + } + + router.serverSsr!.injectScript( + () => + `__TSR_SSR__.closeStream(${jsesc( + { + matchId: match.id, + id: entry.id, + }, + { + isScriptContext: true, + wrap: true, + json: true, + }, + )})`, + ) + } catch (err) { + console.error('stream read error', err) + } + + return '' + }) + } + } + + return null +} + +function deepImmutableSetByPath(obj: T, path: Array, value: any): T { + // immutable set by path retaining array and object references + if (path.length === 0) { + return value + } + + const [key, ...rest] = path + + if (Array.isArray(obj)) { + return obj.map((item, i) => { + if (i === Number(key)) { + return deepImmutableSetByPath(item, rest, value) + } + return item + }) as T + } + + if (isPlainObject(obj)) { + return { + ...obj, + [key!]: deepImmutableSetByPath((obj as any)[key!], rest, value), + } + } + + return obj +} + +export function replaceBy( + obj: T, + cb: (value: any, path: Array) => any, + path: Array = [], +): T { + if (isPlainArray(obj)) { + return obj.map((value, i) => replaceBy(value, cb, [...path, `${i}`])) as any + } + + if (isPlainObject(obj)) { + // Do not allow objects with illegal + const newObj: any = {} + + for (const key in obj) { + newObj[key] = replaceBy(obj[key], cb, [...path, key]) + } + + return newObj + } + + // // Detect classes, functions, and other non-serializable objects + // // and return undefined. Exclude some known types that are serializable + // if ( + // typeof obj === 'function' || + // (typeof obj === 'object' && + // ![Object, Promise, ReadableStream].includes((obj as any)?.constructor)) + // ) { + // console.info(obj) + // warning(false, `Non-serializable value ☝️ found at ${path.join('.')}`) + // return undefined as any + // } + + const newObj = cb(obj, path) + + if (newObj !== obj) { + return newObj + } + + return obj +} diff --git a/packages/start-server/src/transformStreamWithRouter.ts b/packages/start-server/src/transformStreamWithRouter.ts index d17cba0f24..1a3930fa23 100644 --- a/packages/start-server/src/transformStreamWithRouter.ts +++ b/packages/start-server/src/transformStreamWithRouter.ts @@ -1,137 +1,265 @@ -import { Transform } from 'node:stream' +import { ReadableStream } from 'node:stream/web' +import { Readable } from 'node:stream' +import { createControlledPromise } from '@tanstack/react-router' import type { AnyRouter } from '@tanstack/react-router' -export function transformStreamWithRouter(router: AnyRouter) { - const callbacks = transformHtmlCallbacks(() => - router.injectedHtml.map((d) => d()).join(''), - ) - return new Transform({ - transform(chunk, _encoding, callback) { - callbacks - .transform(chunk, this.push.bind(this)) - .then(() => callback()) - .catch((err) => callback(err)) - }, - flush(callback) { - callbacks - .flush(this.push.bind(this)) - .then(() => callback()) - .catch((err) => callback(err)) - }, - }) +export function transformReadableStreamWithRouter( + router: AnyRouter, + routerStream: ReadableStream, +) { + return transformStreamWithRouter(router, routerStream) } -export function transformReadableStreamWithRouter(router: AnyRouter) { - const callbacks = transformHtmlCallbacks(() => - router.injectedHtml.map((d) => d()).join(''), +export function transformPipeableStreamWithRouter( + router: AnyRouter, + routerStream: Readable, +) { + return Readable.fromWeb( + transformStreamWithRouter(router, Readable.toWeb(routerStream)), ) - - const encoder = new TextEncoder() - - return new TransformStream({ - transform(chunk, controller) { - return callbacks.transform(chunk, (chunkToPush) => { - controller.enqueue(encoder.encode(chunkToPush)) - return true - }) - }, - flush(controller) { - return callbacks.flush((chunkToPush) => { - controller.enqueue(chunkToPush) - return true - }) - }, - }) } // regex pattern for matching closing body and html tags const patternBodyStart = /()/ const patternHtmlEnd = /(<\/html>)/ - +const patternHeadStart = /()/ // regex pattern for matching closing tags -const pattern = /(<\/[a-zA-Z][\w:.-]*?>)/g +const patternClosingTag = /(<\/[a-zA-Z][\w:.-]*?>)/g const textDecoder = new TextDecoder() -function transformHtmlCallbacks(getHtml: () => string) { - let bodyStarted = false +type ReadablePassthrough = { + stream: ReadableStream + write: (chunk: string) => void + end: (chunk?: string) => void + destroy: (error: unknown) => void + destroyed: boolean +} + +function createPassthrough() { + let controller: ReadableStreamDefaultController + const stream = new ReadableStream({ + start(c) { + controller = c + }, + }) + + const res: ReadablePassthrough = { + stream, + write: (chunk) => { + controller.enqueue(chunk) + }, + end: (chunk) => { + if (chunk) { + controller.enqueue(chunk) + } + controller.close() + res.destroyed = true + }, + destroy: (error) => { + controller.error(error) + }, + destroyed: false, + } + + return res +} + +async function readStream( + stream: ReadableStream, + opts: { + onData?: (chunk: ReadableStreamReadValueResult) => void + onEnd?: () => void + onError?: (error: unknown) => void + }, +) { + try { + const reader = stream.getReader() + let chunk + while (!(chunk = await reader.read()).done) { + opts.onData?.(chunk) + } + opts.onEnd?.() + } catch (error) { + opts.onError?.(error) + } +} + +export function transformStreamWithRouter( + router: AnyRouter, + appStream: ReadableStream, +) { + const finalPassThrough = createPassthrough() + + let isAppRendering = true as boolean + let routerStreamBuffer = '' + let pendingClosingTags = '' + let bodyStarted = false as boolean + let headStarted = false as boolean let leftover = '' - // If a closing tag is split across chunks, store the HTML to add after it - // This expects that all the HTML that's added is closed properly let leftoverHtml = '' - return { - // eslint-disable-next-line @typescript-eslint/require-await - async transform(chunk: any, push: (chunkToPush: string) => boolean) { - const chunkString = leftover + textDecoder.decode(chunk) + function getBufferedRouterStream() { + const html = routerStreamBuffer + routerStreamBuffer = '' + return html + } + + function decodeChunk(chunk: unknown): string { + if (chunk instanceof Uint8Array) { + return textDecoder.decode(chunk) + } + return String(chunk) + } + + const injectedHtmlDonePromise = createControlledPromise() + + let processingCount = 0 + + // Process any already-injected HTML + router.serverSsr!.injectedHtml.forEach((promise) => { + handleInjectedHtml(promise) + }) + + // Listen for any new injected HTML + const stopListeningToInjectedHtml = router.subscribe( + 'onInjectedHtml', + (e) => { + handleInjectedHtml(e.promise) + }, + ) + + function handleInjectedHtml(promise: Promise) { + processingCount++ + + promise + .then((html) => { + if (!bodyStarted) { + routerStreamBuffer += html + } else { + finalPassThrough.write(html) + } + + return new Promise((resolve) => { + setTimeout(() => { + resolve() + }, 1000) + }) + }) + .catch(injectedHtmlDonePromise.reject) + .finally(() => { + processingCount-- + + if (!isAppRendering && processingCount === 0) { + stopListeningToInjectedHtml() + injectedHtmlDonePromise.resolve() + } + }) + } - const bodyStartMatch = chunkString.match(patternBodyStart) + injectedHtmlDonePromise + .then(() => { + const finalHtml = + leftoverHtml + getBufferedRouterStream() + pendingClosingTags + + finalPassThrough.end(finalHtml) + }) + .catch((err) => { + console.error('Error reading routerStream:', err) + finalPassThrough.destroy(err) + }) + + // Transform the appStream + readStream(appStream, { + onData: (chunk) => { + const text = decodeChunk(chunk.value) + + const chunkString = leftover + text const bodyEndMatch = chunkString.match(patternBodyEnd) const htmlEndMatch = chunkString.match(patternHtmlEnd) - try { + if (!bodyStarted) { + const bodyStartMatch = chunkString.match(patternBodyStart) if (bodyStartMatch) { bodyStarted = true } + } - if (!bodyStarted) { - push(chunkString) - leftover = '' + if (!headStarted) { + const headStartMatch = chunkString.match(patternHeadStart) + if (headStartMatch) { + headStarted = true + const index = headStartMatch.index! + const headTag = headStartMatch[0] + const remaining = chunkString.slice(index + headTag.length) + finalPassThrough.write( + chunkString.slice(0, index) + + headTag + + getBufferedRouterStream() + + remaining, + ) return } + } - const html = getHtml() - - // If a sequence was found - if ( - bodyEndMatch && - htmlEndMatch && - bodyEndMatch.index! < htmlEndMatch.index! - ) { - const bodyIndex = bodyEndMatch.index! + bodyEndMatch[0].length - const htmlIndex = htmlEndMatch.index! + htmlEndMatch[0].length - - // Add the arbitrary HTML before the closing body tag - const processed = - chunkString.slice(0, bodyIndex) + - html + - chunkString.slice(bodyIndex, htmlIndex) + - chunkString.slice(htmlIndex) - - push(processed) - leftover = '' - } else { - // For all other closing tags, add the arbitrary HTML after them - let result - let lastIndex = 0 - - while ((result = pattern.exec(chunkString)) !== null) { - lastIndex = result.index + result[0].length - } - - // If a closing tag was found, add the arbitrary HTML and send it through - if (lastIndex > 0) { - const processed = - chunkString.slice(0, lastIndex) + html + leftoverHtml - push(processed) - leftover = chunkString.slice(lastIndex) - } else { - // If no closing tag was found, store the chunk to process with the next one - leftover = chunkString - leftoverHtml += html - } - } - } catch (err) { - console.error('Error transforming HTML:', err) - throw err + if (!bodyStarted) { + finalPassThrough.write(chunkString) + leftover = '' + return + } + + // If either the body end or html end is in the chunk, + // We need to get all of our data in asap + if ( + bodyEndMatch && + htmlEndMatch && + bodyEndMatch.index! < htmlEndMatch.index! + ) { + const bodyEndIndex = bodyEndMatch.index! + pendingClosingTags = chunkString.slice(bodyEndIndex) + + finalPassThrough.write( + chunkString.slice(0, bodyEndIndex) + getBufferedRouterStream(), + ) + + leftover = '' + return + } + + let result + let lastIndex = 0 + while ((result = patternClosingTag.exec(chunkString)) !== null) { + lastIndex = result.index + result[0].length + } + + if (lastIndex > 0) { + const processed = + chunkString.slice(0, lastIndex) + + getBufferedRouterStream() + + leftoverHtml + + finalPassThrough.write(processed) + leftover = chunkString.slice(lastIndex) + } else { + leftover = chunkString + leftoverHtml += getBufferedRouterStream() } }, - // eslint-disable-next-line @typescript-eslint/require-await - async flush(push: (chunkToPush: string) => boolean) { - if (leftover) { - push(leftover) + onEnd: () => { + // Mark the app as done rendering + isAppRendering = false + + // If there are no pending promises, resolve the injectedHtmlDonePromise + if (processingCount === 0) { + injectedHtmlDonePromise.resolve() } }, - } + onError: (error) => { + console.error('Error reading appStream:', error) + finalPassThrough.destroy(error) + }, + }) + + return finalPassThrough.stream } diff --git a/packages/start-server/src/tsrScript.ts b/packages/start-server/src/tsrScript.ts new file mode 100644 index 0000000000..113a46ad58 --- /dev/null +++ b/packages/start-server/src/tsrScript.ts @@ -0,0 +1,91 @@ +import type { ControllablePromise } from '@tanstack/react-router' +import type { StartSsrGlobal } from '@tanstack/start-client' + +const __TSR_SSR__: StartSsrGlobal = { + matches: [], + streamedValues: {}, + initMatch: (match) => { + __TSR_SSR__.matches.push(match) + + Object.entries(match.extracted).forEach(([_id, ex]) => { + if (ex.type === 'stream') { + let controller + ex.value = new ReadableStream({ + start(c) { + controller = { + enqueue: (chunk: unknown) => { + try { + c.enqueue(chunk) + } catch {} + }, + close: () => { + try { + c.close() + } catch {} + }, + } + }, + }) + ex.value.controller = controller + } else { + let resolve: ControllablePromise['reject'] | undefined + let reject: ControllablePromise['reject'] | undefined + + ex.value = new Promise((_resolve, _reject) => { + reject = _reject + resolve = _resolve + }) as ControllablePromise + ex.value.reject = reject! + ex.value.resolve = resolve! + } + }) + + return true + }, + resolvePromise: ({ matchId, id, promiseState }) => { + const match = __TSR_SSR__.matches.find((m) => m.id === matchId) + if (match) { + const ex = match.extracted[id] + if ( + ex && + ex.type === 'promise' && + ex.value && + promiseState.status === 'success' + ) { + ex.value.resolve(promiseState.data) + return true + } + } + return false + }, + injectChunk: ({ matchId, id, chunk }) => { + const match = __TSR_SSR__.matches.find((m) => m.id === matchId) + + if (match) { + const ex = match.extracted[id] + if (ex && ex.type === 'stream' && ex.value?.controller) { + ex.value.controller.enqueue(new TextEncoder().encode(chunk.toString())) + return true + } + } + return false + }, + closeStream: ({ matchId, id }) => { + const match = __TSR_SSR__.matches.find((m) => m.id === matchId) + if (match) { + const ex = match.extracted[id] + if (ex && ex.type === 'stream' && ex.value?.controller) { + ex.value.controller.close() + return true + } + } + return false + }, + cleanScripts: () => { + document.querySelectorAll('.tsr-once').forEach((el) => { + el.remove() + }) + }, +} + +window.__TSR_SSR__ = __TSR_SSR__ diff --git a/packages/start-client/src/vite-env.d.ts b/packages/start-server/src/vite-env.d.ts similarity index 100% rename from packages/start-client/src/vite-env.d.ts rename to packages/start-server/src/vite-env.d.ts diff --git a/packages/start-server/tsconfig.json b/packages/start-server/tsconfig.json index 51dda9abf2..017bc15e1e 100644 --- a/packages/start-server/tsconfig.json +++ b/packages/start-server/tsconfig.json @@ -4,5 +4,5 @@ "jsx": "react-jsx", "module": "esnext" }, - "include": ["src", "vite.config.ts"] + "include": ["src", "vite.config.ts", "../start-client/src/tsrScript.ts"] } diff --git a/packages/start-client/vite-minify-plugin.ts b/packages/start-server/vite-minify-plugin.ts similarity index 100% rename from packages/start-client/vite-minify-plugin.ts rename to packages/start-server/vite-minify-plugin.ts diff --git a/packages/start-server/vite.config.ts b/packages/start-server/vite.config.ts index 9cdf6497ee..e05e5cc394 100644 --- a/packages/start-server/vite.config.ts +++ b/packages/start-server/vite.config.ts @@ -2,10 +2,11 @@ import { defineConfig, mergeConfig } from 'vitest/config' import { tanstackViteConfig } from '@tanstack/config/vite' import react from '@vitejs/plugin-react' import packageJson from './package.json' +import minifyScriptPlugin from './vite-minify-plugin' import type { ViteUserConfig } from 'vitest/config' const config = defineConfig({ - plugins: [react()] as ViteUserConfig['plugins'], + plugins: [minifyScriptPlugin(), react()] as ViteUserConfig['plugins'], test: { name: packageJson.name, watch: false, @@ -16,7 +17,7 @@ const config = defineConfig({ export default mergeConfig( config, tanstackViteConfig({ - entry: './src/index.tsx', srcDir: './src', + entry: './src/index.tsx', }), ) diff --git a/packages/start/vite-minify-plugin.ts b/packages/start/vite-minify-plugin.ts deleted file mode 100644 index d8da290266..0000000000 --- a/packages/start/vite-minify-plugin.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { transform as esbuildTransform } from 'esbuild' -import type { Plugin } from 'vite' - -export default function minifyScriptPlugin(): Plugin { - return { - name: 'vite-plugin-minify-script', - enforce: 'pre', - async transform(code, id) { - if (!id.endsWith('?script-string')) { - return null - } - - const result = await esbuildTransform(code, { - loader: 'ts', - minify: true, - target: 'esnext', - }) - - return { - code: `export default ${JSON.stringify(result.code)};`, - map: null, - } - }, - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2f66498c4..0855a6eec9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,8 @@ settings: overrides: use-sync-external-store: 1.2.2 + react: ^18.3.1 + react-dom: ^18.3.1 eslint: ^9.17.0 vite: 6.0.9 '@tanstack/react-query': 5.62.3 @@ -79,8 +81,8 @@ importers: specifier: ^25.0.1 version: 25.0.1 nx: - specifier: ^20.3.0 - version: 20.3.0(@swc/core@1.10.1(@swc/helpers@0.5.15)) + specifier: ^20.3.3 + version: 20.3.3(@swc/core@1.10.1(@swc/helpers@0.5.15)) prettier: specifier: ^3.4.2 version: 3.4.2 @@ -136,10 +138,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -176,10 +178,10 @@ importers: specifier: workspace:* version: link:../../../packages/zod-adapter react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -216,10 +218,10 @@ importers: specifier: workspace:* version: link:../../../packages/zod-adapter react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -259,10 +261,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) zod: specifier: ^3.24.1 @@ -299,10 +301,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -342,10 +344,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -382,10 +384,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -422,10 +424,10 @@ importers: specifier: workspace:* version: link:../../../packages/virtual-file-routes react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -465,10 +467,10 @@ importers: specifier: workspace:* version: link:../../../packages/virtual-file-routes react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -591,10 +593,10 @@ importers: specifier: workspace:* version: link:../../../packages/zod-adapter react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1028,10 +1030,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1065,10 +1067,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1102,10 +1104,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1142,10 +1144,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1182,10 +1184,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1225,10 +1227,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1268,10 +1270,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1314,10 +1316,10 @@ importers: specifier: ^7.1.0 version: 7.1.0 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1378,10 +1380,10 @@ importers: specifier: ^7.1.0 version: 7.1.0 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1442,10 +1444,10 @@ importers: specifier: workspace:* version: link:../../../packages/virtual-file-routes react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1485,10 +1487,10 @@ importers: specifier: workspace:* version: link:../../../packages/virtual-file-routes react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1522,10 +1524,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1562,10 +1564,10 @@ importers: specifier: ^10.1.1 version: 10.1.1 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1605,10 +1607,10 @@ importers: specifier: ^10.1.1 version: 10.1.1 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1651,10 +1653,10 @@ importers: specifier: ^10.1.1 version: 10.1.1 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1700,10 +1702,10 @@ importers: specifier: ^10.1.1 version: 10.1.1 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1743,10 +1745,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1786,10 +1788,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1823,10 +1825,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1857,10 +1859,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@types/react': @@ -1891,10 +1893,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -1925,10 +1927,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -2016,10 +2018,10 @@ importers: version: 18.3.1 html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -2051,10 +2053,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -2094,10 +2096,10 @@ importers: specifier: 5.62.3 version: 5.62.3(react@18.3.1) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@tanstack/router-devtools': @@ -2134,10 +2136,10 @@ importers: specifier: 5.62.3 version: 5.62.3(react@18.3.1) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@types/react': @@ -2199,10 +2201,10 @@ importers: specifier: workspace:* version: link:../../../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -2242,10 +2244,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -2282,10 +2284,10 @@ importers: specifier: workspace:* version: link:../router react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@tanstack/router-devtools': @@ -2319,10 +2321,10 @@ importers: specifier: 5.62.3 version: 5.62.3(react@18.3.1) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@types/react': @@ -2356,10 +2358,10 @@ importers: specifier: workspace:* version: link:../../../../../packages/router-plugin react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -2399,10 +2401,10 @@ importers: specifier: workspace:* version: link:../../../packages/router-devtools react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: '@types/react': @@ -2448,10 +2450,10 @@ importers: specifier: ^2.0.0-rc.33 version: 2.0.0-rc.33 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) valibot: specifier: 1.0.0-beta.9 @@ -3062,10 +3064,10 @@ importers: specifier: ^11.13.3 version: 11.13.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -3111,10 +3113,10 @@ importers: specifier: 11.0.0-rc.660 version: 11.0.0-rc.660(typescript@5.7.2) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -3166,10 +3168,10 @@ importers: specifier: 11.0.0-rc.660 version: 11.0.0-rc.660(typescript@5.7.2) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) redaxios: specifier: ^0.5.1 @@ -3252,7 +3254,7 @@ importers: version: 7.0.6 html-webpack-plugin: specifier: ^5.6.0 - version: 5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1) picocolors: specifier: ^1.1.1 version: 1.1.1 @@ -3264,7 +3266,7 @@ importers: version: 18.3.1(react@18.3.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1) tinyglobby: specifier: ^0.2.10 version: 0.2.10 @@ -3475,10 +3477,10 @@ importers: specifier: ^4.3.4 version: 4.3.4(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) packages/react-router: @@ -3515,10 +3517,10 @@ importers: specifier: ^1.1.11 version: 1.1.11 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) zod: specifier: ^3.24.1 @@ -3536,10 +3538,10 @@ importers: specifier: ^4.3.4 version: 4.3.4(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) react: - specifier: '>=18' + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: '>=18' + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) packages/router-cli: @@ -3574,10 +3576,10 @@ importers: specifier: ^4.3.4 version: 4.3.4(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) packages/router-generator: @@ -3795,10 +3797,10 @@ importers: version: 0.5.1(@types/node@22.10.2)(ioredis@5.4.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -3816,10 +3818,10 @@ importers: specifier: ^3.0.2 version: 3.0.2 react: - specifier: '>=18.0.0 || >=19.0.0' + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: '>=18.0.0 || >=19.0.0' + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) tiny-invariant: specifier: ^1.3.3 @@ -3837,9 +3839,6 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) - esbuild: - specifier: ^0.24.2 - version: 0.24.2 packages/start-config: dependencies: @@ -3874,10 +3873,10 @@ importers: specifier: ^1.4.1 version: 1.4.1 react: - specifier: '>=18.0.0 || >=19.0.0' + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: '>=18.0.0 || >=19.0.0' + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) vinxi: specifier: 0.5.1 @@ -3962,10 +3961,10 @@ importers: version: 0.5.1(@types/node@22.10.2)(ioredis@5.4.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4001,11 +4000,14 @@ importers: '@types/jsesc': specifier: ^3.0.3 version: 3.0.3 + esbuild: + specifier: ^0.19.12 + version: 0.19.12 react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4027,10 +4029,10 @@ importers: version: 1.3.3 devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4049,10 +4051,10 @@ importers: version: 4.3.4(vite@6.0.9(@types/node@22.10.2)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4063,6 +4065,9 @@ importers: '@tanstack/react-router': specifier: workspace:* version: link:../react-router + '@tanstack/start-client': + specifier: workspace:* + version: link:../start-client '@tanstack/start-server': specifier: workspace:* version: link:../start-server @@ -4074,10 +4079,10 @@ importers: version: 1.3.3 devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4096,10 +4101,10 @@ importers: version: 1.3.3 devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4121,10 +4126,10 @@ importers: version: 1.3.3 devDependencies: react: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1 react-dom: - specifier: ^18.2.0 + specifier: ^18.3.1 version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.2 @@ -4325,22 +4330,22 @@ packages: resolution: {integrity: sha512-noW5S9PWqRUabBB7b4trEAbYDu4GSQCCY6Z9AismY28vU8DdBluVi6svy7ilKyN0NLQ8tbMFlfVHu/bp2SJ3bQ==} engines: {node: '>=18.17.0'} peerDependencies: - react: ^18 || ^19.0.0-0 - react-dom: ^18 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 '@clerk/clerk-react@5.21.0': resolution: {integrity: sha512-WGIYKeXA/cpvWj2NiMsWYJfRq4Npy6bM9ZrcTENXVox9jpk6iGggoX9zFJG9NBx5LDHBV2kgvpRdhnF/cnrJ+w==} engines: {node: '>=18.17.0'} peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 '@clerk/shared@2.19.3': resolution: {integrity: sha512-O3iMr5lxvQaLrYPdf2WHyVjP71UXBrwF3/qATVApWy3g1dD2uz0zCj5zWbGouxaXYtpV+TCmrpyFQeurefZVmw==} engines: {node: '>=18.17.0'} peerDependencies: - react: ^18 || ^19.0.0-0 - react-dom: ^18 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: react: optional: true @@ -4351,8 +4356,8 @@ packages: resolution: {integrity: sha512-1ndGEO+NejIMFkl47DCeSpVv3nmKh9BHD6wt2Sl3X1wv7sj3eWzSVC14Exkag7D8Og2VcN4LXOFLErsCXHS+YQ==} engines: {node: '>=18.17.0'} peerDependencies: - react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: react: optional: true @@ -4365,8 +4370,8 @@ packages: peerDependencies: '@tanstack/react-router': workspace:* '@tanstack/start': workspace:* - react: '>=18 || >=19.0.0-beta' - react-dom: '>=18 || >=19.0.0-beta' + react: ^18.3.1 + react-dom: ^18.3.1 '@clerk/tanstack-start@0.8.5': resolution: {integrity: sha512-ihnRMbmhsBVgacHEB5uITM7jeXyuBBrdkEolM8iVwrIL0gOegC6vmwCSkNaRse3MPdjcmcEGnETkN8x5PaC27w==} @@ -4374,8 +4379,8 @@ packages: peerDependencies: '@tanstack/react-router': workspace:* '@tanstack/start': workspace:* - react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 - react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 '@clerk/types@4.39.1': resolution: {integrity: sha512-9XpJ9Q7JQvLaEoLIQFkQ4CxAmyE5jHdkb9EnSxDVfZQZVRr9uej7ZaPinrcZT8jfpfCuepvFK8aiG4N9oGbhtw==} @@ -4431,6 +4436,12 @@ packages: '@emnapi/wasi-threads@1.0.1': resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.20.2': resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} @@ -4455,6 +4466,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.20.2': resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} @@ -4479,6 +4496,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.20.2': resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} @@ -4503,6 +4526,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.20.2': resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} @@ -4527,6 +4556,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.20.2': resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} @@ -4551,6 +4586,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.20.2': resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} @@ -4575,6 +4616,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.20.2': resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} @@ -4599,6 +4646,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.20.2': resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} @@ -4623,6 +4676,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.20.2': resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} @@ -4647,6 +4706,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.20.2': resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} @@ -4671,6 +4736,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.20.2': resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} @@ -4695,6 +4766,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.20.2': resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} @@ -4719,6 +4796,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.20.2': resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} @@ -4743,6 +4826,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.20.2': resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} @@ -4767,6 +4856,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.20.2': resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} @@ -4791,6 +4886,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.20.2': resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} @@ -4815,6 +4916,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.20.2': resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} @@ -4845,6 +4952,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.20.2': resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} @@ -4887,6 +5000,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.20.2': resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} @@ -4911,6 +5030,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.20.2': resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} @@ -4935,6 +5060,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.20.2': resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} @@ -4959,6 +5090,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.20.2': resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} @@ -4983,6 +5120,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.20.2': resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -5297,62 +5440,62 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nx/nx-darwin-arm64@20.3.0': - resolution: {integrity: sha512-9PqSe1Sh7qNqA4GL0cZH0t3S0EZzb2Xn14XY9au7yf0+eoxyag1oETjjULrxLeUmSoXW2hDxzNtoqKFE9zF07Q==} + '@nx/nx-darwin-arm64@20.3.3': + resolution: {integrity: sha512-4C7ShMrqp1vbH1ZgvSlkt0f35hJcqKtRcf8n/tCck46rnMkj4egXi3K1dE6uQcOorwiD1ttAr0DHcI1TTqcNXw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@20.3.0': - resolution: {integrity: sha512-gsGGhJVvi5QZVVTZie5sNMo1zOAU+A2edm6DGegObdFRLV41Ju/Yrm/gTaSp4yUtywd3UU4S/30C/nI2c55adA==} + '@nx/nx-darwin-x64@20.3.3': + resolution: {integrity: sha512-OUtJ7gA09pJC+a+RcZf1bGbMM4T7a/IcPb97z1xOoxr5Wm2s8BGBQUW2CKJ5gCp5iI1pGo44F12u0G9gbYClow==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@20.3.0': - resolution: {integrity: sha512-DiymYZBBu0upbiskdfn9KRyoXdyvKohezJiV3j4VkeRE8KR2p04NgwRQviDFbeD1cjWrDy9wk8y+G5PabLlqAA==} + '@nx/nx-freebsd-x64@20.3.3': + resolution: {integrity: sha512-q4SABgKYWPGOcdfRZne6n8HF4CzltRL5nJ3q093jQAUO93yPXtWzhQBaKZIZr6aPoqq0/NuH6xY4gNo4w9F8Bg==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@20.3.0': - resolution: {integrity: sha512-Aksx66e8jmt/4rGJ/5z34SWXbPcYr9Ht52UonEeuCdQdoEvAOs7yBUbllYOjIcUsfZikEyZgvqfiQslsggSJdQ==} + '@nx/nx-linux-arm-gnueabihf@20.3.3': + resolution: {integrity: sha512-e07PJcVsBT/Aelo/Vj6hLplDZamGCZ3zOJpW3XVBhdG4DC4sn+jodsdrIASoEpmF70VB89lzQsm9GrAgQPaWOA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@20.3.0': - resolution: {integrity: sha512-Y5wmYEwF1bl014Ps8QjagI911VbViQSFHSTVOCNSObdAzig9E5o6NOkoWe+doT1UZLrrInnlkrggQUsbtdKjOg==} + '@nx/nx-linux-arm64-gnu@20.3.3': + resolution: {integrity: sha512-1Z9chlN0/hWzliMer7TvdLT8cb6BKpGjZ15a+rQuUbO/CyLhY21Ct+lXtnaBERnNPYJpNOJlrbBDuF/9wpZ4CQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@nx/nx-linux-arm64-musl@20.3.0': - resolution: {integrity: sha512-yGcIkmImyOMfPkQSYH2EVjPmFE0VkLcO71Bbkpr3RlJ1N/vjYxsGbdnqPiBb8Wshib/hmwpiMHf/yzQtKH0SQw==} + '@nx/nx-linux-arm64-musl@20.3.3': + resolution: {integrity: sha512-RrLgujPU5NfDrsDRa7Y2isxGb8XkoQeJkTMUl1xmBK2Qnf4jAUn0PH0ULWrRMNgChi4nYUTn/Sf+2m6Uyoqcfw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@nx/nx-linux-x64-gnu@20.3.0': - resolution: {integrity: sha512-nkA2DLI+rpmiuiy7dyXP4l9s7dgHkQWDX7lG1XltiT41RzAReJF1h8qBE6XrsAYE1CtI76DRWVphnc93+iZr+A==} + '@nx/nx-linux-x64-gnu@20.3.3': + resolution: {integrity: sha512-/WmCnPxv1eR8tyYiFp4XoMbcXrJ8a/OIw1rpZZ5ceMKgH8lPaF2/KFf04JZZygrCKletEdqqIojBXz4AHoaueQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@nx/nx-linux-x64-musl@20.3.0': - resolution: {integrity: sha512-sPMtTt9iTrCmFEIp9Qv27UX9PeL1aqKck2dz2TAFbXKVtF6+djOdTcNnTYw45KIP6izcUcOXXAq4G0QSQE7CLg==} + '@nx/nx-linux-x64-musl@20.3.3': + resolution: {integrity: sha512-y4BJsR0fgJrXY3P7GkWfUZAeQEHMTXvaRHvzJfBSBPmnVcVZDYNTfEQYnslp8m8ahKdlJwtflxzykJ4Bwf55fw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@nx/nx-win32-arm64-msvc@20.3.0': - resolution: {integrity: sha512-ppfNa/8OfpWA9o26Pz3vArN4ulAC+Hx70/ghPRCP7ed1Mb3Z6yR2Ry9KfBRImbqajvuAExM0TePKMGq9LCdXmg==} + '@nx/nx-win32-arm64-msvc@20.3.3': + resolution: {integrity: sha512-BHqZitBaGT9ybv386B5QKxP5N66+xpTiYlKClzQ44o6Ca8QxnkugI64exBdcQyj+DRiL6HJhN14kaPJ1KrsKRA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@20.3.0': - resolution: {integrity: sha512-8FOejZ4emtLSVn3pYWs4PIc3n4//qMbwMDPVxmPE8us3ir91Qh0bzr5zRj7Q8sEdSgvneXRXqtBp2grY2KMJsw==} + '@nx/nx-win32-x64-msvc@20.3.3': + resolution: {integrity: sha512-6HcbAKghEypt4aMAoDjPn2sa6FG0MyiDabpV/cVLKokK09ngyy6qQDa5vSCUSDwI542XBxqtcv0AcZi7Ez+XUQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -5500,7 +5643,7 @@ packages: resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5509,7 +5652,7 @@ packages: resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5519,8 +5662,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5532,8 +5675,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5544,7 +5687,7 @@ packages: resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5554,8 +5697,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5566,7 +5709,7 @@ packages: resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5576,8 +5719,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5589,8 +5732,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5602,8 +5745,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5614,7 +5757,7 @@ packages: resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5623,7 +5766,7 @@ packages: resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5632,7 +5775,7 @@ packages: resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5641,7 +5784,7 @@ packages: resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -5650,7 +5793,7 @@ packages: resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -6116,24 +6259,24 @@ packages: resolution: {integrity: sha512-4iaQap/iP5ErS094u1WehFntHtjRo6g5HJMvyHovBVbsxnvgPc6AtKAw7qxPPoKy6Wj5Bew0045eYP5phiiBmw==} peerDependencies: '@tanstack/react-query': 5.62.3 - react: ^18 || ^19 + react: ^18.3.1 '@tanstack/react-query@5.62.3': resolution: {integrity: sha512-y2zDNKuhgiuMgsKkqd4AcsLIBiCfEO8U11AdrtAUihmLbRNztPrlcZqx2lH1GacZsx+y1qRRbCcJLYTtF1vKsw==} peerDependencies: - react: ^18 || ^19 + react: ^18.3.1 '@tanstack/react-store@0.7.0': resolution: {integrity: sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^18.3.1 + react-dom: ^18.3.1 '@tanstack/react-virtual@3.11.2': resolution: {integrity: sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^18.3.1 + react-dom: ^18.3.1 '@tanstack/store@0.7.0': resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} @@ -6156,8 +6299,8 @@ packages: '@testing-library/dom': ^10.0.0 '@types/react': ^18.0.0 || ^19.0.0 '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -6176,8 +6319,8 @@ packages: '@tanstack/react-query': 5.62.3 '@trpc/client': 11.0.0-rc.660+74625d5e4 '@trpc/server': 11.0.0-rc.660+74625d5e4 - react: '>=18.2.0' - react-dom: '>=18.2.0' + react: ^18.3.1 + react-dom: ^18.3.1 typescript: '>=5.6.2' '@trpc/server@11.0.0-rc.660': @@ -7162,8 +7305,8 @@ packages: peerDependencies: '@auth0/auth0-react': ^2.0.1 '@clerk/clerk-react': ^4.12.8 || ^5.0.0 - react: ^17.0.2 || ^18.0.0 || ^19.0.0-0 - react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0-0 + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@auth0/auth0-react': optional: true @@ -7608,6 +7751,11 @@ packages: peerDependencies: esbuild: '>=0.12 <1' + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -7999,8 +8147,8 @@ packages: resolution: {integrity: sha512-3ZSNuYpDFeNxqVKUyYipOm5A1fXSbMje1XIfEWxKTJ4ughl5FEjvkp6gKmFHLjzwijCVU/PjsMNlTMVCmi+Twg==} peerDependencies: '@emotion/is-prop-valid': '*' - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.3.1 + react-dom: ^18.3.1 peerDependenciesMeta: '@emotion/is-prop-valid': optional: true @@ -9136,8 +9284,8 @@ packages: nwsapi@2.2.16: resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} - nx@20.3.0: - resolution: {integrity: sha512-Nzi4k7tV22zwO2iBLk+pHxorLEWPJpPrVCACtz0SQ63j/LiAgfhoqruJO+VU+V+E9qdyPsvmqIL/Iaf/GRQlqA==} + nx@20.3.3: + resolution: {integrity: sha512-IUu2D8/bVa7aSr3ViRcrmpTGO2FKqzJoio6gjeq/YbyUHyjrrq5HUmHFx30Wm2vmC1BGm0MeyakTNUJzQvfAog==} hasBin: true peerDependencies: '@swc-node/register': ^1.8.0 @@ -9724,8 +9872,8 @@ packages: resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==} engines: {node: '>=10'} peerDependencies: - react: '>=16' - react-dom: '>=16' + react: ^18.3.1 + react-dom: ^18.3.1 react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -9746,7 +9894,7 @@ packages: engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -9756,7 +9904,7 @@ packages: engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -9766,7 +9914,7 @@ packages: engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -10248,7 +10396,7 @@ packages: swr@2.2.5: resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -10708,7 +10856,7 @@ packages: engines: {node: '>=10'} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -10718,7 +10866,7 @@ packages: engines: {node: '>=10'} peerDependencies: '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 peerDependenciesMeta: '@types/react': optional: true @@ -10726,7 +10874,7 @@ packages: use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^18.3.1 util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -11449,6 +11597,9 @@ snapshots: dependencies: tslib: 2.8.1 + '@esbuild/aix-ppc64@0.19.12': + optional: true + '@esbuild/aix-ppc64@0.20.2': optional: true @@ -11461,6 +11612,9 @@ snapshots: '@esbuild/aix-ppc64@0.24.2': optional: true + '@esbuild/android-arm64@0.19.12': + optional: true + '@esbuild/android-arm64@0.20.2': optional: true @@ -11473,6 +11627,9 @@ snapshots: '@esbuild/android-arm64@0.24.2': optional: true + '@esbuild/android-arm@0.19.12': + optional: true + '@esbuild/android-arm@0.20.2': optional: true @@ -11485,6 +11642,9 @@ snapshots: '@esbuild/android-arm@0.24.2': optional: true + '@esbuild/android-x64@0.19.12': + optional: true + '@esbuild/android-x64@0.20.2': optional: true @@ -11497,6 +11657,9 @@ snapshots: '@esbuild/android-x64@0.24.2': optional: true + '@esbuild/darwin-arm64@0.19.12': + optional: true + '@esbuild/darwin-arm64@0.20.2': optional: true @@ -11509,6 +11672,9 @@ snapshots: '@esbuild/darwin-arm64@0.24.2': optional: true + '@esbuild/darwin-x64@0.19.12': + optional: true + '@esbuild/darwin-x64@0.20.2': optional: true @@ -11521,6 +11687,9 @@ snapshots: '@esbuild/darwin-x64@0.24.2': optional: true + '@esbuild/freebsd-arm64@0.19.12': + optional: true + '@esbuild/freebsd-arm64@0.20.2': optional: true @@ -11533,6 +11702,9 @@ snapshots: '@esbuild/freebsd-arm64@0.24.2': optional: true + '@esbuild/freebsd-x64@0.19.12': + optional: true + '@esbuild/freebsd-x64@0.20.2': optional: true @@ -11545,6 +11717,9 @@ snapshots: '@esbuild/freebsd-x64@0.24.2': optional: true + '@esbuild/linux-arm64@0.19.12': + optional: true + '@esbuild/linux-arm64@0.20.2': optional: true @@ -11557,6 +11732,9 @@ snapshots: '@esbuild/linux-arm64@0.24.2': optional: true + '@esbuild/linux-arm@0.19.12': + optional: true + '@esbuild/linux-arm@0.20.2': optional: true @@ -11569,6 +11747,9 @@ snapshots: '@esbuild/linux-arm@0.24.2': optional: true + '@esbuild/linux-ia32@0.19.12': + optional: true + '@esbuild/linux-ia32@0.20.2': optional: true @@ -11581,6 +11762,9 @@ snapshots: '@esbuild/linux-ia32@0.24.2': optional: true + '@esbuild/linux-loong64@0.19.12': + optional: true + '@esbuild/linux-loong64@0.20.2': optional: true @@ -11593,6 +11777,9 @@ snapshots: '@esbuild/linux-loong64@0.24.2': optional: true + '@esbuild/linux-mips64el@0.19.12': + optional: true + '@esbuild/linux-mips64el@0.20.2': optional: true @@ -11605,6 +11792,9 @@ snapshots: '@esbuild/linux-mips64el@0.24.2': optional: true + '@esbuild/linux-ppc64@0.19.12': + optional: true + '@esbuild/linux-ppc64@0.20.2': optional: true @@ -11617,6 +11807,9 @@ snapshots: '@esbuild/linux-ppc64@0.24.2': optional: true + '@esbuild/linux-riscv64@0.19.12': + optional: true + '@esbuild/linux-riscv64@0.20.2': optional: true @@ -11629,6 +11822,9 @@ snapshots: '@esbuild/linux-riscv64@0.24.2': optional: true + '@esbuild/linux-s390x@0.19.12': + optional: true + '@esbuild/linux-s390x@0.20.2': optional: true @@ -11641,6 +11837,9 @@ snapshots: '@esbuild/linux-s390x@0.24.2': optional: true + '@esbuild/linux-x64@0.19.12': + optional: true + '@esbuild/linux-x64@0.20.2': optional: true @@ -11656,6 +11855,9 @@ snapshots: '@esbuild/netbsd-arm64@0.24.2': optional: true + '@esbuild/netbsd-x64@0.19.12': + optional: true + '@esbuild/netbsd-x64@0.20.2': optional: true @@ -11677,6 +11879,9 @@ snapshots: '@esbuild/openbsd-arm64@0.24.2': optional: true + '@esbuild/openbsd-x64@0.19.12': + optional: true + '@esbuild/openbsd-x64@0.20.2': optional: true @@ -11689,6 +11894,9 @@ snapshots: '@esbuild/openbsd-x64@0.24.2': optional: true + '@esbuild/sunos-x64@0.19.12': + optional: true + '@esbuild/sunos-x64@0.20.2': optional: true @@ -11701,6 +11909,9 @@ snapshots: '@esbuild/sunos-x64@0.24.2': optional: true + '@esbuild/win32-arm64@0.19.12': + optional: true + '@esbuild/win32-arm64@0.20.2': optional: true @@ -11713,6 +11924,9 @@ snapshots: '@esbuild/win32-arm64@0.24.2': optional: true + '@esbuild/win32-ia32@0.19.12': + optional: true + '@esbuild/win32-ia32@0.20.2': optional: true @@ -11725,6 +11939,9 @@ snapshots: '@esbuild/win32-ia32@0.24.2': optional: true + '@esbuild/win32-x64@0.19.12': + optional: true + '@esbuild/win32-x64@0.20.2': optional: true @@ -12225,34 +12442,34 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@nx/nx-darwin-arm64@20.3.0': + '@nx/nx-darwin-arm64@20.3.3': optional: true - '@nx/nx-darwin-x64@20.3.0': + '@nx/nx-darwin-x64@20.3.3': optional: true - '@nx/nx-freebsd-x64@20.3.0': + '@nx/nx-freebsd-x64@20.3.3': optional: true - '@nx/nx-linux-arm-gnueabihf@20.3.0': + '@nx/nx-linux-arm-gnueabihf@20.3.3': optional: true - '@nx/nx-linux-arm64-gnu@20.3.0': + '@nx/nx-linux-arm64-gnu@20.3.3': optional: true - '@nx/nx-linux-arm64-musl@20.3.0': + '@nx/nx-linux-arm64-musl@20.3.3': optional: true - '@nx/nx-linux-x64-gnu@20.3.0': + '@nx/nx-linux-x64-gnu@20.3.3': optional: true - '@nx/nx-linux-x64-musl@20.3.0': + '@nx/nx-linux-x64-musl@20.3.3': optional: true - '@nx/nx-win32-arm64-msvc@20.3.0': + '@nx/nx-win32-arm64-msvc@20.3.3': optional: true - '@nx/nx-win32-x64-msvc@20.3.0': + '@nx/nx-win32-x64-msvc@20.3.3': optional: true '@open-draft/deferred-promise@2.2.0': {} @@ -13574,17 +13791,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) @@ -14586,6 +14803,32 @@ snapshots: transitivePeerDependencies: - supports-color + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + esbuild@0.20.2: optionalDependencies: '@esbuild/aix-ppc64': 0.20.2 @@ -15487,7 +15730,7 @@ snapshots: relateurl: 0.2.7 terser: 5.36.0 - html-webpack-plugin@5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)): + html-webpack-plugin@5.6.3(@rspack/core@1.1.8(@swc/helpers@0.5.15))(webpack@5.97.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -16403,7 +16646,7 @@ snapshots: nwsapi@2.2.16: {} - nx@20.3.0(@swc/core@1.10.1(@swc/helpers@0.5.15)): + nx@20.3.3(@swc/core@1.10.1(@swc/helpers@0.5.15)): dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -16440,16 +16683,16 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 20.3.0 - '@nx/nx-darwin-x64': 20.3.0 - '@nx/nx-freebsd-x64': 20.3.0 - '@nx/nx-linux-arm-gnueabihf': 20.3.0 - '@nx/nx-linux-arm64-gnu': 20.3.0 - '@nx/nx-linux-arm64-musl': 20.3.0 - '@nx/nx-linux-x64-gnu': 20.3.0 - '@nx/nx-linux-x64-musl': 20.3.0 - '@nx/nx-win32-arm64-msvc': 20.3.0 - '@nx/nx-win32-x64-msvc': 20.3.0 + '@nx/nx-darwin-arm64': 20.3.3 + '@nx/nx-darwin-x64': 20.3.3 + '@nx/nx-freebsd-x64': 20.3.3 + '@nx/nx-linux-arm-gnueabihf': 20.3.3 + '@nx/nx-linux-arm64-gnu': 20.3.3 + '@nx/nx-linux-arm64-musl': 20.3.3 + '@nx/nx-linux-x64-gnu': 20.3.3 + '@nx/nx-linux-x64-musl': 20.3.3 + '@nx/nx-win32-arm64-msvc': 20.3.3 + '@nx/nx-win32-x64-msvc': 20.3.3 '@swc/core': 1.10.1(@swc/helpers@0.5.15) transitivePeerDependencies: - debug @@ -17556,7 +17799,7 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swc-loader@0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)): + swc-loader@0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1): dependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) '@swc/counter': 0.1.3 @@ -17635,26 +17878,26 @@ snapshots: type-fest: 2.19.0 unique-string: 3.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2) optionalDependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) esbuild: 0.24.2 - terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)): + terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) esbuild: 0.24.2 @@ -18307,9 +18550,9 @@ snapshots: webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.1) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -18323,7 +18566,7 @@ snapshots: optionalDependencies: webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.97.1) - webpack-dev-middleware@7.4.2(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)): + webpack-dev-middleware@7.4.2(webpack@5.97.1): dependencies: colorette: 2.0.20 memfs: 4.14.1 @@ -18362,7 +18605,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + webpack-dev-middleware: 7.4.2(webpack@5.97.1) ws: 8.18.0 optionalDependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4) @@ -18435,7 +18678,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.2)(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: