runtypes
runtypes copied to clipboard
Anyone generating runtype definitions by parsing TS interface/type declarations?
Has anyone built a tool that transforms TS interface declarations into Runtypes declarations? After using Runtypes for a while, I like the runtime API and the flexibility of adding additional logic (constraints, etc.). But I'm still unhappy with having to declare types using the Runtypes API instead of just writing my types using plain TypeScript which is (IMHO) easier to write, easier to read, won't strip JSDoc documentation, etc.
An ideal solution would be a build-time transformation (e.g. a Babel macro) that would emit a Runtype declaration that corresponds to a particular static type. Here's one possible way it could work:
// Like https://github.com/dsherret/ts-nameof/tree/master/packages/ts-nameof.macro,
// but for creating runtypes instead of simply extracting an identifier name
import runtypeof from 'ts-runtypeof.macro';
interface Foo { a: string, b: number }
function Bar (foo: Foo) {
const FooRuntype = runtypeof<Foo>();
FooRuntype.check(foo);
}
type Weird = /* put some types in here that can't be modeled in Runtypes */ ;
function BarWeird (weird: Weird) {
const WeirdRuntype = runtypeof<Weird>(); // Babel should emit error & fail build
WeirdRuntype.check(foo);
}
Obviously not all TS syntax would be supported, but that's true whether or not I'm manually writing the runtype definition code myself or something automatic is generating it.
@pelotom - This issue is essentially a dupe of #87. And I agree with your notes in that issue that building a solution is out of scope for this library. But the need is certainly there, and I think it'd be helpful to have a honeypot issue to attract people who might be building something similar (or who are interested in building it). So I was wondering if leaving this issue open for a year or two might be helpful. If you don't think it would be helpful, feel free to close.
Here's a list of related work happening elsewhere. Feel free to comment with others I didn't capture:
- ts-rpc-http - generates validation based on TS types using codegen
- typescript-is - generates type guard functions via a typescript transformer
-
ts-nameof - simple babel macro that extracts the name of an identifier into a constant that's accessible at runtime. Might be a useful example for building a similar
runtypeof
macro. - tsutils - wrapper functions to more easily use and manipulate TypeScript's AST
- ts-morph - Another wrapper around TS's compiler API. Seems to be more popular than tsutils.
- Developer docs for authoring Babel macros
- tsguard.macro - proof-of-concept implementation of creating type guard functions from TS types. Not under active development.
One significant issue I've seen is the difference between Babel's vs. TSC's AST implementation. Most of the links above (maybe all?) use the TypeScript AST, which may have bad perf implications if used in an environment like create-react-app where Babel not TSC is used to transpile TypeScript files, because the TS compiler API would have to also run. But admittedly I'm not an expert on this stuff so I might be mistaken, but did want to capture it as a concern I've seen from others.
Hey @justingrant, no objection to leaving this as an open issue for the sake of discussion. I share your desire to be able to just generate runtime validators from type definitions. Lots of good links you've provided here, thanks!
That seems problematic, like const enums and namespaces with CRA. Please correct me if I'm wrong here, but if you separate transpiling from typechecking, won't any type-directed emit cause a problem? For example, if you generate runtypes definitions with babel, the values won't be available during typechecking and will cause a compile error. And if you did it with a transformer, then (with the standard tsc --noEmit
or whatever in CRA) then that code wouldn't be transpiled by babel.
I guess these can be worked around, but all that seems fairly intrusive. I just realized that if you do it in babel you could generate a d.ts at the same time. But that still seems like a chicken-and-egg problem depending on which runs first (I thought babel ran first in CRA and it used the forking ts-loader?). I'm interested if I've gotten this all wrong.
I consistently point people towards runtypes
when they ask for this kind of feature in the TS gitter/discord channels. The transform/macro routes just seem super painful in comparison to just writing runtypes, despite the kind of impedance mismatch. Also, runtypes definitions are more expressive (you can i.e. add additional constraints).
I'd honestly prefer codegen over a macro/transform. But that still wouldn't reach the goal of this issue, just writing types as usual.
I created something like this: https://github.com/vedantroy/typecheck.macro, it just generates validation code directly, but I bet the codebase could be split into a few libraries & a new "runtypes" could be created.
Hey @pelotom @justingrant @keithlayne @vedantroy,
After checking out your previous ideas, I've been going a different route, creating a CLI command to extract runtypes from TypeScript types. This has the benefits that this works cross-file, as one would expect, with the full TypeScript type inferrence.
You can find my code example here: https://gist.github.com/skurfuerst/a07ab23c3e40a45f2268f7700ceeceaf
Let me know what you think :-)
All the best, Sebastian
Hello 👋
I recently published generate-runtypes. The purpose of this package is to be a building block for other libraries to use, like jsonschema-to-runtypes (which is just a fork of jsonschema-to-typescript
, but converted to use generate-runtypes
).
All of this is very experimental, but we been fortunate enough to get help from @runeh which has done amazing work to implement a better API to define Runtypes types. Next on my agenda is to try and build an OpenAPI to Runtypes+Client generator that can be used for many of my own API client packages I maintain.
I really wanted to get generate-runtypes
into the wild, to get feedback and get people involved! Like @skurfuerst; I guess generate-runtypes
is a great fit for your CLI command; Maybe you want to have a go at building a CLI command that converts Typescript types to Runtypes using generate-runtypes
– it would be awesome!
If anyone of you want to chat and talk about the opportunities, please raise an issue on generate-runtypes or get in touch with me on keybase.
Thank you! 🙏
It's worth mentioning https://github.com/akameco/s2s here. I've not yet tested it actually, but s2s (source-to-source) seems to be an interesting idea.
I'm using @runtyping/runtypes
for my project, and it's been very nice to use so far.