effect 3.18.5 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/Graph.js CHANGED
@@ -9,17 +9,6 @@ import * as Hash from "./Hash.js";
9
9
  import { format, NodeInspectSymbol } from "./Inspectable.js";
10
10
  import * as Option from "./Option.js";
11
11
  import { pipeArguments } from "./Pipeable.js";
12
- /**
13
- * Safely get a value from a Map, returning an Option.
14
- * Uses explicit key presence check with map.has() for better safety.
15
- * @internal
16
- */
17
- const getMapSafe = (map, key) => {
18
- if (map.has(key)) {
19
- return Option.some(map.get(key));
20
- }
21
- return Option.none();
22
- };
23
12
  /**
24
13
  * Unique identifier for Graph instances.
25
14
  *
@@ -104,6 +93,20 @@ const ProtoGraph = {
104
93
  }
105
94
  };
106
95
  // =============================================================================
96
+ // Errors
97
+ // =============================================================================
98
+ /**
99
+ * Error thrown when a graph operation fails.
100
+ *
101
+ * @since 3.18.0
102
+ * @category errors
103
+ */
104
+ export class GraphError extends /*#__PURE__*/Data.TaggedError("GraphError") {}
105
+ /** @internal */
106
+ const missingNode = node => new GraphError({
107
+ message: `Node ${node} does not exist`
108
+ });
109
+ // =============================================================================
107
110
  // Constructors
108
111
  // =============================================================================
109
112
  /** @internal */
@@ -329,7 +332,7 @@ export const addNode = (mutable, data) => {
329
332
  * @since 3.18.0
330
333
  * @category getters
331
334
  */
332
- export const getNode = (graph, nodeIndex) => getMapSafe(graph.nodes, nodeIndex);
335
+ export const getNode = (graph, nodeIndex) => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)) : Option.none();
333
336
  /**
334
337
  * Checks if a node with the given index exists in the graph.
335
338
  *
@@ -892,10 +895,10 @@ const invalidateCycleFlagOnAddition = mutable => {
892
895
  export const addEdge = (mutable, source, target, data) => {
893
896
  // Validate that both nodes exist
894
897
  if (!mutable.nodes.has(source)) {
895
- throw new Error(`Source node ${source} does not exist`);
898
+ throw missingNode(source);
896
899
  }
897
900
  if (!mutable.nodes.has(target)) {
898
- throw new Error(`Target node ${target} does not exist`);
901
+ throw missingNode(target);
899
902
  }
900
903
  const edgeIndex = mutable.nextEdgeIndex;
901
904
  // Create edge data
@@ -906,23 +909,23 @@ export const addEdge = (mutable, source, target, data) => {
906
909
  });
907
910
  mutable.edges.set(edgeIndex, edgeData);
908
911
  // Update adjacency lists
909
- const sourceAdjacency = getMapSafe(mutable.adjacency, source);
910
- if (Option.isSome(sourceAdjacency)) {
911
- sourceAdjacency.value.push(edgeIndex);
912
+ const sourceAdjacency = mutable.adjacency.get(source);
913
+ if (sourceAdjacency !== undefined) {
914
+ sourceAdjacency.push(edgeIndex);
912
915
  }
913
- const targetReverseAdjacency = getMapSafe(mutable.reverseAdjacency, target);
914
- if (Option.isSome(targetReverseAdjacency)) {
915
- targetReverseAdjacency.value.push(edgeIndex);
916
+ const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
917
+ if (targetReverseAdjacency !== undefined) {
918
+ targetReverseAdjacency.push(edgeIndex);
916
919
  }
917
920
  // For undirected graphs, add reverse connections
918
921
  if (mutable.type === "undirected") {
919
- const targetAdjacency = getMapSafe(mutable.adjacency, target);
920
- if (Option.isSome(targetAdjacency)) {
921
- targetAdjacency.value.push(edgeIndex);
922
+ const targetAdjacency = mutable.adjacency.get(target);
923
+ if (targetAdjacency !== undefined) {
924
+ targetAdjacency.push(edgeIndex);
922
925
  }
923
- const sourceReverseAdjacency = getMapSafe(mutable.reverseAdjacency, source);
924
- if (Option.isSome(sourceReverseAdjacency)) {
925
- sourceReverseAdjacency.value.push(edgeIndex);
926
+ const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
927
+ if (sourceReverseAdjacency !== undefined) {
928
+ sourceReverseAdjacency.push(edgeIndex);
926
929
  }
927
930
  }
928
931
  // Update allocators
@@ -960,16 +963,16 @@ export const removeNode = (mutable, nodeIndex) => {
960
963
  // Collect all incident edges for removal
961
964
  const edgesToRemove = [];
962
965
  // Get outgoing edges
963
- const outgoingEdges = getMapSafe(mutable.adjacency, nodeIndex);
964
- if (Option.isSome(outgoingEdges)) {
965
- for (const edge of outgoingEdges.value) {
966
+ const outgoingEdges = mutable.adjacency.get(nodeIndex);
967
+ if (outgoingEdges !== undefined) {
968
+ for (const edge of outgoingEdges) {
966
969
  edgesToRemove.push(edge);
967
970
  }
968
971
  }
969
972
  // Get incoming edges
970
- const incomingEdges = getMapSafe(mutable.reverseAdjacency, nodeIndex);
971
- if (Option.isSome(incomingEdges)) {
972
- for (const edge of incomingEdges.value) {
973
+ const incomingEdges = mutable.reverseAdjacency.get(nodeIndex);
974
+ if (incomingEdges !== undefined) {
975
+ for (const edge of incomingEdges) {
973
976
  edgesToRemove.push(edge);
974
977
  }
975
978
  }
@@ -1016,43 +1019,43 @@ export const removeEdge = (mutable, edgeIndex) => {
1016
1019
  /** @internal */
1017
1020
  const removeEdgeInternal = (mutable, edgeIndex) => {
1018
1021
  // Get edge data
1019
- const edge = getMapSafe(mutable.edges, edgeIndex);
1020
- if (Option.isNone(edge)) {
1022
+ const edge = mutable.edges.get(edgeIndex);
1023
+ if (edge === undefined) {
1021
1024
  return false; // Edge doesn't exist, no mutation occurred
1022
1025
  }
1023
1026
  const {
1024
1027
  source,
1025
1028
  target
1026
- } = edge.value;
1029
+ } = edge;
1027
1030
  // Remove from adjacency lists
1028
- const sourceAdjacency = getMapSafe(mutable.adjacency, source);
1029
- if (Option.isSome(sourceAdjacency)) {
1030
- const index = sourceAdjacency.value.indexOf(edgeIndex);
1031
+ const sourceAdjacency = mutable.adjacency.get(source);
1032
+ if (sourceAdjacency !== undefined) {
1033
+ const index = sourceAdjacency.indexOf(edgeIndex);
1031
1034
  if (index !== -1) {
1032
- sourceAdjacency.value.splice(index, 1);
1035
+ sourceAdjacency.splice(index, 1);
1033
1036
  }
1034
1037
  }
1035
- const targetReverseAdjacency = getMapSafe(mutable.reverseAdjacency, target);
1036
- if (Option.isSome(targetReverseAdjacency)) {
1037
- const index = targetReverseAdjacency.value.indexOf(edgeIndex);
1038
+ const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
1039
+ if (targetReverseAdjacency !== undefined) {
1040
+ const index = targetReverseAdjacency.indexOf(edgeIndex);
1038
1041
  if (index !== -1) {
1039
- targetReverseAdjacency.value.splice(index, 1);
1042
+ targetReverseAdjacency.splice(index, 1);
1040
1043
  }
1041
1044
  }
1042
1045
  // For undirected graphs, remove reverse connections
1043
1046
  if (mutable.type === "undirected") {
1044
- const targetAdjacency = getMapSafe(mutable.adjacency, target);
1045
- if (Option.isSome(targetAdjacency)) {
1046
- const index = targetAdjacency.value.indexOf(edgeIndex);
1047
+ const targetAdjacency = mutable.adjacency.get(target);
1048
+ if (targetAdjacency !== undefined) {
1049
+ const index = targetAdjacency.indexOf(edgeIndex);
1047
1050
  if (index !== -1) {
1048
- targetAdjacency.value.splice(index, 1);
1051
+ targetAdjacency.splice(index, 1);
1049
1052
  }
1050
1053
  }
1051
- const sourceReverseAdjacency = getMapSafe(mutable.reverseAdjacency, source);
1052
- if (Option.isSome(sourceReverseAdjacency)) {
1053
- const index = sourceReverseAdjacency.value.indexOf(edgeIndex);
1054
+ const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
1055
+ if (sourceReverseAdjacency !== undefined) {
1056
+ const index = sourceReverseAdjacency.indexOf(edgeIndex);
1054
1057
  if (index !== -1) {
1055
- sourceReverseAdjacency.value.splice(index, 1);
1058
+ sourceReverseAdjacency.splice(index, 1);
1056
1059
  }
1057
1060
  }
1058
1061
  }
@@ -1089,7 +1092,7 @@ const removeEdgeInternal = (mutable, edgeIndex) => {
1089
1092
  * @since 3.18.0
1090
1093
  * @category getters
1091
1094
  */
1092
- export const getEdge = (graph, edgeIndex) => getMapSafe(graph.edges, edgeIndex);
1095
+ export const getEdge = (graph, edgeIndex) => graph.edges.has(edgeIndex) ? Option.some(graph.edges.get(edgeIndex)) : Option.none();
1093
1096
  /**
1094
1097
  * Checks if an edge exists between two nodes in the graph.
1095
1098
  *
@@ -1119,14 +1122,14 @@ export const getEdge = (graph, edgeIndex) => getMapSafe(graph.edges, edgeIndex);
1119
1122
  * @category getters
1120
1123
  */
1121
1124
  export const hasEdge = (graph, source, target) => {
1122
- const adjacencyList = getMapSafe(graph.adjacency, source);
1123
- if (Option.isNone(adjacencyList)) {
1125
+ const adjacencyList = graph.adjacency.get(source);
1126
+ if (adjacencyList === undefined) {
1124
1127
  return false;
1125
1128
  }
1126
1129
  // Check if any edge in the adjacency list connects to the target
1127
- for (const edgeIndex of adjacencyList.value) {
1128
- const edge = getMapSafe(graph.edges, edgeIndex);
1129
- if (Option.isSome(edge) && edge.value.target === target) {
1130
+ for (const edgeIndex of adjacencyList) {
1131
+ const edge = graph.edges.get(edgeIndex);
1132
+ if (edge !== undefined && edge.target === target) {
1130
1133
  return true;
1131
1134
  }
1132
1135
  }
@@ -1192,15 +1195,15 @@ export const neighbors = (graph, nodeIndex) => {
1192
1195
  if (graph.type === "undirected") {
1193
1196
  return getUndirectedNeighbors(graph, nodeIndex);
1194
1197
  }
1195
- const adjacencyList = getMapSafe(graph.adjacency, nodeIndex);
1196
- if (Option.isNone(adjacencyList)) {
1198
+ const adjacencyList = graph.adjacency.get(nodeIndex);
1199
+ if (adjacencyList === undefined) {
1197
1200
  return [];
1198
1201
  }
1199
1202
  const result = [];
1200
- for (const edgeIndex of adjacencyList.value) {
1201
- const edge = getMapSafe(graph.edges, edgeIndex);
1202
- if (Option.isSome(edge)) {
1203
- result.push(edge.value.target);
1203
+ for (const edgeIndex of adjacencyList) {
1204
+ const edge = graph.edges.get(edgeIndex);
1205
+ if (edge !== undefined) {
1206
+ result.push(edge.target);
1204
1207
  }
1205
1208
  }
1206
1209
  return result;
@@ -1233,24 +1236,21 @@ export const neighbors = (graph, nodeIndex) => {
1233
1236
  */
1234
1237
  export const neighborsDirected = (graph, nodeIndex, direction) => {
1235
1238
  const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
1236
- const adjacencyList = getMapSafe(adjacencyMap, nodeIndex);
1237
- if (Option.isNone(adjacencyList)) {
1239
+ const adjacencyList = adjacencyMap.get(nodeIndex);
1240
+ if (adjacencyList === undefined) {
1238
1241
  return [];
1239
1242
  }
1240
1243
  const result = [];
1241
- for (const edgeIndex of adjacencyList.value) {
1242
- const edge = getMapSafe(graph.edges, edgeIndex);
1243
- if (Option.isSome(edge)) {
1244
+ for (const edgeIndex of adjacencyList) {
1245
+ const edge = graph.edges.get(edgeIndex);
1246
+ if (edge !== undefined) {
1244
1247
  // For incoming direction, we want the source node instead of target
1245
- const neighborNode = direction === "incoming" ? edge.value.source : edge.value.target;
1248
+ const neighborNode = direction === "incoming" ? edge.source : edge.target;
1246
1249
  result.push(neighborNode);
1247
1250
  }
1248
1251
  }
1249
1252
  return result;
1250
1253
  };
1251
- // =============================================================================
1252
- // GraphViz Export
1253
- // =============================================================================
1254
1254
  /**
1255
1255
  * Exports a graph to GraphViz DOT format for visualization.
1256
1256
  *
@@ -1306,6 +1306,97 @@ export const toGraphViz = (graph, options) => {
1306
1306
  lines.push("}");
1307
1307
  return lines.join("\n");
1308
1308
  };
1309
+ /** @internal */
1310
+ const escapeMermaidLabel = label => {
1311
+ // Escape special characters for Mermaid using HTML entity codes
1312
+ // According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax
1313
+ return label.replace(/#/g, "#35;").replace(/"/g, "#quot;").replace(/</g, "#lt;").replace(/>/g, "#gt;").replace(/&/g, "#amp;").replace(/\[/g, "#91;").replace(/\]/g, "#93;").replace(/\{/g, "#123;").replace(/\}/g, "#125;").replace(/\(/g, "#40;").replace(/\)/g, "#41;").replace(/\|/g, "#124;").replace(/\\/g, "#92;").replace(/\n/g, "<br/>");
1314
+ };
1315
+ /** @internal */
1316
+ const formatMermaidNode = (nodeId, label, shape) => {
1317
+ switch (shape) {
1318
+ case "rectangle":
1319
+ return `${nodeId}["${label}"]`;
1320
+ case "rounded":
1321
+ return `${nodeId}("${label}")`;
1322
+ case "circle":
1323
+ return `${nodeId}(("${label}"))`;
1324
+ case "diamond":
1325
+ return `${nodeId}{"${label}"}`;
1326
+ case "hexagon":
1327
+ return `${nodeId}{{"${label}"}}`;
1328
+ case "stadium":
1329
+ return `${nodeId}(["${label}"])`;
1330
+ case "subroutine":
1331
+ return `${nodeId}[["${label}"]]`;
1332
+ case "cylindrical":
1333
+ return `${nodeId}[("${label}")]`;
1334
+ }
1335
+ };
1336
+ /**
1337
+ * Exports a graph to Mermaid diagram format for visualization.
1338
+ *
1339
+ * @example
1340
+ * ```ts
1341
+ * import { Graph } from "effect"
1342
+ *
1343
+ * const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
1344
+ * const app = Graph.addNode(mutable, "App")
1345
+ * const db = Graph.addNode(mutable, "Database")
1346
+ * const cache = Graph.addNode(mutable, "Cache")
1347
+ * Graph.addEdge(mutable, app, db, 1)
1348
+ * Graph.addEdge(mutable, app, cache, 2)
1349
+ * })
1350
+ *
1351
+ * const mermaid = Graph.toMermaid(graph)
1352
+ * console.log(mermaid)
1353
+ * // flowchart TD
1354
+ * // 0["App"]
1355
+ * // 1["Database"]
1356
+ * // 2["Cache"]
1357
+ * // 0 -->|"1"| 1
1358
+ * // 0 -->|"2"| 2
1359
+ * ```
1360
+ *
1361
+ * @since 3.18.0
1362
+ * @category utils
1363
+ */
1364
+ export const toMermaid = (graph, options) => {
1365
+ // Extract and validate options with defaults
1366
+ const {
1367
+ diagramType,
1368
+ direction = "TD",
1369
+ edgeLabel = data => String(data),
1370
+ nodeLabel = data => String(data),
1371
+ nodeShape = () => "rectangle"
1372
+ } = options ?? {};
1373
+ // Auto-detect diagram type if not specified
1374
+ const finalDiagramType = diagramType ?? (graph.type === "directed" ? "flowchart" : "graph");
1375
+ // Generate diagram header
1376
+ const lines = [];
1377
+ lines.push(`${finalDiagramType} ${direction}`);
1378
+ // Add nodes
1379
+ for (const [nodeIndex, nodeData] of graph.nodes) {
1380
+ const nodeId = String(nodeIndex);
1381
+ const label = escapeMermaidLabel(nodeLabel(nodeData));
1382
+ const shape = nodeShape(nodeData);
1383
+ const formattedNode = formatMermaidNode(nodeId, label, shape);
1384
+ lines.push(` ${formattedNode}`);
1385
+ }
1386
+ // Add edges
1387
+ const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---";
1388
+ for (const [, edgeData] of graph.edges) {
1389
+ const sourceId = String(edgeData.source);
1390
+ const targetId = String(edgeData.target);
1391
+ const label = escapeMermaidLabel(edgeLabel(edgeData.data));
1392
+ if (label) {
1393
+ lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`);
1394
+ } else {
1395
+ lines.push(` ${sourceId} ${edgeOperator} ${targetId}`);
1396
+ }
1397
+ }
1398
+ return lines.join("\n");
1399
+ };
1309
1400
  // =============================================================================
1310
1401
  // =============================================================================
1311
1402
  // Graph Structure Analysis Algorithms (Phase 5A)
@@ -1487,13 +1578,13 @@ export const isBipartite = graph => {
1487
1578
  const getUndirectedNeighbors = (graph, nodeIndex) => {
1488
1579
  const neighbors = new Set();
1489
1580
  // Check edges where this node is the source
1490
- const adjacencyList = getMapSafe(graph.adjacency, nodeIndex);
1491
- if (Option.isSome(adjacencyList)) {
1492
- for (const edgeIndex of adjacencyList.value) {
1493
- const edge = getMapSafe(graph.edges, edgeIndex);
1494
- if (Option.isSome(edge)) {
1581
+ const adjacencyList = graph.adjacency.get(nodeIndex);
1582
+ if (adjacencyList !== undefined) {
1583
+ for (const edgeIndex of adjacencyList) {
1584
+ const edge = graph.edges.get(edgeIndex);
1585
+ if (edge !== undefined) {
1495
1586
  // For undirected graphs, the neighbor is the other endpoint
1496
- const otherNode = edge.value.source === nodeIndex ? edge.value.target : edge.value.source;
1587
+ const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
1497
1588
  neighbors.add(otherNode);
1498
1589
  }
1499
1590
  }
@@ -1627,12 +1718,12 @@ export const stronglyConnectedComponents = graph => {
1627
1718
  visited.add(node);
1628
1719
  scc.push(node);
1629
1720
  // Use reverse adjacency (transpose graph)
1630
- const reverseAdjacency = getMapSafe(graph.reverseAdjacency, node);
1631
- if (Option.isSome(reverseAdjacency)) {
1632
- for (const edgeIndex of reverseAdjacency.value) {
1633
- const edge = getMapSafe(graph.edges, edgeIndex);
1634
- if (Option.isSome(edge)) {
1635
- const predecessor = edge.value.source;
1721
+ const reverseAdjacency = graph.reverseAdjacency.get(node);
1722
+ if (reverseAdjacency !== undefined) {
1723
+ for (const edgeIndex of reverseAdjacency) {
1724
+ const edge = graph.edges.get(edgeIndex);
1725
+ if (edge !== undefined) {
1726
+ const predecessor = edge.source;
1636
1727
  if (!visited.has(predecessor)) {
1637
1728
  stack.push(predecessor);
1638
1729
  }
@@ -1663,7 +1754,7 @@ export const stronglyConnectedComponents = graph => {
1663
1754
  * Graph.addEdge(mutable, b, c, 2)
1664
1755
  * })
1665
1756
  *
1666
- * const result = Graph.dijkstra(graph, 0, 2, (edgeData) => edgeData)
1757
+ * const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
1667
1758
  * if (Option.isSome(result)) {
1668
1759
  * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
1669
1760
  * console.log(result.value.distance) // 7 - total distance
@@ -1673,20 +1764,25 @@ export const stronglyConnectedComponents = graph => {
1673
1764
  * @since 3.18.0
1674
1765
  * @category algorithms
1675
1766
  */
1676
- export const dijkstra = (graph, source, target, edgeWeight) => {
1767
+ export const dijkstra = (graph, config) => {
1768
+ const {
1769
+ cost,
1770
+ source,
1771
+ target
1772
+ } = config;
1677
1773
  // Validate that source and target nodes exist
1678
1774
  if (!graph.nodes.has(source)) {
1679
- throw new Error(`Source node ${source} does not exist`);
1775
+ throw missingNode(source);
1680
1776
  }
1681
1777
  if (!graph.nodes.has(target)) {
1682
- throw new Error(`Target node ${target} does not exist`);
1778
+ throw missingNode(target);
1683
1779
  }
1684
1780
  // Early return if source equals target
1685
1781
  if (source === target) {
1686
1782
  return Option.some({
1687
1783
  path: [source],
1688
1784
  distance: 0,
1689
- edgeWeights: []
1785
+ costs: []
1690
1786
  });
1691
1787
  }
1692
1788
  // Distance tracking and priority queue simulation
@@ -1726,13 +1822,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
1726
1822
  // Get current distance
1727
1823
  const currentDistance = distances.get(currentNode);
1728
1824
  // Examine all outgoing edges
1729
- const adjacencyList = getMapSafe(graph.adjacency, currentNode);
1730
- if (Option.isSome(adjacencyList)) {
1731
- for (const edgeIndex of adjacencyList.value) {
1732
- const edge = getMapSafe(graph.edges, edgeIndex);
1733
- if (Option.isSome(edge)) {
1734
- const neighbor = edge.value.target;
1735
- const weight = edgeWeight(edge.value.data);
1825
+ const adjacencyList = graph.adjacency.get(currentNode);
1826
+ if (adjacencyList !== undefined) {
1827
+ for (const edgeIndex of adjacencyList) {
1828
+ const edge = graph.edges.get(edgeIndex);
1829
+ if (edge !== undefined) {
1830
+ const neighbor = edge.target;
1831
+ const weight = cost(edge.data);
1736
1832
  // Validate non-negative weights
1737
1833
  if (weight < 0) {
1738
1834
  throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
@@ -1744,7 +1840,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
1744
1840
  distances.set(neighbor, newDistance);
1745
1841
  previous.set(neighbor, {
1746
1842
  node: currentNode,
1747
- edgeData: edge.value.data
1843
+ edgeData: edge.data
1748
1844
  });
1749
1845
  // Add to priority queue if not visited
1750
1846
  if (!visited.has(neighbor)) {
@@ -1765,13 +1861,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
1765
1861
  }
1766
1862
  // Reconstruct path
1767
1863
  const path = [];
1768
- const edgeWeights = [];
1864
+ const costs = [];
1769
1865
  let currentNode = target;
1770
1866
  while (currentNode !== null) {
1771
1867
  path.unshift(currentNode);
1772
1868
  const prev = previous.get(currentNode);
1773
1869
  if (prev !== null) {
1774
- edgeWeights.unshift(prev.edgeData);
1870
+ costs.unshift(prev.edgeData);
1775
1871
  currentNode = prev.node;
1776
1872
  } else {
1777
1873
  currentNode = null;
@@ -1780,7 +1876,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
1780
1876
  return Option.some({
1781
1877
  path,
1782
1878
  distance: targetDistance,
1783
- edgeWeights
1879
+ costs
1784
1880
  });
1785
1881
  };
1786
1882
  /**
@@ -1810,7 +1906,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
1810
1906
  * @since 3.18.0
1811
1907
  * @category algorithms
1812
1908
  */
1813
- export const floydWarshall = (graph, edgeWeight) => {
1909
+ export const floydWarshall = (graph, cost) => {
1814
1910
  // Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
1815
1911
  const allNodes = Array.from(graph.nodes.keys());
1816
1912
  // Initialize distance matrix
@@ -1830,7 +1926,7 @@ export const floydWarshall = (graph, edgeWeight) => {
1830
1926
  }
1831
1927
  // Set edge weights
1832
1928
  for (const [, edgeData] of graph.edges) {
1833
- const weight = edgeWeight(edgeData.data);
1929
+ const weight = cost(edgeData.data);
1834
1930
  const i = edgeData.source;
1835
1931
  const j = edgeData.target;
1836
1932
  // Use minimum weight if multiple edges exist
@@ -1863,17 +1959,17 @@ export const floydWarshall = (graph, edgeWeight) => {
1863
1959
  }
1864
1960
  // Build result paths and edge weights
1865
1961
  const paths = new Map();
1866
- const resultEdgeWeights = new Map();
1962
+ const resultCosts = new Map();
1867
1963
  for (const i of allNodes) {
1868
1964
  paths.set(i, new Map());
1869
- resultEdgeWeights.set(i, new Map());
1965
+ resultCosts.set(i, new Map());
1870
1966
  for (const j of allNodes) {
1871
1967
  if (i === j) {
1872
1968
  paths.get(i).set(j, [i]);
1873
- resultEdgeWeights.get(i).set(j, []);
1969
+ resultCosts.get(i).set(j, []);
1874
1970
  } else if (dist.get(i).get(j) === Infinity) {
1875
1971
  paths.get(i).set(j, null);
1876
- resultEdgeWeights.get(i).set(j, []);
1972
+ resultCosts.get(i).set(j, []);
1877
1973
  } else {
1878
1974
  // Reconstruct path iteratively
1879
1975
  const path = [];
@@ -1891,14 +1987,14 @@ export const floydWarshall = (graph, edgeWeight) => {
1891
1987
  path.push(current);
1892
1988
  }
1893
1989
  paths.get(i).set(j, path);
1894
- resultEdgeWeights.get(i).set(j, weights);
1990
+ resultCosts.get(i).set(j, weights);
1895
1991
  }
1896
1992
  }
1897
1993
  }
1898
1994
  return {
1899
1995
  distances: dist,
1900
1996
  paths,
1901
- edgeWeights: resultEdgeWeights
1997
+ costs: resultCosts
1902
1998
  };
1903
1999
  };
1904
2000
  /**
@@ -1924,7 +2020,7 @@ export const floydWarshall = (graph, edgeWeight) => {
1924
2020
  * const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
1925
2021
  * Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
1926
2022
  *
1927
- * const result = Graph.astar(graph, 0, 2, (edgeData) => edgeData, heuristic)
2023
+ * const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
1928
2024
  * if (Option.isSome(result)) {
1929
2025
  * console.log(result.value.path) // [0, 1, 2] - shortest path
1930
2026
  * console.log(result.value.distance) // 2 - total distance
@@ -1934,25 +2030,31 @@ export const floydWarshall = (graph, edgeWeight) => {
1934
2030
  * @since 3.18.0
1935
2031
  * @category algorithms
1936
2032
  */
1937
- export const astar = (graph, source, target, edgeWeight, heuristic) => {
2033
+ export const astar = (graph, config) => {
2034
+ const {
2035
+ cost,
2036
+ heuristic,
2037
+ source,
2038
+ target
2039
+ } = config;
1938
2040
  // Validate that source and target nodes exist
1939
2041
  if (!graph.nodes.has(source)) {
1940
- throw new Error(`Source node ${source} does not exist`);
2042
+ throw missingNode(source);
1941
2043
  }
1942
2044
  if (!graph.nodes.has(target)) {
1943
- throw new Error(`Target node ${target} does not exist`);
2045
+ throw missingNode(target);
1944
2046
  }
1945
2047
  // Early return if source equals target
1946
2048
  if (source === target) {
1947
2049
  return Option.some({
1948
2050
  path: [source],
1949
2051
  distance: 0,
1950
- edgeWeights: []
2052
+ costs: []
1951
2053
  });
1952
2054
  }
1953
2055
  // Get target node data for heuristic calculations
1954
- const targetNodeData = getMapSafe(graph.nodes, target);
1955
- if (Option.isNone(targetNodeData)) {
2056
+ const targetNodeData = graph.nodes.get(target);
2057
+ if (targetNodeData === undefined) {
1956
2058
  throw new Error(`Target node ${target} data not found`);
1957
2059
  }
1958
2060
  // Distance tracking (g-score) and f-score (g + h)
@@ -1968,9 +2070,9 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
1968
2070
  previous.set(node, null);
1969
2071
  }
1970
2072
  // Calculate initial f-score for source
1971
- const sourceNodeData = getMapSafe(graph.nodes, source);
1972
- if (Option.isSome(sourceNodeData)) {
1973
- const h = heuristic(sourceNodeData.value, targetNodeData.value);
2073
+ const sourceNodeData = graph.nodes.get(source);
2074
+ if (sourceNodeData !== undefined) {
2075
+ const h = heuristic(sourceNodeData, targetNodeData);
1974
2076
  fScore.set(source, h);
1975
2077
  }
1976
2078
  // Priority queue using f-score (total estimated cost)
@@ -2000,13 +2102,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2000
2102
  // Get current g-score
2001
2103
  const currentGScore = gScore.get(currentNode);
2002
2104
  // Examine all outgoing edges
2003
- const adjacencyList = getMapSafe(graph.adjacency, currentNode);
2004
- if (Option.isSome(adjacencyList)) {
2005
- for (const edgeIndex of adjacencyList.value) {
2006
- const edge = getMapSafe(graph.edges, edgeIndex);
2007
- if (Option.isSome(edge)) {
2008
- const neighbor = edge.value.target;
2009
- const weight = edgeWeight(edge.value.data);
2105
+ const adjacencyList = graph.adjacency.get(currentNode);
2106
+ if (adjacencyList !== undefined) {
2107
+ for (const edgeIndex of adjacencyList) {
2108
+ const edge = graph.edges.get(edgeIndex);
2109
+ if (edge !== undefined) {
2110
+ const neighbor = edge.target;
2111
+ const weight = cost(edge.data);
2010
2112
  // Validate non-negative weights
2011
2113
  if (weight < 0) {
2012
2114
  throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
@@ -2019,12 +2121,12 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2019
2121
  gScore.set(neighbor, tentativeGScore);
2020
2122
  previous.set(neighbor, {
2021
2123
  node: currentNode,
2022
- edgeData: edge.value.data
2124
+ edgeData: edge.data
2023
2125
  });
2024
2126
  // Calculate f-score using heuristic
2025
- const neighborNodeData = getMapSafe(graph.nodes, neighbor);
2026
- if (Option.isSome(neighborNodeData)) {
2027
- const h = heuristic(neighborNodeData.value, targetNodeData.value);
2127
+ const neighborNodeData = graph.nodes.get(neighbor);
2128
+ if (neighborNodeData !== undefined) {
2129
+ const h = heuristic(neighborNodeData, targetNodeData);
2028
2130
  const f = tentativeGScore + h;
2029
2131
  fScore.set(neighbor, f);
2030
2132
  // Add to open set if not visited
@@ -2047,13 +2149,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2047
2149
  }
2048
2150
  // Reconstruct path
2049
2151
  const path = [];
2050
- const edgeWeights = [];
2152
+ const costs = [];
2051
2153
  let currentNode = target;
2052
2154
  while (currentNode !== null) {
2053
2155
  path.unshift(currentNode);
2054
2156
  const prev = previous.get(currentNode);
2055
2157
  if (prev !== null) {
2056
- edgeWeights.unshift(prev.edgeData);
2158
+ costs.unshift(prev.edgeData);
2057
2159
  currentNode = prev.node;
2058
2160
  } else {
2059
2161
  currentNode = null;
@@ -2062,7 +2164,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2062
2164
  return Option.some({
2063
2165
  path,
2064
2166
  distance: targetGScore,
2065
- edgeWeights
2167
+ costs
2066
2168
  });
2067
2169
  };
2068
2170
  /**
@@ -2085,7 +2187,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2085
2187
  * Graph.addEdge(mutable, a, c, 5)
2086
2188
  * })
2087
2189
  *
2088
- * const result = Graph.bellmanFord(graph, 0, 2, (edgeData) => edgeData)
2190
+ * const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
2089
2191
  * if (Option.isSome(result)) {
2090
2192
  * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
2091
2193
  * console.log(result.value.distance) // 2 - total distance
@@ -2095,20 +2197,25 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
2095
2197
  * @since 3.18.0
2096
2198
  * @category algorithms
2097
2199
  */
2098
- export const bellmanFord = (graph, source, target, edgeWeight) => {
2200
+ export const bellmanFord = (graph, config) => {
2201
+ const {
2202
+ cost,
2203
+ source,
2204
+ target
2205
+ } = config;
2099
2206
  // Validate that source and target nodes exist
2100
2207
  if (!graph.nodes.has(source)) {
2101
- throw new Error(`Source node ${source} does not exist`);
2208
+ throw missingNode(source);
2102
2209
  }
2103
2210
  if (!graph.nodes.has(target)) {
2104
- throw new Error(`Target node ${target} does not exist`);
2211
+ throw missingNode(target);
2105
2212
  }
2106
2213
  // Early return if source equals target
2107
2214
  if (source === target) {
2108
2215
  return Option.some({
2109
2216
  path: [source],
2110
2217
  distance: 0,
2111
- edgeWeights: []
2218
+ costs: []
2112
2219
  });
2113
2220
  }
2114
2221
  // Initialize distances and predecessors
@@ -2122,7 +2229,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
2122
2229
  // Collect all edges for relaxation
2123
2230
  const edges = [];
2124
2231
  for (const [, edgeData] of graph.edges) {
2125
- const weight = edgeWeight(edgeData.data);
2232
+ const weight = cost(edgeData.data);
2126
2233
  edges.push({
2127
2234
  source: edgeData.source,
2128
2235
  target: edgeData.target,
@@ -2165,12 +2272,12 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
2165
2272
  if (affectedNodes.has(node)) continue;
2166
2273
  affectedNodes.add(node);
2167
2274
  // Add all nodes reachable from this node
2168
- const adjacencyList = getMapSafe(graph.adjacency, node);
2169
- if (Option.isSome(adjacencyList)) {
2170
- for (const edgeIndex of adjacencyList.value) {
2171
- const edge = getMapSafe(graph.edges, edgeIndex);
2172
- if (Option.isSome(edge)) {
2173
- queue.push(edge.value.target);
2275
+ const adjacencyList = graph.adjacency.get(node);
2276
+ if (adjacencyList !== undefined) {
2277
+ for (const edgeIndex of adjacencyList) {
2278
+ const edge = graph.edges.get(edgeIndex);
2279
+ if (edge !== undefined) {
2280
+ queue.push(edge.target);
2174
2281
  }
2175
2282
  }
2176
2283
  }
@@ -2188,13 +2295,13 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
2188
2295
  }
2189
2296
  // Reconstruct path
2190
2297
  const path = [];
2191
- const edgeWeights = [];
2298
+ const costs = [];
2192
2299
  let currentNode = target;
2193
2300
  while (currentNode !== null) {
2194
2301
  path.unshift(currentNode);
2195
2302
  const prev = previous.get(currentNode);
2196
2303
  if (prev !== null) {
2197
- edgeWeights.unshift(prev.edgeData);
2304
+ costs.unshift(prev.edgeData);
2198
2305
  currentNode = prev.node;
2199
2306
  } else {
2200
2307
  currentNode = null;
@@ -2203,7 +2310,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
2203
2310
  return Option.some({
2204
2311
  path,
2205
2312
  distance: targetDistance,
2206
- edgeWeights
2313
+ costs
2207
2314
  });
2208
2315
  };
2209
2316
  /**
@@ -2224,7 +2331,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
2224
2331
  * })
2225
2332
  *
2226
2333
  * // Both traversal and element iterators return NodeWalker
2227
- * const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { startNodes: [0] })
2334
+ * const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
2228
2335
  * const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
2229
2336
  *
2230
2337
  * // Common interface for working with node iterables
@@ -2260,7 +2367,7 @@ export class Walker {
2260
2367
  * Graph.addEdge(mutable, a, b, 1)
2261
2368
  * })
2262
2369
  *
2263
- * const dfs = Graph.dfs(graph, { startNodes: [0] })
2370
+ * const dfs = Graph.dfs(graph, { start: [0] })
2264
2371
  *
2265
2372
  * // Map to just the node data
2266
2373
  * const values = Array.from(dfs.visit((index, data) => data))
@@ -2293,7 +2400,7 @@ export class Walker {
2293
2400
  * Graph.addEdge(mutable, a, b, 1)
2294
2401
  * })
2295
2402
  *
2296
- * const dfs = Graph.dfs(graph, { startNodes: [0] })
2403
+ * const dfs = Graph.dfs(graph, { start: [0] })
2297
2404
  *
2298
2405
  * // Map to just the node data
2299
2406
  * const values = Array.from(dfs.visit((index, data) => data))
@@ -2325,7 +2432,7 @@ export class Walker {
2325
2432
  * Graph.addEdge(mutable, a, b, 1)
2326
2433
  * })
2327
2434
  *
2328
- * const dfs = Graph.dfs(graph, { startNodes: [0] })
2435
+ * const dfs = Graph.dfs(graph, { start: [0] })
2329
2436
  * const indices = Array.from(Graph.indices(dfs))
2330
2437
  * console.log(indices) // [0, 1]
2331
2438
  * ```
@@ -2347,7 +2454,7 @@ export const indices = walker => walker.visit((index, _) => index);
2347
2454
  * Graph.addEdge(mutable, a, b, 1)
2348
2455
  * })
2349
2456
  *
2350
- * const dfs = Graph.dfs(graph, { startNodes: [0] })
2457
+ * const dfs = Graph.dfs(graph, { start: [0] })
2351
2458
  * const values = Array.from(Graph.values(dfs))
2352
2459
  * console.log(values) // ["A", "B"]
2353
2460
  * ```
@@ -2369,7 +2476,7 @@ export const values = walker => walker.visit((_, data) => data);
2369
2476
  * Graph.addEdge(mutable, a, b, 1)
2370
2477
  * })
2371
2478
  *
2372
- * const dfs = Graph.dfs(graph, { startNodes: [0] })
2479
+ * const dfs = Graph.dfs(graph, { start: [0] })
2373
2480
  * const entries = Array.from(Graph.entries(dfs))
2374
2481
  * console.log(entries) // [[0, "A"], [1, "B"]]
2375
2482
  * ```
@@ -2397,7 +2504,7 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
2397
2504
  * })
2398
2505
  *
2399
2506
  * // Start from a specific node
2400
- * const dfs1 = Graph.dfs(graph, { startNodes: [0] })
2507
+ * const dfs1 = Graph.dfs(graph, { start: [0] })
2401
2508
  * for (const nodeIndex of Graph.indices(dfs1)) {
2402
2509
  * console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
2403
2510
  * }
@@ -2411,17 +2518,17 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
2411
2518
  * @category iterators
2412
2519
  */
2413
2520
  export const dfs = (graph, config = {}) => {
2414
- const startNodes = config.startNodes ?? [];
2521
+ const start = config.start ?? [];
2415
2522
  const direction = config.direction ?? "outgoing";
2416
2523
  // Validate that all start nodes exist
2417
- for (const nodeIndex of startNodes) {
2524
+ for (const nodeIndex of start) {
2418
2525
  if (!hasNode(graph, nodeIndex)) {
2419
- throw new Error(`Start node ${nodeIndex} does not exist`);
2526
+ throw missingNode(nodeIndex);
2420
2527
  }
2421
2528
  }
2422
2529
  return new Walker(f => ({
2423
2530
  [Symbol.iterator]: () => {
2424
- const stack = [...startNodes];
2531
+ const stack = [...start];
2425
2532
  const discovered = new Set();
2426
2533
  const nextMapped = () => {
2427
2534
  while (stack.length > 0) {
@@ -2430,8 +2537,8 @@ export const dfs = (graph, config = {}) => {
2430
2537
  continue;
2431
2538
  }
2432
2539
  discovered.add(current);
2433
- const nodeDataOption = getMapSafe(graph.nodes, current);
2434
- if (Option.isNone(nodeDataOption)) {
2540
+ const nodeDataOption = graph.nodes.get(current);
2541
+ if (nodeDataOption === undefined) {
2435
2542
  continue;
2436
2543
  }
2437
2544
  const neighbors = neighborsDirected(graph, current, direction);
@@ -2443,7 +2550,7 @@ export const dfs = (graph, config = {}) => {
2443
2550
  }
2444
2551
  return {
2445
2552
  done: false,
2446
- value: f(current, nodeDataOption.value)
2553
+ value: f(current, nodeDataOption)
2447
2554
  };
2448
2555
  }
2449
2556
  return {
@@ -2476,7 +2583,7 @@ export const dfs = (graph, config = {}) => {
2476
2583
  * })
2477
2584
  *
2478
2585
  * // Start from a specific node
2479
- * const bfs1 = Graph.bfs(graph, { startNodes: [0] })
2586
+ * const bfs1 = Graph.bfs(graph, { start: [0] })
2480
2587
  * for (const nodeIndex of Graph.indices(bfs1)) {
2481
2588
  * console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
2482
2589
  * }
@@ -2490,17 +2597,17 @@ export const dfs = (graph, config = {}) => {
2490
2597
  * @category iterators
2491
2598
  */
2492
2599
  export const bfs = (graph, config = {}) => {
2493
- const startNodes = config.startNodes ?? [];
2600
+ const start = config.start ?? [];
2494
2601
  const direction = config.direction ?? "outgoing";
2495
2602
  // Validate that all start nodes exist
2496
- for (const nodeIndex of startNodes) {
2603
+ for (const nodeIndex of start) {
2497
2604
  if (!hasNode(graph, nodeIndex)) {
2498
- throw new Error(`Start node ${nodeIndex} does not exist`);
2605
+ throw missingNode(nodeIndex);
2499
2606
  }
2500
2607
  }
2501
2608
  return new Walker(f => ({
2502
2609
  [Symbol.iterator]: () => {
2503
- const queue = [...startNodes];
2610
+ const queue = [...start];
2504
2611
  const discovered = new Set();
2505
2612
  const nextMapped = () => {
2506
2613
  while (queue.length > 0) {
@@ -2588,7 +2695,7 @@ export const topo = (graph, config = {}) => {
2588
2695
  // Validate that all initial nodes exist
2589
2696
  for (const nodeIndex of initials) {
2590
2697
  if (!hasNode(graph, nodeIndex)) {
2591
- throw new Error(`Initial node ${nodeIndex} does not exist`);
2698
+ throw missingNode(nodeIndex);
2592
2699
  }
2593
2700
  }
2594
2701
  return new Walker(f => ({
@@ -2673,7 +2780,7 @@ export const topo = (graph, config = {}) => {
2673
2780
  * })
2674
2781
  *
2675
2782
  * // Postorder: children before parents
2676
- * const postOrder = Graph.dfsPostOrder(graph, { startNodes: [0] })
2783
+ * const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
2677
2784
  * for (const node of postOrder) {
2678
2785
  * console.log(node) // 1, 2, 0
2679
2786
  * }
@@ -2683,12 +2790,12 @@ export const topo = (graph, config = {}) => {
2683
2790
  * @category iterators
2684
2791
  */
2685
2792
  export const dfsPostOrder = (graph, config = {}) => {
2686
- const startNodes = config.startNodes ?? [];
2793
+ const start = config.start ?? [];
2687
2794
  const direction = config.direction ?? "outgoing";
2688
2795
  // Validate that all start nodes exist
2689
- for (const nodeIndex of startNodes) {
2796
+ for (const nodeIndex of start) {
2690
2797
  if (!hasNode(graph, nodeIndex)) {
2691
- throw new Error(`Start node ${nodeIndex} does not exist`);
2798
+ throw missingNode(nodeIndex);
2692
2799
  }
2693
2800
  }
2694
2801
  return new Walker(f => ({
@@ -2697,9 +2804,9 @@ export const dfsPostOrder = (graph, config = {}) => {
2697
2804
  const discovered = new Set();
2698
2805
  const finished = new Set();
2699
2806
  // Initialize stack with start nodes
2700
- for (let i = startNodes.length - 1; i >= 0; i--) {
2807
+ for (let i = start.length - 1; i >= 0; i--) {
2701
2808
  stack.push({
2702
- node: startNodes[i],
2809
+ node: start[i],
2703
2810
  visitedChildren: false
2704
2811
  });
2705
2812
  }
@@ -2885,9 +2992,9 @@ export const externals = (graph, config = {}) => {
2885
2992
  let current = nodeIterator.next();
2886
2993
  while (!current.done) {
2887
2994
  const [nodeIndex, nodeData] = current.value;
2888
- const adjacencyList = getMapSafe(adjacencyMap, nodeIndex);
2995
+ const adjacencyList = adjacencyMap.get(nodeIndex);
2889
2996
  // Node is external if it has no edges in the specified direction
2890
- if (Option.isNone(adjacencyList) || adjacencyList.value.length === 0) {
2997
+ if (adjacencyList === undefined || adjacencyList.length === 0) {
2891
2998
  return {
2892
2999
  done: false,
2893
3000
  value: f(nodeIndex, nodeData)