Redwood Utility Types
Besides generating types for you, Redwood exposes a handful of utility types for Cells, Scenarios, and DbAuth. You'll see these helpers quite often if you use the generators, so let's walk through some of them. By the end of this, you'll likely see a pattern in these types and their use of Generics.
Cellsβ
Cells created using the generators come with all the types your normally need, including the CellSuccessProps
, CellFailureProps
, and CellLoadingProps
utility types.
CellSuccessProps<TData, TVariables>
β
This is used to type the props of your Cell's Success
component.
It takes two arguments as generics:
Generic | Description |
---|---|
TData | The type of data you're expecting to receive (usually the type generated from the query) |
TVariables | An optional second parameter for the type of the query's variables |
Not only does CellSuccessProps
type the data returned from the query, but it also types the variables and methods returned by Apollo Client's useQuery
hook!
import type { FindBlogPostQuery, FindBlogPostQueryVariables } from 'types/graphql'
import type { CellSuccessProps } from '@redwoodjs/web'
// ...
type SuccessProps = CellSuccessProps<FindBlogPostQuery, FindBlogPostQueryVariables>
export const Success = ({
blogPost, // From the query. This is typed of course
queryResult // π From Apollo Client. This is typed too!
}: SuccessProps) => {
// ...
}
CellFailureProps<TVariables>
β
This gives you the types of the props in your Cell's Failure
component.
It takes TVariables
as an optional generic parameter, which is useful if you want to print error messages like "Couldn't load data for ${variables.searchTerm}"
:
import type { FindBlogPostQuery, FindBlogPostQueryVariables } from 'types/graphql'
import type { CellFailureProps } from '@redwoodjs/web'
// ...
export const Failure = ({
error,
variables // π Variables is typed based on the generic
}: CellFailureProps<FindBlogPostQueryVariables>) => (
// ...
)
CellLoadingProps<TVariables>
β
Similar to CellFailureProps
, but for the props of your Cell's Loading
component:
import type { FindBlogPostQuery, FindBlogPostQueryVariables } from 'types/graphql'
import type { CellLoadingProps } from '@redwoodjs/web'
// ...
export const Loading = (props: CellLoadingProps<FindBlogPostQueryVariables>) => (
<div>Loading...</div>
)
Scenarios & Testingβ
Over on the api side, when you generate SDLs and Services, Redwood generates tests and scenarios with all the types required. Let's take a deeper look at scenario types.
defineScenario
β
This is actually a function, not a type, but it takes a lot of generics. Use as many or as few as you find helpful.
defineScenario<PrismaCreateType, TName, TKey>
Generic | Description |
---|---|
PrismaCreateType | (Optional) the type imported from Prisma's create operation that goes into the "data" key |
TName | (Optional) the name or names of the models in your scenario |
TKeys | (Optional) the key(s) in your scenario. These are really only useful while you write out the scenario |
An example:
import type { Prisma, Post } from '@prisma/client'
export const standard = defineScenario<Prisma.PostCreateArgs, 'post', 'one'>({
//π TName
post: {
// π TKey
one: {
// π PrismaCreateType. Notice how we import the type from @prisma/client
data: { title: 'String', body: 'String', metadata: { foo: 'bar' } },
},
},
})
If you have more than one model in a single scenario, you can use unions:
defineScenario<Prisma.PostCreateArgs | Prisma.UserCreateArgs, 'post' | 'user'>
ScenarioData<TModel, TName, TKeys>
β
This utility type makes it easy for you to access data created by your scenarios in your tests. It takes three generic parameters:
Generic | Description |
---|---|
TData | The Prisma model that'll be returned |
TName | (Optional) the name of the model. ("post" in the example below) |
TKeys | (optional) the key(s) used to define the scenario. ("one" in the example below) |
We know this is a lot of generics, but that's so you get to choose how specific you want to be with the types!
import type { Post } from '@prisma/client'
//...
export type StandardScenario = ScenarioData<Post, 'post'>
import type { StandardScenario } from './posts.scenarios'
scenario('returns a single post', async (scenario: StandardScenario) => {
const result = await post({ id: scenario.post.one.id })
})
You can of course just define the type in the test file instead of importing it. Just be aware that if you change your scenario, you need to update the type in the test file too!
DbAuthβ
When you setup dbAuth, the generated files in api/src/lib/auth.ts
and api/src/functions/auth.ts
have all the types you need. Let's break down some of the utility types.
DbAuthSession
β
You'll notice an import at the top of api/src/lib/auth.ts
:
import type { DbAuthSession } from '@redwoodjs/api'
DbAuthSession
is a utility type that's used to type the argument to getCurrentUser
, session
:
export const getCurrentUser = async (session: DbAuthSession<number>) => {
return await db.user.findUnique({
where: { id: session.id },
select: { id: true },
})
}
The generic it takes should be the type of your User model's id
field.
It's usually a string
or a number
, but it depends on how you've defined it.
Because a session only ever contains id
, all we're doing here is defining the type of id
.
DbAuthHandlerOptions
β
DbAuthHandlerOptions
gives you access to all the types you need to configure your dbAuth handler function in api/src/function/auth.ts
.
It also takes a generic, TUser
βthe type of your User model. Note that this is not the same type as CurrentUser
.
You can import the type of the User model directly from Prisma and pass it to DbAuthHandlerOptions
:
import type { User as PrismaUser } from '@prisma/client'
import type { DbAuthHandlerOptions } from '@redwoodjs/api'
export const handler = async (
event: APIGatewayProxyEvent,
context: Context
) => {
// Pass in the generic to the type here π
const forgotPasswordOptions: DbAuthHandlerOptions<PrismaUser>['forgotPassword'] = {
// ...
// Now in the handler function, `user` will be typed
handler: (user) => {
return user
},
// ...
}
// ...
}
Note that in strict mode, you'll likely see errors where the handlers expect "truthy" values. All you have to do is make sure you return a boolean. For example, return !!user
instead of return user
.
Directivesβ
ValidatorDirectiveFunc
β
When you generate a validator directive you will see your validate
function typed already with ValidatorDirectiveFunc<TDirectiveArgs>
import {
createValidatorDirective,
ValidatorDirectiveFunc,
} from '@redwoodjs/graphql-server'
export const schema = gql`
directive @myValidator on FIELD_DEFINITION
`
// π makes sure "context" and directive args are typed
const validate: ValidatorDirectiveFunc = ({ context, directiveArgs }) => {
This type takes a single generic - the type of your directiveArgs
.
Let's take a look at the built-in @requireAuth(roles: ["ADMIN"])
directive, for example - which we ship with your Redwood app by default in ./api/src/directives/requireAuth/requireAuth.ts
type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }>
const validate: RequireAuthValidate = ({ directiveArgs }) => {
// roles π will be typed correctly as string[] | undefined
const { roles } = directiveArgs
// ....
}
Generic | Description |
---|---|
TDirectiveArgs | The type of arguments passed to your directive in the SDL |
TransformerDirectiveFunc
β
When you generate a transformer directive you will see your transform
function typed with TransformDirectiveFunc<TField, TDirectiveArgs>
.
// π makes sure the functions' arguments are typed
const transform: TransformerDirectiveFunc = ({ context, resolvedValue }) => {
This type takes two generics - the type of the field you are transforming, and the type of your directiveArgs
.
So for example, let's say you have a transformer directive @maskedEmail(permittedRoles: ['ADMIN'])
that you apply to String
fields. You would pass in the following types
type MaskedEmailTransform = TransformerDirectiveFunc<string, {permittedRoles?: string[]}>
Generic | Description |
---|---|
TField | This will type resolvedValue i.e. the type of the field you are transforming |
TDirectiveArgs | The type of arguments passed to your directive in the SDL |