TypeScript satisfies your assertions
TypeScript 4.9 blessed us with the satisfies
operator (see
release notes).
I recommend updating and migrating your as
coercions over to satisfies
since
the latter makes sure that your type actually conforms.
Here is a little contrived example showing the difference:
type CorrectPassword = "%(&@#()%";
"123" as CorrectPassword; // OK
"123" satisfies CorrectPassword; // Type error
Another neat property of satisfies
is that it retains the specific refined
type, whereas
simply overwrites it.
type EventType = string;
const e1 = "success" as EventType;
const e2 = "success" satisfies EventType;
if (
e1 == "failure" || // OK, because e1 is of type string
e2 == "failure" // Type error, e2 is still refined as "success"
) {
}
Recently I have been pining again for type-level assertions, specifically to get a poor person's exhaustive check (this is how the rich do it).
import assert from "assert";
function validateValue(
fieldType: "string" | "number" | "boolean",
value: string,
) {
if (fieldType == "string") {
assert(value.length > 0);
} else if (fieldType == "number") {
assert(!isNaN(Number(value)));
} else {
fieldType satisfies never; // Type error
// fieldType has type "boolean" which does not satisfy never
}
}
Now if we add the missing else-if branch it type-checks:
[...]
} else if (fieldType == "boolean") {
assert(typeof JSON.parse(value) == "boolean");
} else {
fieldType satisfies never;
}
[...]
One might be tempted to remove the satisfies
-assert but don't. Just like we
keep runtime assertions (for branches types can't yet reach) in production
code to be notified about unexpected data, so we should keep type-level
assertions to be notified about unexpected effects of code changes.