meocord 1.4.1 → 1.5.0-beta.2
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 +99 -0
- package/dist/cjs/_shared/controller.decorator-DX5lFlPZ.cjs +216 -0
- package/dist/cjs/_shared/metadata-key.enum-BzzvGUId.cjs +38 -0
- package/dist/cjs/common/index.cjs +51 -1
- package/dist/cjs/core/index.cjs +315 -14
- package/dist/cjs/decorator/index.cjs +30 -131
- package/dist/cjs/enum/index.cjs +3 -32
- package/dist/cjs/testing/index.cjs +103 -0
- package/dist/esm/bin/builder-template/controller/controller.spec.template +17 -0
- package/dist/esm/bin/builder-template/guard.spec.template +18 -0
- package/dist/esm/bin/builder-template/service.spec.template +17 -0
- package/dist/esm/bin/helper/controller-generator.helper.js +10 -1
- package/dist/esm/bin/helper/guard-generator.helper.js +4 -0
- package/dist/esm/bin/helper/service-generator.helper.js +4 -0
- package/dist/esm/common/decorator.js +51 -0
- package/dist/esm/common/index.js +1 -0
- package/dist/esm/core/meocord-factory.js +46 -12
- package/dist/esm/core/meocord.app.js +26 -38
- package/dist/esm/decorator/app.decorator.js +6 -56
- package/dist/esm/decorator/controller.decorator.js +0 -11
- package/dist/esm/decorator/guard.decorator.js +4 -21
- package/dist/esm/decorator/index.js +0 -1
- package/dist/esm/decorator/service.decorator.js +0 -30
- package/dist/esm/enum/metadata-key.enum.js +6 -2
- package/dist/esm/testing/index.js +1 -0
- package/dist/esm/testing/meocord-testing-module.js +99 -0
- package/dist/esm/util/generator-cli.util.js +5 -5
- package/dist/types/common/index.d.ts +35 -1
- package/dist/types/core/index.d.ts +5 -3
- package/dist/types/decorator/index.d.ts +7 -11
- package/dist/types/enum/index.d.ts +7 -2
- package/dist/types/testing/index.d.ts +63 -0
- package/package.json +6 -1
- package/dist/cjs/_shared/meocord.app-Ds9XbbCN.cjs +0 -505
- package/dist/esm/decorator/container.js +0 -6
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ While still growing, MeoCord provides a solid foundation for developers to creat
|
|
|
18
18
|
- [Configuration](#configuration)
|
|
19
19
|
- [CLI Usage](#cli-usage)
|
|
20
20
|
- [Development Guide](#development-guide)
|
|
21
|
+
- [Custom Decorators](#custom-decorators)
|
|
21
22
|
- [Deployment Guide](#deployment-guide)
|
|
22
23
|
- [Contributing](#contributing)
|
|
23
24
|
- [License](#license)
|
|
@@ -484,6 +485,104 @@ Once built, you can deploy or run the application efficiently.
|
|
|
484
485
|
|
|
485
486
|
---
|
|
486
487
|
|
|
488
|
+
## Custom Decorators
|
|
489
|
+
|
|
490
|
+
MeoCord exports two helpers from `meocord/common` for building your own decorators: `applyDecorators` and `SetMetadata`.
|
|
491
|
+
|
|
492
|
+
### `applyDecorators` — compose decorators into one
|
|
493
|
+
|
|
494
|
+
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.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
import { applyDecorators } from 'meocord/common'
|
|
498
|
+
import { UseGuard } from 'meocord/decorator'
|
|
499
|
+
import { DefaultGuard, GlobalRateLimiterGuard, RateLimiterGuard } from '@src/guards'
|
|
500
|
+
|
|
501
|
+
// Reusable decorator that applies a standard guard stack
|
|
502
|
+
export const Protected = () =>
|
|
503
|
+
applyDecorators(
|
|
504
|
+
UseGuard(DefaultGuard, GlobalRateLimiterGuard),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
// With configurable rate limit
|
|
508
|
+
export const RateLimited = (limit: number) =>
|
|
509
|
+
applyDecorators(
|
|
510
|
+
UseGuard(DefaultGuard, { provide: RateLimiterGuard, params: { limit } }),
|
|
511
|
+
)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Usage on a controller:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { Controller } from 'meocord/decorator'
|
|
518
|
+
import { Protected, RateLimited } from '@src/common/decorators'
|
|
519
|
+
|
|
520
|
+
@Controller()
|
|
521
|
+
export class ProfileController {
|
|
522
|
+
@Command('profile', CommandType.SLASH)
|
|
523
|
+
@Protected()
|
|
524
|
+
async profile(interaction: ChatInputCommandInteraction) { ... }
|
|
525
|
+
|
|
526
|
+
@Command('wish', CommandType.SLASH)
|
|
527
|
+
@RateLimited(3)
|
|
528
|
+
async wish(interaction: ChatInputCommandInteraction) { ... }
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### `SetMetadata` + custom guard — attach and read custom metadata
|
|
533
|
+
|
|
534
|
+
Use `SetMetadata` to tag commands with arbitrary data, then read it inside a guard.
|
|
535
|
+
|
|
536
|
+
**1. Define the metadata decorator:**
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import { SetMetadata } from 'meocord/common'
|
|
540
|
+
|
|
541
|
+
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**2. Read it in a guard:**
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
import { Guard } from 'meocord/decorator'
|
|
548
|
+
import { type GuardInterface } from 'meocord/interface'
|
|
549
|
+
import type { ChatInputCommandInteraction } from 'discord.js'
|
|
550
|
+
|
|
551
|
+
@Guard()
|
|
552
|
+
export class RolesGuard implements GuardInterface {
|
|
553
|
+
async canActivate(interaction: ChatInputCommandInteraction): Promise<boolean> {
|
|
554
|
+
const required: string[] = Reflect.getMetadata('roles', interaction.constructor) ?? []
|
|
555
|
+
if (!required.length) return true
|
|
556
|
+
|
|
557
|
+
const memberRoles = interaction.member?.roles
|
|
558
|
+
// ... check member has at least one required role
|
|
559
|
+
return true
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**3. Apply both on a command:**
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { applyDecorators } from 'meocord/common'
|
|
568
|
+
import { UseGuard } from 'meocord/decorator'
|
|
569
|
+
|
|
570
|
+
export const RequireRoles = (...roles: string[]) =>
|
|
571
|
+
applyDecorators(
|
|
572
|
+
Roles(...roles),
|
|
573
|
+
UseGuard(RolesGuard),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
@Controller()
|
|
577
|
+
export class AdminController {
|
|
578
|
+
@Command('ban', CommandType.SLASH)
|
|
579
|
+
@RequireRoles('admin', 'moderator')
|
|
580
|
+
async ban(interaction: ChatInputCommandInteraction) { ... }
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
487
586
|
## Deployment Guide
|
|
488
587
|
|
|
489
588
|
Install all necessary dependencies, including development dependencies, before building:
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('reflect-metadata');
|
|
4
|
+
var inversify = require('inversify');
|
|
5
|
+
var discord_js = require('discord.js');
|
|
6
|
+
var enum_index = require('../enum/index.cjs');
|
|
7
|
+
var metadataKey_enum = require('./metadata-key.enum-BzzvGUId.cjs');
|
|
8
|
+
|
|
9
|
+
const COMMAND_METADATA_KEY = Symbol('commands');
|
|
10
|
+
const MESSAGE_HANDLER_METADATA_KEY = Symbol('message_handlers');
|
|
11
|
+
const REACTION_HANDLER_METADATA_KEY = Symbol('reaction_handlers');
|
|
12
|
+
/**
|
|
13
|
+
* Decorator to register message handlers in the controller.
|
|
14
|
+
*
|
|
15
|
+
* @param keyword - An optional keyword to filter messages this handler should respond to.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* @MessageHandler('hello')
|
|
20
|
+
* async handleHelloMessage(message: Message) {
|
|
21
|
+
* await message.reply('Hello! How can I help you?');
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* @MessageHandler()
|
|
25
|
+
* async handleAnyMessage(message: Message) {
|
|
26
|
+
* console.log(`Received a message: ${message.content}`);
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/ function MessageHandler(keyword) {
|
|
30
|
+
return function(target, propertyKey, _descriptor) {
|
|
31
|
+
const handlers = Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY, target) || [];
|
|
32
|
+
handlers.push({
|
|
33
|
+
keyword,
|
|
34
|
+
method: propertyKey.toString()
|
|
35
|
+
});
|
|
36
|
+
Reflect.defineMetadata(MESSAGE_HANDLER_METADATA_KEY, handlers, target);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decorator to register reaction handlers in the controller.
|
|
41
|
+
*
|
|
42
|
+
* @param emoji - Optional emoji name to filter reactions this handler should respond to.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* @ReactionHandler('👍')
|
|
47
|
+
* async handleThumbsUpReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) {
|
|
48
|
+
* console.log(`User ${user.username} reacted with 👍`);
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* @ReactionHandler()
|
|
52
|
+
* async handleAnyReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) {
|
|
53
|
+
* console.log(`User ${user.username} reacted with ${reaction.emoji.name}`);
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/ function ReactionHandler(emoji) {
|
|
57
|
+
return function(target, propertyKey, _descriptor) {
|
|
58
|
+
const handlers = Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY, target) || [];
|
|
59
|
+
handlers.push({
|
|
60
|
+
emoji,
|
|
61
|
+
method: propertyKey.toString()
|
|
62
|
+
});
|
|
63
|
+
Reflect.defineMetadata(REACTION_HANDLER_METADATA_KEY, handlers, target);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves reaction handlers metadata from a given controller.
|
|
68
|
+
*
|
|
69
|
+
* @param controller - The controller class instance.
|
|
70
|
+
* @returns An array of reaction handler metadata objects.
|
|
71
|
+
*/ function getReactionHandlers(controller) {
|
|
72
|
+
return Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY, controller) || [];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Retrieves message handlers metadata from a given controller.
|
|
76
|
+
*
|
|
77
|
+
* @param controller - The controller class instance.
|
|
78
|
+
* @returns An array of message handler method names.
|
|
79
|
+
*/ function getMessageHandlers(controller) {
|
|
80
|
+
return Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY, controller) || [];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Helper function to create regex and parameter mappings from a pattern string.
|
|
84
|
+
*
|
|
85
|
+
* @param pattern - The pattern string to parse.
|
|
86
|
+
* @returns An object containing the generated regex and parameter names.
|
|
87
|
+
*/ function createRegexFromPattern(pattern) {
|
|
88
|
+
const params = [];
|
|
89
|
+
// Escape special characters except for {} and -
|
|
90
|
+
const escapedPattern = pattern.replace(/[/\\^$*+?.()|[\]]/g, '\\$&') // Removed hyphen `-` from this list
|
|
91
|
+
;
|
|
92
|
+
// Replace placeholders with named capturing groups
|
|
93
|
+
const regexPattern = escapedPattern.replace(/\{(\w+)}/g, (_, param)=>{
|
|
94
|
+
if (!/^\w+$/.test(param)) {
|
|
95
|
+
throw new Error(`Invalid parameter name: ${param}. Parameter names must be alphanumeric.`);
|
|
96
|
+
}
|
|
97
|
+
params.push(param);
|
|
98
|
+
return `(?<${param}>[a-zA-Z0-9]+)`;
|
|
99
|
+
});
|
|
100
|
+
// Construct the final regex
|
|
101
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
102
|
+
return {
|
|
103
|
+
regex,
|
|
104
|
+
params
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Decorator to register command methods in a controller.
|
|
109
|
+
*
|
|
110
|
+
* @param commandName - The name or pattern of the command.
|
|
111
|
+
* @param builderOrType - A command builder class or a command type from `CommandType`.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* @Command('help', CommandType.SLASH)
|
|
116
|
+
* public async handleHelp(interaction: ChatInputCommandInteraction) {
|
|
117
|
+
* await interaction.reply('This is the help command!')
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* @Command('stats-{id}', CommandType.BUTTON)
|
|
121
|
+
* public async handleStats(message: ButtonInteraction, { id }) {
|
|
122
|
+
* await message.reply(`Fetching stats for ID: ${id}`);
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/ function Command(commandName, builderOrType) {
|
|
126
|
+
return function(target, propertyKey, _descriptor) {
|
|
127
|
+
const originalMethod = _descriptor.value;
|
|
128
|
+
if (!originalMethod) {
|
|
129
|
+
throw new Error(`Missing implementation for method ${propertyKey}`);
|
|
130
|
+
}
|
|
131
|
+
// Wrap original method for interaction type validation
|
|
132
|
+
_descriptor.value = function(interaction, params) {
|
|
133
|
+
const expectedInteraction = commandType === enum_index.CommandType.BUTTON && interaction instanceof discord_js.ButtonInteraction || commandType === enum_index.CommandType.SELECT_MENU && interaction instanceof discord_js.StringSelectMenuInteraction || commandType === enum_index.CommandType.SLASH && interaction instanceof discord_js.ChatInputCommandInteraction || commandType === enum_index.CommandType.CONTEXT_MENU && interaction instanceof discord_js.ContextMenuCommandInteraction || commandType === enum_index.CommandType.MODAL_SUBMIT && interaction instanceof discord_js.ModalSubmitInteraction;
|
|
134
|
+
if (!expectedInteraction) {
|
|
135
|
+
throw new Error(`Invalid interaction type passed to @Command for method: ${propertyKey}`);
|
|
136
|
+
}
|
|
137
|
+
return originalMethod.apply(this, [
|
|
138
|
+
interaction,
|
|
139
|
+
params
|
|
140
|
+
]);
|
|
141
|
+
};
|
|
142
|
+
// Retrieve existing metadata or initialize it
|
|
143
|
+
const commands = Reflect.getMetadata(COMMAND_METADATA_KEY, target) || {};
|
|
144
|
+
let builderInstance;
|
|
145
|
+
let commandType;
|
|
146
|
+
let regex;
|
|
147
|
+
let dynamicParams = [];
|
|
148
|
+
// Determine command type and builder
|
|
149
|
+
if (typeof builderOrType === 'function') {
|
|
150
|
+
const builderObj = new builderOrType();
|
|
151
|
+
builderInstance = builderObj.build(commandName);
|
|
152
|
+
commandType = Reflect.getMetadata(metadataKey_enum.MetadataKey.CommandType, builderOrType);
|
|
153
|
+
if (!(commandType in enum_index.CommandType)) {
|
|
154
|
+
throw new Error(`Metadata for 'commandType' is missing on builder ${builderOrType.name}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
commandType = builderOrType;
|
|
158
|
+
}
|
|
159
|
+
if (commandType !== enum_index.CommandType.SLASH && commandType !== enum_index.CommandType.CONTEXT_MENU) {
|
|
160
|
+
const { regex: generatedRegex, params } = createRegexFromPattern(commandName);
|
|
161
|
+
regex = generatedRegex;
|
|
162
|
+
dynamicParams = params;
|
|
163
|
+
}
|
|
164
|
+
// Ensure commandName supports multiple entries
|
|
165
|
+
if (!commands[commandName]) {
|
|
166
|
+
commands[commandName] = [];
|
|
167
|
+
}
|
|
168
|
+
commands[commandName].push({
|
|
169
|
+
methodName: propertyKey,
|
|
170
|
+
builder: builderInstance,
|
|
171
|
+
type: commandType,
|
|
172
|
+
regex,
|
|
173
|
+
dynamicParams
|
|
174
|
+
});
|
|
175
|
+
Reflect.defineMetadata(COMMAND_METADATA_KEY, commands, target);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Retrieves the command map for a given controller.
|
|
180
|
+
*
|
|
181
|
+
* @param controller - The controller class instance.
|
|
182
|
+
* @returns A record containing command metadata indexed by command names.
|
|
183
|
+
*/ function getCommandMap(controller) {
|
|
184
|
+
return Reflect.getMetadata(COMMAND_METADATA_KEY, controller);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Decorator to mark a class as a controller that can later be registered to the App class `(app.ts)` using the `@MeoCord` decorator.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* @Controller()
|
|
192
|
+
* export class PingSlashController {
|
|
193
|
+
* constructor(private pingService: PingService) {}
|
|
194
|
+
*
|
|
195
|
+
* @Command('ping', PingCommandBuilder)
|
|
196
|
+
* async ping(interaction: ChatInputCommandInteraction) {
|
|
197
|
+
* const response = await this.pingService.handlePing()
|
|
198
|
+
* await interaction.reply(response)
|
|
199
|
+
* }
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/ function Controller() {
|
|
203
|
+
return function(target) {
|
|
204
|
+
if (!Reflect.hasMetadata(metadataKey_enum.MetadataKey.Injectable, target)) {
|
|
205
|
+
inversify.injectable()(target);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
exports.Command = Command;
|
|
211
|
+
exports.Controller = Controller;
|
|
212
|
+
exports.MessageHandler = MessageHandler;
|
|
213
|
+
exports.ReactionHandler = ReactionHandler;
|
|
214
|
+
exports.getCommandMap = getCommandMap;
|
|
215
|
+
exports.getMessageHandlers = getMessageHandlers;
|
|
216
|
+
exports.getReactionHandlers = getReactionHandlers;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MeoCord Framework
|
|
5
|
+
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
6
|
+
* SPDX-License-Identifier: MIT
|
|
7
|
+
*/ /**
|
|
8
|
+
* Centralised metadata keys used across the framework's `Reflect` calls.
|
|
9
|
+
*
|
|
10
|
+
* Keeping them here prevents typos, documents the Inversify 8 key rename,
|
|
11
|
+
* and makes key changes a single-file edit.
|
|
12
|
+
*/ var MetadataKey = /*#__PURE__*/ function(MetadataKey) {
|
|
13
|
+
/**
|
|
14
|
+
* Set by Inversify 8's `injectable()` decorator.
|
|
15
|
+
* Renamed from the legacy `'inversify:injectable'` string used in older versions.
|
|
16
|
+
*/ MetadataKey["Injectable"] = "@inversifyjs/core/classIsInjectableFlagReflectKey";
|
|
17
|
+
/**
|
|
18
|
+
* Stores the Inversify `Container` instance on a controller class.
|
|
19
|
+
* Set by `MeoCordFactory.create()`, read by `@UseGuard` at runtime.
|
|
20
|
+
*/ MetadataKey["Container"] = "inversify:container";
|
|
21
|
+
/**
|
|
22
|
+
* Stores the `@MeoCord()` options object on the app class.
|
|
23
|
+
* Read by `MeoCordFactory.create()` to wire up the container.
|
|
24
|
+
*/ MetadataKey["AppOptions"] = "meocord:app-options";
|
|
25
|
+
/**
|
|
26
|
+
* TypeScript compiler-emitted metadata listing constructor parameter types.
|
|
27
|
+
* Requires `"emitDecoratorMetadata": true` in tsconfig.
|
|
28
|
+
*/ MetadataKey["ParamTypes"] = "design:paramtypes";
|
|
29
|
+
/**
|
|
30
|
+
* Stores the guard list applied to a method or class via `@UseGuard`.
|
|
31
|
+
*/ MetadataKey["Guards"] = "guards";
|
|
32
|
+
/**
|
|
33
|
+
* Stores the `CommandType` on a `@CommandBuilder` class.
|
|
34
|
+
*/ MetadataKey["CommandType"] = "commandType";
|
|
35
|
+
return MetadataKey;
|
|
36
|
+
}({});
|
|
37
|
+
|
|
38
|
+
exports.MetadataKey = MetadataKey;
|
|
@@ -10,7 +10,57 @@ require('fs');
|
|
|
10
10
|
require('jiti');
|
|
11
11
|
require('chalk');
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* MeoCord Framework
|
|
15
|
+
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
16
|
+
* SPDX-License-Identifier: MIT
|
|
17
|
+
*/ /**
|
|
18
|
+
* Composes multiple class or method decorators into a single decorator.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* export const Protected = () => applyDecorators(
|
|
23
|
+
* UseGuard(DefaultGuard, GlobalRateLimiterGuard),
|
|
24
|
+
* )
|
|
25
|
+
*
|
|
26
|
+
* @Controller()
|
|
27
|
+
* @Protected()
|
|
28
|
+
* export class PingController {}
|
|
29
|
+
* ```
|
|
30
|
+
*/ function applyDecorators(...decorators) {
|
|
31
|
+
return function(target, propertyKey, descriptor) {
|
|
32
|
+
for (const decorator of decorators){
|
|
33
|
+
if (propertyKey !== undefined && descriptor !== undefined) {
|
|
34
|
+
decorator(target, propertyKey, descriptor);
|
|
35
|
+
} else {
|
|
36
|
+
decorator(target);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return descriptor;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Attaches arbitrary metadata to a class or method. Use alongside `Reflect.getMetadata` to read it back.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
|
|
48
|
+
*
|
|
49
|
+
* @Command('admin', CommandType.SLASH)
|
|
50
|
+
* @Roles('admin', 'moderator')
|
|
51
|
+
* async adminCommand(interaction: ChatInputCommandInteraction) {}
|
|
52
|
+
* ```
|
|
53
|
+
*/ function SetMetadata(metadataKey, metadataValue) {
|
|
54
|
+
return function(target, propertyKey) {
|
|
55
|
+
if (propertyKey !== undefined) {
|
|
56
|
+
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);
|
|
57
|
+
} else {
|
|
58
|
+
Reflect.defineMetadata(metadataKey, metadataValue, target);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
14
62
|
|
|
15
63
|
exports.Logger = theme.Logger;
|
|
16
64
|
exports.Theme = theme.Theme;
|
|
65
|
+
exports.SetMetadata = SetMetadata;
|
|
66
|
+
exports.applyDecorators = applyDecorators;
|