Exposes an extended Schemable typeclass SchemableExt
with types inspired by io-ts-types
, and validators.js
.
- Disclaimer
- Contributing
- Installation
- Documentation
- Schemable types explained
- Currently supported modules:
This library is in its nascent stages (< 1.0.0) and still has a lot of work ahead before it's at feature parity with io-ts-types
, or validators.js
.
This library currently has 100% jest coverage, contributions are highly encouraged and we should seek to maintain this high level of test coverage. Send over a PR!
Schemable combinator modules must have a unique name and can be found in the folder whose primitive they extend, i.e. string, number, or date. A Schemable combinator module must export Decoder
, Eq
, Guard
, TaskDecoder
, and Type
instances (named exactly), along with type aliases: SchemableParams
, SchemableParams1
, and SchemableParams2C
which are the types of the exported Decoder
, etc instances with the HKT/Kind type argument for that particular instance. Optionally, combinator modules can export convenience functions to convert to stronger types, i.e. ISODateString to SafeDate with the assumption that the guard function (isISODateString) matches the strength of the target type.
Create a new combinator file using yarn generate:template [string | number] [NameOfCombinator]
. Note: the name of the new combinator must be unique, and begin with a capital letter.
Once the new combinator modules are in place, run yarn generate:schemables
and the ts script will update the typeclass instances and SchemableExt module with the newly created combinators. Don't forget to add tests!
Our docs pages are automatically generated whenever a PR is merged into the main
branch, so the following step is optional. If you want to manually re-generate the documentation pages, use yarn docs
. This command uses docs-ts and JSDoc-style annotations to parse code comments for metadata about each module.
Uses fp-ts
, and io-ts
as peer dependencies. Read more about peer dependencies at nodejs.org.
yarn add schemable-ts-types
npm install schemable-ts-types
At present there is no way* in vanilla Typescript to derive domain typeclasses from domain types. Languages like Haskell, Purescript, and Rust support this functionality out of the box. With the older io-ts
system you would have a domain declaration that looks like the following.
*(Note: ts-plus seeks to extend Typescript with this functionality and more)
import * as t from 'io-ts'
export const User = t.type({
name: t.string,
email: t.string,
age: t.number,
})
export type User = t.TypeOf<typeof User>
Elsewhere in your package you could consume the User
type using User.decode(/* Some unknown data structure */)
, which would guarantee that the runtime type (if value was a Right
value) conforms to the expected data structure.
The more recent development in io-ts
splits t
– a collection of combinators which construct a class of Decoders/Encoders – into distinct modules, Decoder
, Encoder
, Eq
, etc. Using this newer system presents a challenge, as you can no longer get Decoder
, Encoder
in a unified class with a single definition.
import * as D from 'io-ts/Decoder'
import * as Eq from 'io-ts/Eq'
import * as G from 'io-ts/Guard'
export type User = {
name: string
email: string
age: number
id: string
}
export const decodeUser: D.Decoder<User> = D.struct({
name: D.string,
email: D.string,
age: D.number,
id: D.string,
})
export const eqUser: Eq.Eq<User> = Eq.struct({
name: Eq.string,
email: Eq.string,
age: Eq.number,
id: Eq.string,
})
export const guardUser: G.Guard<User> = G.struct({
name: G.string,
email: G.string,
age: G.number,
id: G.string,
})
This works well and is type safe. But it comes with a big downside: if we need to change something, it is necessary to change it in four different places.
With Schemable, you first provide instructions (Schema
) to construct a domain type, and then provide a way to interpret instructions (Schemable
).
The example above can be refined to the following with Schema and Schemable:
import { interpreter, make, TypeOf } from 'io-ts/Schema'
import * as D from 'io-ts/Decoder'
import * as Eq from 'io-ts/Eq'
import * as G from 'io-ts/Guard'
const UserSchema = make(S =>
S.struct({
name: S.string,
email: S.string,
age: S.number,
id: S.string,
})
)
export type User = TypeOf<UserSchema>
export const decodeUser = interpreter(D.Schemable)(UserSchema)
export const eqUser = interpreter(Eq.Schemable)(UserSchema)
export const guardUser = interpreter(G.Schemable)(UserSchema)
And with this, the structure of domain types and operators come from a single source, making future maintenance and extension trivial.
With an extended schemable typeclass you have the powerful features of io-ts-types
(restricted to the older Type
system) and validators.js
(written in javascript, and more difficult to adopt in a purely functional environment) with a modern domain type declaration system, and the power of fp-ts.
Let's refine our User type.
import { D, Eq, G, SC } from 'schemable-ts-types'
const UserSchema = SC.make(S =>
S.struct({
name: S.NonemptyString,
email: S.Email,
age: S.Natural,
id: S.UUID({ version: 5 }),
})
)
export type User = SC.TypeOf<UserSchema>
export const decodeUser = SC.interpreter(D.Schemable)(UserSchema)
export const eqUser = SC.interpreter(Eq.Schemable)(UserSchema)
export const guardUser = SC.interpreter(G.Schemable)(UserSchema)
And now we can guarantee that a user's email will conform to RFC 5322, their id will be a proper UUID-v5, and their age will not be negative.
primitive | refinement |
---|---|
Date |
SafeDate.ts |
generic |
optionFromExclude.ts |
generic |
optionFromNullable.ts |
generic |
optionFromUndefined.ts |
number |
Int.ts |
number |
Natural.ts |
number |
NegativeFloat.ts |
number |
NegativeInt.ts |
number |
NonNegativeFloat.ts |
number |
NonPositiveFloat.ts |
number |
NonPositiveInt.ts |
number |
PositiveFloat.ts |
number |
PositiveInt.ts |
string |
ASCII.ts |
string |
Base64.ts |
string |
Base64Url.ts |
string |
BigIntString.ts |
string |
BtcAddress.ts |
string |
CreditCard.ts |
string |
EmailAddress.ts |
string |
Hexadecimal.ts |
string |
HexColor.ts |
string |
HslColor.ts |
string |
IntString.ts |
string |
ISODateString.ts |
string |
JWT.ts |
string |
NaturalString.ts |
string |
NegativeFloatString.ts |
string |
NegativeIntString.ts |
string |
NonemptyString.ts |
string |
NonNegativeFloatString.ts |
string |
NonPositiveFloatString.ts |
string |
NonPositiveIntString.ts |
string |
PositiveFloatString.ts |
string |
PositiveIntString.ts |
string |
RGB.ts |
string |
UUID.ts |