UNPKG

jinaga

Version:

Data management for web and mobile applications.

310 lines 13.3 kB
"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; 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.type); } 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, label) => (Object.assign(Object.assign({}, labels), { [label.name]: label.type })), {}); 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 = [...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 allLabels = specification.given.concat(specification.matches.map(match => match.unknown)); const tailGiven = referencedLabels(tailMatches, allLabels); // Project the tail givens const headProjection = tailGiven.length === 1 ? { type: "fact", label: tailGiven[0].name } : { type: "composite", components: tailGiven.map(label => ({ type: "fact", label: 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 allLabels = specification.given .concat(specification.matches.map(match => match.unknown)) .concat([splitLabel]); const tailGiven = referencedLabels(tailMatches, allLabels); // Project the tail givens const headProjection = tailGiven.length === 1 ? { type: "fact", label: tailGiven[0].name } : { type: "composite", components: tailGiven.map(label => ({ type: "fact", label: 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.flatMap(labelsInMatch) .filter(label => definedLabels.indexOf(label) === -1); return labels.filter(label => referencedLabels.indexOf(label.name) !== -1); } function labelsInMatch(match) { return match.conditions.flatMap(labelsInCondition); } function labelsInCondition(condition) { if (condition.type === "path") { return [condition.labelRight]; } else if (condition.type === "existential") { return condition.matches.flatMap(labelsInMatch); } 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