Graph
Algorithms
Find the shortest path between two nodes using A* pathfinding algorithm.
A* is an extension of Dijkstra's algorithm that uses a heuristic function to guide the search towards the target, potentially finding paths faster than Dijkstra's. The heuristic must be admissible (never overestimate the actual cost).
Signature
declare function astar<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: AstarConfig<E, N>): Option<PathResult<E>>Example
import { Graph, Option } from "effect"
const graph = Graph.directed<{x: number, y: number}, number>((mutable) => { const a = Graph.addNode(mutable, {x: 0, y: 0}) const b = Graph.addNode(mutable, {x: 1, y: 0}) const c = Graph.addNode(mutable, {x: 2, y: 0}) Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 1)})
// Manhattan distance heuristicconst heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) => Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })if (Option.isSome(result)) { console.log(result.value.path) // [0, 1, 2] - shortest path console.log(result.value.distance) // 2 - total distance}bellmanFord
Find the shortest path between two nodes using Bellman-Ford algorithm.
Bellman-Ford algorithm can handle negative edge weights and detects negative cycles. It has O(VE) time complexity, slower than Dijkstra's but more versatile. Returns Option.none() if a negative cycle is detected that affects the path.
Signature
declare function bellmanFord<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: BellmanFordConfig<E>): Option<PathResult<E>>Example
import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, -1) // Negative weight allowed Graph.addEdge(mutable, b, c, 3) Graph.addEdge(mutable, a, c, 5)})
const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })if (Option.isSome(result)) { console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C console.log(result.value.distance) // 2 - total distance}connectedComponents
Find connected components in an undirected graph. Each component is represented as an array of node indices.
Signature
declare function connectedComponents<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<Array<number>>Example
import { Graph } from "effect"
const graph = Graph.undirected<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") const d = Graph.addNode(mutable, "D") Graph.addEdge(mutable, a, b, "edge") // Component 1: A-B Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D})
const components = Graph.connectedComponents(graph)console.log(components) // [[0, 1], [2, 3]]Find the shortest path between two nodes using Dijkstra's algorithm.
Dijkstra's algorithm works with non-negative edge weights and finds the shortest path from a source node to a target node in O((V + E) log V) time complexity.
Signature
declare function dijkstra<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: DijkstraConfig<E>): Option<PathResult<E>>Example
import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 5) Graph.addEdge(mutable, a, c, 10) Graph.addEdge(mutable, b, c, 2)})
const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })if (Option.isSome(result)) { console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C console.log(result.value.distance) // 7 - total distance}floydWarshall
Find shortest paths between all pairs of nodes using Floyd-Warshall algorithm.
Floyd-Warshall algorithm computes shortest paths between all pairs of nodes in O(V³) time. It can handle negative edge weights and detect negative cycles.
Signature
declare function floydWarshall<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, cost: (edgeData: E) => number): AllPairsResult<E>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 3) Graph.addEdge(mutable, b, c, 2) Graph.addEdge(mutable, a, c, 7)})
const result = Graph.floydWarshall(graph, (edgeData) => edgeData)const distanceAToC = result.distances.get(0)?.get(2) // 5 (A->B->C)const pathAToC = result.paths.get(0)?.get(2) // [0, 1, 2]Checks if the graph is acyclic (contains no cycles).
Uses depth-first search to detect back edges, which indicate cycles. For directed graphs, any back edge creates a cycle. For undirected graphs, a back edge that doesn't go to the immediate parent creates a cycle.
Signature
declare function isAcyclic<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): booleanExample
import { Graph } from "effect"
// Acyclic directed graph (DAG)const dag = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, "A->B") Graph.addEdge(mutable, b, c, "B->C")})console.log(Graph.isAcyclic(dag)) // true
// Cyclic directed graphconst cyclic = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, "A->B") Graph.addEdge(mutable, b, a, "B->A") // Creates cycle})console.log(Graph.isAcyclic(cyclic)) // falseisBipartite
Checks if an undirected graph is bipartite.
A bipartite graph is one whose vertices can be divided into two disjoint sets such that no two vertices within the same set are adjacent. Uses BFS coloring to determine bipartiteness.
Signature
declare function isBipartite<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): booleanExample
import { Graph } from "effect"
// Bipartite graph (alternating coloring possible)const bipartite = Graph.undirected<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") const d = Graph.addNode(mutable, "D") Graph.addEdge(mutable, a, b, "edge") // Set 1: {A, C}, Set 2: {B, D} Graph.addEdge(mutable, b, c, "edge") Graph.addEdge(mutable, c, d, "edge")})console.log(Graph.isBipartite(bipartite)) // true
// Non-bipartite graph (odd cycle)const triangle = Graph.undirected<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, "edge") Graph.addEdge(mutable, b, c, "edge") Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle)})console.log(Graph.isBipartite(triangle)) // falsestronglyConnectedComponents
Find strongly connected components in a directed graph using Kosaraju's algorithm. Each SCC is represented as an array of node indices.
Signature
declare function stronglyConnectedComponents<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): Array<Array<number>>Example
import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, "A->B") Graph.addEdge(mutable, b, c, "B->C") Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C})
const sccs = Graph.stronglyConnectedComponents(graph)console.log(sccs) // [[0, 1, 2]]Constructors
Creates a directed graph, optionally with initial mutations.
Signature
declare function directed<N, E>(mutate?: (mutable: MutableDirectedGraph<N, E>) => void): DirectedGraph<N, E>Example
import { Graph } from "effect"
// Directed graph with initial nodes and edgesconst graph = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, "A->B") Graph.addEdge(mutable, b, c, "B->C")})undirected
Creates an undirected graph, optionally with initial mutations.
Signature
declare function undirected<N, E>(mutate?: (mutable: MutableUndirectedGraph<N, E>) => void): UndirectedGraph<N, E>Example
import { Graph } from "effect"
// Undirected graph with initial nodes and edgesconst graph = Graph.undirected<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, "A-B") Graph.addEdge(mutable, b, c, "B-C")})Errors
GraphError
Error thrown when a graph operation fails.
Signature
declare class GraphError extends YieldableError<this> & { readonly _tag: "GraphError";} & Readonly<{ readonly message: string;}> { constructor(args: { readonly message: string; });}Getters
Returns the number of edges in the graph.
Signature
declare function edgeCount<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): numberExample
import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()console.log(Graph.edgeCount(emptyGraph)) // 0
const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 1) Graph.addEdge(mutable, nodeB, nodeC, 2) Graph.addEdge(mutable, nodeC, nodeA, 3)})
console.log(Graph.edgeCount(graphWithEdges)) // 3Finds the first edge that matches the given predicate.
Signature
declare function findEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: E, source: number, target: number) => boolean): Option<number>Example
import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 10) Graph.addEdge(mutable, nodeB, nodeC, 20)})
const result = Graph.findEdge(graph, (data) => data > 15)console.log(result) // Option.some(1)
const notFound = Graph.findEdge(graph, (data) => data > 100)console.log(notFound) // Option.none()Finds all edges that match the given predicate.
Signature
declare function findEdges<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: E, source: number, target: number) => boolean): Array<number>Example
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 10) Graph.addEdge(mutable, nodeB, nodeC, 20) Graph.addEdge(mutable, nodeC, nodeA, 30)})
const result = Graph.findEdges(graph, (data) => data >= 20)console.log(result) // [1, 2]
const empty = Graph.findEdges(graph, (data) => data > 100)console.log(empty) // []Finds the first node that matches the given predicate.
Signature
declare function findNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Option<number>Example
import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A") Graph.addNode(mutable, "Node B") Graph.addNode(mutable, "Node C")})
const result = Graph.findNode(graph, (data) => data.startsWith("Node B"))console.log(result) // Option.some(1)
const notFound = Graph.findNode(graph, (data) => data === "Node D")console.log(notFound) // Option.none()Finds all nodes that match the given predicate.
Signature
declare function findNodes<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Array<number>Example
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Start A") Graph.addNode(mutable, "Node B") Graph.addNode(mutable, "Start C")})
const result = Graph.findNodes(graph, (data) => data.startsWith("Start"))console.log(result) // [0, 2]
const empty = Graph.findNodes(graph, (data) => data === "Not Found")console.log(empty) // []Gets the edge data associated with an edge index, if it exists.
Signature
declare function getEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, edgeIndex: number): Option<Edge<E>>Example
import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") Graph.addEdge(mutable, nodeA, nodeB, 42)})
const edgeIndex = 0const edgeData = Graph.getEdge(graph, edgeIndex)
if (Option.isSome(edgeData)) { console.log(edgeData.value.data) // 42 console.log(edgeData.value.source) // NodeIndex(0) console.log(edgeData.value.target) // NodeIndex(1)}Gets the data associated with a node index, if it exists.
Signature
declare function getNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Option<N>Example
import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A")})
const nodeIndex = 0const nodeData = Graph.getNode(graph, nodeIndex)
if (Option.isSome(nodeData)) { console.log(nodeData.value) // "Node A"}Checks if an edge exists between two nodes in the graph.
Signature
declare function hasEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, target: number): booleanExample
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 42)})
const nodeA = 0const nodeB = 1const nodeC = 2
const hasAB = Graph.hasEdge(graph, nodeA, nodeB)console.log(hasAB) // true
const hasAC = Graph.hasEdge(graph, nodeA, nodeC)console.log(hasAC) // falseChecks if a node with the given index exists in the graph.
Signature
declare function hasNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): booleanExample
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A")})
const nodeIndex = 0const exists = Graph.hasNode(graph, nodeIndex)console.log(exists) // true
const nonExistentIndex = 999const notExists = Graph.hasNode(graph, nonExistentIndex)console.log(notExists) // falseReturns the neighboring nodes (targets of outgoing edges) for a given node.
Signature
declare function neighbors<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Array<number>Example
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 1) Graph.addEdge(mutable, nodeA, nodeC, 2)})
const nodeA = 0const nodeB = 1const nodeC = 2
const neighborsA = Graph.neighbors(graph, nodeA)console.log(neighborsA) // [NodeIndex(1), NodeIndex(2)]
const neighborsB = Graph.neighbors(graph, nodeB)console.log(neighborsB) // []Returns the number of nodes in the graph.
Signature
declare function nodeCount<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): numberExample
import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()console.log(Graph.nodeCount(emptyGraph)) // 0
const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => { Graph.addNode(mutable, "Node A") Graph.addNode(mutable, "Node B") Graph.addNode(mutable, "Node C")})
console.log(Graph.nodeCount(graphWithNodes)) // 3Iterators
Creates a new BFS iterator with optional configuration.
The iterator maintains a queue of nodes to visit and tracks discovered nodes. It provides lazy evaluation of the breadth-first search.
Signature
declare function bfs<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 1)})
// Start from a specific nodeconst bfs1 = Graph.bfs(graph, { start: [0] })for (const nodeIndex of Graph.indices(bfs1)) { console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2}
// Empty iterator (no starting nodes)const bfs2 = Graph.bfs(graph)// Can be used programmaticallyCreates a new DFS iterator with optional configuration.
The iterator maintains a stack of nodes to visit and tracks discovered nodes. It provides lazy evaluation of the depth-first search.
Signature
declare function dfs<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 1)})
// Start from a specific nodeconst dfs1 = Graph.dfs(graph, { start: [0] })for (const nodeIndex of Graph.indices(dfs1)) { console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2}
// Empty iterator (no starting nodes)const dfs2 = Graph.dfs(graph)// Can be used programmaticallydfsPostOrder
Creates a new DFS postorder iterator with optional configuration.
The iterator maintains a stack with visit state tracking and emits nodes in postorder (after all descendants have been processed). Essential for dependency resolution and tree destruction algorithms.
Signature
declare function dfsPostOrder<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const root = Graph.addNode(mutable, "root") const child1 = Graph.addNode(mutable, "child1") const child2 = Graph.addNode(mutable, "child2") Graph.addEdge(mutable, root, child1, 1) Graph.addEdge(mutable, root, child2, 1)})
// Postorder: children before parentsconst postOrder = Graph.dfsPostOrder(graph, { start: [0] })for (const node of postOrder) { console.log(node) // 1, 2, 0}Creates an iterator over all edge indices in the graph.
The iterator produces edge indices in the order they were added to the graph. This provides access to all edges regardless of connectivity.
Signature
declare function edges<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): EdgeWalker<E>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 2)})
const indices = Array.from(Graph.indices(Graph.edges(graph)))console.log(indices) // [0, 1]Creates an iterator over external nodes (nodes without edges in specified direction).
External nodes are nodes that have no outgoing edges (direction="outgoing") or no incoming edges (direction="incoming"). These are useful for finding sources, sinks, or isolated nodes.
Signature
declare function externals<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: ExternalsConfig): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const source = Graph.addNode(mutable, "source") // 0 - no incoming const middle = Graph.addNode(mutable, "middle") // 1 - has both const sink = Graph.addNode(mutable, "sink") // 2 - no outgoing const isolated = Graph.addNode(mutable, "isolated") // 3 - no edges
Graph.addEdge(mutable, source, middle, 1) Graph.addEdge(mutable, middle, sink, 2)})
// Nodes with no outgoing edges (sinks + isolated)const sinks = Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" })))console.log(sinks) // [2, 3]
// Nodes with no incoming edges (sources + isolated)const sources = Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" })))console.log(sources) // [0, 3]Creates an iterator over all node indices in the graph.
The iterator produces node indices in the order they were added to the graph. This provides access to all nodes regardless of connectivity.
Signature
declare function nodes<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1)})
const indices = Array.from(Graph.indices(Graph.nodes(graph)))console.log(indices) // [0, 1, 2]Creates a new topological sort iterator with optional configuration.
The iterator uses Kahn's algorithm to lazily produce nodes in topological order. Throws an error if the graph contains cycles.
Signature
declare function topo<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: TopoConfig): NodeWalker<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 1)})
// Standard topological sortconst topo1 = Graph.topo(graph)for (const nodeIndex of Graph.indices(topo1)) { console.log(nodeIndex) // 0, 1, 2 (topological order)}
// With initial nodesconst topo2 = Graph.topo(graph, { initials: [0] })
// Throws error for cyclic graphconst cyclicGraph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, a, 2) // Creates cycle})
try { Graph.topo(cyclicGraph) // Throws: "Cannot perform topological sort on cyclic graph"} catch (error) { console.log((error as Error).message)}Models
AllPairsResult interface
Result of all-pairs shortest path computation.
Signature
interface AllPairsResult<E> { readonly costs: Map<number, Map<number, Array<E>>>; readonly distances: Map<number, Map<number, number>>; readonly paths: Map<number, Map<number, Array<number> | null>>;}AstarConfig interface
Configuration for A* algorithm.
Signature
interface AstarConfig<E, N> { cost: (edgeData: E) => number; heuristic: (sourceNodeData: N, targetNodeData: N) => number; source: number; target: number;}BellmanFordConfig interface
Configuration for Bellman-Ford algorithm.
Signature
interface BellmanFordConfig<E> { cost: (edgeData: E) => number; source: number; target: number;}DijkstraConfig interface
Configuration for Dijkstra's algorithm.
Signature
interface DijkstraConfig<E> { cost: (edgeData: E) => number; source: number; target: number;}DirectedGraph type
Directed graph type alias.
Signature
type DirectedGraph<N, E> = Graph<N, E, "directed">Direction for graph traversal, indicating which edges to follow.
Signature
type Direction = "outgoing" | "incoming"Example
import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, "A->B")})
// Follow outgoing edges (normal direction)const outgoingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [0], direction: "outgoing" })))
// Follow incoming edges (reverse direction)const incomingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [1], direction: "incoming" })))Edge data containing source, target, and user data.
Signature
declare class Edge<E> extends Class<{ readonly data: E; readonly source: NodeIndex; readonly target: NodeIndex;}> { constructor<E>(args: { readonly data: E; readonly source: number; readonly target: number; });}Edge index for edge identification using plain numbers.
Signature
type EdgeIndex = numberEdgeWalker type
Type alias for edge iteration using Walker. EdgeWalker is represented as Walker<EdgeIndex, Edge>.
Signature
type EdgeWalker<E> = Walker<EdgeIndex, Edge<E>>ExternalsConfig interface
Configuration for externals iterator.
Signature
interface ExternalsConfig { readonly direction?: Direction;}Immutable graph interface.
Signature
interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> { readonly mutable: false; readonly type: T;}GraphVizOptions interface
Configuration options for GraphViz DOT format generation from graphs.
Signature
interface GraphVizOptions<N, E> { readonly edgeLabel?: (data: E) => string; readonly graphName?: string; readonly nodeLabel?: (data: N) => string;}Graph type for distinguishing directed and undirected graphs.
Signature
type Kind = "directed" | "undirected"MermaidDiagramType type
Mermaid diagram type.
Signature
type MermaidDiagramType = "flowchart" | "graph"MermaidDirection type
Mermaid diagram direction types.
Signature
type MermaidDirection = "TB" | "TD" | "BT" | "LR" | "RL"MermaidNodeShape type
Mermaid node shape types.
Signature
type MermaidNodeShape = "rectangle" | "rounded" | "circle" | "diamond" | "hexagon" | "stadium" | "subroutine" | "cylindrical"MermaidOptions interface
Configuration options for Mermaid diagram generation.
Signature
interface MermaidOptions<N, E> { readonly diagramType?: MermaidDiagramType; readonly direction?: MermaidDirection; readonly edgeLabel?: (data: E) => string; readonly nodeLabel?: (data: N) => string; readonly nodeShape?: (data: N) => MermaidNodeShape;}MutableDirectedGraph type
Mutable directed graph type alias.
Signature
type MutableDirectedGraph<N, E> = MutableGraph<N, E, "directed">MutableGraph interface
Mutable graph interface.
Signature
interface MutableGraph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> { readonly mutable: true; readonly type: T;}MutableUndirectedGraph type
Mutable undirected graph type alias.
Signature
type MutableUndirectedGraph<N, E> = MutableGraph<N, E, "undirected">Node index for node identification using plain numbers.
Signature
type NodeIndex = numberNodeWalker type
Type alias for node iteration using Walker. NodeWalker is represented as Walker<NodeIndex, N>.
Signature
type NodeWalker<N> = Walker<NodeIndex, N>PathResult interface
Result of a shortest path computation containing the path and total distance.
Signature
interface PathResult<E> { readonly costs: Array<E>; readonly distance: number; readonly path: Array<number>;}Graph prototype interface.
Signature
interface Proto<out N, out E> extends Iterable<readonly [NodeIndex, N]>, Equal, Pipeable, Inspectable { readonly "~effect/Graph": "~effect/Graph"; readonly adjacency: Map<number, Array<number>>; readonly edges: Map<number, Edge<E>>; isAcyclic: Option<boolean>; nextEdgeIndex: number; nextNodeIndex: number; readonly nodes: Map<number, N>; readonly reverseAdjacency: Map<number, Array<number>>;}SearchConfig interface
Configuration for graph search iterators.
Signature
interface SearchConfig { readonly direction?: Direction; readonly start?: Array<number>;}TopoConfig interface
Configuration options for topological sort iterator.
Signature
interface TopoConfig { readonly initials?: Array<number>;}UndirectedGraph type
Undirected graph type alias.
Signature
type UndirectedGraph<N, E> = Graph<N, E, "undirected">Concrete class for iterables that produce [NodeIndex, NodeData] tuples.
This class provides a common abstraction for all iterables that return node data, including traversal iterators (DFS, BFS, etc.) and element iterators (nodes, externals). It uses a mapEntry function pattern for flexible iteration and transformation.
Signature
declare class Walker<T, N> implements Iterable<[T, N]> { constructor<T, N>(visit: <U>(f: (index: T, data: N) => U) => Iterable<U>); readonly [iterator]: () => Iterator<[T, N]>; readonly visit: <U>(f: (index: T, data: N) => U) => Iterable<U>;}Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, 1)})
// Both traversal and element iterators return NodeWalkerconst dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
// Common interface for working with node iterablesfunction processNodes<N>(nodeIterable: Graph.NodeWalker<N>): Array<number> { return Array.from(Graph.indices(nodeIterable))}
// Access node data using values() or entries()const nodeData = Array.from(Graph.values(dfsNodes)) // ["A", "B"]const nodeEntries = Array.from(Graph.entries(allNodes)) // [[0, "A"], [1, "B"]]Mutations
Adds a new edge to a mutable graph and returns its index.
Signature
declare function addEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, source: number, target: number, data: E): numberExample
import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const edge = Graph.addEdge(mutable, nodeA, nodeB, 42) console.log(edge) // EdgeIndex with value 0})Adds a new node to a mutable graph and returns its index.
Signature
declare function addNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, data: N): numberExample
import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") console.log(nodeA) // NodeIndex with value 0 console.log(nodeB) // NodeIndex with value 1})beginMutation
Creates a mutable scope for safe graph mutations by copying the data structure.
Signature
declare function beginMutation<N, E, T extends Kind = "directed">(graph: Graph<N, E, T>): MutableGraph<N, E, T>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const mutable = Graph.beginMutation(graph)// Now mutable can be safely modified without affecting original graphendMutation
Converts a mutable graph back to an immutable graph, ending the mutation scope.
Signature
declare function endMutation<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): Graph<N, E, T>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const mutable = Graph.beginMutation(graph)// ... perform mutations on mutable ...const newGraph = Graph.endMutation(mutable)Performs scoped mutations on a graph, automatically managing the mutation lifecycle.
Signature
declare const mutate: { <N, E, T extends Kind = "directed">(f: (mutable: MutableGraph<N, E, T>) => void): (graph: Graph<N, E, T>) => Graph<N, E, T>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T>, f: (mutable: MutableGraph<N, E, T>) => void): Graph<N, E, T>;}Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const newGraph = Graph.mutate(graph, (mutable) => { // Safe mutations go here // mutable gets automatically converted back to immutable})removeEdge
Removes an edge from a mutable graph.
Signature
declare function removeEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number): voidExample
import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const edge = Graph.addEdge(mutable, nodeA, nodeB, 42)
// Remove the edge Graph.removeEdge(mutable, edge)})removeNode
Removes a node and all its incident edges from a mutable graph.
Signature
declare function removeNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, nodeIndex: number): voidExample
import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") Graph.addEdge(mutable, nodeA, nodeB, 42)
// Remove nodeA and all edges connected to it Graph.removeNode(mutable, nodeA)})updateEdge
Updates a single edge's data by applying a transformation function.
Signature
declare function updateEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number, f: (data: E) => E): voidExample
import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const edgeIndex = Graph.addEdge(mutable, nodeA, nodeB, 10) Graph.updateEdge(mutable, edgeIndex, (data) => data * 2)})
const edgeData = Graph.getEdge(result, 0)console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })Queries
neighborsDirected
Get directed neighbors of a node in a specific direction.
Signature
declare function neighborsDirected<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number, direction: Direction): Array<number>Example
import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, "A->B")})
const nodeA = 0const nodeB = 1
// Get outgoing neighbors (nodes that nodeA points to)const outgoing = Graph.neighborsDirected(graph, nodeA, "outgoing")
// Get incoming neighbors (nodes that point to nodeB)const incoming = Graph.neighborsDirected(graph, nodeB, "incoming")predecessors
Returns the incoming neighbor node indices for a node in a directed graph.
Throws a GraphError when used with an undirected graph.
Signature
declare function predecessors<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>successors
Returns the outgoing neighbor node indices for a node in a directed graph.
Throws a GraphError when used with an undirected graph.
Signature
declare function successors<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>Symbol
Transformations
filterEdges
Filters edges by removing those that don't match the predicate. This function modifies the mutable graph in place.
Signature
declare function filterEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: E) => boolean): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 5) Graph.addEdge(mutable, b, c, 15) Graph.addEdge(mutable, c, a, 25)
// Keep only edges with weight >= 10 Graph.filterEdges(mutable, (data) => data >= 10)})
console.log(Graph.edgeCount(graph)) // 2 (edge with weight 5 removed)filterMapEdges
Filters and optionally transforms edges in a mutable graph using a predicate function. Edges that return Option.none are removed from the graph.
Signature
declare function filterMapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => Option<E>): voidExample
import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 5) Graph.addEdge(mutable, b, c, 15) Graph.addEdge(mutable, c, a, 25)
// Keep only edges with weight >= 10 and double their weight Graph.filterMapEdges(mutable, (data) => data >= 10 ? Option.some(data * 2) : Option.none() )})
console.log(Graph.edgeCount(graph)) // 2 (edges with weight 5 removed)filterMapNodes
Filters and optionally transforms nodes in a mutable graph using a predicate function. Nodes that return Option.none are removed along with all their connected edges.
Signature
declare function filterMapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => Option<N>): voidExample
import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "active") const b = Graph.addNode(mutable, "inactive") const c = Graph.addNode(mutable, "active") Graph.addEdge(mutable, a, b, 1) Graph.addEdge(mutable, b, c, 2)
// Keep only "active" nodes and transform to uppercase Graph.filterMapNodes(mutable, (data) => data === "active" ? Option.some(data.toUpperCase()) : Option.none() )})
console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)filterNodes
Filters nodes by removing those that don't match the predicate. This function modifies the mutable graph in place.
Signature
declare function filterNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: N) => boolean): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { Graph.addNode(mutable, "active") Graph.addNode(mutable, "inactive") Graph.addNode(mutable, "pending") Graph.addNode(mutable, "active")
// Keep only "active" nodes Graph.filterNodes(mutable, (data) => data === "active")})
console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)Transforms all edge data in a mutable graph using the provided mapping function.
Signature
declare function mapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => E): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 10) Graph.addEdge(mutable, b, c, 20) Graph.mapEdges(mutable, (data) => data * 2)})
const edgeData = Graph.getEdge(graph, 0)console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })Creates a new graph with transformed node data using the provided mapping function.
Signature
declare function mapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => N): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { Graph.addNode(mutable, "node a") Graph.addNode(mutable, "node b") Graph.addNode(mutable, "node c") Graph.mapNodes(mutable, (data) => data.toUpperCase())})
const nodeData = Graph.getNode(graph, 0)console.log(nodeData) // Option.some("NODE A")Reverses all edge directions in a mutable graph by swapping source and target nodes.
Signature
declare function reverse<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, a, b, 1) // A -> B Graph.addEdge(mutable, b, c, 2) // B -> C Graph.reverse(mutable) // Now B -> A, C -> B})
const edge0 = Graph.getEdge(graph, 0)console.log(edge0) // Option.some({ source: 1, target: 0, data: 1 }) - B -> AupdateNode
Updates a single node's data by applying a transformation function.
Signature
declare function updateNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, index: number, f: (data: N) => N): voidExample
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { Graph.addNode(mutable, "Node A") Graph.addNode(mutable, "Node B") Graph.updateNode(mutable, 0, (data) => data.toUpperCase())})
const nodeData = Graph.getNode(graph, 0)console.log(nodeData) // Option.some("NODE A")Utilities
Returns an iterator over [index, data] entries in the walker.
Signature
declare function entries<T, N>(walker: Walker<T, N>): Iterable<[T, N]>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, 1)})
const dfs = Graph.dfs(graph, { start: [0] })const entries = Array.from(Graph.entries(dfs))console.log(entries) // [[0, "A"], [1, "B"]]Returns an iterator over the indices in the walker.
Signature
declare function indices<T, N>(walker: Walker<T, N>): Iterable<T>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, 1)})
const dfs = Graph.dfs(graph, { start: [0] })const indices = Array.from(Graph.indices(dfs))console.log(indices) // [0, 1]Returns an iterator over the values (data) in the walker.
Signature
declare function values<T, N>(walker: Walker<T, N>): Iterable<N>Example
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, 1)})
const dfs = Graph.dfs(graph, { start: [0] })const values = Array.from(Graph.values(dfs))console.log(values) // ["A", "B"]Utils
toGraphViz
Exports a graph to GraphViz DOT format for visualization.
Signature
declare function toGraphViz<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: GraphVizOptions<N, E>): stringExample
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const nodeA = Graph.addNode(mutable, "Node A") const nodeB = Graph.addNode(mutable, "Node B") const nodeC = Graph.addNode(mutable, "Node C") Graph.addEdge(mutable, nodeA, nodeB, 1) Graph.addEdge(mutable, nodeB, nodeC, 2) Graph.addEdge(mutable, nodeC, nodeA, 3)})
const dot = Graph.toGraphViz(graph)console.log(dot)// digraph G {// "0" [label="Node A"];// "1" [label="Node B"];// "2" [label="Node C"];// "0" -> "1" [label="1"];// "1" -> "2" [label="2"];// "2" -> "0" [label="3"];// }Exports a graph to Mermaid diagram format for visualization.
Signature
declare function toMermaid<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: MermaidOptions<N, E>): stringExample
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { const app = Graph.addNode(mutable, "App") const db = Graph.addNode(mutable, "Database") const cache = Graph.addNode(mutable, "Cache") Graph.addEdge(mutable, app, db, 1) Graph.addEdge(mutable, app, cache, 2)})
const mermaid = Graph.toMermaid(graph)console.log(mermaid)// flowchart TD// 0["App"]// 1["Database"]// 2["Cache"]// 0 -->|"1"| 1// 0 -->|"2"| 2