meocord 4.1.1 → 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 +104 -0
- package/README.md +23 -23
- 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 +736 -307
- 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 +226 -78
- 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
package/dist/cjs/core/index.cjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
require('reflect-metadata');
|
|
4
|
-
var providers = require('../_shared/providers-
|
|
4
|
+
var providers = require('../_shared/providers-ChvMqnLd.cjs');
|
|
5
5
|
var inversify = require('inversify');
|
|
6
6
|
var discord_js = require('discord.js');
|
|
7
|
-
var controller_decorator = require('../_shared/controller.decorator-
|
|
7
|
+
var controller_decorator = require('../_shared/controller.decorator-CRn9kWob.cjs');
|
|
8
8
|
require('node:crypto');
|
|
9
|
-
var shardedCooldownStore = require('../_shared/sharded-cooldown-store-
|
|
9
|
+
var shardedCooldownStore = require('../_shared/sharded-cooldown-store-BdAUTdYs.cjs');
|
|
10
10
|
var path$1 = require('path');
|
|
11
|
-
var startupRoots = require('../_shared/startup-roots-
|
|
11
|
+
var startupRoots = require('../_shared/startup-roots-CQo8mSP8.cjs');
|
|
12
12
|
var enum_index = require('../enum/index.cjs');
|
|
13
13
|
var node_fs = require('node:fs');
|
|
14
14
|
var path = require('node:path');
|
|
@@ -23,7 +23,7 @@ require('dayjs/plugin/utc.js');
|
|
|
23
23
|
require('dayjs/plugin/timezone.js');
|
|
24
24
|
require('module');
|
|
25
25
|
require('chalk');
|
|
26
|
-
require('../_shared/translate-error-
|
|
26
|
+
require('../_shared/translate-error-DhDVbT5M.cjs');
|
|
27
27
|
|
|
28
28
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
29
29
|
|
|
@@ -1510,14 +1510,14 @@ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
1510
1510
|
*/ function bindDependencies(container, cls, providers$1) {
|
|
1511
1511
|
// A provided class is bound by its own provider, wherever in the list that provider comes
|
|
1512
1512
|
if (container.isBound(cls) || providers$1.has(cls)) return;
|
|
1513
|
-
if (providers.injectedTokens(cls).includes(controller_decorator.ExecutionContext))
|
|
1513
|
+
if (providers.injectedTokens(cls).includes(controller_decorator.ExecutionContext)) return controller_decorator.startupError(providers.singletonContextError(cls));
|
|
1514
1514
|
providers.assertStartupChecked(container, cls);
|
|
1515
1515
|
providers.makeInjectable(cls);
|
|
1516
1516
|
container.bind(cls).toSelf().inSingletonScope();
|
|
1517
1517
|
// By constructor type or @inject token; an interface-typed parameter records Object, which is skipped
|
|
1518
1518
|
for (const dep of providers.injectedTokens(cls)){
|
|
1519
|
-
if (dep === controller_decorator.Translator && !container.isBound(controller_decorator.Translator))
|
|
1520
|
-
if (providers.isAppClassToken(dep)) bindDependencies(container, dep, providers$1);
|
|
1519
|
+
if (dep === controller_decorator.Translator && !container.isBound(controller_decorator.Translator)) controller_decorator.startupError(controller_decorator.missingTranslatorError(cls));
|
|
1520
|
+
else if (providers.isAppClassToken(dep)) bindDependencies(container, dep, providers$1);
|
|
1521
1521
|
}
|
|
1522
1522
|
}
|
|
1523
1523
|
/**
|
|
@@ -1583,34 +1583,51 @@ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
1583
1583
|
* load, naming the file and the reason, with what to do about it, when a provider cannot
|
|
1584
1584
|
* be bound, such as one for a token MeoCord binds itself, when two handlers take one command, or two builder
|
|
1585
1585
|
* classes build one, naming both, and for any other mistake it refuses as the app loads, such as two component
|
|
1586
|
-
* patterns that match the same customIds.
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1586
|
+
* patterns that match the same customIds. Each check reports every error it finds, and the first is thrown,
|
|
1587
|
+
* unchanged. When there are several, each is logged first, with where in the source it comes from; a lone one is
|
|
1588
|
+
* logged in a built application. Either way `isExplainedError()` then tells a caller not to log it again; in a
|
|
1589
|
+
* shard of process sharding, the manager logs it. With `startupErrors: 'all'` in `meocord.config.ts`, the errors
|
|
1590
|
+
* decorators kept are among them.
|
|
1589
1591
|
*/ static create(target) {
|
|
1590
1592
|
try {
|
|
1591
1593
|
return this.createApplication(target);
|
|
1592
1594
|
} catch (error) {
|
|
1593
1595
|
// A shard ends, so its manager restarts it, or stops every shard for a refusal, which it logs
|
|
1594
1596
|
if (shardedCooldownStore.isShardProcess()) endFailedShard(error);
|
|
1595
|
-
else if (controller_decorator.
|
|
1596
|
-
|
|
1597
|
-
controller_decorator.markExplained(error);
|
|
1597
|
+
else if (controller_decorator.isRefusal(error) && !controller_decorator.isExplainedError(error)) {
|
|
1598
|
+
// Every startup error found together is logged, in a test too; a lone one is logged in a built application
|
|
1599
|
+
if (controller_decorator.logStartupErrors(error, (text)=>this.logger.error(text), 'the bot did not start')) controller_decorator.markExplained(error);
|
|
1600
|
+
else if (controller_decorator.isBuiltApplication()) {
|
|
1601
|
+
this.logger.error(controller_decorator.describeRefusal(error, process.cwd()));
|
|
1602
|
+
controller_decorator.markExplained(error);
|
|
1603
|
+
}
|
|
1598
1604
|
}
|
|
1599
1605
|
throw error;
|
|
1600
1606
|
}
|
|
1601
1607
|
}
|
|
1602
1608
|
static createApplication(target) {
|
|
1603
1609
|
const options = Reflect.getMetadata(controller_decorator.META.appOptions, target);
|
|
1610
|
+
// With startupErrors: 'all', what the decorators kept: a built bot's every one, else the app's and its controllers'
|
|
1611
|
+
const declared = controller_decorator.isBuiltApplication() ? controller_decorator.allDeclaredErrors() : controller_decorator.declaredErrorsOf([
|
|
1612
|
+
target,
|
|
1613
|
+
...options?.controllers ?? []
|
|
1614
|
+
]);
|
|
1604
1615
|
if (!options) {
|
|
1616
|
+
// A refused @MeoCord stores no options, so its own errors say why
|
|
1617
|
+
controller_decorator.collectStartupErrors(()=>declared.forEach(controller_decorator.startupError));
|
|
1605
1618
|
throw controller_decorator.refuse(new Error(`${typeof target === 'function' ? target.name : String(target)}: not decorated with @MeoCord(), so there is no app to create.`));
|
|
1606
1619
|
}
|
|
1607
1620
|
const meocordConfig = controller_decorator.loadMeoCordConfig();
|
|
1608
1621
|
if (!meocordConfig) {
|
|
1609
1622
|
throw controller_decorator.refuse(new Error(controller_decorator.compiledConfigMessage()));
|
|
1610
1623
|
}
|
|
1611
|
-
// Before any of the three ways a bot runs, so none registers or dispatches a command only one handler could take
|
|
1612
|
-
|
|
1613
|
-
const providers$1 =
|
|
1624
|
+
// Before any of the three ways a bot runs, so none registers or dispatches a command only one handler could take; every
|
|
1625
|
+
// error each check finds is reported, the first thrown
|
|
1626
|
+
const providers$1 = controller_decorator.collectStartupErrors(()=>{
|
|
1627
|
+
declared.forEach(controller_decorator.startupError);
|
|
1628
|
+
startupRoots.assertDistinctCommands(options.controllers);
|
|
1629
|
+
return providers.providerMap(options.providers ?? [], '@MeoCord({ providers })');
|
|
1630
|
+
});
|
|
1614
1631
|
// A themeFor class is bound on its own, as the cooldown store is, and resolved like a service
|
|
1615
1632
|
const themeResolver = controller_decorator.themeResolverClass(options.themeFor);
|
|
1616
1633
|
// The app's classes and every class they inject, which the container binds
|
|
@@ -1652,153 +1669,174 @@ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
1652
1669
|
config: meocordConfig
|
|
1653
1670
|
});
|
|
1654
1671
|
}
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
container.bind(providers.COOLDOWN_POLICY).toConstantValue(providers.cooldownPolicyFrom(options));
|
|
1692
|
-
// A store of the app's own is resolved like a service, so it can inject its client
|
|
1693
|
-
if (options.cooldownStore) {
|
|
1694
|
-
bindDependencies(container, options.cooldownStore, providers$1);
|
|
1695
|
-
container.bind(controller_decorator.CooldownStore).toService(options.cooldownStore);
|
|
1696
|
-
} else {
|
|
1697
|
-
container.bind(controller_decorator.CooldownStore).toConstantValue(new controller_decorator.MemoryCooldownStore());
|
|
1698
|
-
}
|
|
1699
|
-
// A class that injects the token depends on the app's store, the one unit whose hooks run
|
|
1700
|
-
providers.bindsOwnToken(container, controller_decorator.CooldownStore, options.cooldownStore);
|
|
1701
|
-
// After MeoCord's own tokens, which a provider may not replace, and before the app's classes, so
|
|
1702
|
-
// a class token that is provided is not also bound as itself
|
|
1703
|
-
for (const [token, provider] of providers$1){
|
|
1704
|
-
if (container.isBound(token)) {
|
|
1705
|
-
throw controller_decorator.refuse(new Error(`${target.name}: @MeoCord({ providers }) cannot provide ${providers.tokenName(token)}, which MeoCord binds itself.`));
|
|
1672
|
+
// The checks of what the app binds and runs report every error each finds, and the first is thrown
|
|
1673
|
+
return controller_decorator.collectStartupErrors(()=>{
|
|
1674
|
+
// Every class the app runs, its stages and presenter included, checked before binding, where inversify would
|
|
1675
|
+
// otherwise fail first with an error about compiler options, or a guard only at its first call
|
|
1676
|
+
const stages = providers.appStages(target);
|
|
1677
|
+
const runs = startupRoots.startupClasses({
|
|
1678
|
+
controllers: options.controllers,
|
|
1679
|
+
services: options.services ?? [],
|
|
1680
|
+
providers: providers$1,
|
|
1681
|
+
stages,
|
|
1682
|
+
presenter: options.presenter,
|
|
1683
|
+
cooldownStore: options.cooldownStore,
|
|
1684
|
+
themeFor: options.themeFor,
|
|
1685
|
+
observers: providers.appObservers(target)
|
|
1686
|
+
});
|
|
1687
|
+
startupRoots.assertStartupClasses(runs, {
|
|
1688
|
+
translator: options.i18n !== undefined || providers$1.has(controller_decorator.Translator)
|
|
1689
|
+
});
|
|
1690
|
+
// Outside a built application, the kept errors of the stages and other classes the app's classes reach
|
|
1691
|
+
if (!controller_decorator.isBuiltApplication()) controller_decorator.declaredErrorsOf(runs.classes).forEach(controller_decorator.startupError);
|
|
1692
|
+
// The checks of the wiring below run on classes that passed these, so a mistake in one is not reported again there
|
|
1693
|
+
controller_decorator.stopOnStartupErrors();
|
|
1694
|
+
// The checks bind into this container and change nothing else: what they bind is made once all have passed
|
|
1695
|
+
const container = new inversify.Container();
|
|
1696
|
+
const translator = options.i18n !== undefined || providers$1.has(controller_decorator.Translator);
|
|
1697
|
+
providers.markStartupChecked(container, [
|
|
1698
|
+
...runs.classes,
|
|
1699
|
+
...providers.meocordClasses()
|
|
1700
|
+
], startupRoots.lateGuardCheck(providers$1, translator));
|
|
1701
|
+
providers.bindGlobalStages(container, stages);
|
|
1702
|
+
// Bound now, so no provider stands in for it; made once the checks pass, the one Client the app logs in with
|
|
1703
|
+
const clientOptions = shardedCooldownStore.clientOptionsWithSharding(this.effectiveConfig(meocordConfig), options.clientOptions);
|
|
1704
|
+
container.bind(discord_js.Client).toDynamicValue(()=>new discord_js.Client(clientOptions)).inSingletonScope();
|
|
1705
|
+
if (options.i18n) {
|
|
1706
|
+
container.bind(controller_decorator.Translator).toConstantValue(options.i18n);
|
|
1707
|
+
providers.bindsOwnToken(container, controller_decorator.Translator);
|
|
1706
1708
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
});
|
|
1733
|
-
appClasses.push(...order.filter((token)=>{
|
|
1734
|
-
const provider = providers$1.get(token);
|
|
1735
|
-
return providers.isAppClassToken(token) && (!provider || providers.isClassProvider(provider) && provider.useClass === token);
|
|
1736
|
-
}));
|
|
1737
|
-
providers.assertProvided(container, providers$1, runs.classes, '@MeoCord({ providers })');
|
|
1738
|
-
// The store first, after only what it injects: it is ready before anything a call reaches, and shuts down last
|
|
1739
|
-
const store = options.cooldownStore;
|
|
1740
|
-
const lifecycle = (store ? providers.resolutionOrder(container, providers$1, [
|
|
1741
|
-
store,
|
|
1742
|
-
...order
|
|
1743
|
-
]) : order).map((token)=>({
|
|
1744
|
-
token,
|
|
1745
|
-
name: providers.tokenName(token),
|
|
1746
|
-
dependencies: providers.tokenDependencies(container, providers$1, token),
|
|
1747
|
-
...token === store && {
|
|
1748
|
-
cooldownStore: true
|
|
1749
|
-
}
|
|
1750
|
-
}));
|
|
1751
|
-
// What ShardContext.call reaches: the app's classes, and the classes its providers stand in for
|
|
1752
|
-
const callable = [
|
|
1753
|
-
...new Set([
|
|
1754
|
-
...appClasses,
|
|
1755
|
-
...[
|
|
1756
|
-
...providers$1.keys()
|
|
1757
|
-
].filter(providers.isAppClassToken)
|
|
1758
|
-
])
|
|
1759
|
-
];
|
|
1760
|
-
// From another shard a call names its class, so with process sharding every name it can reach is one class's
|
|
1761
|
-
if (meocordConfig.sharding?.mode === 'process') {
|
|
1762
|
-
const names = new Set();
|
|
1763
|
-
for (const cls of callable){
|
|
1764
|
-
if (names.has(cls.name)) {
|
|
1765
|
-
throw controller_decorator.refuse(new Error(`${cls.name}: two classes have this name; with process sharding, ShardContext.call finds a class in another ` + 'shard by its name, so give each controller, service and provided class a distinct name.'));
|
|
1709
|
+
// Bound before the app's classes, so a class that injects it gets this instance; filled once they are bound
|
|
1710
|
+
const appClasses = [];
|
|
1711
|
+
container.bind(providers.HandlerRegistry).toConstantValue(new providers.HandlerRegistry(appClasses, options.messages, ()=>options.i18n));
|
|
1712
|
+
container.bind(providers.ShardContext).toDynamicValue(()=>{
|
|
1713
|
+
const client = container.get(discord_js.Client);
|
|
1714
|
+
return new providers.ShardContext(client, (service, method, args)=>Reflect.get(client, providers.SHARD_CALL_KEY)(service, method, args));
|
|
1715
|
+
}).inSingletonScope();
|
|
1716
|
+
providers.bindsOwnToken(container, providers.HandlerRegistry);
|
|
1717
|
+
providers.bindsOwnToken(container, providers.ShardContext);
|
|
1718
|
+
container.bind(providers.COOLDOWN_POLICY).toConstantValue(providers.cooldownPolicyFrom(options));
|
|
1719
|
+
// A store of the app's own is resolved like a service, so it can inject its client
|
|
1720
|
+
if (options.cooldownStore) {
|
|
1721
|
+
bindDependencies(container, options.cooldownStore, providers$1);
|
|
1722
|
+
container.bind(controller_decorator.CooldownStore).toService(options.cooldownStore);
|
|
1723
|
+
} else {
|
|
1724
|
+
container.bind(controller_decorator.CooldownStore).toConstantValue(new controller_decorator.MemoryCooldownStore());
|
|
1725
|
+
}
|
|
1726
|
+
// A class that injects the token depends on the app's store, the one unit whose hooks run
|
|
1727
|
+
providers.bindsOwnToken(container, controller_decorator.CooldownStore, options.cooldownStore);
|
|
1728
|
+
// After MeoCord's own tokens, which a provider may not replace, and before the app's classes, so
|
|
1729
|
+
// a class token that is provided is not also bound as itself
|
|
1730
|
+
for (const [token, provider] of providers$1){
|
|
1731
|
+
if (container.isBound(token)) {
|
|
1732
|
+
controller_decorator.startupError(new Error(`${target.name}: @MeoCord({ providers }) cannot provide ${providers.tokenName(token)}, which MeoCord binds itself.`));
|
|
1733
|
+
continue;
|
|
1766
1734
|
}
|
|
1767
|
-
|
|
1735
|
+
providers.bindProvider(container, provider, (cls)=>bindDependencies(container, cls, providers$1));
|
|
1768
1736
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
for (const cls of runs.classes){
|
|
1773
|
-
Reflect.defineMetadata(controller_decorator.META.container, container, cls);
|
|
1774
|
-
}
|
|
1775
|
-
providers.prepareHandlerStages(container, appClasses);
|
|
1776
|
-
providers.bindObservers(container, observers);
|
|
1777
|
-
// Run by start() before it logs in: what may inject a provided value is resolved once every factory
|
|
1778
|
-
// has made its value, including those that return a promise
|
|
1779
|
-
const logger = this.logger;
|
|
1780
|
-
const startup = async ()=>{
|
|
1781
|
-
try {
|
|
1782
|
-
await providers.resolveProviders(container, providers$1, order);
|
|
1783
|
-
} catch (error) {
|
|
1784
|
-
logger.error(`${error.message}. The bot cannot start without it.`);
|
|
1785
|
-
logger.debug('Provider failure:', error.cause);
|
|
1786
|
-
controller_decorator.markExplained(error);
|
|
1787
|
-
throw error;
|
|
1737
|
+
// Bind all controllers and their transitive dependencies
|
|
1738
|
+
for (const ctrl of options.controllers){
|
|
1739
|
+
bindDependencies(container, ctrl, providers$1);
|
|
1788
1740
|
}
|
|
1789
|
-
// The listed services are made now, so constructors that attach listeners or connect run before login
|
|
1790
1741
|
for (const svc of options.services ?? []){
|
|
1791
|
-
container
|
|
1742
|
+
bindDependencies(container, svc, providers$1);
|
|
1743
|
+
}
|
|
1744
|
+
if (themeResolver) bindDependencies(container, themeResolver, providers$1);
|
|
1745
|
+
// Observers are services too: bound here so their lifecycle hooks run in dependency order
|
|
1746
|
+
const observers = providers.appObservers(target);
|
|
1747
|
+
for (const observer of observers)bindDependencies(container, observer, providers$1);
|
|
1748
|
+
// What follows walks what these bound, so a class refused here is not reported again as missing
|
|
1749
|
+
controller_decorator.stopOnStartupErrors();
|
|
1750
|
+
// Providers first, then the services, the controllers and the observers, each after what it depends on. The app's
|
|
1751
|
+
// own classes, which a class injecting CooldownStore does not make of the store
|
|
1752
|
+
const order = providers.resolutionOrder(container, providers$1, [
|
|
1753
|
+
...providers$1.keys(),
|
|
1754
|
+
...options.services ?? [],
|
|
1755
|
+
...themeResolver ? [
|
|
1756
|
+
themeResolver
|
|
1757
|
+
] : [],
|
|
1758
|
+
...options.controllers,
|
|
1759
|
+
...observers
|
|
1760
|
+
], {
|
|
1761
|
+
followOwnTokens: false
|
|
1762
|
+
});
|
|
1763
|
+
appClasses.push(...order.filter((token)=>{
|
|
1764
|
+
const provider = providers$1.get(token);
|
|
1765
|
+
return providers.isAppClassToken(token) && (!provider || providers.isClassProvider(provider) && provider.useClass === token);
|
|
1766
|
+
}));
|
|
1767
|
+
providers.assertProvided(container, providers$1, runs.classes, '@MeoCord({ providers })');
|
|
1768
|
+
// The store first, after only what it injects: it is ready before anything a call reaches, and shuts down last
|
|
1769
|
+
const store = options.cooldownStore;
|
|
1770
|
+
const lifecycle = (store ? providers.resolutionOrder(container, providers$1, [
|
|
1771
|
+
store,
|
|
1772
|
+
...order
|
|
1773
|
+
]) : order).map((token)=>({
|
|
1774
|
+
token,
|
|
1775
|
+
name: providers.tokenName(token),
|
|
1776
|
+
dependencies: providers.tokenDependencies(container, providers$1, token),
|
|
1777
|
+
...token === store && {
|
|
1778
|
+
cooldownStore: true
|
|
1779
|
+
}
|
|
1780
|
+
}));
|
|
1781
|
+
// What ShardContext.call reaches: the app's classes, and the classes its providers stand in for
|
|
1782
|
+
const callable = [
|
|
1783
|
+
...new Set([
|
|
1784
|
+
...appClasses,
|
|
1785
|
+
...[
|
|
1786
|
+
...providers$1.keys()
|
|
1787
|
+
].filter(providers.isAppClassToken)
|
|
1788
|
+
])
|
|
1789
|
+
];
|
|
1790
|
+
// From another shard a call names its class, so with process sharding every name it can reach is one class's
|
|
1791
|
+
if (meocordConfig.sharding?.mode === 'process') {
|
|
1792
|
+
const names = new Set();
|
|
1793
|
+
for (const cls of callable){
|
|
1794
|
+
if (names.has(cls.name)) {
|
|
1795
|
+
controller_decorator.startupError(new Error(`${cls.name}: two classes have this name; with process sharding, ShardContext.call finds a class in another ` + 'shard by its name, so give each controller, service and provided class a distinct name.'));
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
names.add(cls.name);
|
|
1799
|
+
}
|
|
1792
1800
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1801
|
+
providers.prepareHandlerStages(container, appClasses);
|
|
1802
|
+
providers.bindObservers(container, observers);
|
|
1803
|
+
// Read here with the rest, so a pattern that cannot be read is reported with them, before the dispatcher warns
|
|
1804
|
+
providers.buildMessageRoutes(options.controllers, options.messages);
|
|
1805
|
+
// Every check has run, and touched only the container: from here the app is made, once none found an error
|
|
1806
|
+
controller_decorator.stopOnStartupErrors();
|
|
1807
|
+
const discordClient = container.get(discord_js.Client);
|
|
1808
|
+
Reflect.set(discordClient, providers.SHARD_CALL_KEY, providers.shardCallHandler(container, ()=>callable, 'this app'));
|
|
1809
|
+
// Stamp every class the app runs with the container, so @UseGuard resolves guards on a direct call to any of them
|
|
1810
|
+
for (const cls of runs.classes){
|
|
1811
|
+
Reflect.defineMetadata(controller_decorator.META.container, container, cls);
|
|
1795
1812
|
}
|
|
1796
|
-
providers.
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1813
|
+
providers.prepareThemes(container, appClasses);
|
|
1814
|
+
// Run by start() before it logs in: what may inject a provided value is resolved once every factory
|
|
1815
|
+
// has made its value, including those that return a promise
|
|
1816
|
+
const logger = this.logger;
|
|
1817
|
+
const startup = async ()=>{
|
|
1818
|
+
try {
|
|
1819
|
+
await providers.resolveProviders(container, providers$1, order);
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
logger.error(`${error.message}. The bot cannot start without it.`);
|
|
1822
|
+
logger.debug('Provider failure:', error.cause);
|
|
1823
|
+
controller_decorator.markExplained(error);
|
|
1824
|
+
throw error;
|
|
1825
|
+
}
|
|
1826
|
+
// The listed services are made now, so constructors that attach listeners or connect run before login
|
|
1827
|
+
for (const svc of options.services ?? []){
|
|
1828
|
+
container.get(svc);
|
|
1829
|
+
}
|
|
1830
|
+
if (shardedCooldownStore.shardingRole(meocordConfig) === 'shard' && container.get(controller_decorator.CooldownStore) instanceof controller_decorator.MemoryCooldownStore) {
|
|
1831
|
+
warnPerShardCooldowns(options.controllers, logger);
|
|
1832
|
+
}
|
|
1833
|
+
providers.bindAppPresenter(container, target, discordClient);
|
|
1834
|
+
providers.registerClientTheme(discordClient, container);
|
|
1835
|
+
controller_decorator.registerClientTranslator(discordClient, options.i18n);
|
|
1836
|
+
};
|
|
1837
|
+
return new MeoCordApp(options.controllers, container, discordClient, meocordConfig.discordToken, options.activities, appClasses, meocordConfig.shutdownTimeout, startup, lifecycle, options.messages, // In development only, unless the app says otherwise
|
|
1838
|
+
options.warnUnanswered ?? process.env.NODE_ENV === 'development');
|
|
1839
|
+
});
|
|
1802
1840
|
}
|
|
1803
1841
|
}
|
|
1804
1842
|
MeoCordFactory.logger = new controller_decorator.Logger();
|