blockmine 1.25.0 → 1.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -1
- package/backend/cli.js +1 -1
- package/backend/package.json +2 -2
- package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +2 -0
- package/backend/src/api/routes/apiKeys.js +8 -0
- package/backend/src/api/routes/bots.js +258 -9
- package/backend/src/api/routes/eventGraphs.js +151 -1
- package/backend/src/api/routes/health.js +38 -0
- package/backend/src/api/routes/nodeRegistry.js +63 -0
- package/backend/src/api/routes/plugins.js +254 -29
- package/backend/src/container.js +11 -8
- package/backend/src/core/BotCommandLoader.js +161 -0
- package/backend/src/core/BotConnection.js +125 -0
- package/backend/src/core/BotEventHandlers.js +234 -0
- package/backend/src/core/BotIPCHandler.js +445 -0
- package/backend/src/core/BotManager.js +15 -7
- package/backend/src/core/BotProcess.js +75 -142
- package/backend/src/core/EventGraphManager.js +7 -3
- package/backend/src/core/GraphDebugHandler.js +229 -0
- package/backend/src/core/GraphDebugIPC.js +117 -0
- package/backend/src/core/GraphExecutionEngine.js +545 -978
- package/backend/src/core/GraphTraversal.js +80 -0
- package/backend/src/core/GraphValidation.js +73 -0
- package/backend/src/core/NodeDefinition.js +138 -0
- package/backend/src/core/NodeRegistry.js +153 -141
- package/backend/src/core/PluginManager.js +272 -31
- package/backend/src/core/RewindSignal.js +9 -0
- package/backend/src/core/config/ConfigValidator.js +72 -0
- package/backend/src/core/config/FeatureFlags.js +52 -0
- package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
- package/backend/src/core/domain/entities/Bot.js +39 -0
- package/backend/src/core/domain/entities/Command.js +41 -0
- package/backend/src/core/domain/entities/EventGraph.js +39 -0
- package/backend/src/core/domain/entities/Plugin.js +45 -0
- package/backend/src/core/domain/entities/User.js +40 -0
- package/backend/src/core/domain/services/DependencyResolver.js +168 -0
- package/backend/src/core/domain/services/GraphValidator.js +117 -0
- package/backend/src/core/domain/services/PermissionChecker.js +34 -0
- package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
- package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
- package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
- package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
- package/backend/src/core/errors/BaseError.js +29 -0
- package/backend/src/core/errors/ErrorHandler.js +81 -0
- package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
- package/backend/src/core/errors/index.js +68 -0
- package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
- package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
- package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
- package/backend/src/core/infrastructure/RateLimiter.js +64 -0
- package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
- package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
- package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
- package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
- package/backend/src/core/ipc/botApiFactory.js +72 -0
- package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
- package/backend/src/core/logging/AuditLogger.js +61 -0
- package/backend/src/core/logging/StructuredLogger.js +80 -0
- package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
- package/backend/src/core/logging/index.js +7 -0
- package/backend/src/core/metrics/MetricsCollector.js +104 -0
- package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
- package/backend/src/core/node-registries/actionsNodes.js +191 -0
- package/backend/src/core/node-registries/arraysNodes.js +152 -0
- package/backend/src/core/node-registries/botNodes.js +48 -0
- package/backend/src/core/node-registries/containerNodes.js +141 -0
- package/backend/src/core/node-registries/dataNodes.js +284 -0
- package/backend/src/core/node-registries/debugNodes.js +23 -0
- package/backend/src/core/node-registries/eventsNodes.js +223 -0
- package/backend/src/core/node-registries/flowNodes.js +151 -0
- package/backend/src/core/node-registries/furnaceNodes.js +123 -0
- package/backend/src/core/node-registries/index.js +108 -0
- package/backend/src/core/node-registries/inventory.js +102 -106
- package/backend/src/core/node-registries/logicNodes.js +54 -0
- package/backend/src/core/node-registries/mathNodes.js +38 -0
- package/backend/src/core/node-registries/navigationNodes.js +109 -0
- package/backend/src/core/node-registries/objectsNodes.js +90 -0
- package/backend/src/core/node-registries/stringsNodes.js +165 -0
- package/backend/src/core/node-registries/timeNodes.js +105 -0
- package/backend/src/core/node-registries/typeNodes.js +22 -0
- package/backend/src/core/node-registries/usersNodes.js +126 -0
- package/backend/src/core/nodes/arrays/shuffle.js +14 -0
- package/backend/src/core/nodes/bot/get_name.js +8 -0
- package/backend/src/core/nodes/bot/stop_bot.js +5 -0
- package/backend/src/core/nodes/container/open.js +101 -111
- package/backend/src/core/nodes/data/store_read.js +26 -0
- package/backend/src/core/nodes/data/store_write.js +23 -0
- package/backend/src/core/nodes/event/call_event.js +31 -0
- package/backend/src/core/nodes/event/custom_event.js +8 -0
- package/backend/src/core/nodes/flow/timer.js +35 -0
- package/backend/src/core/nodes/inventory/drop.js +73 -65
- package/backend/src/core/nodes/inventory/equip.js +54 -45
- package/backend/src/core/nodes/inventory/select_slot.js +48 -46
- package/backend/src/core/nodes/navigation/follow.js +54 -51
- package/backend/src/core/nodes/navigation/go_to.js +41 -53
- package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
- package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
- package/backend/src/core/nodes/navigation/stop.js +17 -26
- package/backend/src/core/nodes/users/add_to_group.js +24 -0
- package/backend/src/core/nodes/users/check_permission.js +26 -0
- package/backend/src/core/nodes/users/remove_from_group.js +24 -0
- package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
- package/backend/src/core/services/BotLifecycleService.js +41 -632
- package/backend/src/core/services/CacheManager.js +83 -23
- package/backend/src/core/services/CrashRestartManager.js +42 -0
- package/backend/src/core/services/DebugSessionManager.js +114 -12
- package/backend/src/core/services/EventGraphService.js +69 -0
- package/backend/src/core/services/MinecraftBotManager.js +9 -1
- package/backend/src/core/services/PluginManagementService.js +84 -0
- package/backend/src/core/services/TestModeContext.js +65 -0
- package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
- package/backend/src/core/services.js +1 -11
- package/backend/src/core/validation/InputValidator.js +167 -0
- package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
- package/backend/src/real-time/botApi/index.js +1 -1
- package/backend/src/real-time/socketHandler.js +26 -0
- package/backend/src/server.js +10 -5
- package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
- package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
- package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/locales/en/bots.json +4 -1
- package/frontend/dist/locales/en/common.json +7 -1
- package/frontend/dist/locales/en/login.json +2 -0
- package/frontend/dist/locales/en/management.json +79 -1
- package/frontend/dist/locales/en/nodes.json +59 -4
- package/frontend/dist/locales/en/plugin-detail.json +24 -4
- package/frontend/dist/locales/en/plugins.json +226 -7
- package/frontend/dist/locales/en/setup.json +2 -0
- package/frontend/dist/locales/en/sidebar.json +171 -3
- package/frontend/dist/locales/en/visual-editor.json +230 -31
- package/frontend/dist/locales/ru/bots.json +4 -1
- package/frontend/dist/locales/ru/login.json +2 -0
- package/frontend/dist/locales/ru/management.json +79 -1
- package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
- package/frontend/dist/locales/ru/nodes.json +105 -51
- package/frontend/dist/locales/ru/plugins.json +103 -4
- package/frontend/dist/locales/ru/setup.json +2 -0
- package/frontend/dist/locales/ru/sidebar.json +171 -3
- package/frontend/dist/locales/ru/visual-editor.json +232 -33
- package/frontend/package.json +2 -0
- package/nul +12 -0
- package/package.json +3 -3
- package/scripts/postinstall.js +38 -0
- package/backend/package-lock.json +0 -6801
- package/backend/src/core/node-registries/actions.js +0 -202
- package/backend/src/core/node-registries/arrays.js +0 -155
- package/backend/src/core/node-registries/bot.js +0 -23
- package/backend/src/core/node-registries/container.js +0 -162
- package/backend/src/core/node-registries/data.js +0 -290
- package/backend/src/core/node-registries/debug.js +0 -26
- package/backend/src/core/node-registries/events.js +0 -201
- package/backend/src/core/node-registries/flow.js +0 -139
- package/backend/src/core/node-registries/furnace.js +0 -143
- package/backend/src/core/node-registries/logic.js +0 -62
- package/backend/src/core/node-registries/math.js +0 -42
- package/backend/src/core/node-registries/navigation.js +0 -111
- package/backend/src/core/node-registries/objects.js +0 -98
- package/backend/src/core/node-registries/strings.js +0 -187
- package/backend/src/core/node-registries/time.js +0 -113
- package/backend/src/core/node-registries/type.js +0 -25
- package/backend/src/core/node-registries/users.js +0 -79
- package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
class GraphTraversal {
|
|
2
|
+
constructor(activeGraph, memo) {
|
|
3
|
+
this.activeGraph = activeGraph;
|
|
4
|
+
this.memo = memo;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
findConnection(nodeId, fromPinId) {
|
|
8
|
+
if (!this.activeGraph || !this.activeGraph.connections) return null;
|
|
9
|
+
|
|
10
|
+
return this.activeGraph.connections.find(c => {
|
|
11
|
+
if (c.sourceNodeId !== nodeId || c.sourcePinId !== fromPinId) return false;
|
|
12
|
+
const targetExists = this.activeGraph.nodes.some(n => n.id === c.targetNodeId);
|
|
13
|
+
return targetExists;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
findNextNode(connection) {
|
|
18
|
+
if (!connection || !this.activeGraph) return null;
|
|
19
|
+
return this.activeGraph.nodes.find(n => n.id === connection.targetNodeId) || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clearLoopBodyMemo(loopNode) {
|
|
23
|
+
const nodesToClear = new Set();
|
|
24
|
+
const queue = [];
|
|
25
|
+
|
|
26
|
+
const initialConnection = this.activeGraph.connections.find(
|
|
27
|
+
c => c.sourceNodeId === loopNode.id && c.sourcePinId === 'loop_body'
|
|
28
|
+
);
|
|
29
|
+
if (initialConnection) {
|
|
30
|
+
const firstNode = this.activeGraph.nodes.find(n => n.id === initialConnection.targetNodeId);
|
|
31
|
+
if (firstNode) {
|
|
32
|
+
queue.push(firstNode);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const visited = new Set();
|
|
37
|
+
while (queue.length > 0) {
|
|
38
|
+
const currentNode = queue.shift();
|
|
39
|
+
if (visited.has(currentNode.id)) continue;
|
|
40
|
+
visited.add(currentNode.id);
|
|
41
|
+
|
|
42
|
+
nodesToClear.add(currentNode.id);
|
|
43
|
+
|
|
44
|
+
const connections = this.activeGraph.connections.filter(c => c.sourceNodeId === currentNode.id);
|
|
45
|
+
for (const conn of connections) {
|
|
46
|
+
const nextNode = this.activeGraph.nodes.find(n => n.id === conn.targetNodeId);
|
|
47
|
+
if (nextNode) {
|
|
48
|
+
queue.push(nextNode);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const nodeId of nodesToClear) {
|
|
54
|
+
for (const key of this.memo.keys()) {
|
|
55
|
+
if (key.startsWith(nodeId)) {
|
|
56
|
+
this.memo.delete(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getConnectionsFromNode(nodeId) {
|
|
63
|
+
if (!this.activeGraph || !this.activeGraph.connections) return [];
|
|
64
|
+
return this.activeGraph.connections.filter(c => c.sourceNodeId === nodeId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getConnectionsToNode(nodeId) {
|
|
68
|
+
if (!this.activeGraph || !this.activeGraph.connections) return [];
|
|
69
|
+
return this.activeGraph.connections.filter(c => c.targetNodeId === nodeId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hasConnection(nodeId, pinId) {
|
|
73
|
+
if (!this.activeGraph || !this.activeGraph.connections) return false;
|
|
74
|
+
return this.activeGraph.connections.some(conn =>
|
|
75
|
+
conn.targetNodeId === nodeId && conn.targetPinId === pinId
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = GraphTraversal;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const validationService = require('./services/ValidationService');
|
|
2
|
+
|
|
3
|
+
class GraphValidation {
|
|
4
|
+
static validateGraphForExecution(graph, source) {
|
|
5
|
+
if (!graph || graph === 'null') {
|
|
6
|
+
return { shouldSkip: true, error: null };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const parsedGraph = validationService.parseGraph(graph, source);
|
|
10
|
+
if (!parsedGraph) {
|
|
11
|
+
return { shouldSkip: true, error: 'Invalid graph' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const validation = validationService.validateGraphStructure(parsedGraph, source);
|
|
15
|
+
if (validation.shouldSkip) {
|
|
16
|
+
return { shouldSkip: true, error: validation.reason };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { shouldSkip: false, graph: parsedGraph };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static findStartNode(graph, eventType) {
|
|
23
|
+
if (!graph || !graph.nodes) return null;
|
|
24
|
+
|
|
25
|
+
const eventName = eventType || 'command';
|
|
26
|
+
return graph.nodes.find(n => n.type === `event:${eventName}`) || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static getNodeInputPins(nodeConfig, nodeData) {
|
|
30
|
+
if (!nodeConfig) return [];
|
|
31
|
+
|
|
32
|
+
let inputPins = [];
|
|
33
|
+
try {
|
|
34
|
+
if (typeof nodeConfig.getInputs === 'function') {
|
|
35
|
+
inputPins = nodeConfig.getInputs(nodeData || {}) || [];
|
|
36
|
+
} else if (typeof nodeConfig.computeInputs === 'function') {
|
|
37
|
+
inputPins = nodeConfig.computeInputs(nodeData || {}) || [];
|
|
38
|
+
} else if (nodeConfig.pins && Array.isArray(nodeConfig.pins.inputs)) {
|
|
39
|
+
inputPins = nodeConfig.pins.inputs;
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
inputPins = nodeConfig.pins?.inputs || [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return inputPins;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static getNodeOutputPins(nodeConfig, nodeData) {
|
|
49
|
+
if (!nodeConfig) return [];
|
|
50
|
+
|
|
51
|
+
let outputPins = [];
|
|
52
|
+
try {
|
|
53
|
+
if (typeof nodeConfig.getOutputs === 'function') {
|
|
54
|
+
outputPins = nodeConfig.getOutputs(nodeData || {}) || [];
|
|
55
|
+
} else if (typeof nodeConfig.computeOutputs === 'function') {
|
|
56
|
+
outputPins = nodeConfig.computeOutputs(nodeData || {}) || [];
|
|
57
|
+
} else if (nodeConfig.pins && Array.isArray(nodeConfig.pins.outputs)) {
|
|
58
|
+
outputPins = nodeConfig.pins.outputs;
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
outputPins = nodeConfig.pins?.outputs || [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return outputPins;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static isDataNode(nodeConfig) {
|
|
68
|
+
const inputPins = GraphValidation.getNodeInputPins(nodeConfig, {});
|
|
69
|
+
return !inputPins.some(p => p && p.type === 'Exec');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = GraphValidation;
|
|
@@ -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 };
|
|
@@ -1,141 +1,153 @@
|
|
|
1
|
-
const validationService = require('./services/ValidationService');
|
|
2
|
-
const { GRAPH_TYPES } = require('./constants/graphTypes');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
} else {
|
|
122
|
-
console.warn(`NodeRegistry: Файл ${file} не экспортирует
|
|
123
|
-
}
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error(`NodeRegistry: Ошибка загрузки реестра из ${file}:`, error.message);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
console.log(`NodeRegistry: Registered ${this.nodes.size} base nodes from ${files.length} registries`);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error('NodeRegistry: Ошибка чтения директории node-registries:', error.message);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
1
|
+
const validationService = require('./services/ValidationService');
|
|
2
|
+
const { GRAPH_TYPES } = require('./constants/graphTypes');
|
|
3
|
+
const { NodeDefinition } = require('./NodeDefinition');
|
|
4
|
+
|
|
5
|
+
class NodeRegistry {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.nodes = new Map();
|
|
8
|
+
this._registerBaseNodes();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
registerNodeType(config) {
|
|
12
|
+
if (!config.type) {
|
|
13
|
+
throw new Error('Node type is required');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const validation = validationService.validateNode(config, 'NodeRegistry');
|
|
17
|
+
if (validation.shouldSkip) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (this.nodes.has(config.type)) {
|
|
22
|
+
console.warn(`Node type '${config.type}' is already registered. Overriding.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let nodeDef;
|
|
26
|
+
|
|
27
|
+
if (config instanceof NodeDefinition) {
|
|
28
|
+
nodeDef = config;
|
|
29
|
+
} else {
|
|
30
|
+
nodeDef = this._normalizeToNodeDefinition(config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.nodes.set(config.type, nodeDef);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_normalizeToNodeDefinition(config) {
|
|
37
|
+
const inputs = config.computeInputs
|
|
38
|
+
? config.computeInputs
|
|
39
|
+
: (() => config.pins?.inputs || []);
|
|
40
|
+
|
|
41
|
+
const outputs = config.computeOutputs
|
|
42
|
+
? config.computeOutputs
|
|
43
|
+
: (() => config.pins?.outputs || []);
|
|
44
|
+
|
|
45
|
+
return new NodeDefinition({
|
|
46
|
+
type: config.type,
|
|
47
|
+
category: config.category || 'Other',
|
|
48
|
+
label: config.label || config.type,
|
|
49
|
+
description: config.description || '',
|
|
50
|
+
computeInputs: inputs,
|
|
51
|
+
computeOutputs: outputs,
|
|
52
|
+
pins: config.pins || { inputs: [], outputs: [] },
|
|
53
|
+
executor: config.executor || null,
|
|
54
|
+
evaluator: config.evaluator || null,
|
|
55
|
+
defaultData: config.defaultData || {},
|
|
56
|
+
theme: config.theme || {},
|
|
57
|
+
icon: config.icon || null,
|
|
58
|
+
graphType: config.graphType || GRAPH_TYPES.ALL
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getNodeConfig(nodeType) {
|
|
63
|
+
return this.nodes.get(nodeType);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getAllNodes() {
|
|
67
|
+
return Array.from(this.nodes.values());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getNodesByCategory(graphType) {
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const node of this.nodes.values()) {
|
|
73
|
+
if (node.graphType === GRAPH_TYPES.ALL || node.graphType === graphType) {
|
|
74
|
+
if (!result[node.category]) {
|
|
75
|
+
result[node.category] = [];
|
|
76
|
+
}
|
|
77
|
+
result[node.category].push(node);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hasNodeType(nodeType) {
|
|
84
|
+
return this.nodes.has(nodeType);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getNodesByTypes(types) {
|
|
88
|
+
return types.map(type => this.nodes.get(type)).filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_registerBaseNodes() {
|
|
92
|
+
const fs = require('fs');
|
|
93
|
+
const path = require('path');
|
|
94
|
+
|
|
95
|
+
const registriesDir = path.join(__dirname, 'node-registries');
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const files = fs.readdirSync(registriesDir)
|
|
99
|
+
.filter(file => file.endsWith('.js'));
|
|
100
|
+
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
try {
|
|
103
|
+
const registry = require(path.join(registriesDir, file));
|
|
104
|
+
|
|
105
|
+
if (typeof registry.registerNodes === 'function') {
|
|
106
|
+
registry.registerNodes(this);
|
|
107
|
+
} else if (Array.isArray(registry)) {
|
|
108
|
+
for (const def of registry) {
|
|
109
|
+
this.registerNodeType(def);
|
|
110
|
+
}
|
|
111
|
+
} else if (typeof registry.registerNodeDefinitions === 'function') {
|
|
112
|
+
const definitions = registry.registerNodeDefinitions();
|
|
113
|
+
for (const def of definitions) {
|
|
114
|
+
this.registerNodeType(def);
|
|
115
|
+
}
|
|
116
|
+
} else if (typeof registry.createNodeDefinitions === 'function' ||
|
|
117
|
+
typeof registry.getNodesByCategory === 'function' ||
|
|
118
|
+
typeof registry.getAllNodeTypes === 'function') {
|
|
119
|
+
// Это служебный модуль (index.js), не содержит нод напрямую
|
|
120
|
+
// Пропускаем без варнинга
|
|
121
|
+
} else {
|
|
122
|
+
console.warn(`NodeRegistry: Файл ${file} не экспортирует registerNodes, массив нод или registerNodeDefinitions`);
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(`NodeRegistry: Ошибка загрузки реестра из ${file}:`, error.message);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`NodeRegistry: Registered ${this.nodes.size} base nodes from ${files.length} registries`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('NodeRegistry: Ошибка чтения директории node-registries:', error.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toJSON() {
|
|
136
|
+
const result = {};
|
|
137
|
+
for (const [type, nodeDef] of this.nodes) {
|
|
138
|
+
result[type] = nodeDef.toJSON();
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getNodesByCategoryFlat() {
|
|
144
|
+
const flat = [];
|
|
145
|
+
for (const node of this.nodes.values()) {
|
|
146
|
+
flat.push(node.toJSON());
|
|
147
|
+
}
|
|
148
|
+
return flat;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const nodeRegistryInstance = new NodeRegistry();
|
|
153
|
+
module.exports = nodeRegistryInstance;
|