Files
lemonspace_app/components/canvas/canvas-connection-validation.ts

97 lines
3.0 KiB
TypeScript

import type { Connection, Edge as RFEdge, Node as RFNode } from "@xyflow/react";
import {
validateCanvasConnectionPolicy,
type CanvasConnectionValidationReason,
} from "@/lib/canvas-connection-policy";
import { NODE_HANDLE_MAP } from "@/lib/canvas-utils";
import { isOptimisticEdgeId } from "./canvas-helpers";
export function validateCanvasConnection(
connection: Connection,
nodes: RFNode[],
edges: RFEdge[],
edgeToReplaceId?: string,
options?: {
includeOptimisticEdges?: boolean;
},
): CanvasConnectionValidationReason | null {
if (!connection.source || !connection.target) return "incomplete";
if (connection.source === connection.target) return "self-loop";
const sourceNode = nodes.find((node) => node.id === connection.source);
const targetNode = nodes.find((node) => node.id === connection.target);
if (!sourceNode || !targetNode) return "unknown-node";
return validateCanvasConnectionByType({
sourceType: sourceNode.type ?? "",
targetType: targetNode.type ?? "",
targetNodeId: connection.target,
targetHandle: connection.targetHandle,
edges,
edgeToReplaceId,
includeOptimisticEdges: options?.includeOptimisticEdges,
});
}
export function validateCanvasConnectionByType(args: {
sourceType: string;
targetType: string;
targetNodeId: string;
targetHandle?: string | null;
edges: RFEdge[];
edgeToReplaceId?: string;
includeOptimisticEdges?: boolean;
}): CanvasConnectionValidationReason | null {
const targetIncomingEdges = args.edges.filter(
(edge) =>
edge.className !== "temp" &&
(args.includeOptimisticEdges || !isOptimisticEdgeId(edge.id)) &&
edge.target === args.targetNodeId &&
edge.id !== args.edgeToReplaceId,
);
return validateCanvasConnectionPolicy({
sourceType: args.sourceType,
targetType: args.targetType,
targetIncomingCount: targetIncomingEdges.length,
targetHandle: args.targetHandle,
targetIncomingHandles: targetIncomingEdges.map((edge) => edge.targetHandle),
});
}
export function validateCanvasEdgeSplit(args: {
nodes: RFNode[];
edges: RFEdge[];
splitEdge: RFEdge;
middleNode: RFNode;
}): CanvasConnectionValidationReason | null {
const sourceNode = args.nodes.find((node) => node.id === args.splitEdge.source);
const targetNode = args.nodes.find((node) => node.id === args.splitEdge.target);
if (!sourceNode || !targetNode) {
return "unknown-node";
}
const middleNodeHandles = NODE_HANDLE_MAP[args.middleNode.type ?? ""];
return (
validateCanvasConnectionByType({
sourceType: sourceNode.type ?? "",
targetType: args.middleNode.type ?? "",
targetNodeId: args.middleNode.id,
targetHandle: middleNodeHandles?.target,
edges: args.edges,
}) ??
validateCanvasConnectionByType({
sourceType: args.middleNode.type ?? "",
targetType: targetNode.type ?? "",
targetNodeId: targetNode.id,
targetHandle: args.splitEdge.targetHandle,
edges: args.edges,
edgeToReplaceId: args.splitEdge.id,
})
);
}