UNPKG

jinaga

Version:

Data management for web and mobile applications.

239 lines 11.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DistributionEngine = void 0; const description_1 = require("../specification/description"); const skeleton_1 = require("../specification/skeleton"); const specification_1 = require("../specification/specification"); const storage_1 = require("../storage"); class DistributionEngine { constructor(distributionRules, store) { this.distributionRules = distributionRules; this.store = store; } canDistributeToAll(targetFeeds, namedStart, user) { return __awaiter(this, void 0, void 0, function* () { // TODO: Minimize the number hits to the database. const reasons = []; for (const targetFeed of targetFeeds) { const feedResult = yield this.canDistributeTo(targetFeed, namedStart, user); if (feedResult.type === 'failure') { reasons.push(feedResult.reason); } } if (reasons.length > 0) { return { type: 'failure', reason: reasons.join('\n\n') }; } else { return { type: 'success' }; } }); } canDistributeTo(targetFeed, namedStart, user) { return __awaiter(this, void 0, void 0, function* () { const start = targetFeed.given.map(g => namedStart[g.name]); const targetSkeleton = (0, skeleton_1.skeletonOfSpecification)(targetFeed); const reasons = []; for (const rule of this.distributionRules.rules) { for (const ruleFeed of rule.feeds) { const ruleSkeleton = (0, skeleton_1.skeletonOfSpecification)(ruleFeed); const permutations = permutationsOf(start, ruleSkeleton, targetSkeleton); for (const permutation of permutations) { if (skeletonsEqual(ruleSkeleton, targetSkeleton)) { // If this rule applies to any user, then we can distribute. if (rule.user === null) { return { type: 'success' }; } // If there is no user logged in, then we cannot distribute. if (user === null) { if (reasons.length === 0) { reasons.push(`User is not logged in.`); } } else { // The projection must be a singular label. if (rule.user.projection.type !== 'fact') { throw new Error('The projection must be a singular label.'); } const label = rule.user.projection.label; // If the user specification is deterministic, then pick the labeled given. if ((0, specification_1.specificationIsIdentity)(rule.user)) { const userReference = executeDeterministicSpecification(rule.user, label, permutation); // If the user matches the given, then we can distribute to the user. const authorized = (0, storage_1.factReferenceEquals)(user)(userReference); if (authorized) { return { type: 'success' }; } } else { // Find the set of users to whom we can distribute this feed. const users = yield this.store.read(permutation, rule.user); const results = users.map(user => user.tuple[label]); // If any of the results match the user, then we can distribute to the user. const authorized = results.some((0, storage_1.factReferenceEquals)(user)); if (authorized) { return { type: 'success' }; } } reasons.push(`The user does not match ${(0, description_1.describeSpecification)(rule.user, 0)}`); } } } } } if (reasons.length === 0) { reasons.push("No rules apply to this feed."); } return { type: 'failure', reason: `Cannot distribute to ${(0, description_1.describeSpecification)(targetFeed, 0)}${reasons.join('\n')}` }; function executeDeterministicSpecification(specification, label, permutation) { // If the label is a given, then return the associated fact reference. const givenIndex = specification.given.findIndex(g => g.name === label); if (givenIndex !== -1) { const userReference = permutation[givenIndex]; return userReference; } // Find the match with the unknown matching the projected label. const match = specification.matches.find(m => m.unknown.name === label); if (!match) { throw new Error(`The user specification must have a match with an unknown labeled '${label}'.`); } // Find the right-hand side of the path condition in that match. const referencedLabels = match.conditions .filter(specification_1.isPathCondition) .map(c => c.labelRight); if (referencedLabels.length !== 1) { throw new Error(`The user specification must have exactly one path condition with an unknown labeled '${label}'.`); } const referencedLabel = referencedLabels[0]; // Find the given that the match references. const index = specification.given.findIndex(g => g.name === referencedLabel); if (index === -1) { throw new Error(`The user specification must have a given labeled '${label}'.`); } const userReference = permutation[index]; return userReference; } }); } } exports.DistributionEngine = DistributionEngine; function skeletonsEqual(ruleSkeleton, targetSkeleton) { // Compare the sets of facts. if (!compareSets(ruleSkeleton.facts, targetSkeleton.facts, factsEqual)) { return false; } // Do not compare the sets of inputs. // The matching permutation has already been calculated. // Compare the sets of edges. if (!compareSets(ruleSkeleton.edges, targetSkeleton.edges, edgesEqual)) { return false; } // Compare the sets of not-exists conditions. if (!compareSets(ruleSkeleton.notExistsConditions, targetSkeleton.notExistsConditions, notExistsConditionsEqual)) { return false; } // Do not compare the sets of outputs. // They don't affect the rows that match the specification. return true; } function factsEqual(ruleFact, targetFact) { if (ruleFact.factType !== targetFact.factType) { return false; } if (ruleFact.factIndex !== targetFact.factIndex) { return false; } return true; } function edgesEqual(ruleEdge, targetEdge) { if (ruleEdge.edgeIndex !== targetEdge.edgeIndex) { return false; } if (ruleEdge.predecessorFactIndex !== targetEdge.predecessorFactIndex) { return false; } if (ruleEdge.successorFactIndex !== targetEdge.successorFactIndex) { return false; } if (ruleEdge.roleName !== targetEdge.roleName) { return false; } return true; } function notExistsConditionsEqual(ruleCondition, targetCondition) { if (!compareSets(ruleCondition.edges, targetCondition.edges, edgesEqual)) { return false; } // Do not compare nested existential conditions. // These are not executed while generating feeds. return true; } function compareSets(a, b, equals) { if (a.length !== b.length) { return false; } for (const item of a) { if (!b.some(bItem => equals(item, bItem))) { return false; } } return true; } function permutationsOf(start, ruleSkeleton, targetSkeleton) { function permute(ruleInputs, targetInputs) { // If there are no more rule inputs, then end the recursion. // We have found one matching permutation. if (ruleInputs.length === 0) { return [[]]; } const [ruleInput, ...remainingRuleInputs] = ruleInputs; const ruleFact = ruleSkeleton.facts.find(f => f.factIndex === ruleInput.factIndex); if (!ruleFact) { throw new Error(`Rule fact index ${ruleInput.factIndex} was not found.`); } // Find all of the target inputs that match the first rule input. return targetInputs.flatMap((targetInput, targetIndex) => { const targetFact = targetSkeleton.facts.find(f => f.factIndex === targetInput.factIndex); if (!targetFact) { throw new Error(`Target fact index ${targetInput.factIndex} was not found.`); } if (targetFact.factType !== ruleFact.factType) { return []; } // Remove the target input from the set of candidates. const remainingTargetInputs = targetInputs.slice(0, targetIndex) .concat(targetInputs.slice(targetIndex + 1)); // Recursively find all permutations of the remainder. return permute(remainingRuleInputs, remainingTargetInputs) .map(permutation => { permutation[ruleInput.inputIndex] = start[targetInput.inputIndex]; return permutation; }); }); } const permutations = permute(ruleSkeleton.inputs, targetSkeleton.inputs); return permutations; } //# sourceMappingURL=distribution-engine.js.map