| 4 | import { createId } from "@paralleldrive/cuid2"; |
| 5 | |
| 6 | export const topologicalSort = ( |
| 7 | nodes: Node[], |
| 8 | connections: Connection[], |
| 9 | ): Node[] => { |
| 10 | // If no connections, return node as-is (they're all independent) |
| 11 | if (connections.length === 0) { |
| 12 | return nodes; |
| 13 | } |
| 14 | |
| 15 | // Create edges array for toposort |
| 16 | const edges: [string, string][] = connections.map((conn) => [ |
| 17 | conn.fromNodeId, |
| 18 | conn.toNodeId, |
| 19 | ]); |
| 20 | |
| 21 | // Add nodes with no connections as self-edges to ensure they're included |
| 22 | const connectedNodeIds = new Set<string>(); |
| 23 | for (const conn of connections) { |
| 24 | connectedNodeIds.add(conn.fromNodeId); |
| 25 | connectedNodeIds.add(conn.toNodeId); |
| 26 | } |
| 27 | |
| 28 | for (const node of nodes) { |
| 29 | if (!connectedNodeIds.has(node.id)) { |
| 30 | edges.push([node.id, node.id]); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // Perform topological sort |
| 35 | let sortedNodeIds: string[]; |
| 36 | try { |
| 37 | sortedNodeIds = toposort(edges); |
| 38 | // Remove duplicates (from self-edges) |
| 39 | sortedNodeIds = [...new Set(sortedNodeIds)]; |
| 40 | } catch (error) { |
| 41 | if (error instanceof Error && error.message.includes("Cyclic")) { |
| 42 | throw new Error("Workflow contains a cycle"); |
| 43 | } |
| 44 | throw error; |
| 45 | } |
| 46 | |
| 47 | // Map sorted IDs back to node objects |
| 48 | const nodeMap = new Map(nodes.map((n) => [n.id, n])); |
| 49 | return sortedNodeIds.map((id) => nodeMap.get(id)!).filter(Boolean); |
| 50 | }; |
| 51 | |
| 52 | export const sendWorkflowExecution = async (data: { |
| 53 | workflowId: string; |