Skip to content

This site describes solve-engine as it is on main: 2.43.0, which npm does not have yet. npm installs 2.40.0, so a page may show an answer that version does not give yet.

DependencyGraph

Defined in: packages/engine/src/vm/DependencyGraph.ts:371

Dependency graph for variable and data-source tracking across document lines.

Tracks which lines read/write which variables, and propagates changes through the graph when a variable is modified. Supports:

  • Variable dependency tracking (registerLine, getAffectedLines)
  • Data-source dependency tracking (registerLineDataSourceDependency)
  • Topological ordering of affected lines (getAffectedLinesInOrder)
  • Efficient removal of deleted lines (removeLine)
new DependencyGraph(): DependencyGraph;

DependencyGraph

clear(): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1692

Clear all dependency graph state. Called on document switch or engine reset.

void


directConsumersOf(key): ReadonlySet<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1524

The lines that read key, one edge away.

The consumer index, which registerLine keeps clear of the line that writes the key: a definition’s read of its own name is a convention for the graph’s benefit, not a dependency, and x += 1 reads its total to step it, not to depend on another line. That is what makes this index the right one for finding a cycle, where the raw reads would make every definition a self-loop and every twice-defined name a two-cycle.

ParameterTypeDescription
keystringAn edge key.

ReadonlySet<number>

The 1-based readers, or an empty set.


dropDataSourceReads(lineNumber): number;

Defined in: packages/engine/src/vm/DependencyGraph.ts:644

Forget every external data source a line was recorded as reading.

A data-source read is pinned (see registerLineDataSourceDependency): it was discovered while the line ran, so re-registering the line from its text keeps it. That is right for a live line and wrong for a frozen one, which reads its answer from the engine’s store and never the source again. Left in place, the pinned read kept the line a consumer of the query, so the batcher re-ran it whenever the value landed and a background refresh kept fetching for it. Only data-source keys are dropped: they are never part of a cycle, so no other edge, and no cycle bookkeeping, changes.

ParameterTypeDescription
lineNumbernumberThe 1-based line whose data-source reads to drop.

number

How many were dropped.


forgetPositionReads(lineNumber): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1183

Forget every position and tag this line was recorded reading.

For a line whose text has just changed, before it runs: whatever the old text read is not evidence about the new one, and a rule consulting the graph between the edit and the run would otherwise be told the old edges. The next run records what the new text reads. A data-source pin is discovered the same way but is not about the text, and stays until the line is removed.

ParameterTypeDescription
lineNumbernumber1-based line whose positions are to go

void


getAffectedLines(changedVariable): Set<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:714

Find all lines affected by a changed variable via BFS through the consumer graph.

When a variable is modified (e.g., :x = 5 changes to :x = 10), this returns all lines that transitively depend on it, lines that read x, lines that read variables written by those lines, and so on.

ParameterTypeDescription
changedVariablestringThe variable name that changed

Set<number>

Set of line numbers that need re-evaluation


getAffectedLinesByDataSource(dataSourceId, queryKey): ReadonlySet<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1423

Find all lines affected by a data source update.

When an async data source resolves (e.g., currency rate fetch completes), this returns all lines that depend on that specific data query.

ParameterTypeDescription
dataSourceIdstringThe data source identifier
queryKeystring[]The query key that was updated

ReadonlySet<number>

Set of line numbers that need re-evaluation


getAffectedLinesByPosition(lineNumber): ReadonlySet<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1388

ParameterType
lineNumbernumber

ReadonlySet<number>


getAffectedLinesInOrder(startVariable): number[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:763

Phase 1.4 DAG-walk optimization: return affected lines in dependency-safe topological order. Uses Kahn’s algorithm (BFS-based) to ensure every line is evaluated AFTER all lines it depends on have been processed.

This is more correct than ascending line-number sort, which fails when variable definitions and their consumers are not in document order.

ParameterType
startVariablestring

number[]

Line numbers in topological order (producers before consumers).


getConsumers(variable): ReadonlySet<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1494

Get all line numbers that consume (read) a given variable.

A position’s key (linePositionEdgeKey) is answered as getAffectedLinesByPosition answers the position.

ParameterTypeDescription
variablestringThe variable name, or any other edge key

ReadonlySet<number>

Set of line numbers that read this variable, or empty set if none


getDependencies(lineNumber): ReadonlySet<string>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1559

The keys a line that WRITES something reads.

The qualifier is the whole of it, and the reason this doc is longer than the method. The map behind this is filled in registerLine only on the branch that stores a write set, so a line that reads a name and defines nothing answers with an empty set rather than with what it reads. It is not “the variables a line depends on”; it is the dependencies recorded alongside a definition.

That is deliberate, and pinned by a test: it is what lets a redefinition break the old chain rather than depend on itself. It is also a trap, and it has been walked into. The async batcher ordered the lines it was about to re-run by asking this what each one read, so a line defining nothing answered with nothing and got no ordering constraint at all: rate * 2 was run before the line that fetched rate and read the value from before the fetch.

getReads is the question that was meant there, and is almost always the one wanted: every key a line reads, whether or not it writes anything.

ParameterTypeDescription
lineNumbernumberThe line number to query

ReadonlySet<string>

The keys recorded alongside this line’s write set, or an empty set if it writes nothing


getProducers(key): ReadonlySet<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1530

ParameterType
keystring

ReadonlySet<number>


getReads(lineNumber): ReadonlySet<string>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1583

Every key a line reads, whether or not it writes anything.

getDependencies answers this only for a line that writes, because the map behind it is filled alongside the write set. That makes it the wrong question to ask when ordering a set of lines: a line that reads a name and defines nothing is exactly the line whose reads say where it has to come, and it answered with nothing. The batcher ordered such a line before the line producing what it read for that reason.

Includes any data-source key the line was pinned to, since that is a read of the line like any other, and a line: key (see linePositionEdgeKey) for each position it was recorded reading. Those are made from the line’s entry when asked, since the graph holds a span rather than a key per position.

ParameterTypeDescription
lineNumbernumberThe line number to query

ReadonlySet<string>

The keys this line reads, or an empty set if none. A new set when the line reads a position, the caller’s to keep.


getSnapshot(): DagSnapshot;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1640

Get a serializable snapshot of the entire dependency graph for diagnostics.

Returns plain objects (not Maps/Sets) so consumers don’t need to reach into private fields. Used by playground diagnostic tabs for DAG visualization.

DagSnapshot


getWrites(lineNumber): ReadonlySet<string>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1630

Get all variables that a line writes (assigns to).

ParameterTypeDescription
lineNumbernumberThe line number to query

ReadonlySet<string>

Set of variable names this line writes, or empty set if none


hasDownwardPositionRead(): boolean;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1287

Whether any recorded positional edge points from a reader to a line below it.

The precondition for a positional cycle, and so for the walk that looks for one; see downwardPositionReads. A tag edge can reach a line below its reader, and which lines it reaches is decided when asked, so any tag edge counts.

boolean

True while at least one such edge is recorded


keysInUse(): Set<string>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1602

Every key some line reads or writes: the names, tags, globals and data sources the graph indexes. No line: key is among them.

What a caller wanting the document’s vocabulary asks for, rather than building a getSnapshot, which also spells out every positional edge.

Set<string>

The keys, each once.


linesReadingADataSource(): number[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:1613

The lines that read an external data source.

number[]

Their 1-based numbers, ascending.


linesReadingAPosition(): Iterable<number>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1336

Every line that read some position’s result, whichever position it was.

For a structural edit, which changes what a position means rather than what any line says. Inserting a line moves everything below it, so line 5 now names different text, prev names a different neighbour, and an above aggregate covers a different block, all without a character changing on the line that reads them.

Deliberately not filtered by which positions moved. A reader whose target shifted has to re-run, and so does one that shifted past its own target and became a self-reference, and the second is not visible from the target alone. Positional readers are a small minority of a document’s lines, so re-running all of them costs almost nothing and cannot be wrong.

Iterable<number>

The 1-based line numbers doing the reading, valid until the next structural change.


positionReadersOf(lineNumber): Generator<number, void, undefined>;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1368

The lines reading lineNumber’s position, one at a time, as getAffectedLinesByPosition finds them but without collecting them: a reader can come more than once (through a span and through a tag), the line itself never does. For a walk that holds many of these at once, so it holds no set of readers per line; the cycle walk is one, and a running total after every line of a ledger has as many readers as lines above it.

The graph must not change while one is being read.

ParameterTypeDescription
lineNumbernumber1-based line whose readers are wanted.

Generator<number, void, undefined>


positionsReadBy(lineNumber): number[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:1214

The positions a line has been recorded reading, as line numbers.

The forward direction of getAffectedLinesByPosition: that answers “who reads this position”, this answers “which positions does this line read”. Both directions are what finding a cycle takes. A tag edge is not a position and is not listed; readsAnyPosition says whether a line holds either kind.

ParameterTypeDescription
lineNumbernumber1-based line doing the reading

number[]

The positions it has read, in no particular order; empty if none


readsAnyPosition(lineNumber): boolean;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1240

Whether a line has been recorded reading another line’s result, by position or through a category tag.

ParameterTypeDescription
lineNumbernumber1-based line doing the reading

boolean

True while it holds at least one such edge


reconcilePositionReads(lineNumber): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1118

Cut a line’s recorded positions back to the ones its last run read.

For the evaluator to call once a line has executed. A positional read is discovered while the line runs, and a position the line has stopped reading cannot be discovered that way, so without this the recorded set only ever grew: prev + 1 edited to 7 went on reading line 1 in the graph for the rest of the session, and total above kept its edges to the lines above a heading that had cut its block short. Anything asking the graph what a line reads was told what it used to read, and a cycle that the heading had broken was still a cycle to the graph, while a cycle that its removal re-closed was not new to it and so was never noticed. Tag edges are cut back the same way.

A run that read no position at all leaves the line with none. A line that did not execute (compiled only, or skipped) must not be reconciled, since it read nothing for a reason that says nothing about its text.

ParameterTypeDescription
lineNumbernumber1-based line that has just executed

void


registerLine(
lineNumber,
reads,
writes
): readonly string[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:518

Register a line’s variable reads and writes in the dependency graph.

If re-registering the same line (e.g., after editing), old consumer references are cleaned up first. Write-variables are removed from the consumer set so that redefinition breaks the old dependency chain.

ParameterTypeDescription
lineNumbernumber1-based line number in the document
readsstring[]Variable names this line reads
writesstring[]Variable names this line writes (assigns to)

readonly string[]


registerLineDataSourceDependency(
lineNumber,
dataSourceId,
queryKey
): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:674

Register a line’s dependency on an external data source (e.g., currency rate, OSRS GE price).

When the data source updates, getAffectedLinesByDataSource returns all lines that depend on this data, enabling targeted re-evaluation.

ParameterTypeDescription
lineNumbernumber1-based line number in the document
dataSourceIdstringUnique identifier for the data source (e.g., “currency”, “osrs-ge”)
queryKeystring[]Query key array identifying the specific data (e.g., [“USD”, “EUR”])

void


registerLineFigureSpan(
lineNumber,
first,
last
): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1044

Record that lineNumber reads the figures on lines first to last: every line there except a summary line, which a span of figures passes over.

One interval however long the span, for the same reason as registerLinePositionDependency. What a section total reads is its block less the totals inside it, which is every other line of a ledger, so recording the members one by one was a sparse set the length of the block for every total in it. Whether a line in the span is a summary is asked of the document when the edge is followed back (see setDocumentView), so two totals of one section do not read each other, and a cycle is only found where there is one.

ParameterTypeDescription
lineNumbernumber1-based line doing the reading
firstnumberFirst line of the span, 1-based
lastnumberLast line of the span, inclusive; a span with last < first reads nothing

void


registerLinePositionDependency(lineNumber, dependsOnLine): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:956

Record that lineNumber read the result of line dependsOnLine.

A positional read is discovered while the line runs, the same way a data source is, and for the same reason it outlives the next registration of this line, which recovers its edges from the text, where a position it reached for at run time does not appear.

The edge is held as a number in the reader’s own entry, a contiguous span and a sparse set, and nowhere else: “which lines read line k” is answered by an interval index over those spans (see getAffectedLinesByPosition), so an above aggregate over a thousand lines costs one entry, not a thousand keys in three indexes (#733).

A line depending on itself is dropped rather than recorded, since it would be a cycle the ordering has to break and says nothing.

ParameterTypeDescription
lineNumbernumber1-based line doing the reading
dependsOnLinenumber1-based line whose result it read

void


registerLineTagDependency(lineNumber, tag): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1013

Record that lineNumber read the members of the category tag tag, or of every tag when tag is EVERY_TAG.

One edge on the tag, however many lines carry it. total of #food reads every line tagged #food, and recording a position per member made a ledger with a running tag total after each entry cost the square of its length in the graph. Which lines the edge reaches is answered when asked, from the tags each line carries now (see setDocumentView), so a member joining or leaving the group is seen without the reader recording anything. The same run half as a position keeps it exact: a tag the last run did not read is dropped by reconcilePositionReads.

ParameterTypeDescription
lineNumbernumber1-based line doing the reading
tagstringThe tag’s name without its #, in any case, or EVERY_TAG

void


removeLine(lineNumber): readonly string[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:1435

Remove a line from the dependency graph (e.g., when a line is deleted from the document).

Cleans up all consumer references, write registrations, and data source dependencies for the removed line. O(k) where k is the number of variables the line reads.

ParameterTypeDescription
lineNumbernumberThe line number being removed

readonly string[]


setDocumentView(view): void;

Defined in: packages/engine/src/vm/DependencyGraph.ts:1068

Tell the graph what it needs to know about the document’s lines to follow a tag edge or a span of figures back to the lines it reaches.

The document model knows a line’s text and the graph does not, so the path that owns the model hands this over; the incremental pass does so whenever it builds a line context. Kept across clear, which a structural edit calls, since the view reads the model as it is now.

ParameterTypeDescription
viewDocumentView | nullHow to read a line, by 1-based position; null when no document backs the graph, and then a tag edge reaches no line and a span of figures reaches every line in it.

void


takeEdgeChanges(): {
keys: readonly string[];
lines: readonly number[];
};

Defined in: packages/engine/src/vm/DependencyGraph.ts:1253

What changed in the graph since this was last called: the lines whose edge set changed, and the keys whose producer set changed. Taking it clears it. See edgesChangedThisPass.

{
keys: readonly string[];
lines: readonly number[];
}

The changed lines and keys, each possibly empty.

keys: readonly string[];
lines: readonly number[];

takeReadersThatGainedAPosition(): readonly number[];

Defined in: packages/engine/src/vm/DependencyGraph.ts:1269

The readers that recorded a new position since this was last called, and an empty list until one does.

Taking the list clears it. See readersThatGainedAPosition.

readonly number[]

The 1-based readers, in the order they recorded