```ts
/**
 * Performs a topological sort on the given nodes and edges.
 * @param nodes - List of node names.
 * @param edges - List of [from, to] edges where 'from' must come before 'to'.
 * @returns An array of node names in topological order.
 * @throws Error if an edge references a node not in nodes, or if a cycle is detected.
 */
export function topologicalSort(nodes: readonly string[], edges: readonly (readonly [string, string])[]): string[] {
  // Build adjacency list and in-degree count
  const graph = new Map<string, Set<string>>();
  const inDegree = new Map<string, number>();
  
  // Initialize all nodes
  for (const node of nodes) {
    graph.set(node, new Set());
    inDegree.set(node, 0);
  }

  // Process edges
  for (const [from, to] of edges) {
    if (!nodes.includes(from)) {
      throw new Error(`Edge references unknown node: ${from}`);
    }
    if (!nodes.includes(to)) {
      throw new Error(`Edge references unknown node: ${to}`);
    }
    
    // Avoid duplicate edges by checking if already present
    if (!graph.get(from)!.has(to)) {
      graph.get(from)!.add(to);
      inDegree.set(to, inDegree.get(to)! + 1);
    }
  }

  // Kahn's algorithm with priority based on input order
  const result: string[] = [];
  const available: string[] = [];

  // Initialize with nodes that have no incoming edges (in-degree 0)
  // Maintain original node order for tie-breaking
  for (const node of nodes) {
    if (inDegree.get(node) === 0) {
      available.push(node);
    }
  }

  // Process available nodes in order
  while (available.length > 0) {
    // Take the first available node (already in input order)
    const current = available.shift()!;
    result.push(current);

    // Update neighbors
    const neighbors = graph.get(current);
    if (neighbors) {
      for (const neighbor of neighbors) {
        inDegree.set(neighbor, inDegree.get(neighbor)! - 1);
        
        if (inDegree.get(neighbor) === 0) {
          available.push(neighbor);
          // Re-sort available to maintain input order
          available.sort((a, b) => nodes.indexOf(a) - nodes.indexOf(b));
        }
      }
    }
  }

  // Check for cycle
  if (result.length !== nodes.length) {
    throw new Error('Cycle detected in graph');
  }

  return result;
}
```