Comment by simonreiff

3 hours ago

I always felt a little duped whenever I tried coding in TypeScript. You get zero runtime type safety guarantees, plus it's often harder to tell in TypeScript whether the transpilation will result in an efficient and performant implementation. Maybe the worst thing is that if you have two objects, one called EmailAddress and one called UnrelatedThing, but both have a UUID as the first thing and a string as the second thing, and now you create an object at runtime that is called TotallyUnrelatedThing that has a UUID followed by a string, the runtime sees EmailAddress, UnrelatedThing, and TotallyUnrelatedThing as being structurally identical and in fact they are all "compatible" under TS at runtime, which is usually the exact opposite of what one would expect. Now in other languages you can get some additional guarantees like in C# at the cost of more ceremony and boilerplate to establish all your abstract primitives and layers.

My own approach is mostly to prefer JS and JSON objects with helper chains including validators and constructor/builder and parser utilities. Get the age from the user and get the domain from the email address and don't be surprised by the type, because everything is an object, and don't be surprised that you need to validate and parse, but expect to do so always. Do it in as modular and reusable a pattern as makes sense, which often isn't exactly the same for every scenario, but that's OK. Speaking of which, am I the only one who thinks it's usually more of a hassle than it's worth to define a universal EmailAddress for all times and places? Often the conflict happens because even if I try to do so, I usually am using one vendor as an IdP and a different vendor for transactional emails (even if I use the same cloud provider say for both). They each probably have different robust regex implementations to check whether something is truly an email address. I then still need authEmailAddrees and billingEmailAddress objects to pass to each, respectively, but there is no enforcement or requirement to instantiate an interface that contains an enforceable contract in TypeScript, so remind me why I am bothering to say these things are both email addresses? It just always feels like the worst of all worlds when I work in TypeScript, kind of a "rules for thee but not for me" situation. I have to follow typing, but TypeScript doesn't quite have to do so. In particular it always feels like I still have to enforce a lot more validation at the API layer than should be required, without any feeling that I can trust an EmailAddress to instantiate an IEmailAddress interface or that AuthEmailAddress and BillingEmailAddress inherit from the base EmailAddress or that those structures are guaranteed to persist at runtime and that a TotallyUnrelatedThing that just so happens to have a UUID plus a string but isn't strictly instantiating the email class will never accidentally end up populating an email field, which is kind of a concern. (By the way I think the hardcore email address validation really ought to be handled by the upstream provider anyway. I just do some minimal checks on length and presence of dots and at-symbols but don't bother trying to implement a full regex compendium of all email possibilities since these details frankly conflict frequently enough at the edges that I would rather let my IdP and email providers decide for themselves if they truly have an acceptable email input, and handle the failure loudly and up front, rather than try to do all the gatekeeping myself.)