meocord 4.1.0-beta.1 → 4.1.0-beta.4

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 (89) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +446 -35
  3. package/dist/cjs/_shared/{cooldown-store-DTqJP31S.cjs → cooldown-store-CWKSQ7_1.cjs} +26 -13
  4. package/dist/cjs/_shared/explained-error-DxJDCxm7.cjs +196 -0
  5. package/dist/cjs/_shared/{input-runner-CucAPIqF.cjs → providers-GXcy9wT9.cjs} +344 -51
  6. package/dist/cjs/_shared/{shard-context-84GuH11q.cjs → shard-context-DK3sAxsb.cjs} +426 -65
  7. package/dist/cjs/_shared/{translator-DEV2jyFH.cjs → translator-BqSfAkoS.cjs} +129 -0
  8. package/dist/cjs/common/index.cjs +36 -3
  9. package/dist/cjs/core/index.cjs +233 -189
  10. package/dist/cjs/decorator/index.cjs +156 -87
  11. package/dist/cjs/testing/index.cjs +477 -90
  12. package/dist/esm/bin/app-template/README.md.template +2 -1
  13. package/dist/esm/bin/app-template/package.json.template +1 -0
  14. package/dist/esm/bin/app-template/src/controllers/message/sample.message.controller.ts.template +0 -1
  15. package/dist/esm/bin/app-template/tsconfig.eslint.json.template +1 -1
  16. package/dist/esm/bin/app-template/vitest.config.ts.template +2 -1
  17. package/dist/esm/bin/app-template/vitest.setup.ts.template +6 -0
  18. package/dist/esm/bin/builder-template/controller/message.controller.template +0 -1
  19. package/dist/esm/bin/builder-template/observer.spec.template +39 -0
  20. package/dist/esm/bin/builder-template/observer.template +15 -0
  21. package/dist/esm/bin/generator.js +13 -0
  22. package/dist/esm/bin/helper/filter-generator.helper.js +2 -0
  23. package/dist/esm/bin/helper/guard-generator.helper.js +2 -0
  24. package/dist/esm/bin/helper/interceptor-generator.helper.js +2 -0
  25. package/dist/esm/bin/helper/observer-generator.helper.js +47 -0
  26. package/dist/esm/bin/helper/pipe-generator.helper.js +2 -0
  27. package/dist/esm/bin/helper/service-generator.helper.js +2 -0
  28. package/dist/esm/bin/meocord.js +2 -0
  29. package/dist/esm/build/rsbuild-config.js +9 -4
  30. package/dist/esm/build/runnable-bundle.js +91 -0
  31. package/dist/esm/common/cooldown-store.js +9 -8
  32. package/dist/esm/common/execution-context.js +11 -2
  33. package/dist/esm/common/index.js +3 -0
  34. package/dist/esm/common/redis-cooldown-store.js +130 -0
  35. package/dist/esm/common/response/response-state.js +6 -3
  36. package/dist/esm/common/sharded-cooldown-store.js +105 -0
  37. package/dist/esm/common/token.js +30 -0
  38. package/dist/esm/core/cooldown-runner.js +17 -4
  39. package/dist/esm/core/guard-runner.js +25 -2
  40. package/dist/esm/core/handler-pipeline.js +95 -12
  41. package/dist/esm/core/handler-registry.js +2 -2
  42. package/dist/esm/core/input-runner.js +4 -3
  43. package/dist/esm/core/lifecycle-order.js +7 -16
  44. package/dist/esm/core/meocord-factory.js +81 -23
  45. package/dist/esm/core/meocord.app.js +112 -65
  46. package/dist/esm/core/message-routes.js +275 -0
  47. package/dist/esm/core/observer-runner.js +97 -0
  48. package/dist/esm/core/providers.js +170 -0
  49. package/dist/esm/core/route-specificity.js +9 -0
  50. package/dist/esm/core/shard-context.js +2 -0
  51. package/dist/esm/core/shard-manager.js +5 -0
  52. package/dist/esm/core/shard-messages.js +1 -1
  53. package/dist/esm/core/stage-scope.js +1 -1
  54. package/dist/esm/decorator/app.decorator.js +31 -0
  55. package/dist/esm/decorator/controller.decorator.js +12 -24
  56. package/dist/esm/decorator/cooldown.decorator.js +5 -27
  57. package/dist/esm/decorator/index.js +2 -0
  58. package/dist/esm/decorator/inject.decorator.js +24 -0
  59. package/dist/esm/decorator/observer.decorator.js +45 -0
  60. package/dist/esm/package.json.js +1 -1
  61. package/dist/esm/testing/cooldown-store-suite.js +112 -0
  62. package/dist/esm/testing/execution-context.js +11 -3
  63. package/dist/esm/testing/index.js +2 -1
  64. package/dist/esm/testing/inspect-handler.js +9 -4
  65. package/dist/esm/testing/meocord-testing-module.js +93 -46
  66. package/dist/esm/testing/mock-fn.js +38 -1
  67. package/dist/esm/testing/mock-interaction.js +181 -21
  68. package/dist/esm/testing/routing.js +33 -10
  69. package/dist/esm/util/generator-cli.util.js +2 -0
  70. package/dist/types/common/index.d.cts +156 -4
  71. package/dist/types/common/index.d.ts +156 -4
  72. package/dist/types/core/index.d.cts +1 -1
  73. package/dist/types/core/index.d.ts +1 -1
  74. package/dist/types/decorator/index.d.cts +133 -15
  75. package/dist/types/decorator/index.d.ts +133 -15
  76. package/dist/types/{errors-DwvuoFVM.d.cts → errors-CPmW5Jp6.d.cts} +91 -5
  77. package/dist/types/{errors-DwvuoFVM.d.ts → errors-CPmW5Jp6.d.ts} +91 -5
  78. package/dist/types/interface/index.d.cts +147 -11
  79. package/dist/types/interface/index.d.ts +147 -11
  80. package/dist/types/testing/index.d.cts +171 -42
  81. package/dist/types/testing/index.d.ts +171 -42
  82. package/dist/types/{translator-DeDoBQH7.d.ts → translator-CiasEmUn.d.cts} +67 -2
  83. package/dist/types/{translator-DeDoBQH7.d.cts → translator-CiasEmUn.d.ts} +67 -2
  84. package/meocord.eslint.cjs +22 -5
  85. package/meocord.eslint.mjs +22 -5
  86. package/package.json +7 -2
  87. package/dist/cjs/_shared/explained-error-dAMLbuqv.cjs +0 -32
  88. package/dist/types/cooldown-store-D1_CYKwv.d.cts +0 -66
  89. package/dist/types/cooldown-store-D1_CYKwv.d.ts +0 -66
package/CHANGELOG.md CHANGED
@@ -1,5 +1,87 @@
1
1
  # meocord
2
2
 
3
+ ## 4.1.0-beta.4
4
+
5
+ ### Minor Changes
6
+
7
+ - [#137](https://github.com/meocord/meocord/pull/137) [`c778561`](https://github.com/meocord/meocord/commit/c778561f583a1246570881c56348c3b850bc8330) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@Cooldown` takes `by`, a function of the call that counts calls apart by a value, such as the account a button acts on: `@Cooldown({ seconds: 3600, by: (_context, { uid }: { uid: string }) => uid })` lets a user check each of their accounts in once an hour, where before the first check-in blocked the rest. `by` receives the handler's params as the handler does, after validation and pipes, and the handler's params are checked against the ones `by` declares at compile time. With `per: 'global'`, the limit is per resource across every user. Returning `undefined` counts as before; an error `by` throws goes to the exception filters, and nothing is counted. `inspectHandler(...).cooldowns` now reports `by` alongside `bypass`.
8
+
9
+ - [#143](https://github.com/meocord/meocord/pull/143) [`add425d`](https://github.com/meocord/meocord/commit/add425d6d37171828d23fb8d6e07532eec349d7f) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `testCooldownStore` in `meocord/testing` checks a `CooldownStore` you write yourself, over Postgres, SQLite, MongoDB or anything else, against the behaviour `MemoryCooldownStore` defines. It registers its cases with your test framework's `describe`, `it` and `expect`, so it runs under Vitest or Jest:
10
+
11
+ ```typescript
12
+ import { testCooldownStore } from 'meocord/testing'
13
+
14
+ testCooldownStore('PostgresCooldownStore', () => new PostgresCooldownStore(sql), { describe, it, expect })
15
+ ```
16
+
17
+ It covers calls within a window, a sliding window, `retryAfterMs` counted from the oldest call still in the window, each key counted on its own, calls in the same millisecond kept distinct, and several concurrent calls at the limit where exactly one passes. It uses real time with short windows and takes a few seconds. The README's [Store recipes](https://github.com/meocord/meocord/blob/main/README.md#store-recipes) show stores for Postgres, SQLite and MongoDB.
18
+
19
+ - [#140](https://github.com/meocord/meocord/pull/140) [`28f0f0a`](https://github.com/meocord/meocord/commit/28f0f0ae8bd660f3dccc332d87f657b1a069c49b) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `ExecutionContext.getHandlerParams<P>()` returns the handler's params, its second argument, to any stage: a command's options, a component's customId params, a modal's fields or a message pattern's params. A guard sees them raw, an interceptor raw before `next.handle()` and validated and piped after it, and a filter as they were when the error was thrown. It returns a patterned `@MessageHandler`'s params too, and is `undefined` for message listeners, reaction and event handlers, and a call no handler was reached for. It is separate from `getParams()`, which stays the running stage's own `{ provide, params }`. `createExecutionContext` takes `handlerParams` for unit tests. `getArgs()` now returns the arguments as they stand too, so after validation and pipes its second argument is the validated and piped params, the same value `getHandlerParams()` returns; in 4.1.0-beta.1 to beta.3 it kept returning the raw arguments.
20
+
21
+ - [#148](https://github.com/meocord/meocord/pull/148) [`abf9fd8`](https://github.com/meocord/meocord/commit/abf9fd8ba1769ac1cdd2f0c9b1dd1df6de81ff5e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@MessageHandler` takes patterns with params, using the `{name}` syntax of customId routes: `@MessageHandler('roll {sides} {note...?}')` gives the handler `{ sides, note }` as its second argument. `{name}` is one word, and words in quotes count as one; `{name...}` takes the rest of the message as typed; `{name?}` and `{name...?}` are optional; the last three come only at the end. `@Validate`, pipes, `@Cooldown({ by })` and `ExecutionContext.getHandlerParams()` see a patterned message handler's params as they do a component's. `@MeoCord({ messages: { prefix, mention, caseSensitive } })` sets the prefix, a string, a list or a function of the message, accepts a mention of the bot when `mention` is on, and matches the prefix and literal words in any case unless `caseSensitive` is set; a handler overrides them with `@MessageHandler('ping', { prefix: false | '?' | ['?', '??'], caseSensitive })`. Only the most specific matching pattern runs, across every controller: more literal words first, then a fixed number of words before a rest, then fewer params. `@MessageHandler()` without a pattern still runs for every message. A pattern that cannot be read, and two that match the same messages, stop the bot at startup. In `meocord/testing`, `resolveRoute(App, { content })` resolves a message, `invoke(Controller, 'method', message)` passes the params the pattern captures, `inspectHandler(...).pattern` reports the pattern, and `createMockMessage()` comes from a user rather than a bot.
22
+
23
+ A 4.0 keyword now matches in any case and word by word, only the most specific of two matching patterns runs, two handlers with the same keyword stop the bot at startup, and a configured prefix applies to existing keywords unless they set `prefix: false`. See [Message keywords match in any case, and only one runs](https://github.com/meocord/meocord/blob/main/docs/MIGRATING.md#message-keywords-match-in-any-case-and-only-one-runs).
24
+
25
+ - [#147](https://github.com/meocord/meocord/pull/147) [`5a2813f`](https://github.com/meocord/meocord/commit/5a2813f72b9d8afc9e0797ee266929b98433ad5c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Observers see every call MeoCord dispatches once it has settled, for metrics and audit logs. Mark a class `@Observer()`, implement `DispatchObserver`'s `onSettled(context, result)`, and list it in `@MeoCord({ observers })`. It is told about commands, components, modals, autocomplete, message, reaction and event handlers, and interactions no handler matches. The `DispatchResult` carries an `outcome` (`'ran'`, `'denied'`, `'cooldown'`, `'invalid'`, `'error'` or `'not-found'`), `startedAt` in epoch milliseconds, a `durationMs` covering the whole call through the filters and the fallback, the guard that denied it as `deniedBy`, where an interaction's answer stood as `response` (`'replied'`, `'deferred'` or `'unanswered'`), the `error`, and whether a filter or the fallback `handled` it. An optional `onStart(context)` sees the call begin, before the guards, with the same context object `onSettled` later receives, so a `WeakMap` pairs them into a span. Observers run in the order listed, and the call never waits for them: one that is slow never delays a handler, and one that throws is logged while the rest still run. `@Observer({ types })` limits one to some kinds of call. They are services, so they inject dependencies and their `onReady` and `onShutdown` hooks run in dependency order. A message no handler matches is not reported. In `meocord/testing`, `invoke` and `emit` wait for the module's observers, the testing module takes `observers`, and `inspectHandler(...).observers` lists an app's. `npx meocord g ob <name>` generates one with its spec.
26
+
27
+ - [#144](https://github.com/meocord/meocord/pull/144) [`6798abd`](https://github.com/meocord/meocord/commit/6798abd830f3f4b62b073413ca663d48b062f4b7) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `RedisCooldownStore` in `meocord/common` keeps `@Cooldown` counts on Redis, so they survive a restart and are shared by every shard and process that uses the same server, which keeps `'user'` and `'global'` cooldowns exact under process sharding. MeoCord adds no Redis dependency: give `RedisCooldownStore.using` a function that runs a script with your client, and pass what it returns to `@MeoCord({ cooldownStore })`:
28
+
29
+ ```typescript
30
+ import { RedisCooldownStore } from 'meocord/common'
31
+
32
+ // node-redis
33
+ cooldownStore: RedisCooldownStore.using((script, keys, args) => redis.eval(script, { keys, arguments: args }))
34
+ // ioredis
35
+ cooldownStore: RedisCooldownStore.using((script, keys, args) => redis.eval(script, keys.length, ...keys, ...args))
36
+ ```
37
+
38
+ One Lua script checks and records each call as one step, timed by the server's `TIME`, with calls in the same millisecond kept apart and every key set to expire. Keys start with `meocord:cooldown:`, or `{ prefix }`; pass `{ evalsha }` to send the script by its SHA1, in full only when the server answers `NOSCRIPT`. It runs on Redis 5 and later, Valkey, KeyDB, Dragonfly and Upstash; Garnet runs Lua only in part, so check it with `testCooldownStore` first. See [Where calls are counted](https://github.com/meocord/meocord/blob/main/README.md#where-calls-are-counted).
39
+
40
+ - [#145](https://github.com/meocord/meocord/pull/145) [`8f8690c`](https://github.com/meocord/meocord/commit/8f8690c84bc4e6b2676e86842b0ae3e51129c620) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `ShardedCooldownStore` in `meocord/common` makes `'user'` and `'global'` cooldowns exact under process sharding without a database. Each shard asks the shard manager, which counts every shard's calls in its memory over the IPC the shards already use:
41
+
42
+ ```typescript
43
+ import { ShardedCooldownStore } from 'meocord/common'
44
+
45
+ @MeoCord({ controllers: [...], clientOptions: {...}, cooldownStore: ShardedCooldownStore })
46
+ export default class App {}
47
+ ```
48
+
49
+ Counts are kept while the manager runs, so a shard that restarts keeps them, but they start again when the whole bot restarts. For counts that outlive a restart, or a bot on several hosts, use `RedisCooldownStore`. If the manager does not answer within a second, a shard counts the call itself and warns once. The startup warning about per-shard cooldowns stays silent with this store, and now names both shared stores. See [Where calls are counted](https://github.com/meocord/meocord/blob/main/README.md#where-calls-are-counted).
50
+
51
+ ### Patch Changes
52
+
53
+ - [#142](https://github.com/meocord/meocord/pull/142) [`400f903`](https://github.com/meocord/meocord/commit/400f903510961d7b181352586f2c693f5039eabc) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A `bundleDependencies` build starts under Bun, and under any devtool.
54
+
55
+ - **Bun.** A bundled ES module that probes for CommonJS, as lodash-es does with `typeof exports`, left `module` and `exports` in the bundle's top scope. Bun then read the whole bundle as CommonJS and stopped at startup with `Cannot use import statement with CommonJS-only features`, while Node ran it. Those probes now see `undefined`, as they do in any ES module, so `bun dist/main.js` starts. CommonJS dependencies keep their own `module` and `exports`. Rebuild to pick this up; nothing else changes.
56
+ - **Eval devtools.** An `eval-*` devtool, set through `output.sourceMap` or `tools.rspack` in the `rsbuild` hook, is built as its non-eval equivalent (`eval-source-map` as `source-map`, plain `eval` as no source map), with a warning at build time. A module evaluated from a string cannot read `import.meta`, so with `bundleDependencies` such a bundle stopped at startup with `SyntaxError: import.meta is only valid inside modules`. To silence the warning, set `output.sourceMap.js` to the non-eval devtool yourself.
57
+ - Development builds emit `cheap-module-source-map` rather than `eval-source-map`. See [Smaller changes](https://github.com/meocord/meocord/blob/main/docs/MIGRATING.md#smaller-changes) in the upgrade guide.
58
+
59
+ ## 4.1.0-beta.3
60
+
61
+ ### Patch Changes
62
+
63
+ - [#133](https://github.com/meocord/meocord/pull/133) [`9153786`](https://github.com/meocord/meocord/commit/9153786e695a7d98c5d6b777e77cc07381e2c22e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Ships every change listed under 4.1.0-beta.2, which was not published on its own. Providers in `@MeoCord({ providers })` and in the testing module's `providers` can be listed in any order: a class listed there is bound once, even when a provider earlier in the list injects it.
64
+
65
+ ## 4.1.0-beta.2
66
+
67
+ Not published to npm: these changes first ship in 4.1.0-beta.3.
68
+
69
+ ### Minor Changes
70
+
71
+ - [#123](https://github.com/meocord/meocord/pull/123) [`3924763`](https://github.com/meocord/meocord/commit/3924763805de2c1b6c44ca2d94e72e823956b67d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `@MeoCord({ providers })` provides values, classes and sync or async factories under a class, string, symbol or `createToken` token, injected with the new `@Inject(token)`, with their `onReady` and `onShutdown` hooks run in dependency order; `MeoCordTestingModule` takes the same providers.
72
+
73
+ - [#132](https://github.com/meocord/meocord/pull/132) [`9dcfec5`](https://github.com/meocord/meocord/commit/9dcfec5d37b7a2f0e13a8fdf9193be80f39e994f) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `meocord/testing` exports `clearAllMocks()` and `resetAllMocks()`, which reach every mock it made: `createMockFn`, and the mocks inside `createMockInteraction`, `createMockClient` and the rest. Vitest's `clearMocks` and jest's `clearAllMocks()` only reach their own `vi.fn()` and `jest.fn()`. New projects call `resetAllMocks()` after every test from `vitest.setup.ts`. To do the same in an existing project, add `afterEach(() => resetAllMocks())` to a Vitest setup file, and set what a mock returns in the test, or in `beforeEach`, that relies on it.
74
+
75
+ ### Patch Changes
76
+
77
+ - [#125](https://github.com/meocord/meocord/pull/125) [`c8584a6`](https://github.com/meocord/meocord/commit/c8584a63e9b9d0a4db629338c137bf26a96fa765) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `createMockChannel` takes `ThreadChannel`, stubs `threads.create` on text, announcement, forum and media channels, and gives a subclass the managers of the channel class it extends.
78
+
79
+ - [#130](https://github.com/meocord/meocord/pull/130) [`1b81e6d`](https://github.com/meocord/meocord/commit/1b81e6d4035e41a02d5b2cabaed503927c8e09af) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Mocks from `meocord/testing` behave more like discord.js. An interaction mock has a `locale` of `'en-US'`, and a `guildLocale` of `'en-US'` with a `guildId` and `null` without, so `t.for(interaction, { public: true })` works on a default mock. A method that returns a promise in discord.js now resolves instead of returning `undefined`, so `await` and `.catch()` work without setup. `send()` and `reply()` resolve to a mock message. A manager's `fetch(id)`, `create()` and `edit()` resolve to a mock of its item, and a list fetch to an empty `Collection`. `createDM()` resolves to a DM channel, and a structure's own `edit()`, `fetch()` and setters to the structure itself. `mockResolvedValue` and `mockRejectedValue` still override them.
80
+
81
+ - [#126](https://github.com/meocord/meocord/pull/126) [`8cee75d`](https://github.com/meocord/meocord/commit/8cee75d56a250cc86ced77325cdffa20557431eb) Thanks [@l7aromeo](https://github.com/l7aromeo)! - `respond()` reads discord.js's deprecated `ephemeral: true` as `flags: MessageFlags.Ephemeral`, so a private follow-up on a public deferred reply is no longer shown to everyone as an edit of that reply, and tests see the privacy the bot sends; `flags: MessageFlags.Ephemeral` is the supported form.
82
+
83
+ - [#131](https://github.com/meocord/meocord/pull/131) [`b5e7e63`](https://github.com/meocord/meocord/commit/b5e7e6358bdb325fac4a21b0415922a50ec1727a) Thanks [@l7aromeo](https://github.com/l7aromeo)! - A controller or service whose constructor parameter has no runtime type now stops `MeoCordFactory.create`, and the testing module, before anything is bound. The error names the class, the parameter and the classes that inject it, and says how to fix it. This happens when two services import each other, or a parameter is typed with an interface or an `import type`; the error inversify raised before pointed at the compiler options instead. `meocord/eslint` now warns on import cycles (`import-x/no-cycle`, type-only imports ignored) in projects that have `eslint-import-resolver-typescript`, which it needs to follow imports through `@src`. New projects include it; in a project without it the check stays off and lint is unchanged. Add it with `npm i -D eslint-import-resolver-typescript` to get the warning.
84
+
3
85
  ## 4.1.0-beta.1
4
86
 
5
87
  ### Minor Changes