flow-to-graph.ts1.8 KBView on GitHub
/**
 * React Flow → domain. The inverse of `graphToFlow`, and deliberately much smaller than it.
 *
 * Only TWO gestures on the canvas change the document, and each maps to exactly one operation:
 *
 *   a drag        → `moveNode`, ONE `graphLayout` key
 *   a connection  → `connect`, with the currently selected edge kind
 *
 * Everything else the canvas shows — the layout it computed, the lint rings, the hydrated
 * values, the selection — is an OVERLAY and is never written back. That is why there is no
 * `flowToGraph(nodes, edges)` that serializes the whole canvas: a whole-canvas save is how a
 * derived annotation becomes a persisted field, and how one person's open tab overwrites
 * another's concurrent edit.
 */

import type { Connection } from '@xyflow/react';
import type { GraphFlowNode } from './graph-flow-types';
import type { GraphPosition } from '@zero/server/graph';

/** A drag, as the one `moveNode` call it is. */
export function dragToMove(node: Pick<GraphFlowNode, 'id' | 'position'>): {
  node: string;
  position: GraphPosition;
} {
  return { node: node.id, position: { x: node.position.x, y: node.position.y } };
}

/**
 * A drawn connection, as the `connect` call it is.
 *
 * Returns `null` for a connection React Flow can produce but a graph cannot hold: a missing
 * endpoint, or a self-connection. Refusing here rather than at the server keeps the canvas from
 * showing an edge for the moment it takes the write to come back and fail.
 */
export function connectionToEdge(
  connection: Connection,
  kind: string,
): { source: string; target: string; kind: string } | null {
  if (!connection.source || !connection.target) return null;
  if (connection.source === connection.target) return null;
  if (!kind) return null;
  return { source: connection.source, target: connection.target, kind };
}