meocord 4.2.2 → 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.
Files changed (136) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
  3. package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
  4. package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
  5. package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
  6. package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
  7. package/dist/cjs/common/index.cjs +4 -104
  8. package/dist/cjs/core/index.cjs +432 -181
  9. package/dist/cjs/decorator/index.cjs +85 -120
  10. package/dist/cjs/testing/index.cjs +1664 -784
  11. package/dist/cli.json +1 -1
  12. package/dist/esm/bin/app-template/package.json.template +9 -9
  13. package/dist/esm/bin/app-template/src/main.ts.template +3 -2
  14. package/dist/esm/bin/generator.js +6 -4
  15. package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
  16. package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
  17. package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
  18. package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
  19. package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
  20. package/dist/esm/bin/helper/service-generator.helper.js +6 -4
  21. package/dist/esm/bin/meocord.js +106 -48
  22. package/dist/esm/build/native-addons.js +75 -19
  23. package/dist/esm/build/rsbuild-config.js +11 -3
  24. package/dist/esm/build/typed-externals.js +15 -2
  25. package/dist/esm/common/cooldown-store.js +38 -14
  26. package/dist/esm/common/deprecation.js +4 -1
  27. package/dist/esm/common/execution-context.js +3 -1
  28. package/dist/esm/common/explained-error.js +7 -3
  29. package/dist/esm/common/log-level.js +13 -9
  30. package/dist/esm/common/logger.js +4 -2
  31. package/dist/esm/common/meocord-text.js +11 -6
  32. package/dist/esm/common/redis-cooldown-store.js +3 -7
  33. package/dist/esm/common/response/flags.js +17 -1
  34. package/dist/esm/common/response/install-context.js +27 -16
  35. package/dist/esm/common/response/presenter.js +7 -3
  36. package/dist/esm/common/response/response-state.js +27 -5
  37. package/dist/esm/common/sharded-cooldown-store.js +11 -9
  38. package/dist/esm/common/theme.js +3 -1
  39. package/dist/esm/common/translator.js +17 -8
  40. package/dist/esm/core/autocomplete-routes.js +26 -0
  41. package/dist/esm/core/command-conflicts.js +15 -7
  42. package/dist/esm/core/command-registration.js +15 -1
  43. package/dist/esm/core/cooldown-runner.js +7 -9
  44. package/dist/esm/core/defer.js +2 -5
  45. package/dist/esm/core/dispatcher.js +83 -58
  46. package/dist/esm/core/event-requirements.js +67 -19
  47. package/dist/esm/core/event-sources.js +100 -0
  48. package/dist/esm/core/fallback.js +3 -1
  49. package/dist/esm/core/filter-runner.js +4 -4
  50. package/dist/esm/core/guard-runner.js +173 -52
  51. package/dist/esm/core/handler-input.js +24 -3
  52. package/dist/esm/core/handler-pipeline.js +44 -28
  53. package/dist/esm/core/handler-registry.js +51 -21
  54. package/dist/esm/core/input-runner.js +7 -8
  55. package/dist/esm/core/interceptor-runner.js +4 -5
  56. package/dist/esm/core/lifecycle-hooks.js +3 -8
  57. package/dist/esm/core/lifecycle-order.js +1 -15
  58. package/dist/esm/core/meocord-classes.js +14 -1
  59. package/dist/esm/core/meocord-factory.js +80 -53
  60. package/dist/esm/core/meocord.app.js +174 -89
  61. package/dist/esm/core/message-help.js +5 -9
  62. package/dist/esm/core/message-params.js +126 -46
  63. package/dist/esm/core/message-routes.js +225 -45
  64. package/dist/esm/core/message-words.js +30 -2
  65. package/dist/esm/core/observer-runner.js +2 -1
  66. package/dist/esm/core/process-state.js +19 -0
  67. package/dist/esm/core/providers.js +28 -3
  68. package/dist/esm/core/scalar-types.js +57 -16
  69. package/dist/esm/core/shard-context.js +11 -10
  70. package/dist/esm/core/shard-exit.js +3 -3
  71. package/dist/esm/core/shard-manager.js +16 -6
  72. package/dist/esm/core/startup-roots.js +28 -8
  73. package/dist/esm/core/theme-resolvers.js +26 -11
  74. package/dist/esm/core/theme-runtime.js +23 -13
  75. package/dist/esm/core/theme-scope.js +27 -29
  76. package/dist/esm/core/theme-validation.js +1 -1
  77. package/dist/esm/decorator/app.decorator.js +6 -1
  78. package/dist/esm/decorator/command-builder.decorator.js +14 -0
  79. package/dist/esm/decorator/controller-class.decorator.js +1 -1
  80. package/dist/esm/decorator/controller.decorator.js +48 -19
  81. package/dist/esm/decorator/defer.decorator.js +2 -2
  82. package/dist/esm/decorator/event.decorator.js +4 -5
  83. package/dist/esm/decorator/filter.decorator.js +2 -2
  84. package/dist/esm/decorator/guard.decorator.js +4 -61
  85. package/dist/esm/decorator/inject.decorator.js +4 -1
  86. package/dist/esm/decorator/interceptor.decorator.js +2 -2
  87. package/dist/esm/decorator/theme.decorator.js +4 -3
  88. package/dist/esm/decorator/validation.decorator.js +5 -5
  89. package/dist/esm/package.json.js +1 -1
  90. package/dist/esm/testing/catalog.js +29 -13
  91. package/dist/esm/testing/cooldown-store-suite.js +3 -3
  92. package/dist/esm/testing/discord-defaults.js +449 -355
  93. package/dist/esm/testing/inspect-handler.js +91 -10
  94. package/dist/esm/testing/meocord-testing-module.js +195 -77
  95. package/dist/esm/testing/mock-fn.js +8 -2
  96. package/dist/esm/testing/mock-interaction.js +639 -113
  97. package/dist/esm/testing/response-log.js +8 -0
  98. package/dist/esm/testing/response.js +7 -5
  99. package/dist/esm/testing/routing.js +48 -6
  100. package/dist/esm/testing/strict-mocks.js +1 -0
  101. package/dist/esm/testing/theme.js +3 -2
  102. package/dist/esm/util/common.util.js +30 -9
  103. package/dist/esm/util/generator-cli.util.js +6 -4
  104. package/dist/esm/util/inherited-env.util.js +72 -13
  105. package/dist/esm/util/json.util.js +10 -3
  106. package/dist/esm/util/meocord-cli.util.js +3 -2
  107. package/dist/esm/util/meocord-config-loader.util.js +14 -1
  108. package/dist/esm/util/meocord-config-validation.util.js +13 -3
  109. package/dist/esm/util/meocord-source-config.util.js +13 -3
  110. package/dist/esm/util/platform.util.js +3 -2
  111. package/dist/esm/util/refusal.util.js +29 -11
  112. package/dist/esm/util/registration-mode.util.js +14 -1
  113. package/dist/esm/util/timer-limit.util.js +4 -1
  114. package/dist/esm/util/tsconfig-paths.util.js +38 -7
  115. package/dist/esm/util/tsconfig.util.js +16 -58
  116. package/dist/esm/util/user-text.util.js +8 -4
  117. package/dist/esm/util/value.util.js +15 -4
  118. package/dist/types/common/index.d.cts +13 -9
  119. package/dist/types/common/index.d.ts +13 -9
  120. package/dist/types/core/index.d.cts +21 -11
  121. package/dist/types/core/index.d.ts +21 -11
  122. package/dist/types/decorator/index.d.cts +63 -63
  123. package/dist/types/decorator/index.d.ts +63 -63
  124. package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
  125. package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
  126. package/dist/types/interface/index.d.cts +1 -1
  127. package/dist/types/interface/index.d.ts +1 -1
  128. package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
  129. package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
  130. package/dist/types/testing/index.d.cts +137 -54
  131. package/dist/types/testing/index.d.ts +137 -54
  132. package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
  133. package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
  134. package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
  135. package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
  136. package/package.json +5 -2
@@ -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. Each unit whose `onReady` settled, or that has none, is pushed
53
- * to `entries` as it finishes, so a shutdown that starts meanwhile closes exactly those.
54
- */ async function runReadyHooks(container, units, client, info, entries, report, { stopped = ()=>false, slowAfterMs, settled, runOnReady } = {}) {
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 { classUnits, isAppClassToken, lifecycleDependencies };
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 { warnOnce } from '../common/deprecation.js';
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, missingTranslatorError } from '../common/translator.js';
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 { themeResolverClass, ThemeCache } from './theme-resolvers.js';
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 { recordInstanceContainers, injectedTokens, singletonContextError } from './guard-runner.js';
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 { reachableClasses, providerMap, assertProvidedServices, bindsOwnToken, tokenName, bindProvider, resolutionOrder, isClassProvider, assertProvided, tokenDependencies, resolveProviders } from './providers.js';
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 === Translator && !container.isBound(Translator)) startupError(missingTranslatorError(cls));
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
  }
@@ -182,24 +186,24 @@ import { isShardProcess } from '../util/shard-process.util.js';
182
186
  });
183
187
  // A themeFor class is bound on its own, as the cooldown store is, and resolved like a service
184
188
  const themeResolver = themeResolverClass(options.themeFor);
185
- // The app's classes and every class they inject, which the container binds
186
- const roots = [
187
- ...options.controllers,
188
- ...options.services ?? [],
189
- ...themeResolver ? [
190
- themeResolver
191
- ] : [],
192
- ...options.cooldownStore ? [
193
- options.cooldownStore
194
- ] : [],
195
- ...appObservers(target)
196
- ];
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
+ });
197
201
  // A shard's manager runs the same checks, so a sharded bot warns once
198
202
  if (!shard) {
199
203
  warnUnregisteredCommands(options.controllers);
200
204
  warnInheritedRoutes(options.controllers);
201
205
  warnOverlappingPatterns(options.controllers);
202
- warnHandlersOffControllers(options.controllers, reachableClasses(roots, providers));
206
+ warnHandlersOffControllers(options.controllers, runs.classes);
203
207
  const developmentEnv = bunDevelopmentValues();
204
208
  if (developmentEnv.keys.length > 0) {
205
209
  this.logger.warn(bunDevelopmentWarning(developmentEnv));
@@ -220,19 +224,8 @@ import { isShardProcess } from '../util/shard-process.util.js';
220
224
  }
221
225
  // The checks of what the app binds and runs report every error each finds, and the first is thrown
222
226
  return collectStartupErrors(()=>{
223
- // Every class the app runs, its stages and presenter included, checked before binding, where inversify would
224
- // otherwise fail first with an error about compiler options, or a guard only at its first call
225
- const stages = appStages(target);
226
- const runs = startupClasses({
227
- controllers: options.controllers,
228
- services: options.services ?? [],
229
- providers,
230
- stages,
231
- presenter: options.presenter,
232
- cooldownStore: options.cooldownStore,
233
- themeFor: options.themeFor,
234
- observers: appObservers(target)
235
- });
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
236
229
  assertStartupClasses(runs, {
237
230
  translator: options.i18n !== undefined || providers.has(Translator)
238
231
  });
@@ -257,7 +250,8 @@ import { isShardProcess } from '../util/shard-process.util.js';
257
250
  }
258
251
  // Bound before the app's classes, so a class that injects it gets this instance; filled once they are bound
259
252
  const appClasses = [];
260
- container.bind(HandlerRegistry).toConstantValue(new HandlerRegistry(appClasses, options.messages, ()=>options.i18n));
253
+ const registry = new HandlerRegistry(appClasses, options.messages, ()=>options.i18n, options.controllers);
254
+ container.bind(HandlerRegistry).toConstantValue(registry);
261
255
  container.bind(ShardContext).toDynamicValue(()=>{
262
256
  const client = container.get(Client);
263
257
  return new ShardContext(client, (service, method, args)=>Reflect.get(client, SHARD_CALL_KEY)(service, method, args));
@@ -277,12 +271,9 @@ import { isShardProcess } from '../util/shard-process.util.js';
277
271
  // After MeoCord's own tokens, which a provider may not replace, and before the app's classes, so
278
272
  // a class token that is provided is not also bound as itself
279
273
  for (const [token, provider] of providers){
280
- // Bound later, or for each call, so not caught below; supported until 5.0 refuses it
281
- if (token === ThemeCache || token === ExecutionContext) {
282
- warnOnce(this.logger, `${target.name}: @MeoCord({ providers }) provides ${tokenName(token)}, which MeoCord binds itself; ` + 'the next major version (5.0) refuses it. Remove the provider.');
283
- }
284
- if (container.isBound(token)) {
285
- 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);
286
277
  continue;
287
278
  }
288
279
  bindProvider(container, provider, (cls)=>bindDependencies(container, cls, providers));
@@ -367,26 +358,35 @@ import { isShardProcess } from '../util/shard-process.util.js';
367
358
  buildMessageRoutes(options.controllers, options.messages);
368
359
  // Every check has run, and touched only the container: from here the app is made, once none found an error
369
360
  stopOnStartupErrors();
361
+ for (const warning of perCallEventWarnings(runs, providers, options.presenter))this.logger.warn(warning);
370
362
  const discordClient = container.get(Client);
371
363
  Reflect.set(discordClient, SHARD_CALL_KEY, shardCallHandler(container, ()=>callable, 'this app'));
372
- // Stamp every class the app runs with the container, so @UseGuard resolves guards on a direct call to any of them
373
- for (const cls of runs.classes){
374
- Reflect.defineMetadata(META.container, container, cls);
375
- }
376
- 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);
377
366
  prepareThemes(container, appClasses);
378
367
  // Run by start() before it logs in: what may inject a provided value is resolved once every factory
379
368
  // has made its value, including those that return a promise
380
369
  const logger = this.logger;
370
+ let sources;
381
371
  const startup = async (control)=>{
372
+ let made;
382
373
  try {
383
- await resolveProviders(container, providers, order, control);
374
+ made = await resolveProviders(container, providers, order, control);
384
375
  } catch (error) {
385
376
  logger.error(`${error.message}. The bot cannot start without it.`);
386
377
  logger.debug('Provider failure:', error.cause);
387
378
  markExplained(error);
388
379
  throw error;
389
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
+ }
390
390
  // The listed services are made now, so constructors that attach listeners or connect run before login; a stop
391
391
  // meanwhile makes no more of them
392
392
  for (const svc of options.services ?? []){
@@ -401,11 +401,38 @@ import { isShardProcess } from '../util/shard-process.util.js';
401
401
  registerClientTheme(discordClient, container);
402
402
  registerClientTranslator(discordClient, options.i18n);
403
403
  };
404
- 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
405
- 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));
406
406
  });
407
407
  }
408
408
  }
409
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
+ }
410
437
 
411
438
  export { MeoCordFactory };