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
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var controller_decorator = require('./controller.decorator-U2NBWcoU.cjs');
3
+ var controller_decorator = require('./controller.decorator-hp5xdJU4.cjs');
4
4
  require('reflect-metadata');
5
5
  var node_async_hooks = require('node:async_hooks');
6
6
  var inversify = require('inversify');
@@ -11,8 +11,6 @@ var fs = require('fs');
11
11
  var path = require('path');
12
12
  var Table = require('cli-table3');
13
13
  var node_crypto = require('node:crypto');
14
- require('node:fs');
15
- require('node:url');
16
14
 
17
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
18
16
 
@@ -104,13 +102,12 @@ function eventDecorator(event, once) {
104
102
  // Generic over the method, so it is checked against the event's arguments rather than required to match exactly
105
103
  return controller_decorator.declaring(function(target, propertyKey, _descriptor) {
106
104
  controller_decorator.refuseOnClass(once ? '@Once' : '@On', target, propertyKey);
107
- const handlers = controller_decorator.ownHandlerList(controller_decorator.META.eventHandlers, target);
108
- handlers.push({
105
+ // One the class inherits for the same method and event is replaced, as for every other kind of handler
106
+ controller_decorator.addOwnHandler(controller_decorator.META.eventHandlers, target, {
109
107
  event,
110
108
  method: propertyKey,
111
109
  once
112
- });
113
- Reflect.defineMetadata(controller_decorator.META.eventHandlers, handlers, target);
110
+ }, (other)=>other.method === propertyKey && other.event === event);
114
111
  });
115
112
  }
116
113
  /**
@@ -306,14 +303,14 @@ const perCallKeys = new WeakMap();
306
303
  });
307
304
  }
308
305
  }
309
- const logger$4 = new controller_decorator.Logger('Guard');
306
+ const logger$5 = new controller_decorator.Logger('Guard');
310
307
  const warnedShared = new WeakMap();
311
308
  /** Warns once per guard in each app that its params are set on the one instance every call shares, and why. */ function warnSharedParams(container, guardClass, why, remedy) {
312
309
  let warned = warnedShared.get(container);
313
310
  if (!warned) warnedShared.set(container, warned = new Set());
314
311
  if (warned.has(guardClass)) return;
315
312
  warned.add(guardClass);
316
- logger$4.warn(`${guardClass.name} is one instance every call shares, and ${why}, so each call's params are set on that ` + `instance: calls that overlap can read each other's. ${remedy}, or stop binding the guard, in services, ` + `providers or a constructor that injects it, so each call gets its own.`);
313
+ logger$5.warn(`${guardClass.name} is one instance every call shares, and ${why}, so each call's params are set on that ` + `instance: calls that overlap can read each other's. ${remedy}, or stop binding the guard, in services, ` + `providers or a constructor that injects it, so each call gets its own.`);
317
314
  }
318
315
  /** Whether each of these properties can be made to read the call's params, which a frozen or sealed instance's cannot. */ function canReadPerCall(instance, keys) {
319
316
  return keys.every((key)=>{
@@ -427,6 +424,28 @@ const warnedShared = new WeakMap();
427
424
  }
428
425
  return undefined;
429
426
  }
427
+ /**
428
+ * The prototypes on the chain that declare `methodName` as written, base first: the handler and each override of it,
429
+ * looking through methods a class-level `@UseGuard` re-declared to wrap an inherited handler. A handler's method-level
430
+ * stages are read from each, so an override keeps its base method's, as it keeps its route.
431
+ */ function stageOwners(prototype, methodName) {
432
+ const owners = [];
433
+ for(let current = prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
434
+ if (!Object.prototype.hasOwnProperty.call(current, methodName)) continue;
435
+ if (!Reflect.getOwnMetadata(controller_decorator.META.inheritedFrom, current, methodName)) owners.push(current);
436
+ }
437
+ return owners.reverse();
438
+ }
439
+ /** The entries a method-level stage lists under `key` on each of a handler's {@link stageOwners}, base first. */ function methodStages(prototype, methodName, key) {
440
+ return stageOwners(prototype, methodName).flatMap((owner)=>Reflect.getOwnMetadata(key, owner, methodName) ?? []);
441
+ }
442
+ /** The value a method-level stage holds under `key` on the nearest of a handler's {@link stageOwners} that declares one. */ function nearestMethodStage(prototype, methodName, key) {
443
+ for (const owner of stageOwners(prototype, methodName).reverse()){
444
+ const value = Reflect.getOwnMetadata(key, owner, methodName);
445
+ if (value !== undefined) return value;
446
+ }
447
+ return undefined;
448
+ }
430
449
  /**
431
450
  * Caches a function of a class's prototype and one of its handlers. Handler metadata is fixed once
432
451
  * the classes are decorated, so dispatch resolves each handler's stages once, not on every call.
@@ -478,50 +497,165 @@ const warnedShared = new WeakMap();
478
497
  * The guards dispatch runs before a handler: its classes' class-level guards, outermost first, then
479
498
  * the method's. A method that is no handler has only its own, since class guards apply to handlers.
480
499
  */ const handlerGuards = perHandler((prototype, methodName)=>{
481
- const source = sourcePrototype(prototype, methodName);
482
- if (!source) return [];
500
+ if (!sourcePrototype(prototype, methodName)) return [];
483
501
  return [
484
502
  ...handlerMethods$1(prototype).has(methodName) ? stageClasses(prototype, methodName).flatMap(classLevelGuards) : [],
485
- ...Reflect.getOwnMetadata(controller_decorator.META.methodGuards, source, methodName) ?? []
503
+ ...methodStages(prototype, methodName, controller_decorator.META.methodGuards)
504
+ ];
505
+ });
506
+ /**
507
+ * The type a direct call to a handler runs as, which is the one dispatch gives it: `'event'` for an `@On` or `@Once`
508
+ * handler, as its first argument may be a message or an interaction; otherwise read from the first argument.
509
+ */ const directCallType = perHandler((prototype, methodName)=>{
510
+ if (!getEventHandlers(prototype).some((handler)=>handler.method === methodName)) return undefined;
511
+ const routed = [
512
+ ...Object.values(controller_decorator.getCommandMap(prototype) ?? {}).flat().map((command)=>command.methodName),
513
+ ...controller_decorator.getMessageHandlers(prototype).map((handler)=>handler.method),
514
+ ...controller_decorator.getReactionHandlers(prototype).map((handler)=>handler.method),
515
+ ...controller_decorator.getAutocompleteHandlers(prototype).map((handler)=>handler.methodName)
486
516
  ];
517
+ // A method dispatch also routes as a command, message or reaction has no one type
518
+ return routed.includes(methodName) ? undefined : 'event';
487
519
  });
488
- const wrapperCount = perHandler((prototype, methodName)=>{
489
- const owner = declaringPrototype(prototype, methodName);
490
- return owner ? Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, owner, methodName) ?? 0 : 0;
520
+ /** Whether `@UseGuard` wraps the method anywhere on the chain, an override's base included. */ const isWrapped = perHandler((prototype, methodName)=>{
521
+ for(let current = prototype; current; current = Object.getPrototypeOf(current)){
522
+ if (Object.prototype.hasOwnProperty.call(current, methodName) && Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, current, methodName)) return true;
523
+ }
524
+ return false;
491
525
  });
492
526
  /**
493
- * Calls dispatch has already guarded, keyed by first argument and method, counting the wrappers still
494
- * to pass. A user decorator that awaits between two wrappers leaves a pass pending during that await,
495
- * which a concurrent direct call with the same object and method could take; other overlaps fail closed.
496
- * An event whose first argument is not an object, such as `debug`'s string, is keyed by the instance.
497
- */ const dispatched = new WeakMap();
498
- /** The object a call's dispatch mark is kept on: its first argument, or the instance when that is not an object. */ function markKey(first, instance) {
499
- return typeof first === 'object' && first !== null ? first : instance;
500
- }
501
- /** Takes one pass for the wrapper being entered, if dispatch left one for this call. */ function consumeDispatchMark(first, instance, methodName) {
502
- const marks = dispatched.get(markKey(first, instance));
503
- const remaining = marks?.get(methodName);
504
- if (!marks || !remaining) return false;
505
- if (remaining > 1) marks.set(methodName, remaining - 1);
506
- else marks.delete(methodName);
527
+ * The pass of the guarded call running. Kept per call, so a concurrent call with the same object and method runs its own
528
+ * guards, and closed when the call returns, so work it leaves running, such as a timer, does not carry it.
529
+ */ const guardedPasses = new node_async_hooks.AsyncLocalStorage();
530
+ /** Takes the pass for `wrapper` when it belongs to a call whose guards have already run and has not let it through yet. */ function takeGuardedPass(wrapper, first, instance, methodName) {
531
+ const pass = guardedPasses.getStore();
532
+ if (!pass?.open || pass.instance !== instance || pass.methodName !== methodName || pass.first !== first) return false;
533
+ if (pass.entered.has(wrapper)) return false;
534
+ pass.entered.add(wrapper);
507
535
  return true;
508
536
  }
537
+ /** Runs a call whose guards have run, with its pass; `wrapper` is the one that ran them, which it has entered. */ async function runWithPass(instance, methodName, args, call, wrapper) {
538
+ const pass = {
539
+ instance,
540
+ methodName,
541
+ first: args[0],
542
+ entered: new Set(wrapper ? [
543
+ wrapper
544
+ ] : []),
545
+ open: true
546
+ };
547
+ try {
548
+ return await guardedPasses.run(pass, call);
549
+ } finally{
550
+ pass.open = false;
551
+ }
552
+ }
553
+ /**
554
+ * Adds guards to a method's class or method list, then republishes the effective list under
555
+ * `META.guards`, in the order they run: the bases' before the class's own, and within one class or method the
556
+ * latest decorator first, since it wraps outermost.
557
+ */ function recordGuards(key, guards, prototype, methodName) {
558
+ const existing = Reflect.getOwnMetadata(key, prototype, methodName) ?? [];
559
+ const bases = key === controller_decorator.META.classGuards ? Reflect.getOwnMetadata(controller_decorator.META.baseClassGuards, prototype, methodName) ?? 0 : 0;
560
+ Reflect.defineMetadata(key, [
561
+ ...existing.slice(0, bases),
562
+ ...guards,
563
+ ...existing.slice(bases)
564
+ ], prototype, methodName);
565
+ const [classGuards, inheritedGuards] = [
566
+ controller_decorator.META.classGuards,
567
+ controller_decorator.META.inheritedGuards
568
+ ].map((list)=>Reflect.getOwnMetadata(list, prototype, methodName) ?? []);
569
+ // The method's guards and those of the methods it overrides, base first, as dispatch runs them
570
+ const methodGuards = methodStages(prototype, methodName, controller_decorator.META.methodGuards);
571
+ Reflect.defineMetadata(controller_decorator.META.guards, [
572
+ ...inheritedGuards,
573
+ ...classGuards,
574
+ ...methodGuards
575
+ ], prototype, methodName);
576
+ }
577
+ /** The classes {@link guardOwnHandlersWithBaseGuards} has handled, as `@Controller` and each app's startup both call it. */ const guardedWithBases = new WeakSet();
578
+ /**
579
+ * Gives each handler a class declares itself the class-level guards of the classes it extends,
580
+ * as its inherited handlers have: recorded under `META.guards`, and wrapped so a direct call
581
+ * runs them when no other guard wraps the handler. `@Controller` calls this as it decorates, and
582
+ * {@link prepareDirectCalls} for every class an app runs, a service included. Once per class.
583
+ */ function guardOwnHandlersWithBaseGuards(target) {
584
+ if (guardedWithBases.has(target)) return;
585
+ guardedWithBases.add(target);
586
+ const prototype = target.prototype;
587
+ for (const methodName of handlerMethods$1(prototype)){
588
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
589
+ // Inherited handlers have the chain of the class that declares them
590
+ if (typeof descriptor?.value !== 'function' || Reflect.getOwnMetadata(controller_decorator.META.inheritedFrom, prototype, methodName)) continue;
591
+ // Base first, as dispatch runs them
592
+ const guards = classChain(prototype, prototype).slice(1).reverse().flatMap(classLevelGuards);
593
+ if (guards.length === 0) continue;
594
+ if (!Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, prototype, methodName)) {
595
+ applyGuards(descriptor, prototype, methodName);
596
+ Object.defineProperty(prototype, methodName, descriptor);
597
+ }
598
+ recordGuards(controller_decorator.META.inheritedGuards, guards, prototype, methodName);
599
+ }
600
+ }
509
601
  /** The container that made each instance, so a direct call runs in its own app or testing module. */ const instanceContainers = new WeakMap();
510
602
  /**
511
- * Records each instance `container` makes of `classes` as its own. Two testing modules made from one class each run a
512
- * direct call on their own instance in themselves, where the class's metadata names only the last.
513
- */ function recordInstanceContainers(container, classes) {
603
+ * Readies direct calls to the handlers of the classes an app or testing module runs, controllers and services alike:
604
+ * - stamps each class with the container, and records each instance the container makes as its own, so two testing
605
+ * modules made from one class each run a direct call on their own instance in themselves;
606
+ * - gives each handler a class declares itself the class-level guards of the classes it extends, and wraps each
607
+ * override of a guarded handler, so a direct call to it runs the guards it inherits before its body.
608
+ */ function prepareDirectCalls(container, classes) {
514
609
  for (const cls of classes){
610
+ Reflect.defineMetadata(controller_decorator.META.container, container, cls);
611
+ // Each class on the chain, base first, so an override above the one the app runs is wrapped too
612
+ for (const owner of classesOf(cls).reverse())guardOwnHandlersWithBaseGuards(owner);
613
+ wrapGuardedOverrides(cls.prototype);
515
614
  container.onActivation(cls, (_context, instance)=>{
516
615
  if (typeof instance === 'object' && instance !== null) instanceContainers.set(instance, container);
517
616
  return instance;
518
617
  });
519
618
  }
520
619
  }
620
+ /** `cls` and the classes it extends, innermost first. */ function classesOf(cls) {
621
+ const classes = [];
622
+ for(let current = cls.prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
623
+ classes.push(current.constructor);
624
+ }
625
+ return classes;
626
+ }
627
+ /** Wraps each method on the chain that overrides a handler a base method guards and is not wrapped itself. */ function wrapGuardedOverrides(prototype) {
628
+ for (const methodName of handlerMethods$1(prototype)){
629
+ let guardedBelow = false;
630
+ for (const owner of stageOwners(prototype, methodName)){
631
+ const descriptor = Object.getOwnPropertyDescriptor(owner, methodName);
632
+ if (guardedBelow && typeof descriptor?.value === 'function' && !Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, owner, methodName)) {
633
+ applyGuards(descriptor, owner, methodName);
634
+ Object.defineProperty(owner, methodName, descriptor);
635
+ }
636
+ if ((Reflect.getOwnMetadata(controller_decorator.META.methodGuards, owner, methodName) ?? []).length > 0) guardedBelow = true;
637
+ }
638
+ }
639
+ }
640
+ /**
641
+ * Wraps a method so a direct call runs its guards first: the wrapper entered first runs the whole
642
+ * chain dispatch resolves for the handler, and lets the ones inside it through. A call from dispatch,
643
+ * which has already run the guards, passes through every wrapper.
644
+ */ function applyGuards(descriptor, prototype, propertyKey) {
645
+ const originalMethod = descriptor.value;
646
+ // Which wrapper a call has passed, so it passes each once
647
+ const wrapper = {};
648
+ descriptor.value = controller_decorator.wrapsMethod(async function(...args) {
649
+ if (takeGuardedPass(wrapper, args[0], this, propertyKey)) return originalMethod.apply(this, args);
650
+ return runDirectCall(this, propertyKey, args, ()=>originalMethod.apply(this, args), wrapper);
651
+ }, originalMethod);
652
+ const wrappers = Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, prototype, propertyKey) ?? 0;
653
+ Reflect.defineMetadata(controller_decorator.META.guardWrappers, wrappers + 1, prototype, propertyKey);
654
+ }
521
655
  /**
522
- * Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves them, then the
523
- * call with a pass for each wrapper inside the one entered, so no guard runs twice.
524
- */ async function runDirectCall(instance, methodName, args, call) {
656
+ * Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves and types them, then
657
+ * the call with a pass every wrapper inside the one entered takes, so no guard runs twice.
658
+ */ async function runDirectCall(instance, methodName, args, call, wrapper) {
525
659
  const prototype = Object.getPrototypeOf(instance);
526
660
  const controller = instance.constructor;
527
661
  // The container that made the instance, or else the app that made the class or a class it extends: a subclass the
@@ -530,48 +664,30 @@ const wrapperCount = perHandler((prototype, methodName)=>{
530
664
  if (!container) {
531
665
  throw new Error(`${controller.name}.${methodName}: @UseGuard runs its guards through the app that made ${controller.name}, and this ` + `instance was made outside one. Inject ${controller.name}, or call it on an instance the app made.`);
532
666
  }
667
+ const type = directCallType(prototype, methodName);
533
668
  if (!await runGuards(handlerGuards(prototype, methodName), {
534
669
  container,
535
670
  controller,
536
671
  methodName,
537
- args
672
+ args,
673
+ type
538
674
  })) return undefined;
539
- const inner = wrapperCount(prototype, methodName) - 1;
540
- if (inner <= 0) return call();
541
- const key = markKey(args[0], instance);
542
- let marks = dispatched.get(key);
543
- if (!marks) dispatched.set(key, marks = new Map());
544
- marks.set(methodName, inner);
545
- try {
546
- return await call();
547
- } finally{
548
- marks.delete(methodName);
549
- }
675
+ return runWithPass(instance, methodName, args, call, wrapper);
550
676
  }
551
677
  /**
552
- * Calls a handler dispatch has already guarded. Its wrappers let this one call through; the passes
553
- * are removed afterwards, so a later direct call with the same (cached) object runs its guards.
678
+ * Calls a handler dispatch has already guarded. Its wrappers let this one call through; the pass
679
+ * closes afterwards, so a later direct call with the same (cached) object runs its guards.
554
680
  */ async function callGuardedHandler(instance, methodName, args) {
555
- const count = wrapperCount(Object.getPrototypeOf(instance), methodName);
556
- if (count === 0) return instance[methodName](...args);
557
- const key = markKey(args[0], instance);
558
- let marks = dispatched.get(key);
559
- if (!marks) dispatched.set(key, marks = new Map());
560
- marks.set(methodName, count);
561
- try {
562
- return await instance[methodName](...args);
563
- } finally{
564
- marks.delete(methodName);
565
- }
681
+ if (!isWrapped(Object.getPrototypeOf(instance), methodName)) return instance[methodName](...args);
682
+ return runWithPass(instance, methodName, args, ()=>instance[methodName](...args));
566
683
  }
567
684
 
568
- /** The cooldowns on a handler: each class's, base first, then the method's. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
569
- const source = sourcePrototype(prototype, methodName);
570
- if (!source) return [];
685
+ /** The cooldowns on a handler: each class's, base first, then the method's and its overrides', base first. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
686
+ if (!sourcePrototype(prototype, methodName)) return [];
571
687
  return [
572
688
  // Base first, as guards run
573
689
  ...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classCooldowns, cls) ?? []),
574
- ...Reflect.getOwnMetadata(controller_decorator.META.methodCooldowns, source, methodName) ?? []
690
+ ...methodCooldowns(prototype, methodName)
575
691
  ];
576
692
  });
577
693
  /** The counted cooldowns of each list {@link handlerCooldowns} gives, which is the same for a handler. */ const countedCooldowns = new WeakMap();
@@ -612,9 +728,8 @@ const wrapperCount = perHandler((prototype, methodName)=>{
612
728
  }
613
729
  return counted;
614
730
  }
615
- /** The cooldowns declared on a method itself. */ function methodCooldowns(prototype, methodName) {
616
- const source = sourcePrototype(prototype, methodName);
617
- return source ? Reflect.getOwnMetadata(controller_decorator.META.methodCooldowns, source, methodName) ?? [] : [];
731
+ /** The cooldowns declared on a method itself and on the methods it overrides, base first. */ function methodCooldowns(prototype, methodName) {
732
+ return methodStages(prototype, methodName, controller_decorator.META.methodCooldowns);
618
733
  }
619
734
  /** Who and where a call came from: an interaction's or a message's user, server and channel. */ function caller(first) {
620
735
  if (first instanceof discord_js.BaseInteraction) return {
@@ -651,7 +766,7 @@ const DEFAULT_POLICY = {
651
766
  /** The app's cooldown policy, else refusing calls after a second without an answer. */ function cooldownPolicyOf(container) {
652
767
  return container.isBound(COOLDOWN_POLICY) ? container.get(COOLDOWN_POLICY) : DEFAULT_POLICY;
653
768
  }
654
- const logger$3 = new controller_decorator.Logger('Cooldown');
769
+ const logger$4 = new controller_decorator.Logger('Cooldown');
655
770
  /**
656
771
  * How long after its last failure a store must answer for its outage to end. A store that fails some calls and answers
657
772
  * others, as a cluster with one node down does, stays in one outage rather than starting a new one with each failure.
@@ -672,15 +787,15 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
672
787
  });
673
788
  const name = store.constructor.name;
674
789
  const reason = error.timedOut ? `did not answer within ${timeoutMs} ms` : `failed: ${String(error.cause?.message ?? error.cause)}`;
675
- if (failure === 'allow') logger$3.warn(`The cooldown store ${name} ${reason}. Calls run uncounted until it answers again.`);
676
- else logger$3.error(`The cooldown store ${name} ${reason}. Calls with a cooldown are refused until it answers again.`, error.cause ?? '');
790
+ if (failure === 'allow') logger$4.warn(`The cooldown store ${name} ${reason}. Calls run uncounted until it answers again.`);
791
+ else logger$4.error(`The cooldown store ${name} ${reason}. Calls with a cooldown are refused until it answers again.`, error.cause ?? '');
677
792
  }
678
793
  /** Logs that a store answers again, once it has gone {@link OUTAGE_QUIET_MS} without a failure. */ function reportRecovery(store) {
679
794
  const outage = outages.get(store);
680
795
  if (!outage || Date.now() - outage.last < OUTAGE_QUIET_MS) return;
681
796
  outages.delete(store);
682
797
  const seconds = Math.round((outage.last - outage.since) / 1000);
683
- logger$3.log(`The cooldown store ${store.constructor.name} answers again, after ${outage.failures} failed call(s) over ${seconds}s.`);
798
+ logger$4.log(`The cooldown store ${store.constructor.name} answers again, after ${outage.failures} failed call(s) over ${seconds}s.`);
684
799
  }
685
800
  /** What a container's calls wait for before asking its store: the store's own `onReady`, which may connect it. */ const storesReady = new WeakMap();
686
801
  /** The store operations a container's calls have under way, which its store's `onShutdown` waits for. */ const storeOperations = new WeakMap();
@@ -742,7 +857,7 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
742
857
  } catch (error) {
743
858
  if (error instanceof controller_decorator.CooldownStoreError && error.timedOut && !peek && !keepLate) {
744
859
  // Under way from now, so the store does not shut down before the call it counted is given back
745
- const release = attempt.then((verdict)=>verdict.allowed ? verdict.release?.() : undefined).catch((failure)=>logger$3.debug(`Could not release a call the cooldown store counted too late: ${String(failure)}`));
860
+ const release = attempt.then((verdict)=>verdict.allowed ? verdict.release?.() : undefined).catch((failure)=>logger$4.debug(`Could not release a call the cooldown store counted too late: ${String(failure)}`));
746
861
  trackStoreOperation(container, release);
747
862
  }
748
863
  throw error instanceof controller_decorator.CooldownStoreError ? error : new controller_decorator.CooldownStoreError(error, false);
@@ -885,7 +1000,7 @@ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[
885
1000
  try {
886
1001
  return (await askWithin(container, cooldownStoreOf(container), notices, cooldownPolicyOf(container).timeoutMs, false)).allowed;
887
1002
  } catch (error) {
888
- logger$3.debug(`Could not ask the cooldown store whether to tell a refused caller: ${String(error.cause ?? error)}`);
1003
+ logger$4.debug(`Could not ask the cooldown store whether to tell a refused caller: ${String(error.cause ?? error)}`);
889
1004
  return false;
890
1005
  }
891
1006
  }
@@ -1059,6 +1174,7 @@ function describeLength(value) {
1059
1174
  if (existing === undefined) {
1060
1175
  const name = typeof body.name === 'string' ? body.name : commandName;
1061
1176
  unlocalizable.push(...localizationProblems(name, body));
1177
+ const builderName = builderClass?.name;
1062
1178
  byKey.set(key, {
1063
1179
  builderClass: builderClass ?? builder,
1064
1180
  command: {
@@ -1066,6 +1182,9 @@ function describeLength(value) {
1066
1182
  body,
1067
1183
  ...guilds && {
1068
1184
  guilds
1185
+ },
1186
+ ...typeof builderName === 'string' && builderName && {
1187
+ builder: builderName
1069
1188
  }
1070
1189
  }
1071
1190
  });
@@ -1103,6 +1222,14 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
1103
1222
  }
1104
1223
  /** Whether a command is an activity's primary entry point, which Discord takes only globally. */ const isEntryPoint = (command)=>command.body.type === discord_js.ApplicationCommandType.PrimaryEntryPoint;
1105
1224
  /** What a blank guild to register every command to says, as an unset variable gives one. */ const BLANK_GUILD = 'No commands were registered: the guild to register to is blank. Give a guild id, or leave it out to register where commands.* says.';
1225
+ /**
1226
+ * Names every command whose builder lists guilds but no guild id, each with its builder, and says the run registers
1227
+ * none of them yet still succeeds.
1228
+ */ function blankGuildsWarning(commands) {
1229
+ const one = commands.length === 1;
1230
+ const lines = commands.map((command)=>` ${command.builder ? `${command.builder}: ` : ''}"${command.name}"`);
1231
+ return `${commands.length} ${one ? 'command is' : 'commands are'} not registered: ${one ? 'its builder lists' : 'their builders list'} ` + 'no guild ids, as when the variable holding them is unset.\n' + `${lines.join('\n')}\n` + 'Set the guild ids; a builder without guilds registers where commands.* says. `meocord register` still exits 0 for ' + 'these; the next major version (5.0) exits 1.';
1232
+ }
1106
1233
  /**
1107
1234
  * Where each command is sent: one bulk update per scope. A scope's update replaces everything the application has
1108
1235
  * there, so the default scope is included even when it ends up empty, unless `commands.guilds` lists no guild id.
@@ -1146,6 +1273,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
1146
1273
  guild
1147
1274
  });
1148
1275
  const unregistered = [];
1276
+ const blankGuilds = [];
1149
1277
  for (const command of commands){
1150
1278
  if (isEntryPoint(command)) {
1151
1279
  if (command.guilds) {
@@ -1155,7 +1283,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
1155
1283
  } else if (command.guilds) {
1156
1284
  const own = ids(command.guilds);
1157
1285
  // Falling back to the default scope could publish a staff-only command everywhere.
1158
- if (own.length === 0) logger.warn(`Command "${command.name}" lists no guild ids in its builder, so it is not registered.`);
1286
+ if (own.length === 0) blankGuilds.push(command);
1159
1287
  for (const guild of own)add({
1160
1288
  guild
1161
1289
  }, command);
@@ -1169,6 +1297,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
1169
1297
  }, command);
1170
1298
  }
1171
1299
  }
1300
+ if (blankGuilds.length > 0) logger.warn(blankGuildsWarning(blankGuilds));
1172
1301
  if (nowhere) {
1173
1302
  const outcome = unregistered.length === 0 ? targets.has('global') ? 'only the entry point command is registered globally' : 'nothing is registered globally' : `${unregistered.join(', ')} ${unregistered.length === 1 ? 'is' : 'are'} not registered, rather than registered globally`;
1174
1303
  logger.warn(`commands.guilds lists no guild id, as an unset environment variable leaves it, so ${outcome}. ` + `Set the guild ids, or remove commands.guilds to register globally.`);
@@ -1379,10 +1508,7 @@ function logRegistered(target, logger) {
1379
1508
 
1380
1509
  /** How long `@Defer({ mode: 'auto' })` waits for an answer by default. */ const AUTO_DEFER_AFTER_MS = 1500;
1381
1510
  /** The latest `'auto'` acknowledges, counted from the interaction's creation: Discord allows three seconds. */ const AUTO_DEFER_LIMIT_MS = 2500;
1382
- /** The `@Defer` options of a handler, read where the handler is declared. */ const handlerDefer = perHandler((prototype, methodName)=>{
1383
- const source = sourcePrototype(prototype, methodName);
1384
- return source ? Reflect.getOwnMetadata(controller_decorator.META.deferOptions, source, methodName) : undefined;
1385
- });
1511
+ /** The `@Defer` options of a handler: its own, or else those of the nearest method it overrides that has them. */ const handlerDefer = perHandler((prototype, methodName)=>nearestMethodStage(prototype, methodName, controller_decorator.META.deferOptions));
1386
1512
  /** The kind of non-interaction handler a method is, if any: `@Defer` cannot apply to one. */ function nonInteractionHandler(prototype, methodName) {
1387
1513
  if (controller_decorator.getMessageHandlers(prototype).some((handler)=>handler.method === methodName)) return 'message';
1388
1514
  if (controller_decorator.getReactionHandlers(prototype).some((handler)=>handler.method === methodName)) return 'reaction';
@@ -1439,21 +1565,8 @@ function logRegistered(target, logger) {
1439
1565
  */ function isAppClassToken(token) {
1440
1566
  return typeof token === 'function' && token !== discord_js.Client && !NATIVE_SOURCE$1.test(Function.prototype.toString.call(token));
1441
1567
  }
1442
- /**
1443
- * The bound classes `cls` injects, by constructor type or `@inject` token: the classes whose hooks
1444
- * run before its own.
1445
- */ function lifecycleDependencies(container, cls) {
1446
- return injectedTokens(cls).filter((token)=>isAppClassToken(token) && container.isBound(token));
1447
- }
1448
- /** The units for a list of classes already in dependency order, as the app runs them without providers. */ function classUnits(container, classes) {
1449
- return classes.map((cls)=>({
1450
- token: cls,
1451
- name: cls.name,
1452
- dependencies: lifecycleDependencies(container, cls)
1453
- }));
1454
- }
1455
1568
 
1456
- const logger$2 = new controller_decorator.Logger('Interceptor');
1569
+ const logger$3 = new controller_decorator.Logger('Interceptor');
1457
1570
  /** A function's source when the engine wrote it, as for a bound function, a built-in, or what `await` hands a promise. */ const NATIVE_SOURCE = /\{\s*\[native code\]\s*\}$/;
1458
1571
  /**
1459
1572
  * Whether `onRejected` is the app's own handling of a rejection, rather than an engine's step passing it on. The
@@ -1469,13 +1582,12 @@ function isInterceptorWithParams(entry) {
1469
1582
  }
1470
1583
  /**
1471
1584
  * The interceptors around a handler: each class's, base first, down to the controller it is dispatched on, then the
1472
- * method's.
1585
+ * method's and its overrides', base first.
1473
1586
  */ const handlerInterceptors = perHandler((prototype, methodName)=>{
1474
- const source = sourcePrototype(prototype, methodName);
1475
- if (!source) return [];
1587
+ if (!sourcePrototype(prototype, methodName)) return [];
1476
1588
  return [
1477
1589
  ...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classInterceptors, cls) ?? []),
1478
- ...Reflect.getOwnMetadata(controller_decorator.META.methodInterceptors, source, methodName) ?? []
1590
+ ...methodStages(prototype, methodName, controller_decorator.META.methodInterceptors)
1479
1591
  ];
1480
1592
  });
1481
1593
  /** Binds `cls` and its unbound dependencies as singletons, refusing any that injects `ExecutionContext`. */ function bindShared(container, cls) {
@@ -1624,7 +1736,7 @@ function isInterceptorWithParams(entry) {
1624
1736
  // Once the handlers chained from it have had their turn, and those the app's code returned a promise from settled
1625
1737
  void taken.settled.then(()=>new Promise((resolve)=>setTimeout(resolve, 0))).then(()=>Promise.all(taken.disposing)).then(()=>{
1626
1738
  if (!taken.failure || taken.caught) return;
1627
- logger$2.warn(`${cls.name} returned before ${context.getController().name}.${context.getHandlerName()} finished, which then threw; ` + 'nothing caught it, so the call could not report it:', taken.failure.error);
1739
+ logger$3.warn(`${cls.name} returned before ${context.getController().name}.${context.getHandlerName()} finished, which then threw; ` + 'nothing caught it, so the call could not report it:', taken.failure.error);
1628
1740
  });
1629
1741
  }
1630
1742
  // Only a run left on a chain that ends unhandled is the call's; one the interceptor took on, as a timeout
@@ -1660,13 +1772,13 @@ function isInterceptorWithParams(entry) {
1660
1772
  ];
1661
1773
  }
1662
1774
  /** A handler's method and class filters, the classes innermost first, resolved once. */ const ownFilterLevels = perHandler((prototype, methodName)=>{
1663
- const source = sourcePrototype(prototype, methodName);
1664
- if (!source) return {
1775
+ if (!sourcePrototype(prototype, methodName)) return {
1665
1776
  method: [],
1666
1777
  classes: []
1667
1778
  };
1668
1779
  return {
1669
- method: Reflect.getOwnMetadata(controller_decorator.META.methodFilters, source, methodName) ?? [],
1780
+ // An override's filters before those of the methods it overrides, innermost first as the classes are
1781
+ method: stageOwners(prototype, methodName).reverse().flatMap((owner)=>Reflect.getOwnMetadata(controller_decorator.META.methodFilters, owner, methodName) ?? []),
1670
1782
  classes: [
1671
1783
  ...stageClasses(prototype, methodName)
1672
1784
  ].reverse().flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classFilters, cls) ?? [])
@@ -1710,17 +1822,16 @@ function isPipeWithParams(entry) {
1710
1822
  const asList = (entries)=>Array.isArray(entries) ? entries : [
1711
1823
  entries
1712
1824
  ];
1713
- /** The validation and pipes that run on a handler's input: `@Validate`'s first, then `@UsePipe`'s. */ const handlerInputStages = perHandler((prototype, methodName)=>{
1714
- const source = sourcePrototype(prototype, methodName);
1715
- if (!source) return {
1716
- pipes: []
1717
- };
1718
- const validation = Reflect.getOwnMetadata(controller_decorator.META.methodValidation, source, methodName);
1825
+ /**
1826
+ * The validation and pipes that run on a handler's input: `@Validate`'s first, then `@UsePipe`'s. The `@Validate` is the
1827
+ * handler's own, or else that of the nearest method it overrides that has one; `@UsePipe`'s are every one's, base first.
1828
+ */ const handlerInputStages = perHandler((prototype, methodName)=>{
1829
+ const validation = nearestMethodStage(prototype, methodName, controller_decorator.META.methodValidation);
1719
1830
  const inline = Object.entries(validation?.pipes ?? {}).flatMap(([key, entries])=>asList(entries).map((entry)=>({
1720
1831
  key,
1721
1832
  entry
1722
1833
  })));
1723
- const used = Reflect.getOwnMetadata(controller_decorator.META.methodPipes, source, methodName) ?? [];
1834
+ const used = methodStages(prototype, methodName, controller_decorator.META.methodPipes);
1724
1835
  return {
1725
1836
  schema: validation?.schema,
1726
1837
  pipes: [
@@ -1786,7 +1897,7 @@ const asList = (entries)=>Array.isArray(entries) ? entries : [
1786
1897
  }
1787
1898
 
1788
1899
  /** Where a container keeps the observers its calls are reported to, in order. */ const OBSERVERS = Symbol('observers');
1789
- const logger$1 = new controller_decorator.Logger('Observer');
1900
+ const logger$2 = new controller_decorator.Logger('Observer');
1790
1901
  /** Reports each entry that is not an `@Observer` class, naming where it was listed. */ function assertObservers(where, observers) {
1791
1902
  for (const observer of observers){
1792
1903
  if (typeof observer !== 'function' || !Reflect.getMetadata(controller_decorator.META.observerClass, observer)) {
@@ -1832,7 +1943,7 @@ function observersOf(container) {
1832
1943
  */ function notifyStart(container, context) {
1833
1944
  const pending = [];
1834
1945
  for (const observer of observersFor(container, context)){
1835
- const report = (error)=>logger$1.error(`Observer ${observer.name} threw while starting ${describeCall(context)}:`, error);
1946
+ const report = (error)=>logger$2.error(`Observer ${observer.name} threw while starting ${describeCall(context)}:`, error);
1836
1947
  try {
1837
1948
  const instance = container.get(observer);
1838
1949
  if (typeof instance.onStart !== 'function') continue;
@@ -1846,8 +1957,9 @@ function observersOf(container) {
1846
1957
  }
1847
1958
  /**
1848
1959
  * Where an interaction's answer stands: a repliable one's `ResponseState`, and an autocomplete answered
1849
- * or not. `undefined` for anything that is not an interaction.
1960
+ * or not. `undefined` for anything that is not an interaction's call, an event carrying one included.
1850
1961
  */ function responsePhaseOf(context) {
1962
+ if (context.getType() === 'event') return undefined;
1851
1963
  const interaction = context.getInteraction();
1852
1964
  if (!interaction) return undefined;
1853
1965
  if (interaction.isAutocomplete()) return interaction.responded ? 'replied' : 'unanswered';
@@ -1869,13 +1981,14 @@ function observersOf(container) {
1869
1981
  try {
1870
1982
  await container.get(observer).onSettled(context, result);
1871
1983
  } catch (error) {
1872
- logger$1.error(`Observer ${observer.name} threw while observing ${describeCall(context)}:`, error);
1984
+ logger$2.error(`Observer ${observer.name} threw while observing ${describeCall(context)}:`, error);
1873
1985
  }
1874
1986
  }
1875
1987
  }
1876
1988
 
1877
- /** Classes with a `@UseTheme`, on the class or one of its methods. */ const THEMED_CLASSES = new WeakSet();
1878
- const apps = new WeakMap();
1989
+ /** Classes with a `@UseTheme`, on the class or one of its methods, from either build of this version. */ const THEMED_CLASSES = controller_decorator.shared('themedClasses', ()=>new WeakSet());
1990
+ // One per process for this meocord version, so an app started through one build finds its theme from the other
1991
+ const apps = controller_decorator.shared('appThemes', ()=>new WeakMap());
1879
1992
  /** The classes on `cls`'s prototype chain, itself first. */ function chainOf(cls) {
1880
1993
  const chain = [];
1881
1994
  for(let prototype = cls.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)){
@@ -1906,6 +2019,13 @@ const apps = new WeakMap();
1906
2019
  }
1907
2020
  controller_decorator.attachThemeCache(container.get(controller_decorator.ThemeCache), resolvers);
1908
2021
  }
2022
+ /** Counts `classes`, whose handlers are prepared once the app's providers are made, among those a theme can vary by. */ function prepareLateThemes(container, classes) {
2023
+ const themes = apps.get(container);
2024
+ if (!themes || themes.varies || !classes.some((cls)=>chainOf(cls).some((link)=>THEMED_CLASSES.has(link)))) return;
2025
+ themes.varies = true;
2026
+ // Built again on its next read, with each handler's own theme
2027
+ themes.version = -1;
2028
+ }
1909
2029
  /** The app's themes, built again when the layers beneath the app's have changed. */ function current(container) {
1910
2030
  const themes = apps.get(container);
1911
2031
  if (themes && themes.version !== controller_decorator.themeLayersVersion()) {
@@ -1920,13 +2040,12 @@ const apps = new WeakMap();
1920
2040
  }
1921
2041
  /**
1922
2042
  * Decides whether the app's calls need a scope, again whenever the layers beneath it or the app read outside a call
1923
- * change. They do when the handlers can differ in theme. Otherwise a call reads the theme outside a call, so it
1924
- * needs one unless that is its own theme: when the app owns it, or when no app does and the app's theme is MeoCord's
1925
- * defaults.
2043
+ * change. They do when the handlers can differ in theme. Otherwise a call reads the theme outside a call, so it needs
2044
+ * one unless its app owns that theme: another app may claim it while the call runs, and the call keeps the theme it
2045
+ * started with.
1926
2046
  */ function rescope(container, themes) {
1927
2047
  themes.ambientVersion = controller_decorator.ambientThemeVersion();
1928
- const readsOwnTheme = controller_decorator.ownsAmbientTheme(container) || !controller_decorator.hasAmbientTheme() && themes.app === controller_decorator.defaultTheme();
1929
- themes.scoped = themes.varies || !readsOwnTheme ? themes.app : undefined;
2048
+ themes.scoped = themes.varies || !controller_decorator.ownsAmbientTheme(container) ? themes.app : undefined;
1930
2049
  }
1931
2050
  /** The app's theme: MeoCord's defaults and the app's `@MeoCord({ theme })`, or the defaults for a container with none. */ function appTheme(container) {
1932
2051
  return current(container)?.app ?? controller_decorator.defaultTheme();
@@ -1944,8 +2063,10 @@ const apps = new WeakMap();
1944
2063
  for (const cls of stageClasses(prototype, methodName)){
1945
2064
  theme = controller_decorator.mergeTheme(theme, Reflect.getOwnMetadata(controller_decorator.META.classTheme, cls));
1946
2065
  }
1947
- const source = sourcePrototype(prototype, methodName);
1948
- if (source) theme = controller_decorator.mergeTheme(theme, Reflect.getOwnMetadata(controller_decorator.META.methodTheme, source, methodName));
2066
+ // The method's layer over its class's, and an override's over the method it overrides
2067
+ for (const owner of stageOwners(prototype, methodName)){
2068
+ theme = controller_decorator.mergeTheme(theme, Reflect.getOwnMetadata(controller_decorator.META.methodTheme, owner, methodName));
2069
+ }
1949
2070
  byMethod.set(methodName, theme);
1950
2071
  return theme;
1951
2072
  }
@@ -1984,7 +2105,7 @@ const apps = new WeakMap();
1984
2105
  ready
1985
2106
  };
1986
2107
  }
1987
- const clientApps = new WeakMap();
2108
+ const clientApps = controller_decorator.shared('clientThemes', ()=>new WeakMap());
1988
2109
  /** Records which app a client's interactions come to, so an answer outside any call can find its theme. */ function registerClientTheme(client, container) {
1989
2110
  clientApps.set(client, container);
1990
2111
  }
@@ -2155,19 +2276,35 @@ const stagesByGlobals = new WeakMap();
2155
2276
  for (const entry of globals.filters)prepareFilter(container, entry);
2156
2277
  for (const controller of controllers){
2157
2278
  const prototype = controller.prototype;
2158
- for (const method of handlerMethods(prototype)){
2159
- const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
2160
- if (kind) {
2161
- controller_decorator.startupError(deferMisuseError(controller.name, method, kind));
2162
- continue;
2163
- }
2164
- for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
2165
- for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
2166
- for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
2167
- }
2279
+ for (const method of handlerMethods(prototype))prepareHandler(container, controller, method);
2168
2280
  assertInputStagesOnInteractions(controller, prototype);
2169
2281
  }
2170
2282
  }
2283
+ /**
2284
+ * Prepares one `@On` or `@Once` handler of a class the app reaches only as something it makes, as
2285
+ * {@link prepareHandlerStages} prepares a controller's.
2286
+ *
2287
+ * @returns The errors a controller's handler would be refused with; none when the handler is ready to run.
2288
+ */ function prepareEventHandler(container, cls, method) {
2289
+ try {
2290
+ controller_decorator.collectStartupErrors(()=>{
2291
+ prepareHandler(container, cls, method);
2292
+ assertInputStages(cls, cls.prototype, method, 'event');
2293
+ });
2294
+ return [];
2295
+ } catch (error) {
2296
+ if (!controller_decorator.isRefusal(error)) throw error;
2297
+ return controller_decorator.startupErrorsOf(error);
2298
+ }
2299
+ }
2300
+ /** Binds what one handler's interceptors, filters and pipes need, refusing `@Defer` where it cannot apply. */ function prepareHandler(container, controller, method) {
2301
+ const prototype = controller.prototype;
2302
+ const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
2303
+ if (kind) return controller_decorator.startupError(deferMisuseError(controller.name, method, kind));
2304
+ for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
2305
+ for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
2306
+ for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
2307
+ }
2171
2308
  /** 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) {
2172
2309
  const globals = globalStagesOf(container);
2173
2310
  configureThemes(container, globals.theme, controllers, globals.themeFor);
@@ -2213,17 +2350,17 @@ const stagesByGlobals = new WeakMap();
2213
2350
  'event'
2214
2351
  ])
2215
2352
  ];
2216
- for (const [method, kind] of others){
2217
- const { schema, pipes } = handlerInputStages(prototype, method);
2218
- const handler = kind === 'message' ? 'a message handler without a pattern' : `${kind === 'autocomplete' || kind === 'event' ? 'an' : 'a'} ${kind} handler`;
2219
- if (schema || pipes.length > 0) {
2220
- controller_decorator.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}.`));
2221
- continue;
2222
- }
2223
- // A controller's own @Cooldown skips these handlers; one on the method itself is a mistake.
2224
- if (kind !== 'message' && methodCooldowns(prototype, method).length > 0) {
2225
- controller_decorator.startupError(new Error(`${controller.name}.${method}: @Cooldown is for interaction and message handlers, and this is ${handler}.`));
2226
- }
2353
+ for (const [method, kind] of others)assertInputStages(controller, prototype, method, kind);
2354
+ }
2355
+ /** Refuses `@Validate` and `@UsePipe` on one handler of `kind` with no params to check, and `@Cooldown` where it never counts. */ function assertInputStages(controller, prototype, method, kind) {
2356
+ const { schema, pipes } = handlerInputStages(prototype, method);
2357
+ const handler = kind === 'message' ? 'a message handler without a pattern' : `${kind === 'autocomplete' || kind === 'event' ? 'an' : 'a'} ${kind} handler`;
2358
+ if (schema || pipes.length > 0) {
2359
+ return controller_decorator.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}.`));
2360
+ }
2361
+ // A controller's own @Cooldown skips these handlers; one on the method itself is a mistake.
2362
+ if (kind !== 'message' && methodCooldowns(prototype, method).length > 0) {
2363
+ controller_decorator.startupError(new Error(`${controller.name}.${method}: @Cooldown is for interaction and message handlers, and this is ${handler}.`));
2227
2364
  }
2228
2365
  }
2229
2366
  /**
@@ -2251,7 +2388,7 @@ const stagesByGlobals = new WeakMap();
2251
2388
  if (response) result.response = response;
2252
2389
  return result;
2253
2390
  }
2254
- const logger = new controller_decorator.Logger('ExceptionFilter');
2391
+ const logger$1 = new controller_decorator.Logger('ExceptionFilter');
2255
2392
  /**
2256
2393
  * Hands a failed call's error to the first matching filter, level by level, then to the fallback. A
2257
2394
  * filter that throws is logged and the fallback answers the original error; without a fallback, the
@@ -2265,7 +2402,7 @@ const logger = new controller_decorator.Logger('ExceptionFilter');
2265
2402
  } catch (filterError) {
2266
2403
  if (!fallback) throw filterError;
2267
2404
  const name = (typeof filter === 'object' ? filter.provide : filter).name;
2268
- logger.error(`Filter ${name} threw while handling an error:`, filterError);
2405
+ logger$1.error(`Filter ${name} threw while handling an error:`, filterError);
2269
2406
  }
2270
2407
  }
2271
2408
  if (!fallback) throw error;
@@ -2471,6 +2608,23 @@ const logger = new controller_decorator.Logger('ExceptionFilter');
2471
2608
  }, options);
2472
2609
  }
2473
2610
 
2611
+ /** The longest delay a timer keeps: 2^31 - 1 ms, about 24.8 days. Node fires a longer one at once. */ const MAX_TIMER_MS = 2147483647;
2612
+ const MS_PER_SECOND = 1000;
2613
+ /** The most whole seconds a timer keeps, as a message about an option given in seconds names the limit. */ const MAX_TIMER_SECONDS = Math.floor(MAX_TIMER_MS / MS_PER_SECOND);
2614
+ /** Whether a timer keeps a delay given in seconds, a fraction of a second included. */ const timerKeepsSeconds = (seconds)=>seconds * MS_PER_SECOND <= MAX_TIMER_MS;
2615
+ /** A value as a refusal quotes it: a number as itself, `Infinity` and `NaN` included, a string in quotes. */ function quoted(value) {
2616
+ if (typeof value === 'string') return JSON.stringify(value);
2617
+ if (typeof value === 'number' || typeof value === 'boolean' || value === null) return String(value);
2618
+ return Array.isArray(value) ? 'an array' : typeof value;
2619
+ }
2620
+ /**
2621
+ * What is wrong with a timeout option, or `undefined` when it is a number of milliseconds a timer keeps: finite, above
2622
+ * 0, or 0 or more with `allowZero`, and at most `max`, {@link MAX_TIMER_MS} unless less. The caller names the option.
2623
+ */ function timeoutProblem(value, { allowZero = false, max = MAX_TIMER_MS } = {}) {
2624
+ if (typeof value === 'number' && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0) && value <= max) return undefined;
2625
+ return `must be a number of milliseconds ${allowZero ? '0 or more' : 'above 0'}, at most ${max} (got ${quoted(value)})`;
2626
+ }
2627
+
2474
2628
  /** Fetches in flight for a client, by kind, server and ID, so callers asking at once share one request. */ const inFlight = new WeakMap();
2475
2629
  /** The one request for `key`: the one in flight, or a new one from `start`, forgotten once it settles. */ function singleFlight(client, key, start) {
2476
2630
  let requests = inFlight.get(client);
@@ -2682,7 +2836,8 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
2682
2836
  }
2683
2837
  flags.push({
2684
2838
  name,
2685
- value
2839
+ value,
2840
+ at: words.length
2686
2841
  });
2687
2842
  cuts.push(start, j);
2688
2843
  i = j;
@@ -2717,6 +2872,33 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
2717
2872
  }
2718
2873
  return (rest + text.slice(from)).trimEnd();
2719
2874
  }
2875
+ const caseKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase();
2876
+ /** Whether a flag, given bare in place `at` among the other words, is a pattern's own `--` word `literal`. */ const isLiteralFlag = (flag, literal, at, caseSensitive)=>flag.at === at && flag.value === undefined && caseKey(`--${flag.name}`, caseSensitive) === caseKey(literal, caseSensitive);
2877
+ /**
2878
+ * The flags a pattern's own `--` words take from a message, as indexes into `flags`: where the pattern has such a word,
2879
+ * a flag of that name given bare in that place among the message's other words is the word, not a flag. A word typed in
2880
+ * quotes is matched as any word is, and is taken first.
2881
+ */ function literalFlags(tokens, caseSensitive, words, flags) {
2882
+ const taken = new Set();
2883
+ if (flags.length === 0) return taken;
2884
+ let w = 0;
2885
+ for (const token of tokens){
2886
+ if (!('literal' in token)) {
2887
+ // Literal words come before the optional and rest params, which a pattern ends with
2888
+ if (token.rest || token.optional) break;
2889
+ w++;
2890
+ continue;
2891
+ }
2892
+ if (w < words.length && caseKey(words[w].value, caseSensitive) === caseKey(token.literal, caseSensitive)) {
2893
+ w++;
2894
+ continue;
2895
+ }
2896
+ const index = flags.findIndex((flag, i)=>!taken.has(i) && isLiteralFlag(flag, token.literal, w, caseSensitive));
2897
+ if (index === -1) break;
2898
+ taken.add(index);
2899
+ }
2900
+ return taken;
2901
+ }
2720
2902
 
2721
2903
  /** The ID a mention or a bare snowflake names, for the mention's kind. */ function idOf(word, mention) {
2722
2904
  return (mention.exec(word) ?? /^(\d{17,20})$/.exec(word))?.[1];
@@ -2724,6 +2906,10 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
2724
2906
  const USER_MENTION = /^<@!?(\d{17,20})>$/;
2725
2907
  const ROLE_MENTION = /^<@&(\d{17,20})>$/;
2726
2908
  const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2909
+ /** The server's role a word names: its mention or ID, else its name in any case. */ function roleNamed(guild, word) {
2910
+ const id = idOf(word, ROLE_MENTION);
2911
+ return id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
2912
+ }
2727
2913
  /** The scalar types, each turning a word into its value or `undefined`, and the Discord ones, which resolve below. */ const BUILT_IN_TYPES = controller_decorator.lookupTable({
2728
2914
  string: (word)=>word,
2729
2915
  int: (word)=>controller_decorator.number(word, true),
@@ -2754,7 +2940,18 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2754
2940
  'channel'
2755
2941
  ]);
2756
2942
  /** Whether a param type is found only in a server. */ const isGuildType = (type)=>GUILD_TYPES.has(type);
2757
- /** Whether a route's pattern takes a param found only in a server, so a DM gets its usage instead. */ const needsGuild = (route)=>route.tokens.some((token)=>'param' in token && token.type !== undefined && GUILD_TYPES.has(token.type));
2943
+ /** Whether a route's pattern takes a `member`, `role` or `channel` param, optional or not. */ const takesGuildParam = (route)=>route.tokens.some((token)=>'param' in token && token.type !== undefined && GUILD_TYPES.has(token.type));
2944
+ /**
2945
+ * Whether a route works only in a server, as dispatch decides it: a `member`, `role` or `channel` param, optional or
2946
+ * not, or a required flag of one of those types. An optional flag of those types refuses a DM only when it is given.
2947
+ */ const needsGuild = (route)=>takesGuildParam(route) || route.flags.some((flag)=>!flag.optional && flag.type !== undefined && GUILD_TYPES.has(flag.type));
2948
+ /**
2949
+ * Where a route works: its scope, narrowed to servers by a `member`, `role` or `channel` param, or required flag, as
2950
+ * dispatch narrows it.
2951
+ */ function effectiveScope(route) {
2952
+ if (route.scope !== 'any') return route.scope;
2953
+ return needsGuild(route) ? 'guild' : 'any';
2954
+ }
2758
2955
  /** Whether a pattern's `{name:type}` names a type: built in, words to choose from, or one the app adds. */ function isKnownParamType(type, types) {
2759
2956
  return type in BUILT_IN_TYPES || Boolean(controller_decorator.choicesOf(type)) || appType(types, type) !== undefined;
2760
2957
  }
@@ -2834,14 +3031,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2834
3031
  */ function assertMessageScope(route, message, start) {
2835
3032
  const inGuild = message.guildId !== null && message.guildId !== undefined;
2836
3033
  if (route.scope === 'guild' && !inGuild) {
2837
- throw new controller_decorator.MessageUsageError(usageOf(route, start), [
2838
- controller_decorator.usageIssue({
2839
- key: 'meocord.usage.serverOnly'
2840
- })
2841
- ], {
2842
- serverOnly: true,
2843
- quiet: start === ''
2844
- });
3034
+ throw serverOnly(usageOf(route, start), start === '');
2845
3035
  }
2846
3036
  if (route.scope === 'dm' && inGuild) {
2847
3037
  throw new controller_decorator.MessageUsageError(usageOf(route, start), [
@@ -2854,6 +3044,15 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2854
3044
  });
2855
3045
  }
2856
3046
  }
3047
+ /** The usage error for a message sent outside a server to a command, or with a flag, that only a server has. */ const serverOnly = (usage, quiet)=>new controller_decorator.MessageUsageError(usage, [
3048
+ controller_decorator.usageIssue({
3049
+ key: 'meocord.usage.serverOnly'
3050
+ })
3051
+ ], {
3052
+ serverOnly: true,
3053
+ quiet
3054
+ });
3055
+ /** Whether a message gives one of a route's flags of a type only a server has, such as `--in=#general`. */ const givesGuildFlag = (route, flags)=>flags.some((given)=>route.flags.some((flag)=>flag.type !== undefined && GUILD_TYPES.has(flag.type) && caseKey(flag.flag, route.caseSensitive) === caseKey(given.name, route.caseSensitive)));
2857
3056
  /** Whether a value an app's `parse` returned is a ref to resolve after the guards. */ const isRef = (value)=>typeof value === 'object' && value !== null && 'cached' in value && typeof value.resolve === 'function';
2858
3057
  /**
2859
3058
  * The params a matched route's guards see, read from the words with no request to Discord: typed params and lists as
@@ -2876,18 +3075,20 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2876
3075
  const issues = [];
2877
3076
  const items = [];
2878
3077
  for (const token of route.tokens){
2879
- if (!('param' in token) || token.type === undefined || raw[token.param] === undefined) continue;
3078
+ // Only a word the message gave is typed: an own one, never what Object.prototype has under the param's name
3079
+ const word = 'param' in token ? controller_decorator.ownValue(raw, token.param) : undefined;
3080
+ if (!('param' in token) || token.type === undefined || word === undefined) continue;
2880
3081
  if (!token.rest) {
2881
3082
  items.push({
2882
3083
  key: token.param,
2883
3084
  label: token.param,
2884
3085
  type: token.type,
2885
- word: raw[token.param]
3086
+ word
2886
3087
  });
2887
3088
  continue;
2888
3089
  }
2889
- const words = splitWords(raw[token.param]);
2890
- params[token.param] = new Array(words.length);
3090
+ const words = splitWords(word);
3091
+ controller_decorator.setOwn(params, token.param, new Array(words.length));
2891
3092
  words.forEach(({ value }, index)=>items.push({
2892
3093
  key: token.param,
2893
3094
  label: token.param,
@@ -2897,19 +3098,12 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2897
3098
  }));
2898
3099
  }
2899
3100
  const flagIssues = route.flags.length > 0 ? readFlags(route, message, start, params, items) : [];
3101
+ // readFlags takes out each flag's key, so one left out whose name Object.prototype has reads as left out again
3102
+ if (route.inheritedNames.length > 0) controller_decorator.shadowInherited(params, route.inheritedNames);
2900
3103
  const guild = message.guild;
2901
- if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type)))) {
2902
- throw new controller_decorator.MessageUsageError(usage, [
2903
- controller_decorator.usageIssue({
2904
- key: 'meocord.usage.serverOnly'
2905
- })
2906
- ], {
2907
- serverOnly: true,
2908
- quiet
2909
- });
2910
- }
3104
+ if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type)))) throw serverOnly(usage, quiet);
2911
3105
  const put = (item, value)=>{
2912
- if (item.index === undefined) params[item.key] = value;
3106
+ if (item.index === undefined) controller_decorator.setOwn(params, item.key, value);
2913
3107
  else params[item.key][item.index] = value;
2914
3108
  };
2915
3109
  const refs = [];
@@ -2946,8 +3140,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2946
3140
  }
2947
3141
  } else if (type === 'role') {
2948
3142
  // Roles are cached with the Guilds intent, so a role is known here or not at all
2949
- const id = idOf(word, ROLE_MENTION);
2950
- const role = id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
3143
+ const role = roleNamed(guild, word);
2951
3144
  if (role) {
2952
3145
  refer(item, 'role', new MessageEntityRef('role', role.id, message.client, guild));
2953
3146
  continue;
@@ -2987,9 +3180,9 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2987
3180
  const params = {
2988
3181
  ...parsed.params
2989
3182
  };
2990
- for (const [key, value] of Object.entries(params))if (Array.isArray(value)) params[key] = [
3183
+ for (const [key, value] of Object.entries(params))if (Array.isArray(value)) controller_decorator.setOwn(params, key, [
2991
3184
  ...value
2992
- ];
3185
+ ]);
2993
3186
  const issues = [];
2994
3187
  for (const slot of refs){
2995
3188
  const value = slot.ref instanceof MessageEntityRef ? found.get(slot.ref) : await slot.ref.resolve();
@@ -2997,7 +3190,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
2997
3190
  issues.push(missingEntity(slot));
2998
3191
  continue;
2999
3192
  }
3000
- if (slot.index === undefined) params[slot.key] = value;
3193
+ if (slot.index === undefined) controller_decorator.setOwn(params, slot.key, value);
3001
3194
  else params[slot.key][slot.index] = value;
3002
3195
  }
3003
3196
  if (issues.length > 0) throw new controller_decorator.MessageUsageError(usage, issues, {
@@ -3063,7 +3256,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
3063
3256
  return {
3064
3257
  parseArgs: async (args)=>{
3065
3258
  assertMessageScope(route, message, start);
3066
- if (given !== undefined) throw new controller_decorator.MessageUsageError(usageOf(route, start), missingParams(route, given));
3259
+ if (given !== undefined) {
3260
+ // A DM to a command only a server has is told so before what its words lack, as one whose words fit is
3261
+ if (!message.guild && (needsGuild(route) || givesGuildFlag(route, given.flags))) throw serverOnly(usageOf(route, start), start === '');
3262
+ throw new controller_decorator.MessageUsageError(usageOf(route, start), missingParams(route, given, types, message.guild));
3263
+ }
3067
3264
  if (!hasTypedParams(route)) return args;
3068
3265
  parsed = await parseMessageParams(route, params, message, start, types);
3069
3266
  return [
@@ -3114,7 +3311,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
3114
3311
  const given = new Map();
3115
3312
  const unknown = new Set();
3116
3313
  const text = (message.content ?? '').trim();
3117
- for (const flag of splitFlagWords(text.slice(start.length)).flags){
3314
+ const split = splitFlagWords(text.slice(start.length));
3315
+ // A flag the pattern's own `--` word took is that word, and neither one of its flags nor unknown
3316
+ const literal = literalFlags(route.tokens, route.caseSensitive, split.words, split.flags);
3317
+ for (const [index, flag] of split.flags.entries()){
3318
+ if (literal.has(index)) continue;
3118
3319
  const declared = route.flags.find((candidate)=>key(candidate.flag) === key(flag.name));
3119
3320
  if (declared) given.set(declared.flag, flag);
3120
3321
  else if (!unknown.has(flag.name)) {
@@ -3140,7 +3341,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
3140
3341
  value: controller_decorator.userWords(value)
3141
3342
  }
3142
3343
  }, flag.flag));
3143
- else params[flag.flag] = on;
3344
+ else controller_decorator.setOwn(params, flag.flag, on);
3144
3345
  } else if (!given.has(flag.flag)) {
3145
3346
  if (!flag.optional) issues.push(controller_decorator.usageIssue({
3146
3347
  key: 'meocord.usage.missing',
@@ -3168,29 +3369,137 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
3168
3369
  return issues;
3169
3370
  }
3170
3371
  /**
3171
- * What is wrong with a command whose words a message named but whose pattern it does not fit, given how many
3172
- * words followed the command words: each required param it left out, or words beyond what the pattern takes.
3173
- */ function missingParams(route, given) {
3174
- const firstParam = route.tokens.findIndex((token)=>!('literal' in token));
3175
- const after = firstParam === -1 ? [] : route.tokens.slice(firstParam);
3176
- const missing = after.slice(given).filter((token)=>'param' in token && !token.optional).map((token)=>controller_decorator.usageIssue({
3177
- key: 'meocord.usage.missing',
3178
- params: {
3179
- param: token.param
3372
+ * What is wrong with a command whose words a message named but whose pattern it does not fit, read word by word
3373
+ * against the pattern: each word or required param it left out, a word given another in its place, else words
3374
+ * beyond what the pattern takes, else the first word its param's built-in type does not take; and then each required
3375
+ * flag it left out, as a message whose words fit is told. With none of those true, there are none, and the usage stands
3376
+ * alone.
3377
+ */ function missingParams(route, given, types, guild) {
3378
+ const { words, flags } = given;
3379
+ const missing = [];
3380
+ // The flags given bare in place of the pattern's own `--` words, which are those words and set no flag
3381
+ const literal = new Set();
3382
+ let unfit;
3383
+ let w = 0;
3384
+ for (const token of route.tokens){
3385
+ if ('literal' in token) {
3386
+ if (w < words.length && caseKey(words[w].value, route.caseSensitive) === caseKey(token.literal, route.caseSensitive)) {
3387
+ w++;
3388
+ continue;
3180
3389
  }
3181
- }, token.param));
3182
- if (missing.length > 0) return missing;
3183
- return [
3390
+ // A pattern's own `--` word given bare in its place takes no word; another word in its place is not taken
3391
+ const word = flags.find((flag)=>!literal.has(flag) && isLiteralFlag(flag, token.literal, w, route.caseSensitive));
3392
+ if (word) literal.add(word);
3393
+ else missing.push(controller_decorator.usageIssue({
3394
+ key: 'meocord.usage.missing',
3395
+ params: {
3396
+ param: token.literal
3397
+ }
3398
+ }));
3399
+ } else if (w === words.length) {
3400
+ if (!token.optional) missing.push(controller_decorator.usageIssue({
3401
+ key: 'meocord.usage.missing',
3402
+ params: {
3403
+ param: token.param
3404
+ }
3405
+ }, token.param));
3406
+ } else if (token.rest) {
3407
+ w = words.length;
3408
+ } else {
3409
+ // Words fill the params in order, as the usage shows them
3410
+ const word = words[w++].value;
3411
+ const type = token.type;
3412
+ if (!unfit && type !== undefined && isUnfit(type, word, route.caseSensitive, guild)) {
3413
+ unfit = {
3414
+ key: token.param,
3415
+ label: token.param,
3416
+ type,
3417
+ word
3418
+ };
3419
+ }
3420
+ }
3421
+ }
3422
+ // With none of these, the reason may be one MeoCord has no text for, as a role's name the matcher takes only as a
3423
+ // mention or an ID
3424
+ const reasons = missing.length > 0 ? missing : w < words.length ? [
3184
3425
  controller_decorator.usageIssue({
3185
3426
  key: 'meocord.usage.tooManyWords'
3186
3427
  })
3428
+ ] : unfit ? [
3429
+ wrongType(unfit, types)
3430
+ ] : [];
3431
+ // So one reply names all the message lacks, rather than the flags only once its words fit
3432
+ const missingFlags = route.flags.filter((flag)=>flag.type !== undefined && !flag.optional && !flags.some((sent)=>!literal.has(sent) && caseKey(sent.name, route.caseSensitive) === caseKey(flag.flag, route.caseSensitive))).map((flag)=>controller_decorator.usageIssue({
3433
+ key: 'meocord.usage.missing',
3434
+ params: {
3435
+ param: `--${flag.flag}`
3436
+ }
3437
+ }, flag.flag));
3438
+ return [
3439
+ ...reasons,
3440
+ ...missingFlags
3187
3441
  ];
3188
3442
  }
3443
+ /**
3444
+ * Whether a param of a type is known not to take a word: a built-in type or words to choose from that the word doesn't
3445
+ * fit. A role is also found by its name, so only a word that names none of the server's roles is unfit.
3446
+ */ function isUnfit(type, word, caseSensitive, guild) {
3447
+ if (type === 'role') return guild !== null && roleNamed(guild, word) === undefined;
3448
+ return (type in BUILT_IN_TYPES || Boolean(controller_decorator.choicesOf(type))) && !fitsParamType(type, word, caseSensitive);
3449
+ }
3189
3450
  /** Whether a route's params need resolving: it declares a type for one, or has flags. */ const hasTypedParams = (route)=>route.flags.length > 0 || route.tokens.some((token)=>'param' in token && token.type !== undefined);
3190
3451
 
3452
+ const logger = new controller_decorator.Logger('MeoCord');
3191
3453
  /** Whether a route's scope lets it run where the message was sent. */ const fitsScope = (scope, inGuild)=>inGuild === undefined || scope === 'any' || scope === 'guild' === inGuild;
3192
3454
  const PARAM = /^\{(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\.\.\.)?(\?)?\}$/;
3193
3455
  const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3456
+ /**
3457
+ * A whole brace pair that PARAM or FLAG refused, read loosely to tell its name from its type: anything up to `:` or `}`,
3458
+ * then the marks a param ends with, `...` and `?`, wherever they were written.
3459
+ */ const LOOSE_FLAG = /^\{--([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
3460
+ const LOOSE_PARAM = /^\{([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
3461
+ /** A name or type with marks after it, as one written out of order carries them. */ const MARKED = /^(.*?)((?:\.\.\.|\?)+)$/;
3462
+ /** A param's name, and a flag's, which also starts with a letter, as a message could not give it otherwise. */ const PARAM_NAME = /^\w+$/;
3463
+ const FLAG_NAME = /^[A-Za-z]\w*$/;
3464
+ /** A param as its pattern writes it, which the grammar spells only one way. */ const writtenParam = (token)=>`{${token.param}${token.type ? `:${token.type}` : ''}${token.rest ? '...' : ''}${token.optional ? '?' : ''}}`;
3465
+ /** A flag as its pattern writes it. */ const writtenFlag = (flag)=>`{--${flag.flag}${flag.type ? `:${flag.type}` : ''}${flag.optional ? '?' : ''}}`;
3466
+ /**
3467
+ * Why a word with a brace in it is no param or flag: text around its braces, or a whole pair whose name, type or marks
3468
+ * the syntax doesn't take. Each names the rule broken, with the word mended as an example.
3469
+ */ function notAParam(word) {
3470
+ const flag = LOOSE_FLAG.exec(word);
3471
+ if (flag) {
3472
+ const [, name, type, rest] = flag;
3473
+ if (rest || word.includes('...')) {
3474
+ return `"${word}" is not a flag: a flag doesn't take the rest of the message; a param does, such as {${mended(unmarked(name), 'param')}...}.`;
3475
+ }
3476
+ if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
3477
+ const typed = type === undefined ? '' : `:${unmarked(type)}`;
3478
+ return `"${word}" is not a flag: its ? goes last, as in {--${unmarked(name)}${typed}?}.`;
3479
+ }
3480
+ if (!FLAG_NAME.test(name)) {
3481
+ return `"${word}" is not a flag: a flag's name starts with a letter, then letters, digits or _, such as {--${mended(name, 'flag')}}.`;
3482
+ }
3483
+ return `"${word}" is not a flag: a type is a name, or words to choose from joined by |, such as {--${name}:on|off}.`;
3484
+ }
3485
+ const param = LOOSE_PARAM.exec(word);
3486
+ if (param) {
3487
+ const [, name, type] = param;
3488
+ if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
3489
+ const typed = type === undefined ? '' : `:${unmarked(type)}`;
3490
+ const ordered = `{${unmarked(name)}${typed}${word.includes('...') ? '...' : ''}${word.includes('?') ? '?' : ''}}`;
3491
+ return `"${word}" is not a param: its marks go last, ... then ?, as in ${ordered}.`;
3492
+ }
3493
+ if (!PARAM_NAME.test(name)) return `"${word}" is not a param: a param's name is ASCII letters, digits and _, such as {${mended(name, 'param')}}.`;
3494
+ return `"${word}" is not a param: a type is a name, or words to choose from joined by |, such as {${name}:on|off}.`;
3495
+ }
3496
+ return `"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`;
3497
+ }
3498
+ /** A name or type without the marks written after it, `...` and `?`. */ const unmarked = (text)=>MARKED.exec(text)?.[1] ?? text;
3499
+ /** A name with each character the syntax doesn't take as `_`, and a flag's led by a letter, for an example. */ function mended(name, kind) {
3500
+ const word = name.replace(/\W/g, '_') || kind;
3501
+ return kind === 'flag' && !/^[A-Za-z]/.test(word) ? `flag${word}` : word;
3502
+ }
3194
3503
  /**
3195
3504
  * Reads a pattern into its words and flags. Throws for a pattern of flags alone, a param that is not a whole word, a
3196
3505
  * name given twice, a rest param that is not last, a word after an optional param that is not optional too, an untyped
@@ -3226,9 +3535,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3226
3535
  words.forEach((word, index)=>{
3227
3536
  const match = PARAM.exec(word);
3228
3537
  if (!match) {
3229
- if (/[{}]/.test(word)) {
3230
- throw new Error(`"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`);
3231
- }
3538
+ if (/[{}]/.test(word)) throw new Error(notAParam(word));
3232
3539
  tokens.push({
3233
3540
  literal: word
3234
3541
  });
@@ -3251,7 +3558,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3251
3558
  const next = tokens[index + 1];
3252
3559
  if (!('param' in token) || !token.optional || !next) return;
3253
3560
  if (!('param' in next) || !next.optional) {
3254
- throw new Error(`{${token.param}?} is optional, so only optional params may follow it; "${'param' in next ? `{${next.param}}` : next.literal}" is not.`);
3561
+ throw new Error(`${writtenParam(token)} is optional, so only optional params may follow it; "${'param' in next ? writtenParam(next) : next.literal}" is not.`);
3255
3562
  }
3256
3563
  if (token.type === undefined || token.type === 'string') {
3257
3564
  throw new Error(`{${token.param}${token.type ? ':string' : ''}?} comes before another optional param, so it needs a type that tells its words ` + `from the next param's, such as {${token.param}:int?}: as text, it would take every word.`);
@@ -3264,10 +3571,15 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3264
3571
  rest: params.some((token)=>token.rest),
3265
3572
  optional: params.some((token)=>token.optional)
3266
3573
  });
3574
+ const inheritedNames = [
3575
+ ...params.map((token)=>token.param),
3576
+ ...flags.map((flag)=>flag.flag)
3577
+ ].filter((name)=>name in Object.prototype);
3267
3578
  return {
3268
3579
  tokens,
3269
3580
  flags,
3270
- specificity
3581
+ specificity,
3582
+ inheritedNames
3271
3583
  };
3272
3584
  }
3273
3585
  /** A prefix setting as a list, where `''` stands for none. */ function prefixList(prefix) {
@@ -3318,7 +3630,20 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3318
3630
  ...mention
3319
3631
  ]);
3320
3632
  }
3321
- /** Whether two routes accept some of the same messages: a start they share, and words that match alike. */ function sameMessages(a, b, options) {
3633
+ /** Where two routes of equal rank and shape part: the one with a required param where the other has an optional one comes first. */ function compareOptional(a, b) {
3634
+ for(let i = 0; i < Math.min(a.length, b.length); i++){
3635
+ const [aToken, bToken] = [
3636
+ a[i],
3637
+ b[i]
3638
+ ];
3639
+ if ('param' in aToken && 'param' in bToken && aToken.optional !== bToken.optional) return aToken.optional ? 1 : -1;
3640
+ }
3641
+ return 0;
3642
+ }
3643
+ /**
3644
+ * Whether two routes accept some of the same messages: a start they share, and words that match alike. `'optional'`
3645
+ * when they do only because a required param and an optional one, in one position, both take a word there.
3646
+ */ function sameMessages(a, b, options) {
3322
3647
  // A case-insensitive word matches every message the case-sensitive one does
3323
3648
  const exact = a.caseSensitive && b.caseSensitive;
3324
3649
  const key = (start)=>typeof start === 'string' && !exact ? start.toLowerCase() : start;
@@ -3336,13 +3661,55 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3336
3661
  ].some((start)=>theirs.has(key(start)));
3337
3662
  });
3338
3663
  if (!startAlike || a.tokens.length !== b.tokens.length) return false;
3339
- return a.tokens.every((token, i)=>{
3664
+ let byOptional = false;
3665
+ const alike = a.tokens.every((token, i)=>{
3340
3666
  const other = b.tokens[i];
3341
3667
  if ('literal' in token) {
3342
3668
  return 'literal' in other && (exact ? token.literal === other.literal : token.literal.toLowerCase() === other.literal.toLowerCase());
3343
3669
  }
3344
- return 'param' in other && token.rest === other.rest && token.optional === other.optional;
3670
+ if (!('param' in other) || token.rest !== other.rest) return false;
3671
+ if (token.optional !== other.optional) byOptional = true;
3672
+ return true;
3345
3673
  });
3674
+ return alike && (byOptional ? 'optional' : true);
3675
+ }
3676
+ /** What a static empty prefix list does now, which a prefix function's `[]` doesn't: take messages as they are. */ const EMPTY_PREFIX_LIST = "`[]` takes messages as they are, with no prefix; write `''` for that";
3677
+ /**
3678
+ * The warnings for message handlers a bot starts with today though a message can't reach them as written, each once,
3679
+ * for the app or the handler: a static empty prefix list, which takes messages as they are, where a prefix function's
3680
+ * `[]` starts no command; and two flags of a command that reads flags in any case whose names differ only in case, the
3681
+ * second of which is never set. The next major version (5.0) changes the first and refuses the second.
3682
+ */ function messageRouteWarnings(routes, options) {
3683
+ const warnings = [];
3684
+ if (Array.isArray(options.prefix) && options.prefix.length === 0) {
3685
+ warnings.push(`@MeoCord({ messages: { prefix: [] } }): ${EMPTY_PREFIX_LIST}. In the next major version (5.0), \`[]\` starts no command, ` + "as a prefix function's `[]` does.");
3686
+ }
3687
+ const seen = new Map();
3688
+ for (const route of routes){
3689
+ // A handler's aliases share its options and flags, so each is read once; one method can carry several handlers
3690
+ const pattern = route.aliasOf ?? route.pattern;
3691
+ const methods = seen.get(route.controllerClass) ?? seen.set(route.controllerClass, new Map()).get(route.controllerClass);
3692
+ const patterns = methods.get(route.method) ?? methods.set(route.method, new Set()).get(route.method);
3693
+ if (patterns.has(pattern)) continue;
3694
+ patterns.add(pattern);
3695
+ const handler = `${route.controllerClass.name}.${route.method}: @MessageHandler('${pattern}'`;
3696
+ const own = controller_decorator.getMessageHandlers(route.controllerClass.prototype).find((candidate)=>candidate.method === route.method && candidate.pattern === pattern)?.options;
3697
+ if (Array.isArray(own?.prefix) && own.prefix.length === 0) {
3698
+ warnings.push(`${handler}, { prefix: [] }): ${EMPTY_PREFIX_LIST}, or \`false\` for no prefix and no mention. In the next major ` + 'version (5.0), `[]` starts no command.');
3699
+ }
3700
+ if (route.caseSensitive) continue;
3701
+ const first = new Map();
3702
+ for (const flag of route.flags){
3703
+ const same = first.get(flag.flag.toLowerCase());
3704
+ if (!same) {
3705
+ first.set(flag.flag.toLowerCase(), flag);
3706
+ continue;
3707
+ }
3708
+ warnings.push(`${handler}): ${writtenFlag(same)} and ${writtenFlag(flag)} differ only in case, and this command reads flags in any ` + `case, so --${flag.flag} always sets ${writtenFlag(same)} and ${writtenFlag(flag)} is never set. Rename one, or set ` + 'caseSensitive: true. The next major version (5.0) refuses this.');
3709
+ break;
3710
+ }
3711
+ }
3712
+ return warnings;
3346
3713
  }
3347
3714
  /**
3348
3715
  * Every patterned message handler of the given controllers, most specific first, read from metadata
@@ -3411,14 +3778,21 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3411
3778
  });
3412
3779
  }
3413
3780
  }
3414
- routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
3781
+ routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || compareOptional(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
3415
3782
  for(let i = 0; i < routes.length; i++){
3416
3783
  for(let j = i + 1; j < routes.length; j++){
3417
3784
  const [a, b] = [
3418
3785
  routes[i],
3419
3786
  routes[j]
3420
3787
  ];
3421
- if (!sameMessages(a, b, options)) continue;
3788
+ const same = sameMessages(a, b, options);
3789
+ if (!same) continue;
3790
+ // Ranked by their params, so the same one answers whatever the order; 5.0 refuses these as it does the rest
3791
+ if (same === 'optional') {
3792
+ if (a.controllerClass === b.controllerClass && a.method === b.method) continue;
3793
+ controller_decorator.warnOnce(logger, `${a.controllerClass.name}.${a.method}: ${describeRoute(a)} and ${describeRoute(b)} in ${b.controllerClass.name}.${b.method} can ` + `take the same messages, as one takes a word where the other has an optional param; ${a.controllerClass.name}.${a.method} ` + 'answers those. Change one pattern, or give one its own prefix. The next major version (5.0) stops the bot at startup for these.');
3794
+ continue;
3795
+ }
3422
3796
  // One handler under two spellings, such as 'hello' and 'Hello', is one route, its pattern's over an alias's
3423
3797
  if (a.controllerClass === b.controllerClass && a.method === b.method) {
3424
3798
  if (a.aliasOf && !b.aliasOf) routes[i] = b;
@@ -3463,7 +3837,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
3463
3837
  if (scope !== 'dm') return;
3464
3838
  const param = tokens.find((token)=>'param' in token && token.type !== undefined && isGuildType(token.type));
3465
3839
  const flag = flags.find((candidate)=>candidate.type !== undefined && isGuildType(candidate.type));
3466
- const shown = param ? `{${param.param}:${param.type}}` : flag && `{--${flag.flag}:${flag.type}}`;
3840
+ const shown = param ? writtenParam(param) : flag && writtenFlag(flag);
3467
3841
  if (shown) throw new Error(`scope is 'dm', but ${shown} is found only in a server.`);
3468
3842
  }
3469
3843
  /** Refuses a handler's `mention` other than `'only'`. */ function assertMention({ mention }) {
@@ -3575,11 +3949,11 @@ const trieNode = ()=>({
3575
3949
  for(let t = 0; t < tail.length && w < words.length; t++){
3576
3950
  const token = tail[t];
3577
3951
  if (token.rest) {
3578
- params[token.param] = restFrom(text, words[w].start, cuts);
3952
+ controller_decorator.setOwn(params, token.param, restFrom(text, words[w].start, cuts));
3579
3953
  return params;
3580
3954
  }
3581
3955
  if (t < tail.length - 1 && !fitsParamType(token.type, words[w].value, caseSensitive)) continue;
3582
- params[token.param] = words[w++].value;
3956
+ controller_decorator.setOwn(params, token.param, words[w++].value);
3583
3957
  }
3584
3958
  return w === words.length ? params : undefined;
3585
3959
  }
@@ -3624,9 +3998,12 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
3624
3998
  return;
3625
3999
  }
3626
4000
  if ('literal' in token) {
4001
+ var _node1;
3627
4002
  const key = wordKey(token.literal, route.caseSensitive);
3628
4003
  let next = node.words.get(key);
3629
4004
  if (!next) node.words.set(key, next = trieNode());
4005
+ // Such a word typed bare is taken out as a flag before the words are walked, so it is matched as one too
4006
+ if (route.flags.length > 0 && token.literal.startsWith('--')) ((_node1 = node).flagWords ?? (_node1.flagWords = new Map())).set(key, next);
3630
4007
  node = next;
3631
4008
  continue;
3632
4009
  }
@@ -3645,6 +4022,8 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
3645
4022
  ];
3646
4023
  for (const group of all){
3647
4024
  const flagged = group.flagged;
4025
+ // A pattern's own leading `--` word stays reachable there: given bare, it is taken out as a flag, so the message
4026
+ // seems to start with one
3648
4027
  if (flagged) group.flaggedAfterFlag = {
3649
4028
  ...flagged,
3650
4029
  words: new Map()
@@ -3685,7 +4064,16 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
3685
4064
  if (first === undefined || node.param || node.rests.length > 0 || node.tails.length > 0 || node.ends.length > 0) return true;
3686
4065
  return node.words.has(first);
3687
4066
  }
3688
- /** The ranks of every route the words reach from `node`. Each node sits at one word depth, so each is visited once. */ function reach(node, words, i, text, cuts, caseSensitive, found) {
4067
+ /**
4068
+ * The ranks of every route the words reach from `node`. A pattern's own `--` word is reached by that word, or by a flag
4069
+ * of its name given bare in its place, which takes no word.
4070
+ */ function reach(node, words, i, text, cuts, caseSensitive, flags, found) {
4071
+ if (node.flagWords) {
4072
+ for (const flag of flags){
4073
+ const next = flag.at === i && flag.value === undefined ? node.flagWords.get(wordKey(`--${flag.name}`, caseSensitive)) : undefined;
4074
+ if (next) reach(next, words, i, text, cuts, caseSensitive, flags, found);
4075
+ }
4076
+ }
3689
4077
  for (const tail of node.tails)if (assignTail(tail.tokens, words, i, text, cuts, caseSensitive)) found.push(tail.rank);
3690
4078
  if (i === words.length) {
3691
4079
  for (const rank of node.ends)found.push(rank);
@@ -3693,22 +4081,34 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
3693
4081
  }
3694
4082
  for (const rank of node.rests)found.push(rank);
3695
4083
  const next = node.words.get(wordKey(words[i].value, caseSensitive));
3696
- if (next) reach(next, words, i + 1, text, cuts, caseSensitive, found);
3697
- if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, found);
4084
+ if (next) reach(next, words, i + 1, text, cuts, caseSensitive, flags, found);
4085
+ if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, flags, found);
3698
4086
  }
3699
- /** The params a route's words capture, given words its pattern is known to match. */ function paramsOf$1(route, words, text, cuts) {
4087
+ /**
4088
+ * The params a route's words capture, given words its pattern is known to match. A pattern's own `--` word that the
4089
+ * message gave as a flag, one of `taken`, takes no word.
4090
+ */ function paramsOf$1(route, words, text, cuts, taken) {
3700
4091
  const tail = tailStart(route.tokens);
3701
- const params = tail === -1 ? {} : assignTail(route.tokens.slice(tail), words, tail, text, cuts, route.caseSensitive);
3702
- for(let i = 0; i < (tail === -1 ? route.tokens.length : tail) && i < words.length; i++){
4092
+ const params = {};
4093
+ let left = taken;
4094
+ let w = 0;
4095
+ for(let i = 0; i < (tail === -1 ? route.tokens.length : tail) && w < words.length; i++){
3703
4096
  const token = route.tokens[i];
3704
- if ('literal' in token) continue;
4097
+ if ('literal' in token) {
4098
+ if (left > 0 && token.literal.startsWith('--') && wordKey(words[w].value, route.caseSensitive) !== wordKey(token.literal, route.caseSensitive)) left--;
4099
+ else w++;
4100
+ continue;
4101
+ }
3705
4102
  if (token.rest) {
3706
- params[token.param] = restFrom(text, words[i].start, cuts);
3707
- break;
4103
+ controller_decorator.setOwn(params, token.param, restFrom(text, words[w].start, cuts));
4104
+ return params;
3708
4105
  }
3709
- params[token.param] = words[i].value;
4106
+ controller_decorator.setOwn(params, token.param, words[w++].value);
3710
4107
  }
3711
- return params;
4108
+ return tail === -1 ? params : {
4109
+ ...params,
4110
+ ...assignTail(route.tokens.slice(tail), words, w, text, cuts, route.caseSensitive)
4111
+ };
3712
4112
  }
3713
4113
  /**
3714
4114
  * The text after the start a message used for the group's routes, or `undefined` when it used none: a mention
@@ -3761,7 +4161,7 @@ const indexes = new WeakMap();
3761
4161
  const found = [];
3762
4162
  if (mayEnter(group.root, first)) {
3763
4163
  const words = wordsOf(rest);
3764
- reach(group.root, words, 0, rest, NONE, group.caseSensitive, found);
4164
+ reach(group.root, words, 0, rest, NONE, group.caseSensitive, NONE, found);
3765
4165
  for (const rank of found){
3766
4166
  if (fitsScope(routes[rank].scope, starts.inGuild)) {
3767
4167
  if (!best || rank < best.rank) best = {
@@ -3790,7 +4190,7 @@ const indexes = new WeakMap();
3790
4190
  };
3791
4191
  found.length = 0;
3792
4192
  const from = scanned.words[0]?.start === 0 ? flagged : group.flaggedAfterFlag;
3793
- reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, found);
4193
+ reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, scanned.flags, found);
3794
4194
  for (const rank of found){
3795
4195
  const candidate = {
3796
4196
  rank,
@@ -3807,11 +4207,16 @@ const indexes = new WeakMap();
3807
4207
  const chosen = best ?? outside;
3808
4208
  if (!chosen) return undefined;
3809
4209
  const route = routes[chosen.rank];
3810
- const params = paramsOf$1(route, chosen.words, chosen.rest, chosen.cuts);
3811
- for (const { name, value } of chosen.flags){
4210
+ // The flags the pattern's own `--` words took are those words, and set none of its flags
4211
+ const taken = literalFlags(route.tokens, route.caseSensitive, chosen.words, chosen.flags);
4212
+ const params = paramsOf$1(route, chosen.words, chosen.rest, chosen.cuts, taken.size);
4213
+ chosen.flags.forEach(({ name, value }, index)=>{
4214
+ if (taken.has(index)) return;
3812
4215
  const declared = route.flags.find((flag)=>wordKey(flag.flag, route.caseSensitive) === wordKey(name, route.caseSensitive));
3813
- if (declared) params[declared.flag] = value ?? '';
3814
- }
4216
+ if (declared) controller_decorator.setOwn(params, declared.flag, value ?? '');
4217
+ });
4218
+ // A param or flag left out reads as left out, not as what Object.prototype has under its name
4219
+ if (route.inheritedNames.length > 0) controller_decorator.shadowInherited(params, route.inheritedNames);
3815
4220
  return {
3816
4221
  route,
3817
4222
  params,
@@ -3841,19 +4246,28 @@ const indexes = new WeakMap();
3841
4246
  if (first !== undefined && !group.commands.has(first)) continue;
3842
4247
  const plain = splitWords(rest);
3843
4248
  // A route with flags counts the words left once the message's flags are taken out
3844
- const positional = mayEnter(group.flagged, first) && rest.includes('--') ? splitFlagWords(rest).words : plain;
4249
+ const scanned = mayEnter(group.flagged, first) && rest.includes('--') ? splitFlagWords(rest) : {
4250
+ words: plain,
4251
+ flags: NONE
4252
+ };
3845
4253
  const candidates = group.commands.get(wordKey(plain[0]?.value ?? '', group.caseSensitive)) ?? [];
3846
4254
  for (const rank of candidates){
3847
4255
  if (best && rank >= best.rank) break;
3848
- const words = routes[rank].flags.length > 0 ? positional : plain;
3849
- const command = commandWordsOf(routes[rank].tokens);
3850
- if (command.every((word, i)=>words[i] && wordKey(words[i].value, group.caseSensitive) === wordKey(word, group.caseSensitive))) {
4256
+ const flagged = routes[rank].flags.length > 0;
4257
+ const words = flagged ? scanned.words : plain;
4258
+ const flags = flagged ? scanned.flags : NONE;
4259
+ const taken = commandWordsTaken(commandWordsOf(routes[rank].tokens), words, flags, group.caseSensitive);
4260
+ if (taken !== -1) {
3851
4261
  const named = {
3852
4262
  rank,
3853
4263
  start: text.slice(0, text.length - rest.length),
3854
- given: words.length - command.length
4264
+ given: {
4265
+ words,
4266
+ flags
4267
+ }
3855
4268
  };
3856
- if (fitsScope(routes[rank].scope, starts.inGuild)) {
4269
+ // Where it works as dispatch answers it, so a DM's usage comes from a route that works there
4270
+ if (fitsScope(effectiveScope(routes[rank]), starts.inGuild)) {
3857
4271
  best = named;
3858
4272
  break;
3859
4273
  }
@@ -3868,6 +4282,17 @@ const indexes = new WeakMap();
3868
4282
  given: chosen.given
3869
4283
  };
3870
4284
  }
4285
+ /**
4286
+ * How many of a message's words a route's command words take, or -1 when the message names another command. A
4287
+ * command's own `--` word may be given bare as a flag in its place, and takes no word.
4288
+ */ function commandWordsTaken(command, words, flags, caseSensitive) {
4289
+ let w = 0;
4290
+ for (const word of command){
4291
+ if (words[w] && wordKey(words[w].value, caseSensitive) === wordKey(word, caseSensitive)) w++;
4292
+ else if (!flags.some((flag)=>isLiteralFlag(flag, word, w, caseSensitive))) return -1;
4293
+ }
4294
+ return w;
4295
+ }
3871
4296
  /**
3872
4297
  * The subcommands a message names the parent of, when no pattern matches it and it names no command: the routes whose
3873
4298
  * command words begin with the most of the message's words, short of all of them, as `!config` does for `config set …`
@@ -3910,11 +4335,18 @@ const indexes = new WeakMap();
3910
4335
  }
3911
4336
  return found.length > 0 ? found : undefined;
3912
4337
  }
4338
+ /**
4339
+ * Whether a message has text a handler could take; dispatch runs no handler for one without. Content that isn't a
4340
+ * string, as a mock's placeholder without strict mocks, is no text.
4341
+ */ function hasMessageText(content) {
4342
+ return typeof content === 'string' && content.trim() !== '';
4343
+ }
3913
4344
  /**
3914
4345
  * What a test calling a handler with a message alone passes as its params, as dispatch builds them: `undefined` for a
3915
- * listener, `{}` for a message without content, else what the pattern captures with the route and start; for one naming
3916
- * the command without fitting it, the route, start and `given`, the words after the command words. Either only when
3917
- * dispatch would give the message to this handler among `controllers`; otherwise why it does not reach the handler.
4346
+ * listener, `{}` for a message whose content isn't a string, as a mock's without strict mocks, else what the pattern
4347
+ * captures with the route and start; for one naming the command without fitting it, the route, start and `given`, the
4348
+ * message's words and flags. Either only when dispatch would give the message to this handler among
4349
+ * `controllers`; otherwise why it does not reach the handler, a message with no text included.
3918
4350
  */ async function messageParamsFor(controllerClass, methodName, message, options, controllers = [
3919
4351
  controllerClass
3920
4352
  ]) {
@@ -3926,6 +4358,13 @@ const indexes = new WeakMap();
3926
4358
  if (typeof message.content !== 'string') return {
3927
4359
  params: {}
3928
4360
  };
4361
+ const handler = `${controllerClass.name}.${methodName}`;
4362
+ const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
4363
+ if (!hasMessageText(message.content)) {
4364
+ return {
4365
+ mismatch: `message has no text, so dispatch runs no handler for it: give the mock the content ${handler}'s pattern '${pattern}' takes.`
4366
+ };
4367
+ }
3929
4368
  const botId = message.client?.user?.id;
3930
4369
  const all = buildMessageRoutes(controllers, options);
3931
4370
  const starts = await messageStartsFor(all, options, message, typeof botId === 'string' ? botId : undefined);
@@ -3934,7 +4373,7 @@ const indexes = new WeakMap();
3934
4373
  if (target && (target.route.controllerClass !== controllerClass || target.route.method !== methodName)) {
3935
4374
  const other = `${target.route.controllerClass.name}.${target.route.method}`;
3936
4375
  return {
3937
- mismatch: `message '${message.content}' does not reach ${controllerClass.name}.${methodName}: dispatch runs ${other}.`
4376
+ mismatch: `message '${message.content}' does not reach ${handler}: dispatch runs ${other}.`
3938
4377
  };
3939
4378
  }
3940
4379
  if (target && 'params' in target) return {
@@ -3948,9 +4387,8 @@ const indexes = new WeakMap();
3948
4387
  start: target.start,
3949
4388
  given: target.given
3950
4389
  };
3951
- const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
3952
4390
  return {
3953
- mismatch: `message '${message.content}' does not match ${controllerClass.name}.${methodName}'s pattern '${pattern}'.`
4391
+ mismatch: `message '${message.content}' does not match ${handler}'s pattern '${pattern}'.`
3954
4392
  };
3955
4393
  }
3956
4394
 
@@ -3979,12 +4417,13 @@ const indexes = new WeakMap();
3979
4417
  const text = content.trim();
3980
4418
  const rest = afterStart(text, starts.prefixes.filter((prefix)=>prefix !== ''), starts.mention, caseSensitive);
3981
4419
  if (!rest) return undefined;
3982
- const [first] = splitWords(rest);
4420
+ const [first, second] = splitWords(rest);
3983
4421
  const key = (word)=>caseSensitive ? word : word.toLowerCase();
3984
4422
  if (!first || !words.some((word)=>key(word) === key(first.value))) return undefined;
4423
+ // From where the next word starts, as the help word may have been written in quotes
3985
4424
  return {
3986
4425
  start: text.slice(0, text.length - rest.length),
3987
- query: rest.slice(first.value.length).trim()
4426
+ query: second ? rest.slice(second.start).trim() : ''
3988
4427
  };
3989
4428
  }
3990
4429
  /** The start a caller would type a route with here: its own prefix, a mention, or the start the help message used. */ function displayStart(route, start, starts) {
@@ -3993,11 +4432,6 @@ const indexes = new WeakMap();
3993
4432
  if (route.prefix !== undefined) return route.prefix[0] ?? '';
3994
4433
  return start;
3995
4434
  }
3996
- /** Where a route works: its scope, narrowed to servers by a `member`, `role` or `channel` param or flag. */ function effectiveScope(route) {
3997
- if (route.scope !== 'any') return route.scope;
3998
- const guildOnly = route.tokens.some((token)=>'param' in token && token.type !== undefined && isGuildType(token.type)) || route.flags.some((flag)=>flag.type !== undefined && isGuildType(flag.type));
3999
- return guildOnly ? 'guild' : 'any';
4000
- }
4001
4435
  /** A route's params and flags, each with what it takes in words. */ function paramsOf(route, types, render) {
4002
4436
  const params = route.tokens.flatMap((token)=>{
4003
4437
  if (!('param' in token)) return [];
@@ -4298,6 +4732,28 @@ const COMMAND_TYPES = new Set([
4298
4732
  };
4299
4733
  }
4300
4734
  /** The message routes the app's dispatcher reads, by the app's registry, so its help and dispatch read one table. */ const dispatchedRoutes = new WeakMap();
4735
+ /** What every entry has: its class and method, and the metadata on them. */ function entryBase(controller, method) {
4736
+ const context = new controller_decorator.HandlerExecutionContext({
4737
+ controller,
4738
+ methodName: method,
4739
+ args: []
4740
+ });
4741
+ return {
4742
+ controller,
4743
+ method,
4744
+ get: context.get.bind(context),
4745
+ getAll: context.getAll.bind(context)
4746
+ };
4747
+ }
4748
+ const lateEventSources = new WeakMap();
4749
+ // The late sources each registry's cached entries were read with, so a list made before they arrived is read again
4750
+ const collectedWith = new WeakMap();
4751
+ /**
4752
+ * Has `registry` list the `@On` and `@Once` handlers that run on `sources`, the instances the app attaches them for,
4753
+ * where they are on a class it does not list already.
4754
+ */ function listEventSources(registry, sources) {
4755
+ lateEventSources.set(registry, sources);
4756
+ }
4301
4757
  /** Has `registry` work out message help from `routes`, the table the app's dispatcher routes messages with. */ function shareMessageRoutes(registry, routes) {
4302
4758
  dispatchedRoutes.set(registry, routes);
4303
4759
  }
@@ -4338,7 +4794,11 @@ const COMMAND_TYPES = new Set([
4338
4794
  * @param filter - Narrows the list by kind, controller, or both.
4339
4795
  * @returns The handlers, class by class in the order the app makes its classes, each after what it injects.
4340
4796
  */ list(filter = {}) {
4341
- this.entries ?? (this.entries = this.collect());
4797
+ const late = lateEventSources.get(this);
4798
+ if (!this.entries || collectedWith.get(this) !== late) {
4799
+ this.entries = this.collect();
4800
+ collectedWith.set(this, late);
4801
+ }
4342
4802
  return this.entries.filter((entry)=>(filter.kind === undefined || entry.kind === filter.kind) && (filter.controller === undefined || entry.controller === filter.controller));
4343
4803
  }
4344
4804
  /**
@@ -4362,8 +4822,9 @@ const COMMAND_TYPES = new Set([
4362
4822
  * }
4363
4823
  * ```
4364
4824
  */ async messageHelp(message, query) {
4825
+ // Until a dispatcher shares its table, the same one built from the same controllers, so help lists what dispatch reaches
4365
4826
  const routes = dispatchedRoutes.get(this) ?? this.messageRoutes ?? (this.messageRoutes = buildMessageRoutes([
4366
- ...this.classes
4827
+ ...this.controllers
4367
4828
  ], this.messages));
4368
4829
  const starts = await messageStarts(this.messages, message, message.client?.user?.id);
4369
4830
  const text = (message.content ?? '').trim();
@@ -4393,19 +4854,7 @@ const COMMAND_TYPES = new Set([
4393
4854
  const entries = [];
4394
4855
  for (const controller of this.classes){
4395
4856
  const prototype = controller.prototype;
4396
- const base = (method)=>{
4397
- const context = new controller_decorator.HandlerExecutionContext({
4398
- controller,
4399
- methodName: method,
4400
- args: []
4401
- });
4402
- return {
4403
- controller,
4404
- method,
4405
- get: context.get.bind(context),
4406
- getAll: context.getAll.bind(context)
4407
- };
4408
- };
4857
+ const base = (method)=>entryBase(controller, method);
4409
4858
  for (const [name, metas] of Object.entries(controller_decorator.getCommandMap(prototype) ?? {})){
4410
4859
  for (const { methodName, type, builder } of metas){
4411
4860
  if (COMMAND_TYPES.has(type)) {
@@ -4468,34 +4917,35 @@ const COMMAND_TYPES = new Set([
4468
4917
  });
4469
4918
  }
4470
4919
  }
4920
+ // The classes the app reaches only as something it makes, listed once each, with the handlers that run on them
4921
+ const listed = new Set(this.classes);
4922
+ for (const { cls, handlers } of lateEventSources.get(this) ?? []){
4923
+ if (listed.has(cls)) continue;
4924
+ listed.add(cls);
4925
+ for (const { event, method, once } of handlers)entries.push({
4926
+ ...entryBase(cls, method),
4927
+ kind: 'event',
4928
+ name: event,
4929
+ once
4930
+ });
4931
+ }
4471
4932
  return entries;
4472
4933
  }
4473
4934
  /**
4474
- * @param classes - The app's classes to read handlers from. The factory fills the list
4475
- * once the app is bound; entries are read on the first {@link list}.
4935
+ * @param classes - The app's classes to read handlers from, controllers and services alike. The factory fills the
4936
+ * list once the app is bound; entries are read on the first {@link list}.
4476
4937
  * @param messages - The app's `messages` options, whose `caseSensitive` message entries' `matches` follows.
4477
4938
  * @param translator - The app's translator, read when {@link messageHelp} words its labels; none for English.
4478
- */ constructor(classes, messages = {}, translator = ()=>undefined){
4939
+ * @param controllers - The app's controllers, the classes dispatch routes messages to, which {@link messageHelp}
4940
+ * lists; `classes` when not given.
4941
+ */ constructor(classes, messages = {}, translator = ()=>undefined, controllers = classes){
4479
4942
  this.classes = classes;
4480
4943
  this.messages = messages;
4481
4944
  this.translator = translator;
4945
+ this.controllers = controllers;
4482
4946
  }
4483
4947
  }
4484
4948
 
4485
- /** The longest delay a timer keeps: 2^31 - 1 ms, about 24.8 days. Node fires a longer one at once. */ const MAX_TIMER_MS = 2147483647;
4486
- /** A value as a refusal quotes it: a number as itself, `Infinity` and `NaN` included, a string in quotes. */ function quoted(value) {
4487
- if (typeof value === 'string') return JSON.stringify(value);
4488
- if (typeof value === 'number' || typeof value === 'boolean' || value === null) return String(value);
4489
- return Array.isArray(value) ? 'an array' : typeof value;
4490
- }
4491
- /**
4492
- * What is wrong with a timeout option, or `undefined` when it is a number of milliseconds a timer keeps: finite, above
4493
- * 0, or 0 or more with `allowZero`, and at most `max`, {@link MAX_TIMER_MS} unless less. The caller names the option.
4494
- */ function timeoutProblem(value, { allowZero = false, max = MAX_TIMER_MS } = {}) {
4495
- if (typeof value === 'number' && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0) && value <= max) return undefined;
4496
- return `must be a number of milliseconds ${allowZero ? '0 or more' : 'above 0'}, at most ${max} (got ${quoted(value)})`;
4497
- }
4498
-
4499
4949
  /** Where each shard's client keeps the function that runs a `ShardContext.call` in that shard. */ const SHARD_CALL_KEY = Symbol.for('meocord.shardCall');
4500
4950
  const packArgs = (args)=>Object.assign({
4501
4951
  length: args.length
@@ -4603,11 +5053,11 @@ async function withTimeout(promise, ms, what) {
4603
5053
  *
4604
5054
  * With process sharding that is one result per shard; with one process, a single result listing every
4605
5055
  * shard. The arguments and the result pass as JSON in every mode, one process and tests included, so a
4606
- * `Date` arrives as a string and a `Map` as `{}` wherever it runs, and a value JSON cannot carry, such as a
4607
- * `BigInt`, fails the call. A method whose params JSON would change, such as one taking a `Date`, cannot be called:
4608
- * the argument is refused, naming what to declare instead. An `undefined` argument arrives as `undefined`. A process
4609
- * that throws, lacks the service, or does not answer within 10 seconds gives an error result instead of failing the
4610
- * others.
5056
+ * `Date` arrives as a string, a `Map` as `{}` and a class instance as its own fields, without its getters, wherever
5057
+ * it runs, and a value JSON cannot carry, such as a `BigInt`, fails the call. A method whose params JSON would
5058
+ * change, such as one taking a `Date`, cannot be called: the argument is refused, naming what to declare instead. An
5059
+ * `undefined` argument arrives as `undefined`. A process that throws, lacks the service, or does not answer within
5060
+ * 10 seconds gives an error result instead of failing the others.
4611
5061
  *
4612
5062
  * @param service - The controller, the service, or a class a provider stands in for; each process resolves its own
4613
5063
  * instance.
@@ -4713,6 +5163,18 @@ async function withTimeout(promise, ms, what) {
4713
5163
  controller_decorator.ThemeCache,
4714
5164
  controller_decorator.Translator
4715
5165
  ];
5166
+ /**
5167
+ * The tokens the bot binds before an app's providers, which `@MeoCord({ providers })` cannot provide: `Translator` too
5168
+ * when the app gives `i18n`.
5169
+ */ const boundBeforeProviders = (i18n)=>[
5170
+ discord_js.Client,
5171
+ HandlerRegistry,
5172
+ ShardContext,
5173
+ controller_decorator.CooldownStore,
5174
+ ...i18n ? [
5175
+ controller_decorator.Translator
5176
+ ] : []
5177
+ ];
4716
5178
  const created = (cls)=>`create it with \`new ${cls.name}(…)\``;
4717
5179
  /** 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([
4718
5180
  [
@@ -4751,6 +5213,18 @@ const isFactoryProvider = (provider)=>'useFactory' in provider;
4751
5213
  return `'${String(token)}'`;
4752
5214
  }
4753
5215
  const isToken = (value)=>typeof value === 'function' || typeof value === 'string' || typeof value === 'symbol';
5216
+ /**
5217
+ * Checks one of an app's own `@MeoCord({ providers })` as the bot does. A provider of a token MeoCord binds later, or
5218
+ * for each call, is warned about until 5.0 refuses it; one of a token `isOwn` names, which MeoCord binds before the
5219
+ * providers, is refused.
5220
+ *
5221
+ * @returns The refusal, for the caller to report, or `undefined`.
5222
+ */ function appProviderRefusal(appName, token, isOwn, logger) {
5223
+ if (token === controller_decorator.ThemeCache || token === controller_decorator.ExecutionContext) {
5224
+ controller_decorator.warnOnce(logger, `${appName}: @MeoCord({ providers }) provides ${tokenName(token)}, which MeoCord binds itself; ` + 'the next major version (5.0) refuses it. Remove the provider.');
5225
+ }
5226
+ return isOwn(token) ? new Error(`${appName}: @MeoCord({ providers }) cannot provide ${tokenName(token)}, which MeoCord binds itself.`) : undefined;
5227
+ }
4754
5228
  /**
4755
5229
  * Checks each provider's shape and indexes them by token, reporting each that cannot be bound, such as one with no
4756
5230
  * token, not exactly one of `useValue`, `useClass` and `useFactory`, one that injects `ExecutionContext`, or a token
@@ -4810,6 +5284,10 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
4810
5284
  }
4811
5285
  return map;
4812
5286
  }
5287
+ /** For each container, how to forget the value each factory provider made, so a start that failed is made afresh. */ const madeValues = new WeakMap();
5288
+ /** Forgets every value `container`'s factory providers made, so the next resolve runs each factory again. */ function forgetFactoryValues(container) {
5289
+ for (const forget of madeValues.get(container) ?? [])forget();
5290
+ }
4813
5291
  /**
4814
5292
  * Binds one provider into `container` as a singleton. `bindClass` binds a class a provider depends
4815
5293
  * on, as the app binds its own, so a provided class or a factory's `inject` can name any class.
@@ -4830,6 +5308,10 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
4830
5308
  // Made once, as a singleton, but kept only once made: a factory that throws or rejects runs again on the next
4831
5309
  // resolve, so a start() after it failed tries it again. Resolves while one is pending share it.
4832
5310
  let kept;
5311
+ madeValues.set(container, [
5312
+ ...madeValues.get(container) ?? [],
5313
+ ()=>kept = undefined
5314
+ ]);
4833
5315
  container.bind(token).toDynamicValue((context)=>{
4834
5316
  if (kept) return kept.value;
4835
5317
  // Resolved in dependency order before anything needs it, so each injected value is already made
@@ -5001,11 +5483,13 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
5001
5483
  * Runs every factory in `order`, awaiting those that return a promise, so each value is made before
5002
5484
  * anything that injects it is resolved. Rejects with {@link providerFailure} for the first that fails. With `control`,
5003
5485
  * it makes no further value once the app is stopping.
5486
+ *
5487
+ * @returns Whether every factory was made: `false` when the app began stopping first.
5004
5488
  */ async function resolveProviders(container, providers, order, control) {
5005
5489
  for (const token of order){
5006
5490
  const provider = providers.get(token);
5007
5491
  if (!provider || !isFactoryProvider(provider)) continue;
5008
- if (control?.stopped()) return;
5492
+ if (control?.stopped()) return false;
5009
5493
  control?.pending(tokenName(token));
5010
5494
  try {
5011
5495
  await container.getAsync(token);
@@ -5015,12 +5499,14 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
5015
5499
  control?.pending(undefined);
5016
5500
  }
5017
5501
  }
5502
+ return true;
5018
5503
  }
5019
5504
 
5020
5505
  exports.BUILT_IN_TYPES = BUILT_IN_TYPES;
5021
5506
  exports.COOLDOWN_POLICY = COOLDOWN_POLICY;
5022
5507
  exports.HandlerRegistry = HandlerRegistry;
5023
5508
  exports.MAX_TIMER_MS = MAX_TIMER_MS;
5509
+ exports.MAX_TIMER_SECONDS = MAX_TIMER_SECONDS;
5024
5510
  exports.On = On;
5025
5511
  exports.Once = Once;
5026
5512
  exports.SENT_AS = SENT_AS;
@@ -5030,7 +5516,11 @@ exports.THEMED_CLASSES = THEMED_CLASSES;
5030
5516
  exports.adviseInPlaceOfInjecting = adviseInPlaceOfInjecting;
5031
5517
  exports.appObservers = appObservers;
5032
5518
  exports.appPresenterOf = appPresenterOf;
5519
+ exports.appProviderRefusal = appProviderRefusal;
5033
5520
  exports.appStages = appStages;
5521
+ exports.appTheme = appTheme;
5522
+ exports.appliesTo = appliesTo;
5523
+ exports.applyGuards = applyGuards;
5034
5524
  exports.assertObservers = assertObservers;
5035
5525
  exports.assertProvided = assertProvided;
5036
5526
  exports.assertProvidedServices = assertProvidedServices;
@@ -5042,37 +5532,41 @@ exports.bindGlobalStages = bindGlobalStages;
5042
5532
  exports.bindObservers = bindObservers;
5043
5533
  exports.bindProvider = bindProvider;
5044
5534
  exports.bindsOwnToken = bindsOwnToken;
5535
+ exports.boundBeforeProviders = boundBeforeProviders;
5045
5536
  exports.buildMessageRoutes = buildMessageRoutes;
5046
5537
  exports.claimAmbientAppTheme = claimAmbientAppTheme;
5047
5538
  exports.claimCooldownNotice = claimCooldownNotice;
5048
5539
  exports.claimStoreDownNotice = claimStoreDownNotice;
5049
- exports.classChain = classChain;
5050
5540
  exports.classDecorators = classDecorators;
5051
5541
  exports.classLevelGuards = classLevelGuards;
5052
- exports.classUnits = classUnits;
5053
5542
  exports.commandNameOf = commandNameOf;
5054
5543
  exports.commandWordsOf = commandWordsOf;
5055
5544
  exports.computeMessageHelp = computeMessageHelp;
5056
- exports.consumeDispatchMark = consumeDispatchMark;
5057
5545
  exports.cooldownPolicyFrom = cooldownPolicyFrom;
5058
5546
  exports.declaringPrototype = declaringPrototype;
5059
5547
  exports.deferMisuseError = deferMisuseError;
5060
5548
  exports.defineStageTypes = defineStageTypes;
5061
5549
  exports.fitsScope = fitsScope;
5550
+ exports.forgetFactoryValues = forgetFactoryValues;
5062
5551
  exports.getEventHandlers = getEventHandlers;
5063
5552
  exports.globalStagesOf = globalStagesOf;
5553
+ exports.guardOwnHandlersWithBaseGuards = guardOwnHandlersWithBaseGuards;
5064
5554
  exports.handleUnroutedError = handleUnroutedError;
5065
5555
  exports.handlerCooldowns = handlerCooldowns;
5066
5556
  exports.handlerMethods = handlerMethods$1;
5067
5557
  exports.handlerStageClasses = handlerStageClasses;
5068
5558
  exports.handlerStages = handlerStages;
5559
+ exports.hasMessageText = hasMessageText;
5069
5560
  exports.helpInvocation = helpInvocation;
5070
5561
  exports.helpWords = helpWords;
5071
5562
  exports.injectedTokens = injectedTokens;
5072
5563
  exports.isAppClassToken = isAppClassToken;
5073
5564
  exports.isClassProvider = isClassProvider;
5074
5565
  exports.isGuardWithParams = isGuardWithParams;
5566
+ exports.isGuildType = isGuildType;
5075
5567
  exports.isListable = isListable;
5568
+ exports.isValueProvider = isValueProvider;
5569
+ exports.listEventSources = listEventSources;
5076
5570
  exports.makeInjectable = makeInjectable;
5077
5571
  exports.markStartupChecked = markStartupChecked;
5078
5572
  exports.matchHelpRequest = matchHelpRequest;
@@ -5082,17 +5576,21 @@ exports.matchMessageSubcommands = matchMessageSubcommands;
5082
5576
  exports.meocordClasses = meocordClasses;
5083
5577
  exports.messageCommandHooks = messageCommandHooks;
5084
5578
  exports.messageParamsFor = messageParamsFor;
5579
+ exports.messageRouteWarnings = messageRouteWarnings;
5085
5580
  exports.messageStarts = messageStarts;
5086
5581
  exports.messageStartsFor = messageStartsFor;
5087
5582
  exports.needsGuild = needsGuild;
5088
5583
  exports.nonInteractionHandler = nonInteractionHandler;
5089
5584
  exports.observeUnclaimed = observeUnclaimed;
5090
5585
  exports.prepareAppPresenter = prepareAppPresenter;
5586
+ exports.prepareDirectCalls = prepareDirectCalls;
5587
+ exports.prepareEventHandler = prepareEventHandler;
5091
5588
  exports.prepareHandlerStages = prepareHandlerStages;
5589
+ exports.prepareLateThemes = prepareLateThemes;
5092
5590
  exports.prepareThemes = prepareThemes;
5093
5591
  exports.providerMap = providerMap;
5094
5592
  exports.reachableClasses = reachableClasses;
5095
- exports.recordInstanceContainers = recordInstanceContainers;
5593
+ exports.recordGuards = recordGuards;
5096
5594
  exports.registerClientTheme = registerClientTheme;
5097
5595
  exports.registerCommands = registerCommands;
5098
5596
  exports.registrationKey = registrationKey;
@@ -5100,7 +5598,6 @@ exports.releaseAmbientAppTheme = releaseAmbientAppTheme;
5100
5598
  exports.renderMessageHelp = renderMessageHelp;
5101
5599
  exports.resolutionOrder = resolutionOrder;
5102
5600
  exports.resolveProviders = resolveProviders;
5103
- exports.runDirectCall = runDirectCall;
5104
5601
  exports.runGuards = runGuards;
5105
5602
  exports.runHandler = runHandler;
5106
5603
  exports.runInAppTheme = runInAppTheme;
@@ -5114,7 +5611,9 @@ exports.stageTypes = stageTypes;
5114
5611
  exports.staticMessageStarts = staticMessageStarts;
5115
5612
  exports.storeOperationsSettled = storeOperationsSettled;
5116
5613
  exports.subcommandUsageError = subcommandUsageError;
5614
+ exports.takesGuildParam = takesGuildParam;
5117
5615
  exports.timeoutProblem = timeoutProblem;
5616
+ exports.timerKeepsSeconds = timerKeepsSeconds;
5118
5617
  exports.tokenDependencies = tokenDependencies;
5119
5618
  exports.tokenName = tokenName;
5120
5619
  exports.usesAppPrefix = usesAppPrefix;