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.
- package/CHANGELOG.md +188 -0
- package/dist/cjs/_shared/{controller.decorator-XKMlcUZz.cjs → controller.decorator-hp5xdJU4.cjs} +550 -189
- package/dist/cjs/_shared/{startup-roots-CgfPQrNl.cjs → event-sources-DzqTODXg.cjs} +274 -88
- package/dist/cjs/_shared/{providers-BZgJyfZS.cjs → providers-BBvbnmLP.cjs} +837 -295
- package/dist/cjs/_shared/{sharded-cooldown-store-CsJJyCZd.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-BUAR1WYG.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +435 -182
- package/dist/cjs/decorator/index.cjs +116 -121
- package/dist/cjs/testing/index.cjs +1923 -808
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +32 -10
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +27 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +92 -66
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +6 -6
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +54 -32
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +83 -54
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +37 -15
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +4 -2
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +39 -4
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/stage-scope.js +2 -1
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +31 -30
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +37 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +4 -4
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +32 -25
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -335
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +217 -91
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +864 -124
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +8 -4
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +33 -5
- package/dist/types/common/index.d.cts +17 -10
- package/dist/types/common/index.d.ts +17 -10
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +158 -61
- package/dist/types/testing/index.d.ts +158 -61
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
import { getMessageHandlers } from '../decorator/controller.decorator.js';
|
|
2
2
|
import { routeSpecificity } from './route-specificity.js';
|
|
3
|
-
import { isKnownParamType, BUILT_IN_TYPES, isGuildType, fitsParamType } from './message-params.js';
|
|
4
|
-
import { isSpace, splitFlagWords, splitWords, restFrom } from './message-words.js';
|
|
3
|
+
import { isKnownParamType, BUILT_IN_TYPES, isGuildType, effectiveScope, fitsParamType } from './message-params.js';
|
|
4
|
+
import { isSpace, splitFlagWords, literalFlags, splitWords, restFrom, isLiteralFlag } from './message-words.js';
|
|
5
|
+
import { setOwn, shadowInherited } from '../util/value.util.js';
|
|
5
6
|
import { startupError } from '../util/refusal.util.js';
|
|
7
|
+
import { warnOnce } from '../common/deprecation.js';
|
|
8
|
+
import { Logger } from '../common/logger.js';
|
|
6
9
|
|
|
10
|
+
const logger = new Logger('MeoCord');
|
|
7
11
|
/** Whether a route's scope lets it run where the message was sent. */ const fitsScope = (scope, inGuild)=>inGuild === undefined || scope === 'any' || scope === 'guild' === inGuild;
|
|
8
12
|
const PARAM = /^\{(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\.\.\.)?(\?)?\}$/;
|
|
9
13
|
const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
14
|
+
/**
|
|
15
|
+
* A whole brace pair that PARAM or FLAG refused, read loosely to tell its name from its type: anything up to `:` or `}`,
|
|
16
|
+
* then the marks a param ends with, `...` and `?`, wherever they were written.
|
|
17
|
+
*/ const LOOSE_FLAG = /^\{--([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
|
|
18
|
+
const LOOSE_PARAM = /^\{([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
|
|
19
|
+
/** A name or type with marks after it, as one written out of order carries them. */ const MARKED = /^(.*?)((?:\.\.\.|\?)+)$/;
|
|
20
|
+
/** A param's name, and a flag's, which also starts with a letter, as a message could not give it otherwise. */ const PARAM_NAME = /^\w+$/;
|
|
21
|
+
const FLAG_NAME = /^[A-Za-z]\w*$/;
|
|
22
|
+
/** A param as its pattern writes it, which the grammar spells only one way. */ const writtenParam = (token)=>`{${token.param}${token.type ? `:${token.type}` : ''}${token.rest ? '...' : ''}${token.optional ? '?' : ''}}`;
|
|
23
|
+
/** A flag as its pattern writes it. */ const writtenFlag = (flag)=>`{--${flag.flag}${flag.type ? `:${flag.type}` : ''}${flag.optional ? '?' : ''}}`;
|
|
24
|
+
/**
|
|
25
|
+
* Why a word with a brace in it is no param or flag: text around its braces, or a whole pair whose name, type or marks
|
|
26
|
+
* the syntax doesn't take. Each names the rule broken, with the word mended as an example.
|
|
27
|
+
*/ function notAParam(word) {
|
|
28
|
+
const flag = LOOSE_FLAG.exec(word);
|
|
29
|
+
if (flag) {
|
|
30
|
+
const [, name, type, rest] = flag;
|
|
31
|
+
if (rest || word.includes('...')) {
|
|
32
|
+
return `"${word}" is not a flag: a flag doesn't take the rest of the message; a param does, such as {${mended(unmarked(name), 'param')}...}.`;
|
|
33
|
+
}
|
|
34
|
+
if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
|
|
35
|
+
const typed = type === undefined ? '' : `:${unmarked(type)}`;
|
|
36
|
+
return `"${word}" is not a flag: its ? goes last, as in {--${unmarked(name)}${typed}?}.`;
|
|
37
|
+
}
|
|
38
|
+
if (!FLAG_NAME.test(name)) {
|
|
39
|
+
return `"${word}" is not a flag: a flag's name starts with a letter, then letters, digits or _, such as {--${mended(name, 'flag')}}.`;
|
|
40
|
+
}
|
|
41
|
+
return `"${word}" is not a flag: a type is a name, or words to choose from joined by |, such as {--${name}:on|off}.`;
|
|
42
|
+
}
|
|
43
|
+
const param = LOOSE_PARAM.exec(word);
|
|
44
|
+
if (param) {
|
|
45
|
+
const [, name, type] = param;
|
|
46
|
+
if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
|
|
47
|
+
const typed = type === undefined ? '' : `:${unmarked(type)}`;
|
|
48
|
+
const ordered = `{${unmarked(name)}${typed}${word.includes('...') ? '...' : ''}${word.includes('?') ? '?' : ''}}`;
|
|
49
|
+
return `"${word}" is not a param: its marks go last, ... then ?, as in ${ordered}.`;
|
|
50
|
+
}
|
|
51
|
+
if (!PARAM_NAME.test(name)) return `"${word}" is not a param: a param's name is ASCII letters, digits and _, such as {${mended(name, 'param')}}.`;
|
|
52
|
+
return `"${word}" is not a param: a type is a name, or words to choose from joined by |, such as {${name}:on|off}.`;
|
|
53
|
+
}
|
|
54
|
+
return `"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`;
|
|
55
|
+
}
|
|
56
|
+
/** A name or type without the marks written after it, `...` and `?`. */ const unmarked = (text)=>MARKED.exec(text)?.[1] ?? text;
|
|
57
|
+
/** A name with each character the syntax doesn't take as `_`, and a flag's led by a letter, for an example. */ function mended(name, kind) {
|
|
58
|
+
const word = name.replace(/\W/g, '_') || kind;
|
|
59
|
+
return kind === 'flag' && !/^[A-Za-z]/.test(word) ? `flag${word}` : word;
|
|
60
|
+
}
|
|
10
61
|
/**
|
|
11
62
|
* Reads a pattern into its words and flags. Throws for a pattern of flags alone, a param that is not a whole word, a
|
|
12
63
|
* name given twice, a rest param that is not last, a word after an optional param that is not optional too, an untyped
|
|
@@ -42,9 +93,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
42
93
|
words.forEach((word, index)=>{
|
|
43
94
|
const match = PARAM.exec(word);
|
|
44
95
|
if (!match) {
|
|
45
|
-
if (/[{}]/.test(word))
|
|
46
|
-
throw new Error(`"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`);
|
|
47
|
-
}
|
|
96
|
+
if (/[{}]/.test(word)) throw new Error(notAParam(word));
|
|
48
97
|
tokens.push({
|
|
49
98
|
literal: word
|
|
50
99
|
});
|
|
@@ -67,7 +116,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
67
116
|
const next = tokens[index + 1];
|
|
68
117
|
if (!('param' in token) || !token.optional || !next) return;
|
|
69
118
|
if (!('param' in next) || !next.optional) {
|
|
70
|
-
throw new Error(
|
|
119
|
+
throw new Error(`${writtenParam(token)} is optional, so only optional params may follow it; "${'param' in next ? writtenParam(next) : next.literal}" is not.`);
|
|
71
120
|
}
|
|
72
121
|
if (token.type === undefined || token.type === 'string') {
|
|
73
122
|
throw new Error(`{${token.param}${token.type ? ':string' : ''}?} comes before another optional param, so it needs a type that tells its words ` + `from the next param's, such as {${token.param}:int?}: as text, it would take every word.`);
|
|
@@ -80,10 +129,15 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
80
129
|
rest: params.some((token)=>token.rest),
|
|
81
130
|
optional: params.some((token)=>token.optional)
|
|
82
131
|
});
|
|
132
|
+
const inheritedNames = [
|
|
133
|
+
...params.map((token)=>token.param),
|
|
134
|
+
...flags.map((flag)=>flag.flag)
|
|
135
|
+
].filter((name)=>name in Object.prototype);
|
|
83
136
|
return {
|
|
84
137
|
tokens,
|
|
85
138
|
flags,
|
|
86
|
-
specificity
|
|
139
|
+
specificity,
|
|
140
|
+
inheritedNames
|
|
87
141
|
};
|
|
88
142
|
}
|
|
89
143
|
/** A prefix setting as a list, where `''` stands for none. */ function prefixList(prefix) {
|
|
@@ -134,7 +188,20 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
134
188
|
...mention
|
|
135
189
|
]);
|
|
136
190
|
}
|
|
137
|
-
/**
|
|
191
|
+
/** Where two routes of equal rank and shape part: the one with a required param where the other has an optional one comes first. */ function compareOptional(a, b) {
|
|
192
|
+
for(let i = 0; i < Math.min(a.length, b.length); i++){
|
|
193
|
+
const [aToken, bToken] = [
|
|
194
|
+
a[i],
|
|
195
|
+
b[i]
|
|
196
|
+
];
|
|
197
|
+
if ('param' in aToken && 'param' in bToken && aToken.optional !== bToken.optional) return aToken.optional ? 1 : -1;
|
|
198
|
+
}
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Whether two routes accept some of the same messages: a start they share, and words that match alike. `'optional'`
|
|
203
|
+
* when they do only because a required param and an optional one, in one position, both take a word there.
|
|
204
|
+
*/ function sameMessages(a, b, options) {
|
|
138
205
|
// A case-insensitive word matches every message the case-sensitive one does
|
|
139
206
|
const exact = a.caseSensitive && b.caseSensitive;
|
|
140
207
|
const key = (start)=>typeof start === 'string' && !exact ? start.toLowerCase() : start;
|
|
@@ -152,13 +219,55 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
152
219
|
].some((start)=>theirs.has(key(start)));
|
|
153
220
|
});
|
|
154
221
|
if (!startAlike || a.tokens.length !== b.tokens.length) return false;
|
|
155
|
-
|
|
222
|
+
let byOptional = false;
|
|
223
|
+
const alike = a.tokens.every((token, i)=>{
|
|
156
224
|
const other = b.tokens[i];
|
|
157
225
|
if ('literal' in token) {
|
|
158
226
|
return 'literal' in other && (exact ? token.literal === other.literal : token.literal.toLowerCase() === other.literal.toLowerCase());
|
|
159
227
|
}
|
|
160
|
-
|
|
228
|
+
if (!('param' in other) || token.rest !== other.rest) return false;
|
|
229
|
+
if (token.optional !== other.optional) byOptional = true;
|
|
230
|
+
return true;
|
|
161
231
|
});
|
|
232
|
+
return alike && (byOptional ? 'optional' : true);
|
|
233
|
+
}
|
|
234
|
+
/** What a static empty prefix list does now, which a prefix function's `[]` doesn't: take messages as they are. */ const EMPTY_PREFIX_LIST = "`[]` takes messages as they are, with no prefix; write `''` for that";
|
|
235
|
+
/**
|
|
236
|
+
* The warnings for message handlers a bot starts with today though a message can't reach them as written, each once,
|
|
237
|
+
* for the app or the handler: a static empty prefix list, which takes messages as they are, where a prefix function's
|
|
238
|
+
* `[]` starts no command; and two flags of a command that reads flags in any case whose names differ only in case, the
|
|
239
|
+
* second of which is never set. The next major version (5.0) changes the first and refuses the second.
|
|
240
|
+
*/ function messageRouteWarnings(routes, options) {
|
|
241
|
+
const warnings = [];
|
|
242
|
+
if (Array.isArray(options.prefix) && options.prefix.length === 0) {
|
|
243
|
+
warnings.push(`@MeoCord({ messages: { prefix: [] } }): ${EMPTY_PREFIX_LIST}. In the next major version (5.0), \`[]\` starts no command, ` + "as a prefix function's `[]` does.");
|
|
244
|
+
}
|
|
245
|
+
const seen = new Map();
|
|
246
|
+
for (const route of routes){
|
|
247
|
+
// A handler's aliases share its options and flags, so each is read once; one method can carry several handlers
|
|
248
|
+
const pattern = route.aliasOf ?? route.pattern;
|
|
249
|
+
const methods = seen.get(route.controllerClass) ?? seen.set(route.controllerClass, new Map()).get(route.controllerClass);
|
|
250
|
+
const patterns = methods.get(route.method) ?? methods.set(route.method, new Set()).get(route.method);
|
|
251
|
+
if (patterns.has(pattern)) continue;
|
|
252
|
+
patterns.add(pattern);
|
|
253
|
+
const handler = `${route.controllerClass.name}.${route.method}: @MessageHandler('${pattern}'`;
|
|
254
|
+
const own = getMessageHandlers(route.controllerClass.prototype).find((candidate)=>candidate.method === route.method && candidate.pattern === pattern)?.options;
|
|
255
|
+
if (Array.isArray(own?.prefix) && own.prefix.length === 0) {
|
|
256
|
+
warnings.push(`${handler}, { prefix: [] }): ${EMPTY_PREFIX_LIST}, or \`false\` for no prefix and no mention. In the next major ` + 'version (5.0), `[]` starts no command.');
|
|
257
|
+
}
|
|
258
|
+
if (route.caseSensitive) continue;
|
|
259
|
+
const first = new Map();
|
|
260
|
+
for (const flag of route.flags){
|
|
261
|
+
const same = first.get(flag.flag.toLowerCase());
|
|
262
|
+
if (!same) {
|
|
263
|
+
first.set(flag.flag.toLowerCase(), flag);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
warnings.push(`${handler}): ${writtenFlag(same)} and ${writtenFlag(flag)} differ only in case, and this command reads flags in any ` + `case, so --${flag.flag} always sets ${writtenFlag(same)} and ${writtenFlag(flag)} is never set. Rename one, or set ` + 'caseSensitive: true. The next major version (5.0) refuses this.');
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return warnings;
|
|
162
271
|
}
|
|
163
272
|
/**
|
|
164
273
|
* Every patterned message handler of the given controllers, most specific first, read from metadata
|
|
@@ -227,14 +336,21 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
227
336
|
});
|
|
228
337
|
}
|
|
229
338
|
}
|
|
230
|
-
routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
|
|
339
|
+
routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || compareOptional(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
|
|
231
340
|
for(let i = 0; i < routes.length; i++){
|
|
232
341
|
for(let j = i + 1; j < routes.length; j++){
|
|
233
342
|
const [a, b] = [
|
|
234
343
|
routes[i],
|
|
235
344
|
routes[j]
|
|
236
345
|
];
|
|
237
|
-
|
|
346
|
+
const same = sameMessages(a, b, options);
|
|
347
|
+
if (!same) continue;
|
|
348
|
+
// Ranked by their params, so the same one answers whatever the order; 5.0 refuses these as it does the rest
|
|
349
|
+
if (same === 'optional') {
|
|
350
|
+
if (a.controllerClass === b.controllerClass && a.method === b.method) continue;
|
|
351
|
+
warnOnce(logger, `${a.controllerClass.name}.${a.method}: ${describeRoute(a)} and ${describeRoute(b)} in ${b.controllerClass.name}.${b.method} can ` + `take the same messages, as one takes a word where the other has an optional param; ${a.controllerClass.name}.${a.method} ` + 'answers those. Change one pattern, or give one its own prefix. The next major version (5.0) stops the bot at startup for these.');
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
238
354
|
// One handler under two spellings, such as 'hello' and 'Hello', is one route, its pattern's over an alias's
|
|
239
355
|
if (a.controllerClass === b.controllerClass && a.method === b.method) {
|
|
240
356
|
if (a.aliasOf && !b.aliasOf) routes[i] = b;
|
|
@@ -279,7 +395,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
279
395
|
if (scope !== 'dm') return;
|
|
280
396
|
const param = tokens.find((token)=>'param' in token && token.type !== undefined && isGuildType(token.type));
|
|
281
397
|
const flag = flags.find((candidate)=>candidate.type !== undefined && isGuildType(candidate.type));
|
|
282
|
-
const shown = param ?
|
|
398
|
+
const shown = param ? writtenParam(param) : flag && writtenFlag(flag);
|
|
283
399
|
if (shown) throw new Error(`scope is 'dm', but ${shown} is found only in a server.`);
|
|
284
400
|
}
|
|
285
401
|
/** Refuses a handler's `mention` other than `'only'`. */ function assertMention({ mention }) {
|
|
@@ -391,11 +507,11 @@ const trieNode = ()=>({
|
|
|
391
507
|
for(let t = 0; t < tail.length && w < words.length; t++){
|
|
392
508
|
const token = tail[t];
|
|
393
509
|
if (token.rest) {
|
|
394
|
-
params
|
|
510
|
+
setOwn(params, token.param, restFrom(text, words[w].start, cuts));
|
|
395
511
|
return params;
|
|
396
512
|
}
|
|
397
513
|
if (t < tail.length - 1 && !fitsParamType(token.type, words[w].value, caseSensitive)) continue;
|
|
398
|
-
params
|
|
514
|
+
setOwn(params, token.param, words[w++].value);
|
|
399
515
|
}
|
|
400
516
|
return w === words.length ? params : undefined;
|
|
401
517
|
}
|
|
@@ -440,9 +556,12 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
440
556
|
return;
|
|
441
557
|
}
|
|
442
558
|
if ('literal' in token) {
|
|
559
|
+
var _node1;
|
|
443
560
|
const key = wordKey(token.literal, route.caseSensitive);
|
|
444
561
|
let next = node.words.get(key);
|
|
445
562
|
if (!next) node.words.set(key, next = trieNode());
|
|
563
|
+
// Such a word typed bare is taken out as a flag before the words are walked, so it is matched as one too
|
|
564
|
+
if (route.flags.length > 0 && token.literal.startsWith('--')) ((_node1 = node).flagWords ?? (_node1.flagWords = new Map())).set(key, next);
|
|
446
565
|
node = next;
|
|
447
566
|
continue;
|
|
448
567
|
}
|
|
@@ -461,6 +580,8 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
461
580
|
];
|
|
462
581
|
for (const group of all){
|
|
463
582
|
const flagged = group.flagged;
|
|
583
|
+
// A pattern's own leading `--` word stays reachable there: given bare, it is taken out as a flag, so the message
|
|
584
|
+
// seems to start with one
|
|
464
585
|
if (flagged) group.flaggedAfterFlag = {
|
|
465
586
|
...flagged,
|
|
466
587
|
words: new Map()
|
|
@@ -501,7 +622,16 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
501
622
|
if (first === undefined || node.param || node.rests.length > 0 || node.tails.length > 0 || node.ends.length > 0) return true;
|
|
502
623
|
return node.words.has(first);
|
|
503
624
|
}
|
|
504
|
-
/**
|
|
625
|
+
/**
|
|
626
|
+
* The ranks of every route the words reach from `node`. A pattern's own `--` word is reached by that word, or by a flag
|
|
627
|
+
* of its name given bare in its place, which takes no word.
|
|
628
|
+
*/ function reach(node, words, i, text, cuts, caseSensitive, flags, found) {
|
|
629
|
+
if (node.flagWords) {
|
|
630
|
+
for (const flag of flags){
|
|
631
|
+
const next = flag.at === i && flag.value === undefined ? node.flagWords.get(wordKey(`--${flag.name}`, caseSensitive)) : undefined;
|
|
632
|
+
if (next) reach(next, words, i, text, cuts, caseSensitive, flags, found);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
505
635
|
for (const tail of node.tails)if (assignTail(tail.tokens, words, i, text, cuts, caseSensitive)) found.push(tail.rank);
|
|
506
636
|
if (i === words.length) {
|
|
507
637
|
for (const rank of node.ends)found.push(rank);
|
|
@@ -509,22 +639,34 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
509
639
|
}
|
|
510
640
|
for (const rank of node.rests)found.push(rank);
|
|
511
641
|
const next = node.words.get(wordKey(words[i].value, caseSensitive));
|
|
512
|
-
if (next) reach(next, words, i + 1, text, cuts, caseSensitive, found);
|
|
513
|
-
if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, found);
|
|
642
|
+
if (next) reach(next, words, i + 1, text, cuts, caseSensitive, flags, found);
|
|
643
|
+
if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, flags, found);
|
|
514
644
|
}
|
|
515
|
-
/**
|
|
645
|
+
/**
|
|
646
|
+
* The params a route's words capture, given words its pattern is known to match. A pattern's own `--` word that the
|
|
647
|
+
* message gave as a flag, one of `taken`, takes no word.
|
|
648
|
+
*/ function paramsOf(route, words, text, cuts, taken) {
|
|
516
649
|
const tail = tailStart(route.tokens);
|
|
517
|
-
const params =
|
|
518
|
-
|
|
650
|
+
const params = {};
|
|
651
|
+
let left = taken;
|
|
652
|
+
let w = 0;
|
|
653
|
+
for(let i = 0; i < (tail === -1 ? route.tokens.length : tail) && w < words.length; i++){
|
|
519
654
|
const token = route.tokens[i];
|
|
520
|
-
if ('literal' in token)
|
|
655
|
+
if ('literal' in token) {
|
|
656
|
+
if (left > 0 && token.literal.startsWith('--') && wordKey(words[w].value, route.caseSensitive) !== wordKey(token.literal, route.caseSensitive)) left--;
|
|
657
|
+
else w++;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
521
660
|
if (token.rest) {
|
|
522
|
-
params
|
|
523
|
-
|
|
661
|
+
setOwn(params, token.param, restFrom(text, words[w].start, cuts));
|
|
662
|
+
return params;
|
|
524
663
|
}
|
|
525
|
-
params
|
|
664
|
+
setOwn(params, token.param, words[w++].value);
|
|
526
665
|
}
|
|
527
|
-
return params
|
|
666
|
+
return tail === -1 ? params : {
|
|
667
|
+
...params,
|
|
668
|
+
...assignTail(route.tokens.slice(tail), words, w, text, cuts, route.caseSensitive)
|
|
669
|
+
};
|
|
528
670
|
}
|
|
529
671
|
/**
|
|
530
672
|
* The text after the start a message used for the group's routes, or `undefined` when it used none: a mention
|
|
@@ -577,7 +719,7 @@ const indexes = new WeakMap();
|
|
|
577
719
|
const found = [];
|
|
578
720
|
if (mayEnter(group.root, first)) {
|
|
579
721
|
const words = wordsOf(rest);
|
|
580
|
-
reach(group.root, words, 0, rest, NONE, group.caseSensitive, found);
|
|
722
|
+
reach(group.root, words, 0, rest, NONE, group.caseSensitive, NONE, found);
|
|
581
723
|
for (const rank of found){
|
|
582
724
|
if (fitsScope(routes[rank].scope, starts.inGuild)) {
|
|
583
725
|
if (!best || rank < best.rank) best = {
|
|
@@ -606,7 +748,7 @@ const indexes = new WeakMap();
|
|
|
606
748
|
};
|
|
607
749
|
found.length = 0;
|
|
608
750
|
const from = scanned.words[0]?.start === 0 ? flagged : group.flaggedAfterFlag;
|
|
609
|
-
reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, found);
|
|
751
|
+
reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, scanned.flags, found);
|
|
610
752
|
for (const rank of found){
|
|
611
753
|
const candidate = {
|
|
612
754
|
rank,
|
|
@@ -623,11 +765,16 @@ const indexes = new WeakMap();
|
|
|
623
765
|
const chosen = best ?? outside;
|
|
624
766
|
if (!chosen) return undefined;
|
|
625
767
|
const route = routes[chosen.rank];
|
|
626
|
-
|
|
627
|
-
|
|
768
|
+
// The flags the pattern's own `--` words took are those words, and set none of its flags
|
|
769
|
+
const taken = literalFlags(route.tokens, route.caseSensitive, chosen.words, chosen.flags);
|
|
770
|
+
const params = paramsOf(route, chosen.words, chosen.rest, chosen.cuts, taken.size);
|
|
771
|
+
chosen.flags.forEach(({ name, value }, index)=>{
|
|
772
|
+
if (taken.has(index)) return;
|
|
628
773
|
const declared = route.flags.find((flag)=>wordKey(flag.flag, route.caseSensitive) === wordKey(name, route.caseSensitive));
|
|
629
|
-
if (declared) params
|
|
630
|
-
}
|
|
774
|
+
if (declared) setOwn(params, declared.flag, value ?? '');
|
|
775
|
+
});
|
|
776
|
+
// A param or flag left out reads as left out, not as what Object.prototype has under its name
|
|
777
|
+
if (route.inheritedNames.length > 0) shadowInherited(params, route.inheritedNames);
|
|
631
778
|
return {
|
|
632
779
|
route,
|
|
633
780
|
params,
|
|
@@ -657,19 +804,28 @@ const indexes = new WeakMap();
|
|
|
657
804
|
if (first !== undefined && !group.commands.has(first)) continue;
|
|
658
805
|
const plain = splitWords(rest);
|
|
659
806
|
// A route with flags counts the words left once the message's flags are taken out
|
|
660
|
-
const
|
|
807
|
+
const scanned = mayEnter(group.flagged, first) && rest.includes('--') ? splitFlagWords(rest) : {
|
|
808
|
+
words: plain,
|
|
809
|
+
flags: NONE
|
|
810
|
+
};
|
|
661
811
|
const candidates = group.commands.get(wordKey(plain[0]?.value ?? '', group.caseSensitive)) ?? [];
|
|
662
812
|
for (const rank of candidates){
|
|
663
813
|
if (best && rank >= best.rank) break;
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
|
|
814
|
+
const flagged = routes[rank].flags.length > 0;
|
|
815
|
+
const words = flagged ? scanned.words : plain;
|
|
816
|
+
const flags = flagged ? scanned.flags : NONE;
|
|
817
|
+
const taken = commandWordsTaken(commandWordsOf(routes[rank].tokens), words, flags, group.caseSensitive);
|
|
818
|
+
if (taken !== -1) {
|
|
667
819
|
const named = {
|
|
668
820
|
rank,
|
|
669
821
|
start: text.slice(0, text.length - rest.length),
|
|
670
|
-
given:
|
|
822
|
+
given: {
|
|
823
|
+
words,
|
|
824
|
+
flags
|
|
825
|
+
}
|
|
671
826
|
};
|
|
672
|
-
|
|
827
|
+
// Where it works as dispatch answers it, so a DM's usage comes from a route that works there
|
|
828
|
+
if (fitsScope(effectiveScope(routes[rank]), starts.inGuild)) {
|
|
673
829
|
best = named;
|
|
674
830
|
break;
|
|
675
831
|
}
|
|
@@ -684,6 +840,17 @@ const indexes = new WeakMap();
|
|
|
684
840
|
given: chosen.given
|
|
685
841
|
};
|
|
686
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* How many of a message's words a route's command words take, or -1 when the message names another command. A
|
|
845
|
+
* command's own `--` word may be given bare as a flag in its place, and takes no word.
|
|
846
|
+
*/ function commandWordsTaken(command, words, flags, caseSensitive) {
|
|
847
|
+
let w = 0;
|
|
848
|
+
for (const word of command){
|
|
849
|
+
if (words[w] && wordKey(words[w].value, caseSensitive) === wordKey(word, caseSensitive)) w++;
|
|
850
|
+
else if (!flags.some((flag)=>isLiteralFlag(flag, word, w, caseSensitive))) return -1;
|
|
851
|
+
}
|
|
852
|
+
return w;
|
|
853
|
+
}
|
|
687
854
|
/**
|
|
688
855
|
* The subcommands a message names the parent of, when no pattern matches it and it names no command: the routes whose
|
|
689
856
|
* command words begin with the most of the message's words, short of all of them, as `!config` does for `config set …`
|
|
@@ -726,11 +893,18 @@ const indexes = new WeakMap();
|
|
|
726
893
|
}
|
|
727
894
|
return found.length > 0 ? found : undefined;
|
|
728
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Whether a message has text a handler could take; dispatch runs no handler for one without. Content that isn't a
|
|
898
|
+
* string, as a mock's placeholder without strict mocks, is no text.
|
|
899
|
+
*/ function hasMessageText(content) {
|
|
900
|
+
return typeof content === 'string' && content.trim() !== '';
|
|
901
|
+
}
|
|
729
902
|
/**
|
|
730
903
|
* What a test calling a handler with a message alone passes as its params, as dispatch builds them: `undefined` for a
|
|
731
|
-
* listener, `{}` for a message
|
|
732
|
-
* the command without fitting it, the route, start and `given`, the
|
|
733
|
-
* dispatch would give the message to this handler among
|
|
904
|
+
* listener, `{}` for a message whose content isn't a string, as a mock's without strict mocks, else what the pattern
|
|
905
|
+
* captures with the route and start; for one naming the command without fitting it, the route, start and `given`, the
|
|
906
|
+
* message's words and flags. Either only when dispatch would give the message to this handler among
|
|
907
|
+
* `controllers`; otherwise why it does not reach the handler, a message with no text included.
|
|
734
908
|
*/ async function messageParamsFor(controllerClass, methodName, message, options, controllers = [
|
|
735
909
|
controllerClass
|
|
736
910
|
]) {
|
|
@@ -742,6 +916,13 @@ const indexes = new WeakMap();
|
|
|
742
916
|
if (typeof message.content !== 'string') return {
|
|
743
917
|
params: {}
|
|
744
918
|
};
|
|
919
|
+
const handler = `${controllerClass.name}.${methodName}`;
|
|
920
|
+
const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
|
|
921
|
+
if (!hasMessageText(message.content)) {
|
|
922
|
+
return {
|
|
923
|
+
mismatch: `message has no text, so dispatch runs no handler for it: give the mock the content ${handler}'s pattern '${pattern}' takes.`
|
|
924
|
+
};
|
|
925
|
+
}
|
|
745
926
|
const botId = message.client?.user?.id;
|
|
746
927
|
const all = buildMessageRoutes(controllers, options);
|
|
747
928
|
const starts = await messageStartsFor(all, options, message, typeof botId === 'string' ? botId : undefined);
|
|
@@ -750,7 +931,7 @@ const indexes = new WeakMap();
|
|
|
750
931
|
if (target && (target.route.controllerClass !== controllerClass || target.route.method !== methodName)) {
|
|
751
932
|
const other = `${target.route.controllerClass.name}.${target.route.method}`;
|
|
752
933
|
return {
|
|
753
|
-
mismatch: `message '${message.content}' does not reach ${
|
|
934
|
+
mismatch: `message '${message.content}' does not reach ${handler}: dispatch runs ${other}.`
|
|
754
935
|
};
|
|
755
936
|
}
|
|
756
937
|
if (target && 'params' in target) return {
|
|
@@ -764,10 +945,9 @@ const indexes = new WeakMap();
|
|
|
764
945
|
start: target.start,
|
|
765
946
|
given: target.given
|
|
766
947
|
};
|
|
767
|
-
const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
|
|
768
948
|
return {
|
|
769
|
-
mismatch: `message '${message.content}' does not match ${
|
|
949
|
+
mismatch: `message '${message.content}' does not match ${handler}'s pattern '${pattern}'.`
|
|
770
950
|
};
|
|
771
951
|
}
|
|
772
952
|
|
|
773
|
-
export { afterStart, buildMessageRoutes, commandWordsOf, fitsScope, matchMessageCommand, matchMessageRoute, matchMessageSubcommands, messageParamsFor, messageStarts, messageStartsFor, parseMessagePattern, staticMessageStarts, usesAppPrefix };
|
|
953
|
+
export { afterStart, buildMessageRoutes, commandWordsOf, fitsScope, hasMessageText, matchMessageCommand, matchMessageRoute, matchMessageSubcommands, messageParamsFor, messageRouteWarnings, messageStarts, messageStartsFor, parseMessagePattern, staticMessageStarts, usesAppPrefix };
|
|
@@ -109,7 +109,8 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
|
|
|
109
109
|
}
|
|
110
110
|
flags.push({
|
|
111
111
|
name,
|
|
112
|
-
value
|
|
112
|
+
value,
|
|
113
|
+
at: words.length
|
|
113
114
|
});
|
|
114
115
|
cuts.push(start, j);
|
|
115
116
|
i = j;
|
|
@@ -144,5 +145,32 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
|
|
|
144
145
|
}
|
|
145
146
|
return (rest + text.slice(from)).trimEnd();
|
|
146
147
|
}
|
|
148
|
+
const caseKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase();
|
|
149
|
+
/** Whether a flag, given bare in place `at` among the other words, is a pattern's own `--` word `literal`. */ const isLiteralFlag = (flag, literal, at, caseSensitive)=>flag.at === at && flag.value === undefined && caseKey(`--${flag.name}`, caseSensitive) === caseKey(literal, caseSensitive);
|
|
150
|
+
/**
|
|
151
|
+
* The flags a pattern's own `--` words take from a message, as indexes into `flags`: where the pattern has such a word,
|
|
152
|
+
* a flag of that name given bare in that place among the message's other words is the word, not a flag. A word typed in
|
|
153
|
+
* quotes is matched as any word is, and is taken first.
|
|
154
|
+
*/ function literalFlags(tokens, caseSensitive, words, flags) {
|
|
155
|
+
const taken = new Set();
|
|
156
|
+
if (flags.length === 0) return taken;
|
|
157
|
+
let w = 0;
|
|
158
|
+
for (const token of tokens){
|
|
159
|
+
if (!('literal' in token)) {
|
|
160
|
+
// Literal words come before the optional and rest params, which a pattern ends with
|
|
161
|
+
if (token.rest || token.optional) break;
|
|
162
|
+
w++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (w < words.length && caseKey(words[w].value, caseSensitive) === caseKey(token.literal, caseSensitive)) {
|
|
166
|
+
w++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const index = flags.findIndex((flag, i)=>!taken.has(i) && isLiteralFlag(flag, token.literal, w, caseSensitive));
|
|
170
|
+
if (index === -1) break;
|
|
171
|
+
taken.add(index);
|
|
172
|
+
}
|
|
173
|
+
return taken;
|
|
174
|
+
}
|
|
147
175
|
|
|
148
|
-
export { isSpace, restFrom, splitFlagWords, splitWords };
|
|
176
|
+
export { caseKey, isLiteralFlag, isSpace, literalFlags, restFrom, splitFlagWords, splitWords };
|
|
@@ -5,6 +5,7 @@ import { bindShared } from './interceptor-runner.js';
|
|
|
5
5
|
import { appliesTo } from './stage-scope.js';
|
|
6
6
|
import { respond } from '../common/response/response-state.js';
|
|
7
7
|
import { startupError } from '../util/refusal.util.js';
|
|
8
|
+
import { isConstructor } from '../util/value.util.js';
|
|
8
9
|
import { META } from '../util/metadata-keys.js';
|
|
9
10
|
|
|
10
11
|
/** Where a container keeps the observers its calls are reported to, in order. */ const OBSERVERS = Symbol('observers');
|
|
@@ -12,7 +13,7 @@ const logger = new Logger('Observer');
|
|
|
12
13
|
/** Reports each entry that is not an `@Observer` class, naming where it was listed. */ function assertObservers(where, observers) {
|
|
13
14
|
for (const observer of observers){
|
|
14
15
|
if (typeof observer !== 'function' || !Reflect.getMetadata(META.observerClass, observer)) {
|
|
15
|
-
const name = typeof observer === 'function' ?
|
|
16
|
+
const name = isConstructor(observer) ? observer.name || 'an anonymous class' : typeof observer === 'function' ? 'a function' : String(observer);
|
|
16
17
|
startupError(new Error(`${where} takes classes decorated with @Observer(), not ${name}.`));
|
|
17
18
|
continue;
|
|
18
19
|
}
|
|
@@ -68,8 +69,9 @@ function observersOf(container) {
|
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* Where an interaction's answer stands: a repliable one's `ResponseState`, and an autocomplete answered
|
|
71
|
-
* or not. `undefined` for anything that is not an interaction.
|
|
72
|
+
* or not. `undefined` for anything that is not an interaction's call, an event carrying one included.
|
|
72
73
|
*/ function responsePhaseOf(context) {
|
|
74
|
+
if (context.getType() === 'event') return undefined;
|
|
73
75
|
const interaction = context.getInteraction();
|
|
74
76
|
if (!interaction) return undefined;
|
|
75
77
|
if (interaction.isAutocomplete()) return interaction.responded ? 'replied' : 'unanswered';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { stopRequests } from '../util/stop-request.util.js';
|
|
2
|
+
import { shared } from '../util/shared-state.util.js';
|
|
3
|
+
|
|
4
|
+
/** Where both builds of this version keep the process's state, as {@link shared} names it. */ const PROCESS_STATE = 'processState';
|
|
5
|
+
/**
|
|
6
|
+
* What the process's apps share, kept by both builds of this version, so a process that loads both has one pair of
|
|
7
|
+
* signal listeners and one shutdown, which stops the apps either build started, one exit code a failed start set, and
|
|
8
|
+
* one end of a failed shard.
|
|
9
|
+
*/ const processState = shared(PROCESS_STATE, ()=>({
|
|
10
|
+
/** Closes each started app: runs its shutdown hooks and destroys its client, resolving `false` on failure. */ runningApps: new Set(),
|
|
11
|
+
stopRequest: stopRequests(),
|
|
12
|
+
signalHandlersInstalled: false,
|
|
13
|
+
shuttingDown: undefined,
|
|
14
|
+
/** Whether a failed start, of a provider's factory or the login, set the exit code, so a later login knows to clear it. */ startFailureSetExitCode: false,
|
|
15
|
+
/** Whether `meocord start --dev` was told the bot could not log in, so a later login tells it the bot is online. */ toldDevRunnerLoginFailed: false,
|
|
16
|
+
/** Whether this shard is already ending, so a failure reported twice, as one going on uncaught is, ends it once. */ shardEnding: false
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
export { processState };
|