Debug Graph Internals API

This page documents the current internal-only debug graph helpers exported from @fobx/core/internals.

These APIs are intentionally internal and experimental. They are published so people can try them, but the exact shape is not yet treated as stable public API.

Enabling the runtime

The debug graph runtime is only active when FobX runs with NODE_ENV=debug.

NODE_ENV=debug deno test --allow-env --allow-read
cd core && NODE_ENV=debug deno task build

Hot-path instrumentation is guarded by literal if (process.env.NODE_ENV === "debug") checks. In current builds that strips the debug callsites from hot paths when debug is off, even though the helper exports themselves still remain part of the internal surface.

Exports

import {
  buildDebugMermaidGraph,
  buildDebugTextReport,
  buildDebugTraceSummary,
  configureDebugTracking,
  explainDebugTarget,
  getDebugSnapshot,
  resetDebugTracking,
} from "@fobx/core/internals"

configureDebugTracking(options)

Configure the in-memory event buffer.

configureDebugTracking({ maxEvents: 2000 })

Options

Field Type Default Meaning
maxEvents number 2000 Maximum number of retained debug events

If the buffer is already larger than the new cap, older events are dropped immediately.

resetDebugTracking()

Clear all current debug graph state.

resetDebugTracking()

Use this before running a focused scenario so the resulting graph and event log are easier to inspect.

getDebugSnapshot()

Return a serializable snapshot of the currently live debug graph and retained event log.

const snapshot = getDebugSnapshot()

High-level shape

interface DebugSnapshot {
  enabled: boolean
  maxEvents: number
  nodes: DebugNodeSnapshot[]
  events: DebugEventSnapshot[]
}

nodes

Each node represents one tracked runtime object, such as a box, computed, autorun, reaction, tracker, collection admin, object property admin, selector entry, or subscription.

Relevant fields:

Field Meaning
id Internal debug-node id
runtimeId Runtime admin id when one exists
kind Box, computed, autorun, observable-object, selector-entry, and so on
name Runtime debug name
sourceLocation First external source location, when available
sourceStack Up to 4 external frames
sourceGroup file:line:column grouping key
parentId Parent debug-node id for nested nodes such as object properties
propertyKey Property or slot label when applicable
disposed Whether this node has been disposed
reactionState up-to-date, possibly-stale, or stale for reaction-like nodes
dependencyIds Current outgoing dependency edges
observerIds Current incoming observer edges
counts Read, write, notify, schedule, run, and disposal counters
lastValue Shallow value summary
lastScheduleReason Most recent scheduling reason
lastWriteReason Most recent write reason

events

Each event represents a point-in-time debug action such as create, write, notify, schedule, edge-add, edge-remove, run-start, run-end, observer-add, observer-remove, or dispose.

Useful fields:

Field Meaning
id Monotonic event id
kind Event type
nodeId Primary node when the event targets one node
sourceId Source node for edge or schedule events
targetId Target node for edge events
detail Operation-specific detail string
notificationType changed or indeterminate
fromState Previous reaction state
toState New reaction state
location First external frame for stack-backed events
value Shallow value summary for write events
previousValue Previous shallow value summary for tracked writes when available
inTransaction Whether the event happened while batch depth was greater than zero
batchDepth Scheduler batch depth captured for the event

buildDebugTraceSummary(options)

Build a transaction-oriented view of the current debug state: current values, recent writes, and the downstream notify or run sequence.

const trace = buildDebugTraceSummary({
  target: myComputed,
  maxDepth: 2,
  limit: 40,
})

Options

Field Type Default Meaning
target object none Optional root object to summarize a reachable subgraph
maxDepth number 2 Traversal depth when target is provided
sinceEventId number none Ignore older events before this id
limit number 40 Maximum number of retained relevant events to include

High-level shape

interface DebugTraceSummary {
  enabled: boolean
  fromEventId?: number
  toEventId?: number
  snapshot: DebugTraceNodeSnapshot[]
  changes: DebugTraceEventSummary[]
  consequences: DebugTraceEventSummary[]
}

Use this when the question is not “what is the graph?” but “what changed, was it inside a transaction, and what ran because of it?”

buildDebugTextReport(options)

Build a terminal-oriented report that prints the local dependency graph first, then current values, then writes, then downstream consequences.

const report = buildDebugTextReport({
  target: myComputed,
  maxDepth: 2,
  limit: 40,
})

This helper uses the same filtering options as buildDebugTraceSummary(). It is usually the most practical format for:

  • terminal sessions
  • CI logs
  • PR comments
  • bug reports pasted into issues or chat

Use it when you want one human-readable summary instead of multiple separate exports.

explainDebugTarget(target)

Return a focused explanation for one live node.

const explanation = explainDebugTarget(myComputed)

Shape

interface DebugExplanation {
  node: DebugNodeSnapshot
  dependencies: DebugNodeSnapshot[]
  observers: DebugNodeSnapshot[]
  recentEvents: DebugEventSnapshot[]
}

This is the fastest way to answer questions such as:

  • what does this reaction currently depend on?
  • who is observing this computed?
  • what were the most recent events involving this node?

buildDebugMermaidGraph(options)

Build Mermaid markup from the current debug graph.

const mermaid = buildDebugMermaidGraph({
  target: myComputed,
  maxDepth: 2,
})

Options

Field Type Default Meaning
target object none Optional root object to render a reachable subgraph
maxDepth number 2 Traversal depth when target is provided

The output is Mermaid graph LR markup with nodes labeled by name, kind, and source location when one is available. The graph uses different styles for the three main runtime categories:

  • observables: green rectangular nodes
  • computeds: amber rounded nodes
  • reactions and reaction-like controllers: blue double-rectangle nodes

Edges point from dependency to consumer, so the graph reads left-to-right as data or invalidation flow.

Practical workflow

resetDebugTracking()
configureDebugTracking({ maxEvents: 500 })

// Run the scenario you care about here.

const snapshot = getDebugSnapshot()
const report = buildDebugTextReport({ target: someReaction, maxDepth: 3 })
const trace = buildDebugTraceSummary({ target: someReaction, maxDepth: 3 })
const explanation = explainDebugTarget(someReaction)
const mermaid = buildDebugMermaidGraph({ target: someReaction, maxDepth: 3 })

If you want to compare those outputs side by side for the same concrete scenario, the guide at /core/guides/debugging-reactions/ now includes an explicit same-scenario comparison section.

For a step-by-step reaction debugging workflow and concrete export examples, see the guide at /core/guides/debugging-reactions/.