TypeScript's new satisfies
operator allows some new, type-safe patterns that previously required lengthy type annotations or tricky workarounds.
This article covers several use cases where it helps you with common Prisma-related workflows.
Table Of Contents
- A little background
- Infer Prisma output types without
Prisma.validator
- Create lossless schema validators
- Define a collection of reusable query filters
- Strongly typed functions with inferred return types
- Wrapping up
A little background
One of TypeScript's strengths is how it can infer the type of an expression from context. For example, you can declare a variable without a type annotation, and its type will be inferred from the value you assign to it. This is especially useful when the exact type of a value is complex, and explicitly annotating the type would require a lot of duplicate code.
Sometimes, though, explicit type annotations are useful. They can help convey the intent of your code to other developers, and they keep TypeScript errors as close to the actual source of the error as possible.
Consider some code that defines subscription pricing tiers and turns them into strings using the toFixed
method on Number
:
If we use an explicit type annotation on plans
, we can catch the typo earlier, as well as infer the type of the users
arguments. However, we might run into a different problem:
When we use an explicit type annotation, the type gets "widened", and TypeScript can no longer tell which of our plans have flat pricing and which have per-user pricing. Effectively, we have "lost" some information about our application's types.
What we really need is a way to assert that a value is compatible with some broad / reusable type, while letting TypeScript infer a narrower (more specific) type.
Constrained identity functions
Before TypeScript 4.9, a solution to this problem was to use a "constrained identity function". This is a generic, no-op function that takes an argument and a type parameter, ensuring the two are compatible.
An example of this kind of function is the Prisma.validator
utility, which also does some extra work to only allow known fields defined in the provided generic type.
Unfortunately, this solution incurs some runtime overhead just to make TypeScript happy at compile time. There must be a better way!
Introducing satisfies
The new satisfies
operator gives the same benefits, with no runtime impact, and automatically checks for excess or misspelled properties.
Let's look at what our pricing tiers example might look like in TypeScript 4.9:
Now we catch the typo right at the source, but we don't "lose" any information to type widening.
The rest of this article will cover some real situations where you might use satisfies
in your Prisma application.
Infer Prisma output types without Prisma.validator
Prisma Client uses generic functions to give you type-safe results. The static types of data returned from client methods match the shape you asked for in a query.
This works great when calling a Prisma method directly with inline arguments:
However, you might run into some pitfalls:
- If you try to break your query arguments out into smaller objects, type information can get "lost" (widened) and Prisma might not infer the output types correctly.
- It can be difficult to get a type that represents the output of a specific query.
The satisfies
operator can help.
Infer the output type of methods like findMany
and create
One of the most common use cases for the satisfies
operator with Prisma is to infer the return type of a specific query method like a findUnique
— including only the selected fields of a model and its relations.
Infer the output type of the count
method
Prisma Client's count
method allows you to add a select
field, in order to count rows with non-null values for
specified fields. The return type of this method depends on which fields you specified:
Infer the output type of the aggregate
method
We can also get the output shape of the more flexible aggregate
method, which lets us get the average, min value,
max value, and counts of various model fields:
Infer the output type of the groupBy
method
The groupBy
method allows you to perform aggregations on groups of model instances. The results will include fields
that are used for grouping, as well as the results of aggregating fields. Here's how you can use satisfies
to infer
the output type:
Create lossless schema validators
Schema validation libraries (such as a zod or superstruct) are a good option for sanitizing user input at runtime. Some of these libraries can help you reduce duplicate type definitions by inferring a schema's static type. Sometimes, though, you might want to create a schema validator for an existing TypeScript type (like an input type generated by Prisma).
For example, given a Post
type like this in your Prisma schema file:
Prisma will generate the following PostCreateInput
type:
If you try to create a schema with zod that matches this type, you will "lose" some information about the schema object:
A workaround before TypeScript 4.9 was to create a schemaForType
function
(a kind of constrained identity function). Now with the satisfies
operator, you can create a schema for an existing
type, without losing any information about the schema.
Here are some examples for four popular schema validation libraries:
Define a collection of reusable query filters
As your application grows, you might use the same filtering logic across many queries. You may want to define some common filters which can be reused and composed into more complex queries.
Some ORMs have built-in ways to do this — for example, you can define model scopes in Ruby on Rails, or create custom queryset methods in Django.
With Prisma, where
conditions are object literals and can be composed with AND
, OR
, and NOT
. The satisfies
operator gives us a convenient way to define a collection of reusable filters:
Strongly typed functions with inferred return types
Sometimes you might want to assert that a function matches a special function signature, such as a React component or a Remix loader function. In cases like Remix loaders, you also want TypeScript to infer the specific shape returned by the function.
Before TypeScript 4.9, it was difficult to achieve both of these at once. With the satisfies
operator, we can now
ensure a function matches a special function signature without widening its return type.
Let's take a look at an example with a Remix loader that returns some data from Prisma:
Here the satisfies
operator does three things:
- Ensures our
loader
function is compatible with theLoaderFunction
signature from Remix - Infers the argument types for our function from the
LoaderFunction
signature so we don't have to annotate them manually - Infers that our function returns a
Post
object from Prisma, including its relatedcomments
Wrapping up
TypeScript and Prisma make it easy to get type-safe database access in your application. Prisma's API is designed to provide zero-cost type safety, so that in most cases you automatically get strong type checking without having to "opt in", clutter your code with type annotations, or provide generic arguments.
We're excited to see how new TypeScript features like the satisfies
operator can help you get better type safety,
even in more advanced cases, with minimal type noise. Let us know how you are using Prisma and TypeScript 4.9 by
reaching out to us on our Twitter.
Don’t miss the next post!
Sign up for the Prisma Newsletter