io-ts icon indicating copy to clipboard operation
io-ts copied to clipboard

Discussion: a standard for type building

Open Kinrany opened this issue 3 years ago • 5 comments

Having to build something that exists both at runtime and at compile time is a common problem with TypeScript.

Libraries like io-ts make it possible to validate or deserialize values with known types from arbitrary JS values, but this doesn't necessary help with using all the data languages - JSON schema, OpenAPI, GraphQL, etc.

There are libraries like @sinclairzx81's typebox that do the second step and build both the schema and the TS type at the same time. But AFAIK each one has to roll its own type system.

More importantly, they're not interoperable: one cannot write a single type and get schemas for multiple data languages.

I wonder if it's possible to write a common library for specifying types and then a series of converters that build schemas for each data language.

Kinrany avatar Aug 23 '20 09:08 Kinrany

sounds like morphic-ts to me

anthonyjoeseph avatar Aug 29 '20 19:08 anthonyjoeseph

sounds like morphic-ts to me

Looks interesting, but I'm not sure I understand what I'm looking at. What kind of library is it?

Kinrany avatar Aug 29 '20 20:08 Kinrany

It's similar to io-ts's schema, if that gives you a frame of reference, but a bit more general and flexible.

I'm not super familiar with Typebox, but afaict, morphic-ts is the same idea but one level of abstraction higher.

morphic-ts allows you to define a schema by using a 'summoner', a little like how Typebox allows you to build a JSON schema using Type or how io-ts builds codecs with combinators.

In fact, a summoner is an abstraction of both of these things.

Summoned schemas have 'interpreters'. One common interpreter is for JSON schemas, another is for io-ts codecs. So if you use the right summoner, you get a JSON schema and an io-ts codec for free.

There are many possible 'baked in' interpreters - I believe 'summoner-EBASTJ' stands for 'Eq', 'Build', 'ARB', 'Show', 'Type', and 'JSON schema' ('Build' meaning simple creation, 'ARB' being from fast-check, 'Show' and 'Eq' being fp-ts typeclasses, and 'Type' meaning io-ts type). So if you use 'summoner-EBASTJ', you only define the data type once, and you get all those things for free (!)

If you didn't need 'Eq', you would use 'summoner-BASTJ' instead, etc.

Also there's first class support for sum types

It also allows you to configure summoners - so you don't need to make an entire new summoner if you want to change how fast-check generates strings. And you can use a 'config environment' if you want to limit the places where you have to import fast-check.

It also allows you to define your own summoners with their own interpreters using makeSummoner from @morphic/summoners.

There's also a related ADT module which is like a super-powered unionize, but that's another story.

It's also officially part of the fp-ts 'ecosystem'

anthonyjoeseph avatar Aug 30 '20 17:08 anthonyjoeseph

Haha, feels like the time I was trying to figure out the reasons io-ts seemed so complex compared to runtypes.

My intuition was that the API would look like a single "core" library that merely specifies types both at runtime and at compile time, plus one library per target. So using the library would look more like this:

const Person = summon(F => F.interface({
  name: F.string(),
  age: F.number()
}));

const jsonSchema = typeToJsonSchema(Person);
const ioTsType = typeToIoTsType(Person);
const strictIoTsType = typeToStrictIoTsType(Person);
type TypescriptType = TypeToTypescriptType<Static<Person>>;

Can you tell if summoners are basically doing the same thing with a different API, or something slightly different?

Kinrany avatar Aug 30 '20 21:08 Kinrany

Haha yes it is all a little complicated! I think it's worth it though

I'm not sure if it was on purpose, but the API you're describing looks a lot like io-ts's schemable.

Yes I think summoners are doing basically the same thing, just with a few extra features. The main difference imo is the config & config environment stuff.

Although io-ts Schemable is less flexible, it is simpler and I think it might be flexible enough to do what you're describing. Afaik there aren't existing interpreters yet for graphql or OpenAPI for either system but I think they could be implemented.

Tbh I've never used either of these schema abstractions in production (😅) so I would probably refer to these comparisons of the two: a github issue on the morphic-ts repo, and the answer to my own question on the morphic-ts slack (sign up here - channel #morphic)

anthonyjoeseph avatar Aug 31 '20 00:08 anthonyjoeseph