@quokkaquery/graph 0.1.0 → 0.1.1

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/API.md CHANGED
@@ -1,100 +1,100 @@
1
- # @quokkaquery/graph — API Reference
2
-
3
- Distilled API surface for agent prompts and quick reference. 7 exports from `src/index.ts`.
4
-
5
- ## Types
6
-
7
- ```typescript
8
- export type NodeId = string | number
9
-
10
- export interface GraphNode<T = unknown> {
11
- id: NodeId
12
- data: T
13
- }
14
-
15
- export interface Edge<E = unknown> {
16
- from: NodeId
17
- to: NodeId
18
- weight: number
19
- data: E
20
- }
21
-
22
- export interface GraphOptions {
23
- directed?: boolean // default: true
24
- }
25
-
26
- export type Visitor<T> = (node: GraphNode<T>, depth: number) => void | false
27
- export type NodePredicate<T> = (node: GraphNode<T>) => boolean
28
- ```
29
-
30
- ## Class
31
-
32
- ```typescript
33
- export class Graph<T = unknown, E = unknown> {
34
- readonly directed: boolean
35
-
36
- constructor(opts?: GraphOptions)
37
-
38
- // ── Nodes ──────────────────────────────────────────
39
- addNode(id: NodeId, data: T): this
40
- getNode(id: NodeId): GraphNode<T> | undefined
41
- hasNode(id: NodeId): boolean
42
- removeNode(id: NodeId): boolean
43
- get nodeCount(): number
44
- nodeIds(): NodeId[]
45
- nodeEntries(): GraphNode<T>[]
46
-
47
- // ── Edges ──────────────────────────────────────────
48
- addEdge(from: NodeId, to: NodeId, weight?: number, data?: E): this
49
- getEdge(from: NodeId, to: NodeId): Edge<E> | undefined
50
- hasEdge(from: NodeId, to: NodeId): boolean
51
- removeEdge(from: NodeId, to: NodeId): boolean
52
- get edgeCount(): number
53
- edges(): Edge<E>[]
54
- neighbors(id: NodeId): NodeId[]
55
- outEdges(id: NodeId): Edge<E>[]
56
- inEdges(id: NodeId): Edge<E>[]
57
- outDegree(id: NodeId): number
58
- inDegree(id: NodeId): number
59
- degree(id: NodeId): number
60
-
61
- // ── Node helpers ───────────────────────────────────
62
- predecessors(id: NodeId): NodeId[]
63
- successors(id: NodeId): NodeId[]
64
- connectedNodes(id: NodeId): NodeId[]
65
- isSource(id: NodeId): boolean // directed only
66
- isSink(id: NodeId): boolean // directed only
67
- isIsolated(id: NodeId): boolean
68
- sources(): NodeId[] // directed only
69
- sinks(): NodeId[] // directed only
70
- isolatedNodes(): NodeId[]
71
-
72
- // ── Reachability ───────────────────────────────────
73
- reachableFrom(id: NodeId): Set<NodeId>
74
- reachableTo(id: NodeId): Set<NodeId>
75
-
76
- // ── Traversals ─────────────────────────────────────
77
- bfs(start: NodeId, visitor: Visitor<T>): void
78
- dfs(start: NodeId, visitor: Visitor<T>): void
79
- findBfs(start: NodeId, predicate: NodePredicate<T>): GraphNode<T> | undefined
80
- findDfs(start: NodeId, predicate: NodePredicate<T>): GraphNode<T> | undefined
81
-
82
- // ── Algorithms ─────────────────────────────────────
83
- topologicalSort(): NodeId[] // directed only, throws on cycle
84
- hasCycle(): boolean
85
- dijkstra(start: NodeId, end: NodeId): { distance: number; path: NodeId[] } | undefined
86
- connectedComponents(): NodeId[][] // weakly-connected for directed
87
-
88
- // ── Transforms ─────────────────────────────────────
89
- mapNodes<U>(fn: (node: GraphNode<T>) => U): Graph<U, E>
90
- subgraph(ids: Set<NodeId>): Graph<T, E>
91
- }
92
- ```
93
-
94
- ## Notes
95
-
96
- - Adjacency-list representation. Directed by default.
97
- - `addEdge` auto-creates missing nodes. For undirected, both directions are stored internally.
98
- - `edges()` deduplicates for undirected graphs.
99
- - `reachableFrom` includes the start node only if a cycle leads back to it.
100
- - Visitor callbacks can return `false` to stop traversal early.
1
+ # @quokkaquery/graph — API Reference
2
+
3
+ Distilled API surface for agent prompts and quick reference. 7 exports from `src/index.ts`.
4
+
5
+ ## Types
6
+
7
+ ```typescript
8
+ export type NodeId = string | number
9
+
10
+ export interface GraphNode<T = unknown> {
11
+ id: NodeId
12
+ data: T
13
+ }
14
+
15
+ export interface Edge<E = unknown> {
16
+ from: NodeId
17
+ to: NodeId
18
+ weight: number
19
+ data: E
20
+ }
21
+
22
+ export interface GraphOptions {
23
+ directed?: boolean // default: true
24
+ }
25
+
26
+ export type Visitor<T> = (node: GraphNode<T>, depth: number) => void | false
27
+ export type NodePredicate<T> = (node: GraphNode<T>) => boolean
28
+ ```
29
+
30
+ ## Class
31
+
32
+ ```typescript
33
+ export class Graph<T = unknown, E = unknown> {
34
+ readonly directed: boolean
35
+
36
+ constructor(opts?: GraphOptions)
37
+
38
+ // ── Nodes ──────────────────────────────────────────
39
+ addNode(id: NodeId, data: T): this
40
+ getNode(id: NodeId): GraphNode<T> | undefined
41
+ hasNode(id: NodeId): boolean
42
+ removeNode(id: NodeId): boolean
43
+ get nodeCount(): number
44
+ nodeIds(): NodeId[]
45
+ nodeEntries(): GraphNode<T>[]
46
+
47
+ // ── Edges ──────────────────────────────────────────
48
+ addEdge(from: NodeId, to: NodeId, weight?: number, data?: E): this
49
+ getEdge(from: NodeId, to: NodeId): Edge<E> | undefined
50
+ hasEdge(from: NodeId, to: NodeId): boolean
51
+ removeEdge(from: NodeId, to: NodeId): boolean
52
+ get edgeCount(): number
53
+ edges(): Edge<E>[]
54
+ neighbors(id: NodeId): NodeId[]
55
+ outEdges(id: NodeId): Edge<E>[]
56
+ inEdges(id: NodeId): Edge<E>[]
57
+ outDegree(id: NodeId): number
58
+ inDegree(id: NodeId): number
59
+ degree(id: NodeId): number
60
+
61
+ // ── Node helpers ───────────────────────────────────
62
+ predecessors(id: NodeId): NodeId[]
63
+ successors(id: NodeId): NodeId[]
64
+ connectedNodes(id: NodeId): NodeId[]
65
+ isSource(id: NodeId): boolean // directed only
66
+ isSink(id: NodeId): boolean // directed only
67
+ isIsolated(id: NodeId): boolean
68
+ sources(): NodeId[] // directed only
69
+ sinks(): NodeId[] // directed only
70
+ isolatedNodes(): NodeId[]
71
+
72
+ // ── Reachability ───────────────────────────────────
73
+ reachableFrom(id: NodeId): Set<NodeId>
74
+ reachableTo(id: NodeId): Set<NodeId>
75
+
76
+ // ── Traversals ─────────────────────────────────────
77
+ bfs(start: NodeId, visitor: Visitor<T>): void
78
+ dfs(start: NodeId, visitor: Visitor<T>): void
79
+ findBfs(start: NodeId, predicate: NodePredicate<T>): GraphNode<T> | undefined
80
+ findDfs(start: NodeId, predicate: NodePredicate<T>): GraphNode<T> | undefined
81
+
82
+ // ── Algorithms ─────────────────────────────────────
83
+ topologicalSort(): NodeId[] // directed only, throws on cycle
84
+ hasCycle(): boolean
85
+ dijkstra(start: NodeId, end: NodeId): { distance: number; path: NodeId[] } | undefined
86
+ connectedComponents(): NodeId[][] // weakly-connected for directed
87
+
88
+ // ── Transforms ─────────────────────────────────────
89
+ mapNodes<U>(fn: (node: GraphNode<T>) => U): Graph<U, E>
90
+ subgraph(ids: Set<NodeId>): Graph<T, E>
91
+ }
92
+ ```
93
+
94
+ ## Notes
95
+
96
+ - Adjacency-list representation. Directed by default.
97
+ - `addEdge` auto-creates missing nodes. For undirected, both directions are stored internally.
98
+ - `edges()` deduplicates for undirected graphs.
99
+ - `reachableFrom` includes the start node only if a cycle leads back to it.
100
+ - Visitor callbacks can return `false` to stop traversal early.
package/dist/graph.d.ts CHANGED
@@ -1,7 +1,14 @@
1
+ /**
2
+ * In-memory `Graph` class: node and edge storage, degree and reachability
3
+ * queries, BFS/DFS traversal, and topological sort, cycle detection,
4
+ * Dijkstra, connected components, mapping and subgraph extraction.
5
+ */
1
6
  import type { NodeId, GraphNode, Edge, GraphOptions, Visitor, NodePredicate } from "./types.js";
2
7
  /**
3
8
  * Generic graph with adjacency-list representation.
4
9
  * Supports directed and undirected graphs, weighted edges, and common traversals.
10
+ * Undirected edges are stored as a pair of mirrored directed edges, so
11
+ * per-node edge lists contain both directions.
5
12
  *
6
13
  * @typeParam T - Node data type
7
14
  * @typeParam E - Edge data type
@@ -11,19 +18,43 @@ export declare class Graph<T = unknown, E = unknown> {
11
18
  private nodes;
12
19
  private adjOut;
13
20
  private adjIn;
21
+ /** Graphs are directed unless `opts.directed` is explicitly `false`. */
14
22
  constructor(opts?: GraphOptions);
23
+ /** Adds or replaces a node's data; existing edges on `id` are kept. Chainable. */
15
24
  addNode(id: NodeId, data: T): this;
16
25
  getNode(id: NodeId): GraphNode<T> | undefined;
17
26
  hasNode(id: NodeId): boolean;
27
+ /**
28
+ * Deletes a node and its edges; returns false if `id` was not a registered node.
29
+ * Only the first edge between `id` and each other node is removed from that
30
+ * node's lists, so parallel edges leave dangling entries behind.
31
+ */
18
32
  removeNode(id: NodeId): boolean;
19
33
  get nodeCount(): number;
20
34
  nodeIds(): NodeId[];
21
35
  nodeEntries(): GraphNode<T>[];
36
+ /**
37
+ * Appends an edge (weight defaults to 1); parallel edges are allowed. Endpoints
38
+ * not added via `addNode` get adjacency lists but are not registered nodes:
39
+ * they are absent from `nodeIds`/`getNode` and skipped by traversal visitors
40
+ * and `dijkstra`. Chainable.
41
+ */
22
42
  addEdge(from: NodeId, to: NodeId, weight?: number, data?: E): this;
43
+ /** First stored edge from `from` to `to`; with parallel edges the others are not returned. */
23
44
  getEdge(from: NodeId, to: NodeId): Edge<E> | undefined;
24
45
  hasEdge(from: NodeId, to: NodeId): boolean;
46
+ /**
47
+ * Removes one `from`→`to` edge (and its mirror when undirected); returns false
48
+ * if none exists. Parallel edges must be removed one call at a time.
49
+ */
25
50
  removeEdge(from: NodeId, to: NodeId): boolean;
51
+ /** Number of edges; undirected edges count once despite being stored twice. */
26
52
  get edgeCount(): number;
53
+ /**
54
+ * All edges. For undirected graphs, one edge per node pair is returned in
55
+ * whichever direction was seen first, which also collapses parallel edges —
56
+ * so `mapNodes` and `subgraph` drop undirected parallel edges.
57
+ */
27
58
  edges(): Edge<E>[];
28
59
  /** Outgoing neighbors of a node. */
29
60
  neighbors(id: NodeId): NodeId[];
@@ -79,7 +110,7 @@ export declare class Graph<T = unknown, E = unknown> {
79
110
  findDfs(start: NodeId, predicate: NodePredicate<T>): GraphNode<T> | undefined;
80
111
  /**
81
112
  * Topological sort (Kahn's algorithm). Returns node ids in topological order.
82
- * Throws if the graph has a cycle. Only meaningful for directed graphs.
113
+ * Throws if the graph is undirected or has a cycle.
83
114
  */
84
115
  topologicalSort(): NodeId[];
85
116
  /** Detect whether the graph contains a cycle. */
@@ -87,7 +118,9 @@ export declare class Graph<T = unknown, E = unknown> {
87
118
  /**
88
119
  * Dijkstra's shortest path from `start` to `end`.
89
120
  * Returns `{ distance, path }` or `undefined` if unreachable.
90
- * Edges must have non-negative weights.
121
+ * Edges must have non-negative weights. `end` must be a registered node;
122
+ * otherwise the result is a one-element path with an undefined distance
123
+ * rather than `undefined`.
91
124
  */
92
125
  dijkstra(start: NodeId, end: NodeId): {
93
126
  distance: number;
package/dist/graph.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Generic graph with adjacency-list representation.
3
3
  * Supports directed and undirected graphs, weighted edges, and common traversals.
4
+ * Undirected edges are stored as a pair of mirrored directed edges, so
5
+ * per-node edge lists contain both directions.
4
6
  *
5
7
  * @typeParam T - Node data type
6
8
  * @typeParam E - Edge data type
@@ -10,10 +12,12 @@ export class Graph {
10
12
  nodes = new Map();
11
13
  adjOut = new Map(); // outgoing edges
12
14
  adjIn = new Map(); // incoming edges (directed only, for convenience)
15
+ /** Graphs are directed unless `opts.directed` is explicitly `false`. */
13
16
  constructor(opts = {}) {
14
17
  this.directed = opts.directed ?? true;
15
18
  }
16
19
  // ── Node operations ──────────────────────────────────────────────
20
+ /** Adds or replaces a node's data; existing edges on `id` are kept. Chainable. */
17
21
  addNode(id, data) {
18
22
  this.nodes.set(id, { id, data });
19
23
  if (!this.adjOut.has(id))
@@ -28,6 +32,11 @@ export class Graph {
28
32
  hasNode(id) {
29
33
  return this.nodes.has(id);
30
34
  }
35
+ /**
36
+ * Deletes a node and its edges; returns false if `id` was not a registered node.
37
+ * Only the first edge between `id` and each other node is removed from that
38
+ * node's lists, so parallel edges leave dangling entries behind.
39
+ */
31
40
  removeNode(id) {
32
41
  if (!this.nodes.has(id))
33
42
  return false;
@@ -57,6 +66,12 @@ export class Graph {
57
66
  return [...this.nodes.values()];
58
67
  }
59
68
  // ── Edge operations ──────────────────────────────────────────────
69
+ /**
70
+ * Appends an edge (weight defaults to 1); parallel edges are allowed. Endpoints
71
+ * not added via `addNode` get adjacency lists but are not registered nodes:
72
+ * they are absent from `nodeIds`/`getNode` and skipped by traversal visitors
73
+ * and `dijkstra`. Chainable.
74
+ */
60
75
  addEdge(from, to, weight = 1, data) {
61
76
  this.ensureNode(from);
62
77
  this.ensureNode(to);
@@ -70,12 +85,17 @@ export class Graph {
70
85
  }
71
86
  return this;
72
87
  }
88
+ /** First stored edge from `from` to `to`; with parallel edges the others are not returned. */
73
89
  getEdge(from, to) {
74
90
  return this.adjOut.get(from)?.find((e) => e.to === to);
75
91
  }
76
92
  hasEdge(from, to) {
77
93
  return this.getEdge(from, to) !== undefined;
78
94
  }
95
+ /**
96
+ * Removes one `from`→`to` edge (and its mirror when undirected); returns false
97
+ * if none exists. Parallel edges must be removed one call at a time.
98
+ */
79
99
  removeEdge(from, to) {
80
100
  const out = this.adjOut.get(from);
81
101
  if (!out)
@@ -106,18 +126,24 @@ export class Graph {
106
126
  }
107
127
  return true;
108
128
  }
129
+ /** Number of edges; undirected edges count once despite being stored twice. */
109
130
  get edgeCount() {
110
131
  let count = 0;
111
132
  for (const [, edges] of this.adjOut)
112
133
  count += edges.length;
113
134
  return this.directed ? count : count / 2;
114
135
  }
136
+ /**
137
+ * All edges. For undirected graphs, one edge per node pair is returned in
138
+ * whichever direction was seen first, which also collapses parallel edges —
139
+ * so `mapNodes` and `subgraph` drop undirected parallel edges.
140
+ */
115
141
  edges() {
116
142
  const all = [];
117
143
  for (const [, edges] of this.adjOut)
118
144
  all.push(...edges);
119
145
  if (!this.directed) {
120
- // Deduplicate: keep only where from <= to (by string comparison)
146
+ // Deduplicate: keep the first-seen direction of each node pair
121
147
  const seen = new Set();
122
148
  return all.filter((e) => {
123
149
  const key = `${e.from}->${e.to}`;
@@ -314,7 +340,7 @@ export class Graph {
314
340
  // ── Algorithms ───────────────────────────────────────────────────
315
341
  /**
316
342
  * Topological sort (Kahn's algorithm). Returns node ids in topological order.
317
- * Throws if the graph has a cycle. Only meaningful for directed graphs.
343
+ * Throws if the graph is undirected or has a cycle.
318
344
  */
319
345
  topologicalSort() {
320
346
  if (!this.directed)
@@ -394,7 +420,9 @@ export class Graph {
394
420
  /**
395
421
  * Dijkstra's shortest path from `start` to `end`.
396
422
  * Returns `{ distance, path }` or `undefined` if unreachable.
397
- * Edges must have non-negative weights.
423
+ * Edges must have non-negative weights. `end` must be a registered node;
424
+ * otherwise the result is a one-element path with an undefined distance
425
+ * rather than `undefined`.
398
426
  */
399
427
  dijkstra(start, end) {
400
428
  const dist = new Map();
package/dist/index.d.ts CHANGED
@@ -1,2 +1,8 @@
1
+ /**
2
+ * Public entry point for the graph package: re-exports the `Graph` class and
3
+ * its supporting types, so consumers never import from internal module paths.
4
+ * The types are exported with `export type`, so they disappear at compile time
5
+ * and only `Graph` remains as a runtime export.
6
+ */
1
7
  export { Graph } from "./graph.js";
2
8
  export type { NodeId, GraphNode, Edge, GraphOptions, Visitor, NodePredicate, } from "./types.js";
package/dist/index.js CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * Public entry point for the graph package: re-exports the `Graph` class and
3
+ * its supporting types, so consumers never import from internal module paths.
4
+ * The types are exported with `export type`, so they disappear at compile time
5
+ * and only `Graph` remains as a runtime export.
6
+ */
1
7
  export { Graph } from "./graph.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quokkaquery/graph",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Generic reusable graph library with structure representation and traversal methods",
5
5
  "keywords": [
6
6
  "graph",