diff --git a/e2e/solid-start/basic-solid-query/package.json b/e2e/solid-start/basic-solid-query/package.json new file mode 100644 index 00000000000..5af0b6150a0 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/package.json @@ -0,0 +1,37 @@ +{ + "name": "tanstack-solid-start-e2e-basic-solid-query", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "dev:e2e": "vite dev", + "build": "vite build && tsc --noEmit", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", + "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/solid-query": "^5.66.0", + "@tanstack/solid-query-devtools": "^5.66.0", + "@tanstack/solid-router": "workspace:^", + "@tanstack/solid-router-devtools": "workspace:^", + "@tanstack/solid-router-ssr-query": "workspace:^", + "@tanstack/solid-start": "workspace:^", + "solid-js": "^1.9.5", + "redaxios": "^0.5.1", + "tailwind-merge": "^2.6.0", + "vite": "^7.1.7" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/node": "^22.10.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.1", + "srvx": "^0.8.6", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "vite-plugin-solid": "^2.11.8", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/e2e/solid-start/basic-solid-query/playwright.config.ts b/e2e/solid-start/basic-solid-query/playwright.config.ts new file mode 100644 index 00000000000..84636ab0f9a --- /dev/null +++ b/e2e/solid-start/basic-solid-query/playwright.config.ts @@ -0,0 +1,39 @@ +import { defineConfig, devices } from '@playwright/test' +import { + getDummyServerPort, + getTestServerPort, +} from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +const PORT = await getTestServerPort(packageJson.name) +const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) + +const baseURL = `http://localhost:${PORT}` + +export default defineConfig({ + testDir: './tests', + workers: 1, + + reporter: [['line']], + + globalSetup: './tests/setup/global.setup.ts', + globalTeardown: './tests/setup/global.teardown.ts', + + use: { + baseURL, + }, + + webServer: { + command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/solid-start/basic-solid-query/postcss.config.mjs b/e2e/solid-start/basic-solid-query/postcss.config.mjs new file mode 100644 index 00000000000..2e7af2b7f1a --- /dev/null +++ b/e2e/solid-start/basic-solid-query/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx b/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx new file mode 100644 index 00000000000..02f53708c94 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/components/DefaultCatchBoundary.tsx @@ -0,0 +1,8 @@ +import { + ErrorComponent, + type ErrorComponentProps, +} from '@tanstack/solid-router' + +export function DefaultCatchBoundary(props: ErrorComponentProps) { + return +} diff --git a/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx b/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx new file mode 100644 index 00000000000..8ca552b501d --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/components/NotFound.tsx @@ -0,0 +1,26 @@ +import { Link } from '@tanstack/solid-router' +import type { JSX } from 'solid-js' + +export function NotFound(props?: { children?: JSX.Element }) { + return ( +
+
+ {props?.children || 'The page you are looking for does not exist.'} +
+

+ + + Start Over + +

+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts new file mode 100644 index 00000000000..b7444e1e0c8 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routeTree.gen.ts @@ -0,0 +1,304 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as UsersRouteImport } from './routes/users' +import { Route as PostsRouteImport } from './routes/posts' +import { Route as DeferredRouteImport } from './routes/deferred' +import { Route as IndexRouteImport } from './routes/index' +import { Route as UsersIndexRouteImport } from './routes/users.index' +import { Route as PostsIndexRouteImport } from './routes/posts.index' +import { Route as UsersUserIdRouteImport } from './routes/users.$userId' +import { Route as PostsPostIdRouteImport } from './routes/posts.$postId' +import { Route as ApiUsersRouteImport } from './routes/api.users' +import { Route as PostsPostIdDeepRouteImport } from './routes/posts_.$postId.deep' +import { Route as ApiUsersIdRouteImport } from './routes/api/users.$id' + +const UsersRoute = UsersRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => rootRouteImport, +} as any) +const PostsRoute = PostsRouteImport.update({ + id: '/posts', + path: '/posts', + getParentRoute: () => rootRouteImport, +} as any) +const DeferredRoute = DeferredRouteImport.update({ + id: '/deferred', + path: '/deferred', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const UsersIndexRoute = UsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => UsersRoute, +} as any) +const PostsIndexRoute = PostsIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => PostsRoute, +} as any) +const UsersUserIdRoute = UsersUserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => UsersRoute, +} as any) +const PostsPostIdRoute = PostsPostIdRouteImport.update({ + id: '/$postId', + path: '/$postId', + getParentRoute: () => PostsRoute, +} as any) +const ApiUsersRoute = ApiUsersRouteImport.update({ + id: '/api/users', + path: '/api/users', + getParentRoute: () => rootRouteImport, +} as any) +const PostsPostIdDeepRoute = PostsPostIdDeepRouteImport.update({ + id: '/posts_/$postId/deep', + path: '/posts/$postId/deep', + getParentRoute: () => rootRouteImport, +} as any) +const ApiUsersIdRoute = ApiUsersIdRouteImport.update({ + id: '/$id', + path: '/$id', + getParentRoute: () => ApiUsersRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/deferred': typeof DeferredRoute + '/posts': typeof PostsRouteWithChildren + '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts/': typeof PostsIndexRoute + '/users/': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/deferred': typeof DeferredRoute + '/api/users': typeof ApiUsersRouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts': typeof PostsIndexRoute + '/users': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute + '/posts/$postId/deep': typeof PostsPostIdDeepRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/deferred': typeof DeferredRoute + '/posts': typeof PostsRouteWithChildren + '/users': typeof UsersRouteWithChildren + '/api/users': typeof ApiUsersRouteWithChildren + '/posts/$postId': typeof PostsPostIdRoute + '/users/$userId': typeof UsersUserIdRoute + '/posts/': typeof PostsIndexRoute + '/users/': typeof UsersIndexRoute + '/api/users/$id': typeof ApiUsersIdRoute + '/posts_/$postId/deep': typeof PostsPostIdDeepRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/deferred' + | '/posts' + | '/users' + | '/api/users' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/api/users/$id' + | '/posts/$postId/deep' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/deferred' + | '/api/users' + | '/posts/$postId' + | '/users/$userId' + | '/posts' + | '/users' + | '/api/users/$id' + | '/posts/$postId/deep' + id: + | '__root__' + | '/' + | '/deferred' + | '/posts' + | '/users' + | '/api/users' + | '/posts/$postId' + | '/users/$userId' + | '/posts/' + | '/users/' + | '/api/users/$id' + | '/posts_/$postId/deep' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + DeferredRoute: typeof DeferredRoute + PostsRoute: typeof PostsRouteWithChildren + UsersRoute: typeof UsersRouteWithChildren + ApiUsersRoute: typeof ApiUsersRouteWithChildren + PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute +} + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/users': { + id: '/users' + path: '/users' + fullPath: '/users' + preLoaderRoute: typeof UsersRouteImport + parentRoute: typeof rootRouteImport + } + '/posts': { + id: '/posts' + path: '/posts' + fullPath: '/posts' + preLoaderRoute: typeof PostsRouteImport + parentRoute: typeof rootRouteImport + } + '/deferred': { + id: '/deferred' + path: '/deferred' + fullPath: '/deferred' + preLoaderRoute: typeof DeferredRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/users/': { + id: '/users/' + path: '/' + fullPath: '/users/' + preLoaderRoute: typeof UsersIndexRouteImport + parentRoute: typeof UsersRoute + } + '/posts/': { + id: '/posts/' + path: '/' + fullPath: '/posts/' + preLoaderRoute: typeof PostsIndexRouteImport + parentRoute: typeof PostsRoute + } + '/users/$userId': { + id: '/users/$userId' + path: '/$userId' + fullPath: '/users/$userId' + preLoaderRoute: typeof UsersUserIdRouteImport + parentRoute: typeof UsersRoute + } + '/posts/$postId': { + id: '/posts/$postId' + path: '/$postId' + fullPath: '/posts/$postId' + preLoaderRoute: typeof PostsPostIdRouteImport + parentRoute: typeof PostsRoute + } + '/api/users': { + id: '/api/users' + path: '/api/users' + fullPath: '/api/users' + preLoaderRoute: typeof ApiUsersRouteImport + parentRoute: typeof rootRouteImport + } + '/posts_/$postId/deep': { + id: '/posts_/$postId/deep' + path: '/posts/$postId/deep' + fullPath: '/posts/$postId/deep' + preLoaderRoute: typeof PostsPostIdDeepRouteImport + parentRoute: typeof rootRouteImport + } + '/api/users/$id': { + id: '/api/users/$id' + path: '/$id' + fullPath: '/api/users/$id' + preLoaderRoute: typeof ApiUsersIdRouteImport + parentRoute: typeof ApiUsersRoute + } + } +} + +interface PostsRouteChildren { + PostsPostIdRoute: typeof PostsPostIdRoute + PostsIndexRoute: typeof PostsIndexRoute +} + +const PostsRouteChildren: PostsRouteChildren = { + PostsPostIdRoute: PostsPostIdRoute, + PostsIndexRoute: PostsIndexRoute, +} + +const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren) + +interface UsersRouteChildren { + UsersUserIdRoute: typeof UsersUserIdRoute + UsersIndexRoute: typeof UsersIndexRoute +} + +const UsersRouteChildren: UsersRouteChildren = { + UsersUserIdRoute: UsersUserIdRoute, + UsersIndexRoute: UsersIndexRoute, +} + +const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren) + +interface ApiUsersRouteChildren { + ApiUsersIdRoute: typeof ApiUsersIdRoute +} + +const ApiUsersRouteChildren: ApiUsersRouteChildren = { + ApiUsersIdRoute: ApiUsersIdRoute, +} + +const ApiUsersRouteWithChildren = ApiUsersRoute._addFileChildren( + ApiUsersRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + DeferredRoute: DeferredRoute, + PostsRoute: PostsRouteWithChildren, + UsersRoute: UsersRouteWithChildren, + ApiUsersRoute: ApiUsersRouteWithChildren, + PostsPostIdDeepRoute: PostsPostIdDeepRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/e2e/solid-start/basic-solid-query/src/router.tsx b/e2e/solid-start/basic-solid-query/src/router.tsx new file mode 100644 index 00000000000..c090958c01c --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/router.tsx @@ -0,0 +1,30 @@ +import { QueryClient } from '@tanstack/solid-query' +import { createRouter } from '@tanstack/solid-router' +import { setupRouterSsrQueryIntegration } from '@tanstack/solid-router-ssr-query' +import { routeTree } from './routeTree.gen' +import { DefaultCatchBoundary } from './components/DefaultCatchBoundary' +import { NotFound } from './components/NotFound' + +export function getRouter() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Enable only on client to avoid hydration mismatches + experimental_prefetchInRender: typeof window !== 'undefined', + }, + }, + }) + const router = createRouter({ + routeTree, + context: { queryClient }, + scrollRestoration: true, + defaultPreload: 'intent', + defaultErrorComponent: DefaultCatchBoundary, + defaultNotFoundComponent: () => , + }) + setupRouterSsrQueryIntegration({ + router, + queryClient, + }) + return router +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/__root.tsx b/e2e/solid-start/basic-solid-query/src/routes/__root.tsx new file mode 100644 index 00000000000..737926dd27b --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/__root.tsx @@ -0,0 +1,125 @@ +/// +import { + HeadContent, + Link, + Outlet, + Scripts, + createRootRouteWithContext, +} from '@tanstack/solid-router' +import { TanStackRouterDevtoolsInProd } from '@tanstack/solid-router-devtools' +import type { QueryClient } from '@tanstack/solid-query' +import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary' +import { NotFound } from '~/components/NotFound' +import appCss from '~/styles/app.css?url' +import { seo } from '~/utils/seo' + +export const Route = createRootRouteWithContext<{ + queryClient: QueryClient +}>()({ + head: () => ({ + meta: [ + { + charset: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + ...seo({ + title: + 'TanStack Start | Type-Safe, Client-First, Full-Stack Solid Framework', + description: `TanStack Start is a type-safe, client-first, full-stack Solid framework. `, + }), + ], + links: [ + { rel: 'stylesheet', href: appCss }, + { + rel: 'apple-touch-icon', + sizes: '180x180', + href: '/apple-touch-icon.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '32x32', + href: '/favicon-32x32.png', + }, + { + rel: 'icon', + type: 'image/png', + sizes: '16x16', + href: '/favicon-16x16.png', + }, + { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' }, + { rel: 'icon', href: '/favicon.ico' }, + ], + }), + errorComponent: (props) => { + return ( + + + + ) + }, + notFoundComponent: () => , + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + ) +} + +function RootDocument(props: { children?: any }) { + return ( + + + + + +
+ + Home + {' '} + + Posts + {' '} + + Users + {' '} + + Deferred + +
+
+ {props.children} + + + + + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/api.users.ts b/e2e/solid-start/basic-solid-query/src/routes/api.users.ts new file mode 100644 index 00000000000..37e6e2f9c3f --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/api.users.ts @@ -0,0 +1,25 @@ +import { createFileRoute } from '@tanstack/solid-router' +import { json } from '@tanstack/solid-start' +import axios from 'redaxios' +import type { User } from '../utils/users' + +let queryURL = 'https://jsonplaceholder.typicode.com' + +if (import.meta.env.VITE_NODE_ENV === 'test') { + queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` +} + +export const Route = createFileRoute('/api/users')({ + server: { + handlers: { + GET: async ({ request }) => { + console.info('Fetching users... @', request.url) + const res = await axios.get>(`${queryURL}/users`) + const list = res.data.slice(0, 10) + return json( + list.map((u) => ({ id: u.id, name: u.name, email: u.email })), + ) + }, + }, + }, +}) diff --git a/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts b/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts new file mode 100644 index 00000000000..4a1503f0d75 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/api/users.$id.ts @@ -0,0 +1,31 @@ +import { createFileRoute } from '@tanstack/solid-router' +import { json } from '@tanstack/solid-start' +import axios from 'redaxios' +import type { User } from '../../utils/users' + +let queryURL = 'https://jsonplaceholder.typicode.com' + +if (import.meta.env.VITE_NODE_ENV === 'test') { + queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` +} + +export const Route = createFileRoute('/api/users/$id')({ + server: { + handlers: { + GET: async ({ request, params }) => { + console.info(`Fetching users by id=${params.id}... @`, request.url) + try { + const res = await axios.get(`${queryURL}/users/` + params.id) + return json({ + id: res.data.id, + name: res.data.name, + email: res.data.email, + }) + } catch (e) { + console.error(e) + return json({ error: 'User not found' }, { status: 404 }) + } + }, + }, + }, +}) diff --git a/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx b/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx new file mode 100644 index 00000000000..0e0ced4be6e --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/deferred.tsx @@ -0,0 +1,54 @@ +import { createFileRoute } from '@tanstack/solid-router' +import { queryOptions, useQuery } from '@tanstack/solid-query' +import { Suspense, createSignal } from 'solid-js' + +const deferredQueryOptions = () => + queryOptions({ + queryKey: ['deferred'], + queryFn: async () => { + await new Promise((r) => setTimeout(r, 3000)) + return { + message: `Hello deferred from the server!`, + status: 'success', + time: new Date(), + } + }, + }) + +export const Route = createFileRoute('/deferred')({ + loader: ({ context }) => { + // Kick off loading as early as possible! + context.queryClient.prefetchQuery(deferredQueryOptions()) + }, + component: Deferred, +}) + +function Deferred() { + const [count, setCount] = createSignal(0) + + return ( +
+ + + +
Count: {count()}
+
+ +
+
+ ) +} + +function DeferredQuery() { + const deferredQuery = useQuery(() => deferredQueryOptions()) + const data = () => deferredQuery.data + + return ( +
+

Deferred Query

+
Status: {data()?.status ?? 'loading...'}
+
Message: {data()?.message ?? ''}
+
Time: {data() ? new Date(data()!.time).toISOString() : ''}
+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/index.tsx b/e2e/solid-start/basic-solid-query/src/routes/index.tsx new file mode 100644 index 00000000000..bdfb4c76768 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
+

Welcome Home!

+
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx new file mode 100644 index 00000000000..ad8f8789678 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.$postId.tsx @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/solid-query' +import { ErrorComponent, Link, createFileRoute } from '@tanstack/solid-router' +import { postQueryOptions } from '~/utils/posts' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ context, params }) => { + await context.queryClient.ensureQueryData(postQueryOptions(params.postId)) + }, + errorComponent: PostErrorComponent, + component: PostComponent, +}) + +export function PostErrorComponent({ error }: { error: any }) { + return +} + +function PostComponent() { + const params = Route.useParams() + const postQuery = useQuery(() => postQueryOptions(params().postId)) + + return ( +
+

{postQuery.data?.title}

+
{postQuery.data?.body}
+ + Deep View + +
+ ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx new file mode 100644 index 00000000000..33d0386c195 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Select a post.
+} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts.tsx new file mode 100644 index 00000000000..8d198b5a2d7 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts.tsx @@ -0,0 +1,48 @@ +import { useQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' +import { For, Suspense } from 'solid-js' +import { postsQueryOptions } from '~/utils/posts' + +export const Route = createFileRoute('/posts')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(postsQueryOptions()) + }, + head: () => ({ meta: [{ title: 'Posts' }] }), + component: PostsComponent, +}) + +function PostsComponent() { + const postsQuery = useQuery(() => postsQueryOptions()) + + return ( +
+
    + Loading posts...
}> + + {(post) => ( +
  • + +
    {post.title.substring(0, 20)}
    + +
  • + )} +
    + + +
    + + + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx b/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx new file mode 100644 index 00000000000..9352a3d0867 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/posts_.$postId.deep.tsx @@ -0,0 +1,36 @@ +import { Link, createFileRoute } from '@tanstack/solid-router' +import { useQuery } from '@tanstack/solid-query' +import { postQueryOptions } from '../utils/posts' +import { PostErrorComponent } from './posts.$postId' + +export const Route = createFileRoute('/posts_/$postId/deep')({ + loader: async ({ params: { postId }, context }) => { + const data = await context.queryClient.ensureQueryData( + postQueryOptions(postId), + ) + + return { + title: data.title, + } + }, + head: ({ loaderData }) => ({ + meta: loaderData ? [{ title: loaderData.title }] : undefined, + }), + errorComponent: PostErrorComponent, + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const params = Route.useParams() + const postQuery = useQuery(() => postQueryOptions(params().postId)) + + return ( +
    + + ← All Posts + +

    {postQuery.data?.title}

    +
    {postQuery.data?.body}
    +
    + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx new file mode 100644 index 00000000000..71e4b765b49 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.$userId.tsx @@ -0,0 +1,36 @@ +import { useQuery } from '@tanstack/solid-query' +import { ErrorComponent, createFileRoute } from '@tanstack/solid-router' +import type { ErrorComponentProps } from '@tanstack/solid-router' + +import { NotFound } from '~/components/NotFound' +import { userQueryOptions } from '~/utils/users' + +export const Route = createFileRoute('/users/$userId')({ + loader: async ({ context, params: { userId } }) => { + await context.queryClient.ensureQueryData(userQueryOptions(userId)) + }, + errorComponent: UserErrorComponent, + component: UserComponent, + notFoundComponent: () => { + return User not found + }, +}) + +function UserErrorComponent(props: ErrorComponentProps) { + return +} + +function UserComponent() { + const params = Route.useParams() + const userQuery = useQuery(() => userQueryOptions(params().userId)) + const user = () => userQuery.data + + return ( +
    +

    + {user()?.name ?? 'loading...'} +

    +
    {user()?.email ?? ''}
    +
    + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx new file mode 100644 index 00000000000..bbc96801a99 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/users/')({ + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return
    Select a user.
    +} diff --git a/e2e/solid-start/basic-solid-query/src/routes/users.tsx b/e2e/solid-start/basic-solid-query/src/routes/users.tsx new file mode 100644 index 00000000000..d957122a6ed --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/routes/users.tsx @@ -0,0 +1,46 @@ +import { useQuery } from '@tanstack/solid-query' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' +import { For } from 'solid-js' + +import { usersQueryOptions } from '~/utils/users' + +export const Route = createFileRoute('/users')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(usersQueryOptions()) + }, + component: UsersComponent, +}) + +function UsersComponent() { + const usersQuery = useQuery(() => usersQueryOptions()) + + return ( +
    +
      + + {(user) => ( +
    • + +
      {user.name}
      + +
    • + )} +
      +
    +
    + +
    + ) +} diff --git a/e2e/solid-start/basic-solid-query/src/styles/app.css b/e2e/solid-start/basic-solid-query/src/styles/app.css new file mode 100644 index 00000000000..401c9469b21 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/styles/app.css @@ -0,0 +1,8 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply text-base; + font-family: system-ui, sans-serif; +} diff --git a/e2e/solid-start/basic-solid-query/src/utils/posts.tsx b/e2e/solid-start/basic-solid-query/src/utils/posts.tsx new file mode 100644 index 00000000000..c280c9bfbf7 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/utils/posts.tsx @@ -0,0 +1,33 @@ +import { queryOptions } from '@tanstack/solid-query' +import axios from 'redaxios' + +export type PostType = { + id: string + title: string + body: string +} + +export const postsQueryOptions = () => + queryOptions({ + queryKey: ['posts'], + queryFn: async () => { + console.info('Fetching posts...') + await new Promise((r) => setTimeout(r, 500)) + return axios + .get>('https://jsonplaceholder.typicode.com/posts') + .then((r) => r.data.slice(0, 10)) + }, + }) + +export const postQueryOptions = (postId: string) => + queryOptions({ + queryKey: ['posts', postId], + queryFn: async () => { + console.info(`Fetching post with id ${postId}...`) + await new Promise((r) => setTimeout(r, 500)) + + return axios + .get(`https://jsonplaceholder.typicode.com/posts/${postId}`) + .then((r) => r.data) + }, + }) diff --git a/e2e/solid-start/basic-solid-query/src/utils/seo.ts b/e2e/solid-start/basic-solid-query/src/utils/seo.ts new file mode 100644 index 00000000000..f909e9e5fc8 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/utils/seo.ts @@ -0,0 +1,30 @@ +export function seo(params: { + title: string + description?: string + keywords?: string + image?: string +}) { + const { title, description, keywords, image } = params + + const tags = [ + { title }, + { name: 'description', content: description }, + { name: 'keywords', content: keywords }, + { name: 'twitter:title', content: title }, + { name: 'twitter:description', content: description }, + { name: 'twitter:creator', content: '@tannerlinsley' }, + { name: 'twitter:site', content: '@tannerlinsley' }, + { name: 'og:type', content: 'website' }, + { name: 'og:title', content: title }, + { name: 'og:description', content: description }, + ...(image + ? [ + { name: 'twitter:image', content: image }, + { name: 'twitter:card', content: 'summary_large_image' }, + { name: 'og:image', content: image }, + ] + : []), + ] + + return tags +} diff --git a/e2e/solid-start/basic-solid-query/src/utils/users.tsx b/e2e/solid-start/basic-solid-query/src/utils/users.tsx new file mode 100644 index 00000000000..52d659b0016 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/src/utils/users.tsx @@ -0,0 +1,37 @@ +import { queryOptions } from '@tanstack/solid-query' +import axios from 'redaxios' + +export type User = { + id: number + name: string + email: string +} + +const PORT = + import.meta.env.VITE_SERVER_PORT || process.env.VITE_SERVER_PORT || 3000 + +export const DEPLOY_URL = `http://localhost:${PORT}` + +export const usersQueryOptions = () => + queryOptions({ + queryKey: ['users'], + queryFn: () => + axios + .get>(DEPLOY_URL + '/api/users') + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch users') + }), + }) + +export const userQueryOptions = (id: string) => + queryOptions({ + queryKey: ['users', id], + queryFn: () => + axios + .get(DEPLOY_URL + '/api/users/' + id) + .then((r) => r.data) + .catch(() => { + throw new Error('Failed to fetch user') + }), + }) diff --git a/e2e/solid-start/basic-solid-query/tailwind.config.mjs b/e2e/solid-start/basic-solid-query/tailwind.config.mjs new file mode 100644 index 00000000000..be882983bf8 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tailwind.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/e2e/solid-start/basic-solid-query/tests/app.spec.ts b/e2e/solid-start/basic-solid-query/tests/app.spec.ts new file mode 100644 index 00000000000..ed111dd0b8b --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tests/app.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from '@playwright/test' + +test('Navigating to post', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'Posts' }).click() + await page.getByRole('link', { name: 'sunt aut facere repe' }).click() + await page.getByRole('link', { name: 'Deep View' }).click() + await expect(page.getByRole('heading')).toContainText('sunt aut facere') +}) + +test('Navigating to user', async ({ page }) => { + await page.goto('/') + + await page.getByRole('link', { name: 'Users' }).click() + await page.getByRole('link', { name: 'Leanne Graham' }).click() + await expect(page.getByRole('heading')).toContainText('Leanne Graham') +}) diff --git a/e2e/solid-start/basic-solid-query/tests/setup/global.setup.ts b/e2e/solid-start/basic-solid-query/tests/setup/global.setup.ts new file mode 100644 index 00000000000..3593d10ab90 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tests/setup/global.setup.ts @@ -0,0 +1,6 @@ +import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' +import packageJson from '../../package.json' with { type: 'json' } + +export default async function setup() { + await e2eStartDummyServer(packageJson.name) +} diff --git a/e2e/solid-start/basic-solid-query/tests/setup/global.teardown.ts b/e2e/solid-start/basic-solid-query/tests/setup/global.teardown.ts new file mode 100644 index 00000000000..62fd79911cc --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tests/setup/global.teardown.ts @@ -0,0 +1,6 @@ +import { e2eStopDummyServer } from '@tanstack/router-e2e-utils' +import packageJson from '../../package.json' with { type: 'json' } + +export default async function teardown() { + await e2eStopDummyServer(packageJson.name) +} diff --git a/e2e/solid-start/basic-solid-query/tsconfig.json b/e2e/solid-start/basic-solid-query/tsconfig.json new file mode 100644 index 00000000000..ed8b73fa2dd --- /dev/null +++ b/e2e/solid-start/basic-solid-query/tsconfig.json @@ -0,0 +1,24 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true, + "types": ["vite/client"] + } +} diff --git a/e2e/solid-start/basic-solid-query/vite.config.ts b/e2e/solid-start/basic-solid-query/vite.config.ts new file mode 100644 index 00000000000..a0ce060b3a2 --- /dev/null +++ b/e2e/solid-start/basic-solid-query/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import tsConfigPaths from 'vite-tsconfig-paths' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [ + tsConfigPaths({ + projects: ['./tsconfig.json'], + }), + tanstackStart(), + solid({ ssr: true }), + ], +}) diff --git a/e2e/solid-start/query-integration/package.json b/e2e/solid-start/query-integration/package.json new file mode 100644 index 00000000000..53ea397f33b --- /dev/null +++ b/e2e/solid-start/query-integration/package.json @@ -0,0 +1,36 @@ +{ + "name": "tanstack-solid-start-e2e-query-integration", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vite dev --port 3000", + "dev:e2e": "vite dev", + "build": "vite build && tsc --noEmit", + "start": "pnpx srvx --prod -s ../client dist/server/server.js", + "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" + }, + "dependencies": { + "@tanstack/solid-query": "^5.66.0", + "@tanstack/solid-query-devtools": "^5.66.0", + "@tanstack/solid-router": "workspace:^", + "@tanstack/solid-router-devtools": "workspace:^", + "@tanstack/solid-router-ssr-query": "workspace:^", + "@tanstack/solid-start": "workspace:^", + "solid-js": "^1.9.5", + "tailwind-merge": "^2.6.0", + "vite": "^7.1.7", + "zod": "^3.24.2" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@tanstack/router-e2e-utils": "workspace:^", + "@types/node": "^22.10.2", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "vite-plugin-solid": "^2.11.8", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/e2e/solid-start/query-integration/playwright.config.ts b/e2e/solid-start/query-integration/playwright.config.ts new file mode 100644 index 00000000000..c09b27e29f5 --- /dev/null +++ b/e2e/solid-start/query-integration/playwright.config.ts @@ -0,0 +1,36 @@ +import { defineConfig, devices } from '@playwright/test' +import { + getDummyServerPort, + getTestServerPort, +} from '@tanstack/router-e2e-utils' +import packageJson from './package.json' with { type: 'json' } + +const PORT = await getTestServerPort(packageJson.name) +const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) + +const baseURL = `http://localhost:${PORT}` + +export default defineConfig({ + testDir: './tests', + workers: 1, + + reporter: [['line']], + + use: { + baseURL, + }, + + webServer: { + command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`, + url: baseURL, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/solid-start/query-integration/postcss.config.mjs b/e2e/solid-start/query-integration/postcss.config.mjs new file mode 100644 index 00000000000..2e7af2b7f1a --- /dev/null +++ b/e2e/solid-start/query-integration/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/e2e/solid-start/query-integration/src/queryOptions.ts b/e2e/solid-start/query-integration/src/queryOptions.ts new file mode 100644 index 00000000000..10a5073f5e7 --- /dev/null +++ b/e2e/solid-start/query-integration/src/queryOptions.ts @@ -0,0 +1,16 @@ +import { queryOptions } from '@tanstack/solid-query' + +export const makeQueryOptions = (key: string) => + queryOptions({ + queryKey: ['e2e-test-query-integration', key], + queryFn: async () => { + console.log('fetching query data') + await new Promise((resolve) => { + setTimeout(resolve, 500) + }) + const result = typeof window !== 'undefined' ? 'client' : 'server' + console.log('query data result', result) + return result + }, + staleTime: Infinity, + }) diff --git a/e2e/solid-start/query-integration/src/routeTree.gen.ts b/e2e/solid-start/query-integration/src/routeTree.gen.ts new file mode 100644 index 00000000000..bdd21630383 --- /dev/null +++ b/e2e/solid-start/query-integration/src/routeTree.gen.ts @@ -0,0 +1,104 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as UseQueryRouteImport } from './routes/useQuery' +import { Route as IndexRouteImport } from './routes/index' +import { Route as LoaderFetchQueryTypeRouteImport } from './routes/loader-fetchQuery/$type' + +const UseQueryRoute = UseQueryRouteImport.update({ + id: '/useQuery', + path: '/useQuery', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const LoaderFetchQueryTypeRoute = LoaderFetchQueryTypeRouteImport.update({ + id: '/loader-fetchQuery/$type', + path: '/loader-fetchQuery/$type', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/useQuery': typeof UseQueryRoute + '/loader-fetchQuery/$type': typeof LoaderFetchQueryTypeRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/useQuery': typeof UseQueryRoute + '/loader-fetchQuery/$type': typeof LoaderFetchQueryTypeRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/useQuery': typeof UseQueryRoute + '/loader-fetchQuery/$type': typeof LoaderFetchQueryTypeRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/useQuery' | '/loader-fetchQuery/$type' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/useQuery' | '/loader-fetchQuery/$type' + id: '__root__' | '/' | '/useQuery' | '/loader-fetchQuery/$type' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + UseQueryRoute: typeof UseQueryRoute + LoaderFetchQueryTypeRoute: typeof LoaderFetchQueryTypeRoute +} + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/useQuery': { + id: '/useQuery' + path: '/useQuery' + fullPath: '/useQuery' + preLoaderRoute: typeof UseQueryRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/loader-fetchQuery/$type': { + id: '/loader-fetchQuery/$type' + path: '/loader-fetchQuery/$type' + fullPath: '/loader-fetchQuery/$type' + preLoaderRoute: typeof LoaderFetchQueryTypeRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + UseQueryRoute: UseQueryRoute, + LoaderFetchQueryTypeRoute: LoaderFetchQueryTypeRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/solid-start' +declare module '@tanstack/solid-start' { + interface Register { + ssr: true + router: Awaited> + } +} diff --git a/e2e/solid-start/query-integration/src/router.tsx b/e2e/solid-start/query-integration/src/router.tsx new file mode 100644 index 00000000000..dcd13f0fdae --- /dev/null +++ b/e2e/solid-start/query-integration/src/router.tsx @@ -0,0 +1,26 @@ +import { QueryClient } from '@tanstack/solid-query' +import { createRouter } from '@tanstack/solid-router' +import { setupRouterSsrQueryIntegration } from '@tanstack/solid-router-ssr-query' +import { routeTree } from './routeTree.gen' + +export function getRouter() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // Enable only on client to avoid hydration mismatches + experimental_prefetchInRender: typeof window !== 'undefined', + }, + }, + }) + const router = createRouter({ + routeTree, + context: { queryClient }, + scrollRestoration: true, + defaultPreload: 'intent', + }) + setupRouterSsrQueryIntegration({ + router, + queryClient, + }) + return router +} diff --git a/e2e/solid-start/query-integration/src/routes/__root.tsx b/e2e/solid-start/query-integration/src/routes/__root.tsx new file mode 100644 index 00000000000..8c5639a42b1 --- /dev/null +++ b/e2e/solid-start/query-integration/src/routes/__root.tsx @@ -0,0 +1,84 @@ +/// +import { + HeadContent, + Link, + Scripts, + createRootRouteWithContext, +} from '@tanstack/solid-router' +import { TanStackRouterDevtoolsInProd } from '@tanstack/solid-router-devtools' +import type { QueryClient } from '@tanstack/solid-query' +import appCss from '~/styles/app.css?url' + +export const Route = createRootRouteWithContext<{ + queryClient: QueryClient +}>()({ + head: () => ({ + meta: [ + { + charset: 'utf-8', + }, + ], + links: [{ rel: 'stylesheet', href: appCss }], + }), + shellComponent: RootDocument, +}) + +function RootDocument(props: { children?: any }) { + return ( + + + + + +
    + + Home + {' '} + + fetchQuery (sync) + {' '} + + fetchQuery (async) + {' '} + + useQuery + {' '} + {/* + useSuspenseQuery + {' '} */} +
    +
    + {props.children} + + + + + ) +} diff --git a/e2e/solid-start/query-integration/src/routes/index.tsx b/e2e/solid-start/query-integration/src/routes/index.tsx new file mode 100644 index 00000000000..144674be764 --- /dev/null +++ b/e2e/solid-start/query-integration/src/routes/index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: Home, +}) + +function Home() { + return ( +
    +

    Query Integration E2E tests

    +
    + ) +} diff --git a/e2e/solid-start/query-integration/src/routes/loader-fetchQuery/$type.tsx b/e2e/solid-start/query-integration/src/routes/loader-fetchQuery/$type.tsx new file mode 100644 index 00000000000..479a1f80703 --- /dev/null +++ b/e2e/solid-start/query-integration/src/routes/loader-fetchQuery/$type.tsx @@ -0,0 +1,49 @@ +import { useQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' +import z from 'zod' +import { makeQueryOptions } from '~/queryOptions' + +export const Route = createFileRoute('/loader-fetchQuery/$type')({ + component: RouteComponent, + params: { + parse: ({ type }) => + z + .object({ + type: z.union([z.literal('sync'), z.literal('async')]), + }) + .parse({ type }), + }, + context: ({ params }) => ({ + queryOptions: makeQueryOptions(`loader-fetchQuery-${params.type}`), + }), + loader: async ({ context, params }) => { + await context.queryClient.ensureQueryData(context.queryOptions) + if (params.type === 'sync') { + return context.queryClient.getQueryData( + context.queryOptions.queryKey, + ) as string + } + return undefined as any + }, +}) + +function RouteComponent() { + const params = Route.useParams() + const loaderData = Route.useLoaderData() + const query = useQuery(() => { + return makeQueryOptions(`loader-fetchQuery-${params().type}`) + }) + + return ( +
    +
    + loader data:{' '} +
    {loaderData() ?? 'undefined'}
    +
    +
    + query data:{' '} +
    {query.data ?? 'loading...'}
    +
    +
    + ) +} diff --git a/e2e/solid-start/query-integration/src/routes/useQuery.tsx b/e2e/solid-start/query-integration/src/routes/useQuery.tsx new file mode 100644 index 00000000000..669cd454a6e --- /dev/null +++ b/e2e/solid-start/query-integration/src/routes/useQuery.tsx @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' +import { makeQueryOptions } from '~/queryOptions' + +const qOptions = makeQueryOptions('useQuery') + +export const Route = createFileRoute('/useQuery')({ + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(qOptions) + }, + component: RouteComponent, +}) + +function RouteComponent() { + const query = useQuery(() => qOptions) + return ( +
    +
    + query data:{' '} +
    {query.data ?? 'loading...'}
    +
    +
    + ) +} diff --git a/e2e/solid-start/query-integration/src/styles/app.css b/e2e/solid-start/query-integration/src/styles/app.css new file mode 100644 index 00000000000..401c9469b21 --- /dev/null +++ b/e2e/solid-start/query-integration/src/styles/app.css @@ -0,0 +1,8 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply text-base; + font-family: system-ui, sans-serif; +} diff --git a/e2e/solid-start/query-integration/tailwind.config.mjs b/e2e/solid-start/query-integration/tailwind.config.mjs new file mode 100644 index 00000000000..be882983bf8 --- /dev/null +++ b/e2e/solid-start/query-integration/tailwind.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/e2e/solid-start/query-integration/tests/app.spec.ts b/e2e/solid-start/query-integration/tests/app.spec.ts new file mode 100644 index 00000000000..90b7f8deb2a --- /dev/null +++ b/e2e/solid-start/query-integration/tests/app.spec.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' + +test.describe('queries are streamed from the server', () => { + test('direct visit - loader on server runs fetchQuery and awaits it', async ({ + page, + }) => { + await page.goto('/loader-fetchQuery/sync') + + const queryData = page.getByTestId('query-data') + await expect(queryData).toHaveText('server') + + const loaderData = page.getByTestId('loader-data') + await expect(loaderData).toHaveText('server') + }) + test('direct visit - loader on server runs fetchQuery and does not await it', async ({ + page, + }) => { + await page.goto('/loader-fetchQuery/async') + + const queryData = page.getByTestId('query-data') + await expect(queryData).toHaveText('server') + + const loaderData = page.getByTestId('loader-data') + await expect(loaderData).toHaveText('undefined') + }) + + test('useQuery', async ({ page }) => { + await page.goto('/useQuery') + + const queryData = page.getByTestId('query-data') + await expect(queryData).toHaveText('server') + }) +}) diff --git a/e2e/solid-start/query-integration/tsconfig.json b/e2e/solid-start/query-integration/tsconfig.json new file mode 100644 index 00000000000..ed8b73fa2dd --- /dev/null +++ b/e2e/solid-start/query-integration/tsconfig.json @@ -0,0 +1,24 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "target": "ES2022", + "allowJs": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + }, + "noEmit": true, + "types": ["vite/client"] + } +} diff --git a/e2e/solid-start/query-integration/vite.config.ts b/e2e/solid-start/query-integration/vite.config.ts new file mode 100644 index 00000000000..a0ce060b3a2 --- /dev/null +++ b/e2e/solid-start/query-integration/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import tsConfigPaths from 'vite-tsconfig-paths' +import { tanstackStart } from '@tanstack/solid-start/plugin/vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [ + tsConfigPaths({ + projects: ['./tsconfig.json'], + }), + tanstackStart(), + solid({ ssr: true }), + ], +}) diff --git a/package.json b/package.json index ebf70087397..2d5daa7d8bb 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@tanstack/router-vite-plugin": "workspace:*", "@tanstack/router-ssr-query-core": "workspace:*", "@tanstack/react-router-ssr-query": "workspace:*", + "@tanstack/solid-router-ssr-query": "workspace:*", "@tanstack/zod-adapter": "workspace:*", "@tanstack/valibot-adapter": "workspace:*", "@tanstack/arktype-adapter": "workspace:*", diff --git a/packages/solid-router-ssr-query/README.md b/packages/solid-router-ssr-query/README.md new file mode 100644 index 00000000000..7311580061c --- /dev/null +++ b/packages/solid-router-ssr-query/README.md @@ -0,0 +1,38 @@ +# @tanstack/solid-router-ssr-query + +SSR query integration for TanStack Solid Router and TanStack Solid Query. + +This package provides seamless integration between TanStack Router and TanStack Query for server-side rendering in Solid applications. + +## Installation + +```bash +npm install @tanstack/solid-router-ssr-query +# or +pnpm add @tanstack/solid-router-ssr-query +# or +yarn add @tanstack/solid-router-ssr-query +``` + +## Usage + +```tsx +import { QueryClient } from '@tanstack/solid-query' +import { createRouter } from '@tanstack/solid-router' +import { setupRouterSsrQueryIntegration } from '@tanstack/solid-router-ssr-query' + +const queryClient = new QueryClient() +const router = createRouter({ + routeTree, + context: { queryClient }, +}) + +setupRouterSsrQueryIntegration({ + router, + queryClient, +}) +``` + +## License + +MIT diff --git a/packages/solid-router-ssr-query/eslint.config.js b/packages/solid-router-ssr-query/eslint.config.js new file mode 100644 index 00000000000..df59eb36f3d --- /dev/null +++ b/packages/solid-router-ssr-query/eslint.config.js @@ -0,0 +1,12 @@ +// @ts-check + +import pluginSolid from 'eslint-plugin-solid/configs/typescript' +import rootConfig from '../../eslint.config.js' + +export default [ + ...rootConfig, + { + files: ['**/*.{ts,tsx}'], + ...pluginSolid, + }, +] diff --git a/packages/solid-router-ssr-query/package.json b/packages/solid-router-ssr-query/package.json new file mode 100644 index 00000000000..aca33ee2771 --- /dev/null +++ b/packages/solid-router-ssr-query/package.json @@ -0,0 +1,82 @@ +{ + "name": "@tanstack/solid-router-ssr-query", + "version": "1.132.25", + "description": "Modern and scalable routing for Solid applications", + "author": "Tanner Linsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/router.git", + "directory": "packages/solid-router-ssr-query" + }, + "homepage": "https://tanstack.com/router", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "keywords": [ + "solidjs", + "location", + "router", + "routing", + "async", + "async router", + "typescript" + ], + "scripts": { + "clean": "rimraf ./dist && rimraf ./coverage", + "test:eslint": "eslint ./src", + "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", + "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js", + "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", + "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", + "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", + "test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js", + "test:types:ts59": "tsc", + "test:unit": "exit 0; vitest", + "test:unit:dev": "pnpm run test:unit --watch", + "test:build": "publint --strict && attw --ignore-rules no-resolution --pack .", + "build": "vite build" + }, + "type": "module", + "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "engines": { + "node": ">=12" + }, + "dependencies": { + "@tanstack/router-ssr-query-core": "workspace:*", + "eslint-plugin-solid": "^0.14.5" + }, + "devDependencies": { + "@tanstack/solid-query": ">=5.66.0", + "@tanstack/solid-router": "workspace:*", + "solid-js": "^1.9.5", + "vite-plugin-solid": "^2.11.8" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.66.0", + "@tanstack/solid-query": ">=5.66.2", + "@tanstack/solid-router": ">=1.127.0", + "solid-js": "^1.9.5" + } +} diff --git a/packages/solid-router-ssr-query/src/index.tsx b/packages/solid-router-ssr-query/src/index.tsx new file mode 100644 index 00000000000..91f764f15f0 --- /dev/null +++ b/packages/solid-router-ssr-query/src/index.tsx @@ -0,0 +1,32 @@ +import { QueryClientProvider } from '@tanstack/solid-query' +import { setupCoreRouterSsrQueryIntegration } from '@tanstack/router-ssr-query-core' +import type { RouterSsrQueryOptions } from '@tanstack/router-ssr-query-core' +import type { AnyRouter } from '@tanstack/solid-router' +import type { JSX } from 'solid-js' + +export type Options = + RouterSsrQueryOptions & { + wrapQueryClient?: boolean + } + +export function setupRouterSsrQueryIntegration( + opts: Options, +) { + setupCoreRouterSsrQueryIntegration(opts) + + if (opts.wrapQueryClient === false) { + return + } + + const OGWrap = + opts.router.options.Wrap || + ((props: { children: JSX.Element }) => props.children) + + opts.router.options.Wrap = (props) => { + return ( + + {props.children} + + ) + } +} diff --git a/packages/solid-router-ssr-query/tsconfig.json b/packages/solid-router-ssr-query/tsconfig.json new file mode 100644 index 00000000000..e24672b5de0 --- /dev/null +++ b/packages/solid-router-ssr-query/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js" + }, + "include": ["src", "tests", "vite.config.ts"] +} diff --git a/packages/solid-router-ssr-query/vite.config.ts b/packages/solid-router-ssr-query/vite.config.ts new file mode 100644 index 00000000000..b78cdf1373b --- /dev/null +++ b/packages/solid-router-ssr-query/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/config/vite' +import solid from 'vite-plugin-solid' +import packageJson from './package.json' +import type { UserConfig } from 'vitest/config' + +const config = defineConfig({ + plugins: [solid()] as UserConfig['plugins'], + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'jsdom', + typecheck: { enabled: true }, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: './src/index.tsx', + srcDir: './src', + }), +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 032d8235e26..0d88426cf18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,7 @@ overrides: '@tanstack/router-vite-plugin': workspace:* '@tanstack/router-ssr-query-core': workspace:* '@tanstack/react-router-ssr-query': workspace:* + '@tanstack/solid-router-ssr-query': workspace:* '@tanstack/zod-adapter': workspace:* '@tanstack/valibot-adapter': workspace:* '@tanstack/arktype-adapter': workspace:* @@ -2512,6 +2513,70 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + e2e/solid-start/basic-solid-query: + dependencies: + '@tanstack/solid-query': + specifier: ^5.66.0 + version: 5.72.2(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.66.0 + version: 5.72.2(@tanstack/solid-query@5.72.2(solid-js@1.9.5))(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: workspace:^ + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + '@tanstack/solid-router-ssr-query': + specifier: workspace:* + version: link:../../../packages/solid-router-ssr-query + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + vite: + specifier: ^7.1.7 + version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) + devDependencies: + '@playwright/test': + specifier: ^1.52.0 + version: 1.52.0 + '@tanstack/router-e2e-utils': + specifier: workspace:^ + version: link:../../e2e-utils + '@types/node': + specifier: 22.10.2 + version: 22.10.2 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.6) + postcss: + specifier: ^8.5.1 + version: 8.5.6 + srvx: + specifier: ^0.8.6 + version: 0.8.7 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + typescript: + specifier: ^5.7.2 + version: 5.9.2 + vite-plugin-solid: + specifier: ^2.11.8 + version: 2.11.8(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + e2e/solid-start/basic-tsr-config: dependencies: '@tanstack/solid-router': @@ -2613,6 +2678,67 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.8.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + e2e/solid-start/query-integration: + dependencies: + '@tanstack/solid-query': + specifier: ^5.66.0 + version: 5.72.2(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.66.0 + version: 5.72.2(@tanstack/solid-query@5.72.2(solid-js@1.9.5))(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: workspace:^ + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + '@tanstack/solid-router-ssr-query': + specifier: workspace:* + version: link:../../../packages/solid-router-ssr-query + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + vite: + specifier: ^7.1.7 + version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) + zod: + specifier: ^3.24.2 + version: 3.25.57 + devDependencies: + '@playwright/test': + specifier: ^1.52.0 + version: 1.52.0 + '@tanstack/router-e2e-utils': + specifier: workspace:^ + version: link:../../e2e-utils + '@types/node': + specifier: 22.10.2 + version: 22.10.2 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.6) + postcss: + specifier: ^8.5.1 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + typescript: + specifier: ^5.7.2 + version: 5.9.2 + vite-plugin-solid: + specifier: ^2.11.8 + version: 2.11.8(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + e2e/solid-start/scroll-restoration: dependencies: '@tanstack/solid-router': @@ -5306,7 +5432,7 @@ importers: version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) + version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -7257,6 +7383,31 @@ importers: specifier: ^2.11.8 version: 2.11.8(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + packages/solid-router-ssr-query: + dependencies: + '@tanstack/query-core': + specifier: 5.66.0 + version: 5.66.0 + '@tanstack/router-ssr-query-core': + specifier: workspace:* + version: link:../router-ssr-query-core + eslint-plugin-solid: + specifier: ^0.14.5 + version: 0.14.5(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + devDependencies: + '@tanstack/solid-query': + specifier: '>=5.66.0' + version: 5.72.2(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: workspace:* + version: link:../solid-router + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + vite-plugin-solid: + specifier: ^2.11.8 + version: 2.11.8(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) + packages/solid-start: dependencies: '@tanstack/solid-router': @@ -18058,7 +18209,7 @@ snapshots: '@eslint-react/eff': 1.26.2 '@typescript-eslint/types': 8.23.0 '@typescript-eslint/typescript-estree': 8.23.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) string-ts: 2.2.1 ts-pattern: 5.6.2 transitivePeerDependencies: @@ -18076,7 +18227,7 @@ snapshots: '@typescript-eslint/scope-manager': 8.23.0 '@typescript-eslint/type-utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) '@typescript-eslint/types': 8.23.0 - '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) birecord: 0.1.1 ts-pattern: 5.6.2 transitivePeerDependencies: @@ -18114,7 +18265,7 @@ snapshots: '@eslint-react/var': 1.26.2(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) '@typescript-eslint/scope-manager': 8.23.0 '@typescript-eslint/types': 8.23.0 - '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) ts-pattern: 5.6.2 transitivePeerDependencies: - eslint @@ -18138,7 +18289,7 @@ snapshots: '@eslint-react/eff': 1.26.2 '@typescript-eslint/scope-manager': 8.23.0 '@typescript-eslint/types': 8.23.0 - '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) string-ts: 2.2.1 ts-pattern: 5.6.2 transitivePeerDependencies: @@ -23360,7 +23511,7 @@ snapshots: eslint-plugin-solid@0.14.5(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2): dependencies: - '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.44.1(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) eslint: 9.22.0(jiti@2.6.0) estraverse: 5.3.0 is-html: 2.0.0 @@ -27103,7 +27254,7 @@ snapshots: optionalDependencies: vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) - vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@25.0.1)(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -27132,7 +27283,7 @@ snapshots: '@types/node': 22.10.2 '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4) '@vitest/ui': 3.0.6(vitest@3.2.4) - jsdom: 25.0.1 + jsdom: 27.0.0(postcss@8.5.6) transitivePeerDependencies: - jiti - less @@ -27147,7 +27298,7 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@25.0.1)(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -27176,7 +27327,7 @@ snapshots: '@types/node': 22.10.2 '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4) '@vitest/ui': 3.0.6(vitest@3.2.4) - jsdom: 27.0.0(postcss@8.5.6) + jsdom: 25.0.1 transitivePeerDependencies: - jiti - less