Loved this series. Regarding your final thoughts: Is using a combination of new-types and io-ts something you would recommend? I gave it a go but I had to manually cast the type:
Since everything newtypes-ts does is also possible with branded types in io-ts, I'd only use io-ts. The only downside I see is that newtypes-ts exposes convenient types (such as Integer, Char or NonEmptyString) that must be reimplemented using t.Branded.
(there's actually a NonEmptyString codec and a fromNewtype function in the io-ts-types package, but I couldn't manage to make this function work. I believe it uses the newer io-ts APIs, which are still marked as experimental as of today. Feel free to give it a try though :) )
On a side note, I'd use t.brand instead of defining a new t.Type, as it requires less boilerplate.
If you are working with newtypes, you'll have to convert them into Branded types. Indeed, both of these types are not defined in the same way, so they don't interoperate very well. To manage that, we could use some "adapter" to transform a Newtype into a Branded:
// Convert a Newtype<A, B> into a Branded<B, A>typeToBranded<A>=AextendsNewtype<inferBrand,inferUnderlyingType>?t.Branded<UnderlyingType,Brand>:never// extract the brand B from a Branded<A, B>, used to define codecs type guardstypeGetBrand<A>=Aextendst.Brand<inferBrand>?Brand:never
Now we can do the following:
import*asNESfrom'newtype-ts/lib/NonEmptyString'typeNonEmptyString=ToBranded<NES.NonEmptyString>constNonEmptyString=t.brand(t.string,(s:string):sist.Branded<string,GetBrand<NonEmptyString>>=>s.length>0,'NonEmptyString')typeString60=ToBranded<Newtype<{readonlyString60:uniquesymbol},string>>constString60=t.brand(t.string,(s:string):sist.Branded<string,GetBrand<String60>>=>s.length<=60,'String60')constNonEmptyString60=t.intersection([NonEmptyString,String60])typeNonEmptyString60=t.TypeOf<typeofNonEmptyString60>// or: type NonEmptyString60 = NonEmptyString & String60
Note: we could also directly define Branded types and completely discard Newtype:
Loved this series. Regarding your final thoughts: Is using a combination of new-types and io-ts something you would recommend? I gave it a go but I had to manually cast the type:
Hello, thank you for your feedback!
If you want to avoid using a type assertion there, you can create a predicate function:
And use it both for the type guard and validate functions of your codec:
Since everything newtypes-ts does is also possible with branded types in io-ts, I'd only use io-ts. The only downside I see is that newtypes-ts exposes convenient types (such as
Integer,CharorNonEmptyString) that must be reimplemented usingt.Branded.(there's actually a
NonEmptyStringcodec and afromNewtypefunction in the io-ts-types package, but I couldn't manage to make this function work. I believe it uses the newer io-ts APIs, which are still marked as experimental as of today. Feel free to give it a try though :) )On a side note, I'd use
t.brandinstead of defining anew t.Type, as it requires less boilerplate.If you are working with newtypes, you'll have to convert them into
Brandedtypes. Indeed, both of these types are not defined in the same way, so they don't interoperate very well. To manage that, we could use some "adapter" to transform aNewtypeinto aBranded:Now we can do the following:
Note: we could also directly define
Brandedtypes and completely discardNewtype:Hope that helps :)