mastercontroller 1.3.10 → 1.3.13
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/.claude/settings.local.json +4 -1
- package/.eslintrc.json +50 -0
- package/.github/workflows/ci.yml +317 -0
- package/.prettierrc +10 -0
- package/DEPLOYMENT.md +956 -0
- package/MasterControl.js +98 -16
- package/MasterRequest.js +42 -1
- package/MasterRouter.js +15 -5
- package/README.md +485 -28
- package/SENIOR_ENGINEER_AUDIT.md +2477 -0
- package/VERIFICATION_CHECKLIST.md +726 -0
- package/error/README.md +2452 -0
- package/monitoring/HealthCheck.js +347 -0
- package/monitoring/PrometheusExporter.js +416 -0
- package/package.json +64 -11
- package/security/MasterValidator.js +140 -10
- package/security/adapters/RedisCSRFStore.js +428 -0
- package/security/adapters/RedisRateLimiter.js +462 -0
- package/security/adapters/RedisSessionStore.js +476 -0
- package/FIXES_APPLIED.md +0 -378
- package/error/ErrorBoundary.js +0 -353
- package/error/HydrationMismatch.js +0 -265
- package/error/MasterError.js +0 -240
- package/error/MasterError.js.tmp +0 -0
- package/error/MasterErrorRenderer.js +0 -536
- package/error/MasterErrorRenderer.js.tmp +0 -0
- package/error/SSRErrorHandler.js +0 -273
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthCheck - Production-ready health monitoring endpoint
|
|
3
|
+
* Version: 1.0.0
|
|
4
|
+
*
|
|
5
|
+
* Provides a /_health endpoint for load balancers, orchestrators (Kubernetes, Docker Swarm),
|
|
6
|
+
* and monitoring tools (Datadog, New Relic, etc.)
|
|
7
|
+
*
|
|
8
|
+
* Usage in MasterControl pipeline:
|
|
9
|
+
*
|
|
10
|
+
* const { healthCheck } = require('./monitoring/HealthCheck');
|
|
11
|
+
* master.pipeline.use(healthCheck.middleware());
|
|
12
|
+
*
|
|
13
|
+
* Health check endpoint: GET /_health
|
|
14
|
+
* Returns: { status: 'healthy', uptime, memory, version, timestamp }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
19
|
+
|
|
20
|
+
class HealthCheck {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.options = {
|
|
23
|
+
endpoint: options.endpoint || '/_health',
|
|
24
|
+
includeDetails: options.includeDetails !== false,
|
|
25
|
+
customChecks: options.customChecks || [],
|
|
26
|
+
timeout: options.timeout || 5000, // 5 seconds
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.startTime = Date.now();
|
|
31
|
+
this.version = options.version || require('../package.json').version;
|
|
32
|
+
this.customHealthChecks = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add custom health check function
|
|
37
|
+
* @param {string} name - Name of the health check
|
|
38
|
+
* @param {Function} checkFn - Async function that returns { healthy: boolean, details?: any }
|
|
39
|
+
*/
|
|
40
|
+
addCheck(name, checkFn) {
|
|
41
|
+
if (typeof checkFn !== 'function') {
|
|
42
|
+
throw new Error('Health check must be a function');
|
|
43
|
+
}
|
|
44
|
+
this.customHealthChecks.push({ name, checkFn });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Middleware for MasterPipeline
|
|
49
|
+
*/
|
|
50
|
+
middleware() {
|
|
51
|
+
const self = this;
|
|
52
|
+
|
|
53
|
+
return async (ctx, next) => {
|
|
54
|
+
const requestPath = ctx.request.url.split('?')[0];
|
|
55
|
+
|
|
56
|
+
// Only handle health check endpoint
|
|
57
|
+
if (requestPath !== self.options.endpoint) {
|
|
58
|
+
return await next();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Log health check request
|
|
62
|
+
logger.debug({
|
|
63
|
+
code: 'MC_HEALTH_CHECK',
|
|
64
|
+
message: 'Health check requested',
|
|
65
|
+
ip: ctx.request.connection.remoteAddress
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const health = await self.check();
|
|
70
|
+
|
|
71
|
+
// Set response headers
|
|
72
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
73
|
+
ctx.response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
74
|
+
ctx.response.setHeader('X-Content-Type-Options', 'nosniff');
|
|
75
|
+
|
|
76
|
+
// Set status code based on health
|
|
77
|
+
ctx.response.statusCode = health.status === 'healthy' ? 200 : 503;
|
|
78
|
+
|
|
79
|
+
// Send response
|
|
80
|
+
ctx.response.end(JSON.stringify(health, null, 2));
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error({
|
|
84
|
+
code: 'MC_HEALTH_CHECK_ERROR',
|
|
85
|
+
message: 'Health check failed',
|
|
86
|
+
error: error.message,
|
|
87
|
+
stack: error.stack
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Return unhealthy status
|
|
91
|
+
ctx.response.statusCode = 503;
|
|
92
|
+
ctx.response.setHeader('Content-Type', 'application/json');
|
|
93
|
+
ctx.response.end(JSON.stringify({
|
|
94
|
+
status: 'unhealthy',
|
|
95
|
+
error: error.message,
|
|
96
|
+
timestamp: new Date().toISOString()
|
|
97
|
+
}, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Don't call next() - this is a terminal endpoint
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Perform health check
|
|
106
|
+
*/
|
|
107
|
+
async check() {
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Basic system metrics
|
|
112
|
+
const uptime = process.uptime();
|
|
113
|
+
const memory = process.memoryUsage();
|
|
114
|
+
const cpuUsage = process.cpuUsage();
|
|
115
|
+
|
|
116
|
+
// System information
|
|
117
|
+
const systemInfo = {
|
|
118
|
+
platform: process.platform,
|
|
119
|
+
nodeVersion: process.version,
|
|
120
|
+
arch: process.arch,
|
|
121
|
+
cpus: os.cpus().length,
|
|
122
|
+
totalMemory: os.totalmem(),
|
|
123
|
+
freeMemory: os.freemem(),
|
|
124
|
+
loadAverage: os.loadavg()
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Run custom health checks
|
|
128
|
+
const customChecks = {};
|
|
129
|
+
let allHealthy = true;
|
|
130
|
+
|
|
131
|
+
for (const check of this.customHealthChecks) {
|
|
132
|
+
try {
|
|
133
|
+
const result = await Promise.race([
|
|
134
|
+
check.checkFn(),
|
|
135
|
+
new Promise((_, reject) =>
|
|
136
|
+
setTimeout(() => reject(new Error('Health check timeout')), this.options.timeout)
|
|
137
|
+
)
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
customChecks[check.name] = result;
|
|
141
|
+
|
|
142
|
+
if (result.healthy === false) {
|
|
143
|
+
allHealthy = false;
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
customChecks[check.name] = {
|
|
147
|
+
healthy: false,
|
|
148
|
+
error: error.message
|
|
149
|
+
};
|
|
150
|
+
allHealthy = false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Memory health check (fail if >90% memory used)
|
|
155
|
+
const memoryUsagePercent = (memory.heapUsed / memory.heapTotal) * 100;
|
|
156
|
+
const memoryHealthy = memoryUsagePercent < 90;
|
|
157
|
+
|
|
158
|
+
if (!memoryHealthy) {
|
|
159
|
+
allHealthy = false;
|
|
160
|
+
logger.warn({
|
|
161
|
+
code: 'MC_HEALTH_MEMORY_HIGH',
|
|
162
|
+
message: 'Memory usage critically high',
|
|
163
|
+
memoryUsagePercent: memoryUsagePercent.toFixed(2),
|
|
164
|
+
heapUsed: memory.heapUsed,
|
|
165
|
+
heapTotal: memory.heapTotal
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build response
|
|
170
|
+
const response = {
|
|
171
|
+
status: allHealthy && memoryHealthy ? 'healthy' : 'degraded',
|
|
172
|
+
uptime: Math.floor(uptime),
|
|
173
|
+
version: this.version,
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
responseTime: Date.now() - startTime
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Add detailed metrics if enabled
|
|
179
|
+
if (this.options.includeDetails) {
|
|
180
|
+
response.memory = {
|
|
181
|
+
heapUsed: memory.heapUsed,
|
|
182
|
+
heapTotal: memory.heapTotal,
|
|
183
|
+
rss: memory.rss,
|
|
184
|
+
external: memory.external,
|
|
185
|
+
usagePercent: memoryUsagePercent.toFixed(2)
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
response.cpu = {
|
|
189
|
+
user: cpuUsage.user,
|
|
190
|
+
system: cpuUsage.system
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
response.system = systemInfo;
|
|
194
|
+
|
|
195
|
+
if (Object.keys(customChecks).length > 0) {
|
|
196
|
+
response.checks = customChecks;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return response;
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.error({
|
|
204
|
+
code: 'MC_HEALTH_CHECK_FAILURE',
|
|
205
|
+
message: 'Health check encountered error',
|
|
206
|
+
error: error.message,
|
|
207
|
+
stack: error.stack
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
status: 'unhealthy',
|
|
212
|
+
error: error.message,
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
responseTime: Date.now() - startTime
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Express/Connect compatible middleware (for compatibility)
|
|
221
|
+
*/
|
|
222
|
+
expressMiddleware() {
|
|
223
|
+
const self = this;
|
|
224
|
+
|
|
225
|
+
return async (req, res, next) => {
|
|
226
|
+
const requestPath = req.url.split('?')[0];
|
|
227
|
+
|
|
228
|
+
if (requestPath !== self.options.endpoint) {
|
|
229
|
+
return next();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const health = await self.check();
|
|
234
|
+
|
|
235
|
+
res.setHeader('Content-Type', 'application/json');
|
|
236
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
237
|
+
res.statusCode = health.status === 'healthy' ? 200 : 503;
|
|
238
|
+
res.end(JSON.stringify(health, null, 2));
|
|
239
|
+
|
|
240
|
+
} catch (error) {
|
|
241
|
+
res.statusCode = 503;
|
|
242
|
+
res.setHeader('Content-Type', 'application/json');
|
|
243
|
+
res.end(JSON.stringify({
|
|
244
|
+
status: 'unhealthy',
|
|
245
|
+
error: error.message,
|
|
246
|
+
timestamp: new Date().toISOString()
|
|
247
|
+
}, null, 2));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get uptime in seconds
|
|
254
|
+
*/
|
|
255
|
+
getUptime() {
|
|
256
|
+
return Math.floor(process.uptime());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get memory stats
|
|
261
|
+
*/
|
|
262
|
+
getMemory() {
|
|
263
|
+
return process.memoryUsage();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Manual health check (for internal use)
|
|
268
|
+
*/
|
|
269
|
+
async isHealthy() {
|
|
270
|
+
const result = await this.check();
|
|
271
|
+
return result.status === 'healthy';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Singleton instance
|
|
276
|
+
const healthCheck = new HealthCheck({
|
|
277
|
+
endpoint: '/_health',
|
|
278
|
+
includeDetails: process.env.NODE_ENV !== 'production' // Hide details in production for security
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Example custom health checks
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
// Database health check example
|
|
286
|
+
function createDatabaseCheck(db) {
|
|
287
|
+
return async () => {
|
|
288
|
+
try {
|
|
289
|
+
// Example: Check database connectivity
|
|
290
|
+
await db.ping(); // or db.query('SELECT 1')
|
|
291
|
+
return { healthy: true, details: { connected: true } };
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return { healthy: false, error: error.message };
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Redis health check example
|
|
299
|
+
function createRedisCheck(redis) {
|
|
300
|
+
return async () => {
|
|
301
|
+
try {
|
|
302
|
+
await redis.ping();
|
|
303
|
+
return { healthy: true, details: { connected: true } };
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return { healthy: false, error: error.message };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// API dependency health check example
|
|
311
|
+
function createAPIHealthCheck(apiUrl) {
|
|
312
|
+
return async () => {
|
|
313
|
+
try {
|
|
314
|
+
const https = require('https');
|
|
315
|
+
const http = require('http');
|
|
316
|
+
|
|
317
|
+
return new Promise((resolve) => {
|
|
318
|
+
const client = apiUrl.startsWith('https') ? https : http;
|
|
319
|
+
const req = client.get(apiUrl, (res) => {
|
|
320
|
+
resolve({
|
|
321
|
+
healthy: res.statusCode >= 200 && res.statusCode < 300,
|
|
322
|
+
details: { statusCode: res.statusCode }
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
req.on('error', (error) => {
|
|
327
|
+
resolve({ healthy: false, error: error.message });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
req.setTimeout(3000, () => {
|
|
331
|
+
req.destroy();
|
|
332
|
+
resolve({ healthy: false, error: 'Timeout' });
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return { healthy: false, error: error.message };
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = {
|
|
342
|
+
HealthCheck,
|
|
343
|
+
healthCheck,
|
|
344
|
+
createDatabaseCheck,
|
|
345
|
+
createRedisCheck,
|
|
346
|
+
createAPIHealthCheck
|
|
347
|
+
};
|