Execution Order Algorithm
This document explains how Traces calculates the order in which endpoints execute during a collection. Understanding this algorithm helps when debugging dependency issues or unexpected execution sequences.
Overview
Endpoints execute in an order that respects their dependencies. If endpoint B needs data from endpoint A, then A must execute first. The system uses Kahn's algorithm (topological sort) to compute this order, in calculateExecutionOrder().
Source file: apps/web/src/lib/graph/execution-order.ts
The module also exports several related helpers:
calculateExecutionOrder(endpoints, mappings)— the plain topological sort described below.calculateExecutionOrderPreferring(endpoints, mappings, preferred)— a topological sort that stays as close as possible to apreferred(analyst-chosen) order, "self-healing" only what a real dependency forces.validatePhaseOrder(phases, mappings)/autoFixPhaseOrder(phases, mappings, endpoints)— validate and repair a manual phase/stage layout (see the [Phases / execution-order docs note] in the Architecture index) against the dependency graph.canOrderBefore(sourceId, targetId, mappings)— whethersourcecan be placed beforetargetwithout creating a cycle (used when offering reusable variables from a later-running producer).extractPathParameters(path)/checkEndpointParameterConfiguration(endpoint, mappings)— path-parameter analysis (below).
Algorithm Walk-Through
Step 1: Filter Enabled Endpoints
The algorithm starts by filtering to only enabled endpoints. Disabled endpoints are excluded from the execution order entirely.
Input endpoints: [A, B, C, D, E]
Enabled: A, B, C, D (E is disabled)
Working set: [A, B, C, D]
Step 2: Build the Dependency Graph
For each dependency mapping, the algorithm creates a directed edge from the source endpoint to the target endpoint. This represents "source must run before target."
Mapping: A.response.ids -> B.userId (path)
Creates edge: A -> B
Mapping: B.response.profile -> C.profileId (path)
Creates edge: B -> C
Graph edges:
A -> B
B -> C
D (no edges - independent)
Step 3: Calculate In-Degrees
The in-degree of an endpoint is the count of endpoints it depends on. Endpoints with in-degree 0 have no dependencies and can run immediately.
In-degrees:
A: 0 (no dependencies)
B: 1 (depends on A)
C: 1 (depends on B)
D: 0 (no dependencies)
Step 4: Initialize the Queue
All endpoints with in-degree 0 are added to a processing queue. These are the "root" endpoints that can execute first.
Queue: [A, D]
Result: []
Step 5: Process the Queue
The algorithm repeatedly:
- Removes an endpoint from the queue
- Adds it to the result
- Decrements the in-degree of all endpoints that depend on it
- If any endpoint's in-degree becomes 0, adds it to the queue
Iteration 1:
Remove A from queue
Result: [A]
B's in-degree: 1 -> 0 (add B to queue)
Queue: [D, B]
Iteration 2:
Remove D from queue
Result: [A, D]
Queue: [B]
Iteration 3:
Remove B from queue
Result: [A, D, B]
C's in-degree: 1 -> 0 (add C to queue)
Queue: [C]
Iteration 4:
Remove C from queue
Result: [A, D, B, C]
Queue: []
Final order: [A, D, B, C]
Step 6: Handle Circular Dependencies
If the queue is empty but not all endpoints are in the result, there's a circular dependency. These endpoints are appended at the end.
Example circular dependency:
X depends on Y
Y depends on X
Neither X nor Y can reach in-degree 0.
After queue empties: remaining = [X, Y]
Final result: [...other endpoints..., X, Y]
Key Code References
All within calculateExecutionOrder():
| Step | What it does |
|---|---|
Filter ep.enabled !== false | Filter to enabled endpoints only |
Init adjacencyList / inDegree maps | One entry per enabled endpoint |
Loop over mappings | Build edges (source → target) and increment target in-degree |
Seed queue from in-degree 0 | Find initial nodes with no dependencies |
while (queue.length > 0) | Main processing loop (Kahn's algorithm) |
Final loop over endpointIds | Append endpoints with circular dependencies |
Line numbers are intentionally omitted — this file is actively edited and the function has moved. Search for calculateExecutionOrder in execution-order.ts.
Path Parameter Configuration
The file also contains checkEndpointParameterConfiguration() which determines if an endpoint can actually execute. An endpoint won't execute if it has path parameters (like {userId}) without configured mappings.
How Parameter Checking Works
- Extract required parameters from the path (e.g.,
/users/{userId}requiresuserId) - Check mappings for parameters targeting this endpoint's path location
- Check endpoint's
parameterExtractionconfig for additional sources - Return list of missing parameters
Path: /users/{userId}/posts/{postId}
Required: [userId, postId]
Mappings for this endpoint:
- userId <- A.response.data[*].id (path)
Result:
isConfigured: false
missingParams: [postId]
requiredParams: [userId, postId]
Debugging Tips
"Endpoint runs before its dependency"
Check that:
- The source endpoint is enabled
- The mapping connects the correct endpoints
- The target endpoint ID matches exactly
"Endpoint shows as skipped"
The endpoint has unconfigured path parameters. Check:
- All
{param}in the path have mappings - Mappings target the
pathlocation (notqueryorbody)
"Circular dependency detected"
Review the dependency chain. Common causes:
- A depends on B for parameter X, B depends on A for parameter Y
- Indirect cycles through multiple endpoints
Related Documentation
- Parameter Extraction - How values are extracted from responses
- Dependency Mapping - User guide for creating mappings