1
0
mirror of synced 2026-02-07 03:00:10 -05:00

Compare commits

...

13 Commits

Author SHA1 Message Date
github-actions[bot]
107254ece9 Version Packages (#4425)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2025-02-24 13:38:29 +00:00
Siddharth Suresh
816330b9d0 fix: Overriden custom cookies used inside withBlitzAuth (#4424)
* fix: use append rather than set

* Create ninety-stingrays-double.md
2025-02-24 18:47:50 +05:30
github-actions[bot]
18decd1558 Version Packages (#4411)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2025-02-21 13:09:32 +05:30
Siddharth Suresh
1610c73f99 breaking change: remove recipes (#4422)
* remove recipies

* missing change

* fixes

* Create wet-drinks-wait.md

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-02-14 15:10:42 +00:00
Siddharth Suresh
0a257e9150 fix: Pages router template (#4423)
* fixes

* fix

* Create tall-waves-marry.md
2025-02-14 15:02:08 +00:00
Blitz.js Bot
2661bcd98d (meta) added @Daidalos117 as contributor 2025-02-07 06:51:33 -06:00
Roman Rajchert
11c9f00eb9 Fix/4407 set query data use inifinite query (#4413)
* fix(4407): setQueryData with useInifiniteQuery

* chore(4407): merge develop

* fix(4407): typing

* chore(4407): add changeset

* chore(4407): remove prefetch on infninite mutate page

* chore(4407): recreate changeset

* fix(4407): mutating data in infinite

* fix(4407): remove web from changeset

---------

Co-authored-by: Roman Rajchert <roman.rajchert@vodafone.com>
2025-02-07 18:21:28 +05:30
Siddharth Suresh
ce1a603b26 feat: upgrade tanstack query to v5 (#4360)
* upgrade syntax to v5

* blitz-next changes

* fix types and export new functions

* fix blitz suspense logic

* fix workflow

* changeset

* fix pnpm version

* fix pnpm lock file

* latest pnpm version

* fix

* fix pnpm lock file

* fixes

* commit

* commit

* pnpm lock update

* revert change

* fixes

* temp: remove patch

* lockfile

* chore: fix build and lint

* fix: auth-with-rpc tests

* qm test

* remaining tests

* fix rpc unit tests

* revrt

* another revert pnpm version change

* revert back to pnpm 8.6.6

* pnpm lock fix

* try fix

* fix
2025-01-22 17:27:22 +00:00
github-actions[bot]
125370a1d0 Version Packages (#4410)
* Version Packages

* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2025-01-22 22:47:01 +05:30
Blitz.js Bot
39c8c0ab80 (meta) added @kksandr7 as contributor 2025-01-22 11:11:23 -06:00
Ksandr
fbf5e51a78 chore: bump deps (#4409)
* chore: bump deps

* Create spotty-games-peel.md

* Update spotty-games-peel.md

---------

Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2025-01-22 22:41:14 +05:30
Blitz.js Bot
9cda1be11b (meta) added @rene-demonsters as contributor 2025-01-22 11:07:36 -06:00
René Vlugt
5b20ce6282 Fix minimal app template (#4405)
* Fix minimal app template, requires at least next 13.5

* Update to 13.5.8

* Add changeset

* update next to 15.0.1
2025-01-22 22:37:30 +05:30
284 changed files with 2344 additions and 9643 deletions

View File

@@ -4114,6 +4114,36 @@
"doc", "doc",
"code" "code"
] ]
},
{
"login": "rene-demonsters",
"name": "René Vlugt",
"avatar_url": "https://avatars.githubusercontent.com/u/20322259?v=4",
"profile": "https://github.com/rene-demonsters",
"contributions": [
"doc",
"code"
]
},
{
"login": "kksandr7",
"name": "Ksandr",
"avatar_url": "https://avatars.githubusercontent.com/u/132560756?v=4",
"profile": "https://www.drupal.org/u/kksandr",
"contributions": [
"doc",
"code"
]
},
{
"login": "Daidalos117",
"name": "Roman Rajchert",
"avatar_url": "https://avatars.githubusercontent.com/u/15905269?v=4",
"profile": "https://github.com/Daidalos117",
"contributions": [
"doc",
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -7,5 +7,5 @@
"access": "restricted", "access": "restricted",
"baseBranch": "main", "baseBranch": "main",
"updateInternalDependencies": "patch", "updateInternalDependencies": "patch",
"ignore": ["web", "test-*", "toolkit-*", "@blitzjs/recipe-*"] "ignore": ["web", "test-*", "toolkit-*", "next-blitz-auth"]
} }

View File

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

View File

@@ -39,7 +39,7 @@ jobs:
- name: Setup PNPM - name: Setup PNPM
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2 uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with: with:
version: 8.9.0 version: 8.6.6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4

View File

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

View File

@@ -6,7 +6,7 @@
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg=="> <img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
</a> </a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-433-17BB8A.svg?style=for-the-badge&labelColor=000000"></a> <a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-436-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE"> <a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue"> <img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
@@ -762,6 +762,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="cherniavskii.com"><img src="https://avatars.githubusercontent.com/u/13808724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Cherniavskii</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cherniavskii" title="Documentation">📖</a></td> <td align="center"><a href="cherniavskii.com"><img src="https://avatars.githubusercontent.com/u/13808724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Cherniavskii</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cherniavskii" title="Documentation">📖</a></td>
<td align="center"><a href="https://danielidoko-r3zt.vercel.app/"><img src="https://avatars.githubusercontent.com/u/95912955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Idoko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Tests">⚠️</a></td> <td align="center"><a href="https://danielidoko-r3zt.vercel.app/"><img src="https://avatars.githubusercontent.com/u/95912955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Idoko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Tests">⚠️</a></td>
<td align="center"><a href="https://garyfung.medium.com"><img src="https://avatars.githubusercontent.com/u/3803466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gary Fung</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Code">💻</a></td> <td align="center"><a href="https://garyfung.medium.com"><img src="https://avatars.githubusercontent.com/u/3803466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gary Fung</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/rene-demonsters"><img src="https://avatars.githubusercontent.com/u/20322259?v=4?s=100" width="100px;" alt=""/><br /><sub><b>René Vlugt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rene-demonsters" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rene-demonsters" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://www.drupal.org/u/kksandr"><img src="https://avatars.githubusercontent.com/u/132560756?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ksandr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Daidalos117"><img src="https://avatars.githubusercontent.com/u/15905269?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roman Rajchert</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Daidalos117" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Daidalos117" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

View File

@@ -1,5 +1,15 @@
# next-blitz-auth # next-blitz-auth
## 0.1.18
### Patch Changes
- blitz@2.2.1
- @blitzjs/auth@2.2.1
- @blitzjs/next@2.2.1
- @blitzjs/rpc@2.2.1
- @blitzjs/config@2.2.1
## 0.1.17 ## 0.1.17
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "next-blitz-auth", "name": "next-blitz-auth",
"version": "0.1.17", "version": "0.1.18",
"private": true, "private": true,
"scripts": { "scripts": {
"blitz:dev": "next dev", "blitz:dev": "next dev",
@@ -12,15 +12,15 @@
"schema": "prisma/schema.prisma" "schema": "prisma/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "^4.5.0", "@prisma/client": "^4.5.0",
"@tanstack/react-query": "4.0.10", "@tanstack/react-query": "5.51.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"flatted": "3.2.7", "flatted": "3.2.7",
"next": "15.0.1", "next": "15.0.1",
"prisma": "^4.5.0", "prisma": "^4.5.0",

View File

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

View File

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

View File

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

View File

@@ -23,14 +23,14 @@
] ]
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"openid-client": "5.2.1", "openid-client": "5.2.1",
"prisma": "6.1.0", "prisma": "6.1.0",

View File

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

View File

@@ -24,14 +24,14 @@
] ]
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"next-auth": "4.24.7", "next-auth": "4.24.7",
"prisma": "6.1.0", "prisma": "6.1.0",

View File

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

View File

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

View File

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

View File

@@ -16,14 +16,14 @@
"schema": "./db/schema.prisma" "schema": "./db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"@types/jest": "29.2.2", "@types/jest": "29.2.2",
"@types/passport-twitter": "1.0.37", "@types/passport-twitter": "1.0.37",
"blitz": "2.2.0", "blitz": "3.0.1",
"jest": "29.3.0", "jest": "29.3.0",
"jest-environment-jsdom": "29.3.0", "jest-environment-jsdom": "29.3.0",
"next": "15.0.1", "next": "15.0.1",

View File

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

View File

@@ -0,0 +1,90 @@
import {useSuspenseInfiniteQuery} from "@blitzjs/rpc"
import getInfiniteUsers from "src/queries/getInfiniteUsers"
import {useActionState} from "react"
function PageWithInfiniteQueryMutate(props) {
const [usersPages, extraInfo] = useSuspenseInfiniteQuery(
getInfiniteUsers,
(page = {take: 3, skip: 0}) => page,
{
getNextPageParam: (lastPage) => lastPage.nextPage,
initialPageParam: {take: 3, skip: 0},
},
)
const {isFetchingNextPage, fetchNextPage, hasNextPage, setQueryData} = extraInfo
const onOnContactSave = async (previousState, formData: FormData) => {
const name = formData.get("name") as string | null
await setQueryData(
(oldData) => {
if (!oldData) {
return {
pages: [],
pageParams: [],
}
}
return {
...oldData,
pages: oldData.pages.map((page, index) => {
if (index === 0) {
return {
...page,
users: [
{
id: Math.random(),
name,
role: "user",
email: `${name}@yopmail.com`,
createdAt: new Date(),
updatedAt: new Date(),
hashedPassword: "alsdklaskdoaskdokdo",
},
...page.users,
],
}
}
return page
}),
}
},
{refetch: false},
)
}
const [, formAction] = useActionState(onOnContactSave, {name: ""})
return (
<div>
<form action={formAction}>
<input type="text" name="name" placeholder="User name" />
<button type="submit">Add user</button>
</form>
{usersPages.map((usersPage) => (
<>
{usersPage?.users.map((u) => (
<div key={u.name}>
<p>name: {u.name}</p>
<p>role: {u.role}</p>
<p>email: {u.email}</p>
<hr />
</div>
))}
{usersPage.hasMore && (
<button onClick={() => fetchNextPage()} disabled={!hasNextPage || !!isFetchingNextPage}>
{isFetchingNextPage
? "Loading more..."
: hasNextPage
? "Load More"
: "Nothing more to load"}
</button>
)}
</>
))}
</div>
)
}
export default PageWithInfiniteQueryMutate

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,14 +17,14 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"delay": "5.0.0", "delay": "5.0.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -49,7 +49,7 @@
"husky": "8.0.2", "husky": "8.0.2",
"jsdom": "20.0.3", "jsdom": "20.0.3",
"lint-staged": "13.0.3", "lint-staged": "13.0.3",
"playwright": "1.28.0", "playwright": "1.49.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-prisma": "4.4.0", "prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3", "pretty-quick": "3.1.3",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,11 +17,11 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -42,7 +42,7 @@
"fs-extra": "10.0.1", "fs-extra": "10.0.1",
"get-port": "6.1.2", "get-port": "6.1.2",
"node-fetch": "3.2.3", "node-fetch": "3.2.3",
"playwright": "1.28.0", "playwright": "1.49.1",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"typescript": "^4.8.4" "typescript": "^4.8.4"
} }

View File

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

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "2.1.0", "lowdb": "2.1.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",

View File

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

View File

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

View File

@@ -11,10 +11,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0" "react-dom": "19.0.0"

View File

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

View File

@@ -17,12 +17,12 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "2.1.0", "lowdb": "2.1.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -43,7 +43,7 @@
"fs-extra": "10.0.1", "fs-extra": "10.0.1",
"get-port": "6.1.2", "get-port": "6.1.2",
"node-fetch": "3.2.3", "node-fetch": "3.2.3",
"playwright": "1.28.0", "playwright": "1.49.1",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} }

View File

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

View File

@@ -16,11 +16,11 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",

View File

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

View File

@@ -8,13 +8,13 @@
"clean": "rm -rf .turbo && rm -rf node_modules" "clean": "rm -rf .turbo && rm -rf node_modules"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"@tanstack/react-query": "4.0.10", "@tanstack/react-query": "5.51.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
"react": "19.0.0", "react": "19.0.0",

View File

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

View File

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

View File

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

View File

@@ -16,10 +16,10 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -27,7 +27,7 @@
"react-dom": "19.0.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,10 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0" "react-dom": "19.0.0"

View File

@@ -7,10 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0" "react-dom": "19.0.0"

View File

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

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/next": "2.2.0", "@blitzjs/next": "3.0.1",
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@prisma/client": "6.1.0", "@prisma/client": "6.1.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "15.0.1", "next": "15.0.1",
"prisma": "6.1.0", "prisma": "6.1.0",
@@ -28,7 +28,7 @@
"react-dom": "19.0.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",

View File

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

View File

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

View File

@@ -3,9 +3,9 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@blitzjs/config": "workspace:2.2.0", "@blitzjs/config": "workspace:3.0.1",
"@blitzjs/next": "workspace:2.2.0", "@blitzjs/next": "workspace:3.0.1",
"@blitzjs/rpc": "workspace:2.2.0", "@blitzjs/rpc": "workspace:3.0.1",
"@tanstack/react-query": "4.13.0", "@tanstack/react-query": "4.13.0",
"@testing-library/react": "16.0.1", "@testing-library/react": "16.0.1",
"@types/express": "4.17.13", "@types/express": "4.17.13",
@@ -22,7 +22,7 @@
"get-port": "6.1.2", "get-port": "6.1.2",
"node-fetch": "3.2.3", "node-fetch": "3.2.3",
"pkg-dir": "5.0.0", "pkg-dir": "5.0.0",
"playwright-chromium": "1.28.0", "playwright-chromium": "1.49.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"resolve-cwd": "3.0.0", "resolve-cwd": "3.0.0",

View File

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

View File

@@ -1,5 +1,30 @@
# @blitzjs/auth # @blitzjs/auth
## 3.0.1
### Patch Changes
- 816330b9d: fix: Overriden custom cookies used inside `withBlitzAuth`
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- blitz@3.0.0
## 2.2.1
### Patch Changes
- blitz@2.2.1
## 2.2.0 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/auth", "name": "@blitzjs/auth",
"version": "2.2.0", "version": "3.0.1",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -50,7 +50,7 @@
"url": "0.11.0" "url": "0.11.0"
}, },
"peerDependencies": { "peerDependencies": {
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "*", "next": "*",
"next-auth": "*", "next-auth": "*",
"secure-password": "4.0.0" "secure-password": "4.0.0"
@@ -67,14 +67,14 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@testing-library/react": "16.0.1", "@testing-library/react": "16.0.1",
"@types/cookie": "0.4.1", "@types/cookie": "0.4.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/jsonwebtoken": "8.5.8", "@types/jsonwebtoken": "8.5.8",
"@types/react": "npm:types-react@19.0.0", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0", "@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"next-auth": "4.24.7", "next-auth": "4.24.7",
"react": "19.0.0", "react": "19.0.0",

View File

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

View File

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

View File

@@ -466,7 +466,7 @@ export class SessionContextClass implements SessionContext {
} }
const cookieHeaders = this._headers.get("set-cookie") const cookieHeaders = this._headers.get("set-cookie")
if (response instanceof Response) { if (response instanceof Response) {
response.headers.set("Set-Cookie", cookieHeaders!) response.headers.append("Set-Cookie", cookieHeaders!)
} else { } else {
response.setHeader("Set-Cookie", splitCookiesString(cookieHeaders!)) response.setHeader("Set-Cookie", splitCookiesString(cookieHeaders!))
} }

View File

@@ -1,5 +1,33 @@
# @blitzjs/next # @blitzjs/next
## 3.0.1
### Patch Changes
- @blitzjs/rpc@3.0.1
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- Updated dependencies [11c9f00eb]
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- @blitzjs/rpc@3.0.0
- blitz@3.0.0
## 2.2.1
### Patch Changes
- blitz@2.2.1
- @blitzjs/rpc@2.2.1
## 2.2.0 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/next", "name": "@blitzjs/next",
"version": "2.2.0", "version": "3.0.1",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,7 +29,7 @@
"eslint.js" "eslint.js"
], ],
"dependencies": { "dependencies": {
"@blitzjs/rpc": "2.2.0", "@blitzjs/rpc": "3.0.1",
"@types/hoist-non-react-statics": "3.3.1", "@types/hoist-non-react-statics": "3.3.1",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"debug": "4.3.3", "debug": "4.3.3",
@@ -39,13 +39,13 @@
"supports-color": "8.1.1" "supports-color": "8.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "*", "next": "*",
"react": "*", "react": "*",
"tslog": "4.9.0" "tslog": "4.9.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@testing-library/dom": "8.13.0", "@testing-library/dom": "8.13.0",
"@testing-library/jest-dom": "5.16.3", "@testing-library/jest-dom": "5.16.3",
"@testing-library/react": "16.0.1", "@testing-library/react": "16.0.1",
@@ -55,7 +55,7 @@
"@types/react": "npm:types-react@19.0.0", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0", "@types/react-dom": "npm:types-react-dom@19.0.0",
"@types/testing-library__react-hooks": "4.0.0", "@types/testing-library__react-hooks": "4.0.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"find-up": "4.1.0", "find-up": "4.1.0",
"next": "15.0.1", "next": "15.0.1",

View File

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

View File

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

View File

@@ -1,5 +1,30 @@
# @blitzjs/rpc # @blitzjs/rpc
## 3.0.1
### Patch Changes
- blitz@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- 11c9f00eb: fix(4407): setQueryData with useInfiniteQuery
- Updated dependencies [ce1a603b2]
- Updated dependencies [1610c73f9]
- blitz@3.0.0
## 2.2.1
### Patch Changes
- blitz@2.2.1
## 2.2.0 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/rpc", "name": "@blitzjs/rpc",
"version": "2.2.0", "version": "3.0.1",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -27,7 +27,7 @@
], ],
"dependencies": { "dependencies": {
"@swc/core": "1.3.7", "@swc/core": "1.3.7",
"@tanstack/react-query": "4.24.4", "@tanstack/react-query": "5.51.1",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"bad-behavior": "1.0.1", "bad-behavior": "1.0.1",
"chalk": "^4.1.0", "chalk": "^4.1.0",
@@ -36,19 +36,19 @@
"supports-color": "8.1.1" "supports-color": "8.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"@tanstack/query-core": "4.24.4", "@tanstack/query-core": "5.51.1",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "*", "next": "*",
"react": "*" "react": "*"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/auth": "2.2.0", "@blitzjs/auth": "3.0.1",
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@tanstack/query-core": "4.24.4", "@tanstack/query-core": "5.51.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/react": "npm:types-react@19.0.0", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "npm:types-react-dom@19.0.0", "@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.2.0", "blitz": "3.0.1",
"next": "15.0.1", "next": "15.0.1",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,16 +31,10 @@ type MutateOptions = {
} }
export const initializeQueryClient = () => { 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({ return new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
...(isServer && {cacheTime: 0}), ...(isServer && {cacheTime: 0}),
suspense: suspenseEnabled,
retry: (failureCount, error: any) => { retry: (failureCount, error: any) => {
if (process.env.NODE_ENV !== "production") return false if (process.env.NODE_ENV !== "production") return false
@@ -55,7 +49,7 @@ export const initializeQueryClient = () => {
} }
// Query client is initialised in `BlitzRpcPlugin`, and can only be used with BlitzRpcPlugin right now // Query client is initialised in `BlitzRpcPlugin`, and can only be used with BlitzRpcPlugin right now
export const getQueryClient = () => globalThis.queryClient export const getQueryClient = () => globalThis.queryClient as QueryClient
function isRpcClient(f: any): f is RpcClient<any, any> { function isRpcClient(f: any): f is RpcClient<any, any> {
return !!f._isRpcClient return !!f._isRpcClient
@@ -71,9 +65,10 @@ export interface QueryCacheFunctions<T> {
export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>( export const getQueryCacheFunctions = <TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>, resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput, params: TInput,
queryType: QueryType = QueryType.STANDARD,
): QueryCacheFunctions<TResult> => ({ ): QueryCacheFunctions<TResult> => ({
setQueryData: (newData, opts = {refetch: true}) => { setQueryData: (newData, opts = {refetch: true}) => {
return setQueryData(resolver, params, newData, opts) return setQueryData(resolver, params, newData, opts, queryType)
}, },
}) })
@@ -171,19 +166,27 @@ interface InvalidateQuery {
export const invalidateQuery: InvalidateQuery = (resolver = undefined, ...params: []) => { export const invalidateQuery: InvalidateQuery = (resolver = undefined, ...params: []) => {
const fullQueryKey = const fullQueryKey =
typeof resolver === "undefined" ? undefined : getQueryKey(resolver, ...params) typeof resolver === "undefined" ? undefined : getQueryKey(resolver, ...params)
return getQueryClient().invalidateQueries(fullQueryKey) return getQueryClient().invalidateQueries({
queryKey: fullQueryKey,
})
} }
export enum QueryType {
STANDARD = "STANDARD",
INFINITE = "INFINITE",
}
export function setQueryData<TInput, TResult, T extends AsyncFunc>( export function setQueryData<TInput, TResult, T extends AsyncFunc>(
resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>, resolver: T | Resolver<TInput, TResult> | RpcClient<TInput, TResult>,
params: TInput, params: TInput,
newData: TResult | ((oldData: TResult | undefined) => TResult | undefined), newData: TResult | ((oldData: TResult | undefined) => TResult | undefined),
opts: MutateOptions = {refetch: true}, opts: MutateOptions = {refetch: true},
queryType: QueryType = QueryType.STANDARD,
): Promise<void | ReturnType<ReturnType<typeof getQueryClient>["invalidateQueries"]>> { ): Promise<void | ReturnType<ReturnType<typeof getQueryClient>["invalidateQueries"]>> {
if (typeof resolver === "undefined") { if (typeof resolver === "undefined") {
throw new Error("setQueryData is missing the first argument - it must be a resolver function") throw new Error("setQueryData is missing the first argument - it must be a resolver function")
} }
const queryKey = getQueryKey(resolver, params) const getQueryKeyFn = queryType === QueryType.STANDARD ? getQueryKey : getInfiniteQueryKey
const queryKey = getQueryKeyFn(resolver, params)
return new Promise((res) => { return new Promise((res) => {
getQueryClient().setQueryData(queryKey, newData) getQueryClient().setQueryData(queryKey, newData)

View File

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

View File

@@ -1,5 +1,32 @@
# blitz # blitz
## 3.0.1
### Patch Changes
- @blitzjs/generator@3.0.1
## 3.0.0
### Major Changes
- ce1a603b2: TODO: Upgrade @tanstack/react-query to v5.1.1
### Patch Changes
- 1610c73f9: Due to increased maintenance required to keep recipes up to date. Removing them from the codebase from v3
- Updated dependencies [ce1a603b2]
- Updated dependencies [0a257e915]
- @blitzjs/generator@3.0.0
## 2.2.1
### Patch Changes
- Updated dependencies [5b20ce628]
- Updated dependencies [fbf5e51a7]
- @blitzjs/generator@2.2.1
## 2.2.0 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,7 +1,7 @@
import {BuildConfig} from "unbuild" import {BuildConfig} from "unbuild"
const config: BuildConfig = { const config: BuildConfig = {
entries: ["./src/index-browser", "./src/index-server", "./src/cli/index", "./src/installer"], entries: ["./src/index-browser", "./src/index-server", "./src/cli/index"],
externals: ["index-browser.cjs", "index-browser.mjs", "index.cjs", "zod", "react"], externals: ["index-browser.cjs", "index-browser.mjs", "index.cjs", "zod", "react"],
declaration: true, declaration: true,
rollup: { rollup: {

View File

@@ -1 +0,0 @@
export * from "./dist/installer"

View File

@@ -1 +0,0 @@
module.exports = require("./dist/installer.cjs")

View File

@@ -1,6 +1,6 @@
{ {
"name": "blitz", "name": "blitz",
"version": "2.2.0", "version": "3.0.1",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -22,7 +22,6 @@
"sideEffects": false, "sideEffects": false,
"license": "MIT", "license": "MIT",
"files": [ "files": [
"installer.*",
"dist/**", "dist/**",
"bin/**" "bin/**"
], ],
@@ -30,7 +29,7 @@
"blitz": "bin/blitz" "blitz": "bin/blitz"
}, },
"dependencies": { "dependencies": {
"@blitzjs/generator": "2.2.0", "@blitzjs/generator": "3.0.1",
"@mrleebo/prisma-ast": "0.4.1", "@mrleebo/prisma-ast": "0.4.1",
"@types/global-agent": "2.1.1", "@types/global-agent": "2.1.1",
"arg": "5.0.1", "arg": "5.0.1",
@@ -80,7 +79,7 @@
"watchpack": "2.1.1" "watchpack": "2.1.1"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.2.0", "@blitzjs/config": "3.0.1",
"@types/cookie": "0.4.1", "@types/cookie": "0.4.1",
"@types/cross-spawn": "6.0.2", "@types/cross-spawn": "6.0.2",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",

View File

@@ -1,325 +0,0 @@
import arg from "arg"
import {CliCommand} from "../index"
import prompts from "prompts"
import {bootstrap} from "global-agent"
import {baseLogger, log} from "../../logging"
import Debug from "debug"
const debug = Debug("blitz:cli")
import {join, resolve, dirname} from "path"
import {Stream} from "stream"
import {promisify} from "util"
import {RecipeCLIFlags, RecipeExecutor} from "../../installer"
import {setupTsnode} from "../utils/setup-ts-node"
import {isInternalBlitzMonorepoDevelopment} from "../utils/helpers"
import findUp from "find-up"
import resolveFrom from "resolve-from"
import {findNodeModulesRoot} from "../utils/find-node-modules"
interface GlobalAgent {
HTTP_PROXY?: string
HTTPS_PROXY?: string
NO_PROXY?: string
}
declare global {
var GLOBAL_AGENT: GlobalAgent
}
const args = arg(
{
// Types
"--help": Boolean,
"--env": String,
"--yes": Boolean,
// Aliases
"-e": "--env",
"-y": "--yes",
},
{
permissive: true,
},
)
const pipeline = promisify(Stream.pipeline)
const got = async (url: string) => {
return require("got")(url).catch((e: any) => {
if (e.response.statusCode === 403) {
baseLogger().error(e.response.body)
} else {
return e
}
})
}
const gotJSON = async (url: string) => {
debug("[gotJSON] Downloading json from ", url)
const res = await got(url)
return JSON.parse(res.body)
}
const isUrlValid = async (url: string) => {
return (await got(url).catch((e) => e)).statusCode === 200
}
const requireJSON = (file: string) => {
return JSON.parse(require("fs-extra").readFileSync(file).toString("utf-8"))
}
const checkLockFileExists = async (filename: string) => {
const dotBlitz = join(await findNodeModulesRoot(process.cwd()), ".blitz")
return require("fs-extra").existsSync(resolve(join(dotBlitz, "..", "..", filename)))
}
const GH_ROOT = "https://github.com/"
const API_ROOT = "https://api.github.com/repos/"
const RAW_ROOT = "https://raw.githubusercontent.com/"
const CODE_ROOT = "https://codeload.github.com/"
export enum RecipeLocation {
Local,
Remote,
}
interface RecipeMeta {
path: string
subdirectory?: string
location: RecipeLocation
}
interface Tree {
path: string
mode: string
type: string
sha: string
size: number
url: string
}
interface GithubRepoAPITrees {
sha: string
url: string
tree: Tree[]
truncated: boolean
}
const getOfficialRecipeList = async (): Promise<string[]> => {
return await gotJSON(`${API_ROOT}blitz-js/blitz/git/trees/main?recursive=1`).then(
(release: GithubRepoAPITrees) =>
release.tree.reduce((recipesList: string[], item) => {
const filePath = item.path.split("/")
const [directory, recipeName] = filePath
if (
directory === "recipes" &&
filePath.length === 2 &&
item.type === "tree" &&
recipeName
) {
recipesList.push(recipeName)
}
return recipesList
}, []),
)
}
const normalizeRecipePath = (recipeArg: string): RecipeMeta => {
const isNativeRecipe = /^([\w\-_]*)$/.test(recipeArg)
const isUrlRecipe = recipeArg.startsWith(GH_ROOT)
const isGitHubShorthandRecipe = /^([\w-_]*)\/([\w-_]*)$/.test(recipeArg)
if (isNativeRecipe || isUrlRecipe || isGitHubShorthandRecipe) {
let repoUrl
let subdirectory
switch (true) {
case isUrlRecipe:
repoUrl = recipeArg
break
case isNativeRecipe:
repoUrl = `${GH_ROOT}blitz-js/blitz`
subdirectory = `recipes/${recipeArg}`
break
case isGitHubShorthandRecipe:
repoUrl = `${GH_ROOT}${recipeArg}`
break
default:
throw new Error(
"should be impossible, the 3 cases are the only way to get into this switch",
)
}
return {
path: repoUrl,
subdirectory,
location: RecipeLocation.Remote,
}
} else {
return {
path: recipeArg,
location: RecipeLocation.Local,
}
}
}
const cloneRepo = async (
repoFullName: string,
defaultBranch: string,
subdirectory?: string,
): Promise<string> => {
debug("[cloneRepo] starting...")
const dotBlitz = join(await findNodeModulesRoot(process.cwd()), ".blitz")
const recipeDir = join(dotBlitz, "..", "..", "recipe-install")
// clean up from previous run in case of error
require("rimraf").sync(recipeDir)
require("fs-extra").mkdirsSync(recipeDir)
process.chdir(recipeDir)
debug("Extracting recipe to ", recipeDir)
const repoName = repoFullName.split("/")[1]
// `tar` top-level filter is `${repoName}-${defaultBranch}`, and then we want to get our recipe path
// within that folder
const extractPath = subdirectory ? [`${repoName}-${defaultBranch}/${subdirectory}`] : undefined
const depth = subdirectory ? subdirectory.split("/").length + 1 : 1
await pipeline(
require("got").stream(`${CODE_ROOT}${repoFullName}/tar.gz/${defaultBranch}`),
require("tar").extract({strip: depth}, extractPath),
)
return recipeDir
}
const installRecipeAtPath = async (
recipePath: string,
...runArgs: Parameters<RecipeExecutor<any>["run"]>
) => {
const recipe = require(recipePath).default as RecipeExecutor<any>
await recipe.run(...runArgs)
}
const setupProxySupport = async () => {
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY
const noProxy = process.env.no_proxy || process.env.NO_PROXY
if (httpProxy || httpsProxy) {
globalThis.GLOBAL_AGENT = {
HTTP_PROXY: httpProxy,
HTTPS_PROXY: httpsProxy,
NO_PROXY: noProxy,
}
bootstrap()
}
}
const install: CliCommand = async () => {
setupTsnode()
let selectedRecipe: string | null = args._[1] ? `${args._[1]}` : null
await setupProxySupport()
if (!selectedRecipe) {
const officialRecipeList = await getOfficialRecipeList()
const res = await prompts({
type: "select",
name: "recipeName",
message: "Select a recipe to install",
choices: officialRecipeList.map((r) => {
return {title: r, value: r}
}),
})
selectedRecipe = res.recipeName
}
if (selectedRecipe) {
const recipeInfo = normalizeRecipePath(selectedRecipe)
// Take all the args after the recipe string
//
// ['material-ui', '--yes', 'prop=true']
// --> ['material-ui', 'prop=true']
// --> ['prop=true']
// --> { prop: 'true' }
const cliArgs = args._.filter((arg) => !arg.startsWith("--"))
.slice(2)
.reduce(
(acc, arg) => ({
...acc,
[`${arg.split("=")[0]}`]: arg.split("=")[1] ? JSON.parse(`"${arg.split("=")[1]}"`) : true, // if no value is provided, assume it's a boolean flag
}),
{},
)
const cliFlags: RecipeCLIFlags = {
yesToAll: args["--yes"] || false,
}
const chalk = (await import("chalk")).default
if (recipeInfo.location === RecipeLocation.Remote) {
const apiUrl = recipeInfo.path.replace(GH_ROOT, API_ROOT)
const rawUrl = recipeInfo.path.replace(GH_ROOT, RAW_ROOT)
const repoInfo = await gotJSON(apiUrl)
const packageJsonPath = join(
`${rawUrl}`,
repoInfo.default_branch,
recipeInfo.subdirectory ?? "",
"package.json",
)
if (!(await isUrlValid(packageJsonPath))) {
debug("Url is invalid for ", packageJsonPath)
baseLogger().error(`Could not find recipe "${args._[1]}"\n`)
console.log(`${chalk.bold("Please provide one of the following:")}
1. The name of a recipe to install (e.g. "tailwind")
${chalk.dim("- Available recipes listed at https://github.com/blitz-js/blitz/tree/main/recipes")}
2. The full name of a GitHub repository (e.g. "blitz-js/example-recipe"),
3. A full URL to a Github repository (e.g. "https://github.com/blitz-js/example-recipe"), or
4. A file path to a locally-written recipe.\n`)
process.exit(1)
} else {
let spinner = log.spinner(`Cloning GitHub repository for ${selectedRecipe} recipe`).start()
const recipeRepoPath = await cloneRepo(
repoInfo.full_name,
repoInfo.default_branch,
recipeInfo.subdirectory,
)
spinner.stop()
spinner = log.spinner("Installing package.json dependencies").start()
let pkgManager = "npm"
let installArgs = ["install", "--legacy-peer-deps", "--ignore-scripts"]
if (await checkLockFileExists("yarn.lock")) {
pkgManager = "yarn"
installArgs = ["install", "--ignore-scripts"]
} else if (await checkLockFileExists("pnpm-lock.yaml")) {
pkgManager = "pnpm"
installArgs = ["install", "--ignore-scripts"]
}
await new Promise((resolve) => {
const installProcess = require("cross-spawn")(pkgManager, installArgs)
installProcess.on("exit", resolve)
})
spinner.stop()
const recipePackageMain = requireJSON("./package.json").main
const recipeEntry = resolve(recipePackageMain)
process.chdir(join(process.cwd(), ".."))
await installRecipeAtPath(recipeEntry, cliArgs, cliFlags)
require("rimraf").sync(recipeRepoPath)
}
} else {
try {
await installRecipeAtPath(resolve(`${args._[1]}`), cliArgs, cliFlags)
} catch (err) {
if (err instanceof Error) {
throw new Error(err.message)
}
console.log(err)
}
}
}
}
export {install}

View File

@@ -35,7 +35,6 @@ const commands = {
generate: () => import("./commands/generate").then((i) => i.generate), generate: () => import("./commands/generate").then((i) => i.generate),
codegen: () => import("./commands/codegen").then((i) => i.codegen), codegen: () => import("./commands/codegen").then((i) => i.codegen),
db: () => import("./commands/db").then((i) => i.db), db: () => import("./commands/db").then((i) => i.db),
install: () => import("./commands/install").then((i) => i.install),
console: () => import("./commands/console").then((i) => i.consoleREPL), console: () => import("./commands/console").then((i) => i.consoleREPL),
routes: () => import("./commands/routes").then((i) => i.routes), routes: () => import("./commands/routes").then((i) => i.routes),
} }
@@ -47,7 +46,6 @@ const aliases: Record<string, keyof typeof commands> = {
e: "export", e: "export",
n: "new", n: "new",
g: "generate", g: "generate",
i: "install",
c: "console", c: "console",
r: "routes", r: "routes",
} }

View File

@@ -1 +0,0 @@
export * from "../src/installer/index"

View File

@@ -1,12 +0,0 @@
import {Text} from "ink"
import * as React from "react"
import {Newline} from "./newline"
export const EnterToContinue: React.FC<{message?: string}> = ({
message = "Press ENTER to continue",
}) => (
<>
<Newline />
<Text bold>{message}</Text>
</>
)

View File

@@ -1,6 +0,0 @@
import {Box} from "ink"
import * as React from "react"
export const Newline: React.FC<{count?: number}> = ({count = 1}) => {
return <Box paddingBottom={count} />
}

View File

@@ -1,198 +0,0 @@
import {spawn} from "cross-spawn"
import * as fs from "fs-extra"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as path from "path"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs} from "../types"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
interface NpmPackage {
name: string
// defaults to latest published
version?: string
// defaults to false
isDevDep?: boolean
}
export interface Config extends ExecutorConfig {
packages: executorArgument<NpmPackage[]>
}
export function isAddDependencyExecutor(executor: ExecutorConfig): executor is Config {
return (executor as Config).packages !== undefined
}
export const type = "add-dependency"
function Package({pkg, loading}: {pkg: NpmPackage; loading: boolean}) {
return (
<Text>
{` `}
{loading ? <Spinner /> : "📦"}
{` ${pkg.name}@${pkg.version}`}
</Text>
)
}
const DependencyList = ({
lede = "Hang tight! Installing dependencies...",
depsLoading = false,
devDepsLoading = false,
packages,
}: {
lede?: string
depsLoading?: boolean
devDepsLoading?: boolean
packages: NpmPackage[]
}) => {
const prodPackages = packages.filter((p) => !p.isDevDep)
const devPackages = packages.filter((p) => p.isDevDep)
return (
<Box flexDirection="column">
<Text>{lede}</Text>
<Newline />
{prodPackages.length ? <Text>Dependencies to be installed:</Text> : null}
{prodPackages.map((pkg) => (
<Package key={pkg.name} pkg={pkg} loading={depsLoading} />
))}
<Newline />
{devPackages.length ? <Text>Dev Dependencies to be installed:</Text> : null}
{devPackages.map((pkg) => (
<Package key={pkg.name} pkg={pkg} loading={devDepsLoading} />
))}
</Box>
)
}
/**
* Exported for unit testing purposes
*/
export function getPackageManager() {
if (fs.existsSync(path.resolve("yarn.lock"))) {
return "yarn"
} else if (fs.existsSync(path.resolve("pnpm-lock.yaml"))) {
return "pnpm"
} else {
return "npm"
}
}
/**
* Exported for unit testing purposes
*/
export async function installPackages(packages: NpmPackage[], isDev = false) {
const packageManager = getPackageManager()
const isNPM = packageManager === "npm"
const pkgInstallArg = isNPM ? "install" : "add"
const args: string[] = [pkgInstallArg]
if (isDev) {
args.push(isNPM ? "--save-dev" : "-D")
}
packages.forEach((pkg) => {
pkg.version ? args.push(`${pkg.name}@${pkg.version}`) : args.push(pkg.name)
})
await new Promise((resolve) => {
const cp = spawn(packageManager, args, {
stdio: ["inherit", "pipe", "pipe"],
})
cp.on("exit", resolve)
})
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, step, onChangeCommitted}) => {
const userInput = useUserInput(cliFlags)
const [depsInstalled, setDepsInstalled] = React.useState(false)
const [devDepsInstalled, setDevDepsInstalled] = React.useState(false)
const handleChangeCommitted = React.useCallback(() => {
const packages = (step as Config).packages
const dependencies = packages.length === 1 ? "dependency" : "dependencies"
onChangeCommitted(`Installed ${packages.length} ${dependencies}`)
}, [onChangeCommitted, step])
React.useEffect(() => {
async function installDeps() {
const packagesToInstall = getExecutorArgument((step as Config).packages, cliArgs).filter(
(p) => !p.isDevDep,
)
await installPackages(packagesToInstall)
setDepsInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
installDeps()
}, [cliArgs, step])
React.useEffect(() => {
if (!depsInstalled) return
async function installDevDeps() {
const packagesToInstall = getExecutorArgument((step as Config).packages, cliArgs).filter(
(p) => p.isDevDep,
)
await installPackages(packagesToInstall, true)
setDevDepsInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
installDevDeps()
}, [cliArgs, depsInstalled, step])
React.useEffect(() => {
if (depsInstalled && devDepsInstalled) {
handleChangeCommitted()
}
}, [depsInstalled, devDepsInstalled, handleChangeCommitted])
if (!isAddDependencyExecutor(step)) {
onChangeCommitted()
return null
}
const childProps: CommitChildProps = {
depsInstalled,
devDepsInstalled,
handleChangeCommitted,
step,
cliArgs,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
depsInstalled: boolean
devDepsInstalled: boolean
handleChangeCommitted: () => void
step: Config
cliArgs: RecipeCLIArgs
}
const CommitWithInput = ({
depsInstalled,
devDepsInstalled,
handleChangeCommitted,
step,
cliArgs,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, depsInstalled && devDepsInstalled)
return (
<DependencyList
depsLoading={!depsInstalled}
devDepsLoading={!devDepsInstalled}
packages={getExecutorArgument(step.packages, cliArgs)}
/>
)
}
const CommitWithoutInput = ({depsInstalled, devDepsInstalled, step, cliArgs}: CommitChildProps) => (
<DependencyList
depsLoading={!depsInstalled}
devDepsLoading={!devDepsInstalled}
packages={getExecutorArgument(step.packages, cliArgs)}
/>
)

View File

@@ -1,71 +0,0 @@
import {Box, Text} from "ink"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs, RecipeCLIFlags} from "../types"
export interface ExecutorConfig {
successIcon?: string
stepId: string | number
stepName: string
stepType: string
// a bit to display to the user to give context to the change
explanation: string
}
export interface IExecutor {
type: string
Propose?: React.FC<{
step: ExecutorConfig
onProposalAccepted: (data?: any) => void
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
}>
Commit: React.FC<{
step: ExecutorConfig
proposalData?: any
onChangeCommitted: (data?: any) => void
cliArgs: RecipeCLIArgs
cliFlags: RecipeCLIFlags
}>
}
type dynamicExecutorArgument<T> = (cliArgs: RecipeCLIArgs) => T
function isDynamicExecutorArgument<T>(
input: executorArgument<T>,
): input is dynamicExecutorArgument<T> {
return typeof (input as dynamicExecutorArgument<T>) === "function"
}
export type executorArgument<T> = T | dynamicExecutorArgument<T>
export function Frontmatter({executor}: {executor: ExecutorConfig}) {
const lineLength = executor.stepName.length + 6
const verticalBorder = `+${new Array(lineLength).fill("").join("")}+`
return (
<Box flexDirection="column" paddingBottom={1}>
<Newline />
<Box flexDirection="column">
<Text color="#8a3df0" bold>
{verticalBorder}
</Text>
<Text color="#8a3df0" bold>
&nbsp;&nbsp;&nbsp;{executor.stepName}&nbsp;&nbsp;&nbsp;
</Text>
<Text color="#8a3df0" bold>
{verticalBorder}
</Text>
</Box>
<Text color="gray" italic>
{executor.explanation}
</Text>
</Box>
)
}
export function getExecutorArgument<T>(input: executorArgument<T>, cliArgs: RecipeCLIArgs): T {
if (isDynamicExecutorArgument(input)) {
return input(cliArgs)
}
return input
}

View File

@@ -1,41 +0,0 @@
// import { prompt as enquirer } from 'enquirer'
import prompts from "prompts"
enum SearchType {
file,
directory,
}
interface FilePromptOptions {
globFilter?: string
getChoices?(context: any): string[]
searchType?: SearchType
context: any
}
async function getMatchingFiles(filter: string = ""): Promise<string[]> {
let {globby} = await import("globby")
return globby(filter, {expandDirectories: true})
}
export async function filePrompt(options: FilePromptOptions): Promise<string> {
const choices = options.getChoices
? options.getChoices(options.context)
: await getMatchingFiles(options.globFilter)
if (choices.length === 1) {
return `${choices[0]}`
}
const results: {file: string} = await prompts({
type: "autocomplete",
name: "file",
message: "Select the target file",
// @ts-ignore
limit: 10,
choices: choices.map((choice) => {
return {title: choice, value: choice}
}),
})
return results.file
}

View File

@@ -1,183 +0,0 @@
import {createPatch} from "diff"
import * as fs from "fs-extra"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {RecipeCLIArgs} from "../types"
import {
processFile,
stringProcessFile,
StringTransformer,
transform,
Transformer,
TransformStatus,
} from "../utils/transform"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
import {filePrompt} from "./file-prompt"
export interface Config extends ExecutorConfig {
selectTargetFiles?(cliArgs: RecipeCLIArgs): any[]
singleFileSearch?: executorArgument<string>
transform?: Transformer
transformPlain?: StringTransformer
}
export function isFileTransformExecutor(executor: ExecutorConfig): executor is Config {
return (
(executor as Config).transform !== undefined ||
(executor as Config).transformPlain !== undefined
)
}
export const type = "file-transform"
export const Propose: IExecutor["Propose"] = ({cliArgs, cliFlags, onProposalAccepted, step}) => {
const userInput = useUserInput(cliFlags)
const [diff, setDiff] = React.useState<string | null>(null)
const [error, setError] = React.useState<Error | null>(null)
const [filePath, setFilePath] = React.useState("")
const [proposalAccepted, setProposalAccepted] = React.useState(false)
const acceptProposal = React.useCallback(() => {
setProposalAccepted(true)
onProposalAccepted(filePath)
}, [onProposalAccepted, filePath])
React.useEffect(() => {
async function generateDiff() {
const fileToTransform: string = await filePrompt({
context: cliArgs,
globFilter: getExecutorArgument((step as Config).singleFileSearch, cliArgs),
getChoices: (step as Config).selectTargetFiles,
})
setFilePath(fileToTransform)
const originalFile = fs.readFileSync(fileToTransform).toString("utf-8")
const newFile = await ((step as Config).transformPlain
? stringProcessFile(originalFile, (step as Config).transformPlain!)
: processFile(originalFile, (step as Config).transform!))
return createPatch(fileToTransform, originalFile, newFile)
}
generateDiff().then(setDiff, setError)
}, [cliArgs, step])
// Let the renderer deal with errors from file transformers, otherwise the
// process would just hang.
if (error) throw error
if (!diff) {
return (
<Box>
<Text>
<Spinner />
Generating file diff...
</Text>
</Box>
)
}
const childProps: ProposeChildProps = {
diff,
filePath,
proposalAccepted,
acceptProposal,
}
if (userInput) return <ProposeWithInput {...childProps} />
else return <ProposeWithoutInput {...childProps} />
}
interface ProposeChildProps {
diff: string
filePath: string
proposalAccepted: boolean
acceptProposal: () => void
}
const Diff = ({diff}: {diff: string}) => (
<>
{diff
.split("\n")
.slice(2)
.map((line, idx) => {
let styleProps: any = {}
if (line.startsWith("-") && !line.startsWith("---")) {
styleProps.bold = true
styleProps.color = "red"
} else if (line.startsWith("+") && !line.startsWith("+++")) {
styleProps.bold = true
styleProps.color = "green"
}
return (
<Text {...styleProps} key={idx}>
{line}
</Text>
)
})}
</>
)
const ProposeWithInput = ({
diff,
filePath,
proposalAccepted,
acceptProposal,
}: ProposeChildProps) => {
useEnterToContinue(acceptProposal, filePath !== "" && !proposalAccepted)
return (
<Box flexDirection="column">
<Diff diff={diff} />
<EnterToContinue message="The above changes will be made. Press ENTER to continue" />
</Box>
)
}
const ProposeWithoutInput = ({
diff,
filePath,
proposalAccepted,
acceptProposal,
}: ProposeChildProps) => {
React.useEffect(() => {
if (filePath !== "" && !proposalAccepted) {
acceptProposal()
}
}, [acceptProposal, filePath, proposalAccepted])
return (
<Box flexDirection="column">
<Diff diff={diff} />
</Box>
)
}
export const Commit: IExecutor["Commit"] = ({onChangeCommitted, proposalData: filePath, step}) => {
React.useEffect(() => {
void (async function () {
const results = await transform(
async (original) =>
await ((step as Config).transformPlain
? stringProcessFile(original, (step as Config).transformPlain!)
: processFile(original, (step as Config).transform!)),
[filePath],
)
if (results.some((r) => r.status === TransformStatus.Failure)) {
console.error(results)
}
onChangeCommitted(`Modified file: ${filePath}`)
})()
}, [filePath, onChangeCommitted, step])
return (
<Box>
<Spinner />
<Text>Applying file changes</Text>
</Box>
)
}

View File

@@ -1,136 +0,0 @@
import {Generator, GeneratorOptions, SourceRootType} from "@blitzjs/generator"
import {Box, Text} from "ink"
import {useEffect, useState} from "react"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
export interface Config extends ExecutorConfig {
targetDirectory?: executorArgument<string>
templatePath: executorArgument<string>
templateValues: executorArgument<{[key: string]: string}>
destinationPathPrompt?: executorArgument<string>
}
export function isNewFileExecutor(executor: ExecutorConfig): executor is Config {
return (executor as Config).templatePath !== undefined
}
export const type = "new-file"
interface TempGeneratorOptions extends GeneratorOptions {
targetDirectory?: string
templateRoot: string
templateValues: any
run?: any
}
class TempGenerator extends Generator<TempGeneratorOptions> {
sourceRoot: SourceRootType
targetDirectory: string
templateValues: any
returnResults = true
constructor(options: TempGeneratorOptions) {
super(options)
this.sourceRoot = {type: "absolute", path: options.templateRoot}
this.templateValues = options.templateValues
this.targetDirectory = options.targetDirectory || "."
}
getTemplateValues() {
return this.templateValues
}
getTargetDirectory() {
return this.targetDirectory
}
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, onChangeCommitted, step}) => {
const userInput = useUserInput(cliFlags)
const generatorArgs = React.useMemo(
() => ({
destinationRoot: ".",
targetDirectory: getExecutorArgument((step as Config).targetDirectory, cliArgs),
templateRoot: getExecutorArgument((step as Config).templatePath, cliArgs),
templateValues: getExecutorArgument((step as Config).templateValues, cliArgs),
}),
[cliArgs, step],
)
const [fileCreateOutput, setFileCreateOutput] = useState("")
const [changeCommited, setChangeCommited] = useState(false)
const fileCreateLines = fileCreateOutput.split("\n")
const handleChangeCommitted = React.useCallback(() => {
setChangeCommited(true)
onChangeCommitted(
`Successfully created ${fileCreateLines
.map((l) => l.split(" ").slice(1).join("").trim())
.join(", ")}`,
)
}, [fileCreateLines, onChangeCommitted])
useEffect(() => {
async function createNewFiles() {
if (!fileCreateOutput) {
const generator = new TempGenerator(generatorArgs)
const results = (await generator.run()) as unknown as string
setFileCreateOutput(results)
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
createNewFiles()
}, [fileCreateOutput, generatorArgs])
const childProps: CommitChildProps = {
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
changeCommited: boolean
fileCreateOutput: string
handleChangeCommitted: () => void
}
const CommitWithInput = ({
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, !changeCommited && fileCreateOutput !== "")
return (
<Box flexDirection="column">
{fileCreateOutput !== "" && (
<>
<Text>{fileCreateOutput}</Text>
<EnterToContinue />
</>
)}
</Box>
)
}
const CommitWithoutInput = ({
changeCommited,
fileCreateOutput,
handleChangeCommitted,
}: CommitChildProps) => {
React.useEffect(() => {
if (!changeCommited && fileCreateOutput !== "") {
handleChangeCommitted()
}
}, [changeCommited, fileCreateOutput, handleChangeCommitted])
return (
<Box flexDirection="column">{fileCreateOutput !== "" && <Text>{fileCreateOutput}</Text>}</Box>
)
}

View File

@@ -1,77 +0,0 @@
import {Box, Text} from "ink"
import * as React from "react"
import {EnterToContinue} from "../components/enter-to-continue"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, executorArgument, ExecutorConfig, getExecutorArgument} from "./executor"
export interface Config extends ExecutorConfig {
message: executorArgument<string>
}
export const type = "print-message"
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, onChangeCommitted, step}) => {
const userInput = useUserInput(cliFlags)
const generatorArgs = React.useMemo(
() => ({
message: getExecutorArgument((step as Config).message, cliArgs),
stepName: getExecutorArgument((step as Config).stepName, cliArgs),
}),
[cliArgs, step],
)
const [changeCommited, setChangeCommited] = React.useState(false)
const handleChangeCommitted = React.useCallback(() => {
setChangeCommited(true)
onChangeCommitted(generatorArgs.stepName)
}, [onChangeCommitted, generatorArgs])
const childProps: CommitChildProps = {
changeCommited,
generatorArgs,
handleChangeCommitted,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
interface CommitChildProps {
changeCommited: boolean
generatorArgs: {message: string; stepName: string}
handleChangeCommitted: () => void
}
const CommitWithInput = ({
changeCommited,
generatorArgs,
handleChangeCommitted,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, !changeCommited)
return (
<Box flexDirection="column">
<Text>{generatorArgs.message}</Text>
<EnterToContinue />
</Box>
)
}
const CommitWithoutInput = ({
changeCommited,
generatorArgs,
handleChangeCommitted,
}: CommitChildProps) => {
React.useEffect(() => {
if (!changeCommited) {
handleChangeCommitted()
}
}, [changeCommited, handleChangeCommitted])
return (
<Box flexDirection="column">
<Text>{generatorArgs.message}</Text>
</Box>
)
}

View File

@@ -1,136 +0,0 @@
import {spawn} from "cross-spawn"
import {Box, Text} from "ink"
import Spinner from "ink-spinner"
import * as React from "react"
import {Newline} from "../components/newline"
import {RecipeCLIArgs} from "../types"
import {useEnterToContinue} from "../utils/use-enter-to-continue"
import {useUserInput} from "../utils/use-user-input"
import {IExecutor, ExecutorConfig, getExecutorArgument} from "./executor"
export type CliCommand = string | [string, ...string[]]
export interface Config extends ExecutorConfig {
command: CliCommand
}
export interface CommitChildProps {
commandInstalled: boolean
handleChangeCommitted: () => void
command: CliCommand
cliArgs: RecipeCLIArgs
step: Config
}
export const type = "run-command"
function Command({command, loading}: {command: CliCommand; loading: boolean}) {
return (
<Text>
{` `}
{loading ? <Spinner /> : "✅"}
{` ${typeof command === "string" ? command : command.join(" ")}`}
</Text>
)
}
const CommandList = ({
lede = "Hang tight! Running...",
commandLoading = false,
step,
command,
}: {
lede?: string
commandLoading?: boolean
step: Config
command: CliCommand
}) => {
return (
<Box flexDirection="column">
<Text>{lede}</Text>
<Newline />
<Command key={step.stepId} command={command} loading={commandLoading} />
</Box>
)
}
/**
* INFO: Exported for unit testing purposes
*
* This function calls the defined command with their optional arguments if defined
*
* @param {CliCommand} input The Command and arguments
* @return Promise<void>
*
* @example await executeCommand("ls")
* @example await executeCommand(["ls"])
* @example await executeCommand(["ls", ...["-a", "-l"]])
*/
export async function executeCommand(input: CliCommand): Promise<void> {
// from https://stackoverflow.com/a/43766456/9950655
const argsRegex =
/("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|\/[^/\\]*(?:\\[\S\s][^/\\]*)*\/[gimy]*(?=\s|$)|(?:\\\s|\S)+)/g
const command: string[] = Array.isArray(input) ? input : input.match(argsRegex) || []
if (command.length === 0) {
throw new Error(`The command is too short: \`${JSON.stringify(input)}\``)
}
await new Promise((resolve) => {
const cp = spawn(`${command[0]}`, command.slice(1), {
stdio: ["inherit", "pipe", "pipe"],
})
cp.on("exit", resolve)
cp.stdout.on("data", () => {})
})
}
export const Commit: IExecutor["Commit"] = ({cliArgs, cliFlags, step, onChangeCommitted}) => {
const userInput = useUserInput(cliFlags)
const [commandInstalled, setCommandInstalled] = React.useState(false)
const executorCommand = getExecutorArgument((step as Config).command, cliArgs)
const handleChangeCommitted = React.useCallback(() => {
onChangeCommitted(`Executed command ${executorCommand}`)
}, [executorCommand, onChangeCommitted])
React.useEffect(() => {
async function runCommand() {
await executeCommand(executorCommand)
setCommandInstalled(true)
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
runCommand()
}, [cliArgs, step, executorCommand])
React.useEffect(() => {
if (commandInstalled) {
handleChangeCommitted()
}
}, [commandInstalled, handleChangeCommitted])
const childProps: CommitChildProps = {
commandInstalled,
handleChangeCommitted,
command: executorCommand,
cliArgs,
step: step as Config,
}
if (userInput) return <CommitWithInput {...childProps} />
else return <CommitWithoutInput {...childProps} />
}
const CommitWithInput = ({
commandInstalled,
handleChangeCommitted,
command,
step,
}: CommitChildProps) => {
useEnterToContinue(handleChangeCommitted, commandInstalled)
return <CommandList commandLoading={!commandInstalled} step={step} command={command} />
}
const CommitWithoutInput = ({commandInstalled, command, step}: CommitChildProps) => (
<CommandList commandLoading={!commandInstalled} step={step} command={command} />
)

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