Skip to content

Commit 28387a9

Browse files
committed
refactor: полноценный внутренний рефактор нод
1 parent 17bbde8 commit 28387a9

51 files changed

Lines changed: 2579 additions & 2457 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const express = require('express');
2+
const router = express.Router();
3+
const nodeRegistry = require('../../core/NodeRegistry');
4+
5+
router.get('/nodes', (req, res) => {
6+
try {
7+
const graphType = req.query.graphType || null;
8+
const flat = nodeRegistry.getAllNodes().map(node => {
9+
const json = node.toJSON ? node.toJSON() : node;
10+
return json;
11+
});
12+
13+
if (graphType) {
14+
const filtered = flat.filter(node =>
15+
node.graphType === graphType || node.graphType === 'ALL'
16+
);
17+
return res.json({ nodes: filtered, count: filtered.length });
18+
}
19+
20+
res.json({ nodes: flat, count: flat.length });
21+
} catch (error) {
22+
console.error('[API] Error getting nodes:', error);
23+
res.status(500).json({ error: 'Failed to get nodes', message: error.message });
24+
}
25+
});
26+
27+
router.get('/nodes/categories', (req, res) => {
28+
try {
29+
const graphType = req.query.graphType || null;
30+
const byCategory = nodeRegistry.getNodesByCategory(graphType);
31+
32+
const result = {};
33+
for (const [category, nodes] of Object.entries(byCategory)) {
34+
result[category] = nodes.map(node => {
35+
return node.toJSON ? node.toJSON() : node;
36+
});
37+
}
38+
39+
res.json({ categories: result, count: Object.keys(result).length });
40+
} catch (error) {
41+
console.error('[API] Error getting nodes by category:', error);
42+
res.status(500).json({ error: 'Failed to get nodes', message: error.message });
43+
}
44+
});
45+
46+
router.get('/nodes/:type', (req, res) => {
47+
try {
48+
const { type } = req.params;
49+
const node = nodeRegistry.getNodeConfig(type);
50+
51+
if (!node) {
52+
return res.status(404).json({ error: 'Node type not found', type });
53+
}
54+
55+
const json = node.toJSON ? node.toJSON() : node;
56+
res.json({ node: json });
57+
} catch (error) {
58+
console.error('[API] Error getting node:', error);
59+
res.status(500).json({ error: 'Failed to get node', message: error.message });
60+
}
61+
});
62+
63+
module.exports = router;

backend/src/core/NodeDefinition.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Базовый класс для определения ноды в визуальном редакторе.
3+
* Используется как единый формат для всех нод - и backend и frontend.
4+
*/
5+
class NodeDefinition {
6+
constructor(config) {
7+
this.type = config.type;
8+
this.category = config.category || 'Other';
9+
this.label = config.label || config.type;
10+
this.description = config.description || '';
11+
12+
// Функции для вычисления пинов динамически (поддержка conditional pins)
13+
this.computeInputs = config.computeInputs || (() => []);
14+
this.computeOutputs = config.computeOutputs || (() => []);
15+
16+
// Статичные пиньи (fallback для обратной совместимости)
17+
this.pins = config.pins || { inputs: [], outputs: [] };
18+
19+
// Исполнители ноды
20+
// executor - для action нод с exec пинами (асинхронный)
21+
// evaluator - для data пинов (вычисление значений)
22+
this.executor = config.executor || null;
23+
this.evaluator = config.evaluator || null;
24+
25+
// Метаданные ноды
26+
this.defaultData = config.defaultData || {};
27+
this.theme = config.theme || {};
28+
this.icon = config.icon || null;
29+
this.graphType = config.graphType || 'ALL'; // 'COMMAND', 'EVENT', 'ALL'
30+
31+
// Валидация
32+
if (!this.type) {
33+
throw new Error('NodeDefinition: type is required');
34+
}
35+
}
36+
37+
/**
38+
* Получить входные пиньи для данного состояния ноды
39+
* @param {object} data - данные ноды (для условных пиннов)
40+
* @returns {Array} массив описаний входных пиннов
41+
*/
42+
getInputs(data = {}) {
43+
if (typeof this.computeInputs === 'function') {
44+
return this.computeInputs(data);
45+
}
46+
return this.pins.inputs || [];
47+
}
48+
49+
/**
50+
* Получить выходные пиньи для данного состояния ноды
51+
* @param {object} data - данные ноды (для условных пиннов)
52+
* @returns {Array} массив описаний выходных пиннов
53+
*/
54+
getOutputs(data = {}) {
55+
if (typeof this.computeOutputs === 'function') {
56+
return this.computeOutputs(data);
57+
}
58+
return this.pins.outputs || [];
59+
}
60+
61+
/**
62+
* Проверить, является ли нода action-нодой (с exec пинами)
63+
*/
64+
isActionNode() {
65+
const inputs = this.getInputs();
66+
return inputs.some(pin => pin.type === 'Exec');
67+
}
68+
69+
/**
70+
* Проверить, является ли нода data-нодой (только вычисляемые значения)
71+
*/
72+
isDataNode() {
73+
return !this.isActionNode();
74+
}
75+
76+
/**
77+
* Проверить, есть ли у ноды executor
78+
*/
79+
hasExecutor() {
80+
return typeof this.executor === 'function';
81+
}
82+
83+
/**
84+
* Проверить, есть ли у ноды evaluator
85+
*/
86+
hasEvaluator() {
87+
return typeof this.evaluator === 'function';
88+
}
89+
90+
/**
91+
* Конвертировать в JSON-сериализуемый формат
92+
* Используется для отправки в frontend
93+
*/
94+
toJSON() {
95+
return {
96+
type: this.type,
97+
category: this.category,
98+
label: this.label,
99+
description: this.description,
100+
pins: {
101+
inputs: this.pins.inputs || [],
102+
outputs: this.pins.outputs || []
103+
},
104+
defaultData: this.defaultData,
105+
theme: this.theme,
106+
icon: this.icon,
107+
graphType: this.graphType,
108+
isActionNode: this.isActionNode()
109+
};
110+
}
111+
112+
/**
113+
* Получить пин по ID
114+
* @param {string} pinId - ID пина
115+
* @param {string} direction - 'input' или 'output'
116+
*/
117+
getPin(pinId, direction) {
118+
const pins = direction === 'input' ? this.pins.inputs : this.pins.outputs;
119+
return pins.find(pin => pin.id === pinId);
120+
}
121+
122+
/**
123+
* Проверить, является ли пин обязательным
124+
*/
125+
isPinRequired(pinId, direction = 'input') {
126+
const pin = this.getPin(pinId, direction);
127+
return pin?.required === true;
128+
}
129+
}
130+
131+
/**
132+
* Создать определение ноды с билдером
133+
*/
134+
function createNodeDefinition(config) {
135+
return new NodeDefinition(config);
136+
}
137+
138+
module.exports = { NodeDefinition, createNodeDefinition };

0 commit comments

Comments
 (0)