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,280 @@
1
+ 'use strict';
2
+
3
+ const Logger = require('../utils/Logger');
4
+
5
+ class SQLiteAdapter {
6
+
7
+ constructor(options = {}) {
8
+ this.table = options.table || 'lumis_cache';
9
+ this.defaultTTL = options.ttl || 0;
10
+ this.logger = new Logger({ prefix: 'SQLiteCache', level: 'info' });
11
+
12
+ try {
13
+ const Database = require('better-sqlite3');
14
+ this.db = new Database(options.path || ':memory:');
15
+
16
+ this.db.pragma('journal_mode = WAL');
17
+
18
+ // Validate table name to avoid SQL injection via identifiers
19
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(this.table)) {
20
+ throw new TypeError('Invalid table name for SQLiteAdapter. Use only letters, numbers, and underscores, starting with a letter or underscore.');
21
+ }
22
+
23
+ const tableName = `"${this.table}"`;
24
+
25
+ this.db.exec(`
26
+ CREATE TABLE IF NOT EXISTS ${tableName} (
27
+ key TEXT PRIMARY KEY,
28
+ value TEXT NOT NULL,
29
+ expires_at INTEGER
30
+ )
31
+ `);
32
+
33
+ this._stmts = {
34
+ get: this.db.prepare(`SELECT value, expires_at FROM ${tableName} WHERE key = ?`),
35
+ set: this.db.prepare(
36
+ `INSERT OR REPLACE INTO ${tableName} (key, value, expires_at) VALUES (?, ?, ?)`
37
+ ),
38
+ delete: this.db.prepare(`DELETE FROM ${tableName} WHERE key = ?`),
39
+ has: this.db.prepare(`SELECT 1 FROM ${tableName} WHERE key = ?`),
40
+ clear: this.db.prepare(`DELETE FROM ${tableName}`),
41
+ size: this.db.prepare(`SELECT COUNT(*) as count FROM ${tableName}`),
42
+ all: this.db.prepare(`SELECT key, value FROM ${tableName}`),
43
+ keys: this.db.prepare(`SELECT key FROM ${tableName}`),
44
+ cleanup: this.db.prepare(`DELETE FROM ${tableName} WHERE expires_at IS NOT NULL AND expires_at < ?`),
45
+ };
46
+
47
+ this.logger.info('SQLite cache started.');
48
+ } catch (error) {
49
+ throw new Error(
50
+ 'SQLite adapter requires "better-sqlite3" package. Installation: npm install better-sqlite3'
51
+ );
52
+ }
53
+ }
54
+
55
+ async get(key) {
56
+ this._cleanup();
57
+ const row = this._stmts.get.get(key);
58
+ if (!row) return undefined;
59
+
60
+ if (row.expires_at && Date.now() > row.expires_at) {
61
+ this._stmts.delete.run(key);
62
+ return undefined;
63
+ }
64
+
65
+ try {
66
+ return JSON.parse(row.value);
67
+ } catch {
68
+ return row.value;
69
+ }
70
+ }
71
+
72
+ async set(key, value, ttl) {
73
+ const effectiveTTL = ttl ?? this.defaultTTL;
74
+ const expiresAt = effectiveTTL > 0 ? Date.now() + (effectiveTTL * 1000) : null;
75
+ const serialized = JSON.stringify(value);
76
+ this._stmts.set.run(key, serialized, expiresAt);
77
+ }
78
+
79
+ async delete(key) {
80
+ const result = this._stmts.delete.run(key);
81
+ return result.changes > 0;
82
+ }
83
+
84
+ async has(key) {
85
+ const value = await this.get(key);
86
+ return value !== undefined;
87
+ }
88
+
89
+ async clear() {
90
+ this._stmts.clear.run();
91
+ }
92
+
93
+ async size() {
94
+ this._cleanup();
95
+ const row = this._stmts.size.get();
96
+ return row.count;
97
+ }
98
+
99
+ async values() {
100
+ this._cleanup();
101
+ const rows = this._stmts.all.all();
102
+ return rows.map((row) => {
103
+ try { return JSON.parse(row.value); } catch { return row.value; }
104
+ });
105
+ }
106
+
107
+ async keys() {
108
+ this._cleanup();
109
+ return this._stmts.keys.all().map((row) => row.key);
110
+ }
111
+
112
+ async filter(fn) {
113
+ this._cleanup();
114
+ const rows = this._stmts.all.all();
115
+ const results = [];
116
+ for (const row of rows) {
117
+ let value;
118
+ try { value = JSON.parse(row.value); } catch { value = row.value; }
119
+ if (fn(value, row.key)) {
120
+ results.push(value);
121
+ }
122
+ }
123
+ return results;
124
+ }
125
+
126
+ _cleanup() {
127
+ this._stmts.cleanup.run(Date.now());
128
+ }
129
+
130
+ async destroy() {
131
+ if (this.db) {
132
+ this.db.close();
133
+ this.logger.info('SQLite cache closed.');
134
+ }
135
+ }
136
+
137
+ // ── Advanced SQLite Features ───────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Get multiple keys at once.
141
+ * @param {string[]} keys
142
+ * @returns {Promise<Map<string, any>>}
143
+ */
144
+ async getMany(keys) {
145
+ if (keys.length === 0) return new Map();
146
+
147
+ const placeholders = keys.map(() => '?').join(',');
148
+ const query = `SELECT key, value, expires_at FROM "${this.table}" WHERE key IN (${placeholders})`;
149
+ const rows = this.db.prepare(query).all(...keys);
150
+
151
+ const result = new Map();
152
+ const now = Date.now();
153
+
154
+ for (const row of rows) {
155
+ if (row.expires_at && now > row.expires_at) {
156
+ this._stmts.delete.run(row.key);
157
+ continue;
158
+ }
159
+
160
+ try {
161
+ result.set(row.key, JSON.parse(row.value));
162
+ } catch {
163
+ result.set(row.key, row.value);
164
+ }
165
+ }
166
+
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Set multiple keys at once in a transaction.
172
+ * @param {Array<[string, any, number?]>} entries
173
+ * @returns {Promise<void>}
174
+ */
175
+ async setMany(entries) {
176
+ if (entries.length === 0) return;
177
+
178
+ const effectiveTTL = entries.map(([_, __, ttl]) => ttl ?? this.defaultTTL);
179
+ const expiresAts = effectiveTTL.map(ttl => ttl > 0 ? Date.now() + ttl * 1000 : null);
180
+
181
+ const insert = this.db.prepare(
182
+ `INSERT OR REPLACE INTO "${this.table}" (key, value, expires_at) VALUES (?, ?, ?)`
183
+ );
184
+
185
+ const insertMany = this.db.transaction((entries) => {
186
+ for (const [key, value, expiresAt] of entries) {
187
+ const serialized = JSON.stringify(value);
188
+ insert.run(key, serialized, expiresAt);
189
+ }
190
+ });
191
+
192
+ const transactionEntries = entries.map(([key, value, _], i) => [
193
+ key,
194
+ value,
195
+ expiresAts[i]
196
+ ]);
197
+
198
+ insertMany(transactionEntries);
199
+ }
200
+
201
+ /**
202
+ * Get TTL of a key.
203
+ * @param {string} key
204
+ * @returns {Promise<number|null>} TTL in milliseconds, or null if no expiry
205
+ */
206
+ async getTTL(key) {
207
+ const row = this._stmts.get.get(key);
208
+ if (!row || !row.expires_at) return null;
209
+
210
+ const remaining = row.expires_at - Date.now();
211
+ return remaining > 0 ? remaining : 0;
212
+ }
213
+
214
+ /**
215
+ * Set TTL on existing key.
216
+ * @param {string} key
217
+ * @param {number} ttl TTL in seconds
218
+ * @returns {Promise<boolean>}
219
+ */
220
+ async setTTL(key, ttl) {
221
+ const row = this._stmts.get.get(key);
222
+ if (!row) return false;
223
+
224
+ const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : null;
225
+ const update = this.db.prepare(
226
+ `UPDATE "${this.table}" SET expires_at = ? WHERE key = ?`
227
+ );
228
+ const result = update.run(expiresAt, key);
229
+
230
+ return result.changes > 0;
231
+ }
232
+
233
+ /**
234
+ * Get cache statistics.
235
+ * @returns {Promise<object>}
236
+ */
237
+ async getStats() {
238
+ this._cleanup();
239
+
240
+ const sizeRow = this._stmts.size.get();
241
+ const totalSize = this.db.prepare(
242
+ `SELECT SUM(LENGTH(value)) as total FROM "${this.table}"`
243
+ ).get();
244
+
245
+ const expiredCount = this.db.prepare(
246
+ `SELECT COUNT(*) as count FROM "${this.table}" WHERE expires_at IS NOT NULL AND expires_at < ?`
247
+ ).get(Date.now());
248
+
249
+ return {
250
+ size: sizeRow.count,
251
+ memoryUsage: totalSize.total || 0,
252
+ expiredCount: expiredCount.count,
253
+ table: this.table,
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Vacuum the database to reclaim space.
259
+ * @returns {Promise<void>}
260
+ */
261
+ async vacuum() {
262
+ this.db.exec('VACUUM');
263
+ this.logger.info('SQLite cache vacuumed.');
264
+ }
265
+
266
+ /**
267
+ * Get database file size in bytes.
268
+ * @returns {Promise<number>}
269
+ */
270
+ async getDatabaseSize() {
271
+ if (this.db.inMemory) return 0;
272
+
273
+ const result = this.db.prepare('PRAGMA page_count').get();
274
+ const pageSize = this.db.prepare('PRAGMA page_size').get();
275
+
276
+ return result.page_count * pageSize.page_size;
277
+ }
278
+ }
279
+
280
+ module.exports = SQLiteAdapter;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const CacheManager = require('./CacheManager');
4
+ const MemoryAdapter = require('./MemoryAdapter');
5
+ const RedisAdapter = require('./RedisAdapter');
6
+ const SQLiteAdapter = require('./SQLiteAdapter');
7
+ const MultiLevelCache = require('./MultiLevelCache');
8
+
9
+ module.exports = {
10
+ CacheManager,
11
+ MemoryAdapter,
12
+ RedisAdapter,
13
+ SQLiteAdapter,
14
+ MultiLevelCache,
15
+ };