meocord 4.0.0-beta.3 → 4.0.0-beta.5

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 (61) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +37 -2
  3. package/dist/cjs/_shared/component-routes-RTZZ3t5i.cjs +55 -0
  4. package/dist/cjs/_shared/{controller.decorator-9nIxOp9y.cjs → controller.decorator-DnKZ29W4.cjs} +32 -108
  5. package/dist/cjs/_shared/{theme-Dqwlnz0m.cjs → theme-C8w8ivsR.cjs} +4 -12
  6. package/dist/cjs/common/index.cjs +1 -1
  7. package/dist/cjs/core/index.cjs +45 -101
  8. package/dist/cjs/decorator/index.cjs +45 -116
  9. package/dist/cjs/enum/index.cjs +34 -10
  10. package/dist/cjs/testing/index.cjs +144 -144
  11. package/dist/esm/bin/app-template/README.md.template +3 -1
  12. package/dist/esm/bin/app-template/vitest.config.ts.template +3 -5
  13. package/dist/esm/bin/helper/app-generator.helper.js +6 -24
  14. package/dist/esm/bin/helper/controller-generator.helper.js +8 -35
  15. package/dist/esm/bin/helper/guard-generator.helper.js +2 -7
  16. package/dist/esm/bin/helper/service-generator.helper.js +2 -7
  17. package/dist/esm/bin/meocord.js +9 -31
  18. package/dist/esm/build/native-addons.js +10 -33
  19. package/dist/esm/build/rsbuild-config.js +8 -26
  20. package/dist/esm/core/component-routes.js +51 -0
  21. package/dist/esm/core/meocord.app.js +37 -87
  22. package/dist/esm/decorator/app.decorator.js +10 -25
  23. package/dist/esm/decorator/command-builder.decorator.js +6 -9
  24. package/dist/esm/decorator/controller.decorator.js +14 -45
  25. package/dist/esm/decorator/guard.decorator.js +14 -56
  26. package/dist/esm/decorator/index.js +1 -1
  27. package/dist/esm/decorator/service.decorator.js +4 -8
  28. package/dist/esm/enum/controller.enum.js +3 -7
  29. package/dist/esm/enum/metadata-key.enum.js +0 -1
  30. package/dist/esm/package.json.js +1 -1
  31. package/dist/esm/testing/index.js +1 -0
  32. package/dist/esm/testing/meocord-testing-module.js +9 -5
  33. package/dist/esm/testing/mock-fn.js +14 -25
  34. package/dist/esm/testing/mock-interaction.js +58 -109
  35. package/dist/esm/testing/routing.js +59 -0
  36. package/dist/esm/util/common.util.js +5 -22
  37. package/dist/esm/util/generator-cli.util.js +13 -51
  38. package/dist/esm/util/interaction.util.js +15 -60
  39. package/dist/esm/util/json.util.js +1 -9
  40. package/dist/esm/util/meocord-cli.util.js +4 -31
  41. package/dist/esm/util/meocord-config-loader.util.js +3 -11
  42. package/dist/esm/util/meocord-source-config.util.js +2 -9
  43. package/dist/esm/util/package-manager.util.js +2 -6
  44. package/dist/esm/util/package-version.util.js +2 -11
  45. package/dist/esm/util/platform.util.js +3 -8
  46. package/dist/esm/util/runtime.util.js +10 -38
  47. package/dist/esm/util/tsconfig.util.js +4 -9
  48. package/dist/types/{controller.enum-DYfhYaat.d.ts → controller.enum-BL6wEQls.d.cts} +3 -7
  49. package/dist/types/{controller.enum-DYfhYaat.d.cts → controller.enum-BL6wEQls.d.ts} +3 -7
  50. package/dist/types/core/index.d.cts +27 -55
  51. package/dist/types/core/index.d.ts +27 -55
  52. package/dist/types/decorator/index.d.cts +45 -168
  53. package/dist/types/decorator/index.d.ts +45 -168
  54. package/dist/types/enum/index.d.cts +1 -2
  55. package/dist/types/enum/index.d.ts +1 -2
  56. package/dist/types/interface/index.d.cts +49 -58
  57. package/dist/types/interface/index.d.ts +49 -58
  58. package/dist/types/testing/index.d.cts +147 -89
  59. package/dist/types/testing/index.d.ts +147 -89
  60. package/package.json +1 -1
  61. package/dist/cjs/_shared/metadata-key.enum-aABGdodb.cjs +0 -34
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # meocord
2
2
 
3
+ ## 4.0.0-beta.5
4
+
5
+ ### Minor Changes
6
+
7
+ - [#47](https://github.com/l7aromeo/meocord/pull/47) [`9652436`](https://github.com/l7aromeo/meocord/commit/965243660323277827a64851f28da9cda2c2fe0c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Add `resolveRoute` and `findRouteConflicts` to `meocord/testing`, for testing which handler a
8
+ component's customId reaches. `resolveRoute(App, { type, customId })` gives the answer dispatch gives
9
+ — across every controller the app registers, for that component type, most specific pattern first —
10
+ as the controller, the handler method and its name, and the captured params, or `undefined`. `findRouteConflicts(App)` returns the
11
+ pattern pairs that can match the same customId, which MeoCord otherwise only warns about at startup.
12
+ Both read decorator metadata only, and dispatch runs on the same matcher.
13
+
14
+ ## 4.0.0-beta.4
15
+
16
+ ### Major Changes
17
+
18
+ - [#44](https://github.com/l7aromeo/meocord/pull/44) [`e48472e`](https://github.com/l7aromeo/meocord/commit/e48472e9ffee2f0371a7a2a71a4063e99ce61674) Thanks [@l7aromeo](https://github.com/l7aromeo)! - **Breaking:** `meocord/decorator` exports only the decorators. The routing helpers it also exported
19
+ — `getCommandMap`, `getMessageHandlers`, `getReactionHandlers`, `getAutocompleteHandlers`,
20
+ `findAmbiguousRoutes` and `PARAM_SEPARATOR` — are internal to MeoCord now. See the
21
+ [migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#internal-helpers-are-no-longer-exported).
22
+
3
23
  ## 4.0.0-beta.3
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -273,7 +273,9 @@ See [Self-contained builds](#self-contained-builds) for when to turn on `bundleD
273
273
 
274
274
  ### ESLint
275
275
 
276
- MeoCord exports a base ESLint config from `meocord/eslint`. Extend it as needed:
276
+ MeoCord exports a base ESLint config from `meocord/eslint`. It lints your TypeScript, `meocord.config.ts`
277
+ included, with type information from `tsconfig.json`, `tsconfig.test.json` and `tsconfig.eslint.json`; a
278
+ file ESLint reports as not included in any of them needs listing in one. Extend it as needed:
277
279
 
278
280
  ```javascript
279
281
  import meocordEslint, { typescriptConfig } from 'meocord/eslint'
@@ -687,7 +689,7 @@ bun run test:typecheck # tsc -p tsconfig.test.json
687
689
  bun run lint # eslint --fix + tsc
688
690
  ```
689
691
 
690
- In your own bot project, add Vitest (or keep Jest) plus a `vitest.config.ts` with SWC if you use decorator metadata — see this repo's config for reference. Generated apps include `*.spec.ts` stubs but not a test runner config yet.
692
+ Generated apps come with Vitest set up — `vitest.config.ts` with SWC for decorator metadata, and `test`, `test:watch` and `test:coverage` scripts — plus a spec beside every generated component. In an older project, add Vitest (or keep Jest) with the same SWC setup; `meocord/testing` works with either.
691
693
 
692
694
  ### `MeoCordTestingModule`
693
695
 
@@ -882,6 +884,39 @@ expect(msg.delete).toHaveBeenCalledTimes(1)
882
884
 
883
885
  </details>
884
886
 
887
+ <details>
888
+ <summary><b><code>resolveRoute</code> / <code>findRouteConflicts</code></b></summary>
889
+
890
+ Tests which handler a component's customId reaches — the same answer dispatch gives, across every
891
+ controller your app registers, most specific pattern first. They read decorator metadata only, so
892
+ they need no Discord client, config or container. They check routing alone: guards are not run, and
893
+ whether a controller's dependencies are bound is for `MeoCordTestingModule` to test.
894
+
895
+ ```typescript
896
+ import { findRouteConflicts, resolveRoute } from 'meocord/testing'
897
+ import { CommandType } from 'meocord/enum'
898
+ import App from '@src/app'
899
+ import { ProfileController } from '@src/controllers/button/profile.button.controller'
900
+
901
+ it('routes the profile button to its handler', () => {
902
+ const route = resolveRoute(App, { type: CommandType.BUTTON, customId: 'profile/111/8000' })
903
+
904
+ // The method itself rather than its name, so renaming it in your editor updates the test too.
905
+ expect(route?.handler).toBe(ProfileController.prototype.showProfile)
906
+ expect(route?.params).toEqual({ ownerId: '111', uid: '8000' })
907
+ })
908
+
909
+ // Patterns that can match the same customId, as a failing test rather than a startup warning.
910
+ it('has no overlapping component patterns', () => {
911
+ expect(findRouteConflicts(App)).toEqual([])
912
+ })
913
+ ```
914
+
915
+ `resolveRoute` returns the `controller`, the `handler` method and its name as `method`, and the
916
+ `params` the pattern captured — or `undefined` when no route handles the customId.
917
+
918
+ </details>
919
+
885
920
  <details>
886
921
  <summary><b><code>createMock</code></b></summary>
887
922
 
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ var controller_decorator = require('./controller.decorator-DnKZ29W4.cjs');
4
+
5
+ /** Every customId-pattern route of the given controllers, most specific first, read from metadata alone. */ function buildComponentRoutes(controllerClasses) {
6
+ const routes = [];
7
+ for (const controllerClass of controllerClasses){
8
+ const commandMap = controller_decorator.getCommandMap(controllerClass.prototype);
9
+ if (!commandMap) continue;
10
+ for (const [pattern, metaArray] of Object.entries(commandMap)){
11
+ if (!Array.isArray(metaArray)) continue;
12
+ for (const meta of metaArray){
13
+ if (meta.regex) routes.push({
14
+ controllerClass,
15
+ meta,
16
+ pattern
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return routes.sort((a, b)=>(b.meta.specificity ?? 0) - (a.meta.specificity ?? 0));
22
+ }
23
+ /**
24
+ * The route dispatch runs for a customId: the first, in rank order, whose type `acceptsType` allows
25
+ * and whose pattern matches, with the captured params.
26
+ */ function matchComponentRoute(routes, acceptsType, customId) {
27
+ for (const route of routes){
28
+ if (!acceptsType(route.meta.type)) continue;
29
+ const match = route.meta.regex.exec(customId);
30
+ if (match) return {
31
+ route,
32
+ params: {
33
+ ...match.groups
34
+ }
35
+ };
36
+ }
37
+ return undefined;
38
+ }
39
+ /** Pattern pairs that can match one customId, compared only within a component type, as dispatch does. */ function findComponentRouteConflicts(routes) {
40
+ const byType = new Map();
41
+ for (const { meta, pattern } of routes)byType.set(meta.type, [
42
+ ...byType.get(meta.type) ?? [],
43
+ pattern
44
+ ]);
45
+ return [
46
+ ...byType
47
+ ].flatMap(([type, patterns])=>controller_decorator.findAmbiguousRoutes(patterns).map((pair)=>({
48
+ type,
49
+ patterns: pair
50
+ })));
51
+ }
52
+
53
+ exports.buildComponentRoutes = buildComponentRoutes;
54
+ exports.findComponentRouteConflicts = findComponentRouteConflicts;
55
+ exports.matchComponentRoute = matchComponentRoute;
@@ -3,18 +3,11 @@
3
3
  require('reflect-metadata');
4
4
  var inversify = require('inversify');
5
5
  var enum_index = require('../enum/index.cjs');
6
- var metadataKey_enum = require('./metadata-key.enum-aABGdodb.cjs');
7
6
  var discord_js = require('discord.js');
8
7
 
9
8
  /**
10
- * The discord.js class each command type is handled by.
11
- *
12
- * One table rather than a chain of `isButton() || isStringSelectMenu() || ...`: the
13
- * registration guard in `@Command`, the dispatcher, and the type-level
14
- * `CommandInteractionType` all have to agree on what a command type accepts, and a
15
- * chain repeated in three files drifts the moment a fifth select menu appears. Adding
16
- * a `CommandType` member without an entry here is a compile error, not a silent
17
- * fall-through to "Command not found!".
9
+ * The discord.js class check for each command type, shared by `@Command`, the dispatcher and
10
+ * `CommandInteractionType`. A `CommandType` without an entry here fails to compile.
18
11
  */ const INTERACTION_MATCHERS = {
19
12
  [enum_index.CommandType.SLASH]: (interaction)=>interaction instanceof discord_js.ChatInputCommandInteraction,
20
13
  [enum_index.CommandType.CONTEXT_MENU]: (interaction)=>interaction instanceof discord_js.ContextMenuCommandInteraction,
@@ -32,47 +25,24 @@ var discord_js = require('discord.js');
32
25
  enum_index.CommandType.CONTEXT_MENU,
33
26
  enum_index.CommandType.PRIMARY_ENTRY_POINT
34
27
  ]);
35
- /**
36
- * Whether an interaction is the kind the given command type handles.
37
- *
38
- * @param type - The command type declared on `@Command`.
39
- * @param interaction - The interaction being dispatched.
40
- */ function matchesCommandType(type, interaction) {
28
+ /** Whether an interaction is the kind the given command type handles. */ function matchesCommandType(type, interaction) {
41
29
  const matches = INTERACTION_MATCHERS[type];
42
30
  return matches !== undefined && matches(interaction);
43
31
  }
44
32
  /**
45
- * Whether the command type is routed by matching a customId pattern.
46
- *
47
- * Components carry an application-defined customId and so are matched by pattern;
48
- * commands carry a name Discord itself registered and are matched exactly.
49
- *
50
- * @param type - The command type declared on `@Command`.
33
+ * Whether a command type is routed by customId pattern: components carry an application-defined
34
+ * customId, while commands are matched by the name Discord registered.
51
35
  */ function isCustomIdRouted(type) {
52
36
  return !NAME_ROUTED_TYPES.has(type);
53
37
  }
54
- /**
55
- * Whether an interaction carries a customId, and so can be routed by pattern.
56
- *
57
- * @param interaction - The interaction being dispatched.
58
- */ function hasCustomId(interaction) {
38
+ /** Whether an interaction carries a customId, and so can be routed by pattern. */ function hasCustomId(interaction) {
59
39
  return interaction instanceof discord_js.MessageComponentInteraction || interaction instanceof discord_js.ModalSubmitInteraction;
60
40
  }
61
41
  /** Separates a command from its subcommand group and subcommand in a route key. */ const COMMAND_PATH_SEPARATOR = ' ';
62
42
  /**
63
- * The route keys a chat input interaction can be handled by, most specific first.
64
- *
65
- * Discord sends `/settings notify email` as one interaction named `settings`, so
66
- * routing on `commandName` alone gives every subcommand of a command the same handler
67
- * — and the framework would run whichever one was declared first. The full path is
68
- * tried before the bare name so a command can either split its subcommands across
69
- * methods or keep handling them in one, but never both by accident.
70
- *
71
- * A group is never dropped on the way down: `settings notify email` does not fall back
72
- * to `settings email`, because a second group could declare its own `email` and the
73
- * two would be indistinguishable.
74
- *
75
- * @param interaction - The chat input or autocomplete interaction being dispatched.
43
+ * The route keys a chat input interaction can be handled by, most specific first:
44
+ * `settings notify email`, then `settings`. Discord names the whole interaction `settings`, so the
45
+ * full path comes first; a group is never skipped, since two groups may share a subcommand name.
76
46
  * @returns The keys to look up, most specific first.
77
47
  */ function resolveCommandPaths(interaction) {
78
48
  const { commandName } = interaction;
@@ -99,24 +69,14 @@ var discord_js = require('discord.js');
99
69
  discord_js.ApplicationCommandOptionType.SubcommandGroup
100
70
  ]);
101
71
  /**
102
- * The value a handler should receive for one option.
103
- *
104
- * Discord sends entity options as a snowflake plus a `resolved` payload, and discord.js
105
- * puts that payload on the option as `user`/`role`/`channel`/`attachment`. Passing
106
- * `value` alone would hand the handler a bare id string for `@user`, forcing every
107
- * handler to re-fetch what the gateway already delivered.
72
+ * The value a handler receives for one option: the resolved user, role, channel or attachment
73
+ * discord.js attaches, rather than a bare snowflake to re-fetch.
108
74
  */ function resolveOptionValue(option) {
109
75
  return option.attachment ?? option.channel ?? option.role ?? option.user ?? option.member ?? option.value;
110
76
  }
111
77
  /**
112
- * Flattens a chat input interaction's options into the params record handlers receive.
113
- *
114
- * Subcommand and subcommand-group options are containers, not values — for
115
- * `/settings notify email true` the top level holds only `notify`. Recursing past them
116
- * means a subcommand handler sees `{ email: true }`, the same shape a flat command's
117
- * handler sees.
118
- *
119
- * @param interaction - The chat input or autocomplete interaction being dispatched.
78
+ * Flattens an interaction's options into the params record handlers receive, looking through
79
+ * subcommand and group containers, so `/settings notify email true` yields `{ email: true }`.
120
80
  * @returns Each supplied option keyed by name, with entity options resolved.
121
81
  */ function resolveOptionParams(interaction) {
122
82
  const data = interaction.options?.data;
@@ -135,14 +95,8 @@ var discord_js = require('discord.js');
135
95
  return params;
136
96
  }
137
97
  /**
138
- * The name of the option the user is currently typing, or `undefined`.
139
- *
140
- * `getFocused` throws when nothing is focused rather than returning null, and it is
141
- * absent altogether on a hand-built test double. Neither is worth failing a dispatch
142
- * over — an autocomplete with no focused option simply matches no option-specific
143
- * handler.
144
- *
145
- * @param interaction - The autocomplete interaction being dispatched.
98
+ * The name of the option the user is typing, or undefined when nothing is focused; `getFocused`
99
+ * throws then, and hand-built test doubles lack it entirely.
146
100
  */ function focusedOptionName(interaction) {
147
101
  if (typeof interaction.options?.getFocused !== 'function') return undefined;
148
102
  try {
@@ -254,21 +208,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
254
208
  /** The character a parameter will not cross, so one pattern segment maps to one value. */ const PARAM_SEPARATOR = '/';
255
209
  /** Escapes a literal stretch of a pattern so only placeholders stay meaningful. */ const escapeLiteral = (literal)=>literal.replace(/[/\\^$*+?.()|[\]{}]/g, '\\$&');
256
210
  /**
257
- * Helper function to create regex and parameter mappings from a pattern string.
258
- *
259
- * A `{name}` matches anything up to the next `/`, the same rule Express and Rails use
260
- * for a path segment. That is what lets a value the application does not control — a
261
- * uuid, an opaque vendor id, a slug — be captured whole without the author annotating
262
- * anything, since a hyphen inside it is data rather than structure.
263
- *
264
- * It also keeps neighbouring patterns apart: `profile/{uuid}` and `profile/{uuid}/{id}`
265
- * cannot both match one id, because a parameter cannot swallow the separator between
266
- * them. Patterns separated by `-` instead have no such boundary, so a pair like
267
- * `profile-{uuid}` and `profile-{uuid}-{id}` is ambiguous — {@link findAmbiguousRoutes}
268
- * reports those at registration.
269
- *
270
- * @param pattern - The pattern string to parse.
271
- * @returns The regex, the parameter names, and how specific the pattern is.
211
+ * Compiles a pattern into a regex, its parameter names and its specificity. A `{name}` matches up to
212
+ * the next `/`, so a uuid is captured whole and `profile/{uuid}` never overlaps `profile/{uuid}/{id}`;
213
+ * `-`-separated patterns can, which {@link findAmbiguousRoutes} reports at registration.
272
214
  */ function createRegexFromPattern(pattern) {
273
215
  const params = [];
274
216
  let regexPattern = '';
@@ -368,7 +310,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
368
310
  if (typeof builderOrType === 'function') {
369
311
  const builderObj = new builderOrType();
370
312
  builderInstance = builderObj.build(commandName);
371
- commandType = Reflect.getMetadata(metadataKey_enum.MetadataKey.CommandType, builderOrType);
313
+ commandType = Reflect.getMetadata(enum_index.MetadataKey.CommandType, builderOrType);
372
314
  if (!(commandType in enum_index.CommandType)) {
373
315
  throw new Error(`Metadata for 'commandType' is missing on builder ${builderOrType.name}`);
374
316
  }
@@ -405,19 +347,13 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
405
347
  return Reflect.getMetadata(COMMAND_METADATA_KEY, controller);
406
348
  }
407
349
  /**
408
- * Decorator to register an autocomplete handler for a chat input command's option.
350
+ * Registers an autocomplete handler for an option of a chat input command.
409
351
  *
410
- * Autocomplete is a separate interaction from the command it belongs to, and Discord
411
- * sends it while the user is still typing. It is not a `@Command`: nothing is
412
- * registered for it — the option's own `setAutocomplete(true)` is what turns it on —
413
- * and it is answered with `interaction.respond()` rather than a reply. Leaving it
414
- * unhandled is not silent to the user: the client shows a loading state until the
415
- * three-second window closes.
352
+ * Enable it on the option with `setAutocomplete(true)` and answer with `interaction.respond()`.
416
353
  *
417
- * @param commandPath - The command to complete, e.g. `settings` or `settings notify email`
418
- * for a subcommand. Parts are separated by a single space, as Discord displays them.
419
- * @param optionName - The option to complete. Omit to handle every option of the command,
420
- * branching on `interaction.options.getFocused(true)`.
354
+ * @param commandPath - The command, such as `search` or `settings notify email` for a subcommand.
355
+ * @param optionName - The option to complete. Omit to handle every option, branching on
356
+ * `interaction.options.getFocused(true)`.
421
357
  *
422
358
  * @example
423
359
  * ```typescript
@@ -439,13 +375,8 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
439
375
  };
440
376
  }
441
377
  /**
442
- * Retrieves autocomplete handler metadata from a given controller.
443
- *
444
- * Handlers naming an option come first, so a command-wide handler acts as the fallback
445
- * for options no specific handler claimed rather than shadowing them by declaration order.
446
- *
447
- * @param controller - The controller class instance.
448
- * @returns The registered autocomplete handlers, most specific first.
378
+ * Returns a controller's autocomplete handlers, option-specific ones first.
379
+ * @param controller - The controller instance.
449
380
  */ function getAutocompleteHandlers(controller) {
450
381
  const handlers = Reflect.getMetadata(AUTOCOMPLETE_METADATA_KEY, controller) || [];
451
382
  return [
@@ -453,7 +384,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
453
384
  ].sort((a, b)=>Number(Boolean(b.optionName)) - Number(Boolean(a.optionName)));
454
385
  }
455
386
  /**
456
- * Decorator to mark a class as a controller that can later be registered to the App class `(app.ts)` using the `@MeoCord` decorator.
387
+ * Marks a class as a controller, to be listed in `@MeoCord({ controllers })`.
457
388
  *
458
389
  * @example
459
390
  * ```typescript
@@ -463,28 +394,20 @@ const PLACEHOLDER_PATTERN = /\{(\w+)}/g;
463
394
  *
464
395
  * @Command('ping', PingCommandBuilder)
465
396
  * async ping(interaction: ChatInputCommandInteraction) {
466
- * const response = await this.pingService.handlePing()
467
- * await interaction.reply(response)
397
+ * await interaction.reply(await this.pingService.handlePing())
468
398
  * }
469
399
  * }
470
400
  * ```
471
401
  */ function Controller() {
472
402
  return function(target) {
473
- if (!Reflect.hasMetadata(metadataKey_enum.MetadataKey.Injectable, target)) {
403
+ if (!Reflect.hasMetadata(enum_index.MetadataKey.Injectable, target)) {
474
404
  inversify.injectable()(target);
475
405
  }
476
406
  };
477
407
  }
478
408
  /**
479
- * Finds pattern pairs that can both match one customId.
480
- *
481
- * Patterns of different segment counts are disjoint, because a parameter cannot cross
482
- * `/`. Within the same count, two patterns overlap unless some position holds literals
483
- * that differ: `a/{x}/c` and `a/b/{y}` both take `a/b/c`, and neither is more literal
484
- * than the other, so ranking cannot settle it either.
485
- *
486
- * @param patterns - The registered patterns.
487
- * @returns Each ambiguous pair, once, in the order the patterns were given.
409
+ * Finds pairs of customId patterns that can both match one id, such as `a/{x}/c` and `a/b/{y}`.
410
+ * @returns Each ambiguous pair once, in the order the patterns were given.
488
411
  */ function findAmbiguousRoutes(patterns) {
489
412
  const isParam = (segment)=>PLACEHOLDER_PATTERN.test(segment);
490
413
  const segmentsOf = (pattern)=>pattern.split(PARAM_SEPARATOR);
@@ -524,6 +447,7 @@ exports.getCommandMap = getCommandMap;
524
447
  exports.getMessageHandlers = getMessageHandlers;
525
448
  exports.getReactionHandlers = getReactionHandlers;
526
449
  exports.hasCustomId = hasCustomId;
450
+ exports.isCustomIdRouted = isCustomIdRouted;
527
451
  exports.matchesCommandType = matchesCommandType;
528
452
  exports.resolveCommandPaths = resolveCommandPaths;
529
453
  exports.resolveOptionParams = resolveOptionParams;
@@ -13,17 +13,9 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
13
13
  let cachedConfig;
14
14
  let configLoaded = false;
15
15
  /**
16
- * Loads the configuration a built application runs with: `dist/meocord.config.mjs`, which
17
- * `meocord build` compiles from `meocord.config.ts`.
18
- *
19
- * Reads only the compiled file, which is plain JavaScript and needs no transpiler, tsconfig or
20
- * source. Reading `meocord.config.ts` is {@link loadMeoCordSourceConfig}'s job, and it lives in its
21
- * own module because it needs jiti: this one is imported by the logger and the factory, so a
22
- * bot built with `bundleDependencies` bundles whatever it imports.
23
- *
24
- * The result is cached after the first load.
25
- *
26
- * @returns The configuration, or undefined when there is no compiled config or it fails to load.
16
+ * Loads the configuration a built application runs with, `dist/meocord.config.mjs`, and caches it.
17
+ * Imports no transpiler, since the logger and the factory import this module into every bot.
18
+ * @returns The configuration, or undefined when the compiled config is missing or fails to load.
27
19
  */ function loadMeoCordConfig() {
28
20
  if (configLoaded) return cachedConfig;
29
21
  configLoaded = true;
@@ -39,7 +31,7 @@ let configLoaded = false;
39
31
  try {
40
32
  // Called through a variable so a bundler does not try to resolve the path while building the
41
33
  // application; the file is only there once the build is done.
42
- const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-Dqwlnz0m.cjs', document.baseURI).href)));
34
+ const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-C8w8ivsR.cjs', document.baseURI).href)));
43
35
  const loaded = load(compiledPath);
44
36
  return loaded.default ?? loaded;
45
37
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var theme = require('../_shared/theme-Dqwlnz0m.cjs');
3
+ var theme = require('../_shared/theme-C8w8ivsR.cjs');
4
4
  require('node:util');
5
5
  require('dayjs');
6
6
  require('dayjs/plugin/utc.js');