My 2 cents: I think the fact that you're having trouble with the compose signature is something you should rectify and that would transfer well to other languages. I'm however not sure the same holds true for more advanced features of typescript.
I'm only a casual user, so take this with more than a grain of salt, but for me typescript occupies quite a weird point in statically typed language space: on the other hand, typescript's type system is enormously complex (and also quite expressive). Part, but I don't think all of this, comes from being retrofitted onto a untyped language and its ecosystem (so e.g. sum-types tend to be implicit rather than tagged, and in addition to discriminated unsions, there is support for complex sub-typing from the OO heritage). Most statically typed languages have no direct equivalent for many of typescript's more advanced features (e.g. partial types, although Scala and Ocaml have related constructs, in Ocaml's case e.g. polymorphic variants).
But on the other hand it's surprisingly awkward to get what I would consider one of the most basic and beneficial features of a sane statically typed language, namely exhaustiveness checks -- so most people don't even bother. In fact there is not even an agreed upon idiom (just google "exhaustiveness check typescript", all the answers will look spuriously different). The basic pattern is that you want a helper function like so:
function assertUnreachable(_value: never): never {
throw new Error("Statement should be unreachable");
}
and then for any switch(foo) you do an default: assertUnreachable(foo). I can't really fathom why there isn't a better way to express this (the ability is clearly there, but it's un-ergonomic). But if you want something that transfers well, I'd probably de-emphasize the fancy stuff typescript offers unless needed for acceptable JS interop and concentrate more on thinking about exhaustiveness and making undesirable states unrepresentable.