kythia-core 0.9.3-beta

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/README.md ADDED
@@ -0,0 +1,307 @@
1
+ <p align="center">
2
+ <a href="https://kythia.my.id">
3
+ <img src="https://kythia.my.id/assets/img/logo/logo.png" alt="Kythia Logo" height="150" style="border-radius: 10px;">
4
+ </a>
5
+ </p>
6
+
7
+ <h1 align="center">
8
+ Kythia Core
9
+ </h1>
10
+
11
+ <p align="center">
12
+ <strong>The foundational engine for building scalable and maintainable Discord bots based on the Kythia framework.</strong>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://github.com/kenndeclouv/kythia-core/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-CC%20BYNC%204.0-blue?style=for-the-badge" alt="License"></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://github.com/kenndeclouv/kythia-core/issues">Report a Bug</a>
21
+ ยท
22
+ <a href="https://github.com/kenndeclouv/kythia-core/issues">Request a Feature</a>
23
+ </p>
24
+
25
+ <div align="center">
26
+ <p><em>Powered by the following technologies:</em></p>
27
+ <img alt="Discord" src="https://img.shields.io/badge/Discord-5865F2.svg?style=flat&logo=Discord&logoColor=white">
28
+ <img alt="JavaScript" src="https://img.shields.io/badge/JavaScript-F7DF1E.svg?style=flat&logo=JavaScript&logoColor=black">
29
+ <img alt="Node.js" src="https://img.shields.io/badge/Node.js-339933.svg?style=flat&logo=nodedotjs&logoColor=white">
30
+ <img alt="Sequelize" src="https://img.shields.io/badge/Sequelize-52B0E7.svg?style=flat&logo=Sequelize&logoColor=white">
31
+ </div>
32
+
33
+ ---
34
+
35
+ ## ๐Ÿš€ Key Concepts & Architecture
36
+
37
+ `kythia-core` is built around a few core principles:
38
+
39
+ 1. **Dependency Injection (DI) Container:** The heart of the interaction between the core and the consuming application (your main bot and its addons). The `Kythia` class accepts a `dependencies` object during construction and stores critical instances (like the logger, config, database models, core helpers, etc.) in a `container` object attached to the `client`. **Addons MUST access shared resources via this container (`interaction.client.container`) within their execution context (e.g., inside `execute` functions) to prevent critical circular dependency issues during startup.**
40
+ 2. **Manager Pattern:** Core responsibilities are delegated to specialized managers:
41
+ * `AddonManager`: Discovers, loads, and registers commands, events, and components from addons.
42
+ * `EventManager`: Handles all non-interaction Discord gateway events, routing them to relevant addon handlers.
43
+ * `InteractionManager`: Handles all `InteractionCreate` events (slash commands, buttons, modals, autocomplete, context menus), including permission checks, cooldowns, and DI for command execution.
44
+ * `ShutdownManager`: Manages graceful shutdown procedures, tracking intervals and cleaning up active components.
45
+ 3. **Addon-Driven Functionality:** The core provides the *framework*, but the actual bot features (commands, specific event responses) are implemented within **addons** in the main bot application. The core is designed to discover and integrate these addons seamlessly.
46
+ 4. **Smart Database Layer:** Includes a base model (`KythiaModel`) with an advanced caching layer and an intelligent ORM utility (`KythiaORM`) for safe and efficient schema synchronization.
47
+
48
+ ---
49
+
50
+ ## ๐Ÿ”ง Core Components (Exports)
51
+
52
+ This package exports the following key components via its main `index.js`:
53
+
54
+ ### `Kythia` (Default Export & Named Export)
55
+
56
+ * **File:** `src/Kythia.js`
57
+ * **Role:** The main orchestrator class. It initializes all managers, manages the startup sequence, and holds the central dependency container.
58
+ * **Usage:**
59
+ * Instantiated in your main bot's `index.js` (`new Kythia(dependencies)`).
60
+ * Requires a `dependencies` object containing essential services (`config`, `logger`, `translator`, `redis`, `sequelize`, `helpers`, `utils`, `appRoot`).
61
+ * Requires `dbDependencies` to be set *after* instantiation but *before* `start()`.
62
+ * The `start()` method initiates the entire bot lifecycle (addon loading, DB sync, Discord login).
63
+
64
+ ### `KythiaClient`
65
+
66
+ * **File:** `src/KythiaClient.js`
67
+ * **Role:** An extended `discord.js` Client class. It's pre-configured with recommended intents, partials, and cache settings for a typical large bot. Crucially, the `container` is attached directly to the client instance (`client.container`) for easy access within interactions and events.
68
+ * **Usage:** Automatically instantiated by the `Kythia` class constructor. Addons access it via `interaction.client` or `container.client`.
69
+
70
+ ### `KythiaModel`
71
+
72
+ * **File:** `src/database/KythiaModel.js`
73
+ * **Role:** The **base class for ALL Sequelize models** used within the Kythia framework (both core and addons). It provides a powerful, built-in caching layer.
74
+ * **Key Features:**
75
+ * **Hybrid Caching:** Prioritizes Redis for shared caching, seamlessly falls back to an in-memory Map cache if Redis is unavailable, ensuring high availability.
76
+ * **Cache Methods:** Provides `getCache(query)`, `getAllCache(query)`, `findOrCreateWithCache(options)`, `countWithCache(options)`, `aggregateWithCache(options)` for efficient data retrieval.
77
+ * **Automatic Invalidation:** Includes Sequelize lifecycle hooks (`afterSave`, `afterDestroy`, `afterBulkUpdate`, etc.) that automatically invalidate relevant cache entries using **tag-based sniper invalidation** for precision.
78
+ * **Dependency Injection:** Requires core dependencies (`logger`, `config`, `redis`) to be injected *once* at startup using the static `KythiaModel.setDependencies({...})` method in your main `index.js`.
79
+ * **Parent Touching:** Includes helpers (`touchParent`, `setupParentTouch`) for automatically updating parent model timestamps when child models change (useful for cache invalidation).
80
+ * **Usage:** All addon models **must** `extend KythiaModel` and implement their own `static init(sequelize)` method which calls `super.init(...)`.
81
+
82
+ ### `createSequelizeInstance`
83
+
84
+ * **File:** `src/database/KythiaSequelize.js`
85
+ * **Role:** A factory function that creates and configures the main Sequelize instance based on your bot's configuration.
86
+ * **Usage:** Called in your main `index.js` to create the `sequelize` instance that gets passed into the `Kythia` constructor.
87
+
88
+ ### `KythiaORM`
89
+
90
+ * **File:** `src/database/KythiaORM.js`
91
+ * **Role:** An intelligent database synchronization utility designed for safety and efficiency, especially in production.
92
+ * **Key Features:**
93
+ * **Loads All Addon Models:** Automatically discovers and requires all model files within the `addons/*/database/models` directories, ensuring Sequelize is aware of them before syncing. Uses the `appRoot` from the container to find the `addons` directory.
94
+ * **Hash-Based Syncing:** Generates a schema hash for each model (`generateModelHash`). It compares this hash against a stored version in the `model_versions` table.
95
+ * **Selective `alter: true`:** Only runs `model.sync({ alter: true })` on models whose schema hash has actually changed, significantly speeding up startup and reducing risk.
96
+ * **Destructive Change Prevention:** Includes a `checkForDestructiveChanges` check. If it detects that syncing would drop columns, it prompts for confirmation in production or warns in development, preventing accidental data loss.
97
+ * **Handles `dbReadyHooks`:** Executes hooks registered via `kythiaInstance.addDbReadyHook()` *after* all models are loaded but *before* syncing (typically used for defining model associations).
98
+ * **Attaches Cache Hooks:** Calls `KythiaModel.attachHooksToAllModels()` after models are ready.
99
+ * **Usage:** Called internally by `Kythia.start()`.
100
+
101
+ ### `utils`
102
+
103
+ * **File:** `src/utils/index.js` (Barrel File)
104
+ * **Role:** Exports common utility functions used within the core and potentially by addons.
105
+ * **Available Utils:**
106
+ * `color`: Color conversion utilities (e.g., hex to decimal).
107
+ * `formatter`: Data formatting functions.
108
+ * **Usage:** Accessed via `require('@kenndeclouv/kythia-core').utils`. Example: `utils.color.convertColor(...)`.
109
+
110
+ ---
111
+
112
+ ## ๐Ÿ“ฆ Installation
113
+
114
+ ```bash
115
+ npm install @kenndeclouv/kythia-core
116
+ # or
117
+ yarn add @kenndeclouv/kythia-core
118
+ # or
119
+ pnpm add @kenndeclouv/kythia-core
120
+ ````
121
+
122
+ **Important:** This package declares `discord.js` as a **peer dependency**. Your main bot application **must** have `discord.js` installed as a direct dependency.
123
+
124
+ ```bash
125
+ npm install discord.js
126
+ ```
127
+
128
+ -----
129
+
130
+ ## ๐Ÿš€ Basic Usage (in your main `index.js`)
131
+
132
+ ```javascript
133
+ // 1. Load Environment & Aliases (if used)
134
+ require('dotenv').config();
135
+ const kythiaConfig = require('./kythia.config.js');
136
+ require('module-alias/register'); // Important for addon helpers/models if you use aliases
137
+
138
+ // 2. Import Core Components
139
+ const {
140
+ Kythia,
141
+ KythiaModel,
142
+ createSequelizeInstance,
143
+ utils, // Example: Accessing utils
144
+ } = require('@kenndeclouv/kythia-core');
145
+
146
+ // 3. Load Your Addon Helpers (NOT Core Helpers)
147
+ // Core helpers like logger, translator are INJECTED, not required here directly
148
+ // unless they are specifically designed to be standalone.
149
+ const logger = require('@coreHelpers/logger'); // Assuming this comes from your 'core' addon
150
+ const translator = require('@coreHelpers/translator'); // Assuming this comes from your 'core' addon
151
+ const { isTeam, isOwner, embedFooter } = require('@coreHelpers/discord'); // from 'core' addon
152
+ const { loadFonts } = require('@coreHelpers/fonts'); // from 'core' addon
153
+
154
+ // 4. Setup External Dependencies (Redis)
155
+ const Redis = require('ioredis');
156
+ const redisClient = new Redis(kythiaConfig.db.redis, { lazyConnect: true });
157
+
158
+ // 5. Setup Sequelize
159
+ const sequelize = createSequelizeInstance(kythiaConfig, logger);
160
+
161
+ // 6. Inject Dependencies into KythiaModel (CRITICAL - DO THIS ONLY ONCE)
162
+ KythiaModel.setDependencies({ logger, config: kythiaConfig, redis: redisClient });
163
+
164
+ // 7. Prepare the Dependency Container Object
165
+ const dependencies = {
166
+ config: kythiaConfig,
167
+ logger: logger, // Your logger instance
168
+ translator: translator, // Your translator instance
169
+ redis: redisClient,
170
+ sequelize: sequelize,
171
+ models: {}, // Will be populated by KythiaORM after loading addon models
172
+ helpers: { // Your addon helpers accessible via container
173
+ discord: { isTeam, isOwner, embedFooter },
174
+ fonts: { loadFonts },
175
+ color: utils.color, // Injecting core color util as a helper
176
+ // Add other shared addon helpers here if needed
177
+ },
178
+ appRoot: __dirname, // CRITICAL: Tell the core where your project root is
179
+ };
180
+
181
+ // 8. Instantiate and Start Kythia
182
+ try {
183
+ const kythiaInstance = new Kythia(dependencies);
184
+
185
+ // Set dependencies needed specifically by KythiaORM/Model Hooks
186
+ kythiaInstance.dbDependencies = {
187
+ KythiaModel,
188
+ logger,
189
+ config: kythiaConfig,
190
+ };
191
+
192
+ // Liftoff! ๐Ÿš€
193
+ kythiaInstance.start();
194
+
195
+ } catch (error) {
196
+ const log = logger || console;
197
+ log.error('๐Ÿ”ฅ FATAL ERROR during initialization:', error);
198
+ process.exit(1);
199
+ }
200
+ ```
201
+
202
+ -----
203
+
204
+ ## ๐Ÿ’ก Dependency Injection & The Container (CRITICAL FOR ADDONS)
205
+
206
+ **Problem:** Loading addons dynamically while allowing addons to use core functionalities (like models or helpers which *also* depend on the core) creates **circular dependencies**. If an addon command file tries to `require` a core helper or model at the top level, Node.js will often fail because the core (`kythia-core`) is still in the middle of loading that addon file\!
207
+
208
+ **Solution:** The **Dependency Injection Container (`interaction.client.container`)**.
209
+
210
+ * The `Kythia` core initializes all essential services and instances (`logger`, `config`, loaded `models`, core `helpers`, etc.) and puts them into the `container`.
211
+ * **Addon commands, events, and other components MUST access these shared resources via the container.** This access typically happens *inside* the function that handles the interaction or event (e.g., inside the `async execute(interaction)` function for commands).
212
+
213
+ **Example (Inside an Addon Command File):**
214
+
215
+ ```javascript
216
+ // addons/your-addon/commands/your-command.js
217
+
218
+ // โœ… OK: Require external libs or discord.js stuff at top level
219
+ const { EmbedBuilder } = require('discord.js');
220
+
221
+ // โŒ BAD: DO NOT require core helpers or models at the top level!
222
+ // const logger = require('@coreHelpers/logger'); // WRONG - Causes circular dependency
223
+ // const YourModel = require('../database/models/YourModel'); // WRONG
224
+
225
+ module.exports = {
226
+ data: /* ... your SlashCommandBuilder ... */,
227
+ async execute(interaction) {
228
+ // โœ… GOOD: Access everything from the container HERE
229
+ const { logger, models, translator, helpers, kythiaConfig } = interaction.client.container;
230
+ const { YourModel } = models; // Get the initialized model
231
+ const { t } = translator; // Get the translation function
232
+ const { someHelper } = helpers.yourCoreHelpers; // Get helpers
233
+
234
+ // Now you can safely use them
235
+ logger.info('Executing your command!');
236
+ const data = await YourModel.getCache({ userId: interaction.user.id });
237
+ await interaction.reply(await t(interaction, 'your.translation.key'));
238
+ // ...
239
+ }
240
+ }
241
+ ```
242
+
243
+ **Adhering to this DI pattern is essential for preventing startup errors.**
244
+
245
+ -----
246
+
247
+ ## ๐Ÿงฉ Addon Development Integration
248
+
249
+ `kythia-core` is designed to work seamlessly with your addon structure:
250
+
251
+ 1. **Discovery:** `AddonManager` scans the `addons/` directory located relative to the `appRoot` provided in the dependencies.
252
+ 2. **Metadata (`addon.json`):** Each addon **must** have an `addon.json` file in its root directory. This file provides:
253
+ * `name`: Display name.
254
+ * `version`: Addon version.
255
+ * `description`: What it does.
256
+ * `author`: Who made it.
257
+ * `active` (optional, default `true`): Set to `false` to disable the addon.
258
+ * `featureFlag` (optional): The corresponding boolean key in `ServerSetting` model that toggles this addon's commands per-guild (e.g., `"petOn"`). `AddonManager` uses this to perform automatic checks in `InteractionManager`.
259
+ 3. **Initialization (`register.js` - Optional):** If an addon needs to perform setup tasks *after* the core is initialized but *before* the bot logs in (e.g., define model associations, register non-command components), it can include a `register.js` file exporting an `initialize` function:
260
+ ```javascript
261
+ // addons/your-addon/register.js
262
+ module.exports = {
263
+ async initialize(kythiaInstance) {
264
+ // Access core components via kythiaInstance.container
265
+ const { logger } = kythiaInstance.container;
266
+
267
+ // Example: Register a button handler
268
+ kythiaInstance.registerButtonHandler('myButtonPrefix', require('./buttons/myButtonHandler'));
269
+
270
+ // Example: Define model associations (runs after models are loaded by KythiaORM)
271
+ kythiaInstance.addDbReadyHook((sequelize) => {
272
+ const { ModelA, ModelB } = sequelize.models;
273
+ if (ModelA && ModelB) {
274
+ ModelA.hasMany(ModelB);
275
+ ModelB.belongsTo(ModelA);
276
+ logger.info('๐Ÿ”— Model associations for YourAddon defined.');
277
+ }
278
+ });
279
+
280
+ // Return an array of strings for summary logging (optional)
281
+ return ['โœ… YourAddon component registered.'];
282
+ }
283
+ }
284
+ ```
285
+ 4. **Command Loading:** `AddonManager` looks for commands within an addon's `commands/` directory. It supports flexible structures:
286
+ * **Simple Commands:** A single `.js` file per command (e.g., `ping.js`).
287
+ * **Grouped Commands (Top-Level):** A `_command.js` file defining the main command (`/image`), with `.js` files for subcommands (`add.js`, `list.js`) and subdirectories for groups (`admin/_group.js` with `add.js`, `delete.js` inside). `subcommand: true` must be set in the subcommand module exports.
288
+ * **Individual Command Folders:** A folder per command (e.g., `ping/`) containing `_command.js` for the main definition and potentially subcommand/group files inside.
289
+
290
+ -----
291
+
292
+ ## ๐Ÿ—„๏ธ Database Layer (`KythiaModel` & `KythiaORM`)
293
+
294
+ * **`KythiaModel`:** Provides a robust caching layer out-of-the-box. Extend it for all your models. Remember to call `KythiaModel.setDependencies` once. The hybrid Redis/Map cache ensures performance and resilience. Tag-based invalidation keeps caches fresh with precision.
295
+ * **`KythiaORM`:** Handles database synchronization safely. It only alters tables when necessary, preventing accidental data loss in production and speeding up development startup. Ensure all models extend `KythiaModel` and have a `static init(sequelize)` method for `KythiaORM` to discover and initialize them correctly.
296
+
297
+ -----
298
+
299
+ ## ๐Ÿ”— Peer Dependencies
300
+
301
+ * **`discord.js`:** This is a `peerDependency`. `kythia-core` requires `discord.js` to function, but it expects the *consuming application* (your main bot) to provide it by listing `discord.js` in its own `dependencies`. This prevents version conflicts and issues with `instanceof` checks, especially common when using `npm link` during development. Ensure your main bot project has `discord.js` installed.
302
+
303
+ -----
304
+
305
+ ## ๐Ÿ“œ License
306
+
307
+ This project is licensed under the CC BY NC 4.0 License - see the [LICENSE](./LICENSE) file for details.
package/index.js ADDED
@@ -0,0 +1,17 @@
1
+ const Kythia = require('./src/Kythia.js');
2
+
3
+ module.exports = Kythia;
4
+
5
+ module.exports.Kythia = Kythia;
6
+
7
+ module.exports.KythiaClient = require('./src/KythiaClient.js');
8
+
9
+ module.exports.KythiaModel = require('./src/database/KythiaModel.js');
10
+
11
+ module.exports.createSequelizeInstance = require('./src/database/KythiaSequelize.js');
12
+
13
+ module.exports.KythiaORM = require('./src/database/KythiaORM.js');
14
+
15
+ module.exports.utils = require('./src/utils/index.js');
16
+
17
+ module.exports.BaseCommand = require('./src/structures/BaseCommand.js')
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "kythia-core",
3
+ "version": "0.9.3-beta",
4
+ "description": "Core library for the Kythia main Discord bot: extensible, modular, and scalable foundation for commands, components, and event management.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [
10
+ "kythia",
11
+ "discord",
12
+ "bot",
13
+ "core",
14
+ "framework",
15
+ "addon",
16
+ "commands",
17
+ "components",
18
+ "extensible",
19
+ "modular"
20
+ ],
21
+ "author": {
22
+ "name": "kenndeclouv",
23
+ "mail": "kenndeclouv@gmail.com"
24
+ },
25
+ "license": "CC BY NC 4.0",
26
+ "type": "commonjs",
27
+ "dependencies": {
28
+ "@sentry/node": "^10.10.0",
29
+ "async-exit-hook": "^2.0.1",
30
+ "async-mutex": "^0.5.0",
31
+ "cli-color": "^2.0.4",
32
+ "dotenv": "^16.6.1",
33
+ "figlet": "^1.9.3",
34
+ "ioredis": "^5.7.0",
35
+ "json-stable-stringify": "^1.3.0",
36
+ "lru-cache": "^11.2.2",
37
+ "sequelize": "^6.37.7"
38
+ },
39
+ "peerDependencies": {
40
+ "discord.js": "^14.22.1"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/kenndeclouv/kythia-core.git"
45
+ }
46
+ }