UNPKG

jinaga

Version:

Data management for web and mobile applications.

350 lines 15.1 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; /** * 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