94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
import {type Connection, type Edge, getOutgoers, type Node} from '@xyflow/react'
|
|
import {find, has, isEmpty, isEqual, lpad, toStr} from 'licia'
|
|
import NodeRegistry from './NodeRegistry.tsx'
|
|
|
|
export class CheckError extends Error {
|
|
readonly id: string
|
|
|
|
constructor(
|
|
id: number,
|
|
message: string,
|
|
) {
|
|
super(message)
|
|
this.id = `E${lpad(toStr(id), 6, '0')}`
|
|
}
|
|
|
|
public toString(): string {
|
|
return `${this.id}: ${this.message}`
|
|
}
|
|
}
|
|
|
|
const getNodeById = (id: string, nodes: Node[]) => find(nodes, (n: Node) => isEqual(n.id, id))
|
|
|
|
// @ts-ignore
|
|
export const checkAddNode: (type: string, nodes: Node[], edges: Edge[]) => void = (type, nodes, edges) => {
|
|
}
|
|
|
|
export const sourceNodeNotFoundError = () => new CheckError(200, '连线起始节点未找到')
|
|
export const targetNodeNotFoundError = () => new CheckError(201, '连线目标节点未找到')
|
|
export const nodeToSelfError = () => new CheckError(203, '节点不能直连自身')
|
|
export const hasCycleError = () => new CheckError(204, '禁止流程循环')
|
|
|
|
const hasCycle = (sourceNode: Node, targetNode: Node, nodes: Node[], edges: Edge[], visited = new Set<string>()) => {
|
|
if (visited.has(targetNode.id)) return false
|
|
visited.add(targetNode.id)
|
|
for (const outgoer of getOutgoers(targetNode, nodes, edges)) {
|
|
if (isEqual(outgoer.id, sourceNode.id)) return true
|
|
if (hasCycle(sourceNode, outgoer, nodes, edges, visited)) return true
|
|
}
|
|
}
|
|
|
|
export const checkAddConnection: (connection: Connection, nodes: Node[], edges: Edge[]) => void = (connection, nodes, edges) => {
|
|
let sourceNode = getNodeById(connection.source, nodes)
|
|
if (!sourceNode) {
|
|
throw sourceNodeNotFoundError()
|
|
}
|
|
let targetNode = getNodeById(connection.target, nodes)
|
|
if (!targetNode) {
|
|
throw targetNodeNotFoundError()
|
|
}
|
|
|
|
// 禁止流程出现环,必须是有向无环图
|
|
if (isEqual(sourceNode.id, targetNode.id)) {
|
|
throw nodeToSelfError()
|
|
} else if (hasCycle(sourceNode, targetNode, nodes, edges)) {
|
|
throw hasCycleError()
|
|
}
|
|
|
|
// let newEdges = [...clone(edges), {...connection, id: uuid()}]
|
|
// let {hasAbnormalEdges} = getParallelInfo(nodes, newEdges)
|
|
// if (hasAbnormalEdges) {
|
|
// throw hasRedundantEdgeError()
|
|
// }
|
|
}
|
|
|
|
export const atLeastOneNode = () => new CheckError(300, '至少包含一个节点')
|
|
export const hasUnfinishedNode = (nodeId: string) => new CheckError(301, `存在尚未配置完成的节点: ${nodeId}`)
|
|
export const nodeTypeNotFound = () => new CheckError(302, '节点类型不存在')
|
|
export const nodeError = (nodeId: string, reason?: string) => new CheckError(303, reason ?? `节点配置存在错误:${nodeId}`)
|
|
|
|
// @ts-ignore
|
|
export const checkSave: (inputSchema: Record<string, Record<string, any>>, nodes: Node[], edges: Edge[], data: any) => void = (inputSchema, nodes, edges, data) => {
|
|
if (isEmpty(nodes)) {
|
|
throw atLeastOneNode()
|
|
}
|
|
|
|
for (let node of nodes) {
|
|
let nodeId = node.id
|
|
if (!has(data, nodeId) || !data[nodeId]?.finished) {
|
|
throw hasUnfinishedNode(nodeId)
|
|
}
|
|
|
|
if (!has(node, 'type')) {
|
|
throw nodeTypeNotFound()
|
|
}
|
|
let nodeType = node.type!
|
|
let nodeDefine = NodeRegistry[nodeType]
|
|
for (let checker of nodeDefine.checkers) {
|
|
let checkResult = checker(nodeId, inputSchema, nodes, edges, data)
|
|
if (checkResult.error) {
|
|
throw nodeError(nodeId, checkResult.message)
|
|
}
|
|
}
|
|
}
|
|
} |