WorkflowProjection.java
package org.hammer.audio.workflow.editor;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.hammer.audio.workflow.Workflow;
/**
* React Flow–ready read model derived from a {@link Workflow}.
*
* <p>Each {@link NodeProjection} carries typed-port handle descriptors ({@link HandleProjection})
* so the React Flow client can render typed {@code Handle} components without parsing domain
* objects directly. Each {@link EdgeProjection} maps directly to a React Flow edge.
*
* <p>Instances are immutable and created via {@link #fromWorkflow(Workflow)}.
*
* @param workflowId stable workflow identifier
* @param workflowName human-readable workflow name
* @param nodes projected nodes with typed-port handle lists
* @param edges projected edges with source and target handle identifiers
*/
public record WorkflowProjection(
String workflowId,
String workflowName,
List<NodeProjection> nodes,
List<EdgeProjection> edges) {
public WorkflowProjection {
Objects.requireNonNull(workflowId, "workflowId");
Objects.requireNonNull(workflowName, "workflowName");
nodes = List.copyOf(Objects.requireNonNull(nodes, "nodes"));
edges = List.copyOf(Objects.requireNonNull(edges, "edges"));
}
/**
* Projects a {@link Workflow} into the React Flow–ready read model.
*
* @param workflow source workflow
* @return projection ready for serialisation to the browser
*/
public static WorkflowProjection fromWorkflow(Workflow workflow) {
Objects.requireNonNull(workflow, "workflow");
List<NodeProjection> nodeProjections =
workflow.nodes().stream()
.map(
node ->
new NodeProjection(
node.id(),
node.type(),
node.label(),
node.inputPorts().stream()
.map(p -> new HandleProjection(p.id(), p.name(), p.dataType().id()))
.toList(),
node.outputPorts().stream()
.map(p -> new HandleProjection(p.id(), p.name(), p.dataType().id()))
.toList(),
Map.copyOf(node.metadata().entries())))
.toList();
List<EdgeProjection> edgeProjections =
workflow.edges().stream()
.map(
edge ->
new EdgeProjection(
edge.id(),
edge.sourceNodeId(),
edge.sourcePortId(),
edge.targetNodeId(),
edge.targetPortId()))
.toList();
return new WorkflowProjection(workflow.id(), workflow.name(), nodeProjections, edgeProjections);
}
/**
* Projection of a single workflow node with typed-port handle lists and property values.
*
* @param id stable node identifier (maps to React Flow {@code node.id})
* @param type node type string (maps to React Flow {@code node.type})
* @param label human-readable label (maps to React Flow {@code node.data.label})
* @param inputHandles typed input port descriptors (source of React Flow target {@code Handle}s)
* @param outputHandles typed output port descriptors (source of React Flow source {@code
* Handle}s)
* @param properties metadata properties set on the node (e.g. parameter values updated via {@code
* UpdateProperty})
*/
public record NodeProjection(
String id,
String type,
String label,
List<HandleProjection> inputHandles,
List<HandleProjection> outputHandles,
Map<String, String> properties) {
public NodeProjection {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(type, "type");
Objects.requireNonNull(label, "label");
inputHandles = List.copyOf(Objects.requireNonNull(inputHandles, "inputHandles"));
outputHandles = List.copyOf(Objects.requireNonNull(outputHandles, "outputHandles"));
properties = Map.copyOf(Objects.requireNonNull(properties, "properties"));
}
}
/**
* Typed port descriptor used by the React Flow client to render a {@code Handle} component.
*
* @param id stable port identifier (React Flow {@code Handle id})
* @param name human-readable port name
* @param dataType workflow data type identifier (used for visual type colouring and tooltip)
*/
public record HandleProjection(String id, String name, String dataType) {
public HandleProjection {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(name, "name");
Objects.requireNonNull(dataType, "dataType");
}
}
/**
* Projection of a workflow edge.
*
* @param id stable edge identifier (maps to React Flow {@code edge.id})
* @param source source node identifier (maps to React Flow {@code edge.source})
* @param sourceHandle source port identifier (maps to React Flow {@code edge.sourceHandle})
* @param target target node identifier (maps to React Flow {@code edge.target})
* @param targetHandle target port identifier (maps to React Flow {@code edge.targetHandle})
*/
public record EdgeProjection(
String id, String source, String sourceHandle, String target, String targetHandle) {
public EdgeProjection {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(source, "source");
Objects.requireNonNull(sourceHandle, "sourceHandle");
Objects.requireNonNull(target, "target");
Objects.requireNonNull(targetHandle, "targetHandle");
}
}
}