```ts
export function topoSort(n: number, edges: [number, number][]): number[] {
  // Build adjacency list and in-degree count
  const graph: Set<number>[] = Array(n).fill(null).map(() => new Set());
  const inDegree = Array(n).fill(0);
  
  for (const [u, v] of edges) {
    if (u < 0 || u >= n || v < 0 || v >= n) {
      throw new Error("Invalid node index");
    }
    if (u === v) {
      throw new Error("Cycle detected");
    }
    if (!graph[u].has(v)) {
      graph[u].add(v);
      inDegree[v]++;
    }
  }
  
  // Initialize queue with all nodes with no incoming edges
  const queue: number[] = [];
  for (let i = 0; i < n; i++) {
    if (inDegree[i] === 0) {
      queue.push(i);
    }
  }
  
  // Sort queue to ensure lexicographically smallest order
  queue.sort((a, b) => a - b);
  
  const result: number[] = [];
  
  while (queue.length > 0) {
    // Take the smallest node from the queue
    const u = queue.shift()!;
    result.push(u);
    
    // For each neighbor of u, decrease in-degree
    for (const v of graph[u]) {
      inDegree[v]--;
      // If in-degree becomes 0, add to queue
      if (inDegree[v] === 0) {
        queue.push(v);
        // Keep queue sorted to maintain lexicographical order
        queue.sort((a, b) => a - b);
      }
    }
  }
  
  // Check for cycles - if result doesn't contain all nodes, there's a cycle
  if (result.length !== n) {
    throw new Error("Cycle detected");
  }
  
  return result;
}
```