Specifications are used to retrieve information. They describe the shape of the desired facts. You start out by writing a specification function.
A specification function returns j.match
.
It passes in a template, which is a JSON object describing the type and one predecessor of the desired facts.
function postsByAuthor(author) {
return j.match({
type: "Blog.Post",
author
});
}
Most of the time, a specification function matches a successor of the argument. That is to say that the argument appears as a field of the template.
But sometimes a specification function needs to match a predecessor of the argument.
This kind of specification is more commonly used in authorization rules.
When you match against a predecessor, you must first ensure
that the argument has
that predecessor.
function authorOfPost(post) {
ensure(post).has("author");
return j.match(post.author);
}
A specification function can use suchThat
to extend the specification with a predicate.
These apply additional conditions.
function publishedPostsByAuthor(author) {
return j.match({
type: "Blog.Post",
author
}).suchThat(postIsPublished);
}
Pass a conditional function into suchThat
.
A conditional function returns j.exists
or j.notExists
.
Just like a specification function, a conditional function passes in a template.
This template is a JSON object describing the type and one predecessor of facts to test for.
If any such facts are found, then j.exists
evaluates to a true condition.
function postIsPublished(post) {
return j.exists({
type: "Blog.Post.Publish",
post
});
}
You can combine templates and conditions to describe complex shapes. This diagram illustrates the rules for defining a template function.