You can do a similar thing with discriminated unions in TS, with some caveats mainly around ergonomics. You can even get exhaustiveness checking with a little trickery
Edit, example of exhaustiveness checking:
switch(obj.kind) {
case "one": return ...
case "two": return ...
case "three": return ...
default:
// @ts-expect-error
throw Error("Didn't cover " + obj.kind)
}
This will actually err at compile time if you missed a case, because otherwise obj.kind will have type "never", which will cause a type error on that line, which will be expected due to the directive comment. If obj.kind is not "never", the code will not have an error, and so the directive will cause an error.
...it is definitely preferable having language-level support for this stuff though.
Edit, example of exhaustiveness checking:
This will actually err at compile time if you missed a case, because otherwise obj.kind will have type "never", which will cause a type error on that line, which will be expected due to the directive comment. If obj.kind is not "never", the code will not have an error, and so the directive will cause an error....it is definitely preferable having language-level support for this stuff though.