I had to read their problem statement like 4 times to understand what they are asking for. This is exactly why simple, concise writing is essential, and their wall of text is...not it.
> I love you. You do amazing work. You are gods among mortals. You have brought JavaScript from the darkness, and given it the warm light of strong typing. Look upon us, the cowering meek masses, and understand that we live in the muck and mire of a world where we are doomed to endlessly crawl on our bellies through the alleys of Github seeking the one true npm. We will forever wonder in the dark, until we have a type reflection model.
What even are they getting at?
As for their actual ask (runtime type safety), it is probably not going to happen because the TypeScript project has drawn the line at not being an alternate or additive runtime for JavaScript. Their job is simply to compile down to JS code and exit the picture. Whatever happens after that (in V8 or elsewhere) is your own business.
> TypeScript Needs to Emit Runtime Type Information
This is not possible at all because TypeScript is a compiler. They are really asking for a net new product which has very little to do with the TypeScript that exists today.
Nit: they're not asking for runtime type safety; they're asking for the ability to reflect on types (at compile time, to generate values) so that they can use type information at runtime.
This helps ensure runtime type safety because (for example) it would be great to have a generic "validation" function that takes an arbitrary interface and an arbitrary object and validates that object. One way to implement this would be to use /compile time/ reflection to generate code (TS code hypothetical, because I write C++ nowadays):
function validate<T>(obj: Any): T | null {
switch constexpr (T) {
case String:
return typeof obj == "string" ? obj : null;
case Array<U>
if (!Array.isArray(obj)) {
return null;
}
for (const u of obj) {
if (validate<U>(u) == null) {
return null;
}
}
return obj;
// ... more base cases
}
for (const prop: (keyof T) of Reflect<T>.Properties()) {
if (validate<T[prop]>(obj[prop]) == null) {
return null;
}
}
return obj;
}
interface Date {
year: String;
month: String;
day: String;
}
It would be great if this could generate /JavaScript/ code:
function validate__String__(obj) {
return typeof obj == "string" ? obj : null;
}
function validate__Array$Date$__(obj) {
if (!Array.isArray(obj)) {
return null;
}
for (const u of obj) {
if (validate__Date__(u) == null) {
return null;
}
}
return obj;
}
function validate__Date__(obj) {
for (const prop of ["year", "month", "day"])) {
if (validate__String__(obj[prop]) == null) {
return null;
}
}
return obj;
}
Unfortunately this is not possible (AFAIK) in TypeScript currently, and will not be possible with TypeScript's current philosophy.
(The above example is a hypothetical TypeScript compiler that might support "templated" generic functions; with just RTTI TypeScript could accomplish the same thing in a non-templated function by passing in `T` as a function parameter at runtime and doing runtime comparisons on `T`.)
Not to be flip, but if it were really all this easy, we would have done it already.
There are dozens of questions you can throw at this code: What if the input's a union? What if it's a nested union -- how do you avoid combinatorial explosion? What if the input is a function -- how do you validate its parameter types using runtime information? What if the input is a conditional type? What if you're inside a generic function? The list is enormous and it quickly gets into "you've dug too deep and unleashed a Balrog" territory once you get beyond the primitives.
Why don't you just make the transformer API stable, public, and let the community do the hard part? There's plenty of us that have experimental transformers doing all sorts of fun things, these are problems that can be solved external to TS.
I've got a fully functional compile-time dependency injection container that I've been sitting on for literal years because the transformer API isn't public.
^^^ Please do this. I'm completely ok with "using transformers voids your nonexistent warranty" and the community can deal with transformer API churn. Exposing the API makes it easier to adopt those community solutions, versus me needing to explain to teammates why I switched all the `tsc` invocations to `ttsc` and promise it's not that sketchy.
(So as it turns out having thought about it a bit, I am vehemently against this idea.)
Type reflection at runtime would require polluting the JS environment with a lot of cruft.
That might be a global object and lots of helper functions to query types. It might also be tagging objects and fields with additional properties that need to be treated as reserved.
There is certainly no way to do this that doesn't make assumptions about the runtime environment in a way that will cause a mountain of issues further down the line.
The other reason for my disdain is: the need to infer types at runtime is almost certainly indicative of an architectural issue with your code. If you aren't able to write viable code in a given context without requiring runtime type querying then you should step back from your intent and re-evaluate your code structure.
I mostly agree, having every single type checked all the time like other static languages is not appropriate and probably not even a good idea but on the other hand it is also kind of annoying to have your validation code be divorced from your types totally.
For example suppose I have a web request coming in and I have essentially just a string of input coming in on the body. I have an expectation of what it needs to be but there is a step where it is of type `unknown` and I need to convert it into `IMyHandlerInput`. Just casting it is obviously a bad idea and so now I need to essentially use some kind of library such as ajv to do json schema or jtd validation, _then_ I can cast it into my interface.
This is all fine but it definitely feels redundant to me. It would be a cool _ecmascript_ feature to essentially support, not runtime types per-se but syntactic _validation_ which can also be used by typescript to derive types automatically.
This is totally hypothetical but I'm imagining something like this for example:
```ts
schema Example {
@min 0
id: number
@pattern /^\w+ \w+$/
name: string
@future
expiresAt: Date
}
const data: Example = new Example(JSON.parse(body))
```
Something that the JS runtime can use to allow developers to opt-in to extensible validation which can also be used by the typescript interpreter to essentially derive types.
Maybe I'm very mistaken, but to me it seems this code snippet is basically an alternative to writing a ton of "infer"-s and overloads, no? So the same pattern matching could be used in the "switch". Whatever the complier knows can be locally matched, and the combinations have to be already handled by the developer.
It's hard to come up with a compiler that produces a sound checker for arbitrarily complex union/intersection types. Perhaps there could be a restriction on reflection to "simple enough" types, but that's always going to be a weirdly moving target based on heuristics. There's already cases where Typescript tries to generate ~40MB+ .d.ts files which are just re-stating the types themselves. So it's easy to imagine a validator compiler emitting 100MB+ of code to check more wild and crazy types.
Size, computation and other budgets seem like useful knobs to expose to developers. And anything that's locally decidable (so can be run on multiple cores easily).
Here's the thing though: if you wrote your TS properly, you don't need this and asking for it just highlights that you're not using TS the way it's meant to be used.
The only place you need runtime type enforcement (when you're writing your own code in TS) is for validating third party data at the point where you're ingesting it into your own code. Once it's in there, it is type safe if you used TS to compile your code to JS, because your function calls and data copies and everything else that moves data around was defined in terms of compatible shapes.
And we already have runtime validation libraries to cover that validation step. So many of them.
> And we already have runtime validation libraries to cover that validation step.
Right, but now my type information for inbound data must be in two places: the typescript type, and the validation schema. And heaven forbid I make a mistake and those two become out of sync.
Yes, I can use something like zod to define a schema then infer the type from it, but those inferred types are often … suboptimal to work with.
It is also the most frequent thing where junior developers get stuck. TS tooling doesn't make it very easy to work with large extracted types though it is head and shoulders above other mainstream languages. The error messages often become incomprehensible walls of text once your types are Complex enough, and then tsserver will just truncate the type info in pop overs making them useless.
I am personally OK to live with all of the above. My single issue with zod is that it is not easy to use if you don't own your types. If your types are coming from a third party lib you don't have an easy path to derive runtypes from them. If the concept of runtypes was supported by the compiler itself, this could have been possible but as a userland library zod can't handle this easily.
Why would a ts-native offering produce better error messages when it comes to complex types? That is an issue with TS in general (or rather, with complex types in any typesafe language).
The error message is complex because it (effectively) says that coords.lat was expected to be a string from IUserLocation["coords"]["lat"] but it was number
now any errors reported against this interface will be more comprehensible because they will (effectively) say that coords I am providing does not comply with ICoords.
This is a contrived example, but this becomes more complex when you have deep compositions of zod types.
This issue of typescript not using well named intermediate types when types are "extracted" from schema definitions like zod is what makes the error messages complex.
This would not apply if typescript was to provide runtime type checking for interfaces defined in typescript (as opposed to them being inferred) because ts will have a way to "know" about these intermediate types. Then the type errors would not have to be always reported against the complex top level type.
I just realized that I am working around this problem by explicitly setting a `z.Schema<...>` type: [link redacted]. The reason was to retain JSDoc comments on the types, but I guess this was another positive side effect.
In any case, I agree with you that there is room for improvement.
Hah, I wasn't aware! I think that must have been fixed after I tried it the first time because I definitely concluded that it was necessary back then. And thank you for the kind words!
I found Zod a lot easier to use once we were given the "satisfies" keyword. I still basically have to write my schema twice (I don't mind this) but I can ensure the two are tightly coupled. A change to either one will show a compile error in the right place:
interface Person {
firstName: string;
lastName: string;
}
Interesting! I'll probably start doing that. I hadn't connected the satisfies keyword to this problem, but it seems like a good solution.
My complaint about writing the schemas twice isn't the busywork (which is, like, annoying, but whatever I'll live). But the concern about small discrepancies between the two causing problems later.
I like a lot of things about zod (it's what I use when I need to do this kind of validation), but when I was working with a mildly complicated schema the type that z.infer<Schema> produced wasn't great for me.
When I produce a type definition for a nested schema, I'll produce types for individual pieces.
I couldn't find a clean way to decompose the type that z.infer<Schema> produced. It would give me a single type object that was deeply structured. For a pretty flat-simple small schema, z.infer<> was totally fine.
For the more complicated schema it wasn't _terrible_, I still used it. I made it work, but it definitely wasn't the dev experience I was hoping for.
I think something that went the other way would be much preferable for me. I'd rather define the typescript type, then have some validate<Schema>() function that was available to me.
Basically, I think it's _easier_ to get a generated validate() function to play nicely with the rest of my code, than it is to get the inferred type to play nicely with the rest of my code.
Except for the fact that usually there is not just one single typescript agent working within itself, for which you don't need validation.
There are many cases in which you need to verify some object, anything that does not come from your code i would argue is untrusted, I come across this almost daily, it would be absolutely fantastic to just have a way to check does this object conform to this type? Instead, i need to use some external dependency, effectively duplicate my type definitions and add yet another place to introduce bugs.
Just use a run-type transformer like typia, hooked right into typescript-compile. Get your runtime type validators generated from nothing but the typescript definitions.
Alternatively, use a runtime validator the provides good type inference out of the box so you're still only declaring your types once.
I like typia for doing codegen basically in the way this request is asking: it hooks into the typescript API to create runtypes from plain old typescript
And presumably you need to also trust your third party libraries, unless you're also compiling them from TypeScript. Right?
And even if you are--that might also involve ensuring their tsconfig.json is compatibly similar to yours. Otherwise the compiler might allow them to return null instead of the object they say they return, among potentially many other "gotchas" that are bound to appear.
EDIT: Though I think I do agree with you, ultimately. Runtime type checking imposes non-negligible costs and complexity that still theoretically should be able to be guaranteed at compile time for cases where one isn't validating untrusted user input.
Yes, you need to trust your third party libraries, better check them. Otherwise they may steal your data, inject XSS, mine cryptos, have memory leaks or faulty logic.
> And we already have runtime validation libraries to cover that validation step. So many of them.
Did you read the article? I think that's the point. The premise is Typescript should be responsible for solution to runtime validation against Typescript not third party hacks.
It would be nice if the runtime validation was an ecmascript feature designed in such a way that typescript could infer the types for them automatically... Such as
schema Example { ... }
cosnt example: Example = new Example(unkonwnData)
Where a schema applied runtime validation based on declarative syntax and which typescript could use to derive a `type` from
That boundary validation is where that kind of reflection would be useful! And it's a problem that (nearly) every useful application has to face.
Well that, and getting rid of the silly "must be able to strip types without processing them" design ideology would also enable stuff like typeclasses.
In a language with a sound (or even sound modulo escape hatches) type system, I would agree. But Typescript can and will lie to you at compile time, and it's much harder to guard against this than it should be.
I still don't see the point. There is a long list of libraries that do exactly this, and do it well enough. The author has linked to them himself. The overall benefit of this would be maybe slightly better syntax for these libraries (even that is doubtful, because plenty of them already have a `reflect<T>()` interface), but still zero runtime benefit. And getting it to be accurate for 100% of cases would entail exactly what I mentioned – writing an entire runtime to do this inference.
What's this "awkward inner platform language"? They are asking for a `typescript.generateRuntimeType<T>()` function to be native to the language. Well plenty of libraries provide exactly this syntax today. Here's tst-reflect: `const type = getType<T>()`. Notice any difference?
Wow, i didn't know that this is already possible with type script. seems like there are transformers you can add to typescript that can implement it.
So type script more or less supports the feature they asked for. Just not bundled with the main package, but they provide the interface to get it done with 3rd party transformers.
If that's so much needed I wonder why there isn't a thriving ecosystem of pluggable typescript preprocessors that add whatever values based on types direcly to typescript source before compilation.
It would be great if McDonalds would serve first class sushi, and pizza, and seafood, and pasta, and steak, and if they would deliver frozen meals, and iron your laundry, and ...
They just serve burgers and fries. And they are doing very well with that strategy. Same goes for TypeScript.
Counter to this post, as soon as I read the title I knew what this was, & I knew it was speaking exactly to something we've wanted for a long time. This is asking for more official & better supported https://github.com/rbuckton/reflect-metadata .
TypeScript is a compiler. It has a lot of type information during compilation. We could write that type information out into a file. Instead what we do is throw that information out when the compile ends. Taking all that typing information & throwing it away at the end of compile time is a bad dumb & silly limitation. Especially for a language like JavaScript, which historically could be semi-proud it had such a strong Everything Is An Object philosophy running through it (such as the malleable prototype-based inheritance system); so much type information should be on that Class object. Reflect-metadata for example defined new methods on Reflect to store this metadata.
I could not be more delighted to see the pennon of this website go up. We needed a rallying point for this. We needed a rallying point for keeping class data around. A rallying point for enriching the runtime with good actionable data is a good rallying point.
It's not what's afoot here, but I think you're a bit off-base about the impossibility of adding even some type-safety. We might not be able to get exact TS type safety. But we can definitely build some safety in. Owing to the malleable prototype-based type system in JS, we can add getters/setters to objects to do a lot of type checking. This doesn't even begin to explore the possibility of what we might do with es2015's proxies, which could allow even more interesting checks to be layered in. I also wish JS had an official AST (and renderer), so had more official options for code-rewriting that might let us weave in type checks.
What we can do as programmers is limited by what we have at our disposal. Not throwing out all the typing information, keeping it around at runtime, opens a lot of interesting doors.
Why not keep the package separate? I also thought of reflect-metadata separately, and it doesn't hurt to allow users to install plugins to augment core compiler behavior.
Some people have very tight constraints for payload size, and types could blow up payloads
It's hard for a plugin to always be begging for sufficient hooks & access to read out the data. Maybe an external project is fine, but it needs some real TLC, not just being a side-quest by a maintainer or two.
Conceptually it feels like there should be/needs to be some buy in on high, a shared vision that tsc is not the one, only & singular tool in the typescript-verse that ever can or will care about types. Trying to constantly break in & exfiltrate the data isn't ever going to get a position where the world takes this seriously; typescript needs really should gladly be opening the gates.
As for whether the type information is in fact part of the payload, that's a separate question, and one that should ideally be configurable. It's definitely a bit of a complex situation in general; ideally we'd have good/easy ways for libraries to include this information but then we also want want to be able to strip it out easily.
Maybe we just regenerate it as needed from ts source of libraries when we need it. But that implies not running a single typescript compile with one set of settings but running many compiles, as each library has its own tsconfig it'll need that informs how files are laid out & other sundry details. I might be overcomplicating. Perhaps we could just generate a foo.reflect.js, that has all the reflection data? There's options.
> This is not possible at all because TypeScript is a compiler. They are really asking for a net new product which has very little to do with the TypeScript that exists today.
That's not correct. All you would really need to do is output type information as JS objects and then support reflection libraries that looked up information at runtime. Two examples where this already happens:
1. TS enums are output as JS objects, as opposed to, for example, string literal union types. That is, "enum Foo { Bar = 'BAR', Baz = 'BAZ }" outputs information that can be queried at runtime, while "type Foo = 'BAR' | 'BAZ'" does not.
TypeScript enums only exist at all because they were included early on before the project really narrowed on its current goal of being "1-to-1 current JS, but with types". If TypeScript were started from scratch right now with the current philosophy, enums would never exist in the first place.
1. I was responding to the point that seemed to be arguing that just because TS is a compiler (to JS), that it couldn't support runtime type info. That's incorrect, and different from the current philosophy of "TS should really only use type erasure when outputting JS code".
2. After many years I've come to the conclusion that there is huge benefit for an ecosystem to either (a) have a "batteries included" mindset, or (b) have a way to "semi-officially" designate associate libraries as being supported. I think Java really excelled here. For example, the Java Collections library was/is excellent, and for a long time (not sure if it still is, I've been out of the Java ecosystem for some time now) Apache Commons were the go to place for libraries everyone used. Contrast that with the Node/JS ecosystem, where basically "whatever gets the most popular in NPM" becomes a semi-standard, but there are still often 5 competing libraries, and until the module owner decides to delete a widely used library or just stop releasing updates - besides leftpad infamy, there was also an issue where lodash basically went unreleased for a long time despite needs for critical security patches.
When it comes to TS, I understand the guiding principle of "we only implement type erasure", but I wish there at least a way to e.g. set a tsconfig flag if you wanted to allow runtime data, or to have the equivalent of Apache Commons for TS.
> This is exactly why simple, concise writing is essential, and their wall of text is...not it.
I only skimmed this but felt it was clear. They're asking for TypeScript, as part of the type erasure, to emit the type information it has discovered about the types in a side channel to the emitted JavaScript. Think of, say, PDB files as an analogy.
> They are really asking for a net new product which has very little to do with the TypeScript that exists today.
This is information TypeScript already has today but which it discards. It wouldn't take a "net new product".
You can use the TypeScript API to generate this information at whichever level of detail you want.
The level of detail TS has about types during the checking phase is much higher than you would want in practice for 99% of projects (e.g. 1 + 2 + 3 has 6 different types associated with it).
The level of detail TS has about types during the checking phase is potentially lower than you would want in practice for a lot of projects (which is critical since that makes the whole feature useless if that happens). For example, the list of properties of a particular generic instantiation is lazily computed, but it's possible your program never pulls on the list so it never exists in the first place, yet is something your type-based tool might want to know.
They're not asking for reflection on _all_ possible types (which would have the problem you mentioned), just ones explicitly requested at compile time via a function call.
There are plenty of projects that do the same for TypeScript (e.g. https://github.com/typescript-rtti/typescript-rtti) and plenty that support some kind of runtime reflection, and overall emitting TS type information at compile time in some readable format is a pretty trivial problem to solve. The complicated part is on the other side – what do you do with this information? How do you get JavaScript engines to understand it?
So then...exactly how it is done today? TypeScript provides an API to get type info at compile time. Libraries write plugins to consume this type info and use it for runtime and custom validation. What changes in this new world?
> So then...exactly how it is done today? TypeScript provides an API to get type info at compile time.
That's exactly it, it doesn't. That project you linked (typescript-rtti) mentions in the README that you must install and use it via ttypescript, a typescript wrapper that patches the compilation process to expose compilation details to plugins. Except that plugin API is unofficial and changes break the ecosystem built on it. For example, ttypescript doesn't work for TS 5 and the dev doesn't want to spend time on it. ts-patch has stepped in to provide a source transformer API for TS 5.
There's no way I can recommend libraries like typescript-rtti in commercial projects until something changes. I hope the ts-patch folks don't burn out.
The first 7 words of the document are "TypeScript Needs to Emit Runtime Type Information". Was that not clear? I guess I'm a bit confused by your confusion, unless you don't know what runtime type information is, in which case I suppose it's more understandable - but in that case I suppose you're not really the target audience.
I do think theres room in the world for typescript to be able to emit runtime code from its types, but I don’t think it should be in the business of writing validation libraries.
Closest they should get to this work is writing an interface to export types types into runtime code that another lib can pick up and write validators with.
The Typescript compiler could probably emit additional JS files with reflection info just as it currently emits .map and .d.ts files (basically a 'type database' that exposes the information that's already in the .d.ts file as JS module, and which can be queried at runtime by type- and property-names (e.g. stuff like "what was the original Typescript type for this Javascript property").
The only missing link is then that Javascript objects don't know their original Typescript type, that would need to be magically injected by the TS compiler as a custom property.
How useful that would be in practice, no idea... I can pretty much only see downsides (mainly increasing bloat).
Also the same thing can probably be implemented in 'user space' by a tool which parses .d.ts files and code-generates such type-database modules from that information.
>Hundreds of Github comments have been rehashed on this issue over and over again. Import paths are not modified during compilation, and we're not going to modify them during compilation, and it isn't because we haven't thought about it before and just need to spend another hundred comments arguing about it.
>The general tenor of discussion here is not great and I don't think this is likely to lead to any further productive discussion, so I'm just going to lock this.
I'm talking about this thing, apologies if I haven't referred to it precisely enough haha. So is it still "no idea", or is it actually "let's pretend we don't hear him, maybe he'll go away" (because you're understandably fed up with answering the same question many times)?
---
Listen, how about I just tell you why I'm asking you all those weird little questions. I still want to hear how you feel about being called a "god among mortals" for writing JavaScript for Microsoft, but I mean c'mon. You're a busy man, you just wondered "what's the ploy here" for a sec and decided it's safest not to bother answering them, right?
I understand that you, like any software engineer, like to work on impactful and meaningful things, and consequently take a measure of pride in your work; so could I, for a certain project, had I not allowed myself to be pressured into building it in TS - by people with no skin in the game, only the current majority consensus on their side ("all JS bad, but TS least bad").
Let's summarize the original post:
- Someone humiliates themselves, literally begging to be heard out.
- They bring forth a huge list of things that, beyond reasonable doubt, prove that their concern is valid.
- They are pointedly ignored, or rebuffed with some form of "I don't know what you are talking about" or "seems like a you problem".
Seeing this dynamic begin to play out in a feature request (out-of-left-field as the whole thing may be), well, that struck a fucking nerve, let me tell you. This has been my exact experience with TS (and no other programming language), when trying to address matters including, but not limited to:
- Whether to use TS at all (which I shouldn't have conceded to in the first place.)
- Whether TS can be adopted gradually (in my case it took multiple rewrites.)
- Whether I'm just imagining that I'm not gaining much by using TS (I'm not.)
- Whether I'm just imagining that I'm experiencing drawbacks from using TS (I did.)
- Whether TS is "just JS with types" (which is about as true as C++ being "just C with classes".)
- Whether the help implicitly offered, conditional on me using TS because "that's what everyone uses now", will ever materialize (which it didn't.)
- Whether I was using TS of my own free will (which is only true insofar as I rose to the challenge of accomodating others' supposed "discomfort" with good ol' JS, at my own expense.)
Every time I raised any of those questions, I was faced with gaslighting. And yes, in the end I did fall as low as begging my teammates for their help - after all, didn't I just rewrite working JS into nearly-working TS so that it would be more accessible to others?
Unrelated subsequent experiences taught me that the (former) coworkers in question might have been just as lost with TS, and even more lost with the JS ecosystem in general (whereas I feel mostly at home with it, coming from Python), but they were reluctant to take accountability and hence admit vulnerability. (Guess adding type annotations doesn't necessarily make code easier to comprehend or maintain, whether you come from a real dynamic language or a real static language, huh.) Well, whatever, that one's on them. And now it's on me to speak out against such insanity as enabled by your product.
When I see someone publicly putting themselves through the same situation (or "pretending" to - what's the difference when everyone is on so many layers of irony that it's not even funny anymore?), and it's not Clojure or Rust or Zig or Nim or Julia -- or JavaScript -- but fucking TypeScript once again; and it's not even their coworkers that they're addressing in this manner, ridiculous in its sheer desperation, but they're talking to the fucking upstream, then I'm not going to be a "good sport"; for me, this rapidly turns from "some stupid thing someone wrote on the Internet" into a matter of professional conscience.
Why did OP have to communicate in this acutely self-deprecatory manner? Is it perhaps because of a systemic issue in how the TS project handles feedback? Having personally experienced the exact same dynamic when discussing TypeScript, it seems such toxic communication has "trickled down" from upstream to us "cowering meek masses", i.e. the developers of Web-based software, i.e. the people who really should know better, because so much of what we build is intended to be directly consumed by other human beings.
For me, this is because TypeScript is not honest open source software. Say it for all to hear: am I wrong that the direction of TypeScript's development is determined by Microsoft's interests first, and the interests of the community a distant second? And is it not misleading and abusive in the slightest to have people learn "Microsoft-flavored JavaScript" instead of the real JavaScript that their browsers can execute, and pretend it's optional when practice shows it's anything but?
The stock phrase "incredibly privileged" makes me sick, but in this case you, Ryan, may truly not believe how privileged your position is in comparison to downstream developers around the entire globe. You're not the end-of-line code-monkey just trying to give the non-technicals some buttons they could click; you and your team are literally imposing your wills on a pre-existing community of fellow programmers, leveraging the unlikely synergy of Microsoft's marketing machine and the open source community's network effects.
On Monday, you will keep moving TypeScript onward; freely benefitting from the community's input on how to do what you're already doing, only better - more efficiently, more correctly. And just as freely ignoring the community's input on whether you're doing the right thing in the first place.
Meanwhile I'll still be recovering from the way your technology ended up impacting my life, no exaggeration, let's not even go there. If even 1% of TS users have been through a similar wringer as myself, that makes for how many people hobbled by your work? Calculate, and consider.
I understand the desire here. Runtime type checking is often necessary for data validation, and we can see lots of libraries developed to help fill the gap here. But I think the fact that there are so many libraries with different design decisions is pretty indicative that this is not a solved problem with an obvious solution. We knew this going into the early design of TypeScript, and it's a principle that's held up very well.
What have been happy to find is that we've grown TypeScript to be powerful enough to communicate precisely what runtime type-checking libraries are actually doing, so that we can derive the types directly. The dual of this is that people have the tools they need to build up runtime type validation logic out of types by using our APIs. That feels like a reasonable level of flexibility.
I'm relatively new to programming and had a question about TypeScript's functionality. Is there any specific reason why TypeScript doesn't allow for the creation of custom and intricate data types? For example, I'm unable to define a number type within a specific range, or a string that adheres to a certain pattern (like a postal code).
I'm imagining a language where I could define a custom data type with a regular function. For instance, I could have a method that the compiler would use to verify the validity of what I input, as shown below:
function PercentType(value: number) {
if (value > 100 || value < 0) throw new Error();
return true;
}
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?
You could trivially define a `parsePostalCode` function that accepts a string and yields a PostalCode (or throws an error if it's the wrong format).
Ranges like percent are much trickier—TypeScript would need to compute the return type of `Percent + Percent` (0 <= T <= 200), `Percent / Percent` (indeterminate because of division by zero or near-zero values), and so on for all the main operators. In the best case scenario this computation is very expensive and complicates the compiler, but in the worst case there's no clear correct answer for all use cases (should we just return `number` for percent division or should we return `[0, Infinity]`?).
In most mainstream programming languages the solution to this problem is to define a class that enforces the invariants that you care about—Percent would be a class that defines only the operators you need (do you really need to divide Percent by Percent?) and that throws an exception if it's constructed with an invalid value.
This is a feature some (experimental) programming languages have - look into dependent types. The long-and-short of it is that it adds a lot of power, but comes at an ergonomic cost - the more your types say about your code, the more the type checker needs to be able to understand and reason about your code, and you start to run up against some fundamental limits of computation unless you start making trade-offs: giving up Turing-completeness, writing proofs for the type checker, stuff like that.
Another interesting point of reference are "refinement types", which allow you to specify things like ranges to "refine" a type; the various constraints are then run through a kind of automated reasoning system called an SMT solver to ensure they're all compatible with each other.
> 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.
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
? Acc[number]
: Enumerate<N, [...Acc, Acc['length']]>
type NumberRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>
type ZeroToOneHundred = NumberRange<0, 100>
One limitation is that this has to be bounded on both ends so constructing a type for something like GreaterThanZero is not possible.
Similarly for zip codes you could create a union of all possible zip codes like this:
type USZipCodes = '90210' | ...
Often with the idea you have in mind the solution is to implement an object where the constructor does a run time check of the requirements and if the checks pass instantiate the instance and otherwise throw a run time error.
In functional programming this is often handled with the Option which can be thought of as an array with exactly 0 or 1 elements always. 0 elements when a constraint is not met and 1 element when all constraints are met.
This [0] is a library I wrote for JS/TS that provides an implementation of Options. Many others exist and other languages like Rust and Scala support the Option data structure natively.
Maybe official preprocessor plugins for TypeScript compiler could help?
I understand that everybody who needs it can already put their own preprocessor that generates runtime objects from type information before the code is passed to tsc for compilation.
But the effort is inconsistent and distributed.
If TypeScript officially supported pluggable preprocessor and plugin ecosystem for it some good solutions might get discovered.
This is the curse of guest languages, after the initial adoption pain everyone wants idiomatic libraries and pretends the underlying platform doesn't exist.
Until they hit a roadblock caused by a leaky abstraction, that proves them otherwise.
Type script does a very good job not to hide the underlying platform. In it's essence it is just a development time linter and does not interfere with the JavaScript runtime at all (except enums).
And I think that's actually the reason why it won the competition against Googles Dart. They even used Microsofts TypeScript for Angular instead of their own language Dart.
There is a good reason for not doing this. Typescript would become some kind of runtime on top of JavaScript. A new language that compiles to JavaScript. Currently TS is only JavaScript with type annotations.
There are many languages that compile to JavaScript. Pick one of them and use it!
And I have the feeling, that people who want runtime typed Typescript would rather like to write Java/OOP style code instead of JavaScript. But JavaScript is a dynamically typed language, and that's also nice. Be happy with what you have!
Where `generateTypeInfo!` is a macro that expands to a JS object encoding the “Foo” type (e.g. if `Foo` a record type, `fooType` will be a record with its encoded field types). It still compiles to readable JavaScript.
What this macro does break is that TS = JavaScript with type annotations, and all you need to do to compile is remove those annotations (excluding enums, but they are all-but-deprecated and obsoleted by string unions) without even type-checking. Since now you also need to expand the `generateTypeInfo!`, which requires actually computing the structural type of `Foo` (and if you want any sort of nominal type metadata, that too).
And that’s still a problem because type-checking is slow, but removing the annotations is fast. If there’s a limited way to resolve types for these annotations which restricts the resolution scope, that would be a good candidate.
—-
There's also more issues with TypeScript having a separate runtime besides it not being JavaScript. TypeScript types are structural, so they currently are available at runtime: to determine an object’s type, inspect its structure. Anything more and you quickly run into non-trivial cases being literally impossible: if you want erased nominal information like whether a string is part of a string union, TypeScript’s type system is Turing complete; and if you want the nominal type name, implicit structural conversions mean that once a value leaves its annotated cast or definition it’s effectively undefined.
That's an option, and maybe even the ideal one: an extension which takes a TypeScript+macros file and converts it into TypeScript before feeding to the TypeScript compiler.
However, such a preprocessor will basically need to re-implement TypeScript's type checking. So either TypeScript must expose it via an API, or the preprocessor needs to itself implement a subset (which as mentioned, could also be faster)
I'm not sure what `generateTypeInfo!<Foo>();` is supposed to be, but as far as I'm aware there is no way to execute anything in a JS engine from a type generic.
I'm misunderstanding your reasoning here. TS is already a new (superset) language which compiles to JS. Runtime types solve the problem of maintaining two duplicate, separate type systems -- one for compilation, one for validating data. I'm not seeing how that's related to OOP. For instance, my preferred workaround lib for this problem, `io-ts`, leans hard on functional programming.
Yeah but just because types exist at compile time doesn't mean they exist at run time.
For instance with Generics in Java a List<String> is the same as a List<Integer> at run time, the compiler can enforce rules that let you add a String to one but not add a String to the other but the runtime has no idea. This has various negative consequences but it also let them retrofit generic collections on top of the old collection implementation in Java unlike the disaster in .NET where both had to coexist for years.
Similarly C compiles to machine code and in machine code there are just memory locations and registers, types are implicit in how you use those things but not spelled out explicitly. C++ does have RTTI but is one of the many open pits, like Exceptions, in C++.
Typescript types are the same way, once the compiler has done its type checking you know the code is going to behave according to the type system even if the types have been "erased".
Depends on the situation. If you just make an ArrayList<X> (for some specific class X) you can call getClass() on that object and see it is an ArrayList but you cannot find out about the X.
If you make an ArrayListX that is not generic but extends ArrayList<X> it is possible to see the type parameter that was extended, the same is true for implementations.
There are other kinds of type unerasure that you can use w/o the language supporting it explictly, such as you could pass the type parameter X.class into the constructor and put it in a field so that you could call some method like getContainedClass() and get a copy of X.
Now the introspection/reflection API does have support for talking about parameterized generic types that doesn't mean that information always exists at runtime.
I worked on a crazy project where I did a lot of fighting with ordinary types and parameterized types and unerasing types by rewriting the names of methods to avoid conflicts, if you have methods like
but you can't have an overload that takes two differently parameterized expressions, this lets you write Java code in a lisp-like syntax that can be metaprogrammed on:
that project got me thinking a lot about the various ways types manifest in Java and I made the discovery that when you add Expression types you practically discover an extended type systems where there are many things that are implied by the existence of ordinary Java types.
I know how it works. The point of the topic was to have type information available, so we can use it for things like dynamic binding. No one asks for the runtime to be as strict as the type system (the compiler).
BTW. I do not agree with the opinion in the topic. Since Typescript is compiled to something else (usually JS), I'd prefer Typescript to have proper annotations (there are "decorators") and compiler hooks and just use code generation for concerns like (de)serialization.
Emitting types would run counter to several of TypeScript's Design Goals [0]. In particular, it would violate:
> 3. Impose no runtime overhead on emitted programs.
> 9. Use a consistent, fully erasable, structural type system.
It's also explicitly called out as a non-goal in that doc:
> 5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.
> 6. Provide additional runtime functionality or libraries. Instead, use TypeScript to describe existing libraries.
Emitting type information to some kind of separate common format wouldn't undermine most of these goals. There are already apparently dozens of tools that people can use for this task in a dozen different ways. Just have TS emit the data.
In this way there is no runtime overhead (point #3) -- it's pure data to be optionally consumed. They wouldn't have to prescribe any particular reflection module (point #6).
The only point this would violate is #5 but that is, of course, the point. We want them to reconsider because it's such a powerfully useful feature. And people are doing it on top of and outside of TypeScript where TypeScript would be best tool to give this information for TS users.
Today, you can already write a program using the TypeScript API to inspect all of this information to accomplish whichever scenario is at hand. The existence of all these tools, each with different opinions on design direction and implementation trade-offs, demonstrate that this is possible. Yet it's not demonstrated how those tools would benefit from reading from a data file as opposed to using the TypeScript API.
Something like api-extractor has different constraints from io-ts which has different constraints from typescript-schema. The API exists today and is apparently sufficient to meet all these tools' needs, yet what's proposed is a data file that can encapsulate any possible operation you might perform with that API.
Having TypeScript try to imagine, implement, and maintain a data file format that can satisfy _all_ of these tools' use cases, plus any future tool's use case, is a tremendous effort with no clear upside over using the existing API.
It's easy to gain support for the idea of "put the types in a file" because anyone reading that can imagine a straightforward implementation that just achieves their particular goals, but everyone's goals are different and the amount of overlap is not actually all that high when you look at the wide breadth of tools in the list. There's a very different amount of information you'd need to enable a type-aware linter (which is basically type info on every expression in the program!), as compared to a simple documentation generator (which might only need top-level declaration info).
The author explicitly calls out the existence of all of these tools as being the problem. They seem to want a canonical version that TypeScript itself officially supports.
If typescript itself defines a format, it's hard to imagine that the majority of people won't switch to it.
Though while there's a lot of type-data tools, are there actually a lot of type-data formats out there right now? I've been using zod, and it doesn't have one. You have to use code to tell it about a type. Someone did make a library to let zod load types from pure data, but the format used there is... typescript source code.
It would violate #9 no matter what implementation you use, and #9 is absolutely essential to tools like ESBuild, Deno, and Bun, which all rely on the assumption that they can nearly instantly strip out the type annotations and execute the code as JS.
That's because Zod doesn't use type annotations as input. We're talking about wanting to use type annotations as input so that we don't have to make our code as ugly as Zod makes it.
This isn't just some of the design goals, type erasure is the fundamental guiding principle of TypeScript that allowed it to become successful. Throwing it away because it complicates certain programming patterns should not be done lightly.
In my opinion they are not stupid, they make a lot of sense. And many people seem to agree and use typescript. If you don’t agree, use something else. There are many alternatives, pick something that is „smart“ in your opinion.
Unfortunately i can't. Like most of the people here i work in a company, with other people and i can't chose $language that is less dumb for obvious reasons. Typescript IS the only practical solution for web development at scale. I'm stuck with the design philosophy of a guy that's not facing my problems and obviously doesn't care.
Small ot, if you like io-ts, @effect/schema is the spiritual successor from the same ecosystem.
The fp-ts, io-ts author Giulio Canti has decided that the best way to proceed was to merge the two communities of fp-ts and effect, even though it's his work that is mostly known with effect being much more niche.
It's essentially io-ts squared in its powers. The amount of things you get from schemas are endless, not just decoders and encoders but also apis, lenses, constructors, and many other things.
It's obviously heavily fp-leaning, albeit I would say effect systems such as effect, based on Scala's ZIO, are much more approachable than haskell/purescript-like languages.
> TS is already a new (superset) language which compiles to JS.
Yes, but also no. The point is that it doesn't really add more semantics over plain JS. You can erase all the type annotations (well, replace with "any") and it'll do the same thing if you ignore the type checking.
If there's any "transformation" from TS to different-looking JS[0], it's mostly just syntactic embellishment (think LISP macros and such), not semantic.
For any sufficiently large project, dynamically typed languages are not nice, they are a series of interweaved disasters consisting of constantly tripping over what you thought was your feet but is actually NaN and trying to fall forward rather than collapsing entirely.
In every workflow I've seen, TypeScript is already a language that gets compiled to JS. It might as well take full advantage.
Typescript is not really compiled to JS, only the type annotations are removed.
To make dynamically typed languages nice for bigger projects we have typescript for type annotations (it's does not make it a statically typed language!). It is there, it is used, it scales and it works. If your type script project "collapses entirely", it's probably not the languages fault.
TypeScript is not JavaScript with type annotations. One of the modes, may be. TypeScript supports emitting old JS constructions which look nothing like original typescript code. So it's more like type annotations + babel. Adding one more thing to this set. Extremely useful thing. I think it's a good idea. I miss it. It's crazy that I can't JSON.parse string into typed structure safely. Every other language can do that.
You can easily parse json into types using something like typebox. Not only does this work, it works much better than many other languages. Have fun trying to represent even a simple patch request, or discriminated union, in something like c#
Kotlin's type system works great for interop with a nominally typed lang like java but for interop with a dynamically typed lang like js and myriad of its libs not designed with type safety in mind, structural typing is far more convenient.
The reason we want typescript to have java-like features is because we work on teams which have decided, outside of our control, that we are going to write our backend in JS/TS simply because it's easier for the bootcamp grads to transition to backend since they already know JS on the frontend. If we wrote our backend in Java, it would require the bootcamp grads to learn a new language, which they're not prepared to do. Choosing typescript or javascript on the backend is not about choosing the right tool for the job (because they are objectively not the right tool for backend development). It is mostly about minimizing the cost of our labor sourcing
As such, RTTI would make typescript go from a compromise language (we use it because we are stuck in the JS ecosystem, not because it's a good tool) to a legitimately useful backend language (We use it because it has the right features for the use-case)
If I could choose what language we use on the backend, I would leave the JS ecosystem entirely and write everything in .Net. But it's difficult to find .Net developers since every bootcamp these days produces react developers
The solution is to pile features onto typescript that were never intended to be there, the same way we have done with Javascript, HTML, CSS, and HTTP. None of these things are pure, and all of them have been ravaged by competing interests and committees who were all making financially driven decisions when writing the standards.
It would be best to abandon javascript and typescript as backend languages. I don't mind them on the frontend because a typescript compiler is useful when the application you're writing is originating data and not receiving it. And javascript does DOM manipulation admirably. However, we live in a world where humans are more expensive than computers. If I had my way, we'd write C# backends and Angular/Typescript frontends, but that's not the world we live in. Everything has to be JS because most people employed as "software engineers" are not truly very good at their jobs
No, I'm sure you have your reasons, but deserializing and validating is much more painful in typescript than it is in .Net or Java so I'm not sure why you would choose that
Ok. And why is that important? So one could meticulously add typing to your JavaScript application, and then strip it all out with a regex in order to deploy? What real-life benefit does 'compile by erasure' offer?
That's essentially what all the fast transpilers do -- esbuild, babel etc. They don't typecheck the code at all, they just parse it and strip out the type annotations.
This means that you can run and test code that doesn't typecheck yet -- very useful during development. Also, production builds can run the typechecker in parallel with other build steps. That's how we optimized our build system at work.
But essentially, it's a design choice by the TypeScript team. They've decided that TypeScript shouldn't add runtime features on top of JavaScript, but instead should simply type JavaScript, and I think this has contributed to the success of TypeScript.
I think the point is that TypeScript isn't really "a new language". It's an existing language with an additional feature. The fact that TS maintains the promise that types can be stripped means that TS is upholding a contract that it will not stray from Javascript. It can't, for example, add new language features.
Another one: they conflate Type and (Locator) Instance in a unique way that just about nothing else in TS does. Those are two very different things with the same name with Typescript's (antiquated) enums.
There are too many ways to accidentally import/redeclare/rescope the Type of an enum so that TS "knows" the Type, but because that type (generally) has the same "name" as the most likely (Locator) Instance it assumes the same access applies leaving runtime errors behind when that Instance isn't actually imported/available. Typescript has no easy way to tell the difference between access to the Type isn't access to the (Locator) Instance (nor vice versa). Reasoning about those runtime errors or preventing them is additionally tough for people too because of the same "name" problem for two different things.
This is something that's painfully hard to avoid in cases where you are trying to encapsulate an API's Types separate from its imports/exports because they might be introduced or manipulated at runtime (plugins, sandboxes, proxies, etc). Unfortunately, this is also too easy to accidentally do even when you aren't intentionally doing something complicated like that (trying to generate automated .d.ts files in a bundling toolchain, for example, when APIs are in the boundary space between unintentional public API and internal tree-shaking or optimized symbol renaming).
Let's turn it around, union types are so much easier to use and so much more powerful. Enums have only a small subset of the features, are not compatible to JavaScript code and are hard to understand (read the docs about type script enums and you will see).
To be clear, this kind of structure is only emitted for numeric enums. String enums with explicitly declared static values are roughly equivalent to the equivalent Record<string, string> (runtime) and a corresponding type T[keyof T] (type check time).
IME, most of the complaints about enums apply only to numeric ones.
The major exception to that AFAIK is the fact that enum members of any type are treated as nominally typed (as in A.Foo is not assignable to B.Foo even if they resolve to the same static value). I am among the minority who consider this a good thing, but I recognize that it violates expectations and so I understand why my position isn’t widely shared.
Dynamically typed means I can look up the type of an object at runtime. JavaScript lets us do this with classes/prototypes. Since TypeScript adds types and enums and interfaces, why can’t it let us look up those at runtime too? It would seem to fit with the way JS works.
Types can get complicated. To an extent that code gets extremely overcomplicated by using a typed language. Typed languages encourage to use many layers of DTOs and models.
With dynamic languages you can just code, without being held back by a OOP type system, but it can get very complicated to understand what the typed of you parameters and return values actually are.
"Dynamic Languages have fewer language limitations Less need for bookkeeping objects and classes Less need to get around class-restricted design. Study of the Design Patterns book: 16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern[...]"
Above all of what I write below, I think that this is a very valid issue for discussion and debate. I don't think there's one objective correct answer. So this isn't me dictating what TypeScript shalt be.
TypeScript is an entirely optional layer on top of JavaScript, with the exception of `Enum` that emits an object, many of which see as a mistake. You don't even have to "transform" TS code to get JS: you just have to delete the typings. The rest is just JS.
Unless you want to depart heavily from this principle, which I think is crucially important so long as there's still human-maintained JS code out there, what I think they're asking for is a library to take the types and write serializers/validators for. Which there are many of. So I think the real request is to make the one canonical choice out of a field of many choices, which I also agree with the spirit of. But is that really in scope for the TypeScript project?
What I personally don't want is runtime reflection for TypeScript. Or at least, not a core part of the language. Because I like that TypeScript is just for compilation and at runtime you just have JavaScript, without any sort of abstraction layer that turns JS into some sort of intermediate representation with overhead or more indirection for debugging. I cherish the fact that even without source mappings, my output JS is perfectly legible and debuggable.
> with the exception of `Enum` that emits an object, many of which see as a mistake
There are other exceptions, most of which are considered mistakes as well, the most obvious which come to mind being `module` and `namespace` as runtime constructs. But I’m fairly sure these have been primarily used by TypeScript itself for at least a few years, and even they recently migrated away.
Another one that comes to mind is, I believe, actually pretty popular: what they refer to as “parameter properties”, ie class members whose types are defined in a constructor’s parameters. These seem to have escaped controversy because they eliminate a lot of redundant boilerplate, and generally behave in obvious ways (at least as obvious as their redundant boilerplate JS equivalents).
Instead of pleading to the TypeScript gods, why not make a deal with the JavaScript deities? Let's get type-checking into JS!
If I want runtime types, I reach for type guards. If I need to do this a lot, then I reach for io-ts or zod and pretty much exclusively write types as validators/codecs/schemas or what-have-you.
I don't think TS - as a specification, type-checker, community, or otherwise - needs to concern itself with runtime validation UNLESS JS gets some agreed upon way to do it.
I know it’s not perfect, but I’m pretty content writing schemas and types as validators with zod and deriving my types from those. That you can derive the types easily and the validation then evolves with your typing is really nice.
I’ve worked with people who find it too complicated or they feel like it should be baked into the language, but I’d argue there are so many contentious design choices in these libraries that it might actually be best to be an implementation choice, selected based on a project and its specific needs.
Zod has been very good to me though. It’s quite easy to get up and running with, and its API has never gotten in my way when scaling things out.
My one complaint is that certain patterns I love, such as runtime validation with zod and deriving types from schemas, doesn’t necessarily always play well with something like pattern matching in ts-pattern. The type definitions behind these libraries are labyrinths, and cases where something seems like it should work won’t always behave as expected. On some level, I do wish these were seamless language level features, though I fully recognize these are selfish desires.
But imagine having runtime safety combined with exhaustive pattern matching. I’d be very happy to have that.
I vaguely remember one of typescripts developers stating that if they had to start it all over again, enums would not be added; as enums are the only thing emitting runtime code.
Typescript does not change runtime behaviour, there is no special typescript {#if}
> I vaguely remember one of typescripts developers stating that if they had to start it all over again, enums would not be added; as enums are the only thing emitting runtime code.
It's true. I don't remember if that was the only/main reason they said that, but that was definitely one of the things.
I find that a weird sentiment, though, because TypeScript has a couple of other features that are not just "JavaScript + type annotations".
Namespaces is one. It also has a syntactic sugar for defining class properties in the constructor arguments. And it had that experimental decorator feature for a long time, but I never used it, so I don't know much about it. It also has the "`this` parameter" syntax for methods/functions, which does disappear at compile time, but still looks and feels like more than just a JavaScript function with type annotations.
From my understanding, namespaces are on the same list with enums of things that the developers regret ever adding to the language but can't take out without breaking old code. Namespaces also at least have the excuse of being a "necessary" jQuery-era "Production pattern" in the land before ESM was standardized and somewhat relating to the similar syntax sugar of import/export when generating AMD, UMD, and CommonJS modules.
So too are "experimental decorators" another thing that some of the developers seem to list as massive regrets. That example is even so much worse than namespaces because it wasn't justified by existing patterns used in Production JS at the time and that they even believed that requiring a compile-time flag with the word "experimental" in it would stop developers from using that compile-time flag in anything destined for Production usage. (Seriously, we all should shame the many projects/companies that did that.)
> Type erasure is good! It means JavaScript project can consume TypeScript projects without any knowledge of TypeScript. It's just emitted JavaScript. This does not mean you can't emit the type information separately in a consumable lookup table that's separate from the code. The lack of this type information means we use esoteric libraries which ultimately pollute the JavaScript with all the convoluted typing working arounds... soo... type erasure has defeated the purpose of type erasure. It's a second order effect, where the design goal defeats the design goal. :(
I think the author misunderstands the design goals of TypeScript. The goal isn't to have pristine, beautiful JS code with no complexity, the goal is to have the runtime semantics of TypeScript be identical to the runtime semantics of JavaScript. Barring the regretted exception of enums, TypeScript can be converted to JavaScript by simply stripping type annotations. That's the design goal, and that goal is not in conflict with itself.
The ecosystem that has built up around TypeScript depends on complete type erasure, and much of it would evaporate if this wish were granted. TS support in ESBuild, Deno, and Bun would become nearly impossible, because each would need to re-implement all of tsc in their respective languages, while right now they just need to maintain their own simplified parsers.
On the other hand, the convoluted libraries that OP bemoans are perfectly compatible with all of these tools, because they're implemented in user space.
There are plenty of ways to emit runtime type information statically without a runtime. `keyof` could statically convert to the list of keys in a class. Hell, even typescript classes could do what ES6 classes do and initialize class keys to undefined. Currently, typescript classes erase all keys unless explicitly defined. Object.keys() on a newly instantiated typescript class without set members will return nothing, while an ES6 class will return all of the keys. Just an option in tsconfig.json to convert TS classes to ES6 classes (or just allow ES6 classes to exist at the same time in Typescript) would make some code generation WAY easier.
Or some kind of syntax to emit the name of a type as a string. Currently, if you run `typeof foo` it will return the type as a string. If you could have a static version of that, like `tstypeof foo`, or with a class:
class Foo {
bar: string;
};
console.log(tstypeof Foo::bar) // or something less C++-looking; compiles to "string".
then it would be killer. This could break compatibility if you change the type of a field and a compiled client using the code were to expect "string" when it's been changed to "number". But I'd rather live in that world than the one we live in now
Just some basic RTTI would make a lot of ugly codesmelly boilerplate Typescript code evaporate instantly.
Thanks for this. I thought I was screaming into a void. I started with typescript in 2018 and after two years started to believe this is just not really a solution because what it is. I came from Haskell/c#/f#, and TS, while it has a very powerful type system, just doesn’t give you much of the advantages the above languages give you outside (some of) dev. Even when it does, it is really limited in practice (aka when dealing with the real world) vs, let’s say, c#. You can defend from that somewhat, however, you have to always keep the extremely (and that’s intended, but not what it could be) leaky abstractions in mind to not run into bugs that shouldn’t be possible in something that claims static typing (if you don’t know that it drops to js without checks after compile).
The point of TypeScript was always to run on web, it wasn't meant to give you a language advantage over say, C#. You use C# if you have to develop for .NET, you use TypeScript if you have to develop for Web, you wouldn't use one or the other in another context.
That being said, TypeScript is differently capable from C#. C# is nominally typed, with decent reified generics. I like programming in C#, and miss its features when programming in TypeScript. TypeScript, on the other hand, is structurally typed, forgoes soundness, and comes with a powerful type system to express complex type relationships (again, possible because it forgoes soundness). I like programming in TypeScript also, and also miss its features when programming in C#. I doubt a language that combined C# and TypeScript would be very nice, these languages go in different directions to good effect, but they are not compatible directions.
But that’s when you focus on the type system theory and I agree with that. I don’t see how enforcing types after compilation has anything to do with a programming language for the web vs ‘not for the web’ though, but I understand it is the philosophy to check the types and then drop to plain JS. Best tool for the job, sure, but literally losing everything typed you wrote and designed after compilation is just not as useful as it could (should imho) be. You express whatever, it compiles, and then everything can break all that you carefully set up, without any errors.
That’s not the language or type system per se, it’s the compiler implementation or rather the philosophy to stay close to js and have a very thin (powerful) layer at compile time only. Typescript in wasm, Deno maybe (didn’t try yet) etc could change this. Then we can have types as in other languages. Not sure why this would not be a win for everyone vs the current status.
Also, as you know, the run the web went out the door with nodejs; many backends are now typescript and indeed competing (… for at least writing web apps) with c#.
TypeScripts goal of being just a layer over JavaScript led to its type system design being primarily productivity based: the goal was to make programming nicer, using types to get more performance wasn’t a goal or option.
C#, and the CLR underneath it, were performance based, and the types feed into that. The reason they both are as they are is because of the environments they are meant to be used in. So ya, are you running web apps on the CLR, or are you running them in the browser. I don’t really get nodejs, or, i guess it only seems like you would go that route if you wanted to reuse your web browser devs or web browser code to run also in the server.
Yes, and I am aware of the histories, we just want (and need) something more now. Typescript still seems well positioned for this. Not many reasons you cannot fix this with a compiler flag (which is default off).
You would have to change the entire type system to make it C# like. TypeScript makes radically different assumptions about static typing than C# does. It also would be difficult to come up with a run-time type representation that was even sensical (due to unsoundness), let alone efficient. It isn't just a compiler flag that you want, its an entirely new language.
I was perfectly satisfied with using https://zod.dev for some runtime data validation, and found it really cool that I didn't have to define some nominal type off to the side and could instead just say what I meant inline using a fluent API.
I’m mainly a backend programmer, so I know just a little typescript, but I don’t really understand what they’re asking for here. It sounds like they want to serialize and deserialize typescript types automatically? If so, that sounds like a good fit for a library, not something most languages offer. (Please educate me if I’m missing the point)
Javascript already has standard serialization and deserialization tools.
The problem is that the deserialization is untyped. It's just a Javascript object. Usually, the first thing you'll do is to cast it to a typed Typescript definition, but there's no way to guarantee that it actually fits that definition. No type information exists at runtime. It's used solely to check the code itself during compilation.
So any time you get data from outside of your program (over the network, from a database, out of a config file, etc.) you just have to hope that it actually fits the type. You can write code to check it... but you have to write that code yourself.
There are libraries you can use. For example, you can use a Data Description Language with a simpler type system, which usually suffices for the kind of data you want to serialize. Then you can use that to generate both a Typescript definition and a runtime type checker. But that's inelegant, and not standard, so every project is different despite it being something everybody needs.
It's basically the same for typed languages. If you want to map JSON to a typed data structure, you need a serialization library to do that.
JavaScript has a very simple built-in json reader/writer that does no type checking and returns some nested dictionaries or arrays. If you want more, you need a library (for example zod). Or you just trust the data to have the right shape.
Java has runtime type definitions, which you can use to automatically type check deserialized objects. With reflection you can write that in ordinary Java.
I imagine C# and other Java-esque languages have something similar. But Typescript deliberately avoids that, and it would be difficult to get it to do so.
There is currently no good way to automatically validate the object input to a typescript API. In the way that you can simply specify the class of a POST body to a spring boot controller or Asp.Net controller, every class in typescript requires you write the type information twice: once for the typescript compiler, and once again for the runtime deserializer, in the form of decorators. Having to write extra code to make up for a deficiency in your framework, runtime, or language is the definition of code smell. As such: all typescript backends which attempt to properly validate user input are guilty of egregious codesmell.
Additionally, because it is impossible to emit these types to the runtime, even in the form of a string naming the type, you cannot validate input to an API using generics. If you have, say, a class which will run a query for a record based on the keys in that record:
class Query<T>{
filter: Array<keyof T>
};
The typescript compiler can validate if a string in "filter" is, in fact, a key of the class T. However, you cannot write any decorator on this class to make the information of what keys exist on T available at runtime. Therefore, to write such a query interface and validate it at runtime, every record which can be queried with this object must have its own specific "[Record]Query" class written with its own decorators to validate every possible input, usually with the valid keys hardcoded into the decorator.
Because there is no real relationship between the class and the runtime validators, this makes validation more error prone, because it relies on either human beings to consistently remember to add new fields to the relevant decorators, or a custom linting tool to be written which basically pre-processes the typescript source using the typescript API. This is, again, writing code to account for the lack of features in the framework.
WHY do we put up with this crap? We have had Java and C# for a long time, and they solved these problems a long time ago. Why are we writing Typescript on the backend?
The simple answer; it is easier to find javascript developers than it is to find java developers. Bootcamps don't teach Java or C# because there's more react jobs out there. Bootcamp grads are not generally coding enthusiasts and so re-tooling is difficult for them and they don't generally do it for fun. So, we decided to write our backend in Javascript (or Typescript) as a labor decision, not a technology decision.
My experience is the opposite of yours. With typescript I use an openapi spec to validate and generate types. For other entry points I use typebox to create validators and generate types. It requires a bit of work, but it adds a lot of capability even beyond basic deserialization.
With c# I've seen openapi interface generators that don't validate properly, only basic deserialization. I've seen dto's that are deserialized wrong due to lacking null checking attributes. I've seen the way put requests are misused due to how difficult it is to separate null and undefined in patch requests. I've seen dto's with all nullables due to the lack of union types. Maybe I've yet to see a good c# codebase, but I certainly prefer typescript over the above.
My preference is to have my types defined in code first, rather than in markup language files which generate the code. Having to rely on 3rd party code generation tools because your language lacks a feature is, as I see it, code smell. I'm not sure how to handle the scenario of undefined fields in a PATCH, but I also don't tend to write APIs that way
Very interesting post to me. There always was the argument that TS should be erasable and carry no runtime information, enums and decorators are a mistake, etc.
For what it's worth, I want my production bundle to be as small as possible. Most people use bundlers among TypeScript, which further complicates things.
I would love a standard library to e.g. generate type predicate functions from TS types.
Taming the beast from this end might even end up useful for things like compilers, no?
TS provides a nice mechanism to be strict in the frontend and validate types using runtime code, that is, type predicates.
But from a higher level, this has never been enough, and you want to map JSON to TS types using some kind of schema definition.
I would love to just be able to pass in a TypeScript type and generate its type predicate at compile time.
Unfortunately, this is impossible due to the halting problem.
So, a standard solution for the case of a plain serializable JS/JSON object and the according TS interface would be very much welcome.
Edit: and all of this is of course about data from external sources (requests, DOM, compiler...). For all of the code and data known at compile time, there is rarely a need to validate types, as they are statically analyzable.
So there is a huge scope that TypeScript has to cater to.
It includes JS projects where external type definitions often become obsolete and people who code in TS but do not enable strict mode for their own code (that is, disallow "any").
And all of these type defintions have to be interoperable.
For API requests, this is basically calling for a default generic REST client, and I agree that would be useful. The REST client could use type predicate functions extensively.
I dunno, this seems like it would be out of scope for something like TS proper (there's already been a ton of feature creep, anyway). Reflection is generally implemented via runtimes, which can be very clunky. Is it really worth doing this in JS? Tooling already seems too bulky as is.
No runtime necessary, if I understand the problem.
Input is bytes. Bytes will never carry their type information (and if they did, you couldn't trust them). So the type information needs to be used in the Deserialiser, not its input.
To get typed deserialisation of Foo, the compiler needs to generate a FooDeserialiser automatically for you. The compiler can't depend directly on Foo (Foo is written after the compiler), but if Foo could somehow emit its type information during compilation, then the compiler could depend on that.
The one downside of this proposal is faster typescript processors like esbuild. Right now they don't care about types, they just strip them from the code. It allows them to be very fast and provide fast feedback loop.
With this proposal those tools must become extremely more complicated and probably they will just be as slow as tsc.
Agree, zod, arktype, and typia are impressively easy to use! Though, I feel that this environment created a different problem of spreading developers into multiple solutions. So for lib devs like me, we end up having to choose between coupling with a single validation lib or managing support for multiple (like tRPC does). To address that, I ended up doing a reusable lightweight lib that wraps that logic for supporting multiple: https://typeschema.com
Thanks for the lib! I'm always charmed to come across such a concrete example of the fundamental theorem of software engineering: "We can solve any problem by introducing an extra level of indirection."
As listed, Typebox is excellent for this use case - it correctly implements the JSON schema spec and allows you to use it to write OpenAPI specifications. At my company we use this to write type-safe handlers & generate our API documentation!
I love C#. At work, when I write standalone tools, I always do them in C#. My boss tolerates this since I'm usually the only one using them. But I don't get to choose what our backend is written in
> Just kidding, but I'm not pushing for JavaScript anymore. If rust/WASM works as well, I say go with that.
This is the direction I'm going. I use rust a LOT these days, and after trying out typescript for a few projects over the course of a few months, I absolutely hate it when comparing it to rust or even any other compiled language. Something about it just rubs me the wrong way...maybe the fact that it has to be JS-compatible, so the type system seems really crude? I can't put my finger on it. It sort of hits this weird inbetween where js is quick to write/prototype and rust is extremely "correct" but the ts type system slows things down/annoys me without providing enough benefits. Also, working with it in nested projects via npm tends to be a huge pain in the ass. Maybe I need to switch to Deno or something so it's native?
Now that rust/wasm is ramping up, I'm considering using rust for the API layer and something like Leptos for the frontend. I've been getting into svelte a lot, and Leptos seems similar enough to not be a huge leap.
I'm curious what you think about Dart/Flutter if you've gotten a chance to dig into them. I am very impressed, but it trades simplicity for being cross platform. It has a nice set of types though. https://dart.dev/language/built-in-types
Good question! I prototyped some stuff on Dart/Flutter. What I took away from it is that a) I really liked Flutter b) I really do not like Dart. I really tried to like Dart (mainly because I was enthralled by the idea of Flutter) but it just didn't click for me. It seemed like the syntax was trying to be intuitive in some ways, logical in others, and that incongruity really made it hard for me to pick up. I think overall it felt inconsistent in confusing ways. I also was both simulaneously impressed and disgusted by the fact that it render everything in a canvas for desktop...I mean, it totally makes sense, and what a great way to get consistent rendering acros platforms, but I wonder what the tradeoffs are. Accessibility must suffer in some way, but maybe they figured all that out.
I wish Flutter was just a regular GUI library for another language (C maybe, so you can use it from everywhere, that'd be nice). Dart really killed it for me. Also, I know it's beating a dead horse, but Google doesn't have a good track record with long term support of their projects. If it was a grassroots open source project, I'd probably feel a lot more secure putting aside my Dart-inflicted OCD and really picking it up.
Given my current love affair with Rust, my main squeeze right now is Tauri. Once they hit mobile platforms I think it'll be unstoppable, and in the meantime whatever you can do in Tauri you could probably translate to Ionic.
If anything, the wide range of different approaches to run-time type information listed here is pretty good evidence that there are many different use cases and any approach built in to TS would not cover them all.
This is the perennial problem with language design. Everyone wants something and they even have reasonable grounds to ask for it.
I myself have come across moments where some rtti/reflection would have been useful. In those cases I was often able to use branded types. Having something like that built into the language might be nice. But I'd also support TypeScript and JavaScript developers making the choice not to support it ever. IME, working around rtti type code often forces the programmer to come up with a much better approach.
Fairly recently I built a C library for object serialization, based on run-time reflection. Run-time reflection is not necessary to the task and makes it perform worse than a code-generating solution.
The run-time reflection approach is good in one way: it doesn't add tooling to the project. All the modules that use serialization have to make a bunch of tedious API calls to build the type object which represents the C structure. This is created once and stashed in a global variable. Then it is used as a parameter when serializing and deserializing.
There is a performance impact because the marshaling code is interpreting the type object; it recursively walks the type object, and in parallel it walks the C object to extract or stuff material.
A generative approach would just write a serialization and deserialization stub. There would be no need for any type object to be walked at run-time. No need to construct such objects at startup and no need to carry the library for that.
But there would be some tool which itself has to be compiled (for the build machine, not the embedded targets), and then some Makefile steps to run that tool on some interface files. The thought of inflicting that one the project made me go yuck.
A third possibility is to have the run-time type info objects, and add JIT. Why do something easy, like a code generation tool called out of Makefile, when you can do something hard and nonportable?
For validation, ORMs, APIs, etc., once you move beyond the simplest cases, you need more information than is present in Typescript type information. And that's before we get to the app-specific concerns.
So you're going to end up with schemas, boiler-plate, code-generators, and/or glue code anyway. Not to mention the libraries to help manage this stuff.
It's not that it's completely unhelpful, but I think it's a partial solution, and not the tricky part either.
2¢, having non-runtime types makes code behavior easier to reason about, especially when aggressive type inference is present, and I'd hate to lose it
One of the things I don't love about Rust is how type inference affects program behavior in ways that can be really subtle. In Rust's case this is a necessary evil because it doesn't have a dynamic foundation, but TypeScript has done great without that compromise
TypeScript is syntactic expansion layer over JavaScript.
"C preprocessor, please give me arrays that know how big they are at run-time. Oh, and access to the calling function's local variables!"
"Common Lisp defmacro, give me continuations usable anywhere!"
A macro layer can bring you the pie in the sky, but only (1) at some nonozero cost and (2) with additional representations that are not understood by regular code that is not in that framework.
Suppose a TypeScript type object is attached to every run-time object created in TypeScript. Firstly, those objects become more bloated and expensive to construct. [Edit: not really: all objects of the same type can have a pointer to the same meta-data which is created once]. They will need run-time support in their execution environment in order to have those type representations. Objects not created by TypeScript-generated code will not have anything like that attached to them, so places in the system where the two domains interoperate won't be able to rely on reflection; it will need a fallback for objects that don't do reflection.
I think serialization can be done without reflection. Because at the time when the static language is being processed, you could annotate certain types as requiring serialization. Then for those types, TypeScript would generate the marshaling routines.
Generating marshaling stubs from an interface language has been done in the C world for decades. It doesn't require any run-time types. The generated marshaling stubs just know how to walk objects of the type that they handle, and that's it.
It looks like in TypeScript, serialization is just punted to JSON, which is inadequate because when we are deserializing, we want to check that the JSON blob has a type and shape compatible with the TypeScript-level type. (Or even for that that type to be inferred in some way.) There are some libraries that try to do something in this direction like ts-serializer.
I think it's something you really want in TypeScript itself.
Basically round up the half-dozen or so use cases for reflection, and provide for all of them somehow without actual reflection. It's an X/Y problem. People really want to do Y (e.g. serialization), and believe that they must first solve X (have metadata in objects for reflection).
While type hints in Python are not as nice as typescript annotations, I understand their plea, because once thing Python does better is runtime introspection.
Which means we can have dataclass, pydantic, typer and fastapi all generating their stuff from regular type hints. No need for special syntax or functions. And that's super nice.
A lo of people in this thread seem to be misunderstanding what is being asked for. There is no runtime reflection needed. What is really needed is -
1. A way to get a value that represents the type of any statically known type (i.e. known at compile time).
2. A way to compare two typereps for equality.
3. A way to convert between types if the typereps are equal.
This is basically the equivalent of the `Typeable` class in Haskell and PureScript (https://hackage.haskell.org/package/base/docs/Data-Typeable....). Both Haskell and PureScript erase types at runtime, so it is possible to do without adding a runtime to TypeScript as well.
The TypeScript language service which powers IDEs and which is maintained/shipped with TypeScript already has the knowledge of all the types in your application. It seems like it would be relatively straight-forward (though a lot of work!) to develop some sort of code gen library that uses the TypeScript types known to the language service to reflect on those types and emit some sort of runtime type validation functions as part of a build step. This could be done in an npm module similar to webpack or in an IDE plugin. If that functionality doesn't exist today given all the listed open source projects, I'm kind of surprised. I don't think the TypeScript team would need to do anything to allow such a project to be developed.
It has quite an awkward way of being distributed. If you google for standalone TypeScript language services you'll find unofficial ones. Deno's is much simpler. Maybe they should pioneer this.
Normally I'd say "hard pass." I'm pretty comfortable with static types being a model where the compiler throws the type information away at compile time, so I can type with abandon without worrying that I'm going to impact runtime performance in my generated code.
... TypeScript may be one exception to my rule of thumb. It's already compiling down to JavaScript, so you're already paying a performance tax in having your code run in an interpreted language. The question should be "how much more of a performance tax are you paying annotating things with type runtime metadata?"
Most of the entries there are below that number, which begs the question: is this really that much of a problem that it mandates the drastic changes required?
I actually don't know if it's even doable, considering TS's structural type system, where the answer to the question "what type is X?" is not straightforward.
What I really miss is a way to extract type information directly from the compiler. Ie to have a ‘macro’ that takes a type as an input and returns a readable text or a json structure describing that type
It still wouldn’t be runtime typing, the compiler would just insert a constant wherever you use such a macro so I don’t think it would conflict with typescript’s goals. But it would make it a lot easier to build extra checks when deserializing data - or just give richer debug information.
But it would probably be hard for eg esbuild to support such a feature if tsc adds it. So I couldn’t use it anyway :)
The problem with TypeScript isn't the lack of reflection. The problem is that at runtime its type system is still Javascript in all its glory. Workarounds are just band-aids on the underlying mess at best.
Reflection on a language that compiles to javascript? I don't get it. Why are people so allergic to macros?? Every stinking language that has compiled to JS has had a chance to do macros but they don't. Coming from many years spent in the lisp world, metaprogramming is this incredible feature that, in my experience, completely negates the need for runtime reflection while adding many other possibilities as well. I get it...the syntax is way harder when your language isn't basically a pre-parsed AST. But rust does it.
TypeScript imposes more detailed typing onto JS objects. It makes sense to ask the question: can the type imposed by TS onto a JS object be reified as a run-time object?
It could be, but maybe the use cases for that can be solved in another way that don't require the type system at run-time.
The good news is that static type is, well, static. All the objects of the same type can share a pointer to the same type metadata. The overhead is thus not necessarily huge: one extra property to initialize when an object is created.
The generated TS code has to carry the run-time support routines for the type stuff though.
If only ECMAScript had a native macro system, TypeScript could be just a library - and I would have zero problems with its existence.
You're probably familiar with the following anecdote:
>Eich originally joined intending to put Scheme "in the browser",[4] but his Netscape superiors insisted that the language's syntax resemble that of Java.
JavaScript became the language that we all love to hate due to political, not technical reasons. Yet people look at me funny when I call out the TS/React monoculture as the blatant corporate power grab that it is.
If ECMAScript had macros such that TypeScript could be a library, people would still ask the question: can we attach the type object manipulated by the library to the objects?
If ECMAScript had macros, page load times would skyrocket. ECMAScript and/or browsers would have to define a way to distinguish JS-with-macros files and embedded scripts from plain old JS that doesn't require expansion.
Any JS code bases with complicated macros that take time and memory to expand would have to be expanded by the developer and shipped expanded. So, back to the same model as TypeScript.
I beg to differ. There's a world of difference between:
1. a standardized feature with an official spec, which is subject to debate among the community, and of which it's viable to have multiple competing implementations; vs
2. a proprietary product that presents itself to be a superset of the core language (but isn't), is peddled by a single multinational, has a single implementation designed by an unaccountable elite in the employ of said multinational, which just so happens to be the original Dumbing Down The Computer Company
>If ECMAScript had macros, page load times would skyrocket.
>ECMAScript and/or browsers would have to define a way to distinguish JS-with-macros files and embedded scripts from plain old JS that doesn't require expansion.
If you introduce them today by way of a polyfill, maybe. I'm not necessarily advocating for introducing native macros to ECMA-262 at the present juncture. But I can't help speculating what would've happened if they had caught on.
If they were introduced at the point in history when people were first realizing transpilers were a thing; or designed into the language from the start; or a pre-existing metaprogramming-aware language was used in browsers, instead of Eich designing in 10 days an entire new language under the constraint that it also has to be an ad for Java - who knows where we would be as a civilization? What do you think?
What're you talking about? JS has had macros since the dawn of this world. Just eval(anything). For example eval(tsc("function f(x: number): number { return x + 1}")). Easy!
Can anyone do a "explain lime I'm five" summary of the problem and how the solution would work when typescript is converted down to JavaScript again so the interpreter takes it? Looking from the top it seems like it would add more complexity (and potentially new bugs or exceptions) than it could make things faster and safer.
IF typescript was 100% its own thing and not built on top of JS, it would make complete sense to do it, but with a js engine below it...
How complete is arktype actually? The README says a lot of the syntax is yet to be developed, and don't even get me started on the horror of the docs site.
Our JavaScript bundles are not large enough or slow enough. Could you please make it so we can ship even more data to the client and add more branching so we can give our customers an even worse experience.
You want reflection, use a language that has it and ship webassembly to the client, it will likely be significantly more performant than adding even more bloat to what is already needing to run in the JS interpreter.
This is just the desperation that comes with poor understanding. The strength of a type system should ideally be that you could confidently execute the underlying code even with full type erasure, and while I realize that TS has already sacrificed this to some extent, adding reflection/runtime “types” would just further degrade the value of TS.
No thanks! If you want this, compile java to JS or something equally hamfisted…
This comes up so often, and every time I wonder how much the people who want it have a shared understanding of what it is they want. I realize this is reiterating a point that TS team members have already made, but I’m coming from a different perspective: actually having built higher level tooling on top of one of the solutions mentioned in the post, and having built a fairly mature prototype of another solution from the ground up to address problems none of the existing offerings solve.
My use case isn’t particularly outlandish, I can even imagine it being non-niche if I ever get around to publishing it. But it’s also pretty far afield from any of the existing solutions: platform-agnostic documentation as a core/foundational principle.
My original work implemented JSON Schema documentation on top of io-ts, and added some runtime and type level functionality to support that. But my vision for a hypothetical successor is that
- the documentation standard itself (whether JSON Schema, OpenAPI, or any other underlying format) should be the underlying primitive
- the underlying format should be composable to produce APIs which are just as user friendly (ie users shouldn’t have to muck around with the doc format unless they have a really specific use case, and even then they should still be able to compose the parts they’re not concerned with)
- no additional build time codegen tooling: define a schema once, its type is inferrable from the definition without a compiler extension or IDE config, its documentation is a plain function call executable in any standard runtime to resolve what’s composed
This is all very achievable in TypeScript as-is. But it’s way out of scope from what I imagine any first class thing would or should be. And sure, if such first class thing existed, that wouldn’t make my thing any less achievable. But here’s what I think it would do:
1. Disincentivize usage by users who prefer the first class thing with type syntax as the primary authoring mechanism.
2. Incentivize me to cave on tooling to accommodate that.
3. Piss people off because “we wanted less tooling not more”.
4. Recurse into this same debate.
I’d personally prefer the status quo. Even if that means rehashing the same debate every few months, at least it’s at a recursion depth that’s manageable and doesn’t have countless other sets of unintended consequences.
unpopular opinion, but this just fosters my opinion that TS people just don't understand JS at all. I wish TS would just die (another currently unpopular opinion). If you want runtime type safety, go use a different language and stop trying to ruin JS, it's already the messed up crazy cousin and it doesn't need to be in-bread anymore, it's perfect the way it is!
Only writer I know who discusses pure JS patterns that embrace those its dynamism is Raganwald Braithwaite[0]. For example, most people I know would call this hacky code[1], when this is just design patterns in idiomatic, non-Java/C#-constrained JavaScript. The truth, most devs desire intellisense and/or theorycrafting that comes with static types and compilers.
I’m not an expert or anything but I sincerely feel like this is barking up the wrong tree.
To anyone who works on typescript/JavaScript outside the context of a web browser, you DO NOT matter as far as I am concerned. If I was in control of JavaScript/typescript, I’d give zero attention to your demands. You are not my top priority. Go away.
I feel like we are losing focus here. Should JavaScript be an all purpose language? Yes. Should we focus on making a web browser context, also yes.
The reason I led with this is that I think it is up to the JavaScript folks and the web browser vendors to include support for this before typescript can change what it emits. Ideally, I’d say wait a couple of years after browser vendors include support to implement this in typescript.
I don't understand this comment at all. I mostly work on client-side front-end and am absolutely desperate for better type generation. Zod is making me want to jump out a window right now.
My comment is simple. Don’t add anything to JavaScript if it does not help in the context of a web browser. Don’t add anything to typescript emitted output if it isn’t in JavaScript.
> I love you. You do amazing work. You are gods among mortals. You have brought JavaScript from the darkness, and given it the warm light of strong typing. Look upon us, the cowering meek masses, and understand that we live in the muck and mire of a world where we are doomed to endlessly crawl on our bellies through the alleys of Github seeking the one true npm. We will forever wonder in the dark, until we have a type reflection model.
What even are they getting at?
As for their actual ask (runtime type safety), it is probably not going to happen because the TypeScript project has drawn the line at not being an alternate or additive runtime for JavaScript. Their job is simply to compile down to JS code and exit the picture. Whatever happens after that (in V8 or elsewhere) is your own business.
> TypeScript Needs to Emit Runtime Type Information
This is not possible at all because TypeScript is a compiler. They are really asking for a net new product which has very little to do with the TypeScript that exists today.