jinaga
Version:
Data management for web and mobile applications.
422 lines • 15.4 kB
JavaScript
"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