Starting with a model, use the given
and match
functions to write a specification.
The specification can be used with j.query
, j.watch
, and useSpecification
to retrieve results.
const projectsForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.notExists(project => project.successors(ProjectDeleted, deleted => deleted.project))
.select(project => ({
hash: j.hash(project),
identifier: project.identifier,
names: project.successors(ProjectName, name => name.project)
.notExists(name => name.successors(ProjectName, next => next.prior))
.select(name => name.value)
}))
);
A specification takes one or more facts as parameters.
List the fact types in the given
function.
The match
function expects a callback that takes those facts as parameters.
const tasksInProjectAssignedToUser = model.given(Project, User).match((project, user) =>
// ...
);
Use the successors
method to produce a stream of related facts.
The first parameter of successors
is the type of the successor you are looking for.
The second is a callback that takes a successor fact and shows how it is connected to the first.
const tasksInProjectAssignedToUser = model.given(Project, User).match((project, user) =>
user.successors(Task, task => task.project)
// ...
);
Even though the method is called successors
, it can be also used to return predecessors or siblings.
To return predecessors, start from the predecessor and use the identity function.
const companyOfProject = model.given(Project).match(project =>
project.company.successors(Company, company => company)
// ...
);
Or to return siblings, start from their common predecessor.
const otherProjectsInCompany = model.given(Project).match(project =>
project.company.successors(Project, other => other.company)
// ...
);
Use the exists
method to filter the stream to only the facts that have a matching successor.
Pass a callback that takes the candidate fact.
The callback will use the fact repository to see if successors are present.
For example, to find all published posts, look for a successor of type PostPublished
.
const publishedPosts = model.given(Site).match(site =>
site.successors(Post, post => post.site)
.exists(post => post.successors(PostPublished, published => published.post))
// ...
);
Use the notExists
method to filter the stream to only the facts that do not have a matching successor.
It is common to use the notExists
method to filter out deleted facts.
const projectsForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.notExists(project => project.successors(ProjectDeleted, deleted => deleted.project))
// ...
);
It is also common to use notExists
to simulate mutable properties.
Define a fact type for the property.
Give that fact an array of prior
values.
class ProjectName {
static Type = 'Project.Name' as const;
type = ProjectName.Type;
constructor(
public project: Project,
public value: string,
public prior: ProjectName[]
) { }
}
Then, to find the current value, filter out the facts that have a newer value.
const namesOfProject = model.given(Project).match(project =>
project.successors(ProjectName, name => name.project)
.notExists(name => name.successors(ProjectName, next => next.prior))
// ...
);
Use the selectMany
function to bring in more facts.
Pass in a callback that takes a fact from the current stream.
Call successors
to bring in more facts.
const allProjectTasksForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.selectMany(project => project.successors(Task, task => task.project))
);
Use the select
function to project the results into your desired shape.
Pass in a callback that takes a fact from the current stream.
The callback can return results in a few specific ways.
To return the value of a field, access the field of a fact from a stream.
const projectNames = model.given(Project).match(project =>
project.successors(ProjectName, name => name.project)
.notExists(name => name.successors(ProjectName, next => next.prior))
.select(name => name.value)
);
To return the hash of a fact, use the j.hash
function.
const projectHashes = model.given(Project).match(project =>
project.successors(Project, project => project)
.select(project => j.hash(project))
);
To return a composite object, return an object from the callback.
You will usually do this inside of a selectMany
, where you have given names to several stream facts.
const projectHashesAndCompaniesForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.selectMany(project => project.company.successors(Company, company => company)
.select(company => ({
projectHash: j.hash(project),
company: company
}))
)
);
The values of the properties of the returned object can be fields, hashes, facts, or sub-specifications.
They cannot be predecessors.
If you want to return a predecessor fact in a property, you must use the selectMany
and successors
functions as shown above.
You cannot simply reference the predecessor while building the composite.
const projectHashesAndCompaniesForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.select(project => ({
projectHash: j.hash(project),
company: project.company // Incorrect.
}))
);
Within a composite, you can call the successors
function to begin a sub specification.
The sub specification will be evaluated for each fact in the current stream.
The result will be an array of those results.
const projectHashesAndCompaniesForUser = model.given(User).match(user =>
user.successors(Project, project => project.owner)
.select(project => ({
projectHash: j.hash(project),
names: project.successors(ProjectName, name => name.project)
.notExists(name => name.successors(ProjectName, next => next.prior))
.select(name => name.value)
}))
);