godcrm/backend/services/labs/index.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

143 lines
No EOL
4.3 KiB
JavaScript

/**
* Labs Service - Main service for Labs v4
* @see ADR-043: Laboratories Feature
*/
import { getAllNodeTypes, getNodeType, validateNodeConfig, executeNode } from './node-types/index.js';
import { apiLogger } from '../../utils/logger.js';
import crypto from 'crypto';
/**
* Labs Service Class
*/
export class LabsService {
/**
* Get all available node types
* @returns {Array} Array of node type definitions
*/
static getNodeTypes() {
return getAllNodeTypes();
}
/**
* Get specific node type by key
* @param {string} typeKey - Node type key
* @returns {Object|null} Node type definition or null
*/
static getNodeType(typeKey) {
return getNodeType(typeKey);
}
/**
* Validate node configuration
* @param {string} typeKey - Node type key
* @param {Object} config - Configuration to validate
* @returns {Object} Validation result
*/
static validateNodeConfig(typeKey, config) {
return validateNodeConfig(typeKey, config);
}
/**
* Execute a node
* @param {Object} node - Node to execute
* @param {Object} context - Execution context
* @returns {Promise<Object>} Execution result
*/
static async executeNode(node, context = {}) {
try {
return await executeNode(node, context);
} catch (error) {
apiLogger.error({ error, node }, 'Node execution failed');
return {
success: false,
error: 'Node execution failed',
details: error.message
};
}
}
/**
* Create node data with defaults from node type
* @param {string} typeKey - Node type key
* @param {Object} nodeData - Partial node data
* @returns {Object} Complete node data with defaults applied
*/
static createNodeWithDefaults(typeKey, nodeData = {}) {
const nodeType = getNodeType(typeKey);
if (!nodeType) {
throw new Error(`Unknown node type: ${typeKey}`);
}
// Handle position from both formats: {position: {x, y}} and {position_x, position_y}
const posX = nodeData.position?.x ?? nodeData.position_x ?? 0;
const posY = nodeData.position?.y ?? nodeData.position_y ?? 0;
return {
node_id: nodeData.node_id || crypto.randomUUID(),
type_key: typeKey,
title: nodeData.title || nodeType.name,
content: nodeData.content || '',
position_x: posX,
position_y: posY,
position: { x: posX, y: posY }, // Also include nested format for compatibility
width: nodeData.width || nodeType.defaultWidth,
height: nodeData.height || nodeType.defaultHeight,
edges: nodeData.edges || [],
ai_agent_id: nodeData.ai_agent_id || null,
config: { ...nodeType.defaultConfig, ...(nodeData.config || {}) },
order_index: nodeData.order_index || 0
};
}
/**
* Validate complete node data
* @param {Object} nodeData - Node data to validate
* @returns {Object} Validation result
*/
static validateNode(nodeData) {
const errors = [];
if (!nodeData.type_key) {
errors.push('type_key is required');
} else {
const nodeType = getNodeType(nodeData.type_key);
if (!nodeType) {
errors.push(`Unknown node type: ${nodeData.type_key}`);
} else {
// Validate config
const configValidation = validateNodeConfig(nodeData.type_key, nodeData.config || {});
if (!configValidation.valid) {
errors.push(...configValidation.errors);
}
}
}
if (!nodeData.title || typeof nodeData.title !== 'string') {
errors.push('title is required and must be a string');
}
if (nodeData.position_x !== undefined && typeof nodeData.position_x !== 'number') {
errors.push('position_x must be a number');
}
if (nodeData.position_y !== undefined && typeof nodeData.position_y !== 'number') {
errors.push('position_y must be a number');
}
if (nodeData.width !== undefined && (typeof nodeData.width !== 'number' || nodeData.width <= 0)) {
errors.push('width must be a positive number');
}
if (nodeData.height !== undefined && (typeof nodeData.height !== 'number' || nodeData.height <= 0)) {
errors.push('height must be a positive number');
}
return {
valid: errors.length === 0,
errors
};
}
}
// Export convenience functions
export { getAllNodeTypes, getNodeType, validateNodeConfig, executeNode };