meocord 1.6.0 → 1.7.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/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # MeoCord Framework
2
2
 
3
- **MeoCord** is a lightweight and extensible framework designed for building **Discord bots**. With its modular architecture, support for **TypeScript**, and seamless integration with **Discord.js**, MeoCord simplifies bot development with a focus on flexibility, scalability, and modularity. It offers essential tools such as **CLI commands**, **built-in decorators**, and centralized logging, while also giving developers the ability to define their **own decorators** to extend and customize functionality.
4
-
5
- While still growing, MeoCord provides a solid foundation for developers to create bots tailored for communities, automation, or entertainment.
3
+ **MeoCord** is a decorator-based Discord bot framework built on top of discord.js. It brings a NestJS-style architecture — controllers, services, guards, and dependency injection to bot development, with a full CLI, TypeScript-first design, and testing utilities included out of the box.
6
4
 
7
5
  ---
8
6
 
@@ -11,15 +9,17 @@ While still growing, MeoCord provides a solid foundation for developers to creat
11
9
  - [Features](#features)
12
10
  - [Getting Started](#getting-started)
13
11
  - [Prerequisites](#prerequisites)
14
- - [Create Fresh App](#create-fresh-app)
12
+ - [Create a New App](#create-a-new-app)
13
+ - [Quick Example](#quick-example)
15
14
  - [Project Structure](#project-structure)
16
- - [Example Structure](#example-structure)
17
- - [Key Components](#key-components)
18
15
  - [Configuration](#configuration)
19
- - [CLI Usage](#cli-usage)
20
- - [Development Guide](#development-guide)
16
+ - [meocord.config.ts](#meocordconfigts)
17
+ - [ESLint](#eslint)
18
+ - [CLI Reference](#cli-reference)
19
+ - [Guards](#guards)
21
20
  - [Custom Decorators](#custom-decorators)
22
- - [Deployment Guide](#deployment-guide)
21
+ - [Testing](#testing)
22
+ - [Deployment](#deployment)
23
23
  - [Contributing](#contributing)
24
24
  - [License](#license)
25
25
 
@@ -27,31 +27,13 @@ While still growing, MeoCord provides a solid foundation for developers to creat
27
27
 
28
28
  ## Features
29
29
 
30
- - **Powerful CLI Tools**
31
- Easily manage, build, and run projects using an intuitive CLI. Simplify tasks such as scaffolding components, building
32
- applications, and starting bots in development/production modes.
33
- - **Modular Design**
34
- Embrace modularity by organizing application logic into distinct components like controllers and services. This
35
- approach enhances scalability, improves maintainability, and simplifies future development.
36
- - **Built-in Decorators**
37
- Simplify and extend bot behavior through a robust decorator system. Leverage built-in decorators for streamlined
38
- functionality.
39
- - **Specialized Services**
40
- Register specialized services using the `@MeoCord` decorator. For example, add a RabbitMQ service to listen for events
41
- and trigger actions, such as sending Discord messages. This promotes modularity, flexibility, and seamless integration
42
- of custom services.
43
- - **Seamless Discord.js Integration**
44
- Built on top of Discord.js to provide full support for the Discord API, with added features like activity management,
45
- intents, partials, and custom client options.
46
- - **TypeScript-First Approach**
47
- Designed with TypeScript in mind, offering strict type safety, interfaces, and decorators to empower modern
48
- development workflows.
49
- - **Extensible Webpack Integration**
50
- Easily customize your build process using an exposed Webpack configuration hook. Add rules, plugins, or modify setups
51
- to match your project's requirements.
52
- - **Dynamic Activity Support**
53
- Manage bot presence dynamically, such as setting activities (e.g., "Playing X with Y") or linking bot status to
54
- real-time events.
30
+ - **Decorator-based controllers** — Handle slash commands, buttons, modals, select menus, context menus, messages, and reactions with `@Command`, `@Controller`, and `@UseGuard` decorators. No routing boilerplate.
31
+ - **Dependency injection** Built on Inversify. Services are wired into controllers automatically; no manual instantiation or service locators.
32
+ - **Guard system** — Pre-execution hooks for auth, rate limiting, metrics, and anything else. Apply per-method or per-class with `@UseGuard`. Guards receive the full interaction context.
33
+ - **Full CLI** — `meocord create`, `build`, `start`, `generate`. Scaffolds controllers, services, and guards; handles Webpack builds for both development and production.
34
+ - **Testing utilities** `MeoCordTestingModule`, `createMockInteraction`, `createChatInputOptions`, and `overrideGuard` let you test controllers against real guard logic without a Discord connection.
35
+ - **TypeScript-first** — Strict types throughout. Decorator metadata, `DeepMocked<T>` for test mocks, and typed config interfaces included.
36
+ - **Extensible build** — Expose a Webpack config hook in `meocord.config.ts` to add rules, plugins, or loaders without ejecting.
55
37
 
56
38
  ---
57
39
 
@@ -59,571 +41,460 @@ While still growing, MeoCord provides a solid foundation for developers to creat
59
41
 
60
42
  ### Prerequisites
61
43
 
62
- - **Runtime**: **Node.js** (latest LTS) or **Bun** (1.x+).
63
- - **TypeScript**: Version **6** or above.
64
- - **Package Manager**: Any of **npm**, **yarn**, **pnpm**, or **bun**. The CLI will detect installed package managers and let you choose.
65
-
66
- ### Module Support
67
-
68
- **MeoCord** supports both **ESM** (`import`) and **CommonJS** (`require`) consumption:
69
-
70
- - The package exports dual module formats via `dist/esm/` (ESM) and `dist/cjs/` (CommonJS), with type declarations in `dist/types/`.
71
- - New projects generated with the `meocord create` CLI are already configured for ESM, so no additional setup is required.
72
- - **If you are migrating an existing CommonJS-based project**, you can still use MeoCord with your existing setup. If you prefer ESM:
73
- 1. Add `"type": "module"` to your `package.json`:
74
-
75
- ```json
76
- {
77
- "name": "your-project",
78
- "version": "1.0.0",
79
- "type": "module",
80
- "dependencies": {
81
- "meocord": "^x.x.x"
82
- }
83
- }
84
- ```
85
- 2. If your codebase uses `require()` statements, replace them with `import` statements to ensure compatibility with
86
- ESM:
87
-
88
- ```javascript
89
- // Before (CommonJS)
90
- const Package = require('any-package');
91
-
92
- // After (ESM)
93
- import Package from 'any-package';
94
- ```
95
- 3. Update your `tsconfig.json` file to ensure compatibility with ESM:
96
-
97
- ```json
98
- {
99
- "compilerOptions": {
100
- "module": "ESNext",
101
- "target": "ESNext",
102
- "moduleResolution": "Bundler",
103
- "strict": true,
104
- "strictNullChecks": true,
105
- "strictBindCallApply": true,
106
- "strictPropertyInitialization": false,
107
- "forceConsistentCasingInFileNames": true,
108
- "noFallthroughCasesInSwitch": true,
109
- "emitDecoratorMetadata": true,
110
- "experimentalDecorators": true,
111
- "resolveJsonModule": true,
112
- "verbatimModuleSyntax": true,
113
- "noUnusedLocals": true,
114
- "noUnusedParameters": true,
115
- "skipLibCheck": true,
116
- "noImplicitAny": false,
117
- "noEmit": true,
118
- "outDir": "./dist",
119
- "rootDir": ".",
120
- "paths": {
121
- "@src/*": ["./src/*"]
122
- },
123
- "types": ["node"]
124
- },
125
- "include": ["src/**/*.ts"],
126
- "exclude": [
127
- "meocord.config.ts",
128
- "dist",
129
- "jest.config.ts",
130
- "node_modules"
131
- ]
132
- }
133
- ```
134
- 4. Rename files to use `.mjs` if necessary, or adapt them to ESM-compatible `.js` while ensuring that `"type": "module"` is set in your `package.json`.
135
-
136
- For more migration details, refer to the [Node.js ESM documentation](https://nodejs.org/api/esm.html).
137
-
138
- ---
139
-
140
- ### Create Fresh App
141
-
142
- Follow these steps to create and run a **MeoCord** application:
44
+ - **Runtime**: Node.js (latest LTS) or Bun 1.x+
45
+ - **TypeScript**: 5.0+
46
+ - **Package manager**: npm, yarn, pnpm, or bun
143
47
 
144
- ---
48
+ MeoCord ships dual ESM/CJS builds. New projects generated by the CLI are preconfigured for ESM.
145
49
 
146
- ### 1. Create a fresh MeoCord Application
147
-
148
- Use the CLI to generate your application. You'll be prompted to choose a package manager from those installed on your system.
50
+ ### Create a New App
149
51
 
150
52
  ```shell
151
53
  npx meocord create <your-app-name>
152
54
  ```
153
55
 
154
- You can also skip the prompt by specifying a package manager directly:
56
+ The CLI detects installed package managers and prompts you to choose, or you can pass a flag directly:
155
57
 
156
58
  ```shell
157
59
  npx meocord create <your-app-name> --use-bun
158
60
  npx meocord create <your-app-name> --use-npm
159
- npx meocord create <your-app-name> --use-yarn
160
61
  npx meocord create <your-app-name> --use-pnpm
62
+ npx meocord create <your-app-name> --use-yarn
161
63
  ```
162
64
 
163
- ---
164
-
165
- ### 2. Configure `meocord.config.ts`
65
+ Set your Discord bot token in `meocord.config.ts`, then start the bot:
166
66
 
167
- Edit `meocord.config.ts` to add required discord token:
168
-
169
- ```typescript
170
- import { type MeoCordConfig } from 'meocord/interface'
171
-
172
- export default {
173
- appName: 'DJS ChuTao',
174
- discordToken: '<add-your-token-here>',
175
- } satisfies MeoCordConfig
67
+ ```shell
68
+ npx meocord start --dev # development with live-reload
69
+ npx meocord start --build --prod # production build + start
176
70
  ```
177
71
 
178
- ---
179
-
180
- ### 3. Run the Application
72
+ ### Quick Example
181
73
 
182
- Use the CLI to start your application.
74
+ A minimal slash command controller:
183
75
 
184
- - **Development Mode**:
185
- Run in development mode:
76
+ ```typescript
77
+ import { Controller, Command, UseGuard } from 'meocord/decorator'
78
+ import { CommandType } from 'meocord/enum'
79
+ import { type ChatInputCommandInteraction } from 'discord.js'
80
+ import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
81
+ import { GreetingService } from '@src/services/greeting.service.js'
186
82
 
187
- ```shell
188
- npx meocord start --dev
83
+ @Controller()
84
+ export class GreetingSlashController {
85
+ constructor(private readonly greetingService: GreetingService) {}
86
+
87
+ @Command('greet', CommandType.SLASH)
88
+ @UseGuard({ provide: RateLimiterGuard, params: { limit: 3, window: 10_000 } })
89
+ async greet(interaction: ChatInputCommandInteraction) {
90
+ const name = interaction.options.getString('name', true)
91
+ const message = await this.greetingService.buildGreeting(name)
92
+ await interaction.reply({ content: message })
93
+ }
94
+ }
189
95
  ```
190
96
 
191
- - **Production Mode**:
192
- Run in production mode with fresh production build:
97
+ Register it in `src/app.ts`:
193
98
 
194
- ```shell
195
- npx meocord start --build --prod
99
+ ```typescript
100
+ import { MeoCord } from 'meocord/decorator'
101
+ import { GatewayIntentBits, Partials } from 'discord.js'
102
+ import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
103
+ import { GreetingService } from '@src/services/greeting.service.js'
104
+
105
+ @MeoCord({
106
+ controllers: [GreetingSlashController],
107
+ // `services` is for specialized, event-driven services (e.g. RabbitMQ consumers,
108
+ // schedulers). Regular business-logic services are injected via controller
109
+ // constructors — they don't belong here.
110
+ services: [RabbitMQService],
111
+ clientOptions: {
112
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
113
+ partials: [Partials.Message, Partials.Channel],
114
+ },
115
+ })
116
+ export class App {}
196
117
  ```
197
118
 
198
119
  ---
199
120
 
200
121
  ## Project Structure
201
122
 
202
- When using MeoCord, the expected project structure is as follows:
203
-
204
- ### Example Structure
205
-
206
123
  ```
207
124
  .
208
- .
209
- ├── .gitignore
210
- ├── .prettierrc.mjs
125
+ ├── meocord.config.ts
211
126
  ├── eslint.config.ts
212
127
  ├── jest.config.ts
213
- ├── meocord.config.ts
214
- ├── package.json
215
- ├── src
216
- │   ├── app.ts
217
- │   ├── controllers
218
- │   │   ├── button
219
- │   │   ├── context-menu
220
- │   │   ├── message
221
- │   │   ├── modal-submit
222
- │   │   ├── reaction
223
- │   │   ├── select-menu
224
- │   │   └── slash
225
- │   ├── guards
226
- │   │   └── rate-limit.guard.ts
227
- │   ├── main.ts
228
- │   └── services
229
- │   └── sample.service.ts
230
128
  ├── tsconfig.json
231
129
  ├── tsconfig.eslint.json
232
130
  ├── tsconfig.test.json
233
- └── <lockfile> # bun.lock / package-lock.json / yarn.lock / pnpm-lock.yaml
131
+ ├── package.json
132
+ └── src
133
+ ├── main.ts # Entry point — bootstraps the app
134
+ ├── app.ts # Root module — registers controllers and services
135
+ ├── controllers
136
+ │ ├── slash
137
+ │ │ ├── builders/ # Slash command option/subcommand builders
138
+ │ │ ├── sample.slash.controller.ts
139
+ │ │ └── sample.slash.controller.spec.ts
140
+ │ ├── button
141
+ │ │ ├── sample.button.controller.ts
142
+ │ │ └── sample.button.controller.spec.ts
143
+ │ ├── select-menu
144
+ │ │ ├── sample.select-menu.controller.ts
145
+ │ │ └── sample.select-menu.controller.spec.ts
146
+ │ ├── modal-submit
147
+ │ │ ├── sample.modal-submit.controller.ts
148
+ │ │ └── sample.modal-submit.controller.spec.ts
149
+ │ ├── context-menu
150
+ │ │ ├── builders/ # Context menu command builders
151
+ │ │ ├── sample.context-menu.controller.ts
152
+ │ │ └── sample.context-menu.controller.spec.ts
153
+ │ ├── message
154
+ │ │ ├── sample.message.controller.ts
155
+ │ │ └── sample.message.controller.spec.ts
156
+ │ └── reaction
157
+ │ ├── sample.reaction.controller.ts
158
+ │ └── sample.reaction.controller.spec.ts
159
+ ├── guards
160
+ │ ├── rate-limit.guard.ts
161
+ │ └── rate-limit.guard.spec.ts
162
+ └── services
163
+ ├── sample.service.ts
164
+ └── sample.service.spec.ts
234
165
  ```
235
166
 
236
- This structure ensures clear separation of concerns and scalable project architecture.
237
-
238
- ---
239
-
240
- ### Key Components
241
-
242
- 1. **Entry Point** (`src/main.ts`):
243
-
244
- - The main application logic, where you initialize and run the app.
245
- 2. **Application Configuration** (`src/app.ts`):
246
-
247
- - Acts as the central configuration point, where controllers, services, client options, and other metadata are defined.
248
- - Ties all modular components (controllers, specialized services, activities, etc.) together to define the core structure of the app.
249
- 3. **Controllers** (`src/controllers`):
250
-
251
- - Build feature-specific logic (e.g., context menus, message handling in bots).
252
- 4. **Services** (`src/services`):
253
-
254
- - Core business logic and reusable service definitions.
255
- 5. **Guards** (`src/guards`):
256
-
257
- - Middleware-like services for pre-execution logic (e.g., rate-limiting, authorization).
258
- 6. **Assets** (`src/assets`):
259
-
260
- - Fonts, images, and other static files for your application.
261
-
262
167
  ---
263
168
 
264
169
  ## Configuration
265
170
 
266
- ### Customize the ESLint Configuration
267
-
268
- If needed, you can extend or modify the default configuration to fit your project's requirements:
269
-
270
- ```javascript
271
- import unusedImports from 'eslint-plugin-unused-imports';
272
- import meocordEslint, { typescriptConfig } from 'meocord/eslint';
273
-
274
- const customConfig = {
275
- ...typescriptConfig,
276
- plugins: {
277
- ...typescriptConfig.plugins,
278
- 'unused-imports': unusedImports,
279
- },
280
- rules: {
281
- ...typescriptConfig.rules,
282
- 'unused-imports/no-unused-imports': 'error', // Ensure no unused imports
283
- 'unused-imports/no-unused-vars': [
284
- 'error',
285
- {
286
- vars: 'all',
287
- varsIgnorePattern: '^_', // Allow variables starting with `_`
288
- args: 'after-used',
289
- argsIgnorePattern: '^_',
290
- },
291
- ],
292
- // Add or override additional rules here
293
- },
294
- };
295
-
296
- export default [...meocordEslint, customConfig];
297
- ```
298
-
299
- ---
300
-
301
- ### Customize the `meocord.config.ts`
302
-
303
- The **configuration file** (`meocord.config.ts`) is responsible for defining the application-wide settings. It provides essential configurations such as the app name and Discord token.
171
+ ### `meocord.config.ts`
304
172
 
305
- Below is an example setup for `meocord.config.ts`:
173
+ The top-level config file. At minimum it needs `discordToken`. The `webpack` hook lets you extend the build without ejecting.
306
174
 
307
175
  ```typescript
308
- import '@src/common/utils/load-env.util'
309
- import { MeoCordConfig } from 'meocord/interface'
176
+ import { type MeoCordConfig } from 'meocord/interface'
310
177
 
311
178
  export default {
312
- appName: 'DJS ChuTao',
179
+ appName: 'MyBot',
313
180
  discordToken: process.env.TOKEN!,
314
181
  webpack: config => {
315
182
  config.module.rules?.push({
316
- // Add your custom webpack rule
183
+ // add custom rules here
317
184
  })
318
-
319
185
  return config
320
- }
186
+ },
321
187
  } satisfies MeoCordConfig
322
188
  ```
323
189
 
324
- - **Line-by-Line Explanation**:
325
- 1. **Environment Variable Loading**:
326
- The `load-env.util` ensures that environment variables from `.env` files are loaded before executing the app. *(This utility internally uses the `dotenv` package to load variables from `.env` files. For example, a `.env`
327
- file containing `TOKEN=your-discord-bot-token` makes `process.env.TOKEN` accessible in the application).*
328
- **Example of `load-env.util`:**
329
-
330
- ```typescript
331
- import { config } from 'dotenv';
332
- import path from 'path';
333
-
334
- const getEnvFilePath = (): string => {
335
- switch (process.env.NODE_ENV) {
336
- case 'production':
337
- return '.env.prod';
338
- default:
339
- return '.env.dev';
340
- }
341
- };
342
-
343
- const envFilePath = path.resolve(process.cwd(), getEnvFilePath());
344
-
345
- config({
346
- path: envFilePath,
347
- encoding: 'utf8',
348
- });
349
- ```
350
- 2. **Import Configuration Interface**:
351
- Imports the `MeoCordConfig` interface to enforce type safety on the configuration object.
352
- 3. **Application Name**:
353
- The `appName` defines the name of the bot or application.
354
- 4. **Discord Token**:
355
- The `discordToken` property retrieves the bot's token from the environment variables, ensuring security and
356
- flexibility.
357
- 5. **Custom Webpack Configuration**:
358
- Adds an optional function to modify and extend the default Webpack configuration, including custom module rules
359
- or plugins.
360
- 6. **Return Configuration**:
361
- Ensures the final configuration satisfies the `MeoCordConfig` type, guaranteeing that all required properties are
362
- correctly defined.
190
+ ### ESLint
363
191
 
364
- ---
365
-
366
- ## CLI Usage
367
-
368
- ### Overview
192
+ MeoCord exports a base ESLint config from `meocord/eslint`. Extend it as needed:
369
193
 
370
- The **MeoCord CLI** is designed to help you manage, build, and run your application seamlessly. It provides essential commands and options to streamline your workflow.
194
+ ```javascript
195
+ import meocordEslint, { typescriptConfig } from 'meocord/eslint'
196
+ import unusedImports from 'eslint-plugin-unused-imports'
197
+
198
+ export default [
199
+ ...meocordEslint,
200
+ {
201
+ ...typescriptConfig,
202
+ plugins: {
203
+ ...typescriptConfig.plugins,
204
+ 'unused-imports': unusedImports,
205
+ },
206
+ rules: {
207
+ ...typescriptConfig.rules,
208
+ 'unused-imports/no-unused-imports': 'error',
209
+ },
210
+ },
211
+ ]
212
+ ```
371
213
 
372
- #### Viewing All Commands and Options
214
+ ---
373
215
 
374
- To ensure you see the most accurate and complete list of commands and options, always refer to the help menu by running:
216
+ ## CLI Reference
375
217
 
376
218
  ```shell
377
219
  npx meocord --help
378
220
  ```
379
221
 
380
- Below is an example of the help display output:
381
-
382
- ```textmate
383
- npx meocord --help
384
- MeoCord Copyright (c) 2025 Ukasyah Rahmatullah Zada — MIT License
385
-
386
- meocord [options] [command]
387
-
388
- CLI for managing the MeoCord application
389
-
390
- Available Options:
391
- ┌───────────────┬───────────────────────────┐
392
- │ Option │ Description │
393
- ├───────────────┼───────────────────────────┤
394
- │ -V, --version │ output the version number │
395
- └───────────────┴───────────────────────────┘
396
-
397
- Available Commands:
398
- ┌──────────┬───────┬──────────────────────────────────┐
399
- │ Command │ Alias │ Description │
400
- ├──────────┼───────┼──────────────────────────────────┤
401
- │ show │ — │ Display information │
402
- ├──────────┼───────┼──────────────────────────────────┤
403
- │ create │ — │ Create a new MeoCord application │
404
- ├──────────┼───────┼──────────────────────────────────┤
405
- │ build │ — │ Build the application │
406
- ├──────────┼───────┼──────────────────────────────────┤
407
- │ start │ — │ Start the application │
408
- ├──────────┼───────┼──────────────────────────────────┤
409
- │ generate │ g │ Generate components │
410
- └──────────┴───────┴──────────────────────────────────┘
411
- ```
412
-
413
- ### Key Commands Overview
414
-
415
- The following section provides details for frequently used commands, but note that **additional commands** may be available by running `meocord --help`.
416
-
417
- #### `meocord build`
222
+ | Command | Alias | Description |
223
+ |------------|-------|--------------------------------------|
224
+ | `create` | — | Scaffold a new MeoCord application |
225
+ | `build` | — | Compile the application via Webpack |
226
+ | `start` | — | Start the application |
227
+ | `generate` | `g` | Scaffold controllers, services, guards |
228
+ | `show` | — | Display framework info |
418
229
 
419
- Builds the application in **production** or **development** mode.
420
-
421
- **Usage:**
230
+ **Common flags:**
422
231
 
423
232
  ```shell
424
- npx meocord build --prod # Build for production
425
- npx meocord build --dev # Build for development
233
+ npx meocord build --prod # production build
234
+ npx meocord build --dev # development build
235
+ npx meocord start --dev # dev mode with live-reload
236
+ npx meocord start --build --prod # production build + start
237
+ npx meocord g co slash "profile" # generate a slash controller
238
+ npx meocord g --help # list all generator sub-commands
426
239
  ```
427
240
 
428
- #### `meocord start`
429
-
430
- Starts the application with options for either a **production** or **development** environment. The application can also build automatically before starting.
241
+ ---
431
242
 
432
- **Usage:**
243
+ ## Guards
433
244
 
434
- ```shell
435
- npx meocord start --build --prod # Start in production mode with fresh production build
436
- npx meocord start --dev # Start in development mode (will always fresh build)
437
- ```
245
+ Guards run before the handler method. Each guard implements `canActivate` — return `true` to allow, `false` to block.
438
246
 
439
- #### `meocord generate` (Alias: `meocord g`)
247
+ ```typescript
248
+ import { Guard } from 'meocord/decorator'
249
+ import { type GuardInterface } from 'meocord/interface'
250
+ import { type ChatInputCommandInteraction } from 'discord.js'
251
+ import { RedisService } from '@src/services/redis.service.js'
440
252
 
441
- Scaffolds application components such as controllers, services, and other elements.
253
+ @Guard()
254
+ export class RateLimiterGuard implements GuardInterface {
255
+ constructor(private readonly redis: RedisService) {}
442
256
 
443
- **Usage:**
257
+ // limit and window are injected via @UseGuard params
258
+ limit = 5
259
+ window = 60_000
444
260
 
445
- ```text
446
- npx meocord generate|g [options] [command]
261
+ async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
262
+ const key = `ratelimit:${interaction.user.id}`
263
+ const count = await this.redis.increment(key, this.window)
264
+ return count <= this.limit
265
+ }
266
+ }
447
267
  ```
448
268
 
449
- **Example:**
269
+ Apply to a single method or an entire controller:
450
270
 
451
- ```shell
452
- npx meocord g co slash "user"
453
- ```
271
+ ```typescript
272
+ // Per-method, with params
273
+ @Command('search', CommandType.SLASH)
274
+ @UseGuard({ provide: RateLimiterGuard, params: { limit: 5, window: 60_000 } })
275
+ async search(interaction: ChatInputCommandInteraction) { ... }
454
276
 
455
- This command will generate a `user` slash controller.
277
+ // Per-class (applies to every command in the controller)
278
+ @Controller()
279
+ @UseGuard(MetricsGuard, DefaultGuard)
280
+ export class ProfileController { ... }
281
+ ```
456
282
 
457
283
  ---
458
284
 
459
- For detailed usage of any particular command, append the `--help` flag to it. For instance:
285
+ ## Custom Decorators
460
286
 
461
- ```shell
462
- npx meocord g --help
463
- ```
287
+ MeoCord exports `applyDecorators` and `SetMetadata` from `meocord/common` for composing reusable decorators.
464
288
 
465
- This will provide command-specific help and options.
289
+ ### Composing guards into a reusable decorator
466
290
 
467
- ---
291
+ ```typescript
292
+ import { applyDecorators } from 'meocord/common'
293
+ import { UseGuard } from 'meocord/decorator'
294
+ import { DefaultGuard, RateLimiterGuard } from '@src/guards/index.js'
468
295
 
469
- ## Development Guide
296
+ export const Protected = (limit = 5) =>
297
+ applyDecorators(
298
+ UseGuard(DefaultGuard, { provide: RateLimiterGuard, params: { limit } }),
299
+ )
300
+ ```
470
301
 
471
- ### Development Mode
302
+ ```typescript
303
+ @Command('profile', CommandType.SLASH)
304
+ @Protected(3)
305
+ async profile(interaction: ChatInputCommandInteraction) { ... }
306
+ ```
472
307
 
473
- Run the application in development mode with live-reload for a seamless coding experience:
308
+ ### Attaching metadata for guards to read
474
309
 
475
- ```shell
476
- npx meocord start --dev
477
- ```
310
+ ```typescript
311
+ // Define the metadata decorator
312
+ import { SetMetadata } from 'meocord/common'
313
+ export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
478
314
 
479
- ### Building for Production
315
+ // Read it inside a guard
316
+ @Guard()
317
+ export class RolesGuard implements GuardInterface {
318
+ async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
319
+ const required: string[] = Reflect.getMetadata('roles', interaction.constructor) ?? []
320
+ if (!required.length) return true
321
+ // ... validate member roles
322
+ return true
323
+ }
324
+ }
480
325
 
481
- Generate an optimized and compiled production build with:
326
+ // Compose into a single decorator
327
+ export const RequireRoles = (...roles: string[]) =>
328
+ applyDecorators(Roles(...roles), UseGuard(RolesGuard))
482
329
 
483
- ```shell
484
- npx meocord build --prod
330
+ // Apply
331
+ @Command('ban', CommandType.SLASH)
332
+ @RequireRoles('admin', 'moderator')
333
+ async ban(interaction: ChatInputCommandInteraction) { ... }
485
334
  ```
486
335
 
487
- Once built, you can deploy or run the application efficiently.
488
-
489
336
  ---
490
337
 
491
- ## Custom Decorators
338
+ ## Testing
492
339
 
493
- MeoCord exports two helpers from `meocord/common` for building your own decorators: `applyDecorators` and `SetMetadata`.
340
+ MeoCord ships a `meocord/testing` entry point with utilities for testing controllers in isolation no real Discord connection required.
494
341
 
495
- ### `applyDecorators` — compose decorators into one
342
+ ### `MeoCordTestingModule`
496
343
 
497
- Combine multiple existing decorators into a single reusable one. Useful for bundling a common guard pattern so you don't repeat it on every command.
344
+ Builds an isolated DI container from your controllers and providers.
498
345
 
499
346
  ```typescript
500
- import { applyDecorators } from 'meocord/common'
501
- import { UseGuard } from 'meocord/decorator'
502
- import { DefaultGuard, GlobalRateLimiterGuard, RateLimiterGuard } from '@src/guards'
347
+ import { MeoCordTestingModule } from 'meocord/testing'
348
+ import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
349
+ import { GreetingService } from '@src/services/greeting.service.js'
503
350
 
504
- // Reusable decorator that applies a standard guard stack
505
- export const Protected = () =>
506
- applyDecorators(
507
- UseGuard(DefaultGuard, GlobalRateLimiterGuard),
508
- )
351
+ const module = MeoCordTestingModule.create({
352
+ controllers: [GreetingSlashController],
353
+ providers: [{ provide: GreetingService, useValue: mockGreetingService }],
354
+ }).compile()
509
355
 
510
- // With configurable rate limit
511
- export const RateLimited = (limit: number) =>
512
- applyDecorators(
513
- UseGuard(DefaultGuard, { provide: RateLimiterGuard, params: { limit } }),
514
- )
356
+ const controller = module.get(GreetingSlashController)
515
357
  ```
516
358
 
517
- Usage on a controller:
359
+ ### `createMockInteraction`
360
+
361
+ Creates a mock instance of any discord.js class. The full prototype chain is preserved so `instanceof` checks at every level pass. Every method is auto-stubbed as a `jest.fn()`.
518
362
 
519
363
  ```typescript
520
- import { Controller } from 'meocord/decorator'
521
- import { Protected, RateLimited } from '@src/common/decorators'
364
+ import { createMockInteraction } from 'meocord/testing'
365
+ import { ChatInputCommandInteraction, BaseInteraction } from 'discord.js'
522
366
 
523
- @Controller()
524
- export class ProfileController {
525
- @Command('profile', CommandType.SLASH)
526
- @Protected()
527
- async profile(interaction: ChatInputCommandInteraction) { ... }
528
-
529
- @Command('wish', CommandType.SLASH)
530
- @RateLimited(3)
531
- async wish(interaction: ChatInputCommandInteraction) { ... }
532
- }
367
+ const interaction = createMockInteraction(ChatInputCommandInteraction)
368
+
369
+ expect(interaction).toBeInstanceOf(ChatInputCommandInteraction) // true
370
+ expect(interaction).toBeInstanceOf(BaseInteraction) // true
371
+
372
+ interaction.reply.mockResolvedValue(undefined)
373
+ await interaction.reply({ content: 'hi' })
374
+ expect(interaction.reply).toHaveBeenCalledWith({ content: 'hi' })
375
+
376
+ // direct property writes work normally
377
+ interaction.guildId = 'guild-123'
533
378
  ```
534
379
 
535
- ### `SetMetadata` + custom guardattach and read custom metadata
380
+ Works for any discord.js class`ButtonInteraction`, `ModalSubmitInteraction`, `StringSelectMenuInteraction`, `Message`, `MessageReaction`, and any future class. No per-type maintenance.
536
381
 
537
- Use `SetMetadata` to tag commands with arbitrary data, then read it inside a guard.
382
+ ### `createChatInputOptions`
538
383
 
539
- **1. Define the metadata decorator:**
384
+ Builds a typed options resolver from a plain record. Type routing mirrors the real `CommandInteractionOptionResolver`: wrong-type access returns `null`, `required=true` throws if the option is absent.
540
385
 
541
386
  ```typescript
542
- import { SetMetadata } from 'meocord/common'
543
-
544
- export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
387
+ import { createMockInteraction, createChatInputOptions } from 'meocord/testing'
388
+ import { ChatInputCommandInteraction } from 'discord.js'
389
+
390
+ const interaction = createMockInteraction(ChatInputCommandInteraction)
391
+ interaction.options = createChatInputOptions({
392
+ subcommandGroup: 'admin',
393
+ subcommand: 'ban',
394
+ user: { id: '123456789' },
395
+ reason: 'spam',
396
+ duration: 7,
397
+ })
398
+
399
+ interaction.options.getSubcommandGroup() // → 'admin'
400
+ interaction.options.getSubcommand() // → 'ban'
401
+ interaction.options.getUser('user') // → { id: '123456789' }
402
+ interaction.options.getString('reason') // → 'spam'
403
+ interaction.options.getNumber('duration') // → 7
404
+ interaction.options.getString('duration') // → null (wrong type)
405
+ interaction.options.getNumber('x', true) // → throws (absent + required)
545
406
  ```
546
407
 
547
- **2. Read it in a guard:**
408
+ All methods are `jest.fn()` override any per test with `.mockReturnValue()`.
548
409
 
549
- ```typescript
550
- import { Guard } from 'meocord/decorator'
551
- import { type GuardInterface } from 'meocord/interface'
552
- import type { ChatInputCommandInteraction } from 'discord.js'
410
+ ### `overrideGuard`
553
411
 
554
- @Guard()
555
- export class RolesGuard implements GuardInterface {
556
- async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
557
- const required: string[] = Reflect.getMetadata('roles', interaction.constructor) ?? []
558
- if (!required.length) return true
412
+ Replaces a guard class in the DI container with a stub. No guard dependencies need to be provided.
559
413
 
560
- const memberRoles = interaction.member?.roles
561
- // ... check member has at least one required role
562
- return true
563
- }
564
- }
414
+ ```typescript
415
+ const module = MeoCordTestingModule.create({
416
+ controllers: [GreetingSlashController],
417
+ providers: [{ provide: GreetingService, useValue: mockGreetingService }],
418
+ })
419
+ .overrideGuard(MetricsGuard).useValue({ canActivate: () => true })
420
+ .overrideGuard(RateLimiterGuard).useValue({ canActivate: () => true })
421
+ .compile()
565
422
  ```
566
423
 
567
- **3. Apply both on a command:**
424
+ `canActivate: () => true` allows the method to run. `() => false` blocks it. Multiple guards chain fluently.
425
+
426
+ ### Full example
568
427
 
569
428
  ```typescript
570
- import { applyDecorators } from 'meocord/common'
571
- import { UseGuard } from 'meocord/decorator'
429
+ import { jest } from '@jest/globals'
430
+ import { MeoCordTestingModule, createMockInteraction, createChatInputOptions } from 'meocord/testing'
431
+ import { ChatInputCommandInteraction } from 'discord.js'
432
+ import { GreetingSlashController } from '@src/controllers/slash/greeting.slash.controller.js'
433
+ import { GreetingService } from '@src/services/greeting.service.js'
434
+ import { RateLimiterGuard } from '@src/guards/rate-limiter.guard.js'
435
+
436
+ describe('GreetingSlashController', () => {
437
+ let controller: GreetingSlashController
438
+ let greetingService: { buildGreeting: jest.MockedFunction<GreetingService['buildGreeting']> }
439
+
440
+ beforeEach(() => {
441
+ greetingService = { buildGreeting: jest.fn() }
442
+
443
+ const module = MeoCordTestingModule.create({
444
+ controllers: [GreetingSlashController],
445
+ providers: [{ provide: GreetingService, useValue: greetingService }],
446
+ })
447
+ .overrideGuard(RateLimiterGuard).useValue({ canActivate: () => true })
448
+ .compile()
572
449
 
573
- export const RequireRoles = (...roles: string[]) =>
574
- applyDecorators(
575
- Roles(...roles),
576
- UseGuard(RolesGuard),
577
- )
450
+ controller = module.get(GreetingSlashController)
451
+ })
578
452
 
579
- @Controller()
580
- export class AdminController {
581
- @Command('ban', CommandType.SLASH)
582
- @RequireRoles('admin', 'moderator')
583
- async ban(interaction: ChatInputCommandInteraction) { ... }
584
- }
453
+ it('replies with a greeting for the provided name', async () => {
454
+ jest.mocked(greetingService.buildGreeting).mockResolvedValue('Hello, Alice!')
455
+
456
+ const interaction = createMockInteraction(ChatInputCommandInteraction)
457
+ interaction.options = createChatInputOptions({ name: 'Alice' })
458
+ interaction.reply.mockResolvedValue(undefined as any)
459
+
460
+ await controller.greet(interaction)
461
+
462
+ expect(greetingService.buildGreeting).toHaveBeenCalledWith('Alice')
463
+ expect(interaction.reply).toHaveBeenCalledWith({ content: 'Hello, Alice!' })
464
+ })
465
+ })
585
466
  ```
586
467
 
587
468
  ---
588
469
 
589
- ## Deployment Guide
470
+ ## Deployment
590
471
 
591
- Install all necessary dependencies, including development dependencies, before building:
472
+ Install all dependencies and build for production:
592
473
 
593
474
  ```shell
594
- npm ci # npm
595
- yarn install --frozen-lockfile # yarn
596
- pnpm install --frozen-lockfile # pnpm
597
- bun install --frozen-lockfile # bun
475
+ npm ci && npx meocord build --prod
598
476
  ```
599
477
 
600
- Generate an optimized and compiled production build:
478
+ Strip dev dependencies:
601
479
 
602
480
  ```shell
603
- npx meocord build --prod
481
+ npm ci --omit=dev # npm
482
+ yarn install --production # yarn
483
+ pnpm install --prod # pnpm
484
+ bun install --production # bun
604
485
  ```
605
486
 
606
- Clean up and focus on production-only dependencies:
487
+ Required files on the server:
607
488
 
608
- ```shell
609
- npm ci --omit=dev # npm
610
- yarn install --production # yarn
611
- pnpm install --prod # pnpm
612
- bun install --production # bun
613
489
  ```
614
-
615
- Ensure the following essential files and folders are prepared for deployment on the server:
616
-
617
- ```text
618
- .
619
- ├── dist
620
- ├── node_modules (production dependencies only)
621
- ├── .env (if applicable, ensure it contains necessary variables)
622
- ├── package.json
623
- └── <lockfile> # bun.lock / package-lock.json / yarn.lock / pnpm-lock.yaml
490
+ dist/
491
+ node_modules/ (production only)
492
+ package.json
493
+ .env (if used)
494
+ <lockfile>
624
495
  ```
625
496
 
626
- Start the application in production mode on the server:
497
+ Start in production:
627
498
 
628
499
  ```shell
629
500
  npx meocord start --prod
@@ -633,56 +504,21 @@ npx meocord start --prod
633
504
 
634
505
  ## Contributing
635
506
 
636
- We welcome contributions to improve **MeoCord**. Here's how you can get started:
637
-
638
- 1. **Fork the Repository**:
639
- Click the "Fork" button in the top-right corner of the repository page to create your copy of the project.
640
- 2. **Create a Feature Branch**:
641
- Use the following command to create a branch for your changes:
642
-
643
- ```textmate
644
- git checkout -b feature/your-feature-name
645
- ```
646
- 3. **Make Meaningful Commits**:
647
- Commit your changes with clear, descriptive, and concise messages that explain what your changes do:
648
-
649
- ```textmate
650
- git commit -m "feat: add [brief description of your feature or fix]"
651
- ```
652
- 4. **Push Your Changes**:
653
- Push your branch to your forked repository with this command:
654
-
655
- ```textmate
656
- git push origin feature/your-feature-name
657
- ```
658
- 5. **Open a Pull Request (PR)**:
507
+ 1. Fork the repository
508
+ 2. Create a feature branch: `git checkout -b feat/your-feature`
509
+ 3. Commit with conventional commits: `git commit -m "feat: add X"`
510
+ 4. Push and open a pull request against `main`
659
511
 
660
- - Navigate to the original **MeoCord** repository.
661
- - Click "Compare & Pull Request."
662
- - Provide a descriptive title and a detailed description of your changes.
663
-
664
- Be sure to include:
665
-
666
- - The purpose of your changes.
667
- - Any relevant details or links.
668
- - Steps to reproduce/test the changes, if applicable.
669
- 6. **Engage in Reviews**:
670
- Work with maintainers to address any feedback or changes they request.
671
-
672
- Thank you for helping make **MeoCord** better!
512
+ Include a description of what changed and why, and add tests for any new behaviour.
673
513
 
674
514
  ---
675
515
 
676
516
  ## Release Notes
677
517
 
678
- Full release history is available on the [GitHub Releases](https://github.com/l7aromeo/meocord/releases) page.
518
+ Full changelog is available on the [GitHub Releases](https://github.com/l7aromeo/meocord/releases) page.
679
519
 
680
520
  ---
681
521
 
682
522
  ## License
683
523
 
684
524
  **MeoCord Framework** is licensed under the [MIT License](./LICENSE).
685
-
686
- You are free to use, modify, and distribute this software in any project — personal, academic, or commercial — with no restrictions other than preserving the copyright notice.
687
-
688
- For full details, consult the [LICENSE](./LICENSE) file included in the repository.