```ts
/**
 * Performs a topological sort on the given nodes and edges.
 * Returns an ordering where for every edge [from, to], 'from' appears before 'to'.
 * Among available nodes (with no unmet dependencies), selects the earliest in the input nodes array.
 * Throws an error if an edge references a node not in nodes, or if a cycle exists.
 */
export function topologicalSort(nodes: readonly string[], edges: readonly (readonly [string, string])[]): string[] {
  const nodeSet = new Set(nodes);
  
  // Validate all edges reference existing nodes
  for (const [from, to] of edges) {
    if (!nodeSet.has(from)) {
      throw new Error(`Node "${from}" in edge is not in the nodes list`);
    }
    if (!nodeSet.has(to)) {
      throw new Error(`Node "${to}" in edge is not in the nodes list`);
    }
  }

  // Build adjacency list and in-degree count
  const adjacency: Map<string, string[]> = new Map();
  const inDegree: Map<string, number> = new Map();

  for (const node of nodes) {
    adjacency.set(node, []);
    inDegree.set(node, 0);
  }

  for (const [from, to] of edges) {
    adjacency.get(from)!.push(to);
    inDegree.set(to, inDegree.get(to)! + 1);
  }

  // Initialize queue with nodes having in-degree 0, maintaining original order
  const available = nodes.filter(node => inDegree.get(node) === 0);
  const result: string[] = [];

  while (available.length > 0) {
    // Select the first node in the available list (maintains input order)
    const current = available.shift()!;
    result.push(current);

    // Update neighbors
    for (const neighbor of adjacency.get(current)!) {
      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 the graph");
  }

  return result;
}
```