@tamyla/clodo-framework 1.0.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.
- package/CHANGELOG.md +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable Business Logic Modules
|
|
3
|
+
* Allows domain-specific logic to be added as optional modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ModuleManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.modules = new Map();
|
|
9
|
+
this.hooks = new Map();
|
|
10
|
+
|
|
11
|
+
// Enterprise configuration
|
|
12
|
+
this.config = {
|
|
13
|
+
defaultTimeout: 5000,
|
|
14
|
+
// 5 seconds default timeout per hook
|
|
15
|
+
maxConcurrentHooks: 10,
|
|
16
|
+
// Maximum concurrent hook executions
|
|
17
|
+
enableHookIsolation: true,
|
|
18
|
+
// Isolate hook execution contexts
|
|
19
|
+
enableMetrics: true,
|
|
20
|
+
// Track hook performance metrics
|
|
21
|
+
retryFailedHooks: false,
|
|
22
|
+
// Retry failed hooks (disabled by default)
|
|
23
|
+
maxRetries: 2 // Maximum retry attempts
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Hook execution metrics
|
|
27
|
+
this.metrics = {
|
|
28
|
+
totalExecutions: 0,
|
|
29
|
+
totalFailures: 0,
|
|
30
|
+
totalTimeouts: 0,
|
|
31
|
+
executionTimes: new Map(),
|
|
32
|
+
// hookName -> array of execution times
|
|
33
|
+
failureReasons: new Map() // hookName -> array of failure reasons
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Active hook executions for timeout management
|
|
37
|
+
this.activeExecutions = new Map();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a business logic module
|
|
42
|
+
* @param {string} moduleName - Name of the module
|
|
43
|
+
* @param {Object} module - Module definition
|
|
44
|
+
*/
|
|
45
|
+
registerModule(moduleName, module) {
|
|
46
|
+
this.modules.set(moduleName, {
|
|
47
|
+
name: moduleName,
|
|
48
|
+
...module
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Register hooks
|
|
52
|
+
if (module.hooks) {
|
|
53
|
+
Object.entries(module.hooks).forEach(([hookName, hookFn]) => {
|
|
54
|
+
if (!this.hooks.has(hookName)) {
|
|
55
|
+
this.hooks.set(hookName, []);
|
|
56
|
+
}
|
|
57
|
+
this.hooks.get(hookName).push({
|
|
58
|
+
module: moduleName,
|
|
59
|
+
fn: hookFn
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
console.log(`✅ Registered module: ${moduleName}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a registered module
|
|
68
|
+
* @param {string} moduleName - Name of the module
|
|
69
|
+
* @returns {Object} Module definition
|
|
70
|
+
*/
|
|
71
|
+
getModule(moduleName) {
|
|
72
|
+
return this.modules.get(moduleName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a module is registered
|
|
77
|
+
* @param {string} moduleName - Name of the module
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
hasModule(moduleName) {
|
|
81
|
+
return this.modules.has(moduleName);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Execute hooks for a specific event with enterprise-grade features
|
|
86
|
+
* @param {string} hookName - Name of the hook
|
|
87
|
+
* @param {Object} context - Execution context
|
|
88
|
+
* @param {Object} options - Execution options
|
|
89
|
+
* @param {...any} args - Additional arguments
|
|
90
|
+
* @returns {Promise<Object>} Hook execution results with metadata
|
|
91
|
+
*/
|
|
92
|
+
async executeHooks(hookName, context, options = {}, ...args) {
|
|
93
|
+
const executionId = this._generateExecutionId();
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
const executionOptions = {
|
|
96
|
+
timeout: options.timeout || this.config.defaultTimeout,
|
|
97
|
+
parallel: options.parallel || false,
|
|
98
|
+
stopOnError: options.stopOnError || false,
|
|
99
|
+
retryOnFailure: options.retryOnFailure || this.config.retryFailedHooks,
|
|
100
|
+
maxRetries: options.maxRetries || this.config.maxRetries,
|
|
101
|
+
...options
|
|
102
|
+
};
|
|
103
|
+
const hooks = this.hooks.get(hookName) || [];
|
|
104
|
+
const results = [];
|
|
105
|
+
let totalErrors = 0;
|
|
106
|
+
let totalTimeouts = 0;
|
|
107
|
+
|
|
108
|
+
// Track active execution
|
|
109
|
+
this.activeExecutions.set(executionId, {
|
|
110
|
+
hookName,
|
|
111
|
+
startTime,
|
|
112
|
+
hooks: hooks.length,
|
|
113
|
+
options: executionOptions
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
if (executionOptions.parallel && hooks.length > 1) {
|
|
117
|
+
// Parallel execution with concurrency control
|
|
118
|
+
const results = await this._executeHooksParallel(hooks, hookName, context, executionOptions, args);
|
|
119
|
+
totalErrors = results.filter(r => !r.success).length;
|
|
120
|
+
totalTimeouts = results.filter(r => r.timeout).length;
|
|
121
|
+
return this._buildExecutionResult(executionId, hookName, results, startTime, totalErrors, totalTimeouts);
|
|
122
|
+
} else {
|
|
123
|
+
// Sequential execution
|
|
124
|
+
for (const hook of hooks) {
|
|
125
|
+
const hookResult = await this._executeHookWithRetry(hook, hookName, context, executionOptions, args);
|
|
126
|
+
results.push(hookResult);
|
|
127
|
+
if (!hookResult.success) {
|
|
128
|
+
totalErrors++;
|
|
129
|
+
if (hookResult.timeout) totalTimeouts++;
|
|
130
|
+
|
|
131
|
+
// Stop on error if configured
|
|
132
|
+
if (executionOptions.stopOnError) {
|
|
133
|
+
console.warn(`Stopping hook execution for '${hookName}' due to error in module '${hook.module}'`);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return this._buildExecutionResult(executionId, hookName, results, startTime, totalErrors, totalTimeouts);
|
|
140
|
+
} finally {
|
|
141
|
+
// Clean up active execution tracking
|
|
142
|
+
this.activeExecutions.delete(executionId);
|
|
143
|
+
|
|
144
|
+
// Update metrics
|
|
145
|
+
this._updateMetrics(hookName, Date.now() - startTime, totalErrors, totalTimeouts);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Execute a single hook with retry logic
|
|
151
|
+
* @param {Object} hook - Hook configuration
|
|
152
|
+
* @param {string} hookName - Hook name
|
|
153
|
+
* @param {Object} context - Execution context
|
|
154
|
+
* @param {Object} options - Execution options
|
|
155
|
+
* @param {Array} args - Hook arguments
|
|
156
|
+
* @returns {Promise<Object>} Hook result
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
async _executeHookWithRetry(hook, hookName, context, options, args) {
|
|
160
|
+
let lastError = null;
|
|
161
|
+
let attempts = 0;
|
|
162
|
+
const maxAttempts = options.retryOnFailure ? options.maxRetries + 1 : 1;
|
|
163
|
+
while (attempts < maxAttempts) {
|
|
164
|
+
attempts++;
|
|
165
|
+
try {
|
|
166
|
+
const result = await this._executeHookWithTimeout(hook, hookName, context, options, args);
|
|
167
|
+
|
|
168
|
+
// Success - log retry success if this wasn't the first attempt
|
|
169
|
+
if (attempts > 1) {
|
|
170
|
+
console.info(`Hook '${hookName}' in module '${hook.module}' succeeded on attempt ${attempts}`);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
module: hook.module,
|
|
174
|
+
result,
|
|
175
|
+
success: true,
|
|
176
|
+
attempts,
|
|
177
|
+
executionTime: result.executionTime,
|
|
178
|
+
timeout: false
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
lastError = error;
|
|
182
|
+
if (attempts < maxAttempts && options.retryOnFailure) {
|
|
183
|
+
console.warn(`Hook '${hookName}' in module '${hook.module}' failed (attempt ${attempts}/${maxAttempts}): ${error.message}. Retrying...`);
|
|
184
|
+
|
|
185
|
+
// Wait before retry with exponential backoff
|
|
186
|
+
await this._sleep(Math.min(1000 * Math.pow(2, attempts - 1), 5000));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// All retry attempts failed
|
|
192
|
+
const isTimeout = lastError.name === 'TimeoutError';
|
|
193
|
+
console.error(`Hook '${hookName}' in module '${hook.module}' failed after ${attempts} attempts:`, lastError.message);
|
|
194
|
+
return {
|
|
195
|
+
module: hook.module,
|
|
196
|
+
error: lastError.message,
|
|
197
|
+
success: false,
|
|
198
|
+
attempts,
|
|
199
|
+
timeout: isTimeout,
|
|
200
|
+
errorType: lastError.name || 'UnknownError'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Execute a hook with timeout protection
|
|
206
|
+
* @param {Object} hook - Hook configuration
|
|
207
|
+
* @param {string} hookName - Hook name
|
|
208
|
+
* @param {Object} context - Execution context
|
|
209
|
+
* @param {Object} options - Execution options
|
|
210
|
+
* @param {Array} args - Hook arguments
|
|
211
|
+
* @returns {Promise<Object>} Hook result with execution time
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
async _executeHookWithTimeout(hook, hookName, context, options, args) {
|
|
215
|
+
const startTime = Date.now();
|
|
216
|
+
|
|
217
|
+
// Create isolated context if enabled
|
|
218
|
+
const executionContext = options.enableHookIsolation !== false ? this._createIsolatedContext(context, hook.module) : context;
|
|
219
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
const error = new Error(`Hook execution timeout after ${options.timeout}ms`);
|
|
222
|
+
error.name = 'TimeoutError';
|
|
223
|
+
reject(error);
|
|
224
|
+
}, options.timeout);
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
const result = await Promise.race([hook.fn(executionContext, ...args), timeoutPromise]);
|
|
228
|
+
return {
|
|
229
|
+
result,
|
|
230
|
+
executionTime: Date.now() - startTime
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
// Add execution time to error context
|
|
234
|
+
error.executionTime = Date.now() - startTime;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Execute hooks in parallel with concurrency control
|
|
241
|
+
* @param {Array} hooks - Hook configurations
|
|
242
|
+
* @param {string} hookName - Hook name
|
|
243
|
+
* @param {Object} context - Execution context
|
|
244
|
+
* @param {Object} options - Execution options
|
|
245
|
+
* @param {Array} args - Hook arguments
|
|
246
|
+
* @returns {Promise<Array>} Hook results
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
async _executeHooksParallel(hooks, hookName, context, options, args) {
|
|
250
|
+
const concurrencyLimit = Math.min(hooks.length, this.config.maxConcurrentHooks);
|
|
251
|
+
const results = new Array(hooks.length);
|
|
252
|
+
|
|
253
|
+
// Create execution promises
|
|
254
|
+
const executeHook = async hookIndex => {
|
|
255
|
+
const hook = hooks[hookIndex];
|
|
256
|
+
const result = await this._executeHookWithRetry(hook, hookName, context, options, args);
|
|
257
|
+
results[hookIndex] = result;
|
|
258
|
+
return result;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Execute with concurrency control
|
|
262
|
+
const promises = [];
|
|
263
|
+
for (let i = 0; i < hooks.length; i += concurrencyLimit) {
|
|
264
|
+
const batch = [];
|
|
265
|
+
for (let j = i; j < Math.min(i + concurrencyLimit, hooks.length); j++) {
|
|
266
|
+
batch.push(executeHook(j));
|
|
267
|
+
}
|
|
268
|
+
promises.push(...batch);
|
|
269
|
+
|
|
270
|
+
// Wait for current batch before starting next (if not fully parallel)
|
|
271
|
+
if (i + concurrencyLimit < hooks.length) {
|
|
272
|
+
await Promise.all(batch);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Wait for all executions to complete
|
|
277
|
+
await Promise.all(promises);
|
|
278
|
+
return results;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get all registered modules
|
|
283
|
+
* @returns {Map} All modules
|
|
284
|
+
*/
|
|
285
|
+
getAllModules() {
|
|
286
|
+
return this.modules;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get hook execution metrics
|
|
291
|
+
* @param {string} hookName - Optional: specific hook name
|
|
292
|
+
* @returns {Object} Execution metrics
|
|
293
|
+
*/
|
|
294
|
+
getMetrics(hookName = null) {
|
|
295
|
+
if (hookName) {
|
|
296
|
+
return {
|
|
297
|
+
executions: this.metrics.executionTimes.get(hookName)?.length || 0,
|
|
298
|
+
averageTime: this._calculateAverageTime(hookName),
|
|
299
|
+
failures: this.metrics.failureReasons.get(hookName)?.length || 0,
|
|
300
|
+
successRate: this._calculateSuccessRate(hookName)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
totalExecutions: this.metrics.totalExecutions,
|
|
305
|
+
totalFailures: this.metrics.totalFailures,
|
|
306
|
+
totalTimeouts: this.metrics.totalTimeouts,
|
|
307
|
+
overallSuccessRate: this._calculateOverallSuccessRate(),
|
|
308
|
+
hookMetrics: this._getAllHookMetrics()
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Clear execution metrics
|
|
314
|
+
* @param {string} hookName - Optional: specific hook to clear
|
|
315
|
+
*/
|
|
316
|
+
clearMetrics(hookName = null) {
|
|
317
|
+
if (hookName) {
|
|
318
|
+
this.metrics.executionTimes.delete(hookName);
|
|
319
|
+
this.metrics.failureReasons.delete(hookName);
|
|
320
|
+
} else {
|
|
321
|
+
this.metrics.totalExecutions = 0;
|
|
322
|
+
this.metrics.totalFailures = 0;
|
|
323
|
+
this.metrics.totalTimeouts = 0;
|
|
324
|
+
this.metrics.executionTimes.clear();
|
|
325
|
+
this.metrics.failureReasons.clear();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Update module manager configuration
|
|
331
|
+
* @param {Object} config - New configuration options
|
|
332
|
+
*/
|
|
333
|
+
updateConfig(config) {
|
|
334
|
+
this.config = {
|
|
335
|
+
...this.config,
|
|
336
|
+
...config
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get active hook executions
|
|
342
|
+
* @returns {Array} Active executions
|
|
343
|
+
*/
|
|
344
|
+
getActiveExecutions() {
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
return Array.from(this.activeExecutions.entries()).map(([id, execution]) => ({
|
|
347
|
+
id,
|
|
348
|
+
hookName: execution.hookName,
|
|
349
|
+
duration: now - execution.startTime,
|
|
350
|
+
hookCount: execution.hooks,
|
|
351
|
+
options: execution.options
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Private helper methods
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Generate unique execution ID
|
|
359
|
+
* @returns {string} Execution ID
|
|
360
|
+
* @private
|
|
361
|
+
*/
|
|
362
|
+
_generateExecutionId() {
|
|
363
|
+
return `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Create isolated execution context
|
|
368
|
+
* @param {Object} originalContext - Original context
|
|
369
|
+
* @param {string} moduleName - Module name
|
|
370
|
+
* @returns {Object} Isolated context
|
|
371
|
+
* @private
|
|
372
|
+
*/
|
|
373
|
+
_createIsolatedContext(originalContext, moduleName) {
|
|
374
|
+
return {
|
|
375
|
+
...originalContext,
|
|
376
|
+
_module: moduleName,
|
|
377
|
+
_isolated: true,
|
|
378
|
+
_timestamp: Date.now()
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Sleep utility for retry delays
|
|
384
|
+
* @param {number} ms - Milliseconds to sleep
|
|
385
|
+
* @returns {Promise<void>}
|
|
386
|
+
* @private
|
|
387
|
+
*/
|
|
388
|
+
_sleep(ms) {
|
|
389
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Build comprehensive execution result
|
|
394
|
+
* @param {string} executionId - Execution ID
|
|
395
|
+
* @param {string} hookName - Hook name
|
|
396
|
+
* @param {Array} results - Individual hook results
|
|
397
|
+
* @param {number} startTime - Execution start time
|
|
398
|
+
* @param {number} totalErrors - Total error count
|
|
399
|
+
* @param {number} totalTimeouts - Total timeout count
|
|
400
|
+
* @returns {Object} Execution result
|
|
401
|
+
* @private
|
|
402
|
+
*/
|
|
403
|
+
_buildExecutionResult(executionId, hookName, results, startTime, totalErrors, totalTimeouts) {
|
|
404
|
+
const executionTime = Date.now() - startTime;
|
|
405
|
+
const successfulHooks = results.filter(r => r.success);
|
|
406
|
+
const failedHooks = results.filter(r => !r.success);
|
|
407
|
+
return {
|
|
408
|
+
executionId,
|
|
409
|
+
hookName,
|
|
410
|
+
executionTime,
|
|
411
|
+
totalHooks: results.length,
|
|
412
|
+
successful: successfulHooks.length,
|
|
413
|
+
failed: failedHooks.length,
|
|
414
|
+
timeouts: totalTimeouts,
|
|
415
|
+
successRate: results.length > 0 ? successfulHooks.length / results.length * 100 : 0,
|
|
416
|
+
results,
|
|
417
|
+
summary: {
|
|
418
|
+
overallSuccess: totalErrors === 0,
|
|
419
|
+
hasTimeouts: totalTimeouts > 0,
|
|
420
|
+
partialSuccess: successfulHooks.length > 0 && failedHooks.length > 0
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Update execution metrics
|
|
427
|
+
* @param {string} hookName - Hook name
|
|
428
|
+
* @param {number} executionTime - Execution time
|
|
429
|
+
* @param {number} errorCount - Error count
|
|
430
|
+
* @param {number} timeoutCount - Timeout count
|
|
431
|
+
* @private
|
|
432
|
+
*/
|
|
433
|
+
_updateMetrics(hookName, executionTime, errorCount, timeoutCount) {
|
|
434
|
+
if (!this.config.enableMetrics) return;
|
|
435
|
+
this.metrics.totalExecutions++;
|
|
436
|
+
this.metrics.totalFailures += errorCount;
|
|
437
|
+
this.metrics.totalTimeouts += timeoutCount;
|
|
438
|
+
if (!this.metrics.executionTimes.has(hookName)) {
|
|
439
|
+
this.metrics.executionTimes.set(hookName, []);
|
|
440
|
+
}
|
|
441
|
+
this.metrics.executionTimes.get(hookName).push(executionTime);
|
|
442
|
+
|
|
443
|
+
// Keep only last 100 execution times per hook to prevent memory leaks
|
|
444
|
+
const times = this.metrics.executionTimes.get(hookName);
|
|
445
|
+
if (times.length > 100) {
|
|
446
|
+
times.splice(0, times.length - 100);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Calculate average execution time for a hook
|
|
452
|
+
* @param {string} hookName - Hook name
|
|
453
|
+
* @returns {number} Average time in ms
|
|
454
|
+
* @private
|
|
455
|
+
*/
|
|
456
|
+
_calculateAverageTime(hookName) {
|
|
457
|
+
const times = this.metrics.executionTimes.get(hookName) || [];
|
|
458
|
+
if (times.length === 0) return 0;
|
|
459
|
+
return times.reduce((sum, time) => sum + time, 0) / times.length;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Calculate success rate for a hook
|
|
464
|
+
* @param {string} hookName - Hook name
|
|
465
|
+
* @returns {number} Success rate percentage
|
|
466
|
+
* @private
|
|
467
|
+
*/
|
|
468
|
+
_calculateSuccessRate(hookName) {
|
|
469
|
+
const executions = this.metrics.executionTimes.get(hookName)?.length || 0;
|
|
470
|
+
const failures = this.metrics.failureReasons.get(hookName)?.length || 0;
|
|
471
|
+
if (executions === 0) return 100;
|
|
472
|
+
return (executions - failures) / executions * 100;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Calculate overall success rate
|
|
477
|
+
* @returns {number} Overall success rate percentage
|
|
478
|
+
* @private
|
|
479
|
+
*/
|
|
480
|
+
_calculateOverallSuccessRate() {
|
|
481
|
+
if (this.metrics.totalExecutions === 0) return 100;
|
|
482
|
+
return (this.metrics.totalExecutions - this.metrics.totalFailures) / this.metrics.totalExecutions * 100;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Get metrics for all hooks
|
|
487
|
+
* @returns {Object} All hook metrics
|
|
488
|
+
* @private
|
|
489
|
+
*/
|
|
490
|
+
_getAllHookMetrics() {
|
|
491
|
+
const metrics = {};
|
|
492
|
+
for (const hookName of this.metrics.executionTimes.keys()) {
|
|
493
|
+
metrics[hookName] = this.getMetrics(hookName);
|
|
494
|
+
}
|
|
495
|
+
return metrics;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Create singleton instance
|
|
500
|
+
export const moduleManager = new ModuleManager();
|
|
501
|
+
|
|
502
|
+
// Pre-register existing modules for backward compatibility
|
|
503
|
+
|
|
504
|
+
// Authentication Module
|
|
505
|
+
moduleManager.registerModule('auth', {
|
|
506
|
+
name: 'auth',
|
|
507
|
+
description: 'Authentication and user management',
|
|
508
|
+
// Custom methods
|
|
509
|
+
methods: {
|
|
510
|
+
async createMagicLink(dataService, email, userId, expiresMinutes = 15) {
|
|
511
|
+
const expiresAt = new Date();
|
|
512
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + expiresMinutes);
|
|
513
|
+
return await dataService.create({
|
|
514
|
+
token: crypto.randomUUID(),
|
|
515
|
+
user_id: userId,
|
|
516
|
+
email,
|
|
517
|
+
expires_at: expiresAt.toISOString()
|
|
518
|
+
});
|
|
519
|
+
},
|
|
520
|
+
async verifyMagicLink(dataService, token) {
|
|
521
|
+
const magicLink = await dataService.findById(token);
|
|
522
|
+
if (!magicLink) {
|
|
523
|
+
throw new Error('Magic link not found');
|
|
524
|
+
}
|
|
525
|
+
if (new Date() > new Date(magicLink.expires_at)) {
|
|
526
|
+
throw new Error('Magic link expired');
|
|
527
|
+
}
|
|
528
|
+
if (magicLink.used) {
|
|
529
|
+
throw new Error('Magic link already used');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Mark as used
|
|
533
|
+
await dataService.update(token, {
|
|
534
|
+
used: 1
|
|
535
|
+
});
|
|
536
|
+
return magicLink;
|
|
537
|
+
},
|
|
538
|
+
async createAuthToken(dataService, userId, type = 'access', expiresHours = 24) {
|
|
539
|
+
const expiresAt = expiresHours ? new Date(Date.now() + expiresHours * 60 * 60 * 1000).toISOString() : null;
|
|
540
|
+
return await dataService.create({
|
|
541
|
+
token: crypto.randomUUID(),
|
|
542
|
+
user_id: userId,
|
|
543
|
+
type,
|
|
544
|
+
expires_at: expiresAt
|
|
545
|
+
});
|
|
546
|
+
},
|
|
547
|
+
async validateToken(dataService, token, type = null) {
|
|
548
|
+
const tokenRecord = await dataService.find({
|
|
549
|
+
token
|
|
550
|
+
})[0];
|
|
551
|
+
if (!tokenRecord) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
if (type && tokenRecord.type !== type) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
if (tokenRecord.expires_at && new Date() > new Date(tokenRecord.expires_at)) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
return tokenRecord;
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
// Hooks
|
|
564
|
+
hooks: {
|
|
565
|
+
'user.created': async (context, userData) => {
|
|
566
|
+
console.log(`Auth module: User created: ${userData.email}`);
|
|
567
|
+
// Could send welcome email, create default settings, etc.
|
|
568
|
+
},
|
|
569
|
+
'user.deleted': async (context, userId) => {
|
|
570
|
+
console.log(`Auth module: User deleted: ${userId}`);
|
|
571
|
+
// Could clean up related data, revoke sessions, etc.
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// File Management Module
|
|
577
|
+
moduleManager.registerModule('files', {
|
|
578
|
+
name: 'files',
|
|
579
|
+
description: 'File upload and management',
|
|
580
|
+
methods: {
|
|
581
|
+
async createFileRecord(dataService, fileData) {
|
|
582
|
+
return await dataService.create({
|
|
583
|
+
...fileData,
|
|
584
|
+
status: fileData.status || 'uploaded'
|
|
585
|
+
});
|
|
586
|
+
},
|
|
587
|
+
async updateFileStatus(dataService, fileId, status) {
|
|
588
|
+
return await dataService.update(fileId, {
|
|
589
|
+
status,
|
|
590
|
+
updated_at: new Date().toISOString()
|
|
591
|
+
});
|
|
592
|
+
},
|
|
593
|
+
async getUserFiles(dataService, userId, status = null) {
|
|
594
|
+
const criteria = {
|
|
595
|
+
user_id: userId
|
|
596
|
+
};
|
|
597
|
+
if (status) {
|
|
598
|
+
criteria.status = status;
|
|
599
|
+
}
|
|
600
|
+
return await dataService.find(criteria);
|
|
601
|
+
},
|
|
602
|
+
async getFileStats(dataService, userId) {
|
|
603
|
+
const allFiles = await dataService.find({
|
|
604
|
+
user_id: userId
|
|
605
|
+
});
|
|
606
|
+
const stats = {
|
|
607
|
+
total: allFiles.length,
|
|
608
|
+
byStatus: {},
|
|
609
|
+
totalSize: 0
|
|
610
|
+
};
|
|
611
|
+
allFiles.forEach(file => {
|
|
612
|
+
stats.byStatus[file.status] = (stats.byStatus[file.status] || 0) + 1;
|
|
613
|
+
stats.totalSize += file.size || 0;
|
|
614
|
+
});
|
|
615
|
+
return stats;
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
hooks: {
|
|
619
|
+
'file.uploaded': async (context, fileData) => {
|
|
620
|
+
console.log(`Files module: File uploaded: ${fileData.filename}`);
|
|
621
|
+
// Could trigger processing, virus scanning, etc.
|
|
622
|
+
},
|
|
623
|
+
'file.deleted': async (context, fileId) => {
|
|
624
|
+
console.log(`Files module: File deleted: ${fileId}`);
|
|
625
|
+
// Could clean up physical files, update storage quotas, etc.
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// Logging Module
|
|
631
|
+
moduleManager.registerModule('logging', {
|
|
632
|
+
name: 'logging',
|
|
633
|
+
description: 'Centralized logging and audit trails',
|
|
634
|
+
methods: {
|
|
635
|
+
async logActivity(dataService, level, message, userId = null, metadata = {}) {
|
|
636
|
+
return await dataService.create({
|
|
637
|
+
level: level.toUpperCase(),
|
|
638
|
+
message,
|
|
639
|
+
user_id: userId,
|
|
640
|
+
metadata: JSON.stringify(metadata)
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
async getUserActivity(dataService, userId, limit = 50) {
|
|
644
|
+
return await dataService.find({
|
|
645
|
+
user_id: userId
|
|
646
|
+
}, {
|
|
647
|
+
limit,
|
|
648
|
+
orderBy: 'timestamp DESC'
|
|
649
|
+
});
|
|
650
|
+
},
|
|
651
|
+
async getRecentLogs(dataService, level = null, limit = 100) {
|
|
652
|
+
const criteria = {};
|
|
653
|
+
if (level) {
|
|
654
|
+
criteria.level = level.toUpperCase();
|
|
655
|
+
}
|
|
656
|
+
return await dataService.find(criteria, {
|
|
657
|
+
limit,
|
|
658
|
+
orderBy: 'timestamp DESC'
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
hooks: {
|
|
663
|
+
'system.cleanup': async (context, cleanupData) => {
|
|
664
|
+
await context.loggingService.logActivity('INFO', `Data cleanup performed: ${cleanupData.operation}`, null, cleanupData);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
console.log('✅ Module Manager initialized with core modules');
|