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,6 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { assertStartupChecked } from './startup-checked.js';
|
|
3
|
-
import {
|
|
3
|
+
import { injectedTokens, singletonContextError, perHandler, sourcePrototype, stageClasses, methodStages } from './guard-runner.js';
|
|
4
4
|
import { ExecutionContext } from '../common/execution-context.js';
|
|
5
5
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
6
6
|
import { isAppClassToken } from './lifecycle-order.js';
|
|
@@ -24,13 +24,12 @@ function isInterceptorWithParams(entry) {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* The interceptors around a handler: each class's, base first, down to the controller it is dispatched on, then the
|
|
27
|
-
* method's.
|
|
27
|
+
* method's and its overrides', base first.
|
|
28
28
|
*/ const handlerInterceptors = perHandler((prototype, methodName)=>{
|
|
29
|
-
|
|
30
|
-
if (!source) return [];
|
|
29
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
31
30
|
return [
|
|
32
31
|
...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(META.classInterceptors, cls) ?? []),
|
|
33
|
-
...
|
|
32
|
+
...methodStages(prototype, methodName, META.methodInterceptors)
|
|
34
33
|
];
|
|
35
34
|
});
|
|
36
35
|
/** Binds `cls` and its unbound dependencies as singletons, refusing any that injects `ExecutionContext`. */ function bindShared(container, cls) {
|
|
@@ -49,9 +49,9 @@ import { RunningCalls } from './running-calls.js';
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Resolves each unit and runs its `onReady` one at a time, in the units' dependency order. A failure is
|
|
52
|
-
* reported and the next unit still runs.
|
|
53
|
-
* to
|
|
54
|
-
*/ async function runReadyHooks(container, units, client, info,
|
|
52
|
+
* reported and the next unit still runs. `settled` hears of each unit as its `onReady` settles, or once it is found
|
|
53
|
+
* to have none, which is what a shutdown that starts meanwhile reads.
|
|
54
|
+
*/ async function runReadyHooks(container, units, client, info, report, { stopped = ()=>false, slowAfterMs, settled, runOnReady } = {}) {
|
|
55
55
|
const failed = new Set();
|
|
56
56
|
// For each unit, the failed units it depends on, directly or through another dependency
|
|
57
57
|
const failedUpstream = new Map();
|
|
@@ -88,11 +88,7 @@ import { RunningCalls } from './running-calls.js';
|
|
|
88
88
|
continue;
|
|
89
89
|
}
|
|
90
90
|
firstToken.set(instance, unit.token);
|
|
91
|
-
const entry = lifecycleEntry(unit, instance);
|
|
92
|
-
// Only a unit whose onReady has settled, or that has none, is shut down: a stop mid-ready waits for the one still
|
|
93
|
-
// starting, within its time, and skips those not reached yet
|
|
94
91
|
if (typeof instance.onReady !== 'function') {
|
|
95
|
-
entries.push(entry);
|
|
96
92
|
settled?.(unit);
|
|
97
93
|
continue;
|
|
98
94
|
}
|
|
@@ -109,7 +105,6 @@ import { RunningCalls } from './running-calls.js';
|
|
|
109
105
|
} finally{
|
|
110
106
|
clearTimeout(slow);
|
|
111
107
|
}
|
|
112
|
-
entries.push(entry);
|
|
113
108
|
settled?.(unit);
|
|
114
109
|
}
|
|
115
110
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Client } from 'discord.js';
|
|
2
|
-
import { injectedTokens } from './guard-runner.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* The whole source the runtime gives a native function, such as `function NumberFormat() { [native code] }`. A class's
|
|
@@ -11,18 +10,5 @@ import { injectedTokens } from './guard-runner.js';
|
|
|
11
10
|
*/ function isAppClassToken(token) {
|
|
12
11
|
return typeof token === 'function' && token !== Client && !NATIVE_SOURCE.test(Function.prototype.toString.call(token));
|
|
13
12
|
}
|
|
14
|
-
/**
|
|
15
|
-
* The bound classes `cls` injects, by constructor type or `@inject` token: the classes whose hooks
|
|
16
|
-
* run before its own.
|
|
17
|
-
*/ function lifecycleDependencies(container, cls) {
|
|
18
|
-
return injectedTokens(cls).filter((token)=>isAppClassToken(token) && container.isBound(token));
|
|
19
|
-
}
|
|
20
|
-
/** The units for a list of classes already in dependency order, as the app runs them without providers. */ function classUnits(container, classes) {
|
|
21
|
-
return classes.map((cls)=>({
|
|
22
|
-
token: cls,
|
|
23
|
-
name: cls.name,
|
|
24
|
-
dependencies: lifecycleDependencies(container, cls)
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
13
|
|
|
28
|
-
export {
|
|
14
|
+
export { isAppClassToken };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Client } from 'discord.js';
|
|
1
2
|
import { CooldownStore } from '../common/cooldown-store.js';
|
|
2
3
|
import { CooldownError, CooldownStoreError, GuardDeniedError, MessageUsageError, UserError, ValidationError } from '../common/errors.js';
|
|
3
4
|
import { ExecutionContext } from '../common/execution-context.js';
|
|
@@ -16,6 +17,18 @@ import { ThemeCache } from './theme-resolvers.js';
|
|
|
16
17
|
ThemeCache,
|
|
17
18
|
Translator
|
|
18
19
|
];
|
|
20
|
+
/**
|
|
21
|
+
* The tokens the bot binds before an app's providers, which `@MeoCord({ providers })` cannot provide: `Translator` too
|
|
22
|
+
* when the app gives `i18n`.
|
|
23
|
+
*/ const boundBeforeProviders = (i18n)=>[
|
|
24
|
+
Client,
|
|
25
|
+
HandlerRegistry,
|
|
26
|
+
ShardContext,
|
|
27
|
+
CooldownStore,
|
|
28
|
+
...i18n ? [
|
|
29
|
+
Translator
|
|
30
|
+
] : []
|
|
31
|
+
];
|
|
19
32
|
const created = (cls)=>`create it with \`new ${cls.name}(…)\``;
|
|
20
33
|
/** What to do in place of injecting each of MeoCord's classes an app makes itself; the testing module adds its own. */ const advice = new Map([
|
|
21
34
|
[
|
|
@@ -45,4 +58,4 @@ const created = (cls)=>`create it with \`new ${cls.name}(…)\``;
|
|
|
45
58
|
advice.set(cls, instead);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
export { adviseInPlaceOfInjecting, meocordClassAdvice, meocordClasses };
|
|
61
|
+
export { adviseInPlaceOfInjecting, boundBeforeProviders, meocordClassAdvice, meocordClasses };
|
|
@@ -3,9 +3,7 @@ import { COOLDOWN_POLICY, cooldownPolicyFrom, handlerCooldowns } from './cooldow
|
|
|
3
3
|
import { Container } from 'inversify';
|
|
4
4
|
import { Client } from 'discord.js';
|
|
5
5
|
import { Logger } from '../common/logger.js';
|
|
6
|
-
import
|
|
7
|
-
import './theme-scope.js';
|
|
8
|
-
import './theme-validation.js';
|
|
6
|
+
import '../common/theme.js';
|
|
9
7
|
import '../common/decorator.js';
|
|
10
8
|
import { ExecutionContext } from '../common/execution-context.js';
|
|
11
9
|
import '../common/errors.js';
|
|
@@ -13,31 +11,34 @@ import { CooldownStore, MemoryCooldownStore } from '../common/cooldown-store.js'
|
|
|
13
11
|
import 'node:crypto';
|
|
14
12
|
import { getCommandMap, getMessageHandlers } from '../decorator/controller.decorator.js';
|
|
15
13
|
import './scalar-types.js';
|
|
14
|
+
import '../common/deprecation.js';
|
|
16
15
|
import { processShardingEnabled, shardingRole, clientOptionsWithSharding } from '../util/sharding-mode.util.js';
|
|
17
|
-
import { Translator
|
|
16
|
+
import { Translator } from '../common/translator.js';
|
|
18
17
|
import { registerClientTranslator } from '../common/meocord-text.js';
|
|
19
18
|
import '../common/response/response-state.js';
|
|
20
19
|
import { isExplainedError, markExplained } from '../common/explained-error.js';
|
|
21
|
-
import
|
|
20
|
+
import './theme-scope.js';
|
|
21
|
+
import { themeResolverClass } from './theme-resolvers.js';
|
|
22
22
|
import { MeoCordApp } from './meocord.app.js';
|
|
23
23
|
import { loadMeoCordConfig, compiledConfigMessage } from '../util/meocord-config-loader.util.js';
|
|
24
24
|
import { bunDevelopmentValues, bunDevelopmentWarning } from '../util/inherited-env.util.js';
|
|
25
25
|
import { isRegisterOnly } from '../util/registration-mode.util.js';
|
|
26
|
-
import {
|
|
26
|
+
import { prepareDirectCalls, injectedTokens, singletonContextError } from './guard-runner.js';
|
|
27
27
|
import { appStages, bindGlobalStages, prepareHandlerStages, prepareThemes, bindAppPresenter } from './handler-pipeline.js';
|
|
28
|
-
import { startupClasses, assertStartupClasses, lateGuardCheck, stageDependencies } from './startup-roots.js';
|
|
28
|
+
import { startupClasses, assertStartupClasses, lateGuardCheck, stageDependencies, perCallEventWarnings } from './startup-roots.js';
|
|
29
|
+
import { eventSources } from './event-sources.js';
|
|
29
30
|
import { markStartupChecked, assertStartupChecked } from './startup-checked.js';
|
|
30
31
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
31
32
|
import { meocordClasses } from './meocord-classes.js';
|
|
32
33
|
import { buildMessageRoutes } from './message-routes.js';
|
|
33
34
|
import { appObservers, bindObservers } from './observer-runner.js';
|
|
34
35
|
import { isAppClassToken } from './lifecycle-order.js';
|
|
35
|
-
import {
|
|
36
|
-
import { HandlerRegistry } from './handler-registry.js';
|
|
36
|
+
import { providerMap, assertProvidedServices, bindsOwnToken, appProviderRefusal, bindProvider, resolutionOrder, isClassProvider, assertProvided, tokenDependencies, tokenName, resolveProviders, isValueProvider, forgetFactoryValues } from './providers.js';
|
|
37
|
+
import { HandlerRegistry, listEventSources } from './handler-registry.js';
|
|
37
38
|
import { ShardManager } from './shard-manager.js';
|
|
38
39
|
import { warnUnregisteredCommands, warnInheritedRoutes, warnOverlappingPatterns, warnHandlersOffControllers, assertDistinctCommands } from './command-conflicts.js';
|
|
39
40
|
import { ShardContext, SHARD_CALL_KEY, shardCallHandler } from './shard-context.js';
|
|
40
|
-
import { registerClientTheme } from './theme-runtime.js';
|
|
41
|
+
import { prepareLateThemes, registerClientTheme } from './theme-runtime.js';
|
|
41
42
|
import { isRefusal, logStartupErrors, describeRefusal, allDeclaredErrors, declaredErrorsOf, collectStartupErrors, refuse, startupError, stopOnStartupErrors } from '../util/refusal.util.js';
|
|
42
43
|
import { endFailedShard } from './shard-exit.js';
|
|
43
44
|
import { isBuiltApplication } from '../util/bundle-entry.util.js';
|
|
@@ -55,8 +56,7 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
55
56
|
container.bind(cls).toSelf().inSingletonScope();
|
|
56
57
|
// By constructor type or @inject token; an interface-typed parameter records Object, which is skipped
|
|
57
58
|
for (const dep of injectedTokens(cls)){
|
|
58
|
-
if (dep
|
|
59
|
-
else if (isAppClassToken(dep)) bindDependencies(container, dep, providers);
|
|
59
|
+
if (isAppClassToken(dep)) bindDependencies(container, dep, providers);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
@@ -145,6 +145,10 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
145
145
|
markExplained(error);
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
+
// A built bot that refuses its app exits 1, as one whose start fails does, whatever main.ts does with the error
|
|
149
|
+
if (!shard && isBuiltApplication() && isRefusal(error) && (process.exitCode === undefined || process.exitCode === 0)) {
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
}
|
|
148
152
|
throw error;
|
|
149
153
|
}
|
|
150
154
|
}
|
|
@@ -176,28 +180,30 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
176
180
|
const providers = collectStartupErrors(()=>{
|
|
177
181
|
declared.forEach(startupError);
|
|
178
182
|
assertDistinctCommands(options.controllers);
|
|
179
|
-
|
|
183
|
+
const providers = providerMap(options.providers ?? [], '@MeoCord({ providers })');
|
|
184
|
+
assertProvidedServices(typeof target === 'function' ? target.name : String(target), options.services ?? [], providers, '@MeoCord({ providers })');
|
|
185
|
+
return providers;
|
|
180
186
|
});
|
|
181
187
|
// A themeFor class is bound on its own, as the cooldown store is, and resolved like a service
|
|
182
188
|
const themeResolver = themeResolverClass(options.themeFor);
|
|
183
|
-
//
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
// Every class the app runs, its stages and presenter and what they inject included
|
|
190
|
+
const stages = appStages(target);
|
|
191
|
+
const runs = startupClasses({
|
|
192
|
+
controllers: options.controllers,
|
|
193
|
+
services: options.services ?? [],
|
|
194
|
+
providers,
|
|
195
|
+
stages,
|
|
196
|
+
presenter: options.presenter,
|
|
197
|
+
cooldownStore: options.cooldownStore,
|
|
198
|
+
themeFor: options.themeFor,
|
|
199
|
+
observers: appObservers(target)
|
|
200
|
+
});
|
|
195
201
|
// A shard's manager runs the same checks, so a sharded bot warns once
|
|
196
202
|
if (!shard) {
|
|
197
203
|
warnUnregisteredCommands(options.controllers);
|
|
198
204
|
warnInheritedRoutes(options.controllers);
|
|
199
205
|
warnOverlappingPatterns(options.controllers);
|
|
200
|
-
warnHandlersOffControllers(options.controllers,
|
|
206
|
+
warnHandlersOffControllers(options.controllers, runs.classes);
|
|
201
207
|
const developmentEnv = bunDevelopmentValues();
|
|
202
208
|
if (developmentEnv.keys.length > 0) {
|
|
203
209
|
this.logger.warn(bunDevelopmentWarning(developmentEnv));
|
|
@@ -218,19 +224,8 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
218
224
|
}
|
|
219
225
|
// The checks of what the app binds and runs report every error each finds, and the first is thrown
|
|
220
226
|
return collectStartupErrors(()=>{
|
|
221
|
-
// Every class the app runs
|
|
222
|
-
//
|
|
223
|
-
const stages = appStages(target);
|
|
224
|
-
const runs = startupClasses({
|
|
225
|
-
controllers: options.controllers,
|
|
226
|
-
services: options.services ?? [],
|
|
227
|
-
providers,
|
|
228
|
-
stages,
|
|
229
|
-
presenter: options.presenter,
|
|
230
|
-
cooldownStore: options.cooldownStore,
|
|
231
|
-
themeFor: options.themeFor,
|
|
232
|
-
observers: appObservers(target)
|
|
233
|
-
});
|
|
227
|
+
// Every class the app runs checked before binding, where inversify would otherwise fail first with an error about
|
|
228
|
+
// compiler options, or a guard only at its first call
|
|
234
229
|
assertStartupClasses(runs, {
|
|
235
230
|
translator: options.i18n !== undefined || providers.has(Translator)
|
|
236
231
|
});
|
|
@@ -255,7 +250,8 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
255
250
|
}
|
|
256
251
|
// Bound before the app's classes, so a class that injects it gets this instance; filled once they are bound
|
|
257
252
|
const appClasses = [];
|
|
258
|
-
|
|
253
|
+
const registry = new HandlerRegistry(appClasses, options.messages, ()=>options.i18n, options.controllers);
|
|
254
|
+
container.bind(HandlerRegistry).toConstantValue(registry);
|
|
259
255
|
container.bind(ShardContext).toDynamicValue(()=>{
|
|
260
256
|
const client = container.get(Client);
|
|
261
257
|
return new ShardContext(client, (service, method, args)=>Reflect.get(client, SHARD_CALL_KEY)(service, method, args));
|
|
@@ -275,12 +271,9 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
275
271
|
// After MeoCord's own tokens, which a provider may not replace, and before the app's classes, so
|
|
276
272
|
// a class token that is provided is not also bound as itself
|
|
277
273
|
for (const [token, provider] of providers){
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
if (container.isBound(token)) {
|
|
283
|
-
startupError(new Error(`${target.name}: @MeoCord({ providers }) cannot provide ${tokenName(token)}, which MeoCord binds itself.`));
|
|
274
|
+
const refusal = appProviderRefusal(`${target.name}`, token, (own)=>container.isBound(own), this.logger);
|
|
275
|
+
if (refusal) {
|
|
276
|
+
startupError(refusal);
|
|
284
277
|
continue;
|
|
285
278
|
}
|
|
286
279
|
bindProvider(container, provider, (cls)=>bindDependencies(container, cls, providers));
|
|
@@ -365,26 +358,35 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
365
358
|
buildMessageRoutes(options.controllers, options.messages);
|
|
366
359
|
// Every check has run, and touched only the container: from here the app is made, once none found an error
|
|
367
360
|
stopOnStartupErrors();
|
|
361
|
+
for (const warning of perCallEventWarnings(runs, providers, options.presenter))this.logger.warn(warning);
|
|
368
362
|
const discordClient = container.get(Client);
|
|
369
363
|
Reflect.set(discordClient, SHARD_CALL_KEY, shardCallHandler(container, ()=>callable, 'this app'));
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
Reflect.defineMetadata(META.container, container, cls);
|
|
373
|
-
}
|
|
374
|
-
recordInstanceContainers(container, runs.classes);
|
|
364
|
+
// Every class the app runs, so @UseGuard resolves guards on a direct call to any of them
|
|
365
|
+
prepareDirectCalls(container, runs.classes);
|
|
375
366
|
prepareThemes(container, appClasses);
|
|
376
367
|
// Run by start() before it logs in: what may inject a provided value is resolved once every factory
|
|
377
368
|
// has made its value, including those that return a promise
|
|
378
369
|
const logger = this.logger;
|
|
370
|
+
let sources;
|
|
379
371
|
const startup = async (control)=>{
|
|
372
|
+
let made;
|
|
380
373
|
try {
|
|
381
|
-
await resolveProviders(container, providers, order, control);
|
|
374
|
+
made = await resolveProviders(container, providers, order, control);
|
|
382
375
|
} catch (error) {
|
|
383
376
|
logger.error(`${error.message}. The bot cannot start without it.`);
|
|
384
377
|
logger.debug('Provider failure:', error.cause);
|
|
385
378
|
markExplained(error);
|
|
386
379
|
throw error;
|
|
387
380
|
}
|
|
381
|
+
// A stop came while the providers were being made: what follows resolves provided values, which would make the
|
|
382
|
+
// factories it left unmade
|
|
383
|
+
if (!made) return;
|
|
384
|
+
// Read once every provided value is made, and before anything the app makes could list its handlers
|
|
385
|
+
if (!sources) {
|
|
386
|
+
sources = eventSources(container, providers, lifecycle, appClasses, (text)=>logger.warn(text));
|
|
387
|
+
listEventSources(registry, sources);
|
|
388
|
+
prepareLateThemes(container, sources.map(({ cls })=>cls));
|
|
389
|
+
}
|
|
388
390
|
// The listed services are made now, so constructors that attach listeners or connect run before login; a stop
|
|
389
391
|
// meanwhile makes no more of them
|
|
390
392
|
for (const svc of options.services ?? []){
|
|
@@ -399,11 +401,38 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
399
401
|
registerClientTheme(discordClient, container);
|
|
400
402
|
registerClientTranslator(discordClient, options.i18n);
|
|
401
403
|
};
|
|
402
|
-
return new MeoCordApp(options.controllers, container, discordClient, meocordConfig.discordToken, options.activities,
|
|
403
|
-
options.warnUnanswered ?? process.env.NODE_ENV === 'development', shard);
|
|
404
|
+
return new MeoCordApp(options.controllers, container, discordClient, meocordConfig.discordToken, options.activities, ()=>sources ?? [], meocordConfig.shutdownTimeout, startup, lifecycle, options.messages, // In development only, unless the app says otherwise
|
|
405
|
+
options.warnUnanswered ?? process.env.NODE_ENV === 'development', shard, startAttempt(container, lifecycle, providers));
|
|
404
406
|
});
|
|
405
407
|
}
|
|
406
408
|
}
|
|
407
409
|
MeoCordFactory.logger = new Logger();
|
|
410
|
+
/**
|
|
411
|
+
* What the app's starts make: each lifecycle unit is recorded as the container constructs it, as the testing module
|
|
412
|
+
* records them, and a container snapshot marks where a start begins, so a failed start drops every singleton it made,
|
|
413
|
+
* the values factory providers keep included.
|
|
414
|
+
*/ function startAttempt(container, lifecycle, providers) {
|
|
415
|
+
const constructed = new Set();
|
|
416
|
+
for (const { token } of lifecycle){
|
|
417
|
+
container.onActivation(token, (_context, instance)=>{
|
|
418
|
+
constructed.add(token);
|
|
419
|
+
return instance;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
const drop = ()=>{
|
|
423
|
+
container.restore();
|
|
424
|
+
forgetFactoryValues(container);
|
|
425
|
+
};
|
|
426
|
+
const given = new Set(lifecycle.map(({ token })=>token).filter((token)=>{
|
|
427
|
+
const provider = providers.get(token);
|
|
428
|
+
return provider !== undefined && isValueProvider(provider);
|
|
429
|
+
}));
|
|
430
|
+
return {
|
|
431
|
+
constructed,
|
|
432
|
+
given,
|
|
433
|
+
begin: ()=>container.snapshot(),
|
|
434
|
+
drop
|
|
435
|
+
};
|
|
436
|
+
}
|
|
408
437
|
|
|
409
438
|
export { MeoCordFactory };
|