lumisjs 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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/SECURITY.md +160 -0
  4. package/index.js +90 -0
  5. package/package.json +68 -0
  6. package/src/cache/CacheManager.js +393 -0
  7. package/src/cache/MemoryAdapter.js +259 -0
  8. package/src/cache/MultiLevelCache.js +362 -0
  9. package/src/cache/RedisAdapter.js +329 -0
  10. package/src/cache/SQLiteAdapter.js +280 -0
  11. package/src/cache/index.js +15 -0
  12. package/src/client/Client.js +554 -0
  13. package/src/client/ShardingManager.js +274 -0
  14. package/src/config/ConfigValidator.js +172 -0
  15. package/src/config/index.js +7 -0
  16. package/src/dashboard/DashboardManager.js +362 -0
  17. package/src/datagen/DataGenerator.js +47 -0
  18. package/src/datagen/apis/GraphqlMocker.js +16 -0
  19. package/src/datagen/apis/OpenApiMocker.js +18 -0
  20. package/src/datagen/cli.js +130 -0
  21. package/src/datagen/formatters/csv.js +45 -0
  22. package/src/datagen/formatters/index.js +15 -0
  23. package/src/datagen/formatters/json.js +33 -0
  24. package/src/datagen/formatters/mongo.js +22 -0
  25. package/src/datagen/formatters/sql.js +32 -0
  26. package/src/datagen/index.js +17 -0
  27. package/src/datagen/plugin/PluginManager.js +43 -0
  28. package/src/datagen/plugins/example-plugin.js +21 -0
  29. package/src/datagen/schema/SchemaGenerator.js +172 -0
  30. package/src/datagen/schema/loadSchema.js +14 -0
  31. package/src/datagen/utils/seed.js +26 -0
  32. package/src/di/ServiceContainer.js +229 -0
  33. package/src/errors/ErrorCodes.js +110 -0
  34. package/src/errors/LumisError.js +85 -0
  35. package/src/game/EconomyManager.js +161 -0
  36. package/src/game/GameSessionManager.js +76 -0
  37. package/src/game/GuildManager.js +197 -0
  38. package/src/game/InventoryManager.js +140 -0
  39. package/src/game/LevelingSystem.js +194 -0
  40. package/src/game/MusicManager.js +587 -0
  41. package/src/health/HealthChecker.js +170 -0
  42. package/src/health/index.js +7 -0
  43. package/src/performance/PerformanceMonitor.js +244 -0
  44. package/src/performance/index.js +7 -0
  45. package/src/security/InputSanitizer.js +185 -0
  46. package/src/security/SecurityMiddleware.js +231 -0
  47. package/src/security/index.js +9 -0
  48. package/src/shutdown/GracefulShutdown.js +159 -0
  49. package/src/shutdown/index.js +7 -0
  50. package/src/utils/StructuredLogger.js +118 -0
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('node:events');
4
+ const Logger = require('../utils/Logger');
5
+
6
+ /**
7
+ * Health Checker
8
+ * Monitors system health and provides health check endpoints.
9
+ */
10
+
11
+ class HealthChecker extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+
15
+ this.options = {
16
+ checkInterval: options.checkInterval || 30000, // 30 seconds
17
+ timeout: options.timeout || 5000, // 5 seconds
18
+ ...options,
19
+ };
20
+
21
+ this.logger = new Logger({ prefix: 'HealthCheck', level: 'info' });
22
+
23
+ this.checks = new Map();
24
+ this.status = 'healthy';
25
+ this.lastCheck = null;
26
+ this._interval = null;
27
+ }
28
+
29
+ /**
30
+ * Register a health check.
31
+ * @param {string} name
32
+ * @param {Function} checkFn - Function that returns a Promise resolving to { status: 'ok'|'degraded'|'down', message?: string }
33
+ */
34
+ register(name, checkFn) {
35
+ if (typeof checkFn !== 'function') {
36
+ throw new TypeError('Health check must be a function');
37
+ }
38
+ this.checks.set(name, checkFn);
39
+ this.logger.info(`Registered health check: ${name}`);
40
+ }
41
+
42
+ /**
43
+ * Unregister a health check.
44
+ * @param {string} name
45
+ */
46
+ unregister(name) {
47
+ this.checks.delete(name);
48
+ this.logger.info(`Unregistered health check: ${name}`);
49
+ }
50
+
51
+ /**
52
+ * Run all health checks.
53
+ * @returns {Promise<object>}
54
+ */
55
+ async check() {
56
+ const results = {};
57
+ let overallStatus = 'healthy';
58
+ const now = Date.now();
59
+
60
+ for (const [name, checkFn] of this.checks) {
61
+ try {
62
+ const result = await Promise.race([
63
+ checkFn(),
64
+ new Promise((_, reject) =>
65
+ setTimeout(() => reject(new Error('Health check timeout')), this.options.timeout)
66
+ ),
67
+ ]);
68
+
69
+ results[name] = {
70
+ status: result.status || 'ok',
71
+ message: result.message || '',
72
+ timestamp: now,
73
+ };
74
+
75
+ if (result.status === 'down') {
76
+ overallStatus = 'unhealthy';
77
+ } else if (result.status === 'degraded' && overallStatus === 'healthy') {
78
+ overallStatus = 'degraded';
79
+ }
80
+ } catch (error) {
81
+ results[name] = {
82
+ status: 'down',
83
+ message: error.message,
84
+ timestamp: now,
85
+ };
86
+ overallStatus = 'unhealthy';
87
+ this.logger.error(`Health check failed: ${name}`, error);
88
+ }
89
+ }
90
+
91
+ this.status = overallStatus;
92
+ this.lastCheck = now;
93
+
94
+ const healthReport = {
95
+ status: overallStatus,
96
+ timestamp: now,
97
+ uptime: process.uptime(),
98
+ checks: results,
99
+ };
100
+
101
+ this.emit('check', healthReport);
102
+
103
+ if (overallStatus !== 'healthy') {
104
+ this.emit('unhealthy', healthReport);
105
+ }
106
+
107
+ return healthReport;
108
+ }
109
+
110
+ /**
111
+ * Start periodic health checks.
112
+ */
113
+ start() {
114
+ if (this._interval) return;
115
+
116
+ this._interval = setInterval(() => this.check(), this.options.checkInterval);
117
+ if (this._interval.unref) this._interval.unref();
118
+
119
+ this.logger.info('Health checker started');
120
+ this.emit('start');
121
+ }
122
+
123
+ /**
124
+ * Stop periodic health checks.
125
+ */
126
+ stop() {
127
+ if (this._interval) {
128
+ clearInterval(this._interval);
129
+ this._interval = null;
130
+ }
131
+
132
+ this.logger.info('Health checker stopped');
133
+ this.emit('stop');
134
+ }
135
+
136
+ /**
137
+ * Get current health status without running checks.
138
+ * @returns {object}
139
+ */
140
+ getStatus() {
141
+ return {
142
+ status: this.status,
143
+ lastCheck: this.lastCheck,
144
+ uptime: process.uptime(),
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Get a simplified health report for quick checks.
150
+ * @returns {Promise<object>}
151
+ */
152
+ async quickCheck() {
153
+ const report = await this.check();
154
+ return {
155
+ status: report.status,
156
+ timestamp: report.timestamp,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Destroy the health checker.
162
+ */
163
+ destroy() {
164
+ this.stop();
165
+ this.checks.clear();
166
+ this.removeAllListeners();
167
+ }
168
+ }
169
+
170
+ module.exports = HealthChecker;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const HealthChecker = require('./HealthChecker');
4
+
5
+ module.exports = {
6
+ HealthChecker,
7
+ };
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('node:events');
4
+ const Logger = require('../utils/Logger');
5
+
6
+ /**
7
+ * Performance Monitor
8
+ * Tracks and reports performance metrics for the application.
9
+ */
10
+
11
+ class PerformanceMonitor extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+
15
+ this.options = {
16
+ sampleInterval: options.sampleInterval || 1000,
17
+ historySize: options.historySize || 60,
18
+ alertThresholds: options.alertThresholds || {
19
+ cpu: 80, // percentage
20
+ memory: 80, // percentage
21
+ eventLoopDelay: 100, // milliseconds
22
+ },
23
+ ...options,
24
+ };
25
+
26
+ this.logger = new Logger({ prefix: 'PerfMon', level: 'info' });
27
+
28
+ this.metrics = {
29
+ cpu: [],
30
+ memory: [],
31
+ eventLoopDelay: [],
32
+ eventLoopUtilization: [],
33
+ };
34
+
35
+ this._monitoring = false;
36
+ this._interval = null;
37
+ }
38
+
39
+ /**
40
+ * Start performance monitoring.
41
+ */
42
+ start() {
43
+ if (this._monitoring) return;
44
+
45
+ this._monitoring = true;
46
+ this._interval = setInterval(() => this._sample(), this.options.sampleInterval);
47
+ if (this._interval.unref) this._interval.unref();
48
+
49
+ this.logger.info('Performance monitoring started');
50
+ this.emit('start');
51
+ }
52
+
53
+ /**
54
+ * Stop performance monitoring.
55
+ */
56
+ stop() {
57
+ if (!this._monitoring) return;
58
+
59
+ this._monitoring = false;
60
+ if (this._interval) {
61
+ clearInterval(this._interval);
62
+ this._interval = null;
63
+ }
64
+
65
+ this.logger.info('Performance monitoring stopped');
66
+ this.emit('stop');
67
+ }
68
+
69
+ /**
70
+ * Get current performance metrics.
71
+ * @returns {Promise<object>}
72
+ */
73
+ async getMetrics() {
74
+ const cpuUsage = process.cpuUsage();
75
+ const memoryUsage = process.memoryUsage();
76
+
77
+ return {
78
+ cpu: {
79
+ user: cpuUsage.user,
80
+ system: cpuUsage.system,
81
+ percent: this._calculateCPUPercent(cpuUsage),
82
+ },
83
+ memory: {
84
+ rss: memoryUsage.rss,
85
+ heapTotal: memoryUsage.heapTotal,
86
+ heapUsed: memoryUsage.heapUsed,
87
+ external: memoryUsage.external,
88
+ arrayBuffers: memoryUsage.arrayBuffers,
89
+ percent: (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100,
90
+ },
91
+ eventLoop: {
92
+ delay: await this._measureEventLoopDelay(),
93
+ utilization: await this._measureEventLoopUtilization(),
94
+ },
95
+ uptime: process.uptime(),
96
+ timestamp: Date.now(),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Get historical metrics.
102
+ * @returns {object}
103
+ */
104
+ getHistory() {
105
+ return {
106
+ cpu: [...this.metrics.cpu],
107
+ memory: [...this.metrics.memory],
108
+ eventLoopDelay: [...this.metrics.eventLoopDelay],
109
+ eventLoopUtilization: [...this.metrics.eventLoopUtilization],
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Get performance summary.
115
+ * @returns {object}
116
+ */
117
+ getSummary() {
118
+ const avgCPU = this._average(this.metrics.cpu);
119
+ const avgMemory = this._average(this.metrics.memory);
120
+ const avgEventLoopDelay = this._average(this.metrics.eventLoopDelay);
121
+ const avgEventLoopUtil = this._average(this.metrics.eventLoopUtilization);
122
+
123
+ return {
124
+ cpu: {
125
+ average: avgCPU,
126
+ max: Math.max(...this.metrics.cpu, 0),
127
+ min: Math.min(...this.metrics.cpu, 100),
128
+ },
129
+ memory: {
130
+ average: avgMemory,
131
+ max: Math.max(...this.metrics.memory, 0),
132
+ min: Math.min(...this.metrics.memory, 100),
133
+ },
134
+ eventLoop: {
135
+ averageDelay: avgEventLoopDelay,
136
+ maxDelay: Math.max(...this.metrics.eventLoopDelay, 0),
137
+ averageUtilization: avgEventLoopUtil,
138
+ maxUtilization: Math.max(...this.metrics.eventLoopUtilization, 0),
139
+ },
140
+ monitoring: this._monitoring,
141
+ samples: this.metrics.cpu.length,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Reset metrics history.
147
+ */
148
+ reset() {
149
+ this.metrics = {
150
+ cpu: [],
151
+ memory: [],
152
+ eventLoopDelay: [],
153
+ eventLoopUtilization: [],
154
+ };
155
+
156
+ this.emit('reset');
157
+ }
158
+
159
+ /** @private */
160
+ async _sample() {
161
+ const metrics = await this.getMetrics();
162
+
163
+ // Add to history
164
+ this._addToHistory('cpu', metrics.cpu.percent);
165
+ this._addToHistory('memory', metrics.memory.percent);
166
+ this._addToHistory('eventLoopDelay', metrics.eventLoop.delay);
167
+ this._addToHistory('eventLoopUtilization', metrics.eventLoop.utilization);
168
+
169
+ // Check thresholds
170
+ this._checkThresholds(metrics);
171
+
172
+ this.emit('sample', metrics);
173
+ }
174
+
175
+ /** @private */
176
+ _addToHistory(type, value) {
177
+ this.metrics[type].push(value);
178
+
179
+ if (this.metrics[type].length > this.options.historySize) {
180
+ this.metrics[type].shift();
181
+ }
182
+ }
183
+
184
+ /** @private */
185
+ _calculateCPUPercent(cpuUsage) {
186
+ const total = cpuUsage.user + cpuUsage.system;
187
+ // This is a simplified calculation
188
+ return (total / 1000000) * 100;
189
+ }
190
+
191
+ /** @private */
192
+ async _measureEventLoopDelay() {
193
+ return new Promise((resolve) => {
194
+ const start = process.hrtime.bigint();
195
+ setImmediate(() => {
196
+ const delay = Number(process.hrtime.bigint() - start) / 1000000; // Convert to ms
197
+ resolve(delay);
198
+ });
199
+ });
200
+ }
201
+
202
+ /** @private */
203
+ async _measureEventLoopUtilization() {
204
+ if (process.eventLoopUtilization) {
205
+ const utilization = process.eventLoopUtilization();
206
+ return utilization.utilization;
207
+ }
208
+ return 0;
209
+ }
210
+
211
+ /** @private */
212
+ _average(arr) {
213
+ if (arr.length === 0) return 0;
214
+ return arr.reduce((a, b) => a + b, 0) / arr.length;
215
+ }
216
+
217
+ /** @private */
218
+ _checkThresholds(metrics) {
219
+ const thresholds = this.options.alertThresholds;
220
+
221
+ if (metrics.cpu.percent > thresholds.cpu) {
222
+ this.emit('alert', { type: 'cpu', value: metrics.cpu.percent, threshold: thresholds.cpu });
223
+ }
224
+
225
+ if (metrics.memory.percent > thresholds.memory) {
226
+ this.emit('alert', { type: 'memory', value: metrics.memory.percent, threshold: thresholds.memory });
227
+ }
228
+
229
+ if (metrics.eventLoop.delay > thresholds.eventLoopDelay) {
230
+ this.emit('alert', { type: 'eventLoop', value: metrics.eventLoop.delay, threshold: thresholds.eventLoopDelay });
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Destroy the monitor.
236
+ */
237
+ destroy() {
238
+ this.stop();
239
+ this.reset();
240
+ this.removeAllListeners();
241
+ }
242
+ }
243
+
244
+ module.exports = PerformanceMonitor;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const PerformanceMonitor = require('./PerformanceMonitor');
4
+
5
+ module.exports = {
6
+ PerformanceMonitor,
7
+ };
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Input Sanitizer
5
+ * Provides utilities for sanitizing user input to prevent XSS, injection attacks, etc.
6
+ */
7
+
8
+ class InputSanitizer {
9
+ /**
10
+ * Sanitize a string by removing potentially dangerous characters.
11
+ * @param {string} input
12
+ * @param {object} [options]
13
+ * @param {boolean} [options.allowHTML=false]
14
+ * @param {boolean} [options.allowScript=false]
15
+ * @param {number} [options.maxLength=1000]
16
+ * @returns {string}
17
+ */
18
+ static sanitize(input, options = {}) {
19
+ if (typeof input !== 'string') return '';
20
+
21
+ const {
22
+ allowHTML = false,
23
+ allowScript = false,
24
+ maxLength = 1000,
25
+ } = options;
26
+
27
+ let sanitized = input;
28
+
29
+ // Truncate if too long
30
+ if (sanitized.length > maxLength) {
31
+ sanitized = sanitized.substring(0, maxLength);
32
+ }
33
+
34
+ // Remove script tags
35
+ if (!allowScript) {
36
+ sanitized = sanitized.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
37
+ }
38
+
39
+ // Remove dangerous HTML tags if not allowed
40
+ if (!allowHTML) {
41
+ const dangerousTags = ['<script', '<iframe', '<object', '<embed', '<form', '<input', '<button'];
42
+ for (const tag of dangerousTags) {
43
+ sanitized = sanitized.replace(new RegExp(tag, 'gi'), '');
44
+ }
45
+ // Remove all HTML tags
46
+ sanitized = sanitized.replace(/<[^>]*>/g, '');
47
+ } else {
48
+ // Still remove dangerous attributes
49
+ sanitized = sanitized.replace(/on\w+\s*=/gi, '');
50
+ sanitized = sanitized.replace(/javascript:/gi, '');
51
+ sanitized = sanitized.replace(/data:/gi, '');
52
+ }
53
+
54
+ // Remove null bytes and other control characters
55
+ sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, '');
56
+
57
+ return sanitized.trim();
58
+ }
59
+
60
+ /**
61
+ * Sanitize a number to prevent injection.
62
+ * @param {any} input
63
+ * @param {object} [options]
64
+ * @param {number} [options.min]
65
+ * @param {number} [options.max]
66
+ * @param {boolean} [options.allowFloat=true]
67
+ * @returns {number|null}
68
+ */
69
+ static sanitizeNumber(input, options = {}) {
70
+ const { min, max, allowFloat = true } = options;
71
+
72
+ let num = allowFloat ? parseFloat(input) : parseInt(input, 10);
73
+
74
+ if (isNaN(num)) return null;
75
+
76
+ if (min !== undefined && num < min) num = min;
77
+ if (max !== undefined && num > max) num = max;
78
+
79
+ return num;
80
+ }
81
+
82
+ /**
83
+ * Sanitize a boolean value.
84
+ * @param {any} input
85
+ * @returns {boolean}
86
+ */
87
+ static sanitizeBoolean(input) {
88
+ if (typeof input === 'boolean') return input;
89
+ if (typeof input === 'string') {
90
+ return input.toLowerCase() === 'true' || input === '1';
91
+ }
92
+ return Boolean(input);
93
+ }
94
+
95
+ /**
96
+ * Sanitize an object by recursively sanitizing all string values.
97
+ * @param {object} obj
98
+ * @param {object} [options]
99
+ * @returns {object}
100
+ */
101
+ static sanitizeObject(obj, options = {}) {
102
+ if (obj === null || typeof obj !== 'object') return obj;
103
+
104
+ if (Array.isArray(obj)) {
105
+ return obj.map(item => this.sanitizeObject(item, options));
106
+ }
107
+
108
+ const sanitized = {};
109
+ for (const [key, value] of Object.entries(obj)) {
110
+ if (typeof value === 'string') {
111
+ sanitized[key] = this.sanitize(value, options);
112
+ } else if (typeof value === 'object') {
113
+ sanitized[key] = this.sanitizeObject(value, options);
114
+ } else {
115
+ sanitized[key] = value;
116
+ }
117
+ }
118
+
119
+ return sanitized;
120
+ }
121
+
122
+ /**
123
+ * Validate and sanitize a Discord ID.
124
+ * @param {string} input
125
+ * @returns {string|null}
126
+ */
127
+ static sanitizeDiscordId(input) {
128
+ if (typeof input !== 'string') return null;
129
+
130
+ // Discord IDs are numeric strings, typically 17-19 digits
131
+ const match = input.match(/^\d{17,19}$/);
132
+ return match ? match[0] : null;
133
+ }
134
+
135
+ /**
136
+ * Validate and sanitize a URL.
137
+ * @param {string} input
138
+ * @param {object} [options]
139
+ * @param {string[]} [options.allowedProtocols=['https', 'http']]
140
+ * @returns {string|null}
141
+ */
142
+ static sanitizeURL(input, options = {}) {
143
+ if (typeof input !== 'string') return null;
144
+
145
+ const { allowedProtocols = ['https', 'http'] } = options;
146
+
147
+ try {
148
+ const url = new URL(input);
149
+
150
+ if (!allowedProtocols.includes(url.protocol.replace(':', ''))) {
151
+ return null;
152
+ }
153
+
154
+ // Remove javascript: and data: protocols
155
+ if (url.protocol === 'javascript:' || url.protocol === 'data:') {
156
+ return null;
157
+ }
158
+
159
+ return url.toString();
160
+ } catch {
161
+ return null;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Escape special characters for regex.
167
+ * @param {string} string
168
+ * @returns {string}
169
+ */
170
+ static escapeRegex(string) {
171
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
172
+ }
173
+
174
+ /**
175
+ * Validate input against a whitelist of allowed values.
176
+ * @param {any} input
177
+ * @param {any[]} allowedValues
178
+ * @returns {any|null}
179
+ */
180
+ static validateWhitelist(input, allowedValues) {
181
+ return allowedValues.includes(input) ? input : null;
182
+ }
183
+ }
184
+
185
+ module.exports = InputSanitizer;