TypeScript 4.9 beta brings the new satisfies operator

Microsoft has announced the availability of TypeScript 4.9 beta, a version whose most remarkable novelty is the arrival of a new operator: satisfies, as well as the improvement of the operator in.

Operator satisfied

Microsoft explains that TypeScript developers often face a dilemma: ensuring that an expression correspond to one type, while also wanting to keep the type the more specific of this expression for inference purposes.

Microsoft gives this example:

// Each property can be a string or an RGB tuple.
const palette =
red: [255, 0, 0],
green: “#00ff00”,
bleu: [0, 0, 255]
// ^^^^ sacred blue – we’ve made a typo!
;

// We want to be able to use array methods on ‘red’…
const redComponent = palette.red.at(0);

// or string methods on ‘green’…
const greenNormalized = palette.green.toUpperCase();

it is written blue in the code, whereas it is probably blue which should have been written. It would be possible to handle this blue typo by using an on-palette type annotation, but then losing the information about each property.

type Colors = “red” | “green” | “blue”;

type RGB = [red: number, green: number, blue: number];

const palette: Record =
red: [255, 0, 0],
green: “#00ff00”,
bleu: [0, 0, 255]
//  ~~~~ The typo is now correctly detected
;

// But we now have an undesirable error here – ‘palette.red’ “could” be a string.
const redComponent = palette.red.at(0);

The new satisfies operator allows you to validate that the type of an expression corresponds to a certain type, without changing the resulting type of this expression. As an example, one could use satisfies to validate that all palette properties are compatible with string | number[] :

type Colors = “red” | “green” | “blue”;

type RGB = [red: number, green: number, blue: number];

const palette =
red: [255, 0, 0],
green: “#00ff00”,
bleu: [0, 0, 255]
//  ~~~~ The typo is now caught!
satisfies Record;

// Both of these methods are still accessible!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

Operator in

Microsoft explains that as developers we often have to deal with values ​​that are not fully known at runtime. In fact, we often don’t know if properties exist, if we get a response from a server, or if we read a configuration file. JavaScript’s in operator can check if a property exists on an object.

Previously, TypeScript allowed us to restrict all types that didn’t explicitly list a property.

interface RGB
red: number;
green: number;
blue: number;

interface HSV
hue: number;
saturation: number;
value: number;

function setColor(color: RGB | HSV)
if (“hue” in color)
// ‘color’ now has the type HSV

// …

Here the RGB type did not list hue and was restricted, leaving us with the HSV type. But what about examples where no type mentions a given property? In these cases, the language did not help us much. Consider the following example in JavaScript:

function tryGetPackageName(context)
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === “object”)
// Check to see if it has a string name property.
if (“name” in packageJSON && typeof packageJSON.name === “string”)
return packageJSON.name;

return undefined;

Rewriting this in canonical TypeScript would just be a matter of defining and using a type for context. However, choosing a safe type like unknown for the packageJSON property would cause problems in older versions of TypeScript.

This is because while the type of packageJSON has changed from unknown to object, the in operator is strictly limited to types that actually defined the checked property. As a result, the packageJSON type remained object.

TypeScript 4.9 makes the in operator a bit more powerful when reducing types that it is not list the property at all. Instead of leaving them as they are, the language will intersect their types with Record<"property-key-being-checked", unknown>.

So in the example packageJSON will have its type reduced from unknown to object then object & Record<"name", unknown>. allows directly accessing packageJSON.name and shrinking it independently

interface Context
packageJSON: unknown;

function tryGetPackageName(context: Context): string | undefined
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === “object”)
// Check to see if it has a string name property.
if (“name” in packageJSON && typeof packageJSON.name === “string”)
// Just works!
return packageJSON.name;

return undefined;

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.