What does HackerNews think of io-ts?
Runtime type system for IO decoding/encoding
Someone arguing a little defensive programming is equivalently strong to a type system is clearly unaware just how much work a good type system does for you. I of course agree with you: when you're fetching data that you don't know the type of, recklessly casting it to some type is going to cause issues. This is true in every language that has ever existed. It's also why tools like IO-TS[0] exist, and of course you can enforce this with JSON Schema techniques or custom validators or a million other options.
Edit: in case the ultimate point of this comment is not clear, by using a type system and some type validator on your fetches, you are able to reduce the need for defensive programming exclusively to your fetch points. Clearly, defensive programming at the fetch points was already needed, so this is why I do not agree with the claim TypeScript's value add disappears from remote fetches.
You define a decoder schema, and then the resulting TS type gets automatically derived for you. You can then run data through the decoder, it will err if there's a mismatch, or return a value of the inferred type otherwise.
If you're using TS these are pretty much footgun eliminator.
That approach looks nice though. On that subject, JS has some nice libraries including io-ts[1] which has a functional approach using Eithers to encapsulate errors/success.
* Zod: https://github.com/colinhacks/zod * io-ts: https://github.com/gcanti/io-ts (for the more functional-programming-oriented)
With codec, one can make sure that the type is correct and defining type and type-guard becomes a one-time task.
Or if brave enough to dive into the deep end of FP, io-ts is nice. https://github.com/gcanti/io-ts
In our team we use io-ts to validate all of our endpoints, but simple type guards could achieve the same goal.
- Personal site/blog with a bunch of algorithmically generated art and other fun stuff, built on Node/Preact but progressively enhanced/almost completely JS-free at runtime. Motivation for the build approach is that I’m on the low/no client JS static site bandwagon but I quite like the DX of JSX components and CSS-in-JS.
- I’m using a few excellent existing tools[1][2] for said site which unfortunately aren’t designed to work well together, so I have a variety of wrapper tooling that makes them live peacefully together. I’m also developing a bunch of other build-stage tools for my use cases. I plan to open source (or hopefully contribute back) all of that as soon as I’m satisfied with their quality.
- A set libraries for building declarative, type safe, automatically validated/documented service API boundaries (HTTP/REST to start, but I also plan to support other transport protocols) — think io-ts[3] type interfaces but you get swagger docs for free in a transport-agnostic interface. I’ve built this kind of thing before, it was wildly successful in real world use, but it’s proprietary to a previous employer and I’m starting over with all the stuff I learned in hindsight.
- A “nag me” app that’s basically “reading list” plus “reminders” with minimal config, eg “nag me soon” or “nag me after a while”. My personal use case is I frequently screenshot/text myself/etc stuff I want to look at later (usually on phone but need a computer to dive in), then it just goes down the memory hole. I’ve tried setting reminders but it’s often too much fuss, and I’m far too ADHD to use a passive list.
- Exploring building yet another FE build tool/bundler that’s explicitly multi-stage/sequential with static input/output validation, per-step/time travel debugging. Motivation is that existing tools are just a big ball of config magic and totally inscrutable. I’d likely wrap existing build tools because their set of responsibilities isn’t my motivation and I don’t want to introduce that much more new API surface area to weary FE devs.
[1]: https://github.com/natemoo-re/microsite
If you are strict, don’t intentionally try to outsmart the type system, and use manual runtime type checking when necessary [0], TypeScript does a good practical job of ensuring that you won’t run into runtime type errors.
The reason TypeScript doesn’t automatically include runtime type information is (probably) because the overhead of runtime type checking would outweigh the benefit when static type checking does a good enough job.
Also, JavaScript being a dynamic language has allowed some pretty complex patterns of types to emerge from real-world use cases that would be exhausting to enforce all the time at runtime. Typically in TypeScript, runtime type checking is (manually) done when the type system cannot otherwise confidently guarantee the type of a value. For example, it’s common to have a runtime check for the shape of a JSON api response because you’re not necessarily in control of what that will actually return. There are community supported packages to help make this easier [1].
0: https://www.typescriptlang.org/docs/handbook/advanced-types....
io-ts [0] completely solves this issue, to the point where I don't think it's less type-safe than Rust in any practical manner. I've been writing apps in TS this way for the past year and I have quite a large codebase in production. The errors you mention do not happen.
Opt-in runtime checks for SQL queries are also on the roadmap for PgTyped. You are welcome to open an issue to track our progress there if you think this feature will be useful for you.
Anyway, IMHO, Typescript isn't designed for dependency injection in mind. Microsoft also has a DI library [1] but it feels like too much wiring to compensate for the lack of compiled interfaces.
It's not perfect and a bit of a bolt-on, but io.ts works reasonably well in this area:
1. Sub-optimal developer experience: significantly noisier syntax compared to pure typescript, convoluted typescript errors, and slower type checking.
2. Unfixable edge cases in static type checking: Features like conditional types work less reliably on the types produced by the library.
3. Some typescript features can't be supported in the library (again, conditional types).
I think a better way to approach the runtime+static type checking is to do it as a babel plugin, which would fix the DX and edge-case problems and also gracefully degrade in the case of typescript features that can't work in runtime.
Since babel can now parse typescript, it is trivial to write a babel plugin that takes regular typescript files and converts their type annotations into runtime values [4]. Those values can then be fed into a simpler type checking library, giving us runtime type checking on top of the native static type checking experience.
[0] https://github.com/gcanti/io-ts
[1] https://github.com/pelotom/runtypes
[2] mobx-state-tree also has a schema validation library that works to some extent statically: https://github.com/mobxjs/mobx-state-tree/
[3] https://github.com/gcanti/io-ts#branded-types--refinements
[4] https://gist.github.com/AriaMinaei/2f1229178abad4363f5180db2...