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,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrometheusExporter - Production-grade metrics for Prometheus monitoring
|
|
3
|
+
* Version: 1.0.0
|
|
4
|
+
*
|
|
5
|
+
* Provides a /_metrics endpoint compatible with Prometheus scraping format.
|
|
6
|
+
* Tracks HTTP request metrics, system metrics, and custom application metrics.
|
|
7
|
+
*
|
|
8
|
+
* Usage in MasterControl pipeline:
|
|
9
|
+
*
|
|
10
|
+
* const { prometheusExporter } = require('./monitoring/PrometheusExporter');
|
|
11
|
+
*
|
|
12
|
+
* // Register the middleware
|
|
13
|
+
* master.pipeline.use(prometheusExporter.middleware());
|
|
14
|
+
*
|
|
15
|
+
* Metrics endpoint: GET /_metrics
|
|
16
|
+
* Returns: Prometheus text format metrics
|
|
17
|
+
*
|
|
18
|
+
* Optional: Install prom-client for advanced features
|
|
19
|
+
* npm install prom-client --save-optional
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const { logger } = require('../error/MasterErrorLogger');
|
|
24
|
+
|
|
25
|
+
class PrometheusExporter {
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.options = {
|
|
28
|
+
endpoint: options.endpoint || '/_metrics',
|
|
29
|
+
prefix: options.prefix || 'mastercontroller_',
|
|
30
|
+
collectDefaultMetrics: options.collectDefaultMetrics !== false,
|
|
31
|
+
...options
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.startTime = Date.now();
|
|
35
|
+
|
|
36
|
+
// HTTP request metrics
|
|
37
|
+
this.httpRequestsTotal = {}; // Counter by method, path, status
|
|
38
|
+
this.httpRequestDuration = {}; // Histogram by method, path
|
|
39
|
+
this.httpRequestsInFlight = 0; // Current active requests
|
|
40
|
+
this.httpRequestSizeBytes = {}; // Histogram of request sizes
|
|
41
|
+
this.httpResponseSizeBytes = {}; // Histogram of response sizes
|
|
42
|
+
|
|
43
|
+
// Custom metrics storage
|
|
44
|
+
this.customMetrics = new Map();
|
|
45
|
+
|
|
46
|
+
// Try to load prom-client if available (optional peer dependency)
|
|
47
|
+
try {
|
|
48
|
+
this.promClient = require('prom-client');
|
|
49
|
+
this._setupPromClient();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// prom-client not installed, use simple implementation
|
|
52
|
+
this.promClient = null;
|
|
53
|
+
logger.info({
|
|
54
|
+
code: 'MC_PROMETHEUS_SIMPLE_MODE',
|
|
55
|
+
message: 'Running Prometheus exporter in simple mode (install prom-client for advanced features)'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Setup prom-client if available
|
|
62
|
+
*/
|
|
63
|
+
_setupPromClient() {
|
|
64
|
+
const { Registry, Counter, Histogram, Gauge } = this.promClient;
|
|
65
|
+
this.register = new Registry();
|
|
66
|
+
|
|
67
|
+
// HTTP request counter
|
|
68
|
+
this.httpRequestCounter = new Counter({
|
|
69
|
+
name: `${this.options.prefix}http_requests_total`,
|
|
70
|
+
help: 'Total number of HTTP requests',
|
|
71
|
+
labelNames: ['method', 'path', 'status'],
|
|
72
|
+
registers: [this.register]
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// HTTP request duration histogram
|
|
76
|
+
this.httpDurationHistogram = new Histogram({
|
|
77
|
+
name: `${this.options.prefix}http_request_duration_seconds`,
|
|
78
|
+
help: 'HTTP request duration in seconds',
|
|
79
|
+
labelNames: ['method', 'path', 'status'],
|
|
80
|
+
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10],
|
|
81
|
+
registers: [this.register]
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Active requests gauge
|
|
85
|
+
this.activeRequestsGauge = new Gauge({
|
|
86
|
+
name: `${this.options.prefix}http_requests_in_flight`,
|
|
87
|
+
help: 'Number of HTTP requests currently being processed',
|
|
88
|
+
registers: [this.register]
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Request size histogram
|
|
92
|
+
this.requestSizeHistogram = new Histogram({
|
|
93
|
+
name: `${this.options.prefix}http_request_size_bytes`,
|
|
94
|
+
help: 'HTTP request size in bytes',
|
|
95
|
+
labelNames: ['method', 'path'],
|
|
96
|
+
buckets: [100, 1000, 10000, 100000, 1000000, 10000000],
|
|
97
|
+
registers: [this.register]
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Response size histogram
|
|
101
|
+
this.responseSizeHistogram = new Histogram({
|
|
102
|
+
name: `${this.options.prefix}http_response_size_bytes`,
|
|
103
|
+
help: 'HTTP response size in bytes',
|
|
104
|
+
labelNames: ['method', 'path', 'status'],
|
|
105
|
+
buckets: [100, 1000, 10000, 100000, 1000000, 10000000],
|
|
106
|
+
registers: [this.register]
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Collect default system metrics if enabled
|
|
110
|
+
if (this.options.collectDefaultMetrics) {
|
|
111
|
+
this.promClient.collectDefaultMetrics({
|
|
112
|
+
register: this.register,
|
|
113
|
+
prefix: this.options.prefix
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Middleware for MasterPipeline - tracks all HTTP requests
|
|
120
|
+
*/
|
|
121
|
+
middleware() {
|
|
122
|
+
const self = this;
|
|
123
|
+
|
|
124
|
+
return async (ctx, next) => {
|
|
125
|
+
const requestPath = ctx.request.url.split('?')[0];
|
|
126
|
+
|
|
127
|
+
// Handle metrics endpoint
|
|
128
|
+
if (requestPath === self.options.endpoint) {
|
|
129
|
+
return await self._handleMetricsEndpoint(ctx);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Track request metrics
|
|
133
|
+
const startTime = Date.now();
|
|
134
|
+
self.httpRequestsInFlight++;
|
|
135
|
+
|
|
136
|
+
if (self.activeRequestsGauge) {
|
|
137
|
+
self.activeRequestsGauge.inc();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get request size
|
|
141
|
+
const requestSize = parseInt(ctx.request.headers['content-length'] || 0);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Continue pipeline
|
|
145
|
+
await next();
|
|
146
|
+
|
|
147
|
+
// Record metrics on success
|
|
148
|
+
const duration = (Date.now() - startTime) / 1000; // Convert to seconds
|
|
149
|
+
const method = ctx.request.method;
|
|
150
|
+
const status = ctx.response.statusCode || 200;
|
|
151
|
+
const responseSize = parseInt(ctx.response.getHeader('content-length') || 0);
|
|
152
|
+
|
|
153
|
+
self._recordRequest(method, requestPath, status, duration, requestSize, responseSize);
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Record error metrics
|
|
157
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
158
|
+
const method = ctx.request.method;
|
|
159
|
+
const status = 500;
|
|
160
|
+
|
|
161
|
+
self._recordRequest(method, requestPath, status, duration, requestSize, 0);
|
|
162
|
+
|
|
163
|
+
throw error; // Re-throw for error handling middleware
|
|
164
|
+
|
|
165
|
+
} finally {
|
|
166
|
+
self.httpRequestsInFlight--;
|
|
167
|
+
|
|
168
|
+
if (self.activeRequestsGauge) {
|
|
169
|
+
self.activeRequestsGauge.dec();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Record HTTP request metrics
|
|
177
|
+
*/
|
|
178
|
+
_recordRequest(method, path, status, duration, requestSize, responseSize) {
|
|
179
|
+
// Normalize path (remove IDs, etc.) for better grouping
|
|
180
|
+
const normalizedPath = this._normalizePath(path);
|
|
181
|
+
|
|
182
|
+
if (this.promClient) {
|
|
183
|
+
// Use prom-client
|
|
184
|
+
this.httpRequestCounter.inc({ method, path: normalizedPath, status });
|
|
185
|
+
this.httpDurationHistogram.observe({ method, path: normalizedPath, status }, duration);
|
|
186
|
+
|
|
187
|
+
if (requestSize > 0) {
|
|
188
|
+
this.requestSizeHistogram.observe({ method, path: normalizedPath }, requestSize);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (responseSize > 0) {
|
|
192
|
+
this.responseSizeHistogram.observe({ method, path: normalizedPath, status }, responseSize);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
} else {
|
|
196
|
+
// Simple implementation
|
|
197
|
+
const key = `${method}:${normalizedPath}:${status}`;
|
|
198
|
+
|
|
199
|
+
if (!this.httpRequestsTotal[key]) {
|
|
200
|
+
this.httpRequestsTotal[key] = { count: 0, totalDuration: 0 };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.httpRequestsTotal[key].count++;
|
|
204
|
+
this.httpRequestsTotal[key].totalDuration += duration;
|
|
205
|
+
|
|
206
|
+
if (!this.httpRequestDuration[key]) {
|
|
207
|
+
this.httpRequestDuration[key] = [];
|
|
208
|
+
}
|
|
209
|
+
this.httpRequestDuration[key].push(duration);
|
|
210
|
+
|
|
211
|
+
// Keep only last 1000 durations per endpoint to prevent memory leak
|
|
212
|
+
if (this.httpRequestDuration[key].length > 1000) {
|
|
213
|
+
this.httpRequestDuration[key].shift();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Normalize path for metrics (remove IDs, hashes, etc.)
|
|
220
|
+
*/
|
|
221
|
+
_normalizePath(path) {
|
|
222
|
+
return path
|
|
223
|
+
.replace(/\/[0-9a-f]{24}$/i, '/:id') // MongoDB ObjectId
|
|
224
|
+
.replace(/\/[0-9]+$/, '/:id') // Numeric IDs
|
|
225
|
+
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, '/:uuid') // UUIDs
|
|
226
|
+
.replace(/\?.*$/, ''); // Remove query params
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Handle /_metrics endpoint
|
|
231
|
+
*/
|
|
232
|
+
async _handleMetricsEndpoint(ctx) {
|
|
233
|
+
try {
|
|
234
|
+
logger.debug({
|
|
235
|
+
code: 'MC_METRICS_SCRAPE',
|
|
236
|
+
message: 'Metrics scrape requested',
|
|
237
|
+
ip: ctx.request.connection.remoteAddress
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
let metricsText;
|
|
241
|
+
|
|
242
|
+
if (this.promClient) {
|
|
243
|
+
// Use prom-client to generate metrics
|
|
244
|
+
metricsText = await this.register.metrics();
|
|
245
|
+
} else {
|
|
246
|
+
// Simple implementation
|
|
247
|
+
metricsText = this._generateSimpleMetrics();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Set response headers
|
|
251
|
+
ctx.response.setHeader('Content-Type', 'text/plain; version=0.0.4');
|
|
252
|
+
ctx.response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
253
|
+
ctx.response.statusCode = 200;
|
|
254
|
+
ctx.response.end(metricsText);
|
|
255
|
+
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logger.error({
|
|
258
|
+
code: 'MC_METRICS_ERROR',
|
|
259
|
+
message: 'Metrics generation failed',
|
|
260
|
+
error: error.message,
|
|
261
|
+
stack: error.stack
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
ctx.response.statusCode = 500;
|
|
265
|
+
ctx.response.setHeader('Content-Type', 'text/plain');
|
|
266
|
+
ctx.response.end('# Error generating metrics\n');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generate simple metrics text (when prom-client not available)
|
|
272
|
+
*/
|
|
273
|
+
_generateSimpleMetrics() {
|
|
274
|
+
const lines = [];
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
|
|
277
|
+
// Add header
|
|
278
|
+
lines.push('# MasterController Metrics (Simple Mode)');
|
|
279
|
+
lines.push('# Install prom-client for advanced metrics');
|
|
280
|
+
lines.push('');
|
|
281
|
+
|
|
282
|
+
// Process uptime
|
|
283
|
+
const uptime = Math.floor((now - this.startTime) / 1000);
|
|
284
|
+
lines.push('# HELP process_uptime_seconds Process uptime in seconds');
|
|
285
|
+
lines.push('# TYPE process_uptime_seconds gauge');
|
|
286
|
+
lines.push(`process_uptime_seconds ${uptime}`);
|
|
287
|
+
lines.push('');
|
|
288
|
+
|
|
289
|
+
// HTTP requests total
|
|
290
|
+
lines.push('# HELP mastercontroller_http_requests_total Total number of HTTP requests');
|
|
291
|
+
lines.push('# TYPE mastercontroller_http_requests_total counter');
|
|
292
|
+
for (const [key, data] of Object.entries(this.httpRequestsTotal)) {
|
|
293
|
+
const [method, path, status] = key.split(':');
|
|
294
|
+
lines.push(`mastercontroller_http_requests_total{method="${method}",path="${path}",status="${status}"} ${data.count}`);
|
|
295
|
+
}
|
|
296
|
+
lines.push('');
|
|
297
|
+
|
|
298
|
+
// HTTP request duration (avg)
|
|
299
|
+
lines.push('# HELP mastercontroller_http_request_duration_seconds_avg Average HTTP request duration');
|
|
300
|
+
lines.push('# TYPE mastercontroller_http_request_duration_seconds_avg gauge');
|
|
301
|
+
for (const [key, data] of Object.entries(this.httpRequestsTotal)) {
|
|
302
|
+
const [method, path, status] = key.split(':');
|
|
303
|
+
const avgDuration = data.totalDuration / data.count;
|
|
304
|
+
lines.push(`mastercontroller_http_request_duration_seconds_avg{method="${method}",path="${path}",status="${status}"} ${avgDuration.toFixed(6)}`);
|
|
305
|
+
}
|
|
306
|
+
lines.push('');
|
|
307
|
+
|
|
308
|
+
// Active requests
|
|
309
|
+
lines.push('# HELP mastercontroller_http_requests_in_flight Number of HTTP requests in flight');
|
|
310
|
+
lines.push('# TYPE mastercontroller_http_requests_in_flight gauge');
|
|
311
|
+
lines.push(`mastercontroller_http_requests_in_flight ${this.httpRequestsInFlight}`);
|
|
312
|
+
lines.push('');
|
|
313
|
+
|
|
314
|
+
// Memory metrics
|
|
315
|
+
const memory = process.memoryUsage();
|
|
316
|
+
lines.push('# HELP process_memory_heap_used_bytes Heap memory used in bytes');
|
|
317
|
+
lines.push('# TYPE process_memory_heap_used_bytes gauge');
|
|
318
|
+
lines.push(`process_memory_heap_used_bytes ${memory.heapUsed}`);
|
|
319
|
+
lines.push('');
|
|
320
|
+
|
|
321
|
+
lines.push('# HELP process_memory_heap_total_bytes Total heap memory in bytes');
|
|
322
|
+
lines.push('# TYPE process_memory_heap_total_bytes gauge');
|
|
323
|
+
lines.push(`process_memory_heap_total_bytes ${memory.heapTotal}`);
|
|
324
|
+
lines.push('');
|
|
325
|
+
|
|
326
|
+
lines.push('# HELP process_memory_rss_bytes Resident set size in bytes');
|
|
327
|
+
lines.push('# TYPE process_memory_rss_bytes gauge');
|
|
328
|
+
lines.push(`process_memory_rss_bytes ${memory.rss}`);
|
|
329
|
+
lines.push('');
|
|
330
|
+
|
|
331
|
+
// CPU metrics
|
|
332
|
+
const cpuUsage = process.cpuUsage();
|
|
333
|
+
lines.push('# HELP process_cpu_user_microseconds User CPU time in microseconds');
|
|
334
|
+
lines.push('# TYPE process_cpu_user_microseconds counter');
|
|
335
|
+
lines.push(`process_cpu_user_microseconds ${cpuUsage.user}`);
|
|
336
|
+
lines.push('');
|
|
337
|
+
|
|
338
|
+
lines.push('# HELP process_cpu_system_microseconds System CPU time in microseconds');
|
|
339
|
+
lines.push('# TYPE process_cpu_system_microseconds counter');
|
|
340
|
+
lines.push(`process_cpu_system_microseconds ${cpuUsage.system}`);
|
|
341
|
+
lines.push('');
|
|
342
|
+
|
|
343
|
+
// Custom metrics
|
|
344
|
+
for (const [name, metric] of this.customMetrics) {
|
|
345
|
+
lines.push(`# HELP ${this.options.prefix}${name} ${metric.help || 'Custom metric'}`);
|
|
346
|
+
lines.push(`# TYPE ${this.options.prefix}${name} ${metric.type || 'gauge'}`);
|
|
347
|
+
|
|
348
|
+
if (metric.labels) {
|
|
349
|
+
for (const [labelKey, value] of Object.entries(metric.labels)) {
|
|
350
|
+
const labelStr = Object.entries(labelKey)
|
|
351
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
352
|
+
.join(',');
|
|
353
|
+
lines.push(`${this.options.prefix}${name}{${labelStr}} ${value}`);
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
lines.push(`${this.options.prefix}${name} ${metric.value}`);
|
|
357
|
+
}
|
|
358
|
+
lines.push('');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return lines.join('\n');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Register custom metric
|
|
366
|
+
*/
|
|
367
|
+
registerMetric(name, type, help, value, labels = null) {
|
|
368
|
+
this.customMetrics.set(name, { type, help, value, labels });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Update custom metric value
|
|
373
|
+
*/
|
|
374
|
+
updateMetric(name, value, labels = null) {
|
|
375
|
+
const metric = this.customMetrics.get(name);
|
|
376
|
+
if (metric) {
|
|
377
|
+
if (labels) {
|
|
378
|
+
if (!metric.labels) {
|
|
379
|
+
metric.labels = {};
|
|
380
|
+
}
|
|
381
|
+
metric.labels[JSON.stringify(labels)] = value;
|
|
382
|
+
} else {
|
|
383
|
+
metric.value = value;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Increment counter metric
|
|
390
|
+
*/
|
|
391
|
+
incrementCounter(name, labels = null) {
|
|
392
|
+
const metric = this.customMetrics.get(name);
|
|
393
|
+
if (metric && metric.type === 'counter') {
|
|
394
|
+
if (labels) {
|
|
395
|
+
const key = JSON.stringify(labels);
|
|
396
|
+
if (!metric.labels) {
|
|
397
|
+
metric.labels = {};
|
|
398
|
+
}
|
|
399
|
+
metric.labels[key] = (metric.labels[key] || 0) + 1;
|
|
400
|
+
} else {
|
|
401
|
+
metric.value = (metric.value || 0) + 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Singleton instance
|
|
408
|
+
const prometheusExporter = new PrometheusExporter({
|
|
409
|
+
endpoint: '/_metrics',
|
|
410
|
+
collectDefaultMetrics: true
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
module.exports = {
|
|
414
|
+
PrometheusExporter,
|
|
415
|
+
prometheusExporter
|
|
416
|
+
};
|
package/package.json
CHANGED
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "mastercontroller",
|
|
3
|
+
"version": "1.3.13",
|
|
4
|
+
"description": "Fortune 500 ready Node.js MVC framework with enterprise security, monitoring, and horizontal scaling",
|
|
5
|
+
"main": "MasterControl.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/Tailor/MasterController#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Tailor/MasterController.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Tailor/MasterController/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mvc",
|
|
17
|
+
"framework",
|
|
18
|
+
"web",
|
|
19
|
+
"api",
|
|
20
|
+
"rest",
|
|
21
|
+
"enterprise",
|
|
22
|
+
"fortune-500",
|
|
23
|
+
"security",
|
|
24
|
+
"csrf",
|
|
25
|
+
"rate-limiting",
|
|
26
|
+
"monitoring",
|
|
27
|
+
"prometheus",
|
|
28
|
+
"redis",
|
|
29
|
+
"session",
|
|
30
|
+
"horizontal-scaling",
|
|
31
|
+
"load-balancer",
|
|
32
|
+
"production-ready"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
39
|
+
"lint": "eslint *.js **/*.js --fix",
|
|
40
|
+
"lint:check": "eslint *.js **/*.js",
|
|
41
|
+
"format": "prettier --write \"**/*.js\"",
|
|
42
|
+
"format:check": "prettier --check \"**/*.js\"",
|
|
43
|
+
"security-audit": "npm audit && npm audit signatures",
|
|
44
|
+
"security-scan": "snyk test --severity-threshold=high",
|
|
45
|
+
"prepare": "npm run lint:check || true",
|
|
46
|
+
"prepublishOnly": "npm run security-audit || true"
|
|
47
|
+
},
|
|
2
48
|
"dependencies": {
|
|
3
49
|
"content-type": "^1.0.5",
|
|
4
50
|
"cookie": "^1.1.1",
|
|
@@ -7,17 +53,24 @@
|
|
|
7
53
|
"qs": "^6.14.1",
|
|
8
54
|
"winston": "^3.19.0"
|
|
9
55
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"main": "MasterControl.js",
|
|
14
|
-
"name": "mastercontroller",
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/Tailor/MasterController.git"
|
|
56
|
+
"optionalDependencies": {
|
|
57
|
+
"ioredis": "^5.3.2",
|
|
58
|
+
"prom-client": "^15.1.0"
|
|
18
59
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"ioredis": "^5.0.0",
|
|
62
|
+
"prom-client": "^14.0.0 || ^15.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"ioredis": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"prom-client": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
21
71
|
},
|
|
22
|
-
"
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"eslint": "^8.56.0",
|
|
74
|
+
"prettier": "^3.2.4"
|
|
75
|
+
}
|
|
23
76
|
}
|