diff --git a/.github/actions/check_changelog/package.json b/.github/actions/check_changelog/package.json
index b5e5ad3213e5..cd086b5097a7 100644
--- a/.github/actions/check_changelog/package.json
+++ b/.github/actions/check_changelog/package.json
@@ -6,5 +6,5 @@
"@actions/exec": "1.1.1",
"@actions/github": "6.0.0"
},
- "packageManager": "yarn@4.0.2"
+ "packageManager": "yarn@4.1.0"
}
diff --git a/.github/actions/check_changelog/yarn.lock b/.github/actions/check_changelog/yarn.lock
index 1a564f5c72f8..08e54ab4edcb 100644
--- a/.github/actions/check_changelog/yarn.lock
+++ b/.github/actions/check_changelog/yarn.lock
@@ -11,7 +11,7 @@ __metadata:
dependencies:
"@actions/http-client": "npm:^2.0.1"
uuid: "npm:^8.3.2"
- checksum: 7a61446697a23dcad3545cf0634dedbdedf20ae9a0ee6ee977554589a15deb4a93593ee48a41258933d58ce0778f446b0d2c162b60750956fb75e0b9560fb832
+ checksum: 10c0/7a61446697a23dcad3545cf0634dedbdedf20ae9a0ee6ee977554589a15deb4a93593ee48a41258933d58ce0778f446b0d2c162b60750956fb75e0b9560fb832
languageName: node
linkType: hard
@@ -20,7 +20,7 @@ __metadata:
resolution: "@actions/exec@npm:1.1.1"
dependencies:
"@actions/io": "npm:^1.0.1"
- checksum: 4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a
+ checksum: 10c0/4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a
languageName: node
linkType: hard
@@ -32,7 +32,7 @@ __metadata:
"@octokit/core": "npm:^5.0.1"
"@octokit/plugin-paginate-rest": "npm:^9.0.0"
"@octokit/plugin-rest-endpoint-methods": "npm:^10.0.0"
- checksum: 6f86f564e6ec5873c69ff23bed308cef5f964dbdb559c5415c1ba479517bf18352713a2a757c27f8f67a3d675fdd78446cf142b27762489f697edf9c58e72378
+ checksum: 10c0/6f86f564e6ec5873c69ff23bed308cef5f964dbdb559c5415c1ba479517bf18352713a2a757c27f8f67a3d675fdd78446cf142b27762489f697edf9c58e72378
languageName: node
linkType: hard
@@ -42,28 +42,28 @@ __metadata:
dependencies:
tunnel: "npm:^0.0.6"
undici: "npm:^5.25.4"
- checksum: 868fe8529d78beb72f84ea2486e232fa6f66abe00d6ec4591b98c37e762c3d812868a3548638d75b49917961fd10ba1556916b47b1e9e4b55c266e2013c3ae8e
+ checksum: 10c0/868fe8529d78beb72f84ea2486e232fa6f66abe00d6ec4591b98c37e762c3d812868a3548638d75b49917961fd10ba1556916b47b1e9e4b55c266e2013c3ae8e
languageName: node
linkType: hard
"@actions/io@npm:^1.0.1":
version: 1.1.3
resolution: "@actions/io@npm:1.1.3"
- checksum: 5b8751918e5bf0bebd923ba917fb1c0e294401e7ff0037f32c92a4efa4215550df1f6633c63fd4efb2bdaae8711e69b9e36925857db1f38935ff62a5c92ec29e
+ checksum: 10c0/5b8751918e5bf0bebd923ba917fb1c0e294401e7ff0037f32c92a4efa4215550df1f6633c63fd4efb2bdaae8711e69b9e36925857db1f38935ff62a5c92ec29e
languageName: node
linkType: hard
"@fastify/busboy@npm:^2.0.0":
version: 2.1.0
resolution: "@fastify/busboy@npm:2.1.0"
- checksum: 7bb641080aac7cf01d88749ad331af10ba9ec3713ec07cabbe833908c75df21bd56249bb6173bdec07f5a41896b21e3689316f86684c06635da45f91ff4565a2
+ checksum: 10c0/7bb641080aac7cf01d88749ad331af10ba9ec3713ec07cabbe833908c75df21bd56249bb6173bdec07f5a41896b21e3689316f86684c06635da45f91ff4565a2
languageName: node
linkType: hard
"@octokit/auth-token@npm:^4.0.0":
version: 4.0.0
resolution: "@octokit/auth-token@npm:4.0.0"
- checksum: 57acaa6c394c5abab2f74e8e1dcf4e7a16b236f713c77a54b8f08e2d14114de94b37946259e33ec2aab0566b26f724c2b71d2602352b59e541a9854897618f3c
+ checksum: 10c0/57acaa6c394c5abab2f74e8e1dcf4e7a16b236f713c77a54b8f08e2d14114de94b37946259e33ec2aab0566b26f724c2b71d2602352b59e541a9854897618f3c
languageName: node
linkType: hard
@@ -78,7 +78,7 @@ __metadata:
"@octokit/types": "npm:^12.0.0"
before-after-hook: "npm:^2.2.0"
universal-user-agent: "npm:^6.0.0"
- checksum: a1d2882373b4a33cd9f6e56d76bcc82e5589a477829fc3491b1ef471a8a83fa437b339a2c76d97d9e8ea4ca12bf3ebf32e66119ba16977e542d98f1f5dd3c994
+ checksum: 10c0/a1d2882373b4a33cd9f6e56d76bcc82e5589a477829fc3491b1ef471a8a83fa437b339a2c76d97d9e8ea4ca12bf3ebf32e66119ba16977e542d98f1f5dd3c994
languageName: node
linkType: hard
@@ -88,7 +88,7 @@ __metadata:
dependencies:
"@octokit/types": "npm:^12.0.0"
universal-user-agent: "npm:^6.0.0"
- checksum: f1c857c5d85afa9d7e8857f7f97dbec28d3b6ab1dc21fe35172f1bc9e5512c8a3a26edabf6b2d83bb60d700f7ad290c96be960496aa83606095630edfad06db4
+ checksum: 10c0/f1c857c5d85afa9d7e8857f7f97dbec28d3b6ab1dc21fe35172f1bc9e5512c8a3a26edabf6b2d83bb60d700f7ad290c96be960496aa83606095630edfad06db4
languageName: node
linkType: hard
@@ -99,14 +99,14 @@ __metadata:
"@octokit/request": "npm:^8.0.1"
"@octokit/types": "npm:^12.0.0"
universal-user-agent: "npm:^6.0.0"
- checksum: 96e5d6b970be60877134cc147b9249534f3a79d691b9932d731d453426fa1e1a0a36111a1b0a6ab43d61309c630903a65db5559b5c800300dc26cf588f50fea8
+ checksum: 10c0/96e5d6b970be60877134cc147b9249534f3a79d691b9932d731d453426fa1e1a0a36111a1b0a6ab43d61309c630903a65db5559b5c800300dc26cf588f50fea8
languageName: node
linkType: hard
"@octokit/openapi-types@npm:^19.1.0":
version: 19.1.0
resolution: "@octokit/openapi-types@npm:19.1.0"
- checksum: ae8081f52b797b91a12d4f6cddc475699c9d34b06645b337adc77d30b583d8fe8506597a45c42f8f1a96bfb2a9d092cee257d8a65d718bfeed23a0d153448eea
+ checksum: 10c0/ae8081f52b797b91a12d4f6cddc475699c9d34b06645b337adc77d30b583d8fe8506597a45c42f8f1a96bfb2a9d092cee257d8a65d718bfeed23a0d153448eea
languageName: node
linkType: hard
@@ -117,7 +117,7 @@ __metadata:
"@octokit/types": "npm:^12.4.0"
peerDependencies:
"@octokit/core": ">=5"
- checksum: a17055dff8fde5ebc03bf935294ffa4605ed714cb15252f0fa63cda1b95e738fafb5ab9748b18fbdfa5615d5f6686cbf193c6d6426e7dc4fd1dda91c87263f3b
+ checksum: 10c0/a17055dff8fde5ebc03bf935294ffa4605ed714cb15252f0fa63cda1b95e738fafb5ab9748b18fbdfa5615d5f6686cbf193c6d6426e7dc4fd1dda91c87263f3b
languageName: node
linkType: hard
@@ -128,7 +128,7 @@ __metadata:
"@octokit/types": "npm:^12.3.0"
peerDependencies:
"@octokit/core": ">=5"
- checksum: 4d00a2334753955f0c3841ba8fc0880c093b94838e011864ee737d958d2d64e3d45d34fa4c8b64bccf9e13c6de81318cbd6e2b24df37992941d12f54def28432
+ checksum: 10c0/4d00a2334753955f0c3841ba8fc0880c093b94838e011864ee737d958d2d64e3d45d34fa4c8b64bccf9e13c6de81318cbd6e2b24df37992941d12f54def28432
languageName: node
linkType: hard
@@ -139,7 +139,7 @@ __metadata:
"@octokit/types": "npm:^12.0.0"
deprecation: "npm:^2.0.0"
once: "npm:^1.4.0"
- checksum: e72a4627120de345b54876a1f007664095e5be9d624fce2e14fccf7668cd8f5e4929d444d8fc085d48e1fb5cd548538453974aab129a669101110d6679dce6c6
+ checksum: 10c0/e72a4627120de345b54876a1f007664095e5be9d624fce2e14fccf7668cd8f5e4929d444d8fc085d48e1fb5cd548538453974aab129a669101110d6679dce6c6
languageName: node
linkType: hard
@@ -151,7 +151,7 @@ __metadata:
"@octokit/request-error": "npm:^5.0.0"
"@octokit/types": "npm:^12.0.0"
universal-user-agent: "npm:^6.0.0"
- checksum: 0789edd3b600c5b7ca74089e2842b7bb679a0ad1ec56e5dda54f052d2dd266ac8e6e2eb3c34ba57962066f0770444bf1e99805fd2d762a47776f567beafcf038
+ checksum: 10c0/0789edd3b600c5b7ca74089e2842b7bb679a0ad1ec56e5dda54f052d2dd266ac8e6e2eb3c34ba57962066f0770444bf1e99805fd2d762a47776f567beafcf038
languageName: node
linkType: hard
@@ -160,14 +160,14 @@ __metadata:
resolution: "@octokit/types@npm:12.4.0"
dependencies:
"@octokit/openapi-types": "npm:^19.1.0"
- checksum: b52b3fd8af307a1868846991f8376548a790814b20639dee1110271a768c0489081970df893ca2230f6285066003230d22f5877eeac90418971a475c79808241
+ checksum: 10c0/b52b3fd8af307a1868846991f8376548a790814b20639dee1110271a768c0489081970df893ca2230f6285066003230d22f5877eeac90418971a475c79808241
languageName: node
linkType: hard
"before-after-hook@npm:^2.2.0":
version: 2.2.3
resolution: "before-after-hook@npm:2.2.3"
- checksum: 0488c4ae12df758ca9d49b3bb27b47fd559677965c52cae7b335784724fb8bf96c42b6e5ba7d7afcbc31facb0e294c3ef717cc41c5bc2f7bd9e76f8b90acd31c
+ checksum: 10c0/0488c4ae12df758ca9d49b3bb27b47fd559677965c52cae7b335784724fb8bf96c42b6e5ba7d7afcbc31facb0e294c3ef717cc41c5bc2f7bd9e76f8b90acd31c
languageName: node
linkType: hard
@@ -184,7 +184,7 @@ __metadata:
"deprecation@npm:^2.0.0":
version: 2.3.1
resolution: "deprecation@npm:2.3.1"
- checksum: 23d688ba66b74d09b908c40a76179418acbeeb0bfdf218c8075c58ad8d0c315130cb91aa3dffb623aa3a411a3569ce56c6460de6c8d69071c17fe6dd2442f032
+ checksum: 10c0/23d688ba66b74d09b908c40a76179418acbeeb0bfdf218c8075c58ad8d0c315130cb91aa3dffb623aa3a411a3569ce56c6460de6c8d69071c17fe6dd2442f032
languageName: node
linkType: hard
@@ -193,14 +193,14 @@ __metadata:
resolution: "once@npm:1.4.0"
dependencies:
wrappy: "npm:1"
- checksum: 5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0
+ checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0
languageName: node
linkType: hard
"tunnel@npm:^0.0.6":
version: 0.0.6
resolution: "tunnel@npm:0.0.6"
- checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75
+ checksum: 10c0/e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75
languageName: node
linkType: hard
@@ -209,14 +209,14 @@ __metadata:
resolution: "undici@npm:5.28.3"
dependencies:
"@fastify/busboy": "npm:^2.0.0"
- checksum: 3c559ae50ef3104b7085251445dda6f4de871553b9e290845649d2f80b06c0c9cfcdf741b0029c6b20d36c82e6a74dc815b139fa9a26757d70728074ca6d6f5c
+ checksum: 10c0/3c559ae50ef3104b7085251445dda6f4de871553b9e290845649d2f80b06c0c9cfcdf741b0029c6b20d36c82e6a74dc815b139fa9a26757d70728074ca6d6f5c
languageName: node
linkType: hard
"universal-user-agent@npm:^6.0.0":
version: 6.0.1
resolution: "universal-user-agent@npm:6.0.1"
- checksum: 5c9c46ffe19a975e11e6443640ed4c9e0ce48fcc7203325757a8414ac49940ebb0f4667f2b1fa561489d1eb22cb2d05a0f7c82ec20c5cba42e58e188fb19b187
+ checksum: 10c0/5c9c46ffe19a975e11e6443640ed4c9e0ce48fcc7203325757a8414ac49940ebb0f4667f2b1fa561489d1eb22cb2d05a0f7c82ec20c5cba42e58e188fb19b187
languageName: node
linkType: hard
@@ -225,13 +225,13 @@ __metadata:
resolution: "uuid@npm:8.3.2"
bin:
uuid: dist/bin/uuid
- checksum: bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54
+ checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54
languageName: node
linkType: hard
"wrappy@npm:1":
version: 1.0.2
resolution: "wrappy@npm:1.0.2"
- checksum: 56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0
+ checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0
languageName: node
linkType: hard
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f6f436c42bc..d3749b81c4f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+## v7.0.0
+
+- See https://github.com/redwoodjs/redwood/releases/tag/v7.0.0 for the release notes and https://community.redwoodjs.com/t/redwood-v7-0-0-upgrade-guide/5713 for the upgrade guide
+
## v6.6.4
- See https://github.com/redwoodjs/redwood/releases/tag/v6.6.4
diff --git a/__fixtures__/fragment-test-project/api/package.json b/__fixtures__/fragment-test-project/api/package.json
index 1fe562e03fbe..06193a97741f 100644
--- a/__fixtures__/fragment-test-project/api/package.json
+++ b/__fixtures__/fragment-test-project/api/package.json
@@ -3,8 +3,8 @@
"version": "0.0.0",
"private": true,
"dependencies": {
- "@redwoodjs/api": "6.0.7",
- "@redwoodjs/auth-dbauth-api": "6.0.7",
- "@redwoodjs/graphql-server": "6.0.7"
+ "@redwoodjs/api": "7.0.0",
+ "@redwoodjs/auth-dbauth-api": "7.0.0",
+ "@redwoodjs/graphql-server": "7.0.0"
}
}
diff --git a/__fixtures__/fragment-test-project/package.json b/__fixtures__/fragment-test-project/package.json
index 3d2f18b5f1d5..eb82cb3cd099 100644
--- a/__fixtures__/fragment-test-project/package.json
+++ b/__fixtures__/fragment-test-project/package.json
@@ -7,8 +7,8 @@
]
},
"devDependencies": {
- "@redwoodjs/core": "6.0.7",
- "@redwoodjs/project-config": "6.0.7"
+ "@redwoodjs/core": "7.0.0",
+ "@redwoodjs/project-config": "7.0.0"
},
"eslintConfig": {
"extends": "@redwoodjs/eslint-config",
@@ -20,5 +20,5 @@
"prisma": {
"seed": "yarn rw exec seed"
},
- "packageManager": "yarn@4.0.2"
+ "packageManager": "yarn@4.1.0"
}
diff --git a/__fixtures__/fragment-test-project/web/package.json b/__fixtures__/fragment-test-project/web/package.json
index 089dd533bc0c..b75f3bedd4e3 100644
--- a/__fixtures__/fragment-test-project/web/package.json
+++ b/__fixtures__/fragment-test-project/web/package.json
@@ -11,16 +11,16 @@
]
},
"dependencies": {
- "@redwoodjs/auth-dbauth-web": "6.0.7",
- "@redwoodjs/forms": "6.0.7",
- "@redwoodjs/router": "6.0.7",
- "@redwoodjs/web": "6.0.7",
+ "@redwoodjs/auth-dbauth-web": "7.0.0",
+ "@redwoodjs/forms": "7.0.0",
+ "@redwoodjs/router": "7.0.0",
+ "@redwoodjs/web": "7.0.0",
"humanize-string": "2.1.0",
"react": "0.0.0-experimental-e5205658f-20230913",
"react-dom": "0.0.0-experimental-e5205658f-20230913"
},
"devDependencies": {
- "@redwoodjs/vite": "6.0.7",
+ "@redwoodjs/vite": "7.0.0",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"autoprefixer": "^10.4.16",
diff --git a/__fixtures__/test-project-rsa/package.json b/__fixtures__/test-project-rsa/package.json
index d40992f2f0ac..2e99328ded69 100644
--- a/__fixtures__/test-project-rsa/package.json
+++ b/__fixtures__/test-project-rsa/package.json
@@ -19,7 +19,7 @@
"prisma": {
"seed": "yarn rw exec seed"
},
- "packageManager": "yarn@4.0.2",
+ "packageManager": "yarn@4.1.0",
"resolutions": {
"vite@4.4.9": "patch:vite@npm%3A4.4.9#./.yarn/patches/vite-npm-4.4.9-e845c1bbf8.patch"
}
diff --git a/__fixtures__/test-project/api/package.json b/__fixtures__/test-project/api/package.json
index 1fe562e03fbe..06193a97741f 100644
--- a/__fixtures__/test-project/api/package.json
+++ b/__fixtures__/test-project/api/package.json
@@ -3,8 +3,8 @@
"version": "0.0.0",
"private": true,
"dependencies": {
- "@redwoodjs/api": "6.0.7",
- "@redwoodjs/auth-dbauth-api": "6.0.7",
- "@redwoodjs/graphql-server": "6.0.7"
+ "@redwoodjs/api": "7.0.0",
+ "@redwoodjs/auth-dbauth-api": "7.0.0",
+ "@redwoodjs/graphql-server": "7.0.0"
}
}
diff --git a/__fixtures__/test-project/package.json b/__fixtures__/test-project/package.json
index e74028e8ed71..eb82cb3cd099 100644
--- a/__fixtures__/test-project/package.json
+++ b/__fixtures__/test-project/package.json
@@ -7,8 +7,8 @@
]
},
"devDependencies": {
- "@redwoodjs/core": "6.0.7",
- "@redwoodjs/project-config": "6.0.7"
+ "@redwoodjs/core": "7.0.0",
+ "@redwoodjs/project-config": "7.0.0"
},
"eslintConfig": {
"extends": "@redwoodjs/eslint-config",
diff --git a/__fixtures__/test-project/web/package.json b/__fixtures__/test-project/web/package.json
index f57f80d958ad..433398a0e250 100644
--- a/__fixtures__/test-project/web/package.json
+++ b/__fixtures__/test-project/web/package.json
@@ -11,16 +11,16 @@
]
},
"dependencies": {
- "@redwoodjs/auth-dbauth-web": "6.0.7",
- "@redwoodjs/forms": "6.0.7",
- "@redwoodjs/router": "6.0.7",
- "@redwoodjs/web": "6.0.7",
+ "@redwoodjs/auth-dbauth-web": "7.0.0",
+ "@redwoodjs/forms": "7.0.0",
+ "@redwoodjs/router": "7.0.0",
+ "@redwoodjs/web": "7.0.0",
"humanize-string": "2.1.0",
"react": "0.0.0-experimental-e5205658f-20230913",
"react-dom": "0.0.0-experimental-e5205658f-20230913"
},
"devDependencies": {
- "@redwoodjs/vite": "6.0.7",
+ "@redwoodjs/vite": "7.0.0",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"autoprefixer": "^10.4.17",
diff --git a/docs/docs/authentication.md b/docs/docs/authentication.md
index cef3754ea978..88506057eb2f 100644
--- a/docs/docs/authentication.md
+++ b/docs/docs/authentication.md
@@ -5,7 +5,7 @@ description: Set up an authentication provider
# Authentication
Redwood has integrated auth end to end, from the web side to the api side.
-On the web side, the router can protect pages via the `Private` component (or the `Set` component via the `private` prop), and even restrict access at the role-level.
+On the web side, the router can protect pages via the `PrivateSet` component, and even restrict access at the role-level.
And if you'd prefer to work with the primitives, the `useAuth` hook exposes all the pieces to build the experience you want.
Likewise, the api side is locked down by default: all SDLs are generated with the `@requireAuth` directive, ensuring that making things publicly available is something that you opt in to rather than out of.
@@ -117,11 +117,11 @@ Much of what the functions it returns do is self explanatory, but the options th
### Protecting routes
-You can require that a user be authenticated to navigate to a route by wrapping it in the `Private` component or the `Set` component with the `private` prop set to `true`.
+You can require that a user be authenticated to navigate to a route by wrapping it in the `PrivateSet` component.
An unauthenticated user will be redirected to the route specified in either component's `unauthenticated` prop:
```tsx title="web/src/Routes.tsx"
-import { Router, Route, Private } from '@redwoodjs/router'
+import { Router, Route, PrivateSet } from '@redwoodjs/router'
const Routes = () => {
return (
@@ -129,21 +129,20 @@ const Routes = () => {
- // highlight-start
+ // highlight-next-line
- // highlight-end
-
+
)
}
```
-You can also restrict access by role by passing a role or an array of roles to the `Private` or `Set` component's `hasRole` prop:
+You can also restrict access by role by passing a role or an array of roles to the `PrivateSet` component's `hasRole` prop:
```tsx title="web/src/Routes.tsx"
-import { Router, Route, Private, Set } from '@redwoodjs/router'
+import { Router, Route, PrivateSet } from '@redwoodjs/router'
const Routes = () => {
return (
@@ -154,17 +153,17 @@ const Routes = () => {
-
+
// highlight-next-line
-
+
-
+
// highlight-next-line
-
+
)
}
diff --git a/docs/docs/docker.md b/docs/docs/docker.md
index 33c2e65bd711..53a1c3aa0a38 100644
--- a/docs/docs/docker.md
+++ b/docs/docs/docker.md
@@ -637,10 +637,10 @@ await server.start()
`start` is a thin wrapper around [`listen`](https://fastify.dev/docs/latest/Reference/Server/#listen).
It takes the same arguments as `listen`, except for host and port. It computes those in the following way, in order of precedence:
-1. `--host` or `--port` flags:
+1. `--apiHost` or `--apiPort` flags:
```
- yarn node api/dist/server.js --host 0.0.0.0 --port 8913
+ yarn node api/dist/server.js --apiHost 0.0.0.0 --apiPort 8913
```
2. `REDWOOD_API_HOST` or `REDWOOD_API_PORT` env vars:
diff --git a/docs/docs/router.md b/docs/docs/router.md
index f07df829f4ff..aa606ac91909 100644
--- a/docs/docs/router.md
+++ b/docs/docs/router.md
@@ -44,7 +44,7 @@ The `path` prop specifies the URL path to match, starting with the beginning sla
## Private Routes
-Some pages should only be visible to authenticated users.
+Some pages should only be visible to authenticated users. We support this using the `PrivateSet` component. Read more [further down](#privateset).
## Sets of Routes
@@ -87,7 +87,7 @@ Conceptually, this fits with how we think about Context and Layouts as things th
There's a lot of flexibility here. You can even nest `Sets` to great effect:
```jsx title="Routes.js"
-import { Router, Route, Set, Private } from '@redwoodjs/router'
+import { Router, Route, Set } from '@redwoodjs/router'
import BlogContext from 'src/contexts/BlogContext'
import BlogLayout from 'src/layouts/BlogLayout'
import BlogNavLayout from 'src/layouts/BlogNavLayout'
@@ -132,7 +132,7 @@ becomes...
A `PrivateSet` makes all Routes inside that Set require authentication. When a user isn't authenticated and attempts to visit one of the Routes in the `PrivateSet`, they'll be redirected to the Route passed as the `PrivateSet`'s `unauthenticated` prop. The originally-requested Route's path is added to the query string as a `redirectTo` param. This lets you send the user to the page they originally requested once they're logged-in.
-Here's an example of how you'd use a private set:
+Here's an example of how you'd use a `PrivateSet`:
```jsx title="Routes.js"
@@ -145,7 +145,7 @@ Here's an example of how you'd use a private set:
For more fine-grained control, you can specify `roles` (which takes a string for a single role or an array of roles), and the router will check to see that the current user is authorized before giving them access to the Route. If they're not, they will be redirected to the page specified in the `unauthenticated` prop, such as a "forbidden" page. Read more about Role-based Access Control in Redwood [here](how-to/role-based-access-control.md).
-To protect `Private` routes for access by a single role:
+To protect private routes for access by a single role:
```jsx title="Routes.js"
@@ -157,7 +157,7 @@ To protect `Private` routes for access by a single role:
```
-To protect `Private` routes for access by multiple roles:
+To protect private routes for access by multiple roles:
```jsx title="Routes.js"
@@ -613,7 +613,7 @@ Redwood will detect your explicit import and refrain from splitting that page in
Because lazily-loaded pages can take a non-negligible amount of time to load (depending on bundle size and network connection), you may want to show a loading indicator to signal to the user that something is happening after they click a link.
-In order to show a loader as your page chunks are loading, you simply add the `whileLoadingPage` prop to your route, `Set` or `Private` component.
+In order to show a loader as your page chunks are loading, you simply add the `whileLoadingPage` prop to your route, `Set` or `PrivateSet` component.
```jsx title="Routes.js"
import SkeletonLoader from 'src/components/SkeletonLoader'
@@ -659,7 +659,7 @@ When the lazy-loaded page is loading, `PageLoadingContext.Consumer` will pass `{
Let's say you have a dashboard area on your Redwood app, which can only be accessed after logging in. When Redwood Router renders your private page, it will first fetch the user's details, and only render the page if it determines the user is indeed logged in.
-In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private `` or `` component:
+In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your `PrivateSet` component:
```jsx
//Routes.js
@@ -675,7 +675,7 @@ In order to display a loader while auth details are being retrieved you can add
{/* other routes */}
-
+
```
@@ -762,7 +762,7 @@ Note that if you're copy-pasting this example, it uses [Tailwind CSS](https://ta
:::note Can I customize the development one?
-As it's part of the RedwoodJS framework, you can't _change_ the dev fatal error page - but you can always build your own that takes the same props. If there's a feature you want to add to the built-in version, let us know on the [forums](https://community.redwoodjs.com/).
+As it's part of the RedwoodJS framework, you can't _change_ the dev fatal error page, but you can always build your own that takes the same props. If there's a feature you want to add to the built-in version, let us know on the [forums](https://community.redwoodjs.com/).
:::
diff --git a/docs/docs/tutorial/chapter0/what-is-redwood.md b/docs/docs/tutorial/chapter0/what-is-redwood.md
index cae5b7f0565f..e779cafefdf2 100644
--- a/docs/docs/tutorial/chapter0/what-is-redwood.md
+++ b/docs/docs/tutorial/chapter0/what-is-redwood.md
@@ -31,7 +31,7 @@ You can start them both with a single command: `yarn redwood dev`
When you open your web app in a browser, React does its thing initializing your app and monitoring the history for changes so that new content can be shown. Redwood features a custom, declarative Router that lets you specify URLs and the requisite pages (just a React component) will be shown. A simple routes file may look something like:
```jsx
-import { Set, Router, Route } from '@redwoodjs/router'
+import { Route, Router, Set, PrivateSet } from '@redwoodjs/router'
import ApplicationLayout from 'src/layouts/ApplicationLayout'
import { useAuth } from './auth'
diff --git a/docs/docs/tutorial/chapter4/authentication.md b/docs/docs/tutorial/chapter4/authentication.md
index 08bcc2228812..c42b3fb7d393 100644
--- a/docs/docs/tutorial/chapter4/authentication.md
+++ b/docs/docs/tutorial/chapter4/authentication.md
@@ -202,7 +202,7 @@ Going to the admin section now prevents a non-logged in user from seeing posts,
```jsx title="web/src/Routes.jsx"
// highlight-next-line
-import { Private, Router, Route, Set } from '@redwoodjs/router'
+import { PrivateSet, Router, Route, Set } from '@redwoodjs/router'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
import BlogLayout from 'src/layouts/BlogLayout'
@@ -241,7 +241,7 @@ export default Routes
```jsx title="web/src/Routes.tsx"
// highlight-next-line
-import { Private, Router, Route, Set } from '@redwoodjs/router'
+import { PrivateSet, Router, Route, Set } from '@redwoodjs/router'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
import BlogLayout from 'src/layouts/BlogLayout'
diff --git a/docs/docs/tutorial/chapter7/rbac.md b/docs/docs/tutorial/chapter7/rbac.md
index 605409e86f0c..490b98a4d5ac 100644
--- a/docs/docs/tutorial/chapter7/rbac.md
+++ b/docs/docs/tutorial/chapter7/rbac.md
@@ -156,21 +156,21 @@ export const hasRole = (roles: AllowedRoles): boolean => {
### Restricting Access via Routes
-The easiest way to prevent access to an entire URL is via the Router. The `` component takes a prop `roles` in which you can give a list of only those role(s) that should have access:
+The easiest way to prevent access to an entire URL is via the Router. The `` component takes a prop `roles` in which you can give a list of only those role(s) that should have access:
```jsx title="web/src/Routes.jsx"
// highlight-next-line
-
+
-
+
```
@@ -178,14 +178,14 @@ The easiest way to prevent access to an entire URL is via the Router. The `
+
-
+
```
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index 2ec259ca2192..ff65baf284ae 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -34,7 +34,7 @@ const config: Config = {
indexName: 'learn-redwood',
contextualSearch: true,
searchParameters: {},
- externalUrlRegex: 'https://learn-redwood.netlify.app',
+ // externalUrlRegex: 'https://learn-redwood.netlify.app',
},
navbar: {
title: 'RedwoodJS',
diff --git a/docs/netlify.toml b/docs/netlify.toml
index 2f07a9e47bc2..760ef715d5ad 100644
--- a/docs/netlify.toml
+++ b/docs/netlify.toml
@@ -214,7 +214,7 @@
to = "/docs/authentication#self-hosted-auth-installation-and-setup"
status = 301
-# v1.0-v1.5 redirects (to v1.x)
+# v1.0-v1.5 redirects
[[redirects]]
from = "/docs/1.0/*"
@@ -246,7 +246,7 @@
to = "/docs/1.x/:splat"
status = 301
-# v2.0-v2.2 redirects (to v2.x)
+# v2.0-v2.2 redirects
[[redirects]]
from = "/docs/2.0/*"
@@ -263,7 +263,7 @@
to = "/docs/2.x/:splat"
status = 301
-# v3.0-v3.2 redirects (to v3.x)
+# v3.0-v3.2 redirects
[[redirects]]
from = "/docs/3.0/*"
@@ -280,6 +280,102 @@
to = "/docs/3.x/:splat"
status = 301
+# v4 redirects
+
+[[redirects]]
+ from = "/docs/4.0/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/4.1/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/4.2/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/4.3/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/4.4/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/4.5/*"
+ to = "/docs/4.x/:splat"
+ status = 301
+
+# v5 redirects
+
+[[redirects]]
+ from = "/docs/5.0/*"
+ to = "/docs/5.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/5.1/*"
+ to = "/docs/5.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/5.2/*"
+ to = "/docs/5.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/5.3/*"
+ to = "/docs/5.x/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/5.4/*"
+ to = "/docs/5.x/:splat"
+ status = 301
+
+# v6.0-v6.6 redirects
+
+[[redirects]]
+ from = "/docs/6.0/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.1/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.2/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.3/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.4/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.5/*"
+ to = "/docs/:splat"
+ status = 301
+
+[[redirects]]
+ from = "/docs/6.6/*"
+ to = "/docs/:splat"
+ status = 301
+
# Redirects for "Configuring Fastify" after the server file was released in v7
[[redirects]]
@@ -296,3 +392,10 @@
from = "/docs/app-configuration-redwood-toml#how-to-configure-fastify-to-accept-file-uploads"
to = "/docs/docker#configuring-the-server"
status = 301
+
+# This doc was moved in v7 as a part of https://github.com/redwoodjs/redwood/pull/9416.
+
+[[redirects]]
+ from = "/docs/mocking-graphql-requests"
+ to = "/docs/canary/graphql/mocking-graphql-requests"
+ status = 301
diff --git a/docs/versioned_docs/version-6.0/seo-head.md b/docs/versioned_docs/version-6.0/seo-head.md
deleted file mode 100644
index 4b50350c6cdd..000000000000
--- a/docs/versioned_docs/version-6.0/seo-head.md
+++ /dev/null
@@ -1,152 +0,0 @@
----
-description: Use meta tags to set page info for SEO
----
-
-# SEO & Meta tags
-
-## Add app title
-You certainly want to change the title of your Redwood app.
-You can start by adding or modify `title` inside `redwood.toml`
-
-```diff
-[web]
-- title = "Redwood App"
-+ title = "My Cool App"
- port = 8910
- apiUrl = "/.redwood/functions"
-```
-This title (the app title) is used by default for all your pages if you don't define another one.
-It will also be use for the title template !
-### Title template
-Now that you have the app title set, you probably want some consistence with the page title, that's what the title template is for.
-
-Add `titleTemplate` as a prop for `RedwoodProvider` to have a title template for every pages
-
-In _web/src/App.\{tsx,js\}_
-```diff
--
-+
- /* ... */
-
-```
-
-You can write the format you like.
-
-_Examples :_
-```jsx
-"%PageTitle | %AppTitle" => "Home Page | Redwood App"
-
-"%AppTitle · %PageTitle" => "Redwood App · Home Page"
-
-"%PageTitle : %AppTitle" => "Home Page : Redwood App"
-```
-
-So now in your page you only need to write the title of the page.
-
-## Adding to page ``
-So you want to change the title of your page, or add elements to the `` of the page? We've got you!
-
-
-Let's say you want to change the title of your About page,
-Redwood provides a built in `` component, which you can use like this
-
-
-In _AboutPage/AboutPage.\{tsx,js\}_
-```diff
-+import { Head } from '@redwoodjs/web'
-
-const AboutPage = () => {
- return (
-
-
AboutPage
-+
-+ About the team
-+
-```
-
-You can include any valid `` tag in here that you like, but just to make things easier we also have a utility component [MetaTags](#setting-meta-tags-open-graph-directives).
-
-### What about nested tags?
-Redwood uses [react-helmet-async](https://github.com/staylor/react-helmet-async) underneath, which will use the tags furthest down your component tree.
-
-For example, if you set title in your Layout, and a title in your Page, it'll render the one in Page - this way you can override the tags you wish, while sharing the tags defined in Layout.
-
-
-> **Side note**
-> for these headers to appear to bots and scrapers e.g. for twitter to show your title, you have to make sure your page is prerendered
-> If your content is static you can use Redwood's built in [Prerender](prerender.md). For dynamic tags, check the [Dynamic head tags](#dynamic-tags)
-
-## Setting meta tags / open graph directives
-Often we want to set more than just the title - most commonly to set "og" headers. Og standing for
-[open graph](https://ogp.me/) of course.
-
-Redwood provides a convenience component `` to help you get all the relevant tags with one go (but you can totally choose to do them yourself)
-
-Here's an example setting some common headers, including how to set an `og:image`
-```jsx
-import { MetaTags } from '@redwoodjs/web'
-
-const AboutPage = () => {
- return (
-
-
AboutPage
-
-
This is the about page!
-
- )
-}
-
-export default AboutPage
-```
-
-This is great not just for link unfurling on say Facebook or Slack, but also for SEO. Take a look at the [source](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/MetaTags.tsx#L83) if you're curious what tags get set here.
-
-
-## Dynamic tags
-Great - so far we can see the changes, and bots will pick up our tags if we've prerendered the page, but what if I want to set the header based on the output of the Cell?
-
-> **Prerendering cells**
-> As of v3.x, Redwood supports prerendering your [Cells](https://redwoodjs.com/docs/cells) with the data you were querying. For more information please refer [to this section](https://redwoodjs.com/docs/prerender#cell-prerendering).
-
-
-Let's say in our PostCell, we want to set the title to match the Post.
-```jsx
-import Post from 'src/components/Post/Post'
-
-export const QUERY = gql`
- query FindPostById($id: Int!) {
- post: post(id: $id) {
- title
- snippet
- author {
- name
- }
- }
- }
-`
-
-export const Loading = /* ... */
-
-export const Empty = /* ... */
-
-export const Success = ({ post }) => {
- return (
- <>
-
-
- >
- )
-}
-```
-Once the success component renders, it'll update your page's title and set the relevant meta tags for you!
diff --git a/docs/versioned_docs/version-6.0/toast-notifications.md b/docs/versioned_docs/version-6.0/toast-notifications.md
deleted file mode 100644
index 0dab206bd67b..000000000000
--- a/docs/versioned_docs/version-6.0/toast-notifications.md
+++ /dev/null
@@ -1,66 +0,0 @@
----
-description: Toast notifications with react-hot-toast
----
-
-# Toast Notifications
-
-Did you know that those little popup notifications that you sometimes see at the top of a page after you've performed an action are affectionately known as "toast" notifications?
-Because they pop up like a piece of toast from a toaster!
-
-![Example toast animation](https://user-images.githubusercontent.com/300/110032806-71024680-7ced-11eb-8d69-7f462929815e.gif)
-
-Redwood supports these notifications out of the box thanks to the [react-hot-toast](https://react-hot-toast.com/) package.
-We'll refer you to their [docs](https://react-hot-toast.com/docs) since they're very thorough, but here's enough to get you going.
-
-### Add the `Toaster` Component
-
-To render toast notifications, start by adding the `Toaster` component.
-It's usually better to add it at the App or Layout-level than the Page:
-
-```jsx title="web/src/layouts/MainLayout/MainLayout.js"
-// highlight-next-line
-import { Toaster } from '@redwoodjs/web/toast'
-
-const MainLayout = ({ children }) => {
- return (
- <>
- // highlight-next-line
-
- {children}
- >
- )
-}
-
-export default MainLayout
-```
-
-### Call the `toast` function
-
-To render a toast notification, call the `toast` function or one of its methods:
-
-```jsx title="web/src/components/PostForm/PostForm.js"
-// highlight-next-line
-import { toast } from '@redwoodjs/web/toast'
-
-// ...
-
-const PostForm = () => {
- const onSubmit = () => {
- try {
- // Code to save a record...
- // highlight-next-line
- toast('User created!')
- } catch (e) {
- // There's also methods for default styling:
- // highlight-next-line
- toast.error("Error creating post...")
- }
- }
-
- return (
- // JSX...
- )
-})
-
-export default PostForm
-```
diff --git a/docs/versioned_docs/version-6.0/a11y.md b/docs/versioned_docs/version-6.x/a11y.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/a11y.md
rename to docs/versioned_docs/version-6.x/a11y.md
diff --git a/docs/versioned_docs/version-6.0/app-configuration-redwood-toml.md b/docs/versioned_docs/version-6.x/app-configuration-redwood-toml.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/app-configuration-redwood-toml.md
rename to docs/versioned_docs/version-6.x/app-configuration-redwood-toml.md
index c009b12d1ac1..fee88a7b9cdc 100644
--- a/docs/versioned_docs/version-6.0/app-configuration-redwood-toml.md
+++ b/docs/versioned_docs/version-6.x/app-configuration-redwood-toml.md
@@ -290,7 +290,7 @@ api | 🗒 Custom
api | "--------------------------e66d9a27b7c2b271\r\nContent-Disposition: attachment; name=\"image\"; filename=\"favicon.png\"\r\nContent-Type: image/png\r\n\r\n�PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0000 \u0000\u0000\u0000`�\r\n--------------------------e66d9a27b7c2b271--\r\n"
```
-:::caution File uploads only work in a serverful deploy
+:::warning File uploads only work in a serverful deploy
Serverless functions on Netlify or Vercel do not use this Fastify configuration.
They also have memory and execution time limits that don't lend themselves to handling file uploads of any practical size.
diff --git a/docs/versioned_docs/version-6.0/assets-and-files.md b/docs/versioned_docs/version-6.x/assets-and-files.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/assets-and-files.md
rename to docs/versioned_docs/version-6.x/assets-and-files.md
diff --git a/docs/versioned_docs/version-6.0/auth/auth0.md b/docs/versioned_docs/version-6.x/auth/auth0.md
similarity index 94%
rename from docs/versioned_docs/version-6.0/auth/auth0.md
rename to docs/versioned_docs/version-6.x/auth/auth0.md
index b7a47f6f1ae2..a028f9418152 100644
--- a/docs/versioned_docs/version-6.0/auth/auth0.md
+++ b/docs/versioned_docs/version-6.x/auth/auth0.md
@@ -28,7 +28,7 @@ But where in your Redwood app exactly?
Auth0 needs to know, and this setting tells it.
We'll keep things simple for now and make it "http://localhost:8910", but feel free to configure it as you wish.
-Paste "http://localhost:8910" in the text area below "Allowed Callback URLs", then click "Save Changes" at the bottom of the page.
+Paste "http://localhost:8910" in the text areas below "Allowed Callback URLs", "Allowed Logout URLs" and "Allowed Web Origins" then click "Save Changes" at the bottom of the page.
Copy this one over to your project's `.env` file too, as `AUTH0_REDIRECT_URI`.
Ok, just one more to go: under "Applications" in the nav on the left, click "APIs".
diff --git a/docs/versioned_docs/version-6.0/auth/azure.md b/docs/versioned_docs/version-6.x/auth/azure.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/auth/azure.md
rename to docs/versioned_docs/version-6.x/auth/azure.md
diff --git a/docs/versioned_docs/version-6.0/auth/clerk.md b/docs/versioned_docs/version-6.x/auth/clerk.md
similarity index 93%
rename from docs/versioned_docs/version-6.0/auth/clerk.md
rename to docs/versioned_docs/version-6.x/auth/clerk.md
index ed71df136f8e..06266d0b4774 100644
--- a/docs/versioned_docs/version-6.0/auth/clerk.md
+++ b/docs/versioned_docs/version-6.x/auth/clerk.md
@@ -4,7 +4,7 @@ sidebar_label: Clerk
# Clerk Authentication
-:::caution Did you set up Clerk a while ago?
+:::warning Did you set up Clerk a while ago?
If you set up Clerk a while ago, you may be using a deprecated `authDecoder` that's subject to rate limiting.
This decoder will be removed in the next major.
@@ -56,10 +56,15 @@ Lastly, in your project's `redwood.toml` file, include `CLERK_PUBLISHABLE_KEY` i
```
That should be enough; now, things should just work.
-Let's make sure: if this is a brand new project, generate a home page.
+Let's make sure: if this is a brand new project, generate a home page:
+
+```bash
+yarn rw g page Home /
+```
+
There we'll try to sign up by destructuring `signUp` from the `useAuth` hook (import that from `'src/auth'`). We'll also destructure and display `isAuthenticated` to see if it worked:
-```tsx title="web/src/pages/HomePage.tsx"
+```tsx title="web/src/pages/HomePage/HomePage.tsx"
import { useAuth } from 'src/auth'
const HomePage = () => {
@@ -76,11 +81,8 @@ const HomePage = () => {
}
```
-Clicking sign up should open a sign-up box:
-
-
+Clicking sign up should open a sign-up box and after you sign up, you should see `{"isAuthenticated":true}` on the page.
-After you sign up, you should see `{"isAuthenticated":true}` on the page.
## Customizing the session token
diff --git a/docs/versioned_docs/version-6.0/auth/custom.md b/docs/versioned_docs/version-6.x/auth/custom.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/auth/custom.md
rename to docs/versioned_docs/version-6.x/auth/custom.md
diff --git a/docs/versioned_docs/version-6.0/auth/dbauth.md b/docs/versioned_docs/version-6.x/auth/dbauth.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/auth/dbauth.md
rename to docs/versioned_docs/version-6.x/auth/dbauth.md
index 01d8c1d28f1e..b87d4cf3a9f3 100644
--- a/docs/versioned_docs/version-6.0/auth/dbauth.md
+++ b/docs/versioned_docs/version-6.x/auth/dbauth.md
@@ -337,7 +337,7 @@ yarn rw g secret
```
Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that.
-:::caution .env and Version Control
+:::warning .env and Version Control
The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars!
@@ -475,7 +475,7 @@ model UserCredential {
Run `yarn rw prisma migrate dev` to apply the changes to your database.
-:::caution Do Not Allow GraphQL Access to `UserCredential`
+:::warning Do Not Allow GraphQL Access to `UserCredential`
As you can probably tell by the name, this new model contains secret credential info for the user. You **should not** make this data publicly available by adding an SDL file to `api/src/graphql`.
diff --git a/docs/versioned_docs/version-6.0/auth/firebase.md b/docs/versioned_docs/version-6.x/auth/firebase.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/auth/firebase.md
rename to docs/versioned_docs/version-6.x/auth/firebase.md
diff --git a/docs/versioned_docs/version-6.0/auth/netlify.md b/docs/versioned_docs/version-6.x/auth/netlify.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/auth/netlify.md
rename to docs/versioned_docs/version-6.x/auth/netlify.md
diff --git a/docs/versioned_docs/version-6.0/auth/supabase.md b/docs/versioned_docs/version-6.x/auth/supabase.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/auth/supabase.md
rename to docs/versioned_docs/version-6.x/auth/supabase.md
diff --git a/docs/versioned_docs/version-6.0/auth/supertokens.md b/docs/versioned_docs/version-6.x/auth/supertokens.md
similarity index 58%
rename from docs/versioned_docs/version-6.0/auth/supertokens.md
rename to docs/versioned_docs/version-6.x/auth/supertokens.md
index d7d37bd0d740..8b0b6b97ca8a 100644
--- a/docs/versioned_docs/version-6.0/auth/supertokens.md
+++ b/docs/versioned_docs/version-6.x/auth/supertokens.md
@@ -11,18 +11,49 @@ yarn rw setup auth supertokens
```
This installs all the packages, writes all the files, and makes all the code modifications you need.
+
+:::info
+
+You may have noticed that in `api/src/functions/auth.ts` there's an import from `'supertokens-node/framework/awsLambda'`. This is fine, even if your app isn't running in a serverless environment like AWS Lambda. In "serverful" environments, Redwood automatically handles the translation between Fastify's request and reply objects and functions' AWS Lambda signature.
+
+:::
+
For a detailed explanation of all the api- and web-side changes that aren't exclusive to SuperTokens, see the top-level [Authentication](../authentication.md) doc.
For now, let's focus on SuperTokens's side of things.
When you run the setup command it configures your app to support both email+password logins as well as social auth logins (Apple, GitHub and Google). Working with those social auth logins does require quite a few environment variables. And SuperTokens itself needs a couple variables too. Thankfully SuperTokens makes this very easy to setup as they provide values we can use for testing.
-So just copy this to your project's `.env` file.
+# Environment variables
-```bash title=".env"
+The environment variables have to be added either to your project's `.env` file (when running in development environment), or to the environment variables of your hosting provider (when running in production).
+
+## Base setup
+
+```bash
+SUPERTOKENS_APP_NAME="Redwoodjs App" # this will be used in the email template for password reset or email verification emails.
SUPERTOKENS_JWKS_URL=http://localhost:8910/.redwood/functions/auth/jwt/jwks.json
+SUPERTOKENS_CONNECTION_URI=https://try.supertokens.io # set to the correct connection uri
+```
+
+## Production setup
+
+Assuming that your web side is hosted on `https://myapp.com`:
+
+```bash
+SUPERTOKENS_WEBSITE_DOMAIN=https://myapp.com
+SUPERTOKENS_JWKS_URL=https://myapp.com/.redwood/functions/auth/jwt/jwks.json
+```
-SUPERTOKENS_CONNECTION_URI=https://try.supertokens.io
+## Managed Supertokens service setup
+```bash
+SUPERTOKENS_API_KEY=your-api-key # The value can be omitted when self-hosting Supertokens
+```
+
+## Social login setup
+The following environment variables have to be set up (depending on the social login options):
+
+```bash
SUPERTOKENS_APPLE_CLIENT_ID=4398792-io.supertokens.example.service
SUPERTOKENS_APPLE_SECRET_KEY_ID=7M48Y4RYDL
SUPERTOKENS_APPLE_SECRET_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----
@@ -33,7 +64,24 @@ SUPERTOKENS_GOOGLE_CLIENT_ID=1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps
SUPERTOKENS_GOOGLE_CLIENT_SECRET=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
```
-That should be enough; now, things should just work.
+## `redwood.toml` setup
+
+Make sure to modify `redwood.toml` to pass the required environment variables to the web side:
+
+```toml
+[web]
+...
+includeEnvironmentVariables = [
+ 'SUPERTOKENS_WEBSITE_DOMAIN',
+ 'SUPERTOKENS_API_DOMAIN',
+ 'SUPERTOKENS_API_GATEWAY_PATH',
+ 'SUPERTOKENS_APP_NAME'
+]
+```
+
+
+# Page setup
+
Let's make sure: if this is a brand new project, generate a home page.
There we'll try to sign up by destructuring `signUp` from the `useAuth` hook (import that from `'src/auth'`). We'll also destructure and display `isAuthenticated` to see if it worked:
@@ -65,3 +113,7 @@ Clicking sign up should navigate you to `/auth` where SuperToken's default login
After you sign up, you should be redirected back to your Redwood app, and you should see `{"isAuthenticated":true}` on the page.
+
+## Troubleshooting
+
+If going to `http://localhost:8910/auth` results in the plain Javascript file being served instead of the expected auth page, rename the `web/src/auth.tsx` file to `web/src/authentication.tsx`, and update the imports (related to https://github.com/redwoodjs/redwood/issues/9740).
diff --git a/docs/versioned_docs/version-6.0/authentication.md b/docs/versioned_docs/version-6.x/authentication.md
similarity index 96%
rename from docs/versioned_docs/version-6.0/authentication.md
rename to docs/versioned_docs/version-6.x/authentication.md
index d026aa91bb17..88506057eb2f 100644
--- a/docs/versioned_docs/version-6.0/authentication.md
+++ b/docs/versioned_docs/version-6.x/authentication.md
@@ -5,7 +5,7 @@ description: Set up an authentication provider
# Authentication
Redwood has integrated auth end to end, from the web side to the api side.
-On the web side, the router can protect pages via the `Private` component (or the `Set` component via the `private` prop), and even restrict access at the role-level.
+On the web side, the router can protect pages via the `PrivateSet` component, and even restrict access at the role-level.
And if you'd prefer to work with the primitives, the `useAuth` hook exposes all the pieces to build the experience you want.
Likewise, the api side is locked down by default: all SDLs are generated with the `@requireAuth` directive, ensuring that making things publicly available is something that you opt in to rather than out of.
@@ -129,10 +129,8 @@ const Routes = () => {
- // highlight-start
+ // highlight-next-line
- {/* Or... */}
- // highlight-end
@@ -144,7 +142,7 @@ const Routes = () => {
You can also restrict access by role by passing a role or an array of roles to the `PrivateSet` component's `hasRole` prop:
```tsx title="web/src/Routes.tsx"
-import { Router, Route, PrivateSet, Set } from '@redwoodjs/router'
+import { Router, Route, PrivateSet } from '@redwoodjs/router'
const Routes = () => {
return (
@@ -158,9 +156,9 @@ const Routes = () => {
// highlight-next-line
-
+
-
+
// highlight-next-line
diff --git a/docs/versioned_docs/version-6.0/builds.md b/docs/versioned_docs/version-6.x/builds.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/builds.md
rename to docs/versioned_docs/version-6.x/builds.md
diff --git a/docs/versioned_docs/version-6.0/cells.md b/docs/versioned_docs/version-6.x/cells.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/cells.md
rename to docs/versioned_docs/version-6.x/cells.md
index 20c92ace2f37..0a7377cd6b54 100644
--- a/docs/versioned_docs/version-6.0/cells.md
+++ b/docs/versioned_docs/version-6.x/cells.md
@@ -409,4 +409,4 @@ export const Cell = () => {
That's a lot of code. A lot of imperative code too.
-We're basically just dumping the contents of [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/createCell.tsx) into this file. Can you imagine having to do this every time you wanted to fetch data that might be delayed in responding? Yikes.
+We're basically just dumping the contents of [createCell.tsx](https://github.com/redwoodjs/redwood/blob/main/packages/web/src/components/cell/createCell.tsx) into this file. Can you imagine having to do this every time you wanted to fetch data that might be delayed in responding? Yikes.
diff --git a/docs/versioned_docs/version-6.0/cli-commands.md b/docs/versioned_docs/version-6.x/cli-commands.md
similarity index 97%
rename from docs/versioned_docs/version-6.0/cli-commands.md
rename to docs/versioned_docs/version-6.x/cli-commands.md
index c6fa2b18688c..0215aaa8b2ea 100644
--- a/docs/versioned_docs/version-6.0/cli-commands.md
+++ b/docs/versioned_docs/version-6.x/cli-commands.md
@@ -290,7 +290,7 @@ The following command will build, apply Prisma DB migrations, and skip data migr
yarn redwood deploy netlify --no-data-migrate
```
-:::caution
+:::warning
While you may be tempted to use the [Netlify CLI](https://cli.netlify.com) commands to [build](https://cli.netlify.com/commands/build) and [deploy](https://cli.netlify.com/commands/deploy) your project directly from you local project directory, doing so **will lead to errors when deploying and/or when running functions**. I.e. errors in the function needed for the GraphQL server, but also other serverless functions.
The main reason for this is that these Netlify CLI commands simply build and deploy -- they build your project locally and then push the dist folder. That means that when building a RedwoodJS project, the [Prisma client is generated with binaries matching the operating system at build time](https://cli.netlify.com/commands/link) -- and not the [OS compatible](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options) with running functions on Netlify. Your Prisma client engine may be `darwin` for OSX or `windows` for Windows, but it needs to be `debian-openssl-1.1.x` or `rhel-openssl-1.1.x`. If the client is incompatible, your functions will fail.
@@ -771,7 +771,7 @@ $ /redwood-app/node_modules/.bin/redwood g layout user
Done in 1.00s.
```
-A layout will just export it's children:
+A layout will just export its children:
```jsx title="./web/src/layouts/UserLayout/UserLayout.test.js"
const UserLayout = ({ children }) => {
@@ -1722,6 +1722,7 @@ yarn redwood setup
| `deploy` | Set up a deployment configuration for a provider |
| `generator` | Copy default Redwood generator templates locally for customization |
| `i18n` | Set up i18n |
+| `package` | Peform setup actions by running a third-party npm package |
| `tsconfig` | Add relevant tsconfig so you can start using TypeScript |
| `ui` | Set up a UI design or style library |
| `webpack` | Set up a webpack config file in your project so you can add custom config |
@@ -1779,7 +1780,7 @@ yarn redwood setup cache
### setup custom-web-index
-:::caution This command only applies to projects using Webpack
+:::warning This command only applies to projects using Webpack
As of v6, all Redwood projects use Vite by default.
When switching projects to Vite, we made the decision to add the the entry file, `web/src/entry.client.{jsx,tsx}`, back to projects.
@@ -1903,6 +1904,51 @@ In order to use [Netlify Dev](https://www.netlify.com/products/dev/) you need to
> Note: To detect the RedwoodJS framework, please use netlify-cli v3.34.0 or greater.
+### setup mailer
+
+This command adds the necessary packages and files to get started using the RedwoodJS mailer. By default it also creates an example mail template which can be skipped with the `--skip-examples` flag.
+
+```
+yarn redwood setup mailer
+```
+
+| Arguments & Options | Description |
+| :---------------------- | :----------------------------- |
+| `--force, -f` | Overwrite existing files |
+| `--skip-examples` | Do not include example content, such as a React email template |
+
+### setup package
+
+This command takes a published npm package that you specify, performs some compatibility checks, and then executes its bin script. This allows you to use third-party packages that can provide you with an easy-to-use setup command for the particular functionality they provide.
+
+This command behaves similarly to `yarn dlx` but will attempt to confirm compatibility between the package you are attempting to run and the current version of Redwood you are running. You can bypass this check by passing the `--force` flag if you feel you understand any potential compatibility issues.
+
+```
+yarn redwood setup package
+```
+
+| Arguments & Options | Description |
+| :------------------ | :----------------------- |
+| `--force, -f` | Forgo compatibility checks |
+
+**Usage**
+
+Run the made up `@redwoodjs/setup-example` package:
+```bash
+~/redwood-app$ yarn rw setup package @redwoodjs/setup-example
+```
+
+Run the same package but using a particular npm tag and avoiding any compatibility checks:
+```bash
+~/redwood-app$ yarn rw setup package @redwoodjs/setup-example@beta --force
+```
+
+**Compatibility Checks**
+
+We perform a simple compatibility check in an attempt to make you aware of potential compatibility issues with setup packages you might wish to run. This works by examining the version of `@redwoodjs/core` you are using within your root `package.json`. We compare this value with a compatibility range the npm package specifies in the `engines.redwoodjs` field of its own `package.json`. If the version of `@redwoodjs/core` you are using falls outside of the compatibility range specified by the package you are attempting to run, we will warn you and ask you to confirm that you wish to continue.
+
+It's the author of the npm package's responsibility to specify the correct compatibility range, so **you should always research the packages you use with this command**. Especially since they will be executing code on your machine!
+
### setup tsconfig
Add a `tsconfig.json` to both the web and api sides so you can start using [TypeScript](typescript/index).
diff --git a/docs/versioned_docs/version-6.0/connection-pooling.md b/docs/versioned_docs/version-6.x/connection-pooling.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/connection-pooling.md
rename to docs/versioned_docs/version-6.x/connection-pooling.md
diff --git a/docs/versioned_docs/version-6.0/contributing-overview.md b/docs/versioned_docs/version-6.x/contributing-overview.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/contributing-overview.md
rename to docs/versioned_docs/version-6.x/contributing-overview.md
diff --git a/docs/versioned_docs/version-6.0/contributing-walkthrough.md b/docs/versioned_docs/version-6.x/contributing-walkthrough.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/contributing-walkthrough.md
rename to docs/versioned_docs/version-6.x/contributing-walkthrough.md
diff --git a/docs/versioned_docs/version-6.0/cors.md b/docs/versioned_docs/version-6.x/cors.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/cors.md
rename to docs/versioned_docs/version-6.x/cors.md
diff --git a/docs/versioned_docs/version-6.0/create-redwood-app.md b/docs/versioned_docs/version-6.x/create-redwood-app.md
similarity index 95%
rename from docs/versioned_docs/version-6.0/create-redwood-app.md
rename to docs/versioned_docs/version-6.x/create-redwood-app.md
index 2f1e600f5eda..c6244ac0e837 100644
--- a/docs/versioned_docs/version-6.0/create-redwood-app.md
+++ b/docs/versioned_docs/version-6.x/create-redwood-app.md
@@ -24,7 +24,7 @@ node -v
If you need to update your version of Node or run multiple versions of Node, we recommend installing nvm and have [documentation about how to get up and running.](./how-to/using-nvm)
-You also need to have yarn version 1.15 or higher installed. To see what version of yarn you're running, you can run the following command in your terminal:
+You also need to have yarn version 1.22.21 or higher installed. To see what version of yarn you're running, you can run the following command in your terminal:
```terminal
yarn -v
diff --git a/docs/versioned_docs/version-6.0/custom-web-index.md b/docs/versioned_docs/version-6.x/custom-web-index.md
similarity index 96%
rename from docs/versioned_docs/version-6.0/custom-web-index.md
rename to docs/versioned_docs/version-6.x/custom-web-index.md
index 775fa2c891a8..8fd30f548856 100644
--- a/docs/versioned_docs/version-6.0/custom-web-index.md
+++ b/docs/versioned_docs/version-6.x/custom-web-index.md
@@ -4,7 +4,7 @@ description: Change how App mounts to the DOM
# Custom Web Index
-:::caution This doc only applies to projects using Webpack
+:::warning This doc only applies to projects using Webpack
As of v6, all Redwood projects use Vite by default.
When switching projects to Vite, we made the decision to add the the entry file, `web/src/entry.client.{jsx,tsx}`, back to projects.
diff --git a/docs/versioned_docs/version-6.0/data-migrations.md b/docs/versioned_docs/version-6.x/data-migrations.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/data-migrations.md
rename to docs/versioned_docs/version-6.x/data-migrations.md
diff --git a/docs/versioned_docs/version-6.0/deploy/baremetal.md b/docs/versioned_docs/version-6.x/deploy/baremetal.md
similarity index 97%
rename from docs/versioned_docs/version-6.0/deploy/baremetal.md
rename to docs/versioned_docs/version-6.x/deploy/baremetal.md
index 4703a7910a37..aec882f28381 100644
--- a/docs/versioned_docs/version-6.0/deploy/baremetal.md
+++ b/docs/versioned_docs/version-6.x/deploy/baremetal.md
@@ -22,7 +22,7 @@ Subsequent deploys:
yarn rw deploy baremetal production
```
-:::caution Deploying to baremetal is an advanced topic
+:::warning Deploying to baremetal is an advanced topic
If you haven't done any kind of remote server work before, you may be in a little over your head to start with. But don't worry: until relatively recently (cloud computing, serverless, lambda functions) this is how all websites were deployed, so we've got a good 30 years of experience getting this working!
@@ -173,7 +173,7 @@ This lists a single server, in the `production` environment, providing the hostn
* `branch` - [optional] The branch to deploy (defaults to `main`)
* `keepReleases` - [optional] The number of previous releases to keep on the server, including the one currently being served (defaults to 5)
-The easiest connection method is generally to include your own public key in the server's `~/.ssh/authorized_keys` file, [enable agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding), and then set `agentForward = true` in `deploy.toml`. This will allow you to use your own credentials when pulling code from GitHub (required for private repos). Otherwise you can create a [deploy key](https://docs.github.com/en/developers/overview/managing-deploy-keys) and keep it on the server.
+The easiest connection method is generally to include your own public key in the server's `~/.ssh/authorized_keys` mannually or by running `ssh-copy-id user@server.com` from your local machine, [enable agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding), and then set `agentForward = true` in `deploy.toml`. This will allow you to use your own credentials when pulling code from GitHub (required for private repos). Otherwise you can create a [deploy key](https://docs.github.com/en/developers/overview/managing-deploy-keys) and keep it on the server.
#### Using Environment Variables in `deploy.toml`
@@ -274,7 +274,7 @@ sudo chown deploy:deploy /var/www/myapp
You'll want to create an `.env` file in this directory containing any environment variables that are needed by your app (like `DATABASE_URL` at a minimum). This will be symlinked to each release directory so that it's available as the app expects (in the root directory of the codebase).
-:::caution SSH and Non-interactive Sessions
+:::warning SSH and Non-interactive Sessions
The deployment process uses a '[non-interactive](https://tldp.org/LDP/abs/html/intandnonint.html)' SSH session to run commands on the remote server. A non-interactive session will often load a minimal amount of settings for better compatibility and speed. In some versions of Linux `.bashrc` by default does not load (by design) from a non-interactive session. This can lead to `yarn` (or other commands) not being found by the deployment script, even though they are in your path, because additional ENV vars are set in `~/.bashrc` which provide things like NPM paths and setup.
@@ -418,7 +418,7 @@ pm2 startup
You will see some output similar to the output below. We care about the output after "copy/paste the following command:" You'll need to do just that: copy the command starting with `sudo` and then paste and execute it. *Note* this command uses `sudo` so you'll need the root password to the machine in order for it to complete successfully.
-:::caution
+:::warning
The below text is *example* output, yours will be different, don't copy and paste ours!
@@ -467,7 +467,7 @@ You can define your before/after commands in three different places:
* Environment specific - runs for only a single environment
* Server specific - runs for only a single server in a single environment
-:::caution
+:::warning
Custom commands are run in the new **deploy** directory, not the root of your application directory. During a deploy the `current` symlink will point to the previous directory while your code is executed in the new one, before the `current` symlink location is updated.
diff --git a/docs/versioned_docs/version-6.0/deploy/coherence.md b/docs/versioned_docs/version-6.x/deploy/coherence.md
similarity index 97%
rename from docs/versioned_docs/version-6.0/deploy/coherence.md
rename to docs/versioned_docs/version-6.x/deploy/coherence.md
index 970eaa98fa31..a2b9ec845d2a 100644
--- a/docs/versioned_docs/version-6.0/deploy/coherence.md
+++ b/docs/versioned_docs/version-6.x/deploy/coherence.md
@@ -17,7 +17,7 @@ To deploy to Coherence, your Redwood project needs to be hosted on GitHub and yo
## Coherence Deploy
-:::caution Prerender doesn't work with Coherence yet
+:::warning Prerender doesn't work with Coherence yet
You can see its current status and follow updates here on GitHub: https://github.com/redwoodjs/redwood/issues/8333.
diff --git a/docs/versioned_docs/version-6.0/deploy/edgio.md b/docs/versioned_docs/version-6.x/deploy/edgio.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/deploy/edgio.md
rename to docs/versioned_docs/version-6.x/deploy/edgio.md
diff --git a/docs/versioned_docs/version-6.0/deploy/flightcontrol.md b/docs/versioned_docs/version-6.x/deploy/flightcontrol.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/deploy/flightcontrol.md
rename to docs/versioned_docs/version-6.x/deploy/flightcontrol.md
diff --git a/docs/versioned_docs/version-6.0/deploy/introduction.md b/docs/versioned_docs/version-6.x/deploy/introduction.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/deploy/introduction.md
rename to docs/versioned_docs/version-6.x/deploy/introduction.md
diff --git a/docs/versioned_docs/version-6.0/deploy/netlify.md b/docs/versioned_docs/version-6.x/deploy/netlify.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/deploy/netlify.md
rename to docs/versioned_docs/version-6.x/deploy/netlify.md
index 4f5c87e217bd..ad62b9b5d8f5 100644
--- a/docs/versioned_docs/version-6.0/deploy/netlify.md
+++ b/docs/versioned_docs/version-6.x/deploy/netlify.md
@@ -13,7 +13,7 @@ If you simply want to experience the Netlify deployment process without a databa
3. run the command `yarn rw setup deploy netlify` and commit and push changes
4. use the Netlify [Quick Start](https://app.netlify.com/signup) to deploy
-:::caution
+:::warning
While you may be tempted to use the [Netlify CLI](https://cli.netlify.com) commands to [build](https://cli.netlify.com/commands/build) and [deploy](https://cli.netlify.com/commands/deploy) your project directly from you local project directory, doing so **will lead to errors when deploying and/or when running functions**. I.e. errors in the function needed for the GraphQL server, but also other serverless functions.
The main reason for this is that these Netlify CLI commands simply build and deploy -- they build your project locally and then push the dist folder. That means that when building a RedwoodJS project, the [Prisma client is generated with binaries matching the operating system at build time](https://cli.netlify.com/commands/link) -- and not the [OS compatible](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options) with running functions on Netlify. Your Prisma client engine may be `darwin` for OSX or `windows` for Windows, but it needs to be `debian-openssl-1.1.x` or `rhel-openssl-1.1.x`. If the client is incompatible, your functions will fail.
diff --git a/docs/versioned_docs/version-6.0/deploy/render.md b/docs/versioned_docs/version-6.x/deploy/render.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/deploy/render.md
rename to docs/versioned_docs/version-6.x/deploy/render.md
diff --git a/docs/versioned_docs/version-6.0/deploy/serverless.md b/docs/versioned_docs/version-6.x/deploy/serverless.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/deploy/serverless.md
rename to docs/versioned_docs/version-6.x/deploy/serverless.md
diff --git a/docs/versioned_docs/version-6.0/deploy/vercel.md b/docs/versioned_docs/version-6.x/deploy/vercel.md
similarity index 88%
rename from docs/versioned_docs/version-6.0/deploy/vercel.md
rename to docs/versioned_docs/version-6.x/deploy/vercel.md
index 38beee72ec6d..5f4e6e33fe04 100644
--- a/docs/versioned_docs/version-6.0/deploy/vercel.md
+++ b/docs/versioned_docs/version-6.x/deploy/vercel.md
@@ -73,3 +73,18 @@ Go ahead, click that "Visit" button. You’ve earned it 🎉
From the Vercel Dashboard you can access the full settings and information for your Redwood App. The default settings seem to work just fine for most Redwood projects. Do take a look around, but be sure check out the [docs as well](https://vercel.com/docs).
From now on, each time you push code to your git repo, Vercel will automatically trigger a deploy of the new code. You can also manually redeploy if you select "Deployments", then the specific deployment from the list, and finally the "Redeploy" option from the vertical dots menu next to "Visit".
+
+## vercel.json configuration
+
+By default, API requests in Vercel have a timeout limit of 15 seconds. To extend this duration, you can modify the vercel.json file by inserting the code snippet provided below. Please be aware that the ability to increase the timeout limit is exclusive to Pro plan subscribers. Additionally, it is important to note that the timeout can be increased up to a maximum of 300 seconds, which is equivalent to 5 minutes.
+
+```
+{
+ "functions": {
+ "api/src/functions/graphql.*": {
+ "maxDuration": 120,
+ "runtime": "@vercel/redwood@2.0.5"
+ }
+ }
+}
+```
diff --git a/docs/versioned_docs/version-6.0/directives.md b/docs/versioned_docs/version-6.x/directives.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/directives.md
rename to docs/versioned_docs/version-6.x/directives.md
diff --git a/docs/versioned_docs/version-6.x/docker.md b/docs/versioned_docs/version-6.x/docker.md
new file mode 100644
index 000000000000..2a3587bba87a
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/docker.md
@@ -0,0 +1,468 @@
+---
+description: Redwood's Dockerfile
+---
+
+# Docker
+
+:::note The Dockerfile is experimental
+
+Redwood's Dockerfile is the collective effort of several hard-working community members.
+We've worked hard to optimize it, but expect changes as we collaborate with users and deploy providers.
+
+:::
+
+If you're not familiar with Docker, we recommend going through their [getting started](https://docs.docker.com/get-started/) documentation.
+
+## Set up
+
+To get started, run the setup command:
+
+```
+yarn rw experimental setup-docker
+```
+
+The setup commands does several things:
+- writes four files: `Dockerfile`, `.dockerignore`, `docker-compose.dev.yml`, and `docker-compose.prod.yml`
+- adds the `@redwoodjs/api-server` and `@redwoodjs/web-server` packages to the api and web sides respectively
+- edits the `browser.open` setting in the `redwood.toml` (right now, if it's set to `true`, it'll break the dev server when running the `docker-compose.dev.yml`)
+
+## Usage
+
+You can start the dev compose file with:
+
+```
+docker compose -f ./docker-compose.dev.yml up
+```
+
+And the prod compose file with:
+
+```
+docker compose -f ./docker-compose.prod.yml up
+```
+
+:::info make sure to specify build args
+
+If your api side or web side depend on env vars at build time, you may need to supply them as `--build-args`, or in the compose files.
+
+This is often the most tedious part of setting up Docker. Have ideas of how it could be better? Let us know on the [forums](https://community.redwoodjs.com/)!
+
+:::
+
+The first time you do this, you'll have to use the `console` stage to go in and migrate the database—just like you would with a Redwood app on your machine:
+
+```
+docker compose -f ./docker-compose.dev.yml run --rm -it console /bin/bash
+root@...:/home/node/app# yarn rw prisma migrate dev
+```
+
+## The Dockerfile in detail
+
+The documentation here goes through and explains every line of Redwood's Dockerfile.
+If you'd like to see the whole Dockerfile for reference, you can find it [here](https://github.com/redwoodjs/redwood/tree/main/packages/cli/src/commands/experimental/templates/docker/Dockerfile) or by setting it up in your project: `yarn rw experimental setup-docker`.
+
+Redwood takes advantage of [Docker's multi-stage build support](https://docs.docker.com/build/building/multi-stage/) to keep the final production images lean.
+
+### The `base` stage
+
+The `base` stage installs dependencies.
+It's used as the base image for the build stages and the `console` stage.
+
+```Dockerfile
+FROM node:18-bookworm-slim as base
+```
+
+We use a Node.js 18 image as the base image because that's the version Redwood targets.
+"bookworm" is the codename for the current stable distribution of Debian (version 12).
+Lastly, the "slim" variant of the `node:18-bookworm` image only includes what Node.js needs which reduces the image's size while making it more secure.
+
+:::tip Why not alpine?
+
+While alpine may be smaller, it uses musl, a different C standard library.
+In developing this Dockerfile, we prioritized security over size.
+
+If you know what you're doing feel free to change this—it's your Dockerfile now!
+Just remember to change the `apt-get` instructions further down too if needed.
+
+:::
+
+Moving on, next we have `corepack enable`:
+
+```Dockerfile
+RUN corepack enable
+```
+
+[Corepack](https://nodejs.org/docs/latest-v18.x/api/corepack.html), Node's manager for package managers, needs to be enabled so that Yarn can use the `packageManager` field in your project's root `package.json` to pick the right version of itself.
+If you'd rather check in the binary, you still can, but you'll need to remember to copy it over (i.e. `COPY --chown=node:node .yarn/releases .yarn/releases`).
+
+```Dockerfile
+RUN apt-get update && apt-get install -y \
+ openssl \
+ # python3 make gcc \
+ && rm -rf /var/lib/apt/lists/*
+```
+
+The `node:18-bookworm-slim` image doesn't have [OpenSSL](https://www.openssl.org/), which [seems to be a bug](https://github.com/nodejs/docker-node/issues/1919).
+(It was included in the "bullseye" image, the codename for Debian 11.)
+On Linux, [Prisma needs OpenSSL](https://www.prisma.io/docs/reference/system-requirements#linux-runtime-dependencies), so we install it here via Ubuntu's package manager APT.
+Python and its dependencies are there ready to be uncommented if you need them. See the [Troubleshooting](#python) section for more information.
+
+[It's recommended](https://docs.docker.com/develop/develop-images/instructions/#apt-get) to combine `apt-get update` and `apt-get install -y` in the same `RUN` statement for cache busting.
+After installing, we clean up the apt cache to keep the layer lean. (Running `apt-get clean` isn't required—[official Debian images do it automatically](https://github.com/moby/moby/blob/03e2923e42446dbb830c654d0eec323a0b4ef02a/contrib/mkimage/debootstrap#L82-L105).)
+
+```Dockerfile
+USER node
+```
+
+This and subsequent `chown` options in `COPY` instructions are for security.
+[Services that can run without privileges should](https://docs.docker.com/develop/develop-images/instructions/#user).
+The Node.js image includes a user, `node`, created with an explicit `uid` and `gid` (`1000`).
+We reuse it.
+
+```Dockerfile
+WORKDIR /home/node/app
+
+COPY --chown=node:node .yarnrc.yml .
+COPY --chown=node:node package.json .
+COPY --chown=node:node api/package.json api/
+COPY --chown=node:node web/package.json web/
+COPY --chown=node:node yarn.lock .
+```
+
+Here we copy the minimum set of files that the `yarn install` step needs.
+The order isn't completely arbitrary—it tries to maximize [Docker's layer caching](https://docs.docker.com/build/cache/).
+We expect `yarn.lock` to change more than the `package.json`s and the `package.json`s to change more than `.yarnrc.yml`.
+That said, it's hard to argue that these files couldn't be arranged differently, or that the `COPY` instructions couldn't be combined.
+The important thing is that they're all here, before the `yarn install` step:
+
+```Dockerfile
+RUN mkdir -p /home/node/.yarn/berry/index
+RUN mkdir -p /home/node/.cache
+
+RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
+ --mount=type=cache,target=/home/node/.cache,uid=1000 \
+ CI=1 yarn install
+```
+
+This step installs all your project's dependencies—production and dev.
+Since we use multi-stage builds, your production images won't pay for the dev dependencies installed in this step.
+The build stages need the dev dependencies.
+
+The `mkdir` steps are a workaround for a permission error. We're working on removing them, but for now if you remove them the install step will probably fail.
+
+This step is a bit more involved than the others.
+It uses a [cache mount](https://docs.docker.com/build/cache/#use-your-package-manager-wisely).
+Yarn operates in three steps: resolution, fetch, and link.
+If you're not careful, the cache for the fetch step basically doubles the number of `node_modules` installed on disk.
+We could disable it all together, but by using a cache mount, we can still get the benefits without paying twice.
+We set it to the default directory here, but you can change its location in `.yarnrc.yml`.
+If you've done so you'll have to change it here too.
+
+One more thing to note: without setting `CI=1`, depending on the deploy provider, yarn may think it's in a TTY, making the logs difficult to read. With this set, yarn adapts accordingly.
+Enabling CI enables [immutable installs](https://v3.yarnpkg.com/configuration/yarnrc#enableImmutableInstalls) and [inline builds](https://v3.yarnpkg.com/configuration/yarnrc#enableInlineBuilds), both of which are highly recommended.
+
+```Dockerfile
+COPY --chown=node:node redwood.toml .
+COPY --chown=node:node graphql.config.js .
+COPY --chown=node:node .env.defaults .env.defaults
+```
+
+We'll need these config files for the build and production stages.
+The `redwood.toml` file is Redwood's de-facto config file.
+Both the build and serve stages read it to enable and configure functionality.
+
+:::warning `.env.defaults` is ok to include but `.env` is not
+
+If you add a secret to the Dockerfile, it can be excavated.
+While it's technically true that multi stage builds add a sort of security layer, it's not a best practice.
+Leave them out and look to your deploy provider for further configuration.
+
+:::
+
+### The `api_build` stage
+
+The `api_build` stage builds the api side:
+
+```Dockerfile
+FROM base as api_build
+
+# If your api side build relies on build-time environment variables,
+# specify them here as ARGs.
+#
+# ARG MY_BUILD_TIME_ENV_VAR
+
+COPY --chown=node:node api api
+RUN yarn rw build api
+```
+
+After the work we did in the base stage, building the api side amounts to copying in the api directory and running `yarn rw build api`.
+
+### The `api_serve` stage
+
+The `api_serve` stage serves your GraphQL api and functions:
+
+```Dockerfile
+FROM node:18-bookworm-slim as api_serve
+
+RUN corepack enable
+
+RUN apt-get update && apt-get install -y \
+ openssl \
+ # python3 make gcc \
+ && rm -rf /var/lib/apt/lists/*
+```
+
+We don't start from the `base` stage, but begin anew with the `node:18-bookworm-slim` image.
+Since this is a production stage, it's important for it to be as small as possible.
+Docker's [multi-stage builds](https://docs.docker.com/build/building/multi-stage/) enables this.
+
+```Dockerfile
+USER node
+WORKDIR /home/node/app
+
+COPY --chown=node:node .yarnrc.yml .yarnrc.yml
+COPY --chown=node:node package.json .
+COPY --chown=node:node api/package.json api/
+COPY --chown=node:node yarn.lock yarn.lock
+```
+
+Like other `COPY` instructions, ordering these files with care enables layering caching.
+
+```Dockerfile
+RUN mkdir -p /home/node/.yarn/berry/index
+RUN mkdir -p /home/node/.cache
+
+RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
+ --mount=type=cache,target=/home/node/.cache,uid=1000 \
+ CI=1 yarn workspaces focus api --production
+```
+
+This is a critical step for image size.
+We don't use the regular `yarn install` command.
+Using the [official workspaces plugin](https://github.com/yarnpkg/berry/tree/master/packages/plugin-workspace-tools)—which is included by default in yarn v4—we "focus" on the api workspace, only installing its production dependencies.
+
+The cache mount will be populated at this point from the install in the `base` stage, so the fetch step should fly by.
+
+```Dockerfile
+COPY --chown=node:node redwood.toml .
+COPY --chown=node:node graphql.config.js .
+COPY --chown=node:node .env.defaults .env.defaults
+
+COPY --chown=node:node --from=api_build /home/node/app/api/dist /home/node/app/api/dist
+COPY --chown=node:node --from=api_build /home/node/app/api/db /home/node/app/api/db
+COPY --chown=node:node --from=api_build /home/node/app/node_modules/.prisma /home/node/app/node_modules/.prisma
+```
+
+Here's where we really take advantage of multi-stage builds by copying from the `api_build` stage.
+At this point all the building has been done. Now we can just grab the artifacts without having to lug around the dev dependencies.
+
+There's one more thing that was built: the prisma client in `node_modules/.prisma`.
+We need to grab it too.
+
+```Dockerfile
+ENV NODE_ENV=production
+
+CMD [ "node_modules/.bin/rw-server", "api", "--load-env-files" ]
+```
+
+Lastly, the default command is to start the api server using the bin from the `@redwoodjs/api-server` package.
+You can override this command if you have more specific needs.
+
+Note that the Redwood CLI isn't available anymore. (It's a dev dependency.)
+To access the server bin, we have to find its path in `node_modules`.
+Though this is somewhat discouraged in modern yarn, since we're using the `node-modules` node linker, it's in `node_modules/.bin`.
+
+### The `web_build` stage
+
+This `web_build` builds the web side:
+
+```Dockerfile
+FROM base as web_build
+
+COPY --chown=node:node web web
+RUN yarn rw build web --no-prerender
+```
+
+After the work we did in the base stage, building the web side amounts to copying in the web directory and running `yarn rw build web`.
+
+This stage is a bit of a simplification.
+It foregoes Redwood's prerendering (SSG) capability.
+Prerendering is a little trickier; see [the `web_prerender_build` stage](#the-web_prerender_build-stage).
+
+If you've included environment variables in your `redwood.toml`'s `web.includeEnvironmentVariables` field, you'll want to specify them as ARGs here.
+The setup command should've inlined them for you.
+
+### The `web_prerender_build` stage
+
+The `web_prerender_build` stage builds the web side with prerender.
+
+```Dockerfile
+FROM api_build as web_build_with_prerender
+
+COPY --chown=node:node web web
+RUN yarn rw build web
+```
+
+Building the web side with prerendering poses a challenge.
+Prerender needs the api side around to get data for your Cells and route hooks.
+The key line here is the first one—this stage uses the `api_build` stage as its base image.
+
+### The `web_serve` stage
+
+```Dockerfile
+FROM node:18-bookworm-slim as web_serve
+
+RUN corepack enable
+
+USER node
+WORKDIR /home/node/app
+
+COPY --chown=node:node .yarnrc.yml .
+COPY --chown=node:node package.json .
+COPY --chown=node:node web/package.json web/
+COPY --chown=node:node yarn.lock .
+
+RUN mkdir -p /home/node/.yarn/berry/index
+RUN mkdir -p /home/node/.cache
+
+RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
+ --mount=type=cache,target=/home/node/.cache,uid=1000 \
+ CI=1 yarn workspaces focus web --production
+
+COPY --chown=node:node redwood.toml .
+COPY --chown=node:node graphql.config.js .
+COPY --chown=node:node .env.defaults .env.defaults
+
+COPY --chown=node:node --from=web_build /home/node/app/web/dist /home/node/app/web/dist
+
+ENV NODE_ENV=production \
+ API_HOST=http://api:8911
+
+CMD "node_modules/.bin/rw-web-server" "--apiHost" "$API_HOST"
+```
+
+Most of this stage is similar to the `api_serve` stage, except that we're copying from the `web_build` stage instead of the `api_build`.
+(If you're prerendering, you'll want to change the `--from=web_build` to `--from=web_prerender_build`.)
+
+The binary we're using here to serve the web side is `rw-web-server` which comes from the `@redwoodjs/web-server` package.
+While this web server will be much more fully featured in the future, right now it's mostly just to get you going.
+Ideally you want to put a web server like Nginx or Caddy in front of it.
+
+Lastly, note that we use the shell form of `CMD` here for its variable expansion.
+
+### The `console` stage
+
+The `console` stage is an optional stage for debugging:
+
+```Dockerfile
+FROM base as console
+
+# To add more packages:
+#
+# ```
+# USER root
+#
+# RUN apt-get update && apt-get install -y \
+# curl
+#
+# USER node
+# ```
+
+COPY --chown=node:node api api
+COPY --chown=node:node web web
+COPY --chown=node:node scripts scripts
+```
+
+The console stage completes the base stage by copying in the rest of your Redwood app.
+But then it pretty much leaves you to your own devices.
+The intended way to use it is to create an ephemeral container by starting a shell like `/bin/bash` in the image built by targeting this stage:
+
+```bash
+# Build the console image:
+docker build . -t console --target console
+# Start an ephemeral container from it:
+docker run --rm -it console /bin/bash
+```
+
+As the comment says, feel free to add more packages.
+We intentionally kept them to a minimum in the base stage, but you shouldn't worry about the size of the image here.
+
+## Troubleshooting
+
+### Python
+
+We tried to make the Dockerfile as lean as possible.
+In some cases, that means we excluded a dependency your project needs.
+And by far the most common is Python.
+
+During a stage's `yarn install` step (`RUN ... yarn install`), if you see an error like the following:
+
+```
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python Python is not set from command line or npm configuration
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python Python is not set from environment variable PYTHON
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python checking if "python3" can be used
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - executable path is ""
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - "" could not be run
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python checking if "python" can be used
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - executable path is ""
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - "" could not be run
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python **********************************************************
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python You need to install the latest version of Python.
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python Node-gyp should be able to find and use Python. If not,
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python you can try one of the following options:
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - Use the switch --python="/path/to/pythonexecutable"
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python (accepted by both node-gyp and npm)
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - Set the environment variable PYTHON
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python - Set the npm configuration variable python:
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python npm config set python "/path/to/pythonexecutable"
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python For more information consult the documentation at:
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python https://github.com/nodejs/node-gyp#installation
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python **********************************************************
+➤ YN0000: │ bufferutil@npm:4.0.8 STDERR gyp ERR! find Python
+```
+
+It's because your project depends on Python and the image doesn't provide it.
+
+It's easy to fix: just add `python3` and its dependencies (usually `make` and `gcc`):
+
+```diff
+ FROM node:18-bookworm-slim as base
+
+ RUN apt-get update && apt-get install -y \
+ openssl \
++ python3 make gcc \
+ && rm -rf /var/lib/apt/lists/*
+```
+
+Not sure why your project depends on Python? `yarn why` is your friend.
+From the error message, we know `bufferutil` couldn't build.
+But why do we have `bufferutil`?
+
+```
+yarn why bufferutil
+└─ websocket@npm:1.0.34
+ └─ bufferutil@npm:4.0.8 (via npm:^4.0.1)
+```
+
+`websocket` needs `bufferutil`. But why do we have `websocket`?
+Keep pulling the thread till you get to a top-level dependency:
+
+```
+yarn why websocket
+└─ @supabase/realtime-js@npm:2.8.4
+ └─ websocket@npm:1.0.34 (via npm:^1.0.34)
+
+yarn why @supabase/realtime-js
+└─ @supabase/supabase-js@npm:2.38.4
+ └─ @supabase/realtime-js@npm:2.8.4 (via npm:^2.8.4)
+
+yarn why @supabase/supabase-js
+├─ api@workspace:api
+│ └─ @supabase/supabase-js@npm:2.38.4 (via npm:^2.21.0)
+│
+└─ web@workspace:web
+ └─ @supabase/supabase-js@npm:2.38.4 (via npm:^2.21.0)
+```
+
+In this case, it looks like it's ultimately because of our auth provider, `@supabase/supabase-js`.
diff --git a/docs/versioned_docs/version-6.0/environment-variables.md b/docs/versioned_docs/version-6.x/environment-variables.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/environment-variables.md
rename to docs/versioned_docs/version-6.x/environment-variables.md
diff --git a/docs/versioned_docs/version-6.0/forms.md b/docs/versioned_docs/version-6.x/forms.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/forms.md
rename to docs/versioned_docs/version-6.x/forms.md
diff --git a/docs/versioned_docs/version-6.0/graphql.md b/docs/versioned_docs/version-6.x/graphql.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/graphql.md
rename to docs/versioned_docs/version-6.x/graphql.md
index ff6e55044f86..b8afad901199 100644
--- a/docs/versioned_docs/version-6.0/graphql.md
+++ b/docs/versioned_docs/version-6.x/graphql.md
@@ -999,7 +999,7 @@ export const handler = createGraphQLHandler({
})
```
-> Note: Check-out the [in-depth look at Redwood Directives](directives.md) that explains how to generate directives so you may use them to validate access and transform the response.
+> Note: Check-out the [in-depth look at Redwood Directives](./directives.md) that explains how to generate directives so you may use them to validate access and transform the response.
### Logging Setup
@@ -1014,9 +1014,9 @@ Logging is essential in production apps to be alerted about critical errors and
We want to make logging simple when using RedwoodJS and therefore have configured the api-side GraphQL handler to log common information about your queries and mutations. Log statements also be optionally enriched with [operation names](https://graphql.org/learn/queries/#operation-name), user agents, request ids, and performance timings to give you more visibility into your GraphQL api.
-By configuring the GraphQL handler to use your api side [RedwoodJS logger](logger.md), any errors and other log statements about the [GraphQL execution](https://graphql.org/learn/execution/) will be logged to the [destination](logger.md#destination-aka-where-to-log) you've set up: to standard output, file, or transport stream.
+By configuring the GraphQL handler to use your api side [RedwoodJS logger](./logger.md), any errors and other log statements about the [GraphQL execution](https://graphql.org/learn/execution/) will be logged to the [destination](./logger.md#destination-aka-where-to-log) you've set up: to standard output, file, or transport stream.
-You configure the logger using the `loggerConfig` that accepts a [`logger`](logger.md) and a set of [GraphQL Logger Options](#graphql-logger-options).
+You configure the logger using the `loggerConfig` that accepts a [`logger`](./logger.md) and a set of [GraphQL Logger Options](#graphql-logger-options).
### Configure the GraphQL Logger
@@ -1147,9 +1147,9 @@ export const post = async ({ id }) => {
//...
```
-The GraphQL handler will then take care of logging your query and data -- as long as your logger is setup to log at the `info` [level](logger.md#log-level) and above.
+The GraphQL handler will then take care of logging your query and data -- as long as your logger is setup to log at the `info` [level](./logger.md#log-level) and above.
-> You can also disable the statements in production by just logging at the `warn` [level](logger.md#log-level) or above
+> You can also disable the statements in production by just logging at the `warn` [level](./logger.md#log-level) or above
This means that you can keep your services free of logger statements, but still see what's happening!
@@ -1184,7 +1184,7 @@ Stream to third-party log and application monitoring services vital to productio
Everyone has heard of reports that Company X logged emails, or passwords to files or systems that may not have been secured. While RedwoodJS logging won't necessarily prevent that, it does provide you with the mechanism to ensure that won't happen.
-To redact sensitive information, you can supply paths to keys that hold sensitive data using the RedwoodJS logger [redact option](logger.md#redaction).
+To redact sensitive information, you can supply paths to keys that hold sensitive data using the RedwoodJS logger [redact option](./logger.md#redaction).
Because this logger is used with the GraphQL handler, it will respect any redaction paths setup.
@@ -1291,7 +1291,7 @@ By default, your GraphQL endpoint is open to the world.
That means anyone can request any query and invoke any Mutation.
Whatever types and fields are defined in your SDL is data that anyone can access.
-Redwood [encourages being secure by default](http://localhost:3000/docs/canary/directives#secure-by-default-with-built-in-directives) by defaulting all queries and mutations to have the `@requireAuth` directive when generating SDL or a service.
+Redwood [encourages being secure by default](./directives.md#secure-by-default-with-built-in-directives) by defaulting all queries and mutations to have the `@requireAuth` directive when generating SDL or a service.
When your app builds and your server starts up, Redwood checks that **all** queries and mutations have `@requireAuth`, `@skipAuth` or a custom directive applied.
@@ -1417,7 +1417,7 @@ The `@requireAuth` directive lets you define roles that are permitted to perform
```ts
type Mutation {
createPost(input: CreatePostInput!): Post! @requireAuth(roles: ['AUTHOR', 'EDITOR'])
- updatePost(id: Int!, input: UpdatePostInput!): Post! @@requireAuth(roles: ['EDITOR']
+ updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth(roles: ['EDITOR']
deletePost(id: Int!): Post! @requireAuth(roles: ['ADMIN']
}
```
@@ -1472,7 +1472,7 @@ export const handler = createGraphQLHandler({
})
```
-:::caution
+:::warning
Enabling introspection in production may pose a security risk, as it allows users to access information about your schema, queries, and mutations. Use this option with caution and make sure to secure your GraphQL API properly.
diff --git a/docs/versioned_docs/version-6.0/how-to/background-worker.md b/docs/versioned_docs/version-6.x/how-to/background-worker.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/background-worker.md
rename to docs/versioned_docs/version-6.x/how-to/background-worker.md
diff --git a/docs/versioned_docs/version-6.0/how-to/build-dashboards-fast-with-tremor.md b/docs/versioned_docs/version-6.x/how-to/build-dashboards-fast-with-tremor.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/build-dashboards-fast-with-tremor.md
rename to docs/versioned_docs/version-6.x/how-to/build-dashboards-fast-with-tremor.md
diff --git a/docs/versioned_docs/version-6.0/how-to/custom-function.md b/docs/versioned_docs/version-6.x/how-to/custom-function.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/custom-function.md
rename to docs/versioned_docs/version-6.x/how-to/custom-function.md
diff --git a/docs/versioned_docs/version-6.0/how-to/dbauth-passwordless.md b/docs/versioned_docs/version-6.x/how-to/dbauth-passwordless.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/how-to/dbauth-passwordless.md
rename to docs/versioned_docs/version-6.x/how-to/dbauth-passwordless.md
index d938e62e20cb..e07e0bcd4c05 100644
--- a/docs/versioned_docs/version-6.0/how-to/dbauth-passwordless.md
+++ b/docs/versioned_docs/version-6.x/how-to/dbauth-passwordless.md
@@ -637,4 +637,4 @@ const Routes = () => {
```
## You did it!
-Now that you did you can rest easy. You're authentication relies on just your database but also, if some bad actor got access to it the only user data you have is really the email address.
+Now that you did you can rest easy. Your authentication relies on just your database but also, if some bad actor got access to it the only user data you have is really the email address.
diff --git a/docs/versioned_docs/version-6.0/how-to/disable-api-database.md b/docs/versioned_docs/version-6.x/how-to/disable-api-database.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/disable-api-database.md
rename to docs/versioned_docs/version-6.x/how-to/disable-api-database.md
diff --git a/docs/versioned_docs/version-6.0/how-to/file-uploads.md b/docs/versioned_docs/version-6.x/how-to/file-uploads.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/file-uploads.md
rename to docs/versioned_docs/version-6.x/how-to/file-uploads.md
diff --git a/docs/versioned_docs/version-6.0/how-to/gotrue-auth.md b/docs/versioned_docs/version-6.x/how-to/gotrue-auth.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/gotrue-auth.md
rename to docs/versioned_docs/version-6.x/how-to/gotrue-auth.md
diff --git a/docs/versioned_docs/version-6.0/how-to/mocking-graphql-in-storybook.md b/docs/versioned_docs/version-6.x/how-to/mocking-graphql-in-storybook.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/mocking-graphql-in-storybook.md
rename to docs/versioned_docs/version-6.x/how-to/mocking-graphql-in-storybook.md
diff --git a/docs/versioned_docs/version-6.x/how-to/oauth.md b/docs/versioned_docs/version-6.x/how-to/oauth.md
new file mode 100644
index 000000000000..fb7449472c6c
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/how-to/oauth.md
@@ -0,0 +1,831 @@
+# OAuth
+
+If you're using an auth provider like [Auth0](/docs/auth/auth0), OAuth login to third party services (GitHub, Google, Facebook) is usually just a setting you can toggle on in your provider's dashboard. But if you're using [dbAuth](/docs/auth/dbauth) you'll only have username/password login to start. But, adding one or more OAuth clients isn't hard. This recipe will walk you through it from scratch, adding OAuth login via GitHub.
+
+## Prerequisites
+
+This article assumes you have an app set up and are using dbAuth. We're going to make use of the dbAuth system to validate that you're who you say you are. If you just want to try this code out in a sandbox app, you can create a test blog app from scratch by checking out the [Redwood codebase](https://github.com/redwoodjs/redwood) itself and then running a couple of commands:
+
+```bash
+yarn install
+yarn build
+
+# typescript
+yarn run build:test-project ~/oauth-app
+
+# javascript
+yarn run build:test-project ~/oauth-app --javascript
+```
+
+That will create a simple blog application at `~/oauth-app`. You'll get a login and signup page, which we're going to enhance to include a **Login with GitHub** button.
+
+Speaking of GitHub, you'll also need a GitHub account so you can create an [OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app).
+
+We also assume you're familiar with the basics of OAuth and the terminology surrounding it.
+
+## Login Flow
+
+Here's the logic flow we're going to implement:
+
+1. User comes to the login page and clicks a **Login with GitHub** button/link.
+2. The link directs the browser to GitHub's OAuth process at github.com.
+3. The user logs in with their GitHub credentials and approves our app.
+4. The browser is redirected back to our app, to a new function `/api/src/functions/oauth/oauth.js`.
+5. The function fetches the OAuth **access_token** with a call to GitHub, using the **code** that was included with the redirect from GitHub in the previous step.
+6. When the **access_token** is received, the function then requests the user data from GitHub via another fetch to GitHub's API.
+7. The function then checks our database for a user identified by GitHub's `id`. If no user is found, the `User` record is created using the info from the fetch in the previous step.
+8. The user data from our own database is used to create the same cookie that dbAuth creates on a successful login.
+9. The browser is redirected back to our site, and the user is now logged in.
+
+## GitHub OAuth App Setup
+
+In order to allow OAuth login with GitHub, we need to create an OAuth App. The instructions below are for creating one on your personal GitHub account, but if your app lives in a separate organization then you can perform the same steps under the org instead.
+
+First go to your [Settings](https://github.com/settings/profile) and then the [Developer settings](https://github.com/settings/apps) at the bottom left. Finally, click the [OAuth Apps](https://github.com/settings/developers) nav item at left:
+
+![OAuth app settings screenshot](https://user-images.githubusercontent.com/300/245297416-34821cb6-ace0-4a6a-9bf6-4e434d3cefc5.png)
+
+Click [**New OAuth App**](https://github.com/settings/applications/new) and fill it in something like this:
+
+![New OAuth app settings](https://user-images.githubusercontent.com/300/245298106-b35a6abe-6e8c-4ab1-8ab5-7b7e1dcc0a39.png)
+
+The important part is the **Authorization callback URL** which is where GitHub will redirect you back once authenticated (step 4 of the login flow above). This callback URL assumes you're using the default function location of `/.redwood/functions`. If you've changed that in your app be sure to change it here as well.
+
+Click **Register application** and then on the screen that follows, click the **Generate a new client secret** button:
+
+![New client secret button](https://user-images.githubusercontent.com/300/245298639-6e08a201-b0db-4df6-975f-592544bdced7.png)
+
+You may be asked to use your 2FA code to verify that you're who you say you are, but eventually you should see your new **Client secret**. Copy that, and the **Client ID** above it:
+
+![Client secret](https://user-images.githubusercontent.com/300/245298897-129b5d00-3bed-4d7e-a40e-f4c9cda8a21f.png)
+
+Add those to your app's `.env` file (or wherever you're managing your secrets). Note that it's best to have a different OAuth app on GitHub for each environment you deploy to. Consider this one the **dev** app, and you'll create a separate one with a different client ID and secret when you're ready to deploy to production:
+
+```bash title="/.env"
+GITHUB_OAUTH_CLIENT_ID=41a08ae238b5aee4121d
+GITHUB_OAUTH_CLIENT_SECRET=92e8662e9c562aca8356d45562911542d89450e1
+```
+
+We also need to denote what data we want permission to read from GitHub once someone authorizes our app. We'll want the user's public info, and probably their email address. That's only two scopes, and we can add those as another ENV var:
+
+```bash title="/.env"
+GITHUB_OAUTH_CLIENT_ID=41a08ae238b5aee4121d
+GITHUB_OAUTH_CLIENT_SECRET=92e8662e9c562aca8356d45562911542d89450e1
+# highlight-next-line
+GITHUB_OAUTH_SCOPES="read:user user:email"
+```
+
+If you wanted access to more GitHub data, you can specify [additional scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps) here and they'll be listed to the user when they go to authorize your app. You can also change this list in the future, but you'll need to log the user out and the next time they click **Login with GitHub** they'll be asked to authorize your app again, with a new list of requested scopes.
+
+One more ENV var, this is the same callback URL we told GitHub about. This is used in the link in the **Login with GitHub** button and gives GitHub another chance to verify that you're who you say you are: you're proving that you know where you're supposed to redirect back to:
+
+```bash title="/.env"
+GITHUB_OAUTH_CLIENT_ID=41a08ae238b5aee4121d
+GITHUB_OAUTH_CLIENT_SECRET=92e8662e9c562aca8356d45562911542d89450e1
+GITHUB_OAUTH_SCOPES="read:user user:email"
+# highlight-next-line
+GITHUB_OAUTH_REDIRECT_URI="http://localhost:8910/.redwood/functions/oauth/callback"
+```
+
+## The Login Button
+
+This part is pretty easy, we're just going to add a link/button to go directly to GitHub to begin the OAuth process:
+
+```jsx title="/web/src/pages/LoginPage/LoginPage.jsx"
+
+ Login with GitHub
+
+```
+
+:::info
+This example uses Tailwind to style the link to match the rest of the default dbAuth login page
+:::
+
+You can put this same link on your signup page as well, since using the OAuth flow will be dual-purpose: it will log the user in if a local user already exists, or it will create the user and then log them in.
+
+We're using several of our new ENV vars here, and need to tell Redwood to make them available to the web side during the build process. Add them to the `includeEnvironmentVariables` key in `redwood.toml`:
+
+```toml title="/redwood.toml"
+[web]
+ title = "Redwood App"
+ port = "${WEB_DEV_PORT:8910}"
+ apiUrl = "/.redwood/functions"
+ # highlight-next-line
+ includeEnvironmentVariables = ["GITHUB_OAUTH_CLIENT_ID", "GITHUB_OAUTH_REDIRECT_URI", "GITHUB_OAUTH_SCOPES"]
+[api]
+ port = "${API_DEV_PORT:8911}"
+[browser]
+ open = true
+[notifications]
+ versionUpdates = ["latest"]
+```
+
+Restart your dev server to pick up the new TOML settings, and your link should appear:
+
+![Login button](https://user-images.githubusercontent.com/300/245899085-0b946a14-cd7c-402a-9d86-b6527fd89c7f.png)
+
+Go ahead and click it, and you should be taken to GitHub to authorize your GitHub login to work with your app. You'll see the scopes we requested listed under the **Personal User Data** heading:
+
+![GitHub Oauth Access Page](https://user-images.githubusercontent.com/300/245899872-8ddd7e69-dbfa-4544-ab6f-78fd4ff02da8.png)
+
+:::warning
+
+If you get an error here that says "The redirect_uri MUST match the registered callback URL for this application" verify that the redirect URL you entered on GitHub and the one you put into the `GITHUB_OAUTH_REDIRECT_URL` ENV var are identical!
+
+:::
+
+Click **authorize** and you should end up seeing some JSON, and an error:
+
+![/oauth function not found](https://user-images.githubusercontent.com/300/245900327-b21a178e-5539-4c6d-a5d6-9bb736100940.png)
+
+That's coming from our app because we haven't created the `oauth` function that GitHub redirects to. But you'll see a `code` in the URL, which means GitHub is happy with our flow so far. Now we need to trade that `code` for an `access_token`. We'll do that in our `/oauth` function.
+
+:::info
+This nicely formatted JSON comes from the [JSON Viewer](https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh) Chrome extension.
+:::
+
+## The `/oauth` Function
+
+We can have Redwood generate a shell of our new function for us:
+
+```bash
+yarn rw g function oauth
+```
+
+That will create the function at `/api/src/functions/oauth/oauth.js`. If we retry the **Login with GitHub** button now, we'll see the output of that function instead of the error:
+
+![Oauth function responding](https://user-images.githubusercontent.com/300/245903068-760596fa-4139-4d11-b3b3-a90edfbbf496.png)
+
+Now let's start filling out this function with the code we need to get the `access_token`.
+
+### Fetching the `access_token`
+
+We told GitHub to redirect to `/oauth/callback` which *appears* like it would be a subdirectory, or child route of our `oauth` function, but in reality everything after `/oauth` just gets shoved into an `event.path` variable that we'll need to inspect to make sure it has the proper parts (like `/callback`). We can do that in the `hander()`:
+
+```js title="/api/src/functions/oauth/oauth.js"
+export const handler = async (event, _context) => {
+ switch (event.path) {
+ case '/oauth/callback':
+ return await callback(event)
+ default:
+ // Whatever this is, it's not correct, so return "Not Found"
+ return {
+ statusCode: 404,
+ }
+ }
+}
+
+const callback = async (event) => {
+ return { body: 'ok' }
+}
+```
+
+The `callback()` function is where we'll actually define the rest of our flow. We can verify this is working by trying a couple of different URLs in the browser and see that `/oauth/callback` returns a 200 and "ok" in the body of the page, but anything else returns a 404.
+
+Now we need to make a request to GitHub to trade the `code` for an `access_token`. This is handled by a `fetch`:
+
+```js title="/api/src/functions/oauth/oauth.js"
+const callback = async (event) => {
+ // highlight-start
+ const { code } = event.queryStringParameters
+
+ const response = await fetch(`https://github.com/login/oauth/access_token`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ client_id: process.env.GITHUB_OAUTH_CLIENT_ID,
+ client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
+ redirect_uri: process.env.GITHUB_OAUTH_REDIRECT_URI,
+ code,
+ }),
+ })
+
+ const { access_token, scope, error } = JSON.parse(await response.text())
+
+ if (error) {
+ return { statuscode: 400, body: error }
+ }
+
+ return {
+ body: JSON.stringify({ access_token, scope, error })
+ }
+ // highlight-end
+}
+```
+
+First we get the `code` out of the query string variables, then make a POST `fetch()` to GitHub, setting the required JSON body to include several of the ENV vars we've set, as well as the `code` we got from the GitHub redirect. Then we parse the response JSON and just return it in the browser to make sure it worked. If something went wrong (`error` is not `undefined`) then we'll output the error message in the body of the page.
+
+Let's try it: go back to the login page, click the **Login with GitHub** button and see what happens:
+
+![GitHub OAuth access_token granted](https://user-images.githubusercontent.com/300/245906529-d08f9d6e-4947-4d14-9377-def3645d9c68.png)
+
+You can also verify that the error response works by, for example, removing the `code` key from the `fetch()`, and see GitHub complain:
+
+![GitHub OAuth error](https://user-images.githubusercontent.com/300/245906827-703a4a21-b279-428c-be1c-b73c559a72b3.png)
+
+Great, GitHub has authorized us and now we can get details about the actual user from GitHub.
+
+### Retrieving GitHub User Details
+
+We need some unique identifier to tie a user in GitHub to a user in our database. The `access_token` we retrieved allows us to make requests to GitHub's API and return data for the user that the `access_token` is attached to, up to the limits of the `scopes` we requested. GitHub has a unique user `id` that we can use to tie the two together. Let's request that data and dump it to the browser so we can see that the request works.
+
+To keep things straight in our heads, let's call our local user `user` and the GitHub user the `providerUser` (since GitHub is "providing" the OAuth credentials).
+
+Let's make the API call to GitHub's user info endpoint and dump the result to the browser:
+
+```js title="/api/src/functions/oauth/oauth.js"
+const callback = async (event) => {
+ const { code } = event.queryStringParameters
+
+ const response = await fetch(`https://github.com/login/oauth/access_token`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ client_id: process.env.GITHUB_OAUTH_CLIENT_ID,
+ client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
+ redirect_uri: process.env.GITHUB_OAUTH_REDIRECT_URI,
+ code,
+ }),
+ })
+
+ const { access_token, scope, error } = JSON.parse(await response.text())
+
+ if (error) {
+ return { statuscode: 400, body: error }
+ }
+
+ // highlight-start
+ try {
+ const providerUser = await getProviderUser(access_token)
+ return {
+ body: JSON.stringify(providerUser)
+ }
+ } catch (e) {
+ return { statuscode: 500, body: e.message }
+ }
+ // highlight-end
+}
+
+// highlight-start
+const getProviderUser = async (token) => {
+ const response = await fetch('https://api.github.com/user', {
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ const body = JSON.parse(await response.text())
+
+ return body
+}
+// highlight-end
+```
+
+If all went well you should get a ton of juicy data:
+
+![GitHub user output](https://user-images.githubusercontent.com/300/245909925-c984eeb4-f172-46f6-8102-297b72e26bbd.png)
+
+If something went wrong with the fetch you should get a 500 and the error message output in the body. Try setting the `token` in the `Authorization` header to something like `foobar` to verify:
+
+![GitHub API error](https://user-images.githubusercontent.com/300/245910198-2975e90e-9af1-49b1-a41a-81b9269ff71d.png)
+
+Great, we've got the user data, now what do we do with it?
+
+### Database Updates
+
+We've got a bunch of user data that we can use to create a `User` in our own database. But we'll want to look up that same user in the future when they log back in. We have a couple of ways we can go about doing this:
+
+1. Keep our `User` model as-is and create the user in our local database. When the user logs in again, look them by their email address stored in GitHub. **Cons:** If the user changes their email in GitHub we won't be able to find them the next time they log in, and we would create a new user.
+2. Keep the `User` model as-is but create the user with the same `id` as the one we get from GitHub. **Cons:** If we keep username/password login, we would need to create new users with an `id` that won't ever clash with the ones from GitHub.
+2. Add a column to `User` like `githubId` that stores the GitHub `id` so that we can find the user again the next time they come to login. **Cons:** If we add more providers in the future we'll need to keep adding new `*Id` columns for each.
+3. Create a new one-to-many relationship model that stores the GitHub `id` as a single row, tied to the `userId` of the `User` table, and a new row for each ID of any future providers. **Cons:** More complex data structure.
+
+Option #4 will be the most flexible going forward if we ever decide to add more OAuth providers. And if my experience is any indication, everyone always wants more login providers.
+
+So let's create a new `Identity` table that stores the name of the provider and the ID in that system. Logically it will look like this:
+
+```
+┌───────────┐ ┌────────────┐
+│ User │ │ Identity │
+├───────────┤ ├────────────┤
+│ id │•──┐ │ id │
+│ name │ └──<│ userId │
+│ email │ │ provider │
+│ ... │ │ uid │
+└───────────┘ │ ... │
+ └────────────┘
+```
+
+For now `provider` will always be `github` and the `uid` will be the GitHub's unique ID. `uid` should be a `String`, because although GitHub's ID's are integers, not every OAuth provider is guaranteed to use ints.
+
+#### Prisma Schema Updates
+
+Here's the `Identity` model definition:
+
+```prisma title="/api/db/schema.prisma"
+model Identity {
+ id Int @id @default(autoincrement())
+ provider String
+ uid String
+ userId Int
+ user User @relation(fields: [userId], references: [id])
+ accessToken String?
+ scope String?
+ lastLoginAt DateTime @default(now())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@unique([provider, uid])
+ @@index(userId)
+}
+```
+
+We're also storing the `accessToken` and `scope` that we got back from the last time we retrived them from GitHub, as well as a timestamp for the last time the user logged in. Storing the `scope` is useful because if you ever change them, you may want to notify users that have the previous scope definition to re-login so the new scopes can be authorized.
+
+:::caution
+
+There's no GraphQL SDL tied to the Identity table, so it is not accessible via our API. But, if you ever did create an SDL and service, be sure that `accessToken` is not in the list of fields exposed publicly!
+
+:::
+
+We'll need to add an `identities` relation to the `User` model, and make the previously required `hashedPassword` and `salt` fields optional (since users may want to *only* authenticate via GitHub, they'll never get to enter a password):
+
+```prisma title="/api/db/schema.prisma"
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ // highlight-start
+ hashedPassword String?
+ salt String?
+ identities Identity[]
+ // highlight-end
+ ...
+}
+```
+
+Save these as a migration and apply them to the database:
+
+```bash
+yarn rw prisma migrate dev
+```
+
+Give it a name like "create identity". That's it for the database. Let's return to the `/oauth` function and start working with our new `Identity` model.
+
+### Creating Users and Identities
+
+On a successful GitHub OAuth login we'll want to first check and see if a user already exists with the provider info. If so, we can go ahead and log them in. If not, we'll need to create it first, then log them in.
+
+Let's add some code that returns the user if found, otherwise it creates the user *and* returns it, so that the rest of our code doesn't have to care.
+
+:::info
+Be sure to import `db` at the top of the file if you haven't already!
+:::
+
+```js title="/api/src/functions/oauth/oauth.js"
+// highlight-start
+import { db } from 'src/lib/db'
+import { user, createUser } from 'src/services/users'
+// highlight-end
+
+const callback = async (event) => {
+ const { code } = event.queryStringParameters
+
+ const response = await fetch(`https://github.com/login/oauth/access_token`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ client_id: process.env.GITHUB_OAUTH_CLIENT_ID,
+ client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
+ redirect_uri: process.env.GITHUB_OAUTH_REDIRECT_URI,
+ code,
+ }),
+ })
+
+ const { access_token, scope, error } = JSON.parse(await response.text())
+
+ if (error) {
+ return { statuscode: 400, body: error }
+ }
+
+ try {
+ const providerUser = await getProviderUser(access_token)
+ // highlight-start
+ const user = await getUser({ providerUser, accessToken: access_token, scope })
+ return {
+ body: JSON.stringify(user)
+ }
+ // highlight-end
+ } catch (e) {
+ return { statuscode: 500, body: e.message }
+ }
+}
+
+// highlight-start
+const getUser = async ({ providerUser, accessToken, scope }) => {
+ const { user, identity } = await findOrCreateUser(providerUser)
+
+ await db.identity.update({
+ where: { id: identity.id },
+ data: { accessToken, scope, lastLoginAt: new Date() },
+ })
+
+ return user
+}
+// highlight-end
+
+// highlight-start
+const findOrCreateUser = async (providerUser) => {
+ const identity = await db.identity.findFirst({
+ where: { provider: 'github', uid: providerUser.id.toString() }
+ })
+
+ if (identity) {
+ // identity exists, return the user
+ const user = await user({ id: identity.userId })
+ return { user, identity }
+ }
+
+ // identity not found, need to create it and the user
+ const user = await createUser({
+ input: {
+ email: providerUser.email,
+ fullName: providerUser.name,
+ },
+ })
+
+ const identity = await tx.identity.create({
+ data: {
+ userId: user.id,
+ provider: 'github',
+ uid: providerUser.id.toString()
+ }
+ })
+
+ return { user, identity }
+}
+// highlight-end
+```
+
+Let's break that down.
+
+```js
+const providerUser = await getProviderUser(access_token)
+// highlight-next-line
+const user = await getUser({ providerUser, accessToken: access_token, scope })
+return {
+ body: JSON.stringify(user)
+}
+```
+
+After getting the `providerUser` we're going to find our local `user`, and then dump the user to the browser to verify.
+
+```js
+const getUser = async ({ providerUser, accessToken, scope }) => {
+ const { user, identity } = await getOrCreateUser(providerUser)
+
+ await db.identity.update({
+ where: { id: identity.id },
+ data: { accessToken, scope, lastLoginAt: new Date() },
+ })
+
+ return user
+}
+```
+
+The `getUser()` function is going to return the user, whether it had to be created or not. Either way, the attached identity is updated with the current value for the `access_token` (note the case change, try not to get confused!), as well as the `scope` and `lastLoginAt` timestamp. `findOrCreateUser()` is going to do the heavy lifting:
+
+```js
+const findOrCreateUser = async (providerUser) => {
+ const identity = await db.identity.findFirst({
+ where: { provider: 'github', uid: providerUser.id.toString() }
+ })
+
+ if (identity) {
+ const user = await user({ id: identity.userId })
+ return { user, identity }
+ }
+
+ // ...
+}
+```
+
+Note we're using the `user()` function defined in our service, re-using any business logic you may have added around looking up a user. If the user already exists, great! Return it, and the attached `identity` so that we can update the details. If the user doesn't exist already:
+
+```js
+const findOrCreateUser = async (providerUser) => {
+ // ...
+
+ const user = await createUser({
+ input: {
+ email: providerUser.email,
+ fullName: providerUser.name,
+ },
+ })
+
+ const identity = await tx.identity.create({
+ data: {
+ userId: user.id,
+ provider: 'github',
+ uid: providerUser.id.toString()
+ }
+ })
+
+ return { user, identity }
+}
+```
+
+We create the `user` via the existing `createUser()` service, but the `identity` directly in the database. For this particular usecase we have no need of allowing access to the `Identity` data via GraphQL, so there's no reason to create and SDL or underlying service. If you did make them available via GraphQL, it would make sense to replace this create with the `createIdentity()` service. Any error raised during creation would bubble up to the try/catch inside `callback()`. (The Redwood test project has a required `fullName` field that we fill with the `name` attribute from GitHub.)
+
+:::info
+Don't forget the `toString()` calls whenever we read or write the `providerUser.id` since we made the `uid` of type `String`.
+:::
+
+If everything worked then on clicking **Login with GitHub** we should now see a dump of the actual user from our local database:
+
+![User details](https://user-images.githubusercontent.com/300/245922971-caaeb3ed-9231-4edf-aac5-9ea76b488824.png)
+
+You can take a look in the database and verify that the User and Identity were created. Start up the [Prisma Studio](https://www.prisma.io/studio) (which is already included with Redwood):
+
+```bash
+yarn rw prisma studio
+```
+
+![Inspecting the Identity record](https://user-images.githubusercontent.com/300/245923393-d61233cc-52d2-4568-858e-9059dfe31bfc.png)
+
+Great! But, if you go back to your homepage, you'll find that you're not actually logged in. That's because we're not setting the cookie that dbAuth expects to see to consider you logged in. Let's do that, and then our login will be complete!
+
+### Setting the Login Cookie
+
+In order to let dbAuth do the work of actually considering us logged in (and handling stuff like reauthentication and logout) we'll just set the same cookie that the username/password login system would have if the user logged in with a username and password.
+
+Setting a cookie in the browser is a matter of returning a `Set-Cookie` header in the response from the server. We've been responding with a dump of the user object, but now we'll do a real return, including the cookie and a `Location` header to redirect us back to the site.
+
+Redwood provides the cookie encryption helper as a function that you can use in your own code, as well as the function that returns the cookie name based on what you set in your auth config:
+
+```js title="/api/src/functions/oauth/oauth.js"
+// highlight-start
+import { cookieName, encryptSession } from '@redwoodjs/auth-dbauth-api'
+import { cookieName as sessionCookieName } from 'src/lib/auth'
+// highlight-end
+
+const callback = async (event) => {
+ const { code } = event.queryStringParameters
+
+ const response = await fetch(`https://github.com/login/oauth/access_token`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ client_id: process.env.GITHUB_OAUTH_CLIENT_ID,
+ client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
+ redirect_uri: process.env.GITHUB_OAUTH_REDIRECT_URI,
+ code,
+ }),
+ })
+
+ const { access_token, scope, error } = JSON.parse(await response.text())
+
+ if (error) {
+ return { statuscode: 400, body: error }
+ }
+
+ try {
+ const providerUser = await getProviderUser(access_token)
+ const user = await getUser({
+ providerUser,
+ accessToken: access_token,
+ scope,
+ })
+ // highlight-start
+ const cookie = secureCookie(user)
+
+ return {
+ statusCode: 302,
+ headers: {
+ 'Set-Cookie': cookie,
+ Location: '/',
+ },
+ }
+ // highlight-end
+ } catch (e) {
+ return { statuscode: 500, body: e.message }
+ }
+}
+
+// highlight-start
+const secureCookie = (user) => {
+ const expires = new Date()
+ expires.setFullYear(expires.getFullYear() + 1)
+
+ const cookieAttrs = [
+ `Expires=${expires.toUTCString()}`,
+ 'HttpOnly=true',
+ 'Path=/',
+ 'SameSite=Strict',
+ `Secure=${process.env.NODE_ENV !== 'development'}`,
+ ]
+ const data = JSON.stringify({ id: user.id })
+ const encrypted = encryptSession(data)
+
+ return [`${cookieName(sessionCookieName)}=${encrypted}`, ...cookieAttrs].join(
+ '; '
+ )
+}
+// highlight-end
+```
+
+`secureCookie()` takes care of creating the cookie that matches the one set by dbAuth. The attributes that we're setting are actually a copy of the ones set in the `authHandler` in `/api/src/functions/auth.js` and you could remove some duplication between the two by exporting the `cookie` object from `auth.js` and then importing it and using it here. We've set the cookie to expire in a year because, let's admit it, no one likes having to log back in again.
+
+At the end of `callback()` we set the `Set-Cookie` and `Location` headers to send the browser back to the homepage of our app.
+
+Try it out, and as long as you have an indication on your site that a user is logged in, you should see it! In the case of the test project, you'll see "Log Out" at the right side of the top nav instead of "Log In". Try logging out and then back again to test the whole flow from scratch.
+
+## The Complete `/oauth` Function
+
+Here's the `oauth` function in its entirety:
+
+```jsx title="/api/src/functions/oauth/oauth.js"
+import { cookieName, encryptSession } from '@redwoodjs/auth-dbauth-api'
+
+import { cookieName as sessionCookieName } from 'src/lib/auth'
+import { user, createUser } from 'src/services/users'
+import { db } from 'src/lib/db'
+
+export const handler = async (event, _context) => {
+ switch (event.path) {
+ case '/oauth/callback':
+ return await callback(event)
+ default:
+ // Whatever this is, it's not correct, so return "Not Found"
+ return {
+ statusCode: 404,
+ }
+ }
+}
+
+const callback = async (event) => {
+ const { code } = event.queryStringParameters
+
+ const response = await fetch(`https://github.com/login/oauth/access_token`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ client_id: process.env.GITHUB_OAUTH_CLIENT_ID,
+ client_secret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
+ redirect_uri: process.env.GITHUB_OAUTH_REDIRECT_URI,
+ code,
+ }),
+ })
+
+ const { access_token, scope, error } = JSON.parse(await response.text())
+
+ if (error) {
+ return { statuscode: 400, body: error }
+ }
+
+ try {
+ const providerUser = await getProviderUser(access_token)
+ const user = await getUser({
+ providerUser,
+ accessToken: access_token,
+ scope,
+ })
+ const cookie = secureCookie(user)
+
+ return {
+ statusCode: 302,
+ headers: {
+ 'Set-Cookie': cookie,
+ Location: '/',
+ },
+ }
+ } catch (e) {
+ return { statuscode: 500, body: e.message }
+ }
+}
+
+const secureCookie = (user) => {
+ const expires = new Date()
+ expires.setFullYear(expires.getFullYear() + 1)
+
+ const cookieAttrs = [
+ `Expires=${expires.toUTCString()}`,
+ 'HttpOnly=true',
+ 'Path=/',
+ 'SameSite=Strict',
+ `Secure=${process.env.NODE_ENV !== 'development'}`,
+ ]
+ const data = JSON.stringify({ id: user.id })
+ const encrypted = encryptSession(data)
+
+ return [`${cookieName(sessionCookieName)}=${encrypted}`, ...cookieAttrs].join(
+ '; '
+ )
+}
+
+const getProviderUser = async (token) => {
+ const response = await fetch('https://api.github.com/user', {
+ headers: { Authorization: `Bearer ${token}` },
+ })
+ const body = JSON.parse(await response.text())
+
+ return body
+}
+
+const getUser = async ({ providerUser, accessToken, scope }) => {
+ const { user, identity } = await findOrCreateUser(providerUser)
+
+ await db.identity.update({
+ where: { id: identity.id },
+ data: { accessToken, scope, lastLoginAt: new Date() },
+ })
+
+ return user
+}
+
+const findOrCreateUser = async (providerUser) => {
+ const identity = await db.identity.findFirst({
+ where: { provider: 'github', uid: providerUser.id.toString() },
+ })
+
+ if (identity) {
+ // identity exists, return the user
+ const user = await user({ id: identity.userId })
+ return { user, identity }
+ }
+
+ // identity not found, need to create it and the user
+ const user = await createUser({
+ input: {
+ email: providerUser.email,
+ fullName: providerUser.name,
+ },
+ })
+
+ const identity = await tx.identity.create({
+ data: {
+ userId: user.id,
+ provider: 'github',
+ uid: providerUser.id.toString(),
+ },
+ })
+
+ return { user, identity }
+}
+```
+
+## Enhancements
+
+This is barebones implementation of a single OAuth provider. What can we do to make it better?
+
+### More Providers
+
+We hardcoded "github" as the provider in a couple of places, as well as hardcoding GitHub's API endpoint for fetching user data. That obviously limits this implementation to only support GitHub.
+
+A more flexible version could include the provider as part of the callback URL, and then our code can see that and choose which provider to set and how to get user details. Maybe the OAuth redirect is `/oauth/github/callback` and `/oauth/twitter/callback`. Then parse that out and delegate to a different function altogether, or implement each provider's specific info into separate files and `import` them into the `/oauth` function, invoking each as needed.
+
+### Changing User Details
+
+Right now we just copy the user details from GitHub right into our new User object. Maybe we want to give the user a chance to update those details first, or add additional information before saving to the database. One solution could be to create the `Identity` record, but redirect to your real Signup page with the info from GitHub (and the `accessToken`) and prefill the signup fields, giving the user a chance to change or enhance them, adding the `accessToken` to a hidden field. Then when the user submits that form, if the `accessToken` is part of the form, get the user details from GitHub again (so we can get their GitHub `id`) and then create the `Identity` and `User` record as before.
+
+### Better Error Handling
+
+Right now if an error occurs in the OAuth flow, the browser just stays on the `/oauth/callback` function and sees a plain text error message. A better experience would be to redirect the user back to the login page, with the error message in a query string variable, something like `http://localhost:8910/login?error=Application+not+authorized` Then in the LoginPage, add a `useParams()` to pull out the query variables, and show a toast message if an error is present:
+
+```jsx
+import { useParams } from '@redwoodjs/router'
+import { toast, Toaster } from '@redwoodjs/web/toast'
+
+const LoginPage = () => {
+ const params = useParams()
+
+ useEffect(() => {
+ if (params.error) {
+ toast.error(error)
+ }
+ }, [params]
+
+ return (
+ <>
+
+ // ...
+ >
+ )
+}
+```
diff --git a/docs/versioned_docs/version-6.0/how-to/pagination.md b/docs/versioned_docs/version-6.x/how-to/pagination.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/pagination.md
rename to docs/versioned_docs/version-6.x/how-to/pagination.md
diff --git a/docs/versioned_docs/version-6.0/how-to/role-based-access-control.md b/docs/versioned_docs/version-6.x/how-to/role-based-access-control.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/how-to/role-based-access-control.md
rename to docs/versioned_docs/version-6.x/how-to/role-based-access-control.md
index dbe467f68964..da138d62d71f 100644
--- a/docs/versioned_docs/version-6.0/how-to/role-based-access-control.md
+++ b/docs/versioned_docs/version-6.x/how-to/role-based-access-control.md
@@ -238,10 +238,10 @@ export const getCurrentUser = async (decoded) => {
#### How to Protect a Route
-To protect a `Private` route for access by a single role:
+To protect a `PrivateSet` route for access by a single role:
```jsx
-import { Router, Route, Private } from '@redwoodjs/router'
+import { Router, Route, PrivateSet } from '@redwoodjs/router'
const Routes = () => {
return (
@@ -254,10 +254,10 @@ const Routes = () => {
}
```
-To protect a `Private` route for access by a multiple roles:
+To protect a `PrivateSet` route for access by a multiple roles:
```jsx
-import { Router, Route, Private } from '@redwoodjs/router'
+import { Router, Route, PrivateSet } from '@redwoodjs/router'
const Routes = () => {
return (
diff --git a/docs/versioned_docs/version-6.0/how-to/self-hosting-redwood.md b/docs/versioned_docs/version-6.x/how-to/self-hosting-redwood.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/how-to/self-hosting-redwood.md
rename to docs/versioned_docs/version-6.x/how-to/self-hosting-redwood.md
index 63a01b75e95e..870b61ef84a9 100644
--- a/docs/versioned_docs/version-6.0/how-to/self-hosting-redwood.md
+++ b/docs/versioned_docs/version-6.x/how-to/self-hosting-redwood.md
@@ -1,5 +1,5 @@
# Self-hosting Redwood (Serverful)
-:::caution
+:::warning
This doc has been deprecated in favor of the [Baremetal](../deploy/baremetal.md) docs.
diff --git a/docs/versioned_docs/version-6.0/how-to/sending-emails.md b/docs/versioned_docs/version-6.x/how-to/sending-emails.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/sending-emails.md
rename to docs/versioned_docs/version-6.x/how-to/sending-emails.md
diff --git a/docs/versioned_docs/version-6.0/how-to/supabase-auth.md b/docs/versioned_docs/version-6.x/how-to/supabase-auth.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/supabase-auth.md
rename to docs/versioned_docs/version-6.x/how-to/supabase-auth.md
diff --git a/docs/versioned_docs/version-6.0/how-to/test-in-github-actions.md b/docs/versioned_docs/version-6.x/how-to/test-in-github-actions.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/how-to/test-in-github-actions.md
rename to docs/versioned_docs/version-6.x/how-to/test-in-github-actions.md
index dc41fbecff2f..8064e49b681f 100644
--- a/docs/versioned_docs/version-6.0/how-to/test-in-github-actions.md
+++ b/docs/versioned_docs/version-6.x/how-to/test-in-github-actions.md
@@ -93,7 +93,7 @@ model UserExample {
Then add your connection strings to your `.env` file:
-:::caution
+:::warning
Make sure you don't commit this file to your repo since it contains sensitive information.
diff --git a/docs/versioned_docs/version-6.0/how-to/using-a-third-party-api.md b/docs/versioned_docs/version-6.x/how-to/using-a-third-party-api.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/using-a-third-party-api.md
rename to docs/versioned_docs/version-6.x/how-to/using-a-third-party-api.md
diff --git a/docs/versioned_docs/version-6.0/how-to/using-nvm.md b/docs/versioned_docs/version-6.x/how-to/using-nvm.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/how-to/using-nvm.md
rename to docs/versioned_docs/version-6.x/how-to/using-nvm.md
index b7fb82ac43c7..05ed1f716bca 100644
--- a/docs/versioned_docs/version-6.0/how-to/using-nvm.md
+++ b/docs/versioned_docs/version-6.x/how-to/using-nvm.md
@@ -6,7 +6,7 @@
## Installing nvm
-:::caution
+:::warning
If you've already installed Node.js on your machine, uninstall Node.js before installing nvm. This will prevent any conflicts between the Node.js and nvm.
### If you're on a Mac
@@ -77,7 +77,7 @@ To see all the versions of Node that you can install, run the following command:
nvm ls-remote
```
-:::caution
+:::warning
You'll need to [install yarn](https://yarnpkg.com/getting-started/install) **for each version of Node that you install.**
[Corepack](https://nodejs.org/dist/latest/docs/api/corepack.html) is included with all Node.js >=16.10 installs, but you must opt-in. To enable it, run the following command:
diff --git a/docs/versioned_docs/version-6.0/how-to/using-yarn.md b/docs/versioned_docs/version-6.x/how-to/using-yarn.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/how-to/using-yarn.md
rename to docs/versioned_docs/version-6.x/how-to/using-yarn.md
index 3c429e41a38c..5198209e6e85 100644
--- a/docs/versioned_docs/version-6.0/how-to/using-yarn.md
+++ b/docs/versioned_docs/version-6.x/how-to/using-yarn.md
@@ -22,7 +22,7 @@ To see the version of yarn that you have installed, run the following command:
yarn --version
```
-**Redwood requires Yarn (>=1.15)**
+**Redwood requires Yarn (>=1.22.21)**
You can upgrade yarn by running the following command:
diff --git a/docs/versioned_docs/version-6.0/how-to/windows-development-setup.md b/docs/versioned_docs/version-6.x/how-to/windows-development-setup.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/how-to/windows-development-setup.md
rename to docs/versioned_docs/version-6.x/how-to/windows-development-setup.md
diff --git a/docs/versioned_docs/version-6.0/intro-to-servers.md b/docs/versioned_docs/version-6.x/intro-to-servers.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/intro-to-servers.md
rename to docs/versioned_docs/version-6.x/intro-to-servers.md
index b3d05cbfb941..121097e67e6f 100644
--- a/docs/versioned_docs/version-6.0/intro-to-servers.md
+++ b/docs/versioned_docs/version-6.x/intro-to-servers.md
@@ -63,7 +63,7 @@ If you're connecting to cloud-based servers, turning them on and off, and potent
Once you're past that prompt you'll then either be prompted for your password, or logged in automatically (when using a private or public key). Let's look at each one in detail.
-:::caution Baremetal First Deploy Woes?
+:::warning Baremetal First Deploy Woes?
If you're having trouble deploying to your server with Baremetal, and you've never connected to your server manually via SSH, this could be why: Baremetal provides no interactive prompt to accept this server fingerprint. You need to connect manually at least once before Baremetal can connect.
diff --git a/docs/versioned_docs/version-6.0/introduction.md b/docs/versioned_docs/version-6.x/introduction.md
similarity index 91%
rename from docs/versioned_docs/version-6.0/introduction.md
rename to docs/versioned_docs/version-6.x/introduction.md
index 560cd1fce7d0..7a89099ca8d1 100644
--- a/docs/versioned_docs/version-6.0/introduction.md
+++ b/docs/versioned_docs/version-6.x/introduction.md
@@ -10,8 +10,9 @@ For full inspiration and vision, see Redwood's [README](https://github.com/redwo
Development on Redwood happens in the [redwoodjs/redwood repo on GitHub](https://github.com/redwoodjs/redwood).
The docs are [there too](https://github.com/redwoodjs/redwood/tree/main/docs).
-While Redwood's [founders and core team](https://github.com/redwoodjs/redwood#core-team) handle most of the high-priority items and the day-to-day,
-Redwood wouldn't be where it is without [all its contributors](https://github.com/redwoodjs/redwood#all-contributors)!
+While Redwood's [leadership and maintainers](https://github.com/redwoodjs/redwood#core-team-leadership)
+handle most of the high-priority items and the day-to-day, Redwood wouldn't be
+where it is without [all its contributors](https://github.com/redwoodjs/redwood#all-contributors)!
Feel free to reach out to us on the [forums](https://community.redwoodjs.com) or on [Discord](https://discord.gg/redwoodjs), and follow us on [Twitter](https://twitter.com/redwoodjs) for updates.
## Getting the Most out of Redwood
diff --git a/docs/versioned_docs/version-6.0/local-postgres-setup.md b/docs/versioned_docs/version-6.x/local-postgres-setup.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/local-postgres-setup.md
rename to docs/versioned_docs/version-6.x/local-postgres-setup.md
diff --git a/docs/versioned_docs/version-6.0/logger.md b/docs/versioned_docs/version-6.x/logger.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/logger.md
rename to docs/versioned_docs/version-6.x/logger.md
diff --git a/docs/versioned_docs/version-6.x/mailer.md b/docs/versioned_docs/version-6.x/mailer.md
new file mode 100644
index 000000000000..0707e9b5bc85
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/mailer.md
@@ -0,0 +1,289 @@
+# Mailer
+
+RedwoodJS offers a convenient Mailer for sending emails to your users. It's not just about sending an email; delivery matters too. The way you deliver the feature requiring email is as significant as how you prepare the mail to be delivered by the infrastructure that sends emails over the internet.
+
+When designing the Mailer, it was crucial that mail could be:
+
+* sent by popular third-party services like [Resend](), [SendGrid](), [Postmark](), [Amazon SES](), and others.
+* sent by [Nodemailer]() as a self-hosted OSS solution.
+* use different providers depending on the use case. For instance, some transactional emails might be sent via Resend and some digest emails sent by SES. You should be able to choose the method for a specific email.
+* send safely in both development and test environments in a "sandbox" without worrying that emails might accidentally leak.
+* be sent as text and/or html and composed using templates by popular tools like [React Email]() or [MJML](), with support for more methods in the future.
+* unit tested to set the proper to, from, cc, subject, body, and more.
+* integrated with RedwoodJS Studio to help design and preview templates.
+
+The RedwoodJS Mailer does more than "just send an email". It is a complete end-to-end design, development, and testing package for emails.
+
+## Overview
+
+The RedwoodJS Mailer consists of [handlers](#handlers) and [renderers](#renderers), which carry out the core functionality of sending (handling) your emails and composing (rendering) your emails, respectively. This is combined with a few required files which define the necessary configuration.
+
+A high-level overview of the Mailer Flow is shown in the diagram below, and each case is covered in more detail below the diagram.
+
+
+### Renderers
+
+A **renderer** transforms your React components into strings of text or HTML that can be sent as an email.
+
+Mailer currently offers the following renderers:
+* [@redwoodjs/mailer-renderer-react-email]() based on [React Email]()
+* [@redwoodjs/mailer-renderer-mjml-react]() based on [MJML]()
+
+You can find community-maintained renderers by searching across npm, our forums, and other community spaces.
+
+:::important
+
+Email clients are notoriously inconsistent in how they render HTML into the visual email content. Consider using a robust react library to help you write components that produce attractive emails, rendered consistently across email clients.
+
+:::
+
+### Handlers
+
+A **handler** is responsible for taking your rendered content and passing it on to a service that can send your email to the intended recipients, e.g., Nodemailer or Amazon SES.
+
+Mailer currently offers the following handlers:
+* [@redwoodjs/mailer-handler-in-memory](), a simple in-memory handler typically used for testing.
+* [@redwoodjs/mailer-handler-nodemailer](), which uses [Nodemailer]().
+* [@redwoodjs/mailer-handler-studio](), which sends emails to the RedwoodJS Studio using nodemailer internally.
+* [@redwoodjs/mailer-handler-resend](), which uses [Resend]().
+
+You can find community-maintained handlers by searching across npm, our forums, and other community spaces.
+
+### Files & Directories
+
+The core file for the Mailer functions is `api/src/lib/mailer.ts`. This file contains configuration defining which handlers and renderers to use and when. It starts out looking like this:
+```ts title=api/src/lib/mailer.ts
+import { Mailer } from '@redwoodjs/mailer-core'
+import { NodemailerMailHandler } from '@redwoodjs/mailer-handler-nodemailer'
+import { ReactEmailRenderer } from '@redwoodjs/mailer-renderer-react-email'
+
+import { logger } from 'src/lib/logger'
+
+export const mailer = new Mailer({
+ handling: {
+ handlers: {
+ // TODO: Update this handler config or switch it out for a different handler completely
+ nodemailer: new NodemailerMailHandler({
+ transport: {
+ host: 'localhost',
+ port: 4319,
+ secure: false,
+ },
+ }),
+ },
+ default: 'nodemailer',
+ },
+
+ rendering: {
+ renderers: {
+ reactEmail: new ReactEmailRenderer(),
+ },
+ default: 'reactEmail',
+ },
+
+ logger,
+})
+```
+
+In the above, you can see how handlers and renderers are defined. Handlers are defined in the `handling` object where the keys are any name you wish to give, and the values are instances of the handler you want to use. Similarly for renderers, which are defined in the `rendering` object. Each must have a `default` provided, specifying which option to use by default in production.
+
+Mailer also expects you to put your mail react components inside the `api/src/mail` directory. For example, if you had a welcome email, it should be found in `api/src/mail/Welcome/Welcome.tsx`.
+
+## Setup
+
+The Mailer is not set up by default when you create a new RedwoodJS app, but it is easy to do so. Simply run the following CLI command:
+
+```bash title="RedwoodJS CLI"
+yarn rw setup mailer
+```
+
+This command sets up the necessary files and dependencies. You can find more information on this command at [this](https://redwoodjs.com/docs/cli-commands#setup-mailer) specific section of our docs.
+
+## Usage
+
+### Example
+
+The best way to understand using the Mailer is with an example.
+
+In the tutorial, we built out a blog site. Let's say we have added a contact us functionality and the contact us form takes a name, email, and message and stores it in the database.
+
+For this example, suppose we want to also send an email to some internal inbox with this contact us submission.
+
+The service would be updated like so:
+
+```ts title=api/src/services/contacts.ts
+import { mailer } from 'src/lib/mailer'
+import { ContactUsEmail } from 'src/mail/Example/Example'
+
+// ...
+
+export const createContact: MutationResolvers['createContact'] = async ({
+ input,
+}) => {
+ const contact = await db.contact.create({
+ data: input,
+ })
+
+ // Send email
+ await mailer.send(
+ ContactUsEmail({
+ name: input.name,
+ email: input.email,
+ // Note the date is hardcoded here for the sake of test snapshot consistency
+ when: new Date(0).toLocaleString(),
+ }),
+ {
+ to: 'inbox@example.com',
+ subject: 'New Contact Us Submission',
+ replyTo: input.email,
+ from: 'contact-us@example.com',
+ }
+ )
+
+ return contact
+}
+```
+
+In the code above, we do the following:
+
+- Import the Mailer and our mail template.
+- Call the `mailer.send` function with:
+ - Our template, which we pass props into based on the user input.
+ - A set of send options to specify to, from, etc.
+
+In the example above, we specified a `replyTo` because that suited our business logic. However, we probably don't want to write `replyTo: 'no-reply@example.com'` in all our other emails where we might want that to be set.
+
+In that case, we can use the `defaults` property in our `api/src/lib/mailer.ts` config:
+
+```ts title=api/src/lib/mailer.ts
+defaults: {
+ replyTo: 'no-reply@example.com',
+},
+```
+
+Now that we implemented our example, we might start to think about testing or how to try this out ourselves during development.
+
+The Mailer behaves slightly differently based on which environment you are running in.
+
+This helps improve your experience as you don't have to worry about sending real emails during testing or development.
+
+### Testing
+
+When your `NODE_ENV` is set to `test`, then the Mailer will start in test mode. In this mode, all mail will be sent using a test handler rather than the default production one or any specific one set when calling `send` or `sendWithoutRendering`.
+
+By default, when the Mailer is created, it will check if the `@redwoodjs/mailer-handler-in-memory` package is available. If it is, this will become the test handler; otherwise, the test handler will be a no-op that does nothing. The `yarn rw setup mailer` command adds this `@redwoodjs/mailer-handler-in-memory` package as a `devDependency` automatically for you.
+
+If you want control over this test mode behavior, you can include the following configuration in the `mailer.ts` file:
+
+```ts title=api/src/lib/mailer.ts
+test: {
+ when: process.env.NODE_ENV === 'test',
+ handler: 'someOtherHandler',
+}
+```
+
+The `when` property can either be a boolean or a function that returns a boolean. This decides if the Mailer starts in test mode when it is created. The `handler` property can specify a different handler to use in test mode.
+
+As an example of how this helps with testing, let's work off the example we created above. Let's now test our email functionality in the corresponding test file:
+
+```ts title=api/src/services/contacts/contacts.test.ts
+describe('contacts', () => {
+ scenario('creates a contact', async () => {
+ const result = await createContact({
+ input: { name: 'String', email: 'String', message: 'String' },
+ })
+
+ expect(result.name).toEqual('String')
+ expect(result.email).toEqual('String')
+ expect(result.message).toEqual('String')
+
+ // Mail
+ const testHandler = mailer.getTestHandler() as InMemoryMailHandler
+ expect(testHandler.inbox.length).toBe(1)
+ const sentMail = testHandler.inbox[0]
+ expect({
+ ...sentMail,
+ htmlContent: undefined,
+ textContent: undefined,
+ }).toMatchInlineSnapshot(`
+ {
+ "attachments": [],
+ "bcc": [],
+ "cc": [],
+ "from": "contact-us@example.com",
+ "handler": "nodemailer",
+ "handlerOptions": undefined,
+ "headers": {},
+ "htmlContent": undefined,
+ "renderer": "reactEmail",
+ "rendererOptions": {},
+ "replyTo": "String",
+ "subject": "New Contact Us Submission",
+ "textContent": undefined,
+ "to": [
+ "inbox@example.com",
+ ],
+ }
+ `)
+ expect(sentMail.htmlContent).toMatchSnapshot()
+ expect(sentMail.textContent).toMatchSnapshot()
+ })
+})
+```
+
+Above we tested that our service did the following:
+
+- Sent one email.
+- All the send options (such as to, from, what handler, etc.) match a set of expected values (the inline snapshot).
+- The rendered text and HTML content match the expected value (the snapshots).
+
+### Development
+
+Similar to the test mode, the Mailer also has a development mode. This mode is selected automatically when the Mailer is created if `NODE_ENV` is **not** set to `production`. This mode behaves similarly to the test mode and by default will attempt to use the `@redwoodjs/mailer-handler-studio` package if it is available.
+
+You can control the development mode behavior with the following configuration in the `mailer.ts` file:
+
+```ts title=api/src/lib/mailer.ts
+development: {
+ when: process.env.NODE_ENV !== 'production',
+ handler: 'someOtherHandler',
+},
+```
+
+:::tip
+
+The Mailer studio has some helpful features when it comes to using the Mailer during development. It can provide a mail inbox so that you can send mail to your local machine and see the results. It can also provide live previews of your rendered mail templates as a guide to what they will likely look like when sent to your end users.
+
+:::
+
+### Production
+
+If neither the test nor development mode conditions are met, the Mailer will start in production mode. In this mode, there is no rerouting of your mail to different handlers. Instead, your mail will go directly to your default handler unless you specifically state a different one in your send options.
+
+### Studio
+
+Redwood Studio is tightly integrated with the mailer. The goal is to provide you with not just the ability to send mail but also the development tools to make your experience easier and more enjoyable.
+
+#### Template Previews
+
+
+You can have a preview of what your mail templates will look like. These will rerender live as you update your template code and you can even provide a JSON payload which will be used as the props to your template component. These previews are approximate but should easily get you 90% of the way there.
+
+#### Local Inbox
+
+
+When running in development mode, using the default `@redwoodjs/mailer-handler-studio` development handler, your mail will be sent to a local SMTP inbox running inside of Studio. This allows you to use your app and have full emails sent without worrying about setting up a local inbox yourself or using some other online temporary inbox service.
+
+:::warning
+
+Redwood Studio is an experimental feature and is still in development. Some of the UI shown above might look slightly different and the functionality may be tweaked over time to provide you with a better experience.
+
+:::
+
+## Need a Renderer or Handler?
+
+If the Mailer does not currenly provide a [handler](notion://www.notion.so/redwoodjs/133467eb46b744fd8ae60df2d493d7d0#handlers) or [renderer](notion://www.notion.so/redwoodjs/133467eb46b744fd8ae60df2d493d7d0#renderers) for the service or technology you wish to use, this doesn't prevent you from using the Mailer. Instead, you can create your own handler or renderer which you can then open source to the wider RedwoodJS community.
+
+To do this, read over the existing implementations for handlers [here](https://github.com/redwoodjs/redwood/tree/main/packages/mailer/handlers) and renderers [here](https://github.com/redwoodjs/redwood/tree/main/packages/mailer/renderers). You can also find the interfaces that a handler or mailer must satisfy [here](https://github.com/redwoodjs/redwood/tree/main/packages/mailer/core) in the `@redwoodjs/mailer-core` package.
+
+Be sure to check out the community forum for people working on similar work, to document your own creations, or to get help on anything.
diff --git a/docs/versioned_docs/version-6.0/mocking-graphql-requests.md b/docs/versioned_docs/version-6.x/mocking-graphql-requests.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/mocking-graphql-requests.md
rename to docs/versioned_docs/version-6.x/mocking-graphql-requests.md
diff --git a/docs/versioned_docs/version-6.0/prerender.md b/docs/versioned_docs/version-6.x/prerender.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/prerender.md
rename to docs/versioned_docs/version-6.x/prerender.md
index fa467aea495c..2ac27195086f 100644
--- a/docs/versioned_docs/version-6.0/prerender.md
+++ b/docs/versioned_docs/version-6.x/prerender.md
@@ -11,7 +11,7 @@ We thought a lot about what the developer experience should be for route-based p
:::info How's Prerendering different from SSR/SSG/SWR/ISSG/...?
As Danny said in his [Prerender demo](https://www.youtube.com/watch?v=iorKyMlASZc&t=2844s) at our Community Meetup, the thing all of these have in common is that they render your markup in a Node.js context to produce HTML. The difference is when (build or runtime) and how often.
-Redwood currently supports prerendering at _build_ time. So before your deploy your web side, Redwood will render your pages into HTML, and once the JavaScript has been loaded on the browser, the page becomes dynamic.
+Redwood currently supports prerendering at _build_ time. So before you deploy your web side, Redwood will render your pages into HTML, and once the JavaScript has been loaded on the browser, the page becomes dynamic.
:::
@@ -56,7 +56,7 @@ This will prerender your NotFoundPage to `404.html` in your dist folder. Note th
For Private Routes, Redwood prerenders your Private Routes' `whileLoadingAuth` prop:
```jsx
-
+
// Loading is shown while we're checking to see if the user's logged in
} prerender/>
diff --git a/docs/versioned_docs/version-6.0/project-configuration-dev-test-build.mdx b/docs/versioned_docs/version-6.x/project-configuration-dev-test-build.mdx
similarity index 84%
rename from docs/versioned_docs/version-6.0/project-configuration-dev-test-build.mdx
rename to docs/versioned_docs/version-6.x/project-configuration-dev-test-build.mdx
index 3b78f9378183..37df966dafeb 100644
--- a/docs/versioned_docs/version-6.0/project-configuration-dev-test-build.mdx
+++ b/docs/versioned_docs/version-6.x/project-configuration-dev-test-build.mdx
@@ -85,7 +85,7 @@ const config = {
module.exports = config
```
-> You can always see Redwood's latest configuration templates in the [create-redwood-app package](https://github.com/redwoodjs/redwood/blob/main/packages/create-redwood-app/template/web/jest.config.js).
+> You can always see Redwood's latest configuration templates in the [create-redwood-app package](https://github.com/redwoodjs/redwood/blob/main/packages/create-redwood-app/templates/ts/web/jest.config.js).
The preset includes all the setup required to test everything that's going on in web: rendering React components and transforming JSX, automatically mocking Cells, transpiling with Babel, mocking the Router and the GraphQL client—the list goes on!
You can find all the details in the [source](https://github.com/redwoodjs/redwood/blob/main/packages/testing/config/jest/web/jest-preset.js).
@@ -104,40 +104,28 @@ You can find all the details in the [source](https://github.com/redwoodjs/redwoo
You can customize the types that Redwood generates from your project too! This is documented in a bit more detail in the [Generated Types](typescript/generated-types#customising-codegen-config) doc.
-## Debugger configuration
-The `yarn rw dev` command is configured by default to launch a debugger on the port `18911`, your Redwood app also ships with default configuration to attach a debugger from VSCode.
-
-Simply run your dev server, then attach the debugger from the "run and debug" panel. Quick demo below:
-
-
+## Debug configurations
+### Dev Server
+The `yarn rw dev` command is configured by default to open a browser and a debugger on the port `18911` and your redwood app ships with several default configurations to debug with VSCode.
-
-
-> **ℹ️ Tip: Can't see the "Attach debugger" configuration?** In VSCode
->
-> You can grab the latest launch.json from the Redwood template [here](https://github.com/redwoodjs/redwood/blob/main/packages/create-redwood-app/templates/ts/.vscode/launch.json). Copy the contents into your project's `.vscode/launch.json`
-
-
-#### Customizing the debug port
-You can choose to use a different debug port in one of two ways:
-
+#### Customizing the configuration
**a) Using the redwood.toml**
-Add/change the `debugPort` under your api settings
+Add/change the `debugPort` or `open` under your api settings
```toml title="redwood.toml"
[web]
# .
- # .
[api]
- port = 8911
+ # .
+ // highlight-next-line
+ debugPort = 18911 # change me!
+[browser]
// highlight-next-line
- debugPort = 18911 # 👈 change me!
+ open = true # change me!
```
-If you set it to `false`, no debug port will be exposed. The `debugPort` is only ever used during development when running `yarn rw dev`
-
**b) Pass a flag to `rw dev` command**
You can also pass a flag when you launch your dev servers, for example:
@@ -149,6 +137,21 @@ The flag passed in the CLI will always take precedence over your setting in the
Just remember to also change the port you are attaching to in your `./vscode/launch.json`
+### API and Web Debuggers
+Simply run your dev server, then attach the debugger from the "run and debug" panel. Quick demo below:
+
+
+
+### Compound Debugger
+The compound configuration is a combination of the dev, api and web configurations.
+It allows you to start all debugging configurations at once, facilitating simultaneous debugging of server and client-side code.
+
+
+
+> **ℹ️ Tip: Can't see the debug configurations?** In VSCode
+>
+> You can grab the latest launch.json from the Redwood template [here](https://github.com/redwoodjs/redwood/blob/main/packages/create-redwood-app/templates/ts/.vscode/launch.json). Copy the contents into your project's `.vscode/launch.json`
+
## Ignoring the `.yarn` folder
The `.yarn` folder contains the most recent Yarn executable that Redwood supports
@@ -219,3 +222,17 @@ Admittedly, the `.yarn` folder won't change that often, so this may not be
the best example. But we thought we'd share this technique with you
so that you'd know how to apply it to any folders that you know change very often,
and how to tell VSCode not to bother wasting any CPU cycles on them.
+
+## Trailing whitespace
+
+If you're using VS Code, or another editor that supports
+[EditorConfig](https://editorconfig.org), trailing whitespace will be trimmed
+in source files, but preserved in html, markdown and mjml files when saving.
+
+This behavior is controlled by `.vscode/settings` or `.editorconfig` depending
+on your editor.
+
+In JavaScript and TypeScript files trailing whitespace has no significance,
+but for html, markdown and mjml it does. That's why the behavior is different
+for those files. If you don't like the default behavior Redwood has configured
+for you, you're free to change the settings in those two files.
diff --git a/docs/versioned_docs/version-6.0/quick-start.md b/docs/versioned_docs/version-6.x/quick-start.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/quick-start.md
rename to docs/versioned_docs/version-6.x/quick-start.md
index 40f35f9134bf..3fcf885a3324 100644
--- a/docs/versioned_docs/version-6.0/quick-start.md
+++ b/docs/versioned_docs/version-6.x/quick-start.md
@@ -6,7 +6,7 @@ description: Redwood quick start
:::info Prerequisites
-- Redwood requires [Node.js](https://nodejs.org/en/) (=18.x) and [Yarn](https://yarnpkg.com/) (>=1.15)
+- Redwood requires [Node.js](https://nodejs.org/en/) (=18.x) and [Yarn](https://yarnpkg.com/) (>=1.22.21)
- Are you on Windows? For best results, follow our [Windows development setup](how-to/windows-development-setup.md) guide
:::
diff --git a/docs/versioned_docs/version-6.x/realtime.md b/docs/versioned_docs/version-6.x/realtime.md
new file mode 100644
index 000000000000..39ef6fd5a2c9
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/realtime.md
@@ -0,0 +1,706 @@
+# Realtime
+
+One of the most often asked questions of RedwoodJS before and after the launch of V1 was, “When will RedwoodJS support a realtime solution?”
+
+The answer is: **now**.
+
+## What is Realtime?
+
+RedwoodJS's initial real-time solution leverages GraphQL and relies on a serverful deployment to maintain a long-running connection between the client and server.
+
+:::note
+This means that your cannot currently use RedwoodJS Realtime when deployed to Netlify or Vercel.
+
+**More information about deploying a serverful RedwoodJS application is forthcoming.**
+:::
+
+RedwoodJS's GraphQL Server uses [GraphQL over Server-Sent Events](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#distinct-connections-mode) spec "distinct connections mode" for subscriptions.
+
+Advantages of SSE over WebSockets include:
+
+* Transported over simple HTTP instead of a custom protocol
+* Built in support for re-connection and event-id
+* Simpler protocol
+* No trouble with corporate firewalls doing packet inspection
+
+### Subscriptions and Live Queries
+
+In GraphQL, there are two options for real-time updates: **live queries** and **subscriptions**.
+
+Subscriptions are part of the GraphQL specification, whereas live queries are not.
+
+There are times where subscriptions are well-suited for a realtime problem — and in some cases live queries may be a better fit. Later we’ll explore the pros and cons of each approach and how best to decide that to use and when.
+
+### Defer and Stream
+
+[Stream and defer](https://the-guild.dev/graphql/yoga-server/docs/features/defer-stream) are directives that allow you to improve latency for clients by sending data the most important data as soon as it's ready.
+
+As applications grow, the GraphQL operation documents can get bigger. The server will only send the response back once all the data requested in the query is ready. However, not all requested data is of equal importance, and the client may not need all of the data at once.
+
+#### Using Defer
+
+The `@defer`` directive allows you to post-pone the delivery of one or more (slow) fields grouped in an inlined or spread fragment.
+
+#### Using Stream
+
+The '@stream' directive allows you to stream the individual items of a field of the list type as the items are available.
+
+:::info
+The `@stream` directive is currently **not** supported by Apollo GraphQL client.
+:::
+
+## Features
+
+RedwoodJS Realtime handles the hard parts of a GraphQL Realtime implementation by automatically:
+
+- allowing GraphQL Subscription operations to be handled
+- merging in your subscriptions types and mapping their handler functions (subscribe, and resolve) to your GraphQL schema letting you keep your subscription logic organized and apart from services (your subscription my use a service to respond to an event)
+- authenticating subscription requests using the same `@requireAuth` directives already protecting other queries and mutations (or you can implement your own validator directive)
+- adding in the `@live` query directive to your GraphQL schema and setting up the `useLiveQuery` envelop plugin to handle requests, invalidation, and managing the storage mechanism needed
+- creating and configuring in-memory and persisted Redis stores uses by the PubSub transport for subscriptions and Live Queries (and letting you switch between them in development and production)
+- placing the pubSub transport and stores into the GraphQL context so you can use them in services, subscription resolvers, or elsewhere (like a webhook, function, or job) to publish an event or invalidate data
+- typing you subscription channel event payloads
+- support `@defer` and `@stream` directives
+
+It provides a first-class developer experience for real-time updates with GraphQL so you can easily
+
+- respond to an event (e.g. NewPost, NewUserNotification)
+- respond to a data change (e.g. Post 123's title updated)
+
+and have the latest data reflected in your app.
+
+Lastly, the Redwood CLI has commands to
+
+- generate a boilerplate implementation and sample code needed to create your custom
+ - subscriptions
+ - live Queries
+
+Regardless of the implementation chosen, **a stateful server and store are needed** to track changes, invalidation, or who wants to be informed about the change.
+
+### What can I build with Realtime?
+
+- Application Alerts and Messages
+- User Notifications
+- Live Charts
+- Location updates
+- Auction bid updates
+- Messaging
+- OpenAI streaming responses
+
+## RedwoodJS Realtime Setup
+
+To setup Realtime in an existing RedwoodJS project, run the following commands:
+
+* `yarn rw exp setup-server-file`
+* `yarn rw exp setup-realtime`
+
+You will get:
+
+* `api/server.ts` where you configure your Fastify server and GraphQL
+* `api/lib/realtime.ts` where you consume your subscriptions and configure realtime with an in-memory or Redis store
+* Usage examples for live queries, subscriptions, defer, and stream. You'll get sdl, services/subscriptions for each.
+* The [`auction` live query](#auction-live-query-example) example
+* The [`countdown timer` subscription](#countdown-timer-example) example
+* The [`chat` subscription](#chatnew-message-example) examples
+* The [`alphabet` stream](#alphabet-stream-example) example
+* The [`slow and fast` field defer](#slow-and-fast-field-defer-example) example
+
+:::note
+There is no UI setup for these examples. You can find information on how to try them out using the GraphiQL playground.
+:::
+
+### GraphQL Configuration
+
+Now that how have a serverful project, you will configure your GraphQL server in the `api/server.ts` file.
+
+:::important
+That means you **must** manually configure your GraphQL server accordingly
+:::
+
+For example, you will have to setup any authentication and the realtime config:
+
+```ts
+ await fastify.register(redwoodFastifyGraphQLServer, {
+ // If authenticating, be sure to import and add in
+ // authDecoder,
+ // getCurrentUser,
+ loggerConfig: {
+ logger: logger,
+ options: {
+ query: true,
+ data: true,
+ operationName: true,
+ requestId: true,
+ },
+ },
+ graphiQLEndpoint: enableWeb ? '/.redwood/functions/graphql' : '/graphql',
+ sdls,
+ services,
+ directives,
+ allowIntrospection: true,
+ allowGraphiQL: true,
+ // Configure if using RedwoodJS Realtime
+ realtime,
+ })
+```
+
+You can now remove the GraphQL handler function that resides in `api/functions/graphql.ts`.
+
+### Realtime Configuration
+
+By default, RedwoodJS realtime configures an in-memory store for the Pub Sub client used with subscriptions and live query invalidation.
+
+Realtime supports in-memory and Redis stores:
+
+- In-memory stores are useful for development and testing.
+- Redis stores are useful for production.
+
+To enable defer and streaming, set `enableDeferStream` to true.
+
+Configure a Redis store and defer and stream in:
+
+```ts
+// api/lib/realtime.ts
+import { RedwoodRealtimeOptions } from '@redwoodjs/realtime'
+
+import subscriptions from 'src/subscriptions/**/*.{js,ts}'
+
+// if using a Redis store
+// import { Redis } from 'ioredis'
+// const publishClient = new Redis()
+// const subscribeClient = new Redis()
+
+/**
+ * Configure RedwoodJS Realtime
+ *
+ * See https://redwoodjs.com/docs/realtime
+ *
+ * Realtime supports Live Queries and Subscriptions over GraphQL SSE.
+ *
+ * Live Queries are GraphQL queries that are automatically re-run when the data they depend on changes.
+ *
+ * Subscriptions are GraphQL queries that are run when a client subscribes to a channel.
+ *
+ * Redwood Realtime
+ * - uses a publish/subscribe model to broadcast data to clients.
+ * - uses a store to persist Live Query and Subscription data.
+ *
+ * Redwood Realtime supports in-memory and Redis stores:
+ * - In-memory stores are useful for development and testing.
+ * - Redis stores are useful for production.
+ */
+export const realtime: RedwoodRealtimeOptions = {
+ subscriptions: {
+ subscriptions,
+ store: 'in-memory',
+ // if using a Redis store
+ // store: { redis: { publishClient, subscribeClient } },
+ },
+ liveQueries: {
+ store: 'in-memory',
+ // if using a Redis store
+ // store: { redis: { publishClient, subscribeClient } },
+ },
+ // To enable defer and streaming, set to true.
+ // enableDeferStream: true,
+}
+```
+
+#### PubSub and LiveQueryStore
+
+By setting up RedwoodJS Realtime, the GraphQL server adds two helpers on the context:
+
+* pubSub
+* liveQueryStory
+
+With `context.pubSub` you can subscribe to and publish messages via `context.pubSub.publish('the-topic', id, id2)`.
+
+With `context.liveQueryStore.` you can `context.liveQueryStore.invalidate(key)` where your key may be a reference or schema coordinate:
+
+##### Reference
+Where the query is: `auction(id: ID!): Auction @requireAuth`:
+
+* `"Auction:123"`
+
+##### Schema Coordinate
+When the query is: `auctions: [Auction!]! @requireAuth`:
+
+* `"Query.auctions"`
+
+## Subscriptions
+
+RedwoodJS has a first-class developer experience for GraphQL subscriptions.
+
+#### Subscribe to Events
+
+- Granular information on what data changed
+- Why has the data changed?
+- Spec compliant
+
+### Chat/New Message Example
+
+```graphql
+type Subscription {
+ newMessage(roomId: ID!): Message! @requireAuth
+}
+```
+
+1. I subscribed to a "newMessage” in room “2”
+2. Someone added a message to room “2” with a from and body
+3. A "NewMessage" event to Room 2 gets published
+4. I find out and see who the message is from and what they messaged (the body)
+
+### Countdown Timer Example
+
+Counts down from a starting values by an interval.
+
+```graphql
+subscription CountdownFromInterval {
+ countdown(from: 100, interval: 10)
+}
+```
+
+This example showcases how a subscription can yields its own response.
+
+## Live Queries
+
+RedwoodJS has made it super easy to add live queries to your GraphQL server! You can push new data to your clients automatically once the data selected by a GraphQL operation becomes stale by annotating your query operation with the `@live` directive.
+
+The invalidation mechanism is based on GraphQL ID fields and schema coordinates. Once a query operation has been invalidated, the query is re-executed, and the result is pushed to the client.
+
+##### Listen for Data Changes
+
+- I'm not interested in what exactly changed it.
+- Just give me the data.
+- This is not part of the GraphQL specification.
+- There can be multiple root fields.
+
+### Auction Live Query Example
+
+```graphql
+query GetCurrentAuctionBids @live {
+ auction(id: "1") {
+ bids {
+ amount
+ }
+ highestBid {
+ amount
+ }
+ id
+ title
+ }
+}
+
+mutation MakeBid {
+ bid(input: { auctionId: "1", amount: 10 }) {
+ amount
+ }
+}
+```
+
+1. I listen for changes to Auction 1 by querying the auction.
+2. A bid was placed on Auction 1.
+3. The information for Auction 1 is no longer valid.
+4. My query automatically refetches the latest Auction and Bid details.
+
+## Defer Directive
+
+The `@defer` directive allows you to post-pone the delivery of one or more (slow) fields grouped in an inlined or spread fragment.
+
+### Slow and Fast Field Defer Example
+
+Here, the GraphQL schema defines two queries for a "fast" and a "slow" (ie, delayed) information.
+
+```graphql
+export const schema = gql`
+ type Query {
+ """
+ A field that resolves fast.
+ """
+ fastField: String! @skipAuth
+
+ """
+ A field that resolves slowly.
+ Maybe you want to @defer this field ;)
+ """
+ slowField(waitFor: Int! = 5000): String @skipAuth
+ }
+`
+```
+
+The Redwood services for these queries return the `fastField` immediately and the `showField` after some delay.
+
+```ts
+import { logger } from 'src/lib/logger'
+
+const wait = (time: number) =>
+ new Promise((resolve) => setTimeout(resolve, time))
+
+export const fastField = async () => {
+ return 'I am speedy'
+}
+
+export const slowField = async (_, { waitFor = 5000 }) => {
+ logger.debug('deferring slowField until ...')
+ await wait(waitFor)
+ logger.debug('now!')
+
+ return 'I am slow'
+}
+```
+
+When making the query:
+
+```graphql
+query SlowAndFastFieldWithDefer {
+ ... on Query @defer {
+ slowField
+ }
+ fastField
+}
+```
+
+The response returns:
+
+```json
+{
+ "data": {
+ "fastField": "I am speedy"
+ }
+}
+```
+
+and will await the deferred field to then present:
+
+```json
+{
+ "data": {
+ "fastField": "I am speedy",
+ "slowField": "I am slow"
+ }
+}
+```
+
+## Stream Directive
+
+The `@stream` directive allows you to stream the individual items of a field of the list type as the items are available.
+
+### Alphabet Stream Example
+
+Here, the GraphQL schema defines a query to return the letters of the alphabet:
+
+```graphql
+export const schema = gql`
+ type Query {
+ alphabet: [String!]! @skipAuth
+`
+```
+
+The service uses `Repeater` to write a safe stream resolver.
+
+:::info
+[AsyncGenerators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) as declared via the `async *` keywords are prone to memory leaks and leaking timers. For real-world usage, use Repeater.
+:::
+
+```ts
+import { Repeater } from '@redwoodjs/realtime'
+
+import { logger } from 'src/lib/logger'
+
+export const alphabet = async () => {
+ return new Repeater(async (push, stop) => {
+ const values = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
+ const publish = () => {
+ const value = values.shift()
+
+ if (value) {
+ logger.debug({ value }, 'publishing')
+
+ push(value)
+ }
+
+ if (values.length === 0) {
+ stop()
+ }
+ }
+
+ const interval = setInterval(publish, 1000)
+
+ stop.then(() => {
+ logger.debug('cancel')
+ clearInterval(interval)
+ })
+
+ publish()
+ })
+}
+```
+
+### What does the incremental stream look like?
+
+Since Apollo Client does not yet support the `@stream` directive, you can use them in the GraphiQL Playground or see them in action via CURL.
+
+When making the request with the `@stream` directive:
+
+```bash
+curl -g -X POST \
+ -H "accept:multipart/mixed" \
+ -H "content-type: application/json" \
+ -d '{"query":"query StreamAlphabet { alphabet @stream }"}' \
+ http://localhost:8911/graphql
+```
+
+Here you see the initial response has `[]` for alphabet data.
+
+Then on each push to the Repeater, an incremental update the the list of letters is sent.
+
+The stream ends when `hasNext` is false:
+
+```bash
+* Connected to localhost (127.0.0.1) port 8911 (#0)
+> POST /graphql HTTP/1.1
+> Host: localhost:8911
+> User-Agent: curl/8.1.2
+> accept:multipart/mixed
+> content-type: application/json
+> Content-Length: 53
+>
+< HTTP/1.1 200 OK
+< connection: keep-alive
+< content-type: multipart/mixed; boundary="-"
+< transfer-encoding: chunked
+<
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 39
+
+{"data":{"alphabet":[]},"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["b"],"path":["alphabet",1]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["c"],"path":["alphabet",2]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["d"],"path":["alphabet",3]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["e"],"path":["alphabet",4]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["f"],"path":["alphabet",5]}],"hasNext":true}
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 70
+
+{"incremental":[{"items":["g"],"path":["alphabet",6]}],"hasNext":true}
+---
+...
+
+---
+Content-Type: application/json; charset=utf-8
+Content-Length: 17
+
+{"hasNext":false}
+-----
+```
+
+## How do I choose Subscriptions or Live Queries?
+
+![image](https://github.com/ahaywood/redwoodjs-streaming-realtime-demos/assets/1051633/e3c51908-434c-4396-856a-8bee7329bcdd)
+
+When deciding on how to offer realtime data updates in your RedwoodJS app, you’ll want to consider:
+
+- How frequently do your users require information updates?
+ - Determine the value of "real-time" versus "near real-time" to your users. Do they need to know in less than 1-2 seconds, or is 10, 30, or 60 seconds acceptable for them to receive updates?
+ - Consider the criticality of the data update. Is it low, such as a change in shipment status, or higher, such as a change in stock price for an investment app?
+ - Consider the cost of maintaining connections and tracking updates across your user base. Is the infrastructure cost justifiable?
+ - If you don't require "real" real-time, consider polling for data updates on a reasonable interval. According to Apollo, [in most cases](https://www.apollographql.com/docs/react/data/subscriptions/), your client should not use subscriptions to stay up to date with your backend. Instead, you should poll intermittently with queries or re-execute queries on demand when a user performs a relevant action, such as clicking a button.
+- How are you deploying? Serverless or Serverful?
+ - Real-time options depend on your deployment method.
+ - If you are using a serverless architecture, your application cannot maintain a stateful connection to your users' applications. Therefore, it's not easy to "push," "publish," or "stream" data updates to the web client.
+ - In this case, you may need to look for third-party solutions that manage the infrastructure to maintain such stateful connections to your web client, such as [Supabase Realtime](https://supabase.com/realtime), [SendBird](https://sendbird.com/), [Pusher](https://pusher.com/), or consider creating your own [AWS SNS-based](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) functionality.
+
+
+
+## Showcase Demos
+
+Please see our [showcase RedwoodJS Realtime app](https://realtime-demo.fly.dev) for exampes of subscriptions and live queries. It also demonstrates how you can handle streaming responses, like those used by OpenAI chat completions.
+
+### Chat Room (Subscription)
+
+Sends a message to one of four Chat Rooms.
+
+Each room subscribes to its new messages via the `NewMessage` channel aka topic.
+
+```ts
+context.pubSub.publish('newMessage', roomId, { from, body })
+```
+
+#### Simulate
+
+```bash
+./scripts/simulate_chat.sh -h
+Usage: ./scripts/simulate_chat.sh -r [roomId] -n [num_messages]
+ ./scripts/simulate_chat.sh -h
+
+Options:
+ -r roomId Specify the room ID (1-4) for sending chat messages.
+ -n num_messages Specify the number of chat messages to send. If not provided, the script will run with a random number of messages.
+```
+#### Test
+
+```ts
+/**
+ * To test this NewMessage subscription, run the following in one GraphQL Playground to subscribe:
+ *
+ * subscription ListenForNewMessagesInRoom {
+ * newMessage(roomId: "1") {
+ * body
+ * from
+ * }
+ * }
+ *
+ *
+ * And run the following in another GraphQL Playground to publish and send a message to the room:
+ *
+ * mutation SendMessageToRoom {
+ * sendMessage(input: {roomId: "1", from: "hello", body: "bob"}) {
+ * body
+ * from
+ * }
+ * }
+ */
+ ```
+
+### Auction Bids (Live Query)
+
+Bid on a fancy pair of new sneaks!
+
+When a bid is made, the auction updates via a Live Query due to the invalidation of the auction key.
+
+```ts
+ const key = `Auction:${auctionId}`
+ context.liveQueryStore.invalidate(key)
+ ```
+
+#### Simulate
+
+```bash
+./scripts/simulate_bids.sh -h
+Usage: ./scripts/simulate_bids.sh [options]
+
+Options:
+ -a Specify the auction ID (1-5) for which to send bids (optional).
+ -n Specify the number of bids to send (optional).
+ -h, --help Display this help message.
+ ```
+
+#### Test
+
+```ts
+/**
+ * To test this live query, run the following in the GraphQL Playground:
+ *
+ * query GetCurrentAuctionBids @live {
+ * auction(id: "1") {
+ * bids {
+ * amount
+ * }
+ * highestBid {
+ * amount
+ * }
+ * id
+ * title
+ * }
+ * }
+ *
+ * And then make a bid with the following mutation:
+ *
+ * mutation MakeBid {
+ * bid(input: {auctionId: "1", amount: 10}) {
+ * amount
+ * }
+ * }
+ */
+```
+
+### Countdown (Streaming Subscription)
+
+> It started slowly and I thought it was my heart
+> But then I realised that this time it was for real
+
+Counts down from a starting values by an interval.
+
+This example showcases how a subscription can yields its own response.
+
+#### Test
+
+```ts
+/**
+ * To test this Countdown subscription, run the following in the GraphQL Playground:
+ *
+ * subscription CountdownFromInterval {
+ * countdown(from: 100, interval: 10)
+ * }
+ */
+```
+
+### Bedtime Story (Subscription with OpenAI Streaming)
+
+> Tell me a story about a happy, purple penguin that goes to a concert.
+
+Showcases how to use OpenAI to stream a chat completion via a prompt that writes a bedtime story:
+
+```ts
+const PROMPT = `Write a short children's bedtime story about an Animal that is a given Color and that does a given Activity.
+
+Give the animal a cute descriptive and memorable name.
+
+The story should teach a lesson.
+
+The story should be told in a quality, style and feeling of the given Adjective.
+
+The story should be no longer than 3 paragraphs.
+
+Format the story using Markdown.`
+
+```
+
+The story updates on each stream content delta via a `newStory` subscription topic event.
+
+```ts
+context.pubSub.publish('newStory', id, story)
+```
+
+### Movie Mashup (Live Query with OpenAI Streaming)
+
+> It's Out of Africa meets Pretty Woman.
+
+> So it's a psychic, political, thriller comedy with a heart With a heart, not unlike Ghost meets Manchurian Candidate.
+
+-- The Player, 1992
+
+Mashup some of your favorite movies to create something new and Netflix-worthy to watch.
+
+Powered by OpenAI, this movie tagline and treatment updates on each stream content delta via a Live Query bui invalidating the `MovieMashup key.
+
+```ts
+context.liveQueryStore.invalidate(`MovieMashup:${id}`)
+```
diff --git a/docs/versioned_docs/version-6.0/redwoodrecord.md b/docs/versioned_docs/version-6.x/redwoodrecord.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/redwoodrecord.md
rename to docs/versioned_docs/version-6.x/redwoodrecord.md
diff --git a/docs/versioned_docs/version-6.0/router.md b/docs/versioned_docs/version-6.x/router.md
similarity index 94%
rename from docs/versioned_docs/version-6.0/router.md
rename to docs/versioned_docs/version-6.x/router.md
index 98d1493d9ab9..a4d48c6d0c56 100644
--- a/docs/versioned_docs/version-6.0/router.md
+++ b/docs/versioned_docs/version-6.x/router.md
@@ -44,9 +44,7 @@ The `path` prop specifies the URL path to match, starting with the beginning sla
## Private Routes
-Some pages should only be visible to authenticated users.
-
-We support this using private `` component. Read more [further down](#private-set).
+Some pages should only be visible to authenticated users. We support this using the `PrivateSet` component. Read more [further down](#privateset).
## Sets of Routes
@@ -89,7 +87,7 @@ Conceptually, this fits with how we think about Context and Layouts as things th
There's a lot of flexibility here. You can even nest `Sets` to great effect:
```jsx title="Routes.js"
-import { Router, Route, Set, Private } from '@redwoodjs/router'
+import { Router, Route, Set } from '@redwoodjs/router'
import BlogContext from 'src/contexts/BlogContext'
import BlogLayout from 'src/layouts/BlogLayout'
import BlogNavLayout from 'src/layouts/BlogNavLayout'
@@ -130,24 +128,11 @@ becomes...
```
-### `private` Set
+### `PrivateSet`
-Sets can take a `private` prop which makes all Routes inside that Set require authentication. When a user isn't authenticated and attempts to visit one of the Routes in the private Set, they'll be redirected to the Route passed as the Set's `unauthenticated` prop. The originally-requested Route's path is added to the query string as a `redirectTo` param. This lets you send the user to the page they originally requested once they're logged-in.
+A `PrivateSet` makes all Routes inside that Set require authentication. When a user isn't authenticated and attempts to visit one of the Routes in the `PrivateSet`, they'll be redirected to the Route passed as the `PrivateSet`'s `unauthenticated` prop. The originally-requested Route's path is added to the query string as a `redirectTo` param. This lets you send the user to the page they originally requested once they're logged-in.
-Here's an example of how you'd use a private set:
-
-```jsx title="Routes.js"
-
-
-
-
-
-
-```
-
-Private routes are important and should be easy to spot in your Routes file. The larger your Routes file gets, the more difficult it will probably become to find `` among your other Sets. So we also provide a `` component that's just an alias for ``. Most of our documentation uses ``.
-
-Here's the same example again, but now using ``
+Here's an example of how you'd use a `PrivateSet`:
```jsx title="Routes.js"
@@ -160,7 +145,7 @@ Here's the same example again, but now using ``
For more fine-grained control, you can specify `roles` (which takes a string for a single role or an array of roles), and the router will check to see that the current user is authorized before giving them access to the Route. If they're not, they will be redirected to the page specified in the `unauthenticated` prop, such as a "forbidden" page. Read more about Role-based Access Control in Redwood [here](how-to/role-based-access-control.md).
-To protect `PrivateSet` routes for access by a single role:
+To protect private routes for access by a single role:
```jsx title="Routes.js"
@@ -172,7 +157,7 @@ To protect `PrivateSet` routes for access by a single role:
```
-To protect `PrivateSet` routes for access by multiple roles:
+To protect private routes for access by multiple roles:
```jsx title="Routes.js"
@@ -261,7 +246,9 @@ More granular match, `page` key only and `tab=tutorial`
activeMatchParams={[{ tab: 'tutorial' }, 'page' ]}
```
-You can `useMatch` to create your own component with active styles.
+### useMatch
+
+You can use `useMatch` to create your own component with active styles.
> `NavLink` uses it internally!
@@ -528,7 +515,7 @@ Redwood will detect your explicit import and refrain from splitting that page in
Because lazily-loaded pages can take a non-negligible amount of time to load (depending on bundle size and network connection), you may want to show a loading indicator to signal to the user that something is happening after they click a link.
-In order to show a loader as your page chunks are loading, you simply add the `whileLoadingPage` prop to your route, `Set` or `Private` component.
+In order to show a loader as your page chunks are loading, you simply add the `whileLoadingPage` prop to your route, `Set` or `PrivateSet` component.
```jsx title="Routes.js"
import SkeletonLoader from 'src/components/SkeletonLoader'
@@ -574,7 +561,7 @@ When the lazy-loaded page is loading, `PageLoadingContext.Consumer` will pass `{
Let's say you have a dashboard area on your Redwood app, which can only be accessed after logging in. When Redwood Router renders your private page, it will first fetch the user's details, and only render the page if it determines the user is indeed logged in.
-In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private ``, `` component:
+In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your `PrivateSet` component:
```jsx
//Routes.js
@@ -677,7 +664,7 @@ Note that if you're copy-pasting this example, it uses [Tailwind CSS](https://ta
:::note Can I customize the development one?
-As it's part of the RedwoodJS framework, you can't. But if there's a feature you want to add, let us know on the [forums](https://community.redwoodjs.com/).
+As it's part of the RedwoodJS framework, you can't _change_ the dev fatal error page, but you can always build your own that takes the same props. If there's a feature you want to add to the built-in version, let us know on the [forums](https://community.redwoodjs.com/).
:::
diff --git a/docs/versioned_docs/version-6.0/schema-relations.md b/docs/versioned_docs/version-6.x/schema-relations.md
similarity index 95%
rename from docs/versioned_docs/version-6.0/schema-relations.md
rename to docs/versioned_docs/version-6.x/schema-relations.md
index 8c5e65b71358..0d0b3add404b 100644
--- a/docs/versioned_docs/version-6.0/schema-relations.md
+++ b/docs/versioned_docs/version-6.x/schema-relations.md
@@ -39,7 +39,7 @@ model Tag {
}
```
-These relationships can be [implicit](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#implicit-many-to-many-relations) (as this diagram shows) or [explicit](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#explicit-many-to-many-relations) (explained below). Redwood's SDL generator (which is also used by the scaffold generator) only supports an **explicit** many-to-many relationship when generating with the `--crud` flag. What's up with that?
+These relationships can be [implicit](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#implicit-many-to-many-relations) (as this diagram shows) or [explicit](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#explicit-many-to-many-relations) (explained below). Redwood's SDL generator (which is also used by the scaffold generator) only supports an **explicit** many-to-many relationship when generating with the `--crud` flag. What's up with that?
## CRUD Requires an `@id`
diff --git a/docs/versioned_docs/version-6.0/security.md b/docs/versioned_docs/version-6.x/security.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/security.md
rename to docs/versioned_docs/version-6.x/security.md
diff --git a/docs/versioned_docs/version-6.x/seo-head.md b/docs/versioned_docs/version-6.x/seo-head.md
new file mode 100644
index 000000000000..7cb5772edd62
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/seo-head.md
@@ -0,0 +1,356 @@
+---
+description: Use meta tags to set page info for SEO
+---
+
+# SEO & `` tags
+
+Search Engine Optimization is a dark art that some folks dedicate their entire lives to. We've add a couple of features to Redwood to make HTML-based SEO fairly simple.
+
+## Adding a Title
+
+You certainly want to change the title of your Redwood app from the default of "Redwood App." You can start by adding or modifying `title` inside of `/redwood.toml`
+
+```diff title=redwood.toml
+[web]
+- title = "Redwood App"
++ title = "My Cool App"
+ port = 8910
+ apiUrl = "/.redwood/functions"
+```
+
+This title (the app title) is used by default for all your pages if you don't define another one.
+It will also be use for the title template.
+
+### Title Template
+
+Now that you have the app title set, you probably want some consistence with the page title, that's what the title template is for.
+
+Add `titleTemplate` as a prop for `RedwoodProvider` to have a title template for every pages
+
+```diff title=web/src/App.(tsx|jsx)
+-
++
+ /* ... */
+
+```
+
+You can use whatever formatting you'd like in here. Some examples:
+
+```jsx
+"%PageTitle | %AppTitle" => "Home Page | Redwood App"
+
+"%AppTitle · %PageTitle" => "Redwood App · Home Page"
+
+"%PageTitle : %AppTitle" => "Home Page : Redwood App"
+```
+
+## Adding to Page ``
+
+So you want to change the title of your page, or add elements to the `` of the page? We've got you!
+
+Let's say you want to change the title of your About page, Redwood provides a built in `` component, which you can use like this:
+
+```diff title=web/src/pages/AboutPage/AboutPage.(tsx|jsx)
++import { Head } from '@redwoodjs/web'
+
+const AboutPage = () => {
+ return (
+
+
AboutPage
++
++ About the team
++
+```
+
+You can include any valid `` tag in here that you like. However, Redwood also provides a utility component [<Metadata>](#setting-meta-tags-and-opengraph-directives-with-metadata).
+
+:::caution `` Deprecation
+
+Prior to Redwood 6.6.0 this component was called `` and had several special hard-coded props like `ogContentUrl`, which didn't properly map to the OpenGraph spec. We'll still render `` for the foreseeable future, but it's deprecated and you should migrate to `` if you have an existing app.
+
+:::
+
+### What About Nested Tags?
+
+Redwood uses [react-helmet-async](https://github.com/staylor/react-helmet-async) underneath, which will use the tags furthest down your component tree.
+
+For example, if you set title in your Layout, and a title in your Page, it'll render the one in Page - this way you can override the tags you wish, while sharing the tags defined in Layout.
+
+:::info Bots & `` Tags
+
+For these headers to appear to bots and scrapers e.g. for twitter to show your title, you have to make sure your page is prerendered. If your content is static you can use Redwood's built in [Prerender](prerender.md). For dynamic tags, check the [Dynamic head tags](#dynamic-tags)
+
+:::
+
+## Setting `` Tags and OpenGraph Directives with ``
+
+Often we want to set more than just the title and description of the page—most commonly [OpenGraph](https://ogp.me/) headers.
+
+Redwood provides a convenience component `` to help you create most of these `` tags for you with a more concise syntax. But, you can also pass children and define any custom content that you want.
+
+Here's an example setting some common meta, including a page title, description, `og:image` and an `http-equiv`:
+
+```jsx
+import { Metadata } from '@redwoodjs/web'
+
+const AboutPage = () => {
+ return (
+
+
+
+
+
+
About Page
+
This is the about page!
+
+ )
+}
+
+export default AboutPage
+```
+
+This code would be transformed into this HTML and injected into the `` tag:
+
+```html
+About page
+
+
+
+
+
+
+
+
+
+```
+
+Setting an `og:image` is how sites like Facebook and Slack can show a preview of a URL when pasted into a post (also known as "unfurling"):
+
+![Typical URL unfurl](/img/facebook_unfurl.png)
+
+Sites like GitHub go a step farther than a generic image by actually creating an image for a repo on the fly, including details about the repo itself:
+
+![GitHub's og:image for the redwood repo](https://opengraph.githubassets.com/322ce8081bb85a86397a59494eab1c0fbe942b5104461f625e2c973c46ae4179/redwoodjs/redwood)
+
+If you want to write your own `` tags, skipping the interpolation that `` does for you, you can pass them as children to `` or just write them into the `` tag as normal.
+
+### `` Props
+
+For the most part `` creates simple `` tags based on the structure of the props you pass in. There are a couple of special behaviors described below.
+
+#### Plain Key/Value Props
+
+Any "plain" key/value prop will be turned into a `` tag with `name` and `content` attributes:
+
+```jsx
+
+// generates
+
+```
+
+Child elements are just copied 1:1 to the resulting output:
+
+```jsx
+
+
+
+// generates
+
+
+```
+
+#### Passing Objects to Props
+
+Any props that contain an object will create a `` tag with `property` and `content` attributes, and the `property` being the names of the nested keys with a `:` between each:
+
+```jsx
+
+// generates
+
+```
+
+This is most commonly used to create the "nested" structure that a spec like OpenGraph uses:
+
+```jsx
+
+// generates
+
+```
+
+You can create multiple `` tags with the same name/property (allowed by the OpenGraph spec) by using an array:
+
+```jsx
+
+// generates
+
+
+```
+
+You can combine nested objects with strings to create any structure you like:
+
+```jsx
+
+// generates
+
+
+
+
+
+
+
+```
+
+#### Special OpenGraph Helpers
+
+If you define _any_ `og` prop, we will copy any `title` and `description` to an `og:title` and `og:description`:
+
+```jsx
+
+// generates
+
+
+```
+
+You can override this behavior by explicitly setting `og:title` or `og:description` to `null`:
+
+```jsx
+
+// generates
+
+```
+
+Of course, if you don't want any auto-generated `og` tags, then don't include any `og` prop at all!
+
+In addition to `og:title` and `og:description`, if you define _any_ `og` prop we will generate an `og:type` set to `website`:
+
+```jsx
+
+// generates
+
+```
+
+You can override the `og:type` by setting it directly:
+
+```jsx
+
+// generates
+
+```
+
+#### Other Special Cases
+
+If you define a `title` prop we will automatically prepend a `` tag to the output:
+
+```jsx
+
+// generates
+My Website
+
+```
+
+If you define a `charSet` prop we will create a `` tag with the `charset` attribute:
+
+```jsx
+
+// generates
+
+```
+
+We simplified some of the examples above by excluding the generated `` and `og:type` tags, so here's the real output if you included `title` and `og` props:
+
+```jsx
+
+// generates
+My Website
+
+
+
+
+```
+
+:::info Do I need to apply these same tags over and over in every page?
+
+Some `` tags, like `charset` or `locale` are probably applicable to the entire site, in which case it would be simpler to just include these once in your `index.html` instead of having to set them manually on each and every page/cell of your site.
+
+:::
+
+This should allow you to create a fairly full-featured set of `` tags with minimal special syntax! A typical `` invocation could look like:
+
+```jsx
+
+```
+
+## Dynamic tags
+
+Bots will pick up our tags if we've prerendered the page, but what if we want to set the `` based on the output of the Cell?
+
+:::info Prerendering
+
+As of v3.x, Redwood supports prerendering your [Cells](https://redwoodjs.com/docs/cells) with the data you were querying. For more information please refer [to this section](https://redwoodjs.com/docs/prerender#cell-prerendering).
+
+:::
+
+Let's say in our `PostCell`, we want to set the title to match the `Post`.
+
+```jsx
+import { Metadata } from '@redwoodjs/web'
+
+import Post from 'src/components/Post/Post'
+
+export const QUERY = gql`
+ query FindPostById($id: Int!) {
+ post: post(id: $id) {
+ title
+ snippet
+ author {
+ name
+ }
+ }
+ }
+`
+
+export const Loading = /* ... */
+
+export const Empty = /* ... */
+
+export const Success = ({ post }) => {
+ return (
+ <>
+
+
+ >
+ )
+}
+```
+
+Once the `Success` component renders, it will update your page's `` and set the relevant `` tags for you!
diff --git a/docs/versioned_docs/version-6.0/serverless-functions.md b/docs/versioned_docs/version-6.x/serverless-functions.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/serverless-functions.md
rename to docs/versioned_docs/version-6.x/serverless-functions.md
index fc327a39063e..c0c53fc8a846 100644
--- a/docs/versioned_docs/version-6.0/serverless-functions.md
+++ b/docs/versioned_docs/version-6.x/serverless-functions.md
@@ -2,10 +2,19 @@
description: Create, develop, and run serverless functions
---
-# Serverless Functions
+# Serverless Functions (API Endpoints)
+
+:::info
+
+You can think of serverless functions as API Endpoints, and in the future we'll update the terminology used.
+
+Originally, Redwood apps were intended to be deployed as serverless functions to AWS Lambda. Whenever a Redwood app is deployed to a "serverful" environment such as Fly or Render, a Fastify server is started and your Redwood app's functions in `api/src/functions` are automatically registered onto the server. Request adapters are also automatically configured to handle the translation between Fastify's request and reply objects to the functions' AWS Lambda signature.
+
+:::
+
Redwood looks for serverless functions in `api/src/functions`. Each function is mapped to a URI based on its filename. For example, you can find `api/src/functions/graphql.js` at `http://localhost:8911/graphql`.
## Creating Serverless Functions
@@ -34,12 +43,6 @@ export const handler = async (event, context) => {
}
```
-:::info
-
-We call them 'serverless' but they can also be used on 'serverful' hosted environments too, such as Render or Heroku.
-
-:::
-
## The handler
For a lambda function to be a lambda function, it must export a handler that returns a status code. The handler receives two arguments: `event` and `context`. Whatever it returns is the `response`, which should include a `statusCode` at the very least.
diff --git a/docs/versioned_docs/version-6.0/services.md b/docs/versioned_docs/version-6.x/services.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/services.md
rename to docs/versioned_docs/version-6.x/services.md
index eecf04215ded..52bb916f0a8a 100644
--- a/docs/versioned_docs/version-6.0/services.md
+++ b/docs/versioned_docs/version-6.x/services.md
@@ -618,7 +618,7 @@ validate(input.value, 'Value', {
```
### validateWithSync()
-`validateWith()` is simply given a function to execute. This function should throw with a message if there is a problem, otherwise do nothing.
+`validateWithSync()` is simply given a function to execute. This function should throw with a message if there is a problem, otherwise do nothing.
```jsx
validateWithSync(() => {
@@ -636,14 +636,14 @@ validateWithSync(() => {
Either of these errors will be caught and re-thrown as a `ServiceValidationError` with your text as the `message` of the error (although technically you should always throw errors with `new Error()` like in the second example).
-You could just write your own function and throw whatever you like, without using `validateWith()`. But, when accessing your Service function through GraphQL, that error would be swallowed and the user would simply see "Something went wrong" for security reasons: error messages could reveal source code or other sensitive information so most are hidden. Errors thrown by Service Validations are considered "safe" and allowed to be shown to the client.
+You could just write your own function and throw whatever you like, without using `validateWithSync()`. But, when accessing your Service function through GraphQL, that error would be swallowed and the user would simply see "Something went wrong" for security reasons: error messages could reveal source code or other sensitive information so most are hidden. Errors thrown by Service Validations are considered "safe" and allowed to be shown to the client.
-### validateWithSync()
+### validateWith()
-The same behavior as `validateWithSync()` but works with Promises.
+The same behavior as `validateWithSync()` but works with Promises. Remember to `await` the validation.
```jsx
-validateWithSync(async () => {
+await validateWith(async () => {
if (await db.products.count() >= 100) {
throw "There can only be a maximum of 100 products in your store"
}
@@ -1114,7 +1114,7 @@ const updateUser = async ({ id, input }) => {
})
```
-:::caution
+:::warning
When explicitly deleting cache keys like this you could find yourself going down a rabbit hole. What if there is another service somewhere that also updates user? Or another service that updates an organization, as well as all of its underlying child users at the same time? You'll need to be sure to call `deleteCacheKey()` in these places as well. As a general guideline, it's better to come up with a cache key that encapsulates any triggers for when the data has changed (like the `updatedAt` timestamp, which will change no matter who updates the user, anywhere in your codebase).
diff --git a/docs/versioned_docs/version-6.0/storybook.md b/docs/versioned_docs/version-6.x/storybook.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/storybook.md
rename to docs/versioned_docs/version-6.x/storybook.md
diff --git a/docs/versioned_docs/version-6.0/testing.md b/docs/versioned_docs/version-6.x/testing.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/testing.md
rename to docs/versioned_docs/version-6.x/testing.md
index 426038a6b85a..13bebbcec89d 100644
--- a/docs/versioned_docs/version-6.0/testing.md
+++ b/docs/versioned_docs/version-6.x/testing.md
@@ -274,6 +274,7 @@ render(, {
})
```
:::
+
### Mocking useLocation
To mock `useLocation` in your component tests, wrap the component with `LocationProvider`:
@@ -288,6 +289,22 @@ render(
)
```
+### Mocking useParams
+
+To mock `useParams` in your component tests, wrap the component with `ParamsProvider`:
+
+```jsx
+import { ParamsProvider } from '@redwoodjs/router';
+
+render(
+
+
+
+)
+```
+
+The `allParams` argument accepts an object that will provide parameters as you expect them from the query parameters of a URL string. In the above example, we are assuming the URL looks like `/?param1=val1¶m2=val2`.
+
### Queries
In most cases you will want to exclude the design elements and structure of your components from your test. Then you're free to redesign the component all you want without also having to make the same changes to your test suite. Let's look at some of the functions that React Testing Library provides (they call them "[queries](https://testing-library.com/docs/queries/about/)") that let you check for *parts* of the rendered component, rather than a full string match.
@@ -1131,9 +1148,9 @@ export default NameForm
Now, we can extend the `test` file which Redwood generated. We're going to want to:
-1) Import `waitFor` from the `@redwoodjs/testing/web` library.
-2) Add an import to `@testing-library/user-event` for its `default`.
-3) Provide an `onSubmit` prop to our "renders successfully" test.
+1. Import `waitFor` from the `@redwoodjs/testing/web` library.
+2. Add an import to `@testing-library/user-event` for its `default`.
+3. Provide an `onSubmit` prop to our "renders successfully" test.
```jsx title="NameForm.test.js"
import { render, screen, waitFor } from '@redwoodjs/testing/web'
@@ -1154,9 +1171,9 @@ describe('NameForm', () => {
Finally, we'll create three simple tests which ensure our form works as expected.
-1) Does our component NOT submit when required fields are empty?
-2) Does our component submit when required fields are populated?
-3) Does our component submit, passing our (submit) handler the data we entered?
+1. Does our component NOT submit when required fields are empty?
+2. Does our component submit when required fields are populated?
+3. Does our component submit, passing our (submit) handler the data we entered?
The important takeaways are:
@@ -1261,7 +1278,7 @@ Does anyone else find it confusing that the software itself is called a "databas
When you start your test suite you may notice some output from Prisma talking about migrating the database. Redwood will automatically run `yarn rw prisma db push` against your test database to make sure it's up-to-date.
-:::caution What if I have custom migration SQL?
+:::warning What if I have custom migration SQL?
The `prisma db push` command only restores a snapshot of the current database schema (so that it runs as fast as possible). **It does not actually run migrations in sequence.** This can cause a [problem](https://github.com/redwoodjs/redwood/issues/5818) if you have certain database configuration that *must* occur as a result of the SQL statements inside the migration files.
@@ -1958,6 +1975,16 @@ console.log(testCacheClient.storage)
This is mainly helpful when you are testing for a very specific value, or have edgecases in how the serialization/deserialization works in the cache.
+## Testing Mailer
+
+If your project uses [RedwoodJS Mailer](./mailer.md) to send emails, you can [also write tests](./mailer.md#testing) to make sure that email:
+
+* is sent to an sandbox inbox
+* renders properly
+* sets the expected to, from, cc, bcc, subject attributes based on the email sending logic
+* checks that the html and text content is set correctly
+
+Since these tests send mail to a sandbox inbox, you can be confident that no emails accidentally get sent into the wild as part of your test or CI runs.
## Wrapping Up
diff --git a/docs/versioned_docs/version-6.x/toast-notifications.md b/docs/versioned_docs/version-6.x/toast-notifications.md
new file mode 100644
index 000000000000..9b22ee524bd4
--- /dev/null
+++ b/docs/versioned_docs/version-6.x/toast-notifications.md
@@ -0,0 +1,133 @@
+---
+description: Toast notifications with react-hot-toast
+---
+
+# Toast Notifications
+
+Did you know that those little popup notifications that you sometimes see at the top of a page after you've performed an action are affectionately known as "toast" notifications?
+Because they pop up like a piece of toast from a toaster!
+
+![Example toast animation](https://user-images.githubusercontent.com/300/110032806-71024680-7ced-11eb-8d69-7f462929815e.gif)
+
+Redwood supports these notifications out of the box thanks to the [react-hot-toast](https://react-hot-toast.com/) package.
+We'll refer you to their [docs](https://react-hot-toast.com/docs) since they're very thorough, but here's enough to get you going.
+
+### Add the `Toaster` Component
+
+To render toast notifications, start by adding the `Toaster` component.
+It's usually better to add it at the App or Layout-level than the Page:
+
+```jsx title="web/src/layouts/MainLayout/MainLayout.js"
+// highlight-next-line
+import { Toaster } from '@redwoodjs/web/toast'
+
+const MainLayout = ({ children }) => {
+ return (
+ <>
+ // highlight-next-line
+
+ {children}
+ >
+ )
+}
+
+export default MainLayout
+```
+
+### Call the `toast` function
+
+To render a basic toast notification with default styles, call the `toast` function:
+
+```jsx title="web/src/layouts/MainLayout/MainLayout.js"
+import { toast } from '@redwoodjs/web/toast'
+
+// ...
+
+const PostForm = () => {
+ const [create, { loading, error }] = useMutation(CREATE_POST_MUTATION)
+
+ const onSubmit = async (data) => {
+ try {
+ await create({ variables: { input: data }})
+ // highlight-next-line
+ toast('Post created')
+ }
+ catch (e) {
+ // highlight-next-line
+ toast('Error creating post')
+ }
+ }
+
+ return (
+ //
+ )
+})
+
+export default PostForm
+```
+
+### Call the `toast` variants
+
+To render a toast notification with default icons and default styles, call the `toast` variants:
+
+```jsx title="web/src/components/PostForm/PostForm.js"
+import { toast } from '@redwoodjs/web/toast'
+
+// ...
+
+const PostForm = () => {
+ const [create, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
+ onCompleted: () => {
+ // highlight-next-line
+ toast.success('Post created')
+ }
+ onError: () => {
+ // highlight-next-line
+ toast.error('Error creating post')
+ }
+ })
+
+ const onSubmit = (data) => {
+ create({ variables: { input: data }})
+ }
+
+ return (
+ //
+ )
+})
+
+export default PostForm
+```
+
+or render an async toast by calling the `toast.promise` function:
+
+```jsx title="web/src/components/PostForm/PostForm.js"
+import { toast } from '@redwoodjs/web/toast'
+
+// ...
+
+const PostForm = () => {
+ const [create, { loading, error }] = useMutation(CREATE_POST_MUTATION)
+
+ const onSubmit = (data) => {
+ // highlight-next-line
+ toast.promise(create({ variables: { input: data }}), {
+ loading: 'Creating post...',
+ success: 'Post created',
+ error: 'Error creating post',
+ })
+ }
+
+ return (
+ //
+ )
+})
+
+export default PostForm
+```
+
+:::warning
+
+You can't use the [onError](https://www.apollographql.com/docs/react/api/react/hooks/#onerror) callback in combination with the `toast.promise` function.
+
+:::
diff --git a/docs/versioned_docs/version-6.0/tutorial/afterword.md b/docs/versioned_docs/version-6.x/tutorial/afterword.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/afterword.md
rename to docs/versioned_docs/version-6.x/tutorial/afterword.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter0/what-is-redwood.md b/docs/versioned_docs/version-6.x/tutorial/chapter0/what-is-redwood.md
similarity index 91%
rename from docs/versioned_docs/version-6.0/tutorial/chapter0/what-is-redwood.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter0/what-is-redwood.md
index 652f36148400..e779cafefdf2 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter0/what-is-redwood.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter0/what-is-redwood.md
@@ -28,10 +28,10 @@ You can start them both with a single command: `yarn redwood dev`
### The Router
-When you open your web app in a browser, React does its thing initializing your app and monitoring the history for changes so that new content can be shown. Redwood features a custom, declaritive Router that lets you specify URLs and the requisite pages (just a React component) will be shown. A simple routes file may look something like:
+When you open your web app in a browser, React does its thing initializing your app and monitoring the history for changes so that new content can be shown. Redwood features a custom, declarative Router that lets you specify URLs and the requisite pages (just a React component) will be shown. A simple routes file may look something like:
```jsx
-import { Set, Router, Route } from '@redwoodjs/router'
+import { Route, Router, Set, PrivateSet } from '@redwoodjs/router'
import ApplicationLayout from 'src/layouts/ApplicationLayout'
import { useAuth } from './auth'
@@ -41,10 +41,10 @@ const Routes = () => {
-
+
-
+
@@ -54,7 +54,7 @@ const Routes = () => {
}
```
-You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.
+You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.
#### Prerender
@@ -66,7 +66,7 @@ This is Redwood's version of static site generation, aka SSG.
### Authentication
-The `` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!
+The `` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!
Once authenticated, how do you know what a user is allowed to do or not do? Redwood includes helpers for [role-based access control](https://redwoodjs.com/docs/how-to/role-based-access-control-rbac) that integrates on both the front- and backend.
@@ -99,7 +99,7 @@ Going back to our testimonals hypothetical, a cell to fetch and display them may
```js
export const QUERY = gql`
query GetTestimonials {
- testimonals {
+ testimonials {
id
author
quote
@@ -122,11 +122,11 @@ export const Success = ({ testimonials }) => {
}
```
-(In this case we don't export `Empty` so that if there aren't any, that section of the final page won't render anything, not even indicating to the user that something is missing.)
+(In this case we don't export `Empty` so that if there aren't any testimonials, that section of the final page won't render anything, not even indicating to the user that something is missing.)
If you ever create additional clients for your server (a mobile app, perhaps) you'll be giving yourself a huge advantage by using GraphQL from the start.
-Oh, and prerendering also works with cells! At build time, Redwood will start up the GraphQL server and make requests, just as if a user was access the pages, rendering the result to plain HTML, ready to be loaded instantly by the browser.
+Oh, and prerendering also works with cells! At build time, Redwood will start up the GraphQL server and make requests, just as if a user was accessing the pages, rendering the result to plain HTML, ready to be loaded instantly by the browser.
### Apollo Cache
@@ -170,7 +170,7 @@ model Testimonial {
}
```
-Prisma has a couple command line tools that take changes to this file and turn them into [SQL DDL commands](https://www.sqlshack.com/sql-ddl-getting-started-with-sql-ddl-commands-in-sql-server/) which are executed against your database to update its structure to match.
+Prisma has a couple of command line tools that take changes to this file and turn them into [SQL DDL commands](https://www.sqlshack.com/sql-ddl-getting-started-with-sql-ddl-commands-in-sql-server/) which are executed against your database to update its structure to match.
#### GraphQL
@@ -234,7 +234,7 @@ export const schema = gql`
`
```
-The `testimonials` query is marked with the [GraphQL directive](../../directives.md) `@skipAuth` meaning that requests here should *not* be limited to authenticated users. However, the critical `createTestimonail` and `deleteTestimonial` mutations are marked `@requireAuth`, and so can only be called by a logged in user.
+The `testimonials` query is marked with the [GraphQL directive](../../directives.md) `@skipAuth` meaning that requests here should *not* be limited to authenticated users. However, the critical `createTestimonial` and `deleteTestimonial` mutations are marked `@requireAuth`, and so can only be called by a logged in user.
Redwood's backend GraphQL server is powered by [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) and so you have access to everything that makes Yoga secure and performant: rate and depth limiting, logging, directives, and a ton more.
@@ -275,7 +275,7 @@ There's even an interactive console that lets you, for example, execute Prisma q
## Jest
-Being able to develop a full-stack application this easily is great, but how do you verify that it's working as intended? That's where a great test suite comes in. [Jest](https://jestjs.io/) is a test framework that, as they say, focuses on simplicty. We felt that it was a natural fit with Redwood, and so most files you can generate will include the related test file automatically (pre-filled with some tests, even!).
+Being able to develop a full-stack application this easily is great, but how do you verify that it's working as intended? That's where a great test suite comes in. [Jest](https://jestjs.io/) is a test framework that, as they say, focuses on simplicity. We felt that it was a natural fit with Redwood, and so most files you can generate will include the related test file automatically (pre-filled with some tests, even!).
Redwood includes several Jest helpers and matchers, allowing you to mock out GraphQL requests, database data, logged in users, and more.
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/file-structure.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/file-structure.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/file-structure.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/file-structure.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/first-page.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/first-page.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/first-page.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/first-page.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/installation.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/installation.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/installation.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/installation.md
index 26df59ca3c32..266664f74687 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter1/installation.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter1/installation.md
@@ -24,6 +24,7 @@ You'll have a new directory `redwoodblog` containing several directories and fil
```bash
cd redwoodblog
+yarn install
yarn redwood dev
```
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/layouts.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/layouts.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/layouts.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/layouts.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/prerequisites.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/prerequisites.md
similarity index 89%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/prerequisites.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/prerequisites.md
index 4013773b7d2c..094a84230861 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter1/prerequisites.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter1/prerequisites.md
@@ -31,7 +31,7 @@ If you have an existing site created with a prior version, you'll need to upgrad
During installation, RedwoodJS checks if your system meets version requirements for Node and Yarn:
- node: "=18.x"
-- yarn: ">=1.15"
+- yarn: ">=1.22.21"
If you're using a version of Node or Yarn that's **less** than what's required, _the installation bootstrap will result in an ERROR_. To check, please run the following from your terminal command line:
@@ -46,14 +46,21 @@ Please do upgrade accordingly. Then proceed to the Redwood installation when you
There are many ways to install and manage both Node.js and Yarn. If you're installing for the first time, we recommend the following:
-**1. Yarn**
-We recommend following the [instructions via Yarnpkg.com](https://yarnpkg.com/getting-started/install).
-
-**2. Node.js**
+**1. Node.js**
Using the recommended [LTS version from Nodejs.org](https://nodejs.org/en/) is preferred.
- `nvm` is a great tool for managing multiple versions of Node on one system. It takes a bit more effort to set up and learn, however. Follow the [nvm installation instructions](https://github.com/nvm-sh/nvm#installing-and-updating). (Windows users should go to [nvm-windows](https://github.com/coreybutler/nvm-windows/releases)). For **Mac** users with Homebrew installed, you can alternatively use it to [install `nvm`](https://formulae.brew.sh/formula/nvm). Or, refer to our how to guide [using nvm](../../how-to/using-nvm.md).
+**2. Yarn**
+As of Node.js v18+, Node.js ships with a CLI tool called [Corepack](https://nodejs.org/docs/latest-v18.x/api/corepack.html) to manage package managers. All you have to do is enable it, then you'll have Yarn:
+
+```
+corepack enable
+yarn -v
+```
+
+The version of Yarn will probably be `1.22.21`, but don't worry—in your Redwood project, Corepack will know to use a modern version of Yarn because of the `packageManager` field in the root `package.json`.
+
**Windows:** Recommended Development Setup
- JavaScript development on Windows has specific requirements in addition to Yarn and npm. Follow our simple setup guide:
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter1/second-page.md b/docs/versioned_docs/version-6.x/tutorial/chapter1/second-page.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter1/second-page.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter1/second-page.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter2/cells.md b/docs/versioned_docs/version-6.x/tutorial/chapter2/cells.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter2/cells.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter2/cells.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter2/getting-dynamic.md b/docs/versioned_docs/version-6.x/tutorial/chapter2/getting-dynamic.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter2/getting-dynamic.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter2/getting-dynamic.md
index 627de868701e..c67b0712b314 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter2/getting-dynamic.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter2/getting-dynamic.md
@@ -135,7 +135,7 @@ Okay but what if we click "Delete"?
So, Redwood just created all the pages, components and services necessary to perform all CRUD actions on our posts table. No need to even open Prisma Studio or login through a terminal window and write SQL from scratch. Redwood calls these _scaffolds_.
-:::caution
+:::warning
If you head back to VSCode at some point and get a notice in one of the generated Post cells about `Cannot query "posts" on type "Query"` don't worry: we've seen this from time to time on some systems. There are two easy fixes:
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter2/routing-params.md b/docs/versioned_docs/version-6.x/tutorial/chapter2/routing-params.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter2/routing-params.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter2/routing-params.md
index dbeb77632382..ff502bb040ac 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter2/routing-params.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter2/routing-params.md
@@ -349,7 +349,7 @@ export const Success = ({ article }) => {
```tsx title="web/src/components/ArticleCell/ArticleCell.tsx"
-import type { FindArticleQuery } from 'types/graphql'
+import type { FindArticleQuery, FindArticleQueryVariables } from 'types/graphql'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
export const QUERY = gql`
@@ -775,7 +775,7 @@ export const Success = ({ article }) => {
// highlight-next-line
import Article from 'src/components/Article'
-import type { FindArticleQuery } from 'types/graphql'
+import type { FindArticleQuery, FindArticleQueryVariables } from 'types/graphql'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
export const QUERY = gql`
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter2/side-quest.md b/docs/versioned_docs/version-6.x/tutorial/chapter2/side-quest.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter2/side-quest.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter2/side-quest.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter3/forms.md b/docs/versioned_docs/version-6.x/tutorial/chapter3/forms.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter3/forms.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter3/forms.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter3/saving-data.md b/docs/versioned_docs/version-6.x/tutorial/chapter3/saving-data.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter3/saving-data.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter3/saving-data.md
index 67b8c1f8de47..7b4732c5c14a 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter3/saving-data.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter3/saving-data.md
@@ -1795,7 +1795,7 @@ const [create, { loading, error }] = useMutation<
-:::caution
+:::warning
You can put the email validation back into the `` now, but you should leave the server validation in place, just in case.
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter4/authentication.md b/docs/versioned_docs/version-6.x/tutorial/chapter4/authentication.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter4/authentication.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter4/authentication.md
index 562d7b34b0a3..c42b3fb7d393 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter4/authentication.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter4/authentication.md
@@ -95,7 +95,7 @@ Redwood includes [integrations](../../authentication.md) for several of the most
- [Auth0](https://auth0.com/)
- [Clerk](https://clerk.dev/)
- [Netlify Identity](https://docs.netlify.com/visitor-access/identity/)
-- [Firebase's GoogleAuthProvider](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider)
+- [Firebase's GoogleAuthProvider](https://firebase.google.com/docs/reference/js/v8/firebase.auth.GoogleAuthProvider)
- [Supabase](https://supabase.io/docs/guides/auth)
- [SuperTokens](https://supertokens.com)
@@ -202,7 +202,7 @@ Going to the admin section now prevents a non-logged in user from seeing posts,
```jsx title="web/src/Routes.jsx"
// highlight-next-line
-import { Private, Router, Route, Set } from '@redwoodjs/router'
+import { PrivateSet, Router, Route, Set } from '@redwoodjs/router'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
import BlogLayout from 'src/layouts/BlogLayout'
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter4/deployment.md b/docs/versioned_docs/version-6.x/tutorial/chapter4/deployment.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter4/deployment.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter4/deployment.md
index fd273091f468..bf7376195eb3 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter4/deployment.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter4/deployment.md
@@ -87,7 +87,7 @@ This adds a `netlify.toml` config file in the root of the project that is good t
And with that, we're ready to setup Netlify itself.
-:::caution
+:::warning
While you may be tempted to use the [Netlify CLI](https://cli.netlify.com) commands to [build](https://cli.netlify.com/commands/build) and [deploy](https://cli.netlify.com/commands/deploy) your project directly from you local project directory, doing so **will lead to errors when deploying and/or when running functions**. I.e. errors in the function needed for the GraphQL server, but also other serverless functions.
The main reason for this is that these Netlify CLI commands simply build and deploy -- they build your project locally and then push the dist folder. That means that when building a RedwoodJS project, the [Prisma client is generated with binaries matching the operating system at build time](https://cli.netlify.com/commands/link) -- and not the [OS compatible](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options) with running functions on Netlify. Your Prisma client engine may be `darwin` for OSX or `windows` for Windows, but it needs to be `debian-openssl-1.1.x` or `rhel-openssl-1.1.x`. If the client is incompatible, your functions will fail.
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter5/first-story.md b/docs/versioned_docs/version-6.x/tutorial/chapter5/first-story.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter5/first-story.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter5/first-story.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter5/first-test.md b/docs/versioned_docs/version-6.x/tutorial/chapter5/first-test.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter5/first-test.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter5/first-test.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter5/storybook.md b/docs/versioned_docs/version-6.x/tutorial/chapter5/storybook.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter5/storybook.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter5/storybook.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter5/testing.md b/docs/versioned_docs/version-6.x/tutorial/chapter5/testing.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter5/testing.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter5/testing.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter6/comment-form.md b/docs/versioned_docs/version-6.x/tutorial/chapter6/comment-form.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter6/comment-form.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter6/comment-form.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter6/comments-schema.md b/docs/versioned_docs/version-6.x/tutorial/chapter6/comments-schema.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter6/comments-schema.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter6/comments-schema.md
index 421bfb51b2e5..243488167736 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter6/comments-schema.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter6/comments-schema.md
@@ -685,7 +685,7 @@ export const standard = defineScenario({
-```javascript title="api/src/services/comments/comments.scenarios.ts"
+```ts title="api/src/services/comments/comments.scenarios.ts"
import type { Prisma } from '@prisma/client'
export const standard = defineScenario({
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter6/multiple-comments.md b/docs/versioned_docs/version-6.x/tutorial/chapter6/multiple-comments.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter6/multiple-comments.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter6/multiple-comments.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter6/the-redwood-way.md b/docs/versioned_docs/version-6.x/tutorial/chapter6/the-redwood-way.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/tutorial/chapter6/the-redwood-way.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter6/the-redwood-way.md
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter7/api-side-currentuser.md b/docs/versioned_docs/version-6.x/tutorial/chapter7/api-side-currentuser.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter7/api-side-currentuser.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter7/api-side-currentuser.md
index 105cc05349ce..bce5ffaa2efd 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter7/api-side-currentuser.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter7/api-side-currentuser.md
@@ -112,7 +112,7 @@ Whoops!
Similar to what happened when we added `roles` to `User`, We made `userId` a required field, but we already have several posts in our development database. Since we don't have a default value for `userId` defined, it's impossible to add this column to the database.
-:::caution Why don't we just set `@default(1)` in the schema?
+:::warning Why don't we just set `@default(1)` in the schema?
This would get us past this problem, but could cause hard-to-track-down bugs in the future: if you ever forget to assign a `post` to a `user`, rather than fail it'll happily just set `userId` to `1`, which may or may not even exist some day! It's best to take the extra time to do things The Right Way and avoid the quick hacks to get past an annoyance like this. Your future self will thank you!
@@ -605,7 +605,7 @@ Finally, we'll need to update several of the scaffold components to use the new
```javascript title="web/src/components/Post/EditPostCell/EditPostCell.js"
export const QUERY = gql`
- query FindPostById($id: Int!) {
+ query EditPostById($id: Int!) {
// highlight-next-line
post: adminPost(id: $id) {
id
diff --git a/docs/versioned_docs/version-6.0/tutorial/chapter7/rbac.md b/docs/versioned_docs/version-6.x/tutorial/chapter7/rbac.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/chapter7/rbac.md
rename to docs/versioned_docs/version-6.x/tutorial/chapter7/rbac.md
index bac9123c38c4..490b98a4d5ac 100644
--- a/docs/versioned_docs/version-6.0/tutorial/chapter7/rbac.md
+++ b/docs/versioned_docs/version-6.x/tutorial/chapter7/rbac.md
@@ -178,14 +178,14 @@ The easiest way to prevent access to an entire URL is via the Router. The `
+
-
+
```
@@ -228,7 +228,7 @@ Which should return the new content of the user:
}
```
-:::caution
+:::warning
If you re-used the same console session from the previous section, you'll need to quit it and start it again for it to know about the new Prisma data structure. If you still can't get the update to work, maybe your user doesn't have an `id` of `1`! Run `db.user.findMany()` first and then get the `id` of the user you want to update.
@@ -895,7 +895,7 @@ describe('Comment', () => {
We moved the default `comment` object to a constant `COMMENT` and then used that in all tests. We also needed to add `waitFor()` since the `hasRole()` check in the Comment itself actually executes some GraphQL calls behind the scenes to figure out who the user is. The test suite makes mocked GraphQL calls, but they're still asynchronous and need to be waited for. If you don't wait, then `currentUser` will be `null` when the test starts, and Jest will be happy with that result. But we won't—we need to wait for the actual value from the GraphQL call.
-:::caution Seeing errors in your test suite?
+:::warning Seeing errors in your test suite?
We added fields to the database and sometimes the test runner doesn't realize this. You may need to restart it to get the test database migrated to match what's in `schema.prisma`. Press `q` or `Ctrl-C` in your test runner if it's still running, then:
diff --git a/docs/versioned_docs/version-6.0/tutorial/foreword.md b/docs/versioned_docs/version-6.x/tutorial/foreword.md
similarity index 99%
rename from docs/versioned_docs/version-6.0/tutorial/foreword.md
rename to docs/versioned_docs/version-6.x/tutorial/foreword.md
index f6b44e65c5de..ec1f1810ed40 100644
--- a/docs/versioned_docs/version-6.0/tutorial/foreword.md
+++ b/docs/versioned_docs/version-6.x/tutorial/foreword.md
@@ -24,7 +24,7 @@ They might look like this...
:::
-:::caution
+:::warning
or sometimes like this...
diff --git a/docs/versioned_docs/version-6.0/tutorial/intermission.md b/docs/versioned_docs/version-6.x/tutorial/intermission.md
similarity index 97%
rename from docs/versioned_docs/version-6.0/tutorial/intermission.md
rename to docs/versioned_docs/version-6.x/tutorial/intermission.md
index 0416b563de5b..793730157af2 100644
--- a/docs/versioned_docs/version-6.0/tutorial/intermission.md
+++ b/docs/versioned_docs/version-6.x/tutorial/intermission.md
@@ -34,9 +34,9 @@ yarn rw dev
If you haven't been through the first tutorial, or maybe you went through it on an older version of Redwood (anything pre-0.41) you can clone [this repo](https://github.com/redwoodjs/redwood-tutorial) which contains everything built so far and also adds a little styling so it isn't quite so...tough to look at. The example repo includes [TailwindCSS](https://tailwindcss.com) to style things up and adds a `
` or two to give us some additional hooks to hang styling on.
-:::caution The TypeScript version of the Example Repo is currently in progress
+:::warning The TypeScript version of the Example Repo is currently in progress
-If you want to complete the tutorial in TypeScript, continue with your own repo, making any necessary edits. Don't worry, the remainder of the tutorial continues to offer both TypeScript and JavaScript example code changes.
+If you want to complete the tutorial in TypeScript, continue with your own repo, making any necessary edits. Don't worry, the remainder of the tutorial continues to offer both TypeScript and JavaScript example code changes.
:::
diff --git a/docs/versioned_docs/version-6.0/typescript/generated-types.md b/docs/versioned_docs/version-6.x/typescript/generated-types.md
similarity index 84%
rename from docs/versioned_docs/version-6.0/typescript/generated-types.md
rename to docs/versioned_docs/version-6.x/typescript/generated-types.md
index 76d33cb1b451..644cc7b39bd4 100644
--- a/docs/versioned_docs/version-6.0/typescript/generated-types.md
+++ b/docs/versioned_docs/version-6.x/typescript/generated-types.md
@@ -164,9 +164,40 @@ You can configure graphql-codegen in a number of different ways: `codegen.yml`,
For completeness, [here's the docs](https://www.graphql-code-generator.com/docs/config-reference/config-field) on configuring GraphQL Code Generator. Currently, Redwood only supports the root level `config` option.
+## Experimental SDL Code Generation
+
+There is also an experimental code generator based on [sdl-codegen](https://github.com/sdl-codegen/sdl-codegen) available. sdl-codegen is a fresh implementation of code generation for service files, built with Redwood in mind. It is currently in opt-in and can be enabled by setting the `experimentalSdlCodeGen` flag to `true` in your `redwood.toml` file:
+
+```toml title="redwood.toml"
+[experimental]
+ useSDLCodeGenForGraphQLTypes = true
+```
+
+Running `yarn rw g types` will generate types for your resolvers on a per-file basis, this feature can be paired with the optional eslint auto-fix rule to have types automatically applied to your resolvers in TypeScript service files by editing your root `package.json` with:
+
+```diff title="package.json"
+ "eslintConfig": {
+ "extends": "@redwoodjs/eslint-config",
+ "root": true,
+ "parserOptions": {
+ "warnOnUnsupportedTypeScriptVersion": false
+ },
++ "overrides": [
++ {
++ "files": [
++ "api/src/services/**/*.ts"
++ ],
++ "rules": {
++ "@redwoodjs/service-type-annotations": "error"
++ }
++ }
+ ]
+ },
+```
+
:::tip Using VSCode?
-As a part of type generation, the [VSCode GraphQL extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) configures itself based on the merged schema Redwood generates in `.redwood/schema.graphql`.
+As a part of type generation, the extension [GraphQL: Language Feature Support](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) configures itself based on the merged schema Redwood generates in `.redwood/schema.graphql`.
You can configure it further in `graphql.config.js` at the root of your project.
:::
diff --git a/docs/versioned_docs/version-6.0/typescript/introduction.md b/docs/versioned_docs/version-6.x/typescript/introduction.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/typescript/introduction.md
rename to docs/versioned_docs/version-6.x/typescript/introduction.md
diff --git a/docs/versioned_docs/version-6.0/typescript/strict-mode.md b/docs/versioned_docs/version-6.x/typescript/strict-mode.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/typescript/strict-mode.md
rename to docs/versioned_docs/version-6.x/typescript/strict-mode.md
diff --git a/docs/versioned_docs/version-6.0/typescript/utility-types.md b/docs/versioned_docs/version-6.x/typescript/utility-types.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/typescript/utility-types.md
rename to docs/versioned_docs/version-6.x/typescript/utility-types.md
diff --git a/docs/versioned_docs/version-6.0/vite-configuration.md b/docs/versioned_docs/version-6.x/vite-configuration.md
similarity index 100%
rename from docs/versioned_docs/version-6.0/vite-configuration.md
rename to docs/versioned_docs/version-6.x/vite-configuration.md
diff --git a/docs/versioned_docs/version-6.0/webhooks.md b/docs/versioned_docs/version-6.x/webhooks.md
similarity index 98%
rename from docs/versioned_docs/version-6.0/webhooks.md
rename to docs/versioned_docs/version-6.x/webhooks.md
index 706c37abb8f6..a185c7ebc4fa 100644
--- a/docs/versioned_docs/version-6.0/webhooks.md
+++ b/docs/versioned_docs/version-6.x/webhooks.md
@@ -214,6 +214,14 @@ This is a variation on the SHA256 HMAC verification that works with binary buffe
Svix (and by extension, Clerk) gives you a secret token that it uses to create a hash signature with each payload. This hash signature is included with the headers of each request as `svix-signature`.
+> Some production environments, like Vercel, might base64 encode the request body string. In that case, the body must be conditionally parsed.
+> ```js
+> export const handler = async (event: APIGatewayEvent) => {
+> const body = event.isBase64Encoded
+> ? Buffer.from(event.body, 'base64').toString('utf-8')
+> : event.body
+> ```
+
```tsx
import type { APIGatewayEvent } from 'aws-lambda'
import {
diff --git a/docs/versioned_docs/version-7.0/a11y.md b/docs/versioned_docs/version-7.0/a11y.md
new file mode 100644
index 000000000000..7cc09b9b0df3
--- /dev/null
+++ b/docs/versioned_docs/version-7.0/a11y.md
@@ -0,0 +1,170 @@
+---
+slug: accessibility
+description: Accessibility is a core feature that's built-in
+---
+
+# Accessibility (aka a11y)
+
+We built Redwood to make building websites more accessible (we write all the config so you don't have to), but Redwood's also built to help you make more accessible websites.
+Accessibility shouldn't be a nice-to-have.
+It should be a given from the start.
+A core feature that's built-in and well-supported.
+
+There's a lot of great tooling out there that'll not only help you build accessible websites, but also help you learn exactly what that means.
+
+> **Does tooling obviate the need for manual testing?**
+>
+> No—even with all the tooling in the world, manual testing is still important, especially for accessibility.
+> But just because tooling doesn't catch everything doesn't mean it's not valuable.
+> It'd be much harder to learn what to look for without it.
+
+## Accessible Routing
+
+For single-page applications (SPAs), accessibility starts with the router.
+Without a full-page refresh, you just can't be sure that things like announcements and focus are being taken care of the way they're supposed to be.
+Here's a great example of [how disorienting SPAs can be to screen-reader users](https://www.youtube.com/watch?v=NKTdNv8JpuM).
+On navigation, nothing's announced.
+The lack of an announcement isn't just buggy behavior—it's broken.
+
+Normally, the onus would be on you as a developer to announce to screen-reader users that they've navigated somewhere new.
+That's a lot to ask—and hard to get right—especially when you're just trying to build your app.
+
+Luckily, if you're writing thoughtful content and marking it up semantically, there's nothing you have to do!
+The router automatically announces pages on navigation, and looks for announcements in this order:
+
+1. The `RouteAnnouncement` component
+2. The page's `
`
+3. `document.title`
+4. `location.pathname`
+
+The reason for this order is that announcements should be as specific as possible.
+more specific usually means more descriptive, and more descriptive usually means that users can not only orient themselves and navigate through the content, but also find it again.
+
+> If you're not sure if your content is descriptive enough, see the [W3 guidelines](https://www.w3.org/WAI/WCAG21/Techniques/general/G88.html).
+
+Even though Redwood looks for a `RouteAnnouncement` component first, you don't have to have one on every page—it's more than ok for the `
` to be what's announced.
+`RouteAnnouncement` is there for when the situation calls for a custom announcement.
+
+### `RouteAnnouncement`
+
+The way `RouteAnnouncement` works is simple: its children will be announced.
+Note that this can be something on the page or can be something that's visually hidden using the `visuallyHidden` prop:
+
+```jsx title="web/src/pages/HomePage/HomePage.js"
+import { RouteAnnouncement } from '@redwoodjs/router'
+
+const HomePage = () => {
+ return (
+ // This will still be visible
+
+
+ // This won't be visible
+ // highlight-start
+
+ All about me
+
+ // highlight-end
+ )
+}
+
+export default AboutPage
+```
+
+`visuallyHidden` shouldn't be the first thing you reach for—it's good to maintain parity between your site's visual and audible experiences.
+But it's there if you need it.
+
+## Focus
+
+On page change, Redwood Router resets focus to the top of the DOM so that users can navigate through the new page.
+While this is the expected behavior (and the behavior you usually want), for some pages—especially those with a lot of navigation—it can be cumbersome for users to have tab through navigation before getting to the main point.
+(And that goes for every page change!)
+
+Right now, there's two ways to alleviate this: with skip links or the `RouteFocus` component.
+
+### Skip Links
+
+Since the main content isn't usually the first thing on the page, it's a best practice to provide a shortcut for keyboard and screen-reader users to skip to it.
+Skip links do just that, and if you generate a layout using the `--skipLink` option, you'll get one with a skip link:
+
+```
+yarn rw g layout main --skipLink
+```
+
+```jsx title="web/src/layouts/MainLayout/MainLayout.js"
+import { SkipNavLink, SkipNavContent } from '@redwoodjs/router'
+import '@redwoodjs/router/skip-nav.css'
+
+const MainLayout = ({ children }) => {
+ return (
+ <>
+
+
+
+ {children}
+ >
+ )
+}
+
+export default MainLayout
+```
+
+`SkipNavLink` renders a link that remains hidden till focused and `SkipNavContent` renders a div as the target for the link.
+The code for these components comes from Reach UI. For more details, see [Reach UI's docs](https://reach.tech/skip-nav/#reach-skip-nav).
+
+One thing you'll probably want to do is change the URL the skip link sends the user to when activated.
+You can do that by changing the `contentId` and `id` props of `SkipNavLink` and `SkipNavContent` respectively:
+
+```jsx
+
+{/* ... */}
+
+```
+
+If you'd prefer to implement your own skip link, [Ben Myers' blog](https://benmyers.dev/blog/skip-links/) is a great resource, and a great place to read about accessibility in general.
+
+### `RouteFocus`
+
+Sometimes you don't want to just skip the nav, but send a user somewhere.
+In this situation, you of course have the foresight that that place is where the user wants to be.
+So please use this at your discretion—sending a user to an unexpected location can be worse than sending them back the top.
+
+Having said that, if you know that on a particular page change a user's focus is better off being directed to a particular element, the `RouteFocus` component is what you want:
+
+```jsx title="web/src/pages/ContactPage/ContactPage.js"
+// highlight-next-line
+import { RouteFocus } from '@redwoodjs/router'
+
+const ContactPage = () => (
+
+
+ // The contact form the user actually wants to interact with
+ // highlight-start
+
+
+
+ // highlight-end
+)
+
+export default ContactPage
+```
+
+`RouteFocus` tells the router to send focus to it's child on page change. In the example above, when the user navigates to the contact page, the name text field on the form is focused—the first field of the form they're here to fill out.
+
+
+
+
diff --git a/docs/versioned_docs/version-7.0/app-configuration-redwood-toml.md b/docs/versioned_docs/version-7.0/app-configuration-redwood-toml.md
new file mode 100644
index 000000000000..4439e88ea3db
--- /dev/null
+++ b/docs/versioned_docs/version-7.0/app-configuration-redwood-toml.md
@@ -0,0 +1,194 @@
+---
+title: App Configuration
+description: Configure your app with redwood.toml
+---
+
+# App Configuration: redwood.toml
+
+One of the premier places you can configure your Redwood app is `redwood.toml`. By default, `redwood.toml` lists the following configuration options:
+
+```toml title="redwood.toml"
+[web]
+ title = "Redwood App"
+ port = 8910
+ apiUrl = "/.redwood/functions"
+ includeEnvironmentVariables = []
+[api]
+ port = 8911
+[browser]
+ open = true
+[notifications]
+ versionUpdates = ["latest"]
+```
+
+These are listed by default because they're the ones that you're most likely to configure, but there are plenty more available.
+
+You can think of `redwood.toml` as a frontend for configuring Redwood's build tools.
+For certain options, instead of having to configure build tools directly, there's quick access via `redwood.toml`.
+
+## [web]
+
+| Key | Description | Default |
+| :---------------------------- | :--------------------------------------------------------- | :---------------------- |
+| `title` | Title of your Redwood app | `'Redwood App'` |
+| `port` | Port for the web server to listen at | `8910` |
+| `apiUrl` | URL to your api server. This can be a relative URL in which case it acts like a proxy, or a fully-qualified URL | `'/.redwood/functions'` |
+| `includeEnvironmentVariables` | Environment variables made available to the web side during dev and build | `[]` |
+| `host` | Hostname for the web server to listen at | Defaults to `'0.0.0.0'` in production and `'::'` in development |
+| `apiGraphQLUrl` | URL to your GraphQL function | `'${apiUrl}/graphql'` |
+| `apiDbAuthUrl` | URL to your dbAuth function | `'${apiUrl}/auth'` |
+| `sourceMap` | Enable source maps for production builds | `false` |
+| `a11y` | Enable storybook `addon-a11y` and `eslint-plugin-jsx-a11y` | `true` |
+
+### Customizing the GraphQL Endpoint
+
+By default, Redwood derives the GraphQL endpoint from `apiUrl` such that it's `${apiUrl}/graphql`, (with the default `apiUrl`, `./redwood/functions/graphql`).
+But sometimes you want to host your api side somewhere else.
+There's two ways you can do this:
+
+1. Change `apiUrl`:
+
+```toml title="redwood.toml"
+[web]
+ apiUrl = "https://api.coolredwoodapp.com"
+```
+
+Now the GraphQL endpoint is at `https://api.coolredwoodapp.com/graphql`.
+
+2. Change `apiGraphQLUrl`:
+
+```diff title="redwood.toml"
+ [web]
+ apiUrl = "/.redwood/functions"
++ apiGraphQLUrl = "https://api.coolredwoodapp.com/graphql"
+```
+
+### Customizing the dbAuth Endpoint
+
+Similarly, if you're using dbAuth, you may decide to host it somewhere else.
+To do this without affecting your other endpoints, you can add `apiDbAuthUrl` to your `redwood.toml`:
+
+```diff title="redwood.toml"
+ [web]
+ apiUrl = "/.redwood/functions"
++ apiDbAuthUrl = "https://api.coolredwoodapp.com/auth"
+```
+
+:::tip
+
+If you host your web and api sides at different domains and don't use a proxy, make sure you have [CORS](./cors.md) configured.
+Otherwise browser security features may block client requests.
+
+:::
+
+### includeEnvironmentVariables
+
+`includeEnvironmentVariables` is the set of environment variables that should be available to your web side during dev and build.
+Use it to include env vars like public keys for third-party services you've defined in your `.env` file:
+
+```toml title="redwood.toml"
+[web]
+ includeEnvironmentVariables = ["PUBLIC_KEY"]
+```
+
+```text title=".env"
+PUBLIC_KEY=...
+```
+
+Instead of including them in `includeEnvironmentVariables`, you can also prefix them with `REDWOOD_ENV_` (see [Environment Variables](environment-variables.md#web)).
+
+:::caution `includeEnvironmentVariables` isn't for secrets
+
+Don't make secrets available to your web side. Everything in `includeEnvironmentVariables` is included in the bundle.
+
+:::
+
+## [api]
+
+| Key | Description | Default |
+| :------------- | :---------------------------------- | :------------------------- |
+| `port` | Port for the api server to listen at | `8911` |
+| `host` | Hostname for the api server to listen at | Defaults to `'0.0.0.0'` in production and `'::'` in development |
+| `debugPort` | Port for the debugger to listen at | `18911` |
+| `serverConfig` | [Deprecated; use the [server file](./docker.md#using-the-server-file) instead] Path to the `server.config.js` file | `'./api/server.config.js'` |
+
+## [browser]
+
+```toml title="redwood.toml"
+[browser]
+ open = true
+```
+
+Setting `open` to `true` opens your browser to `http://${web.host}:${web.port}` (by default, `http://localhost:8910`) after the dev server starts.
+If you want your browser to stop opening when you run `yarn rw dev`, set this to `false`.
+(Or just remove it entirely.)
+
+There's actually a lot more you can do here. For more, see Vite's docs on [`preview.open`](https://vitejs.dev/config/preview-options.html#preview-open).
+
+## [generate]
+
+```toml title="redwood.toml"
+[generate]
+ tests = true
+ stories = true
+```
+
+Many of Redwood's generators create Jest tests or Storybook stories.
+Understandably, this can be lot of files, and sometimes you don't want all of them, either because you don't plan on using Jest or Storybook, or are just getting started and don't want the overhead.
+These options allows you to disable the generation of test and story files.
+
+## [cli]
+
+```toml title="redwood.toml"
+[notifications]
+ versionUpdates = ["latest"]
+```
+
+There are new versions of the framework all the time—a major every couple months, a minor every week or two, and patches when appropriate.
+And if you're on an experimental release line, like canary, there's new versions every day, multiple times.
+
+If you'd like to get notified (at most, once a day) when there's a new version, set `versionUpdates` to include the version tags you're interested in.
+
+## Using Environment Variables in `redwood.toml`
+
+You may find yourself wanting to change keys in `redwood.toml` based on the environment you're deploying to.
+For example, you may want to point to a different `apiUrl` in your staging environment.
+
+You can do so with environment variables.
+Let's look at an example:
+
+```toml title="redwood.toml"
+[web]
+ // highlight-start
+ title = "App running on ${APP_TITLE}"
+ port = "${PORT:8910}"
+ apiUrl = "${API_URL:/.redwood/functions}"
+ // highlight-end
+ includeEnvironmentVariables = []
+```
+
+This `${:[fallback]}` syntax does the following:
+
+- sets `title` by interpolating the env var `APP_TITLE`
+- sets `port` to the env var `PORT`, falling back to `8910`
+- sets `apiUrl` to the env var `API_URL`, falling back to `/.redwood/functions` (the default)
+
+That's pretty much all there is to it.
+Just remember two things:
+
+1. fallback is always a string
+2. these values are interpolated at build time
+
+## Running in a Container or VM
+
+To run a Redwood app in a container or VM, you'll want to set both the web and api's `host` to `0.0.0.0` to allow network connections to and from the host:
+
+```toml title="redwood.toml"
+[web]
+ host = '0.0.0.0'
+[api]
+ host = '0.0.0.0'
+```
+
+You can also configure these values via `REDWOOD_WEB_HOST` and `REDWOOD_API_HOST`.
+And if you set `NODE_ENV` to production, these will be the defaults anyway.
diff --git a/docs/versioned_docs/version-7.0/assets-and-files.md b/docs/versioned_docs/version-7.0/assets-and-files.md
new file mode 100644
index 000000000000..785f92861ef5
--- /dev/null
+++ b/docs/versioned_docs/version-7.0/assets-and-files.md
@@ -0,0 +1,180 @@
+---
+description: How to include assets—like images—in your app
+---
+
+# Assets and Files
+
+There are two ways to add an asset to your Redwood app:
+
+1. co-locate it with the component using it and import it into the component as if it were code
+2. add it to the `web/public` directory and reference it relative to your site's root
+
+Where possible, prefer the first strategy.
+
+It lets Vite include the asset in the bundle when the file is small enough.
+
+### Co-locating and Importing Assets
+
+Let's say you want to show your app's logo in your `Header` component.
+First, add your logo to the `Header` component's directory:
+
+```text
+web/src/components/Header/
+// highlight-next-line
+├── logo.png
+├── Header.js
+├── Header.stories.js
+└── Header.test.js
+```
+
+Then, in the `Header` component, import your logo as if it were code:
+
+```jsx title="web/src/components/Header/Header.js"
+// highlight-next-line
+import logo from './logo.png'
+
+const Header = () => {
+ return (
+
+ {/* ... */}
+ // highlight-next-line
+
+
+ )
+}
+
+export default Header
+```
+
+If you're curious how this works, see the Vite docs on [static asset handling](https://vitejs.dev/guide/assets.html).
+
+## Adding to the `web/public` Directory
+
+You can also add assets to the `web/public` directory, effectively adding static files to your app.
+During dev and build, Redwood copies `web/public`'s contents into `web/dist`.
+
+> Changes to `web/public` don't hot-reload.
+
+Again, because assets in this directory don't go through Vite, **use this strategy sparingly**, and mainly for assets like favicons, manifests, `robots.txt`, libraries incompatible with Vite, etc.
+
+### Example: Adding Your Logo and Favicon to `web/public`
+
+Let's say that you've added your logo and favicon to `web/public`:
+
+```
+web/public/
+├── img/
+│ └── logo.png
+└── favicon.png
+```
+
+When you run `yarn rw dev` and `yarn rw build`, Redwood copies
+`web/public/img/logo.png` to `web/dist/img/logo.png` and `web/public/favicon.png` to `web/dist/favicon.png`:
+
+```text
+web/dist/
+├── static/
+│ ├── js/
+│ └── css/
+// highlight-start
+├── img/
+│ └── logo.png
+└── favicon.png
+// highlight-end
+```
+
+You can reference these files in your code without any special handling:
+
+```jsx title="web/src/components/Header/Header.js"
+import { Head } from '@redwoodjs/web'
+
+const Header = () => {
+ return (
+ <>
+
+ // highlight-next-line
+
+
+ // highlight-next-line
+
+ >
+ )
+}
+
+export default Header
+```
+
+## Styling SVGs: The special type of image
+
+By default you can import and use SVG images like any other image asset.
+
+```jsx title="web/src/components/Example.jsx"
+// highlight-next-line
+import svgIconSrc from '../mySvg.svg'
+
+const Example = () => {
+ return (
+ <>
+ // highlight-next-line
+
+ >
+ )
+}
+
+export default Example
+```
+
+Sometimes however, you might want more control over styling your SVGs - maybe you want to modify the `stroke-width` or `fill` color.
+
+The easiest way to achieve this, is to make your SVGs a React component. Open up your SVG file, and drop in its contents into a component – for example:
+
+```tsx title="web/src/components/icons/CarIcon.tsx"
+import type { SVGProps } from "react"
+
+export const CarIcon = (props: SVGProps) => {
+ return (
+ // 👇 content of your SVG file
+