@zintrust/workers 0.1.27
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/README.md +861 -0
- package/dist/AnomalyDetection.d.ts +102 -0
- package/dist/AnomalyDetection.js +321 -0
- package/dist/AutoScaler.d.ts +127 -0
- package/dist/AutoScaler.js +425 -0
- package/dist/BroadcastWorker.d.ts +21 -0
- package/dist/BroadcastWorker.js +24 -0
- package/dist/CanaryController.d.ts +103 -0
- package/dist/CanaryController.js +380 -0
- package/dist/ChaosEngineering.d.ts +79 -0
- package/dist/ChaosEngineering.js +216 -0
- package/dist/CircuitBreaker.d.ts +106 -0
- package/dist/CircuitBreaker.js +374 -0
- package/dist/ClusterLock.d.ts +90 -0
- package/dist/ClusterLock.js +385 -0
- package/dist/ComplianceManager.d.ts +177 -0
- package/dist/ComplianceManager.js +556 -0
- package/dist/DatacenterOrchestrator.d.ts +133 -0
- package/dist/DatacenterOrchestrator.js +404 -0
- package/dist/DeadLetterQueue.d.ts +122 -0
- package/dist/DeadLetterQueue.js +539 -0
- package/dist/HealthMonitor.d.ts +42 -0
- package/dist/HealthMonitor.js +301 -0
- package/dist/MultiQueueWorker.d.ts +89 -0
- package/dist/MultiQueueWorker.js +277 -0
- package/dist/NotificationWorker.d.ts +21 -0
- package/dist/NotificationWorker.js +23 -0
- package/dist/Observability.d.ts +153 -0
- package/dist/Observability.js +530 -0
- package/dist/PluginManager.d.ts +123 -0
- package/dist/PluginManager.js +392 -0
- package/dist/PriorityQueue.d.ts +117 -0
- package/dist/PriorityQueue.js +244 -0
- package/dist/ResourceMonitor.d.ts +164 -0
- package/dist/ResourceMonitor.js +605 -0
- package/dist/SLAMonitor.d.ts +110 -0
- package/dist/SLAMonitor.js +274 -0
- package/dist/WorkerFactory.d.ts +193 -0
- package/dist/WorkerFactory.js +1507 -0
- package/dist/WorkerInit.d.ts +85 -0
- package/dist/WorkerInit.js +223 -0
- package/dist/WorkerMetrics.d.ts +114 -0
- package/dist/WorkerMetrics.js +509 -0
- package/dist/WorkerRegistry.d.ts +145 -0
- package/dist/WorkerRegistry.js +319 -0
- package/dist/WorkerShutdown.d.ts +61 -0
- package/dist/WorkerShutdown.js +159 -0
- package/dist/WorkerVersioning.d.ts +107 -0
- package/dist/WorkerVersioning.js +300 -0
- package/dist/build-manifest.json +462 -0
- package/dist/config/workerConfig.d.ts +3 -0
- package/dist/config/workerConfig.js +19 -0
- package/dist/createQueueWorker.d.ts +23 -0
- package/dist/createQueueWorker.js +113 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/types.d.ts +117 -0
- package/dist/dashboard/types.js +1 -0
- package/dist/dashboard/workers-api.d.ts +4 -0
- package/dist/dashboard/workers-api.js +638 -0
- package/dist/dashboard/workers-dashboard-ui.d.ts +3 -0
- package/dist/dashboard/workers-dashboard-ui.js +1026 -0
- package/dist/dashboard/workers-dashboard.d.ts +4 -0
- package/dist/dashboard/workers-dashboard.js +904 -0
- package/dist/helper/index.d.ts +5 -0
- package/dist/helper/index.js +10 -0
- package/dist/http/WorkerApiController.d.ts +38 -0
- package/dist/http/WorkerApiController.js +312 -0
- package/dist/http/WorkerController.d.ts +374 -0
- package/dist/http/WorkerController.js +1351 -0
- package/dist/http/middleware/CustomValidation.d.ts +92 -0
- package/dist/http/middleware/CustomValidation.js +270 -0
- package/dist/http/middleware/DatacenterValidator.d.ts +3 -0
- package/dist/http/middleware/DatacenterValidator.js +94 -0
- package/dist/http/middleware/EditWorkerValidation.d.ts +7 -0
- package/dist/http/middleware/EditWorkerValidation.js +55 -0
- package/dist/http/middleware/FeaturesValidator.d.ts +3 -0
- package/dist/http/middleware/FeaturesValidator.js +60 -0
- package/dist/http/middleware/InfrastructureValidator.d.ts +31 -0
- package/dist/http/middleware/InfrastructureValidator.js +226 -0
- package/dist/http/middleware/OptionsValidator.d.ts +3 -0
- package/dist/http/middleware/OptionsValidator.js +112 -0
- package/dist/http/middleware/PayloadSanitizer.d.ts +7 -0
- package/dist/http/middleware/PayloadSanitizer.js +42 -0
- package/dist/http/middleware/ProcessorPathSanitizer.d.ts +3 -0
- package/dist/http/middleware/ProcessorPathSanitizer.js +74 -0
- package/dist/http/middleware/QueueNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/QueueNameSanitizer.js +45 -0
- package/dist/http/middleware/ValidateDriver.d.ts +7 -0
- package/dist/http/middleware/ValidateDriver.js +20 -0
- package/dist/http/middleware/VersionSanitizer.d.ts +3 -0
- package/dist/http/middleware/VersionSanitizer.js +25 -0
- package/dist/http/middleware/WorkerNameSanitizer.d.ts +3 -0
- package/dist/http/middleware/WorkerNameSanitizer.js +46 -0
- package/dist/http/middleware/WorkerValidationChain.d.ts +27 -0
- package/dist/http/middleware/WorkerValidationChain.js +185 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +48 -0
- package/dist/routes/workers.d.ts +12 -0
- package/dist/routes/workers.js +81 -0
- package/dist/storage/WorkerStore.d.ts +45 -0
- package/dist/storage/WorkerStore.js +195 -0
- package/dist/type.d.ts +76 -0
- package/dist/type.js +1 -0
- package/dist/ui/router/ui.d.ts +3 -0
- package/dist/ui/router/ui.js +83 -0
- package/dist/ui/types/worker-ui.d.ts +229 -0
- package/dist/ui/types/worker-ui.js +5 -0
- package/package.json +53 -0
- package/src/AnomalyDetection.ts +434 -0
- package/src/AutoScaler.ts +654 -0
- package/src/BroadcastWorker.ts +34 -0
- package/src/CanaryController.ts +531 -0
- package/src/ChaosEngineering.ts +301 -0
- package/src/CircuitBreaker.ts +495 -0
- package/src/ClusterLock.ts +499 -0
- package/src/ComplianceManager.ts +815 -0
- package/src/DatacenterOrchestrator.ts +561 -0
- package/src/DeadLetterQueue.ts +733 -0
- package/src/HealthMonitor.ts +390 -0
- package/src/MultiQueueWorker.ts +431 -0
- package/src/NotificationWorker.ts +33 -0
- package/src/Observability.ts +696 -0
- package/src/PluginManager.ts +551 -0
- package/src/PriorityQueue.ts +351 -0
- package/src/ResourceMonitor.ts +769 -0
- package/src/SLAMonitor.ts +408 -0
- package/src/WorkerFactory.ts +2108 -0
- package/src/WorkerInit.ts +313 -0
- package/src/WorkerMetrics.ts +709 -0
- package/src/WorkerRegistry.ts +443 -0
- package/src/WorkerShutdown.ts +210 -0
- package/src/WorkerVersioning.ts +422 -0
- package/src/config/workerConfig.ts +25 -0
- package/src/createQueueWorker.ts +174 -0
- package/src/dashboard/index.ts +6 -0
- package/src/dashboard/types.ts +141 -0
- package/src/dashboard/workers-api.ts +785 -0
- package/src/dashboard/zintrust.svg +30 -0
- package/src/helper/index.ts +11 -0
- package/src/http/WorkerApiController.ts +369 -0
- package/src/http/WorkerController.ts +1512 -0
- package/src/http/middleware/CustomValidation.ts +360 -0
- package/src/http/middleware/DatacenterValidator.ts +124 -0
- package/src/http/middleware/EditWorkerValidation.ts +74 -0
- package/src/http/middleware/FeaturesValidator.ts +82 -0
- package/src/http/middleware/InfrastructureValidator.ts +295 -0
- package/src/http/middleware/OptionsValidator.ts +144 -0
- package/src/http/middleware/PayloadSanitizer.ts +52 -0
- package/src/http/middleware/ProcessorPathSanitizer.ts +86 -0
- package/src/http/middleware/QueueNameSanitizer.ts +55 -0
- package/src/http/middleware/ValidateDriver.ts +29 -0
- package/src/http/middleware/VersionSanitizer.ts +30 -0
- package/src/http/middleware/WorkerNameSanitizer.ts +56 -0
- package/src/http/middleware/WorkerValidationChain.ts +230 -0
- package/src/index.ts +98 -0
- package/src/routes/workers.ts +154 -0
- package/src/storage/WorkerStore.ts +240 -0
- package/src/type.ts +89 -0
- package/src/types/queue-monitor.d.ts +38 -0
- package/src/types/queue-redis.d.ts +38 -0
- package/src/ui/README.md +13 -0
- package/src/ui/components/JsonEditor.js +670 -0
- package/src/ui/components/JsonViewer.js +387 -0
- package/src/ui/components/WorkerCard.js +178 -0
- package/src/ui/components/WorkerExpandPanel.js +257 -0
- package/src/ui/components/fetcher.js +42 -0
- package/src/ui/components/sla-scorecard.js +32 -0
- package/src/ui/components/styles.css +30 -0
- package/src/ui/components/table-expander.js +34 -0
- package/src/ui/integration/worker-ui-integration.js +565 -0
- package/src/ui/router/ui.ts +99 -0
- package/src/ui/services/workerApi.js +240 -0
- package/src/ui/types/worker-ui.ts +283 -0
- package/src/ui/utils/jsonValidator.js +444 -0
- package/src/ui/workers/index.html +202 -0
- package/src/ui/workers/main.js +1781 -0
- package/src/ui/workers/styles.css +1350 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager
|
|
3
|
+
* Extensible plugin system with lifecycle hooks
|
|
4
|
+
* Sealed namespace for immutability
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ErrorFactory, Logger } from '@zintrust/core';
|
|
8
|
+
|
|
9
|
+
export type PluginHook =
|
|
10
|
+
| 'beforeStart'
|
|
11
|
+
| 'afterStart'
|
|
12
|
+
| 'beforeProcess'
|
|
13
|
+
| 'afterProcess'
|
|
14
|
+
| 'beforeStop'
|
|
15
|
+
| 'afterStop'
|
|
16
|
+
| 'onError'
|
|
17
|
+
| 'onComplete'
|
|
18
|
+
| 'onRetry'
|
|
19
|
+
| 'onCircuitOpen'
|
|
20
|
+
| 'onCircuitClose'
|
|
21
|
+
| 'onScaleUp'
|
|
22
|
+
| 'onScaleDown';
|
|
23
|
+
|
|
24
|
+
export type HookContext = {
|
|
25
|
+
workerName: string;
|
|
26
|
+
jobId?: string;
|
|
27
|
+
version?: string;
|
|
28
|
+
jobData?: unknown;
|
|
29
|
+
error?: Error;
|
|
30
|
+
metadata?: Record<string, unknown>;
|
|
31
|
+
timestamp: Date;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type HookResult = {
|
|
35
|
+
modified?: boolean;
|
|
36
|
+
jobData?: unknown;
|
|
37
|
+
stop?: boolean;
|
|
38
|
+
error?: Error;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PluginMetadata = {
|
|
42
|
+
name: string;
|
|
43
|
+
version: string;
|
|
44
|
+
author?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
dependencies?: string[]; // Other plugin names this plugin depends on
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type Plugin = {
|
|
50
|
+
metadata: PluginMetadata;
|
|
51
|
+
hooks: Partial<Record<PluginHook, PluginHookHandler>>;
|
|
52
|
+
onEnable?: () => void | Promise<void>;
|
|
53
|
+
onDisable?: () => void | Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type PluginHookHandler = (
|
|
57
|
+
context: HookContext
|
|
58
|
+
) => HookResult | Promise<HookResult> | undefined | Promise<HookResult | undefined>;
|
|
59
|
+
|
|
60
|
+
export type RegisteredPlugin = {
|
|
61
|
+
plugin: Plugin;
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
registeredAt: Date;
|
|
64
|
+
priority: number; // Lower number = higher priority (executes first)
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type HookExecutionResult = {
|
|
68
|
+
data: unknown;
|
|
69
|
+
hook: PluginHook;
|
|
70
|
+
executionTime: number;
|
|
71
|
+
pluginsExecuted: number;
|
|
72
|
+
errors: Array<{ pluginName: string; error: Error }>;
|
|
73
|
+
context: HookContext;
|
|
74
|
+
modified: boolean;
|
|
75
|
+
stopped: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Internal state
|
|
79
|
+
const plugins = new Map<string, RegisteredPlugin>();
|
|
80
|
+
const hookExecutionHistory: HookExecutionResult[] = [];
|
|
81
|
+
const MAX_HISTORY = 1000;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Helper: Validate plugin metadata
|
|
85
|
+
*/
|
|
86
|
+
const validatePlugin = (plugin: Plugin): void => {
|
|
87
|
+
if (!plugin.metadata.name) {
|
|
88
|
+
throw ErrorFactory.createWorkerError('Plugin name is required');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!plugin.metadata.version) {
|
|
92
|
+
throw ErrorFactory.createWorkerError('Plugin version is required');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Object.keys(plugin.hooks).length === 0) {
|
|
96
|
+
throw ErrorFactory.createWorkerError('Plugin must implement at least one hook');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Helper: Check plugin dependencies
|
|
102
|
+
*/
|
|
103
|
+
const checkDependencies = (plugin: Plugin): { satisfied: boolean; missing: string[] } => {
|
|
104
|
+
if (!plugin.metadata.dependencies || plugin.metadata.dependencies.length === 0) {
|
|
105
|
+
return { satisfied: true, missing: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const missing: string[] = [];
|
|
109
|
+
|
|
110
|
+
for (const depName of plugin.metadata.dependencies) {
|
|
111
|
+
const dep = plugins.get(depName);
|
|
112
|
+
if (dep?.enabled !== true) {
|
|
113
|
+
missing.push(depName);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
satisfied: missing.length === 0,
|
|
119
|
+
missing,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Helper: Get enabled plugins for a hook, sorted by priority
|
|
125
|
+
*/
|
|
126
|
+
const getEnabledPluginsForHook = (hook: PluginHook): RegisteredPlugin[] => {
|
|
127
|
+
const enabledPlugins: RegisteredPlugin[] = [];
|
|
128
|
+
|
|
129
|
+
for (const registered of plugins.values()) {
|
|
130
|
+
if (registered.enabled && registered.plugin.hooks[hook]) {
|
|
131
|
+
enabledPlugins.push(registered);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Sort by priority (lower number = higher priority)
|
|
136
|
+
return enabledPlugins.sort((a, b) => a.priority - b.priority);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Helper: Store execution result
|
|
141
|
+
*/
|
|
142
|
+
const storeExecutionResult = (result: HookExecutionResult): void => {
|
|
143
|
+
hookExecutionHistory.push(result);
|
|
144
|
+
|
|
145
|
+
// Keep only last MAX_HISTORY results
|
|
146
|
+
if (hookExecutionHistory.length > MAX_HISTORY) {
|
|
147
|
+
hookExecutionHistory.shift();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Plugin Manager - Sealed namespace
|
|
153
|
+
*/
|
|
154
|
+
export const PluginManager = Object.freeze({
|
|
155
|
+
/**
|
|
156
|
+
* Register a plugin
|
|
157
|
+
*/
|
|
158
|
+
async register(plugin: Plugin, priority = 100): Promise<void> {
|
|
159
|
+
validatePlugin(plugin);
|
|
160
|
+
|
|
161
|
+
const { name } = plugin.metadata;
|
|
162
|
+
|
|
163
|
+
if (plugins.has(name)) {
|
|
164
|
+
throw ErrorFactory.createWorkerError(`Plugin "${name}" is already registered`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check dependencies
|
|
168
|
+
const depCheck = checkDependencies(plugin);
|
|
169
|
+
if (!depCheck.satisfied) {
|
|
170
|
+
throw ErrorFactory.createWorkerError(
|
|
171
|
+
`Plugin "${name}" has unsatisfied dependencies: ${depCheck.missing.join(', ')}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const registered: RegisteredPlugin = {
|
|
176
|
+
plugin,
|
|
177
|
+
enabled: true,
|
|
178
|
+
registeredAt: new Date(),
|
|
179
|
+
priority,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
plugins.set(name, registered);
|
|
183
|
+
|
|
184
|
+
// Call onEnable if provided
|
|
185
|
+
if (plugin.onEnable) {
|
|
186
|
+
try {
|
|
187
|
+
await plugin.onEnable();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
Logger.error(`Plugin "${name}" onEnable failed`, error);
|
|
190
|
+
plugins.delete(name);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Logger.info(`Plugin registered: ${name}@${plugin.metadata.version}`, {
|
|
196
|
+
hooks: Object.keys(plugin.hooks),
|
|
197
|
+
priority,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Unregister a plugin
|
|
203
|
+
*/
|
|
204
|
+
async unregister(pluginName: string): Promise<void> {
|
|
205
|
+
const registered = plugins.get(pluginName);
|
|
206
|
+
|
|
207
|
+
if (!registered) {
|
|
208
|
+
throw ErrorFactory.createNotFoundError(`Plugin "${pluginName}" not found`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if other plugins depend on this one
|
|
212
|
+
const dependents: string[] = [];
|
|
213
|
+
for (const [name, reg] of plugins.entries()) {
|
|
214
|
+
if (name !== pluginName && reg.enabled) {
|
|
215
|
+
const deps = reg.plugin.metadata.dependencies ?? [];
|
|
216
|
+
if (deps.includes(pluginName)) {
|
|
217
|
+
dependents.push(name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (dependents.length > 0) {
|
|
223
|
+
throw ErrorFactory.createWorkerError(
|
|
224
|
+
`Cannot unregister plugin "${pluginName}": required by ${dependents.join(', ')}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Call onDisable if provided
|
|
229
|
+
if (registered.plugin.onDisable) {
|
|
230
|
+
try {
|
|
231
|
+
await registered.plugin.onDisable();
|
|
232
|
+
} catch (error) {
|
|
233
|
+
Logger.error(`Plugin "${pluginName}" onDisable failed`, error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
plugins.delete(pluginName);
|
|
238
|
+
|
|
239
|
+
Logger.info(`Plugin unregistered: ${pluginName}`);
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Enable a plugin
|
|
244
|
+
*/
|
|
245
|
+
async enable(pluginName: string): Promise<void> {
|
|
246
|
+
const registered = plugins.get(pluginName);
|
|
247
|
+
|
|
248
|
+
if (!registered) {
|
|
249
|
+
throw ErrorFactory.createNotFoundError(`Plugin "${pluginName}" not found`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (registered.enabled) {
|
|
253
|
+
Logger.warn(`Plugin "${pluginName}" is already enabled`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check dependencies
|
|
258
|
+
const depCheck = checkDependencies(registered.plugin);
|
|
259
|
+
if (!depCheck.satisfied) {
|
|
260
|
+
throw ErrorFactory.createWorkerError(
|
|
261
|
+
`Cannot enable plugin "${pluginName}": unsatisfied dependencies: ${depCheck.missing.join(', ')}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
registered.enabled = true;
|
|
266
|
+
|
|
267
|
+
// Call onEnable if provided
|
|
268
|
+
if (registered.plugin.onEnable) {
|
|
269
|
+
try {
|
|
270
|
+
await registered.plugin.onEnable();
|
|
271
|
+
} catch (error) {
|
|
272
|
+
Logger.error(`Plugin "${pluginName}" onEnable failed`, error);
|
|
273
|
+
registered.enabled = false;
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
Logger.info(`Plugin enabled: ${pluginName}`);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Disable a plugin
|
|
283
|
+
*/
|
|
284
|
+
async disable(pluginName: string): Promise<void> {
|
|
285
|
+
const registered = plugins.get(pluginName);
|
|
286
|
+
|
|
287
|
+
if (!registered) {
|
|
288
|
+
throw ErrorFactory.createNotFoundError(`Plugin "${pluginName}" not found`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!registered.enabled) {
|
|
292
|
+
Logger.warn(`Plugin "${pluginName}" is already disabled`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check if other enabled plugins depend on this one
|
|
297
|
+
const dependents: string[] = [];
|
|
298
|
+
for (const [name, reg] of plugins.entries()) {
|
|
299
|
+
if (name !== pluginName && reg.enabled) {
|
|
300
|
+
const deps = reg.plugin.metadata.dependencies ?? [];
|
|
301
|
+
if (deps.includes(pluginName)) {
|
|
302
|
+
dependents.push(name);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (dependents.length > 0) {
|
|
308
|
+
throw ErrorFactory.createWorkerError(
|
|
309
|
+
`Cannot disable plugin "${pluginName}": required by enabled plugins: ${dependents.join(', ')}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
registered.enabled = false;
|
|
314
|
+
|
|
315
|
+
// Call onDisable if provided
|
|
316
|
+
if (registered.plugin.onDisable) {
|
|
317
|
+
try {
|
|
318
|
+
await registered.plugin.onDisable();
|
|
319
|
+
} catch (error) {
|
|
320
|
+
Logger.error(`Plugin "${pluginName}" onDisable failed`, error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
Logger.info(`Plugin disabled: ${pluginName}`);
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Execute hooks for a lifecycle event
|
|
329
|
+
*/
|
|
330
|
+
async executeHook(hook: PluginHook, context: HookContext): Promise<HookExecutionResult> {
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
const enabledPlugins = getEnabledPluginsForHook(hook);
|
|
333
|
+
|
|
334
|
+
const result: HookExecutionResult = {
|
|
335
|
+
data: context.jobData,
|
|
336
|
+
hook,
|
|
337
|
+
executionTime: 0,
|
|
338
|
+
pluginsExecuted: 0,
|
|
339
|
+
errors: [],
|
|
340
|
+
context,
|
|
341
|
+
modified: false,
|
|
342
|
+
stopped: false,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const currentContext = { ...context };
|
|
346
|
+
|
|
347
|
+
let stopped = false;
|
|
348
|
+
|
|
349
|
+
const executeHandler = async (registered: RegisteredPlugin): Promise<void> => {
|
|
350
|
+
if (stopped) return;
|
|
351
|
+
|
|
352
|
+
const { plugin } = registered;
|
|
353
|
+
const handler = plugin.hooks[hook];
|
|
354
|
+
if (!handler) return;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const hookResult = await handler(currentContext);
|
|
358
|
+
result.pluginsExecuted++;
|
|
359
|
+
|
|
360
|
+
if (hookResult !== undefined) {
|
|
361
|
+
if (hookResult.modified === true) {
|
|
362
|
+
result.modified = true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (hookResult.jobData !== undefined) {
|
|
366
|
+
currentContext.jobData = hookResult.jobData;
|
|
367
|
+
result.data = hookResult.jobData;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (hookResult.stop === true) {
|
|
371
|
+
result.stopped = true;
|
|
372
|
+
stopped = true;
|
|
373
|
+
Logger.warn(`Hook execution stopped by plugin: ${plugin.metadata.name}`, { hook });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (hookResult.error instanceof Error) {
|
|
377
|
+
result.errors.push({
|
|
378
|
+
pluginName: plugin.metadata.name,
|
|
379
|
+
error: hookResult.error,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
Logger.error(`Plugin "${plugin.metadata.name}" hook "${hook}" failed`, error);
|
|
385
|
+
result.errors.push({
|
|
386
|
+
pluginName: plugin.metadata.name,
|
|
387
|
+
error: error as Error,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (hook === 'beforeStart' || hook === 'beforeStop') {
|
|
391
|
+
result.stopped = true;
|
|
392
|
+
stopped = true;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
let chain = Promise.resolve();
|
|
398
|
+
for (const registered of enabledPlugins) {
|
|
399
|
+
chain = chain.then(async () => executeHandler(registered));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await chain;
|
|
403
|
+
|
|
404
|
+
result.executionTime = Date.now() - startTime;
|
|
405
|
+
storeExecutionResult(result);
|
|
406
|
+
|
|
407
|
+
Logger.debug(`Hook executed: ${hook}`, {
|
|
408
|
+
pluginsExecuted: result.pluginsExecuted,
|
|
409
|
+
executionTime: result.executionTime,
|
|
410
|
+
errors: result.errors.length,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
return result;
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get registered plugins
|
|
418
|
+
*/
|
|
419
|
+
getPlugins(): ReadonlyArray<RegisteredPlugin & { name: string }> {
|
|
420
|
+
return Array.from(plugins.entries()).map(([name, registered]) => ({
|
|
421
|
+
name,
|
|
422
|
+
...registered,
|
|
423
|
+
}));
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get plugin by name
|
|
428
|
+
*/
|
|
429
|
+
getPlugin(pluginName: string): (RegisteredPlugin & { name: string }) | null {
|
|
430
|
+
const registered = plugins.get(pluginName);
|
|
431
|
+
if (!registered) return null;
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
name: pluginName,
|
|
435
|
+
...registered,
|
|
436
|
+
};
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check if plugin is registered
|
|
441
|
+
*/
|
|
442
|
+
isRegistered(pluginName: string): boolean {
|
|
443
|
+
return plugins.has(pluginName);
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Check if plugin is enabled
|
|
448
|
+
*/
|
|
449
|
+
isEnabled(pluginName: string): boolean {
|
|
450
|
+
const registered = plugins.get(pluginName);
|
|
451
|
+
return registered ? registered.enabled : false;
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get hook execution history
|
|
456
|
+
*/
|
|
457
|
+
getExecutionHistory(hook?: PluginHook, limit = 100): ReadonlyArray<HookExecutionResult> {
|
|
458
|
+
let history = hookExecutionHistory;
|
|
459
|
+
|
|
460
|
+
if (hook) {
|
|
461
|
+
history = history.filter((result) => result.hook === hook);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return history.slice(-limit).map((result) => ({ ...result }));
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get plugin statistics
|
|
469
|
+
*/
|
|
470
|
+
getStatistics(): {
|
|
471
|
+
totalPlugins: number;
|
|
472
|
+
enabledPlugins: number;
|
|
473
|
+
disabledPlugins: number;
|
|
474
|
+
totalHookExecutions: number;
|
|
475
|
+
hookExecutionsByType: Record<PluginHook, number>;
|
|
476
|
+
averageExecutionTime: number;
|
|
477
|
+
totalErrors: number;
|
|
478
|
+
} {
|
|
479
|
+
const stats = {
|
|
480
|
+
totalPlugins: plugins.size,
|
|
481
|
+
enabledPlugins: 0,
|
|
482
|
+
disabledPlugins: 0,
|
|
483
|
+
totalHookExecutions: hookExecutionHistory.length,
|
|
484
|
+
hookExecutionsByType: {} as Record<PluginHook, number>,
|
|
485
|
+
averageExecutionTime: 0,
|
|
486
|
+
totalErrors: 0,
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
for (const registered of plugins.values()) {
|
|
490
|
+
if (registered.enabled) {
|
|
491
|
+
stats.enabledPlugins++;
|
|
492
|
+
} else {
|
|
493
|
+
stats.disabledPlugins++;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let totalExecutionTime = 0;
|
|
498
|
+
|
|
499
|
+
for (const result of hookExecutionHistory) {
|
|
500
|
+
const hookType = result.hook;
|
|
501
|
+
stats.hookExecutionsByType[hookType] = (stats.hookExecutionsByType[hookType] || 0) + 1;
|
|
502
|
+
totalExecutionTime += result.executionTime;
|
|
503
|
+
stats.totalErrors += result.errors.length;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (hookExecutionHistory.length > 0) {
|
|
507
|
+
stats.averageExecutionTime = totalExecutionTime / hookExecutionHistory.length;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return stats;
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Clear execution history
|
|
515
|
+
*/
|
|
516
|
+
clearHistory(): void {
|
|
517
|
+
hookExecutionHistory.length = 0;
|
|
518
|
+
Logger.info('Plugin execution history cleared');
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Shutdown and disable all plugins
|
|
523
|
+
*/
|
|
524
|
+
async shutdown(): Promise<void> {
|
|
525
|
+
Logger.info('PluginManager shutting down...');
|
|
526
|
+
|
|
527
|
+
// Disable all plugins in reverse order of priority
|
|
528
|
+
const sortedPlugins = Array.from(plugins.entries()).sort(
|
|
529
|
+
([, a], [, b]) => b.priority - a.priority
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
const disableTasks = sortedPlugins.map(async ([name, registered]) => {
|
|
533
|
+
if (!registered.enabled || !registered.plugin.onDisable) return;
|
|
534
|
+
try {
|
|
535
|
+
await registered.plugin.onDisable();
|
|
536
|
+
Logger.debug(`Plugin disabled: ${name}`);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
Logger.error(`Plugin "${name}" onDisable failed during shutdown`, error);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
await Promise.allSettled(disableTasks);
|
|
543
|
+
|
|
544
|
+
plugins.clear();
|
|
545
|
+
hookExecutionHistory.length = 0;
|
|
546
|
+
|
|
547
|
+
Logger.info('PluginManager shutdown complete');
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Graceful shutdown handled by WorkerShutdown
|