I’ve spent way too much time trying to perfect this technique and the best solution (until TS has nominal types, which I think they’re looking at for 4.5) is branding, with a private declared class field that’s optional and always never. Eg
declare class _MySpecialPrimitive {
private mySpecialPrimitive?: never;
}
type MySpecialPrimitive = number & _ MySpecialPrimitive;
Combined with type guards (which you can conditionally execute at runtime), you can narrow overly broad structural types safely, eg:
const isMySpecialPrimitive(val: unknown): val is MySpecialPrimitive => /* anything that produces a boolean */
(And you can use assert guards in a similar way, but I find they make it easier to treat them as a noop)
Yeah, don’t do that unless you want to destroy every language facility that depends on reference equality checks. It boxes your primitive and breaks the world.
Libs provide something out of the box to do this, e.g. `ts-essentials` provides `Opaque`, so you just write `type DateString = Opaque<string, 'DateString'>`
I've build a library to do that (based on various known techniques) in [1]; it's also combined with validation based on zod. I would say it's usable but of course having true nominal type support in TypeScript would be way better.
For complex types it also disallows spreading to prevent circumventing validation. However, errors are often not has human readable as one would like them to be.
eyelidlessness|4 years ago
eyelidlessness|4 years ago
jestar_jokin|4 years ago
renke1|4 years ago
For complex types it also disallows spreading to prevent circumventing validation. However, errors are often not has human readable as one would like them to be.
[1]: https://github.com/renke/vo/tree/master/packages/vod