jinaga
Version:
Data management for web and mobile applications.
350 lines • 15.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.reduceSpecification = exports.specificationIsIdentity = exports.splitBeforeFirstSuccessor = exports.specificationIsNotDeterministic = exports.specificationIsDeterministic = exports.getAllRoles = exports.getAllFactTypes = exports.emptySpecification = exports.isExistentialCondition = exports.isPathCondition = void 0;
function isPathCondition(condition) {
return condition.type === "path";
}
exports.isPathCondition = isPathCondition;
/**
* Type guard that checks if the given condition is an existential condition.
* @param condition The condition to check.
* @returns True if the condition is an ExistentialCondition, false otherwise.
* @example
* const condition: Condition = { type: "existential", exists: true, matches: [] };
* if (isExistentialCondition(condition)) {
* // condition is now typed as ExistentialCondition
* console.log(condition.exists);
* }
*/
function isExistentialCondition(condition) {
return condition.type === "existential";
}
exports.isExistentialCondition = isExistentialCondition;
exports.emptySpecification = {
given: [],
matches: [],
projection: { type: "composite", components: [] }
};
function getAllFactTypes(specification) {
const factTypes = [];
for (const given of specification.given) {
factTypes.push(given.label.type);
// Add fact types from existential conditions on givens
for (const condition of given.conditions) {
factTypes.push(...getAllFactTypesFromMatches(condition.matches));
}
}
factTypes.push(...getAllFactTypesFromMatches(specification.matches));
if (specification.projection.type === "composite") {
factTypes.push(...getAllFactTypesFromProjection(specification.projection));
}
const distinctFactTypes = Array.from(new Set(factTypes));
return distinctFactTypes;
}
exports.getAllFactTypes = getAllFactTypes;
function getAllFactTypesFromMatches(matches) {
const factTypes = [];
for (const match of matches) {
factTypes.push(match.unknown.type);
for (const condition of match.conditions) {
if (condition.type === "path") {
for (const role of condition.rolesLeft) {
factTypes.push(role.predecessorType);
}
}
else if (condition.type === "existential") {
factTypes.push(...getAllFactTypesFromMatches(condition.matches));
}
}
}
return factTypes;
}
function getAllFactTypesFromProjection(projection) {
const factTypes = [];
for (const component of projection.components) {
if (component.type === "specification") {
factTypes.push(...getAllFactTypesFromMatches(component.matches));
if (component.projection.type === "composite") {
factTypes.push(...getAllFactTypesFromProjection(component.projection));
}
}
}
return factTypes;
}
function getAllRoles(specification) {
const labels = specification.given
.reduce((labels, given) => (Object.assign(Object.assign({}, labels), { [given.label.name]: given.label.type })), {});
// Collect roles from existential conditions on givens
let rolesFromGivenConditions = [];
for (const given of specification.given) {
for (const condition of given.conditions) {
const { roles } = getAllRolesFromMatches(labels, condition.matches);
rolesFromGivenConditions.push(...roles);
}
}
const { roles: rolesFromMatches, labels: labelsFromMatches } = getAllRolesFromMatches(labels, specification.matches);
const components = specification.projection.type === "composite" ? specification.projection.components : [];
const rolesFromComponents = getAllRolesFromComponents(labelsFromMatches, components);
const roles = [...rolesFromGivenConditions, ...rolesFromMatches, ...rolesFromComponents];
const distinctRoles = roles.filter((value, index, array) => {
return array.findIndex(r => r.successorType === value.successorType &&
r.name === value.name) === index;
});
return distinctRoles;
}
exports.getAllRoles = getAllRoles;
function getAllRolesFromMatches(labels, matches) {
const roles = [];
for (const match of matches) {
labels = Object.assign(Object.assign({}, labels), { [match.unknown.name]: match.unknown.type });
for (const condition of match.conditions) {
if (condition.type === "path") {
let type = match.unknown.type;
for (const role of condition.rolesLeft) {
roles.push({ successorType: type, name: role.name, predecessorType: role.predecessorType });
type = role.predecessorType;
}
type = labels[condition.labelRight];
if (!type) {
throw new Error(`Label ${condition.labelRight} not found`);
}
for (const role of condition.rolesRight) {
roles.push({ successorType: type, name: role.name, predecessorType: role.predecessorType });
type = role.predecessorType;
}
}
else if (condition.type === "existential") {
const { roles: newRoleDescriptions } = getAllRolesFromMatches(labels, condition.matches);
roles.push(...newRoleDescriptions);
}
}
}
return { roles, labels };
}
function getAllRolesFromComponents(labels, components) {
const roles = [];
for (const component of components) {
if (component.type === "specification") {
const { roles: rolesFromMatches, labels: labelsFromMatches } = getAllRolesFromMatches(labels, component.matches);
roles.push(...rolesFromMatches);
if (component.projection.type === "composite") {
roles.push(...getAllRolesFromComponents(labelsFromMatches, component.projection.components));
}
}
}
return roles;
}
function specificationIsDeterministic(specification) {
return specification.matches.every(match => match.conditions.every(condition => condition.type === "path" &&
condition.rolesLeft.length === 0));
}
exports.specificationIsDeterministic = specificationIsDeterministic;
function specificationIsNotDeterministic(specification) {
return specification.matches.some(match => match.conditions.some(condition => condition.type === "path" &&
condition.rolesLeft.length > 0));
}
exports.specificationIsNotDeterministic = specificationIsNotDeterministic;
function splitBeforeFirstSuccessor(specification) {
// Find the first match (if any) that seeks successors or has an existential condition
const firstMatchWithSuccessor = specification.matches.findIndex(match => match.conditions.length !== 1 || match.conditions.some(condition => condition.type !== "path" || condition.rolesLeft.length > 0));
if (firstMatchWithSuccessor === -1) {
// No match seeks successors, so the whole specification is deterministic
return {
head: specification,
tail: undefined
};
}
else {
// If there is only a single path condition, then split that path.
const pivot = specification.matches[firstMatchWithSuccessor];
const pathConditions = pivot.conditions.filter(isPathCondition);
if (pathConditions.length !== 1) {
// Fall back to running the entire specification in the tail
return {
head: undefined,
tail: specification
};
}
const existentialConditions = pivot.conditions.filter(isExistentialCondition);
const condition = pathConditions[0];
if (condition.rolesRight.length === 0) {
// The path contains only successor joins.
// Put the entire match in the tail.
if (firstMatchWithSuccessor === 0) {
// There is nothing to put in the head
return {
head: undefined,
tail: specification
};
}
else {
// Split the matches between the head and tail
const headMatches = specification.matches.slice(0, firstMatchWithSuccessor);
const tailMatches = specification.matches.slice(firstMatchWithSuccessor);
// Compute the givens of the head and tail
const headGiven = referencedLabels(headMatches, specification.given);
const unknownAsGiven = specification.matches.map(match => ({
label: { name: match.unknown.name, type: match.unknown.type },
conditions: []
}));
const allLabels = specification.given.concat(unknownAsGiven);
const tailGiven = referencedLabels(tailMatches, allLabels);
// Project the tail givens
const headProjection = tailGiven.length === 1 ?
{ type: "fact", label: tailGiven[0].label.name } :
{ type: "composite", components: tailGiven.map(given => ({
type: "fact",
name: given.label.name,
label: given.label.name
})) };
const head = {
given: headGiven,
matches: headMatches,
projection: headProjection
};
const tail = {
given: tailGiven,
matches: tailMatches,
projection: specification.projection
};
return {
head,
tail
};
}
}
else {
// The path contains both predecessor and successor joins.
// Split the path into two paths.
const splitLabel = {
name: 's1',
type: condition.rolesRight[condition.rolesRight.length - 1].predecessorType
};
const headCondition = {
type: "path",
labelRight: condition.labelRight,
rolesLeft: [],
rolesRight: condition.rolesRight
};
const headMatch = {
unknown: splitLabel,
conditions: [headCondition]
};
const tailCondition = {
type: "path",
labelRight: splitLabel.name,
rolesLeft: condition.rolesLeft,
rolesRight: []
};
const tailMatch = {
unknown: pivot.unknown,
conditions: [tailCondition, ...existentialConditions]
};
// Assemble the head and tail matches
const headMatches = specification.matches.slice(0, firstMatchWithSuccessor).concat(headMatch);
const tailMatches = [tailMatch].concat(specification.matches.slice(firstMatchWithSuccessor + 1));
// Compute the givens of the head and tail
const headGiven = referencedLabels(headMatches, specification.given);
const unknownAsGiven = specification.matches.map(match => ({
label: { name: match.unknown.name, type: match.unknown.type },
conditions: []
}));
const allLabels = specification.given
.concat(unknownAsGiven)
.concat([{ label: { name: splitLabel.name, type: splitLabel.type }, conditions: [] }]);
const tailGiven = referencedLabels(tailMatches, allLabels);
// Project the tail givens
const headProjection = tailGiven.length === 1 ?
{ type: "fact", label: tailGiven[0].label.name } :
{ type: "composite", components: tailGiven.map(given => ({
type: "fact",
name: given.label.name,
label: given.label.name
})) };
const head = {
given: headGiven,
matches: headMatches,
projection: headProjection
};
const tail = {
given: tailGiven,
matches: tailMatches,
projection: specification.projection
};
return {
head,
tail
};
}
}
}
exports.splitBeforeFirstSuccessor = splitBeforeFirstSuccessor;
function referencedLabels(matches, labels) {
// Find all labels referenced in the matches
const definedLabels = matches.map(match => match.unknown.name);
const referencedLabels = matches.map(labelsInMatch).reduce((acc, val) => acc.concat(val), [])
.filter(label => definedLabels.indexOf(label) === -1);
return labels
.filter(given => referencedLabels.indexOf(given.label.name) !== -1);
}
function labelsInMatch(match) {
return match.conditions.map(labelsInCondition).reduce((acc, val) => acc.concat(val), []);
}
function labelsInCondition(condition) {
if (condition.type === "path") {
return [condition.labelRight];
}
else if (condition.type === "existential") {
return condition.matches.map(labelsInMatch).reduce((acc, val) => acc.concat(val), []);
}
else {
const _exhaustiveCheck = condition;
throw new Error(`Unexpected condition type ${_exhaustiveCheck.type}`);
}
}
function specificationIsIdentity(specification) {
return specification.matches.every(match => match.conditions.every(condition => condition.type === "path" &&
condition.rolesLeft.length === 0 &&
condition.rolesRight.length === 0));
}
exports.specificationIsIdentity = specificationIsIdentity;
function reduceSpecification(specification) {
// Remove all projections except for specification projections.
return {
given: specification.given,
matches: specification.matches,
projection: reduceProjection(specification.projection)
};
}
exports.reduceSpecification = reduceSpecification;
function reduceProjection(projection) {
if (projection.type === "composite") {
const reducedComponents = projection.components
.map(reduceComponent)
.filter((component) => component !== null);
return {
type: "composite",
components: reducedComponents
};
}
else {
return {
type: "composite",
components: []
};
}
}
function reduceComponent(component) {
if (component.type === "specification") {
return {
type: "specification",
name: component.name,
matches: component.matches,
projection: reduceProjection(component.projection)
};
}
else {
return null;
}
}
//# sourceMappingURL=specification.js.map