zumito-framework 1.21.0 → 1.22.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.
@@ -10,6 +10,7 @@ import { ModuleManager } from './services/managers/ModuleManager.js';
10
10
  import { Route } from './definitions/Route.js';
11
11
  import { Db } from 'mongodb';
12
12
  import { MongoService } from './services/MongoService.js';
13
+ import { DatabaseManager } from 'zumito-db';
13
14
  /**
14
15
  * @class ZumitoFramework
15
16
  * @description The main class of the framework.
@@ -70,15 +71,20 @@ export declare class ZumitoFramework {
70
71
  /**
71
72
  * The MongoDB service instance.
72
73
  * @type {MongoService}
73
- * @private
74
+ * @deprecated Use `framework.db` (DatabaseManager from zumito-db) instead.
74
75
  */
75
76
  mongoService: MongoService;
76
77
  /**
77
78
  * The MongoDB database instance (shortcut).
78
79
  * @type {Db}
79
- * @private
80
+ * @deprecated Use `framework.db.getRepository(Model).find(...)` instead.
80
81
  */
81
82
  database: Db;
83
+ /**
84
+ * The new zumito-db DatabaseManager instance.
85
+ * @type {DatabaseManager}
86
+ */
87
+ db: DatabaseManager;
82
88
  /**
83
89
  * The ExpressJS app instance.
84
90
  * @type {express.Application}
@@ -162,7 +168,7 @@ export declare class ZumitoFramework {
162
168
  *
163
169
  * @deprecated
164
170
  */
165
- getGuildSettings(guildId: string): Promise<import("mongodb").WithId<import("bson").Document>>;
171
+ getGuildSettings(guildId: string): Promise<any>;
166
172
  /**
167
173
  * @deprecated
168
174
  */
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as url from 'url';
3
- import { Client, } from 'discord.js';
3
+ import { Client, Events, } from 'discord.js';
4
4
  import { ApiResponse } from './definitions/api/ApiResponse.js';
5
5
  import { EventEmitter } from "tseep";
6
6
  import { StatusManager } from './services/managers/StatusManager.js';
@@ -23,6 +23,7 @@ import { ErrorHandler } from './services/handlers/ErrorHandler.js';
23
23
  import { ErrorType } from './definitions/ErrorType.js';
24
24
  import { MongoService } from './services/MongoService.js';
25
25
  import { registerDefaultExecutionRules } from './modules/core/baseModule/defaultRules.js';
26
+ import { DatabaseManager } from 'zumito-db';
26
27
  // import better-logging
27
28
  betterLogging(console);
28
29
  /**
@@ -85,15 +86,20 @@ export class ZumitoFramework {
85
86
  /**
86
87
  * The MongoDB service instance.
87
88
  * @type {MongoService}
88
- * @private
89
+ * @deprecated Use `framework.db` (DatabaseManager from zumito-db) instead.
89
90
  */
90
91
  mongoService;
91
92
  /**
92
93
  * The MongoDB database instance (shortcut).
93
94
  * @type {Db}
94
- * @private
95
+ * @deprecated Use `framework.db.getRepository(Model).find(...)` instead.
95
96
  */
96
97
  database;
98
+ /**
99
+ * The new zumito-db DatabaseManager instance.
100
+ * @type {DatabaseManager}
101
+ */
102
+ db;
97
103
  /**
98
104
  * The ExpressJS app instance.
99
105
  * @type {express.Application}
@@ -140,6 +146,7 @@ export class ZumitoFramework {
140
146
  ServiceContainer.addService(TranslationManager, [], true, this.translations);
141
147
  ServiceContainer.addService(CommandManager, [], true, this.commands);
142
148
  ServiceContainer.addService(EventManager, [], true, this.eventManager);
149
+ ServiceContainer.addService(SlashCommandRefresher, [], true, new SlashCommandRefresher(this));
143
150
  if (settings.logLevel) {
144
151
  console.logLevel = settings.logLevel;
145
152
  }
@@ -167,6 +174,8 @@ export class ZumitoFramework {
167
174
  this.eventManager.addEventEmitter('framework', this.eventEmitter);
168
175
  await this.registerModules();
169
176
  registerDefaultExecutionRules();
177
+ // Ensure all model schemas (from config + module auto-load) are created
178
+ await this.db.ensureSchemas();
170
179
  await this.refreshSlashCommands();
171
180
  this.startApiServer();
172
181
  if (this.settings.statusOptions) {
@@ -179,23 +188,54 @@ export class ZumitoFramework {
179
188
  }
180
189
  async initializeDatabase() {
181
190
  const mongoUri = this.settings?.mongoQueryString || process.env.MONGO_URI;
182
- if (!mongoUri) {
183
- console.error('[🗄️🔴] MongoDB connection string not provided.');
191
+ const dbSettings = this.settings.database;
192
+ let config;
193
+ if (dbSettings?.default) {
194
+ config = {
195
+ default: dbSettings.default,
196
+ drivers: dbSettings.drivers || {},
197
+ };
198
+ }
199
+ else if (mongoUri) {
200
+ const match = mongoUri.match(/\/([^/?]+)(\?|$)/);
201
+ const dbName = match ? match[1] : 'zumito';
202
+ config = {
203
+ default: 'mongo',
204
+ drivers: { mongo: { uri: mongoUri, database: dbName } },
205
+ };
206
+ }
207
+ else {
208
+ console.error('[🗄️🔴] No database configured.');
209
+ console.error(' Set database.default in zumito.config.ts (memory, tingo, mongo, sqlite)');
210
+ console.error(' or set MONGO_URI / mongoQueryString for MongoDB.');
184
211
  process.exit(1);
185
212
  }
186
- // Extract dbName from URI or fallback
187
- const match = mongoUri.match(/\/([^/?]+)(\?|$)/);
188
- const dbName = match ? match[1] : 'zumito';
189
- this.mongoService = new MongoService(mongoUri, dbName);
213
+ if (this.settings.models) {
214
+ config.models = this.settings.models;
215
+ }
216
+ this.db = new DatabaseManager();
190
217
  try {
191
- await this.mongoService.connect();
192
- this.database = this.mongoService.db;
193
- console.log('[🗄️🟢] MongoDB connection successful!');
218
+ await this.db.connect(config);
219
+ ServiceContainer.addService(DatabaseManager, [], true, this.db);
220
+ console.log(`[🗄️🟢] Database connected (driver: ${config.default})`);
194
221
  }
195
222
  catch (err) {
196
- console.error('[🗄️🔴] MongoDB connection error:', err.message);
223
+ console.error(`[🗄️🔴] Database connection error: ${err.message}`);
197
224
  process.exit(1);
198
225
  }
226
+ // Backward compat: only set up mongoService for mongo driver
227
+ if (config.default === 'mongo' && mongoUri) {
228
+ const driver = this.db.getDriver();
229
+ this.database = driver.raw;
230
+ const dbName = config.drivers.mongo?.database || 'zumito';
231
+ this.mongoService = new MongoService(mongoUri, dbName);
232
+ this.mongoService.db = this.database;
233
+ this.mongoService.client = driver.client;
234
+ }
235
+ // Apply migrations if configured
236
+ if (this.settings.database?.migrations && this.settings.database.migrations.length > 0) {
237
+ await this.db.migrator.latest(this.settings.database.migrations);
238
+ }
199
239
  }
200
240
  /**
201
241
  * Initializes and starts the API server using ExpressJS.
@@ -306,7 +346,6 @@ export class ZumitoFramework {
306
346
  await this.modules.instanceModule(module, path.join(modulesFolder, moduleName), moduleName);
307
347
  }
308
348
  async registerBundle(bundlePath, bundleOptions) {
309
- console.log(bundlePath);
310
349
  const bundle = await this.modules.loadModuleFile(bundlePath);
311
350
  const bundleName = path.basename(bundlePath);
312
351
  await this.modules.instanceModule(bundle, bundlePath, bundleName, bundleOptions);
@@ -323,7 +362,7 @@ export class ZumitoFramework {
323
362
  this.client.login(this.settings.discordClientOptions.token);
324
363
  ServiceContainer.addService(Client, [], true, this.client);
325
364
  await new Promise((resolve) => {
326
- this.client.on('ready', () => {
365
+ this.client.on(Events.ClientReady, () => {
327
366
  // Bot emoji
328
367
  console.log('[🤖🟢] Discord client ready');
329
368
  resolve();
@@ -1,7 +1,6 @@
1
1
  import { ZumitoFramework } from '../ZumitoFramework.js';
2
2
  import { Command } from './commands/Command.js';
3
3
  import { FrameworkEvent } from './FrameworkEvent.js';
4
- import { CommandManager } from '../services/managers/CommandManager.js';
5
4
  import { ModuleParameters } from './parameters/ModuleParameters.js';
6
5
  import { ErrorHandler } from '../services/handlers/ErrorHandler.js';
7
6
  export type ModuleRequeriments = {
@@ -13,10 +12,8 @@ export declare abstract class Module {
13
12
  protected path: string;
14
13
  protected parameters: ModuleParameters;
15
14
  protected framework: ZumitoFramework;
16
- protected commands: CommandManager;
17
15
  protected events: Map<string, FrameworkEvent>;
18
16
  static requeriments: ModuleRequeriments;
19
- protected commandManager: CommandManager;
20
17
  protected errorHandler: ErrorHandler;
21
18
  constructor(path: any, parameters?: ModuleParameters);
22
19
  initialize(): Promise<void>;
@@ -29,6 +26,7 @@ export declare abstract class Module {
29
26
  parseEventArgs(args: any[]): any;
30
27
  getEvents(): Map<string, FrameworkEvent>;
31
28
  registerTranslations(subpath?: string): Promise<void>;
29
+ registerModels(): Promise<void>;
32
30
  registerRoutes(): Promise<void>;
33
31
  registerRoutesFolder(folder: string): Promise<void>;
34
32
  }
@@ -2,24 +2,21 @@ import { ZumitoFramework } from '../ZumitoFramework.js';
2
2
  import * as fs from 'fs';
3
3
  import path from 'path';
4
4
  import { ButtonInteraction, CommandInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, } from 'discord.js';
5
- import { CommandManager } from '../services/managers/CommandManager.js';
6
5
  import { ServiceContainer } from '../services/ServiceContainer.js';
7
6
  import { ErrorHandler } from '../services/handlers/ErrorHandler.js';
8
7
  import { ErrorType } from './ErrorType.js';
8
+ import { getModelMetadata } from 'zumito-db';
9
9
  export class Module {
10
10
  path;
11
11
  parameters;
12
12
  framework;
13
- commands;
14
13
  events = new Map();
15
14
  static requeriments;
16
- commandManager;
17
15
  errorHandler;
18
16
  constructor(path, parameters) {
19
17
  this.path = path;
20
18
  this.parameters = parameters;
21
19
  this.framework = ServiceContainer.getService(ZumitoFramework);
22
- this.commands = new CommandManager(this.framework);
23
20
  this.errorHandler = ServiceContainer.getService(ErrorHandler);
24
21
  }
25
22
  async initialize() {
@@ -27,26 +24,21 @@ export class Module {
27
24
  await this.registerEvents();
28
25
  await this.registerTranslations();
29
26
  await this.registerRoutes();
27
+ await this.registerModels();
30
28
  }
31
29
  async onAllReady() {
32
30
  }
33
31
  async registerCommands() {
34
32
  const commandsFolder = path.join(this.path, 'commands');
35
33
  if (fs.existsSync(commandsFolder)) {
36
- await this.commands.loadCommandsFolder(commandsFolder);
37
- // register watcher
34
+ await this.framework.commands.loadCommandsFolder(commandsFolder);
38
35
  if (process.env.DEBUG) {
39
- /*
40
- Debug only cause in prod environment commands should't be changed.
41
- Appart from that, esm module cache invalidation is not working properly
42
- and can cause memory leaks and crashes.
43
- */
44
- this.commands.watchCommandsFolder(commandsFolder);
36
+ this.framework.commands.watchCommandsFolder(commandsFolder);
45
37
  }
46
38
  }
47
39
  }
48
40
  getCommands() {
49
- return this.commands.getAll();
41
+ return this.framework.commands.getAll();
50
42
  }
51
43
  async registerEvents() {
52
44
  if (!fs.existsSync(path.join(this.path, 'events')))
@@ -55,7 +47,6 @@ export class Module {
55
47
  for (const file of files) {
56
48
  // if file is folder
57
49
  if (fs.lstatSync(path.join(this.path, 'events', file)).isDirectory()) {
58
- console.log('registering events folder ' + file);
59
50
  this.registerEventsFolder(file);
60
51
  }
61
52
  }
@@ -115,6 +106,35 @@ export class Module {
115
106
  return;
116
107
  this.framework.translations.registerTranslationsFromFolder(path.join(this.path, 'translations', subpath), '', process.env.DEBUG ? true : false);
117
108
  }
109
+ async registerModels() {
110
+ const modelsFolder = path.join(this.path, 'models');
111
+ if (!fs.existsSync(modelsFolder))
112
+ return;
113
+ const db = this.framework.db;
114
+ if (!db)
115
+ return;
116
+ const files = fs.readdirSync(modelsFolder);
117
+ for (const file of files) {
118
+ if (file.endsWith('.d.ts'))
119
+ continue;
120
+ if (file.endsWith('.js') || file.endsWith('.ts')) {
121
+ const mod = await import('file://' + path.join(modelsFolder, file)).catch((e) => {
122
+ this.errorHandler.handleError(e, {
123
+ type: ErrorType.ModuleLoad,
124
+ moduleName: this.constructor.name,
125
+ });
126
+ });
127
+ if (!mod)
128
+ continue;
129
+ // Register every exported class that has @Collection metadata
130
+ for (const exportValue of Object.values(mod)) {
131
+ if (typeof exportValue === 'function' && getModelMetadata(exportValue)) {
132
+ db.registerModel(exportValue);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
118
138
  async registerRoutes() {
119
139
  const folderPath = path.join(this.path, 'routes');
120
140
  if (fs.existsSync(folderPath)) {
@@ -1,7 +1,8 @@
1
1
  import { ModuleParameters } from "../parameters/ModuleParameters";
2
2
  import { StatusManagerOptions } from "../StatusManagerOptions";
3
+ import type { DatabaseConfig } from 'zumito-db';
3
4
  export interface FrameworkSettings {
4
- mongoQueryString: string;
5
+ mongoQueryString?: string;
5
6
  logLevel?: number;
6
7
  debug?: boolean;
7
8
  discordClientOptions: {
@@ -25,4 +26,15 @@ export interface FrameworkSettings {
25
26
  port?: number;
26
27
  disableNotFoundHandler?: boolean;
27
28
  };
29
+ /**
30
+ * New database configuration for zumito-db.
31
+ * If omitted, falls back to `mongoQueryString` with MongoDB driver.
32
+ */
33
+ database?: Partial<DatabaseConfig> & {
34
+ migrations?: any[];
35
+ };
36
+ /**
37
+ * Model classes to register with zumito-db.
38
+ */
39
+ models?: any[];
28
40
  }
package/dist/index.d.ts CHANGED
@@ -32,10 +32,14 @@ import { ErrorType } from './definitions/ErrorType.js';
32
32
  import { InviteUrlGenerator } from './services/utilities/InviteUrlGenerator.js';
33
33
  import { PrefixResolver } from './services/utilities/PrefixResolver.js';
34
34
  import { CommandExecutionChecker } from './services/CommandExecutionChecker.js';
35
+ import { DatabaseManager } from 'zumito-db';
36
+ export { Collection, Field, HasMany, BelongsTo, HasOne, Migration, Repository, QueryBuilder } from 'zumito-db';
37
+ export type { CollectionOptions, FieldOptions, HasManyOptions, BelongsToOptions, HasOneOptions } from 'zumito-db';
38
+ export type { FieldType, Operator, WhereClause, SortClause, QueryIR, FieldMetadata, RelationMetadata, ModelMetadata, DriverConfig, DatabaseConfig, DatabaseDriver } from 'zumito-db';
35
39
  export type { CommandExecutionRule, CommandExecutionContext, CommandExecutionCheck, CommandExecutionType } from './definitions/CommandExecutionRule.js';
36
40
  export { ModalSubmitParameters } from './definitions/parameters/ModalSubmitParameters.js';
37
41
  export { CommandBinds } from './definitions/commands/CommandBinds.js';
38
42
  export { Injectable } from './definitions/decorators/Injectable.decorator.js';
39
43
  export { LauncherConfig } from './definitions/config/LauncherConfig.js';
40
44
  export { ModuleParameters } from './definitions/parameters/ModuleParameters.js';
41
- export { ZumitoFramework, FrameworkSettings, Command, Module, CommandParameters, CommandArguments, FrameworkEvent, Translation, TranslationManager, ApiResponse, SelectMenuParameters, CommandType, CommandArgDefinition, CommandChoiceDefinition, ButtonPressed, ButtonPressedParams, TextFormatter, EmojiFallback, DatabaseConfigLoader, PresenceDataRule, RuledPresenceData, StatusManagerOptions, discord, EventParameters, ServiceContainer, GuildDataGetter, SlashCommandRefresher, CommandParser, ErrorHandler, ErrorType, Route, RouteMethod, InteractionHandler, CommandManager, InviteUrlGenerator, PrefixResolver, CommandExecutionChecker, };
45
+ export { ZumitoFramework, FrameworkSettings, Command, Module, CommandParameters, CommandArguments, FrameworkEvent, Translation, TranslationManager, ApiResponse, SelectMenuParameters, CommandType, CommandArgDefinition, CommandChoiceDefinition, ButtonPressed, ButtonPressedParams, TextFormatter, EmojiFallback, DatabaseConfigLoader, PresenceDataRule, RuledPresenceData, StatusManagerOptions, discord, EventParameters, ServiceContainer, GuildDataGetter, SlashCommandRefresher, CommandParser, ErrorHandler, ErrorType, Route, RouteMethod, InteractionHandler, CommandManager, InviteUrlGenerator, PrefixResolver, CommandExecutionChecker, DatabaseManager, };
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ import { ErrorType } from './definitions/ErrorType.js';
25
25
  import { InviteUrlGenerator } from './services/utilities/InviteUrlGenerator.js';
26
26
  import { PrefixResolver } from './services/utilities/PrefixResolver.js';
27
27
  import { CommandExecutionChecker } from './services/CommandExecutionChecker.js';
28
+ import { DatabaseManager } from 'zumito-db';
29
+ export { Collection, Field, HasMany, BelongsTo, HasOne, Migration, Repository, QueryBuilder } from 'zumito-db';
28
30
  export { Injectable } from './definitions/decorators/Injectable.decorator.js';
29
31
  ServiceContainer.addService(TextFormatter, []);
30
32
  ServiceContainer.addService(EmojiFallback, [discord.Client.name, TranslationManager.name]);
@@ -37,4 +39,4 @@ ServiceContainer.addService(InviteUrlGenerator, []);
37
39
  ServiceContainer.addService(PrefixResolver, []);
38
40
  ServiceContainer.addService(CommandExecutionChecker, [], true);
39
41
  ServiceContainer.addService(ErrorHandler, ['ZumitoFramework']);
40
- export { ZumitoFramework, Command, Module, CommandArguments, FrameworkEvent, Translation, TranslationManager, ApiResponse, CommandType, ButtonPressed, TextFormatter, EmojiFallback, DatabaseConfigLoader, discord, ServiceContainer, GuildDataGetter, SlashCommandRefresher, CommandParser, ErrorHandler, ErrorType, Route, RouteMethod, InteractionHandler, CommandManager, InviteUrlGenerator, PrefixResolver, CommandExecutionChecker, };
42
+ export { ZumitoFramework, Command, Module, CommandArguments, FrameworkEvent, Translation, TranslationManager, ApiResponse, CommandType, ButtonPressed, TextFormatter, EmojiFallback, DatabaseConfigLoader, discord, ServiceContainer, GuildDataGetter, SlashCommandRefresher, CommandParser, ErrorHandler, ErrorType, Route, RouteMethod, InteractionHandler, CommandManager, InviteUrlGenerator, PrefixResolver, CommandExecutionChecker, DatabaseManager, };
package/dist/launcher.js CHANGED
@@ -12,7 +12,6 @@ dotenv.config();
12
12
  const REQUIRED_ENV_VARS = {
13
13
  DISCORD_TOKEN: 'Discord Bot Token',
14
14
  DISCORD_CLIENT_ID: 'Discord Client ID',
15
- MONGO_URI: 'MongoDB connection URI',
16
15
  };
17
16
  EnvValidator.validate(REQUIRED_ENV_VARS);
18
17
  const defaultConfig = {
@@ -22,7 +21,6 @@ const defaultConfig = {
22
21
  clientId: process.env.DISCORD_CLIENT_ID,
23
22
  },
24
23
  defaultPrefix: process.env.BOT_PREFIX || "z-",
25
- mongoQueryString: process.env.MONGO_URI,
26
24
  logLevel: parseInt(process.env.LOGLEVEL || "3"),
27
25
  };
28
26
  const configFilePath = path.join(process.cwd(), 'zumito.config.ts');
@@ -7,6 +7,7 @@ export declare class CommandManager {
7
7
  protected commands: Map<string, Command>;
8
8
  protected framework: ZumitoFramework;
9
9
  protected errorHandler: ErrorHandler;
10
+ private importCounters;
10
11
  constructor(framework: any);
11
12
  set(name: string, command: Command): void;
12
13
  get(name: string): Command;
@@ -22,7 +23,7 @@ export declare class CommandManager {
22
23
  * @param filePath - Absolute path to command file
23
24
  * @returns {Promise<Command>}
24
25
  */
25
- loadCommandFile(filePath: string): Promise<any>;
26
+ loadCommandFile(filePath: string, silent?: boolean): Promise<any>;
26
27
  /**
27
28
  * Load all command files from a folder
28
29
  * @async
@@ -40,5 +41,4 @@ export declare class CommandManager {
40
41
  * @returns {Promise<Map<string, Command>>}
41
42
  */
42
43
  watchCommandsFolder(folderPath: string): void;
43
- refreshSlashCommands(): Promise<void>;
44
44
  }
@@ -1,18 +1,16 @@
1
1
  import chalk from "chalk";
2
- import * as chokidar from 'chokidar';
3
2
  import path from "path";
4
- import boxen from "boxen";
5
3
  import fs from 'fs';
6
- import { REST, Routes, SlashCommandBuilder } from "discord.js";
7
- import { CommandType } from "../../definitions/commands/CommandType.js";
8
4
  import { ErrorHandler } from "../handlers/ErrorHandler";
9
5
  import { ServiceContainer } from "../ServiceContainer";
10
6
  import { ErrorType } from "../../definitions/ErrorType";
7
+ import { FileWatcher } from "../utilities/FileWatcher.js";
11
8
  import 'reflect-metadata';
12
9
  export class CommandManager {
13
10
  commands;
14
11
  framework;
15
12
  errorHandler;
13
+ importCounters = new Map();
16
14
  constructor(framework) {
17
15
  this.commands = new Map;
18
16
  this.errorHandler = ServiceContainer.getService(ErrorHandler);
@@ -40,15 +38,15 @@ export class CommandManager {
40
38
  * @param filePath - Absolute path to command file
41
39
  * @returns {Promise<Command>}
42
40
  */
43
- async loadCommandFile(filePath) {
44
- let loaded = false;
45
- // Validate file has .ts or .js extension
41
+ async loadCommandFile(filePath, silent = false) {
46
42
  if (!filePath.endsWith('.js') && !filePath.endsWith('.ts')) {
47
- throw new Error("File must be a .ts or .js");
43
+ return;
48
44
  }
49
- // import file
50
- let command = await import('file://' + filePath + '?update=' + Date.now().toString()).catch(e => {
51
- console.error('[🆕🔴 ] Error loading command ' + chalk.blue(filePath.toString().replace(/^.*[\\\/]/, '').split('.').slice(0, -1).join('.')));
45
+ const counter = (this.importCounters.get(filePath) || 0) + 1;
46
+ this.importCounters.set(filePath, counter);
47
+ const baseName = path.basename(filePath).split('.').slice(0, -1).join('.');
48
+ let command = await import('file://' + filePath + '?v=' + counter).catch(e => {
49
+ console.error('[🆕🔴 ] Error loading command ' + chalk.blue(baseName));
52
50
  console.log(e + '\n' + e.name + '\n' + e.stack);
53
51
  });
54
52
  if (!command)
@@ -56,9 +54,12 @@ export class CommandManager {
56
54
  command = Object.values(command)[0];
57
55
  try {
58
56
  command = new command();
59
- this.framework.commands.set(command.constructor.name.toLowerCase(), command);
60
- console.debug('[🆕🟢 ] Command ' + chalk.blue(filePath.toString().replace(/^.*[\\\/]/, '').split('.').slice(0, -1).join('.')) + ' loaded');
61
- loaded = true;
57
+ const commandName = command.constructor.name.toLowerCase();
58
+ this.commands.set(commandName, command);
59
+ if (!silent) {
60
+ console.debug('[🆕🟢 ] Command ' + chalk.blue(baseName) + ' loaded');
61
+ }
62
+ return command;
62
63
  }
63
64
  catch (error) {
64
65
  this.errorHandler.handleError(error, {
@@ -66,8 +67,6 @@ export class CommandManager {
66
67
  command: command,
67
68
  });
68
69
  }
69
- if (loaded)
70
- return command;
71
70
  }
72
71
  /**
73
72
  * Load all command files from a folder
@@ -82,7 +81,7 @@ export class CommandManager {
82
81
  if (file.endsWith('.d.ts'))
83
82
  continue;
84
83
  if (file.endsWith('.js') || file.endsWith('.ts')) {
85
- const command = await this.loadCommandFile(path.join(folderPath, file));
84
+ const command = await this.loadCommandFile(path.join(folderPath, file), true);
86
85
  if (command) {
87
86
  const commandName = command.constructor.name.toLowerCase();
88
87
  if (options?.blacklist && options.blacklist.includes(commandName))
@@ -104,87 +103,20 @@ export class CommandManager {
104
103
  * @returns {Promise<Map<string, Command>>}
105
104
  */
106
105
  watchCommandsFolder(folderPath) {
107
- chokidar
108
- .watch(path.resolve(folderPath), {
109
- ignored: /^\./,
110
- persistent: true,
111
- ignoreInitial: true,
112
- })
113
- .on('add', (filePath) => {
114
- this.loadCommandFile(filePath);
115
- })
116
- .on('change', (filePath) => {
117
- this.loadCommandFile(filePath);
118
- })
119
- .on('error', (error) => {
120
- console.error('[🔄🔴 ] Error reloading command');
121
- console.log(boxen(error + '\n' + error.stack, { padding: 1 }));
122
- });
123
- // TODO: Handle file removal
124
- //.on('unlink', function(path) {console.log('File', path, 'has been removed');})
125
- }
126
- async refreshSlashCommands() {
127
- const rest = new REST({ version: '10' }).setToken(this.framework.settings.discordClientOptions.token);
128
- const commands = Array.from(this.commands.values())
129
- .filter((command) => (command.type == CommandType.slash ||
130
- command.type == CommandType.separated ||
131
- command.type == CommandType.any) &&
132
- !command.parent)
133
- .map((command) => {
134
- const slashCommand = new SlashCommandBuilder()
135
- .setName(command.name)
136
- .setDescription(this.framework.translations.get('command.' + command.name + '.description', 'en'));
137
- if (command.args) {
138
- command.args.forEach((arg) => {
139
- let method;
140
- switch (arg.type) {
141
- case 'string':
142
- method = 'addStringOption';
143
- break;
144
- case 'number':
145
- method = 'addNumberOption';
146
- break;
147
- case 'user':
148
- case 'member':
149
- method = 'addUserOption';
150
- break;
151
- case 'channel':
152
- method = 'addChannelOption';
153
- break;
154
- case 'role':
155
- method = 'addRoleOption';
156
- break;
157
- default:
158
- throw new Error(`Invalid argument type ${arg.type} in command ${command.name} for argument ${arg.name}`);
159
- }
160
- slashCommand[method]((option) => {
161
- option.setName(arg.name);
162
- option.setDescription(this.framework.translations.get('command.' +
163
- command.name +
164
- '.args.' +
165
- arg.name +
166
- '.description', 'en'));
167
- option.setRequired(!arg.optional);
168
- if (arg.choices) {
169
- // if arg.choices is function, call it
170
- if (typeof arg.choices == 'function') {
171
- arg.choices =
172
- arg.choices();
173
- }
174
- arg.choices.forEach((choice) => {
175
- option.addChoices({
176
- name: choice.name,
177
- value: choice.value,
178
- });
179
- });
180
- }
181
- return option;
182
- });
183
- });
184
- }
185
- return slashCommand.toJSON();
186
- });
187
- const data = await rest.put(Routes.applicationCommands(this.framework.settings.discordClientOptions.clientId), { body: commands });
188
- console.debug(`Successfully reloaded ${data.length} of ${commands.length} application (/) commands.`);
106
+ new FileWatcher().watch(folderPath, {
107
+ onAdd: (filePath) => {
108
+ this.loadCommandFile(filePath);
109
+ },
110
+ onChange: (filePath) => {
111
+ this.loadCommandFile(filePath);
112
+ },
113
+ onUnlink: (filePath) => {
114
+ const fileName = path.basename(filePath).split('.').slice(0, -1).join('.');
115
+ const commandName = fileName.toLowerCase();
116
+ this.commands.delete(commandName);
117
+ this.importCounters.delete(filePath);
118
+ console.debug('[🔄🟡] Command ' + chalk.blue(fileName) + ' removed');
119
+ },
120
+ }, 'command');
189
121
  }
190
122
  }
@@ -49,21 +49,8 @@ export class ModuleManager {
49
49
  return moduleClass || exports[0];
50
50
  }
51
51
  registerModule(module) {
52
- // Register module commands
53
- if (module.getCommands()) {
54
- module.getCommands().forEach((command) => {
55
- this.framework.commands.set(command.name, command);
56
- });
57
- }
58
52
  // Register module events
59
53
  this.framework.events = new Map([...this.framework.events, ...module.getEvents()]);
60
- // Register models (eliminado, migración a MongoDB)
61
- /*
62
-
63
- // Register module routes
64
- this.routes = new Map([...this.routes, ...moduleInstance.getRoutes()]);
65
-
66
- */
67
54
  }
68
55
  async instanceModule(module, rootPath, name, options) {
69
56
  // Comprobar requerimientos del módulo
@@ -1,9 +1,8 @@
1
1
  import { Translation } from '../../definitions/Translation.js';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
- import * as chokidar from 'chokidar';
5
- import boxen from 'boxen';
6
4
  import chalk from "chalk";
5
+ import { FileWatcher } from '../utilities/FileWatcher.js';
7
6
  export class TranslationManager {
8
7
  translations = new Map();
9
8
  defaultLanguage = 'en';
@@ -94,30 +93,23 @@ export class TranslationManager {
94
93
  }
95
94
  }
96
95
  watchTranslationFolder(folderPath, baseKey) {
97
- chokidar
98
- .watch(path.resolve(folderPath), {
99
- ignored: /^\./,
100
- persistent: true,
101
- ignoreInitial: true,
102
- })
103
- .on('add', async (filePath) => {
104
- const json = await this.loadTranslationFile(filePath);
105
- const lang = filePath.replace(/^.*[\\/]/, '').slice(0, -5);
106
- this.importTranslationsJson(baseKey, lang, json);
107
- console.debug('[🆕🟢 ] Translations file ' + chalk.blue(filePath) + ' loaded');
108
- })
109
- .on('change', async (filePath) => {
110
- const json = await this.loadTranslationFile(filePath);
111
- const lang = filePath.replace(/^.*[\\/]/, '').slice(0, -5);
112
- this.importTranslationsJson(baseKey, lang, json);
113
- console.debug('[🆕🟢 ] Translations file ' + chalk.blue(filePath) + ' loaded');
114
- })
115
- .on('error', (error) => {
116
- console.error('[🔄🔴 ] Error reloading translation file');
117
- console.log(boxen(error + '\n' + error.stack, { padding: 1 }));
118
- });
119
- // TODO: Handle file removal
120
- //.on('unlink', function(path) {console.log('File', path, 'has been removed');})
96
+ new FileWatcher().watch(folderPath, {
97
+ onAdd: async (filePath) => {
98
+ const json = await this.loadTranslationFile(filePath);
99
+ const lang = filePath.replace(/^.*[\\/]/, '').slice(0, -5);
100
+ this.importTranslationsJson(baseKey, lang, json);
101
+ console.debug('[🆕🟢 ] Translations file ' + chalk.blue(filePath) + ' loaded');
102
+ },
103
+ onChange: async (filePath) => {
104
+ const json = await this.loadTranslationFile(filePath);
105
+ const lang = filePath.replace(/^.*[\\/]/, '').slice(0, -5);
106
+ this.importTranslationsJson(baseKey, lang, json);
107
+ console.debug('[🆕🟢 ] Translations file ' + chalk.blue(filePath) + ' loaded');
108
+ },
109
+ onUnlink: (filePath) => {
110
+ console.debug('[🔄🟡] Translations file ' + chalk.blue(filePath) + ' removed');
111
+ },
112
+ }, 'translation');
121
113
  }
122
114
  getShortHandMethod(keyPrefix, language) {
123
115
  return (key, params, lang) => {
@@ -23,7 +23,8 @@ export class EnvValidator {
23
23
  const divider = chalk.yellow('━'.repeat(54));
24
24
  const envPath = path.join(process.cwd(), '.env');
25
25
  const hasEnvFile = fs.existsSync(envPath);
26
- console.error(`\n${divider}`);
26
+ console.error('');
27
+ console.error(divider);
27
28
  console.error(` ${chalk.bold.red('CONFIGURATION ERROR')} — Missing environment variables`);
28
29
  console.error(divider);
29
30
  for (const key of present) {
@@ -44,7 +45,8 @@ export class EnvValidator {
44
45
  else {
45
46
  console.error(` ${chalk.dim('.env file found but missing required variables.')}`);
46
47
  }
47
- console.error(divider + '\n');
48
+ console.error(divider);
49
+ console.error('');
48
50
  process.exit(1);
49
51
  }
50
52
  }
@@ -0,0 +1,10 @@
1
+ import * as chokidar from 'chokidar';
2
+ export type FileWatcherCallbacks = {
3
+ onAdd?: (filePath: string) => void | Promise<void>;
4
+ onChange?: (filePath: string) => void | Promise<void>;
5
+ onUnlink?: (filePath: string) => void | Promise<void>;
6
+ onError?: (error: Error) => void;
7
+ };
8
+ export declare class FileWatcher {
9
+ watch(folderPath: string, callbacks: FileWatcherCallbacks, label?: string): chokidar.FSWatcher;
10
+ }
@@ -0,0 +1,32 @@
1
+ import * as chokidar from 'chokidar';
2
+ import path from 'path';
3
+ import boxen from 'boxen';
4
+ export class FileWatcher {
5
+ watch(folderPath, callbacks, label = 'file') {
6
+ const watcher = chokidar
7
+ .watch(path.resolve(folderPath), {
8
+ ignored: /^\./,
9
+ persistent: true,
10
+ ignoreInitial: true,
11
+ })
12
+ .on('add', (filePath) => {
13
+ callbacks.onAdd?.(filePath);
14
+ })
15
+ .on('change', (filePath) => {
16
+ callbacks.onChange?.(filePath);
17
+ })
18
+ .on('unlink', (filePath) => {
19
+ callbacks.onUnlink?.(filePath);
20
+ })
21
+ .on('error', (error) => {
22
+ if (callbacks.onError) {
23
+ callbacks.onError(error);
24
+ }
25
+ else {
26
+ console.error(`[🔄🔴 ] Error watching ${label}`);
27
+ console.log(boxen(error + '\n' + error.stack, { padding: 1 }));
28
+ }
29
+ });
30
+ return watcher;
31
+ }
32
+ }
@@ -25,5 +25,5 @@ export declare class GuildDataGetter {
25
25
  * // returns the guild settings
26
26
  * getGuildSettings(interaction.guildId);
27
27
  */
28
- getGuildSettings(guildId: string): Promise<import("mongodb").WithId<import("bson").Document>>;
28
+ getGuildSettings(guildId: string): Promise<any>;
29
29
  }
@@ -1,4 +1,6 @@
1
+ import { ServiceContainer } from "../ServiceContainer";
1
2
  import { ObjectId } from "mongodb";
3
+ import { DatabaseManager } from 'zumito-db';
2
4
  export class GuildDataGetter {
3
5
  framework;
4
6
  constructor(framework) {
@@ -28,6 +30,30 @@ export class GuildDataGetter {
28
30
  * getGuildSettings(interaction.guildId);
29
31
  */
30
32
  async getGuildSettings(guildId) {
33
+ // Use new zumito-db DatabaseManager if available
34
+ const db = ServiceContainer.getService(DatabaseManager);
35
+ if (db) {
36
+ const driver = db.getDriver();
37
+ const results = await driver.find('guilds', {
38
+ collection: 'guilds',
39
+ type: 'findOne',
40
+ where: [{ field: 'guild_id', operator: 'eq', value: guildId, logic: 'and' }],
41
+ });
42
+ let guild = results && results.length > 0 ? results[0] : null;
43
+ if (!guild) {
44
+ guild = {
45
+ guild_id: guildId,
46
+ lang: 'en',
47
+ prefix: null,
48
+ public: false,
49
+ deleteCommands: false
50
+ };
51
+ await driver.insert('guilds', guild);
52
+ }
53
+ return guild;
54
+ }
55
+ // Fallback to deprecated raw MongoDB access
56
+ console.warn('[🗄️⚠️] GuildDataGetter is using deprecated raw MongoDB access. Please update to zumito-db.');
31
57
  const collection = this.framework.database.collection('guilds');
32
58
  let guild = await collection.findOne({ guild_id: guildId });
33
59
  if (!guild) {
@@ -27,4 +27,5 @@ export declare function createTestFramework(overrides?: Record<string, any>): {
27
27
  options: {};
28
28
  };
29
29
  eventEmitter: EventEmitter<import("tseep").DefaultEventMap>;
30
+ db: any;
30
31
  };
@@ -6,6 +6,51 @@ export function createTestFramework(overrides = {}) {
6
6
  ServiceContainer.services.clear();
7
7
  const mockClient = createMockClient(overrides.client);
8
8
  const eventEmitter = new EventEmitter();
9
+ const mockDb = {
10
+ getDriver: vi.fn().mockReturnValue({
11
+ find: vi.fn().mockResolvedValue([]),
12
+ findOne: vi.fn().mockResolvedValue(null),
13
+ insert: vi.fn().mockResolvedValue({}),
14
+ update: vi.fn().mockResolvedValue(0),
15
+ delete: vi.fn().mockResolvedValue(0),
16
+ count: vi.fn().mockResolvedValue(0),
17
+ ensureSchema: vi.fn().mockResolvedValue(undefined),
18
+ dropCollection: vi.fn().mockResolvedValue(undefined),
19
+ raw: {},
20
+ disconnect: vi.fn().mockResolvedValue(undefined),
21
+ }),
22
+ getRepository: vi.fn().mockReturnValue({
23
+ find: vi.fn().mockResolvedValue([]),
24
+ findOne: vi.fn().mockResolvedValue(null),
25
+ insert: vi.fn().mockResolvedValue({}),
26
+ update: vi.fn().mockResolvedValue(0),
27
+ delete: vi.fn().mockResolvedValue(0),
28
+ count: vi.fn().mockResolvedValue(0),
29
+ query: vi.fn().mockReturnValue({
30
+ where: vi.fn().mockReturnThis(),
31
+ andWhere: vi.fn().mockReturnThis(),
32
+ orWhere: vi.fn().mockReturnThis(),
33
+ select: vi.fn().mockReturnThis(),
34
+ sort: vi.fn().mockReturnThis(),
35
+ limit: vi.fn().mockReturnThis(),
36
+ offset: vi.fn().mockReturnThis(),
37
+ exec: vi.fn().mockResolvedValue([]),
38
+ first: vi.fn().mockResolvedValue(null),
39
+ count: vi.fn().mockResolvedValue(0),
40
+ }),
41
+ collection: 'test',
42
+ }),
43
+ registerModel: vi.fn(),
44
+ ensureSchemas: vi.fn().mockResolvedValue(undefined),
45
+ connect: vi.fn().mockResolvedValue(undefined),
46
+ disconnect: vi.fn().mockResolvedValue(undefined),
47
+ migrator: {
48
+ latest: vi.fn().mockResolvedValue([]),
49
+ rollback: vi.fn().mockResolvedValue([]),
50
+ status: vi.fn().mockResolvedValue([]),
51
+ },
52
+ ...overrides.db,
53
+ };
9
54
  const mockFramework = {
10
55
  client: mockClient,
11
56
  commands: {
@@ -39,10 +84,13 @@ export function createTestFramework(overrides = {}) {
39
84
  getAll: vi.fn().mockReturnValue(new Map()),
40
85
  size: 0,
41
86
  },
87
+ db: mockDb,
42
88
  ...overrides.framework,
43
89
  };
44
90
  ServiceContainer.addService(class ZumitoFramework {
45
91
  }, [], true, mockFramework);
92
+ ServiceContainer.addService(class DatabaseManager {
93
+ }, [], true, mockDb);
46
94
  class MockErrorHandler {
47
95
  handleError = vi.fn();
48
96
  }
@@ -52,5 +100,6 @@ export function createTestFramework(overrides = {}) {
52
100
  framework: mockFramework,
53
101
  client: mockClient,
54
102
  eventEmitter,
103
+ db: mockDb,
55
104
  };
56
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zumito-framework",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "Discord.js bot framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,12 +37,12 @@
37
37
  "error-stack-parser": "^2.1.4",
38
38
  "express": "^4.18.1",
39
39
  "leven": "^4.0.0",
40
- "mongoose": "^6.6.5",
40
+ "mongodb": "^6.3.0",
41
41
  "reflect-metadata": "^0.2.2",
42
42
  "source-fragment": "^1.1.0",
43
43
  "tingodb": "^0.6.1",
44
44
  "tseep": "^1.2.2",
45
- "zumito-db": "1.0.1"
45
+ "zumito-db": "^2.0.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^18.19.44",