@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,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Checker Module
|
|
3
|
+
* Endpoint health checking and validation utilities
|
|
4
|
+
*
|
|
5
|
+
* Consolidates health checking across 20+ scripts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { exec, execSync } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import https from 'https';
|
|
11
|
+
import http from 'http';
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
// Load framework configuration
|
|
15
|
+
const {
|
|
16
|
+
frameworkConfig
|
|
17
|
+
} = await import('../../../src/utils/framework-config.js');
|
|
18
|
+
const timing = frameworkConfig.getTiming();
|
|
19
|
+
function makeHttpRequest(url, method = 'GET', timeout = 5000) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const protocol = url.startsWith('https:') ? https : http;
|
|
22
|
+
const req = protocol.request(url, {
|
|
23
|
+
method,
|
|
24
|
+
timeout
|
|
25
|
+
}, res => {
|
|
26
|
+
let data = '';
|
|
27
|
+
res.on('data', chunk => data += chunk);
|
|
28
|
+
res.on('end', () => {
|
|
29
|
+
resolve({
|
|
30
|
+
data,
|
|
31
|
+
statusCode: res.statusCode
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
req.on('error', err => {
|
|
36
|
+
reject(err);
|
|
37
|
+
});
|
|
38
|
+
req.on('timeout', () => {
|
|
39
|
+
req.destroy();
|
|
40
|
+
reject(new Error('Request timed out'));
|
|
41
|
+
});
|
|
42
|
+
req.end();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function checkHealth(url, timeout = timing.healthCheckTimeout) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const protocol = url.startsWith('https:') ? https : http;
|
|
48
|
+
const healthUrl = `${url}/health`;
|
|
49
|
+
const req = protocol.get(healthUrl, {
|
|
50
|
+
timeout
|
|
51
|
+
}, res => {
|
|
52
|
+
let data = '';
|
|
53
|
+
res.on('data', chunk => data += chunk);
|
|
54
|
+
res.on('end', () => {
|
|
55
|
+
try {
|
|
56
|
+
const response = JSON.parse(data);
|
|
57
|
+
if (response.status === 'healthy' || response.status === 'ok') {
|
|
58
|
+
resolve({
|
|
59
|
+
status: 'ok',
|
|
60
|
+
framework: response.framework,
|
|
61
|
+
message: 'Service is healthy',
|
|
62
|
+
url: healthUrl,
|
|
63
|
+
timestamp: new Date().toISOString()
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
resolve({
|
|
67
|
+
status: 'error',
|
|
68
|
+
message: `Service reported unhealthy: ${response.status}`,
|
|
69
|
+
url: healthUrl,
|
|
70
|
+
timestamp: new Date().toISOString()
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
resolve({
|
|
75
|
+
status: 'error',
|
|
76
|
+
message: 'Invalid JSON response from health endpoint',
|
|
77
|
+
url: healthUrl,
|
|
78
|
+
timestamp: new Date().toISOString()
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
req.on('error', err => {
|
|
84
|
+
reject({
|
|
85
|
+
status: 'error',
|
|
86
|
+
message: `Health check failed: ${err.message}`,
|
|
87
|
+
url: healthUrl,
|
|
88
|
+
timestamp: new Date().toISOString()
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
req.on('timeout', () => {
|
|
92
|
+
req.destroy();
|
|
93
|
+
reject({
|
|
94
|
+
status: 'error',
|
|
95
|
+
message: 'Health check timed out',
|
|
96
|
+
url: healthUrl,
|
|
97
|
+
timestamp: new Date().toISOString()
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
export async function waitForDeployment(url, maxWaitTime = timing.deploymentTimeout, interval = timing.deploymentInterval) {
|
|
103
|
+
const startTime = Date.now();
|
|
104
|
+
let attempts = 0;
|
|
105
|
+
console.log(`⏳ Waiting for deployment at ${url}...`);
|
|
106
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
107
|
+
try {
|
|
108
|
+
attempts++;
|
|
109
|
+
console.log(` 🔍 Attempt ${attempts}: Checking health...`);
|
|
110
|
+
const health = await checkHealth(url);
|
|
111
|
+
if (health.status === 'ok') {
|
|
112
|
+
console.log(` ✅ Deployment active! Framework: ${health.framework?.name || 'Unknown'}`);
|
|
113
|
+
return health;
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.log(` ⏳ Not ready yet: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Deployment verification timed out after ${maxWaitTime}ms (${attempts} attempts)`);
|
|
121
|
+
}
|
|
122
|
+
export async function validateEndpoints(baseUrl, endpoints = [], timeout = timing.endpointValidationTimeout) {
|
|
123
|
+
console.log(`🔍 Validating ${endpoints.length} endpoints...`);
|
|
124
|
+
const results = [];
|
|
125
|
+
for (const endpoint of endpoints) {
|
|
126
|
+
try {
|
|
127
|
+
const url = endpoint.startsWith('http') ? endpoint : `${baseUrl}${endpoint}`;
|
|
128
|
+
console.log(` Testing: ${endpoint}`);
|
|
129
|
+
const result = await makeHttpRequest(url, 'GET', timeout);
|
|
130
|
+
results.push({
|
|
131
|
+
endpoint,
|
|
132
|
+
url,
|
|
133
|
+
status: 'ok',
|
|
134
|
+
response: result.data.substring(0, 200) + (result.data.length > 200 ? '...' : '')
|
|
135
|
+
});
|
|
136
|
+
console.log(` ✅ OK`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
results.push({
|
|
139
|
+
endpoint,
|
|
140
|
+
url: endpoint.startsWith('http') ? endpoint : `${baseUrl}${endpoint}`,
|
|
141
|
+
status: 'failed',
|
|
142
|
+
error: error.message
|
|
143
|
+
});
|
|
144
|
+
console.log(` ❌ Failed: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const successful = results.filter(r => r.status === 'ok').length;
|
|
148
|
+
const failed = results.filter(r => r.status === 'failed').length;
|
|
149
|
+
console.log(`📊 Results: ${successful} passed, ${failed} failed`);
|
|
150
|
+
return {
|
|
151
|
+
summary: {
|
|
152
|
+
successful,
|
|
153
|
+
failed,
|
|
154
|
+
total: results.length
|
|
155
|
+
},
|
|
156
|
+
results,
|
|
157
|
+
allPassed: failed === 0
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export async function quickHealthCheck(url) {
|
|
161
|
+
try {
|
|
162
|
+
const health = await checkHealth(url, 5000);
|
|
163
|
+
return {
|
|
164
|
+
isHealthy: true,
|
|
165
|
+
details: health
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
isHealthy: false,
|
|
170
|
+
error: error.message
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export async function comprehensiveHealthCheck(url) {
|
|
175
|
+
console.log(`🏥 Running comprehensive health check for ${url}...`);
|
|
176
|
+
const checks = {
|
|
177
|
+
basic: {
|
|
178
|
+
endpoint: '/health',
|
|
179
|
+
description: 'Basic health check'
|
|
180
|
+
},
|
|
181
|
+
auth: {
|
|
182
|
+
endpoint: '/auth/magic-link',
|
|
183
|
+
description: 'Authentication endpoint',
|
|
184
|
+
method: 'POST'
|
|
185
|
+
},
|
|
186
|
+
users: {
|
|
187
|
+
endpoint: '/users/profile',
|
|
188
|
+
description: 'User profile endpoint',
|
|
189
|
+
requiresAuth: true
|
|
190
|
+
},
|
|
191
|
+
framework: {
|
|
192
|
+
endpoint: '/framework/models',
|
|
193
|
+
description: 'Framework models'
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const results = {};
|
|
197
|
+
for (const [name, config] of Object.entries(checks)) {
|
|
198
|
+
try {
|
|
199
|
+
console.log(` Testing ${config.description}...`);
|
|
200
|
+
if (config.requiresAuth) {
|
|
201
|
+
console.log(` ⏭️ Skipped (requires authentication)`);
|
|
202
|
+
results[name] = {
|
|
203
|
+
status: 'skipped',
|
|
204
|
+
reason: 'requires authentication'
|
|
205
|
+
};
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const endpoint = `${url}${config.endpoint}`;
|
|
209
|
+
const method = config.method || 'GET';
|
|
210
|
+
const response = await makeHttpRequest(endpoint, method, 5000);
|
|
211
|
+
results[name] = {
|
|
212
|
+
status: 'ok',
|
|
213
|
+
endpoint: config.endpoint,
|
|
214
|
+
response: response.data.substring(0, 100) + (response.data.length > 100 ? '...' : '')
|
|
215
|
+
};
|
|
216
|
+
console.log(` ✅ ${config.description} - OK`);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
results[name] = {
|
|
219
|
+
status: 'failed',
|
|
220
|
+
endpoint: config.endpoint,
|
|
221
|
+
error: error.message
|
|
222
|
+
};
|
|
223
|
+
console.log(` ❌ ${config.description} - ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const passed = Object.values(results).filter(r => r.status === 'ok').length;
|
|
227
|
+
const total = Object.keys(results).length;
|
|
228
|
+
console.log(`📊 Health check complete: ${passed}/${total} checks passed`);
|
|
229
|
+
return {
|
|
230
|
+
summary: {
|
|
231
|
+
passed,
|
|
232
|
+
total,
|
|
233
|
+
success: passed === total
|
|
234
|
+
},
|
|
235
|
+
results,
|
|
236
|
+
timestamp: new Date().toISOString()
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
export function formatHealthReport(healthData) {
|
|
240
|
+
const lines = ['🏥 HEALTH REPORT', '===============', '', `🕐 Timestamp: ${healthData.timestamp || new Date().toISOString()}`, `🌐 URL: ${healthData.url || 'Unknown'}`, `📊 Status: ${healthData.status || 'Unknown'}`];
|
|
241
|
+
if (healthData.framework) {
|
|
242
|
+
lines.push('');
|
|
243
|
+
lines.push('🏗️ Framework Details:');
|
|
244
|
+
lines.push(` 📦 Name: ${healthData.framework.name || 'Unknown'}`);
|
|
245
|
+
lines.push(` 📊 Models: ${healthData.framework.models?.length || 0}`);
|
|
246
|
+
lines.push(` 🛣️ Routes: ${healthData.framework.routes?.length || 0}`);
|
|
247
|
+
lines.push(` 🔧 Modules: ${healthData.framework.modules?.length || 0}`);
|
|
248
|
+
}
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Manager
|
|
3
|
+
* Implements memory leak prevention and cleanup routines for long-running processes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class MemoryManager {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.config = {
|
|
9
|
+
gcInterval: options.gcInterval || 300000,
|
|
10
|
+
// 5 minutes
|
|
11
|
+
memoryThreshold: options.memoryThreshold || 0.8,
|
|
12
|
+
// 80% of heap
|
|
13
|
+
maxHeapSize: options.maxHeapSize || 512 * 1024 * 1024,
|
|
14
|
+
// 512MB
|
|
15
|
+
enableGcHints: options.enableGcHints !== false,
|
|
16
|
+
cleanupInterval: options.cleanupInterval || 60000,
|
|
17
|
+
// 1 minute
|
|
18
|
+
leakDetection: options.leakDetection !== false,
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
this.gcIntervalId = null;
|
|
22
|
+
this.cleanupIntervalId = null;
|
|
23
|
+
this.memoryStats = [];
|
|
24
|
+
this.eventListeners = new Set();
|
|
25
|
+
this.timers = new Set();
|
|
26
|
+
this.intervals = new Set();
|
|
27
|
+
this.isMonitoring = false;
|
|
28
|
+
|
|
29
|
+
// Bind methods to preserve context
|
|
30
|
+
this.gcCallback = this.gcCallback.bind(this);
|
|
31
|
+
this.cleanupCallback = this.cleanupCallback.bind(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start memory monitoring and cleanup
|
|
36
|
+
*/
|
|
37
|
+
startMonitoring() {
|
|
38
|
+
if (this.isMonitoring) return;
|
|
39
|
+
this.isMonitoring = true;
|
|
40
|
+
if (this.config.enableGcHints) {
|
|
41
|
+
this.gcIntervalId = setInterval(this.gcCallback, this.config.gcInterval);
|
|
42
|
+
this.gcIntervalId.unref?.(); // Don't keep process alive
|
|
43
|
+
}
|
|
44
|
+
this.cleanupIntervalId = setInterval(this.cleanupCallback, this.config.cleanupInterval);
|
|
45
|
+
this.cleanupIntervalId.unref?.();
|
|
46
|
+
|
|
47
|
+
// Register process cleanup handlers
|
|
48
|
+
this.registerProcessHandlers();
|
|
49
|
+
console.log('🧠 Memory monitoring started');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Stop memory monitoring
|
|
54
|
+
*/
|
|
55
|
+
stopMonitoring() {
|
|
56
|
+
if (!this.isMonitoring) return;
|
|
57
|
+
this.isMonitoring = false;
|
|
58
|
+
if (this.gcIntervalId) {
|
|
59
|
+
clearInterval(this.gcIntervalId);
|
|
60
|
+
this.gcIntervalId = null;
|
|
61
|
+
}
|
|
62
|
+
if (this.cleanupIntervalId) {
|
|
63
|
+
clearInterval(this.cleanupIntervalId);
|
|
64
|
+
this.cleanupIntervalId = null;
|
|
65
|
+
}
|
|
66
|
+
this.unregisterProcessHandlers();
|
|
67
|
+
console.log('🧠 Memory monitoring stopped');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Garbage collection callback
|
|
72
|
+
*/
|
|
73
|
+
gcCallback() {
|
|
74
|
+
const memUsage = process.memoryUsage();
|
|
75
|
+
|
|
76
|
+
// Store memory stats for trend analysis
|
|
77
|
+
this.memoryStats.push({
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
...memUsage
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Keep only last 100 readings
|
|
83
|
+
if (this.memoryStats.length > 100) {
|
|
84
|
+
this.memoryStats.shift();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check memory thresholds
|
|
88
|
+
const heapUsagePercent = memUsage.heapUsed / memUsage.heapTotal;
|
|
89
|
+
if (heapUsagePercent > this.config.memoryThreshold) {
|
|
90
|
+
console.warn(`⚠️ High memory usage detected: ${(heapUsagePercent * 100).toFixed(1)}%`);
|
|
91
|
+
|
|
92
|
+
// Force garbage collection if available
|
|
93
|
+
if (global.gc) {
|
|
94
|
+
console.log('🗑️ Running forced garbage collection');
|
|
95
|
+
global.gc();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Run cleanup routines
|
|
99
|
+
this.runCleanupRoutines();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for memory leaks
|
|
103
|
+
if (this.config.leakDetection) {
|
|
104
|
+
this.detectMemoryLeaks();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cleanup callback
|
|
110
|
+
*/
|
|
111
|
+
cleanupCallback() {
|
|
112
|
+
this.runCleanupRoutines();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Run cleanup routines
|
|
117
|
+
*/
|
|
118
|
+
runCleanupRoutines() {
|
|
119
|
+
// Clear expired cache entries
|
|
120
|
+
this.clearExpiredCache();
|
|
121
|
+
|
|
122
|
+
// Clean up event listeners
|
|
123
|
+
this.cleanupEventListeners();
|
|
124
|
+
|
|
125
|
+
// Clean up timers and intervals
|
|
126
|
+
this.cleanupTimers();
|
|
127
|
+
|
|
128
|
+
// Force garbage collection hint
|
|
129
|
+
if (global.gc) {
|
|
130
|
+
global.gc();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Clear expired cache entries
|
|
136
|
+
*/
|
|
137
|
+
clearExpiredCache() {
|
|
138
|
+
// This would integrate with cache managers
|
|
139
|
+
// For now, just hint that caches should be cleaned
|
|
140
|
+
if (typeof global !== 'undefined' && global.cacheManagers) {
|
|
141
|
+
global.cacheManagers.forEach(manager => {
|
|
142
|
+
if (typeof manager.cleanup === 'function') {
|
|
143
|
+
manager.cleanup();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Clean up event listeners
|
|
151
|
+
*/
|
|
152
|
+
cleanupEventListeners() {
|
|
153
|
+
// Remove listeners that are no longer needed
|
|
154
|
+
// This is a placeholder - actual implementation would track listeners
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clean up timers and intervals
|
|
159
|
+
*/
|
|
160
|
+
cleanupTimers() {
|
|
161
|
+
// Clear any timers that may have been leaked
|
|
162
|
+
// This is a placeholder - actual implementation would track timers
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Detect memory leaks
|
|
167
|
+
*/
|
|
168
|
+
detectMemoryLeaks() {
|
|
169
|
+
if (this.memoryStats.length < 10) return;
|
|
170
|
+
const recent = this.memoryStats.slice(-10);
|
|
171
|
+
const older = this.memoryStats.slice(-20, -10);
|
|
172
|
+
if (recent.length === 0 || older.length === 0) return;
|
|
173
|
+
const recentAvg = recent.reduce((sum, stat) => sum + stat.heapUsed, 0) / recent.length;
|
|
174
|
+
const olderAvg = older.reduce((sum, stat) => sum + stat.heapUsed, 0) / older.length;
|
|
175
|
+
const growthRate = (recentAvg - olderAvg) / olderAvg;
|
|
176
|
+
if (growthRate > 0.1) {
|
|
177
|
+
// 10% growth
|
|
178
|
+
console.warn(`🚨 Potential memory leak detected: ${(growthRate * 100).toFixed(1)}% growth over time`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Register process signal handlers
|
|
184
|
+
*/
|
|
185
|
+
registerProcessHandlers() {
|
|
186
|
+
// Handle graceful shutdown
|
|
187
|
+
process.on('SIGTERM', () => this.gracefulShutdown('SIGTERM'));
|
|
188
|
+
process.on('SIGINT', () => this.gracefulShutdown('SIGINT'));
|
|
189
|
+
|
|
190
|
+
// Handle uncaught exceptions
|
|
191
|
+
process.on('uncaughtException', error => {
|
|
192
|
+
console.error('💥 Uncaught exception:', error);
|
|
193
|
+
this.gracefulShutdown('uncaughtException');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Handle unhandled promise rejections
|
|
197
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
198
|
+
console.error('💥 Unhandled rejection at:', promise, 'reason:', reason);
|
|
199
|
+
this.gracefulShutdown('unhandledRejection');
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Unregister process signal handlers
|
|
205
|
+
*/
|
|
206
|
+
unregisterProcessHandlers() {
|
|
207
|
+
process.removeAllListeners('SIGTERM');
|
|
208
|
+
process.removeAllListeners('SIGINT');
|
|
209
|
+
process.removeAllListeners('uncaughtException');
|
|
210
|
+
process.removeAllListeners('unhandledRejection');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Graceful shutdown
|
|
215
|
+
*/
|
|
216
|
+
gracefulShutdown(reason) {
|
|
217
|
+
console.log(`🛑 Graceful shutdown initiated: ${reason}`);
|
|
218
|
+
|
|
219
|
+
// Stop monitoring
|
|
220
|
+
this.stopMonitoring();
|
|
221
|
+
|
|
222
|
+
// Run final cleanup
|
|
223
|
+
this.runCleanupRoutines();
|
|
224
|
+
|
|
225
|
+
// Force final garbage collection
|
|
226
|
+
if (global.gc) {
|
|
227
|
+
global.gc();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Exit gracefully
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Track an event listener for cleanup
|
|
236
|
+
*/
|
|
237
|
+
trackEventListener(emitter, event, listener) {
|
|
238
|
+
const tracked = {
|
|
239
|
+
emitter,
|
|
240
|
+
event,
|
|
241
|
+
listener
|
|
242
|
+
};
|
|
243
|
+
this.eventListeners.add(tracked);
|
|
244
|
+
|
|
245
|
+
// Return cleanup function
|
|
246
|
+
return () => {
|
|
247
|
+
emitter.removeListener(event, listener);
|
|
248
|
+
this.eventListeners.delete(tracked);
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Track a timer for cleanup
|
|
254
|
+
*/
|
|
255
|
+
trackTimer(timerId) {
|
|
256
|
+
this.timers.add(timerId);
|
|
257
|
+
|
|
258
|
+
// Return cleanup function
|
|
259
|
+
return () => {
|
|
260
|
+
clearTimeout(timerId);
|
|
261
|
+
this.timers.delete(timerId);
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Track an interval for cleanup
|
|
267
|
+
*/
|
|
268
|
+
trackInterval(intervalId) {
|
|
269
|
+
this.intervals.add(intervalId);
|
|
270
|
+
|
|
271
|
+
// Return cleanup function
|
|
272
|
+
return () => {
|
|
273
|
+
clearInterval(intervalId);
|
|
274
|
+
this.intervals.delete(intervalId);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get memory statistics
|
|
280
|
+
*/
|
|
281
|
+
getMemoryStats() {
|
|
282
|
+
const memUsage = process.memoryUsage();
|
|
283
|
+
return {
|
|
284
|
+
current: {
|
|
285
|
+
rss: memUsage.rss / 1024 / 1024,
|
|
286
|
+
// MB
|
|
287
|
+
heapUsed: memUsage.heapUsed / 1024 / 1024,
|
|
288
|
+
// MB
|
|
289
|
+
heapTotal: memUsage.heapTotal / 1024 / 1024,
|
|
290
|
+
// MB
|
|
291
|
+
external: memUsage.external / 1024 / 1024,
|
|
292
|
+
// MB
|
|
293
|
+
heapUsagePercent: memUsage.heapUsed / memUsage.heapTotal * 100
|
|
294
|
+
},
|
|
295
|
+
history: this.memoryStats.slice(-10),
|
|
296
|
+
// Last 10 readings
|
|
297
|
+
thresholds: {
|
|
298
|
+
memoryThreshold: this.config.memoryThreshold * 100,
|
|
299
|
+
maxHeapSize: this.config.maxHeapSize / 1024 / 1024
|
|
300
|
+
},
|
|
301
|
+
tracking: {
|
|
302
|
+
eventListeners: this.eventListeners.size,
|
|
303
|
+
timers: this.timers.size,
|
|
304
|
+
intervals: this.intervals.size
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Force garbage collection (if available)
|
|
311
|
+
*/
|
|
312
|
+
forceGc() {
|
|
313
|
+
if (global.gc) {
|
|
314
|
+
console.log('🗑️ Forced garbage collection');
|
|
315
|
+
global.gc();
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create a memory-safe interval
|
|
323
|
+
*/
|
|
324
|
+
createSafeInterval(callback, delay) {
|
|
325
|
+
const intervalId = setInterval(() => {
|
|
326
|
+
try {
|
|
327
|
+
callback();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error('Error in safe interval:', error);
|
|
330
|
+
// Could remove the interval if it keeps failing
|
|
331
|
+
}
|
|
332
|
+
}, delay);
|
|
333
|
+
intervalId.unref?.();
|
|
334
|
+
return this.trackInterval(intervalId);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Create a memory-safe timeout
|
|
339
|
+
*/
|
|
340
|
+
createSafeTimeout(callback, delay) {
|
|
341
|
+
const timeoutId = setTimeout(() => {
|
|
342
|
+
try {
|
|
343
|
+
callback();
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('Error in safe timeout:', error);
|
|
346
|
+
}
|
|
347
|
+
}, delay);
|
|
348
|
+
timeoutId.unref?.();
|
|
349
|
+
return this.trackTimer(timeoutId);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Global memory manager instance
|
|
354
|
+
let globalMemoryManager = null;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get the global memory manager instance
|
|
358
|
+
*/
|
|
359
|
+
export function getMemoryManager(options = {}) {
|
|
360
|
+
if (!globalMemoryManager) {
|
|
361
|
+
globalMemoryManager = new MemoryManager(options);
|
|
362
|
+
}
|
|
363
|
+
return globalMemoryManager;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Start global memory monitoring
|
|
368
|
+
*/
|
|
369
|
+
export function startMemoryMonitoring(options = {}) {
|
|
370
|
+
const manager = getMemoryManager(options);
|
|
371
|
+
manager.startMonitoring();
|
|
372
|
+
return manager;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Stop global memory monitoring
|
|
377
|
+
*/
|
|
378
|
+
export function stopMemoryMonitoring() {
|
|
379
|
+
if (globalMemoryManager) {
|
|
380
|
+
globalMemoryManager.stopMonitoring();
|
|
381
|
+
}
|
|
382
|
+
}
|