meocord 4.0.0-beta.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/README.md +34 -1
- package/dist/cjs/_shared/component-routes-RTZZ3t5i.cjs +55 -0
- package/dist/cjs/_shared/{controller.decorator-CqgGgjj3.cjs → controller.decorator-DnKZ29W4.cjs} +3 -3
- package/dist/cjs/core/index.cjs +18 -46
- package/dist/cjs/decorator/index.cjs +11 -12
- package/dist/cjs/enum/index.cjs +31 -3
- package/dist/cjs/testing/index.cjs +63 -5
- package/dist/esm/core/component-routes.js +51 -0
- package/dist/esm/core/meocord.app.js +14 -41
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/index.js +1 -0
- package/dist/esm/testing/routing.js +59 -0
- package/dist/types/core/index.d.cts +2 -2
- package/dist/types/core/index.d.ts +2 -2
- package/dist/types/testing/index.d.cts +63 -3
- package/dist/types/testing/index.d.ts +63 -3
- package/package.json +1 -1
- package/dist/cjs/_shared/metadata-key.enum-DG7cVMc4.cjs +0 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,127 @@
|
|
|
1
1
|
# meocord
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
MeoCord 4 builds with Rsbuild instead of webpack, requires Node.js 22.13 and dotenv 18, and can
|
|
6
|
+
deploy a bot without `node_modules`. Most bots need two changes; see the
|
|
7
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md).
|
|
8
|
+
|
|
9
|
+
### Major Changes
|
|
10
|
+
|
|
11
|
+
- [#22](https://github.com/l7aromeo/meocord/pull/22) [`4deb96f`](https://github.com/l7aromeo/meocord/commit/4deb96fd73f86a29c46b9bb080c13e6267246cb1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Build with [Rsbuild](https://rsbuild.rs) instead of webpack. Production builds are several times
|
|
12
|
+
faster, and webpack and its four loader and plugin packages are no longer installed with MeoCord.
|
|
13
|
+
The output layout is unchanged: `dist/main.js`, and assets under `dist/assets/` with the same names.
|
|
14
|
+
Production source maps now list sources as `../src/...` paths instead of `webpack://` URLs.
|
|
15
|
+
|
|
16
|
+
**Breaking:** the `webpack` hook in `meocord.config.ts` is replaced by `rsbuild`, which receives
|
|
17
|
+
Rsbuild's configuration. A config that still declares `webpack` stops the build with a message
|
|
18
|
+
saying so. `MeoCordWebpackConfig` is removed; import `RsbuildConfig` from `meocord/interface`
|
|
19
|
+
instead. The [migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#2-replace-the-webpack-hook-with-rsbuild) shows where each
|
|
20
|
+
webpack setting goes.
|
|
21
|
+
|
|
22
|
+
- [#44](https://github.com/l7aromeo/meocord/pull/44) [`e48472e`](https://github.com/l7aromeo/meocord/commit/e48472e9ffee2f0371a7a2a71a4063e99ce61674) Thanks [@l7aromeo](https://github.com/l7aromeo)! - **Breaking:** `meocord/decorator` exports only the decorators. The routing helpers it also exported
|
|
23
|
+
— `getCommandMap`, `getMessageHandlers`, `getReactionHandlers`, `getAutocompleteHandlers`,
|
|
24
|
+
`findAmbiguousRoutes` and `PARAM_SEPARATOR` — are internal to MeoCord now. See the
|
|
25
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#internal-helpers-are-no-longer-exported).
|
|
26
|
+
|
|
27
|
+
- [#19](https://github.com/l7aromeo/meocord/pull/19) [`204c7be`](https://github.com/l7aromeo/meocord/commit/204c7bec74886c931732cb771d9178b47fbec5e8) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Require dotenv 18. The `dotenv` peer dependency moves from `^17.4.2` to `^18.0.3`, so install
|
|
28
|
+
`dotenv@18` alongside MeoCord 4. No application code changes — `import 'dotenv/config'` behaves
|
|
29
|
+
the same. See the [migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#1-upgrade-dotenv-to-18).
|
|
30
|
+
|
|
31
|
+
- [#33](https://github.com/l7aromeo/meocord/pull/33) [`21b06ae`](https://github.com/l7aromeo/meocord/commit/21b06ae6810cfe203abadcadc6f7b7247c7b305b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Require Node.js 22.13 or newer, up from 22.0. A built bot loads its compiled config with `require()`
|
|
32
|
+
of an ES module. Node 22.12 runs that without a flag but still warns on every start and crashes on a
|
|
33
|
+
config that throws; 22.13 is the first 22 release that does neither. Bun is unaffected. See the
|
|
34
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#before-you-start-nodejs-2213).
|
|
35
|
+
|
|
36
|
+
- [#36](https://github.com/l7aromeo/meocord/pull/36) [`45e564e`](https://github.com/l7aromeo/meocord/commit/45e564e6baff17313037372ec56baa1711a3584c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - **Breaking:** `app.start()` rejects when the login fails. It logged the error and resolved, so a bot
|
|
37
|
+
with an invalid token went on to log "Application started" and exit with code 0, which Docker's
|
|
38
|
+
`restart: on-failure`, systemd and CI all read as success. It now sets the exit code to 1 before
|
|
39
|
+
rejecting, so a bot that fails to log in exits 1 with its entry point unchanged. See the
|
|
40
|
+
[migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#4-build-and-start).
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- [#22](https://github.com/l7aromeo/meocord/pull/22) [`4deb96f`](https://github.com/l7aromeo/meocord/commit/4deb96fd73f86a29c46b9bb080c13e6267246cb1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Add `bundleDependencies`, which puts everything a bot needs inside `dist`, so it deploys without
|
|
45
|
+
`node_modules` or an install step. Plain JavaScript dependencies are bundled into `main.js`. Native
|
|
46
|
+
addons such as `sharp` are found while building and copied, with their platform binary, into
|
|
47
|
+
`dist/node_modules` — nothing has to be listed. Only binaries for the platform building are copied,
|
|
48
|
+
whichever package manager installed them.
|
|
49
|
+
|
|
50
|
+
A build carrying native addons records its platform in `dist/meocord.platform.json`, and a bot
|
|
51
|
+
started on another platform stops before going online with a message naming both. Build on the
|
|
52
|
+
platform you deploy to. See
|
|
53
|
+
[Self-contained builds](https://github.com/l7aromeo/meocord#self-contained-builds).
|
|
54
|
+
|
|
55
|
+
- [#47](https://github.com/l7aromeo/meocord/pull/47) [`9652436`](https://github.com/l7aromeo/meocord/commit/965243660323277827a64851f28da9cda2c2fe0c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Add `resolveRoute` and `findRouteConflicts` to `meocord/testing`, for testing which handler a
|
|
56
|
+
component's customId reaches. `resolveRoute(App, { type, customId })` gives the answer dispatch
|
|
57
|
+
gives — across every controller the app registers, for that component type, most specific pattern
|
|
58
|
+
first — as the controller, the handler method and its name, and the captured params, or `undefined`.
|
|
59
|
+
`findRouteConflicts(App)` returns the pattern pairs that can match the same customId, which MeoCord
|
|
60
|
+
otherwise only warns about at startup. Both read decorator metadata only, and dispatch runs on the
|
|
61
|
+
same matcher.
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- [#22](https://github.com/l7aromeo/meocord/pull/22) [`4deb96f`](https://github.com/l7aromeo/meocord/commit/4deb96fd73f86a29c46b9bb080c13e6267246cb1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Read `meocord.config.ts` on every build. `meocord build` read the compiled `dist/meocord.config.mjs`
|
|
66
|
+
left by the previous build, so a config edit took effect one build late, and the watcher's reload
|
|
67
|
+
on a config change reloaded nothing.
|
|
68
|
+
|
|
69
|
+
- [#22](https://github.com/l7aromeo/meocord/pull/22) [`4deb96f`](https://github.com/l7aromeo/meocord/commit/4deb96fd73f86a29c46b9bb080c13e6267246cb1) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Start the bot with `bun --no-install` from `meocord start`. Without it, bun downloads any package it
|
|
70
|
+
cannot find while the bot runs, which a bot deployed without `node_modules` would do in
|
|
71
|
+
production. If you start `dist/main.js` with bun yourself, pass the flag too.
|
|
72
|
+
|
|
73
|
+
- [#37](https://github.com/l7aromeo/meocord/pull/37) [`a9452c2`](https://github.com/l7aromeo/meocord/commit/a9452c256beace70141bb87ed33c693d14064d14) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Type the CommonJS build as CommonJS. Every entry point's `require` condition resolved to the ESM
|
|
74
|
+
declarations, so a CommonJS TypeScript project was told `meocord/core` is an ES module it cannot
|
|
75
|
+
`require`, even with `skipLibCheck`. Each entry now ships `.d.cts` declarations for `require`, and
|
|
76
|
+
`meocord/eslint` types its `module.exports` array as what `require` returns.
|
|
77
|
+
|
|
78
|
+
- [#33](https://github.com/l7aromeo/meocord/pull/33) [`21b06ae`](https://github.com/l7aromeo/meocord/commit/21b06ae6810cfe203abadcadc6f7b7247c7b305b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Load the compiled config without a transpiler. A built bot reads `dist/meocord.config.mjs` with
|
|
79
|
+
`require()` and no longer falls back to `meocord.config.ts` when it is missing, so jiti stays out of
|
|
80
|
+
a bot bundled with `bundleDependencies`. `meocord build` fails when the config does not compile,
|
|
81
|
+
instead of warning and producing a bot that cannot start.
|
|
82
|
+
|
|
83
|
+
- [#35](https://github.com/l7aromeo/meocord/pull/35) [`b93a771`](https://github.com/l7aromeo/meocord/commit/b93a771b19664345fec4222d5a2c29dcf5b31a0d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Generate code that passes a new application's own `lint`. A generated guard failed `tsc` and ESLint
|
|
84
|
+
— `GuardInterface` imported as a value, and an unused `context` parameter — and generated message and
|
|
85
|
+
reaction controllers imported names they never used, which only the application's ESLint, run in the
|
|
86
|
+
background after generating, removed. New applications also typecheck `meocord.config.ts`: the
|
|
87
|
+
template's `tsconfig.json` includes it instead of excluding it, so a type error in the config fails
|
|
88
|
+
`lint`, and editors resolve `paths` aliases imported there. `noEmit` stays on, so `tsc` writes nothing
|
|
89
|
+
beside it.
|
|
90
|
+
|
|
91
|
+
- [#25](https://github.com/l7aromeo/meocord/pull/25) [`2179f85`](https://github.com/l7aromeo/meocord/commit/2179f85c2912d45e3fefbf996ca267c19a1a773c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Stop `meocord generate` overwriting files. Slash, context-menu and primary entry point controllers
|
|
92
|
+
all wrote one shared `builders/sample.builder.ts`, so generating a second controller replaced a
|
|
93
|
+
builder you had already edited. Each controller now gets its own `builders/<name>.builder.ts`
|
|
94
|
+
exporting `<Name>CommandBuilder`, and registers a command named after it — `admin/ban` registers
|
|
95
|
+
`admin-ban` — rather than every one registering `sample-slash`. Generating a controller, service or
|
|
96
|
+
guard refuses if any file it would write already exists.
|
|
97
|
+
|
|
98
|
+
- [#40](https://github.com/l7aromeo/meocord/pull/40) [`3629edd`](https://github.com/l7aromeo/meocord/commit/3629eddaf20bd0947d24e5582aa167b2c0ca0d47) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Lint `meocord.config.ts`. The shared ESLint config from `meocord/eslint` ignored it, so the config
|
|
99
|
+
alone skipped the rules every other file follows — unused imports, formatting. It is linted like the
|
|
100
|
+
rest now; the typecheck it already gets is unchanged.
|
|
101
|
+
|
|
102
|
+
- [#24](https://github.com/l7aromeo/meocord/pull/24) [`5e2a54b`](https://github.com/l7aromeo/meocord/commit/5e2a54b67a1f9f51c4c3a3e8cf51247de2ac2528) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Type `deleted` on `createMockMessage()`. The mock tracks and documents it, but it was missing from
|
|
103
|
+
the type, so `message.deleted` did not compile.
|
|
104
|
+
|
|
105
|
+
- [#24](https://github.com/l7aromeo/meocord/pull/24) [`5e2a54b`](https://github.com/l7aromeo/meocord/commit/5e2a54b67a1f9f51c4c3a3e8cf51247de2ac2528) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Accept slash command builders that add options. `@CommandBuilder(CommandType.SLASH)` rejected
|
|
106
|
+
`new SlashCommandBuilder().addStringOption(...)`, whose type narrows to
|
|
107
|
+
`SlashCommandOptionsOnlyBuilder`, so the most common builder — one command with an option — did
|
|
108
|
+
not compile.
|
|
109
|
+
|
|
110
|
+
- [#21](https://github.com/l7aromeo/meocord/pull/21) [`bebf116`](https://github.com/l7aromeo/meocord/commit/bebf1168466595546017a317b62acd3707ff2d1c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Generate applications with current test tooling: vitest and `@vitest/coverage-istanbul` 5,
|
|
111
|
+
`unplugin-swc` 2, and current eslint, prettier and typescript-eslint. A new application's
|
|
112
|
+
`test:coverage` no longer fails on its decorated entry files.
|
|
113
|
+
|
|
114
|
+
## 4.0.0-beta.5
|
|
115
|
+
|
|
116
|
+
### Minor Changes
|
|
117
|
+
|
|
118
|
+
- [#47](https://github.com/l7aromeo/meocord/pull/47) [`9652436`](https://github.com/l7aromeo/meocord/commit/965243660323277827a64851f28da9cda2c2fe0c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Add `resolveRoute` and `findRouteConflicts` to `meocord/testing`, for testing which handler a
|
|
119
|
+
component's customId reaches. `resolveRoute(App, { type, customId })` gives the answer dispatch gives
|
|
120
|
+
— across every controller the app registers, for that component type, most specific pattern first —
|
|
121
|
+
as the controller, the handler method and its name, and the captured params, or `undefined`. `findRouteConflicts(App)` returns the
|
|
122
|
+
pattern pairs that can match the same customId, which MeoCord otherwise only warns about at startup.
|
|
123
|
+
Both read decorator metadata only, and dispatch runs on the same matcher.
|
|
124
|
+
|
|
3
125
|
## 4.0.0-beta.4
|
|
4
126
|
|
|
5
127
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -689,7 +689,7 @@ bun run test:typecheck # tsc -p tsconfig.test.json
|
|
|
689
689
|
bun run lint # eslint --fix + tsc
|
|
690
690
|
```
|
|
691
691
|
|
|
692
|
-
|
|
692
|
+
Generated apps come with Vitest set up — `vitest.config.ts` with SWC for decorator metadata, and `test`, `test:watch` and `test:coverage` scripts — plus a spec beside every generated component. In an older project, add Vitest (or keep Jest) with the same SWC setup; `meocord/testing` works with either.
|
|
693
693
|
|
|
694
694
|
### `MeoCordTestingModule`
|
|
695
695
|
|
|
@@ -884,6 +884,39 @@ expect(msg.delete).toHaveBeenCalledTimes(1)
|
|
|
884
884
|
|
|
885
885
|
</details>
|
|
886
886
|
|
|
887
|
+
<details>
|
|
888
|
+
<summary><b><code>resolveRoute</code> / <code>findRouteConflicts</code></b></summary>
|
|
889
|
+
|
|
890
|
+
Tests which handler a component's customId reaches — the same answer dispatch gives, across every
|
|
891
|
+
controller your app registers, most specific pattern first. They read decorator metadata only, so
|
|
892
|
+
they need no Discord client, config or container. They check routing alone: guards are not run, and
|
|
893
|
+
whether a controller's dependencies are bound is for `MeoCordTestingModule` to test.
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
import { findRouteConflicts, resolveRoute } from 'meocord/testing'
|
|
897
|
+
import { CommandType } from 'meocord/enum'
|
|
898
|
+
import App from '@src/app'
|
|
899
|
+
import { ProfileController } from '@src/controllers/button/profile.button.controller'
|
|
900
|
+
|
|
901
|
+
it('routes the profile button to its handler', () => {
|
|
902
|
+
const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
|
|
903
|
+
|
|
904
|
+
// The method itself rather than its name, so renaming it in your editor updates the test too.
|
|
905
|
+
expect(route?.handler).toBe(ProfileController.prototype.showProfile)
|
|
906
|
+
expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
// Patterns that can match the same customId, as a failing test rather than a startup warning.
|
|
910
|
+
it('has no overlapping component patterns', () => {
|
|
911
|
+
expect(findRouteConflicts(App)).toEqual([])
|
|
912
|
+
})
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
`resolveRoute` returns the `controller`, the `handler` method and its name as `method`, and the
|
|
916
|
+
`params` the pattern captured — or `undefined` when no route handles the customId.
|
|
917
|
+
|
|
918
|
+
</details>
|
|
919
|
+
|
|
887
920
|
<details>
|
|
888
921
|
<summary><b><code>createMock</code></b></summary>
|
|
889
922
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var controller_decorator = require('./controller.decorator-DnKZ29W4.cjs');
|
|
4
|
+
|
|
5
|
+
/** Every customId-pattern route of the given controllers, most specific first, read from metadata alone. */ function buildComponentRoutes(controllerClasses) {
|
|
6
|
+
const routes = [];
|
|
7
|
+
for (const controllerClass of controllerClasses){
|
|
8
|
+
const commandMap = controller_decorator.getCommandMap(controllerClass.prototype);
|
|
9
|
+
if (!commandMap) continue;
|
|
10
|
+
for (const [pattern, metaArray] of Object.entries(commandMap)){
|
|
11
|
+
if (!Array.isArray(metaArray)) continue;
|
|
12
|
+
for (const meta of metaArray){
|
|
13
|
+
if (meta.regex) routes.push({
|
|
14
|
+
controllerClass,
|
|
15
|
+
meta,
|
|
16
|
+
pattern
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return routes.sort((a, b)=>(b.meta.specificity ?? 0) - (a.meta.specificity ?? 0));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The route dispatch runs for a customId: the first, in rank order, whose type `acceptsType` allows
|
|
25
|
+
* and whose pattern matches, with the captured params.
|
|
26
|
+
*/ function matchComponentRoute(routes, acceptsType, customId) {
|
|
27
|
+
for (const route of routes){
|
|
28
|
+
if (!acceptsType(route.meta.type)) continue;
|
|
29
|
+
const match = route.meta.regex.exec(customId);
|
|
30
|
+
if (match) return {
|
|
31
|
+
route,
|
|
32
|
+
params: {
|
|
33
|
+
...match.groups
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Pattern pairs that can match one customId, compared only within a component type, as dispatch does. */ function findComponentRouteConflicts(routes) {
|
|
40
|
+
const byType = new Map();
|
|
41
|
+
for (const { meta, pattern } of routes)byType.set(meta.type, [
|
|
42
|
+
...byType.get(meta.type) ?? [],
|
|
43
|
+
pattern
|
|
44
|
+
]);
|
|
45
|
+
return [
|
|
46
|
+
...byType
|
|
47
|
+
].flatMap(([type, patterns])=>controller_decorator.findAmbiguousRoutes(patterns).map((pair)=>({
|
|
48
|
+
type,
|
|
49
|
+
patterns: pair
|
|
50
|
+
})));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.buildComponentRoutes = buildComponentRoutes;
|
|
54
|
+
exports.findComponentRouteConflicts = findComponentRouteConflicts;
|
|
55
|
+
exports.matchComponentRoute = matchComponentRoute;
|
package/dist/cjs/_shared/{controller.decorator-CqgGgjj3.cjs → controller.decorator-DnKZ29W4.cjs}
RENAMED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
4
|
var inversify = require('inversify');
|
|
5
5
|
var enum_index = require('../enum/index.cjs');
|
|
6
|
-
var metadataKey_enum = require('./metadata-key.enum-DG7cVMc4.cjs');
|
|
7
6
|
var discord_js = require('discord.js');
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -311,7 +310,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
|
|
|
311
310
|
if (typeof builderOrType === 'function') {
|
|
312
311
|
const builderObj = new builderOrType();
|
|
313
312
|
builderInstance = builderObj.build(commandName);
|
|
314
|
-
commandType = Reflect.getMetadata(
|
|
313
|
+
commandType = Reflect.getMetadata(enum_index.MetadataKey.CommandType, builderOrType);
|
|
315
314
|
if (!(commandType in enum_index.CommandType)) {
|
|
316
315
|
throw new Error(`Metadata for 'commandType' is missing on builder ${builderOrType.name}`);
|
|
317
316
|
}
|
|
@@ -401,7 +400,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
|
|
|
401
400
|
* ```
|
|
402
401
|
*/ function Controller() {
|
|
403
402
|
return function(target) {
|
|
404
|
-
if (!Reflect.hasMetadata(
|
|
403
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
|
|
405
404
|
inversify.injectable()(target);
|
|
406
405
|
}
|
|
407
406
|
};
|
|
@@ -448,6 +447,7 @@ exports.getCommandMap = getCommandMap;
|
|
|
448
447
|
exports.getMessageHandlers = getMessageHandlers;
|
|
449
448
|
exports.getReactionHandlers = getReactionHandlers;
|
|
450
449
|
exports.hasCustomId = hasCustomId;
|
|
450
|
+
exports.isCustomIdRouted = isCustomIdRouted;
|
|
451
451
|
exports.matchesCommandType = matchesCommandType;
|
|
452
452
|
exports.resolveCommandPaths = resolveCommandPaths;
|
|
453
453
|
exports.resolveOptionParams = resolveOptionParams;
|
package/dist/cjs/core/index.cjs
CHANGED
|
@@ -4,13 +4,13 @@ require('reflect-metadata');
|
|
|
4
4
|
var inversify = require('inversify');
|
|
5
5
|
var discord_js = require('discord.js');
|
|
6
6
|
var theme = require('../_shared/theme-C8w8ivsR.cjs');
|
|
7
|
-
var controller_decorator = require('../_shared/controller.decorator-
|
|
7
|
+
var controller_decorator = require('../_shared/controller.decorator-DnKZ29W4.cjs');
|
|
8
8
|
var lodashEs = require('lodash-es');
|
|
9
9
|
var enum_index = require('../enum/index.cjs');
|
|
10
10
|
var Table = require('cli-table3');
|
|
11
|
+
var componentRoutes = require('../_shared/component-routes-RTZZ3t5i.cjs');
|
|
11
12
|
var fs = require('fs');
|
|
12
13
|
var path = require('path');
|
|
13
|
-
var metadataKey_enum = require('../_shared/metadata-key.enum-DG7cVMc4.cjs');
|
|
14
14
|
require('node:util');
|
|
15
15
|
require('dayjs');
|
|
16
16
|
require('dayjs/plugin/utc.js');
|
|
@@ -200,44 +200,18 @@ class MeoCordApp {
|
|
|
200
200
|
}
|
|
201
201
|
getComponentRoutes() {
|
|
202
202
|
if (this.componentRoutes) return this.componentRoutes;
|
|
203
|
-
const routes =
|
|
204
|
-
for (const controllerClass of this.controllerClasses){
|
|
205
|
-
const commandMap = controller_decorator.getCommandMap(this.getInstance(controllerClass));
|
|
206
|
-
if (!commandMap) continue;
|
|
207
|
-
for (const [pattern, metaArray] of Object.entries(commandMap)){
|
|
208
|
-
if (!Array.isArray(metaArray)) continue;
|
|
209
|
-
for (const meta of metaArray){
|
|
210
|
-
if (meta.regex) routes.push({
|
|
211
|
-
controllerClass,
|
|
212
|
-
meta,
|
|
213
|
-
pattern
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
routes.sort((a, b)=>(b.meta.specificity ?? 0) - (a.meta.specificity ?? 0));
|
|
203
|
+
const routes = componentRoutes.buildComponentRoutes(this.controllerClasses);
|
|
219
204
|
this.reportAmbiguousRoutes(routes);
|
|
220
205
|
this.componentRoutes = routes;
|
|
221
206
|
return routes;
|
|
222
207
|
}
|
|
223
208
|
/**
|
|
224
|
-
* Warns rather than throws: an app whose patterns overlap boots and works
|
|
225
|
-
*
|
|
209
|
+
* Warns rather than throws: an app whose patterns overlap boots and works, and refusing to start
|
|
210
|
+
* would turn a latent mis-route into an outage.
|
|
226
211
|
*/ reportAmbiguousRoutes(routes) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const byType = new Map();
|
|
231
|
-
for (const { meta, pattern } of routes){
|
|
232
|
-
const patterns = byType.get(meta.type) ?? [];
|
|
233
|
-
patterns.push(pattern);
|
|
234
|
-
byType.set(meta.type, patterns);
|
|
235
|
-
}
|
|
236
|
-
const collisions = [
|
|
237
|
-
...byType.values()
|
|
238
|
-
].flatMap((patterns)=>controller_decorator.findAmbiguousRoutes(patterns));
|
|
239
|
-
if (collisions.length === 0) return;
|
|
240
|
-
this.logger.warn(`${collisions.length} pattern pair(s) can match the same customId, so which one runs is decided by ` + `ranking rather than by the ids themselves:\n` + collisions.map(([left, right])=>` "${left}" vs "${right}"`).join('\n') + `\nA parameter stops at "${controller_decorator.PARAM_SEPARATOR}", so separating these segments with it makes them distinct.`);
|
|
212
|
+
const conflicts = componentRoutes.findComponentRouteConflicts(routes);
|
|
213
|
+
if (conflicts.length === 0) return;
|
|
214
|
+
this.logger.warn(`${conflicts.length} pattern pair(s) can match the same customId, so which one runs is decided by ` + `ranking rather than by the ids themselves:\n` + conflicts.map(({ patterns: [left, right] })=>` "${left}" vs "${right}"`).join('\n') + `\nA parameter stops at "${controller_decorator.PARAM_SEPARATOR}", so separating these segments with it makes them distinct.`);
|
|
241
215
|
}
|
|
242
216
|
getAutocompleteRoutes() {
|
|
243
217
|
if (this.autocompleteRoutes) return this.autocompleteRoutes;
|
|
@@ -283,14 +257,12 @@ class MeoCordApp {
|
|
|
283
257
|
// Commands match their registered name exactly and cannot overlap.
|
|
284
258
|
if (controller_decorator.hasCustomId(interaction)) {
|
|
285
259
|
const customId = interaction.customId;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
interaction.dynamicParams = match.groups ?? {};
|
|
293
|
-
await this.executeCommand(this.getInstance(controllerClass), meta, interaction, customId);
|
|
260
|
+
// The component type as well as the pattern: a button and a select menu may share a customId shape.
|
|
261
|
+
const matched = componentRoutes.matchComponentRoute(this.getComponentRoutes(), (type)=>controller_decorator.matchesCommandType(type, interaction), customId);
|
|
262
|
+
if (matched) {
|
|
263
|
+
const { route, params } = matched;
|
|
264
|
+
interaction.dynamicParams = params;
|
|
265
|
+
await this.executeCommand(this.getInstance(route.controllerClass), route.meta, interaction, customId);
|
|
294
266
|
return;
|
|
295
267
|
}
|
|
296
268
|
}
|
|
@@ -560,11 +532,11 @@ class MeoCordApp {
|
|
|
560
532
|
* Recursively binds a class and all its constructor dependencies to the container in singleton scope.
|
|
561
533
|
*/ function bindDependencies(container, cls) {
|
|
562
534
|
if (container.isBound(cls)) return;
|
|
563
|
-
if (!Reflect.hasMetadata(
|
|
535
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, cls)) {
|
|
564
536
|
inversify.injectable()(cls);
|
|
565
537
|
}
|
|
566
538
|
container.bind(cls).toSelf().inSingletonScope();
|
|
567
|
-
const deps = Reflect.getMetadata(
|
|
539
|
+
const deps = Reflect.getMetadata(enum_index.MetadataKey.ParamTypes, cls) || [];
|
|
568
540
|
for (const dep of deps){
|
|
569
541
|
if (dep === discord_js.Client) continue;
|
|
570
542
|
bindDependencies(container, dep);
|
|
@@ -572,7 +544,7 @@ class MeoCordApp {
|
|
|
572
544
|
}
|
|
573
545
|
class MeoCordFactory {
|
|
574
546
|
static create(target) {
|
|
575
|
-
const options = Reflect.getMetadata(
|
|
547
|
+
const options = Reflect.getMetadata(enum_index.MetadataKey.AppOptions, target);
|
|
576
548
|
if (!options) {
|
|
577
549
|
if (typeof target === 'function') {
|
|
578
550
|
this.logger.error(`No @MeoCord() options found for class: ${target.name}`);
|
|
@@ -605,7 +577,7 @@ class MeoCordFactory {
|
|
|
605
577
|
}
|
|
606
578
|
// Stamp each controller class with the container so @UseGuard can resolve guards
|
|
607
579
|
for (const ctrl of options.controllers){
|
|
608
|
-
Reflect.defineMetadata(
|
|
580
|
+
Reflect.defineMetadata(enum_index.MetadataKey.Container, container, ctrl);
|
|
609
581
|
}
|
|
610
582
|
return new MeoCordApp(options.controllers, container, discordClient, meocordConfig.discordToken, options.activities);
|
|
611
583
|
}
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
4
|
var inversify = require('inversify');
|
|
5
|
-
var
|
|
6
|
-
var controller_decorator = require('../_shared/controller.decorator-
|
|
5
|
+
var enum_index = require('../enum/index.cjs');
|
|
6
|
+
var controller_decorator = require('../_shared/controller.decorator-DnKZ29W4.cjs');
|
|
7
7
|
var discord_js = require('discord.js');
|
|
8
|
-
require('../enum/index.cjs');
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Marks a class as a service, injectable into controllers and other services.
|
|
@@ -21,7 +20,7 @@ require('../enum/index.cjs');
|
|
|
21
20
|
* ```
|
|
22
21
|
*/ function Service() {
|
|
23
22
|
return function(target) {
|
|
24
|
-
if (!Reflect.hasMetadata(
|
|
23
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
|
|
25
24
|
inversify.injectable()(target);
|
|
26
25
|
}
|
|
27
26
|
};
|
|
@@ -44,11 +43,11 @@ require('../enum/index.cjs');
|
|
|
44
43
|
*/ function CommandBuilder(commandType) {
|
|
45
44
|
return function(target) {
|
|
46
45
|
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
47
|
-
if (!Reflect.hasMetadata(
|
|
46
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
|
|
48
47
|
inversify.injectable()(target);
|
|
49
48
|
}
|
|
50
49
|
// Define the command type metadata for the target class
|
|
51
|
-
Reflect.defineMetadata(
|
|
50
|
+
Reflect.defineMetadata(enum_index.MetadataKey.CommandType, commandType, target);
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -62,7 +61,7 @@ function applyGuards(descriptor, guards, propertyKey) {
|
|
|
62
61
|
if (!isValidContext(context)) {
|
|
63
62
|
throw new Error(`The first argument of ${String(propertyKey)} must be an instance of Interaction, Message, or MessageReaction.`);
|
|
64
63
|
}
|
|
65
|
-
const container = Reflect.getMetadata(
|
|
64
|
+
const container = Reflect.getMetadata(enum_index.MetadataKey.Container, this.constructor);
|
|
66
65
|
for (const guard of guards){
|
|
67
66
|
let guardInstance;
|
|
68
67
|
if (isGuardWithParams(guard)) {
|
|
@@ -101,7 +100,7 @@ function applyGuards(descriptor, guards, propertyKey) {
|
|
|
101
100
|
* ```
|
|
102
101
|
*/ function Guard() {
|
|
103
102
|
return function(target) {
|
|
104
|
-
if (!Reflect.hasMetadata(
|
|
103
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
|
|
105
104
|
inversify.injectable()(target);
|
|
106
105
|
}
|
|
107
106
|
};
|
|
@@ -129,7 +128,7 @@ function applyGuards(descriptor, guards, propertyKey) {
|
|
|
129
128
|
if (descriptor && propertyKey) {
|
|
130
129
|
// Method Decorator
|
|
131
130
|
applyGuards(descriptor, guards, String(propertyKey));
|
|
132
|
-
Reflect.defineMetadata(
|
|
131
|
+
Reflect.defineMetadata(enum_index.MetadataKey.Guards, guards, target, propertyKey);
|
|
133
132
|
} else if (typeof target === 'function' && !propertyKey && !descriptor) {
|
|
134
133
|
// Class Decorator
|
|
135
134
|
const prototype = target.prototype;
|
|
@@ -145,7 +144,7 @@ function applyGuards(descriptor, guards, propertyKey) {
|
|
|
145
144
|
if (methodDescriptor) {
|
|
146
145
|
applyGuards(methodDescriptor, guards, methodName);
|
|
147
146
|
Object.defineProperty(prototype, methodName, methodDescriptor);
|
|
148
|
-
Reflect.defineMetadata(
|
|
147
|
+
Reflect.defineMetadata(enum_index.MetadataKey.Guards, guards, prototype, methodName);
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
}
|
|
@@ -175,10 +174,10 @@ function applyGuards(descriptor, guards, propertyKey) {
|
|
|
175
174
|
* ```
|
|
176
175
|
*/ function MeoCord(options) {
|
|
177
176
|
return (target)=>{
|
|
178
|
-
if (!Reflect.hasMetadata(
|
|
177
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
|
|
179
178
|
inversify.injectable()(target);
|
|
180
179
|
}
|
|
181
|
-
Reflect.defineMetadata(
|
|
180
|
+
Reflect.defineMetadata(enum_index.MetadataKey.AppOptions, options, target);
|
|
182
181
|
};
|
|
183
182
|
}
|
|
184
183
|
|
package/dist/cjs/enum/index.cjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var metadataKey_enum = require('../_shared/metadata-key.enum-DG7cVMc4.cjs');
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* The kind of interaction a `@Command` method handles. Each member is one Discord interaction type,
|
|
7
5
|
* so the handler's parameter type follows from it; entity select menus are separate members for
|
|
@@ -27,6 +25,36 @@ var metadataKey_enum = require('../_shared/metadata-key.enum-DG7cVMc4.cjs');
|
|
|
27
25
|
return ReactionHandlerAction;
|
|
28
26
|
}({});
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Centralised metadata keys used across the framework's `Reflect` calls.
|
|
30
|
+
*
|
|
31
|
+
* Keeping them here prevents typos, documents the Inversify 8 key rename,
|
|
32
|
+
* and makes key changes a single-file edit.
|
|
33
|
+
*/ var MetadataKey = /*#__PURE__*/ function(MetadataKey) {
|
|
34
|
+
/**
|
|
35
|
+
* Set by Inversify 8's `injectable()` decorator.
|
|
36
|
+
*/ MetadataKey["Injectable"] = "@inversifyjs/core/classIsInjectableFlagReflectKey";
|
|
37
|
+
/**
|
|
38
|
+
* Stores the Inversify `Container` instance on a controller class.
|
|
39
|
+
* Set by `MeoCordFactory.create()`, read by `@UseGuard` at runtime.
|
|
40
|
+
*/ MetadataKey["Container"] = "inversify:container";
|
|
41
|
+
/**
|
|
42
|
+
* Stores the `@MeoCord()` options object on the app class.
|
|
43
|
+
* Read by `MeoCordFactory.create()` to wire up the container.
|
|
44
|
+
*/ MetadataKey["AppOptions"] = "meocord:app-options";
|
|
45
|
+
/**
|
|
46
|
+
* TypeScript compiler-emitted metadata listing constructor parameter types.
|
|
47
|
+
* Requires `"emitDecoratorMetadata": true` in tsconfig.
|
|
48
|
+
*/ MetadataKey["ParamTypes"] = "design:paramtypes";
|
|
49
|
+
/**
|
|
50
|
+
* Stores the guard list applied to a method or class via `@UseGuard`.
|
|
51
|
+
*/ MetadataKey["Guards"] = "guards";
|
|
52
|
+
/**
|
|
53
|
+
* Stores the `CommandType` on a `@CommandBuilder` class.
|
|
54
|
+
*/ MetadataKey["CommandType"] = "commandType";
|
|
55
|
+
return MetadataKey;
|
|
56
|
+
}({});
|
|
57
|
+
|
|
31
58
|
exports.CommandType = CommandType;
|
|
59
|
+
exports.MetadataKey = MetadataKey;
|
|
32
60
|
exports.ReactionHandlerAction = ReactionHandlerAction;
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
4
|
var inversify = require('inversify');
|
|
5
|
-
var
|
|
5
|
+
var enum_index = require('../enum/index.cjs');
|
|
6
6
|
var discord_js = require('discord.js');
|
|
7
|
+
var componentRoutes = require('../_shared/component-routes-RTZZ3t5i.cjs');
|
|
8
|
+
var controller_decorator = require('../_shared/controller.decorator-DnKZ29W4.cjs');
|
|
7
9
|
|
|
8
10
|
function isValueProvider(p) {
|
|
9
11
|
return 'useValue' in p;
|
|
@@ -67,7 +69,7 @@ function isValueProvider(p) {
|
|
|
67
69
|
container.bind(provider.provide).toConstantValue(provider.useValue);
|
|
68
70
|
} else {
|
|
69
71
|
const cls = provider.useClass;
|
|
70
|
-
if (!Reflect.hasMetadata(
|
|
72
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, cls)) {
|
|
71
73
|
inversify.injectable()(cls);
|
|
72
74
|
}
|
|
73
75
|
container.bind(provider.provide).to(cls).inSingletonScope();
|
|
@@ -80,11 +82,11 @@ function isValueProvider(p) {
|
|
|
80
82
|
// Recursively bind controllers and their dependencies, skipping already-bound tokens
|
|
81
83
|
const bindClass = (cls)=>{
|
|
82
84
|
if (container.isBound(cls)) return;
|
|
83
|
-
if (!Reflect.hasMetadata(
|
|
85
|
+
if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, cls)) {
|
|
84
86
|
inversify.injectable()(cls);
|
|
85
87
|
}
|
|
86
88
|
container.bind(cls).toSelf().inSingletonScope();
|
|
87
|
-
const deps = Reflect.getMetadata(
|
|
89
|
+
const deps = Reflect.getMetadata(enum_index.MetadataKey.ParamTypes, cls) || [];
|
|
88
90
|
for (const dep of deps){
|
|
89
91
|
bindClass(dep);
|
|
90
92
|
}
|
|
@@ -92,7 +94,7 @@ function isValueProvider(p) {
|
|
|
92
94
|
for (const ctrl of this.options.controllers ?? []){
|
|
93
95
|
bindClass(ctrl);
|
|
94
96
|
// Stamp container on controller class so @UseGuard works in tests too
|
|
95
|
-
Reflect.defineMetadata(
|
|
97
|
+
Reflect.defineMetadata(enum_index.MetadataKey.Container, container, ctrl);
|
|
96
98
|
}
|
|
97
99
|
return new TestingModule(container);
|
|
98
100
|
}
|
|
@@ -866,6 +868,60 @@ function findPrototypeMethod(instance, name) {
|
|
|
866
868
|
return stubDeep(base);
|
|
867
869
|
}
|
|
868
870
|
|
|
871
|
+
/** The controllers `@MeoCord({ controllers })` registers on an application class. */ function controllersOf(app) {
|
|
872
|
+
const options = Reflect.getMetadata(enum_index.MetadataKey.AppOptions, app);
|
|
873
|
+
if (!options) throw new TypeError(`${app.name || 'The given class'} is not decorated with @MeoCord().`);
|
|
874
|
+
return options.controllers ?? [];
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Resolves which handler a component's customId reaches, the way dispatch does: across every
|
|
878
|
+
* controller the application registers, for that component type, most specific pattern first.
|
|
879
|
+
*
|
|
880
|
+
* Reads decorator metadata only, so it runs in a plain unit test with no Discord client, config
|
|
881
|
+
* or container. It checks routing alone: guards are not run, and whether the controller's
|
|
882
|
+
* dependencies are bound is for `MeoCordTestingModule` to test.
|
|
883
|
+
*
|
|
884
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
885
|
+
* @param component - The component type and the customId it carries.
|
|
886
|
+
* @returns The handler that runs, or `undefined` when no route handles the customId.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```ts
|
|
890
|
+
* const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
|
|
891
|
+
* expect(route?.handler).toBe(ProfileController.prototype.showProfile)
|
|
892
|
+
* expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
|
|
893
|
+
* ```
|
|
894
|
+
*/ function resolveRoute(app, component) {
|
|
895
|
+
if (!controller_decorator.isCustomIdRouted(component.type)) {
|
|
896
|
+
throw new TypeError(`${component.type} commands are routed by name, not by customId.`);
|
|
897
|
+
}
|
|
898
|
+
const routes = componentRoutes.buildComponentRoutes(controllersOf(app));
|
|
899
|
+
const matched = componentRoutes.matchComponentRoute(routes, (type)=>type === component.type, component.customId);
|
|
900
|
+
if (!matched) return undefined;
|
|
901
|
+
const { route, params } = matched;
|
|
902
|
+
const method = route.meta.methodName;
|
|
903
|
+
return {
|
|
904
|
+
controller: route.controllerClass,
|
|
905
|
+
method,
|
|
906
|
+
handler: route.controllerClass.prototype[method],
|
|
907
|
+
params
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Finds component patterns that can match the same customId, which MeoCord otherwise only warns
|
|
912
|
+
* about at startup. Patterns are compared within a component type, as dispatch does.
|
|
913
|
+
*
|
|
914
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
915
|
+
* @returns Each conflicting pattern pair, with its component type.
|
|
916
|
+
*
|
|
917
|
+
* @example
|
|
918
|
+
* ```ts
|
|
919
|
+
* expect(findRouteConflicts(App)).toEqual([])
|
|
920
|
+
* ```
|
|
921
|
+
*/ function findRouteConflicts(app) {
|
|
922
|
+
return componentRoutes.findComponentRouteConflicts(componentRoutes.buildComponentRoutes(controllersOf(app)));
|
|
923
|
+
}
|
|
924
|
+
|
|
869
925
|
exports.MeoCordTestingModule = MeoCordTestingModule;
|
|
870
926
|
exports.TestingModule = TestingModule;
|
|
871
927
|
exports.TestingModuleBuilder = TestingModuleBuilder;
|
|
@@ -878,4 +934,6 @@ exports.createMockGuild = createMockGuild;
|
|
|
878
934
|
exports.createMockInteraction = createMockInteraction;
|
|
879
935
|
exports.createMockMessage = createMockMessage;
|
|
880
936
|
exports.createMockUser = createMockUser;
|
|
937
|
+
exports.findRouteConflicts = findRouteConflicts;
|
|
881
938
|
exports.isMockFunction = isMockFunction;
|
|
939
|
+
exports.resolveRoute = resolveRoute;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { findAmbiguousRoutes, getCommandMap } from '../decorator/controller.decorator.js';
|
|
2
|
+
|
|
3
|
+
/** Every customId-pattern route of the given controllers, most specific first, read from metadata alone. */ function buildComponentRoutes(controllerClasses) {
|
|
4
|
+
const routes = [];
|
|
5
|
+
for (const controllerClass of controllerClasses){
|
|
6
|
+
const commandMap = getCommandMap(controllerClass.prototype);
|
|
7
|
+
if (!commandMap) continue;
|
|
8
|
+
for (const [pattern, metaArray] of Object.entries(commandMap)){
|
|
9
|
+
if (!Array.isArray(metaArray)) continue;
|
|
10
|
+
for (const meta of metaArray){
|
|
11
|
+
if (meta.regex) routes.push({
|
|
12
|
+
controllerClass,
|
|
13
|
+
meta,
|
|
14
|
+
pattern
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return routes.sort((a, b)=>(b.meta.specificity ?? 0) - (a.meta.specificity ?? 0));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The route dispatch runs for a customId: the first, in rank order, whose type `acceptsType` allows
|
|
23
|
+
* and whose pattern matches, with the captured params.
|
|
24
|
+
*/ function matchComponentRoute(routes, acceptsType, customId) {
|
|
25
|
+
for (const route of routes){
|
|
26
|
+
if (!acceptsType(route.meta.type)) continue;
|
|
27
|
+
const match = route.meta.regex.exec(customId);
|
|
28
|
+
if (match) return {
|
|
29
|
+
route,
|
|
30
|
+
params: {
|
|
31
|
+
...match.groups
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/** Pattern pairs that can match one customId, compared only within a component type, as dispatch does. */ function findComponentRouteConflicts(routes) {
|
|
38
|
+
const byType = new Map();
|
|
39
|
+
for (const { meta, pattern } of routes)byType.set(meta.type, [
|
|
40
|
+
...byType.get(meta.type) ?? [],
|
|
41
|
+
pattern
|
|
42
|
+
]);
|
|
43
|
+
return [
|
|
44
|
+
...byType
|
|
45
|
+
].flatMap(([type, patterns])=>findAmbiguousRoutes(patterns).map((pair)=>({
|
|
46
|
+
type,
|
|
47
|
+
patterns: pair
|
|
48
|
+
})));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { buildComponentRoutes, findComponentRouteConflicts, matchComponentRoute };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ApplicationCommandType, SlashCommandBuilder, MessageFlagsBitField } from 'discord.js';
|
|
2
2
|
import { Logger } from '../common/logger.js';
|
|
3
3
|
import '../common/theme.js';
|
|
4
|
-
import { getCommandMap,
|
|
4
|
+
import { getCommandMap, PARAM_SEPARATOR, getAutocompleteHandlers, getMessageHandlers, getReactionHandlers } from '../decorator/controller.decorator.js';
|
|
5
5
|
import { sample } from 'lodash-es';
|
|
6
6
|
import { createErrorEmbed } from '../util/embed.util.js';
|
|
7
7
|
import { describeInteraction, hasCustomId, matchesCommandType, resolveCommandPaths, focusedOptionName, resolveOptionParams } from '../util/interaction.util.js';
|
|
8
8
|
import { ReactionHandlerAction, CommandType } from '../enum/controller.enum.js';
|
|
9
9
|
import CliTable3 from 'cli-table3';
|
|
10
|
+
import { buildComponentRoutes, findComponentRouteConflicts, matchComponentRoute } from './component-routes.js';
|
|
10
11
|
|
|
11
12
|
/** What Discord assumes a command body is when it carries no type of its own. */ const DEFAULT_APPLICATION_COMMAND_TYPE = ApplicationCommandType.ChatInput;
|
|
12
13
|
/**
|
|
@@ -182,44 +183,18 @@ class MeoCordApp {
|
|
|
182
183
|
}
|
|
183
184
|
getComponentRoutes() {
|
|
184
185
|
if (this.componentRoutes) return this.componentRoutes;
|
|
185
|
-
const routes =
|
|
186
|
-
for (const controllerClass of this.controllerClasses){
|
|
187
|
-
const commandMap = getCommandMap(this.getInstance(controllerClass));
|
|
188
|
-
if (!commandMap) continue;
|
|
189
|
-
for (const [pattern, metaArray] of Object.entries(commandMap)){
|
|
190
|
-
if (!Array.isArray(metaArray)) continue;
|
|
191
|
-
for (const meta of metaArray){
|
|
192
|
-
if (meta.regex) routes.push({
|
|
193
|
-
controllerClass,
|
|
194
|
-
meta,
|
|
195
|
-
pattern
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
routes.sort((a, b)=>(b.meta.specificity ?? 0) - (a.meta.specificity ?? 0));
|
|
186
|
+
const routes = buildComponentRoutes(this.controllerClasses);
|
|
201
187
|
this.reportAmbiguousRoutes(routes);
|
|
202
188
|
this.componentRoutes = routes;
|
|
203
189
|
return routes;
|
|
204
190
|
}
|
|
205
191
|
/**
|
|
206
|
-
* Warns rather than throws: an app whose patterns overlap boots and works
|
|
207
|
-
*
|
|
192
|
+
* Warns rather than throws: an app whose patterns overlap boots and works, and refusing to start
|
|
193
|
+
* would turn a latent mis-route into an outage.
|
|
208
194
|
*/ reportAmbiguousRoutes(routes) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const byType = new Map();
|
|
213
|
-
for (const { meta, pattern } of routes){
|
|
214
|
-
const patterns = byType.get(meta.type) ?? [];
|
|
215
|
-
patterns.push(pattern);
|
|
216
|
-
byType.set(meta.type, patterns);
|
|
217
|
-
}
|
|
218
|
-
const collisions = [
|
|
219
|
-
...byType.values()
|
|
220
|
-
].flatMap((patterns)=>findAmbiguousRoutes(patterns));
|
|
221
|
-
if (collisions.length === 0) return;
|
|
222
|
-
this.logger.warn(`${collisions.length} pattern pair(s) can match the same customId, so which one runs is decided by ` + `ranking rather than by the ids themselves:\n` + collisions.map(([left, right])=>` "${left}" vs "${right}"`).join('\n') + `\nA parameter stops at "${PARAM_SEPARATOR}", so separating these segments with it makes them distinct.`);
|
|
195
|
+
const conflicts = findComponentRouteConflicts(routes);
|
|
196
|
+
if (conflicts.length === 0) return;
|
|
197
|
+
this.logger.warn(`${conflicts.length} pattern pair(s) can match the same customId, so which one runs is decided by ` + `ranking rather than by the ids themselves:\n` + conflicts.map(({ patterns: [left, right] })=>` "${left}" vs "${right}"`).join('\n') + `\nA parameter stops at "${PARAM_SEPARATOR}", so separating these segments with it makes them distinct.`);
|
|
223
198
|
}
|
|
224
199
|
getAutocompleteRoutes() {
|
|
225
200
|
if (this.autocompleteRoutes) return this.autocompleteRoutes;
|
|
@@ -265,14 +240,12 @@ class MeoCordApp {
|
|
|
265
240
|
// Commands match their registered name exactly and cannot overlap.
|
|
266
241
|
if (hasCustomId(interaction)) {
|
|
267
242
|
const customId = interaction.customId;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
interaction.dynamicParams = match.groups ?? {};
|
|
275
|
-
await this.executeCommand(this.getInstance(controllerClass), meta, interaction, customId);
|
|
243
|
+
// The component type as well as the pattern: a button and a select menu may share a customId shape.
|
|
244
|
+
const matched = matchComponentRoute(this.getComponentRoutes(), (type)=>matchesCommandType(type, interaction), customId);
|
|
245
|
+
if (matched) {
|
|
246
|
+
const { route, params } = matched;
|
|
247
|
+
interaction.dynamicParams = params;
|
|
248
|
+
await this.executeCommand(this.getInstance(route.controllerClass), route.meta, interaction, customId);
|
|
276
249
|
return;
|
|
277
250
|
}
|
|
278
251
|
}
|
package/dist/esm/package.json.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder } from './meocord-testing-module.js';
|
|
2
2
|
export { createChatInputOptions, createMock, createMockChannel, createMockClient, createMockGuild, createMockInteraction, createMockMessage, createMockUser } from './mock-interaction.js';
|
|
3
3
|
export { createMockFn, isMockFunction } from './mock-fn.js';
|
|
4
|
+
export { findRouteConflicts, resolveRoute } from './routing.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MetadataKey } from '../enum/metadata-key.enum.js';
|
|
2
|
+
import { findComponentRouteConflicts, buildComponentRoutes, matchComponentRoute } from '../core/component-routes.js';
|
|
3
|
+
import { isCustomIdRouted } from '../util/interaction.util.js';
|
|
4
|
+
|
|
5
|
+
/** The controllers `@MeoCord({ controllers })` registers on an application class. */ function controllersOf(app) {
|
|
6
|
+
const options = Reflect.getMetadata(MetadataKey.AppOptions, app);
|
|
7
|
+
if (!options) throw new TypeError(`${app.name || 'The given class'} is not decorated with @MeoCord().`);
|
|
8
|
+
return options.controllers ?? [];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolves which handler a component's customId reaches, the way dispatch does: across every
|
|
12
|
+
* controller the application registers, for that component type, most specific pattern first.
|
|
13
|
+
*
|
|
14
|
+
* Reads decorator metadata only, so it runs in a plain unit test with no Discord client, config
|
|
15
|
+
* or container. It checks routing alone: guards are not run, and whether the controller's
|
|
16
|
+
* dependencies are bound is for `MeoCordTestingModule` to test.
|
|
17
|
+
*
|
|
18
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
19
|
+
* @param component - The component type and the customId it carries.
|
|
20
|
+
* @returns The handler that runs, or `undefined` when no route handles the customId.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
|
|
25
|
+
* expect(route?.handler).toBe(ProfileController.prototype.showProfile)
|
|
26
|
+
* expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
|
|
27
|
+
* ```
|
|
28
|
+
*/ function resolveRoute(app, component) {
|
|
29
|
+
if (!isCustomIdRouted(component.type)) {
|
|
30
|
+
throw new TypeError(`${component.type} commands are routed by name, not by customId.`);
|
|
31
|
+
}
|
|
32
|
+
const routes = buildComponentRoutes(controllersOf(app));
|
|
33
|
+
const matched = matchComponentRoute(routes, (type)=>type === component.type, component.customId);
|
|
34
|
+
if (!matched) return undefined;
|
|
35
|
+
const { route, params } = matched;
|
|
36
|
+
const method = route.meta.methodName;
|
|
37
|
+
return {
|
|
38
|
+
controller: route.controllerClass,
|
|
39
|
+
method,
|
|
40
|
+
handler: route.controllerClass.prototype[method],
|
|
41
|
+
params
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Finds component patterns that can match the same customId, which MeoCord otherwise only warns
|
|
46
|
+
* about at startup. Patterns are compared within a component type, as dispatch does.
|
|
47
|
+
*
|
|
48
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
49
|
+
* @returns Each conflicting pattern pair, with its component type.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* expect(findRouteConflicts(App)).toEqual([])
|
|
54
|
+
* ```
|
|
55
|
+
*/ function findRouteConflicts(app) {
|
|
56
|
+
return findComponentRouteConflicts(buildComponentRoutes(controllersOf(app)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { findRouteConflicts, resolveRoute };
|
|
@@ -54,8 +54,8 @@ declare class MeoCordApp {
|
|
|
54
54
|
private componentRoutes?;
|
|
55
55
|
private getComponentRoutes;
|
|
56
56
|
/**
|
|
57
|
-
* Warns rather than throws: an app whose patterns overlap boots and works
|
|
58
|
-
*
|
|
57
|
+
* Warns rather than throws: an app whose patterns overlap boots and works, and refusing to start
|
|
58
|
+
* would turn a latent mis-route into an outage.
|
|
59
59
|
*/
|
|
60
60
|
private reportAmbiguousRoutes;
|
|
61
61
|
/**
|
|
@@ -54,8 +54,8 @@ declare class MeoCordApp {
|
|
|
54
54
|
private componentRoutes?;
|
|
55
55
|
private getComponentRoutes;
|
|
56
56
|
/**
|
|
57
|
-
* Warns rather than throws: an app whose patterns overlap boots and works
|
|
58
|
-
*
|
|
57
|
+
* Warns rather than throws: an app whose patterns overlap boots and works, and refusing to start
|
|
58
|
+
* would turn a latent mis-route into an outage.
|
|
59
59
|
*/
|
|
60
60
|
private reportAmbiguousRoutes;
|
|
61
61
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ServiceIdentifier, Container } from 'inversify';
|
|
2
2
|
import { GuardInterface } from '../interface/index.cjs';
|
|
3
3
|
import { CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User } from 'discord.js';
|
|
4
|
+
import { CommandType } from '../enum/index.cjs';
|
|
4
5
|
import '@rsbuild/core';
|
|
5
|
-
import '../controller.enum-BL6wEQls.cjs';
|
|
6
6
|
|
|
7
7
|
interface ValueProvider<T = any> {
|
|
8
8
|
provide: ServiceIdentifier<T>;
|
|
@@ -268,5 +268,65 @@ interface ChatInputOptions {
|
|
|
268
268
|
*/
|
|
269
269
|
declare function createChatInputOptions<Cached extends CacheType = any>(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver<Cached>>;
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
type ControllerClass = new (...args: any[]) => any;
|
|
272
|
+
|
|
273
|
+
/** The command types routed by customId pattern: buttons, select menus and modals. */
|
|
274
|
+
type ComponentCommandType = Exclude<CommandType, CommandType.SLASH | CommandType.CONTEXT_MENU | CommandType.PRIMARY_ENTRY_POINT>;
|
|
275
|
+
/** The handler a component interaction reaches. */
|
|
276
|
+
interface ResolvedRoute {
|
|
277
|
+
/** The controller class declaring the handler. */
|
|
278
|
+
controller: ControllerClass;
|
|
279
|
+
/** The name of the handler method. */
|
|
280
|
+
method: string;
|
|
281
|
+
/**
|
|
282
|
+
* The handler method itself, for assertions that survive renaming it:
|
|
283
|
+
* `expect(route?.handler).toBe(ProfileController.prototype.showProfile)`.
|
|
284
|
+
*/
|
|
285
|
+
handler: (...args: any[]) => unknown;
|
|
286
|
+
/** The values captured by the pattern's `{name}` segments. */
|
|
287
|
+
params: Record<string, string>;
|
|
288
|
+
}
|
|
289
|
+
/** Two patterns of one component type that can both match a customId. */
|
|
290
|
+
interface RouteConflict {
|
|
291
|
+
type: ComponentCommandType;
|
|
292
|
+
patterns: [string, string];
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Resolves which handler a component's customId reaches, the way dispatch does: across every
|
|
296
|
+
* controller the application registers, for that component type, most specific pattern first.
|
|
297
|
+
*
|
|
298
|
+
* Reads decorator metadata only, so it runs in a plain unit test with no Discord client, config
|
|
299
|
+
* or container. It checks routing alone: guards are not run, and whether the controller's
|
|
300
|
+
* dependencies are bound is for `MeoCordTestingModule` to test.
|
|
301
|
+
*
|
|
302
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
303
|
+
* @param component - The component type and the customId it carries.
|
|
304
|
+
* @returns The handler that runs, or `undefined` when no route handles the customId.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
|
|
309
|
+
* expect(route?.handler).toBe(ProfileController.prototype.showProfile)
|
|
310
|
+
* expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
declare function resolveRoute(app: ControllerClass, component: {
|
|
314
|
+
type: ComponentCommandType;
|
|
315
|
+
customId: string;
|
|
316
|
+
}): ResolvedRoute | undefined;
|
|
317
|
+
/**
|
|
318
|
+
* Finds component patterns that can match the same customId, which MeoCord otherwise only warns
|
|
319
|
+
* about at startup. Patterns are compared within a component type, as dispatch does.
|
|
320
|
+
*
|
|
321
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
322
|
+
* @returns Each conflicting pattern pair, with its component type.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* expect(findRouteConflicts(App)).toEqual([])
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
declare function findRouteConflicts(app: ControllerClass): RouteConflict[];
|
|
330
|
+
|
|
331
|
+
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, findRouteConflicts, isMockFunction, resolveRoute };
|
|
332
|
+
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, RouteConflict, TestingModuleOptions, ValueProvider };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ServiceIdentifier, Container } from 'inversify';
|
|
2
2
|
import { GuardInterface } from '../interface/index.js';
|
|
3
3
|
import { CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User } from 'discord.js';
|
|
4
|
+
import { CommandType } from '../enum/index.js';
|
|
4
5
|
import '@rsbuild/core';
|
|
5
|
-
import '../controller.enum-BL6wEQls.js';
|
|
6
6
|
|
|
7
7
|
interface ValueProvider<T = any> {
|
|
8
8
|
provide: ServiceIdentifier<T>;
|
|
@@ -268,5 +268,65 @@ interface ChatInputOptions {
|
|
|
268
268
|
*/
|
|
269
269
|
declare function createChatInputOptions<Cached extends CacheType = any>(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver<Cached>>;
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
type ControllerClass = new (...args: any[]) => any;
|
|
272
|
+
|
|
273
|
+
/** The command types routed by customId pattern: buttons, select menus and modals. */
|
|
274
|
+
type ComponentCommandType = Exclude<CommandType, CommandType.SLASH | CommandType.CONTEXT_MENU | CommandType.PRIMARY_ENTRY_POINT>;
|
|
275
|
+
/** The handler a component interaction reaches. */
|
|
276
|
+
interface ResolvedRoute {
|
|
277
|
+
/** The controller class declaring the handler. */
|
|
278
|
+
controller: ControllerClass;
|
|
279
|
+
/** The name of the handler method. */
|
|
280
|
+
method: string;
|
|
281
|
+
/**
|
|
282
|
+
* The handler method itself, for assertions that survive renaming it:
|
|
283
|
+
* `expect(route?.handler).toBe(ProfileController.prototype.showProfile)`.
|
|
284
|
+
*/
|
|
285
|
+
handler: (...args: any[]) => unknown;
|
|
286
|
+
/** The values captured by the pattern's `{name}` segments. */
|
|
287
|
+
params: Record<string, string>;
|
|
288
|
+
}
|
|
289
|
+
/** Two patterns of one component type that can both match a customId. */
|
|
290
|
+
interface RouteConflict {
|
|
291
|
+
type: ComponentCommandType;
|
|
292
|
+
patterns: [string, string];
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Resolves which handler a component's customId reaches, the way dispatch does: across every
|
|
296
|
+
* controller the application registers, for that component type, most specific pattern first.
|
|
297
|
+
*
|
|
298
|
+
* Reads decorator metadata only, so it runs in a plain unit test with no Discord client, config
|
|
299
|
+
* or container. It checks routing alone: guards are not run, and whether the controller's
|
|
300
|
+
* dependencies are bound is for `MeoCordTestingModule` to test.
|
|
301
|
+
*
|
|
302
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
303
|
+
* @param component - The component type and the customId it carries.
|
|
304
|
+
* @returns The handler that runs, or `undefined` when no route handles the customId.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
|
|
309
|
+
* expect(route?.handler).toBe(ProfileController.prototype.showProfile)
|
|
310
|
+
* expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
declare function resolveRoute(app: ControllerClass, component: {
|
|
314
|
+
type: ComponentCommandType;
|
|
315
|
+
customId: string;
|
|
316
|
+
}): ResolvedRoute | undefined;
|
|
317
|
+
/**
|
|
318
|
+
* Finds component patterns that can match the same customId, which MeoCord otherwise only warns
|
|
319
|
+
* about at startup. Patterns are compared within a component type, as dispatch does.
|
|
320
|
+
*
|
|
321
|
+
* @param app - The application class decorated with `@MeoCord`.
|
|
322
|
+
* @returns Each conflicting pattern pair, with its component type.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* expect(findRouteConflicts(App)).toEqual([])
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
declare function findRouteConflicts(app: ControllerClass): RouteConflict[];
|
|
330
|
+
|
|
331
|
+
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, findRouteConflicts, isMockFunction, resolveRoute };
|
|
332
|
+
export type { ChatInputOptions, ClassProvider, ComponentCommandType, DeepMocked, Mock, MockInstance, MockProps, MockResult, MockState, MockedFunction, Provider, ResolvedRoute, RouteConflict, TestingModuleOptions, ValueProvider };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meocord",
|
|
3
3
|
"description": "Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.",
|
|
4
|
-
"version": "4.0.0
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.13"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Centralised metadata keys used across the framework's `Reflect` calls.
|
|
5
|
-
*
|
|
6
|
-
* Keeping them here prevents typos, documents the Inversify 8 key rename,
|
|
7
|
-
* and makes key changes a single-file edit.
|
|
8
|
-
*/ var MetadataKey = /*#__PURE__*/ function(MetadataKey) {
|
|
9
|
-
/**
|
|
10
|
-
* Set by Inversify 8's `injectable()` decorator.
|
|
11
|
-
*/ MetadataKey["Injectable"] = "@inversifyjs/core/classIsInjectableFlagReflectKey";
|
|
12
|
-
/**
|
|
13
|
-
* Stores the Inversify `Container` instance on a controller class.
|
|
14
|
-
* Set by `MeoCordFactory.create()`, read by `@UseGuard` at runtime.
|
|
15
|
-
*/ MetadataKey["Container"] = "inversify:container";
|
|
16
|
-
/**
|
|
17
|
-
* Stores the `@MeoCord()` options object on the app class.
|
|
18
|
-
* Read by `MeoCordFactory.create()` to wire up the container.
|
|
19
|
-
*/ MetadataKey["AppOptions"] = "meocord:app-options";
|
|
20
|
-
/**
|
|
21
|
-
* TypeScript compiler-emitted metadata listing constructor parameter types.
|
|
22
|
-
* Requires `"emitDecoratorMetadata": true` in tsconfig.
|
|
23
|
-
*/ MetadataKey["ParamTypes"] = "design:paramtypes";
|
|
24
|
-
/**
|
|
25
|
-
* Stores the guard list applied to a method or class via `@UseGuard`.
|
|
26
|
-
*/ MetadataKey["Guards"] = "guards";
|
|
27
|
-
/**
|
|
28
|
-
* Stores the `CommandType` on a `@CommandBuilder` class.
|
|
29
|
-
*/ MetadataKey["CommandType"] = "commandType";
|
|
30
|
-
return MetadataKey;
|
|
31
|
-
}({});
|
|
32
|
-
|
|
33
|
-
exports.MetadataKey = MetadataKey;
|