bun:graph-native extracts a code graph from source files and runs graph algorithms on it in a worker thread. bun:graph stores graphs, runs and artifacts in a SQLite registry and queries them.
Build and query a graph
executeGraph() takes one request and resolves with its result. Each request has a scope (a graph:-prefixed source name and a profile) and an op.
import { executeGraph } from "bun:graph-native";
const scope = { source: "graph:demo", profile: "bun" } as const;
const built = await executeGraph({
op: "build",
scope,
files: [{ path: "a.ts", content: "export function a() { return b(); }\nfunction b() { return 1; }\n" }],
});
console.log(built.result.graph.nodes.length, built.result.stats.calls);
const path = await executeGraph({ op: "path", scope, graph: built.result.graph, from: "a", to: "b" });
console.log(path.result.hops);
build extracts nodes and links from Rust (.rs), JavaScript and TypeScript (.js, .jsx, .mjs, .cjs, .ts, .tsx, .mts, .cts) and Markdown (.md, .mdx) files. Its result has the graph, call resolution statistics and the files that failed to parse.
op | Input | Result |
|---|---|---|
build | files ({ path, content }), packages? | graph, stats, parse_errors |
query | graph, question, depth?, budget_tokens? | The best-matching nodes and their neighbourhood as text |
path | graph, from, to, directed? | The hops of the shortest path, or null |
explain | graph, node | The node with its incoming and outgoing connections |
analyze | graph, top? | Communities, modularity and the most connected nodes |
export | graph | The normalized graph |
limits bounds a request: max_files, max_file_bytes, max_source_bytes, max_nodes, max_edges, max_ast_depth and max_output_bytes. Pass an AbortSignal as { signal } to cancel a request. At most four requests run at once; further requests reject until one finishes.
Store graphs
BunPython (also exported as PyJS) opens a SQLite registry. With no argument it uses bun_python.sqlite in the current directory (registryPath).
import { BunPython } from "bun:graph";
import { executeGraph } from "bun:graph-native";
using registry = new BunPython("graph.sqlite");
const repository = registry.repository("my-app", "git", process.cwd());
const { result } = await executeGraph({
op: "build",
scope: { source: "graph:my-app", profile: "bun" },
files: [{ path: "a.ts", content: "export function a() {}\n" }],
});
const hash = new Bun.CryptoHasher("sha256").update(JSON.stringify(result.graph)).digest("hex");
await registry.importGraph(repository, result.graph, hash);
console.log(registry.queryNodes({ repositoryId: repository, search: "a" }));
console.log(registry.counts());
A registry holds repositories, files, nodes, edges, graph snapshots, runs, samples, fixes, events and artifacts. Its main methods:
| Method | Description |
|---|---|
repository(name, kind, root, revision?) | Create or update a repository and return its id |
importGraph(repositoryId, graph, sha256) | Import a graph as a new snapshot |
queryNodes({ repositoryId, kind?, label?, search? }) | Find nodes, paginated with limit and offset |
neighbors(id), incoming(id), outgoing(id) | Adjacent nodes with their edges |
traverse(id, { direction?, maxDepth? }) | Walk the graph from a node, with node and work limits |
snapshots(repositoryId), getSnapshot(repositoryId) | Snapshots of a repository |
coverage({ repositoryId, snapshotId? }) | Node and edge counts by provenance, and unresolved nodes |
startRun(), finishRun(), sample(), event() | Record command runs, benchmark samples and events |
export(path?) | Write the registry as JSON, next to the database by default |
bun:graphx adds a Valkey/Redis result cache (GraphRedisCache) and helpers that render a registry as Markdown or HTML and stream bounded JSON. packages/bun-types/graph.d.ts declares every type.
The bun mcp graph tools and bun lsp use the same extraction to answer questions about a workspace.