> This is not possible at all because TypeScript is a compiler.
Sorry, can you clarify? Many compilers exist in other languages that support RTTI, so it's not clear to me what you mean by this.
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.
Not sure if this supports your point. Lots of compilers emit runtime type information, e.g. Golang, Java.
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`.)
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.
Typescript is mature. There is no low hanging fruit to be develop.
I don't necessarily think it needs this but this is hardly the right grounds to dismiss it.
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.
(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.
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.
I write one of them (typedload).
If it's a union, it has a bunch of heuristics to guess right at the 1st try, but otherwise it will just try them all.
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.
At which point it makes a lot of sense to ask why that feature shouldn't be merged.
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.
Alternatively, use a runtime validator the provides good type inference out of the box so you're still only declaring your types once.
If typescript emitted type information, we'd be able to automatically get runtime validation at the boundaries of our apps.
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.
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.
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.
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.
When I produce a type definition for a nested schema, I'll produce types for individual pieces.
So
type Schema = { foo: Foo; bar: Bar; baz: Baz[]; }
type Foo = { red: Color; white: Color; blue: Color; }
type Color = { r: number; g: number; b: number; }
type Bar = {...} type Baz = {...}
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.
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.
Imagine a Zod schema like:
const UserLocation = z.object({ address: Address, coords: Coords })
where Address, Coords are other zod schemas.
Now if I infer a type like:
type IUserLocation = z.infer<typeof UserLocation>
Th inferred IUserLocation is something like:
type IUserLocation = { address: { city: string, country: string, ... }, coords: { lat: string, long: string } }
The extracted type does not refer to separate Address, Coords type, it is a single complex type that represents the complete nested structure.
So if I do something like:
const userLocation: IUserLocation = { address: { ... }, coords: { lat: 0, long: 0 }}
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
However, if I define a ts interface directly:
interface IUserLocation { address: IAddress coords: ICoords }
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: https://github.com/kristiandupont/kanel/blob/main/example/mo...
But I agree with you that there is room for improvement.
Thanks for maintaining kanel btw. We used to use this in a previous role alongside knex. It was very useful.
[1] https://lorefnon.me/2022/06/25/generating-api-docs-for-zod-t...
interface Person { firstName: string; lastName: string; }
const PersonSchema = z.object({ firstName: z.string(), lastName: z.string(), }) satisfies z.Schema<Person>;
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'll definitely use this going forward.
To the extend that TS's type-system is unsound, and everything may crash at runtime at random anytime.
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` fromThey just serve burgers and fries. And they are doing very well with that strategy. Same goes for TypeScript.
I wrote a compile time IoC container that worked totally fine using `ttsc` four or five years ago. It's possible, has always been possible, and the only reason it's not more widely used is the API is intentionally unfriendly and will likely never be allowed to be public.
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".
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.
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.
Some people have very tight constraints for payload size, and types could blow up payloads
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.
They are literally groveling. That should tell you enough. Cringe af
Just consider how far we've progressed since the 90s - instead of helplessly cursing at a distant Microsoft, we now have the freedom to beg them to make the shit that they're forcing down our throats a little more nutritious! And a realistic chance of them hearing us, maybe even responding: "hell, are you really stupid enough to think that we're feeding you shit because of its nutritional value?"
Embrace, extend, extinguish is part of Microsoft "corporate DNA". TypeScript amounts to a wildly successful social engineering attack against the open Web development community. Its purpose is to subjugate, plain and simple. It's "open source" just as much as it's a "superset of JavaScript" - which is to say, it isn't either, but pretends to be. And nobody bats an eyelid.
There was a phase (2010s) when JS-to-JS compilation was not only a valid approach, but a necessity. Rolling out ES6 to the heterogeneous browser landscape was a tremendous feat in distributed, decentralized, public computing. It was also Microsoft's opening: "JS transpilers are becoming a jungle; now is the time to make the One True Transpiler which accidentally only kills JS!" So they did, to great success: how many newbies you see learning how JS works, before making an informed decision whether they need TS or not?
But while rookie programmers with oodles of talent and ability to learn (and the occasional stroke of beginner's luck at achieving the impossible) are busy enthusiastically developing learned helplessness from wrangling the TypeScript toolchain (and the other cancerous growths on the Web ecosystem, take your pick), they can't empower themselves. If they can't empower themselves, they can't innovate. If they can't innovate, they can't fight the lumbering shoggoth that is the surveillance economy. If nobody takes down the surveillance economy this generation, bad times for everyone. Am I explaining this simply enough?
We've even got the TS lead in this comment section (but of course)! And he's probably ready to question my mental faculties (in friendly, polite American) for relating my lived experience (and the only remotely sane explanation for it that I can conjure). Because his salary depends on that or smth. Hello Ryan, have you folks fixed TS generating invalid ESM imports and telling people to suck it up in the documentation[0] yet?
And how do you feel about people "ironically" humiliating themselves to beg you for stuff (unrealistic or not) - considering the author of that README addresses you and your team as something akin to demigods?
[0]: https://www.typescriptlang.org/docs/handbook/esm-node.html "This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will typically just do this for you." Yeah, no.
Hello. :-)
>Please log a bug.
I'll check if the bug opened last year (as Node 16 tightened ESM resolution... start to remember?) is still open.
But nevermind that.
What I really want to ask you, is about this introduction:
>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 wander in the dark, until we have a type reflection model.
(source: https://github.com/akutruff/typescript-needs-types i.e. TFA, as of 2023-07-08T00:05:20Z)
Had you read this text before I just brought it to your attention?
What were your immediate feelings regarding its tone?
Did you enjoy being addressed like that?
Or were you embarrassed?
Or did you just brush it off as a harmless, playful display of "nerdiness"?
Or something else entirely?
What would it take to convince you that, if someone needs to debase themselves to "endlessly crawling on their bellies" to get your attention as a platform vendor -- or veer towards the other extreme (as in the case of my rants) -- your "solution" just might have become part of a bigger problem?
>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.
This issue. 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"?
---
Listen, how about I just tell you why I'm asking you all those weird little questions. I mean you're a busy man, you'd wonder "what's the ploy here" and decide it's safest not to bother answering them, anyway.
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.)
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 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. 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, 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 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.
For me, this is because TypeScript is not honest open source software. Say it honestly for all to hear: am I wrong to say that the direction of TypeScript is determined by Microsoft's interests first, and the interests of the community a distant second?
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 giving non-technical users buttons to click; you're literally imposing your will on an 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 forward; freely benefitting from the community's input on how to do what you're already doing, but even better; 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 recovering from the way your technology ended up impacting my life, no exaggeration. If even 1% of JS/TS users have been through a similar wringer as myself, that makes for how many people hobbled by your work? Calculate, and consider.
All the best.
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.
2. TS already supports runtime type guards functions, https://www.typescriptlang.org/docs/handbook/2/narrowing.htm.... It wouldn't be hard to generate those functions automatically using information already in the type system.
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.
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 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.
> 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.
The "one true npm"? What's that supposed to mean? Package managers like yarn or pnpm? Or do they mean "ts-node"? That's somewhat related to types. But yeah, guessing what the text could be about is also symptomatic of ChatGPT output
I don't understand. Go is a compiler too, but here's https://pkg.go.dev/reflect
> 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.