blockmine 1.25.0 → 1.27.0

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +46 -3
  2. package/backend/cli.js +1 -1
  3. package/backend/package.json +2 -2
  4. package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
  5. package/backend/prisma/migrations/migration_lock.toml +2 -2
  6. package/backend/prisma/schema.prisma +2 -0
  7. package/backend/src/api/routes/apiKeys.js +8 -0
  8. package/backend/src/api/routes/bots.js +258 -9
  9. package/backend/src/api/routes/eventGraphs.js +151 -1
  10. package/backend/src/api/routes/health.js +38 -0
  11. package/backend/src/api/routes/nodeRegistry.js +63 -0
  12. package/backend/src/api/routes/plugins.js +254 -29
  13. package/backend/src/container.js +11 -8
  14. package/backend/src/core/BotCommandLoader.js +161 -0
  15. package/backend/src/core/BotConnection.js +125 -0
  16. package/backend/src/core/BotEventHandlers.js +234 -0
  17. package/backend/src/core/BotIPCHandler.js +445 -0
  18. package/backend/src/core/BotManager.js +15 -7
  19. package/backend/src/core/BotProcess.js +75 -142
  20. package/backend/src/core/EventGraphManager.js +7 -3
  21. package/backend/src/core/GraphDebugHandler.js +229 -0
  22. package/backend/src/core/GraphDebugIPC.js +117 -0
  23. package/backend/src/core/GraphExecutionEngine.js +545 -978
  24. package/backend/src/core/GraphTraversal.js +80 -0
  25. package/backend/src/core/GraphValidation.js +73 -0
  26. package/backend/src/core/NodeDefinition.js +138 -0
  27. package/backend/src/core/NodeRegistry.js +153 -141
  28. package/backend/src/core/PluginManager.js +272 -31
  29. package/backend/src/core/RewindSignal.js +9 -0
  30. package/backend/src/core/config/ConfigValidator.js +72 -0
  31. package/backend/src/core/config/FeatureFlags.js +52 -0
  32. package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
  33. package/backend/src/core/domain/entities/Bot.js +39 -0
  34. package/backend/src/core/domain/entities/Command.js +41 -0
  35. package/backend/src/core/domain/entities/EventGraph.js +39 -0
  36. package/backend/src/core/domain/entities/Plugin.js +45 -0
  37. package/backend/src/core/domain/entities/User.js +40 -0
  38. package/backend/src/core/domain/services/DependencyResolver.js +168 -0
  39. package/backend/src/core/domain/services/GraphValidator.js +117 -0
  40. package/backend/src/core/domain/services/PermissionChecker.js +34 -0
  41. package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
  42. package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
  43. package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
  44. package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
  45. package/backend/src/core/errors/BaseError.js +29 -0
  46. package/backend/src/core/errors/ErrorHandler.js +81 -0
  47. package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
  48. package/backend/src/core/errors/index.js +68 -0
  49. package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
  50. package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
  51. package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
  52. package/backend/src/core/infrastructure/RateLimiter.js +64 -0
  53. package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
  54. package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
  55. package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
  56. package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
  57. package/backend/src/core/ipc/botApiFactory.js +72 -0
  58. package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
  59. package/backend/src/core/logging/AuditLogger.js +61 -0
  60. package/backend/src/core/logging/StructuredLogger.js +80 -0
  61. package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
  62. package/backend/src/core/logging/index.js +7 -0
  63. package/backend/src/core/metrics/MetricsCollector.js +104 -0
  64. package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
  65. package/backend/src/core/node-registries/actionsNodes.js +191 -0
  66. package/backend/src/core/node-registries/arraysNodes.js +152 -0
  67. package/backend/src/core/node-registries/botNodes.js +48 -0
  68. package/backend/src/core/node-registries/containerNodes.js +141 -0
  69. package/backend/src/core/node-registries/dataNodes.js +284 -0
  70. package/backend/src/core/node-registries/debugNodes.js +23 -0
  71. package/backend/src/core/node-registries/eventsNodes.js +223 -0
  72. package/backend/src/core/node-registries/flowNodes.js +151 -0
  73. package/backend/src/core/node-registries/furnaceNodes.js +123 -0
  74. package/backend/src/core/node-registries/index.js +108 -0
  75. package/backend/src/core/node-registries/inventory.js +102 -106
  76. package/backend/src/core/node-registries/logicNodes.js +54 -0
  77. package/backend/src/core/node-registries/mathNodes.js +38 -0
  78. package/backend/src/core/node-registries/navigationNodes.js +109 -0
  79. package/backend/src/core/node-registries/objectsNodes.js +90 -0
  80. package/backend/src/core/node-registries/stringsNodes.js +165 -0
  81. package/backend/src/core/node-registries/timeNodes.js +105 -0
  82. package/backend/src/core/node-registries/typeNodes.js +22 -0
  83. package/backend/src/core/node-registries/usersNodes.js +126 -0
  84. package/backend/src/core/nodes/arrays/shuffle.js +14 -0
  85. package/backend/src/core/nodes/bot/get_name.js +8 -0
  86. package/backend/src/core/nodes/bot/stop_bot.js +5 -0
  87. package/backend/src/core/nodes/container/open.js +101 -111
  88. package/backend/src/core/nodes/data/store_read.js +26 -0
  89. package/backend/src/core/nodes/data/store_write.js +23 -0
  90. package/backend/src/core/nodes/event/call_event.js +31 -0
  91. package/backend/src/core/nodes/event/custom_event.js +8 -0
  92. package/backend/src/core/nodes/flow/timer.js +35 -0
  93. package/backend/src/core/nodes/inventory/drop.js +73 -65
  94. package/backend/src/core/nodes/inventory/equip.js +54 -45
  95. package/backend/src/core/nodes/inventory/select_slot.js +48 -46
  96. package/backend/src/core/nodes/navigation/follow.js +54 -51
  97. package/backend/src/core/nodes/navigation/go_to.js +41 -53
  98. package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
  99. package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
  100. package/backend/src/core/nodes/navigation/stop.js +17 -26
  101. package/backend/src/core/nodes/users/add_to_group.js +24 -0
  102. package/backend/src/core/nodes/users/check_permission.js +26 -0
  103. package/backend/src/core/nodes/users/remove_from_group.js +24 -0
  104. package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
  105. package/backend/src/core/services/BotLifecycleService.js +41 -632
  106. package/backend/src/core/services/CacheManager.js +83 -23
  107. package/backend/src/core/services/CrashRestartManager.js +42 -0
  108. package/backend/src/core/services/DebugSessionManager.js +114 -12
  109. package/backend/src/core/services/EventGraphService.js +69 -0
  110. package/backend/src/core/services/MinecraftBotManager.js +9 -1
  111. package/backend/src/core/services/PluginManagementService.js +84 -0
  112. package/backend/src/core/services/TestModeContext.js +65 -0
  113. package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
  114. package/backend/src/core/services.js +1 -11
  115. package/backend/src/core/validation/InputValidator.js +167 -0
  116. package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
  117. package/backend/src/real-time/botApi/index.js +1 -1
  118. package/backend/src/real-time/socketHandler.js +26 -0
  119. package/backend/src/server.js +10 -5
  120. package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
  121. package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
  122. package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
  123. package/frontend/dist/index.html +2 -2
  124. package/frontend/dist/locales/en/bots.json +4 -1
  125. package/frontend/dist/locales/en/common.json +7 -1
  126. package/frontend/dist/locales/en/login.json +2 -0
  127. package/frontend/dist/locales/en/management.json +79 -1
  128. package/frontend/dist/locales/en/nodes.json +59 -4
  129. package/frontend/dist/locales/en/plugin-detail.json +24 -4
  130. package/frontend/dist/locales/en/plugins.json +226 -7
  131. package/frontend/dist/locales/en/setup.json +2 -0
  132. package/frontend/dist/locales/en/sidebar.json +171 -3
  133. package/frontend/dist/locales/en/visual-editor.json +230 -31
  134. package/frontend/dist/locales/ru/bots.json +4 -1
  135. package/frontend/dist/locales/ru/login.json +2 -0
  136. package/frontend/dist/locales/ru/management.json +79 -1
  137. package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
  138. package/frontend/dist/locales/ru/nodes.json +105 -51
  139. package/frontend/dist/locales/ru/plugins.json +103 -4
  140. package/frontend/dist/locales/ru/setup.json +2 -0
  141. package/frontend/dist/locales/ru/sidebar.json +171 -3
  142. package/frontend/dist/locales/ru/visual-editor.json +232 -33
  143. package/frontend/package.json +2 -0
  144. package/nul +12 -0
  145. package/package.json +3 -3
  146. package/backend/package-lock.json +0 -6801
  147. package/backend/src/core/node-registries/actions.js +0 -202
  148. package/backend/src/core/node-registries/arrays.js +0 -155
  149. package/backend/src/core/node-registries/bot.js +0 -23
  150. package/backend/src/core/node-registries/container.js +0 -162
  151. package/backend/src/core/node-registries/data.js +0 -290
  152. package/backend/src/core/node-registries/debug.js +0 -26
  153. package/backend/src/core/node-registries/events.js +0 -201
  154. package/backend/src/core/node-registries/flow.js +0 -139
  155. package/backend/src/core/node-registries/furnace.js +0 -143
  156. package/backend/src/core/node-registries/logic.js +0 -62
  157. package/backend/src/core/node-registries/math.js +0 -42
  158. package/backend/src/core/node-registries/navigation.js +0 -111
  159. package/backend/src/core/node-registries/objects.js +0 -98
  160. package/backend/src/core/node-registries/strings.js +0 -187
  161. package/backend/src/core/node-registries/time.js +0 -113
  162. package/backend/src/core/node-registries/type.js +0 -25
  163. package/backend/src/core/node-registries/users.js +0 -79
  164. package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
@@ -0,0 +1,117 @@
1
+ const MALICIOUS_PATTERNS = [
2
+ /require\s*\(/,
3
+ /process\s*\./,
4
+ /eval\s*\(/,
5
+ /Function\s*\(/,
6
+ /__proto__/,
7
+ /constructor\s*\[/,
8
+ ];
9
+
10
+ class GraphValidator {
11
+ validate(graph) {
12
+ const errors = [];
13
+
14
+ if (!graph || typeof graph !== 'object') {
15
+ return { valid: false, errors: ['graph.errors.invalidType'] };
16
+ }
17
+
18
+ if (!Array.isArray(graph.nodes)) {
19
+ errors.push('graph.errors.missingNodes');
20
+ }
21
+
22
+ if (!Array.isArray(graph.connections)) {
23
+ errors.push('graph.errors.missingConnections');
24
+ }
25
+
26
+ if (errors.length > 0) {
27
+ return { valid: false, errors };
28
+ }
29
+
30
+ const nodeIds = new Set();
31
+ for (const node of graph.nodes) {
32
+ if (!node.id || typeof node.id !== 'string') {
33
+ errors.push('graph.errors.nodeInvalidId');
34
+ continue;
35
+ }
36
+ if (nodeIds.has(node.id)) {
37
+ errors.push('graph.errors.nodeDuplicateId');
38
+ }
39
+ nodeIds.add(node.id);
40
+
41
+ if (!node.type || typeof node.type !== 'string') {
42
+ errors.push('graph.errors.nodeInvalidType');
43
+ }
44
+
45
+ if (this._hasMaliciousContent(node)) {
46
+ errors.push('graph.errors.nodeMaliciousContent');
47
+ }
48
+ }
49
+
50
+ for (const conn of graph.connections) {
51
+ if (!conn.sourceNodeId || !conn.targetNodeId) {
52
+ errors.push('graph.errors.connectionInvalidNodes');
53
+ continue;
54
+ }
55
+ if (!nodeIds.has(conn.sourceNodeId)) {
56
+ errors.push('graph.errors.connectionSourceNotFound');
57
+ }
58
+ if (!nodeIds.has(conn.targetNodeId)) {
59
+ errors.push('graph.errors.connectionTargetNotFound');
60
+ }
61
+ }
62
+
63
+ const cycles = this._detectCycles(graph);
64
+ if (cycles.length > 0) {
65
+ errors.push('graph.errors.cycleDetected');
66
+ }
67
+
68
+ return { valid: errors.length === 0, errors };
69
+ }
70
+
71
+ _hasMaliciousContent(node) {
72
+ const str = JSON.stringify(node.data || {});
73
+ return MALICIOUS_PATTERNS.some(p => p.test(str));
74
+ }
75
+
76
+ _detectCycles(graph) {
77
+ const execConnections = graph.connections.filter(c => {
78
+ return true;
79
+ });
80
+
81
+ const adj = new Map();
82
+ for (const node of graph.nodes) {
83
+ adj.set(node.id, []);
84
+ }
85
+ for (const conn of execConnections) {
86
+ if (adj.has(conn.sourceNodeId)) {
87
+ adj.get(conn.sourceNodeId).push(conn.targetNodeId);
88
+ }
89
+ }
90
+
91
+ const visited = new Set();
92
+ const visiting = new Set();
93
+ const cycles = [];
94
+
95
+ const visit = (id) => {
96
+ if (visiting.has(id)) {
97
+ cycles.push(id);
98
+ return;
99
+ }
100
+ if (visited.has(id)) return;
101
+ visiting.add(id);
102
+ for (const next of (adj.get(id) || [])) {
103
+ visit(next);
104
+ }
105
+ visiting.delete(id);
106
+ visited.add(id);
107
+ };
108
+
109
+ for (const node of graph.nodes) {
110
+ visit(node.id);
111
+ }
112
+
113
+ return cycles;
114
+ }
115
+ }
116
+
117
+ module.exports = GraphValidator;
@@ -0,0 +1,34 @@
1
+ class PermissionChecker {
2
+ check(user, permissionName) {
3
+ if (!user || !permissionName) return false;
4
+
5
+ const groups = user.groups || [];
6
+ for (const group of groups) {
7
+ const perms = group.permissions || [];
8
+ for (const perm of perms) {
9
+ const name = typeof perm === 'string' ? perm : perm.name;
10
+ if (this._matches(name, permissionName)) return true;
11
+ }
12
+ }
13
+ return false;
14
+ }
15
+
16
+ checkAny(user, permissionNames) {
17
+ return permissionNames.some(p => this.check(user, p));
18
+ }
19
+
20
+ checkAll(user, permissionNames) {
21
+ return permissionNames.every(p => this.check(user, p));
22
+ }
23
+
24
+ _matches(pattern, permission) {
25
+ if (pattern === permission) return true;
26
+ if (pattern.endsWith('.*')) {
27
+ const prefix = pattern.slice(0, -2);
28
+ return permission === prefix || permission.startsWith(prefix + '.');
29
+ }
30
+ return false;
31
+ }
32
+ }
33
+
34
+ module.exports = PermissionChecker;
@@ -0,0 +1,126 @@
1
+ const DependencyResolver = require('../DependencyResolver');
2
+
3
+ function makePlugin(name, version, deps = {}, optionalDeps = {}) {
4
+ return {
5
+ id: name,
6
+ name,
7
+ version,
8
+ manifest: JSON.stringify({ dependencies: deps, optionalDependencies: optionalDeps }),
9
+ };
10
+ }
11
+
12
+ describe('DependencyResolver', () => {
13
+ let resolver;
14
+
15
+ beforeEach(() => {
16
+ resolver = new DependencyResolver();
17
+ });
18
+
19
+ describe('resolve', () => {
20
+ it('returns empty sorted list for no plugins', () => {
21
+ const result = resolver.resolve([], []);
22
+ expect(result.sortedPlugins).toEqual([]);
23
+ expect(result.hasCriticalIssues).toBe(false);
24
+ });
25
+
26
+ it('resolves plugins with no dependencies', () => {
27
+ const plugins = [makePlugin('a', '1.0.0'), makePlugin('b', '2.0.0')];
28
+ const result = resolver.resolve(plugins, plugins);
29
+ expect(result.sortedPlugins).toHaveLength(2);
30
+ expect(result.hasCriticalIssues).toBe(false);
31
+ });
32
+
33
+ it('sorts plugins in dependency order', () => {
34
+ const a = makePlugin('a', '1.0.0');
35
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
36
+ const result = resolver.resolve([a, b], [a, b]);
37
+ const names = result.sortedPlugins.map(p => p.name);
38
+ expect(names.indexOf('a')).toBeLessThan(names.indexOf('b'));
39
+ });
40
+
41
+ it('detects missing dependency', () => {
42
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
43
+ const result = resolver.resolve([b], [b]);
44
+ expect(result.hasCriticalIssues).toBe(true);
45
+ expect(result.pluginInfo['b'].issues[0].type).toBe('missing_dependency');
46
+ });
47
+
48
+ it('detects disabled dependency', () => {
49
+ const a = makePlugin('a', '1.0.0');
50
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
51
+ const result = resolver.resolve([b], [a, b]);
52
+ expect(result.pluginInfo['b'].issues[0].type).toBe('disabled_dependency');
53
+ });
54
+
55
+ it('detects version mismatch', () => {
56
+ const a = makePlugin('a', '0.5.0');
57
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
58
+ const result = resolver.resolve([a, b], [a, b]);
59
+ expect(result.hasCriticalIssues).toBe(true);
60
+ expect(result.pluginInfo['b'].issues[0].type).toBe('version_mismatch');
61
+ });
62
+
63
+ it('detects circular dependency', () => {
64
+ const a = makePlugin('a', '1.0.0', { b: '^1.0.0' });
65
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
66
+ const result = resolver.resolve([a, b], [a, b]);
67
+ expect(result.hasCriticalIssues).toBe(true);
68
+ const hasCircular = Object.values(result.pluginInfo).some(info =>
69
+ info.issues.some(i => i.type === 'circular_dependency')
70
+ );
71
+ expect(hasCircular).toBe(true);
72
+ });
73
+
74
+ it('does not flag missing optional dependency as critical', () => {
75
+ const b = makePlugin('b', '1.0.0', {}, { a: '^1.0.0' });
76
+ const result = resolver.resolve([b], [b]);
77
+ expect(result.hasCriticalIssues).toBe(false);
78
+ });
79
+
80
+ it('satisfies semver ranges ^, ~, >=', () => {
81
+ const a1 = makePlugin('a', '1.2.3');
82
+ const b1 = makePlugin('b', '1.0.0', { a: '^1.0.0' });
83
+ expect(resolver.resolve([a1, b1], [a1, b1]).hasCriticalIssues).toBe(false);
84
+
85
+ const a2 = makePlugin('a', '1.2.3');
86
+ const b2 = makePlugin('b', '1.0.0', { a: '~1.2.0' });
87
+ expect(resolver.resolve([a2, b2], [a2, b2]).hasCriticalIssues).toBe(false);
88
+
89
+ const a3 = makePlugin('a', '2.0.0');
90
+ const b3 = makePlugin('b', '1.0.0', { a: '>=1.0.0' });
91
+ expect(resolver.resolve([a3, b3], [a3, b3]).hasCriticalIssues).toBe(false);
92
+ });
93
+ });
94
+
95
+ describe('checkCompatibility', () => {
96
+ it('returns compatible when all deps satisfied', () => {
97
+ const a = makePlugin('a', '1.0.0');
98
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
99
+ const result = resolver.checkCompatibility(b, [a]);
100
+ expect(result.compatible).toBe(true);
101
+ expect(result.issues).toHaveLength(0);
102
+ });
103
+
104
+ it('returns incompatible when dep missing', () => {
105
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
106
+ const result = resolver.checkCompatibility(b, []);
107
+ expect(result.compatible).toBe(false);
108
+ expect(result.issues[0].type).toBe('missing_dependency');
109
+ });
110
+
111
+ it('returns incompatible when version mismatch', () => {
112
+ const a = makePlugin('a', '0.5.0');
113
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
114
+ const result = resolver.checkCompatibility(b, [a]);
115
+ expect(result.compatible).toBe(false);
116
+ expect(result.issues[0].type).toBe('version_mismatch');
117
+ });
118
+
119
+ it('includes suggestion in version mismatch issue', () => {
120
+ const a = makePlugin('a', '0.5.0');
121
+ const b = makePlugin('b', '1.0.0', { a: '^1.0.0' });
122
+ const result = resolver.checkCompatibility(b, [a]);
123
+ expect(result.issues[0].context.suggestion).toBeDefined();
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,27 @@
1
+ class BotConfig {
2
+ constructor(data) {
3
+ if (!data || typeof data !== 'object') {
4
+ throw new TypeError('domain.botConfig.invalidData');
5
+ }
6
+ this._data = Object.freeze({ ...data });
7
+ }
8
+
9
+ get id() { return this._data.id; }
10
+ get username() { return this._data.username; }
11
+ get host() { return this._data.host; }
12
+ get port() { return this._data.port; }
13
+ get version() { return this._data.version; }
14
+ get prefix() { return this._data.prefix || '@'; }
15
+ get owners() { return this._data.owners || ''; }
16
+ get plugins() { return this._data.plugins || []; }
17
+
18
+ with(overrides) {
19
+ return new BotConfig({ ...this._data, ...overrides });
20
+ }
21
+
22
+ toJSON() {
23
+ return { ...this._data };
24
+ }
25
+ }
26
+
27
+ module.exports = BotConfig;
@@ -0,0 +1,86 @@
1
+ class DependencyGraph {
2
+ constructor() {
3
+ this._nodes = new Map();
4
+ this._edges = new Map();
5
+ }
6
+
7
+ addNode(name, version) {
8
+ this._nodes.set(name, version);
9
+ if (!this._edges.has(name)) {
10
+ this._edges.set(name, []);
11
+ }
12
+ }
13
+
14
+ addEdge(from, to, versionRange) {
15
+ if (!this._edges.has(from)) {
16
+ this._edges.set(from, []);
17
+ }
18
+ this._edges.get(from).push({ to, versionRange });
19
+ }
20
+
21
+ getNodes() {
22
+ return new Map(this._nodes);
23
+ }
24
+
25
+ getDependencies(name) {
26
+ return this._edges.get(name) || [];
27
+ }
28
+
29
+ hasNode(name) {
30
+ return this._nodes.has(name);
31
+ }
32
+
33
+ getVersion(name) {
34
+ return this._nodes.get(name) || null;
35
+ }
36
+
37
+ detectCycles() {
38
+ const visited = new Set();
39
+ const visiting = new Set();
40
+ const cycles = [];
41
+
42
+ const visit = (name, path) => {
43
+ if (visiting.has(name)) {
44
+ const cycleStart = path.indexOf(name);
45
+ cycles.push(path.slice(cycleStart).concat(name));
46
+ return;
47
+ }
48
+ if (visited.has(name)) return;
49
+
50
+ visiting.add(name);
51
+ for (const { to } of this.getDependencies(name)) {
52
+ visit(to, [...path, name]);
53
+ }
54
+ visiting.delete(name);
55
+ visited.add(name);
56
+ };
57
+
58
+ for (const name of this._nodes.keys()) {
59
+ visit(name, []);
60
+ }
61
+
62
+ return cycles;
63
+ }
64
+
65
+ topologicalSort() {
66
+ const visited = new Set();
67
+ const result = [];
68
+
69
+ const visit = (name) => {
70
+ if (visited.has(name)) return;
71
+ visited.add(name);
72
+ for (const { to } of this.getDependencies(name)) {
73
+ if (this._nodes.has(to)) visit(to);
74
+ }
75
+ result.push(name);
76
+ };
77
+
78
+ for (const name of this._nodes.keys()) {
79
+ visit(name);
80
+ }
81
+
82
+ return result;
83
+ }
84
+ }
85
+
86
+ module.exports = DependencyGraph;
@@ -0,0 +1,36 @@
1
+ class PluginManifest {
2
+ constructor(data) {
3
+ if (!data || typeof data !== 'object') {
4
+ throw new TypeError('domain.pluginManifest.invalidData');
5
+ }
6
+ this._data = Object.freeze({
7
+ name: data.name || '',
8
+ version: data.version || '0.0.0',
9
+ main: data.main || 'index.js',
10
+ description: data.description || '',
11
+ dependencies: Object.freeze(data.dependencies || {}),
12
+ settings: Object.freeze(data.settings || {}),
13
+ });
14
+ }
15
+
16
+ get name() { return this._data.name; }
17
+ get version() { return this._data.version; }
18
+ get main() { return this._data.main; }
19
+ get description() { return this._data.description; }
20
+ get dependencies() { return this._data.dependencies; }
21
+ get settings() { return this._data.settings; }
22
+
23
+ hasDependencies() {
24
+ return Object.keys(this._data.dependencies).length > 0;
25
+ }
26
+
27
+ getDependencyNames() {
28
+ return Object.keys(this._data.dependencies);
29
+ }
30
+
31
+ toJSON() {
32
+ return { ...this._data };
33
+ }
34
+ }
35
+
36
+ module.exports = PluginManifest;
@@ -0,0 +1,29 @@
1
+ class BaseError extends Error {
2
+ constructor(messageKey, { code, statusCode, context = {}, isOperational = true, cause } = {}) {
3
+ super(messageKey);
4
+ this.name = this.constructor.name;
5
+ this.messageKey = messageKey;
6
+ this.code = code;
7
+ this.statusCode = statusCode;
8
+ this.context = context;
9
+ this.isOperational = isOperational;
10
+ if (cause) this.cause = cause;
11
+ if (Error.captureStackTrace) {
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+
16
+ toJSON() {
17
+ return {
18
+ name: this.name,
19
+ code: this.code,
20
+ messageKey: this.messageKey,
21
+ statusCode: this.statusCode,
22
+ context: this.context,
23
+ isOperational: this.isOperational,
24
+ stack: this.stack,
25
+ };
26
+ }
27
+ }
28
+
29
+ module.exports = BaseError;
@@ -0,0 +1,81 @@
1
+ const { EventEmitter } = require('events');
2
+ const { BaseError } = require('./index');
3
+
4
+ const SENSITIVE_PATTERNS = [
5
+ /password/i,
6
+ /token/i,
7
+ /secret/i,
8
+ /\bkey\b/i,
9
+ /authorization/i,
10
+ /cookie/i,
11
+ /apiKey/i,
12
+ /api_key/i,
13
+ ];
14
+
15
+ class ErrorHandler extends EventEmitter {
16
+ constructor({ logger }) {
17
+ super();
18
+ this.logger = logger;
19
+ }
20
+
21
+ handle(error, context = {}) {
22
+ const sanitizedContext = ErrorHandler.sanitize(context);
23
+ const isOperational = ErrorHandler.isOperational(error);
24
+
25
+ const logEntry = {
26
+ botId: sanitizedContext.botId,
27
+ userId: sanitizedContext.userId,
28
+ requestId: sanitizedContext.requestId,
29
+ timestamp: new Date().toISOString(),
30
+ code: error.code,
31
+ statusCode: error.statusCode,
32
+ messageKey: error.messageKey || error.message,
33
+ isOperational,
34
+ stack: error.stack,
35
+ context: sanitizedContext,
36
+ };
37
+
38
+ if (isOperational) {
39
+ this.logger.warn(logEntry, 'errors.operational');
40
+ } else {
41
+ this.logger.error(logEntry, 'errors.unexpected');
42
+ }
43
+
44
+ this.emit('error:handled', { error, context: sanitizedContext, isOperational });
45
+
46
+ return {
47
+ success: false,
48
+ error: {
49
+ code: error.code || 'INTERNAL_ERROR',
50
+ messageKey: error.messageKey || 'errors.internal',
51
+ statusCode: error.statusCode || 500,
52
+ isOperational,
53
+ },
54
+ };
55
+ }
56
+
57
+ static isOperational(error) {
58
+ return error instanceof BaseError && error.isOperational === true;
59
+ }
60
+
61
+ static sanitize(data) {
62
+ if (data === null || data === undefined) return data;
63
+
64
+ if (typeof data !== 'object') return data;
65
+
66
+ if (Array.isArray(data)) {
67
+ return data.map(ErrorHandler.sanitize);
68
+ }
69
+
70
+ return Object.fromEntries(
71
+ Object.entries(data).map(([key, value]) => {
72
+ const isSensitive = SENSITIVE_PATTERNS.some((pattern) => pattern.test(key));
73
+ if (isSensitive) return [key, '[REDACTED]'];
74
+ if (value && typeof value === 'object') return [key, ErrorHandler.sanitize(value)];
75
+ return [key, value];
76
+ })
77
+ );
78
+ }
79
+ }
80
+
81
+ module.exports = ErrorHandler;