```typescript

/**
 * Topological sort based on given edges in directed acyclic graph (DAG).
 *
 * @param nodes - Array of node names.
 * @param edges - Array of [from, to] tuples representing directed edges.
 * @returns An array of node names in topological order.
 */
export function topologicalSort(nodes: readonly string[], edges: readonly (readonly [string, string])[]): string[] {
  // Build an adjacency list representation of the graph
  const adjList = new Map<string, Set<string>>();
  for (const edge of edges) {
    if (!adjList.has(edge[0])) {
      adjList.set(edge[0], new Set());
    }
    adjList.get(edge[0])?.add(edge[1]);
  }

  // Track the in-degree of each node
  const inDegree = new Map<string, number>();
  for (const node of nodes) {
    inDegree.set(node, 0);
  }
  for (const [from] of edges) {
    inDegree.set(from, (inDegree.get(from) ?? 0) + 1);
  }

  // Initialize a queue with all nodes having zero in-degree
  const queue: string[] = [];
  for (const node of nodes) {
    if (!inDegree.has(node)) {
      queue.push(node);
    }
  }

  // Perform topological sort
  const sortedNodes: string[] = [];
  while (queue.length > 0) {
    const current = queue.shift()!;
    sortedNodes.push(current);
    for (const next of adjList.get(current) ?? []) {
      inDegree.set(next, inDegree.get(next)! - 1);
      if (!inDegree.has(next)) {
        continue;
      }
      if (inDegree.get(next) === 0) {
        queue.push(next);
      }
    }
  }

  // Check for cycles
  const visited = new Set<string>();
  for (const node of nodes) {
    if (!visited.has(node)) {
      if (detectCycle(node, adjList, visited)) {
        throw new Error('Cyclic graph detected');
      }
    }
  }

  return sortedNodes;
}

/**
 * Detects cycles in a directed acyclic graph using DFS.
 *
 * @param node - Current node being checked.
 * @param adjList - Adjacency list representation of the graph.
 * @param visited - Set to keep track of visited nodes.
 * @returns true if a cycle is detected, false otherwise.
 */
function detectCycle(node: string, adjList: Map<string, Set<string>>, visited: Set<string>): boolean {
  if (visited.has(node)) {
    return true;
  }
  visited.add(node);
  for (const next of adjList.get(node) ?? []) {
    if (detectCycle(next, adjList, visited)) {
      return true;
    }
  }
  visited.delete(node);
  return false;
}
```