meocord 4.2.1 → 4.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/cjs/_shared/{controller.decorator-XKMlcUZz.cjs → controller.decorator-hp5xdJU4.cjs} +550 -189
  3. package/dist/cjs/_shared/{startup-roots-CgfPQrNl.cjs → event-sources-DzqTODXg.cjs} +274 -88
  4. package/dist/cjs/_shared/{providers-BZgJyfZS.cjs → providers-BBvbnmLP.cjs} +837 -295
  5. package/dist/cjs/_shared/{sharded-cooldown-store-CsJJyCZd.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
  6. package/dist/cjs/_shared/{translate-error-BUAR1WYG.cjs → translate-error-wsgfsUET.cjs} +1 -1
  7. package/dist/cjs/common/index.cjs +4 -104
  8. package/dist/cjs/core/index.cjs +435 -182
  9. package/dist/cjs/decorator/index.cjs +116 -121
  10. package/dist/cjs/testing/index.cjs +1923 -808
  11. package/dist/cli.json +1 -1
  12. package/dist/esm/bin/app-template/package.json.template +9 -9
  13. package/dist/esm/bin/app-template/src/main.ts.template +3 -2
  14. package/dist/esm/bin/generator.js +6 -4
  15. package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
  16. package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
  17. package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
  18. package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
  19. package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
  20. package/dist/esm/bin/helper/service-generator.helper.js +6 -4
  21. package/dist/esm/bin/meocord.js +106 -48
  22. package/dist/esm/build/native-addons.js +75 -19
  23. package/dist/esm/build/rsbuild-config.js +11 -3
  24. package/dist/esm/build/typed-externals.js +15 -2
  25. package/dist/esm/common/cooldown-store.js +38 -14
  26. package/dist/esm/common/deprecation.js +4 -1
  27. package/dist/esm/common/execution-context.js +3 -1
  28. package/dist/esm/common/explained-error.js +7 -3
  29. package/dist/esm/common/log-level.js +13 -9
  30. package/dist/esm/common/logger.js +4 -2
  31. package/dist/esm/common/meocord-text.js +11 -6
  32. package/dist/esm/common/redis-cooldown-store.js +3 -7
  33. package/dist/esm/common/response/flags.js +17 -1
  34. package/dist/esm/common/response/install-context.js +27 -16
  35. package/dist/esm/common/response/presenter.js +7 -3
  36. package/dist/esm/common/response/response-state.js +32 -10
  37. package/dist/esm/common/sharded-cooldown-store.js +11 -9
  38. package/dist/esm/common/theme.js +3 -1
  39. package/dist/esm/common/translator.js +27 -8
  40. package/dist/esm/core/autocomplete-routes.js +26 -0
  41. package/dist/esm/core/command-conflicts.js +15 -7
  42. package/dist/esm/core/command-registration.js +15 -1
  43. package/dist/esm/core/cooldown-runner.js +7 -9
  44. package/dist/esm/core/defer.js +2 -5
  45. package/dist/esm/core/dispatcher.js +92 -66
  46. package/dist/esm/core/event-requirements.js +67 -19
  47. package/dist/esm/core/event-sources.js +100 -0
  48. package/dist/esm/core/fallback.js +3 -1
  49. package/dist/esm/core/filter-runner.js +6 -6
  50. package/dist/esm/core/guard-runner.js +173 -52
  51. package/dist/esm/core/handler-input.js +24 -3
  52. package/dist/esm/core/handler-pipeline.js +54 -32
  53. package/dist/esm/core/handler-registry.js +51 -21
  54. package/dist/esm/core/input-runner.js +7 -8
  55. package/dist/esm/core/interceptor-runner.js +4 -5
  56. package/dist/esm/core/lifecycle-hooks.js +3 -8
  57. package/dist/esm/core/lifecycle-order.js +1 -15
  58. package/dist/esm/core/meocord-classes.js +14 -1
  59. package/dist/esm/core/meocord-factory.js +83 -54
  60. package/dist/esm/core/meocord.app.js +174 -89
  61. package/dist/esm/core/message-help.js +37 -15
  62. package/dist/esm/core/message-params.js +126 -46
  63. package/dist/esm/core/message-routes.js +225 -45
  64. package/dist/esm/core/message-words.js +30 -2
  65. package/dist/esm/core/observer-runner.js +4 -2
  66. package/dist/esm/core/process-state.js +19 -0
  67. package/dist/esm/core/providers.js +39 -4
  68. package/dist/esm/core/scalar-types.js +57 -16
  69. package/dist/esm/core/shard-context.js +11 -10
  70. package/dist/esm/core/shard-exit.js +3 -3
  71. package/dist/esm/core/shard-manager.js +16 -6
  72. package/dist/esm/core/stage-scope.js +2 -1
  73. package/dist/esm/core/startup-roots.js +28 -8
  74. package/dist/esm/core/theme-resolvers.js +26 -11
  75. package/dist/esm/core/theme-runtime.js +23 -13
  76. package/dist/esm/core/theme-scope.js +31 -30
  77. package/dist/esm/core/theme-validation.js +1 -1
  78. package/dist/esm/decorator/app.decorator.js +37 -1
  79. package/dist/esm/decorator/command-builder.decorator.js +14 -0
  80. package/dist/esm/decorator/controller-class.decorator.js +1 -1
  81. package/dist/esm/decorator/controller.decorator.js +48 -19
  82. package/dist/esm/decorator/defer.decorator.js +2 -2
  83. package/dist/esm/decorator/event.decorator.js +4 -5
  84. package/dist/esm/decorator/filter.decorator.js +4 -4
  85. package/dist/esm/decorator/guard.decorator.js +4 -61
  86. package/dist/esm/decorator/inject.decorator.js +4 -1
  87. package/dist/esm/decorator/interceptor.decorator.js +2 -2
  88. package/dist/esm/decorator/theme.decorator.js +4 -3
  89. package/dist/esm/decorator/validation.decorator.js +5 -5
  90. package/dist/esm/package.json.js +1 -1
  91. package/dist/esm/testing/catalog.js +32 -25
  92. package/dist/esm/testing/cooldown-store-suite.js +3 -3
  93. package/dist/esm/testing/discord-defaults.js +449 -335
  94. package/dist/esm/testing/inspect-handler.js +91 -10
  95. package/dist/esm/testing/meocord-testing-module.js +217 -91
  96. package/dist/esm/testing/mock-fn.js +8 -2
  97. package/dist/esm/testing/mock-interaction.js +864 -124
  98. package/dist/esm/testing/response-log.js +8 -0
  99. package/dist/esm/testing/response.js +7 -5
  100. package/dist/esm/testing/routing.js +48 -6
  101. package/dist/esm/testing/strict-mocks.js +8 -4
  102. package/dist/esm/testing/theme.js +3 -2
  103. package/dist/esm/util/common.util.js +30 -9
  104. package/dist/esm/util/generator-cli.util.js +6 -4
  105. package/dist/esm/util/inherited-env.util.js +72 -13
  106. package/dist/esm/util/json.util.js +10 -3
  107. package/dist/esm/util/meocord-cli.util.js +3 -2
  108. package/dist/esm/util/meocord-config-loader.util.js +14 -1
  109. package/dist/esm/util/meocord-config-validation.util.js +13 -3
  110. package/dist/esm/util/meocord-source-config.util.js +13 -3
  111. package/dist/esm/util/platform.util.js +3 -2
  112. package/dist/esm/util/refusal.util.js +29 -11
  113. package/dist/esm/util/registration-mode.util.js +14 -1
  114. package/dist/esm/util/timer-limit.util.js +4 -1
  115. package/dist/esm/util/tsconfig-paths.util.js +38 -7
  116. package/dist/esm/util/tsconfig.util.js +16 -58
  117. package/dist/esm/util/user-text.util.js +8 -4
  118. package/dist/esm/util/value.util.js +33 -5
  119. package/dist/types/common/index.d.cts +17 -10
  120. package/dist/types/common/index.d.ts +17 -10
  121. package/dist/types/core/index.d.cts +21 -11
  122. package/dist/types/core/index.d.ts +21 -11
  123. package/dist/types/decorator/index.d.cts +63 -63
  124. package/dist/types/decorator/index.d.ts +63 -63
  125. package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
  126. package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
  127. package/dist/types/interface/index.d.cts +1 -1
  128. package/dist/types/interface/index.d.ts +1 -1
  129. package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
  130. package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
  131. package/dist/types/testing/index.d.cts +158 -61
  132. package/dist/types/testing/index.d.ts +158 -61
  133. package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
  134. package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
  135. package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
  136. package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
  137. package/package.json +5 -2
@@ -2,7 +2,7 @@ import 'reflect-metadata';
2
2
  import { assertValidTheme } from '../core/theme-validation.js';
3
3
  import { THEMED_CLASSES } from '../core/theme-runtime.js';
4
4
  import { copyLayer } from '../core/theme-scope.js';
5
- import { declaring, refuse } from '../util/refusal.util.js';
5
+ import { declaring, decoratedName, refuse } from '../util/refusal.util.js';
6
6
  import { META } from '../util/metadata-keys.js';
7
7
 
8
8
  /**
@@ -37,8 +37,9 @@ import { META } from '../util/metadata-keys.js';
37
37
  */ function UseTheme(theme) {
38
38
  return declaring(function(target, propertyKey) {
39
39
  const onClass = propertyKey === undefined;
40
- const owner = onClass ? target : target.constructor;
41
- const where = `${owner.name}${onClass ? '' : `.${String(propertyKey)}`}: @UseTheme`;
40
+ // The class it themes: the target itself on a class or a static member, else the target's constructor
41
+ const owner = onClass || typeof target === 'function' ? target : target.constructor;
42
+ const where = `${decoratedName(target, propertyKey)}: @UseTheme`;
42
43
  assertValidTheme(theme, where);
43
44
  const key = onClass ? META.classTheme : META.methodTheme;
44
45
  const existing = onClass ? Reflect.getOwnMetadata(key, target) : Reflect.getOwnMetadata(key, target, propertyKey);
@@ -1,7 +1,7 @@
1
1
  import 'reflect-metadata';
2
2
  import { makeInjectable } from '../util/injectable.util.js';
3
3
  import { assertStageEntries } from '../core/stage-scope.js';
4
- import { declaring, refuseOnClass, refuse, refuseOnMethod } from '../util/refusal.util.js';
4
+ import { declaring, refuseOnClass, refuse, decoratedName, refuseOnMethod } from '../util/refusal.util.js';
5
5
  import { META } from '../util/metadata-keys.js';
6
6
 
7
7
  /**
@@ -43,15 +43,15 @@ import { META } from '../util/metadata-keys.js';
43
43
  refuseOnClass('@Validate', target, propertyKey);
44
44
  // Checked where it applies, so the refusal names the handler
45
45
  if (typeof schema?.['~standard']?.validate !== 'function') {
46
- throw refuse(new Error(`${target.constructor.name}.${propertyKey}: @Validate takes a Standard Schema, such as a zod, valibot or arktype schema.`));
46
+ throw refuse(new Error(`${decoratedName(target, propertyKey)}: @Validate takes a Standard Schema, such as a zod, valibot or arktype schema.`));
47
47
  }
48
48
  if (Reflect.hasOwnMetadata(META.methodValidation, target, propertyKey)) {
49
- throw refuse(new Error(`${target.constructor.name}.${propertyKey}: more than one @Validate; one @Validate per handler: combine the schemas into one.`));
49
+ throw refuse(new Error(`${decoratedName(target, propertyKey)}: more than one @Validate; one @Validate per handler: combine the schemas into one.`));
50
50
  }
51
51
  const inlinePipes = Object.values(options.pipes ?? {}).flatMap((entries)=>Array.isArray(entries) ? entries : [
52
52
  entries
53
53
  ]);
54
- assertStageEntries('@Validate', 'pipe', `${target.constructor.name}.${propertyKey}`, inlinePipes);
54
+ assertStageEntries('@Validate', 'pipe', decoratedName(target, propertyKey), inlinePipes);
55
55
  const metadata = {
56
56
  schema,
57
57
  pipes: options.pipes ?? {}
@@ -93,7 +93,7 @@ import { META } from '../util/metadata-keys.js';
93
93
  */ function UsePipe(key, ...pipes) {
94
94
  return declaring(function(target, propertyKey, _descriptor) {
95
95
  refuseOnClass('@UsePipe', target, propertyKey);
96
- assertStageEntries('@UsePipe', 'pipe', `${target.constructor.name}.${propertyKey}`, pipes);
96
+ assertStageEntries('@UsePipe', 'pipe', decoratedName(target, propertyKey), pipes);
97
97
  // Decorators apply bottom-up, so a higher @UsePipe's pipes go first, in the order they read.
98
98
  const existing = Reflect.getOwnMetadata(META.methodPipes, target, propertyKey) ?? [];
99
99
  Reflect.defineMetadata(META.methodPipes, [
@@ -1,4 +1,4 @@
1
- var version = "4.2.1";
1
+ var version = "4.2.3";
2
2
  var engines = {
3
3
  node: ">=22.13"
4
4
  };
@@ -1,34 +1,33 @@
1
- import { CATALOGS, placeholderNames } from '../common/translator.js';
1
+ import { isPluralShape, CATALOGS, placeholderNames } from '../common/translator.js';
2
2
  import { MEOCORD_MESSAGES } from '../common/meocord-messages.js';
3
3
 
4
- const PLURAL_CATEGORIES = new Set([
5
- 'zero',
6
- 'one',
7
- 'two',
8
- 'few',
9
- 'many',
10
- 'other'
11
- ]);
12
- /** A plural: an object whose every key is a plural category, as the translator's types read one; any other is a group. */ const isPlural = (value)=>typeof value === 'object' && value !== null && Object.keys(value).length > 0 && Object.keys(value).every((key)=>PLURAL_CATEGORIES.has(key));
13
- /** Every message key of a catalog, and whether it is a plural. */ function leaves(catalog, prefix = '') {
4
+ /**
5
+ * Every message key of a catalog, and whether it is a plural. `reference` decides whether an object is a plural or a
6
+ * group, as the translator reads a translation by the default catalog's shapes; where it lacks a key, the object's own
7
+ * keys decide.
8
+ */ function leaves(catalog, prefix = '', reference = catalog) {
14
9
  return Object.entries(catalog).flatMap(([name, value])=>{
15
10
  const key = `${prefix}${name}`;
16
- if (typeof value === 'string') return [
11
+ // A null message is one where it stands, which the checks below read as missing
12
+ if (typeof value === 'string' || value === null) return [
17
13
  {
18
14
  key,
19
15
  plural: false
20
16
  }
21
17
  ];
22
- if (isPlural(value)) return [
18
+ const shape = typeof reference === 'object' && reference !== null && Object.hasOwn(reference, name) ? reference[name] : undefined;
19
+ const plural = typeof shape === 'object' && shape !== null ? isPluralShape(shape) : isPluralShape(value);
20
+ if (plural) return [
23
21
  {
24
22
  key,
25
23
  plural: true
26
24
  }
27
25
  ];
28
- return leaves(value, `${key}.`);
26
+ return leaves(value, `${key}.`, shape);
29
27
  });
30
28
  }
31
29
  /** A translation's text, which an empty string isn't: the translator reads `''` as missing, as tools export one untranslated. */ const isText = (value)=>typeof value === 'string' && value !== '';
30
+ /** A message the translator reads as missing: none, `null` or `''`, as tools export one untranslated. */ const isMissing = (value)=>value === undefined || value === null || value === '';
32
31
  /** The value at a key's path, through own properties only, so `constructor` or `toString` is not read off a prototype. */ function at(catalog, key) {
33
32
  return key.split('.').reduce((current, part)=>typeof current === 'object' && current !== null && Object.hasOwn(current, part) ? current[part] : undefined, catalog);
34
33
  }
@@ -43,7 +42,7 @@ const isMeoCordKey = (key)=>key.startsWith('meocord.');
43
42
  /** A message as a report shows it: the text, or a plural's `other` form. */ const shown = (message)=>typeof message === 'string' ? message : String(message.other);
44
43
  /** The `{params}` a translation uses that the message it translates doesn't take; a plural's `{count}` is taken. */ function strayParams(translation, original) {
45
44
  const taken = placeholders(original);
46
- if (isPlural(original)) taken.add('count');
45
+ if (isPluralShape(original)) taken.add('count');
47
46
  return [
48
47
  ...placeholders(translation)
49
48
  ].filter((name)=>!taken.has(name));
@@ -53,11 +52,13 @@ const isMeoCordKey = (key)=>key.startsWith('meocord.');
53
52
  *
54
53
  * Use it in a test for a team that wants no fallback in production. It reports the messages a locale lacks, the
55
54
  * messages the default catalog doesn't have, the plural forms a language needs but a plural lacks, such as `few` for
56
- * Russian, a translation of another shape than the default's, a text for a plural or a plural for a text, and each
57
- * `{param}` a translation uses that the message it translates doesn't take. An empty translation, `''`, counts as
58
- * lacking, as the translator reads it as missing. It reads the catalogs' own strings, so it checks the params of a
59
- * catalog from a plain variable or a JSON file too, which the compiler can't. An object counts as a plural only when
60
- * its every key is a plural category, so a group with an `other` key is a group.
55
+ * Russian, a translation of another shape than the default's, a text for a plural or a group, or a plural for a text, and each
56
+ * `{param}` a translation uses that the message it translates doesn't take. An empty translation, `''` or `null`, counts
57
+ * as lacking, as the translator reads it as missing. It reads the catalogs' own strings, so it checks the params of a
58
+ * catalog from a plain variable or a JSON file too, which the compiler can't. An object in the default catalog counts as
59
+ * a plural only when its every key is a plural category, so a group with an `other` key is a group, and a translation
60
+ * is read by the default catalog's shapes, as the translator reads it: one that keeps only a group's `other` message
61
+ * lacks the group's other messages.
61
62
  *
62
63
  * @remarks
63
64
  * MeoCord's own texts, a catalog's `meocord` group, fall back to English by design, so only a key MeoCord lacks, and
@@ -104,12 +105,12 @@ const isMeoCordKey = (key)=>key.startsWith('meocord.');
104
105
  const categories = new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
105
106
  for (const { key, plural } of reference){
106
107
  const message = at(catalog, key);
107
- if (message === undefined || message === '') {
108
+ if (isMissing(message)) {
108
109
  problems.push(`missing ${key}`);
109
110
  continue;
110
111
  }
111
112
  // A translation of another shape than the default's can't be read as it: a text for a plural, or the reverse
112
- if (plural !== isPlural(message) || !plural && typeof message !== 'string') {
113
+ if (plural !== isPluralShape(message) || !plural && typeof message !== 'string') {
113
114
  problems.push(`${key} should be a ${plural ? 'plural' : 'text'}, as the default is`);
114
115
  continue;
115
116
  }
@@ -122,9 +123,11 @@ const isMeoCordKey = (key)=>key.startsWith('meocord.');
122
123
  for (const name of stray)problems.push(`${key} takes no {${name}}; the default is "${shown(original)}"`);
123
124
  }
124
125
  if (options.meocord && !locale.startsWith('en-')) {
125
- for (const key of MEOCORD_KEYS)if (at(catalog, key) === undefined || at(catalog, key) === '') problems.push(`missing ${key}`);
126
+ for (const key of MEOCORD_KEYS)if (isMissing(at(catalog, key))) problems.push(`missing ${key}`);
126
127
  }
127
- for (const { key } of leaves(catalog)){
128
+ for (const { key } of leaves(catalog, '', defaultCatalog)){
129
+ // A message left out, as a null one is, that the default has is reported missing above, and is nothing extra
130
+ if (isMissing(at(catalog, key)) && at(defaultCatalog, key) !== undefined) continue;
128
131
  if (isMeoCordKey(key)) {
129
132
  if (!MEOCORD_KEYS.has(key)) problems.push(`${key} is not one of MeoCord's texts`);
130
133
  else {
@@ -132,7 +135,11 @@ const isMeoCordKey = (key)=>key.startsWith('meocord.');
132
135
  const stray = strayParams(at(catalog, key), english);
133
136
  for (const name of stray)problems.push(`${key} takes no {${name}}: MeoCord's English is "${shown(english)}"`);
134
137
  }
135
- } else if (!referenceKeys.has(key)) problems.push(`${key} is not in the default catalog`);
138
+ } else if (!referenceKeys.has(key)) {
139
+ // A text where the default has a group: the key is the default's, in another shape
140
+ const original = at(defaultCatalog, key);
141
+ problems.push(typeof original === 'object' && original !== null ? `${key} should be a group, as the default is` : `${key} is not in the default catalog`);
142
+ }
136
143
  }
137
144
  if (problems.length > 0) gaps.push(` ${locale}: ${problems.join('; ')}`);
138
145
  }
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { CooldownStore } from '../common/cooldown-store.js';
2
+ import { isDefaultBatchMethod } from '../common/cooldown-store.js';
3
3
 
4
4
  /** Long enough for a store over the network to answer each case, short enough that the suite takes seconds. */ const CASE_TIMEOUT_MS = 10000;
5
5
  const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
@@ -408,10 +408,10 @@ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
408
408
  });
409
409
  }
410
410
  /** Whether a store overrides `consumeMany`, as a store that counts a batch at once does. */ function overridesConsumeMany(store) {
411
- return store.consumeMany !== CooldownStore.prototype.consumeMany;
411
+ return !isDefaultBatchMethod(store.consumeMany);
412
412
  }
413
413
  /** Whether a store overrides `peekMany`, as a store that can check a batch without recording it does. */ function overridesPeekMany(store) {
414
- return store.peekMany !== CooldownStore.prototype.peekMany;
414
+ return !isDefaultBatchMethod(store.peekMany);
415
415
  }
416
416
 
417
417
  export { testCooldownStore };