Concepts
js.spec has two main concepts: Predicates and specs. However this distinction is rather technical and should not matter in practice as they can be used interchangeably.
tl;dr
- Predicates
- Smallest building block for schemas
- Functions
any → boolean
- Users should implement custom predicates if built-ins are insufficient
- Specs
- Powerful way to compose predicates
- Users should not implement custom spec types (ie. something you would use instead of
spec.map
)
Predicates
Predicates are check functions that take input data and return a boolean. For instance the predicate isString
would return true iff the provided parameter is a string and could be implemented like this:
function isString(data) {
return typeof data === 'string';
}
Now with a naive approach that could be it: If you would want to check for an even number between 0 and 100, you'd need to write a predicate for that. This doesn't scale, which is (also) why there are specifications.
Specs
Specs are somewhat like predicates. They can also check for validity, but more. To add to the confusion, every predicate can be used as spec (will be automatically converted).
What are the powerful features?
Composability
You can define how an address looks in your system, e.g. a map with keys country
, zipcode
and street
:
const classic_address = spec.map("classic address", {
country: spec.string,
zipcode: spec.string,
street: spec.string
})
...and use that when you define a person!
const person = spec.map("person", {
name: spec.map("name", {
first: spec.string,
last: spec.string
}),
address: classic_address
})
Support for alternatives
Sometimes you want your data to match one of many alternatives. For instance there are countries that use more than zip code and street, so for sake of argument you could add GPS as a universal alternative.
const gps_address = spec.map("gps", {
lat: spec.number,
lon: spec.number
})
const address = spec.or("address", {
gps: gps_address,
simple: classic_address
})
Reverse match
Consider the address example from above: At some point you will want to know which type of address you are working with. Maybe you want to plot your users on a map and need to convert simple addresses to GPS coordinates first. You can do that with specifications:
conform(address, { lat: 13, lon: 55 })
// ['gps', { lat: 13, lon: 55 }]
Introspection
Get metadata about a specification, e.g. type and configuration (🚧 WIP).