npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eeegl/tsgraph

v1.0.1

Published

A TypeScript library for immutable directed graphs with functional operations

Readme

@eeegl/tsgraph

A TypeScript library for immutable directed graphs with functional operations.

Installation

npm install @eeegl/tsgraph

Quick Start

import { createGraphV1, newNode, newEdge } from "@eeegl/tsgraph";

const graph = createGraphV1<string, string>();

const nodeA = newNode("A");
const nodeB = newNode("B");

const g = graph
  .setNode(nodeA)
  .setNode(nodeB)
  .setEdge(newEdge({ fromId: nodeA.id, toId: nodeB.id, value: "connects" }));

console.log(g.nodeCount()); // 2
console.log(g.edgeCount()); // 1

Features

  • Fully immutable operations
  • Type-safe with TypeScript generics
  • Functional programming style (map, filter, reduce)
  • Error handling with Result types
  • JSON serialization/deserialization

API Reference

Creating a Graph

createGraphV1<N, E>()

Creates a new empty directed graph.

Type Parameters:

  • N - Type of node values
  • E - Type of edge values (defaults to string)

Returns: DiGraph<N, E>

Example:

const graph = createGraphV1<number, string>();

Graph Properties

id(): string

Returns the unique identifier of the graph.

created(): IsoDatetimeUtcExtendedMs

Returns the creation timestamp of the graph in ISO format.

error(): Error | undefined

Returns the error state of the graph, or undefined if no error exists.

hasNodes(): boolean

Returns true if the graph contains any nodes.

hasEdges(): boolean

Returns true if the graph contains any edges.

nodeCount(predicate?: (node: Node<N>) => boolean): number

Returns the count of nodes. If predicate is provided, counts only matching nodes.

Example:

const total = graph.nodeCount();
const filtered = graph.nodeCount(node => node.value > 10);

edgeCount(predicate?: (edge: Edge<E>) => boolean): number

Returns the count of edges. If predicate is provided, counts only matching edges.

Creating Nodes and Edges

newNode<N>(value: N): Node<N>

Creates a new node with the given value. The node has a unique ID and timestamp. This is a standalone function exported from the package.

Example:

import { newNode } from "@eeegl/tsgraph";

const node = newNode({ name: "Alice", age: 30 });

newEdge<E>(params: { fromId: string; toId: string; value: E }): Edge<E>

Creates a new edge connecting two nodes. This is a standalone function exported from the package.

Example:

import { newEdge } from "@eeegl/tsgraph";

const edge = newEdge({
  fromId: nodeA.id,
  toId: nodeB.id,
  value: "follows"
});

Modifying the Graph

setNode(node: Node<N>): DiGraph<N, E>

Adds or updates a node in the graph. Returns a new graph instance.

Example:

import { newNode } from "@eeegl/tsgraph";

const node = newNode("value");
const newGraph = graph.setNode(node);

setEdge(edge: Edge<E>): DiGraph<N, E>

Adds an edge to the graph. Both nodes must exist. Updates the edge lists of connected nodes. Returns a new graph instance or a graph with an error if nodes don't exist.

Example:

import { newEdge } from "@eeegl/tsgraph";

const edge = newEdge({ fromId: n1.id, toId: n2.id, value: "edge" });
const newGraph = graph.setEdge(edge);

Querying the Graph

getNode(id: string): Node<N> | undefined

Retrieves a node by its ID.

Example:

const node = graph.getNode("node-id");
if (node) {
  console.log(node.value);
}

getEdge(id: string): Edge<E> | undefined

Retrieves an edge by its ID.

nodes(predicate?: (node: Node<N>) => boolean): Node<N>[]

Returns all nodes, optionally filtered by predicate.

Example:

const allNodes = graph.nodes();
const filtered = graph.nodes(node => node.value > 5);

edges(predicate?: (edge: Edge<E>) => boolean): Edge<E>[]

Returns all edges, optionally filtered by predicate.

nodeValues(predicate?: (value: N) => boolean): N[]

Returns all node values, optionally filtered by predicate.

Example:

const values = graph.nodeValues();
const positives = graph.nodeValues(v => v > 0);

edgeValues(predicate?: (value: E) => boolean): E[]

Returns all edge values, optionally filtered by predicate.

Filtering

filterNodes(predicate: (node: Node<N>) => boolean): DiGraph<N, E>

Returns a new graph containing only nodes that match the predicate.

Example:

const filtered = graph.filterNodes(node => node.value.active);

filterEdges(predicate: (edge: Edge<E>) => boolean): DiGraph<N, E>

Returns a new graph containing only edges that match the predicate.

filterNodeValues(predicate: (value: N) => boolean): DiGraph<N, E>

Filters nodes by their values.

Example:

const filtered = graph.filterNodeValues(v => v > 10);

filterEdgeValues(predicate: (value: E) => boolean): DiGraph<N, E>

Filters edges by their values.

Mapping

mapNodes<T>(fn: (node: Node<N>) => Node<T>): DiGraph<T, E>

Transforms all nodes and returns a new graph with different node type.

Example:

const transformed = graph.mapNodes(node => ({
  ...node,
  value: node.value * 2
}));

mapEdges<T>(fn: (edge: Edge<E>) => Edge<T>): DiGraph<N, T>

Transforms all edges and returns a new graph with different edge type.

mapNodeValues<T>(fn: (value: N) => T): DiGraph<T, E>

Transforms node values.

Example:

const stringGraph = graph.mapNodeValues(n => n.toString());

mapEdgeValues<T>(fn: (value: E) => T): DiGraph<N, T>

Transforms edge values.

Reducing

reduceNodes<T>(fn: (acc: T, current: Node<N>, index: number) => T, start: T): T

Reduces all nodes to a single value.

Example:

const sum = graph.reduceNodes((acc, node) => acc + node.value, 0);

reduceEdges<T>(fn: (acc: T, current: Edge<E>, index: number) => T, start: T): T

Reduces all edges to a single value.

reduceNodeValues<T>(fn: (acc: T, current: N, index: number) => T, start: T): T

Reduces node values.

Example:

const total = graph.reduceNodeValues((sum, val) => sum + val, 0);

reduceEdgeValues<T>(fn: (acc: T, current: E, index: number) => T, start: T): T

Reduces edge values.

Iteration

forEachNode(fn: (node: Node<N>) => void): DiGraph<N, E>

Executes a function for each node. Returns the same graph for chaining.

Example:

graph.forEachNode(node => console.log(node.value));

forEachEdge(fn: (edge: Edge<E>) => void): DiGraph<N, E>

Executes a function for each edge.

forEachNodeValue(fn: (value: N) => void): DiGraph<N, E>

Executes a function for each node value.

forEachEdgeValue(fn: (value: E) => void): DiGraph<N, E>

Executes a function for each edge value.

Error Handling

match<T, ErrT>(successFn: (graph: DiGraph<N, E>) => T, errorFn: (e: Error) => ErrT): Result<T, ErrT>

Pattern matching for error handling. Executes successFn if no error exists, otherwise executes errorFn.

Example:

const result = graph.match(
  g => g.nodeCount(),
  err => {
    console.error(err.message);
    return -1;
  }
);

if (result.ok) {
  console.log("Node count:", result.value);
} else {
  console.log("Error result:", result.error);
}

Serialization

toJson(params?: { pretty: boolean }): Result<string, Error>

Serializes the graph to JSON string. Returns a Result type.

Example:

const result = graph.toJson({ pretty: true });
if (result.ok) {
  console.log(result.value);
}

fromJson<N, E>(json: string): Result<DiGraph<N, E>, Error>

Deserializes a graph from JSON string. Returns a Result type.

Example:

const result = graph.fromJson(jsonString);
if (result.ok) {
  const restoredGraph = result.value;
}

Types

Node<T>

type Node<T> = {
  id: string;
  type: "node";
  created: IsoDatetimeUtcExtendedMs;
  value: T;
  edgeIdsOut: string[];  // IDs of outgoing edges
  edgeIdsIn: string[];   // IDs of incoming edges
};

Edge<T>

type Edge<T> = {
  id: string;
  type: "edge";
  created: IsoDatetimeUtcExtendedMs;
  fromId: string;  // Source node ID
  toId: string;    // Target node ID
  value: T;
};

Result<T, ErrT>

type Result<T, ErrT> =
  | { ok: true; value: T }
  | { ok: false; error: ErrT };

Examples

Building a Graph

import { createGraphV1, newNode, newEdge } from "@eeegl/tsgraph";

const graph = createGraphV1<string, number>();

const nodes = ["A", "B", "C"].map(v => newNode(v));
let g = graph;

for (const node of nodes) {
  g = g.setNode(node);
}

const edge1 = newEdge({ fromId: nodes[0].id, toId: nodes[1].id, value: 1 });
const edge2 = newEdge({ fromId: nodes[1].id, toId: nodes[2].id, value: 2 });

g = g.setEdge(edge1).setEdge(edge2);

console.log(g.nodeCount()); // 3
console.log(g.edgeCount()); // 2

Filtering and Mapping

import { createGraphV1, newNode } from "@eeegl/tsgraph";

const graph = createGraphV1<number, string>();

const g = graph
  .setNode(newNode(1))
  .setNode(newNode(2))
  .setNode(newNode(3))
  .setNode(newNode(4));

const result = g
  .filterNodeValues(v => v % 2 === 0)
  .mapNodeValues(v => v * 10)
  .reduceNodeValues((sum, v) => sum + v, 0);

console.log(result); // 60 (20 + 40)

Error Handling

import { createGraphV1, newNode, newEdge } from "@eeegl/tsgraph";

const graph = createGraphV1<string, string>();
const node = newNode("A");
const g = graph.setNode(node);

const badEdge = newEdge({
  fromId: node.id,
  toId: "nonexistent",
  value: "error"
});

const g2 = g.setEdge(badEdge);

const result = g2.match(
  graph => `Success: ${graph.nodeCount()} nodes`,
  err => `Failed: ${err.message}`
);

if (!result.ok) {
  console.log(result.error); // "Failed: undefined edge: fromId=...; toId=nonexistent"
}

JSON Round-Trip

import { createGraphV1, newNode } from "@eeegl/tsgraph";

const graph = createGraphV1<string, string>();

const original = graph
  .setNode(newNode("A"))
  .setNode(newNode("B"));

const jsonResult = original.toJson({ pretty: true });

if (jsonResult.ok) {
  const restored = original.fromJson(jsonResult.value);

  if (restored.ok) {
    console.log(restored.value.nodeCount()); // 2
    console.log(restored.value.id() === original.id()); // true
  }
}

Development

npm install
npm test
npm run test:watch