What I'd really like is a tsc that creates code to check types at runtime, like for API boundaries and parsing unknown input. Kind of like a built-in Zod. Maybe it's just an automatic type guard anywhere you have an "as SomeType" or an ignore directive.
You can use io-ts [0] to define your types, and it'll generate functions to typecheck for you. Syntactically it's a bit gnarly and the documentation isn't great; a first-party solution would definitely be nicer. But it works, and it's amazing that it works.
It's a neat library, but you end up defining your types in an external (outside of TS) syntax, and you lose a lot of language-server features. Also, last I checked it could not handle generics. But it's been a while.
You don’t lose any language server features, you just access them slightly differently. Each function in io-ts, zod, and other TS libraries like them) are type guards, and have companion utility types to extract the static types from their runtime definitions (with `typeof`). I’m certain that io-ts handles generics (I’ve used it to do so), albeit again slightly differently, in that you need to define them as generic type guards.
I think the clamor for runtime type checking in TS is partly because type guards and their benefits could be better explained at the language level, and partly that libraries implementing them effectively are mostly aimed at users who already understand those benefits.
You really don’t want pervasive runtime type checking, except at API boundaries where data is untyped (i.e. the appropriate type is `unknown`). Type guards are exactly designed for that use case. For other cases where the types are well known, runtime type checking is needlessly expensive and redundant.
Agree with all of that, but also, it's been at quite a while since I used it. I'm sure it's improved a lot since then, and my memory might be off as well. I really remember not being able to get it to work with generics. But maybe I didn't read the docs deep enough.
We just do what you describe now, and don't even really want automated type checking. We just write our own assertion functions. The weakness of writing your own is that you have to sort of "manually" upgrade them when there type changes, or they drift and your editor won't tell you about it.
If your editor isn’t telling you there’s a type error in your guard, that’s usually a good sign your guard is too large. Even if you’re rolling your own (which seems an odd choice but I’ll take it as read that you have reasons), it’s a good idea to take inspiration from the prior art. With libraries like zod/io-ts etc, it’s harder to end up with a mismatch like you describe than to always have your types and guards in sync, because the guards are built from small composable primitives and the types are derived from them. Larger guards built without that are basically a lot of ceremony around `as Foo`, with all the false sense of safety that implies.
Not trying to dissuade you from rolling your own, mind you. Heck, it’s been stalled for a while as I focus on other things, but I’m rolling my own whole library (also for reasons, foremost of which is handling JSON Schema generation and validation at runtime without relying on codegen like other solutions do).
It's not external to TS. You write your types by passing object literals to the functions that generate the validators; TypeScript then infers shockingly precise types, which can be extracted using TypeScript's type manipulation utilities.
I really wanted this in my earlier usage of typescript as well.
But the solution really is to assume `: unknown` at API boundaries and run the values through assertion functions: `isSomeType(x: unknown): asserts x is SomeType`.
After using this sort of pattern, I don't think I would want automatic runtime checks anymore, because creating your own explicitly and calling it explicitly works out not so bad.
yeah. You have to assert an object (`Record<string, unknown>`) type first within your asserter (or array or whatever). We ended up having whole stacks of re-usable assertion functions to be used at these boundaries (re-usable on server side too if you're using node!)
Just a simple function (ensureType) that checks primitive types (angle brackets for min length). You reconstruct the object using ensureType, writing it back into itself. EnsureType returns what is passed in, with the corresponding type. Has worked well.
At some point I used a TS->JSONSchema generator and that worked out pretty great. Obviously it would be greater to be built into the language but I think that's always going to be out of scope of what TS aims to do.
This x1000! Type guards are a joke and real runtime type checking could be so easy if they prioritized it. Yes you can use zod but then you have to define your types through zod which isn't as neat.