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,32 @@
1
+ 'use strict';
2
+
3
+ const { Readable } = require('stream');
4
+
5
+ function SqlFormatter(generator, { schema }) {
6
+ const stream = new Readable({ objectMode: true, read() {} });
7
+ const table = schema.table || 'data';
8
+ const columns = Object.keys(schema.properties || {});
9
+
10
+ stream._read = function () {
11
+ const { value, done } = generator.next ? generator.next() : { done: true };
12
+ if (done) {
13
+ this.push(null);
14
+ return;
15
+ }
16
+
17
+ const values = columns
18
+ .map((col) => {
19
+ const val = value[col];
20
+ if (val === null || val === undefined) return 'NULL';
21
+ if (typeof val === 'number' || typeof val === 'boolean') return val;
22
+ return `'${String(val).replace(/'/g, "''")}'`;
23
+ })
24
+ .join(', ');
25
+
26
+ this.push(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${values});\n`);
27
+ };
28
+
29
+ return stream;
30
+ }
31
+
32
+ module.exports = { SqlFormatter };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const { DataGenerator } = require('./DataGenerator');
4
+ const { loadSchema } = require('./schema/loadSchema');
5
+ const { formatters } = require('./formatters');
6
+ const { PluginManager } = require('./plugin/PluginManager');
7
+ const { mockFromOpenApi } = require('./apis/OpenApiMocker');
8
+ const { mockFromGraphqlSchema } = require('./apis/GraphqlMocker');
9
+
10
+ module.exports = {
11
+ DataGenerator,
12
+ loadSchema,
13
+ formatters,
14
+ PluginManager,
15
+ mockFromOpenApi,
16
+ mockFromGraphqlSchema,
17
+ };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ class PluginManager {
7
+ constructor({ pluginDir, rootDir, locale }) {
8
+ this.rootDir = rootDir;
9
+ this.locale = locale;
10
+ this.plugins = [];
11
+
12
+ if (pluginDir) {
13
+ this.loadFromDirectory(pluginDir);
14
+ }
15
+ }
16
+
17
+ loadFromDirectory(dir) {
18
+ const resolved = path.isAbsolute(dir) ? dir : path.join(this.rootDir, dir);
19
+ if (!fs.existsSync(resolved)) return;
20
+ const files = fs.readdirSync(resolved);
21
+ for (const file of files) {
22
+ if (!file.toLowerCase().endsWith('.js')) continue;
23
+ try {
24
+ const plugin = require(path.join(resolved, file));
25
+ if (plugin && typeof plugin.init === 'function') {
26
+ plugin.init({ locale: this.locale, register: this.register.bind(this) });
27
+ }
28
+ } catch (err) {
29
+ // ignore
30
+ }
31
+ }
32
+ }
33
+
34
+ register(plugin) {
35
+ this.plugins.push(plugin);
36
+ }
37
+
38
+ getGenerator(name) {
39
+ return this.plugins.find((p) => p.name === name);
40
+ }
41
+ }
42
+
43
+ module.exports = { PluginManager };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ // Example plugin that registers a custom generator
4
+ module.exports = {
5
+ init({ register }) {
6
+ register({
7
+ name: 'randomCountry',
8
+ generate({ rng, locale }) {
9
+ const list = [
10
+ 'United States',
11
+ 'Germany',
12
+ 'France',
13
+ 'Türkiye',
14
+ 'Japan',
15
+ 'Brazil',
16
+ ];
17
+ return list[rng.integer({ min: 0, max: list.length - 1 })];
18
+ },
19
+ });
20
+ },
21
+ };
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ const { createSeededRandom } = require('../utils/seed');
4
+ const { faker } = require('@faker-js/faker');
5
+
6
+ /**
7
+ * Generate a value for a `string` schema node.
8
+ */
9
+ const generateString = ({ schema, rng }) => {
10
+ if (schema.enum && Array.isArray(schema.enum) && schema.enum.length > 0) {
11
+ return rng.pick(schema.enum);
12
+ }
13
+
14
+ if (schema.format) {
15
+ switch (schema.format) {
16
+ case 'uuid':
17
+ return faker.string.uuid();
18
+ case 'email':
19
+ return faker.internet.email();
20
+ case 'url':
21
+ return faker.internet.url();
22
+ case 'date':
23
+ return faker.date.past().toISOString().split('T')[0];
24
+ case 'date-time':
25
+ return faker.date.past().toISOString();
26
+ case 'uri':
27
+ return faker.internet.url();
28
+ case 'hostname':
29
+ return faker.internet.domainName();
30
+ case 'ipv4':
31
+ return faker.internet.ip();
32
+ case 'ipv6':
33
+ return faker.internet.ipv6();
34
+ default:
35
+ break;
36
+ }
37
+ }
38
+
39
+ if (schema.pattern) {
40
+ // Return a placeholder string that matches the pattern description,
41
+ // since full regex generation requires an extra library.
42
+ // If a future version adds RandExp, it can slot in here.
43
+ return schema.pattern;
44
+ }
45
+
46
+ const min = schema.minLength ?? 3;
47
+ const max = schema.maxLength ?? Math.max(min, 20);
48
+ const length = rng.integer({ min, max });
49
+ return faker.string.alpha(length);
50
+ };
51
+
52
+ /**
53
+ * Generate a value for a `number` or `integer` schema node.
54
+ */
55
+ const generateNumber = ({ schema, rng, integer = false }) => {
56
+ const min = typeof schema.minimum === 'number' ? schema.minimum : 0;
57
+ const max = typeof schema.maximum === 'number' ? schema.maximum : min + 100;
58
+ if (integer) return rng.integer({ min: Math.ceil(min), max: Math.floor(max) });
59
+ return rng.number({ min, max });
60
+ };
61
+
62
+ const TYPE_GENERATORS = {
63
+ string: generateString,
64
+ number: (ctx) => generateNumber({ ...ctx, integer: false }),
65
+ integer: (ctx) => generateNumber({ ...ctx, integer: true }),
66
+ boolean: ({ rng }) => rng.boolean(),
67
+
68
+ object: ({ schema, rng, locale, pluginManager }) => {
69
+ const result = {};
70
+ const props = schema.properties || {};
71
+ const required = new Set(schema.required || []);
72
+
73
+ for (const [key, propSchema] of Object.entries(props)) {
74
+ // Skip optional fields ~30% of the time unless they're required
75
+ if (!required.has(key) && rng.number({ min: 0, max: 1 }) < 0.3) continue;
76
+ result[key] = generateBySchema({ schema: propSchema, rng, locale, pluginManager });
77
+ }
78
+ return result;
79
+ },
80
+
81
+ array: ({ schema, rng, locale, pluginManager }) => {
82
+ const minItems = schema.minItems ?? 1;
83
+ const maxItems = schema.maxItems ?? Math.max(minItems, 5);
84
+ const length = rng.integer({ min: minItems, max: maxItems });
85
+ const items = [];
86
+ for (let i = 0; i < length; i++) {
87
+ items.push(generateBySchema({ schema: schema.items || {}, rng, locale, pluginManager }));
88
+ }
89
+ return items;
90
+ },
91
+
92
+ null: () => null,
93
+ };
94
+
95
+ /**
96
+ * Generate a value from any schema node.
97
+ * @param {object} ctx
98
+ * @param {object} ctx.schema
99
+ * @param {object} ctx.rng
100
+ * @param {string} ctx.locale
101
+ * @param {object} [ctx.pluginManager]
102
+ * @returns {any}
103
+ */
104
+ function generateBySchema({ schema = {}, rng, locale, pluginManager }) {
105
+ if (schema === null || schema === undefined) return null;
106
+ if (schema.const !== undefined) return schema.const;
107
+
108
+ // oneOf / anyOf support
109
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
110
+ return generateBySchema({ schema: rng.pick(schema.oneOf), rng, locale, pluginManager });
111
+ }
112
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
113
+ return generateBySchema({ schema: rng.pick(schema.anyOf), rng, locale, pluginManager });
114
+ }
115
+
116
+ // Plugin generators
117
+ if (schema.generator && pluginManager) {
118
+ const plugin = pluginManager.getGenerator?.(schema.generator);
119
+ if (plugin && typeof plugin.generate === 'function') {
120
+ return plugin.generate({ schema, rng, locale });
121
+ }
122
+ }
123
+
124
+ // Nullable support
125
+ if (schema.nullable && rng.number({ min: 0, max: 1 }) < 0.15) return null;
126
+
127
+ // Infer type when not explicitly set
128
+ const type =
129
+ schema.type ||
130
+ (schema.properties ? 'object' : schema.items ? 'array' : 'string');
131
+
132
+ // Handle type arrays: e.g. { type: ['string', 'null'] }
133
+ const resolvedType = Array.isArray(type) ? rng.pick(type) : type;
134
+
135
+ const generator = TYPE_GENERATORS[resolvedType];
136
+ if (!generator) {
137
+ // Unknown type — return null rather than silently crashing
138
+ return null;
139
+ }
140
+
141
+ return generator({ schema, rng, locale, pluginManager });
142
+ }
143
+
144
+ class SchemaGenerator {
145
+ /**
146
+ * @param {object} options
147
+ * @param {object} options.schema - JSON Schema object
148
+ * @param {string|number} [options.seed] - Seed for reproducible output
149
+ * @param {string} [options.locale='en']
150
+ * @param {object} [options.pluginManager]
151
+ */
152
+ constructor({ schema, seed, locale = 'en', pluginManager } = {}) {
153
+ if (!schema || typeof schema !== 'object') {
154
+ throw new TypeError('schema must be a non-null object');
155
+ }
156
+ this.schema = schema;
157
+ this.locale = locale;
158
+ this.rng = createSeededRandom(seed, { locale });
159
+ this.pluginManager = pluginManager;
160
+ }
161
+
162
+ generate() {
163
+ return generateBySchema({
164
+ schema: this.schema,
165
+ rng: this.rng,
166
+ locale: this.locale,
167
+ pluginManager: this.pluginManager,
168
+ });
169
+ }
170
+ }
171
+
172
+ module.exports = { SchemaGenerator, generateBySchema };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ function loadSchema(filePath) {
6
+ const raw = fs.readFileSync(filePath, 'utf8');
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch (err) {
10
+ throw new Error(`Unable to parse schema JSON: ${err.message}`);
11
+ }
12
+ }
13
+
14
+ module.exports = { loadSchema };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const seedrandom = require('seedrandom');
4
+
5
+ function createSeededRandom(seed, { locale } = {}) {
6
+ const rng = seedrandom(String(seed || Date.now()));
7
+
8
+ return {
9
+ number({ min = 0, max = 1 }) {
10
+ return rng() * (max - min) + min;
11
+ },
12
+ integer({ min = 0, max = 100 }) {
13
+ return Math.floor(rng() * (max - min + 1)) + min;
14
+ },
15
+ boolean() {
16
+ return rng() >= 0.5;
17
+ },
18
+ pick(array) {
19
+ if (!Array.isArray(array) || array.length === 0) return null;
20
+ const idx = Math.floor(rng() * array.length);
21
+ return array[idx];
22
+ },
23
+ };
24
+ }
25
+
26
+ module.exports = { createSeededRandom };
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Dependency Injection Container.
5
+ * Register services and inject them into commands/plugins automatically.
6
+ *
7
+ * @example
8
+ * client.services.register('db', new DatabaseService());
9
+ * client.services.register('api', new ExternalAPI());
10
+ *
11
+ * // In a command:
12
+ * class MyCmd extends Command {
13
+ * inject = ['db', 'api'];
14
+ * async execute(interaction) {
15
+ * await this.db.query(...);
16
+ * }
17
+ * }
18
+ */
19
+ class ServiceContainer {
20
+ constructor(client) {
21
+ this.client = client;
22
+
23
+ /** @type {Map<string, { instance: any, singleton: boolean, factory: Function|null, initialized: boolean }>} */
24
+ this._services = new Map();
25
+
26
+ /** @type {Map<string, Function[]>} Lifecycle hooks */
27
+ this._hooks = new Map();
28
+ }
29
+
30
+ /**
31
+ * Register a service or factory.
32
+ * @param {string} name - Unique service identifier
33
+ * @param {any|Function} instanceOrFactory - Service instance or factory (client) => instance
34
+ * @param {object} [options]
35
+ * @param {boolean} [options.singleton=true] - False creates a new instance per get()
36
+ * @returns {ServiceContainer} this (chainable)
37
+ */
38
+ register(name, instanceOrFactory, options = {}) {
39
+ if (typeof name !== 'string' || !name.trim()) {
40
+ throw new TypeError('Service name must be a non-empty string.');
41
+ }
42
+ if (instanceOrFactory === undefined || instanceOrFactory === null) {
43
+ throw new TypeError(`Service "${name}" value cannot be null or undefined.`);
44
+ }
45
+
46
+ const singleton = options.singleton !== false;
47
+ // Heuristic: arrow functions and regular functions with no .prototype.constructor
48
+ // pairing are treated as factories. Named classes / constructed objects are not.
49
+ const isFactory =
50
+ typeof instanceOrFactory === 'function' &&
51
+ !instanceOrFactory.prototype?.constructor?.name?.length;
52
+
53
+ this._services.set(name, {
54
+ instance: isFactory && singleton ? null : isFactory ? undefined : instanceOrFactory,
55
+ singleton,
56
+ factory: isFactory ? instanceOrFactory : null,
57
+ initialized: !isFactory,
58
+ });
59
+
60
+ this.client.logger?.debug?.(`[ServiceContainer] Registered: ${name}`);
61
+ this._runHooks('register', name);
62
+ return this;
63
+ }
64
+
65
+ /**
66
+ * Register a service that is lazily constructed using a factory.
67
+ * More explicit alternative to passing a factory to register().
68
+ * @param {string} name
69
+ * @param {Function} factory - (client) => instance
70
+ * @param {object} [options]
71
+ * @returns {ServiceContainer}
72
+ */
73
+ registerFactory(name, factory, options = {}) {
74
+ if (typeof factory !== 'function') {
75
+ throw new TypeError(`Factory for "${name}" must be a function.`);
76
+ }
77
+ return this.register(name, factory, { ...options, _forceFactory: true });
78
+ }
79
+
80
+ /**
81
+ * Get a service by name. Lazily initialises singleton factories.
82
+ * @param {string} name
83
+ * @returns {any}
84
+ */
85
+ get(name) {
86
+ const entry = this._services.get(name);
87
+ if (!entry) {
88
+ throw new Error(
89
+ `Service "${name}" is not registered. ` +
90
+ `Available: [${[...this._services.keys()].join(', ')}]`
91
+ );
92
+ }
93
+
94
+ if (entry.factory) {
95
+ if (entry.singleton) {
96
+ if (!entry.initialized) {
97
+ entry.instance = entry.factory(this.client);
98
+ entry.initialized = true;
99
+ }
100
+ return entry.instance;
101
+ }
102
+ // Transient: new instance every call
103
+ return entry.factory(this.client);
104
+ }
105
+
106
+ return entry.instance;
107
+ }
108
+
109
+ /**
110
+ * Check if a service is registered.
111
+ * @param {string} name
112
+ * @returns {boolean}
113
+ */
114
+ has(name) {
115
+ return this._services.has(name);
116
+ }
117
+
118
+ /**
119
+ * Unregister and destroy a service.
120
+ * @param {string} name
121
+ * @returns {boolean}
122
+ */
123
+ unregister(name) {
124
+ const entry = this._services.get(name);
125
+ if (!entry) return false;
126
+
127
+ if (entry.instance && typeof entry.instance.destroy === 'function') {
128
+ try {
129
+ entry.instance.destroy();
130
+ } catch (err) {
131
+ this.client.logger?.warn?.(`[ServiceContainer] Error destroying "${name}": ${err.message}`);
132
+ }
133
+ }
134
+
135
+ this._services.delete(name);
136
+ this._runHooks('unregister', name);
137
+ return true;
138
+ }
139
+
140
+ /**
141
+ * Inject registered services into a target object's `inject` array as lazy getters.
142
+ * @param {object} target - Object with `inject: string[]` property
143
+ * @returns {object} target (mutated in-place)
144
+ */
145
+ inject(target) {
146
+ if (!target || !Array.isArray(target.inject)) return target;
147
+
148
+ for (const name of target.inject) {
149
+ if (this.has(name)) {
150
+ Object.defineProperty(target, name, {
151
+ get: () => this.get(name),
152
+ configurable: true,
153
+ enumerable: false,
154
+ });
155
+ } else {
156
+ this.client.logger?.warn?.(
157
+ `[ServiceContainer] Cannot inject "${name}": not registered.`
158
+ );
159
+ }
160
+ }
161
+
162
+ return target;
163
+ }
164
+
165
+ /**
166
+ * Subscribe to lifecycle events.
167
+ * @param {'register'|'unregister'} event
168
+ * @param {Function} fn - (name: string) => void
169
+ */
170
+ on(event, fn) {
171
+ if (!['register', 'unregister'].includes(event)) {
172
+ throw new Error(`Unknown ServiceContainer event: "${event}"`);
173
+ }
174
+ if (!this._hooks.has(event)) this._hooks.set(event, []);
175
+ this._hooks.get(event).push(fn);
176
+ }
177
+
178
+ /** @private */
179
+ _runHooks(event, name) {
180
+ const hooks = this._hooks.get(event);
181
+ if (!hooks) return;
182
+ for (const fn of hooks) {
183
+ try {
184
+ fn(name);
185
+ } catch (err) {
186
+ this.client.logger?.warn?.(`[ServiceContainer] Hook error on "${event}": ${err.message}`);
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * List metadata for all registered services.
193
+ * @returns {Array<{ name: string, singleton: boolean, initialized: boolean }>}
194
+ */
195
+ list() {
196
+ return [...this._services.entries()].map(([name, entry]) => ({
197
+ name,
198
+ singleton: entry.singleton,
199
+ initialized: entry.initialized,
200
+ hasFactory: entry.factory !== null,
201
+ }));
202
+ }
203
+
204
+ /**
205
+ * Destroy all services and clear the container.
206
+ */
207
+ async destroy() {
208
+ const destroyPromises = [];
209
+
210
+ for (const [name, entry] of this._services) {
211
+ if (entry.instance && typeof entry.instance.destroy === 'function') {
212
+ const result = entry.instance.destroy();
213
+ if (result && typeof result.then === 'function') {
214
+ destroyPromises.push(
215
+ result.catch((err) =>
216
+ this.client.logger?.warn?.(`[ServiceContainer] Error destroying "${name}": ${err.message}`)
217
+ )
218
+ );
219
+ }
220
+ }
221
+ }
222
+
223
+ await Promise.all(destroyPromises);
224
+ this._services.clear();
225
+ this._hooks.clear();
226
+ }
227
+ }
228
+
229
+ module.exports = ServiceContainer;
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ const ErrorCodes = {
4
+ // Generic
5
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
6
+ INVALID_TOKEN: 'INVALID_TOKEN',
7
+ INVALID_OPTION: 'INVALID_OPTION',
8
+ MISSING_OPTION: 'MISSING_OPTION',
9
+ INVALID_ARGUMENT: 'INVALID_ARGUMENT',
10
+
11
+ // Client
12
+ CLIENT_NOT_READY: 'CLIENT_NOT_READY',
13
+ CLIENT_ALREADY_LOGGED_IN: 'CLIENT_ALREADY_LOGGED_IN',
14
+ CLIENT_DESTROYED: 'CLIENT_DESTROYED',
15
+
16
+ // REST / HTTP
17
+ API_ERROR: 'API_ERROR',
18
+ HTTP_ERROR: 'HTTP_ERROR',
19
+ RATE_LIMITED: 'RATE_LIMITED',
20
+ RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
21
+
22
+ // WebSocket
23
+ WS_CONNECTION_FAILED: 'WS_CONNECTION_FAILED',
24
+ WS_CLOSE_REQUESTED: 'WS_CLOSE_REQUESTED',
25
+ WS_NOT_OPEN: 'WS_NOT_OPEN',
26
+ WS_ALREADY_CONNECTED: 'WS_ALREADY_CONNECTED',
27
+ WS_AUTHENTICATION_FAILED: 'WS_AUTHENTICATION_FAILED',
28
+ WS_INVALID_SESSION: 'WS_INVALID_SESSION',
29
+
30
+ // Cache
31
+ CACHE_ADAPTER_ERROR: 'CACHE_ADAPTER_ERROR',
32
+ CACHE_ADAPTER_NOT_FOUND: 'CACHE_ADAPTER_NOT_FOUND',
33
+
34
+ // Plugins
35
+ PLUGIN_LOAD_ERROR: 'PLUGIN_LOAD_ERROR',
36
+ PLUGIN_NOT_FOUND: 'PLUGIN_NOT_FOUND',
37
+ PLUGIN_ALREADY_LOADED: 'PLUGIN_ALREADY_LOADED',
38
+ PLUGIN_DEPENDENCY_MISSING: 'PLUGIN_DEPENDENCY_MISSING',
39
+ PLUGIN_INVALID: 'PLUGIN_INVALID',
40
+
41
+ // Structures
42
+ INVALID_STRUCTURE: 'INVALID_STRUCTURE',
43
+ MISSING_PERMISSIONS: 'MISSING_PERMISSIONS',
44
+ INVALID_CHANNEL_TYPE: 'INVALID_CHANNEL_TYPE',
45
+
46
+ // i18n
47
+ LOCALE_NOT_FOUND: 'LOCALE_NOT_FOUND',
48
+ TRANSLATION_KEY_MISSING: 'TRANSLATION_KEY_MISSING',
49
+
50
+ // Interactions
51
+ INTERACTION_ALREADY_REPLIED: 'INTERACTION_ALREADY_REPLIED',
52
+ INTERACTION_CALLBACK_NOT_FOUND: 'INTERACTION_CALLBACK_NOT_FOUND',
53
+ INTERACTION_NOT_AUTOCOMPLETE: 'INTERACTION_NOT_AUTOCOMPLETE',
54
+ };
55
+
56
+ const ErrorMessages = {
57
+ [ErrorCodes.UNKNOWN_ERROR]: 'An unknown error occurred.',
58
+ [ErrorCodes.INVALID_TOKEN]: 'An invalid bot token was provided.',
59
+ [ErrorCodes.INVALID_OPTION]: (option) => `Invalid option: ${option}`,
60
+ [ErrorCodes.MISSING_OPTION]: (option) => `Missing required option: ${option}`,
61
+ [ErrorCodes.INVALID_ARGUMENT]: (arg) => `Invalid argument: ${arg}`,
62
+
63
+ [ErrorCodes.CLIENT_NOT_READY]: 'The client is not ready yet.',
64
+ [ErrorCodes.CLIENT_ALREADY_LOGGED_IN]: 'The client is already logged in.',
65
+ [ErrorCodes.CLIENT_DESTROYED]: 'The client has been destroyed.',
66
+
67
+ [ErrorCodes.API_ERROR]: (code, message) => `Discord API error [${code}]: ${message}`,
68
+ [ErrorCodes.HTTP_ERROR]: (status) => `HTTP error: ${status}`,
69
+ [ErrorCodes.RATE_LIMITED]: (retryAfter) =>
70
+ `Rate limited. Retry after ${retryAfter}ms.`,
71
+ [ErrorCodes.RATE_LIMIT_EXCEEDED]: 'Maximum rate limit retry attempts reached.',
72
+
73
+ [ErrorCodes.WS_CONNECTION_FAILED]: 'WebSocket connection failed.',
74
+ [ErrorCodes.WS_CLOSE_REQUESTED]: 'WebSocket close was requested.',
75
+ [ErrorCodes.WS_NOT_OPEN]: 'WebSocket connection is not open.',
76
+ [ErrorCodes.WS_ALREADY_CONNECTED]: 'WebSocket is already connected.',
77
+ [ErrorCodes.WS_AUTHENTICATION_FAILED]: 'WebSocket authentication failed.',
78
+ [ErrorCodes.WS_INVALID_SESSION]: 'Invalid WebSocket session.',
79
+
80
+ [ErrorCodes.CACHE_ADAPTER_ERROR]: (msg) => `Cache adapter error: ${msg}`,
81
+ [ErrorCodes.CACHE_ADAPTER_NOT_FOUND]: (name) =>
82
+ `Cache adapter not found: "${name}". Valid options: memory, redis, sqlite`,
83
+
84
+ [ErrorCodes.PLUGIN_LOAD_ERROR]: (name, err) =>
85
+ `Failed to load plugin "${name}": ${err}`,
86
+ [ErrorCodes.PLUGIN_NOT_FOUND]: (name) => `Plugin not found: "${name}"`,
87
+ [ErrorCodes.PLUGIN_ALREADY_LOADED]: (name) =>
88
+ `Plugin "${name}" is already loaded.`,
89
+ [ErrorCodes.PLUGIN_DEPENDENCY_MISSING]: (name, dep) =>
90
+ `Plugin "${name}" is missing dependency: "${dep}"`,
91
+ [ErrorCodes.PLUGIN_INVALID]: (name) =>
92
+ `Invalid plugin "${name}". Must export a valid plugin object or class.`,
93
+
94
+ [ErrorCodes.INVALID_STRUCTURE]: (name) => `Invalid structure: ${name}`,
95
+ [ErrorCodes.MISSING_PERMISSIONS]: (perms) => `Missing permissions: ${perms}`,
96
+ [ErrorCodes.INVALID_CHANNEL_TYPE]: (type) => `Invalid channel type: ${type}`,
97
+
98
+ [ErrorCodes.LOCALE_NOT_FOUND]: (locale) => `Locale not found: "${locale}"`,
99
+ [ErrorCodes.TRANSLATION_KEY_MISSING]: (key) =>
100
+ `Translation key not found: "${key}"`,
101
+
102
+ [ErrorCodes.INTERACTION_ALREADY_REPLIED]:
103
+ 'This interaction has already been replied to.',
104
+ [ErrorCodes.INTERACTION_CALLBACK_NOT_FOUND]:
105
+ 'Interaction callback not found.',
106
+ [ErrorCodes.INTERACTION_NOT_AUTOCOMPLETE]:
107
+ 'This interaction is not an autocomplete interaction.',
108
+ };
109
+
110
+ module.exports = { ErrorCodes, ErrorMessages };