diff --git a/__fixtures__/rsc-caching/.editorconfig b/__fixtures__/rsc-caching/.editorconfig new file mode 100644 index 000000000000..ae10a5cce3b2 --- /dev/null +++ b/__fixtures__/rsc-caching/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/__fixtures__/rsc-caching/.env.defaults b/__fixtures__/rsc-caching/.env.defaults new file mode 100644 index 000000000000..fb88fb33b334 --- /dev/null +++ b/__fixtures__/rsc-caching/.env.defaults @@ -0,0 +1,19 @@ +# These environment variables will be used by default if you do not create any +# yourself in .env. This file should be safe to check into your version control +# system. Any custom values should go in .env and .env should *not* be checked +# into version control. + +# schema.prisma defaults +DATABASE_URL=file:./dev.db + +# location of the test database for api service scenarios (defaults to ./.redwood/test.db if not set) +# TEST_DATABASE_URL=file:./.redwood/test.db + +# disables Prisma CLI update notifier +PRISMA_HIDE_UPDATE_MESSAGE=true + +# Option to override the current environment's default api-side log level +# See: https://redwoodjs.com/docs/logger for level options, defaults to "trace" otherwise. +# Most applications want "debug" or "info" during dev, "trace" when you have issues and "warn" in production. +# Ordered by how verbose they are: trace | debug | info | warn | error | silent +# LOG_LEVEL=debug diff --git a/__fixtures__/rsc-caching/.env.example b/__fixtures__/rsc-caching/.env.example new file mode 100644 index 000000000000..2a2de6c026ca --- /dev/null +++ b/__fixtures__/rsc-caching/.env.example @@ -0,0 +1,4 @@ +# DATABASE_URL=file:./dev.db +# TEST_DATABASE_URL=file:./.redwood/test.db +# PRISMA_HIDE_UPDATE_MESSAGE=true +# LOG_LEVEL=trace diff --git a/__fixtures__/rsc-caching/.gitignore b/__fixtures__/rsc-caching/.gitignore new file mode 100644 index 000000000000..a7adbb3c2499 --- /dev/null +++ b/__fixtures__/rsc-caching/.gitignore @@ -0,0 +1,23 @@ +.idea +.DS_Store +.env* +!.env.example +!.env.defaults +.netlify +.redwood/* +!.redwood/README.md +dist +dist-babel +node_modules +yarn-error.log +web/public/mockServiceWorker.js +web/types/graphql.d.ts +api/types/graphql.d.ts +api/src/lib/generateGraphiQLHeader.* +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/__fixtures__/rsc-caching/.gitpod.yml b/__fixtures__/rsc-caching/.gitpod.yml new file mode 100644 index 000000000000..a357a6e8c9a9 --- /dev/null +++ b/__fixtures__/rsc-caching/.gitpod.yml @@ -0,0 +1,25 @@ +# To learn about this file, please see https://www.gitpod.io/docs/references/gitpod-yml + +image: gitpod/workspace-node-lts + +tasks: + - init: | + # Cleanup terminal + printf "\033[3J\033c\033[3J" + + yarn install + + command: yarn rw build -v && yarn rw serve +ports: + - port: 8910 + name: RedwoodJS web application + onOpen: notify # because we already have [browser].open = true in redwood.toml + - port: 8911 + name: Serverless functions + onOpen: ignore + +vscode: + extensions: + - "dbaeumer.vscode-eslint" + - "mgmcdermott.vscode-language-babel" + - "editorconfig.editorconfig" diff --git a/__fixtures__/rsc-caching/.redwood/README.md b/__fixtures__/rsc-caching/.redwood/README.md new file mode 100644 index 000000000000..8829edb84776 --- /dev/null +++ b/__fixtures__/rsc-caching/.redwood/README.md @@ -0,0 +1,44 @@ +# .redwood + +## What is this directory? + +Redwood uses this `.redwood` directory to store transitory data that aids in the smooth and convenient operation of your Redwood project. + +## Do I need to do anything with this directory? + +No. You shouldn't have to create, edit or delete anything in this directory in your day-to-day work with Redwood. + +You don't need to commit any other contents of this directory to your version control system. It's ignored by default. + +## What's in this directory? + +### Files + +| Name | Description | +| :---------------- | :------- | +| commandCache.json | This file contains mappings to assist the Redwood CLI in efficiently executing commands. | +| schema.graphql | This is the GraphQL schema which has been automatically generated from your Redwood project. | +| telemetry.txt | Contains a unique ID used for telemetry. This value is rotated every 24 hours to protect your project's anonymity. | +| test.db | The sqlite database used when running tests. | + +### Directories + +| Name | Description | +| :---------- | :------- | +| locks | Stores temporary files that Redwood uses to keep track of the execution of async/background tasks between processes. | +| logs | Stores log files for background tasks such as update checking. | +| prebuild | Stores transpiled JavaScript that is generated as part of Redwood's build process. | +| telemetry | Stores the recent telemetry that the Redwood CLI has generated. You may inspect these files to see everything Redwood is anonymously collecting. | +| types | Stores the results of type generation. | +| updateCheck | Stores a file which contains the results of checking for Redwood updates. | +| studio | Used to store data for `rw studio` | + +We try to keep this README up to date but you may, from time to time, find other files or directories in this `.redwood` directory that have not yet been documented here. This is likely nothing to worry about but feel free to let us know and we'll update this list. + +### Telemetry + +RedwoodJS collects completely anonymous telemetry data about general usage. For transparency, that data is viewable in the respective directories and files. To learn more and manage your project's settings, visit [telemetry.redwoodjs.com](https://telemetry.redwoodjs.com). + +### Have any questions? + +Feel free to reach out to us in the [RedwoodJS Community](https://community.redwoodjs.com/) forum if you have any questions. diff --git a/__fixtures__/rsc-caching/.vscode/extensions.json b/__fixtures__/rsc-caching/.vscode/extensions.json new file mode 100644 index 000000000000..7fc50a119fc6 --- /dev/null +++ b/__fixtures__/rsc-caching/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "ofhumanbondage.react-proptypes-intellisense", + "mgmcdermott.vscode-language-babel", + "wix.vscode-import-cost", + "pflannery.vscode-versionlens", + "editorconfig.editorconfig", + "prisma.prisma", + "graphql.vscode-graphql" + ], + "unwantedRecommendations": [] +} diff --git a/__fixtures__/rsc-caching/.vscode/launch.json b/__fixtures__/rsc-caching/.vscode/launch.json new file mode 100644 index 000000000000..340be43c34da --- /dev/null +++ b/__fixtures__/rsc-caching/.vscode/launch.json @@ -0,0 +1,56 @@ +{ + "version": "0.3.0", + "configurations": [ + { + "command": "yarn redwood dev --apiDebugPort 18911", // you can add --fwd='--open=false' to prevent the browser from opening + "name": "Run Dev Server", + "request": "launch", + "type": "node-terminal" + }, + { + "name": "Attach API debugger", + "port": 18911, // you can change this port, see https://redwoodjs.com/docs/project-configuration-dev-test-build#debugger-configuration + "request": "attach", + "skipFiles": [ + "/**" + ], + "type": "node", + "localRoot": "${workspaceFolder}/node_modules/@redwoodjs/api-server/dist", + "remoteRoot": "${workspaceFolder}/node_modules/@redwoodjs/api-server/dist", + "sourceMaps": true, + "restart": true, + "preLaunchTask": "WaitForDevServer", + }, + { + "name": "Launch Web debugger", + "type": "chrome", + "request": "launch", + "url": "http://localhost:8910", + "webRoot": "${workspaceRoot}/web/src", + "preLaunchTask": "WaitForDevServer", + }, + { + "command": "yarn redwood test api", + "name": "Test api", + "request": "launch", + "type": "node-terminal" + }, + { + "command": "yarn redwood test web", + "name": "Test web", + "request": "launch", + "type": "node-terminal" + }, + ], + "compounds": [ + { + "name": "Start Debug", + "configurations": [ + "Run Dev Server", + "Attach API debugger", + "Launch Web debugger" + ], + "stopAll": true + } + ] +} diff --git a/__fixtures__/rsc-caching/.vscode/settings.json b/__fixtures__/rsc-caching/.vscode/settings.json new file mode 100644 index 000000000000..6887d360eb96 --- /dev/null +++ b/__fixtures__/rsc-caching/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "editor.tabSize": 2, + "files.trimTrailingWhitespace": true, + "editor.formatOnSave": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "[prisma]": { + "editor.formatOnSave": true + } +} diff --git a/__fixtures__/rsc-caching/.vscode/tasks.json b/__fixtures__/rsc-caching/.vscode/tasks.json new file mode 100644 index 000000000000..549249ec6324 --- /dev/null +++ b/__fixtures__/rsc-caching/.vscode/tasks.json @@ -0,0 +1,29 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "WaitForDevServer", + "group": "none", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "while ! echo -n > /dev/tcp/localhost/18911; do sleep 1; done;" + ], + "windows": { + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", "Bypass", + "while (-not (Test-NetConnection -ComputerName localhost -Port 18911)) { Start-Sleep -Seconds 1 };" + ] + }, + "presentation": { + "reveal": "silent", + "revealProblems": "onProblem", + "panel": "shared", + "close": true + } + }, + ] +} diff --git a/__fixtures__/rsc-caching/.yarnrc.yml b/__fixtures__/rsc-caching/.yarnrc.yml new file mode 100644 index 000000000000..e8c5d50aa786 --- /dev/null +++ b/__fixtures__/rsc-caching/.yarnrc.yml @@ -0,0 +1,15 @@ +# Yarn's manifest file. You can configure yarn here. +# See https://yarnpkg.com/configuration/yarnrc. + +# For `node_modules` (see `nodeLinker` below), this is almost always the preferred option. +compressionLevel: 0 + +enableGlobalCache: true + +# Lets yarn use hardlinks inside `node_modules` to dedupe packages. +# For a more pnpm-like experience, consider `hardlinks-global` where hardlinks point to a global store. +nmMode: hardlinks-local + +# How to install Node packages. +# Heads up: right now, Redwood expects this to be `node-modules`. +nodeLinker: node-modules diff --git a/__fixtures__/rsc-caching/README.md b/__fixtures__/rsc-caching/README.md new file mode 100644 index 000000000000..8047b281d03c --- /dev/null +++ b/__fixtures__/rsc-caching/README.md @@ -0,0 +1 @@ +Project used for testing and visualizing caching in an RWSC app diff --git a/__fixtures__/rsc-caching/api/db/dev.db b/__fixtures__/rsc-caching/api/db/dev.db new file mode 100644 index 000000000000..f4f348d46816 Binary files /dev/null and b/__fixtures__/rsc-caching/api/db/dev.db differ diff --git a/__fixtures__/rsc-caching/api/db/schema.prisma b/__fixtures__/rsc-caching/api/db/schema.prisma new file mode 100644 index 000000000000..8c8666606f09 --- /dev/null +++ b/__fixtures__/rsc-caching/api/db/schema.prisma @@ -0,0 +1,24 @@ +// Don't forget to tell Prisma about your edits to this file using +// `yarn rw prisma migrate dev` or `yarn rw prisma db push`. +// `migrate` is like committing while `push` is for prototyping. +// Read more about both here: +// https://www.prisma.io/docs/orm/prisma-migrate + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +// Define your own datamodels here and run `yarn redwood prisma migrate dev` +// to create migrations for them and apply to your dev DB. +// TODO: Please remove the following example: +model UserExample { + id Int @id @default(autoincrement()) + email String @unique + name String? +} diff --git a/__fixtures__/rsc-caching/api/jest.config.js b/__fixtures__/rsc-caching/api/jest.config.js new file mode 100644 index 000000000000..932fc82dce93 --- /dev/null +++ b/__fixtures__/rsc-caching/api/jest.config.js @@ -0,0 +1,8 @@ +// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build + +const config = { + rootDir: '../', + preset: '@redwoodjs/testing/config/jest/api', +} + +module.exports = config diff --git a/__fixtures__/rsc-caching/api/package.json b/__fixtures__/rsc-caching/api/package.json new file mode 100644 index 000000000000..1cb928e646d9 --- /dev/null +++ b/__fixtures__/rsc-caching/api/package.json @@ -0,0 +1,9 @@ +{ + "name": "api", + "version": "0.0.0", + "private": true, + "dependencies": { + "@redwoodjs/api": "8.0.0-canary.1102", + "@redwoodjs/graphql-server": "8.0.0-canary.1102" + } +} diff --git a/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.test.ts b/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.test.ts new file mode 100644 index 000000000000..0f01aa367a85 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.test.ts @@ -0,0 +1,18 @@ +import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api' + +import requireAuth from './requireAuth' + +describe('requireAuth directive', () => { + it('declares the directive sdl as schema, with the correct name', () => { + expect(requireAuth.schema).toBeTruthy() + expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth') + }) + + it('requireAuth has stub implementation. Should not throw when current user', () => { + // If you want to set values in context, pass it through e.g. + // mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }}) + const mockExecution = mockRedwoodDirective(requireAuth, { context: {} }) + + expect(mockExecution).not.toThrowError() + }) +}) diff --git a/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.ts b/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.ts new file mode 100644 index 000000000000..77b31a70ae07 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/directives/requireAuth/requireAuth.ts @@ -0,0 +1,25 @@ +import gql from 'graphql-tag' + +import type { ValidatorDirectiveFunc } from '@redwoodjs/graphql-server' +import { createValidatorDirective } from '@redwoodjs/graphql-server' + +import { requireAuth as applicationRequireAuth } from 'src/lib/auth' + +export const schema = gql` + """ + Use to check whether or not a user is authenticated and is associated + with an optional set of roles. + """ + directive @requireAuth(roles: [String]) on FIELD_DEFINITION +` + +type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }> + +const validate: RequireAuthValidate = ({ directiveArgs }) => { + const { roles } = directiveArgs + applicationRequireAuth({ roles }) +} + +const requireAuth = createValidatorDirective(schema, validate) + +export default requireAuth diff --git a/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.test.ts b/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.test.ts new file mode 100644 index 000000000000..88d99a56eab2 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.test.ts @@ -0,0 +1,10 @@ +import { getDirectiveName } from '@redwoodjs/testing/api' + +import skipAuth from './skipAuth' + +describe('skipAuth directive', () => { + it('declares the directive sdl as schema, with the correct name', () => { + expect(skipAuth.schema).toBeTruthy() + expect(getDirectiveName(skipAuth.schema)).toBe('skipAuth') + }) +}) diff --git a/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.ts b/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.ts new file mode 100644 index 000000000000..e85b94ae8b89 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/directives/skipAuth/skipAuth.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag' + +import { createValidatorDirective } from '@redwoodjs/graphql-server' + +export const schema = gql` + """ + Use to skip authentication checks and allow public access. + """ + directive @skipAuth on FIELD_DEFINITION +` + +const skipAuth = createValidatorDirective(schema, () => { + return +}) + +export default skipAuth diff --git a/__fixtures__/rsc-caching/api/src/functions/graphql.ts b/__fixtures__/rsc-caching/api/src/functions/graphql.ts new file mode 100644 index 000000000000..db1e2492f979 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/functions/graphql.ts @@ -0,0 +1,21 @@ +import { createGraphQLHandler } from '@redwoodjs/graphql-server' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { getCurrentUser } from 'src/lib/auth' +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +export const handler = createGraphQLHandler({ + getCurrentUser, + loggerConfig: { logger, options: {} }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) diff --git a/__fixtures__/rsc-caching/api/src/graphql/.keep b/__fixtures__/rsc-caching/api/src/graphql/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/rsc-caching/api/src/lib/auth.ts b/__fixtures__/rsc-caching/api/src/lib/auth.ts new file mode 100644 index 000000000000..4b0b88784d47 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/lib/auth.ts @@ -0,0 +1,32 @@ +/** + * Once you are ready to add authentication to your application + * you'll build out requireAuth() with real functionality. For + * now we just return `true` so that the calls in services + * have something to check against, simulating a logged + * in user that is allowed to access that service. + * + * See https://redwoodjs.com/docs/authentication for more info. + */ +export const isAuthenticated = () => { + return true +} + +export const hasRole = ({ roles }) => { + return roles !== undefined +} + +// This is used by the redwood directive +// in ./api/src/directives/requireAuth + +// Roles are passed in by the requireAuth directive if you have auth setup +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars +export const requireAuth = ({ roles }) => { + return isAuthenticated() +} + +export const getCurrentUser = async () => { + throw new Error( + 'Auth is not set up yet. See https://redwoodjs.com/docs/authentication ' + + 'to get started' + ) +} diff --git a/__fixtures__/rsc-caching/api/src/lib/db.ts b/__fixtures__/rsc-caching/api/src/lib/db.ts new file mode 100644 index 000000000000..5006d00aae49 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/lib/db.ts @@ -0,0 +1,21 @@ +// See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor +// for options. + +import { PrismaClient } from '@prisma/client' + +import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' + +import { logger } from './logger' + +/* + * Instance of the Prisma Client + */ +export const db = new PrismaClient({ + log: emitLogLevels(['info', 'warn', 'error']), +}) + +handlePrismaLogging({ + db, + logger, + logLevels: ['info', 'warn', 'error'], +}) diff --git a/__fixtures__/rsc-caching/api/src/lib/logger.ts b/__fixtures__/rsc-caching/api/src/lib/logger.ts new file mode 100644 index 000000000000..150a309767c5 --- /dev/null +++ b/__fixtures__/rsc-caching/api/src/lib/logger.ts @@ -0,0 +1,17 @@ +import { createLogger } from '@redwoodjs/api/logger' + +/** + * Creates a logger with RedwoodLoggerOptions + * + * These extend and override default LoggerOptions, + * can define a destination like a file or other supported pino log transport stream, + * and sets whether or not to show the logger configuration settings (defaults to false) + * + * @param RedwoodLoggerOptions + * + * RedwoodLoggerOptions have + * @param {options} LoggerOptions - defines how to log, such as redaction and format + * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file + * @param {boolean} showConfig - whether to display logger configuration on initialization + */ +export const logger = createLogger({}) diff --git a/__fixtures__/rsc-caching/api/src/services/.keep b/__fixtures__/rsc-caching/api/src/services/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/rsc-caching/api/tsconfig.json b/__fixtures__/rsc-caching/api/tsconfig.json new file mode 100644 index 000000000000..a3a4bcdf5b58 --- /dev/null +++ b/__fixtures__/rsc-caching/api/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": false, + "rootDirs": [ + "./src", + "../.redwood/types/mirror/api/src" + ], + "paths": { + "src/*": [ + "./src/*", + "../.redwood/types/mirror/api/src/*" + ], + "types/*": ["./types/*", "../types/*"], + "@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"] + }, + "typeRoots": [ + "../node_modules/@types", + "./node_modules/@types" + ], + "types": ["jest"], + "jsx": "react-jsx" + }, + "include": [ + "src", + "../.redwood/types/includes/all-*", + "../.redwood/types/includes/api-*", + "../types" + ] +} diff --git a/__fixtures__/rsc-caching/graphql.config.js b/__fixtures__/rsc-caching/graphql.config.js new file mode 100644 index 000000000000..c564451acb66 --- /dev/null +++ b/__fixtures__/rsc-caching/graphql.config.js @@ -0,0 +1,11 @@ +// This file is used by the VSCode GraphQL extension + +const { getPaths } = require('@redwoodjs/project-config') + +/** @type {import('graphql-config').IGraphQLConfig} */ +const config = { + schema: getPaths().generated.schema, + documents: './web/src/**/!(*.d).{ts,tsx,js,jsx}', +} + +module.exports = config diff --git a/__fixtures__/rsc-caching/jest.config.js b/__fixtures__/rsc-caching/jest.config.js new file mode 100644 index 000000000000..c6b395cb762a --- /dev/null +++ b/__fixtures__/rsc-caching/jest.config.js @@ -0,0 +1,8 @@ +// This the Redwood root jest config +// Each side, e.g. ./web/ and ./api/ has specific config that references this root +// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build + +module.exports = { + rootDir: '.', + projects: ['/{*,!(node_modules)/**/}/jest.config.js'], +} diff --git a/__fixtures__/rsc-caching/package.json b/__fixtures__/rsc-caching/package.json new file mode 100644 index 000000000000..53ecf65b6784 --- /dev/null +++ b/__fixtures__/rsc-caching/package.json @@ -0,0 +1,29 @@ +{ + "private": true, + "workspaces": { + "packages": [ + "api", + "web" + ] + }, + "devDependencies": { + "@redwoodjs/core": "8.0.0-canary.1102", + "@redwoodjs/project-config": "8.0.0-canary.1102" + }, + "eslintConfig": { + "extends": "@redwoodjs/eslint-config", + "root": true + }, + "engines": { + "node": "=20.x" + }, + "prisma": { + "seed": "yarn rw exec seed" + }, + "packageManager": "yarn@4.1.1", + "resolutions": { + "@apollo/client-react-streaming/superjson": "^1.12.2", + "@apollo/client/rehackt": "0.1.0", + "react-is": "19.0.0-beta-04b058868c-20240508" + } +} diff --git a/__fixtures__/rsc-caching/prettier.config.js b/__fixtures__/rsc-caching/prettier.config.js new file mode 100644 index 000000000000..45058f7aa2a8 --- /dev/null +++ b/__fixtures__/rsc-caching/prettier.config.js @@ -0,0 +1,18 @@ +// https://prettier.io/docs/en/options.html +/** @type {import('prettier').RequiredOptions} */ +module.exports = { + trailingComma: 'es5', + bracketSpacing: true, + tabWidth: 2, + semi: false, + singleQuote: true, + arrowParens: 'always', + overrides: [ + { + files: 'Routes.*', + options: { + printWidth: 999, + }, + }, + ], +} diff --git a/__fixtures__/rsc-caching/redwood.toml b/__fixtures__/rsc-caching/redwood.toml new file mode 100644 index 000000000000..a88cd593d6dd --- /dev/null +++ b/__fixtures__/rsc-caching/redwood.toml @@ -0,0 +1,31 @@ +# This file contains the configuration settings for your Redwood app. +# This file is also what makes your Redwood app a Redwood app. +# If you remove it and try to run `yarn rw dev`, you'll get an error. +# +# For the full list of options, see the "App Configuration: redwood.toml" doc: +# https://redwoodjs.com/docs/app-configuration-redwood-toml + +[web] + title = "Redwood App" + port = 8910 + apiUrl = "/.redwood/functions" # You can customize graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths + includeEnvironmentVariables = [ + # Add any ENV vars that should be available to the web side to this array + # See https://redwoodjs.com/docs/environment-variables#web + ] +[api] + port = 8911 +[browser] + open = true +[notifications] + versionUpdates = ["latest"] + +[generate] + tests = false + stories = false + +[experimental.streamingSsr] + enabled = true + +[experimental.rsc] + enabled = true diff --git a/__fixtures__/rsc-caching/scripts/.keep b/__fixtures__/rsc-caching/scripts/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/rsc-caching/scripts/seed.ts b/__fixtures__/rsc-caching/scripts/seed.ts new file mode 100644 index 000000000000..deb5b7798237 --- /dev/null +++ b/__fixtures__/rsc-caching/scripts/seed.ts @@ -0,0 +1,67 @@ +import type { Prisma } from '@prisma/client' +import { db } from 'api/src/lib/db' + +export default async () => { + try { + // + // Manually seed via `yarn rw prisma db seed` + // Seeds automatically with `yarn rw prisma migrate dev` and `yarn rw prisma migrate reset` + // + // Update "const data = []" to match your data model and seeding needs + // + const data: Prisma.UserExampleCreateArgs['data'][] = [ + // To try this example data with the UserExample model in schema.prisma, + // uncomment the lines below and run 'yarn rw prisma migrate dev' + // + { name: 'alice', email: 'alice@example.com' }, + { name: 'mark', email: 'mark@example.com' }, + { name: 'jackie', email: 'jackie@example.com' }, + { name: 'bob', email: 'bob@example.com' }, + ] + console.log( + "\nUsing the default './scripts/seed.ts' template\nEdit the file to add seed data\n" + ) + + if ((await db.userExample.count()) === 0) { + // Note: if using PostgreSQL, using `createMany` to insert multiple records is much faster + // @see: https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#createmany + await Promise.all( + // + // Change to match your data model and seeding needs + // + data.map(async (data: Prisma.UserExampleCreateArgs['data']) => { + const record = await db.userExample.create({ data }) + console.log(record) + }) + ) + } else { + console.log('Users already seeded') + } + + // If using dbAuth and seeding users, you'll need to add a `hashedPassword` + // and associated `salt` to their record. Here's how to create them using + // the same algorithm that dbAuth uses internally: + // + // import { hashPassword } from '@redwoodjs/auth-dbauth-api' + // + // const users = [ + // { name: 'john', email: 'john@example.com', password: 'secret1' }, + // { name: 'jane', email: 'jane@example.com', password: 'secret2' } + // ] + // + // for (const user of users) { + // const [hashedPassword, salt] = hashPassword(user.password) + // await db.user.create({ + // data: { + // name: user.name, + // email: user.email, + // hashedPassword, + // salt + // } + // }) + // } + } catch (error) { + console.warn('Please define your seed data.') + console.error(error) + } +} diff --git a/__fixtures__/rsc-caching/scripts/tsconfig.json b/__fixtures__/rsc-caching/scripts/tsconfig.json new file mode 100644 index 000000000000..1e6487a5ffa6 --- /dev/null +++ b/__fixtures__/rsc-caching/scripts/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "paths": { + "$api/*": [ + "../api/*" + ], + "api/*": [ + "../api/*" + ], + "$web/*": [ + "../web/*" + ], + "web/*": [ + "../web/*" + ], + "$web/src/*": [ + "../web/src/*", + "../.redwood/types/mirror/web/src/*" + ], + "web/src/*": [ + "../web/src/*", + "../.redwood/types/mirror/web/src/*" + ], + "types/*": ["../types/*", "../web/types/*", "../api/types/*"] + }, + "typeRoots": ["../node_modules/@types"], + "jsx": "preserve" + }, + "include": [ + ".", + "../.redwood/types/includes/all-*", + "../.redwood/types/includes/web-*", + "../types" + ] +} diff --git a/__fixtures__/rsc-caching/web/jest.config.js b/__fixtures__/rsc-caching/web/jest.config.js new file mode 100644 index 000000000000..0e54869ebdcb --- /dev/null +++ b/__fixtures__/rsc-caching/web/jest.config.js @@ -0,0 +1,8 @@ +// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build + +const config = { + rootDir: '../', + preset: '@redwoodjs/testing/config/jest/web', +} + +module.exports = config diff --git a/__fixtures__/rsc-caching/web/package.json b/__fixtures__/rsc-caching/web/package.json new file mode 100644 index 000000000000..22fbd0700d9b --- /dev/null +++ b/__fixtures__/rsc-caching/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "web", + "version": "0.0.0", + "private": true, + "browserslist": { + "development": [ + "last 1 version" + ], + "production": [ + "defaults" + ] + }, + "dependencies": { + "@apollo/client-react-streaming": "0.10.0", + "@jtoar/throw-on-client": "0.0.1", + "@redwoodjs/auth-dbauth-middleware": "8.0.0-canary.1102", + "@redwoodjs/auth-dbauth-web": "8.0.0-canary.1102", + "@redwoodjs/forms": "8.0.0-canary.1102", + "@redwoodjs/router": "8.0.0-canary.1102", + "@redwoodjs/web": "8.0.0-canary.1102", + "@tobbe.dev/rsc-test": "0.0.5", + "client-only": "0.0.1", + "react": "19.0.0-beta-04b058868c-20240508", + "react-dom": "19.0.0-beta-04b058868c-20240508" + }, + "devDependencies": { + "@redwoodjs/vite": "8.0.0-canary.1102", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19" + } +} diff --git a/__fixtures__/rsc-caching/web/public/README.md b/__fixtures__/rsc-caching/web/public/README.md new file mode 100644 index 000000000000..618395f02033 --- /dev/null +++ b/__fixtures__/rsc-caching/web/public/README.md @@ -0,0 +1,35 @@ +# Static Assets +Use this folder to add static files directly to your app. All included files and folders will be copied directly into the `/dist` folder (created when Vite builds for production). They will also be available during development when you run `yarn rw dev`. +>Note: files will *not* hot reload while the development server is running. You'll need to manually stop/start to access file changes. + +### Example Use +A file like `favicon.png` will be copied to `/dist/favicon.png`. A folder containing a file such as `static-files/my-logo.jpg` will be copied to `/dist/static-files/my-logo.jpg`. These can be referenced in your code directly without any special handling, e.g. +``` + +``` +and +``` + alt="Logo" /> +``` + + +## Best Practices +Because assets in this folder are bypassing the javascript module system, **this folder should be used sparingly** for assets such as favicons, robots.txt, manifests, libraries incompatible with Vite, etc. + +In general, it's best to import files directly into a template, page, or component. This allows Vite to include that file in the bundle when small enough, or to copy it over to the `dist` folder with a hash. + +### Example Asset Import with Vite +Instead of handling our logo image as a static file per the example above, we can do the following: +``` +import React from "react" +import logo from "./my-logo.jpg" + + +function Header() { + return Logo +} + +export default Header +``` + +See Vite's docs for [static asset handling](https://vitejs.dev/guide/assets.html) diff --git a/__fixtures__/rsc-caching/web/public/favicon.png b/__fixtures__/rsc-caching/web/public/favicon.png new file mode 100644 index 000000000000..47414294173c Binary files /dev/null and b/__fixtures__/rsc-caching/web/public/favicon.png differ diff --git a/__fixtures__/rsc-caching/web/public/robots.txt b/__fixtures__/rsc-caching/web/public/robots.txt new file mode 100644 index 000000000000..eb0536286f30 --- /dev/null +++ b/__fixtures__/rsc-caching/web/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/__fixtures__/rsc-caching/web/src/App.tsx b/__fixtures__/rsc-caching/web/src/App.tsx new file mode 100644 index 000000000000..6b8033a749ea --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/App.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from 'react' + +import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from './pages/FatalErrorPage/FatalErrorPage' + +import './index.css' + +interface AppProps { + children?: ReactNode +} + +const App = ({ children }: AppProps) => ( + + + {children} + + +) + +export default App diff --git a/__fixtures__/rsc-caching/web/src/Document.tsx b/__fixtures__/rsc-caching/web/src/Document.tsx new file mode 100644 index 000000000000..8eb4285b2625 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/Document.tsx @@ -0,0 +1,27 @@ +import React from 'react' + +import { Css, Meta } from '@redwoodjs/web/htmlTags' +import type { TagDescriptor } from '@redwoodjs/web/htmlTags' + +interface DocumentProps { + children: React.ReactNode + css: string[] // array of css import strings + meta?: TagDescriptor[] +} + +export const Document: React.FC = ({ children, css, meta }) => { + return ( + + + + + + + + + +
{children}
+ + + ) +} diff --git a/__fixtures__/rsc-caching/web/src/Routes.tsx b/__fixtures__/rsc-caching/web/src/Routes.tsx new file mode 100644 index 000000000000..96da913501e6 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/Routes.tsx @@ -0,0 +1,29 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Route } from '@redwoodjs/router/Route' +import { Router } from '@redwoodjs/router/RscRouter' +import { Set } from '@redwoodjs/router/Set' + +import MainLayout from './layouts/MainLayout/MainLayout' + +const Routes = () => { + return ( + + + + + + + + + ) +} + +export default Routes diff --git a/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.css b/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.css new file mode 100644 index 000000000000..1da171e095d1 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.css @@ -0,0 +1,10 @@ +.caching-boxes { + > div { + width: 100px; + height: 100px; + display: inline-block; + margin: 10px; + border: 1px solid black; + padding: 2px; + } +} diff --git a/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.tsx b/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.tsx new file mode 100644 index 000000000000..fa3dbfac6856 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/components/CachingBoxes/CachingBoxes.tsx @@ -0,0 +1,66 @@ +import './CachingBoxes.css' + +const colors = [ + 'red', + 'green', + 'blue', + 'yellow', + 'purple', + 'orange', + 'pink', + 'brown', + 'lightblue', + 'lightgreen', + 'cyan', + 'magenta', + 'lime', + 'maroon', + 'navy', + 'olive', + 'teal', + 'aqua', + 'fuchsia', + 'silver', + 'gray', + 'gold', + 'coral', + 'indigo', + 'violet', +] + +const CachingBoxes = () => { + const shuffledColors = colors + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value) + + return ( +
+
+
{shuffledColors[0]}
+
{shuffledColors[0]}
+
+ +
+
{shuffledColors[1]}
+
{shuffledColors[1]}
+
+ +
+
{shuffledColors[2]}
+
{shuffledColors[2]}
+
+
+ ) +} + +export default CachingBoxes diff --git a/__fixtures__/rsc-caching/web/src/entry.client.tsx b/__fixtures__/rsc-caching/web/src/entry.client.tsx new file mode 100644 index 000000000000..915c14d76df7 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/entry.client.tsx @@ -0,0 +1,35 @@ +import { hydrateRoot, createRoot } from 'react-dom/client' + +import App from './App' +import Routes from './Routes' + +/** + * When `#redwood-app` isn't empty then it's very likely that you're using + * prerendering. So React attaches event listeners to the existing markup + * rather than replacing it. + * https://react.dev/reference/react-dom/client/hydrateRoot + */ +const redwoodAppElement = document.getElementById('redwood-app') + +if (!redwoodAppElement) { + throw new Error( + "Could not find an element with ID 'redwood-app'. Please ensure it " + + "exists in your 'web/src/index.html' file." + ) +} + +if (redwoodAppElement.children?.length > 0) { + hydrateRoot( + redwoodAppElement, + + + + ) +} else { + const root = createRoot(redwoodAppElement) + root.render( + + + + ) +} diff --git a/__fixtures__/rsc-caching/web/src/entry.server.tsx b/__fixtures__/rsc-caching/web/src/entry.server.tsx new file mode 100644 index 000000000000..5cf49b406a89 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/entry.server.tsx @@ -0,0 +1,20 @@ +import type { TagDescriptor } from '@redwoodjs/web/htmlTags' + +import App from './App' +import { Document } from './Document' +import Routes from './Routes' + +interface Props { + css: string[] + meta?: TagDescriptor[] +} + +export const ServerEntry: React.FC = ({ css, meta }) => { + return ( + + + + + + ) +} diff --git a/__fixtures__/rsc-caching/web/src/index.css b/__fixtures__/rsc-caching/web/src/index.css new file mode 100644 index 000000000000..57c14ee231a9 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/index.css @@ -0,0 +1,4 @@ +html, body { + margin: 0; + padding: 0; +} diff --git a/__fixtures__/rsc-caching/web/src/layouts/.keep b/__fixtures__/rsc-caching/web/src/layouts/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.css b/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.css new file mode 100644 index 000000000000..46ea5d1668fb --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.css @@ -0,0 +1,29 @@ +.main-layout { + nav { + display: flex; + justify-content: center; + background-color: #333; + + ul { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + } + + ul li { + padding: 14px 20px; + } + + ul li a { + color: white; + text-align: center; + text-decoration: none; + display: block; + } + + ul li a:hover { + background-color: #111; + } + } +} diff --git a/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.tsx b/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.tsx new file mode 100644 index 000000000000..11183396ecec --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/layouts/MainLayout/MainLayout.tsx @@ -0,0 +1,41 @@ +import { namedRoutes } from '@redwoodjs/router/namedRoutes' +import { NavLink } from '@redwoodjs/router/NavLink' + +import CachingBoxes from 'src/components/CachingBoxes/CachingBoxes' + +import './MainLayout.css' + +interface Props { + children?: React.ReactNode +} + +const MainLayout = ({ children }: Props) => { + return ( +
+ + + +
{children}
+
+ ) +} + +export default MainLayout diff --git a/__fixtures__/rsc-caching/web/src/pages/CachingOnePage/CachingOnePage.tsx b/__fixtures__/rsc-caching/web/src/pages/CachingOnePage/CachingOnePage.tsx new file mode 100644 index 000000000000..6dc3b274849d --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/pages/CachingOnePage/CachingOnePage.tsx @@ -0,0 +1,23 @@ +import { Link } from '@redwoodjs/router/Link' +import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes' +import { Metadata } from '@redwoodjs/web/Metadata' + +const CachingOnePage = () => { + return ( + <> + + +

CachingOnePage

+

+ Find me in{' '} + ./web/src/pages/CachingOnePage/CachingOnePage.tsx +

+

+ My default route is named cachingOne, link to me with ` + CachingOne` +

+ + ) +} + +export default CachingOnePage diff --git a/__fixtures__/rsc-caching/web/src/pages/CachingTwoPage/CachingTwoPage.tsx b/__fixtures__/rsc-caching/web/src/pages/CachingTwoPage/CachingTwoPage.tsx new file mode 100644 index 000000000000..f08b8116541f --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/pages/CachingTwoPage/CachingTwoPage.tsx @@ -0,0 +1,23 @@ +import { Link } from '@redwoodjs/router/Link' +import { namedRoutes as routes } from '@redwoodjs/router/namedRoutes' +import { Metadata } from '@redwoodjs/web/Metadata' + +const CachingTwoPage = () => { + return ( + <> + + +

CachingTwoPage

+

+ Find me in{' '} + ./web/src/pages/CachingTwoPage/CachingTwoPage.tsx +

+

+ My default route is named cachingTwo, link to me with ` + CachingTwo` +

+ + ) +} + +export default CachingTwoPage diff --git a/__fixtures__/rsc-caching/web/src/pages/FatalErrorPage/FatalErrorPage.tsx b/__fixtures__/rsc-caching/web/src/pages/FatalErrorPage/FatalErrorPage.tsx new file mode 100644 index 000000000000..b2bb436f8ed0 --- /dev/null +++ b/__fixtures__/rsc-caching/web/src/pages/FatalErrorPage/FatalErrorPage.tsx @@ -0,0 +1,57 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) + +// This import will be automatically removed when building for production +import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + +export default DevFatalErrorPage || + (() => ( +
+