meocord 4.1.2 → 4.2.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 +98 -0
- package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-CRn9kWob.cjs} +295 -87
- package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-ChvMqnLd.cjs} +74 -40
- package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-BdAUTdYs.cjs} +1 -1
- package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CQo8mSP8.cjs} +158 -72
- package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-DhDVbT5M.cjs} +12 -3
- package/dist/cjs/common/index.cjs +4 -4
- package/dist/cjs/core/index.cjs +195 -157
- package/dist/cjs/decorator/index.cjs +66 -57
- package/dist/cjs/testing/index.cjs +733 -301
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/README.md.template +3 -1
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
- package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
- package/dist/esm/build/load-config.pre-entry.js +4 -1
- package/dist/esm/common/decorator.js +6 -6
- package/dist/esm/common/deprecation.js +2 -2
- package/dist/esm/common/errors.js +2 -2
- package/dist/esm/common/execution-context.js +1 -1
- package/dist/esm/common/metadata.js +1 -1
- package/dist/esm/common/route.js +10 -1
- package/dist/esm/common/translate-error.js +1 -1
- package/dist/esm/core/command-conflicts.js +34 -34
- package/dist/esm/core/component-routes.js +74 -31
- package/dist/esm/core/dispatcher.js +51 -7
- package/dist/esm/core/filter-runner.js +2 -2
- package/dist/esm/core/handler-pipeline.js +27 -14
- package/dist/esm/core/handler-registry.js +1 -1
- package/dist/esm/core/interceptor-runner.js +2 -2
- package/dist/esm/core/meocord-factory.js +196 -157
- package/dist/esm/core/message-routes.js +4 -3
- package/dist/esm/core/observer-runner.js +4 -3
- package/dist/esm/core/providers.js +38 -21
- package/dist/esm/core/route-specificity.js +4 -4
- package/dist/esm/core/scalar-types.js +15 -3
- package/dist/esm/core/shard-context.js +1 -1
- package/dist/esm/core/startup-roots.js +2 -3
- package/dist/esm/core/theme-runtime.js +2 -2
- package/dist/esm/decorator/app.decorator.js +12 -7
- package/dist/esm/decorator/command-builder.decorator.js +4 -4
- package/dist/esm/decorator/controller-class.decorator.js +9 -4
- package/dist/esm/decorator/controller.decorator.js +110 -67
- package/dist/esm/decorator/cooldown.decorator.js +3 -3
- package/dist/esm/decorator/defer.decorator.js +4 -4
- package/dist/esm/decorator/event.decorator.js +5 -5
- package/dist/esm/decorator/filter.decorator.js +7 -7
- package/dist/esm/decorator/guard.decorator.js +8 -8
- package/dist/esm/decorator/inject.decorator.js +4 -4
- package/dist/esm/decorator/interceptor.decorator.js +7 -7
- package/dist/esm/decorator/observer.decorator.js +4 -4
- package/dist/esm/decorator/service.decorator.js +4 -4
- package/dist/esm/decorator/theme.decorator.js +3 -3
- package/dist/esm/decorator/validation.decorator.js +10 -10
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +1 -1
- package/dist/esm/testing/cooldown-store-suite.js +18 -10
- package/dist/esm/testing/discord-defaults.js +60 -7
- package/dist/esm/testing/index.js +4 -2
- package/dist/esm/testing/inspect-handler.js +20 -3
- package/dist/esm/testing/meocord-testing-module.js +229 -188
- package/dist/esm/testing/mock-fn.js +80 -10
- package/dist/esm/testing/mock-interaction.js +223 -72
- package/dist/esm/testing/response.js +1 -1
- package/dist/esm/testing/routing.js +39 -14
- package/dist/esm/testing/startup-errors.js +26 -0
- package/dist/esm/testing/strict-mocks.js +45 -0
- package/dist/esm/testing/theme.js +1 -1
- package/dist/esm/util/bundle-entry.util.js +2 -1
- package/dist/esm/util/interaction.util.js +1 -1
- package/dist/esm/util/meocord-config-validation.util.js +1 -0
- package/dist/esm/util/metadata-keys.js +1 -0
- package/dist/esm/util/refusal.util.js +148 -8
- package/dist/types/common/index.d.cts +8 -8
- package/dist/types/common/index.d.ts +8 -8
- package/dist/types/core/index.d.cts +8 -6
- package/dist/types/core/index.d.ts +8 -6
- package/dist/types/decorator/index.d.cts +30 -29
- package/dist/types/decorator/index.d.ts +30 -29
- package/dist/types/{index-D7n0FBMm.d.ts → index-CdXsFgjE.d.ts} +62 -15
- package/dist/types/{index-DEP0NYrU.d.cts → index-nn5p7OoX.d.cts} +62 -15
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-C-aMI7Sl.d.cts → route-BZgFoNeB.d.cts} +5 -2
- package/dist/types/{route-C-aMI7Sl.d.ts → route-BZgFoNeB.d.ts} +5 -2
- package/dist/types/testing/index.d.cts +245 -31
- package/dist/types/testing/index.d.ts +245 -31
- package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-BcOJVzR9.d.ts} +1 -1
- package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-UKr3-6Ys.d.cts} +1 -1
- package/dist/types/{translator-J6tIUfdp.d.ts → translator-BViWKtLJ.d.ts} +1 -1
- package/dist/types/{translator-B4mc1lzJ.d.cts → translator-DHMkkCpg.d.cts} +1 -1
- package/package.json +3 -2
|
@@ -34,18 +34,30 @@ const UNIT_MS = lookupTable({
|
|
|
34
34
|
return total + Number(amount) * UNIT_MS[unit];
|
|
35
35
|
}, 0);
|
|
36
36
|
}
|
|
37
|
+
/** The largest snowflake: Discord's IDs are unsigned 64-bit integers. */ const MAX_SNOWFLAKE = 2n ** 64n - 1n;
|
|
38
|
+
/**
|
|
39
|
+
* A Discord ID, kept as its text: 17 to 20 digits, no leading zero, up to the largest 64-bit value. An ID's top 42 bits
|
|
40
|
+
* are milliseconds since 2015-01-01, so every ID made from 2015-01-28 on has 17 digits or more, and none is a safe
|
|
41
|
+
* integer.
|
|
42
|
+
*/ const snowflake = (word)=>/^[1-9]\d{16,19}$/.test(word) && BigInt(word) <= MAX_SNOWFLAKE ? word : undefined;
|
|
43
|
+
/** A UUID in its canonical 8-4-4-4-12 form, in either case, kept as written. */ const uuid = (word)=>/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(word) ? word : undefined;
|
|
37
44
|
/** The words a type such as `on|off` chooses from, or `undefined` for any other type. */ const choicesOf = (type)=>type.includes('|') ? type.split('|') : undefined;
|
|
38
45
|
/** The types a component's customId segment can take: each parsed from the segment alone, with no request. */ const SEGMENT_TYPES = lookupTable({
|
|
39
46
|
string: (segment)=>segment,
|
|
40
47
|
int: (segment)=>number(segment, true),
|
|
41
48
|
number: (segment)=>number(segment, false),
|
|
42
|
-
bool
|
|
49
|
+
bool,
|
|
50
|
+
snowflake,
|
|
51
|
+
uuid
|
|
43
52
|
});
|
|
44
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Whether `type` can type a customId segment: `string`, `int`, `number`, `bool`, `snowflake`, `uuid`, or words to
|
|
55
|
+
* choose from.
|
|
56
|
+
*/ const isSegmentType = (type)=>type in SEGMENT_TYPES || choicesOf(type) !== undefined;
|
|
45
57
|
/** A customId segment as its type's value, words to choose from compared as written: `undefined` when it is not one. */ function parseSegment(type, segment) {
|
|
46
58
|
const choices = choicesOf(type);
|
|
47
59
|
if (choices) return choices.includes(segment) ? segment : undefined;
|
|
48
60
|
return SEGMENT_TYPES[type](segment);
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
export { bool, choicesOf, duration, isSegmentType, lookupTable, number, parseSegment };
|
|
63
|
+
export { bool, choicesOf, duration, isSegmentType, lookupTable, number, parseSegment, snowflake, uuid };
|
|
@@ -95,7 +95,7 @@ async function withTimeout(promise, ms, what) {
|
|
|
95
95
|
*
|
|
96
96
|
* @group Controllers
|
|
97
97
|
* @see {@link ShardCallResult}
|
|
98
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
98
|
+
* @see {@link https://meocord.dev/docs/4.2/sharding | Sharding}
|
|
99
99
|
*/ class ShardContext {
|
|
100
100
|
/** The ids of the shards this process runs. With process sharding, one id; otherwise every shard, once ready. */ get ids() {
|
|
101
101
|
if (this.client?.shard) return [
|
|
@@ -4,7 +4,7 @@ import { classDecorators, handlerStageClasses } from './handler-pipeline.js';
|
|
|
4
4
|
import { reachableClasses, assertTypedParameters } from './providers.js';
|
|
5
5
|
import { themeResolverClass } from './theme-resolvers.js';
|
|
6
6
|
import { META } from '../util/metadata-keys.js';
|
|
7
|
-
import {
|
|
7
|
+
import { startupError } from '../util/refusal.util.js';
|
|
8
8
|
|
|
9
9
|
/** The guards `@UseGuard` puts on a class's methods, handlers or not, which a direct call runs too. */ function methodGuards(cls) {
|
|
10
10
|
const guards = [];
|
|
@@ -75,8 +75,7 @@ import { refuse } from '../util/refusal.util.js';
|
|
|
75
75
|
/** Runs the startup checks over every class an app runs, so a class that cannot be made is refused by name now. */ function assertStartupClasses({ classes, decorators }, { translator }) {
|
|
76
76
|
assertTypedParameters(classes, decorators);
|
|
77
77
|
if (translator) return;
|
|
78
|
-
const needsOne
|
|
79
|
-
if (needsOne) throw refuse(missingTranslatorError(needsOne));
|
|
78
|
+
for (const needsOne of classes.filter((cls)=>injectedTokens(cls).includes(Translator)))startupError(missingTranslatorError(needsOne));
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
export { assertStartupClasses, lateGuardCheck, startupClasses };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { stageClasses, sourcePrototype } from './guard-runner.js';
|
|
3
3
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
4
|
-
import { resolverCaches, ThemeCache, attachThemeCache
|
|
5
|
-
import { releaseAmbientTheme, claimAmbientTheme,
|
|
4
|
+
import { lookupLayers, resolverCaches, ThemeCache, attachThemeCache } from './theme-resolvers.js';
|
|
5
|
+
import { releaseAmbientTheme, claimAmbientTheme, mergeTheme, defaultTheme, themeLayersVersion, ambientThemeVersion, ownsAmbientTheme, hasAmbientTheme, setThemeOutsideCalls, useTheme } from './theme-scope.js';
|
|
6
6
|
import { META } from '../util/metadata-keys.js';
|
|
7
7
|
|
|
8
8
|
/** Classes with a `@UseTheme`, on the class or one of its methods. */ const THEMED_CLASSES = new WeakSet();
|
|
@@ -7,7 +7,7 @@ import { BUILT_IN_TYPES } from '../core/message-params.js';
|
|
|
7
7
|
import { assertObservers } from '../core/observer-runner.js';
|
|
8
8
|
import { assertValidTheme, themeForProblem } from '../core/theme-validation.js';
|
|
9
9
|
import { copyLayer } from '../core/theme-scope.js';
|
|
10
|
-
import { deprecatedOnMethod, refuse } from '../util/refusal.util.js';
|
|
10
|
+
import { declaring, deprecatedOnMethod, refuse } from '../util/refusal.util.js';
|
|
11
11
|
import { timeoutProblem } from '../util/timer-limit.util.js';
|
|
12
12
|
import { META } from '../util/metadata-keys.js';
|
|
13
13
|
|
|
@@ -18,14 +18,19 @@ import { META } from '../util/metadata-keys.js';
|
|
|
18
18
|
if (prefix !== undefined && !isText(prefix) && typeof prefix !== 'function' && !(Array.isArray(prefix) && prefix.every(isText))) {
|
|
19
19
|
throw refuse(new TypeError(`${appName}: @MeoCord({ messages: { prefix } }) takes a string, a list of strings, or a function of the message returning them.`));
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const flags = {
|
|
22
22
|
caseSensitive,
|
|
23
23
|
replyEmoji: messages.replyEmoji,
|
|
24
24
|
dmOnError: messages.dmOnError,
|
|
25
|
-
dmOnCooldown: messages.dmOnCooldown
|
|
26
|
-
|
|
25
|
+
dmOnCooldown: messages.dmOnCooldown,
|
|
26
|
+
slowHandlerWarning: messages.slowHandlerWarning
|
|
27
|
+
};
|
|
28
|
+
for (const [name, value] of Object.entries(flags)){
|
|
27
29
|
if (value !== undefined && typeof value !== 'boolean') throw refuse(new TypeError(`${appName}: @MeoCord({ messages: { ${name} } }) takes true or false.`));
|
|
28
30
|
}
|
|
31
|
+
if (messages.handlers !== undefined && messages.handlers !== 'sequential' && messages.handlers !== 'concurrent') {
|
|
32
|
+
throw refuse(new TypeError(`${appName}: @MeoCord({ messages: { handlers } }) takes 'sequential' or 'concurrent'.`));
|
|
33
|
+
}
|
|
29
34
|
if (mention !== undefined && typeof mention !== 'boolean' && mention !== 'only') {
|
|
30
35
|
throw refuse(new TypeError(`${appName}: @MeoCord({ messages: { mention } }) takes true, false or 'only'.`));
|
|
31
36
|
}
|
|
@@ -87,9 +92,9 @@ import { META } from '../util/metadata-keys.js';
|
|
|
87
92
|
* @see {@link MeoCordOptions}
|
|
88
93
|
* @see {@link MeoCordFactory}
|
|
89
94
|
* @see {@link MeoCordConfig}
|
|
90
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
95
|
+
* @see {@link https://meocord.dev/docs/4.2/configuration | Configuration}
|
|
91
96
|
*/ function MeoCord(options) {
|
|
92
|
-
return (target, propertyKey)=>{
|
|
97
|
+
return declaring((target, propertyKey)=>{
|
|
93
98
|
if (deprecatedOnMethod('@MeoCord', target, propertyKey)) return;
|
|
94
99
|
assertStageEntries('@MeoCord({ guards })', 'guard', target.name, options.guards ?? []);
|
|
95
100
|
assertStageEntries('@MeoCord({ interceptors })', 'interceptor', target.name, options.interceptors ?? []);
|
|
@@ -111,7 +116,7 @@ import { META } from '../util/metadata-keys.js';
|
|
|
111
116
|
...options,
|
|
112
117
|
theme
|
|
113
118
|
}, target);
|
|
114
|
-
};
|
|
119
|
+
});
|
|
115
120
|
}
|
|
116
121
|
/** Refuses theme resolvers and cache options the runtime cannot follow, where the app is declared. */ function assertThemeFor(appName, { themeFor, themeCache, themeForTimeoutMs }) {
|
|
117
122
|
const problem = themeFor === undefined ? undefined : themeForProblem(themeFor, `${appName}: @MeoCord({ themeFor })`, 'or a class implementing ThemeResolver.');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
3
3
|
import { META } from '../util/metadata-keys.js';
|
|
4
|
-
import { deprecatedOnMethod } from '../util/refusal.util.js';
|
|
4
|
+
import { declaring, deprecatedOnMethod } from '../util/refusal.util.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Marks a class as a command's builder, which describes the command MeoCord registers with Discord.
|
|
@@ -30,9 +30,9 @@ import { deprecatedOnMethod } from '../util/refusal.util.js';
|
|
|
30
30
|
* @group Decorators
|
|
31
31
|
* @category Handlers
|
|
32
32
|
* @see {@link Command}
|
|
33
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
33
|
+
* @see {@link https://meocord.dev/docs/4.2/slash-commands | Slash commands}
|
|
34
34
|
*/ function CommandBuilder(commandType, options = {}) {
|
|
35
|
-
return function(target, propertyKey) {
|
|
35
|
+
return declaring(function(target, propertyKey) {
|
|
36
36
|
if (deprecatedOnMethod('@CommandBuilder', target, propertyKey)) return;
|
|
37
37
|
makeInjectable(target);
|
|
38
38
|
// Define the command type metadata for the target class
|
|
@@ -40,7 +40,7 @@ import { deprecatedOnMethod } from '../util/refusal.util.js';
|
|
|
40
40
|
if (options.guilds) Reflect.defineMetadata(META.builderGuilds, [
|
|
41
41
|
...options.guilds
|
|
42
42
|
], target);
|
|
43
|
-
};
|
|
43
|
+
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export { CommandBuilder };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { dropInheritedRoutes } from './controller.decorator.js';
|
|
1
2
|
import { guardOwnHandlersWithBaseGuards } from './guard.decorator.js';
|
|
2
3
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
3
4
|
import { META } from '../util/metadata-keys.js';
|
|
4
|
-
import { deprecatedOnMethod } from '../util/refusal.util.js';
|
|
5
|
+
import { declaring, deprecatedOnMethod } from '../util/refusal.util.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Marks a class as a controller, whose methods handle commands, components, messages, reactions or events.
|
|
@@ -35,14 +36,18 @@ import { deprecatedOnMethod } from '../util/refusal.util.js';
|
|
|
35
36
|
* @category Controllers
|
|
36
37
|
* @see {@link Command}
|
|
37
38
|
* @see {@link ControllerOptions}
|
|
38
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
39
|
+
* @see {@link https://meocord.dev/docs/4.2/how-a-call-runs | How a call runs}
|
|
39
40
|
*/ function Controller(options = {}) {
|
|
40
|
-
return function(target, propertyKey) {
|
|
41
|
+
return declaring(function(target, propertyKey) {
|
|
41
42
|
if (deprecatedOnMethod('@Controller', target, propertyKey)) return;
|
|
42
43
|
makeInjectable(target);
|
|
43
44
|
if (options.inheritStages === false) Reflect.defineMetadata(META.inheritStages, false, target);
|
|
45
|
+
if (options.inheritedRoutes === 'replace') {
|
|
46
|
+
dropInheritedRoutes(target.prototype);
|
|
47
|
+
Reflect.defineMetadata(META.replacesInheritedRoutes, true, target.prototype);
|
|
48
|
+
}
|
|
44
49
|
guardOwnHandlersWithBaseGuards(target);
|
|
45
|
-
};
|
|
50
|
+
});
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export { Controller };
|
|
@@ -6,8 +6,8 @@ import { matchesCommandType, interactionClassName, isCustomIdRouted } from '../u
|
|
|
6
6
|
import { warnDeprecatedBehaviour } from '../common/deprecation.js';
|
|
7
7
|
import { Logger } from '../common/logger.js';
|
|
8
8
|
import { routeSpecificity } from '../core/route-specificity.js';
|
|
9
|
-
import { isSegmentType, choicesOf,
|
|
10
|
-
import {
|
|
9
|
+
import { lookupTable, isSegmentType, choicesOf, parseSegment } from '../core/scalar-types.js';
|
|
10
|
+
import { declaring, refuse, refuseOnClass } from '../util/refusal.util.js';
|
|
11
11
|
import { withArticle, describeValue } from '../util/value.util.js';
|
|
12
12
|
import { META } from '../util/metadata-keys.js';
|
|
13
13
|
|
|
@@ -69,6 +69,42 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
69
69
|
else handlers[index] = entry;
|
|
70
70
|
Reflect.defineMetadata(key, handlers, target);
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Leaves each method the class's own decorators declare only those routes, of every kind, dropping the ones it inherits
|
|
74
|
+
* for it: what `@Controller({ inheritedRoutes: 'replace' })` does once the class's method decorators have run.
|
|
75
|
+
*/ function dropInheritedRoutes(prototype) {
|
|
76
|
+
const redeclared = new Set(getDeclaredRoutes(prototype).map((route)=>route.method));
|
|
77
|
+
if (redeclared.size === 0) return;
|
|
78
|
+
const base = Object.getPrototypeOf(prototype);
|
|
79
|
+
// An entry the class takes from its base, for a method it re-declares
|
|
80
|
+
const inheritedFor = (inherited, method)=>(entry)=>inherited.includes(entry) && redeclared.has(method(entry));
|
|
81
|
+
const commands = ownCommandMap(prototype);
|
|
82
|
+
const baseCommands = Reflect.getMetadata(META.commands, base) ?? {};
|
|
83
|
+
for (const [name, metas] of Object.entries(commands)){
|
|
84
|
+
const kept = metas.filter((meta)=>!inheritedFor(baseCommands[name] ?? [], (entry)=>entry.methodName)(meta));
|
|
85
|
+
if (kept.length > 0) commands[name] = kept;
|
|
86
|
+
else delete commands[name];
|
|
87
|
+
}
|
|
88
|
+
Reflect.defineMetadata(META.commands, commands, prototype);
|
|
89
|
+
const lists = [
|
|
90
|
+
[
|
|
91
|
+
META.messageHandlers,
|
|
92
|
+
(entry)=>entry.method
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
META.reactionHandlers,
|
|
96
|
+
(entry)=>entry.method
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
META.autocompleteHandlers,
|
|
100
|
+
(entry)=>entry.methodName
|
|
101
|
+
]
|
|
102
|
+
];
|
|
103
|
+
for (const [key, method] of lists){
|
|
104
|
+
const inherited = Reflect.getMetadata(key, base) ?? [];
|
|
105
|
+
Reflect.defineMetadata(key, ownHandlerList(key, prototype).filter((entry)=>!inheritedFor(inherited, method)(entry)), prototype);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
72
108
|
/** The class's own command map, started from a copy of the inherited one, for the same reason. */ function ownCommandMap(target) {
|
|
73
109
|
const own = Reflect.getOwnMetadata(META.commands, target);
|
|
74
110
|
if (own) return own;
|
|
@@ -81,7 +117,7 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
81
117
|
]));
|
|
82
118
|
}
|
|
83
119
|
function MessageHandler(pattern, options = {}) {
|
|
84
|
-
return function(target, propertyKey) {
|
|
120
|
+
return declaring(function(target, propertyKey) {
|
|
85
121
|
refuseOnClass('@MessageHandler', target, propertyKey);
|
|
86
122
|
// An empty pattern means every message, as no pattern does
|
|
87
123
|
if (pattern === '') {
|
|
@@ -95,7 +131,7 @@ function MessageHandler(pattern, options = {}) {
|
|
|
95
131
|
};
|
|
96
132
|
addOwnHandler(META.messageHandlers, target, declared, (other)=>other.method === method && other.pattern === declared.pattern);
|
|
97
133
|
declareRoute(target, messageRoute(method, declared.pattern));
|
|
98
|
-
};
|
|
134
|
+
});
|
|
99
135
|
}
|
|
100
136
|
function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
101
137
|
const [emoji, own] = typeof emojiOrSettings === 'object' ? [
|
|
@@ -105,7 +141,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
|
105
141
|
emojiOrSettings,
|
|
106
142
|
settings
|
|
107
143
|
];
|
|
108
|
-
return function(target, propertyKey) {
|
|
144
|
+
return declaring(function(target, propertyKey) {
|
|
109
145
|
refuseOnClass('@ReactionHandler', target, propertyKey);
|
|
110
146
|
const method = propertyKey.toString();
|
|
111
147
|
addOwnHandler(META.reactionHandlers, target, {
|
|
@@ -114,7 +150,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
|
114
150
|
settings: own
|
|
115
151
|
}, (other)=>other.method === method && other.emoji === emoji);
|
|
116
152
|
declareRoute(target, reactionRoute(method, emoji));
|
|
117
|
-
};
|
|
153
|
+
});
|
|
118
154
|
}
|
|
119
155
|
/** A custom emoji as Discord writes it in a message: `<:name:id>`, or `<a:name:id>` for an animated one. */ const CUSTOM_EMOJI = /^<a?:\w+:(\d+)>$/;
|
|
120
156
|
/**
|
|
@@ -146,7 +182,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
146
182
|
/** The character a parameter will not cross, so one pattern segment maps to one value. */ const PARAM_SEPARATOR = '/';
|
|
147
183
|
/** Escapes a literal stretch of a pattern so only placeholders stay meaningful. */ const escapeLiteral = (literal)=>literal.replace(/[/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
148
184
|
/**
|
|
149
|
-
* Compiles a pattern into a regex, its parameter names, their types and its specificity. A `{name}` takes a
|
|
185
|
+
* Compiles a pattern into a regex, its parameter names, their types and its 4.1 specificity. A `{name}` takes a
|
|
150
186
|
* whole `/`-separated segment, so a uuid is captured whole and `profile/{uuid}` never overlaps
|
|
151
187
|
* `profile/{uuid}/{id}`; a param that shares a segment with literal text is refused.
|
|
152
188
|
*/ function createRegexFromPattern(pattern) {
|
|
@@ -186,14 +222,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
186
222
|
literalLength += trailing.length;
|
|
187
223
|
regexPattern += escapeLiteral(trailing);
|
|
188
224
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
189
|
-
//
|
|
190
|
-
// more exactly than one leaving it to a parameter. Fewer parameters breaks a tie
|
|
191
|
-
// between equal-length patterns, then narrower typed params; the order the controllers
|
|
192
|
-
// and handlers are listed in settles what is left, and the startup warning names the pair.
|
|
225
|
+
// What 4.1 ranked by, kept for code that reads CommandMeta.specificity; routing ranks segment by segment
|
|
193
226
|
const specificity = routeSpecificity({
|
|
194
227
|
literals: literalLength,
|
|
195
228
|
params: params.length,
|
|
196
|
-
typed: Object.values(types).reduce((sum, type)=>sum +
|
|
229
|
+
typed: Object.values(types).reduce((sum, type)=>sum + (choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1), 0)
|
|
197
230
|
});
|
|
198
231
|
return {
|
|
199
232
|
regex,
|
|
@@ -202,9 +235,8 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
202
235
|
specificity
|
|
203
236
|
};
|
|
204
237
|
}
|
|
205
|
-
/** How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, int, number. */ const narrowness = (type)=>choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1;
|
|
206
238
|
/** Why a `{name:type}` cannot type a customId segment, which holds text the bot wrote, with no message to read. */ function segmentTypeProblem(param, type) {
|
|
207
|
-
const kinds = 'string, int, number, bool, or words to choose from such as {mode:on|off}';
|
|
239
|
+
const kinds = 'string, int, number, bool, snowflake, uuid, or words to choose from such as {mode:on|off}';
|
|
208
240
|
if ([
|
|
209
241
|
'member',
|
|
210
242
|
'user',
|
|
@@ -228,10 +260,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
228
260
|
* the keys the handler's params require are checked against the pattern or route, a typed segment's value against
|
|
229
261
|
* its type, and a select menu's choices, such as `values: string[]`, against what discord.js gives; a key a pipe
|
|
230
262
|
* produces is declared `Piped<T>`, and left to the pipe. Two component handlers of one type whose patterns match
|
|
231
|
-
* exactly the same ids stop the bot at startup
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
263
|
+
* exactly the same ids stop the bot at startup. Patterns that both match an id rank segment by segment, left to
|
|
264
|
+
* right: at the first segment one spells out as literal text and the other leaves to a param, the literal one runs,
|
|
265
|
+
* so `profile/me/{section}` takes `profile/me/edit` from `profile/{userId}/edit`; between patterns that leaves tied,
|
|
266
|
+
* the narrower type at the first param where they differ. Two still tied are warned about as the bot starts, and the
|
|
267
|
+
* one listed first runs. A context menu handler receives the kind its builder's `setType()` names, and one
|
|
235
268
|
* declaring the other kind fails to compile; when the compiler cannot tell the kind, `@Command` checks the parameter
|
|
236
269
|
* type it emits as it applies. A subclass that re-declares an inherited handler on the same name or pattern takes its
|
|
237
270
|
* own builder and options; on another it still answers the inherited one too, which the bot warns about as it
|
|
@@ -264,11 +297,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
264
297
|
* @category Handlers
|
|
265
298
|
* @see {@link CommandBuilder}
|
|
266
299
|
* @see {@link route}
|
|
267
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
268
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
300
|
+
* @see {@link https://meocord.dev/docs/4.2/slash-commands | Slash commands}
|
|
301
|
+
* @see {@link https://meocord.dev/docs/4.2/components | Buttons, selects and modals}
|
|
269
302
|
*/ function Command(name, builderOrType) {
|
|
270
303
|
const commandName = typeof name === 'string' ? name : name.pattern;
|
|
271
|
-
return function(target, propertyKey, _descriptor) {
|
|
304
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
272
305
|
var _commands, _commandName;
|
|
273
306
|
refuseOnClass('@Command', target, propertyKey);
|
|
274
307
|
const originalMethod = _descriptor.value;
|
|
@@ -349,10 +382,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
349
382
|
cause: error
|
|
350
383
|
}));
|
|
351
384
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
specificity = patternSpecificity;
|
|
385
|
+
regex = pattern.regex;
|
|
386
|
+
dynamicParams = pattern.params;
|
|
387
|
+
specificity = pattern.specificity;
|
|
356
388
|
}
|
|
357
389
|
const declared = {
|
|
358
390
|
methodName: propertyKey,
|
|
@@ -376,7 +408,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
376
408
|
else metas[replaced] = declared;
|
|
377
409
|
Reflect.defineMetadata(META.commands, commands, target);
|
|
378
410
|
declareRoute(target, commandRoute(propertyKey, commandType, commandName));
|
|
379
|
-
};
|
|
411
|
+
});
|
|
380
412
|
}
|
|
381
413
|
/** The interaction class Discord sends for each context menu kind. */ const CONTEXT_MENU_INTERACTIONS = new Map([
|
|
382
414
|
[
|
|
@@ -448,9 +480,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
448
480
|
* @group Decorators
|
|
449
481
|
* @category Handlers
|
|
450
482
|
* @see {@link Command}
|
|
451
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
483
|
+
* @see {@link https://meocord.dev/docs/4.2/autocomplete | Autocomplete}
|
|
452
484
|
*/ function Autocomplete(commandPath, optionName) {
|
|
453
|
-
return function(target, propertyKey, _descriptor) {
|
|
485
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
454
486
|
refuseOnClass('@Autocomplete', target, propertyKey);
|
|
455
487
|
const methodName = propertyKey.toString();
|
|
456
488
|
addOwnHandler(META.autocompleteHandlers, target, {
|
|
@@ -459,7 +491,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
459
491
|
methodName
|
|
460
492
|
}, (other)=>other.methodName === methodName && other.commandPath === commandPath && other.optionName === optionName);
|
|
461
493
|
declareRoute(target, autocompleteRoute(methodName, commandPath, optionName));
|
|
462
|
-
};
|
|
494
|
+
});
|
|
463
495
|
}
|
|
464
496
|
/**
|
|
465
497
|
* Returns a controller's autocomplete handlers, option-specific ones first.
|
|
@@ -481,52 +513,63 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
481
513
|
const match = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`).exec(segment);
|
|
482
514
|
return match ? typeKey(match[2]) : undefined;
|
|
483
515
|
}
|
|
484
|
-
/**
|
|
516
|
+
/**
|
|
517
|
+
* How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, uuid,
|
|
518
|
+
* snowflake, int, number, and text last. A snowflake is narrower than a number, which takes all its values.
|
|
519
|
+
*/ const NARROWNESS = lookupTable({
|
|
520
|
+
bool: 5,
|
|
521
|
+
uuid: 4,
|
|
522
|
+
snowflake: 3,
|
|
523
|
+
int: 2,
|
|
524
|
+
number: 1,
|
|
525
|
+
'': 0
|
|
526
|
+
});
|
|
527
|
+
const narrowness = (type)=>choicesOf(type) ? 6 : NARROWNESS[type];
|
|
528
|
+
/**
|
|
529
|
+
* How narrow a pattern segment that is a param, `{name}` or `{name:type}`, is: from `0` for text to `4` for words to
|
|
530
|
+
* choose from. `undefined` for a segment of literal text.
|
|
531
|
+
*/ function paramNarrowness(segment) {
|
|
532
|
+
const type = segmentParamType(segment);
|
|
533
|
+
return type === undefined ? undefined : narrowness(type);
|
|
534
|
+
}
|
|
535
|
+
/** A value of each scalar segment type: an int, a bool, a snowflake and a uuid; text takes any. */ const SAMPLE_VALUES = [
|
|
536
|
+
'1',
|
|
537
|
+
'true',
|
|
538
|
+
'10000000000000000',
|
|
539
|
+
'00000000-0000-0000-0000-000000000000'
|
|
540
|
+
];
|
|
541
|
+
/** A value both readings of a segment take: a literal, text, or a typed param; `undefined` when they share none. */ function sharedValue(left, right) {
|
|
485
542
|
const [a, b] = [
|
|
486
543
|
segmentParamType(left),
|
|
487
544
|
segmentParamType(right)
|
|
488
545
|
];
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
546
|
+
const takes = (type, segment, value)=>type === undefined ? segment === value : type === '' || parseSegment(type, value) !== undefined;
|
|
547
|
+
// Every type takes one of these when it takes anything another reading takes: a literal, a listed word, or a value
|
|
548
|
+
// of each scalar type
|
|
549
|
+
const candidates = [
|
|
550
|
+
...a === undefined ? [
|
|
493
551
|
left
|
|
494
|
-
] : [
|
|
495
|
-
|
|
552
|
+
] : [],
|
|
553
|
+
...b === undefined ? [
|
|
496
554
|
right
|
|
497
|
-
]
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// Words to choose from overlap another type when one of them is a value of it
|
|
502
|
-
const [wordsA, wordsB] = [
|
|
503
|
-
choicesOf(a),
|
|
504
|
-
choicesOf(b)
|
|
555
|
+
] : [],
|
|
556
|
+
...choicesOf(a ?? '') ?? [],
|
|
557
|
+
...choicesOf(b ?? '') ?? [],
|
|
558
|
+
...SAMPLE_VALUES
|
|
505
559
|
];
|
|
506
|
-
|
|
507
|
-
if (wordsB) return wordsB.some((word)=>parseSegment(a, word) !== undefined);
|
|
508
|
-
// Of the scalar types, only a whole number and a number share values
|
|
509
|
-
return a === 'int' && b === 'number' || a === 'number' && b === 'int';
|
|
560
|
+
return candidates.find((value)=>takes(a, left, value) && takes(b, right, value));
|
|
510
561
|
}
|
|
511
562
|
/**
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*/ function
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const disjoint = left.some((segment, index)=>!segmentsOverlap(segment, right[index]));
|
|
523
|
-
if (!disjoint) collisions.push([
|
|
524
|
-
patterns[i],
|
|
525
|
-
patterns[j]
|
|
526
|
-
]);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return collisions;
|
|
563
|
+
* A customId both patterns match, such as `ticket/1/close` for `ticket/{id}/close` and `ticket/{n:int}/close`;
|
|
564
|
+
* `undefined` when no id matches both.
|
|
565
|
+
*/ function sharedCustomId(left, right) {
|
|
566
|
+
const [a, b] = [
|
|
567
|
+
left.split(PARAM_SEPARATOR),
|
|
568
|
+
right.split(PARAM_SEPARATOR)
|
|
569
|
+
];
|
|
570
|
+
if (a.length !== b.length) return undefined;
|
|
571
|
+
const values = a.map((segment, index)=>sharedValue(segment, b[index]));
|
|
572
|
+
return values.every((value)=>value !== undefined) ? values.join(PARAM_SEPARATOR) : undefined;
|
|
530
573
|
}
|
|
531
574
|
|
|
532
|
-
export { Autocomplete, Command, MessageHandler, PARAM_SEPARATOR, ReactionHandler, createRegexFromPattern,
|
|
575
|
+
export { Autocomplete, Command, MessageHandler, PARAM_SEPARATOR, ReactionHandler, createRegexFromPattern, dropInheritedRoutes, getAutocompleteHandlers, getCommandMap, getDeclaredRoutes, getHandlerRoutes, getMessageHandlers, getReactionHandlers, matchesEmoji, ownHandlerList, paramNarrowness, patternShape, sharedCustomId };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { refuse, decoratedName } from '../util/refusal.util.js';
|
|
2
|
+
import { declaring, refuse, decoratedName } from '../util/refusal.util.js';
|
|
3
3
|
import { META } from '../util/metadata-keys.js';
|
|
4
4
|
|
|
5
5
|
function Cooldown(options) {
|
|
@@ -10,7 +10,7 @@ function Cooldown(options) {
|
|
|
10
10
|
per,
|
|
11
11
|
windowMs: Math.round(seconds * 1000)
|
|
12
12
|
};
|
|
13
|
-
return function(target, propertyKey) {
|
|
13
|
+
return declaring(function(target, propertyKey) {
|
|
14
14
|
// Checked where it applies, so the refusal names the handler or controller
|
|
15
15
|
const problem = cooldownProblem(seconds, uses, per, by);
|
|
16
16
|
if (problem) throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${problem}`));
|
|
@@ -28,7 +28,7 @@ function Cooldown(options) {
|
|
|
28
28
|
...existing
|
|
29
29
|
], target, propertyKey);
|
|
30
30
|
}
|
|
31
|
-
};
|
|
31
|
+
});
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* The longest window, in seconds: half the furthest time a `Date` holds, 8.64e15 ms, so the moment a wait ends, now
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { nonInteractionHandler, deferMisuseError } from '../core/defer.js';
|
|
3
|
-
import { refuseOnClass, refuse } from '../util/refusal.util.js';
|
|
3
|
+
import { declaring, refuseOnClass, refuse } from '../util/refusal.util.js';
|
|
4
4
|
import { META } from '../util/metadata-keys.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -37,9 +37,9 @@ import { META } from '../util/metadata-keys.js';
|
|
|
37
37
|
* @category Pipeline stages
|
|
38
38
|
* @see {@link DeferOptions}
|
|
39
39
|
* @see {@link respond}
|
|
40
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
40
|
+
* @see {@link https://meocord.dev/docs/4.2/defer | Deferring}
|
|
41
41
|
*/ function Defer(options = {}) {
|
|
42
|
-
return (target, propertyKey)=>{
|
|
42
|
+
return declaring((target, propertyKey)=>{
|
|
43
43
|
refuseOnClass('@Defer', target, propertyKey);
|
|
44
44
|
const methodName = String(propertyKey);
|
|
45
45
|
const kind = nonInteractionHandler(target, methodName);
|
|
@@ -47,7 +47,7 @@ import { META } from '../util/metadata-keys.js';
|
|
|
47
47
|
Reflect.defineMetadata(META.deferOptions, {
|
|
48
48
|
...options
|
|
49
49
|
}, target, methodName);
|
|
50
|
-
};
|
|
50
|
+
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export { Defer };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { ownHandlerList } from './controller.decorator.js';
|
|
3
3
|
import { META } from '../util/metadata-keys.js';
|
|
4
|
-
import { refuseOnClass } from '../util/refusal.util.js';
|
|
4
|
+
import { declaring, refuseOnClass } from '../util/refusal.util.js';
|
|
5
5
|
|
|
6
6
|
function eventDecorator(event, once) {
|
|
7
7
|
// Generic over the method, so it is checked against the event's arguments rather than required to match exactly
|
|
8
|
-
return function(target, propertyKey, _descriptor) {
|
|
8
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
9
9
|
refuseOnClass(once ? '@Once' : '@On', target, propertyKey);
|
|
10
10
|
const handlers = ownHandlerList(META.eventHandlers, target);
|
|
11
11
|
handlers.push({
|
|
@@ -14,7 +14,7 @@ function eventDecorator(event, once) {
|
|
|
14
14
|
once
|
|
15
15
|
});
|
|
16
16
|
Reflect.defineMetadata(META.eventHandlers, handlers, target);
|
|
17
|
-
};
|
|
17
|
+
});
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Handles a discord.js client event every time it is emitted, on a controller or a service.
|
|
@@ -42,7 +42,7 @@ function eventDecorator(event, once) {
|
|
|
42
42
|
* @group Decorators
|
|
43
43
|
* @category Handlers
|
|
44
44
|
* @see {@link Once}
|
|
45
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
45
|
+
* @see {@link https://meocord.dev/docs/4.2/gateway-events | Gateway events}
|
|
46
46
|
*/ function On(event) {
|
|
47
47
|
return eventDecorator(event, false);
|
|
48
48
|
}
|
|
@@ -69,7 +69,7 @@ function eventDecorator(event, once) {
|
|
|
69
69
|
* @group Decorators
|
|
70
70
|
* @category Handlers
|
|
71
71
|
* @see {@link On}
|
|
72
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
72
|
+
* @see {@link https://meocord.dev/docs/4.2/gateway-events | Gateway events}
|
|
73
73
|
*/ function Once(event) {
|
|
74
74
|
return eventDecorator(event, true);
|
|
75
75
|
}
|
|
@@ -5,7 +5,7 @@ import { Logger } from '../common/logger.js';
|
|
|
5
5
|
import { warnDeprecatedBehaviour } from '../common/deprecation.js';
|
|
6
6
|
import { isConstructor, describeValue } from '../util/value.util.js';
|
|
7
7
|
import { META } from '../util/metadata-keys.js';
|
|
8
|
-
import { refuseOnMethod } from '../util/refusal.util.js';
|
|
8
|
+
import { declaring, refuseOnMethod } from '../util/refusal.util.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Marks a class as an exception filter for the given error types.
|
|
@@ -34,9 +34,9 @@ import { refuseOnMethod } from '../util/refusal.util.js';
|
|
|
34
34
|
* @group Decorators
|
|
35
35
|
* @category Pipeline stages
|
|
36
36
|
* @see {@link UseFilter}
|
|
37
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
37
|
+
* @see {@link https://meocord.dev/docs/4.2/exception-filters | Exception filters}
|
|
38
38
|
*/ function Catch(...errorTypes) {
|
|
39
|
-
return function(target, propertyKey) {
|
|
39
|
+
return declaring(function(target, propertyKey) {
|
|
40
40
|
refuseOnMethod('@Catch', target, propertyKey);
|
|
41
41
|
makeInjectable(target);
|
|
42
42
|
errorTypes.forEach((type, index)=>{
|
|
@@ -45,7 +45,7 @@ import { refuseOnMethod } from '../util/refusal.util.js';
|
|
|
45
45
|
warnDeprecatedBehaviour(logger, `${target.name}: @Catch's ${entry}, ${describeValue(type)}, which matches no error,`, 'is refused', 'an error class, such as @Catch(CooldownError),');
|
|
46
46
|
});
|
|
47
47
|
Reflect.defineMetadata(META.catchTypes, errorTypes, target);
|
|
48
|
-
};
|
|
48
|
+
});
|
|
49
49
|
}
|
|
50
50
|
const logger = new Logger('Catch');
|
|
51
51
|
const ORDINALS = [
|
|
@@ -85,9 +85,9 @@ const ORDINALS = [
|
|
|
85
85
|
* @group Decorators
|
|
86
86
|
* @category Pipeline stages
|
|
87
87
|
* @see {@link Catch}
|
|
88
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
88
|
+
* @see {@link https://meocord.dev/docs/4.2/exception-filters | Exception filters}
|
|
89
89
|
*/ function UseFilter(...filters) {
|
|
90
|
-
return function(target, propertyKey) {
|
|
90
|
+
return declaring(function(target, propertyKey) {
|
|
91
91
|
const where = propertyKey === undefined ? target.name : `${target.constructor.name}.${String(propertyKey)}`;
|
|
92
92
|
assertStageEntries('@UseFilter', 'filter', where, filters);
|
|
93
93
|
// Decorators apply bottom-up, so a higher decorator's filters are tried first.
|
|
@@ -104,7 +104,7 @@ const ORDINALS = [
|
|
|
104
104
|
...existing
|
|
105
105
|
], target, propertyKey);
|
|
106
106
|
}
|
|
107
|
-
};
|
|
107
|
+
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export { Catch, UseFilter };
|