meocord 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-CRn9kWob.cjs} +295 -87
  3. package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-ChvMqnLd.cjs} +74 -40
  4. package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-BdAUTdYs.cjs} +1 -1
  5. package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CQo8mSP8.cjs} +158 -72
  6. package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-DhDVbT5M.cjs} +12 -3
  7. package/dist/cjs/common/index.cjs +4 -4
  8. package/dist/cjs/core/index.cjs +195 -157
  9. package/dist/cjs/decorator/index.cjs +66 -57
  10. package/dist/cjs/testing/index.cjs +733 -301
  11. package/dist/cli.json +1 -1
  12. package/dist/esm/bin/app-template/README.md.template +3 -1
  13. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
  14. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
  15. package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
  16. package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
  17. package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
  18. package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
  19. package/dist/esm/build/load-config.pre-entry.js +4 -1
  20. package/dist/esm/common/decorator.js +6 -6
  21. package/dist/esm/common/deprecation.js +2 -2
  22. package/dist/esm/common/errors.js +2 -2
  23. package/dist/esm/common/execution-context.js +1 -1
  24. package/dist/esm/common/metadata.js +1 -1
  25. package/dist/esm/common/route.js +10 -1
  26. package/dist/esm/common/translate-error.js +1 -1
  27. package/dist/esm/core/command-conflicts.js +34 -34
  28. package/dist/esm/core/component-routes.js +74 -31
  29. package/dist/esm/core/dispatcher.js +51 -7
  30. package/dist/esm/core/filter-runner.js +2 -2
  31. package/dist/esm/core/handler-pipeline.js +27 -14
  32. package/dist/esm/core/handler-registry.js +1 -1
  33. package/dist/esm/core/interceptor-runner.js +2 -2
  34. package/dist/esm/core/meocord-factory.js +196 -157
  35. package/dist/esm/core/message-routes.js +4 -3
  36. package/dist/esm/core/observer-runner.js +4 -3
  37. package/dist/esm/core/providers.js +38 -21
  38. package/dist/esm/core/route-specificity.js +4 -4
  39. package/dist/esm/core/scalar-types.js +15 -3
  40. package/dist/esm/core/shard-context.js +1 -1
  41. package/dist/esm/core/startup-roots.js +2 -3
  42. package/dist/esm/core/theme-runtime.js +2 -2
  43. package/dist/esm/decorator/app.decorator.js +12 -7
  44. package/dist/esm/decorator/command-builder.decorator.js +4 -4
  45. package/dist/esm/decorator/controller-class.decorator.js +9 -4
  46. package/dist/esm/decorator/controller.decorator.js +110 -67
  47. package/dist/esm/decorator/cooldown.decorator.js +3 -3
  48. package/dist/esm/decorator/defer.decorator.js +4 -4
  49. package/dist/esm/decorator/event.decorator.js +5 -5
  50. package/dist/esm/decorator/filter.decorator.js +7 -7
  51. package/dist/esm/decorator/guard.decorator.js +8 -8
  52. package/dist/esm/decorator/inject.decorator.js +4 -4
  53. package/dist/esm/decorator/interceptor.decorator.js +7 -7
  54. package/dist/esm/decorator/observer.decorator.js +4 -4
  55. package/dist/esm/decorator/service.decorator.js +4 -4
  56. package/dist/esm/decorator/theme.decorator.js +3 -3
  57. package/dist/esm/decorator/validation.decorator.js +10 -10
  58. package/dist/esm/package.json.js +1 -1
  59. package/dist/esm/testing/catalog.js +1 -1
  60. package/dist/esm/testing/cooldown-store-suite.js +18 -10
  61. package/dist/esm/testing/discord-defaults.js +60 -7
  62. package/dist/esm/testing/index.js +4 -2
  63. package/dist/esm/testing/inspect-handler.js +20 -3
  64. package/dist/esm/testing/meocord-testing-module.js +229 -188
  65. package/dist/esm/testing/mock-fn.js +80 -10
  66. package/dist/esm/testing/mock-interaction.js +223 -72
  67. package/dist/esm/testing/response.js +1 -1
  68. package/dist/esm/testing/routing.js +39 -14
  69. package/dist/esm/testing/startup-errors.js +26 -0
  70. package/dist/esm/testing/strict-mocks.js +45 -0
  71. package/dist/esm/testing/theme.js +1 -1
  72. package/dist/esm/util/bundle-entry.util.js +2 -1
  73. package/dist/esm/util/interaction.util.js +1 -1
  74. package/dist/esm/util/meocord-config-validation.util.js +1 -0
  75. package/dist/esm/util/metadata-keys.js +1 -0
  76. package/dist/esm/util/refusal.util.js +148 -8
  77. package/dist/types/common/index.d.cts +8 -8
  78. package/dist/types/common/index.d.ts +8 -8
  79. package/dist/types/core/index.d.cts +8 -6
  80. package/dist/types/core/index.d.ts +8 -6
  81. package/dist/types/decorator/index.d.cts +30 -29
  82. package/dist/types/decorator/index.d.ts +30 -29
  83. package/dist/types/{index-D7n0FBMm.d.ts → index-CdXsFgjE.d.ts} +62 -15
  84. package/dist/types/{index-DEP0NYrU.d.cts → index-nn5p7OoX.d.cts} +62 -15
  85. package/dist/types/interface/index.d.cts +1 -1
  86. package/dist/types/interface/index.d.ts +1 -1
  87. package/dist/types/{route-C-aMI7Sl.d.cts → route-BZgFoNeB.d.cts} +5 -2
  88. package/dist/types/{route-C-aMI7Sl.d.ts → route-BZgFoNeB.d.ts} +5 -2
  89. package/dist/types/testing/index.d.cts +245 -31
  90. package/dist/types/testing/index.d.ts +245 -31
  91. package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-BcOJVzR9.d.ts} +1 -1
  92. package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-UKr3-6Ys.d.cts} +1 -1
  93. package/dist/types/{translator-J6tIUfdp.d.ts → translator-BViWKtLJ.d.ts} +1 -1
  94. package/dist/types/{translator-B4mc1lzJ.d.cts → translator-DHMkkCpg.d.cts} +1 -1
  95. package/package.json +3 -2
@@ -27,6 +27,7 @@ var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
27
27
 
28
28
  /** Where the built bundle's pre-entry records the bundle's own path. */ const BUNDLE_ENTRY_KEY = Symbol.for('meocord.bundleEntry');
29
29
  /** Where the built bundle's pre-entry records the mode the bundle was built in. */ const BUILD_MODE_KEY = Symbol.for('meocord.buildMode');
30
+ /** Where the built bot's pre-entry records `startupErrors` from its config, before the application's modules load. */ const STARTUP_ERRORS_KEY = Symbol.for('meocord.startupErrorsMode');
30
31
  /**
31
32
  * The file a shard process runs: the built bundle, as its pre-entry recorded it, else the script this
32
33
  * process was started with; undefined when there is none, as for code run with `node -e` outside a built bundle.
@@ -119,7 +120,7 @@ let problem;
119
120
  try {
120
121
  // Called through a variable so a bundler does not try to resolve the path while building the
121
122
  // application; the file is only there once the build is done.
122
- const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/controller.decorator-BywwkU9O.cjs', document.baseURI).href)));
123
+ const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/controller.decorator-CRn9kWob.cjs', document.baseURI).href)));
123
124
  const loaded = load(compiledPath);
124
125
  return loaded.default ?? loaded;
125
126
  } catch (error) {
@@ -352,7 +353,7 @@ const warned = new Set();
352
353
  */ function warnPlaceholder(logger, value, outcome, how) {
353
354
  warnOnce(logger, `${value} reads a placeholder here; in the next major version (5.0) ${outcome}. ${how}`);
354
355
  }
355
- function warnOnce(logger, text) {
356
+ /** Logs `text` once per run, however often it is asked for. */ function warnOnce(logger, text) {
356
357
  if (warned.has(text)) return;
357
358
  warned.add(text);
358
359
  logger.warn(text);
@@ -692,10 +693,10 @@ const REFUSAL = Symbol.for('meocord.refusal');
692
693
  // A stack frame's function and file: `at fn (file:line:col)` or `at file:line:col`, the file possibly a file:// URL
693
694
  const FRAME = /^\s*at (?:(.*?) \()?(.+?):\d+:\d+\)?$/;
694
695
  /**
695
- * A refusal as the application reports it: the message, and the first file of the application's own source the stack
696
- * passes through, other than its entry, which only loads the rest. The file alone: a line from a development build's
697
- * source map can be off, and a compiler's decorate helper can be placed in any file, so those frames are skipped.
698
- */ function describeRefusal(error, root, windows = process.platform === 'win32') {
696
+ * The first file of the application's own source an error's stack passes through, other than its entry, which only
697
+ * loads the rest, relative to `root`. The file alone: a line from a development build's source map can be off, and a
698
+ * compiler's decorate helper can be placed in any file, so those frames are skipped.
699
+ */ function sourceFileOf(error, root, windows = process.platform === 'win32') {
699
700
  const paths = windows ? path__default.default.win32 : path__default.default.posix;
700
701
  const source = comparablePath(paths.join(root, 'src') + paths.sep, windows);
701
702
  const entry = comparablePath(paths.join(root, 'src', 'main.ts'), windows);
@@ -706,9 +707,149 @@ const FRAME = /^\s*at (?:(.*?) \()?(.+?):\d+:\d+\)?$/;
706
707
  const file = paths.normalize(framePath(name, windows));
707
708
  const compared = comparablePath(file, windows);
708
709
  if (!compared.startsWith(source) || compared === entry || compared.includes(dependencies)) continue;
709
- return `${error.message}\n in ${paths.relative(root, file).split(paths.sep).join('/')}`;
710
+ return paths.relative(root, file).split(paths.sep).join('/');
710
711
  }
711
- return error.message;
712
+ return undefined;
713
+ }
714
+ /**
715
+ * A refusal as the application reports it: the message, led by the declaration it is about where the message does not
716
+ * name it, and the file of the application's source it comes from; see {@link sourceFileOf}.
717
+ */ function describeRefusal(error, root, windows = process.platform === 'win32') {
718
+ const declaration = declarationOf(error);
719
+ const message = declaration && !error.message.startsWith(declaration) ? `${declaration}: ${error.message}` : error.message;
720
+ const file = sourceFileOf(error, root, windows);
721
+ return file ? `${message}\n in ${file}` : message;
722
+ }
723
+ // ---------------------------------------------------------------------------
724
+ // Startup errors: those create() and a testing module's compile() find, and those decorators find at import
725
+ // ---------------------------------------------------------------------------
726
+ const STARTUP_ERRORS = Symbol.for('meocord.startupErrors');
727
+ const DECLARED = Symbol.for('meocord.declaredStartupErrors');
728
+ const ALL_DECLARED = Symbol.for('meocord.allDeclaredStartupErrors');
729
+ // The startup errors kept while checks run under collectStartupErrors; undefined outside them
730
+ let kept;
731
+ /**
732
+ * Reports an error a startup check finds. Outside {@link collectStartupErrors} it is thrown, as a refusal; inside, it
733
+ * is kept, and the check goes on to find the rest.
734
+ */ function startupError(error) {
735
+ refuse(error);
736
+ if (!kept) throw error;
737
+ // Two checks of one mistake, such as a class needing a translator, name it once
738
+ if (!kept.some((other)=>other.message === error.message)) kept.push(error);
739
+ }
740
+ /**
741
+ * Runs startup checks, and once they are done throws the first error they found, unchanged, with every one found on it
742
+ * for {@link startupErrorsOf}. An error that ends the checks after one was found is left out, as one the found error
743
+ * caused; one that ends them before any was found is thrown as it is.
744
+ */ function collectStartupErrors(checks) {
745
+ const outer = kept;
746
+ const found = [];
747
+ kept = found;
748
+ let result;
749
+ try {
750
+ result = checks();
751
+ } catch (error) {
752
+ if (found.length === 0) throw error;
753
+ if (isRefusal(error) && !found.includes(error)) found.push(error);
754
+ throw firstOf(found);
755
+ } finally{
756
+ kept = outer;
757
+ }
758
+ if (found.length > 0) throw firstOf(found);
759
+ return result;
760
+ }
761
+ /**
762
+ * Ends the checks under way with the errors found so far, before a step that is no check, such as binding or a warning:
763
+ * a run with one error stops where it stopped when the first error was thrown, and the errors of one pass of the
764
+ * checks are still reported together.
765
+ */ function stopOnStartupErrors() {
766
+ if (kept && kept.length > 0) throw kept[0];
767
+ }
768
+ function firstOf(errors) {
769
+ const [first] = errors;
770
+ Object.defineProperty(first, STARTUP_ERRORS, {
771
+ value: errors,
772
+ configurable: true
773
+ });
774
+ return first;
775
+ }
776
+ /** Every startup error found together with `error`, the first of them; `error` alone when it was the only one. */ function startupErrorsOf(error) {
777
+ return error[STARTUP_ERRORS] ?? [
778
+ error
779
+ ];
780
+ }
781
+ /**
782
+ * Logs each startup error found with `error` as the application reports a refusal, and how many there were, when there
783
+ * was more than one; a lone error is left to whoever reports it now.
784
+ */ function logStartupErrors(error, log, ending) {
785
+ const errors = startupErrorsOf(error);
786
+ if (errors.length < 2) return false;
787
+ for (const each of errors)log(describeRefusal(each, process.cwd()));
788
+ log(`MeoCord found ${errors.length} startup errors; ${ending}.`);
789
+ return true;
790
+ }
791
+ /** Sets how decorators report startup errors from here on, before the application's modules load. */ function setStartupErrorsMode(mode) {
792
+ globalThis[STARTUP_ERRORS_KEY] = mode;
793
+ }
794
+ /** Whether decorators keep their startup errors for create(), as `startupErrors: 'all'` has them. */ const keepsDeclaredErrors = ()=>globalThis[STARTUP_ERRORS_KEY] === 'all';
795
+ /** The declaration a decorator's startup error is about, `Class.method` or `Class`. */ function declarationOf(error) {
796
+ return error.declaration;
797
+ }
798
+ /**
799
+ * A decorator that names, on each startup error it throws, the declaration it was applied to, `Class.method` or
800
+ * `Class`, as `declaration`, and the source file it is declared in, as `file`: the same error, thrown at the same
801
+ * moment, with its message unchanged. With `startupErrors: 'all'` the error is kept on the class instead, for create()
802
+ * to report with the rest, and the decorator applies nothing more.
803
+ */ function declaring(decorate) {
804
+ return function(target, propertyKey, ...rest) {
805
+ try {
806
+ return decorate.call(this, target, propertyKey, ...rest);
807
+ } catch (error) {
808
+ if (!isRefusal(error)) throw error;
809
+ keepOrThrow(error, target, propertyKey);
810
+ return undefined;
811
+ }
812
+ };
813
+ }
814
+ function keepOrThrow(error, target, propertyKey) {
815
+ var _globalThis, _ALL_DECLARED;
816
+ // A decorator applied through another, such as applyDecorators, names the declaration once, where it was first thrown
817
+ if (declarationOf(error) === undefined) {
818
+ Object.defineProperty(error, 'declaration', {
819
+ value: decoratedName(target, propertyKey),
820
+ configurable: true
821
+ });
822
+ Object.defineProperty(error, 'file', {
823
+ get: ()=>sourceFileOf(error, process.cwd()),
824
+ configurable: true
825
+ });
826
+ }
827
+ if (!keepsDeclaredErrors()) throw error;
828
+ const owner = typeof target === 'function' ? target : target.constructor;
829
+ if (!Object.prototype.hasOwnProperty.call(owner, DECLARED)) Object.defineProperty(owner, DECLARED, {
830
+ value: []
831
+ });
832
+ const declared = owner[DECLARED];
833
+ if (!declared.includes(error)) declared.push(error);
834
+ const all = (_globalThis = globalThis)[_ALL_DECLARED = ALL_DECLARED] ?? (_globalThis[_ALL_DECLARED] = []);
835
+ if (!all.includes(error)) all.push(error);
836
+ }
837
+ /** The startup errors decorators kept on `classes` and the classes they extend, each once, in the order they were found. */ function declaredErrorsOf(classes) {
838
+ const found = new Set();
839
+ for (const cls of classes){
840
+ for(let current = cls; typeof current === 'function' && current !== Function.prototype; current = Object.getPrototypeOf(current)){
841
+ if (Object.prototype.hasOwnProperty.call(current, DECLARED)) for (const error of current[DECLARED])found.add(error);
842
+ }
843
+ }
844
+ const order = allDeclaredErrors();
845
+ return [
846
+ ...found
847
+ ].sort((a, b)=>order.indexOf(a) - order.indexOf(b));
848
+ }
849
+ /** Every startup error decorators kept in this process, in the order they were found. */ function allDeclaredErrors() {
850
+ return [
851
+ ...globalThis[ALL_DECLARED] ?? []
852
+ ];
712
853
  }
713
854
  /**
714
855
  * Reports a refusal nothing catches as one line, with its source file, and exits 1; other errors keep the runtime's
@@ -902,9 +1043,9 @@ const PLAIN = "give a plain object, such as { ...value }, or a row's toObject(),
902
1043
  *
903
1044
  * @group Utilities
904
1045
  * @see {@link createMetadata}
905
- * @see {@link https://meocord.dev/docs/4.1/custom-decorators | Custom decorators}
1046
+ * @see {@link https://meocord.dev/docs/4.2/custom-decorators | Custom decorators}
906
1047
  */ function applyDecorators(...decorators) {
907
- return function(target, propertyKey, descriptor) {
1048
+ return declaring(function(target, propertyKey, descriptor) {
908
1049
  // Last first, as TypeScript applies decorators stacked, each given what the one before it returned
909
1050
  const ordered = [
910
1051
  ...decorators
@@ -917,7 +1058,7 @@ const PLAIN = "give a plain object, such as { ...value }, or a row's toObject(),
917
1058
  let current = target;
918
1059
  for (const decorator of ordered)current = decorator(current) ?? current;
919
1060
  return current;
920
- };
1061
+ });
921
1062
  }
922
1063
  const logger$5 = new Logger('SetMetadata');
923
1064
  /** The prefix of every key MeoCord keeps its own metadata under. */ const MEOCORD_PREFIX = 'meocord:';
@@ -956,7 +1097,7 @@ const logger$5 = new Logger('SetMetadata');
956
1097
  * @see {@link createMetadata}
957
1098
  */ function SetMetadata(metadataKey, metadataValue) {
958
1099
  warnDeprecated(logger$5, 'SetMetadata', 'createMetadata');
959
- return function(target, propertyKey) {
1100
+ return declaring(function(target, propertyKey) {
960
1101
  // Checked where it applies, so the refusal names the handler or controller
961
1102
  const owner = metadataKey.startsWith(MEOCORD_PREFIX) ? `MeoCord keeps its own metadata under keys beginning "${MEOCORD_PREFIX}"` : INJECTION_KEYS.has(metadataKey) ? 'dependency injection reads how to make the class from it' : undefined;
962
1103
  if (owner) {
@@ -967,7 +1108,7 @@ const logger$5 = new Logger('SetMetadata');
967
1108
  } else {
968
1109
  Reflect.defineMetadata(metadataKey, metadataValue, target);
969
1110
  }
970
- };
1111
+ });
971
1112
  }
972
1113
 
973
1114
  /**
@@ -1456,7 +1597,7 @@ const issueTexts = new WeakMap();
1456
1597
  * @group Responses
1457
1598
  * @category Errors
1458
1599
  * @see {@link Catch}
1459
- * @see {@link https://meocord.dev/docs/4.1/exception-filters | Exception filters}
1600
+ * @see {@link https://meocord.dev/docs/4.2/exception-filters | Exception filters}
1460
1601
  */ class CommandNotFoundError extends Error {
1461
1602
  /** @param message - What the error says, `No handler matched the interaction.` unless given. */ constructor(message = 'No handler matched the interaction.'){
1462
1603
  super(message);
@@ -1529,7 +1670,7 @@ const issueTexts = new WeakMap();
1529
1670
  * @category Errors
1530
1671
  * @see {@link MessageHandler}
1531
1672
  * @see {@link translateError}
1532
- * @see {@link https://meocord.dev/docs/4.1/message-commands | Message commands}
1673
+ * @see {@link https://meocord.dev/docs/4.2/message-commands | Message commands}
1533
1674
  */ class MessageUsageError extends Error {
1534
1675
  /**
1535
1676
  * @param usage - The command as the user should type it, such as `!ban <target> [reason…]`, or one line per
@@ -3700,7 +3841,7 @@ const states = new WeakMap();
3700
3841
  * @group Utilities
3701
3842
  * @see {@link createMetadata}
3702
3843
  * @see {@link StageParams}
3703
- * @see {@link https://meocord.dev/docs/4.1/guards | Guards}
3844
+ * @see {@link https://meocord.dev/docs/4.2/guards | Guards}
3704
3845
  */ class ExecutionContext {
3705
3846
  /**
3706
3847
  * The response state of the interaction being handled, the same one `respond(interaction)` returns,
@@ -4399,10 +4540,10 @@ const messageOf = (error)=>String(error?.message ?? error);
4399
4540
  }
4400
4541
 
4401
4542
  /**
4402
- * How specific a route pattern is, for ranking the patterns that can match one input: more literal
4403
- * text first, then a fixed length before one taking the rest, then no optional param before one, then
4404
- * fewer params, then narrower typed params, whose segments take fewer values. Component patterns count literal
4405
- * characters; message patterns count literal words.
4543
+ * How specific a route pattern is, for ranking the message patterns that can match one message: more literal
4544
+ * words first, then a fixed length before one taking the rest, then no optional param before one, then
4545
+ * fewer params, then narrower typed params, whose words take fewer values. Component patterns count literal
4546
+ * characters, for CommandMeta.specificity alone.
4406
4547
  */ function routeSpecificity({ literals, params, rest = false, optional = false, typed = 0 }) {
4407
4548
  return literals * 1000 - (rest ? 500 : 0) - (optional ? 250 : 0) - params + typed / 1000;
4408
4549
  }
@@ -4443,14 +4584,26 @@ const UNIT_MS = lookupTable({
4443
4584
  return total + Number(amount) * UNIT_MS[unit];
4444
4585
  }, 0);
4445
4586
  }
4587
+ /** The largest snowflake: Discord's IDs are unsigned 64-bit integers. */ const MAX_SNOWFLAKE = 2n ** 64n - 1n;
4588
+ /**
4589
+ * A Discord ID, kept as its text: 17 to 20 digits, no leading zero, up to the largest 64-bit value. An ID's top 42 bits
4590
+ * are milliseconds since 2015-01-01, so every ID made from 2015-01-28 on has 17 digits or more, and none is a safe
4591
+ * integer.
4592
+ */ const snowflake = (word)=>/^[1-9]\d{16,19}$/.test(word) && BigInt(word) <= MAX_SNOWFLAKE ? word : undefined;
4593
+ /** A UUID in its canonical 8-4-4-4-12 form, in either case, kept as written. */ const uuid = (word)=>/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(word) ? word : undefined;
4446
4594
  /** The words a type such as `on|off` chooses from, or `undefined` for any other type. */ const choicesOf = (type)=>type.includes('|') ? type.split('|') : undefined;
4447
4595
  /** The types a component's customId segment can take: each parsed from the segment alone, with no request. */ const SEGMENT_TYPES = lookupTable({
4448
4596
  string: (segment)=>segment,
4449
4597
  int: (segment)=>number(segment, true),
4450
4598
  number: (segment)=>number(segment, false),
4451
- bool
4599
+ bool,
4600
+ snowflake,
4601
+ uuid
4452
4602
  });
4453
- /** Whether `type` can type a customId segment: `string`, `int`, `number`, `bool`, or words to choose from. */ const isSegmentType = (type)=>type in SEGMENT_TYPES || choicesOf(type) !== undefined;
4603
+ /**
4604
+ * Whether `type` can type a customId segment: `string`, `int`, `number`, `bool`, `snowflake`, `uuid`, or words to
4605
+ * choose from.
4606
+ */ const isSegmentType = (type)=>type in SEGMENT_TYPES || choicesOf(type) !== undefined;
4454
4607
  /** A customId segment as its type's value, words to choose from compared as written: `undefined` when it is not one. */ function parseSegment(type, segment) {
4455
4608
  const choices = choicesOf(type);
4456
4609
  if (choices) return choices.includes(segment) ? segment : undefined;
@@ -4481,6 +4634,7 @@ const UNIT_MS = lookupTable({
4481
4634
  /** How many guard wrappers `@UseGuard` and `@Controller` put around one method. */ guardWrappers: 'meocord:guard-wrappers',
4482
4635
  /** The prototype whose method a class-level `@UseGuard` re-declared on a subclass. */ inheritedFrom: 'meocord:inherited-from',
4483
4636
  /** `false` when `@Controller({ inheritStages: false })` stops class stages at this class. */ inheritStages: 'meocord:inherit-stages',
4637
+ /** `true` when `@Controller({ inheritedRoutes: 'replace' })` drops the routes this class's re-decorated methods inherit. */ replacesInheritedRoutes: 'meocord:replaces-inherited-routes',
4484
4638
  /** A controller's class-level `@Cooldown`s. */ classCooldowns: 'meocord:class-cooldowns',
4485
4639
  /** A method's `@Cooldown`s, in declaration order. */ methodCooldowns: 'meocord:method-cooldowns',
4486
4640
  /** The error types a filter's `@Catch` names; empty to catch everything. */ catchTypes: 'meocord:catch-types',
@@ -4555,6 +4709,42 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
4555
4709
  else handlers[index] = entry;
4556
4710
  Reflect.defineMetadata(key, handlers, target);
4557
4711
  }
4712
+ /**
4713
+ * Leaves each method the class's own decorators declare only those routes, of every kind, dropping the ones it inherits
4714
+ * for it: what `@Controller({ inheritedRoutes: 'replace' })` does once the class's method decorators have run.
4715
+ */ function dropInheritedRoutes(prototype) {
4716
+ const redeclared = new Set(getDeclaredRoutes(prototype).map((route)=>route.method));
4717
+ if (redeclared.size === 0) return;
4718
+ const base = Object.getPrototypeOf(prototype);
4719
+ // An entry the class takes from its base, for a method it re-declares
4720
+ const inheritedFor = (inherited, method)=>(entry)=>inherited.includes(entry) && redeclared.has(method(entry));
4721
+ const commands = ownCommandMap(prototype);
4722
+ const baseCommands = Reflect.getMetadata(META.commands, base) ?? {};
4723
+ for (const [name, metas] of Object.entries(commands)){
4724
+ const kept = metas.filter((meta)=>!inheritedFor(baseCommands[name] ?? [], (entry)=>entry.methodName)(meta));
4725
+ if (kept.length > 0) commands[name] = kept;
4726
+ else delete commands[name];
4727
+ }
4728
+ Reflect.defineMetadata(META.commands, commands, prototype);
4729
+ const lists = [
4730
+ [
4731
+ META.messageHandlers,
4732
+ (entry)=>entry.method
4733
+ ],
4734
+ [
4735
+ META.reactionHandlers,
4736
+ (entry)=>entry.method
4737
+ ],
4738
+ [
4739
+ META.autocompleteHandlers,
4740
+ (entry)=>entry.methodName
4741
+ ]
4742
+ ];
4743
+ for (const [key, method] of lists){
4744
+ const inherited = Reflect.getMetadata(key, base) ?? [];
4745
+ Reflect.defineMetadata(key, ownHandlerList(key, prototype).filter((entry)=>!inheritedFor(inherited, method)(entry)), prototype);
4746
+ }
4747
+ }
4558
4748
  /** The class's own command map, started from a copy of the inherited one, for the same reason. */ function ownCommandMap(target) {
4559
4749
  const own = Reflect.getOwnMetadata(META.commands, target);
4560
4750
  if (own) return own;
@@ -4567,7 +4757,7 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
4567
4757
  ]));
4568
4758
  }
4569
4759
  function MessageHandler(pattern, options = {}) {
4570
- return function(target, propertyKey) {
4760
+ return declaring(function(target, propertyKey) {
4571
4761
  refuseOnClass('@MessageHandler', target, propertyKey);
4572
4762
  // An empty pattern means every message, as no pattern does
4573
4763
  if (pattern === '') {
@@ -4581,7 +4771,7 @@ function MessageHandler(pattern, options = {}) {
4581
4771
  };
4582
4772
  addOwnHandler(META.messageHandlers, target, declared, (other)=>other.method === method && other.pattern === declared.pattern);
4583
4773
  declareRoute(target, messageRoute(method, declared.pattern));
4584
- };
4774
+ });
4585
4775
  }
4586
4776
  function ReactionHandler(emojiOrSettings, settings = {}) {
4587
4777
  const [emoji, own] = typeof emojiOrSettings === 'object' ? [
@@ -4591,7 +4781,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
4591
4781
  emojiOrSettings,
4592
4782
  settings
4593
4783
  ];
4594
- return function(target, propertyKey) {
4784
+ return declaring(function(target, propertyKey) {
4595
4785
  refuseOnClass('@ReactionHandler', target, propertyKey);
4596
4786
  const method = propertyKey.toString();
4597
4787
  addOwnHandler(META.reactionHandlers, target, {
@@ -4600,7 +4790,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
4600
4790
  settings: own
4601
4791
  }, (other)=>other.method === method && other.emoji === emoji);
4602
4792
  declareRoute(target, reactionRoute(method, emoji));
4603
- };
4793
+ });
4604
4794
  }
4605
4795
  /** A custom emoji as Discord writes it in a message: `<:name:id>`, or `<a:name:id>` for an animated one. */ const CUSTOM_EMOJI = /^<a?:\w+:(\d+)>$/;
4606
4796
  /**
@@ -4632,7 +4822,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4632
4822
  /** The character a parameter will not cross, so one pattern segment maps to one value. */ const PARAM_SEPARATOR = '/';
4633
4823
  /** Escapes a literal stretch of a pattern so only placeholders stay meaningful. */ const escapeLiteral = (literal)=>literal.replace(/[/\\^$*+?.()|[\]{}]/g, '\\$&');
4634
4824
  /**
4635
- * Compiles a pattern into a regex, its parameter names, their types and its specificity. A `{name}` takes a
4825
+ * Compiles a pattern into a regex, its parameter names, their types and its 4.1 specificity. A `{name}` takes a
4636
4826
  * whole `/`-separated segment, so a uuid is captured whole and `profile/{uuid}` never overlaps
4637
4827
  * `profile/{uuid}/{id}`; a param that shares a segment with literal text is refused.
4638
4828
  */ function createRegexFromPattern(pattern) {
@@ -4672,14 +4862,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4672
4862
  literalLength += trailing.length;
4673
4863
  regexPattern += escapeLiteral(trailing);
4674
4864
  const regex = new RegExp(`^${regexPattern}$`);
4675
- // Literal text is the signal: a pattern spelling out more of the id describes it
4676
- // more exactly than one leaving it to a parameter. Fewer parameters breaks a tie
4677
- // between equal-length patterns, then narrower typed params; the order the controllers
4678
- // and handlers are listed in settles what is left, and the startup warning names the pair.
4865
+ // What 4.1 ranked by, kept for code that reads CommandMeta.specificity; routing ranks segment by segment
4679
4866
  const specificity = routeSpecificity({
4680
4867
  literals: literalLength,
4681
4868
  params: params.length,
4682
- typed: Object.values(types).reduce((sum, type)=>sum + narrowness(type), 0)
4869
+ typed: Object.values(types).reduce((sum, type)=>sum + (choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1), 0)
4683
4870
  });
4684
4871
  return {
4685
4872
  regex,
@@ -4688,9 +4875,8 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4688
4875
  specificity
4689
4876
  };
4690
4877
  }
4691
- /** How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, int, number. */ const narrowness = (type)=>choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1;
4692
4878
  /** Why a `{name:type}` cannot type a customId segment, which holds text the bot wrote, with no message to read. */ function segmentTypeProblem(param, type) {
4693
- const kinds = 'string, int, number, bool, or words to choose from such as {mode:on|off}';
4879
+ const kinds = 'string, int, number, bool, snowflake, uuid, or words to choose from such as {mode:on|off}';
4694
4880
  if ([
4695
4881
  'member',
4696
4882
  'user',
@@ -4714,10 +4900,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4714
4900
  * the keys the handler's params require are checked against the pattern or route, a typed segment's value against
4715
4901
  * its type, and a select menu's choices, such as `values: string[]`, against what discord.js gives; a key a pipe
4716
4902
  * produces is declared `Piped<T>`, and left to the pipe. Two component handlers of one type whose patterns match
4717
- * exactly the same ids stop the bot at startup; patterns that only overlap are warned about, naming the one that
4718
- * runs: the more specific, or between equally specific ones, the one listed first. In the next major version (5.0),
4719
- * the one that spells out the first segment where two equally specific patterns differ runs instead, and the warning
4720
- * names the pairs that changes. A context menu handler receives the kind its builder's `setType()` names, and one
4903
+ * exactly the same ids stop the bot at startup. Patterns that both match an id rank segment by segment, left to
4904
+ * right: at the first segment one spells out as literal text and the other leaves to a param, the literal one runs,
4905
+ * so `profile/me/{section}` takes `profile/me/edit` from `profile/{userId}/edit`; between patterns that leaves tied,
4906
+ * the narrower type at the first param where they differ. Two still tied are warned about as the bot starts, and the
4907
+ * one listed first runs. A context menu handler receives the kind its builder's `setType()` names, and one
4721
4908
  * declaring the other kind fails to compile; when the compiler cannot tell the kind, `@Command` checks the parameter
4722
4909
  * type it emits as it applies. A subclass that re-declares an inherited handler on the same name or pattern takes its
4723
4910
  * own builder and options; on another it still answers the inherited one too, which the bot warns about as it
@@ -4750,11 +4937,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4750
4937
  * @category Handlers
4751
4938
  * @see {@link CommandBuilder}
4752
4939
  * @see {@link route}
4753
- * @see {@link https://meocord.dev/docs/4.1/slash-commands | Slash commands}
4754
- * @see {@link https://meocord.dev/docs/4.1/components | Buttons, selects and modals}
4940
+ * @see {@link https://meocord.dev/docs/4.2/slash-commands | Slash commands}
4941
+ * @see {@link https://meocord.dev/docs/4.2/components | Buttons, selects and modals}
4755
4942
  */ function Command(name, builderOrType) {
4756
4943
  const commandName = typeof name === 'string' ? name : name.pattern;
4757
- return function(target, propertyKey, _descriptor) {
4944
+ return declaring(function(target, propertyKey, _descriptor) {
4758
4945
  var _commands, _commandName;
4759
4946
  refuseOnClass('@Command', target, propertyKey);
4760
4947
  const originalMethod = _descriptor.value;
@@ -4835,10 +5022,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4835
5022
  cause: error
4836
5023
  }));
4837
5024
  }
4838
- const { regex: generatedRegex, params, specificity: patternSpecificity } = pattern;
4839
- regex = generatedRegex;
4840
- dynamicParams = params;
4841
- specificity = patternSpecificity;
5025
+ regex = pattern.regex;
5026
+ dynamicParams = pattern.params;
5027
+ specificity = pattern.specificity;
4842
5028
  }
4843
5029
  const declared = {
4844
5030
  methodName: propertyKey,
@@ -4862,7 +5048,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4862
5048
  else metas[replaced] = declared;
4863
5049
  Reflect.defineMetadata(META.commands, commands, target);
4864
5050
  declareRoute(target, commandRoute(propertyKey, commandType, commandName));
4865
- };
5051
+ });
4866
5052
  }
4867
5053
  /** The interaction class Discord sends for each context menu kind. */ const CONTEXT_MENU_INTERACTIONS = new Map([
4868
5054
  [
@@ -4934,9 +5120,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4934
5120
  * @group Decorators
4935
5121
  * @category Handlers
4936
5122
  * @see {@link Command}
4937
- * @see {@link https://meocord.dev/docs/4.1/autocomplete | Autocomplete}
5123
+ * @see {@link https://meocord.dev/docs/4.2/autocomplete | Autocomplete}
4938
5124
  */ function Autocomplete(commandPath, optionName) {
4939
- return function(target, propertyKey, _descriptor) {
5125
+ return declaring(function(target, propertyKey, _descriptor) {
4940
5126
  refuseOnClass('@Autocomplete', target, propertyKey);
4941
5127
  const methodName = propertyKey.toString();
4942
5128
  addOwnHandler(META.autocompleteHandlers, target, {
@@ -4945,7 +5131,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4945
5131
  methodName
4946
5132
  }, (other)=>other.methodName === methodName && other.commandPath === commandPath && other.optionName === optionName);
4947
5133
  declareRoute(target, autocompleteRoute(methodName, commandPath, optionName));
4948
- };
5134
+ });
4949
5135
  }
4950
5136
  /**
4951
5137
  * Returns a controller's autocomplete handlers, option-specific ones first.
@@ -4967,52 +5153,63 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
4967
5153
  const match = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`).exec(segment);
4968
5154
  return match ? typeKey(match[2]) : undefined;
4969
5155
  }
4970
- /** Whether a segment's two readings can both take one value: a literal, text, or a typed param. */ function segmentsOverlap(left, right) {
5156
+ /**
5157
+ * How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, uuid,
5158
+ * snowflake, int, number, and text last. A snowflake is narrower than a number, which takes all its values.
5159
+ */ const NARROWNESS = lookupTable({
5160
+ bool: 5,
5161
+ uuid: 4,
5162
+ snowflake: 3,
5163
+ int: 2,
5164
+ number: 1,
5165
+ '': 0
5166
+ });
5167
+ const narrowness = (type)=>choicesOf(type) ? 6 : NARROWNESS[type];
5168
+ /**
5169
+ * How narrow a pattern segment that is a param, `{name}` or `{name:type}`, is: from `0` for text to `4` for words to
5170
+ * choose from. `undefined` for a segment of literal text.
5171
+ */ function paramNarrowness(segment) {
5172
+ const type = segmentParamType(segment);
5173
+ return type === undefined ? undefined : narrowness(type);
5174
+ }
5175
+ /** A value of each scalar segment type: an int, a bool, a snowflake and a uuid; text takes any. */ const SAMPLE_VALUES = [
5176
+ '1',
5177
+ 'true',
5178
+ '10000000000000000',
5179
+ '00000000-0000-0000-0000-000000000000'
5180
+ ];
5181
+ /** A value both readings of a segment take: a literal, text, or a typed param; `undefined` when they share none. */ function sharedValue(left, right) {
4971
5182
  const [a, b] = [
4972
5183
  segmentParamType(left),
4973
5184
  segmentParamType(right)
4974
5185
  ];
4975
- if (a === undefined && b === undefined) return left === right;
4976
- if (a === undefined || b === undefined) {
4977
- const [type, literal] = a === undefined ? [
4978
- b,
5186
+ const takes = (type, segment, value)=>type === undefined ? segment === value : type === '' || parseSegment(type, value) !== undefined;
5187
+ // Every type takes one of these when it takes anything another reading takes: a literal, a listed word, or a value
5188
+ // of each scalar type
5189
+ const candidates = [
5190
+ ...a === undefined ? [
4979
5191
  left
4980
- ] : [
4981
- a,
5192
+ ] : [],
5193
+ ...b === undefined ? [
4982
5194
  right
4983
- ];
4984
- return type === '' || parseSegment(type, literal) !== undefined;
4985
- }
4986
- if (a === '' || b === '' || a === b) return true;
4987
- // Words to choose from overlap another type when one of them is a value of it
4988
- const [wordsA, wordsB] = [
4989
- choicesOf(a),
4990
- choicesOf(b)
5195
+ ] : [],
5196
+ ...choicesOf(a ?? '') ?? [],
5197
+ ...choicesOf(b ?? '') ?? [],
5198
+ ...SAMPLE_VALUES
4991
5199
  ];
4992
- if (wordsA) return wordsA.some((word)=>parseSegment(b, word) !== undefined);
4993
- if (wordsB) return wordsB.some((word)=>parseSegment(a, word) !== undefined);
4994
- // Of the scalar types, only a whole number and a number share values
4995
- return a === 'int' && b === 'number' || a === 'number' && b === 'int';
5200
+ return candidates.find((value)=>takes(a, left, value) && takes(b, right, value));
4996
5201
  }
4997
5202
  /**
4998
- * Finds pairs of customId patterns that can both match one id, such as `a/{x}/c` and `a/b/{y}`.
4999
- * @returns Each ambiguous pair once, in the order the patterns were given.
5000
- */ function findAmbiguousRoutes(patterns) {
5001
- const segmentsOf = (pattern)=>pattern.split(PARAM_SEPARATOR);
5002
- const collisions = [];
5003
- for(let i = 0; i < patterns.length; i++){
5004
- for(let j = i + 1; j < patterns.length; j++){
5005
- const left = segmentsOf(patterns[i]);
5006
- const right = segmentsOf(patterns[j]);
5007
- if (left.length !== right.length) continue;
5008
- const disjoint = left.some((segment, index)=>!segmentsOverlap(segment, right[index]));
5009
- if (!disjoint) collisions.push([
5010
- patterns[i],
5011
- patterns[j]
5012
- ]);
5013
- }
5014
- }
5015
- return collisions;
5203
+ * A customId both patterns match, such as `ticket/1/close` for `ticket/{id}/close` and `ticket/{n:int}/close`;
5204
+ * `undefined` when no id matches both.
5205
+ */ function sharedCustomId(left, right) {
5206
+ const [a, b] = [
5207
+ left.split(PARAM_SEPARATOR),
5208
+ right.split(PARAM_SEPARATOR)
5209
+ ];
5210
+ if (a.length !== b.length) return undefined;
5211
+ const values = a.map((segment, index)=>sharedValue(segment, b[index]));
5212
+ return values.every((value)=>value !== undefined) ? values.join(PARAM_SEPARATOR) : undefined;
5016
5213
  }
5017
5214
 
5018
5215
  exports.Autocomplete = Autocomplete;
@@ -5041,6 +5238,7 @@ exports.Translator = Translator;
5041
5238
  exports.UnroutedExecutionContext = UnroutedExecutionContext;
5042
5239
  exports.UserError = UserError;
5043
5240
  exports.ValidationError = ValidationError;
5241
+ exports.allDeclaredErrors = allDeclaredErrors;
5044
5242
  exports.ambientThemeVersion = ambientThemeVersion;
5045
5243
  exports.applyDecorators = applyDecorators;
5046
5244
  exports.assertValidTheme = assertValidTheme;
@@ -5053,6 +5251,7 @@ exports.bundleEntry = bundleEntry;
5053
5251
  exports.callOrder = callOrder;
5054
5252
  exports.choicesOf = choicesOf;
5055
5253
  exports.claimAmbientTheme = claimAmbientTheme;
5254
+ exports.collectStartupErrors = collectStartupErrors;
5056
5255
  exports.compiledConfigMessage = compiledConfigMessage;
5057
5256
  exports.cooldownMessage = cooldownMessage;
5058
5257
  exports.cooldownStoreMessage = cooldownStoreMessage;
@@ -5060,6 +5259,8 @@ exports.cooldownText = cooldownText;
5060
5259
  exports.copyLayer = copyLayer;
5061
5260
  exports.createRegexFromPattern = createRegexFromPattern;
5062
5261
  exports.createTranslator = createTranslator;
5262
+ exports.declaredErrorsOf = declaredErrorsOf;
5263
+ exports.declaring = declaring;
5063
5264
  exports.decoratedName = decoratedName;
5064
5265
  exports.defaultTheme = defaultTheme;
5065
5266
  exports.defineCatalog = defineCatalog;
@@ -5067,10 +5268,10 @@ exports.deprecatedOnMethod = deprecatedOnMethod;
5067
5268
  exports.describeInteraction = describeInteraction;
5068
5269
  exports.describeRefusal = describeRefusal;
5069
5270
  exports.describeValue = describeValue;
5271
+ exports.dropInheritedRoutes = dropInheritedRoutes;
5070
5272
  exports.duration = duration;
5071
5273
  exports.escapeForLog = escapeForLog;
5072
5274
  exports.existingResponse = existingResponse;
5073
- exports.findAmbiguousRoutes = findAmbiguousRoutes;
5074
5275
  exports.focusedOptionName = focusedOptionName;
5075
5276
  exports.forgetRecorded = forgetRecorded;
5076
5277
  exports.getAutocompleteHandlers = getAutocompleteHandlers;
@@ -5096,6 +5297,7 @@ exports.isUserOutcome = isUserOutcome;
5096
5297
  exports.listed = listed;
5097
5298
  exports.loadMeoCordConfig = loadMeoCordConfig;
5098
5299
  exports.logFailedSend = logFailedSend;
5300
+ exports.logStartupErrors = logStartupErrors;
5099
5301
  exports.lookup = lookup;
5100
5302
  exports.lookupLayers = lookupLayers;
5101
5303
  exports.lookupTable = lookupTable;
@@ -5109,6 +5311,7 @@ exports.missingTranslatorError = missingTranslatorError;
5109
5311
  exports.number = number;
5110
5312
  exports.ownHandlerList = ownHandlerList;
5111
5313
  exports.ownsAmbientTheme = ownsAmbientTheme;
5314
+ exports.paramNarrowness = paramNarrowness;
5112
5315
  exports.parseSegment = parseSegment;
5113
5316
  exports.patternShape = patternShape;
5114
5317
  exports.placeholderNames = placeholderNames;
@@ -5132,8 +5335,12 @@ exports.runInThemeScope = runInThemeScope;
5132
5335
  exports.runWithTheme = runWithTheme;
5133
5336
  exports.setLegacyThemeLayer = setLegacyThemeLayer;
5134
5337
  exports.setPresenter = setPresenter;
5338
+ exports.setStartupErrorsMode = setStartupErrorsMode;
5135
5339
  exports.setThemeOutsideCalls = setThemeOutsideCalls;
5340
+ exports.sharedCustomId = sharedCustomId;
5136
5341
  exports.stampCall = stampCall;
5342
+ exports.startupError = startupError;
5343
+ exports.stopOnStartupErrors = stopOnStartupErrors;
5137
5344
  exports.textOfIssue = textOfIssue;
5138
5345
  exports.textRenderer = textRenderer;
5139
5346
  exports.themeForInteraction = themeForInteraction;
@@ -5148,6 +5355,7 @@ exports.useTheme = useTheme;
5148
5355
  exports.userWords = userWords;
5149
5356
  exports.warnDeprecated = warnDeprecated;
5150
5357
  exports.warnDeprecatedBehaviour = warnDeprecatedBehaviour;
5358
+ exports.warnOnce = warnOnce;
5151
5359
  exports.warnPlaceholder = warnPlaceholder;
5152
5360
  exports.withRelease = withRelease;
5153
5361
  exports.withSendableFiles = withSendableFiles;