Core API Draft
This is the first-pass shape for the library core.
The goal is to keep the model: - UI-agnostic - registry-driven - easy to validate - easy to serialize
Type Model
sealed interface WorkflowType {
data object StringType : WorkflowType
data object IntType : WorkflowType
data object DoubleType : WorkflowType
data object BooleanType : WorkflowType
data class ListType(val elementType: WorkflowType) : WorkflowType
data class NullableType(val wrappedType: WorkflowType) : WorkflowType
data class RecordType(
val fields: Map<String, WorkflowType>,
) : WorkflowType
data class EnumType(
val values: List<String>,
) : WorkflowType
data object OpaqueType : WorkflowType
}
Notes:
- DoubleType is the default decimal type.
- OpaqueType is for runtime handles or values the workflow model should not inspect.
- RecordType lets us model structured input/output without inventing custom node-specific primitives.
Ports and Nodes
data class PortSpec(
val name: String,
val type: WorkflowType,
val required: Boolean = true,
)
data class NodeSpec(
val type: String,
val label: String,
val inputs: List<PortSpec>,
val outputs: List<PortSpec>,
val defaultValues: Map<String, WorkflowValue> = emptyMap(),
)
data class NodeRef(
val id: String,
val type: String,
val config: Map<String, WorkflowValue> = emptyMap(),
)
data class ConnectionRef(
val fromNodeId: String,
val fromPort: String,
val toNodeId: String,
val toPort: String,
)
Values
sealed interface WorkflowValue {
data class StringValue(val value: String) : WorkflowValue
data class IntValue(val value: Int) : WorkflowValue
data class DoubleValue(val value: Double) : WorkflowValue
data class BooleanValue(val value: Boolean) : WorkflowValue
data class ListValue(val items: List<WorkflowValue>) : WorkflowValue
data class RecordValue(val fields: Map<String, WorkflowValue>) : WorkflowValue
data object NullValue : WorkflowValue
data object OpaqueValue : WorkflowValue
}
Workflow Definition
data class WorkflowDefinition(
val id: String,
val name: String,
val nodes: List<NodeRef>,
val connections: List<ConnectionRef>,
)
Validation
data class ValidationError(
val code: String,
val message: String,
val nodeId: String? = null,
val port: String? = null,
)
interface WorkflowValidator {
fun validate(workflow: WorkflowDefinition): List<ValidationError>
}
Validation should check: - node existence - port existence - type compatibility - required input satisfaction - invalid cycles if the runtime cannot support them
Execution Contract
interface NodeExecutor {
suspend fun execute(
input: Map<String, WorkflowValue>,
): Map<String, WorkflowValue>
}
interface NodeExecutorRegistry {
fun resolve(type: String): NodeExecutor?
}
The core should define the contract, but not the UI.