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,231 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('node:events');
4
+ const InputSanitizer = require('./InputSanitizer');
5
+
6
+ /**
7
+ * Security Middleware
8
+ * Provides security checks and sanitization for incoming requests and data.
9
+ */
10
+
11
+ class SecurityMiddleware extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+
15
+ this.options = {
16
+ maxRequestSize: options.maxRequestSize || 1024 * 1024, // 1MB
17
+ rateLimitWindow: options.rateLimitWindow || 60000, // 1 minute
18
+ rateLimitMax: options.rateLimitMax || 100,
19
+ blockSuspiciousPatterns: options.blockSuspiciousPatterns !== false,
20
+ ...options,
21
+ };
22
+
23
+ this._rateLimits = new Map();
24
+ this._blockedIPs = new Set();
25
+ this._suspiciousPatterns = [
26
+ /<script/i,
27
+ /javascript:/i,
28
+ /data:/i,
29
+ /on\w+\s*=/i,
30
+ /exec\(/i,
31
+ /system\(/i,
32
+ /\.\.\//i,
33
+ /<iframe/i,
34
+ /<object/i,
35
+ ];
36
+ }
37
+
38
+ /**
39
+ * Check if a request should be rate limited.
40
+ * @param {string} identifier - IP address or user ID
41
+ * @returns {boolean}
42
+ */
43
+ isRateLimited(identifier) {
44
+ const now = Date.now();
45
+ const windowStart = now - this.options.rateLimitWindow;
46
+
47
+ let requests = this._rateLimits.get(identifier) || [];
48
+
49
+ // Remove old requests outside the window
50
+ requests = requests.filter(time => time > windowStart);
51
+
52
+ if (requests.length >= this.options.rateLimitMax) {
53
+ this.emit('rateLimit', { identifier, count: requests.length });
54
+ return true;
55
+ }
56
+
57
+ // Add current request
58
+ requests.push(now);
59
+ this._rateLimits.set(identifier, requests);
60
+
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Block an IP address.
66
+ * @param {string} ip
67
+ * @param {number} [duration] - Duration in milliseconds (0 = permanent)
68
+ */
69
+ blockIP(ip, duration = 0) {
70
+ this._blockedIPs.add(ip);
71
+ this.emit('ipBlocked', { ip, duration });
72
+
73
+ if (duration > 0) {
74
+ setTimeout(() => {
75
+ this._blockedIPs.delete(ip);
76
+ this.emit('ipUnblocked', { ip });
77
+ }, duration);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Check if an IP is blocked.
83
+ * @param {string} ip
84
+ * @returns {boolean}
85
+ */
86
+ isIPBlocked(ip) {
87
+ return this._blockedIPs.has(ip);
88
+ }
89
+
90
+ /**
91
+ * Sanitize and validate input data.
92
+ * @param {any} data
93
+ * @param {object} [options]
94
+ * @returns {any}
95
+ */
96
+ sanitizeInput(data, options = {}) {
97
+ if (typeof data === 'string') {
98
+ return InputSanitizer.sanitize(data, options);
99
+ }
100
+
101
+ if (typeof data === 'object' && data !== null) {
102
+ return InputSanitizer.sanitizeObject(data, options);
103
+ }
104
+
105
+ return data;
106
+ }
107
+
108
+ /**
109
+ * Check for suspicious patterns in input.
110
+ * @param {string} input
111
+ * @returns {boolean}
112
+ */
113
+ containsSuspiciousPatterns(input) {
114
+ if (!this.options.blockSuspiciousPatterns) return false;
115
+
116
+ if (typeof input !== 'string') return false;
117
+
118
+ for (const pattern of this._suspiciousPatterns) {
119
+ if (pattern.test(input)) {
120
+ this.emit('suspiciousPattern', { input, pattern: pattern.source });
121
+ return true;
122
+ }
123
+ }
124
+
125
+ return false;
126
+ }
127
+
128
+ /**
129
+ * Validate request size.
130
+ * @param {number} size
131
+ * @returns {boolean}
132
+ */
133
+ isValidRequestSize(size) {
134
+ if (size > this.options.maxRequestSize) {
135
+ this.emit('requestTooLarge', { size, max: this.options.maxRequestSize });
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+
141
+ /**
142
+ * Create a middleware function for Express-like frameworks.
143
+ * @returns {Function}
144
+ */
145
+ createMiddleware() {
146
+ return (req, res, next) => {
147
+ const identifier = req.ip || req.connection.remoteAddress;
148
+
149
+ // Check IP block
150
+ if (this.isIPBlocked(identifier)) {
151
+ return res.status(403).json({ error: 'IP blocked' });
152
+ }
153
+
154
+ // Check rate limit
155
+ if (this.isRateLimited(identifier)) {
156
+ return res.status(429).json({ error: 'Rate limit exceeded' });
157
+ }
158
+
159
+ // Check request size
160
+ const contentLength = parseInt(req.headers['content-length'] || '0');
161
+ if (!this.isValidRequestSize(contentLength)) {
162
+ return res.status(413).json({ error: 'Request too large' });
163
+ }
164
+
165
+ // Sanitize body if present
166
+ if (req.body) {
167
+ try {
168
+ req.body = this.sanitizeInput(req.body);
169
+ } catch (err) {
170
+ this.emit('sanitizationError', { error: err });
171
+ return res.status(400).json({ error: 'Invalid input' });
172
+ }
173
+ }
174
+
175
+ // Check for suspicious patterns
176
+ if (req.body && this.containsSuspiciousPatterns(JSON.stringify(req.body))) {
177
+ this.emit('suspiciousRequest', { body: req.body });
178
+ return res.status(400).json({ error: 'Suspicious input detected' });
179
+ }
180
+
181
+ next();
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Clean up rate limit data.
187
+ */
188
+ cleanup() {
189
+ const now = Date.now();
190
+ const windowStart = now - this.options.rateLimitWindow;
191
+
192
+ for (const [identifier, requests] of this._rateLimits) {
193
+ const filtered = requests.filter(time => time > windowStart);
194
+ if (filtered.length === 0) {
195
+ this._rateLimits.delete(identifier);
196
+ } else {
197
+ this._rateLimits.set(identifier, filtered);
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Get security statistics.
204
+ * @returns {object}
205
+ */
206
+ getStats() {
207
+ return {
208
+ blockedIPs: this._blockedIPs.size,
209
+ rateLimitedIdentifiers: this._rateLimits.size,
210
+ totalRequests: Array.from(this._rateLimits.values()).reduce((sum, reqs) => sum + reqs.length, 0),
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Reset all security data.
216
+ */
217
+ reset() {
218
+ this._rateLimits.clear();
219
+ this._blockedIPs.clear();
220
+ }
221
+
222
+ /**
223
+ * Destroy the middleware.
224
+ */
225
+ destroy() {
226
+ this.reset();
227
+ this.removeAllListeners();
228
+ }
229
+ }
230
+
231
+ module.exports = SecurityMiddleware;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const InputSanitizer = require('./InputSanitizer');
4
+ const SecurityMiddleware = require('./SecurityMiddleware');
5
+
6
+ module.exports = {
7
+ InputSanitizer,
8
+ SecurityMiddleware,
9
+ };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('node:events');
4
+ const Logger = require('../utils/Logger');
5
+
6
+ /**
7
+ * Graceful Shutdown Handler
8
+ * Handles graceful shutdown of the application with proper cleanup.
9
+ */
10
+
11
+ class GracefulShutdown extends EventEmitter {
12
+ constructor(options = {}) {
13
+ super();
14
+
15
+ this.options = {
16
+ timeout: options.timeout || 30000, // 30 seconds
17
+ signals: options.signals || ['SIGINT', 'SIGTERM'],
18
+ ...options,
19
+ };
20
+
21
+ this.logger = new Logger({ prefix: 'Shutdown', level: 'info' });
22
+
23
+ this.shutdownHandlers = new Map();
24
+ this.isShuttingDown = false;
25
+ this._setupSignalHandlers();
26
+ }
27
+
28
+ /**
29
+ * Register a shutdown handler.
30
+ * @param {string} name
31
+ * @param {Function} handler - Async function to run during shutdown
32
+ * @param {number} [priority=0] - Higher priority runs first
33
+ */
34
+ register(name, handler, priority = 0) {
35
+ if (typeof handler !== 'function') {
36
+ throw new TypeError('Shutdown handler must be a function');
37
+ }
38
+
39
+ this.shutdownHandlers.set(name, { handler, priority });
40
+ this.logger.info(`Registered shutdown handler: ${name} (priority: ${priority})`);
41
+ }
42
+
43
+ /**
44
+ * Unregister a shutdown handler.
45
+ * @param {string} name
46
+ */
47
+ unregister(name) {
48
+ this.shutdownHandlers.delete(name);
49
+ this.logger.info(`Unregistered shutdown handler: ${name}`);
50
+ }
51
+
52
+ /**
53
+ * Setup signal handlers for graceful shutdown.
54
+ * @private
55
+ */
56
+ _setupSignalHandlers() {
57
+ for (const signal of this.options.signals) {
58
+ process.on(signal, () => {
59
+ this.logger.info(`Received ${signal} signal`);
60
+ this.shutdown(signal);
61
+ });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Initiate graceful shutdown.
67
+ * @param {string} [reason='manual']
68
+ * @returns {Promise<void>}
69
+ */
70
+ async shutdown(reason = 'manual') {
71
+ if (this.isShuttingDown) {
72
+ this.logger.warn('Shutdown already in progress');
73
+ return;
74
+ }
75
+
76
+ this.isShuttingDown = true;
77
+ this.logger.info(`Initiating graceful shutdown: ${reason}`);
78
+ this.emit('shutdown', { reason });
79
+
80
+ // Sort handlers by priority (higher first)
81
+ const sortedHandlers = Array.from(this.shutdownHandlers.entries())
82
+ .sort(([, a], [, b]) => b.priority - a.priority);
83
+
84
+ // Run all shutdown handlers with timeout
85
+ const shutdownPromises = sortedHandlers.map(([name, { handler }]) => {
86
+ return Promise.race([
87
+ handler().catch(error => {
88
+ this.logger.error(`Shutdown handler failed: ${name}`, error);
89
+ throw error;
90
+ }),
91
+ new Promise((_, reject) =>
92
+ setTimeout(() => reject(new Error('Handler timeout')), this.options.timeout)
93
+ ),
94
+ ]);
95
+ });
96
+
97
+ try {
98
+ await Promise.allSettled(shutdownPromises);
99
+ this.logger.info('All shutdown handlers completed');
100
+ this.emit('complete');
101
+ } catch (error) {
102
+ this.logger.error('Error during shutdown', error);
103
+ this.emit('error', error);
104
+ }
105
+
106
+ // Force exit after timeout
107
+ setTimeout(() => {
108
+ this.logger.warn('Forcing exit after timeout');
109
+ process.exit(1);
110
+ }, this.options.timeout + 1000);
111
+ }
112
+
113
+ /**
114
+ * Abort shutdown (if not too late).
115
+ */
116
+ abort() {
117
+ if (!this.isShuttingDown) {
118
+ this.logger.warn('No shutdown in progress to abort');
119
+ return;
120
+ }
121
+
122
+ this.logger.warn('Shutdown abort requested (may not be possible)');
123
+ this.emit('abort');
124
+ }
125
+
126
+ /**
127
+ * Check if shutdown is in progress.
128
+ * @returns {boolean}
129
+ */
130
+ isShuttingDown() {
131
+ return this.isShuttingDown;
132
+ }
133
+
134
+ /**
135
+ * Get registered handlers.
136
+ * @returns {Array}
137
+ */
138
+ getHandlers() {
139
+ return Array.from(this.shutdownHandlers.entries()).map(([name, { priority }]) => ({
140
+ name,
141
+ priority,
142
+ }));
143
+ }
144
+
145
+ /**
146
+ * Destroy the shutdown handler.
147
+ */
148
+ destroy() {
149
+ this.shutdownHandlers.clear();
150
+ this.removeAllListeners();
151
+
152
+ // Remove signal handlers
153
+ for (const signal of this.options.signals) {
154
+ process.removeAllListeners(signal);
155
+ }
156
+ }
157
+ }
158
+
159
+ module.exports = GracefulShutdown;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const GracefulShutdown = require('./GracefulShutdown');
4
+
5
+ module.exports = {
6
+ GracefulShutdown,
7
+ };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ const winston = require('winston');
4
+
5
+ /**
6
+ * Structured Logger
7
+ * Provides structured logging with winston.
8
+ */
9
+
10
+ class StructuredLogger {
11
+ constructor(options = {}) {
12
+ const {
13
+ level = options.level || 'info',
14
+ service = options.service || 'lumis',
15
+ environment = options.environment || 'development',
16
+ ...winstonOptions
17
+ } = options;
18
+
19
+ this.logger = winston.createLogger({
20
+ level,
21
+ format: winston.format.combine(
22
+ winston.format.timestamp(),
23
+ winston.format.errors({ stack: true }),
24
+ winston.format.splat(),
25
+ winston.format.json()
26
+ ),
27
+ defaultMeta: { service, environment },
28
+ transports: [
29
+ new winston.transports.Console({
30
+ format: winston.format.combine(
31
+ winston.format.colorize(),
32
+ winston.format.printf(({ timestamp, level, message, service, environment, ...meta }) => {
33
+ const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : '';
34
+ return `${timestamp} [${service}] [${environment}] ${level}: ${message} ${metaStr}`;
35
+ })
36
+ ),
37
+ }),
38
+ ],
39
+ ...winstonOptions,
40
+ });
41
+
42
+ // Add file transport in production
43
+ if (environment === 'production') {
44
+ this.logger.add(new winston.transports.File({ filename: 'error.log', level: 'error' }));
45
+ this.logger.add(new winston.transports.File({ filename: 'combined.log' }));
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Log an error.
51
+ * @param {string} message
52
+ * @param {object} [meta]
53
+ */
54
+ error(message, meta) {
55
+ this.logger.error(message, meta);
56
+ }
57
+
58
+ /**
59
+ * Log a warning.
60
+ * @param {string} message
61
+ * @param {object} [meta]
62
+ */
63
+ warn(message, meta) {
64
+ this.logger.warn(message, meta);
65
+ }
66
+
67
+ /**
68
+ * Log info.
69
+ * @param {string} message
70
+ * @param {object} [meta]
71
+ */
72
+ info(message, meta) {
73
+ this.logger.info(message, meta);
74
+ }
75
+
76
+ /**
77
+ * Log debug.
78
+ * @param {string} message
79
+ * @param {object} [meta]
80
+ */
81
+ debug(message, meta) {
82
+ this.logger.debug(message, meta);
83
+ }
84
+
85
+ /**
86
+ * Log verbose.
87
+ * @param {string} message
88
+ * @param {object} [meta]
89
+ */
90
+ verbose(message, meta) {
91
+ this.logger.verbose(message, meta);
92
+ }
93
+
94
+ /**
95
+ * Create a child logger with additional metadata.
96
+ * @param {object} meta
97
+ * @returns {StructuredLogger}
98
+ */
99
+ child(meta) {
100
+ const childLogger = new StructuredLogger({
101
+ level: this.logger.level,
102
+ service: this.logger.defaultMeta.service,
103
+ environment: this.logger.defaultMeta.environment,
104
+ });
105
+ childLogger.logger.defaultMeta = { ...this.logger.defaultMeta, ...meta };
106
+ return childLogger;
107
+ }
108
+
109
+ /**
110
+ * Get the underlying winston logger.
111
+ * @returns {winston.Logger}
112
+ */
113
+ getLogger() {
114
+ return this.logger;
115
+ }
116
+ }
117
+
118
+ module.exports = StructuredLogger;