> Is the lack of such a feature in TypeScript (or any language) a deliberate design decision to avoid unnecessary complexity, or due to technical constraints such as performance considerations?
It makes a lot of things impossible. For example, if you defined two different types of ranges, OneToFifty and OneToHundred similarly to your PercentType above, the following code would be problematic:
let x: OneToFifty = <...>;
let y: OneToHundred = <...>;
y = x;
Any human programmer would say the third line makes sense because every OneToFifty number is also OneToHundred. But for a compiler, that's impossible to determine because JavaScript code is Turing-complete, and so it can't generally say that one is certainly a subset of the other.
In other words, any two custom-defined types like that would be unassignable from and to each other, making the language much less usable. Now add generics, co-/contravariance, type deduction, etc., and suddenly it becomes clear how much work adding a new type to the type system is; much more than just a boolean function.
That said, TypeScript has a lot of primitives, for example, template string types for five-digit zip codes:
type Digit = '0' | '1' | '2' | '3' | <...> | '9';
type FiveDigitZipCode = `${Digit}${Digit}${Digit}${Digit}${Digit}`;
(Actually, some of these are Turing-complete too, which means type-checking will sometimes fail, but those cases are rare enough for the TS team to deem the tradeoff worth.)
It's the fundamental programming language design conundrum: Every programming language feature looks easy in isolation, but once you start composing it with everything else, they get hard. And hardly anything composes as complexly as programming languages.
There's sort of a meme where you should never ask why someone doesn't "just" do something, and of all the people you shouldn't ask that of, programming language designers are way, way up there. Every feature interacts not just with itself, not just with every other feature in the language, but also in every other possible combination of those features at arbitrary levels of complexity, and you can be assured that someone, somewhere out there is using that exact combination, either deliberately for some purpose, or without even realizing it.
It makes a lot of things impossible. For example, if you defined two different types of ranges, OneToFifty and OneToHundred similarly to your PercentType above, the following code would be problematic:
Any human programmer would say the third line makes sense because every OneToFifty number is also OneToHundred. But for a compiler, that's impossible to determine because JavaScript code is Turing-complete, and so it can't generally say that one is certainly a subset of the other.In other words, any two custom-defined types like that would be unassignable from and to each other, making the language much less usable. Now add generics, co-/contravariance, type deduction, etc., and suddenly it becomes clear how much work adding a new type to the type system is; much more than just a boolean function.
That said, TypeScript has a lot of primitives, for example, template string types for five-digit zip codes:
(Actually, some of these are Turing-complete too, which means type-checking will sometimes fail, but those cases are rare enough for the TS team to deem the tradeoff worth.)