@bernierllc/generic-workflow-ui
Version:
Generic, reusable workflow UI components with linear and graph visualization
122 lines (121 loc) • 4.91 kB
JavaScript
;
/*
Copyright (c) 2025 Bernier LLC
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateWorkflowJSON = validateWorkflowJSON;
/**
* Validate JSON workflow definition
*/
function validateWorkflowJSON(json) {
const errors = [];
// Validate required fields
if (!json.name || typeof json.name !== 'string') {
errors.push({
path: 'name',
message: 'Workflow name is required and must be a string',
code: 'REQUIRED_FIELD'
});
}
if (!Array.isArray(json.nodes)) {
errors.push({
path: 'nodes',
message: 'Nodes must be an array',
code: 'INVALID_TYPE'
});
}
else {
// Validate nodes
json.nodes.forEach((node, index) => {
if (!node.id || typeof node.id !== 'string') {
errors.push({
path: `nodes[${index}].id`,
message: 'Node ID is required and must be a string',
code: 'REQUIRED_FIELD'
});
}
if (!node.name || typeof node.name !== 'string') {
errors.push({
path: `nodes[${index}].name`,
message: 'Node name is required and must be a string',
code: 'REQUIRED_FIELD'
});
}
if (!node.type || typeof node.type !== 'string') {
errors.push({
path: `nodes[${index}].type`,
message: 'Node type is required and must be a string',
code: 'REQUIRED_FIELD'
});
}
if (!Array.isArray(node.position) || node.position.length !== 2) {
errors.push({
path: `nodes[${index}].position`,
message: 'Node position must be an array of [x, y] coordinates',
code: 'INVALID_FORMAT'
});
}
});
// Check for duplicate node IDs
const nodeIds = json.nodes.map((n) => n.id);
const duplicates = nodeIds.filter((id, index) => nodeIds.indexOf(id) !== index);
duplicates.forEach((id) => {
errors.push({
path: 'nodes',
message: `Duplicate node ID: ${id}`,
code: 'DUPLICATE_ID'
});
});
}
if (!json.connections || typeof json.connections !== 'object') {
errors.push({
path: 'connections',
message: 'Connections must be an object',
code: 'INVALID_TYPE'
});
}
else if (Array.isArray(json.nodes)) {
// Validate connections reference valid nodes (only if nodes is a valid array)
const nodeIds = new Set(json.nodes.map((n) => n.id));
Object.entries(json.connections).forEach(([nodeId, outputs]) => {
if (!nodeIds.has(nodeId)) {
errors.push({
path: `connections.${nodeId}`,
message: `Connection references non-existent node: ${nodeId}`,
code: 'INVALID_REFERENCE'
});
}
Object.entries(outputs).forEach(([outputType, connectionGroups]) => {
connectionGroups.forEach((connections, groupIndex) => {
connections.forEach((connection, connIndex) => {
if (!nodeIds.has(connection.node)) {
errors.push({
path: `connections.${nodeId}.${outputType}[${groupIndex}][${connIndex}].node`,
message: `Connection references non-existent node: ${connection.node}`,
code: 'INVALID_REFERENCE'
});
}
if (!connection.type || typeof connection.type !== 'string') {
errors.push({
path: `connections.${nodeId}.${outputType}[${groupIndex}][${connIndex}].type`,
message: 'Connection type is required and must be a string',
code: 'REQUIRED_FIELD'
});
}
if (connection.index === undefined || typeof connection.index !== 'number') {
errors.push({
path: `connections.${nodeId}.${outputType}[${groupIndex}][${connIndex}].index`,
message: 'Connection index is required and must be a number',
code: 'REQUIRED_FIELD'
});
}
});
});
});
});
}
return {
valid: errors.length === 0,
errors
};
}