1
0
mirror of synced 2026-02-06 09:00:12 -05:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Brandon Bayer
5c83167fda empty 2022-04-08 15:05:12 -05:00
122 changed files with 775 additions and 6317 deletions

View File

@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["blitz"], ["@blitzjs/*"]],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["web", "test-*"]
}

View File

@@ -1,5 +0,0 @@
---
"blitz": patch
---
downgrade pkg-dir to non-esm only version

View File

@@ -1,9 +0,0 @@
---
"blitz": patch
"@blitzjs/auth": patch
"@blitzjs/next": patch
"@blitzjs/rpc": patch
"@blitzjs/generator": patch
---
initial publish

View File

@@ -1,5 +0,0 @@
---
"@blitzjs/generator": patch
---
fix generator npm package dist

View File

@@ -1,18 +0,0 @@
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"web": "0.0.0",
"test-auth": "0.0.0",
"test-rpc": "0.0.0",
"test-utils": "0.0.0",
"blitz": "2.0.0-alpha.0",
"@blitzjs/auth": "2.0.0-alpha.0",
"@blitzjs/next": "2.0.0-alpha.0",
"@blitzjs/rpc": "2.0.0-alpha.0",
"@blitzjs/config": "0.0.0",
"@blitzjs/generator": "2.0.0-alpha.0",
"template": "0.0.0"
},
"changesets": ["nine-onions-admire", "ninety-pets-heal", "poor-peas-lick", "ten-rivers-burn"]
}

View File

@@ -1,5 +0,0 @@
---
"blitz": patch
---
fix more cli problems

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with:
version: 6.32.6
version: 6.10.0
- name: Setup node
uses: actions/setup-node@v2
with:
@@ -32,7 +32,7 @@ jobs:
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm manypkg check
- run: pnpm build
- run: pnpm lint
- run: pnpm build
- run: pnpm build:apps
- run: pnpm test

View File

@@ -3,4 +3,4 @@
pnpm manypkg check
pnpm lint
pnpm pretty-quick --staged
pnpx pretty-quick --staged

View File

@@ -1,19 +1,3 @@
# Contributing
[Read the Contributing Guide at Blitzjs.com](https://blitzjs.com/docs/contributing)
## To run tests
Make sure you have `chromedriver` installed for your Chrome version. You can install it with
- `brew install --cask chromedriver` on Mac OS X
- `chocolatey install chromedriver` on Windows
- Or manually download the version that matches your installed chrome version (if there's no match, download a version under it, but not above) from the [chromedriver repo](https://chromedriver.storage.googleapis.com/index.html) and add the binary to `<next-repo>/node_modules/.bin`
You may also have to [install Rust](https://www.rust-lang.org/tools/install) and build our native packages to see all tests pass locally. We check in binaries for the most common targets and those required for CI so that most people don't have to, but if you do not see a binary for your target in `packages/next/native`, you can build it by running `yarn --cwd packages/next build-native`. If you are working on the Rust code and you need to build the binaries for ci, you can manually trigger [the workflow](https://github.com/vercel/next.js/actions/workflows/build_native.yml) to build and commit with the "Run workflow" button.
Running all tests:
```sh
pnpm test
```

View File

@@ -1,20 +0,0 @@
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupClient} from "@blitzjs/next"
import {BlitzRpcPlugin} from "@blitzjs/rpc"
const {withBlitz} = setupClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "webapp-cookie-prefix",
}),
BlitzRpcPlugin({
reactQueryOptions: {
queries: {
staleTime: 7000,
},
},
}),
],
})
export {withBlitz}

View File

@@ -1,14 +0,0 @@
import {Ctx} from "blitz"
import {prisma} from "../../prisma"
import {User} from "prisma"
export default async function createUser(
input: {name: string; email: string},
ctx: Ctx,
): Promise<User> {
ctx.session.$authorize()
const user = await prisma.user.create({data: {name: input.name, email: input.email}})
return user
}

View File

@@ -1,4 +0,0 @@
export default function setBasic(input, ctx) {
console.log("SET BASIC input", input)
return
}

View File

@@ -1,5 +0,0 @@
export default async function getBasic(input, ctx) {
console.log("INPUT", input)
return "basic-result"
}

View File

@@ -1,11 +0,0 @@
import {Ctx} from "blitz"
import {prisma} from "../../prisma"
import {User} from "prisma"
export default async function getUsers(_input: {}, ctx: Ctx): Promise<User[]> {
ctx.session.$authorize()
const users = await prisma.user.findMany()
return users
}

View File

@@ -1,5 +0,0 @@
export default function getV2Basic(input, ctx) {
console.log("INPUT", input)
return "basic-result"
}

View File

@@ -1,10 +1,7 @@
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
})
const {withBlitz} = require("@blitzjs/next")
module.exports = withBlitz(
withBundleAnalyzer({
reactStrictMode: true,
}),
)
module.exports = withBundleAnalyzer({
reactStrictMode: true,
})

View File

@@ -16,12 +16,11 @@
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@prisma/client": "3.9.0",
"@types/jest": "27.4.1",
"blitz": "workspace:*",
"jest": "27.5.1",
"next": "12.1.1",
"next": "12.1.4",
"prisma": "3.9.0",
"react": "18.0.0",
"react-dom": "18.0.0",

View File

@@ -1,8 +1,8 @@
import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
import {AuthenticationError, AuthorizationError} from "blitz"
import type {AppProps} from "next/app"
import React, {Suspense} from "react"
import {withBlitz} from "app/blitz-client"
import React from "react"
import {withBlitz} from "../src/client-setup"
function RootErrorFallback({error}: ErrorFallbackProps) {
if (error instanceof AuthenticationError) {
@@ -27,9 +27,7 @@ function RootErrorFallback({error}: ErrorFallbackProps) {
function MyApp({Component, pageProps}: AppProps) {
return (
<ErrorBoundary FallbackComponent={RootErrorFallback}>
<Suspense fallback="Loading...">
<Component {...pageProps} />
</Suspense>
<Component {...pageProps} />
</ErrorBoundary>
)
}

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
import {SessionContext} from "@blitzjs/auth"
import {prisma} from "../../prisma/index"

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
export default api(async (_req, res, ctx) => {
const {session} = ctx

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
export default api(async (_req, res, ctx) => {
const blitzContext = ctx

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
import {prisma} from "../../prisma/index"
export default api(async (_req, res) => {

View File

@@ -1,4 +0,0 @@
import {rpcHandler} from "@blitzjs/rpc"
import {api} from "app/blitz-server"
export default api(rpcHandler({onError: console.log}))

View File

@@ -1,5 +1,6 @@
import {setPublicDataForUser} from "@blitzjs/auth"
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
import {prisma} from "../../prisma/index"
export default api(async (req, res, ctx) => {

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
import {prisma} from "../../prisma/index"
import {SecurePassword} from "@blitzjs/auth"

View File

@@ -1,4 +1,4 @@
import {api} from "app/blitz-server"
import {api} from "../../src/server-setup"
import {prisma} from "../../prisma/index"
import {SecurePassword} from "@blitzjs/auth"

View File

@@ -1,41 +0,0 @@
import {useState} from "react"
import {SessionContext} from "@blitzjs/auth"
import {useMutation} from "@blitzjs/rpc"
import createUser from "app/mutations/createUser"
import {User} from "prisma"
function Page() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [createUserMutation, {error}] = useMutation(createUser)
const [newUser, setNewUser] = useState<null | User>(null)
return (
<div>
New User Form:
<form
onSubmit={async (e) => {
e.preventDefault()
const user = await createUserMutation({name, email})
setNewUser(user)
}}
>
<label>
Name:
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</label>
<label>
Email:
<input type="text" value={email} onChange={(e) => setEmail(e.target.value)} />
</label>
<button type="submit">Create User</button>
</form>
<div style={{paddingTop: 20}}>
<div>Error: {JSON.stringify(error, null, 2)}</div>
New user: {JSON.stringify(newUser, null, 2)}
</div>
</div>
)
}
export default Page

View File

@@ -1,6 +1,3 @@
import {invoke} from "@blitzjs/rpc"
import getBasic from "app/queries/getBasic"
export const getServerSideProps = () => {
return {props: {}}
}
@@ -9,7 +6,6 @@ export default function Web() {
return (
<div>
<h1>Web</h1>
<button onClick={() => invoke(getBasic, "FROM BROWSER")}>GetBasic</button>
</div>
)
}

View File

@@ -1,4 +1,4 @@
const PageWithAuthRedirect = () => {
const PageWithRedirect = () => {
return (
<div>
{JSON.stringify(
@@ -13,6 +13,6 @@ const PageWithAuthRedirect = () => {
)
}
PageWithAuthRedirect.redirectAuthenticatedTo = "/"
PageWithRedirect.redirectAuthenticatedTo = "/"
export default PageWithAuthRedirect
export default PageWithRedirect

View File

@@ -1,4 +1,4 @@
import {gSP} from "app/blitz-server"
import {gSP} from "../src/server-setup"
export const getStaticProps = gSP(async ({ctx}) => {
return {
@@ -14,8 +14,8 @@ export const getStaticProps = gSP(async ({ctx}) => {
}
})
function PageWithGsp({data}) {
function Page({data}) {
return <div>{JSON.stringify(data, null, 2)}</div>
}
export default PageWithGsp
export default Page

View File

@@ -1,5 +1,5 @@
import {SessionContext} from "@blitzjs/auth"
import {gSSP} from "app/blitz-server"
import {gSSP} from "../src/server-setup"
type Props = {
userId: unknown
@@ -16,8 +16,8 @@ export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
}
})
function PageWithGssp(props: Props) {
function Page(props: Props) {
return <div>{JSON.stringify(props, null, 2)}</div>
}
export default PageWithGssp
export default Page

View File

@@ -1,6 +1,6 @@
import {useAuthenticatedSession} from "@blitzjs/auth"
import {useAuthenticatedSession} from "../src/client-setup"
export default function PageWithUseAuthSession() {
export default function PgaeWithUseAuthorizeIf() {
useAuthenticatedSession()
return <div>This page is using useAuthenticatedSession</div>
}

View File

@@ -1,6 +1,6 @@
import {useAuthorizeIf} from "@blitzjs/auth"
import {useAuthorizeIf} from "../src/client-setup"
export default function PageWithUseAuthorizeIf() {
export default function PgaeWithUseAuthorizeIf() {
useAuthorizeIf(Math.random() > 0.5)
return <div>This page is using useAuthorizeIf</div>
}

View File

@@ -1,4 +1,4 @@
import {useAuthorize} from "@blitzjs/auth"
import {useAuthorize} from "../src/client-setup"
export default function PgaeWithUseAuthorize() {
useAuthorize()

View File

@@ -1,6 +1,6 @@
import {useRedirectAuthenticated} from "@blitzjs/auth"
import {useRedirectAuthenticated} from "../src/client-setup"
export default function PageWithUseRedirectAuth() {
export default function PgaeWithUseAuthorizeIf() {
useRedirectAuthenticated("/")
return <div>This page is using useRedirectAuthenticated</div>
}

View File

@@ -1,4 +1,4 @@
import {useSession} from "@blitzjs/auth"
import {useSession} from "../src/client-setup"
export default function PgaeWithUseSession() {
const data = useSession()

View File

@@ -1,7 +1,7 @@
const PageWithoutFlicker = ({data}) => {
const PageWithRedirect = ({data}) => {
return <div>{JSON.stringify(data)}</div>
}
PageWithoutFlicker.suppressFirstRenderFlicker = true
PageWithRedirect.suppressFirstRenderFlicker = true
export default PageWithoutFlicker
export default PageWithRedirect

View File

@@ -1,20 +0,0 @@
import {useQuery} from "@blitzjs/rpc"
import getUsers from "app/queries/getUsers"
function UsersPage() {
const [users] = useQuery(getUsers, {})
return (
<div>
Users:
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
)
}
export default UsersPage

View File

@@ -0,0 +1,26 @@
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupClient} from "@blitzjs/next"
const {
withBlitz,
useSession,
useAuthorize,
useAuthorizeIf,
useRedirectAuthenticated,
useAuthenticatedSession,
} = setupClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "webapp-cookie-prefix",
}),
],
})
export {
withBlitz,
useSession,
useAuthorize,
useAuthorizeIf,
useRedirectAuthenticated,
useAuthenticatedSession,
}

View File

@@ -1,8 +1,5 @@
{
"extends": "@blitzjs/config/tsconfig.nextjs.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"],
"exclude": ["node_modules", "test"]
}

View File

@@ -1,12 +0,0 @@
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupClient} from "@blitzjs/next"
const {withBlitz} = setupClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "auth-tests-cookie-prefix",
}),
],
})
export {withBlitz}

View File

@@ -1,4 +1,10 @@
const {withBlitz} = require("@blitzjs/next")
module.exports = withBlitz({
// update me
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
})
module.exports = withBundleAnalyzer({
reactStrictMode: true,
experimental: {
esmExternals: "loose",
},
})

View File

@@ -19,7 +19,7 @@
"@prisma/client": "3.9.0",
"blitz": "workspace:*",
"lowdb": "3.0.0",
"next": "12.1.1",
"next": "12.1.4",
"prisma": "3.9.0",
"react": "18.0.0",
"react-dom": "18.0.0"
@@ -28,13 +28,22 @@
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",
"@types/get-port": "4.2.0",
"@types/node-fetch": "2.6.1",
"@types/react": "17.0.43",
"@types/rimraf": "3.0.2",
"@types/selenium-webdriver": "4.0.18",
"b64-lite": "1.4.0",
"chromedriver": "100.0.0",
"cross-spawn": "7.0.3",
"eslint": "7.32.0",
"express": "4.17.3",
"fs-extra": "10.0.1",
"get-port": "6.1.2",
"node-fetch": "3.2.3",
"rimraf": "3.0.2",
"selenium-webdriver": "4.1.1",
"tree-kill": "1.2.2",
"typescript": "^4.5.3"
}
}

View File

@@ -2,7 +2,7 @@ import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
import {AuthenticationError, AuthorizationError} from "blitz"
import type {AppProps} from "next/app"
import React from "react"
import {withBlitz} from "../app/blitz-client"
import {withBlitz} from "../src/client-setup"
function RootErrorFallback({error}: ErrorFallbackProps) {
if (error instanceof AuthenticationError) {

View File

@@ -1,4 +1,4 @@
import {api} from "../../app/blitz-server"
import {api} from "../../src/server-setup"
export default api(async (_req, res, ctx) => {
const blitzContext = ctx

View File

@@ -1,4 +1,4 @@
import {api} from "../../app/blitz-server"
import {api} from "../../src/server-setup"
export default api(async (_req, res, ctx) => {
res.status(200).end()

View File

@@ -1,4 +1,4 @@
import {api} from "../../app/blitz-server"
import {api} from "../../src/server-setup"
import {prisma} from "../../prisma/index"
import {SecurePassword} from "@blitzjs/auth"

View File

@@ -1,4 +1,4 @@
import {useAuthorize} from "@blitzjs/auth"
import {useAuthorize} from "../src/client-setup"
export const getServerSideProps = () => {
return {props: {}}

View File

@@ -0,0 +1,26 @@
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupClient} from "@blitzjs/next"
const {
withBlitz,
useSession,
useAuthorize,
useAuthorizeIf,
useRedirectAuthenticated,
useAuthenticatedSession,
} = setupClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "webapp-cookie-prefix",
}),
],
})
export {
withBlitz,
useSession,
useAuthorize,
useAuthorizeIf,
useRedirectAuthenticated,
useAuthenticatedSession,
}

View File

@@ -6,7 +6,7 @@ import {prisma as db} from "../prisma/index"
const {gSSP, gSP, api} = setupBlitz({
plugins: [
AuthServerPlugin({
cookiePrefix: "auth-tests-cookie-prefix",
cookiePrefix: "webapp-cookie-prefix",
storage: PrismaStorage(db as any),
isAuthorized: simpleRolesIsAuthorized,
}),

View File

@@ -1,7 +1,6 @@
import {describe, it, expect, beforeAll, afterAll} from "vitest"
import {killApp, findPort, launchApp, nextBuild, nextStart} from "../../utils/next-test-utils"
import webdriver from "../../utils/next-webdriver"
import {killApp, findPort, launchApp, nextBuild, nextStart} from "./next-test-utils"
import webdriver from "./next-webdriver"
import {join} from "path"
import seed from "../prisma/seed"
import fetch from "node-fetch"
@@ -11,10 +10,10 @@ let app: any
let appPort: number
const appDir = join(__dirname, "../")
const HEADER_CSRF = "anti-csrf"
const COOKIE_PUBLIC_DATA_TOKEN = "auth-tests-cookie-prefix_sPublicDataToken"
const COOKIE_SESSION_TOKEN = "auth-tests-cookie-prefix_sSessionToken"
const COOKIE_ANONYMOUS_SESSION_TOKEN = "auth-tests-cookie-prefix_sAnonymousSessionToken"
const COOKIE_REFRESH_TOKEN = "auth-tests-cookie-prefix_sIdRefreshToken"
const COOKIE_PUBLIC_DATA_TOKEN = "webapp-cookie-prefix_sPublicDataToken"
const COOKIE_SESSION_TOKEN = "webapp-cookie-prefix_sSessionToken"
const COOKIE_ANONYMOUS_SESSION_TOKEN = "webapp-cookie-prefix_sAnonymousSessionToken"
const COOKIE_REFRESH_TOKEN = "webapp-cookie-prefix_sIdRefreshToken"
const HEADER_PUBLIC_DATA_TOKEN = "public-data-token"
function readCookie(cookieHeader, name) {
@@ -128,7 +127,7 @@ describe("dev mode", () => {
console.log(error)
}
}, 5000 * 60 * 2)
afterAll(async () => await killApp(app))
afterAll(() => killApp(app))
runTests()
})
@@ -142,7 +141,7 @@ describe("server mode", () => {
console.log(err)
}
}, 5000 * 60 * 2)
afterAll(async () => await killApp(app))
afterAll(() => killApp(app))
runTests()
})

View File

@@ -96,7 +96,9 @@ interface RunNextCommandOptions {
}
export function runNextCommand(argv: any[], options: RunNextCommandOptions = {}) {
const cwd = options.cwd
const nextDir = path.dirname(require.resolve("next/package"))
const nextBin = path.join(nextDir, "dist/bin/next")
const cwd = options.cwd || nextDir
// Let Next.js decide the environment
const env = {
...process.env,
@@ -107,7 +109,7 @@ export function runNextCommand(argv: any[], options: RunNextCommandOptions = {})
return new Promise<any>((resolve, reject) => {
console.log(`Running command "next ${argv.join(" ")}"`)
const instance = spawn("pnpm", ["exec", "next", ...argv], {
const instance = spawn("node", ["--no-deprecation", nextBin, ...argv], {
...options.spawnOptions,
cwd,
env,
@@ -165,8 +167,11 @@ interface RunNextCommandDevOptions {
nextStart?: boolean
}
export function runNextCommandDev(argv, opts: RunNextCommandDevOptions = {}) {
const cwd = opts.cwd // || nextDir
export function runNextCommandDev(argv, stdOut, opts: RunNextCommandDevOptions = {}) {
const nextDir = path.dirname(require.resolve("next/package"))
const nextBin = path.join(nextDir, "dist/bin/next")
const cwd = opts.cwd || nextDir
const env = {
...process.env,
NODE_ENV: opts.nextStart ? ("production" as const) : ("development" as const),
@@ -174,8 +179,9 @@ export function runNextCommandDev(argv, opts: RunNextCommandDevOptions = {}) {
...opts.env,
}
const nodeArgs = opts.nodeArgs || []
return new Promise<void | string | ChildProcess>((resolve, reject) => {
const instance = spawn("pnpm", ["exec", "next", ...argv], {
const instance = spawn("node", [...nodeArgs, "--no-deprecation", nextBin, ...argv], {
cwd,
env,
})
@@ -230,7 +236,7 @@ export function runNextCommandDev(argv, opts: RunNextCommandDevOptions = {}) {
// Launch the app in dev mode.
export function launchApp(dir, port, opts) {
return runNextCommandDev(["-p", port], {cwd: dir, ...opts})
return runNextCommandDev([dir, "-p", port], undefined, opts)
}
export function nextBuild(dir, args = [], opts = {}) {
@@ -238,27 +244,26 @@ export function nextBuild(dir, args = [], opts = {}) {
}
export function nextExport(dir, {outdir}, opts = {}) {
return runNextCommand(["export", dir, "--outdir", outdir], {cwd: dir, ...opts})
return runNextCommand(["export", dir, "--outdir", outdir], opts)
}
export function nextExportDefault(dir, opts = {}) {
return runNextCommand(["export", dir], {cwd: dir, ...opts})
return runNextCommand(["export", dir], opts)
}
export function nextLint(dir, args = [], opts = {}) {
return runNextCommand(["lint", dir, ...args], {cwd: dir, ...opts})
return runNextCommand(["lint", dir, ...args], opts)
}
export function nextStart(dir, port, opts = {}) {
return runNextCommandDev(["start", "-p", port], {
cwd: dir,
return runNextCommandDev(["start", "-p", port, dir], undefined, {
...opts,
nextStart: true,
})
}
export function buildTS(args = [], cwd, env = {NODE_ENV: "production" as const}) {
cwd = cwd
cwd = cwd || path.dirname(require.resolve("next/package"))
env = {...process.env, ...env}
return new Promise<void>((resolve, reject) => {

View File

@@ -144,19 +144,6 @@ const getDeviceIP = async () => {
}
}
export const closeBrowser = async () => {
// First we close all extra windows left over
let allWindows = await browser.getAllWindowHandles()
for (const win of allWindows) {
if (win === initialWindow) continue
try {
await browser.switchTo().window(win)
await browser.close()
} catch (_) {}
}
}
// eslint-disable-next-line no-unused-vars
const freshWindow = async (appPort) => {
// First we close all extra windows left over

View File

@@ -1,4 +0,0 @@
export default async function setBasic(input, ctx) {
global.basic = input
return global.basic
}

View File

@@ -1,12 +0,0 @@
if (typeof window !== "undefined") {
throw new Error("This should not be loaded on the client")
}
export default async function getBasic() {
if (typeof window !== "undefined") {
throw new Error("This should not be loaded on the client")
}
global.basic ??= "basic-result"
return global.basic
}

View File

@@ -1,3 +0,0 @@
export default async function getFailure() {
throw new Error("error on purpose for test")
}

View File

@@ -1,3 +0,0 @@
export default async function getNestedBasic() {
return "nested-basic"
}

View File

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

View File

@@ -1,4 +0,0 @@
const {withBlitz} = require("@blitzjs/next")
module.exports = withBlitz({
// update me
})

View File

@@ -1,29 +0,0 @@
{
"name": "test-rpc",
"version": "0.0.0",
"private": true,
"scripts": {
"test": "vitest --config ./vitest.config.ts run",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"blitz": "workspace:*",
"next": "12.1.1",
"react": "18.0.0",
"react-dom": "18.0.0"
},
"devDependencies": {
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1",
"@types/react": "17.0.43",
"b64-lite": "1.4.0",
"eslint": "7.32.0",
"fs-extra": "10.0.1",
"typescript": "^4.5.3"
}
}

View File

@@ -1,3 +0,0 @@
import {rpcHandler} from "@blitzjs/rpc"
export default rpcHandler({onError: console.log})

View File

@@ -1,4 +0,0 @@
const Page = () => {
return <div id="page-container">Hello World</div>
}
export default Page

View File

@@ -1,7 +0,0 @@
import getBasic from "../app/queries/getBasic"
const Page = () => {
getBasic().then(console.log)
return <div id="page-container">Hello World</div>
}
export default Page

View File

@@ -1,199 +0,0 @@
import {describe, it, expect, beforeAll, afterAll} from "vitest"
import fs from "fs-extra"
import {join} from "path"
import {
killApp,
findPort,
launchApp,
fetchViaHTTP,
nextBuild,
nextStart,
nextExport,
getPageFileFromBuildManifest,
getPageFileFromPagesManifest,
} from "../../utils/next-test-utils"
// jest.setTimeout(1000 * 60 * 2)
const appDir = join(__dirname, "../")
const nextConfig = join(appDir, "next.config.js")
let appPort
let mode
let app
function runTests(dev = false) {
describe("api requests", () => {
it(
"returns 200 for HEAD",
async () => {
const res = await fetchViaHTTP(appPort, "/api/rpc/getBasic", null, {
method: "HEAD",
})
expect(res.status).toEqual(200)
},
5000 * 60 * 2,
)
it(
"returns 404 for GET",
async () => {
const res = await fetchViaHTTP(appPort, "/api/rpc/getBasic", null, {
method: "GET",
})
expect(res.status).toEqual(404)
},
5000 * 60 * 2,
)
it(
"requires params",
async () => {
const res = await fetchViaHTTP(appPort, "/api/rpc/getBasic", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
})
const json = await res.json()
expect(res.status).toEqual(400)
expect(json.error.message).toBe("Request body is missing the `params` key")
},
5000 * 60 * 2,
)
it(
"query works",
async () => {
const data = await fetchViaHTTP(appPort, "/api/rpc/getBasic", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify({params: {}}),
}).then((res) => res.ok && res.json())
expect(data).toEqual({result: "basic-result", error: null, meta: {}})
},
5000 * 60 * 2,
)
it(
"mutation works",
async () => {
const data = await fetchViaHTTP(appPort, "/api/rpc/setBasic", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify({params: "new-basic"}),
}).then((res) => res.ok && res.json())
expect(data).toEqual({result: "new-basic", error: null, meta: {}})
const data2 = await fetchViaHTTP(appPort, "/api/rpc/getBasic", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify({params: {}}),
}).then((res) => res.ok && res.json())
expect(data2).toEqual({result: "new-basic", error: null, meta: {}})
},
5000 * 60 * 2,
)
it(
"handles resolver errors",
async () => {
const res = await fetchViaHTTP(appPort, "/api/rpc/getFailure", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify({params: {}}),
})
const json = await res.json()
expect(res.status).toEqual(200)
expect(json).toEqual({
result: null,
error: {name: "Error", message: "error on purpose for test", statusCode: 500},
meta: {error: {values: ["Error"]}},
})
},
5000 * 60 * 2,
)
it(
"nested query works",
async () => {
const data = await fetchViaHTTP(appPort, "/api/rpc/v2/getNestedBasic", null, {
method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify({params: {}}),
}).then((res) => res.ok && res.json())
expect(data).toEqual({result: "nested-basic", error: null, meta: {}})
},
5000 * 60 * 2,
)
})
if (!dev) {
it("should show warning with next export", async () => {
const {stderr} = await nextExport(appDir, {outdir: join(appDir, "out")}, {stderr: true})
expect(stderr).toContain("https://nextjs.org/docs/messages/api-routes-static-export")
})
}
}
describe("RPC", () => {
describe(
"dev mode",
() => {
beforeAll(async () => {
try {
appPort = await findPort()
app = await launchApp(appDir, appPort)
} catch (err) {
console.log(err)
}
})
afterAll(() => killApp(app))
runTests(true)
},
5000 * 60 * 2,
)
describe(
"server mode",
() => {
beforeAll(async () => {
await nextBuild(appDir)
mode = "server"
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests()
},
5000 * 60 * 2,
)
describe(
"serverless mode",
() => {
let nextConfigContent = ""
const nextConfigPath = join(appDir, "next.config.js")
beforeAll(async () => {
nextConfigContent = await fs.readFile(nextConfigPath, "utf8")
await fs.writeFile(
nextConfigPath,
nextConfigContent.replace("// update me", `target: 'experimental-serverless-trace',`),
)
await nextBuild(appDir)
mode = "serverless"
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
await fs.writeFile(nextConfigPath, nextConfigContent)
})
runTests()
},
5000 * 60 * 2,
)
})

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View File

@@ -1,8 +0,0 @@
import {defineConfig} from "vitest/config"
export default defineConfig({
test: {
testTimeout: 5000 * 60 * 2,
hookTimeout: 5000 * 60 * 2,
},
})

View File

@@ -1,24 +0,0 @@
{
"name": "test-utils",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1",
"@types/react": "17.0.43",
"@types/rimraf": "3.0.2",
"@types/selenium-webdriver": "4.0.18",
"chromedriver": "100.0.0",
"cross-spawn": "7.0.3",
"eslint": "7.32.0",
"express": "4.17.3",
"fs-extra": "10.0.1",
"get-port": "6.1.2",
"node-fetch": "3.2.3",
"rimraf": "3.0.2",
"selenium-webdriver": "4.1.1",
"tree-kill": "1.2.2",
"typescript": "^4.5.3"
}
}

View File

@@ -16,18 +16,15 @@
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"pre-publish": "pnpm i && pnpm build && changeset add && changeset version",
"publish-release": "changeset publish && git push --follow-tags"
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"dependencies": {
"@blitzjs/manypkg": "0.19.1",
"@changesets/cli": "2.22.0",
"eslint": "7.32.0",
"husky": "7.0.4",
"jsdom": "19.0.0",
"lint-staged": "12.1.7",
"next": "12.1.1",
"next": "12.1.4",
"only-allow": "1.1.0",
"patch-package": "6.4.7",
"prettier": "^2.5.1",

View File

@@ -1,29 +0,0 @@
# @blitzjs/auth
## 2.0.0-alpha.4
### Patch Changes
- Updated dependencies
- blitz@2.0.0-alpha.4
## 2.0.0-alpha.3
### Patch Changes
- Updated dependencies
- blitz@2.0.0-alpha.3
## 2.0.0-alpha.2
### Patch Changes
- blitz@2.0.0-alpha.2
## 2.0.0-alpha.1
### Patch Changes
- 46a34c7b: initial publish
- Updated dependencies [46a34c7b]
- blitz@2.0.0-alpha.1

View File

@@ -1,10 +1,10 @@
{
"name": "@blitzjs/auth",
"version": "2.0.0-alpha.4",
"version": "0.0.0",
"scripts": {
"build": "unbuild",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
"dev": "pnpm run predev && watch unbuild src --wait=0.2",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
"lint": "eslint . --fix",
"test": "vitest run",
"test-watch": "vitest",
@@ -19,8 +19,26 @@
"files": [
"dist/**"
],
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@testing-library/react": "13.0.0",
"@testing-library/react-hooks": "7.0.2",
"@types/cookie": "0.4.1",
"@types/jsonwebtoken": "8.5.8",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"react": "18.0.0",
"react-dom": "18.0.0",
"typescript": "^4.5.3",
"unbuild": "0.6.9",
"watch": "1.0.2"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@types/b64-lite": "1.3.0",
"@types/debug": "4.1.7",
"@types/secure-password": "3.1.1",
"b64-lite": "1.4.0",
"bad-behavior": "1.0.1",
@@ -33,23 +51,5 @@
"path": "0.12.7",
"secure-password": "4.0.0",
"url": "0.11.0"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@testing-library/react": "13.0.0",
"@testing-library/react-hooks": "7.0.2",
"@types/cookie": "0.4.1",
"@types/debug": "4.1.7",
"@types/jsonwebtoken": "8.5.8",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"react": "18.0.0",
"react-dom": "18.0.0",
"typescript": "^4.5.3",
"unbuild": "0.6.9",
"watch": "1.0.2"
},
"publishConfig": {
"access": "public"
}
}

View File

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

View File

@@ -3,5 +3,4 @@ import {SessionConfig} from "./shared/types"
declare global {
var sessionConfig: SessionConfig
var __BLITZ_SESSION_COOKIE_PREFIX: string | undefined
var __BLITZ_SUSPENSE_ENABLED: boolean
}

View File

@@ -1,7 +1,6 @@
import "./global"
export * from "./client"
export * from "./shared/constants"
export type {
SessionContextBase,
SessionContext,

View File

@@ -1,27 +0,0 @@
# @blitzjs/next
## 2.0.0-alpha.4
### Patch Changes
- @blitzjs/rpc@2.0.0-alpha.4
## 2.0.0-alpha.3
### Patch Changes
- @blitzjs/rpc@2.0.0-alpha.3
## 2.0.0-alpha.2
### Patch Changes
- @blitzjs/rpc@2.0.0-alpha.2
## 2.0.0-alpha.1
### Patch Changes
- 46a34c7b: initial publish
- Updated dependencies [46a34c7b]
- @blitzjs/rpc@2.0.0-alpha.1

View File

@@ -1,10 +1,10 @@
{
"name": "@blitzjs/next",
"version": "2.0.0-alpha.4",
"version": "0.0.0",
"scripts": {
"build": "unbuild",
"dev": "pnpm predev && pnpm watch unbuild src --wait=0.2",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts && wait-on -d 250 ../blitz-rpc/dist/index-server.d.ts",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
"lint": "eslint . --fix",
"test": "vitest run",
"test-watch": "vitest",
@@ -20,10 +20,8 @@
"dist/**"
],
"dependencies": {
"@blitzjs/rpc": "2.0.0-alpha.4",
"debug": "4.3.3",
"fs-extra": "10.0.1",
"react-query": "3.21.1"
"react-query": "3.34.12"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
@@ -39,7 +37,7 @@
"@types/testing-library__react-hooks": "4.0.0",
"blitz": "workspace:*",
"lodash.frompairs": "4.0.1",
"next": "12.1.1",
"next": "12.1.4",
"react": "18.0.0",
"react-dom": "18.0.0",
"ts-jest": "27.1.4",

View File

@@ -220,7 +220,8 @@ test("withErrorBoundary HOC", () => {
expect(cleanStack(onErrorComponentStack)).toMatchInlineSnapshot(`
{
"componentStack": "
at ErrorBoundary
at __vite_ssr_import_4__.withErrorBoundary.FallbackComponent
at ErrorBoundary
at withErrorBoundary",
}
`)

View File

@@ -1,5 +0,0 @@
import {QueryClient} from "react-query"
declare global {
var queryClient: QueryClient
}

View File

@@ -1,4 +1,3 @@
import "./global"
import type {
ClientPlugin,
BlitzProvider as BlitzProviderType,
@@ -7,9 +6,8 @@ import type {
} from "blitz"
import {AppProps} from "next/app"
import Head from "next/head"
import React from "react"
import {QueryClient, QueryClientProvider} from "react-query"
import {Hydrate, HydrateOptions} from "react-query/hydration"
import React, {FC} from "react"
import {Hydrate, HydrateOptions, QueryClient, QueryClientProvider} from "react-query"
export * from "./error-boundary"
export * from "./error-component"
@@ -38,11 +36,9 @@ const buildWithBlitz = <TPlugins extends readonly ClientPlugin<object>[]>(plugin
return (
<BlitzProvider dehydratedState={props.pageProps?.dehydratedState}>
<>
{/* @ts-ignore todo */}
{props.Component.suppressFirstRenderFlicker && <NoPageFlicker />}
<UserAppRoot {...props} Component={component} />
</>
{/* @ts-ignore todo */}
{props.Component.suppressFirstRenderFlicker && <NoPageFlicker />}
<UserAppRoot {...props} Component={component} />
</BlitzProvider>
)
}
@@ -57,27 +53,20 @@ export type BlitzProviderProps = {
hydrateOptions?: HydrateOptions
}
const BlitzProvider = ({
const BlitzProvider: FC<BlitzProviderProps> = ({
client,
contextSharing = false,
dehydratedState,
hydrateOptions,
children,
}: BlitzProviderProps & {children: JSX.Element}) => {
if (globalThis.queryClient) {
return (
<QueryClientProvider
client={client || globalThis.queryClient}
contextSharing={contextSharing}
>
<Hydrate state={dehydratedState} options={hydrateOptions}>
{children}
</Hydrate>
</QueryClientProvider>
)
}
return children
}) => {
return (
<QueryClientProvider client={client || queryClient} contextSharing={contextSharing}>
<Hydrate state={dehydratedState} options={hydrateOptions}>
{children}
</Hydrate>
</QueryClientProvider>
)
}
export type PluginsExports<TPlugins extends readonly ClientPlugin<object>[]> = Simplify<
@@ -123,6 +112,34 @@ const setupClient = <TPlugins extends readonly ClientPlugin<object>[]>({
export {setupClient}
// ------------------------------------ QUERY CLIENT CODE --------------------------------------------
const initializeQueryClient = () => {
let suspenseEnabled = true
if (!process.env.CLI_COMMAND_CONSOLE && !process.env.CLI_COMMAND_DB) {
suspenseEnabled = Boolean(process.env.__BLITZ_SUSPENSE_ENABLED)
}
return new QueryClient({
defaultOptions: {
queries: {
...(typeof window === "undefined" && {cacheTime: 0}),
suspense: suspenseEnabled,
retry: (failureCount, error: any) => {
if (process.env.NODE_ENV !== "production") return false
// Retry (max. 3 times) only if network error detected
if (error.message === "Network request failed" && failureCount <= 3) return true
return false
},
},
},
})
}
const queryClient = initializeQueryClient()
const customCSS = `
body::before {
content: "";

View File

@@ -73,18 +73,3 @@ export const setupBlitz = ({plugins}: SetupBlitzOptions) => {
return {gSSP, gSP, api}
}
import type {NextConfig} from "next"
import {installWebpackConfig} from "@blitzjs/rpc"
export function withBlitz(nextConfig: NextConfig = {}) {
return Object.assign({}, nextConfig, {
webpack: (config: any, options: any) => {
installWebpackConfig(config)
if (typeof nextConfig.webpack === "function") {
return nextConfig.webpack(config, options)
}
return config
},
} as NextConfig)
}

View File

@@ -1,33 +0,0 @@
# @blitzjs/rpc
## 2.0.0-alpha.4
### Patch Changes
- Updated dependencies
- blitz@2.0.0-alpha.4
- @blitzjs/auth@2.0.0-alpha.4
## 2.0.0-alpha.3
### Patch Changes
- Updated dependencies
- blitz@2.0.0-alpha.3
- @blitzjs/auth@2.0.0-alpha.3
## 2.0.0-alpha.2
### Patch Changes
- blitz@2.0.0-alpha.2
- @blitzjs/auth@2.0.0-alpha.2
## 2.0.0-alpha.1
### Patch Changes
- 46a34c7b: initial publish
- Updated dependencies [46a34c7b]
- blitz@2.0.0-alpha.1
- @blitzjs/auth@2.0.0-alpha.1

View File

@@ -1,19 +1,8 @@
import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: [
"./src/index-browser",
"./src/index-server",
"./src/loader-server",
"./src/loader-client",
],
externals: [
"index-browser.cjs",
"index-browser.mjs",
"index-server.cjs",
"index-server.mjs",
"react",
],
entries: ["./src/index-browser", "./src/index-server"],
externals: ["index-browser.cjs", "index-browser.mjs", "react"],
declaration: true,
rollup: {
emitCJS: true,

View File

@@ -1,10 +1,9 @@
{
"name": "@blitzjs/rpc",
"version": "2.0.0-alpha.4",
"version": "0.0.0",
"scripts": {
"build": "unbuild",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts && wait-on -d 250 ../blitz-auth/dist/index-browser.d.ts",
"dev": "pnpm run predev && watch unbuild src --wait=0.2",
"dev": "watch unbuild src --wait=0.2",
"lint": "eslint . --fix",
"test": "vitest run",
"test-watch": "vitest",
@@ -19,32 +18,13 @@
"files": [
"dist/**"
],
"dependencies": {
"@blitzjs/auth": "2.0.0-alpha.4",
"b64-lite": "1.4.0",
"bad-behavior": "1.0.1",
"chalk": "^4.1.0",
"debug": "4.3.3",
"react-query": "3.21.1",
"superjson": "1.8.0"
},
"dependencies": {},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@types/debug": "4.1.7",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"blitz": "2.0.0-alpha.4",
"next": "12.1.1",
"react": "18.0.0",
"react-dom": "18.0.0",
"typescript": "^4.5.3",
"unbuild": "0.6.9",
"watch": "1.0.2"
},
"peerDependencies": {
"blitz": "2.0.0-alpha.4",
"next": "*"
},
"publishConfig": {
"access": "public"
}

View File

@@ -1,13 +0,0 @@
export * from "./rpc"
export {useQuery, usePaginatedQuery, useInfiniteQuery, useMutation} from "./react-query"
export type {MutateFunction} from "./react-query"
export {
queryClient,
getQueryKey,
getInfiniteQueryKey,
invalidateQuery,
setQueryData,
} from "./react-query-utils"
export {useQueryErrorResetBoundary, QueryClient} from "react-query"
export {dehydrate} from "react-query/hydration"
export {invoke} from "./invoke"

View File

@@ -1,21 +0,0 @@
import {FirstParam, PromiseReturnType, isClient} from "blitz"
import {RpcClient} from "./rpc"
export function invoke<T extends (...args: any) => any, TInput = FirstParam<T>>(
queryFn: T,
params: TInput,
): Promise<PromiseReturnType<T>> {
if (typeof queryFn === "undefined") {
throw new Error(
"invoke is missing the first argument - it must be a query or mutation function",
)
}
if (isClient) {
const fn = queryFn as unknown as RpcClient
return fn(params, {fromInvoke: true}) as ReturnType<T>
} else {
const fn = queryFn as unknown as RpcClient
return fn(params) as ReturnType<T>
}
}

View File

@@ -1,208 +0,0 @@
import {QueryClient, QueryKey} from "react-query"
import {serialize} from "superjson"
import {isClient, isServer, AsyncFunc} from "blitz"
import {ResolverType, RpcClient} from "./rpc"
export type Resolver<TInput, TResult> = (input: TInput, ctx?: any) => Promise<TResult>
type RequestIdleCallbackDeadline = {
readonly didTimeout: boolean
timeRemaining: () => number
}
export const requestIdleCallback =
(typeof self !== "undefined" &&
self.requestIdleCallback &&
self.requestIdleCallback.bind(window)) ||
function (cb: (deadline: RequestIdleCallbackDeadline) => void): NodeJS.Timeout {
let start = Date.now()
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start))
},
})
}, 1)
}
type MutateOptions = {
refetch?: boolean
}
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
// Retry (max. 3 times) only if network error detected
if (error.message === "Network request failed" && failureCount <= 3) return true
return false
},
},
},
})
}
// Create internal QueryClient instance
export const queryClient = initializeQueryClient()
function isRpcClient(f: any): f is RpcClient<any, any> {
return !!f._isRpcClient
}
export interface QueryCacheFunctions<T> {
setQueryData: (
newData: T | ((oldData: T | undefined) => T),
opts?: MutateOptions,
) => ReturnType<typeof setQueryData>
}
export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput,
): QueryCacheFunctions<TResult> => ({
setQueryData: (newData, opts = {refetch: true}) => {
return setQueryData(resolver, params, newData, opts)
},
})
export const emptyQueryFn: RpcClient<unknown, unknown> = (() => {
const fn = (() => new Promise(() => {})) as any as RpcClient
fn._isRpcClient = true
return fn
})()
const isNotInUserTestEnvironment = () => {
if (process.env.JEST_WORKER_ID === undefined) return true
if (process.env.BLITZ_TEST_ENVIRONMENT !== undefined) return true
return false
}
export const validateQueryFn = <TInput, TResult>(
queryFn: Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
) => {
if (isClient && !isRpcClient(queryFn) && isNotInUserTestEnvironment()) {
throw new Error(
`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 "react-query")`,
)
}
}
const sanitize =
(type: ResolverType) =>
<TInput, TResult>(
queryFn: Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
): RpcClient<TInput, TResult> => {
if (isServer) return queryFn as any
validateQueryFn(queryFn)
const rpcClient = queryFn as RpcClient<TInput, TResult>
const queryFnName = type === "mutation" ? "useMutation" : "useQuery"
if (rpcClient._resolverType !== type && isNotInUserTestEnvironment()) {
throw new Error(
`"${queryFnName}" was expected to be called with a ${type} but was called with a "${rpcClient._resolverType}"`,
)
}
return rpcClient
}
export const sanitizeQuery = sanitize("query")
export const sanitizeMutation = sanitize("mutation")
export const getQueryKeyFromUrlAndParams = (url: string, params: unknown) => {
const queryKey = [url]
const args = typeof params === "function" ? (params as Function)() : params
queryKey.push(serialize(args) as any)
return queryKey as [string, any]
}
export function getQueryKey<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params?: TInput,
) {
if (typeof resolver === "undefined") {
throw new Error("getQueryKey is missing the first argument - it must be a resolver function")
}
return getQueryKeyFromUrlAndParams(sanitizeQuery(resolver)._routePath, params)
}
export function getInfiniteQueryKey<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params?: TInput,
) {
if (typeof resolver === "undefined") {
throw new Error(
"getInfiniteQueryKey is missing the first argument - it must be a resolver function",
)
}
const queryKey = getQueryKeyFromUrlAndParams(sanitizeQuery(resolver)._routePath, params)
return [...queryKey, "infinite"]
}
export function invalidateQuery<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params?: TInput,
) {
if (typeof resolver === "undefined") {
throw new Error(
"invalidateQuery is missing the first argument - it must be a resolver function",
)
}
const fullQueryKey = getQueryKey(resolver, params)
let queryKey: QueryKey
if (params) {
queryKey = fullQueryKey
} else {
// Params not provided, only use first query key item (url)
queryKey = fullQueryKey[0]
}
return queryClient.invalidateQueries(queryKey)
}
export function setQueryData<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput,
newData: TResult | ((oldData: TResult | undefined) => TResult),
opts: MutateOptions = {refetch: true},
): Promise<void | ReturnType<typeof queryClient.invalidateQueries>> {
if (typeof resolver === "undefined") {
throw new Error("setQueryData is missing the first argument - it must be a resolver function")
}
const queryKey = getQueryKey(resolver, params)
return new Promise((res) => {
queryClient.setQueryData(queryKey, newData)
let result: void | ReturnType<typeof queryClient.invalidateQueries>
if (opts.refetch) {
result = invalidateQuery(resolver, params)
}
if (isClient) {
// Fix for https://github.com/blitz-js/blitz/issues/1174
requestIdleCallback(() => {
res(result)
})
} else {
res(result)
}
})
}

View File

@@ -1,345 +0,0 @@
import {
useInfiniteQuery as useInfiniteReactQuery,
UseInfiniteQueryOptions,
UseInfiniteQueryResult,
useQuery as useReactQuery,
UseQueryOptions,
UseQueryResult,
MutateOptions,
useMutation as useReactQueryMutation,
UseMutationOptions,
UseMutationResult,
} from "react-query"
import {useSession} from "@blitzjs/auth"
import {isServer, FirstParam, PromiseReturnType, AsyncFunc} from "blitz"
import {
emptyQueryFn,
getQueryCacheFunctions,
getQueryKey,
QueryCacheFunctions,
sanitizeQuery,
sanitizeMutation,
getInfiniteQueryKey,
} from "./react-query-utils"
import {useRouter} from "next/router"
type QueryLazyOptions = {suspense: unknown} | {enabled: unknown}
type QueryNonLazyOptions =
| {suspense: true; enabled?: never}
| {suspense?: never; enabled: true}
| {suspense: true; enabled: true}
| {suspense?: never; enabled?: never}
// -------------------------
// useQuery
// -------------------------
type RestQueryResult<TResult, TError> = Omit<UseQueryResult<TResult, TError>, "data"> &
QueryCacheFunctions<TResult>
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options?: UseQueryOptions<TResult, TError, TSelectedData> & QueryNonLazyOptions,
): [TSelectedData, RestQueryResult<TSelectedData, TError>]
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: UseQueryOptions<TResult, TError, TSelectedData> & QueryLazyOptions,
): [TSelectedData | undefined, RestQueryResult<TSelectedData, TError>]
export function useQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: UseQueryOptions<TResult, TError, TSelectedData> = {},
) {
if (typeof queryFn === "undefined") {
throw new Error("useQuery is missing the first argument - it must be a query function")
}
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
const suspense = enabled === false ? false : options?.suspense
const session = useSession({suspense})
if (session.isLoading) {
enabled = false
}
const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled)
const enhancedResolverRpcClient = sanitizeQuery(queryFn)
const queryKey = getQueryKey(queryFn, params)
const {data, ...queryRest} = useReactQuery({
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? () => enhancedResolverRpcClient(params, {fromQueryHook: true})
: (emptyQueryFn as any),
...options,
enabled,
})
if (
queryRest.isIdle &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
throw new Promise(() => {})
}
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, params),
}
// return [data, rest as RestQueryResult<TResult>]
return [data, rest]
}
// -------------------------
// usePaginatedQuery
// -------------------------
type RestPaginatedResult<TResult, TError> = Omit<UseQueryResult<TResult, TError>, "data"> &
QueryCacheFunctions<TResult>
export function usePaginatedQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
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 = unknown,
TSelectedData = TResult,
>(
queryFn: T,
params: FirstParam<T>,
options: UseQueryOptions<TResult, TError, TSelectedData> = {},
) {
if (typeof queryFn === "undefined") {
throw new Error("usePaginatedQuery is missing the first argument - it must be a query function")
}
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
const suspense = enabled === false ? false : options?.suspense
const session = useSession({suspense})
if (session.isLoading) {
enabled = false
}
const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled)
const enhancedResolverRpcClient = sanitizeQuery(queryFn)
const queryKey = getQueryKey(queryFn, params)
const {data, ...queryRest} = useReactQuery({
queryKey: routerIsReady ? queryKey : ["_routerNotReady_"],
queryFn: routerIsReady
? () => enhancedResolverRpcClient(params, {fromQueryHook: true})
: (emptyQueryFn as any),
...options,
keepPreviousData: true,
enabled,
})
if (
queryRest.isIdle &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
throw new Promise(() => {})
}
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, params),
}
// return [data, rest as RestPaginatedResult<TResult>]
return [data, rest]
}
// -------------------------
// useInfiniteQuery
// -------------------------
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 useInfiniteQuery<
T extends AsyncFunc,
TResult = PromiseReturnType<T>,
TError = unknown,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
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 = unknown,
TSelectedData = TResult,
>(
queryFn: T,
getQueryParams: (pageParam: any) => FirstParam<T>,
options: InfiniteQueryConfig<TResult, TError, TSelectedData>,
) {
if (typeof queryFn === "undefined") {
throw new Error("useInfiniteQuery is missing the first argument - it must be a query function")
}
const suspenseEnabled = Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
let enabled = isServer && suspenseEnabled ? false : options?.enabled ?? options?.enabled !== null
const suspense = enabled === false ? false : options?.suspense
const session = useSession({suspense})
if (session.isLoading) {
enabled = false
}
const routerIsReady = useRouter().isReady || (isServer && suspenseEnabled)
const enhancedResolverRpcClient = sanitizeQuery(queryFn)
const queryKey = getInfiniteQueryKey(queryFn, getQueryParams)
const {data, ...queryRest} = useInfiniteReactQuery({
// 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}) =>
enhancedResolverRpcClient(getQueryParams(pageParam), {
fromQueryHook: true,
})
: (emptyQueryFn as any),
...options,
enabled,
})
if (
queryRest.isIdle &&
isServer &&
suspenseEnabled !== false &&
!data &&
(!options || !("suspense" in options) || options.suspense) &&
(!options || !("enabled" in options) || options.enabled)
) {
throw new Promise(() => {})
}
const rest = {
...queryRest,
...getQueryCacheFunctions<FirstParam<T>, TResult, T>(queryFn, getQueryParams),
pageParams: data?.pageParams,
}
return [data?.pages as any, rest]
}
// -------------------------------------------------------------------
// useMutation
// -------------------------------------------------------------------
/*
* We have to override react-query's MutationFunction and MutationResultPair
* types so because we have throwOnError:true by default. And by the RQ types
* have the mutate function result typed as TData|undefined which isn't typed
* properly with throwOnError.
*
* So this fixes that.
*/
export declare type MutateFunction<
TData,
TError = unknown,
TVariables = unknown,
TContext = unknown,
> = (
variables?: TVariables,
config?: MutateOptions<TData, TError, TVariables, TContext>,
) => Promise<TData>
export declare type MutationResultPair<TData, TError, TVariables, TContext> = [
MutateFunction<TData, TError, TVariables, TContext>,
Omit<UseMutationResult<TData, TError>, "mutate" | "mutateAsync">,
]
export declare type MutationFunction<TData, TVariables = unknown> = (
variables: TVariables,
ctx?: any,
) => Promise<TData>
export function useMutation<
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown,
>(
mutationResolver: MutationFunction<TData, TVariables>,
config?: UseMutationOptions<TData, TError, TVariables, TContext>,
): MutationResultPair<TData, TError, TVariables, TContext> {
const enhancedResolverRpcClient = sanitizeMutation(mutationResolver)
const {mutate, mutateAsync, ...rest} = useReactQueryMutation<TData, TError, TVariables, TContext>(
(variables) => enhancedResolverRpcClient(variables, {fromQueryHook: true}),
{
throwOnError: true,
...config,
} as any,
)
return [mutateAsync, rest] as MutationResultPair<TData, TError, TVariables, TContext>
}

View File

@@ -1,205 +0,0 @@
import {normalizePathTrailingSlash} from "next/dist/client/normalize-trailing-slash"
import {addBasePath} from "next/dist/shared/lib/router/router"
import {deserialize, serialize} from "superjson"
import {SuperJSONResult} from "superjson/dist/types"
import {isServer, CSRFTokenMismatchError} from "blitz"
import {getQueryKeyFromUrlAndParams, queryClient} from "./react-query-utils"
import {
getAntiCSRFToken,
getPublicDataStore,
HEADER_CSRF,
HEADER_CSRF_ERROR,
HEADER_PUBLIC_DATA_TOKEN,
HEADER_SESSION_CREATED,
} from "@blitzjs/auth"
export function normalizeApiRoute(path: string): string {
return normalizePathTrailingSlash(addBasePath(path))
}
export type ResolverType = "query" | "mutation"
export interface BuildRpcClientParams {
resolverName: string
resolverType: ResolverType
routePath: string
}
export interface RpcOptions {
fromQueryHook?: boolean
fromInvoke?: boolean
alreadySerialized?: boolean
}
export interface EnhancedRpc {
_isRpcClient: true
_resolverType: ResolverType
_resolverName: string
_routePath: string
}
export interface RpcClientBase<Input = unknown, Result = unknown> {
(params: Input, opts?: RpcOptions): Promise<Result>
}
export interface RpcClient<Input = unknown, Result = unknown>
extends EnhancedRpc,
RpcClientBase<Input, Result> {}
// export interface RpcResolver<Input = unknown, Result = unknown> extends EnhancedRpc {
// (params: Input, ctx?: Ctx): Promise<Result>
// }
export function __internal_buildRpcClient({
resolverName,
resolverType,
routePath,
}: BuildRpcClientParams): RpcClient {
const fullRoutePath = normalizeApiRoute("/api/rpc/" + routePath)
const httpClient: RpcClientBase = async (params, opts = {}) => {
const debug = (await import("debug")).default("blitz:rpc")
if (!opts.fromQueryHook && !opts.fromInvoke) {
console.warn(
"[Deprecation] Directly calling queries/mutations is deprecated in favor of invoke(queryFn, params)",
)
}
if (isServer) {
return Promise.resolve() as unknown
}
debug("Starting request for", fullRoutePath, "with", params, "and", opts)
const headers: Record<string, any> = {
"Content-Type": "application/json",
}
const antiCSRFToken = getAntiCSRFToken()
if (antiCSRFToken) {
debug("Adding antiCSRFToken cookie header", antiCSRFToken)
headers[HEADER_CSRF] = antiCSRFToken
} else {
debug("No antiCSRFToken cookie found")
}
let serialized: SuperJSONResult
if (opts.alreadySerialized) {
// params is already serialized with superjson when it gets here
// We have to serialize the params before passing to react-query in the query key
// because otherwise react-query will use JSON.parse(JSON.stringify)
// so by the time the arguments come here the real JS objects are lost
serialized = params as unknown as SuperJSONResult
} else {
serialized = serialize(params)
}
// Create a new AbortController instance for this request
const controller = new AbortController()
const promise = window
.fetch(fullRoutePath, {
method: "POST",
headers,
credentials: "include",
redirect: "follow",
body: JSON.stringify({
params: serialized.json,
meta: {
params: serialized.meta,
},
}),
signal: controller.signal,
})
.then(async (response) => {
debug("Received request for", routePath)
if (response.headers) {
if (response.headers.get(HEADER_PUBLIC_DATA_TOKEN)) {
getPublicDataStore().updateState()
debug("Public data updated")
}
if (response.headers.get(HEADER_SESSION_CREATED)) {
// This also runs on logout, because on logout a new anon session is created
debug("Session created")
setTimeout(async () => {
// Do these in the next tick to prevent various bugs like https://github.com/blitz-js/blitz/issues/2207
debug("Invalidating react-query cache...")
await queryClient.cancelQueries()
await queryClient.resetQueries()
queryClient.getMutationCache().clear()
// We have a 100ms delay here to prevent unnecessary stale queries from running
// This prevents the case where you logout on a page with
// Page.authenticate = {redirectTo: '/login'}
// Without this delay, queries that require authentication on the original page
// will still run (but fail because you are now logged out)
// Ref: https://github.com/blitz-js/blitz/issues/1935
}, 100)
}
if (response.headers.get(HEADER_CSRF_ERROR)) {
const err = new CSRFTokenMismatchError()
err.stack = null!
throw err
}
}
if (!response.ok) {
const error = new Error(response.statusText)
;(error as any).statusCode = response.status
;(error as any).path = routePath
error.stack = null!
throw error
} else {
let payload
try {
payload = await response.json()
} catch (error) {
const err = new Error(`Failed to parse json from ${routePath}`)
err.stack = null!
throw err
}
if (payload.error) {
let error = deserialize({
json: payload.error,
meta: payload.meta?.error,
}) as any
// We don't clear the publicDataStore for anonymous users,
// because there is not sensitive data
if (error.name === "AuthenticationError" && getPublicDataStore().getData().userId) {
getPublicDataStore().clear()
}
const prismaError = error.message.match(/invalid.*prisma.*invocation/i)
if (prismaError && !("code" in error)) {
error = new Error(prismaError[0])
error.statusCode = 500
}
error.stack = null
throw error
} else {
const data = deserialize({
json: payload.result,
meta: payload.meta?.result,
})
if (!opts.fromQueryHook) {
const queryKey = getQueryKeyFromUrlAndParams(routePath, params)
queryClient.setQueryData(queryKey, data)
}
return data
}
}
})
return promise
}
const rpcClient = httpClient as RpcClient
rpcClient._isRpcClient = true
rpcClient._resolverName = resolverName
rpcClient._resolverType = resolverType
rpcClient._routePath = fullRoutePath
return rpcClient
}

View File

@@ -1,6 +0,0 @@
import {QueryClient} from "react-query"
declare global {
var queryClient: QueryClient
var __BLITZ_SUSPENSE_ENABLED: boolean
}

View File

@@ -1,47 +1 @@
import "./global"
import {createClientPlugin} from "blitz"
import {DefaultOptions, QueryClient} from "react-query"
export * from "./data-client/index"
export const queryClient = globalThis.queryClient
interface BlitzRpcOptions {
reactQueryOptions?: DefaultOptions
}
export const BlitzRpcPlugin = createClientPlugin<BlitzRpcOptions, any>(
({reactQueryOptions}: BlitzRpcOptions) => {
const initializeQueryClient = () => {
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,
queries: {
...(typeof window === "undefined" && {cacheTime: 0}),
retry: (failureCount, error: any) => {
if (process.env.NODE_ENV !== "production") return false
// Retry (max. 3 times) only if network error detected
if (error.message === "Network request failed" && failureCount <= 3) return true
return false
},
...reactQueryOptions?.queries,
suspense: suspenseEnabled,
},
},
})
}
globalThis.queryClient = initializeQueryClient()
return {
events: {},
middleware: {},
exports: () => {},
}
},
)
export const todo = true

View File

@@ -1,216 +1,3 @@
import {assert, Ctx, baseLogger, prettyMs, newLine} from "blitz"
import {NextApiRequest, NextApiResponse} from "next"
import {deserialize, serialize as superjsonSerialize} from "superjson"
import chalk from "chalk"
// TODO - optimize end user server bundles by not exporting all client stuff here
export * from "./index-browser"
// Mechanism used by Vite/Next/Nuxt plugins for automatically loading query and mutation resolvers
function isObject(value: unknown): value is Record<string | symbol, unknown> {
return typeof value === "object" && value !== null
}
function getGlobalObject<T extends Record<string, unknown>>(key: string, defaultValue: T): T {
assert(key.startsWith("__internal_blitz"), "unsupported key")
if (typeof global === "undefined") {
return defaultValue
}
assert(isObject(global), "not an object")
return ((global as Record<string, unknown>)[key] =
((global as Record<string, unknown>)[key] as T) || defaultValue)
}
type Resolver = (...args: unknown[]) => Promise<unknown>
type ResolverFiles = Record<string, () => Promise<{default?: Resolver}>>
// We define `global.__internal_blitzRpcResolverFiles` to ensure we use the same global object.
// Needed for Next.js. I'm guessing that Next.js is including the `node_modules/` files in a seperate bundle than user files.
const g = getGlobalObject<{blitzRpcResolverFilesLoaded: ResolverFiles | null}>(
"__internal_blitzRpcResolverFiles",
{
blitzRpcResolverFilesLoaded: null,
},
)
export function loadBlitzRpcResolverFilesWithInternalMechanism() {
return g.blitzRpcResolverFilesLoaded
}
export function __internal_addBlitzRpcResolver(
routePath: string,
resolver: () => Promise<{default?: Resolver}>,
) {
g.blitzRpcResolverFilesLoaded = g.blitzRpcResolverFilesLoaded || {}
g.blitzRpcResolverFilesLoaded[routePath] = resolver
return resolver
}
import {resolve} from "path"
const dir = __dirname + (() => "")() // trick to avoid `@vercel/ncc` to glob import
const loaderServer = resolve(dir, "./loader-server.cjs")
const loaderClient = resolve(dir, "./loader-client.cjs")
export function installWebpackConfig<T extends any[]>(config: {module?: {rules?: T}}) {
config.module!.rules!.push({
test: /\/\[\[\.\.\.blitz]]\.[jt]s$/,
use: [{loader: loaderServer}],
})
config.module!.rules!.push({
test: /[\\/](queries|mutations)[\\/]/,
use: [{loader: loaderClient}],
})
}
// ----------
// END LOADER
// ----------
async function getResolverMap(): Promise<ResolverFiles | null | undefined> {
// Handles:
// - Next.js
// - Nuxt
// - Vite with `importBuild.js`
{
const resolverFilesLoaded = loadBlitzRpcResolverFilesWithInternalMechanism()
if (resolverFilesLoaded) {
return resolverFilesLoaded
}
}
// Handles:
// - Vite
// {
// const {resolverFilesLoaded, viteProvider} = await loadTelefuncFilesWithVite(runContext)
// if (resolverFilesLoaded) {
// assertUsage(
// Object.keys(resolverFilesLoaded).length > 0,
// getErrMsg(`Vite [\`${viteProvider}\`]`),
// )
// return resolverFilesLoaded
// }
// }
}
interface RpcConfig {
onError?: (error: Error) => void
}
export function rpcHandler(config: RpcConfig) {
return async function handleRpcRequest(req: NextApiRequest, res: NextApiResponse, ctx: Ctx) {
const resolverMap = await getResolverMap()
assert(resolverMap, "No query or mutation resolvers found")
assert(
Array.isArray(req.query.blitz),
"It seems your Blitz RPC endpoint file is not named [[...blitz]].(jt)s. Please ensure it is",
)
const relativeRoutePath = req.query.blitz.join("/")
const routePath = "/" + relativeRoutePath
const loadableResolver = resolverMap[routePath]
if (!loadableResolver) {
throw new Error("No resolver for path: " + routePath)
}
const resolver = (await loadableResolver()).default
if (!resolver) {
throw new Error("No default export for resolver path: " + routePath)
}
const log = baseLogger().getChildLogger({
prefix: [relativeRoutePath + "()"],
})
const customChalk = new chalk.Instance({
level: log.settings.type === "json" ? 0 : chalk.level,
})
if (req.method === "HEAD") {
// We used to initiate database connection here
res.status(200).end()
return
} else if (req.method === "POST") {
// Handle RPC call
if (typeof req.body.params === "undefined") {
const error = {message: "Request body is missing the `params` key"}
log.error(error.message)
res.status(400).json({
result: null,
error,
})
return
}
try {
const data = deserialize({
json: req.body.params,
meta: req.body.meta?.params,
})
log.info(customChalk.dim("Starting with input:"), data ? data : JSON.stringify(data))
const startTime = Date.now()
const result = await resolver(data, (res as any).blitzCtx)
const resolverDuration = Date.now() - startTime
log.debug(customChalk.dim("Result:"), result ? result : JSON.stringify(result))
const serializerStartTime = Date.now()
const serializedResult = superjsonSerialize(result)
const nextSerializerStartTime = Date.now()
;(res as any).blitzResult = result
res.json({
result: serializedResult.json,
error: null,
meta: {
result: serializedResult.meta,
},
})
log.debug(
customChalk.dim(
`Next.js serialization:${prettyMs(Date.now() - nextSerializerStartTime)}`,
),
)
const serializerDuration = Date.now() - serializerStartTime
const duration = Date.now() - startTime
log.info(
customChalk.dim(
`Finished: resolver:${prettyMs(resolverDuration)} serializer:${prettyMs(
serializerDuration,
)} total:${prettyMs(duration)}`,
),
)
newLine()
return
} catch (error: any) {
if (error._clearStack) {
delete error.stack
}
log.error(error)
newLine()
if (!error.statusCode) {
error.statusCode = 500
}
const serializedError = superjsonSerialize(error)
res.json({
result: null,
error: serializedError.json,
meta: {
error: serializedError.meta,
},
})
return
}
} else {
// Everything else is error
log.warn(`${req.method} method not supported`)
res.status(404).end()
return
}
}
}
export const todoServer = true

View File

@@ -1,58 +0,0 @@
import {
assertPosixPath,
convertFilePathToResolverName,
convertFilePathToResolverType,
convertPageFilePathToRoutePath,
toPosixPath,
} from "./loader-utils"
import {assert} from "blitz"
import {posix} from "path"
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
type Loader = {
_compiler?: {
name: string
context: string
}
resource: string
cacheable: (enabled: boolean) => void
}
export async function loader(this: Loader, input: string): Promise<string> {
const compiler = this._compiler!
const id = this.resource
const root = this._compiler!.context
const isSSR = compiler.name === "server"
if (!isSSR) {
const code = await transformBlitzRpcResolverClient(input, toPosixPath(id), toPosixPath(root))
return code
}
return input
}
module.exports = loader
export async function transformBlitzRpcResolverClient(_src: string, id: string, root: string) {
assertPosixPath(id)
assertPosixPath(root)
const resolverFilePath = "/" + posix.relative(root, id)
assertPosixPath(resolverFilePath)
const routePath = convertPageFilePathToRoutePath(resolverFilePath)
const resolverName = convertFilePathToResolverName(resolverFilePath)
const resolverType = convertFilePathToResolverType(resolverFilePath)
const code = `
// @ts-nocheck
import { __internal_buildRpcClient } from "@blitzjs/rpc";
export default __internal_buildRpcClient({
resolverName: "${resolverName}",
resolverType: "${resolverType}",
routePath: "${routePath}",
});
`
return code
}

View File

@@ -1,109 +0,0 @@
import {posix, join, dirname} from "path"
import {promises} from "fs"
import {
assertPosixPath,
toPosixPath,
buildPageExtensionRegex,
getIsRpcFile,
topLevelFoldersThatMayContainResolvers,
convertPageFilePathToRoutePath,
} from "./loader-utils"
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
type Loader = {
_compiler?: {
name: string
context: string
}
resource: string
cacheable: (enabled: boolean) => void
}
export async function loader(this: Loader, input: string): Promise<string> {
const compiler = this._compiler!
const id = this.resource
const root = this._compiler!.context
const isSSR = compiler.name === "server"
if (isSSR) {
this.cacheable(false)
const resolvers = await collectResolvers(root, ["ts", "js"])
const code = await transformBlitzRpcServer(input, toPosixPath(id), toPosixPath(root), resolvers)
return code
}
return input
}
module.exports = loader
export async function transformBlitzRpcServer(
src: string,
id: string,
root: string,
resolvers: string[],
) {
assertPosixPath(id)
assertPosixPath(root)
const blitzImport = 'import { __internal_addBlitzRpcResolver } from "@blitzjs/rpc";'
// No break line between `blitzImport` and `src` in order to preserve the source map's line mapping
let code = blitzImport + src
code += "\n\n"
for (let resolverFilePath of resolvers) {
const relativeResolverPath = posix.relative(dirname(id), join(root, resolverFilePath))
const routePath = convertPageFilePathToRoutePath(resolverFilePath)
code += `__internal_addBlitzRpcResolver('${routePath}', () => import('${relativeResolverPath}'));`
code += "\n"
}
// console.log("NEW CODE", code)
return code
}
export function collectResolvers(directory: string, pageExtensions: string[]): Promise<string[]> {
return recursiveFindResolvers(directory, buildPageExtensionRegex(pageExtensions))
}
export async function recursiveFindResolvers(
dir: string,
filter: RegExp,
ignore?: RegExp,
arr: string[] = [],
rootDir: string = dir,
): Promise<string[]> {
let folders = await promises.readdir(dir)
if (dir === rootDir) {
folders = folders.filter((folder) => topLevelFoldersThatMayContainResolvers.includes(folder))
}
await Promise.all(
folders.map(async (part: string) => {
const absolutePath = join(dir, part)
if (ignore && ignore.test(part)) return
const pathStat = await promises.stat(absolutePath)
if (pathStat.isDirectory()) {
await recursiveFindResolvers(absolutePath, filter, ignore, arr, rootDir)
return
}
if (!filter.test(part)) {
return
}
const relativeFromRoot = absolutePath.replace(rootDir, "")
if (getIsRpcFile(relativeFromRoot)) {
arr.push(relativeFromRoot)
return
}
}),
)
return arr.sort()
}

View File

@@ -1,58 +0,0 @@
import {assert} from "blitz"
import {win32, posix, sep} from "path"
export function assertPosixPath(path: string) {
const errMsg = `Wrongly formatted path: ${path}`
assert(!path.includes(win32.sep), errMsg)
// assert(path.startsWith('/'), errMsg)
}
export function toPosixPath(path: string) {
if (process.platform !== "win32") {
assert(sep === posix.sep, "TODO")
assertPosixPath(path)
return path
} else {
assert(sep === win32.sep, "TODO")
const pathPosix = path.split(win32.sep).join(posix.sep)
assertPosixPath(pathPosix)
return pathPosix
}
}
export function toSystemPath(path: string) {
path = path.split(posix.sep).join(sep)
path = path.split(win32.sep).join(sep)
return path
}
export const topLevelFoldersThatMayContainResolvers = ["src", "app", "integrations"]
export function buildPageExtensionRegex(pageExtensions: string[]) {
return new RegExp(`(?<!\\.test|\\.spec)\\.(?:${pageExtensions.join("|")})$`)
}
const fileExtensionRegex = /\.([a-z]+)$/
export function convertPageFilePathToRoutePath(filePath: string) {
return filePath
.replace(/^.*?[\\/]queries[\\/]/, "/")
.replace(/^.*?[\\/]mutations[\\/]/, "/")
.replace(fileExtensionRegex, "")
}
export function convertFilePathToResolverName(filePathFromAppRoot: string) {
return filePathFromAppRoot
.replace(/^.*[\\/](queries|mutations)[\\/]/, "")
.replace(fileExtensionRegex, "")
}
export function convertFilePathToResolverType(filePathFromAppRoot: string) {
return filePathFromAppRoot.match(/[\\/]queries[\\/]/) ? "query" : "mutation"
}
export function getIsRpcFile(filePathFromAppRoot: string) {
return (
/[\\/]queries[\\/]/.test(filePathFromAppRoot) || /[\\/]mutations[\\/]/.test(filePathFromAppRoot)
)
}

View File

@@ -1,8 +1,5 @@
{
"extends": "@blitzjs/config/tsconfig.library.json",
"compilerOptions": {
"lib": ["DOM", "ES2015"]
},
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

View File

@@ -1,30 +0,0 @@
# blitz
## 2.0.0-alpha.4
### Patch Changes
- fix more cli problems
- @blitzjs/generator@2.0.0-alpha.4
## 2.0.0-alpha.3
### Patch Changes
- downgrade pkg-dir to non-esm only version
- @blitzjs/generator@2.0.0-alpha.3
## 2.0.0-alpha.2
### Patch Changes
- Updated dependencies
- @blitzjs/generator@2.0.0-alpha.2
## 2.0.0-alpha.1
### Patch Changes
- 46a34c7b: initial publish
- Updated dependencies [46a34c7b]
- @blitzjs/generator@2.0.0-alpha.1

View File

@@ -2,7 +2,15 @@ import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: ["./src/index-browser", "./src/index-server", "./src/cli/index"],
externals: ["index-browser.cjs", "index-browser.mjs", "zod"],
externals: [
"index-browser.cjs",
"index-browser.mjs",
"react",
"chalk",
"console-table-printer",
"tslog",
"ora",
],
declaration: true,
rollup: {
emitCJS: true,

Some files were not shown because too many files have changed in this diff Show More