This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IMPORTANT: When working with code in this repository, always refer to these official documentation sources:
- ALWAYS USE THIS FOR RESCRIPT CODE: https://rescript-lang.org/llms/manual/llms.txt
- LLM Full Documentation: https://rescript-lang.org/llms/manual/llm-full.txt
- Language Manual: https://rescript-lang.org/docs/manual/introduction
- Use for:
- ReScript syntax and language features
- Standard library APIs
- Type system details
- External bindings and interop patterns
- Best practices and idioms
- Ensure suggestions match this version. Refer to the indexed ReScript manual and LLM documentation.
- When dealing with promises, prefer using
async/awaitsyntax. - Never ever use the
BeltorJsmodules, these are legacy. - Always use the
JSON.ttype for json. - Module with React components do require a signature file (
.resi) for Vite HMR to work. Only the React components can be exposed from the javascript.
- URL: https://graphology.github.io
- Use for:
- Graphology API reference
- Graph data structure concepts
- Algorithm implementations
- JavaScript API behavior that this ReScript binding wraps
This is a ReScript binding for the Graphology JavaScript graph library. It provides type-safe bindings to Graphology's graph data structures and algorithms, including shortest path algorithms, traversal methods, layout algorithms, and import/export functionality (GEXF, SVG).
Important: The project is currently on ReScript v12.0.0-rc.5 and has been upgraded to use the latest Graphology libraries that support the JS native Iterator protocol.
- Build:
npm run buildoryarn buildorrescript build . - Watch mode:
npm run watchoryarn watchorrescript watch . - Clean:
npm run cleanoryarn cleanorrescript clean . - Test:
npm testoryarn test(runs Jest) - Run demo:
node src/Demo.res.mjs
The codebase uses a functor-based architecture to provide type-safe graph bindings:
-
Core Type Module (
Graphology__GraphTypes.res): Defines the base module typeTwith abstract types fort(graph),node,edge, and attribute types (graphAttr,nodeAttr,edgeAttr). These use extensible object types{..} as 'afor flexible attribute handling. -
Main Graph Module (
Graphology__Graph.res): Contains:CONFIGmodule type for specifying concretenodeandedgetypesGRAPHmodule type defining the full graph APIMakeGraphfunctor that takes aCONFIGand produces aGRAPHimplementation
-
Iterator Modules: Separate functor-based modules for iteration:
Graphology__Graph_NodesIter.res: Node iteration (nodes, forEachNode, mapNodes, filterNodes, nodeEntries, etc.)Graphology__Graph_EdgesIter.res: Edge iteration with direction variants (All, Node, InOut, FromTo)Graphology__Graph_NeighborsIter.res: Neighbor traversal
-
Algorithm Modules: Each algorithm library is in its own functor:
Graphology__ShortestPath.res: Dijkstra, A*, unweighted shortest pathsGraphology__SimplePath.res: Simple path algorithmsGraphology__Traversal.res: BFS, DFS traversalGraphology__Layout.res: Circular, CirclePack, Rotation, etc.Graphology__Generators.res: Graph generators (karate club, etc.)Graphology__Utils.res: Utility functions (isGraph, inferMulti, inferType, mergeClique, mergeCycle, mergePath, mergeStar, renameGraphKeys, updateGraphKeys)
-
Import/Export Modules:
Graphology__GEXF.res: GEXF format import/exportGraphology__SVG.res: SVG rendering
-
Top-level Module (
Graphology.res): Re-exports all submodules for convenient access.
To use the library, instantiate a graph with concrete types via the MakeGraph functor:
module G = Graph.MakeGraph({
type node = string
type edge = string
})
let g = G.makeGraph()
g->G.addNode("John")
g->G.addEdge("John", "Mary")All algorithm modules (ShortestPath, Layout, Traversal, etc.) are nested under the instantiated graph module, ensuring type consistency.
- Functors for Type Safety: The functor pattern ensures that graph types, node types, and edge types remain consistent across all operations and algorithm invocations.
- Module Composition: The
GRAPHmodule signature includes all iterator and algorithm modules, making them accessible through a single instantiation. - Native Iterator Support: Recent upgrade added support for JavaScript's native Iterator protocol (e.g.,
nodeEntriesreturnsIterator.t<nodeIterValue<'a>>).
- Package manager: Yarn 4.11.0 (configured via
packageManagerfield) - Module system: ES modules with in-source builds (
.res.mjssuffix) - Source directory:
src/with subdirectories - Dependencies: Uses
rescript-nodejsfor Node.js bindings
This project uses Jest with @glennsl/rescript-jest for testing:
- Test directory:
__tests__/ - Naming convention:
*_Test.res(e.g.,Graphology_Test.res) - Test framework: Jest 27.3.1 with Babel for transpilation
- ReScript bindings:
@glennsl/rescript-jest(ReScript v12 fork)
The project uses Jest with Babel transformation to handle ES modules:
- Test files match pattern:
**/__tests__/*_Test.res.(js|ts|jsx|tsx|mjs) - Babel preset-env configured for Node.js with CommonJS modules
- Transform ignore patterns configured for ReScript packages
- Test script:
npm testruns all tests in the__tests__/directory
open Jest
open Expect
describe("Test Suite Name", () => {
test("test description", () => {
expect(actualValue)->toEqual(expectedValue)
})
})The project has comprehensive test coverage across all modules:
- Graph_Test.res: Core graph operations (329 total tests across all modules)
- NodesIter_Test.res: Node iteration functions
- EdgesIter_Test.res: Edge iteration functions
- ShortestPath_Test.res: Shortest path algorithms
- SimplePath_Test.res: Simple path algorithms
- Traversal_Test.res: BFS/DFS traversal
- Layout_Test.res: Layout algorithms
- Generators_Test.res: Graph generators
- Utils_Test.res: Utility functions (80 tests)
- GEXF_Test.res: GEXF import/export
- SVG_Test.res: SVG rendering
IMPORTANT: Each test must have only a single expect statement. Use tuples to test multiple values:
// CORRECT - Single expect with tuple
test("returns multiple values", () => {
let result1 = doSomething()
let result2 = doSomethingElse()
expect((result1, result2))->toEqual((expected1, expected2))
})
// INCORRECT - Multiple expects
test("returns multiple values", () => {
expect(doSomething())->toEqual(expected1) // ❌ Don't do this
expect(doSomethingElse())->toEqual(expected2) // ❌ Don't do this
})- Use
areNeighborsto check if two nodes are connected (nothasEdgewith source/target) - Use
hasEdgewith an edge key to check if a specific edge exists - Pattern functions (
mergeClique,mergePath,mergeStar,mergeCycle) may behave differently with single nodes - Use parentheses when negating function calls:
!(g->G.areNeighbors("A", "B"))
The iterator modules (NodesIter, EdgesIter, NeighborsIter) are accessed as nested modules:
// Correct way to access iterator functions
g->G.NodesIter.nodes // Get all nodes
g->G.EdgesIter.edges(All) // Get all edges
g->G.NeighborsIter.neighbors(Node("A")) // Get neighbors of node AKey Iterator Module Behaviors:
-
NodesIter module:
- VERIFIED:
reduceNodescorrectly returns generic'rtype (accumulated result) - VERIFIED:
findNodereturnsNullable.t<node>(can returnundefinedif no match found) - Callbacks receive:
(node, attributes) nodeEntriesreturns an iterator over{node, attributes}objects
- VERIFIED:
-
EdgesIter module:
- Edge iteration uses variant arguments:
All,Node(nodeKey),InOut(nodeKey),FromTo(source, target) - VERIFIED: Edge callbacks receive:
(edge, edgeAttr, source, target, sourceAttr, targetAttr, undirected) - VERIFIED:
findEdgecallback returnsbool(predicate function) - VERIFIED:
findEdgereturnsNullable.t<edge>(can returnundefinedif no match found) reduceEdgesreturns generic'rtypeedgeEntriesiterator provides:{edge, attributes, source, target, sourceAttributes, targetAttributes}
- Edge iteration uses variant arguments:
-
NeighborsIter module:
- VERIFIED:
neighborsfunction returns an array of neighbor node keys (not edge keys) - VERIFIED: Neighbor iteration callbacks receive:
(neighbor, attributes)- NOT edge information - VERIFIED:
reduceNeighborscallback:(accumulator, neighbor, attributes) => 'r - VERIFIED:
someNeighborandeveryNeighborcallbacks:(neighbor, attributes) => bool - VERIFIED:
findNeighborcallback:(neighbor, attributes) => bool, returnsNullable.t<edge> - Use
neighbors(Node("A"))to get neighbors of a specific node (notneighbors(All)) - VERIFIED:
neighborEntriesiterator provides:{neighbor, attributes}(not edge information) - The module focuses on neighbor nodes and their attributes, not the edges connecting them
- VERIFIED:
-
Common Iterator Patterns:
- Variant arguments must wrap callbacks:
All((arg1, arg2) => ...)notAll(arg1, arg2 => ...) - Iterator protocol: check
next.done, accessnext.valuewhich isOption<T> - Use recursive functions to consume iterators completely:
let rec collect = () => { let next = iter->Iterator.next if !next.done { // Process next.value collect() } }
- Variant arguments must wrap callbacks:
- Default
makeGraph()creates a simple undirected graph (no multi-edges, no mixed edges) - Use
makeDirectedGraph(),makeUndirectedGraph(),makeMultiGraph(), etc. for specific graph types - There is no
makeMixedGraph()- usemakeGraph()with appropriate options if mixed edges are needed - Methods like
addDirectedEdgeandaddUndirectedEdgeare not available on all graph types
IMPORTANT: ReScript v12 introduced breaking changes in the standard library APIs. Always use the modern APIs:
- Use
Array.includesinstead of deprecatedJs.Array2.includes - Use
Array.pushinstead of deprecatedJs.Array2.push- Breaking change: Returns
unit(not array length like in v11) - No need for
->ignore: Since it returnsunit, you can call it directly - Example:
// ReScript v11 (deprecated) arr->Js.Array2.push(item)->ignore // Returns length, need to ignore // ReScript v12 (correct) arr->Array.push(item) // Returns unit, no ignore needed
- Breaking change: Returns
- Use
String.startsWithinstead ofJs.String2.startsWith - Use
Dict.getinstead of deprecatedJs.Dict.getfor dict operations - Attribute objects use extensible object types
{..} as 'a- access withobj["key"]syntax
Migration Tip: Run rescript-tools migrate-all <project-root> to automatically migrate deprecated APIs, but always review the changes as some migrations may need manual adjustments.
CRITICAL: All bindings have been verified against the official Graphology API documentation at https://graphology.github.io (as of 2025-11-25).
-
Node Iteration - Callbacks receive:
(node, attributes)- Returns: Arrays of nodes, generic types for reduce,
Nullable.t<node>for find
- Returns: Arrays of nodes, generic types for reduce,
-
Edge Iteration - Callbacks receive:
(edge, attributes, source, target, sourceAttributes, targetAttributes, undirected)- Returns: Arrays of edges, generic types for reduce,
Nullable.t<edge>for find
- Returns: Arrays of edges, generic types for reduce,
-
Neighbor Iteration - Callbacks receive:
(neighbor, attributes)(NOT edge information!)- Returns: Arrays of neighbor nodes, generic types for reduce,
Nullable.t<edge>for find - This is the most commonly misunderstood API - neighbors are nodes, not edges
- Returns: Arrays of neighbor nodes, generic types for reduce,
The following functions return Nullable.t because they can return null/undefined:
- Node iteration:
findNode→Nullable.t<node> - Edge iteration:
findEdge→Nullable.t<edge> - Neighbor iteration:
findNeighbor→Nullable.t<edge> - Shortest paths: All
bidirectionalfunctions →Nullable.t<array<node>>Unweighted.bidirectional(source, target)→Nullable.t<array<node>>Dijkstra.bidirectional(source, target, ~weight)→Nullable.t<array<node>>AStar.bidirectional(source, target, ~weight, ~heuristic)→Nullable.t<array<node>>
// Pattern 1: Using Nullable.make for equality
let result = g->G.NodesIter.findNode(predicate)
expect(result)->toEqual(Nullable.make("expectedNode"))
// Pattern 2: Using pattern matching for complex logic
let result = g->G.NodesIter.findNode(predicate)
switch result->Nullable.toOption {
| Some(node) => expect(node)->toBe("expectedNode")
| None => fail("Expected to find a node")
}
// Pattern 3: For shortest paths
let path = g->G.ShortestPath.Unweighted.bidirectional("A", "C")
expect(path)->toEqual(Nullable.make(["A", "B", "C"]))CRITICAL: When reviewing or modifying tests, follow this systematic process to ensure API compliance:
- ALWAYS consult the official Graphology documentation first: https://graphology.github.io
- NEVER assume API behavior - verify against official docs
- Check both the API reference AND design choices: https://graphology.github.io/design-choices.html
Different iteration methods have COMPLETELY different callback signatures - verify each one:
// Node iteration callbacks: (node, attributes)
g->G.NodesIter.forEachNode((node, attr) => ...)
// Edge iteration callbacks: (edge, attr, source, target, sourceAttr, targetAttr, undirected)
g->G.EdgesIter.forEachEdge(All((edge, attr, src, tgt, sAttr, tAttr, undirected) => ...))
// Neighbor iteration callbacks: (neighbor, attributes) - NOT edge info!
g->G.NeighborsIter.forEachNeighbor(Node("A", (neighbor, attr) => ...))Common Mistake: Confusing neighbor iteration with edge iteration. Neighbors are NODES, not edges!
- Find operations: Return
Nullable.t<T>(not direct values)findNode,findEdge,findNeighborall return nullable
- Shortest paths: ALL
bidirectionalfunctions returnNullable.t<array<node>> - Reduce operations: Return generic
'rtype (accumulated result type)
- String-based keys: Graphology coerces all keys to strings internally
- Using
type node = intis OK - Graphology will coerce to string - Don't assume numeric ordering of integer keys
- Using
- No insertion order: Never rely on iteration order matching insertion order
- Return values:
addNode/addEdgereturn the element, other methods return graph for chaining - Errors throw: Graphology throws errors rather than returning error codes
When reviewing tests, verify:
- ✅ Callback signatures match official API exactly
- ✅ Nullable returns are handled with
Nullable.make()or.toOption - ✅ No assumptions about insertion order
- ✅ No assumptions about key types (strings vs integers)
- ✅ Each test has only ONE expect statement (use tuples for multiple values)
- ✅ Tests don't rely on implementation details
❌ WRONG: Assuming neighbors returns edges
// This is WRONG - neighbors returns array of NODES, not edges
let neighborEdges = g->G.NeighborsIter.neighbors(Node("A"))✅ CORRECT: Understanding neighbors returns nodes
// This is CORRECT - neighbors returns array of neighbor NODES
let neighborNodes = g->G.NeighborsIter.neighbors(Node("A"))❌ WRONG: Not handling nullable returns
// This is WRONG - findNode returns Nullable.t<node>
let node = g->G.NodesIter.findNode(predicate)
expect(node)->toBe("Alice") // Type error!✅ CORRECT: Handling nullable with Nullable.make or pattern matching
// CORRECT - using Nullable.make
let node = g->G.NodesIter.findNode(predicate)
expect(node)->toEqual(Nullable.make("Alice"))
// CORRECT - using pattern matching
switch node->Nullable.toOption {
| Some(n) => expect(n)->toBe("Alice")
| None => fail("Expected to find node")
}❌ WRONG: Using wrong callback signatures
// This is WRONG - neighbor callbacks only receive (neighbor, attributes)
g->G.NeighborsIter.forEachNeighbor(Node("A",
(edge, attr, source, target, sAttr, tAttr, undirected) => ... // WRONG!
))✅ CORRECT: Using correct callback signatures
// CORRECT - neighbor callbacks receive (neighbor, attributes)
g->G.NeighborsIter.forEachNeighbor(Node("A",
(neighbor, attr) => ... // CORRECT!
))Latest comprehensive test compliance review (2025-11-25):
- ✅ All 532 tests reviewed and verified against official API
- ✅ 12 test files fully compliant with Graphology API
- ✅ No API compliance issues found
- ✅ All callback signatures verified correct
- ✅ All nullable returns handled properly
- ✅ All design choices respected (string keys, no order guarantees, etc.)
Test Files Verified:
- Graph_Test.res - Core operations ✅
- NodesIter_Test.res - Node iteration ✅
- EdgesIter_Test.res - Edge iteration ✅
- NeighborsIter_Test.res - Neighbor iteration ✅
- ShortestPath_Test.res - Shortest path algorithms ✅
- Traversal_Test.res - BFS/DFS traversal ✅
- Layout_Test.res - Layout algorithms ✅
- Generators_Test.res - Graph generators ✅
- Utils_Test.res - Utility functions ✅
- SimplePath_Test.res - Simple path algorithms ✅
- GEXF_Test.res - GEXF import/export ✅
- SVG_Test.res - SVG rendering ✅
- Some demo code in
Demo.resincludes commented-out sections and experimental iterator usage - Warning suppressions (
@@warning("-26-32-44-27")) are used in demo files - The project includes several
.gexfgraph files for testing/demo purposes - Ensure each test has only a single expect statement, using tuples where multiple results need to be tested
- Remember to use conventional commits spec for commit message
- Remember to run tests and make sure all tests passes before committing any changes
- ALWAYS verify bindings against official documentation: https://graphology.github.io is the single source of truth for the Graphology API
- Remember https://graphology.github.io/design-choices.html contains the design choices for Graphology - this is the authoritative source