UNPKG

@mikro-orm/core

Version:

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.

112 lines (111 loc) 4.33 kB
import { ReferenceKind } from '../enums.js'; export var NodeState; (function (NodeState) { NodeState[NodeState["NOT_VISITED"] = 0] = "NOT_VISITED"; NodeState[NodeState["IN_PROGRESS"] = 1] = "IN_PROGRESS"; NodeState[NodeState["VISITED"] = 2] = "VISITED"; })(NodeState || (NodeState = {})); /** * CommitOrderCalculator implements topological sorting, which is an ordering * algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by * using a depth-first searching (DFS) to traverse the graph built in memory. * This algorithm have a linear running time based on nodes (V) and dependency * between the nodes (E), resulting in a computational complexity of O(V + E). * * Based on https://github.com/doctrine/orm/blob/master/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php * @internal */ export class CommitOrderCalculator { /** Matrix of nodes, keys are provided hashes and values are the node definition objects. */ #nodes = new Map(); /** Volatile variable holding calculated nodes during sorting process. */ #sortedNodeList = []; /** * Checks for node existence in graph. */ hasNode(hash) { return this.#nodes.has(hash); } /** * Adds a new node to the graph, assigning its hash. */ addNode(hash) { this.#nodes.set(hash, { hash, state: 0 /* NodeState.NOT_VISITED */, dependencies: new Map() }); } /** * Adds a new dependency (edge) to the graph using their hashes. */ addDependency(from, to, weight) { this.#nodes.get(from).dependencies.set(to, { from, to, weight }); } discoverProperty(prop, entityName) { const toOneOwner = (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) || prop.kind === ReferenceKind.MANY_TO_ONE; const toManyOwner = prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !prop.pivotEntity; if (!toOneOwner && !toManyOwner) { return; } const propertyType = prop.targetMeta?.root._id; if (propertyType == null || !this.hasNode(propertyType)) { return; } this.addDependency(propertyType, entityName, prop.nullable || prop.persist === false ? 0 : 1); } /** * Return a valid order list of all current nodes. * The desired topological sorting is the reverse post order of these searches. * * @internal Highly performance-sensitive method. */ sort() { for (const vertex of this.#nodes.values()) { if (vertex.state !== 0 /* NodeState.NOT_VISITED */) { continue; } this.visit(vertex); } const sortedList = this.#sortedNodeList.reverse(); this.#nodes = new Map(); this.#sortedNodeList = []; return sortedList; } /** * Visit a given node definition for reordering. * * @internal Highly performance-sensitive method. */ visit(node) { node.state = 1 /* NodeState.IN_PROGRESS */; for (const edge of node.dependencies.values()) { const target = this.#nodes.get(edge.to); switch (target.state) { case 2 /* NodeState.VISITED */: break; // Do nothing, since node was already visited case 1 /* NodeState.IN_PROGRESS */: this.visitOpenNode(node, target, edge); break; case 0 /* NodeState.NOT_VISITED */: this.visit(target); } } if (node.state !== 2 /* NodeState.VISITED */) { node.state = 2 /* NodeState.VISITED */; this.#sortedNodeList.push(node.hash); } } /** * Visits all target's dependencies if in cycle with given node */ visitOpenNode(node, target, edge) { if (!target.dependencies.has(node.hash) || target.dependencies.get(node.hash).weight >= edge.weight) { return; } for (const edge of target.dependencies.values()) { const targetNode = this.#nodes.get(edge.to); if (targetNode.state === 0 /* NodeState.NOT_VISITED */) { this.visit(targetNode); } } target.state = 2 /* NodeState.VISITED */; this.#sortedNodeList.push(target.hash); } }