meocord 4.1.2 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-CRn9kWob.cjs} +295 -87
- package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-ChvMqnLd.cjs} +74 -40
- package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-BdAUTdYs.cjs} +1 -1
- package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CQo8mSP8.cjs} +158 -72
- package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-DhDVbT5M.cjs} +12 -3
- package/dist/cjs/common/index.cjs +4 -4
- package/dist/cjs/core/index.cjs +195 -157
- package/dist/cjs/decorator/index.cjs +66 -57
- package/dist/cjs/testing/index.cjs +733 -301
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/README.md.template +3 -1
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
- package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
- package/dist/esm/build/load-config.pre-entry.js +4 -1
- package/dist/esm/common/decorator.js +6 -6
- package/dist/esm/common/deprecation.js +2 -2
- package/dist/esm/common/errors.js +2 -2
- package/dist/esm/common/execution-context.js +1 -1
- package/dist/esm/common/metadata.js +1 -1
- package/dist/esm/common/route.js +10 -1
- package/dist/esm/common/translate-error.js +1 -1
- package/dist/esm/core/command-conflicts.js +34 -34
- package/dist/esm/core/component-routes.js +74 -31
- package/dist/esm/core/dispatcher.js +51 -7
- package/dist/esm/core/filter-runner.js +2 -2
- package/dist/esm/core/handler-pipeline.js +27 -14
- package/dist/esm/core/handler-registry.js +1 -1
- package/dist/esm/core/interceptor-runner.js +2 -2
- package/dist/esm/core/meocord-factory.js +196 -157
- package/dist/esm/core/message-routes.js +4 -3
- package/dist/esm/core/observer-runner.js +4 -3
- package/dist/esm/core/providers.js +38 -21
- package/dist/esm/core/route-specificity.js +4 -4
- package/dist/esm/core/scalar-types.js +15 -3
- package/dist/esm/core/shard-context.js +1 -1
- package/dist/esm/core/startup-roots.js +2 -3
- package/dist/esm/core/theme-runtime.js +2 -2
- package/dist/esm/decorator/app.decorator.js +12 -7
- package/dist/esm/decorator/command-builder.decorator.js +4 -4
- package/dist/esm/decorator/controller-class.decorator.js +9 -4
- package/dist/esm/decorator/controller.decorator.js +110 -67
- package/dist/esm/decorator/cooldown.decorator.js +3 -3
- package/dist/esm/decorator/defer.decorator.js +4 -4
- package/dist/esm/decorator/event.decorator.js +5 -5
- package/dist/esm/decorator/filter.decorator.js +7 -7
- package/dist/esm/decorator/guard.decorator.js +8 -8
- package/dist/esm/decorator/inject.decorator.js +4 -4
- package/dist/esm/decorator/interceptor.decorator.js +7 -7
- package/dist/esm/decorator/observer.decorator.js +4 -4
- package/dist/esm/decorator/service.decorator.js +4 -4
- package/dist/esm/decorator/theme.decorator.js +3 -3
- package/dist/esm/decorator/validation.decorator.js +10 -10
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +1 -1
- package/dist/esm/testing/cooldown-store-suite.js +18 -10
- package/dist/esm/testing/discord-defaults.js +60 -7
- package/dist/esm/testing/index.js +4 -2
- package/dist/esm/testing/inspect-handler.js +20 -3
- package/dist/esm/testing/meocord-testing-module.js +229 -188
- package/dist/esm/testing/mock-fn.js +80 -10
- package/dist/esm/testing/mock-interaction.js +223 -72
- package/dist/esm/testing/response.js +1 -1
- package/dist/esm/testing/routing.js +39 -14
- package/dist/esm/testing/startup-errors.js +26 -0
- package/dist/esm/testing/strict-mocks.js +45 -0
- package/dist/esm/testing/theme.js +1 -1
- package/dist/esm/util/bundle-entry.util.js +2 -1
- package/dist/esm/util/interaction.util.js +1 -1
- package/dist/esm/util/meocord-config-validation.util.js +1 -0
- package/dist/esm/util/metadata-keys.js +1 -0
- package/dist/esm/util/refusal.util.js +148 -8
- package/dist/types/common/index.d.cts +8 -8
- package/dist/types/common/index.d.ts +8 -8
- package/dist/types/core/index.d.cts +8 -6
- package/dist/types/core/index.d.ts +8 -6
- package/dist/types/decorator/index.d.cts +30 -29
- package/dist/types/decorator/index.d.ts +30 -29
- package/dist/types/{index-D7n0FBMm.d.ts → index-CdXsFgjE.d.ts} +62 -15
- package/dist/types/{index-DEP0NYrU.d.cts → index-nn5p7OoX.d.cts} +62 -15
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-C-aMI7Sl.d.cts → route-BZgFoNeB.d.cts} +5 -2
- package/dist/types/{route-C-aMI7Sl.d.ts → route-BZgFoNeB.d.ts} +5 -2
- package/dist/types/testing/index.d.cts +245 -31
- package/dist/types/testing/index.d.ts +245 -31
- package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-BcOJVzR9.d.ts} +1 -1
- package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-UKr3-6Ys.d.cts} +1 -1
- package/dist/types/{translator-J6tIUfdp.d.ts → translator-BViWKtLJ.d.ts} +1 -1
- package/dist/types/{translator-B4mc1lzJ.d.cts → translator-DHMkkCpg.d.cts} +1 -1
- package/package.json +3 -2
|
@@ -38,6 +38,7 @@ import { existingResponse } from '../common/response/response-state.js';
|
|
|
38
38
|
if (state && state !== 'unanswered') return true;
|
|
39
39
|
return 'replied' in interaction && (interaction.replied || interaction.deferred);
|
|
40
40
|
}
|
|
41
|
+
/** How long a message's handler may take, with others waiting after it, before it is named as slow. */ const SLOW_HANDLER_MS = 5000;
|
|
41
42
|
/**
|
|
42
43
|
* Routes interactions, messages and reactions to the handlers that take them, as the bot does, and runs
|
|
43
44
|
* each through its pipeline. The app and the testing module share it, so a test routes exactly as the bot.
|
|
@@ -293,22 +294,64 @@ import { existingResponse } from '../common/response/response-state.js';
|
|
|
293
294
|
message
|
|
294
295
|
], error, this.runOptions(call));
|
|
295
296
|
}
|
|
297
|
+
// The matched handler, then every listener, each resolved from the container only as it starts
|
|
298
|
+
const runs = [];
|
|
296
299
|
if (target) {
|
|
297
300
|
const { route, params, start, given } = target;
|
|
298
301
|
noteInvocation(message, `${start}${commandWordsOf(route.tokens).join(' ')}`);
|
|
299
302
|
const hooks = messageCommandHooks(route, params, message, start, given, this.messageOptions.types);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
runs.push({
|
|
304
|
+
name: `${route.controllerClass.name}.${route.method}`,
|
|
305
|
+
run: (at)=>this.invokeHandler(this.getInstance(route.controllerClass), route.method, [
|
|
306
|
+
message,
|
|
307
|
+
params
|
|
308
|
+
], at, hooks)
|
|
309
|
+
});
|
|
304
310
|
}
|
|
305
311
|
for (const { controllerClass, method } of this.messageListeners){
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
312
|
+
runs.push({
|
|
313
|
+
name: `${controllerClass.name}.${method}`,
|
|
314
|
+
run: (at)=>this.invokeHandler(this.getInstance(controllerClass), method, [
|
|
315
|
+
message
|
|
316
|
+
], at)
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (this.messageOptions.handlers === 'concurrent') await this.runTogether(runs, call);
|
|
320
|
+
else await this.runInTurn(runs, call);
|
|
321
|
+
}
|
|
322
|
+
/** Runs a message's handlers one after another, warning once about one that held the rest back for long. */ async runInTurn(runs, call) {
|
|
323
|
+
const warn = this.messageOptions.slowHandlerWarning ?? this.options.warnSlowMessageHandlers ?? true;
|
|
324
|
+
for (const [index, { name, run }] of runs.entries()){
|
|
325
|
+
const started = performance.now();
|
|
326
|
+
await run(call);
|
|
327
|
+
const waiting = runs.length - 1 - index;
|
|
328
|
+
if (warn && waiting > 0) this.warnSlowOnce(name, performance.now() - started, waiting);
|
|
309
329
|
}
|
|
310
330
|
}
|
|
311
331
|
/**
|
|
332
|
+
* Starts a message's handlers together and settles once all have. Each is reported as settled in the order the
|
|
333
|
+
* handlers are listed, whichever settles first, and a call that rejects, rejects this once every one has settled.
|
|
334
|
+
*/ async runTogether(runs, call) {
|
|
335
|
+
const { record } = call;
|
|
336
|
+
const reports = runs.map(()=>[]);
|
|
337
|
+
const results = await Promise.allSettled(runs.map(({ run }, index)=>run(record ? {
|
|
338
|
+
...call,
|
|
339
|
+
record: {
|
|
340
|
+
settled: (...report)=>reports[index].push(report),
|
|
341
|
+
unhandled: (error)=>record.unhandled(error)
|
|
342
|
+
}
|
|
343
|
+
} : call)));
|
|
344
|
+
for (const report of reports.flat())record?.settled(...report);
|
|
345
|
+
const failed = results.find((result)=>result.status === 'rejected');
|
|
346
|
+
if (failed) throw failed.reason;
|
|
347
|
+
}
|
|
348
|
+
/** Warns, once per handler, that it took `SLOW_HANDLER_MS` or longer with `waiting` handlers held back behind it. */ warnSlowOnce(name, took, waiting) {
|
|
349
|
+
if (took < SLOW_HANDLER_MS || this.warnedSlow.has(name)) return;
|
|
350
|
+
this.warnedSlow.add(name);
|
|
351
|
+
const held = waiting === 1 ? 'the listener after it' : `the ${waiting} listeners after it`;
|
|
352
|
+
this.logger.warn(`${name} took ${(took / 1000).toFixed(1)} s on a message, and held back ${held}. ` + "@MeoCord({ messages: { handlers: 'concurrent' } }) runs them side by side; messages: { slowHandlerWarning: false } turns this off.");
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
312
355
|
* Answers a message that asks the built-in help, when `messages.help` is on and no handler took the message:
|
|
313
356
|
* through the presenter's `messageHelp` when it has one, else in plain text, once the app's guards allow it.
|
|
314
357
|
* `known` is the message's starts when they hold the app's prefixes already. Whether it answered, or a
|
|
@@ -429,6 +472,7 @@ import { existingResponse } from '../common/response/response-state.js';
|
|
|
429
472
|
this.controllerInstancesCache = new Map();
|
|
430
473
|
/** Handler and name pairs already warned about, so a collision warns once, not on every submit or selection. */ this.warnedCollisions = new Set();
|
|
431
474
|
/** The handlers already warned about, so each is named once however often it runs. */ this.warnedUnanswered = new Set();
|
|
475
|
+
/** The message handlers already warned about as slow, so each is named once. */ this.warnedSlow = new Set();
|
|
432
476
|
this.container = options.container;
|
|
433
477
|
this.controllerClasses = options.controllerClasses;
|
|
434
478
|
this.messageOptions = options.messageOptions ?? {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { perHandler, sourcePrototype, stageClasses } from './guard-runner.js';
|
|
3
3
|
import { bindShared } from './interceptor-runner.js';
|
|
4
|
-
import {
|
|
4
|
+
import { startupError } from '../util/refusal.util.js';
|
|
5
5
|
import { isConstructor } from '../util/value.util.js';
|
|
6
6
|
import { META } from '../util/metadata-keys.js';
|
|
7
7
|
|
|
@@ -41,7 +41,7 @@ import { META } from '../util/metadata-keys.js';
|
|
|
41
41
|
/** Binds a filter as a singleton, after checking it is one. */ function prepareFilter(container, entry) {
|
|
42
42
|
const cls = filterClass(entry);
|
|
43
43
|
if (!Reflect.hasOwnMetadata(META.catchTypes, cls)) {
|
|
44
|
-
|
|
44
|
+
return startupError(new Error(`${cls.name || 'A filter'}: used as an exception filter, but not decorated with @Catch().`));
|
|
45
45
|
}
|
|
46
46
|
bindShared(container, cls);
|
|
47
47
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { refuse } from '../util/refusal.util.js';
|
|
2
|
+
import { refuse, startupError } from '../util/refusal.util.js';
|
|
3
3
|
import { setPresenter } from '../common/response/presenter.js';
|
|
4
4
|
import { responseOf } from '../common/response/response-state.js';
|
|
5
5
|
import { handlerDefer, nonInteractionHandler, deferMisuseError, startDefer } from './defer.js';
|
|
6
|
-
import { perHandler,
|
|
7
|
-
import { prepareInterceptor, handlerInterceptors, bindShared,
|
|
6
|
+
import { perHandler, runGuards, callGuardedHandler, handlerGuards, isGuardWithParams } from './guard-runner.js';
|
|
7
|
+
import { prepareInterceptor, handlerInterceptors, bindShared, runInterceptors, interceptorClass } from './interceptor-runner.js';
|
|
8
8
|
import { inferContextType, HandlerExecutionContext, UnroutedExecutionContext } from '../common/execution-context.js';
|
|
9
9
|
import { prepareFilter, handlerFilterLevels, filterClass, matchFilter, callFilter } from './filter-runner.js';
|
|
10
|
-
import { handlerInputStages, preparePipe,
|
|
10
|
+
import { handlerInputStages, preparePipe, prepareHandlerArgs, pipeClass } from './input-runner.js';
|
|
11
11
|
import { hasObservers, notifyStart, outcomeOf, responsePhaseOf, notifyObservers } from './observer-runner.js';
|
|
12
|
-
import {
|
|
12
|
+
import { handlerCooldowns, peekCooldowns, methodCooldowns } from './cooldown-runner.js';
|
|
13
13
|
import { Logger } from '../common/logger.js';
|
|
14
|
-
import {
|
|
14
|
+
import { beginCallTheme, configureThemes } from './theme-runtime.js';
|
|
15
15
|
import { runInThemeScope } from './theme-scope.js';
|
|
16
16
|
import { getEventHandlers } from '../decorator/event.decorator.js';
|
|
17
17
|
import { getCommandMap, getMessageHandlers, getReactionHandlers, getAutocompleteHandlers } from '../decorator/controller.decorator.js';
|
|
@@ -57,12 +57,16 @@ const NO_GLOBAL_STAGES = {
|
|
|
57
57
|
*/ function bindAppPresenter(container, app, client) {
|
|
58
58
|
const options = Reflect.getMetadata(META.appOptions, app);
|
|
59
59
|
if (!options?.presenter) return undefined;
|
|
60
|
-
|
|
60
|
+
prepareAppPresenter(container, app);
|
|
61
61
|
const presenter = container.get(options.presenter);
|
|
62
62
|
container.bind(APP_PRESENTER).toConstantValue(presenter);
|
|
63
63
|
if (client) setPresenter(client, presenter);
|
|
64
64
|
return presenter;
|
|
65
65
|
}
|
|
66
|
+
/** Binds the app's presenter class as a shared stage, checking it as one, without making it. */ function prepareAppPresenter(container, app) {
|
|
67
|
+
const options = Reflect.getMetadata(META.appOptions, app);
|
|
68
|
+
if (options?.presenter) bindShared(container, options.presenter);
|
|
69
|
+
}
|
|
66
70
|
/** Where a container keeps the application's presenter. */ const APP_PRESENTER = Symbol('app_presenter');
|
|
67
71
|
/** The presenter `bindAppPresenter` resolved into `container`, if any. */ function appPresenterOf(container) {
|
|
68
72
|
return container.isBound(APP_PRESENTER) ? container.get(APP_PRESENTER) : undefined;
|
|
@@ -152,18 +156,21 @@ const stagesByGlobals = new WeakMap();
|
|
|
152
156
|
/**
|
|
153
157
|
* Binds the interceptors, filters and pipes every handler of `controllers` uses, and the global ones, as singletons, so
|
|
154
158
|
* one that cannot be shared, or a filter without `@Catch`, fails at startup; refuses there `@Defer`, `@Validate`,
|
|
155
|
-
* `@UsePipe` and `@Cooldown` where they cannot apply, and two classes that keep state under one name.
|
|
159
|
+
* `@UsePipe` and `@Cooldown` where they cannot apply, and two classes that keep state under one name. Binds only; it
|
|
160
|
+
* makes nothing, so it runs with the other startup checks.
|
|
156
161
|
*/ function prepareHandlerStages(container, controllers) {
|
|
157
162
|
assertDistinctNamesWhereKeyed(controllers);
|
|
158
163
|
const globals = globalStagesOf(container);
|
|
159
|
-
configureThemes(container, globals.theme, controllers, globals.themeFor);
|
|
160
164
|
for (const entry of globals.interceptors)prepareInterceptor(container, entry);
|
|
161
165
|
for (const entry of globals.filters)prepareFilter(container, entry);
|
|
162
166
|
for (const controller of controllers){
|
|
163
167
|
const prototype = controller.prototype;
|
|
164
168
|
for (const method of handlerMethods(prototype)){
|
|
165
169
|
const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
|
|
166
|
-
if (kind)
|
|
170
|
+
if (kind) {
|
|
171
|
+
startupError(deferMisuseError(controller.name, method, kind));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
167
174
|
for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
|
|
168
175
|
for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
|
|
169
176
|
for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
|
|
@@ -171,6 +178,10 @@ const stagesByGlobals = new WeakMap();
|
|
|
171
178
|
assertInputStagesOnInteractions(controller, prototype);
|
|
172
179
|
}
|
|
173
180
|
}
|
|
181
|
+
/** Records the app's theme for the handlers of `controllers`, once the startup checks have passed: it makes the theme cache. */ function prepareThemes(container, controllers) {
|
|
182
|
+
const globals = globalStagesOf(container);
|
|
183
|
+
configureThemes(container, globals.theme, controllers, globals.themeFor);
|
|
184
|
+
}
|
|
174
185
|
/** Whether a class has state keyed by its name: a cooldown on a handler, or a `@Once` handler. */ function keyedByName(cls) {
|
|
175
186
|
const prototype = cls.prototype;
|
|
176
187
|
const handlers = [
|
|
@@ -187,7 +198,8 @@ const stagesByGlobals = new WeakMap();
|
|
|
187
198
|
for (const cls of classes){
|
|
188
199
|
const other = byName.get(cls.name);
|
|
189
200
|
if (other && other !== cls && (keyedByName(cls) || keyedByName(other))) {
|
|
190
|
-
|
|
201
|
+
startupError(new Error(`${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.`));
|
|
202
|
+
continue;
|
|
191
203
|
}
|
|
192
204
|
byName.set(cls.name, cls);
|
|
193
205
|
}
|
|
@@ -215,11 +227,12 @@ const stagesByGlobals = new WeakMap();
|
|
|
215
227
|
const { schema, pipes } = handlerInputStages(prototype, method);
|
|
216
228
|
const handler = kind === 'message' ? 'a message handler without a pattern' : `${kind === 'autocomplete' || kind === 'event' ? 'an' : 'a'} ${kind} handler`;
|
|
217
229
|
if (schema || pipes.length > 0) {
|
|
218
|
-
|
|
230
|
+
startupError(new Error(`${controller.name}.${method}: @Validate and @UsePipe are for interaction and patterned message handlers, ` + `whose options, customId params, modal fields and pattern params they check, and this is ${handler}.`));
|
|
231
|
+
continue;
|
|
219
232
|
}
|
|
220
233
|
// A controller's own @Cooldown skips these handlers; one on the method itself is a mistake.
|
|
221
234
|
if (kind !== 'message' && methodCooldowns(prototype, method).length > 0) {
|
|
222
|
-
|
|
235
|
+
startupError(new Error(`${controller.name}.${method}: @Cooldown is for interaction and message handlers, and this is ${handler}.`));
|
|
223
236
|
}
|
|
224
237
|
}
|
|
225
238
|
}
|
|
@@ -462,4 +475,4 @@ const logger = new Logger('ExceptionFilter');
|
|
|
462
475
|
}, options);
|
|
463
476
|
}
|
|
464
477
|
|
|
465
|
-
export { appPresenterOf, appStages, bindAppPresenter, bindGlobalStages, classDecorators, globalStagesOf, handleUnroutedError, handlerStageClasses, handlerStages, observeUnclaimed, prepareHandlerStages, runHandler, runInAppTheme };
|
|
478
|
+
export { appPresenterOf, appStages, bindAppPresenter, bindGlobalStages, classDecorators, globalStagesOf, handleUnroutedError, handlerStageClasses, handlerStages, observeUnclaimed, prepareAppPresenter, prepareHandlerStages, prepareThemes, runHandler, runInAppTheme };
|
|
@@ -97,7 +97,7 @@ const COMMAND_TYPES = new Set([
|
|
|
97
97
|
*
|
|
98
98
|
* @group Controllers
|
|
99
99
|
* @see {@link HandlerEntry}
|
|
100
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
100
|
+
* @see {@link https://meocord.dev/docs/4.2/handler-discovery | Handler discovery}
|
|
101
101
|
*/ class HandlerRegistry {
|
|
102
102
|
/**
|
|
103
103
|
* Lists the registered handlers.
|
|
@@ -4,7 +4,7 @@ import { perHandler, injectedTokens, singletonContextError, sourcePrototype, sta
|
|
|
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';
|
|
7
|
-
import {
|
|
7
|
+
import { startupError } from '../util/refusal.util.js';
|
|
8
8
|
import { Logger } from '../common/logger.js';
|
|
9
9
|
import { META } from '../util/metadata-keys.js';
|
|
10
10
|
|
|
@@ -35,7 +35,7 @@ function isInterceptorWithParams(entry) {
|
|
|
35
35
|
});
|
|
36
36
|
/** Binds `cls` and its unbound dependencies as singletons, refusing any that injects `ExecutionContext`. */ function bindShared(container, cls) {
|
|
37
37
|
if (container.isBound(cls)) return;
|
|
38
|
-
if (injectedTokens(cls).includes(ExecutionContext))
|
|
38
|
+
if (injectedTokens(cls).includes(ExecutionContext)) return startupError(singletonContextError(cls));
|
|
39
39
|
assertStartupChecked(container, cls);
|
|
40
40
|
makeInjectable(cls);
|
|
41
41
|
container.bind(cls).toSelf().inSingletonScope();
|