1
0
mirror of synced 2026-02-04 12:08:33 -05:00

Compare commits

...

1 Commits

Author SHA1 Message Date
Siddharth Suresh
bddc1d88f5 Revert "feat: upgrade tanstack query to v5 (#4360)"
This reverts commit ce1a603b26.
2025-01-30 20:26:17 +05:30
65 changed files with 433 additions and 550 deletions

View File

@@ -1,11 +0,0 @@
---
"@blitzjs/next": major
"@blitzjs/rpc": major
"blitz": major
"@blitzjs/auth": major
"@blitzjs/codemod": major
"@blitzjs/config": major
"@blitzjs/generator": major
---
TODO: Upgrade @tanstack/react-query to v5.1.1

View File

@@ -148,7 +148,7 @@ jobs:
- name: Install playwright
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
run: |
pnpx playwright@1.49.1 install --with-deps
pnpx playwright@1.28.0 install --with-deps
shell: bash
- name: Build

View File

@@ -29,7 +29,7 @@ jobs:
run: |
pr="$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})"
head_sha="$(echo "$pr" | jq -r .head.sha)"
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4
@@ -39,7 +39,7 @@ jobs:
- name: Setup PNPM
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with:
version: 8.6.6
version: 8.9.0
- name: Setup Node
uses: actions/setup-node@v4

View File

@@ -37,7 +37,7 @@ jobs:
- name: Pre-publish
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with:
version: 8.6.6
version: 8.9.0
- run: pnpm install
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1

View File

@@ -19,7 +19,7 @@
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "^4.5.0",
"@tanstack/react-query": "5.51.1",
"@tanstack/react-query": "4.0.10",
"blitz": "2.2.1",
"flatted": "3.2.7",
"next": "15.0.1",

View File

@@ -1,4 +0,0 @@
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return "Loading..."
}

View File

@@ -1,6 +1,6 @@
"use client"
import {useQuery, useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useQuery, useMutation} from "@blitzjs/rpc"
import logout from "../auth/mutations/logout"
import getCurrentUser from "../users/queries/getCurrentUser"
import {useTransition} from "react"
@@ -8,7 +8,7 @@ import {useRouter} from "next/navigation"
export default function Test() {
const router = useRouter()
const [user] = useSuspenseQuery(getCurrentUser, null)
const [user] = useQuery(getCurrentUser, null)
const [isPending, startTransition] = useTransition()
const [logoutMutation] = useMutation(logout)
console.log(user)

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,3 +1,4 @@
const { withNextAuthAdapter } = require("@blitzjs/auth/next-auth")
const { withBlitz } = require("@blitzjs/next")
/**
@@ -10,4 +11,4 @@ const config = {
},
}
module.exports = withBlitz(config)
module.exports = withBlitz(withNextAuthAdapter(config))

View File

@@ -0,0 +1,48 @@
import { api } from "src/blitz-server"
import GithubProvider from "next-auth/providers/github"
import EmailProvider from "next-auth/providers/email"
import { NextAuthAdapter, BlitzNextAuthOptions } from "@blitzjs/auth/next-auth"
import db, { User } from "db"
import { Role } from "types"
// Has to be defined separately for `profile` to be correctly typed below
const providers = [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
}),
EmailProvider({
from: process.env.GITHUB_CLIENT_ID as string,
server: process.env.GITHUB_CLIENT_SECRET as string,
}),
]
export default api(
NextAuthAdapter({
successRedirectUrl: "/",
errorRedirectUrl: "/error",
providers,
callback: async (user, account, profile, session) => {
console.log("USER SIDE PROFILE_DATA", { user, account, profile })
let newUser: User
try {
newUser = await db.user.findFirstOrThrow({ where: { name: { equals: user.name } } })
} catch (e) {
newUser = await db.user.create({
data: {
email: user.email as string,
name: user.name as string,
role: "USER",
},
})
}
const publicData = {
userId: newUser.id,
role: newUser.role as Role,
source: "github",
}
await session.$create(publicData)
return { redirectUrl: "/" }
},
})
)

View File

@@ -44,6 +44,11 @@ const UserInfo = () => {
<Link href={"/auth/login"} className={styles.loginButton}>
<strong>Login</strong>
</Link>
<Link href="/api/auth/github/login" passHref legacyBehavior>
<a className="button small">
<strong>Sign in with GitHub</strong>
</a>
</Link>
</>
)
}

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,44 @@
import {passportAuth} from "@blitzjs/auth"
import {api} from "src/blitz-server"
import db from "db"
import {Strategy as TwitterStrategy} from "passport-twitter"
export default api(
passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
strategies: [
{
strategy: new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY as string,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET as string,
accessTokenURL: "https://api.twitter.com/oauth/access_token",
callbackURL: "http://127.0.0.1:3000/api/auth/twitter/callback",
includeEmail: true,
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails?.[0]?.value ?? "blitz@test.com"
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const publicData = {
userId: user.id,
roles: [user.role],
source: "twitter",
}
done(undefined, {publicData})
},
),
},
],
}),
)

View File

@@ -1,4 +1,4 @@
import {useSuspenseInfiniteQuery} from "@blitzjs/rpc"
import {useInfiniteQuery} from "@blitzjs/rpc"
import {gSSP} from "src/blitz-server"
import getInfiniteUsers from "src/queries/getInfiniteUsers"
@@ -10,14 +10,9 @@ export const getServerSideProps = gSSP(async ({ctx}) => {
})
function PageWithPrefetchInfiniteQuery(props) {
const [usersPages] = useSuspenseInfiniteQuery(
getInfiniteUsers,
(page = {take: 3, skip: 0}) => page,
{
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: {take: 3, skip: 0},
},
)
const [usersPages] = useInfiniteQuery(getInfiniteUsers, (page = {take: 3, skip: 0}) => page, {
getNextPageParam: (lastPage) => lastPage.nextPage,
})
return (
<div>
{usersPages.map((usersPage) =>

View File

@@ -1,4 +1,4 @@
import {useSuspenseQuery} from "@blitzjs/rpc"
import {useQuery} from "@blitzjs/rpc"
import {gSSP} from "src/blitz-server"
import getUsers from "src/queries/getUsers"
@@ -10,7 +10,7 @@ export const getServerSideProps = gSSP(async ({ctx}) => {
})
function PageWithPrefetch(props) {
const [users] = useSuspenseQuery(getUsers, {})
const [users] = useQuery(getUsers, {})
return (
<div>
{users.map((u) => (

View File

@@ -7,7 +7,7 @@ function UsersPage() {
<div>
Users:
<ul>
{users?.map((user) => (
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -49,7 +49,7 @@
"husky": "8.0.2",
"jsdom": "20.0.3",
"lint-staged": "13.0.3",
"playwright": "1.49.1",
"playwright": "1.28.0",
"prettier": "^2.7.1",
"prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3",

View File

@@ -1,14 +1,13 @@
import {QueryClient, useMutation, useQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result, {isLoading, isError, error}] = useQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
if (isError) throw error
if (isLoading || !result) return <div>Loading...</div>
return (
<>
<div>
<div id="content">{result}</div>
<button
id="logout"
@@ -18,14 +17,16 @@ function Content() {
>
logout
</button>
</>
</div>
)
}
function AuthenticatedQuery() {
return (
<div id="page">
<Content />
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}

View File

@@ -1,4 +1,4 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import AuthenticateRedirectLayout from "../layouts/AuthenticateRedirectLayout"
import logout from "../mutations/logout"
@@ -6,7 +6,7 @@ import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -1,5 +1,5 @@
import {useRouter} from "next/router"
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import login from "../mutations/login"
import logout from "../mutations/logout"
import getCurrentUser from "../queries/getCurrentUser"
@@ -8,7 +8,7 @@ import {Suspense, useState} from "react"
function Content() {
const router = useRouter()
const [error, setError] = useState(null)
const [userId] = useSuspenseQuery(getCurrentUser, null)
const [userId] = useQuery(getCurrentUser, null)
const [loginMutation] = useMutation(login)
const [logoutMutation] = useMutation(logout)

View File

@@ -1,9 +1,9 @@
import {useSuspenseQuery} from "@blitzjs/rpc"
import {useQuery} from "@blitzjs/rpc"
import getNoauthBasic from "../queries/getNoauthBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getNoauthBasic, undefined)
const [result] = useQuery(getNoauthBasic, undefined)
return <div id="content">{result}</div>
}

View File

@@ -1,11 +1,11 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -1,11 +1,11 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -1,11 +1,11 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -1,11 +1,11 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -1,11 +1,11 @@
import {useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getAuthenticatedBasic, undefined)
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>

View File

@@ -32,9 +32,9 @@ type Props = {
}
export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
await getQueryClient().prefetchQuery({
queryKey: getQueryKey(getNoauthBasic),
})
await getQueryClient().prefetchQuery(getQueryKey(getNoauthBasic, null), () =>
getNoauthBasic(null, ctx),
)
return {
props: {
dehydratedState: dehydrate(queryClient),

View File

@@ -1,10 +1,10 @@
import {invalidateQuery, useMutation, useSuspenseQuery} from "@blitzjs/rpc"
import {invalidateQuery, useMutation, useQuery} from "@blitzjs/rpc"
import changeRole from "../mutations/changeRole"
import getPublicDataForUser from "../queries/getPublicDataForUser"
import {Suspense} from "react"
function Content() {
const [publicData] = useSuspenseQuery(getPublicDataForUser, {userId: 1})
const [publicData] = useQuery(getPublicDataForUser, {userId: 1})
return (
<div id="session">
<>

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -42,7 +42,7 @@
"fs-extra": "10.0.1",
"get-port": "6.1.2",
"node-fetch": "3.2.3",
"playwright": "1.49.1",
"playwright": "1.28.0",
"ts-node": "10.9.1",
"typescript": "^4.8.4"
}

View File

@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,16 +1,10 @@
{
"extends": "@blitzjs/config/tsconfig.nextjs.json",
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "types", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"],
"compilerOptions": {
"paths": {
"react": ["./node_modules/@types/react"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
}
},
"exclude": ["node_modules"],
"baseUrl": "."

View File

@@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,10 +1,10 @@
"use client"
import {getQueryData, useSuspenseQuery} from "@blitzjs/rpc"
import {getQueryData, useQuery} from "@blitzjs/rpc"
import {Suspense, useState} from "react"
import getBasic from "../../src/queries/getBasic"
function Content() {
const [data] = useSuspenseQuery(getBasic, undefined)
const [data] = useQuery(getBasic, undefined)
const [newData, setNewData] = useState<string>()
return (
<div>

View File

@@ -43,7 +43,7 @@
"fs-extra": "10.0.1",
"get-port": "6.1.2",
"node-fetch": "3.2.3",
"playwright": "1.49.1",
"playwright": "1.28.0",
"ts-node": "10.9.1",
"typescript": "^4.9.5"
}

View File

@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,16 +1,10 @@
{
"extends": "@blitzjs/config/tsconfig.nextjs.json",
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "types", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"],
"compilerOptions": {
"paths": {
"react": ["./node_modules/@types/react"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
}
},
"exclude": ["node_modules"],
"baseUrl": "."

View File

@@ -13,7 +13,7 @@
"@blitzjs/next": "2.2.1",
"@blitzjs/rpc": "2.2.1",
"@prisma/client": "6.1.0",
"@tanstack/react-query": "5.51.1",
"@tanstack/react-query": "4.0.10",
"blitz": "2.2.1",
"next": "15.0.1",
"prisma": "6.1.0",

View File

@@ -3,7 +3,3 @@
exports[`useQuery > a "query" that converts the string parameter to uppercase > shouldn't work with mutation function 1`] = `"\\"useQuery\\" was expected to be called with a query but was called with a \\"mutation\\""`;
exports[`useQuery > a "query" that converts the string parameter to uppercase > shouldn't work with regular functions 1`] = `"Either the file path to your resolver is incorrect (must be in a \\"queries\\" or \\"mutations\\" folder that isn't nested inside \\"pages\\" or \\"api\\") or you are trying to use Blitz's useQuery to fetch from third-party APIs (to do that, import useQuery directly from \\"@tanstack/react-query\\")."`;
exports[`useSuspenseQuery > a "query" that converts the string parameter to uppercase > shouldn't work with mutation function 1`] = `"\\"useQuery\\" was expected to be called with a query but was called with a \\"mutation\\""`;
exports[`useSuspenseQuery > a "query" that converts the string parameter to uppercase > shouldn't work with regular functions 1`] = `"Either the file path to your resolver is incorrect (must be in a \\"queries\\" or \\"mutations\\" folder that isn't nested inside \\"pages\\" or \\"api\\") or you are trying to use Blitz's useQuery to fetch from third-party APIs (to do that, import useQuery directly from \\"@tanstack/react-query\\")."`;

View File

@@ -1,12 +1,6 @@
import {describe, it, expect, beforeAll, vi} from "vitest"
import {act, screen, waitForElementToBeRemoved} from "@testing-library/react"
import {
useSuspenseQuery,
useQuery,
useSuspenseInfiniteQuery,
BlitzRpcPlugin,
BlitzProvider,
} from "@blitzjs/rpc"
import {useQuery, useInfiniteQuery, BlitzRpcPlugin, BlitzProvider} from "@blitzjs/rpc"
import React from "react"
import delay from "delay"
import {buildMutationRpc, buildQueryRpc, mockRouter, render} from "../../utils/blitz-test-utils"
@@ -17,18 +11,19 @@ beforeAll(() => {
globalThis.IS_REACT_ACT_ENVIRONMENT = true
})
describe("useSuspenseQuery", () => {
describe("useQuery", () => {
const setupHook = (
ID: string,
params: any,
queryFn: (...args: any) => any,
options: Parameters<typeof useSuspenseQuery>[2] = {} as any,
options: Parameters<typeof useQuery>[2] = {} as any,
): [{data?: any; setQueryData?: any}, Function] => {
let res = {}
const qc = BlitzRpcPlugin({})
function TestSuspenseHarness() {
const [data, {setQueryData}] = useSuspenseQuery(queryFn, params, {
function TestHarness() {
const [data, {setQueryData}] = useQuery(queryFn, params, {
suspense: true,
...(options as any),
} as any)
@@ -43,7 +38,7 @@ describe("useSuspenseQuery", () => {
const ui = () => (
<React.Suspense fallback="Loading...">
<TestSuspenseHarness />
<TestHarness />
</React.Suspense>
)
@@ -95,101 +90,6 @@ describe("useSuspenseQuery", () => {
expect(() => setupHook("5", "test", buildMutationRpc(upcase))).toThrowErrorMatchingSnapshot()
})
it("works with options other than enabled & suspense without type error", () => {
const Demo = () => {
useSuspenseQuery(buildQueryRpc(upcase), undefined, {refetchInterval: 10000})
return <div></div>
}
const ui = () => <Demo />
const {rerender} = render(ui(), {
wrapper: ({children}) => (
<BlitzProvider>
<RouterContext.Provider value={mockRouter}>{children}</RouterContext.Provider>
</BlitzProvider>
),
})
})
})
})
describe("useQuery", () => {
const setupHook = (
ID: string,
params: any,
queryFn: (...args: any) => any,
options: Parameters<typeof useQuery>[2] = {} as any,
): [{data?: any; setQueryData?: any}, Function] => {
let res = {}
const qc = BlitzRpcPlugin({})
function TestHarness() {
const [data, {setQueryData, isLoading}] = useQuery(queryFn, params, {
...(options as any),
} as any)
Object.assign(res, {data, setQueryData})
if (isLoading) {
return <div>Loading...</div>
}
return (
<div id={`harness-${ID}`}>
<span>{data ? `Ready${ID}` : "No data"}</span>
<span>{data}</span>
</div>
)
}
const ui = () => <TestHarness />
const {rerender} = render(ui(), {
wrapper: ({children}) => (
<BlitzProvider>
<RouterContext.Provider value={mockRouter}>{children}</RouterContext.Provider>
</BlitzProvider>
),
})
return [res, () => rerender(ui())]
}
describe('a "query" that converts the string parameter to uppercase', () => {
const upcase = async (args: string) => {
await delay(500)
return args.toUpperCase()
}
it("should work with Blitz queries", async () => {
const [res] = setupHook("2", "test", buildQueryRpc(upcase))
await waitForElementToBeRemoved(() => screen.getByText("Loading..."))
await act(async () => {
await screen.queryAllByText("Ready2")[0]
expect(res.data).toBe("TEST")
})
})
it("should be able to change the data with setQueryData", async () => {
const [res] = setupHook("3", "fooBar", buildQueryRpc(upcase))
await waitForElementToBeRemoved(() => screen.getByText("Loading..."))
await act(async () => {
await screen.queryAllByText("Ready3")[0]
expect(res.data).toBe("FOOBAR")
res.setQueryData((p: string) => p.substr(3, 3), {refetch: false})
await delay(100)
})
expect(res.data).toBe("BAR")
})
it("shouldn't work with regular functions", () => {
console.error = vi.fn()
expect(() => setupHook("4", "test", upcase)).toThrowErrorMatchingSnapshot()
})
it("shouldn't work with mutation function", () => {
console.error = vi.fn()
expect(() => setupHook("5", "test", buildMutationRpc(upcase))).toThrowErrorMatchingSnapshot()
})
it("suspense disabled if enabled is false", async () => {
setupHook("6", "test", buildQueryRpc(upcase), {enabled: false})
await screen.findByText("No data")
@@ -200,9 +100,17 @@ describe("useQuery", () => {
await screen.findByText("No data")
})
// it("suspense disabled if enabled is false and suspense set", async () => {
// setupHook("8", "test", buildQueryRpc(upcase), {
// enabled: false,
// suspense: true,
// })
// await screen.findByText("No data")
// })
it("works with options other than enabled & suspense without type error", () => {
const Demo = () => {
useSuspenseQuery(buildQueryRpc(upcase), undefined, {refetchInterval: 10000})
useQuery(buildQueryRpc(upcase), undefined, {refetchInterval: 10000})
return <div></div>
}
const ui = () => <Demo />
@@ -218,7 +126,7 @@ describe("useQuery", () => {
})
})
describe("useSuspenseInfiniteQuery", () => {
describe("useInfiniteQuery", () => {
const setupHook = (
ID: string,
params: (arg?: any) => any,
@@ -230,7 +138,7 @@ describe("useSuspenseInfiniteQuery", () => {
function TestHarness() {
// TODO - fix typing
//@ts-ignore
const [groupedData] = useSuspenseInfiniteQuery(queryFn, params, {
const [groupedData] = useInfiniteQuery(queryFn, params, {
suspense: true,
getNextPageParam: () => {},
})

View File

@@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,9 +1,9 @@
import {getQueryData, useSuspenseQuery} from "@blitzjs/rpc"
import {getQueryData, useQuery} from "@blitzjs/rpc"
import {Suspense, useState} from "react"
import getBasic from "../app/queries/getBasic"
function Content() {
const [data] = useSuspenseQuery(getBasic, undefined)
const [data] = useQuery(getBasic, undefined)
const [newData, setNewData] = useState<string>()
return (
<div>

View File

@@ -1,9 +1,9 @@
import React, {Suspense} from "react"
import {BlitzPage} from "@blitzjs/next"
import {invalidateQuery, useSuspenseQuery} from "@blitzjs/rpc"
import {invalidateQuery, useQuery} from "@blitzjs/rpc"
import getSequence from "../app/queries/getSequence"
const useSuspenseQueryOptions = {
const useQueryOptions = {
refetchInterval: 0,
refetchOnMount: false,
refetchOnReconnect: false,
@@ -11,16 +11,8 @@ const useSuspenseQueryOptions = {
}
const PageWithInvalidateQuery: React.FC = () => {
const [query1, {isFetching: isQ1Fetching}] = useSuspenseQuery(
getSequence,
"query1",
useSuspenseQueryOptions,
)
const [query2, {isFetching: isQ2Fetching}] = useSuspenseQuery(
getSequence,
"query2",
useSuspenseQueryOptions,
)
const [query1, {isFetching: isQ1Fetching}] = useQuery(getSequence, "query1", useQueryOptions)
const [query2, {isFetching: isQ2Fetching}] = useQuery(getSequence, "query2", useQueryOptions)
const isFetching = isQ1Fetching || isQ2Fetching

View File

@@ -1,9 +1,9 @@
import {getQueryData, useSuspenseQuery} from "@blitzjs/rpc"
import {getQueryData, useQuery} from "@blitzjs/rpc"
import {Suspense, useState} from "react"
import getNoSuspenseBasic from "../../no-suspense/app/queries/getNoSuspenseBasic"
function Content() {
const [data] = useSuspenseQuery(getNoSuspenseBasic, undefined)
const [data] = useQuery(getNoSuspenseBasic, undefined)
const [newData, setNewData] = useState<string>()
return (
<div>

View File

@@ -1,4 +1,4 @@
import {useSuspenseInfiniteQuery} from "@blitzjs/rpc"
import {useInfiniteQuery} from "@blitzjs/rpc"
import {gSSP} from "../app/blitz-server"
import testQuery from "../app/queries/getInfiniteData"
@@ -12,12 +12,12 @@ export const getServerSideProps = gSSP(async ({ctx}) => {
})
const PageWithPrefetchInfQuery = () => {
const [data] = useSuspenseInfiniteQuery(
const [data] = useInfiniteQuery(
testQuery,
(pageParams) => ({...pageParams, name: "hello world"}),
{
suspense: false,
getNextPageParam: (lastPage) => lastPage,
initialPageParam: {name: "hello world"},
},
)
return <div id="data">{data ? data : "no-data"}</div>

View File

@@ -51,22 +51,22 @@ const runTests = () => {
)
})
// describe("prefetch infinite query", () => {
// it(
// "should work",
// async () => {
// const browser = await webdriver(appPort, "/page-with-prefetch-inf-query")
describe("prefetch infinite query", () => {
it(
"should work",
async () => {
const browser = await webdriver(appPort, "/page-with-prefetch-inf-query")
// browser.waitForElementByCss("#data", 0)
// const newText = await browser.elementByCss("#data").text()
// expect(newText).not.toMatch("no-data")
// expect(newText).toMatch("thanks")
browser.waitForElementByCss("#data", 0)
const newText = await browser.elementByCss("#data").text()
expect(newText).not.toMatch("no-data")
expect(newText).toMatch("thanks")
// if (browser) await browser.close()
// },
// 5000 * 60 * 2,
// )
// })
if (browser) await browser.close()
},
5000 * 60 * 2,
)
})
describe("invalidate query", () => {
it(

View File

@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,9 +1,9 @@
import getBasic from "../app/queries/getBasic"
import {useSuspenseQuery} from "@blitzjs/rpc"
import {useQuery} from "@blitzjs/rpc"
import {Suspense} from "react"
function Content() {
const [result] = useSuspenseQuery(getBasic, undefined)
const [result] = useQuery(getBasic, undefined)
return <div id="content">{result}</div>
}

View File

@@ -1,16 +1,10 @@
{
"extends": "@blitzjs/config/tsconfig.nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"],
"compilerOptions": {
"paths": {
"react": ["./node_modules/@types/react"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
}
},
"exclude": ["node_modules"],
"baseUrl": "."

View File

@@ -22,7 +22,7 @@
"get-port": "6.1.2",
"node-fetch": "3.2.3",
"pkg-dir": "5.0.0",
"playwright-chromium": "1.49.1",
"playwright-chromium": "1.28.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"resolve-cwd": "3.0.0",

View File

@@ -46,6 +46,9 @@
]
},
"pnpm": {
"patchedDependencies": {
"next-auth@4.24.7": "patches/next-auth@4.24.7.patch"
},
"overrides": {
"@types/mime": "3.0.4",
"next": "15.0.1",

View File

@@ -152,7 +152,7 @@ export interface UseSessionOptions {
}
export const useSession = (options: UseSessionOptions = {}): ClientSession => {
const suspense = options?.suspense ?? true
const suspense = options?.suspense ?? Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let initialState: ClientSession
if (options.initialPublicData) {

View File

@@ -5,5 +5,6 @@ import type {SessionConfigMethods} from "./shared"
declare global {
var sessionConfig: AuthPluginOptions & SessionConfigMethods
var __BLITZ_SESSION_COOKIE_PREFIX: string | undefined
var __BLITZ_SUSPENSE_ENABLED: boolean
var __BLITZ_GET_RSC_CONTEXT: () => Promise<Ctx>
}

View File

@@ -123,15 +123,11 @@ const prefetchQueryFactory = (
}
if (infinite) {
await queryClient.prefetchQuery({
queryKey: getInfiniteQueryKey(fn, input),
queryFn: () => fn(input, ctx),
})
await queryClient.prefetchInfiniteQuery(getInfiniteQueryKey(fn, input), () =>
fn(input, ctx),
)
} else {
await queryClient.prefetchQuery({
queryKey: getQueryKey(fn, input),
queryFn: () => fn(input, ctx),
})
await queryClient.prefetchQuery(getQueryKey(fn, input), () => fn(input, ctx))
}
},
}
@@ -247,6 +243,7 @@ export interface BlitzConfig extends NextConfig {
}
}
export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig {
if (
process.env.NODE_ENV !== "production" &&

View File

@@ -1,4 +1,4 @@
import {QueryClientProvider, HydrationBoundary} from "@blitzjs/rpc"
import {QueryClientProvider, Hydrate} from "@blitzjs/rpc"
import type {QueryClient, HydrateOptions} from "@blitzjs/rpc"
import React from "react"
@@ -12,16 +12,20 @@ export type BlitzProviderProps = {
export const BlitzProvider = ({
client = globalThis.queryClient,
contextSharing = false,
dehydratedState,
hydrateOptions,
children,
}: BlitzProviderProps) => {
if (client) {
return (
<QueryClientProvider client={client || globalThis.queryClient}>
<HydrationBoundary state={dehydratedState} options={hydrateOptions}>
<QueryClientProvider
client={client || globalThis.queryClient}
contextSharing={contextSharing}
>
<Hydrate state={dehydratedState} options={hydrateOptions}>
{children}
</HydrationBoundary>
</Hydrate>
</QueryClientProvider>
)
}

View File

@@ -27,7 +27,7 @@
],
"dependencies": {
"@swc/core": "1.3.7",
"@tanstack/react-query": "5.51.1",
"@tanstack/react-query": "4.24.4",
"b64-lite": "1.4.0",
"bad-behavior": "1.0.1",
"chalk": "^4.1.0",
@@ -36,7 +36,7 @@
"supports-color": "8.1.1"
},
"peerDependencies": {
"@tanstack/query-core": "5.51.1",
"@tanstack/query-core": "4.24.4",
"blitz": "2.2.1",
"next": "*",
"react": "*"
@@ -44,7 +44,7 @@
"devDependencies": {
"@blitzjs/auth": "2.2.1",
"@blitzjs/config": "2.2.1",
"@tanstack/query-core": "5.51.1",
"@tanstack/query-core": "4.24.4",
"@types/debug": "4.1.7",
"@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0",

View File

@@ -4,6 +4,7 @@ import type {Ctx} from "blitz"
declare global {
var queryClient: QueryClient
var __BLITZ_SUSPENSE_ENABLED: boolean
var blitzRpcRpcLoggerOptions: RpcLoggerOptions | undefined
var __BLITZ_GET_RSC_CONTEXT: () => Promise<Ctx>
}

View File

@@ -15,8 +15,6 @@ export {
useMutation,
usePaginatedQuery,
useQuery,
useSuspenseInfiniteQuery,
useSuspenseQuery,
} from "./query/react-query"
export type {
DefaultOptions,
@@ -30,6 +28,5 @@ export type {
export * from "./query/utils"
import {reactQueryClientReExports} from "./query/react-query"
const {QueryClientProvider, HydrationBoundary, useQueryErrorResetBoundary} =
reactQueryClientReExports
export {QueryClientProvider, HydrationBoundary, useQueryErrorResetBoundary}
const {QueryClientProvider, Hydrate, useQueryErrorResetBoundary} = reactQueryClientReExports
export {QueryClientProvider, Hydrate, useQueryErrorResetBoundary}

View File

@@ -28,6 +28,11 @@ export const BlitzRpcPlugin = createClientPlugin<
>((options?: BlitzRpcOptions) => {
const initializeQueryClient = () => {
const {reactQueryOptions} = options || {}
let suspenseEnabled = reactQueryOptions?.queries?.suspense ?? true
if (!process.env.CLI_COMMAND_CONSOLE && !process.env.CLI_COMMAND_DB) {
globalThis.__BLITZ_SUSPENSE_ENABLED = suspenseEnabled
}
return new QueryClient({
defaultOptions: {
...reactQueryOptions,
@@ -42,6 +47,7 @@ export const BlitzRpcPlugin = createClientPlugin<
return false
},
...reactQueryOptions?.queries,
suspense: suspenseEnabled,
},
},
})

View File

@@ -1,21 +1,13 @@
import {
useQueryErrorResetBoundary,
QueryClientProvider,
HydrationBoundary,
keepPreviousData,
} from "@tanstack/react-query"
import type {DefaultError, InfiniteData} from "@tanstack/query-core"
import {useQueryErrorResetBoundary, QueryClientProvider, Hydrate} from "@tanstack/react-query"
import {useInfiniteQuery as useInfiniteReactQuery} from "@tanstack/react-query"
import {useSuspenseInfiniteQuery as useSuspenseInfiniteReactQuery} from "@tanstack/react-query"
import {useQuery as useReactQuery} from "@tanstack/react-query"
import {useSuspenseQuery as useSuspenseReactQuery} from "@tanstack/react-query"
import {useMutation as useReactQueryMutation} from "@tanstack/react-query"
export const reactQueryClientReExports = {
useQueryErrorResetBoundary,
QueryClientProvider,
HydrationBoundary,
Hydrate,
}
import type {
@@ -26,8 +18,6 @@ import type {
UseMutationOptions,
UseMutationResult,
MutateOptions,
UseSuspenseQueryOptions,
UseSuspenseInfiniteQueryOptions,
} from "@tanstack/react-query"
import {isServer, FirstParam, PromiseReturnType, AsyncFunc} from "blitz"
@@ -62,42 +52,43 @@ export type RestQueryResult<TResult, TError> = Omit<UseQueryResult<TResult, TErr
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options?: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> & QueryNonLazyOptions,
): [TSelectedData | undefined, RestQueryResult<TSelectedData | undefined, TError>]
options?: UseQueryOptions<TResult, TError, TSelectedData> & QueryNonLazyOptions,
): [TSelectedData, RestQueryResult<TSelectedData, TError>]
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> & QueryLazyOptions,
): [TSelectedData | undefined, RestQueryResult<TSelectedData | undefined, TError>]
options: UseQueryOptions<TResult, TError, TSelectedData> & QueryLazyOptions,
): [TSelectedData | undefined, RestQueryResult<TSelectedData, TError>]
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> = {},
options: UseQueryOptions<TResult, TError, TSelectedData> = {},
) {
if (typeof queryFn === "undefined") {
throw new Error("useQuery is missing the first argument - it must be a query function")
}
let enabled = isServer ? false : options?.enabled ?? options?.enabled !== null
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
let routerIsReady = false
const router = useRouter()
if (router) {
routerIsReady = router?.isReady || isServer
routerIsReady = router?.isReady || (isServer && suspenseEnabled)
} else {
routerIsReady = true
}
@@ -108,70 +99,19 @@ export function useQuery<
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? ({signal}) => enhancedResolverRpcClient(params, {fromQueryHook: true}, signal)
: (emptyQueryFn as PromiseReturnType<T>),
: (emptyQueryFn as any),
...options,
enabled,
})
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, params),
}
return [data, rest]
}
// -------------------------
// useSuspenseQuery
// -------------------------
export function useSuspenseQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options?: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> & QueryNonLazyOptions,
): [TSelectedData, RestQueryResult<TSelectedData, TError>]
export function useSuspenseQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseSuspenseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> &
QueryLazyOptions,
): [TSelectedData | undefined, RestQueryResult<TSelectedData, TError>]
export function useSuspenseQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseSuspenseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> = {},
) {
if (typeof queryFn === "undefined") {
throw new Error("useQuery is missing the first argument - it must be a query function")
}
const enhancedResolverRpcClient = sanitizeQuery(queryFn)
const queryKey = getQueryKey(queryFn, params)
let routerIsReady = false
const router = useRouter()
if (router) {
routerIsReady = router?.isReady || isServer
} else {
routerIsReady = true
}
if (isServer) {
if (
queryRest.fetchStatus === "idle" &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
const e = new NextError()
e.name = "Rendering Suspense fallback..."
e.digest = "DYNAMIC_SERVER_USAGE"
@@ -181,19 +121,12 @@ export function useSuspenseQuery<
throw e
}
const {data, ...queryRest} = useSuspenseReactQuery({
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? ({signal}) => enhancedResolverRpcClient(params, {fromQueryHook: true}, signal)
: (emptyQueryFn as PromiseReturnType<T>),
...options,
})
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, params),
}
// return [data, rest as RestQueryResult<TResult>]
return [data, rest]
}
@@ -206,42 +139,43 @@ export type RestPaginatedResult<TResult, TError> = Omit<UseQueryResult<TResult,
export function usePaginatedQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options?: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> & QueryNonLazyOptions,
options?: UseQueryOptions<TResult, TError, TSelectedData> & QueryNonLazyOptions,
): [TSelectedData, RestPaginatedResult<TSelectedData, TError>]
export function usePaginatedQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: UseQueryOptions<TResult, TError, TSelectedData> & QueryLazyOptions,
): [TSelectedData | undefined, RestPaginatedResult<TSelectedData, TError>]
export function usePaginatedQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> & QueryLazyOptions,
): [TSelectedData | undefined, RestPaginatedResult<TSelectedData, TError>]
export function usePaginatedQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: Omit<UseQueryOptions<TResult, TError, TSelectedData>, "queryKey"> = {},
options: UseQueryOptions<TResult, TError, TSelectedData> = {},
) {
if (typeof queryFn === "undefined") {
throw new Error("usePaginatedQuery is missing the first argument - it must be a query function")
}
let enabled = isServer ? false : options?.enabled ?? options?.enabled !== null
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
let routerIsReady = false
const router = useRouter()
if (router) {
routerIsReady = router?.isReady || isServer
routerIsReady = router?.isReady || (isServer && suspenseEnabled)
} else {
routerIsReady = true
}
@@ -252,13 +186,20 @@ export function usePaginatedQuery<
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? ({signal}) => enhancedResolverRpcClient(params, {fromQueryHook: true}, signal)
: (emptyQueryFn as PromiseReturnType<T>),
: (emptyQueryFn as any),
...options,
placeholderData: keepPreviousData,
keepPreviousData: true,
enabled,
})
if (queryRest.fetchStatus === "idle" && isServer && !data) {
if (
queryRest.fetchStatus === "idle" &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
const e = new NextError()
e.name = "Rendering Suspense fallback..."
e.digest = "DYNAMIC_SERVER_USAGE"
@@ -295,43 +236,43 @@ interface InfiniteQueryConfig<TResult, TError, TSelectedData>
export function useInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<InfiniteQueryConfig<TResult, TError, TSelectedData>, "queryKey"> &
QueryNonLazyOptions,
options: InfiniteQueryConfig<TResult, TError, TSelectedData> & QueryNonLazyOptions,
): [TSelectedData[], RestInfiniteResult<TSelectedData, TError>]
export function useInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: InfiniteQueryConfig<TResult, TError, TSelectedData> & QueryLazyOptions,
): [TSelectedData[] | undefined, RestInfiniteResult<TSelectedData, TError>]
export function useInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<InfiniteQueryConfig<TResult, TError, TSelectedData>, "queryKey"> & QueryLazyOptions,
): [TSelectedData[] | undefined, RestInfiniteResult<TSelectedData, TError>]
export function useInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<InfiniteQueryConfig<TResult, TError, TSelectedData>, "queryKey">,
options: InfiniteQueryConfig<TResult, TError, TSelectedData>,
) {
if (typeof queryFn === "undefined") {
throw new Error("useInfiniteQuery is missing the first argument - it must be a query function")
}
let enabled = isServer ? false : options?.enabled ?? options?.enabled !== null
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
let routerIsReady = false
const router = useRouter()
if (router) {
routerIsReady = router?.isReady || isServer
routerIsReady = router?.isReady || (isServer && suspenseEnabled)
} else {
routerIsReady = true
}
@@ -351,93 +292,14 @@ export function useInfiniteQuery<
enabled,
})
const infiniteQueryData = data as InfiniteData<TResult>
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
pageParams: infiniteQueryData?.pageParams,
}
return [infiniteQueryData?.pages as any, rest]
}
// -------------------------
// useInfiniteQuery
// -------------------------
export interface RestInfiniteResult<TResult, TError>
extends Omit<UseInfiniteQueryResult<TResult, TError>, "data">,
QueryCacheFunctions<TResult> {
pageParams: any
}
interface InfiniteQueryConfig<TResult, TError, TSelectedData>
extends UseInfiniteQueryOptions<TResult, TError, TSelectedData, TResult> {
// getPreviousPageParam?: (lastPage: TResult, allPages: TResult[]) => TGetPageParamResult
// getNextPageParam?: (lastPage: TResult, allPages: TResult[]) => TGetPageParamResult
}
export function useSuspenseInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<UseSuspenseInfiniteQueryOptions<TResult, TError, TSelectedData>, "queryKey"> &
QueryNonLazyOptions,
): [TSelectedData[], RestInfiniteResult<TSelectedData, TError>]
export function useSuspenseInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<UseSuspenseInfiniteQueryOptions<TResult, TError, TSelectedData>, "queryKey"> &
QueryLazyOptions,
): [TSelectedData[] | undefined, RestInfiniteResult<TSelectedData, TError>]
export function useSuspenseInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = DefaultError,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: Omit<UseSuspenseInfiniteQueryOptions<TResult, TError, TSelectedData>, "queryKey">,
) {
if (typeof queryFn === "undefined") {
throw new Error("useInfiniteQuery is missing the first argument - it must be a query function")
}
let routerIsReady = false
const router = useRouter()
if (router) {
routerIsReady = router?.isReady || isServer
} else {
routerIsReady = true
}
const enhancedResolverRpcClient = sanitizeQuery(queryFn)
const queryKey = getInfiniteQueryKey(queryFn, getQueryParams)
const {data, ...queryRest} = useSuspenseInfiniteReactQuery({
// we need an extra cache key for infinite loading so that the cache for
// for this query is stored separately since the hook result is an array of results.
// Without this cache for usePaginatedQuery and this will conflict and break.
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? ({pageParam, signal}) =>
enhancedResolverRpcClient(getQueryParams(pageParam), {fromQueryHook: true}, signal)
: (emptyQueryFn as any),
...options,
})
const infiniteQueryData = data as InfiniteData<TResult>
if (queryRest.fetchStatus === "idle" && isServer && !infiniteQueryData) {
if (
queryRest.fetchStatus === "idle" &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
const e = new NextError()
e.name = "Rendering Suspense fallback..."
e.digest = "DYNAMIC_SERVER_USAGE"
@@ -450,10 +312,10 @@ export function useSuspenseInfiniteQuery<
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
pageParams: infiniteQueryData?.pageParams,
pageParams: data?.pageParams,
}
return [infiniteQueryData?.pages as unknown, rest]
return [data?.pages as any, rest]
}
// -------------------------------------------------------------------
@@ -490,7 +352,7 @@ export declare type MutationFunction<TData, TVariables = unknown> = (
export function useMutation<
TData = unknown,
TError = DefaultError,
TError = unknown,
TVariables = void,
TContext = unknown,
>(
@@ -500,11 +362,11 @@ export function useMutation<
const enhancedResolverRpcClient = sanitizeMutation(mutationResolver)
const {mutate, mutateAsync, ...rest} = useReactQueryMutation<TData, TError, TVariables, TContext>(
(variables) => enhancedResolverRpcClient(variables, {fromQueryHook: true}),
{
mutationFn: (variables) => enhancedResolverRpcClient(variables, {fromQueryHook: true}),
throwOnError: true,
...config,
},
} as any,
)
return [mutateAsync, rest] as MutationResultPair<TData, TError, TVariables, TContext>

View File

@@ -31,10 +31,16 @@ type MutateOptions = {
}
export const initializeQueryClient = () => {
let suspenseEnabled = true
if (!process.env.CLI_COMMAND_CONSOLE && !process.env.CLI_COMMAND_DB) {
suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
}
return new QueryClient({
defaultOptions: {
queries: {
...(isServer && {cacheTime: 0}),
suspense: suspenseEnabled,
retry: (failureCount, error: any) => {
if (process.env.NODE_ENV !== "production") return false
@@ -49,7 +55,7 @@ export const initializeQueryClient = () => {
}
// Query client is initialised in `BlitzRpcPlugin`, and can only be used with BlitzRpcPlugin right now
export const getQueryClient = () => globalThis.queryClient as QueryClient
export const getQueryClient = () => globalThis.queryClient
function isRpcClient(f: any): f is RpcClient<any, any> {
return !!f._isRpcClient
@@ -165,9 +171,7 @@ interface InvalidateQuery {
export const invalidateQuery: InvalidateQuery = (resolver = undefined, ...params: []) => {
const fullQueryKey =
typeof resolver === "undefined" ? undefined : getQueryKey(resolver, ...params)
return getQueryClient().invalidateQueries({
queryKey: fullQueryKey,
})
return getQueryClient().invalidateQueries(fullQueryKey)
}
export function setQueryData<TInput, TResult, T extends AsyncFunc>(

View File

@@ -59,7 +59,7 @@ describe("invalidateQuery", () => {
expect(spyRefetchQueries).toBeCalledTimes(1)
const calledWith = spyRefetchQueries.mock.calls[0]![0] as any
// json of the queryKey is "a"
expect(calledWith.queryKey[1].json).toEqual("a")
expect(calledWith[1].json).toEqual("a")
})
})
@@ -90,7 +90,7 @@ describe("setQueryData", () => {
expect(spySetQueryData).toBeCalledTimes(1)
const invalidateCalledWith = spyRefetchQueries.mock.calls[0]![0] as any
expect(invalidateCalledWith.queryKey[1].json).toEqual("params")
expect(invalidateCalledWith[1].json).toEqual("params")
const calledWith = spySetQueryData.mock.calls[0] as Array<any>
expect(calledWith[0][1].json).toEqual("params")

View File

@@ -0,0 +1,23 @@
diff --git a/package.json b/package.json
index c89e03c41a03de738cfb0ad090a63c2e99dadc7b..a5f38083c24732651c427c2b2d17cbcb23495473 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,18 @@
"./providers/*": {
"types": "./providers/*.d.ts",
"default": "./providers/*.js"
+ },
+ "./core/init": {
+ "types": "./core/init.d.ts",
+ "default": "./core/init.js"
+ },
+ "./core/lib/oauth/authorization-url": {
+ "types": "./core/lib/oauth/authorization-url.d.ts",
+ "default": "./core/lib/oauth/authorization-url.js"
+ },
+ "./core/lib/oauth/callback": {
+ "types": "./core/lib/oauth/callback.d.ts",
+ "default": "./core/lib/oauth/callback.js"
}
},
"files": [

131
pnpm-lock.yaml generated
View File

@@ -10,6 +10,11 @@ overrides:
"@types/react": npm:types-react@rc
"@types/react-dom": npm:types-react-dom@rc
patchedDependencies:
next-auth@4.24.7:
hash: xxwv3g3ul7bnzqsfasefw3kyq4
path: patches/next-auth@4.24.7.patch
importers:
.:
dependencies:
@@ -80,8 +85,8 @@ importers:
specifier: ^4.5.0
version: 4.6.1(prisma@4.6.1)
"@tanstack/react-query":
specifier: 5.51.1
version: 5.51.1(react@19.0.0)
specifier: 4.0.10
version: 4.0.10(react-dom@19.0.0)(react@19.0.0)
blitz:
specifier: 2.2.1
version: link:../../packages/blitz
@@ -160,7 +165,7 @@ importers:
version: 15.0.1(@babel/core@7.20.2)(react-dom@19.0.0)(react@19.0.0)
next-auth:
specifier: 4.24.7
version: 4.24.7(next@15.0.1)(react-dom@19.0.0)(react@19.0.0)
version: 4.24.7(patch_hash=xxwv3g3ul7bnzqsfasefw3kyq4)(next@15.0.1)(react-dom@19.0.0)(react@19.0.0)
prisma:
specifier: 6.1.0
version: 6.1.0
@@ -493,8 +498,8 @@ importers:
specifier: 3.2.3
version: 3.2.3
playwright:
specifier: 1.49.1
version: 1.49.1
specifier: 1.28.0
version: 1.28.0
ts-node:
specifier: 10.9.1
version: 10.9.1(@types/node@18.7.13)(typescript@4.8.4)
@@ -596,8 +601,8 @@ importers:
specifier: 13.0.3
version: 13.0.3
playwright:
specifier: 1.49.1
version: 1.49.1
specifier: 1.28.0
version: 1.28.0
prettier:
specifier: ^2.7.1
version: 2.7.1
@@ -821,8 +826,8 @@ importers:
specifier: 3.2.3
version: 3.2.3
playwright:
specifier: 1.49.1
version: 1.49.1
specifier: 1.28.0
version: 1.28.0
ts-node:
specifier: 10.9.1
version: 10.9.1(@types/node@18.7.13)(typescript@4.9.5)
@@ -918,8 +923,8 @@ importers:
specifier: 6.1.0
version: 6.1.0(prisma@6.1.0)
"@tanstack/react-query":
specifier: 5.51.1
version: 5.51.1(react@19.0.0)
specifier: 4.0.10
version: 4.0.10(react-dom@19.0.0)(react@19.0.0)
blitz:
specifier: 2.2.1
version: link:../../packages/blitz
@@ -1259,8 +1264,8 @@ importers:
specifier: 5.0.0
version: 5.0.0
playwright-chromium:
specifier: 1.49.1
version: 1.49.1
specifier: 1.28.0
version: 1.28.0
react:
specifier: 19.0.0
version: 19.0.0
@@ -1607,7 +1612,7 @@ importers:
version: 15.0.1(@babel/core@7.20.2)(react-dom@19.0.0)(react@19.0.0)
next-auth:
specifier: 4.24.7
version: 4.24.7(next@15.0.1)(react-dom@19.0.0)(react@19.0.0)
version: 4.24.7(patch_hash=xxwv3g3ul7bnzqsfasefw3kyq4)(next@15.0.1)(react-dom@19.0.0)(react@19.0.0)
react:
specifier: 19.0.0
version: 19.0.0
@@ -1730,8 +1735,8 @@ importers:
specifier: 1.3.7
version: 1.3.7
"@tanstack/react-query":
specifier: 5.51.1
version: 5.51.1(react@19.0.0)
specifier: 4.24.4
version: 4.24.4(react-dom@19.0.0)(react@19.0.0)
b64-lite:
specifier: 1.4.0
version: 1.4.0
@@ -1758,8 +1763,8 @@ importers:
specifier: 2.2.1
version: link:../config
"@tanstack/query-core":
specifier: 5.51.1
version: 5.51.1
specifier: 4.24.4
version: 4.24.4
"@types/debug":
specifier: 4.1.7
version: 4.1.7
@@ -6738,12 +6743,34 @@ packages:
}
dev: true
/@tanstack/query-core@5.51.1:
/@tanstack/query-core@4.24.4:
resolution:
{
integrity: sha512-fJBMQMpo8/KSsWW5ratJR5+IFr7YNJ3K2kfP9l5XObYHsgfVy1w3FJUWU4FT2fj7+JMaEg33zOcNDBo0LMwHnw==,
integrity: sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==,
}
/@tanstack/react-query@4.0.10(react-dom@19.0.0)(react@19.0.0):
resolution:
{
integrity: sha512-Wn5QhZUE5wvr6rGClV7KeQIUsdTmYR9mgmMZen7DSRWauHW2UTynFg3Kkf6pw+XlxxOLsyLWwz/Q6q1lSpM3TQ==,
}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
react-native: "*"
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
dependencies:
"@tanstack/query-core": 4.24.4
"@types/use-sync-external-store": 0.0.3
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
use-sync-external-store: 1.2.0(react@19.0.0)
dev: false
/@tanstack/react-query@4.13.0(react-dom@19.0.0)(react@19.0.0):
resolution:
{
@@ -6765,16 +6792,25 @@ packages:
use-sync-external-store: 1.2.0(react@19.0.0)
dev: true
/@tanstack/react-query@5.51.1(react@19.0.0):
/@tanstack/react-query@4.24.4(react-dom@19.0.0)(react@19.0.0):
resolution:
{
integrity: sha512-s47HKFnQ4HOJAHoIiXcpna/roMMPZJPy6fJ6p4ZNVn8+/onlLBEDd1+xc8OnDuwgvecqkZD7Z2mnSRbcWefrKw==,
integrity: sha512-RpaS/3T/a3pHuZJbIAzAYRu+1nkp+/enr9hfRXDS/mojwx567UiMksoqW4wUFWlwIvWTXyhot2nbIipTKEg55Q==,
}
peerDependencies:
react: ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
react-native: "*"
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
dependencies:
"@tanstack/query-core": 5.51.1
"@tanstack/query-core": 4.24.4
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
use-sync-external-store: 1.2.0(react@19.0.0)
dev: false
/@testim/chrome-version@1.1.2:
@@ -7732,6 +7768,13 @@ packages:
integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==,
}
/@types/use-sync-external-store@0.0.3:
resolution:
{
integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==,
}
dev: false
/@types/vinyl@2.0.6:
resolution:
{
@@ -13696,17 +13739,6 @@ packages:
integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==,
}
/fsevents@2.3.2:
resolution:
{
integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==,
}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/fsevents@2.3.3:
resolution:
{
@@ -17999,7 +18031,7 @@ packages:
}
dev: false
/next-auth@4.24.7(next@15.0.1)(react-dom@19.0.0)(react@19.0.0):
/next-auth@4.24.7(patch_hash=xxwv3g3ul7bnzqsfasefw3kyq4)(next@15.0.1)(react-dom@19.0.0)(react@19.0.0):
resolution:
{
integrity: sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==,
@@ -18025,6 +18057,7 @@ packages:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
uuid: 8.3.2
patched: true
/next-router-mock@0.9.1(next@15.0.1)(react@19.0.0):
resolution:
@@ -19276,38 +19309,37 @@ packages:
pathe: 0.3.2
dev: true
/playwright-chromium@1.49.1:
/playwright-chromium@1.28.0:
resolution:
{
integrity: sha512-XAQDkZ1Eem1OONhfS8B2LM2mgHG/i5jIxooxjvqjbF/9GnLnRTJHdQamNjo1e4FZvt7J0BFD/15+qAcT0eKlfA==,
integrity: sha512-5IUBJShMJMaK6NmRj/7KWdvVqd7J8DqLH0wVdXBs/4MfZUAvByBB35y5v6B33NjlJg3SZGX5alR8TANrcKaJNA==,
}
engines: {node: ">=18"}
engines: {node: ">=14"}
hasBin: true
requiresBuild: true
dependencies:
playwright-core: 1.49.1
playwright-core: 1.28.0
dev: true
/playwright-core@1.49.1:
/playwright-core@1.28.0:
resolution:
{
integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==,
integrity: sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==,
}
engines: {node: ">=18"}
engines: {node: ">=14"}
hasBin: true
dev: true
/playwright@1.49.1:
/playwright@1.28.0:
resolution:
{
integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==,
integrity: sha512-kyOXGc5y1mgi+hgEcCIyE1P1+JumLrxS09nFHo5sdJNzrucxPRAGwM4A2X3u3SDOfdgJqx61yIoR6Av+5plJPg==,
}
engines: {node: ">=18"}
engines: {node: ">=14"}
hasBin: true
requiresBuild: true
dependencies:
playwright-core: 1.49.1
optionalDependencies:
fsevents: 2.3.2
playwright-core: 1.28.0
dev: true
/pluralize@8.0.0:
@@ -22947,7 +22979,6 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 19.0.0
dev: true
/use@3.1.1:
resolution: