meocord 4.2.1 → 4.2.3
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 +188 -0
- package/dist/cjs/_shared/{controller.decorator-XKMlcUZz.cjs → controller.decorator-hp5xdJU4.cjs} +550 -189
- package/dist/cjs/_shared/{startup-roots-CgfPQrNl.cjs → event-sources-DzqTODXg.cjs} +274 -88
- package/dist/cjs/_shared/{providers-BZgJyfZS.cjs → providers-BBvbnmLP.cjs} +837 -295
- package/dist/cjs/_shared/{sharded-cooldown-store-CsJJyCZd.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-BUAR1WYG.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +435 -182
- package/dist/cjs/decorator/index.cjs +116 -121
- package/dist/cjs/testing/index.cjs +1923 -808
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +32 -10
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +27 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +92 -66
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +6 -6
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +54 -32
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +83 -54
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +37 -15
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +4 -2
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +39 -4
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/stage-scope.js +2 -1
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +31 -30
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +37 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +4 -4
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +32 -25
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -335
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +217 -91
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +864 -124
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +8 -4
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +33 -5
- package/dist/types/common/index.d.cts +17 -10
- package/dist/types/common/index.d.ts +17 -10
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +158 -61
- package/dist/types/testing/index.d.ts +158 -61
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var discord_js = require('discord.js');
|
|
4
|
-
var controller_decorator = require('./controller.decorator-
|
|
5
|
-
var providers = require('./providers-
|
|
4
|
+
var controller_decorator = require('./controller.decorator-hp5xdJU4.cjs');
|
|
5
|
+
var providers = require('./providers-BBvbnmLP.cjs');
|
|
6
6
|
var enum_index = require('../enum/index.cjs');
|
|
7
7
|
var node_async_hooks = require('node:async_hooks');
|
|
8
|
-
var translateError = require('./translate-error-
|
|
8
|
+
var translateError = require('./translate-error-wsgfsUET.cjs');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* How a pattern ranks, one character per segment twice over: `L` for literal text or `P` for a param, then how narrow
|
|
@@ -392,9 +392,9 @@ const quoted = (names)=>names.length < 2 ? names.map((name)=>`"${name}"`).join('
|
|
|
392
392
|
}
|
|
393
393
|
/**
|
|
394
394
|
* Warns about a handler a subclass re-declares on other routes while it still answers the ones it inherits, for
|
|
395
|
-
* `@Command`, `@MessageHandler`, `@ReactionHandler` and `@
|
|
396
|
-
* the inherited routes. Every class a listed controller extends is checked, a base that is not listed
|
|
397
|
-
* the methods the controller still inherits through it.
|
|
395
|
+
* `@Command`, `@MessageHandler`, `@ReactionHandler`, `@Autocomplete`, `@On` and `@Once` alike. The next major version
|
|
396
|
+
* (5.0) drops the inherited routes. Every class a listed controller extends is checked, a base that is not listed
|
|
397
|
+
* included, for the methods the controller still inherits through it.
|
|
398
398
|
*/ function warnInheritedRoutes(controllerClasses) {
|
|
399
399
|
// A set, so a class two listed controllers extend is named once, for the routes either of them still answers
|
|
400
400
|
const problems = new Set();
|
|
@@ -403,11 +403,18 @@ const quoted = (names)=>names.length < 2 ? names.map((name)=>`"${name}"`).join('
|
|
|
403
403
|
// nothing of them from above it, so a class above is not named for them
|
|
404
404
|
const cut = new Set();
|
|
405
405
|
for(let prototype = controllerClass.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)){
|
|
406
|
-
const declared = controller_decorator.getDeclaredRoutes(prototype);
|
|
407
406
|
const base = Object.getPrototypeOf(prototype);
|
|
408
|
-
if (
|
|
407
|
+
if (!base) continue;
|
|
408
|
+
const declared = [
|
|
409
|
+
...controller_decorator.getDeclaredRoutes(prototype),
|
|
410
|
+
...controller_decorator.ownEventHandlers(prototype).map(controller_decorator.eventRoute)
|
|
411
|
+
];
|
|
412
|
+
if (declared.length === 0) continue;
|
|
409
413
|
if (Reflect.getOwnMetadata(controller_decorator.META.replacesInheritedRoutes, prototype)) for (const route of declared)cut.add(route.method);
|
|
410
|
-
const inherited =
|
|
414
|
+
const inherited = [
|
|
415
|
+
...controller_decorator.getHandlerRoutes(base),
|
|
416
|
+
...providers.getEventHandlers(base).map(controller_decorator.eventRoute)
|
|
417
|
+
].filter((route)=>!cut.has(route.method));
|
|
411
418
|
for (const method of new Set(declared.map((route)=>route.method))){
|
|
412
419
|
const own = [
|
|
413
420
|
...new Set(declared.filter((route)=>route.method === method).map((route)=>route.label))
|
|
@@ -721,7 +728,8 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
721
728
|
parse: []
|
|
722
729
|
}
|
|
723
730
|
}));
|
|
724
|
-
|
|
731
|
+
// Longer than a timer can wait keeps it, as 0 does; the timer would fire at once
|
|
732
|
+
if (seconds > 0 && providers.timerKeepsSeconds(seconds)) {
|
|
725
733
|
setTimeout(()=>{
|
|
726
734
|
reply.delete().catch((failure)=>controller_decorator.logFailedSend(logger, 'delete a reply to a command', failure));
|
|
727
735
|
}, seconds * 1000).unref?.();
|
|
@@ -919,9 +927,9 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
919
927
|
}
|
|
920
928
|
/**
|
|
921
929
|
* Resolves each unit and runs its `onReady` one at a time, in the units' dependency order. A failure is
|
|
922
|
-
* reported and the next unit still runs.
|
|
923
|
-
* to
|
|
924
|
-
*/ async function runReadyHooks(container, units, client, info,
|
|
930
|
+
* reported and the next unit still runs. `settled` hears of each unit as its `onReady` settles, or once it is found
|
|
931
|
+
* to have none, which is what a shutdown that starts meanwhile reads.
|
|
932
|
+
*/ async function runReadyHooks(container, units, client, info, report, { stopped = ()=>false, slowAfterMs, settled, runOnReady } = {}) {
|
|
925
933
|
const failed = new Set();
|
|
926
934
|
// For each unit, the failed units it depends on, directly or through another dependency
|
|
927
935
|
const failedUpstream = new Map();
|
|
@@ -958,11 +966,7 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
958
966
|
continue;
|
|
959
967
|
}
|
|
960
968
|
firstToken.set(instance, unit.token);
|
|
961
|
-
const entry = lifecycleEntry(unit, instance);
|
|
962
|
-
// Only a unit whose onReady has settled, or that has none, is shut down: a stop mid-ready waits for the one still
|
|
963
|
-
// starting, within its time, and skips those not reached yet
|
|
964
969
|
if (typeof instance.onReady !== 'function') {
|
|
965
|
-
entries.push(entry);
|
|
966
970
|
settled?.(unit);
|
|
967
971
|
continue;
|
|
968
972
|
}
|
|
@@ -979,7 +983,6 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
979
983
|
} finally{
|
|
980
984
|
clearTimeout(slow);
|
|
981
985
|
}
|
|
982
|
-
entries.push(entry);
|
|
983
986
|
settled?.(unit);
|
|
984
987
|
}
|
|
985
988
|
}
|
|
@@ -1093,6 +1096,28 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1093
1096
|
]);
|
|
1094
1097
|
}
|
|
1095
1098
|
|
|
1099
|
+
/**
|
|
1100
|
+
* Every `@Autocomplete` handler of the given controllers, read from their metadata, with the handlers of one option
|
|
1101
|
+
* before those of a whole command, so an option's handler wins over its command's whatever order they are declared in.
|
|
1102
|
+
*/ function buildAutocompleteRoutes(controllers) {
|
|
1103
|
+
const routes = controllers.flatMap((controllerClass)=>controller_decorator.getAutocompleteHandlers(controllerClass.prototype).map((meta)=>({
|
|
1104
|
+
controllerClass,
|
|
1105
|
+
meta
|
|
1106
|
+
})));
|
|
1107
|
+
return routes.sort((a, b)=>Number(Boolean(b.meta.optionName)) - Number(Boolean(a.meta.optionName)));
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* The handler an autocomplete reaches, as dispatch routes it: its full command path, then its command's name, and at
|
|
1111
|
+
* each the first handler whose option is the focused one, or that names none.
|
|
1112
|
+
*/ function matchAutocompleteRoute(routes, interaction) {
|
|
1113
|
+
const focused = controller_decorator.focusedOptionName(interaction);
|
|
1114
|
+
for (const path of controller_decorator.resolveCommandPaths(interaction)){
|
|
1115
|
+
const route = routes.find(({ meta })=>meta.commandPath === path && (meta.optionName === undefined || meta.optionName === focused));
|
|
1116
|
+
if (route) return route;
|
|
1117
|
+
}
|
|
1118
|
+
return undefined;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1096
1121
|
/** The keys a command is routed by, most specific first: a chat command's full path, then its name; none for anything else. */ function commandRouteKeys(interaction) {
|
|
1097
1122
|
if (interaction.isChatInputCommand()) return controller_decorator.resolveCommandPaths(interaction);
|
|
1098
1123
|
if (interaction.isContextMenuCommand() || interaction.isPrimaryEntryPointCommand()) return [
|
|
@@ -1255,6 +1280,26 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1255
1280
|
if (winner) return `command '${keys[0]}' does not reach ${handler}: dispatch runs ${winner.controllerClass.name}.${winner.meta.methodName}.`;
|
|
1256
1281
|
return `command '${keys[0]}' does not match ${handler}'s route ${named.map(({ route })=>`'${route}'`).join(' or ')}.`;
|
|
1257
1282
|
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Why an autocomplete could not reach a handler, for a test calling the handler directly: the handler is checked
|
|
1285
|
+
* against the one dispatch routes the autocomplete to among `controllers`, by its command path and focused option.
|
|
1286
|
+
* `undefined` when it is that one, when the handler has no `@Autocomplete` route, or when the interaction is not an
|
|
1287
|
+
* autocomplete or has no command name.
|
|
1288
|
+
*/ function autocompleteMismatch(controllers, controller, methodName, interaction) {
|
|
1289
|
+
if (!interaction.isAutocomplete() || typeof interaction.commandName !== 'string') return undefined;
|
|
1290
|
+
const own = controller_decorator.getAutocompleteHandlers(controller.prototype).filter((meta)=>meta.methodName === methodName);
|
|
1291
|
+
if (own.length === 0) return undefined;
|
|
1292
|
+
const winner = matchAutocompleteRoute(buildAutocompleteRoutes(controllers), interaction);
|
|
1293
|
+
if (winner?.controllerClass === controller && winner.meta.methodName === methodName) return undefined;
|
|
1294
|
+
const handler = `${controller.name}.${methodName}`;
|
|
1295
|
+
const received = controller_decorator.describeInteraction(interaction);
|
|
1296
|
+
// A mock without a focused option reaches no option's handler, the commonest way to miss one
|
|
1297
|
+
const option = controller_decorator.focusedOptionName(interaction) === undefined ? own.find((meta)=>meta.optionName !== undefined)?.optionName : undefined;
|
|
1298
|
+
const fix = option === undefined ? '' : ` Give the mock the focused option: options: createChatInputOptions({ focused: '${option}' }).`;
|
|
1299
|
+
if (winner) return `${received} does not reach ${handler}: dispatch runs ${winner.controllerClass.name}.${winner.meta.methodName}.${fix}`;
|
|
1300
|
+
const routes = own.map((meta)=>meta.optionName === undefined ? `'${meta.commandPath}'` : `'${meta.commandPath}' option '${meta.optionName}'`);
|
|
1301
|
+
return `${received} does not match ${handler}'s route ${routes.join(' or ')}.${fix}`;
|
|
1302
|
+
}
|
|
1258
1303
|
|
|
1259
1304
|
/**
|
|
1260
1305
|
* How long a component or modal submission no route takes is left to the client's other listeners, such as a
|
|
@@ -1322,13 +1367,19 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1322
1367
|
this.logger.warn(`messages.help is on, but ${route.controllerClass.name}.${route.method} (${JSON.stringify(route.pattern)}) takes "${shown}", ` + `which runs it instead, so the built-in help never answers. Turn help off, or give the handler another word.`);
|
|
1323
1368
|
}
|
|
1324
1369
|
}
|
|
1325
|
-
/**
|
|
1370
|
+
/**
|
|
1371
|
+
* The call for one event, stamped as it is made, so every handler it runs reports one start and a duration that
|
|
1372
|
+
* includes routing and any handler that ran before it; the fallback is told of each error that reaches it when there
|
|
1373
|
+
* is a recorder.
|
|
1374
|
+
*/ callFor(record) {
|
|
1326
1375
|
const fallback = record ? async (error, context)=>{
|
|
1327
1376
|
record.unhandled(error);
|
|
1328
|
-
|
|
1377
|
+
const failure = await this.fallback(error, context);
|
|
1378
|
+
if (failure !== undefined) record.unanswered(error, failure);
|
|
1379
|
+
return failure;
|
|
1329
1380
|
} : this.fallback;
|
|
1330
1381
|
return {
|
|
1331
|
-
startedAt,
|
|
1382
|
+
startedAt: performance.now(),
|
|
1332
1383
|
fallback,
|
|
1333
1384
|
record
|
|
1334
1385
|
};
|
|
@@ -1339,32 +1390,35 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1339
1390
|
}
|
|
1340
1391
|
return this.controllerInstancesCache.get(controllerClass);
|
|
1341
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Runs `run` on the controller's instance, resolved as its handler starts. A controller that cannot be made fails
|
|
1395
|
+
* only its own handlers: the error goes to the global filters, then the fallback, and the call goes on without it.
|
|
1396
|
+
*/ async withInstance(controllerClass, args, call, run, unresolved = new Set()) {
|
|
1397
|
+
// A controller that failed to resolve for this event is reported once, however many of its handlers it reaches
|
|
1398
|
+
if (unresolved.has(controllerClass)) return;
|
|
1399
|
+
let instance;
|
|
1400
|
+
try {
|
|
1401
|
+
instance = this.getInstance(controllerClass);
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
unresolved.add(controllerClass);
|
|
1404
|
+
await providers.handleUnroutedError(this.container, args, error, this.runOptions(call));
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
await run(instance);
|
|
1408
|
+
}
|
|
1342
1409
|
/** The component routes, built on first use; the app calls it as it is created. */ getComponentRoutes() {
|
|
1343
1410
|
return this.componentRoutes ?? (this.componentRoutes = buildComponentRoutes([
|
|
1344
1411
|
...this.controllerClasses
|
|
1345
1412
|
]));
|
|
1346
1413
|
}
|
|
1347
1414
|
getAutocompleteRoutes() {
|
|
1348
|
-
|
|
1349
|
-
const routes = [];
|
|
1350
|
-
for (const controllerClass of this.controllerClasses){
|
|
1351
|
-
for (const meta of controller_decorator.getAutocompleteHandlers(this.getInstance(controllerClass))){
|
|
1352
|
-
routes.push({
|
|
1353
|
-
controllerClass,
|
|
1354
|
-
meta
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
routes.sort((a, b)=>Number(Boolean(b.meta.optionName)) - Number(Boolean(a.meta.optionName)));
|
|
1359
|
-
this.autocompleteRoutes = routes;
|
|
1360
|
-
return routes;
|
|
1415
|
+
return this.autocompleteRoutes ?? (this.autocompleteRoutes = buildAutocompleteRoutes(this.controllerClasses));
|
|
1361
1416
|
}
|
|
1362
1417
|
/**
|
|
1363
1418
|
* Dispatches an interaction. A failure outside any handler, such as no route matching or resolving
|
|
1364
1419
|
* the controller, goes to the global filters, then the fallback.
|
|
1365
1420
|
*/ async interaction(interaction, record) {
|
|
1366
|
-
|
|
1367
|
-
const call = this.callFor(record, performance.now());
|
|
1421
|
+
const call = this.callFor(record);
|
|
1368
1422
|
try {
|
|
1369
1423
|
await this.dispatchInteraction(interaction, call);
|
|
1370
1424
|
} catch (error) {
|
|
@@ -1390,14 +1444,14 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1390
1444
|
if (matched) {
|
|
1391
1445
|
const { route, params } = matched;
|
|
1392
1446
|
interaction.dynamicParams = params;
|
|
1393
|
-
await this.executeCommand(
|
|
1447
|
+
await this.executeCommand(route.controllerClass, route.meta, interaction, call);
|
|
1394
1448
|
return;
|
|
1395
1449
|
}
|
|
1396
1450
|
}
|
|
1397
1451
|
// The same match a test's invoke checks a handler against, so the two cannot disagree
|
|
1398
1452
|
const command = matchCommandRoute(this.controllerClasses, interaction);
|
|
1399
1453
|
if (command) {
|
|
1400
|
-
await this.executeCommand(
|
|
1454
|
+
await this.executeCommand(command.controllerClass, command.meta, interaction, call);
|
|
1401
1455
|
return;
|
|
1402
1456
|
}
|
|
1403
1457
|
// A component or modal no route takes may be a collector's: with another listener on the client, it has the
|
|
@@ -1420,21 +1474,17 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1420
1474
|
* Answers an autocomplete interaction from the `@Autocomplete` handler that claims it. An unclaimed
|
|
1421
1475
|
* option gets an empty list and a warning, rather than a menu stuck loading until Discord times out.
|
|
1422
1476
|
*/ async handleAutocomplete(interaction, call) {
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
], call);
|
|
1435
|
-
if (!ran) await closeAutocomplete(interaction, this.logger);
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1477
|
+
const route = matchAutocompleteRoute(this.getAutocompleteRoutes(), interaction);
|
|
1478
|
+
if (route) {
|
|
1479
|
+
const { controllerClass, meta } = route;
|
|
1480
|
+
this.logger.log('[AUTOCOMPLETE]', `[${meta.commandPath}]`, `[${meta.methodName}]`);
|
|
1481
|
+
const params = controller_decorator.resolveOptionParams(interaction);
|
|
1482
|
+
const ran = await this.invokeHandler(this.getInstance(controllerClass), meta.methodName, [
|
|
1483
|
+
interaction,
|
|
1484
|
+
params
|
|
1485
|
+
], call);
|
|
1486
|
+
if (!ran) await closeAutocomplete(interaction, this.logger);
|
|
1487
|
+
return;
|
|
1438
1488
|
}
|
|
1439
1489
|
this.logger.warn(`No handler matched ${controller_decorator.describeInteraction(interaction)}. Declare an @Autocomplete handler for it, ` + `or drop setAutocomplete(true) from the option.`);
|
|
1440
1490
|
await closeAutocomplete(interaction, this.logger);
|
|
@@ -1442,19 +1492,22 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1442
1492
|
interaction
|
|
1443
1493
|
], this.runOptions(call));
|
|
1444
1494
|
}
|
|
1445
|
-
/** Warns, in development, that a modal field or select menu choice is hidden by a customId param of the same name. */ warnCollisions(methodName, names) {
|
|
1495
|
+
/** Warns, in development, that a modal field or select menu choice is hidden by a customId param of the same name. */ warnCollisions(controllerClass, methodName, names) {
|
|
1446
1496
|
if (process.env.NODE_ENV !== 'development') return;
|
|
1497
|
+
let warned = this.warnedCollisions.get(controllerClass);
|
|
1498
|
+
if (!warned) this.warnedCollisions.set(controllerClass, warned = new Set());
|
|
1447
1499
|
for (const name of names){
|
|
1448
1500
|
const key = `${methodName}:${name}`;
|
|
1449
|
-
if (
|
|
1450
|
-
|
|
1451
|
-
this.logger.warn(`"${name}" is both a customId param and a modal field or select menu choice of ${methodName}; the handler receives the customId
|
|
1501
|
+
if (warned.has(key)) continue;
|
|
1502
|
+
warned.add(key);
|
|
1503
|
+
this.logger.warn(`"${name}" is both a customId param and a modal field or select menu choice of ${controllerClass.name}.${methodName}; the handler ` + `receives the customId param. Rename one to receive both.`);
|
|
1452
1504
|
}
|
|
1453
1505
|
}
|
|
1454
1506
|
/**
|
|
1455
1507
|
* Runs a resolved command, shared by both dispatch paths so a pattern-matched
|
|
1456
1508
|
* component and a named slash command behave identically once the route is chosen.
|
|
1457
|
-
*/ async executeCommand(
|
|
1509
|
+
*/ async executeCommand(controllerClass, commandMetadata, interaction, call) {
|
|
1510
|
+
const controllerInstance = this.getInstance(controllerClass);
|
|
1458
1511
|
const { methodName, type } = commandMetadata;
|
|
1459
1512
|
// No interaction-type check here: both callers pick the route with
|
|
1460
1513
|
// `matchesCommandType` before getting this far, and `@Command` re-checks the
|
|
@@ -1462,7 +1515,7 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1462
1515
|
this.logger.log('[INTERACTION]', `[${type}]`, `[${methodName}]`);
|
|
1463
1516
|
const routeParams = interaction.dynamicParams;
|
|
1464
1517
|
const { params, collisions } = handlerInput(interaction, routeParams);
|
|
1465
|
-
this.warnCollisions(methodName, collisions);
|
|
1518
|
+
this.warnCollisions(controllerClass, methodName, collisions);
|
|
1466
1519
|
await this.invokeHandler(controllerInstance, methodName, [
|
|
1467
1520
|
interaction,
|
|
1468
1521
|
params
|
|
@@ -1480,10 +1533,7 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1480
1533
|
* says whether the handler ran. Its guard wrappers let this call through, so each guard runs once.
|
|
1481
1534
|
*/ async invokeHandler(instance, methodName, args, call, hooks = {}) {
|
|
1482
1535
|
const handler = `${instance.constructor.name}.${methodName}`;
|
|
1483
|
-
const onUnanswered = this.options.warnUnanswered ? (
|
|
1484
|
-
name: returnedBy.interceptor.name,
|
|
1485
|
-
handlerStarted: returnedBy.handlerStarted
|
|
1486
|
-
}) : undefined;
|
|
1536
|
+
const onUnanswered = this.options.warnUnanswered ? (unanswered)=>this.warnUnansweredOnce(handler, unanswered) : undefined;
|
|
1487
1537
|
const outcome = await providers.runHandler(this.container, instance, methodName, args, {
|
|
1488
1538
|
...this.runOptions(call),
|
|
1489
1539
|
onUnanswered,
|
|
@@ -1493,13 +1543,17 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1493
1543
|
return outcome.ran;
|
|
1494
1544
|
}
|
|
1495
1545
|
/**
|
|
1496
|
-
* Warns, once per handler, that it left its interaction unanswered or deferred without a follow-up,
|
|
1497
|
-
*
|
|
1498
|
-
*/ warnUnansweredOnce(handler, phase,
|
|
1546
|
+
* Warns, once per handler, that it left its interaction or autocomplete unanswered or deferred without a follow-up,
|
|
1547
|
+
* or that an interceptor returned before the handler ran or finished, and the call ended so.
|
|
1548
|
+
*/ warnUnansweredOnce(handler, { type, phase, returnedBy }) {
|
|
1499
1549
|
if (this.warnedUnanswered.has(handler)) return;
|
|
1500
1550
|
this.warnedUnanswered.add(handler);
|
|
1551
|
+
const interceptor = returnedBy && {
|
|
1552
|
+
name: returnedBy.interceptor.name,
|
|
1553
|
+
handlerStarted: returnedBy.handlerStarted
|
|
1554
|
+
};
|
|
1501
1555
|
const returned = interceptor && `${handler}: its interceptor ${interceptor.name} returned before the handler ${interceptor.handlerStarted ? 'finished' : 'ran'}`;
|
|
1502
|
-
const what = interceptor ? phase === 'unanswered' ? `${returned}, without answering the interaction, so the user saw "The application did not respond". Answer it ` + `in ${interceptor.name}, or await next.handle().` : interceptor.handlerStarted ? `${returned}, and the deferred interaction had no follow-up when the call ended, so @Defer's lock was ` + `released while the user still saw it thinking. Follow up in ${interceptor.name} with respond(interaction).send(), ` + 'or await next.handle().' : `${returned}, and the deferred interaction was never followed up, so the user saw it thinking until Discord ` + `gave up. Follow up in ${interceptor.name} with respond(interaction).send(), or await next.handle().` : phase === 'unanswered' ? `${handler} finished without answering its interaction, so the user saw "The application did not respond". ` + 'Answer it with respond(interaction).send(), or acknowledge it first with @Defer().' : `${handler} deferred its interaction and never followed up, so the user saw it thinking until Discord gave up. ` + 'Follow up with respond(interaction).send().';
|
|
1556
|
+
const what = type === 'autocomplete' ? `${handler} finished without answering its autocomplete, so the user saw its options fail to load. Answer it ` + 'with interaction.respond(choices) within 3 seconds.' : interceptor ? phase === 'unanswered' ? `${returned}, without answering the interaction, so the user saw "The application did not respond". Answer it ` + `in ${interceptor.name}, or await next.handle().` : interceptor.handlerStarted ? `${returned}, and the deferred interaction had no follow-up when the call ended, so @Defer's lock was ` + `released while the user still saw it thinking. Follow up in ${interceptor.name} with respond(interaction).send(), ` + 'or await next.handle().' : `${returned}, and the deferred interaction was never followed up, so the user saw it thinking until Discord ` + `gave up. Follow up in ${interceptor.name} with respond(interaction).send(), or await next.handle().` : phase === 'unanswered' ? `${handler} finished without answering its interaction, so the user saw "The application did not respond". ` + 'Answer it with respond(interaction).send(), or acknowledge it first with @Defer().' : `${handler} deferred its interaction and never followed up, so the user saw it thinking until Discord gave up. ` + 'Follow up with respond(interaction).send().';
|
|
1503
1557
|
this.logger.warn(`${what} Shown once per handler; @MeoCord({ warnUnanswered: false }) turns it off.`);
|
|
1504
1558
|
}
|
|
1505
1559
|
/**
|
|
@@ -1508,7 +1562,7 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1508
1562
|
* usage through that handler's filters. A bare parent's unguarded subcommands' usage, once the app's guards allow
|
|
1509
1563
|
* it, and a failure to read the prefixes, go through the global filters, then the fallback; the listeners still run.
|
|
1510
1564
|
*/ async message(message, record) {
|
|
1511
|
-
if (message.author.bot || !message.content
|
|
1565
|
+
if (message.author.bot || !providers.hasMessageText(message.content)) return;
|
|
1512
1566
|
const call = this.callFor(record);
|
|
1513
1567
|
let target;
|
|
1514
1568
|
try {
|
|
@@ -1535,24 +1589,29 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1535
1589
|
}
|
|
1536
1590
|
// The matched handler, then every listener, each resolved from the container only as it starts
|
|
1537
1591
|
const runs = [];
|
|
1592
|
+
const unresolved = new Set();
|
|
1538
1593
|
if (target) {
|
|
1539
1594
|
const { route, params, start, given } = target;
|
|
1540
1595
|
noteInvocation(message, `${start}${providers.commandWordsOf(route.tokens).join(' ')}`);
|
|
1541
1596
|
const hooks = providers.messageCommandHooks(route, params, message, start, given, this.messageOptions.types);
|
|
1542
1597
|
runs.push({
|
|
1543
1598
|
name: `${route.controllerClass.name}.${route.method}`,
|
|
1544
|
-
run: (at)=>this.
|
|
1545
|
-
message
|
|
1546
|
-
|
|
1547
|
-
|
|
1599
|
+
run: (at)=>this.withInstance(route.controllerClass, [
|
|
1600
|
+
message
|
|
1601
|
+
], at, (instance)=>this.invokeHandler(instance, route.method, [
|
|
1602
|
+
message,
|
|
1603
|
+
params
|
|
1604
|
+
], at, hooks), unresolved)
|
|
1548
1605
|
});
|
|
1549
1606
|
}
|
|
1550
1607
|
for (const { controllerClass, method } of this.messageListeners){
|
|
1551
1608
|
runs.push({
|
|
1552
1609
|
name: `${controllerClass.name}.${method}`,
|
|
1553
|
-
run: (at)=>this.
|
|
1610
|
+
run: (at)=>this.withInstance(controllerClass, [
|
|
1554
1611
|
message
|
|
1555
|
-
], at)
|
|
1612
|
+
], at, (instance)=>this.invokeHandler(instance, method, [
|
|
1613
|
+
message
|
|
1614
|
+
], at), unresolved)
|
|
1556
1615
|
});
|
|
1557
1616
|
}
|
|
1558
1617
|
if (this.messageOptions.handlers === 'concurrent') await this.runTogether(runs, call);
|
|
@@ -1577,7 +1636,8 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1577
1636
|
...call,
|
|
1578
1637
|
record: {
|
|
1579
1638
|
settled: (...report)=>reports[index].push(report),
|
|
1580
|
-
unhandled: (error)=>record.unhandled(error)
|
|
1639
|
+
unhandled: (error)=>record.unhandled(error),
|
|
1640
|
+
unanswered: (error, failure)=>record.unanswered(error, failure)
|
|
1581
1641
|
}
|
|
1582
1642
|
} : call)));
|
|
1583
1643
|
for (const report of reports.flat())record?.settled(...report);
|
|
@@ -1657,7 +1717,7 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1657
1717
|
const matching = this.controllerClasses.map((controllerClass)=>({
|
|
1658
1718
|
controllerClass,
|
|
1659
1719
|
// Handlers for this emoji first, then those for every emoji
|
|
1660
|
-
handlers: controller_decorator.getReactionHandlers(
|
|
1720
|
+
handlers: controller_decorator.getReactionHandlers(controllerClass.prototype).filter(forEmoji).sort((a, b)=>Number(!a.emoji) - Number(!b.emoji))
|
|
1661
1721
|
})).filter(({ handlers })=>handlers.length > 0);
|
|
1662
1722
|
if (matching.length === 0) return;
|
|
1663
1723
|
// Only asked when a matching handler leaves bots out, since a partial user costs a fetch
|
|
@@ -1681,16 +1741,21 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1681
1741
|
return;
|
|
1682
1742
|
}
|
|
1683
1743
|
for (const { controllerClass, handlers } of runs){
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1744
|
+
await this.withInstance(controllerClass, [
|
|
1745
|
+
reaction,
|
|
1746
|
+
{
|
|
1747
|
+
user,
|
|
1748
|
+
action
|
|
1749
|
+
}
|
|
1750
|
+
], call, async (instance)=>{
|
|
1751
|
+
for (const { method } of handlers)await this.invokeHandler(instance, method, [
|
|
1687
1752
|
reaction,
|
|
1688
1753
|
{
|
|
1689
1754
|
user,
|
|
1690
1755
|
action
|
|
1691
1756
|
}
|
|
1692
1757
|
], call);
|
|
1693
|
-
}
|
|
1758
|
+
});
|
|
1694
1759
|
}
|
|
1695
1760
|
}
|
|
1696
1761
|
/**
|
|
@@ -1709,7 +1774,10 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1709
1774
|
constructor(options){
|
|
1710
1775
|
this.options = options;
|
|
1711
1776
|
this.controllerInstancesCache = new Map();
|
|
1712
|
-
/**
|
|
1777
|
+
/**
|
|
1778
|
+
* Method and name pairs already warned about, by controller class, so a collision warns once per handler, not on
|
|
1779
|
+
* every submit or selection. Keyed by the class itself, so two controllers of one name stay apart.
|
|
1780
|
+
*/ this.warnedCollisions = new WeakMap();
|
|
1713
1781
|
/** The handlers already warned about, so each is named once however often it runs. */ this.warnedUnanswered = new Set();
|
|
1714
1782
|
/** The message handlers already warned about as slow, so each is named once. */ this.warnedSlow = new Set();
|
|
1715
1783
|
this.container = options.container;
|
|
@@ -1724,6 +1792,8 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1724
1792
|
// The app's own help command reads this table too, so it lists the commands dispatch reaches
|
|
1725
1793
|
if (this.container.isBound(providers.HandlerRegistry)) providers.shareMessageRoutes(this.container.get(providers.HandlerRegistry), this.messageRoutes);
|
|
1726
1794
|
this.warnUnreachableHelp();
|
|
1795
|
+
// Here, where the routes are built once, rather than in buildMessageRoutes, which startup runs twice
|
|
1796
|
+
for (const warning of providers.messageRouteWarnings(this.messageRoutes, this.messageOptions))this.logger.warn(warning);
|
|
1727
1797
|
this.messageListeners = this.controllerClasses.flatMap((controllerClass)=>controller_decorator.getMessageHandlers(controllerClass.prototype).filter((handler)=>handler.pattern === undefined).map(({ method })=>({
|
|
1728
1798
|
controllerClass,
|
|
1729
1799
|
method
|
|
@@ -1807,19 +1877,38 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1807
1877
|
seen.add(cls);
|
|
1808
1878
|
return providers.injectedTokens(cls).some((token)=>token === controller_decorator.ExecutionContext || providers.isAppClassToken(token) && !providers$1.has(token) && !seen.has(token) && reachesContext(token, providers$1, seen));
|
|
1809
1879
|
}
|
|
1810
|
-
/**
|
|
1811
|
-
* The classes the app's stages and presenter inject, which they would make only at their first call. Made with the
|
|
1812
|
-
* app's own classes, a service among them gets its lifecycle hooks. One that reaches the call's `ExecutionContext` is
|
|
1813
|
-
* made for each call, so it is left out, and a stage a test replaces is not among the stages, so what it would inject
|
|
1814
|
-
* is not made.
|
|
1815
|
-
*/ function stageDependencies({ decorators }, providers$1, presenter) {
|
|
1880
|
+
/** The app's classes the stages and presenter inject. */ function stageInjected({ decorators }, presenter) {
|
|
1816
1881
|
const stages = [
|
|
1817
1882
|
...decorators
|
|
1818
1883
|
].filter(([, decorator])=>decorator !== '@Controller()').map(([stage])=>stage);
|
|
1819
1884
|
if (typeof presenter === 'function') stages.push(presenter);
|
|
1820
1885
|
return [
|
|
1821
1886
|
...new Set(stages.flatMap((stage)=>providers.injectedTokens(stage)))
|
|
1822
|
-
].filter(providers.isAppClassToken)
|
|
1887
|
+
].filter(providers.isAppClassToken);
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* The classes the app's stages and presenter inject, which they would make only at their first call. Made with the
|
|
1891
|
+
* app's own classes, a service among them gets its lifecycle hooks. One that reaches the call's `ExecutionContext` is
|
|
1892
|
+
* made for each call, so it is left out, and a stage a test replaces is not among the stages, so what it would inject
|
|
1893
|
+
* is not made.
|
|
1894
|
+
*/ function stageDependencies(startup, providers, presenter) {
|
|
1895
|
+
return stageInjected(startup, presenter).filter((cls)=>!reachesContext(cls, providers));
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* A warning for each `@On` and `@Once` handler on a class the stages and presenter reach that is made for each call,
|
|
1899
|
+
* as it reaches `ExecutionContext`: no one instance of it receives events.
|
|
1900
|
+
*/ function perCallEventWarnings(startup, providers$1, presenter) {
|
|
1901
|
+
const perCall = new Set();
|
|
1902
|
+
// What a class made for each call injects is made for each call too only if it also reaches the context
|
|
1903
|
+
const visit = (cls)=>{
|
|
1904
|
+
if (perCall.has(cls) || providers$1.has(cls) || !reachesContext(cls, providers$1)) return;
|
|
1905
|
+
perCall.add(cls);
|
|
1906
|
+
providers.injectedTokens(cls).filter(providers.isAppClassToken).forEach(visit);
|
|
1907
|
+
};
|
|
1908
|
+
stageInjected(startup, presenter).forEach(visit);
|
|
1909
|
+
return [
|
|
1910
|
+
...perCall
|
|
1911
|
+
].flatMap((cls)=>providers.getEventHandlers(cls.prototype).map(({ event, method, once })=>`${cls.name}.${method}: @${once ? 'Once' : 'On'}('${event}') is on a class made for each call, as it injects ` + 'ExecutionContext or a class that does, so it receives no events. The next major version (5.0) refuses it; ' + 'move the handler to a service that does not.'));
|
|
1823
1912
|
}
|
|
1824
1913
|
/** How a container checks a guard first met at a call: the guard and what it injects, as the startup checks would. */ function lateGuardCheck(providers$1, translator) {
|
|
1825
1914
|
return (guard)=>assertStartupClasses({
|
|
@@ -1842,6 +1931,100 @@ async function replyToMessage(message, error, logger, withEmoji, answering) {
|
|
|
1842
1931
|
for (const needsOne of classes.filter((cls)=>providers.injectedTokens(cls).includes(controller_decorator.Translator)))controller_decorator.startupError(controller_decorator.missingTranslatorError(needsOne));
|
|
1843
1932
|
}
|
|
1844
1933
|
|
|
1934
|
+
/**
|
|
1935
|
+
* One source for each instance whose lifecycle hooks run, the cooldown store aside, in lifecycle order; read once the
|
|
1936
|
+
* providers are made, as a value's or a factory's class is its instance's. A class `prepared` lacks has its handlers
|
|
1937
|
+
* prepared here, and one a controller's would be refused for is left out, with `warn` naming the refusal.
|
|
1938
|
+
*/ function eventSources(container, providers$1, units, prepared, warn) {
|
|
1939
|
+
// The tokens of each class the container makes, bound as itself or given as a provider's `useClass`, the cooldown
|
|
1940
|
+
// store's included: no event reaches it, nor its instance through a factory
|
|
1941
|
+
const classTokens = new Map();
|
|
1942
|
+
for (const { token } of units){
|
|
1943
|
+
const provider = providers$1.get(token);
|
|
1944
|
+
if (provider && !providers.isClassProvider(provider) || !provider && !providers.isAppClassToken(token)) continue;
|
|
1945
|
+
const cls = provider ? provider.useClass : token;
|
|
1946
|
+
classTokens.set(cls, [
|
|
1947
|
+
...classTokens.get(cls) ?? [],
|
|
1948
|
+
token
|
|
1949
|
+
]);
|
|
1950
|
+
}
|
|
1951
|
+
const names = new Map(prepared.map((cls)=>[
|
|
1952
|
+
cls.name,
|
|
1953
|
+
cls
|
|
1954
|
+
]));
|
|
1955
|
+
const handlers = new Map();
|
|
1956
|
+
const handlersOf = (cls)=>{
|
|
1957
|
+
let found = handlers.get(cls);
|
|
1958
|
+
if (!found) {
|
|
1959
|
+
found = prepared.includes(cls) ? providers.getEventHandlers(cls.prototype) : prepareClass(container, cls, names, warn);
|
|
1960
|
+
handlers.set(cls, found);
|
|
1961
|
+
}
|
|
1962
|
+
return found;
|
|
1963
|
+
};
|
|
1964
|
+
const sources = [];
|
|
1965
|
+
const given = new Set();
|
|
1966
|
+
for (const { token, cooldownStore } of units){
|
|
1967
|
+
// The cooldown store is the app's infrastructure, never one of its classes, so no event reaches it
|
|
1968
|
+
if (cooldownStore) continue;
|
|
1969
|
+
const provider = providers$1.get(token);
|
|
1970
|
+
if (!provider || providers.isClassProvider(provider)) {
|
|
1971
|
+
const cls = provider ? provider.useClass : token;
|
|
1972
|
+
const found = handlersOf(cls);
|
|
1973
|
+
if (found.length > 0) sources.push({
|
|
1974
|
+
cls,
|
|
1975
|
+
handlers: found,
|
|
1976
|
+
resolve: ()=>container.get(token)
|
|
1977
|
+
});
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
// A value, or a factory's, made by now; one instance under several tokens is one source
|
|
1981
|
+
const instance = container.get(token);
|
|
1982
|
+
if (typeof instance !== 'object' || instance === null || given.has(instance)) continue;
|
|
1983
|
+
given.add(instance);
|
|
1984
|
+
const cls = instance.constructor;
|
|
1985
|
+
if (typeof cls !== 'function') continue;
|
|
1986
|
+
const found = handlersOf(cls);
|
|
1987
|
+
if (found.length === 0) continue;
|
|
1988
|
+
// A factory may return the instance the container made for a class unit, whose own source runs it; only units of
|
|
1989
|
+
// the instance's class are resolved, and each resolves on the event through its own source anyway
|
|
1990
|
+
const tokens = classTokens.get(cls) ?? [];
|
|
1991
|
+
const owned = ()=>tokens.some((token)=>container.get(token) === instance);
|
|
1992
|
+
sources.push({
|
|
1993
|
+
cls,
|
|
1994
|
+
handlers: found,
|
|
1995
|
+
resolve: ()=>owned() ? undefined : instance
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
return sources;
|
|
1999
|
+
}
|
|
2000
|
+
/** The handlers of `cls` that pass a controller's checks, warning of each other one and why it does not run. */ function prepareClass(container, cls, names, warn) {
|
|
2001
|
+
const declared = providers.getEventHandlers(cls.prototype);
|
|
2002
|
+
if (declared.length === 0) return [];
|
|
2003
|
+
const other = names.get(cls.name);
|
|
2004
|
+
if (!other) names.set(cls.name, cls);
|
|
2005
|
+
// @Once tells classes apart by name, so this class's would share another's
|
|
2006
|
+
const sharesName = other !== undefined && other !== cls && declared.some((handler)=>handler.once);
|
|
2007
|
+
if (sharesName) {
|
|
2008
|
+
refused(warn, cls, `${cls.name}: two classes have this name; @Cooldown and @Once tell classes apart by name, so they would share their counts. Rename one of them.`, declared.filter((handler)=>handler.once));
|
|
2009
|
+
}
|
|
2010
|
+
const errors = new Map();
|
|
2011
|
+
return declared.filter((handler)=>{
|
|
2012
|
+
if (sharesName && handler.once) return false;
|
|
2013
|
+
let found = errors.get(handler.method);
|
|
2014
|
+
if (!found) {
|
|
2015
|
+
found = providers.prepareEventHandler(container, cls, handler.method);
|
|
2016
|
+
errors.set(handler.method, found);
|
|
2017
|
+
const onMethod = declared.filter((other)=>other.method === handler.method && !(sharesName && other.once));
|
|
2018
|
+
for (const error of found)refused(warn, cls, error.message, onMethod);
|
|
2019
|
+
}
|
|
2020
|
+
return found.length === 0;
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
function refused(warn, cls, reason, handlers) {
|
|
2024
|
+
const labels = handlers.map(({ event, method, once })=>`@${once ? 'Once' : 'On'}('${event}') in ${cls.name}.${method}`);
|
|
2025
|
+
warn(`${reason} Until the next major version (5.0) refuses it at startup, ${labels.join(', ')} does not run.`);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1845
2028
|
exports.DEFAULT_SHUTDOWN_TIMEOUT_MS = DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
1846
2029
|
exports.Dispatcher = Dispatcher;
|
|
1847
2030
|
exports.ReadyPass = ReadyPass;
|
|
@@ -1849,11 +2032,13 @@ exports.RunningCalls = RunningCalls;
|
|
|
1849
2032
|
exports.SHUTDOWN_MARGIN_MS = SHUTDOWN_MARGIN_MS;
|
|
1850
2033
|
exports.assertDistinctCommands = assertDistinctCommands;
|
|
1851
2034
|
exports.assertStartupClasses = assertStartupClasses;
|
|
2035
|
+
exports.autocompleteMismatch = autocompleteMismatch;
|
|
1852
2036
|
exports.buildComponentRoutes = buildComponentRoutes;
|
|
1853
2037
|
exports.callsSettled = callsSettled;
|
|
1854
2038
|
exports.commandMismatch = commandMismatch;
|
|
1855
2039
|
exports.componentRouteFor = componentRouteFor;
|
|
1856
2040
|
exports.createFallback = createFallback;
|
|
2041
|
+
exports.eventSources = eventSources;
|
|
1857
2042
|
exports.findComponentRouteConflicts = findComponentRouteConflicts;
|
|
1858
2043
|
exports.handlerInput = handlerInput;
|
|
1859
2044
|
exports.lateGuardCheck = lateGuardCheck;
|
|
@@ -1861,6 +2046,7 @@ exports.lifecycleEntry = lifecycleEntry;
|
|
|
1861
2046
|
exports.matchComponentRoute = matchComponentRoute;
|
|
1862
2047
|
exports.messageDecorator = messageDecorator;
|
|
1863
2048
|
exports.ownInteractionListener = ownInteractionListener;
|
|
2049
|
+
exports.perCallEventWarnings = perCallEventWarnings;
|
|
1864
2050
|
exports.reactionDecorator = reactionDecorator;
|
|
1865
2051
|
exports.readCustomId = readCustomId;
|
|
1866
2052
|
exports.replyWithUserError = replyWithUserError;
|