DEV Community

Ayc0
Ayc0

Posted on • Edited on

TypeScript 4.9: satisfies operator

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' };
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (9)

Collapse
 
jarzka profile image
Jarzka • Edited

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:

type RGB = readonly [red: number, green: number, blue: number];
type Color = RGB | string;

const myColor: Color = 'red';
myColor.toUpperCase(); // This was not supposed to work, but it does
Enter fullscreen mode Exit fullscreen mode

It also works on TypeScript Playground.

Collapse
 
ayc0 profile image
Ayc0 • Edited

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:

type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };

const myColor: Color = { value: 'red' };
myColor.value.toUpperCase();
Enter fullscreen mode Exit fullscreen mode

In this case, TS will use the type Color for value and not string.

See playground

You can also try in TS 4.9

type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };

const myColor = { value: 'red' } satisfies Color;
myColor.value.toUpperCase();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dcsan profile image
dc

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:

type RGB = readonly [red: number, green: number, blue: number];
type Color = RGB | string;
const myColor = 'red' satisfies Color; // works
myColor.toUpperCase(); // valid operation as myColor is a string
Enter fullscreen mode Exit fullscreen mode

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?

Collapse
 
ayc0 profile image
Ayc0

why is readonly used in this instance?

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.

so if myColor can be a string OR an RGB

That's the point of satisfies: it won't be a string or a RGB: when you do const myColor = 'red', myColor will have the type string.
Adding satisfies Color doesn't change its type, but instead checks that it can be casted into a Color. And indeed, string can be casted (but won't be casted) as a RGB | string.

So as myColor stays a string, TS won't throw an error on myColor.toUpperCase()

Collapse
 
eric_ profile image
Eric

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 a Color should either be a RGB or a string. And, using satisfies 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 a readonly array (RGB) should work or have some standard behavior across all Color instances. I don't really understand how that works or how that's supposed to be handled.

Thread Thread
 
ayc0 profile image
Ayc0

what actually happens when you try to run .toUpperCase()?

As the types are stripped away in the runtime, it'd be regular JS:

type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };

const myColor = { value: 'red' } satisfies Color;
myColor.value.toUpperCase();
Enter fullscreen mode Exit fullscreen mode

becomes

const myColor = { value: 'red' };
myColor.value.toUpperCase();
Enter fullscreen mode Exit fullscreen mode

so it'd just apply toUpperCase() to 'red', returning 'RED'.

I don't really understand how that works or how that's supposed to be handled.

satisfies doesn't change the type of myColor. It just checks that the type of the variable can be assimilated as another one. You can think about it as:

type RGB = readonly [red: number, green: number, blue: number];
type Color = { value: RGB | string };

const myColor = { value: 'red' };
const validatedMyColor: Color = myColor;
// ^ this variable isn't used, its only purpose is
// to check that `myColor` is assignable as a `Color` variable.
// But it doesn't change the fact that `myColor.value` is a string,
// and not `string | RGB`.
myColor.value.toUpperCase();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ayc0 profile image
Ayc0 • Edited

what actually is the result type? an array of strings or some type of enum? eg
["red": 1, "green": 2]

It'd be a tuple of 3 numbers, something like:

const myColor = [120, 255, 10];
Enter fullscreen mode Exit fullscreen mode

Adding readonly will only tell TS that you cannot push / mutate its content. And the red, green, blue is just to gave labels to the 3 values (hints on the values).
See devblogs.microsoft.com/typescript/...

Collapse
 
ggenya132 profile image
Eugene Vedensky

This is actually quite nice and addresses a personal painpoint for me.

Collapse
 
mehdiraash profile image
Mehdi Raash

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.