kythia-core 0.9.3-beta → 0.9.4-beta.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.
package/changelog.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ### 0.9.4-beta.0 (2025-11-04)
6
+
7
+
8
+ ### 🔧 Changed
9
+
10
+ * Kythia core library structure and update version to 0.9.1-beta. Simplified index.js exports, improved README with installation instructions, and enhanced documentation across various modules. Updated license to CC BY NC 4.0. Cleaned up unnecessary comments and improved code readability. ([1967b65](https://github.com/kenndeclouv/kythia-core/commit/1967b651d49b4c2a85403c2e5d7a58d95ba1d1ed))
11
+ * Update package name to "kythia-core", adjust LRUCache import, and ensure logger availability in InteractionManager for command execution context. ([9e90cf6](https://github.com/kenndeclouv/kythia-core/commit/9e90cf6e04c4655ffbf71813e173de8b8d074b79))
12
+ * Update version to 0.9.2-beta, enhance caching layer with LRU cache support, and improve sharding awareness in KythiaModel. ([6cfb275](https://github.com/kenndeclouv/kythia-core/commit/6cfb275543410aee6e5df429036be9571134618b))
13
+
14
+
15
+ ### ✨ Added
16
+
17
+ * Enhance KythiaModel with multi-Redis fallback support, allowing connection to multiple Redis URLs for improved failover handling. Updated dependency injection to accept various Redis options and refined error handling for Redis connections. ([9eac22b](https://github.com/kenndeclouv/kythia-core/commit/9eac22b299b06842514e9576cdf1c08c09944a50))
18
+ * Introduce failover cache flushing in KythiaModel during Redis connection recovery, enhancing error handling and connection management. Added a flag to track failover state and updated event handlers for improved logging and cache management. ([bb30d36](https://github.com/kenndeclouv/kythia-core/commit/bb30d36a845086675fd35774d773fc04d213bc0b))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kythia-core",
3
- "version": "0.9.3-beta",
3
+ "version": "0.9.4-beta.0",
4
4
  "description": "Core library for the Kythia main Discord bot: extensible, modular, and scalable foundation for commands, components, and event management.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Kythia.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * @file src/Kythia.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * This file contains the main Bot class - acting as an orchestrator (CEO) that
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaModel.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Caching layer for Sequelize Models, now sharding-aware. When config.db.redis.shard === true,
@@ -16,6 +16,7 @@
16
16
  * - Shard Mode: If using Redis sharding, disables Map fallback for strict consistency.
17
17
  * - Hybrid Fallback: For non-shard setups, automatic fallback is preserved.
18
18
  * - Fast, consistent, safe cache busting.
19
+ * - Multi-Redis Fallback: Support multiple Redis URLs for failover/fallback. Will try connect to next Redis if one fails.
19
20
  */
20
21
 
21
22
  const jsonStringify = require('json-stable-stringify');
@@ -68,7 +69,12 @@ class KythiaModel extends Model {
68
69
 
69
70
  static redisErrorTimestamps = [];
70
71
 
71
- static isShardMode = false; // when true, local fallback is disabled
72
+ static isShardMode = false;
73
+
74
+ static _redisFallbackURLs = [];
75
+ static _redisCurrentIndex = 0;
76
+ static _redisFailedIndexes = new Set();
77
+ static _justFailedOver = false;
72
78
 
73
79
  /**
74
80
  * 💉 Injects core dependencies into the KythiaModel class.
@@ -77,7 +83,8 @@ class KythiaModel extends Model {
77
83
  * @param {Object} dependencies.logger - The logger instance
78
84
  * @param {Object} dependencies.config - The application config object
79
85
  * @param {Object} [dependencies.redis] - Optional Redis client instance
80
- * @param {Object} [dependencies.redisOptions] - Redis connection options if not providing a client
86
+ * @param {Object|Array|string} [dependencies.redisOptions] - Redis connection options if not providing a client.
87
+ * Can now be string (URL), object (ioredis options), or array of URLs/options for fallback.
81
88
  */
82
89
  static setDependencies({ logger, config, redis, redisOptions }) {
83
90
  if (!logger || !config) {
@@ -88,18 +95,30 @@ class KythiaModel extends Model {
88
95
  this.config = config;
89
96
  this.CACHE_VERSION = config.db?.redisCacheVersion || '1.0.0';
90
97
 
91
- // Check for sharding
92
98
  this.isShardMode = !!config?.db?.redis?.shard || false;
93
-
94
99
  if (this.isShardMode) {
95
100
  this.logger.info('🟣 [REDIS][SHARD] Detected redis sharding mode (shard: true). Local fallback cache DISABLED!');
96
101
  }
97
102
 
103
+ if (Array.isArray(redisOptions)) {
104
+ this._redisFallbackURLs = redisOptions.slice();
105
+ } else if (typeof redisOptions === 'string') {
106
+ this._redisFallbackURLs = redisOptions.split(',').map((url) => url.trim());
107
+ } else if (redisOptions && typeof redisOptions === 'object' && Array.isArray(redisOptions.urls)) {
108
+ this._redisFallbackURLs = redisOptions.urls.slice();
109
+ } else if (redisOptions) {
110
+ this._redisFallbackURLs = [redisOptions];
111
+ } else {
112
+ this._redisFallbackURLs = [];
113
+ }
114
+
115
+ this._redisCurrentIndex = 0;
116
+
98
117
  if (redis) {
99
118
  this.redis = redis;
100
119
  this.isRedisConnected = redis.status === 'ready';
101
- } else if (redisOptions) {
102
- this.initializeRedis(redisOptions);
120
+ } else if (this._redisFallbackURLs.length > 0) {
121
+ this.initializeRedis();
103
122
  } else {
104
123
  if (this.isShardMode) {
105
124
  this.logger.error('❌ [REDIS][SHARD] No Redis client/options, but shard:true. Application will work WITHOUT caching!');
@@ -114,7 +133,8 @@ class KythiaModel extends Model {
114
133
  /**
115
134
  * Helper: Track redis error timestamp, and check if error count in interval exceeds tolerance.
116
135
  * Jika error yang terjadi >= REDIS_ERROR_TOLERANCE_COUNT dalam REDIS_ERROR_TOLERANCE_INTERVAL_MS,
117
- * barulah fallback ke In-Memory (isRedisConnected = false) -- KECUALI jika shard: true.
136
+ * barulah coba connect ke redis berikutnya (multi redis), jika tidak ada, baru fallback ke In-Memory (isRedisConnected = false)
137
+ * -- KECUALI jika shard: true.
118
138
  */
119
139
  static _trackRedisError(err) {
120
140
  const now = Date.now();
@@ -124,21 +144,22 @@ class KythiaModel extends Model {
124
144
 
125
145
  if (this.redisErrorTimestamps.length >= REDIS_ERROR_TOLERANCE_COUNT) {
126
146
  if (this.isRedisConnected) {
127
- // In shard mode, fallback is not allowed!
128
- if (this.isShardMode) {
147
+ const triedFallback = this._tryRedisFailover();
148
+ if (triedFallback) {
149
+ this.logger.warn(`[REDIS] Error tolerance reached, switching to NEXT Redis failover...`);
150
+ } else if (this.isShardMode) {
129
151
  this.logger.error(
130
152
  `❌ [REDIS][SHARD] ${this.redisErrorTimestamps.length} consecutive errors in ${
131
153
  REDIS_ERROR_TOLERANCE_INTERVAL_MS / 1000
132
154
  }s. SHARD MODE: Disabling cache (NO fallback), all queries go to DB. (Last error: ${err?.message})`
133
155
  );
134
156
  this.isRedisConnected = false;
135
- // Do not schedule reconnect if redis is not supposed to fallback. Reconnect logic is fine.
136
157
  this._scheduleReconnect();
137
158
  } else {
138
159
  this.logger.error(
139
160
  `❌ [REDIS] ${this.redisErrorTimestamps.length} consecutive errors in ${
140
161
  REDIS_ERROR_TOLERANCE_INTERVAL_MS / 1000
141
- }s. Fallback to In-Memory Cache! (Last error: ${err?.message})`
162
+ }s. All Redis exhausted, fallback to In-Memory Cache! (Last error: ${err?.message})`
142
163
  );
143
164
  this.isRedisConnected = false;
144
165
  this._scheduleReconnect();
@@ -154,94 +175,162 @@ class KythiaModel extends Model {
154
175
  }
155
176
 
156
177
  /**
157
- * 🔌 Initializes the Redis connection if not already initialized.
158
- * @param {string|Object} redisOptions - Redis connection string or options object
159
- * @returns {Object} The Redis client instance
178
+ * Coba switch ke redis URL berikutnya jika ada. Return true jika switching, false jika tidak ada lagi.
179
+ * PRIVATE.
160
180
  */
161
- static initializeRedis(redisOptions) {
162
- if (this.redis) return this.redis;
181
+ static _tryRedisFailover() {
182
+ if (!Array.isArray(this._redisFallbackURLs) || this._redisFallbackURLs.length < 2) {
183
+ return false;
184
+ }
185
+ const prevIndex = this._redisCurrentIndex;
186
+ if (this._redisCurrentIndex + 1 < this._redisFallbackURLs.length) {
187
+ this._redisCurrentIndex++;
188
+ this.logger.warn(
189
+ `[REDIS][FAILOVER] Trying to switch Redis connection from url index ${prevIndex} to ${this._redisCurrentIndex}`
190
+ );
163
191
 
164
- const Redis = require('ioredis');
165
- this.lastRedisOpts = redisOptions;
192
+ this._justFailedOver = true;
166
193
 
167
- // Check sharding now if not set yet (for runtime .initializeRedis case)
168
- if (redisOptions && typeof redisOptions === 'object' && redisOptions.shard) {
169
- this.isShardMode = true;
194
+ this._closeCurrentRedis();
195
+ this.initializeRedis();
196
+ return true;
170
197
  }
198
+ return false;
199
+ }
171
200
 
172
- if (!redisOptions || (typeof redisOptions === 'string' && redisOptions.trim() === '')) {
201
+ /**
202
+ * Close the current Redis (if exists).
203
+ * PRIVATE.
204
+ */
205
+ static _closeCurrentRedis() {
206
+ if (this.redis && typeof this.redis.quit === 'function') {
207
+ try {
208
+ this.redis.quit();
209
+ } catch (e) {}
210
+ }
211
+ this.redis = undefined;
212
+ this.isRedisConnected = false;
213
+ }
214
+
215
+ /**
216
+ * 🔌 Initializes the Redis connection if not already initialized.
217
+ * (Versi ini MENGHAPUS lazyConnect dan _attemptConnection untuk fix race condition)
218
+ */
219
+ static initializeRedis(redisOptions) {
220
+ if (redisOptions) {
221
+ if (Array.isArray(redisOptions)) {
222
+ this._redisFallbackURLs = redisOptions.slice();
223
+ this._redisCurrentIndex = 0;
224
+ } else if (redisOptions && typeof redisOptions === 'object' && Array.isArray(redisOptions.urls)) {
225
+ this._redisFallbackURLs = redisOptions.urls.slice();
226
+ this._redisCurrentIndex = 0;
227
+ } else {
228
+ this._redisFallbackURLs = [redisOptions];
229
+ this._redisCurrentIndex = 0;
230
+ }
231
+ }
232
+
233
+ if (!Array.isArray(this._redisFallbackURLs) || this._redisFallbackURLs.length === 0) {
173
234
  if (this.isShardMode) {
174
235
  this.logger.error('❌ [REDIS][SHARD] No Redis URL/options provided but shard:true. Will run without caching!');
175
236
  this.isRedisConnected = false;
176
237
  } else {
177
- this.logger.warn('🟠 [REDIS] No Redis URL provided. Operating in In-Memory Cache mode only.');
238
+ this.logger.warn('🟠 [REDIS] No Redis client or options provided. Operating in In-Memory Cache mode only.');
178
239
  this.isRedisConnected = false;
179
240
  }
180
241
  return null;
181
242
  }
182
243
 
183
- const retryStrategy = (times) => {
184
- if (times > 5) {
185
- if (this.isShardMode) {
186
- this.logger.error(`❌ [REDIS][SHARD] Could not connect after ${times - 1} retries. Disabling cache (no fallback)!`);
187
- } else {
188
- this.logger.error(`❌ [REDIS] Could not connect after ${times - 1} retries. Falling back to In-Memory Cache.`);
189
- }
190
- return null;
191
- }
192
- const delay = Math.min(times * 500, 2000);
193
- this.logger.warn(`🟠 [REDIS] Connection failed. Retrying in ${delay}ms (Attempt ${times})...`);
194
- return delay;
195
- };
244
+ const Redis = require('ioredis');
245
+ this.lastRedisOpts = Array.isArray(this._redisFallbackURLs) ? this._redisFallbackURLs.slice() : [this._redisFallbackURLs];
196
246
 
197
- const finalOptions =
198
- typeof redisOptions === 'string'
199
- ? { url: redisOptions, retryStrategy, lazyConnect: true }
200
- : { maxRetriesPerRequest: 2, enableReadyCheck: true, retryStrategy, lazyConnect: true, ...redisOptions };
247
+ if (this.redis) return this.redis;
201
248
 
202
- this.redis = new Redis(
203
- typeof redisOptions === 'string' ? redisOptions : finalOptions,
204
- typeof redisOptions === 'string' ? finalOptions : undefined
249
+ const opt = this._redisFallbackURLs[this._redisCurrentIndex];
250
+
251
+ if (opt && typeof opt === 'object' && opt.shard) {
252
+ this.isShardMode = true;
253
+ }
254
+
255
+ let redisOpt;
256
+ if (typeof opt === 'string') {
257
+ redisOpt = { url: opt, retryStrategy: this._makeRetryStrategy() };
258
+ } else if (opt && typeof opt === 'object') {
259
+ redisOpt = {
260
+ maxRetriesPerRequest: 2,
261
+ enableReadyCheck: true,
262
+ retryStrategy: this._makeRetryStrategy(),
263
+ ...opt,
264
+ };
265
+ } else {
266
+ this.logger.error('❌ [REDIS] Invalid redis config detected in list');
267
+ this.isRedisConnected = false;
268
+ return null;
269
+ }
270
+
271
+ this.logger.info(
272
+ `[REDIS][INIT] Connecting to Redis fallback #${this._redisCurrentIndex + 1}/${this._redisFallbackURLs.length}: ${
273
+ typeof opt === 'string' ? opt : redisOpt.url || '(object)'
274
+ }`
205
275
  );
206
276
 
207
- this.redis.connect().catch((err) => {
208
- if (this.isShardMode) {
209
- this.logger.error('❌ [REDIS][SHARD] Initial connection failed: ' + err.message);
210
- } else {
211
- this.logger.error('❌ [REDIS] Initial connection failed:', err.message);
212
- }
213
- });
277
+ this.redis = new Redis(redisOpt.url || redisOpt);
214
278
 
215
279
  this._setupRedisEventHandlers();
280
+
216
281
  return this.redis;
217
282
  }
218
283
 
284
+ /**
285
+ * Internal: Makes retry strategy function which wraps the fallback failover logic if all failed.
286
+ * Used by initializeRedis.
287
+ */
288
+ static _makeRetryStrategy() {
289
+ return (times) => {
290
+ if (times > 5) {
291
+ this.logger.error(`❌ [REDIS] Could not connect after ${times - 1} retries for Redis #${this._redisCurrentIndex + 1}.`);
292
+ return null;
293
+ }
294
+ const delay = Math.min(times * 500, 2000);
295
+ this.logger.warn(
296
+ `🟠 [REDIS] Connection failed for Redis #${this._redisCurrentIndex + 1}. Retrying in ${delay}ms (Attempt ${times})...`
297
+ );
298
+ return delay;
299
+ };
300
+ }
301
+
219
302
  /**
220
303
  * 🔌 Sets up Redis event handlers
221
304
  * @private
222
305
  */
223
306
  static _setupRedisEventHandlers() {
224
- this.redis.on('connect', () => {
307
+ this.redis.on('connect', async () => {
225
308
  if (!this.isRedisConnected) {
226
309
  this.logger.info('✅ [REDIS] Connection established. Switching to Redis Cache mode.');
227
310
  }
228
311
  this.isRedisConnected = true;
229
-
230
312
  this.redisErrorTimestamps = [];
231
-
232
313
  if (this.reconnectTimeout) {
233
314
  clearTimeout(this.reconnectTimeout);
234
315
  this.reconnectTimeout = null;
235
316
  }
317
+ this._redisFailedIndexes.delete(this._redisCurrentIndex);
318
+
319
+ if (this._justFailedOver) {
320
+ this.logger.warn(`[REDIS][FAILOVER] Connected to new server, flushing potentially stale cache...`);
321
+ try {
322
+ await this.redis.flushdb();
323
+ this.logger.info(`[REDIS][FAILOVER] Stale cache flushed successfully.`);
324
+ } catch (err) {
325
+ this.logger.error(`[REDIS][FAILOVER] FAILED TO FLUSH CACHE:`, err);
326
+ }
327
+ this._justFailedOver = false;
328
+ }
236
329
  });
237
330
 
238
331
  this.redis.on('error', (err) => {
239
332
  if (err && (err.code === 'ECONNREFUSED' || err.message)) {
240
- if (this.isShardMode) {
241
- this.logger.warn(`🟠 [REDIS][SHARD] Error: ${err.message}`);
242
- } else {
243
- this.logger.warn(`🟠 [REDIS] Error: ${err.message}`);
244
- }
333
+ this.logger.warn(`🟠 [REDIS] Connection error: ${err.message}`);
245
334
  }
246
335
  });
247
336
 
@@ -250,11 +339,20 @@ class KythiaModel extends Model {
250
339
  if (this.isShardMode) {
251
340
  this.logger.error('❌ [REDIS][SHARD] Connection closed. Cache DISABLED (no fallback).');
252
341
  } else {
253
- this.logger.error('❌ [REDIS] Connection closed. Falling back to In-Memory Cache mode.');
342
+ this.logger.error('❌ [REDIS] Connection closed. Fallback/failover will be attempted.');
254
343
  }
255
344
  }
256
345
  this.isRedisConnected = false;
257
- this._scheduleReconnect();
346
+
347
+ this._redisFailedIndexes.add(this._redisCurrentIndex);
348
+
349
+ this.logger.warn(`[REDIS] Connection #${this._redisCurrentIndex + 1} closed. Attempting immediate failover...`);
350
+ const triedFailover = this._tryRedisFailover();
351
+
352
+ if (!triedFailover) {
353
+ this.logger.warn(`[REDIS] Failover exhausted. Scheduling full reconnect...`);
354
+ this._scheduleReconnect();
355
+ }
258
356
  });
259
357
  }
260
358
 
@@ -277,7 +375,11 @@ class KythiaModel extends Model {
277
375
 
278
376
  this.reconnectTimeout = setTimeout(() => {
279
377
  this.reconnectTimeout = null;
280
- this.initializeRedis(this.lastRedisOpts);
378
+
379
+ this._redisCurrentIndex = 0;
380
+ this._redisFailedIndexes.clear();
381
+ this._closeCurrentRedis();
382
+ this.initializeRedis();
281
383
  }, RECONNECT_DELAY_MINUTES * 60 * 1000);
282
384
  }
283
385
 
@@ -329,9 +431,8 @@ class KythiaModel extends Model {
329
431
  if (this.isRedisConnected) {
330
432
  await this._redisSetCacheEntry(cacheKey, data, finalTtl, tags);
331
433
  } else if (!this.isShardMode) {
332
- // NON-shard only
333
434
  this._mapSetCacheEntry(cacheKey, data, finalTtl);
334
- } // else: shard mode, Redis is down, DO NOT cache
435
+ }
335
436
  }
336
437
 
337
438
  /**
@@ -345,10 +446,9 @@ class KythiaModel extends Model {
345
446
  if (this.isRedisConnected) {
346
447
  return this._redisGetCachedEntry(cacheKey, includeOptions);
347
448
  } else if (!this.isShardMode) {
348
- // fallback only if not sharding
349
449
  return this._mapGetCachedEntry(cacheKey, includeOptions);
350
450
  }
351
- // SHARD MODE: no local fallback
451
+
352
452
  return { hit: false, data: undefined };
353
453
  }
354
454
 
@@ -494,7 +594,7 @@ class KythiaModel extends Model {
494
594
  * DISABLED in shard mode.
495
595
  */
496
596
  static _mapGetCachedEntry(cacheKey, includeOptions) {
497
- if (this.isShardMode) return { hit: false, data: undefined }; // DISABLED in shard mode
597
+ if (this.isShardMode) return { hit: false, data: undefined };
498
598
 
499
599
  if (this.localNegativeCache.has(cacheKey)) {
500
600
  this.cacheStats.mapHits++;
@@ -544,7 +644,7 @@ class KythiaModel extends Model {
544
644
  * DISABLED in shard mode.
545
645
  */
546
646
  static _mapClearCache(cacheKey) {
547
- if (this.isShardMode) return; // DISABLED in shard mode
647
+ if (this.isShardMode) return;
548
648
  this.localCache.delete(cacheKey);
549
649
  this.localNegativeCache.delete(cacheKey);
550
650
  this.cacheStats.clears++;
@@ -556,7 +656,7 @@ class KythiaModel extends Model {
556
656
  * DISABLED in shard mode.
557
657
  */
558
658
  static _mapClearAllModelCache() {
559
- if (this.isShardMode) return; // DISABLED in shard mode
659
+ if (this.isShardMode) return;
560
660
  const prefix = `${this.CACHE_VERSION}:${this.name}:`;
561
661
  let cleared = 0;
562
662
 
@@ -639,7 +739,6 @@ class KythiaModel extends Model {
639
739
 
640
740
  const queryPromise = this.findOne(normalizedOptions)
641
741
  .then((record) => {
642
- // Only cache if allowed (no cache in shard/failover unless redis is up)
643
742
  if (this.isRedisConnected || !this.isShardMode) {
644
743
  const tags = [`${this.name}`];
645
744
  if (record) {
@@ -683,7 +782,6 @@ class KythiaModel extends Model {
683
782
 
684
783
  const queryPromise = this.findAll(normalizedOptions)
685
784
  .then((records) => {
686
- // Only cache if allowed
687
785
  if (this.isRedisConnected || !this.isShardMode) {
688
786
  const tags = [`${this.name}`];
689
787
 
@@ -723,7 +821,6 @@ class KythiaModel extends Model {
723
821
  }
724
822
  const findOrCreatePromise = this.findOrCreate(findOrCreateOptions)
725
823
  .then(([instance, created]) => {
726
- // Only cache if allowed
727
824
  if (this.isRedisConnected || !this.isShardMode) {
728
825
  const tags = [`${this.name}`];
729
826
  if (instance) {
@@ -757,7 +854,6 @@ class KythiaModel extends Model {
757
854
  this.cacheStats.misses++;
758
855
  const count = await this.count(countOptions);
759
856
 
760
- // Only cache if allowed
761
857
  if (this.isRedisConnected || !this.isShardMode) {
762
858
  const tags = [`${this.name}`];
763
859
  this.setCacheEntry(cacheKey, count, ttl, tags);
@@ -807,7 +903,6 @@ class KythiaModel extends Model {
807
903
 
808
904
  const result = await this.findAll(queryOptions);
809
905
 
810
- // Only cache if allowed
811
906
  if (this.isRedisConnected || !this.isShardMode) {
812
907
  const tags = [`${this.name}`];
813
908
  if (Array.isArray(cacheTags)) tags.push(...cacheTags);
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaORM.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * A utility for intelligent, hash-based syncing of Sequelize models.
@@ -4,7 +4,7 @@
4
4
  * @file src/database/KythiaSequelize.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Main Sequelize connection factory for the application
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/AddonManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all addon loading, command registration, and component management.
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/EventManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all Discord event listeners except InteractionCreate.
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/InteractionManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Handles all Discord interaction events including slash commands, buttons, modals,
@@ -4,7 +4,7 @@
4
4
  * @file src/managers/ShutdownManager.js
5
5
  * @copyright © 2025 kenndeclouv
6
6
  * @assistant chaa & graa
7
- * @version 0.9.3-beta
7
+ * @version 0.9.4-beta
8
8
  *
9
9
  * @description
10
10
  * Handles graceful shutdown procedures including interval tracking,