carthage
Version:
An API Server and Framework with scaffold functions for node.js
299 lines (207 loc) • 5.91 kB
JavaScript
'use strict';
const inflect = require('i')();
let __id__ = 0;
class RelationshipPath {
constructor(path) {
this.path = path;
}
toString() {
return this.path.join(' <-> ');
}
joinName(reverse) {
let path = [].slice.call(this.path);
if (reverse) {
path = path.reverse();
}
let names = [];
while (path.length > 1) {
let node = path.pop();
let edge = path.pop();
names.push(edge.hasChild(node) ? edge.options.name : edge.options.as);
}
return names.join('__');
}
add(node, edge) {
return new this.constructor([node, edge].concat(this.path));
}
getModel() {
return this.path[0].Model;
}
multiple() {
for (let i = 1; i < this.path.length; i += 2) {
let edge = this.path[i];
let node = this.path[i - 1];
if (edge.hasChild(node) && edge.options.multiple) {
return true;
}
}
return false;
}
immediateMultiple() {
let node = this.path[0];
let edge = this.path[1];
if (edge.hasChild(node) && edge.options.multiple) {
return true;
}
return false;
}
joins(alias, firstTable) {
let node;
let i = 0;
return this.path.slice().reverse().reduce((joins, item) => {
if (item instanceof RelationshipNode) {
node = item;
return joins;
}
let edge = item;
let join = {
joinTable: edge.opposite(node).Model.table(),
prevTable: joins[joins.length - 1] ? joins[joins.length - 1].joinAlias : (firstTable || null),
};
if (edge.hasChild(node)) {
join.prevColumn = edge.options.via;
join.joinColumn = edge.options.using;
join.joinAlias = edge.options.name;
} else {
join.prevColumn = edge.options.using;
join.joinColumn = edge.options.via;
join.joinAlias = edge.options.as;
}
join.joinAlias = alias ? `${alias}${++i}` : join.joinAlias;
joins.push(join);
return joins;
}, []);
}
}
class RelationshipNode {
constructor(Graph, Model) {
this.Graph = Graph;
this.Model = Model;
this.edges = [];
}
toString() {
return `[Node: ${this.Model.name}]`;
}
joinsTo(Model, options) {
if (!Model.name) {
// Sanity check for circular dependency resolution
return null;
}
options = options || {};
options.multiple = !!options.multiple;
options.as = options.as || (options.multiple ? `${inflect.pluralize(inflect.camelize(this.Model.name, false))}` : `${inflect.camelize(this.Model.name, false)}`);
options.name = options.name || `${inflect.camelize(Model.name, false)}`;
options.via = options.via || `${inflect.underscore(options.name)}_id`;
options.using = options.using || `id`;
let parentNode = this.Graph.of(Model);
let edge = this.edges.filter(e => e.parent === parentNode && e.options.name === options.name).pop();
if (!edge) {
edge = new RelationshipEdge(parentNode, this, options);
}
return edge;
}
childEdges() {
return this.edges.filter(edge => edge.parent === this);
}
cascade() {
let queue = this.childEdges();
let paths = queue.map(e => new RelationshipPath([e.child, e, e.parent]));
let i = 0;
while (queue.length) {
let edge = queue.shift();
let curPath = paths[i++];
let nextEdges = edge.child.childEdges();
queue = queue.concat(nextEdges);
paths = paths.concat(nextEdges.map(e => curPath.add(e.child, e)));
}
return paths;
}
findExplicit(pathname) {
let names = pathname.split('__');
let node = this;
let path = new RelationshipPath([node]);
while (names.length) {
let name = names.shift();
let edges = node.edges.filter(edge => {
return (edge.hasChild(node) && edge.options.name === name) || edge.options.as === name;
});
if (edges.length === 0) {
return null;
}
let edge = edges.pop();
let nextNode = edge.opposite(node);
path = path.add(nextNode, edge);
node = nextNode;
}
return path;
}
find(name) {
let queue = this.edges
.slice()
.map(edge => {
return {edge: edge, path: new RelationshipPath([this])}
});
let traversed = {};
while (queue.length) {
let item = queue[0];
let curEdge = item.edge;
let path = item.path;
let node;
traversed[curEdge.id] = true;
let curNode = path.path[0];
node = curEdge.opposite(curNode);
if ((curEdge.hasChild(curNode) && curEdge.options.name === name) || curEdge.options.as === name) {
return path.add(node, curEdge);
}
queue = queue.slice(1).concat(
node.edges
.filter(edge => !traversed[edge.id])
.map(edge => {
return {
edge: edge,
path: path.add(node, curEdge)
};
})
);
}
return null;
}
}
class RelationshipEdge {
constructor(parent, child, options) {
this.id = ++__id__;
this.parent = parent;
this.child = child;
this.options = options;
parent.edges.push(this);
child.edges.push(this);
}
toString() {
return `[Edge: ${this.parent.Model.name}, ${this.child.Model.name}]`;
}
hasChild(child) {
return this.child === child;
}
hasParent(parent) {
return this.parent === parent;
}
opposite(node) {
let opposite = this.child === node ? this.parent : (this.parent === node ? this.child : null);
return opposite;
}
}
class RelationshipGraph {
constructor() {
this.nodes = [];
this.edges = [];
}
of(Model) {
let node = this.nodes.filter(n => n.Model === Model).pop();
if (!node) {
node = new RelationshipNode(this, Model);
this.nodes.push(node);
}
return node;
}
}
module.exports = RelationshipGraph;