Compare commits
10 Commits
test
...
@blitzjs/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1ef45bf28 | ||
|
|
c6e8df5efd | ||
|
|
46a34c7b3a | ||
|
|
18d4ef74a9 | ||
|
|
212a1cb941 | ||
|
|
151dcc308e | ||
|
|
fd90cbedb4 | ||
|
|
10d27c74af | ||
|
|
9810d984f1 | ||
|
|
10574b7359 |
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$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-*"]
|
||||
}
|
||||
9
.changeset/ninety-pets-heal.md
Normal file
9
.changeset/ninety-pets-heal.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"blitz": patch
|
||||
"@blitzjs/auth": patch
|
||||
"@blitzjs/next": patch
|
||||
"@blitzjs/rpc": patch
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
initial publish
|
||||
5
.changeset/poor-peas-lick.md
Normal file
5
.changeset/poor-peas-lick.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
fix generator npm package dist
|
||||
18
.changeset/pre.json
Normal file
18
.changeset/pre.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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": ["ninety-pets-heal", "poor-peas-lick"]
|
||||
}
|
||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
|
||||
with:
|
||||
version: 6.10.0
|
||||
version: 6.32.6
|
||||
- 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 lint
|
||||
- run: pnpm build
|
||||
- run: pnpm lint
|
||||
- run: pnpm build:apps
|
||||
- run: pnpm test
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
pnpm manypkg check
|
||||
pnpm lint
|
||||
pnpx pretty-quick --staged
|
||||
pnpm pretty-quick --staged
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
20
apps/web/app/blitz-client.ts
Normal file
20
apps/web/app/blitz-client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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}
|
||||
14
apps/web/app/mutations/createUser.ts
Normal file
14
apps/web/app/mutations/createUser.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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
|
||||
}
|
||||
4
apps/web/app/mutations/setBasic.ts
Normal file
4
apps/web/app/mutations/setBasic.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function setBasic(input, ctx) {
|
||||
console.log("SET BASIC input", input)
|
||||
return
|
||||
}
|
||||
5
apps/web/app/queries/getBasic.ts
Normal file
5
apps/web/app/queries/getBasic.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default async function getBasic(input, ctx) {
|
||||
console.log("INPUT", input)
|
||||
|
||||
return "basic-result"
|
||||
}
|
||||
11
apps/web/app/queries/getUsers.ts
Normal file
11
apps/web/app/queries/getUsers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
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
|
||||
}
|
||||
5
apps/web/app/queries/v2/getV2Basic.ts
Normal file
5
apps/web/app/queries/v2/getV2Basic.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function getV2Basic(input, ctx) {
|
||||
console.log("INPUT", input)
|
||||
|
||||
return "basic-result"
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
})
|
||||
module.exports = withBlitz(
|
||||
withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
"@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.4",
|
||||
"next": "12.1.1",
|
||||
"prisma": "3.9.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 "../src/client-setup"
|
||||
import React, {Suspense} from "react"
|
||||
import {withBlitz} from "app/blitz-client"
|
||||
|
||||
function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
@@ -27,7 +27,9 @@ function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={RootErrorFallback}>
|
||||
<Component {...pageProps} />
|
||||
<Suspense fallback="Loading...">
|
||||
<Component {...pageProps} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const {session} = ctx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (_req, res) => {
|
||||
|
||||
4
apps/web/pages/api/rpc/[[...blitz]].ts
Normal file
4
apps/web/pages/api/rpc/[[...blitz]].ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import {rpcHandler} from "@blitzjs/rpc"
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(rpcHandler({onError: console.log}))
|
||||
@@ -1,6 +1,5 @@
|
||||
import {setPublicDataForUser} from "@blitzjs/auth"
|
||||
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
|
||||
41
apps/web/pages/create-user.tsx
Normal file
41
apps/web/pages/create-user.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
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
|
||||
@@ -1,3 +1,6 @@
|
||||
import {invoke} from "@blitzjs/rpc"
|
||||
import getBasic from "app/queries/getBasic"
|
||||
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
}
|
||||
@@ -6,6 +9,7 @@ export default function Web() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Web</h1>
|
||||
<button onClick={() => invoke(getBasic, "FROM BROWSER")}>GetBasic</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const PageWithRedirect = () => {
|
||||
const PageWithAuthRedirect = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
@@ -13,6 +13,6 @@ const PageWithRedirect = () => {
|
||||
)
|
||||
}
|
||||
|
||||
PageWithRedirect.redirectAuthenticatedTo = "/"
|
||||
PageWithAuthRedirect.redirectAuthenticatedTo = "/"
|
||||
|
||||
export default PageWithRedirect
|
||||
export default PageWithAuthRedirect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {gSP} from "../src/server-setup"
|
||||
import {gSP} from "app/blitz-server"
|
||||
|
||||
export const getStaticProps = gSP(async ({ctx}) => {
|
||||
return {
|
||||
@@ -14,8 +14,8 @@ export const getStaticProps = gSP(async ({ctx}) => {
|
||||
}
|
||||
})
|
||||
|
||||
function Page({data}) {
|
||||
function PageWithGsp({data}) {
|
||||
return <div>{JSON.stringify(data, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default Page
|
||||
export default PageWithGsp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {gSSP} from "../src/server-setup"
|
||||
import {gSSP} from "app/blitz-server"
|
||||
|
||||
type Props = {
|
||||
userId: unknown
|
||||
@@ -16,8 +16,8 @@ export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
|
||||
}
|
||||
})
|
||||
|
||||
function Page(props: Props) {
|
||||
function PageWithGssp(props: Props) {
|
||||
return <div>{JSON.stringify(props, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default Page
|
||||
export default PageWithGssp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useAuthenticatedSession} from "../src/client-setup"
|
||||
import {useAuthenticatedSession} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
export default function PageWithUseAuthSession() {
|
||||
useAuthenticatedSession()
|
||||
return <div>This page is using useAuthenticatedSession</div>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useAuthorizeIf} from "../src/client-setup"
|
||||
import {useAuthorizeIf} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
export default function PageWithUseAuthorizeIf() {
|
||||
useAuthorizeIf(Math.random() > 0.5)
|
||||
return <div>This page is using useAuthorizeIf</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useAuthorize} from "../src/client-setup"
|
||||
import {useAuthorize} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseAuthorize() {
|
||||
useAuthorize()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useRedirectAuthenticated} from "../src/client-setup"
|
||||
import {useRedirectAuthenticated} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseAuthorizeIf() {
|
||||
export default function PageWithUseRedirectAuth() {
|
||||
useRedirectAuthenticated("/")
|
||||
return <div>This page is using useRedirectAuthenticated</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useSession} from "../src/client-setup"
|
||||
import {useSession} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseSession() {
|
||||
const data = useSession()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const PageWithRedirect = ({data}) => {
|
||||
const PageWithoutFlicker = ({data}) => {
|
||||
return <div>{JSON.stringify(data)}</div>
|
||||
}
|
||||
|
||||
PageWithRedirect.suppressFirstRenderFlicker = true
|
||||
PageWithoutFlicker.suppressFirstRenderFlicker = true
|
||||
|
||||
export default PageWithRedirect
|
||||
export default PageWithoutFlicker
|
||||
|
||||
20
apps/web/pages/users.tsx
Normal file
20
apps/web/pages/users.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
@@ -1,26 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "@blitzjs/config/tsconfig.nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"],
|
||||
"exclude": ["node_modules", "test"]
|
||||
}
|
||||
|
||||
12
integration-tests/auth/app/blitz-client.ts
Normal file
12
integration-tests/auth/app/blitz-client.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {AuthClientPlugin} from "@blitzjs/auth"
|
||||
import {setupClient} from "@blitzjs/next"
|
||||
|
||||
const {withBlitz} = setupClient({
|
||||
plugins: [
|
||||
AuthClientPlugin({
|
||||
cookiePrefix: "auth-tests-cookie-prefix",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {withBlitz}
|
||||
@@ -6,7 +6,7 @@ import {prisma as db} from "../prisma/index"
|
||||
const {gSSP, gSP, api} = setupBlitz({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
cookiePrefix: "auth-tests-cookie-prefix",
|
||||
storage: PrismaStorage(db as any),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
@@ -1,10 +1,4 @@
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
esmExternals: "loose",
|
||||
},
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
module.exports = withBlitz({
|
||||
// update me
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@prisma/client": "3.9.0",
|
||||
"blitz": "workspace:*",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "12.1.4",
|
||||
"next": "12.1.1",
|
||||
"prisma": "3.9.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0"
|
||||
@@ -28,22 +28,13 @@
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "../src/client-setup"
|
||||
import {withBlitz} from "../app/blitz-client"
|
||||
|
||||
function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "../../app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "../../app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
res.status(200).end()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {api} from "../../src/server-setup"
|
||||
import {api} from "../../app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useAuthorize} from "../src/client-setup"
|
||||
import {useAuthorize} from "@blitzjs/auth"
|
||||
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {describe, it, expect, beforeAll, afterAll} from "vitest"
|
||||
import {killApp, findPort, launchApp, nextBuild, nextStart} from "./next-test-utils"
|
||||
import webdriver from "./next-webdriver"
|
||||
import {killApp, findPort, launchApp, nextBuild, nextStart} from "../../utils/next-test-utils"
|
||||
import webdriver from "../../utils/next-webdriver"
|
||||
|
||||
import {join} from "path"
|
||||
import seed from "../prisma/seed"
|
||||
import fetch from "node-fetch"
|
||||
@@ -10,10 +11,10 @@ let app: any
|
||||
let appPort: number
|
||||
const appDir = join(__dirname, "../")
|
||||
const HEADER_CSRF = "anti-csrf"
|
||||
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 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 HEADER_PUBLIC_DATA_TOKEN = "public-data-token"
|
||||
|
||||
function readCookie(cookieHeader, name) {
|
||||
@@ -127,7 +128,7 @@ describe("dev mode", () => {
|
||||
console.log(error)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(() => killApp(app))
|
||||
afterAll(async () => await killApp(app))
|
||||
runTests()
|
||||
})
|
||||
|
||||
@@ -141,7 +142,7 @@ describe("server mode", () => {
|
||||
console.log(err)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(() => killApp(app))
|
||||
afterAll(async () => await killApp(app))
|
||||
|
||||
runTests()
|
||||
})
|
||||
|
||||
4
integration-tests/rpc/app/mutations/setBasic.js
Normal file
4
integration-tests/rpc/app/mutations/setBasic.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export default async function setBasic(input, ctx) {
|
||||
global.basic = input
|
||||
return global.basic
|
||||
}
|
||||
12
integration-tests/rpc/app/queries/getBasic.js
Normal file
12
integration-tests/rpc/app/queries/getBasic.js
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
}
|
||||
3
integration-tests/rpc/app/queries/getFailure.js
Normal file
3
integration-tests/rpc/app/queries/getFailure.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async function getFailure() {
|
||||
throw new Error("error on purpose for test")
|
||||
}
|
||||
3
integration-tests/rpc/app/queries/v2/getNestedBasic.js
Normal file
3
integration-tests/rpc/app/queries/v2/getNestedBasic.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async function getNestedBasic() {
|
||||
return "nested-basic"
|
||||
}
|
||||
5
integration-tests/rpc/next-env.d.ts
vendored
Normal file
5
integration-tests/rpc/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <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.
|
||||
4
integration-tests/rpc/next.config.js
Normal file
4
integration-tests/rpc/next.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
module.exports = withBlitz({
|
||||
// update me
|
||||
})
|
||||
29
integration-tests/rpc/package.json
Normal file
29
integration-tests/rpc/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
3
integration-tests/rpc/pages/api/rpc/[[...blitz]].ts
Normal file
3
integration-tests/rpc/pages/api/rpc/[[...blitz]].ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import {rpcHandler} from "@blitzjs/rpc"
|
||||
|
||||
export default rpcHandler({onError: console.log})
|
||||
4
integration-tests/rpc/pages/index.js
Normal file
4
integration-tests/rpc/pages/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const Page = () => {
|
||||
return <div id="page-container">Hello World</div>
|
||||
}
|
||||
export default Page
|
||||
7
integration-tests/rpc/pages/query.js
Normal file
7
integration-tests/rpc/pages/query.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import getBasic from "../app/queries/getBasic"
|
||||
|
||||
const Page = () => {
|
||||
getBasic().then(console.log)
|
||||
return <div id="page-container">Hello World</div>
|
||||
}
|
||||
export default Page
|
||||
199
integration-tests/rpc/test/index.test.js
Normal file
199
integration-tests/rpc/test/index.test.js
Normal file
@@ -0,0 +1,199 @@
|
||||
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,
|
||||
)
|
||||
})
|
||||
20
integration-tests/rpc/tsconfig.json
Normal file
20
integration-tests/rpc/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
8
integration-tests/rpc/vitest.config.ts
Normal file
8
integration-tests/rpc/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {defineConfig} from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 5000 * 60 * 2,
|
||||
hookTimeout: 5000 * 60 * 2,
|
||||
},
|
||||
})
|
||||
@@ -96,9 +96,7 @@ interface RunNextCommandOptions {
|
||||
}
|
||||
|
||||
export function runNextCommand(argv: any[], options: RunNextCommandOptions = {}) {
|
||||
const nextDir = path.dirname(require.resolve("next/package"))
|
||||
const nextBin = path.join(nextDir, "dist/bin/next")
|
||||
const cwd = options.cwd || nextDir
|
||||
const cwd = options.cwd
|
||||
// Let Next.js decide the environment
|
||||
const env = {
|
||||
...process.env,
|
||||
@@ -109,7 +107,7 @@ export function runNextCommand(argv: any[], options: RunNextCommandOptions = {})
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
console.log(`Running command "next ${argv.join(" ")}"`)
|
||||
const instance = spawn("node", ["--no-deprecation", nextBin, ...argv], {
|
||||
const instance = spawn("pnpm", ["exec", "next", ...argv], {
|
||||
...options.spawnOptions,
|
||||
cwd,
|
||||
env,
|
||||
@@ -167,11 +165,8 @@ interface RunNextCommandDevOptions {
|
||||
nextStart?: boolean
|
||||
}
|
||||
|
||||
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
|
||||
export function runNextCommandDev(argv, opts: RunNextCommandDevOptions = {}) {
|
||||
const cwd = opts.cwd // || nextDir
|
||||
const env = {
|
||||
...process.env,
|
||||
NODE_ENV: opts.nextStart ? ("production" as const) : ("development" as const),
|
||||
@@ -179,9 +174,8 @@ export function runNextCommandDev(argv, stdOut, opts: RunNextCommandDevOptions =
|
||||
...opts.env,
|
||||
}
|
||||
|
||||
const nodeArgs = opts.nodeArgs || []
|
||||
return new Promise<void | string | ChildProcess>((resolve, reject) => {
|
||||
const instance = spawn("node", [...nodeArgs, "--no-deprecation", nextBin, ...argv], {
|
||||
const instance = spawn("pnpm", ["exec", "next", ...argv], {
|
||||
cwd,
|
||||
env,
|
||||
})
|
||||
@@ -236,7 +230,7 @@ export function runNextCommandDev(argv, stdOut, opts: RunNextCommandDevOptions =
|
||||
|
||||
// Launch the app in dev mode.
|
||||
export function launchApp(dir, port, opts) {
|
||||
return runNextCommandDev([dir, "-p", port], undefined, opts)
|
||||
return runNextCommandDev(["-p", port], {cwd: dir, ...opts})
|
||||
}
|
||||
|
||||
export function nextBuild(dir, args = [], opts = {}) {
|
||||
@@ -244,26 +238,27 @@ export function nextBuild(dir, args = [], opts = {}) {
|
||||
}
|
||||
|
||||
export function nextExport(dir, {outdir}, opts = {}) {
|
||||
return runNextCommand(["export", dir, "--outdir", outdir], opts)
|
||||
return runNextCommand(["export", dir, "--outdir", outdir], {cwd: dir, ...opts})
|
||||
}
|
||||
|
||||
export function nextExportDefault(dir, opts = {}) {
|
||||
return runNextCommand(["export", dir], opts)
|
||||
return runNextCommand(["export", dir], {cwd: dir, ...opts})
|
||||
}
|
||||
|
||||
export function nextLint(dir, args = [], opts = {}) {
|
||||
return runNextCommand(["lint", dir, ...args], opts)
|
||||
return runNextCommand(["lint", dir, ...args], {cwd: dir, ...opts})
|
||||
}
|
||||
|
||||
export function nextStart(dir, port, opts = {}) {
|
||||
return runNextCommandDev(["start", "-p", port, dir], undefined, {
|
||||
return runNextCommandDev(["start", "-p", port], {
|
||||
cwd: dir,
|
||||
...opts,
|
||||
nextStart: true,
|
||||
})
|
||||
}
|
||||
|
||||
export function buildTS(args = [], cwd, env = {NODE_ENV: "production" as const}) {
|
||||
cwd = cwd || path.dirname(require.resolve("next/package"))
|
||||
cwd = cwd
|
||||
env = {...process.env, ...env}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
@@ -144,6 +144,19 @@ 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
|
||||
24
integration-tests/utils/package.json
Normal file
24
integration-tests/utils/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,17 @@
|
||||
"lint": "turbo run lint",
|
||||
"test": "turbo run test",
|
||||
"clean": "turbo run clean && rm -rf node_modules",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"release": "changeset publish && git push --follow-tags"
|
||||
},
|
||||
"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.4",
|
||||
"next": "12.1.1",
|
||||
"only-allow": "1.1.0",
|
||||
"patch-package": "6.4.7",
|
||||
"prettier": "^2.5.1",
|
||||
|
||||
15
packages/blitz-auth/CHANGELOG.md
Normal file
15
packages/blitz-auth/CHANGELOG.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @blitzjs/auth
|
||||
|
||||
## 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
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@blitzjs/auth",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0-alpha.2",
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "pnpm run predev && watch unbuild src --wait=0.2",
|
||||
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
|
||||
"dev": "pnpm run predev && watch unbuild src --wait=0.2",
|
||||
"lint": "eslint . --fix",
|
||||
"test": "vitest run",
|
||||
"test-watch": "vitest",
|
||||
@@ -19,26 +19,8 @@
|
||||
"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",
|
||||
@@ -51,5 +33,23 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export interface UseSessionOptions {
|
||||
}
|
||||
|
||||
export const useSession = (options: UseSessionOptions = {}): ClientSession => {
|
||||
const suspense = options?.suspense ?? Boolean(process.env.__BLITZ_SUSPENSE_ENABLED)
|
||||
const suspense = options?.suspense ?? Boolean(globalThis.__BLITZ_SUSPENSE_ENABLED)
|
||||
|
||||
let initialState: ClientSession
|
||||
if (options.initialPublicData) {
|
||||
|
||||
@@ -3,4 +3,5 @@ import {SessionConfig} from "./shared/types"
|
||||
declare global {
|
||||
var sessionConfig: SessionConfig
|
||||
var __BLITZ_SESSION_COOKIE_PREFIX: string | undefined
|
||||
var __BLITZ_SUSPENSE_ENABLED: boolean
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "./global"
|
||||
|
||||
export * from "./client"
|
||||
export * from "./shared/constants"
|
||||
export type {
|
||||
SessionContextBase,
|
||||
SessionContext,
|
||||
|
||||
15
packages/blitz-next/CHANGELOG.md
Normal file
15
packages/blitz-next/CHANGELOG.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @blitzjs/next
|
||||
|
||||
## 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
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@blitzjs/next",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0-alpha.2",
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "pnpm predev && pnpm watch unbuild src --wait=0.2",
|
||||
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
|
||||
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts && wait-on -d 250 ../blitz-rpc/dist/index-server.d.ts",
|
||||
"lint": "eslint . --fix",
|
||||
"test": "vitest run",
|
||||
"test-watch": "vitest",
|
||||
@@ -20,8 +20,10 @@
|
||||
"dist/**"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blitzjs/rpc": "2.0.0-alpha.2",
|
||||
"debug": "4.3.3",
|
||||
"react-query": "3.34.12"
|
||||
"fs-extra": "10.0.1",
|
||||
"react-query": "3.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "workspace:*",
|
||||
@@ -37,7 +39,7 @@
|
||||
"@types/testing-library__react-hooks": "4.0.0",
|
||||
"blitz": "workspace:*",
|
||||
"lodash.frompairs": "4.0.1",
|
||||
"next": "12.1.4",
|
||||
"next": "12.1.1",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"ts-jest": "27.1.4",
|
||||
|
||||
@@ -220,8 +220,7 @@ test("withErrorBoundary HOC", () => {
|
||||
expect(cleanStack(onErrorComponentStack)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"componentStack": "
|
||||
at __vite_ssr_import_4__.withErrorBoundary.FallbackComponent
|
||||
at ErrorBoundary
|
||||
at ErrorBoundary
|
||||
at withErrorBoundary",
|
||||
}
|
||||
`)
|
||||
|
||||
5
packages/blitz-next/src/global.ts
Normal file
5
packages/blitz-next/src/global.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {QueryClient} from "react-query"
|
||||
|
||||
declare global {
|
||||
var queryClient: QueryClient
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import "./global"
|
||||
import type {
|
||||
ClientPlugin,
|
||||
BlitzProvider as BlitzProviderType,
|
||||
@@ -6,8 +7,9 @@ import type {
|
||||
} from "blitz"
|
||||
import {AppProps} from "next/app"
|
||||
import Head from "next/head"
|
||||
import React, {FC} from "react"
|
||||
import {Hydrate, HydrateOptions, QueryClient, QueryClientProvider} from "react-query"
|
||||
import React from "react"
|
||||
import {QueryClient, QueryClientProvider} from "react-query"
|
||||
import {Hydrate, HydrateOptions} from "react-query/hydration"
|
||||
|
||||
export * from "./error-boundary"
|
||||
export * from "./error-component"
|
||||
@@ -36,9 +38,11 @@ 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>
|
||||
)
|
||||
}
|
||||
@@ -53,20 +57,27 @@ export type BlitzProviderProps = {
|
||||
hydrateOptions?: HydrateOptions
|
||||
}
|
||||
|
||||
const BlitzProvider: FC<BlitzProviderProps> = ({
|
||||
const BlitzProvider = ({
|
||||
client,
|
||||
contextSharing = false,
|
||||
dehydratedState,
|
||||
hydrateOptions,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<QueryClientProvider client={client || queryClient} contextSharing={contextSharing}>
|
||||
<Hydrate state={dehydratedState} options={hydrateOptions}>
|
||||
{children}
|
||||
</Hydrate>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}: 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
|
||||
}
|
||||
|
||||
export type PluginsExports<TPlugins extends readonly ClientPlugin<object>[]> = Simplify<
|
||||
@@ -112,34 +123,6 @@ 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: "";
|
||||
|
||||
@@ -73,3 +73,18 @@ 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)
|
||||
}
|
||||
|
||||
17
packages/blitz-rpc/CHANGELOG.md
Normal file
17
packages/blitz-rpc/CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# @blitzjs/rpc
|
||||
|
||||
## 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
|
||||
@@ -1,8 +1,19 @@
|
||||
import {BuildConfig} from "unbuild"
|
||||
|
||||
const config: BuildConfig = {
|
||||
entries: ["./src/index-browser", "./src/index-server"],
|
||||
externals: ["index-browser.cjs", "index-browser.mjs", "react"],
|
||||
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",
|
||||
],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@blitzjs/rpc",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0-alpha.2",
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "watch unbuild src --wait=0.2",
|
||||
"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",
|
||||
"lint": "eslint . --fix",
|
||||
"test": "vitest run",
|
||||
"test-watch": "vitest",
|
||||
@@ -18,13 +19,32 @@
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.0-alpha.2",
|
||||
"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"
|
||||
},
|
||||
"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.2",
|
||||
"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.2",
|
||||
"next": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
13
packages/blitz-rpc/src/data-client/index.ts
Normal file
13
packages/blitz-rpc/src/data-client/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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"
|
||||
21
packages/blitz-rpc/src/data-client/invoke.ts
Normal file
21
packages/blitz-rpc/src/data-client/invoke.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
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>
|
||||
}
|
||||
}
|
||||
208
packages/blitz-rpc/src/data-client/react-query-utils.ts
Normal file
208
packages/blitz-rpc/src/data-client/react-query-utils.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
345
packages/blitz-rpc/src/data-client/react-query.tsx
Normal file
345
packages/blitz-rpc/src/data-client/react-query.tsx
Normal file
@@ -0,0 +1,345 @@
|
||||
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>
|
||||
}
|
||||
205
packages/blitz-rpc/src/data-client/rpc.ts
Normal file
205
packages/blitz-rpc/src/data-client/rpc.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
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
|
||||
}
|
||||
6
packages/blitz-rpc/src/global.ts
Normal file
6
packages/blitz-rpc/src/global.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {QueryClient} from "react-query"
|
||||
|
||||
declare global {
|
||||
var queryClient: QueryClient
|
||||
var __BLITZ_SUSPENSE_ENABLED: boolean
|
||||
}
|
||||
@@ -1 +1,47 @@
|
||||
export const todo = true
|
||||
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: () => {},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,3 +1,216 @@
|
||||
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"
|
||||
|
||||
export const todoServer = true
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
packages/blitz-rpc/src/loader-client.ts
Normal file
58
packages/blitz-rpc/src/loader-client.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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
|
||||
}
|
||||
109
packages/blitz-rpc/src/loader-server.ts
Normal file
109
packages/blitz-rpc/src/loader-server.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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()
|
||||
}
|
||||
58
packages/blitz-rpc/src/loader-utils.ts
Normal file
58
packages/blitz-rpc/src/loader-utils.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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)
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "@blitzjs/config/tsconfig.library.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2015"]
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
16
packages/blitz/CHANGELOG.md
Normal file
16
packages/blitz/CHANGELOG.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# blitz
|
||||
|
||||
## 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
|
||||
@@ -2,15 +2,7 @@ import {BuildConfig} from "unbuild"
|
||||
|
||||
const config: BuildConfig = {
|
||||
entries: ["./src/index-browser", "./src/index-server", "./src/cli/index"],
|
||||
externals: [
|
||||
"index-browser.cjs",
|
||||
"index-browser.mjs",
|
||||
"react",
|
||||
"chalk",
|
||||
"console-table-printer",
|
||||
"tslog",
|
||||
"ora",
|
||||
],
|
||||
externals: ["index-browser.cjs", "index-browser.mjs", "zod"],
|
||||
declaration: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "blitz",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0-alpha.2",
|
||||
"scripts": {
|
||||
"build": "unbuild",
|
||||
"dev": "watch unbuild src --wait=0.2",
|
||||
@@ -28,12 +28,19 @@
|
||||
"chalk": "^4.1.0",
|
||||
"console-table-printer": "2.10.0",
|
||||
"cross-spawn": "7.0.3",
|
||||
"debug": "4.3.3",
|
||||
"detect-port": "1.3.0",
|
||||
"dotenv": "16.0.0",
|
||||
"dotenv-expand": "8.0.3",
|
||||
"esbuild": "0.14.34",
|
||||
"fs-extra": "10.0.1",
|
||||
"hasbin": "1.2.3",
|
||||
"npm-which": "3.0.1",
|
||||
"ora": "5.3.0",
|
||||
"p-event": "4.2.0",
|
||||
"pkg-dir": "6.0.1",
|
||||
"prompts": "2.4.2",
|
||||
"resolve-cwd": "3.0.0",
|
||||
"superjson": "1.8.0",
|
||||
"tslog": "3.3.1"
|
||||
},
|
||||
@@ -41,7 +48,10 @@
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@types/cookie": "0.4.1",
|
||||
"@types/cross-spawn": "6.0.2",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/detect-port": "1.3.2",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/hasbin": "1.2.0",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/npm-which": "3.0.1",
|
||||
@@ -50,18 +60,17 @@
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"express": "4.17.3",
|
||||
"http": "0.0.1-security",
|
||||
"node-fetch": "3.2.3",
|
||||
"p-event": "5.0.1",
|
||||
"pkg-dir": "6.0.1",
|
||||
"react": "18.0.0",
|
||||
"resolve-cwd": "3.0.0",
|
||||
"test-listen": "1.1.0",
|
||||
"typescript": "^4.5.3",
|
||||
"unbuild": "0.6.9",
|
||||
"watch": "1.0.2",
|
||||
"zod": "3.10.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
9
packages/blitz/src/cli/commands/codegen.ts
Normal file
9
packages/blitz/src/cli/commands/codegen.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {CliCommand} from "../index"
|
||||
/* @ts-ignore */
|
||||
import {generateManifest} from "../utils/routes-manifest"
|
||||
|
||||
const codegen: CliCommand = async () => {
|
||||
await generateManifest()
|
||||
}
|
||||
|
||||
export {codegen}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user