UNPKG

jinaga

Version:

Data management for web and mobile applications.

422 lines 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPayload = exports.FactRepository = exports.Traversal = exports.SpecificationOf = exports.Model = exports.buildModel = exports.ModelBuilder = void 0; const hydrate_1 = require("../fact/hydrate"); const description_1 = require("./description"); class FactOptions { constructor(factTypeByRole) { this.factTypeByRole = factTypeByRole; } predecessor(role, predecessorConstructor) { return new FactOptions(Object.assign(Object.assign({}, this.factTypeByRole), { [role]: predecessorConstructor.Type })); } } class ModelBuilder { constructor(factTypeMap = {}) { this.factTypeMap = factTypeMap; } type(factConstructor, options) { if (options) { const factOptions = options(new FactOptions({})); return new ModelBuilder(Object.assign(Object.assign({}, this.factTypeMap), { [factConstructor.Type]: factOptions.factTypeByRole })); } else { return new ModelBuilder(Object.assign(Object.assign({}, this.factTypeMap), { [factConstructor.Type]: {} })); } } with(types) { return types(this); } } exports.ModelBuilder = ModelBuilder; function buildModel(types) { return new Model(types(new ModelBuilder()).factTypeMap); } exports.buildModel = buildModel; class Model { constructor(factTypeMap = {}) { this.factTypeMap = factTypeMap; } given(...factConstructors) { return new Given(factConstructors.map(c => c.Type), this.factTypeMap); } } exports.Model = Model; class SpecificationOf { constructor(specification) { this.specification = specification; } toDescriptiveString(depth) { return (0, description_1.describeSpecification)(this.specification, depth); } } exports.SpecificationOf = SpecificationOf; class Given { constructor(factTypes, factTypeMap) { this.factTypes = factTypes; this.factTypeMap = factTypeMap; } match(definition) { var _a; const factRepository = new FactRepository(this.factTypeMap); const labels = this.factTypes.map((factType, i) => { const name = `p${i + 1}`; return createFactProxy(factRepository, this.factTypeMap, name, [], factType); }); const result = definition(...labels, factRepository); const matches = (_a = result.matches) !== null && _a !== void 0 ? _a : []; const projection = result.projection; const given = this.factTypes.map((type, i) => { const name = `p${i + 1}`; return { name, type }; }); const specification = { given, matches, projection }; return new SpecificationOf(specification); } select(selector) { const factRepository = new FactRepository(this.factTypeMap); const labels = this.factTypes.map((factType, i) => { const name = `p${i + 1}`; return createFactProxy(factRepository, this.factTypeMap, name, [], factType); }); const result = selector(...labels, factRepository); const matches = []; const traversal = traversalFromDefinition(result, matches); const projection = traversal.projection; const given = this.factTypes.map((type, i) => { const name = `p${i + 1}`; return { name, type }; }); const specification = { given, matches, projection }; return new SpecificationOf(specification); } } class Traversal { constructor(input, matches, projection) { this.input = input; this.matches = matches; this.projection = projection; } join(left, right) { const leftResult = left(this.input); const payloadLeft = getPayload(leftResult); const payloadRight = getPayload(right); const condition = joinCondition(payloadLeft, payloadRight); const matches = this.withCondition(condition); return new Traversal(this.input, matches, this.projection); } notExists(tupleDefinition) { return this.existentialCondition(tupleDefinition, false); } exists(tupleDefinition) { return this.existentialCondition(tupleDefinition, true); } existentialCondition(tupleDefinition, exists) { const result = tupleDefinition(this.input); const existentialCondition = { type: "existential", exists, matches: result.matches }; const matches = this.withCondition(existentialCondition); return new Traversal(this.input, matches, this.projection); } withCondition(condition) { if (this.matches.length === 0) { throw new Error("Cannot add a condition without declaring an unknown."); } const lastMatch = this.matches[this.matches.length - 1]; const conditions = [ ...lastMatch.conditions, condition ]; const matches = [ ...this.matches.slice(0, this.matches.length - 1), Object.assign(Object.assign({}, lastMatch), { conditions }) ]; return matches; } select(selector) { const definition = selector(this.input); const matches = this.matches; return traversalFromDefinition(definition, matches); } selectMany(selector) { const traversal = selector(this.input); const matches = [ ...this.matches, ...traversal.matches ]; const projection = traversal.projection; return new Traversal(traversal.input, matches, projection); } } exports.Traversal = Traversal; class FactRepository { constructor(factTypeMap) { this.factTypeMap = factTypeMap; this.unknownIndex = 1; } ofType(factConstructor) { const name = `u${this.unknownIndex++}`; return new Source(this, this.factTypeMap, name, factConstructor.Type); } } exports.FactRepository = FactRepository; class Source { constructor(factRepository, factTypeMap, name, factType) { this.factRepository = factRepository; this.factTypeMap = factTypeMap; this.name = name; this.factType = factType; } join(left, right) { const unknown = createFactProxy(this.factRepository, this.factTypeMap, this.name, [], this.factType); const ancestor = left(unknown); const payloadLeft = getPayload(ancestor); const payloadRight = getPayload(right); if (payloadLeft.root !== this.name) { throw new Error("The left side must be based on the source"); } const condition = joinCondition(payloadLeft, payloadRight); const match = { unknown: { name: this.name, type: this.factType }, conditions: [ condition ] }; const projection = { type: "fact", label: this.name }; return new Traversal(unknown, [match], projection); } } const IDENTITY = Symbol('proxy_target_identity'); function joinCondition(payloadLeft, payloadRight) { if (payloadLeft.type === "field") { throw new Error(`The property "${payloadLeft.fieldName}" is not defined to be a predecessor, and is therefore interpreted as a field. ` + `A field cannot be used in a join.`); } if (payloadRight.type === "field") { throw new Error(`The property "${payloadRight.fieldName}" is not defined to be a predecessor, and is therefore interpreted as a field. ` + `A field cannot be used in a join.`); } if (payloadLeft.type === "hash") { throw new Error("You cannot join on a hash."); } if (payloadRight.type === "hash") { throw new Error("You cannot join on a hash."); } if (payloadLeft.factType !== payloadRight.factType) { throw new Error(`Cannot join "${payloadLeft.factType}" with "${payloadRight.factType}"`); } const condition = { type: "path", rolesLeft: payloadLeft.path, labelRight: payloadRight.root, rolesRight: payloadRight.path }; return condition; } function createFactProxy(factRepository, factTypeMap, root, path, factType) { const payload = { type: "fact", root, path, factType }; return new Proxy(payload, { get: (target, property) => { if (property === IDENTITY) { return target; } if (property === hydrate_1.hashSymbol) { if (target.path.length > 0) { throw new Error(`You cannot hash the fact "${target.path[target.path.length - 1].name}" directly. ` + `You must label it first.`); } return createHashProxy(target.root); } if (property === "successors") { return (type, selector) => { const unknown = factRepository.ofType(type); return unknown.join(selector, target); }; } else if (property === "predecessor") { return () => { const unknown = factRepository.ofType({ Type: target.factType }); return unknown.join((input) => input, target); }; } const role = property.toString(); const predecessorType = lookupRoleType(factTypeMap, target.factType, role); if (predecessorType) { const path = [...target.path, { name: role, predecessorType }]; return createFactProxy(factRepository, factTypeMap, target.root, path, predecessorType); } else { if (target.path.length > 0) { throw new Error(`The property "${role}" is not defined to be a predecessor, and is therefore interpreted as a field. ` + `You cannot access a field of the predecessor "${target.path[target.path.length - 1].name}". ` + `If you want the field of a predecessor, you need to label the predecessor first.`); } return createFieldProxy(target.root, role); } } }); } function createFieldProxy(root, fieldName) { const payload = { type: "field", root, fieldName }; return new Proxy(payload, { get: (target, property) => { if (property === IDENTITY) { return target; } throw new Error(`The property "${property.toString()}" is not defined to be a predecessor, and is therefore interpreted as a field. ` + `You cannot operate on a field within a specification.`); } }); } function createHashProxy(root) { const payload = { type: "hash", root }; return new Proxy(payload, { get: (target, property) => { if (property === IDENTITY) { return target; } throw new Error(`The property "${property.toString()}" is not defined on a hash.`); } }); } function getPayload(label) { const proxy = label; return proxy[IDENTITY] || proxy; } exports.getPayload = getPayload; function isLabel(value) { return value[IDENTITY] !== undefined; } function lookupRoleType(factTypeMap, factType, role) { const roleMap = factTypeMap[factType]; if (!roleMap) { throw new Error(`Unknown fact type "${factType}"`); } const roleType = roleMap[role]; if (!roleType) { return undefined; } return roleType; } function traversalFromDefinition(definition, matches) { if (isLabel(definition)) { const payload = getPayload(definition); if (payload.type === "field") { const fieldProjection = { type: "field", label: payload.root, field: payload.fieldName }; return new Traversal(definition, matches, fieldProjection); } else if (payload.type === "fact") { if (payload.path.length > 0) { throw new Error(`Cannot select ${payload.root}.${payload.path.join(".")} directly. Give the fact a label first.`); } const factProjection = { type: "fact", label: payload.root, }; return new Traversal(definition, matches, factProjection); } else if (payload.type === "hash") { const hashProjection = { type: "hash", label: payload.root }; return new Traversal(definition, matches, hashProjection); } else { const _exhaustiveCheck = payload; throw new Error(`Unexpected payload type: ${_exhaustiveCheck.type}`); } } else { const components = Object.getOwnPropertyNames(definition).map((key) => { const child = definition[key]; if (isLabel(child)) { const payload = getPayload(child); if (payload.type === "field") { const projection = { type: "field", name: key, label: payload.root, field: payload.fieldName }; return projection; } else if (payload.type === "fact") { if (payload.path.length > 0) { throw new Error(`You cannot project the fact "${payload.path[payload.path.length - 1].name}" directly. ` + `You must label it first.`); } const projection = { type: "fact", name: key, label: payload.root }; return projection; } else if (payload.type === "hash") { const projection = { type: "hash", name: key, label: payload.root }; return projection; } else { const _exhaustiveCheck = payload; throw new Error(`Unexpected payload type: ${_exhaustiveCheck.type}`); } } else if (child instanceof Traversal) { const projection = { type: "specification", name: key, matches: child.matches, projection: child.projection }; return projection; } else { throw new Error(`Unexpected type for property ${key}: ${typeof child}`); } }); const compositeProjection = { type: "composite", components }; return new Traversal(definition, matches, compositeProjection); } } //# sourceMappingURL=model.js.map