In their v4.9, the TypeScript team is releasing a new operator: satisfies
(see blog post https://devblogs.microsoft.com/typescript/announcing-typescript-4-9-beta/#the-satisfies-operator).
Purpose
The purpose of satisfies
is to enforce a constraint on a variable, without changing its type.
For instance, you want to say that a color is "either a string, or a RGB tuple", which would give something like that:
type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };
const myColor: Color = { value 'red' };
But now, we don't know whether myColor.value
is a string or a tuple. So we cannot do something like myColor.value.toUpperCase()
(even if it's actually a string).
In TS 4.9, it'll be possible to do this (TypeScript Playground):
type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };
const myColor = { value: 'red' } satisfies Color; // works
const myIncorrectColor = { value: 100 } satisfies Color; // throws error
myColor.value.toUpperCase(); // valid operation as myColor is a string
Combining as const
and satisfies
As expected, you combine as const
and satisfies
(TypeScript Playground).
type RGB = readonly [red: number, green: number, blue: number];
type Color = RGB | string;
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [1,2,3],
} satisfies Record<string, Color>;
console.log(palette.green);
// ^? green is string
const constantPalette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [1,2,3],
} as const satisfies Record<string, Color>;
console.log(constantPalette.green);
// ^? green is "#00ff00"
Note: the order matters. While as const satisfies <type>
works, the opposite isn't true: satisfies <type> as const
will throw a TS error (TypeScript Playground):
type RGB = readonly [red: number, green: number, blue: number];
type Color = RGB | string;
const invalidPalette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [1,2,3],
} satisfies Record<string, string | RGB> as const; // throws an error
Top comments (9)
Hi. I think your example does not fully explain the need of
satisfies
. At least my VS Code & TypeScript compiler is happy with the following example:It also works on TypeScript Playground.
You're right, in this case TS is able to properly infer the type
string
. I had a more complex example in my previous draft, but I tried to make it simpler for the blog post (and I didn't try the new version 😬)Try with this piece of code:
In this case, TS will use the type
Color
forvalue
and notstring
.See playground
You can also try in TS 4.9
type RGB = readonly [red: number, green: number, blue: number];
why is
readonly
used in this instance?what actually is the result type? an array of strings or some type of enum? eg
["red": 1, "green": 2]
could you explain more why:
so if
myColor
can be a string OR an RGB, how is it always going to work with the.toUpperCase
? Would that not fail on the tuple type of RGB?It's used because using
as const
on an array creates a readonly tuple. So I had to use it to make the 2 types compatible.That's the point of
satisfies
: it won't be a string or a RGB: when you doconst myColor = 'red'
,myColor
will have the typestring
.Adding
satisfies Color
doesn't change its type, but instead checks that it can be casted into aColor
. And indeed,string
can be casted (but won't be casted) as aRGB | string
.So as
myColor
stays a string, TS won't throw an error onmyColor.toUpperCase()
But hypothetically, then, you have an RGB type which satisfies the requirements for the Color type; what actually happens when you try to run
.toUpperCase()
?At the end of the day,
type Color = RGB | string
is really just saying that anything stored as aColor
should either be aRGB
or astring
. And, usingsatisfies
is supposed to put us in a position where we can be confident that it's safe to use string methods or methods specific to areadonly array
(RGB
) should work or have some standard behavior across allColor
instances. I don't really understand how that works or how that's supposed to be handled.As the types are stripped away in the runtime, it'd be regular JS:
becomes
so it'd just apply
toUpperCase()
to'red'
, returning'RED'
.satisfies
doesn't change the type ofmyColor
. It just checks that the type of the variable can be assimilated as another one. You can think about it as:It'd be a tuple of 3 numbers, something like:
Adding
readonly
will only tell TS that you cannot push / mutate its content. And thered
,green
,blue
is just to gave labels to the 3 values (hints on the values).See devblogs.microsoft.com/typescript/...
This is actually quite nice and addresses a personal painpoint for me.
Cool, just wondering how this feature can be helpful, If there are two possible types for a property, so it's better the code handles both scenarios.
To me it's against the premise of TS even, unless I'm missing something.