meocord 4.1.1 → 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 (96) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +23 -23
  3. package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-CRn9kWob.cjs} +295 -87
  4. package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-ChvMqnLd.cjs} +74 -40
  5. package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-BdAUTdYs.cjs} +1 -1
  6. package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CQo8mSP8.cjs} +158 -72
  7. package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-DhDVbT5M.cjs} +12 -3
  8. package/dist/cjs/common/index.cjs +4 -4
  9. package/dist/cjs/core/index.cjs +195 -157
  10. package/dist/cjs/decorator/index.cjs +66 -57
  11. package/dist/cjs/testing/index.cjs +736 -307
  12. package/dist/cli.json +1 -1
  13. package/dist/esm/bin/app-template/README.md.template +3 -1
  14. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
  15. package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
  16. package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
  17. package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
  18. package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
  19. package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
  20. package/dist/esm/build/load-config.pre-entry.js +4 -1
  21. package/dist/esm/common/decorator.js +6 -6
  22. package/dist/esm/common/deprecation.js +2 -2
  23. package/dist/esm/common/errors.js +2 -2
  24. package/dist/esm/common/execution-context.js +1 -1
  25. package/dist/esm/common/metadata.js +1 -1
  26. package/dist/esm/common/route.js +10 -1
  27. package/dist/esm/common/translate-error.js +1 -1
  28. package/dist/esm/core/command-conflicts.js +34 -34
  29. package/dist/esm/core/component-routes.js +74 -31
  30. package/dist/esm/core/dispatcher.js +51 -7
  31. package/dist/esm/core/filter-runner.js +2 -2
  32. package/dist/esm/core/handler-pipeline.js +27 -14
  33. package/dist/esm/core/handler-registry.js +1 -1
  34. package/dist/esm/core/interceptor-runner.js +2 -2
  35. package/dist/esm/core/meocord-factory.js +196 -157
  36. package/dist/esm/core/message-routes.js +4 -3
  37. package/dist/esm/core/observer-runner.js +4 -3
  38. package/dist/esm/core/providers.js +38 -21
  39. package/dist/esm/core/route-specificity.js +4 -4
  40. package/dist/esm/core/scalar-types.js +15 -3
  41. package/dist/esm/core/shard-context.js +1 -1
  42. package/dist/esm/core/startup-roots.js +2 -3
  43. package/dist/esm/core/theme-runtime.js +2 -2
  44. package/dist/esm/decorator/app.decorator.js +12 -7
  45. package/dist/esm/decorator/command-builder.decorator.js +4 -4
  46. package/dist/esm/decorator/controller-class.decorator.js +9 -4
  47. package/dist/esm/decorator/controller.decorator.js +110 -67
  48. package/dist/esm/decorator/cooldown.decorator.js +3 -3
  49. package/dist/esm/decorator/defer.decorator.js +4 -4
  50. package/dist/esm/decorator/event.decorator.js +5 -5
  51. package/dist/esm/decorator/filter.decorator.js +7 -7
  52. package/dist/esm/decorator/guard.decorator.js +8 -8
  53. package/dist/esm/decorator/inject.decorator.js +4 -4
  54. package/dist/esm/decorator/interceptor.decorator.js +7 -7
  55. package/dist/esm/decorator/observer.decorator.js +4 -4
  56. package/dist/esm/decorator/service.decorator.js +4 -4
  57. package/dist/esm/decorator/theme.decorator.js +3 -3
  58. package/dist/esm/decorator/validation.decorator.js +10 -10
  59. package/dist/esm/package.json.js +1 -1
  60. package/dist/esm/testing/catalog.js +1 -1
  61. package/dist/esm/testing/cooldown-store-suite.js +18 -10
  62. package/dist/esm/testing/discord-defaults.js +60 -7
  63. package/dist/esm/testing/index.js +4 -2
  64. package/dist/esm/testing/inspect-handler.js +20 -3
  65. package/dist/esm/testing/meocord-testing-module.js +229 -188
  66. package/dist/esm/testing/mock-fn.js +80 -10
  67. package/dist/esm/testing/mock-interaction.js +226 -78
  68. package/dist/esm/testing/response.js +1 -1
  69. package/dist/esm/testing/routing.js +39 -14
  70. package/dist/esm/testing/startup-errors.js +26 -0
  71. package/dist/esm/testing/strict-mocks.js +45 -0
  72. package/dist/esm/testing/theme.js +1 -1
  73. package/dist/esm/util/bundle-entry.util.js +2 -1
  74. package/dist/esm/util/interaction.util.js +1 -1
  75. package/dist/esm/util/meocord-config-validation.util.js +1 -0
  76. package/dist/esm/util/metadata-keys.js +1 -0
  77. package/dist/esm/util/refusal.util.js +148 -8
  78. package/dist/types/common/index.d.cts +8 -8
  79. package/dist/types/common/index.d.ts +8 -8
  80. package/dist/types/core/index.d.cts +8 -6
  81. package/dist/types/core/index.d.ts +8 -6
  82. package/dist/types/decorator/index.d.cts +30 -29
  83. package/dist/types/decorator/index.d.ts +30 -29
  84. package/dist/types/{index-D7n0FBMm.d.ts → index-CdXsFgjE.d.ts} +62 -15
  85. package/dist/types/{index-DEP0NYrU.d.cts → index-nn5p7OoX.d.cts} +62 -15
  86. package/dist/types/interface/index.d.cts +1 -1
  87. package/dist/types/interface/index.d.ts +1 -1
  88. package/dist/types/{route-C-aMI7Sl.d.cts → route-BZgFoNeB.d.cts} +5 -2
  89. package/dist/types/{route-C-aMI7Sl.d.ts → route-BZgFoNeB.d.ts} +5 -2
  90. package/dist/types/testing/index.d.cts +245 -31
  91. package/dist/types/testing/index.d.ts +245 -31
  92. package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-BcOJVzR9.d.ts} +1 -1
  93. package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-UKr3-6Ys.d.cts} +1 -1
  94. package/dist/types/{translator-J6tIUfdp.d.ts → translator-BViWKtLJ.d.ts} +1 -1
  95. package/dist/types/{translator-B4mc1lzJ.d.cts → translator-DHMkkCpg.d.cts} +1 -1
  96. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,109 @@
1
1
  # meocord
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#463](https://github.com/meocord/meocord/pull/463) [`4d6b965`](https://github.com/meocord/meocord/commit/4d6b965ced8ecad9aceedb92ab7d084c0c2761bb) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@MeoCord({ messages: { handlers: 'concurrent' } })` runs a message's matched command and its `@MessageHandler()` listeners together, so a slow or hung command no longer holds back logging, moderation and the other listeners. Each keeps its own guards, interceptors, filters and observers, and the call settles once all have; no order holds between them, so a listener that reads what the command writes for the same message should keep the default, `'sequential'`. ([docs](https://meocord.dev/docs/4.2/message-commands))
8
+
9
+ Under `'sequential'`, a running bot now warns, once per handler, when a message's handler takes 5 seconds or more with listeners waiting after it, naming the option. `messages: { slowHandlerWarning: false }` turns the warning off. A `MeoCordTestingModule` doesn't warn unless `slowHandlerWarning` is `true`, so a test whose fake clock passes 5 seconds inside a handler sees nothing new.
10
+
11
+ - [#460](https://github.com/meocord/meocord/pull/460) [`ded179f`](https://github.com/meocord/meocord/commit/ded179fc74d83e0228f89dc271033d61c04d0809) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Controller({ inheritedRoutes: 'replace' })` makes a handler the class re-decorates answer only the routes the class declares for it, dropping the ones its base classes declare for that method. That covers every kind: commands, component patterns, message patterns and listeners, reactions and autocompletes. A slash or context menu command that no handler answers any more is not registered. A method overridden without decorators keeps every route it inherits. `'keep'`, the default, answers both, as before; the next major version (5.0) replaces them by default.
12
+
13
+ Two things help with the move, without changing what a bot does:
14
+
15
+ - The warning that names a re-declared handler still answering an inherited route now says how to drop that route now.
16
+ - `inspectHandler` reports the routes a handler answers by inheritance, as `inheritedRoutes`.
17
+
18
+ See https://meocord.dev/docs/4.2/how-a-call-runs.
19
+
20
+ - [#459](https://github.com/meocord/meocord/pull/459) [`ff45ac5`](https://github.com/meocord/meocord/commit/ff45ac5b7fedbcfa6a51aef774cbc464c442df19) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `createMockRawMember()` builds the member Discord sends with an interaction from a server the bot isn't in: plain data with `roles` as role ids and `permissions` as a bitfield string, as discord.js keeps it. To test a user-installed command run there, give it as an interaction's `member` with the server's `guildId` and no `guild`:
21
+
22
+ - the interaction reads `inRawGuild()` true, `guild` and `channel` `null` (with its `channelId` kept), `user` the member's user and `memberPermissions` the member's;
23
+ - a user option's member is the resolved member Discord sends;
24
+ - the interaction is typed as a `'raw'` one;
25
+ - a guard that reads `member.roles.cache` throws, as it would in Discord.
26
+
27
+ A raw member with a `channel`, without a `guildId`, or with a different `user` is refused. Given with a `guild`, it reads as a cached server's member, as before, with a warning once to leave the guild out. A `guildId` given alone builds the interaction as before. See https://meocord.dev/docs/4.2/mocks.
28
+
29
+ - [#476](https://github.com/meocord/meocord/pull/476) [`586a493`](https://github.com/meocord/meocord/commit/586a493c39b5a834157d9ab084a5607733ba915c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - customId patterns take two new param types: `{id:snowflake}` and `{id:uuid}`. The handler gets each as text, typed `string`, and `route().build()` refuses text that isn't one. `build()` takes either only as a string and throws a `TypeError` for a number, which may have lost an ID's digits before `build()` sees it: `12345678901234567` arrives as `12345678901234568`.
30
+
31
+ - `{id:snowflake}` takes a Discord ID: 17 to 20 digits with no leading zero, up to the largest 64-bit value. Every ID Discord has made since 2015-01-28 has at least 17 digits, because an ID's top 42 bits count milliseconds since 2015-01-01. The handler keeps the ID as text: from 17 digits on, a JavaScript number can't hold it exactly.
32
+ - `{id:uuid}` takes a UUID in its canonical 8-4-4-4-12 hex form, in either case, kept as written.
33
+
34
+ Both rank ahead of an untyped param, so `ticket/{id:snowflake}` takes an ID before `ticket/{name}` does, whatever the listing. Neither shares a value with `int`, `bool` or the other, so they never tie with those. Shorter digits stay an `int` while a JavaScript number holds them exactly. A 16-digit value above `Number.MAX_SAFE_INTEGER` is neither an `int` nor a snowflake, so it goes to a `number` param or to text. Between a snowflake and a `number` param, which also takes those digits, the snowflake wins. Use `{id:snowflake}` for Discord IDs: `{id:number}` rounds one, giving `12345678901234568` for `12345678901234567`.
35
+
36
+ See https://meocord.dev/docs/4.2/components.
37
+
38
+ - [#462](https://github.com/meocord/meocord/pull/462) [`db96d3d`](https://github.com/meocord/meocord/commit/db96d3ddcdaa940f6f86f32b9fb5c0f2a9f55de9) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `MeoCordFactory.create()` and a testing module's `compile()` report every startup error their checks find, not only the first. These include two handlers of one command, two same-named classes with a cooldown, a provider for a token MeoCord binds itself, and a class nothing can make. Each error is logged with the file it comes from, and then the first is thrown as before: the same error, with the same message, so code and tests that catch it or match its text keep working. A lone error is reported as before.
39
+
40
+ A decorator's startup error, such as an invalid customId pattern, is still thrown as its class is defined, with its message unchanged. It now names what it is about and where:
41
+
42
+ - `error.declaration` is the handler or class it was applied to, such as `Tickets.close`;
43
+ - `error.file` is the source file it is declared in.
44
+
45
+ The built bot's report puts the handler first wherever the message doesn't already name it.
46
+
47
+ New, and opt-in: set `startupErrors: 'all'` in `meocord.config.ts` to report every startup error in one run.
48
+
49
+ - Decorators keep their errors instead of throwing them as each file loads.
50
+ - `create()` logs those together with its own errors, so a bot with three mistakes shows all three in one run.
51
+ - In a test, call `reportAllStartupErrors()` from `meocord/testing` in the setup file instead.
52
+
53
+ The default stays `'first'`, where a decorator throws as its class is defined. The next major version (5.0) makes `'all'` the default. See https://meocord.dev/docs/4.2/configuration.
54
+
55
+ - [#457](https://github.com/meocord/meocord/pull/457) [`ea32209`](https://github.com/meocord/meocord/commit/ea322096a1050dc725e2ab339a1f3fa3f02320f2) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `useStrictMocks()`, called once in a test setup file, has every mock from `meocord/testing` compute the values discord.js computes, where it reads a truthy placeholder otherwise. Those values include:
56
+
57
+ - a message's `editable`, `deletable`, `pinnable`, `crosspostable`, `bulkDeletable`, `hasThread` and `partial`;
58
+ - a member's `manageable`, `kickable`, `bannable` and `moderatable`;
59
+ - a role's `editable`;
60
+ - a channel's and a thread's `viewable`, `manageable`, `deletable` and `joinable`;
61
+ - `partial` on users, channels and reactions.
62
+
63
+ `message.thread` is `null` unless the channel caches a thread under the message's id, and no placeholder warning is logged.
64
+
65
+ To give those values what Discord would, strict mocks:
66
+
67
+ - cache the bot's member in its server from the start;
68
+ - give @everyone the permissions Discord gives it in a new server;
69
+ - give a channel or thread made without a server one of its own.
70
+
71
+ So a default message from another user is not `editable` or `deletable`, and a member is not `kickable` until the bot's member has a role above theirs with the permission. A value a test sets on a mock still wins. The next major version (5.0) computes these values by default. See https://meocord.dev/docs/4.2/mocks.
72
+
73
+ Without the call, mocks read as before. A voice channel's `joinable` and `speakable` and a DM channel's `partial`, which read a placeholder without the warning, now give it too. Every placeholder warning now ends ", or call useStrictMocks() to have the mock compute it now.", so a test that matches a warning's whole text needs the new ending.
74
+
75
+ - [#455](https://github.com/meocord/meocord/pull/455) [`602285b`](https://github.com/meocord/meocord/commit/602285b5e709909ae9e88ede8d296f6924053f50) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `useMockFn(vi.fn)`, called once in a test setup file, makes every mock from `meocord/testing` with the test runner's own mock function. The runner then treats them as its own: Vitest's `clearMocks` and `mockReset` config, `vi.clearAllMocks()` and `vi.mocked(...)` reach them, and bun's matchers, which accept only bun's mocks, read them with `useMockFn(mock)`. jest takes `useMockFn(jest.fn)`. Without the call, mocks are meocord's own as before. Under jest and bun, whose `mockReset` drops a mock's starting behaviour, reset with meocord's `resetAllMocks()`, which puts it back. node:test keeps meocord's own mock function.
76
+
77
+ A new project's `vitest.setup.ts` calls `useMockFn(vi.fn)`. An existing project can add the same line to its setup file, before any mock is made. See https://meocord.dev/docs/4.2/mocks.
78
+
79
+ ### Patch Changes
80
+
81
+ - [#466](https://github.com/meocord/meocord/pull/466) [`e646771`](https://github.com/meocord/meocord/commit/e646771ca9a92a28fe00cecd6a265b2d7ede409d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `testCooldownStore` no longer fails a correct store on a slow CI runner. Its sliding-window and `retryAfterMs` cases measured elapsed time against their nominal waits, with margins of tens of milliseconds, so a timer that fired a few hundred milliseconds late failed them. They now check against the times measured around each call, in a 2-second window with a second between calls, and still fail a store that resets fixed buckets or counts from the newest call. The suite takes about 2 seconds longer.
82
+
83
+ - [#474](https://github.com/meocord/meocord/pull/474) [`4f6b307`](https://github.com/meocord/meocord/commit/4f6b307d6d9a886e9b0bbcaa0439b36e9b4bae62) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Component customId patterns now rank segment by segment, left to right, the way most routers rank paths. When several patterns match an id, the first segment where one pattern spells out literal text and the other leaves a param decides it: the literal one runs, whatever order the controllers are listed in. So `profile/me/{section}` takes `profile/me/edit` from `profile/{userId}/edit`, while `profile/123/edit` still goes to `{userId}`. If that leaves a pair tied, the narrower type at the first param where they differ runs, the same type order as before: words to choose from, then `bool`, `int`, `number`, then text.
84
+
85
+ 4.1 ranked by how much literal text a whole pattern had. Only these overlapping pairs change handler, and 4.1 named every one of them in its startup warning:
86
+
87
+ - A pattern with more literal text, or more literal segments, now loses to one that spells out an earlier segment. For example, `a/{x}` now runs for `a/abcd` instead of `{x}/abcd`.
88
+ - Pairs 4.1 left to listing order, such as `a/{x}/c` and `a/b/{y}`, now go to the earlier literal whatever the listing, which is the handler 4.1's warning said would run in 5.0.
89
+ - Typed params are compared position by position instead of summed. For example, `{n:int}/{s}` now runs for `7/7` instead of the one listed first among it and `{s}/{n:int}`.
90
+
91
+ Two patterns that the ranking still can't tell apart log a warning at startup, with an id both match. Such a pair has the same literals and equally narrow params at every position, such as `t/{a:on|off}` and `t/{b:off|no}`, which both take `t/off`, and the one listed first runs. Pairs the ranking decides log no warning.
92
+
93
+ `findRouteConflicts` lists only those tied pairs. A test asserting `toEqual([])` passes for every app whose patterns the ranking tells apart. A test expecting a pair the ranking now decides, such as `profile/summary/{uid}` and `profile/{ownerId}/{uid}`, gets `[]` instead.
94
+
95
+ The internal `CommandMetadata`'s `specificity` still holds the value 4.1 gave, but routing no longer reads it. It's deprecated and goes in the next major version (5.0).
96
+
97
+ See https://meocord.dev/docs/4.2/components.
98
+
99
+ - [#484](https://github.com/meocord/meocord/pull/484) [`cecfe6a`](https://github.com/meocord/meocord/commit/cecfe6ad53bd2f834d8f17472eb5690a3be9865b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A new app from `meocord create` turns on strict mocks in its `vitest.setup.ts`, with `useStrictMocks()`, so its mocks compute what discord.js computes, such as a message's `editable` or a member's `kickable`, rather than placeholders. Its sample button and `OwnerGuard` read the owner's id as `{ownerId:snowflake}`, so a customId that isn't a Discord id matches nothing. An existing app can add `useStrictMocks()` to its own setup file, before any mock is made ([docs](https://meocord.dev/docs/4.2/mocks)).
100
+
101
+ ## 4.1.2
102
+
103
+ ### Patch Changes
104
+
105
+ - [#454](https://github.com/meocord/meocord/pull/454) [`1f71fe1`](https://github.com/meocord/meocord/commit/1f71fe12b96c339bf3e1f19f714f24ebd8e7e853) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A mock interaction's type guards, such as `isButton()` and `isRepliable()`, and its `inGuild()`, `inCachedGuild()` and `inRawGuild()` keep answering as discord.js does after `resetAllMocks()`. They returned `undefined` after a reset, so a mock made once for several tests, such as in `beforeAll`, failed every type check after the first test in a project that resets between tests, as a generated one does.
106
+
3
107
  ## 4.1.1
4
108
 
5
109
  ### Patch Changes
package/README.md CHANGED
@@ -15,17 +15,17 @@
15
15
 
16
16
  If you know NestJS, the shape will feel familiar.
17
17
 
18
- **Documentation:** [meocord.dev](https://meocord.dev/docs/4.1) · [Getting started](https://meocord.dev/docs/4.1/getting-started) · [API reference](https://meocord.dev/docs/4.1/api) · [Upgrading to 4.1](https://meocord.dev/docs/4.1/migrating) · [Changelog](https://meocord.dev/docs/4.1/changelog)
18
+ **Documentation:** [meocord.dev](https://meocord.dev/docs/latest) · [Getting started](https://meocord.dev/docs/latest/getting-started) · [API reference](https://meocord.dev/docs/latest/api) · [Upgrading to 4.1](https://meocord.dev/docs/latest/migrating) · [Changelog](https://meocord.dev/docs/latest/changelog)
19
19
 
20
20
  ## Features
21
21
 
22
- - **Controllers for every interaction** — [slash commands](https://meocord.dev/docs/4.1/slash-commands) and their subcommands, autocomplete, [buttons, selects and modals](https://meocord.dev/docs/4.1/components) routed by customId, context menus, message commands and reactions, each a decorated method.
23
- - **Message commands** — [commands typed in chat](https://meocord.dev/docs/4.1/message-commands), from a pattern such as `ban {target:member} {reason...?}`: typed params, flags and lists, a usage reply when a message does not fit, and built-in help.
24
- - **A request pipeline** — [guards](https://meocord.dev/docs/4.1/guards) decide whether a handler runs, interceptors wrap it, validation and pipes check its input, [cooldowns](https://meocord.dev/docs/4.1/cooldowns) limit how often it runs, and exception filters decide what the user is told.
22
+ - **Controllers for every interaction** — [slash commands](https://meocord.dev/docs/latest/slash-commands) and their subcommands, autocomplete, [buttons, selects and modals](https://meocord.dev/docs/latest/components) routed by customId, context menus, message commands and reactions, each a decorated method.
23
+ - **Message commands** — [commands typed in chat](https://meocord.dev/docs/latest/message-commands), from a pattern such as `ban {target:member} {reason...?}`: typed params, flags and lists, a usage reply when a message does not fit, and built-in help.
24
+ - **A request pipeline** — [guards](https://meocord.dev/docs/latest/guards) decide whether a handler runs, interceptors wrap it, validation and pipes check its input, [cooldowns](https://meocord.dev/docs/latest/cooldowns) limit how often it runs, and exception filters decide what the user is told.
25
25
  - **Dependency injection** — services, providers and lifecycle hooks, wired into controllers with no manual instantiation.
26
- - **Answers that fit the interaction** — `respond(interaction)` replies, edits or follows up from whatever state the interaction is in, styled by a presenter and a [theme](https://meocord.dev/docs/4.1/theming).
27
- - **Testing without Discord** — `meocord/testing` runs a handler through its whole pipeline, with [mocks](https://meocord.dev/docs/4.1/mocks) for every interaction type.
28
- - **A CLI** — `meocord create`, `build`, `start`, `register` and `generate`, with Rsbuild builds for development and production, sharding, and [self-contained builds](https://meocord.dev/docs/4.1/self-contained-builds) that deploy without `node_modules`.
26
+ - **Answers that fit the interaction** — `respond(interaction)` replies, edits or follows up from whatever state the interaction is in, styled by a presenter and a [theme](https://meocord.dev/docs/latest/theming).
27
+ - **Testing without Discord** — `meocord/testing` runs a handler through its whole pipeline, with [mocks](https://meocord.dev/docs/latest/mocks) for every interaction type.
28
+ - **A CLI** — `meocord create`, `build`, `start`, `register` and `generate`, with Rsbuild builds for development and production, sharding, and [self-contained builds](https://meocord.dev/docs/latest/self-contained-builds) that deploy without `node_modules`.
29
29
 
30
30
  ## Getting started
31
31
 
@@ -38,7 +38,7 @@ cp .env.example .env # then put your bot token in DISCORD_TOKEN
38
38
  npx meocord start --dev # development, restarting on every change
39
39
  ```
40
40
 
41
- The new project comes with a sample of each controller type, a guard, a presenter, a service and a spec for each. [Getting started](https://meocord.dev/docs/4.1/getting-started) walks through it, and [Your first command](https://meocord.dev/docs/4.1/first-command) adds one.
41
+ The new project comes with a sample of each controller type, a guard, a presenter, a service and a spec for each. [Getting started](https://meocord.dev/docs/latest/getting-started) walks through it, and [Your first command](https://meocord.dev/docs/latest/first-command) adds one.
42
42
 
43
43
  ## Example
44
44
 
@@ -77,18 +77,18 @@ export class GreetController {
77
77
  export default class App {}
78
78
  ```
79
79
 
80
- The command's options arrive as the handler's second argument. [Slash commands](https://meocord.dev/docs/4.1/slash-commands) covers options, subcommands and registration.
80
+ The command's options arrive as the handler's second argument. [Slash commands](https://meocord.dev/docs/latest/slash-commands) covers options, subcommands and registration.
81
81
 
82
82
  ## Documentation
83
83
 
84
- Everything else is on [meocord.dev](https://meocord.dev/docs/4.1), for each version:
84
+ Everything else is on [meocord.dev](https://meocord.dev/docs/latest), for each version:
85
85
 
86
- | To | Read |
87
- | ----------------------------------- | --------------------------------------------------------------- |
88
- | Learn MeoCord from the start | [Getting started](https://meocord.dev/docs/4.1/getting-started) |
89
- | Look up a decorator, type or helper | [API reference](https://meocord.dev/docs/4.1/api) |
90
- | Upgrade from 4.0, or from 3.x | [Upgrading](https://meocord.dev/docs/4.1/migrating) |
91
- | See what changed in each release | [Changelog](https://meocord.dev/docs/4.1/changelog) |
86
+ | To | Read |
87
+ | ----------------------------------- | ------------------------------------------------------------------ |
88
+ | Learn MeoCord from the start | [Getting started](https://meocord.dev/docs/latest/getting-started) |
89
+ | Look up a decorator, type or helper | [API reference](https://meocord.dev/docs/latest/api) |
90
+ | Upgrade from 4.0, or from 3.x | [Upgrading](https://meocord.dev/docs/latest/migrating) |
91
+ | See what changed in each release | [Changelog](https://meocord.dev/docs/latest/changelog) |
92
92
 
93
93
  ## Support
94
94
 
@@ -108,28 +108,28 @@ These sections moved to meocord.dev. Their headings stay here so that links from
108
108
 
109
109
  ### Theming
110
110
 
111
- [Theming](https://meocord.dev/docs/4.1/theming) on meocord.dev.
111
+ [Theming](https://meocord.dev/docs/latest/theming) on meocord.dev.
112
112
 
113
113
  ### Themes per server and per user
114
114
 
115
- [Themes per server and per user](https://meocord.dev/docs/4.1/theming#per-server-and-per-user) on meocord.dev.
115
+ [Themes per server and per user](https://meocord.dev/docs/latest/theming#per-server-and-per-user) on meocord.dev.
116
116
 
117
117
  ### Adding tokens of your own
118
118
 
119
- [Adding tokens of your own](https://meocord.dev/docs/4.1/theming#adding-tokens-of-your-own) on meocord.dev.
119
+ [Adding tokens of your own](https://meocord.dev/docs/latest/theming#adding-tokens-of-your-own) on meocord.dev.
120
120
 
121
121
  ### Where calls are counted
122
122
 
123
- [Where calls are counted](https://meocord.dev/docs/4.1/cooldowns#where-calls-are-counted) on meocord.dev.
123
+ [Where calls are counted](https://meocord.dev/docs/latest/cooldowns#where-calls-are-counted) on meocord.dev.
124
124
 
125
125
  ### Store recipes
126
126
 
127
- [Cooldown stores](https://meocord.dev/docs/4.1/recipes/cooldown-stores) on meocord.dev.
127
+ [Cooldown stores](https://meocord.dev/docs/latest/recipes/cooldown-stores) on meocord.dev.
128
128
 
129
129
  ### Stack traces
130
130
 
131
- [Stack traces](https://meocord.dev/docs/4.1/configuration#stack-traces) on meocord.dev.
131
+ [Stack traces](https://meocord.dev/docs/latest/configuration#stack-traces) on meocord.dev.
132
132
 
133
133
  ### Running tests
134
134
 
135
- [Running tests](https://meocord.dev/docs/4.1/testing#running-tests) on meocord.dev.
135
+ [Running tests](https://meocord.dev/docs/latest/testing#running-tests) on meocord.dev.