meocord 4.2.2 → 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 +130 -0
- package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
- package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
- package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
- package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +432 -181
- package/dist/cjs/decorator/index.cjs +85 -120
- package/dist/cjs/testing/index.cjs +1664 -784
- 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 +27 -5
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +17 -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 +83 -58
- 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 +4 -4
- 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 +44 -28
- 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 +80 -53
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +5 -9
- 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 +2 -1
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +28 -3
- 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/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 +27 -29
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +6 -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 +2 -2
- 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 +29 -13
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -355
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +195 -77
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +639 -113
- 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 +1 -0
- 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 +15 -4
- package/dist/types/common/index.d.cts +13 -9
- package/dist/types/common/index.d.ts +13 -9
- 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 +137 -54
- package/dist/types/testing/index.d.ts +137 -54
- 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
|
@@ -10,6 +10,7 @@ import { GuardDeniedError } from '../common/errors.js';
|
|
|
10
10
|
import { getCommandMap, getMessageHandlers, getReactionHandlers, getAutocompleteHandlers } from '../decorator/controller.decorator.js';
|
|
11
11
|
import { getEventHandlers } from '../decorator/event.decorator.js';
|
|
12
12
|
import { META } from '../util/metadata-keys.js';
|
|
13
|
+
import { wrapsMethod } from '../util/method-wrapper.util.js';
|
|
13
14
|
|
|
14
15
|
/** Whether a `@UseGuard` entry is a guard with params rather than a guard class. */ function isGuardWithParams(guard) {
|
|
15
16
|
// Entries are checked when @UseGuard or @MeoCord applies, so an object here is always { provide, params? }
|
|
@@ -259,6 +260,28 @@ const warnedShared = new WeakMap();
|
|
|
259
260
|
}
|
|
260
261
|
return undefined;
|
|
261
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The prototypes on the chain that declare `methodName` as written, base first: the handler and each override of it,
|
|
265
|
+
* looking through methods a class-level `@UseGuard` re-declared to wrap an inherited handler. A handler's method-level
|
|
266
|
+
* stages are read from each, so an override keeps its base method's, as it keeps its route.
|
|
267
|
+
*/ function stageOwners(prototype, methodName) {
|
|
268
|
+
const owners = [];
|
|
269
|
+
for(let current = prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
|
|
270
|
+
if (!Object.prototype.hasOwnProperty.call(current, methodName)) continue;
|
|
271
|
+
if (!Reflect.getOwnMetadata(META.inheritedFrom, current, methodName)) owners.push(current);
|
|
272
|
+
}
|
|
273
|
+
return owners.reverse();
|
|
274
|
+
}
|
|
275
|
+
/** The entries a method-level stage lists under `key` on each of a handler's {@link stageOwners}, base first. */ function methodStages(prototype, methodName, key) {
|
|
276
|
+
return stageOwners(prototype, methodName).flatMap((owner)=>Reflect.getOwnMetadata(key, owner, methodName) ?? []);
|
|
277
|
+
}
|
|
278
|
+
/** The value a method-level stage holds under `key` on the nearest of a handler's {@link stageOwners} that declares one. */ function nearestMethodStage(prototype, methodName, key) {
|
|
279
|
+
for (const owner of stageOwners(prototype, methodName).reverse()){
|
|
280
|
+
const value = Reflect.getOwnMetadata(key, owner, methodName);
|
|
281
|
+
if (value !== undefined) return value;
|
|
282
|
+
}
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
262
285
|
/**
|
|
263
286
|
* Caches a function of a class's prototype and one of its handlers. Handler metadata is fixed once
|
|
264
287
|
* the classes are decorated, so dispatch resolves each handler's stages once, not on every call.
|
|
@@ -310,50 +333,165 @@ const warnedShared = new WeakMap();
|
|
|
310
333
|
* The guards dispatch runs before a handler: its classes' class-level guards, outermost first, then
|
|
311
334
|
* the method's. A method that is no handler has only its own, since class guards apply to handlers.
|
|
312
335
|
*/ const handlerGuards = perHandler((prototype, methodName)=>{
|
|
313
|
-
|
|
314
|
-
if (!source) return [];
|
|
336
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
315
337
|
return [
|
|
316
338
|
...handlerMethods(prototype).has(methodName) ? stageClasses(prototype, methodName).flatMap(classLevelGuards) : [],
|
|
317
|
-
...
|
|
339
|
+
...methodStages(prototype, methodName, META.methodGuards)
|
|
318
340
|
];
|
|
319
341
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
342
|
+
/**
|
|
343
|
+
* The type a direct call to a handler runs as, which is the one dispatch gives it: `'event'` for an `@On` or `@Once`
|
|
344
|
+
* handler, as its first argument may be a message or an interaction; otherwise read from the first argument.
|
|
345
|
+
*/ const directCallType = perHandler((prototype, methodName)=>{
|
|
346
|
+
if (!getEventHandlers(prototype).some((handler)=>handler.method === methodName)) return undefined;
|
|
347
|
+
const routed = [
|
|
348
|
+
...Object.values(getCommandMap(prototype) ?? {}).flat().map((command)=>command.methodName),
|
|
349
|
+
...getMessageHandlers(prototype).map((handler)=>handler.method),
|
|
350
|
+
...getReactionHandlers(prototype).map((handler)=>handler.method),
|
|
351
|
+
...getAutocompleteHandlers(prototype).map((handler)=>handler.methodName)
|
|
352
|
+
];
|
|
353
|
+
// A method dispatch also routes as a command, message or reaction has no one type
|
|
354
|
+
return routed.includes(methodName) ? undefined : 'event';
|
|
355
|
+
});
|
|
356
|
+
/** Whether `@UseGuard` wraps the method anywhere on the chain, an override's base included. */ const isWrapped = perHandler((prototype, methodName)=>{
|
|
357
|
+
for(let current = prototype; current; current = Object.getPrototypeOf(current)){
|
|
358
|
+
if (Object.prototype.hasOwnProperty.call(current, methodName) && Reflect.getOwnMetadata(META.guardWrappers, current, methodName)) return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
323
361
|
});
|
|
324
362
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
/** Takes one pass for the wrapper being entered, if dispatch left one for this call. */ function consumeDispatchMark(first, instance, methodName) {
|
|
334
|
-
const marks = dispatched.get(markKey(first, instance));
|
|
335
|
-
const remaining = marks?.get(methodName);
|
|
336
|
-
if (!marks || !remaining) return false;
|
|
337
|
-
if (remaining > 1) marks.set(methodName, remaining - 1);
|
|
338
|
-
else marks.delete(methodName);
|
|
363
|
+
* The pass of the guarded call running. Kept per call, so a concurrent call with the same object and method runs its own
|
|
364
|
+
* guards, and closed when the call returns, so work it leaves running, such as a timer, does not carry it.
|
|
365
|
+
*/ const guardedPasses = new AsyncLocalStorage();
|
|
366
|
+
/** Takes the pass for `wrapper` when it belongs to a call whose guards have already run and has not let it through yet. */ function takeGuardedPass(wrapper, first, instance, methodName) {
|
|
367
|
+
const pass = guardedPasses.getStore();
|
|
368
|
+
if (!pass?.open || pass.instance !== instance || pass.methodName !== methodName || pass.first !== first) return false;
|
|
369
|
+
if (pass.entered.has(wrapper)) return false;
|
|
370
|
+
pass.entered.add(wrapper);
|
|
339
371
|
return true;
|
|
340
372
|
}
|
|
373
|
+
/** Runs a call whose guards have run, with its pass; `wrapper` is the one that ran them, which it has entered. */ async function runWithPass(instance, methodName, args, call, wrapper) {
|
|
374
|
+
const pass = {
|
|
375
|
+
instance,
|
|
376
|
+
methodName,
|
|
377
|
+
first: args[0],
|
|
378
|
+
entered: new Set(wrapper ? [
|
|
379
|
+
wrapper
|
|
380
|
+
] : []),
|
|
381
|
+
open: true
|
|
382
|
+
};
|
|
383
|
+
try {
|
|
384
|
+
return await guardedPasses.run(pass, call);
|
|
385
|
+
} finally{
|
|
386
|
+
pass.open = false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Adds guards to a method's class or method list, then republishes the effective list under
|
|
391
|
+
* `META.guards`, in the order they run: the bases' before the class's own, and within one class or method the
|
|
392
|
+
* latest decorator first, since it wraps outermost.
|
|
393
|
+
*/ function recordGuards(key, guards, prototype, methodName) {
|
|
394
|
+
const existing = Reflect.getOwnMetadata(key, prototype, methodName) ?? [];
|
|
395
|
+
const bases = key === META.classGuards ? Reflect.getOwnMetadata(META.baseClassGuards, prototype, methodName) ?? 0 : 0;
|
|
396
|
+
Reflect.defineMetadata(key, [
|
|
397
|
+
...existing.slice(0, bases),
|
|
398
|
+
...guards,
|
|
399
|
+
...existing.slice(bases)
|
|
400
|
+
], prototype, methodName);
|
|
401
|
+
const [classGuards, inheritedGuards] = [
|
|
402
|
+
META.classGuards,
|
|
403
|
+
META.inheritedGuards
|
|
404
|
+
].map((list)=>Reflect.getOwnMetadata(list, prototype, methodName) ?? []);
|
|
405
|
+
// The method's guards and those of the methods it overrides, base first, as dispatch runs them
|
|
406
|
+
const methodGuards = methodStages(prototype, methodName, META.methodGuards);
|
|
407
|
+
Reflect.defineMetadata(META.guards, [
|
|
408
|
+
...inheritedGuards,
|
|
409
|
+
...classGuards,
|
|
410
|
+
...methodGuards
|
|
411
|
+
], prototype, methodName);
|
|
412
|
+
}
|
|
413
|
+
/** The classes {@link guardOwnHandlersWithBaseGuards} has handled, as `@Controller` and each app's startup both call it. */ const guardedWithBases = new WeakSet();
|
|
414
|
+
/**
|
|
415
|
+
* Gives each handler a class declares itself the class-level guards of the classes it extends,
|
|
416
|
+
* as its inherited handlers have: recorded under `META.guards`, and wrapped so a direct call
|
|
417
|
+
* runs them when no other guard wraps the handler. `@Controller` calls this as it decorates, and
|
|
418
|
+
* {@link prepareDirectCalls} for every class an app runs, a service included. Once per class.
|
|
419
|
+
*/ function guardOwnHandlersWithBaseGuards(target) {
|
|
420
|
+
if (guardedWithBases.has(target)) return;
|
|
421
|
+
guardedWithBases.add(target);
|
|
422
|
+
const prototype = target.prototype;
|
|
423
|
+
for (const methodName of handlerMethods(prototype)){
|
|
424
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
|
|
425
|
+
// Inherited handlers have the chain of the class that declares them
|
|
426
|
+
if (typeof descriptor?.value !== 'function' || Reflect.getOwnMetadata(META.inheritedFrom, prototype, methodName)) continue;
|
|
427
|
+
// Base first, as dispatch runs them
|
|
428
|
+
const guards = classChain(prototype, prototype).slice(1).reverse().flatMap(classLevelGuards);
|
|
429
|
+
if (guards.length === 0) continue;
|
|
430
|
+
if (!Reflect.getOwnMetadata(META.guardWrappers, prototype, methodName)) {
|
|
431
|
+
applyGuards(descriptor, prototype, methodName);
|
|
432
|
+
Object.defineProperty(prototype, methodName, descriptor);
|
|
433
|
+
}
|
|
434
|
+
recordGuards(META.inheritedGuards, guards, prototype, methodName);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
341
437
|
/** The container that made each instance, so a direct call runs in its own app or testing module. */ const instanceContainers = new WeakMap();
|
|
342
438
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
|
|
439
|
+
* Readies direct calls to the handlers of the classes an app or testing module runs, controllers and services alike:
|
|
440
|
+
* - stamps each class with the container, and records each instance the container makes as its own, so two testing
|
|
441
|
+
* modules made from one class each run a direct call on their own instance in themselves;
|
|
442
|
+
* - gives each handler a class declares itself the class-level guards of the classes it extends, and wraps each
|
|
443
|
+
* override of a guarded handler, so a direct call to it runs the guards it inherits before its body.
|
|
444
|
+
*/ function prepareDirectCalls(container, classes) {
|
|
346
445
|
for (const cls of classes){
|
|
446
|
+
Reflect.defineMetadata(META.container, container, cls);
|
|
447
|
+
// Each class on the chain, base first, so an override above the one the app runs is wrapped too
|
|
448
|
+
for (const owner of classesOf(cls).reverse())guardOwnHandlersWithBaseGuards(owner);
|
|
449
|
+
wrapGuardedOverrides(cls.prototype);
|
|
347
450
|
container.onActivation(cls, (_context, instance)=>{
|
|
348
451
|
if (typeof instance === 'object' && instance !== null) instanceContainers.set(instance, container);
|
|
349
452
|
return instance;
|
|
350
453
|
});
|
|
351
454
|
}
|
|
352
455
|
}
|
|
456
|
+
/** `cls` and the classes it extends, innermost first. */ function classesOf(cls) {
|
|
457
|
+
const classes = [];
|
|
458
|
+
for(let current = cls.prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
|
|
459
|
+
classes.push(current.constructor);
|
|
460
|
+
}
|
|
461
|
+
return classes;
|
|
462
|
+
}
|
|
463
|
+
/** Wraps each method on the chain that overrides a handler a base method guards and is not wrapped itself. */ function wrapGuardedOverrides(prototype) {
|
|
464
|
+
for (const methodName of handlerMethods(prototype)){
|
|
465
|
+
let guardedBelow = false;
|
|
466
|
+
for (const owner of stageOwners(prototype, methodName)){
|
|
467
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, methodName);
|
|
468
|
+
if (guardedBelow && typeof descriptor?.value === 'function' && !Reflect.getOwnMetadata(META.guardWrappers, owner, methodName)) {
|
|
469
|
+
applyGuards(descriptor, owner, methodName);
|
|
470
|
+
Object.defineProperty(owner, methodName, descriptor);
|
|
471
|
+
}
|
|
472
|
+
if ((Reflect.getOwnMetadata(META.methodGuards, owner, methodName) ?? []).length > 0) guardedBelow = true;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Wraps a method so a direct call runs its guards first: the wrapper entered first runs the whole
|
|
478
|
+
* chain dispatch resolves for the handler, and lets the ones inside it through. A call from dispatch,
|
|
479
|
+
* which has already run the guards, passes through every wrapper.
|
|
480
|
+
*/ function applyGuards(descriptor, prototype, propertyKey) {
|
|
481
|
+
const originalMethod = descriptor.value;
|
|
482
|
+
// Which wrapper a call has passed, so it passes each once
|
|
483
|
+
const wrapper = {};
|
|
484
|
+
descriptor.value = wrapsMethod(async function(...args) {
|
|
485
|
+
if (takeGuardedPass(wrapper, args[0], this, propertyKey)) return originalMethod.apply(this, args);
|
|
486
|
+
return runDirectCall(this, propertyKey, args, ()=>originalMethod.apply(this, args), wrapper);
|
|
487
|
+
}, originalMethod);
|
|
488
|
+
const wrappers = Reflect.getOwnMetadata(META.guardWrappers, prototype, propertyKey) ?? 0;
|
|
489
|
+
Reflect.defineMetadata(META.guardWrappers, wrappers + 1, prototype, propertyKey);
|
|
490
|
+
}
|
|
353
491
|
/**
|
|
354
|
-
* Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves them, then
|
|
355
|
-
* call with a pass
|
|
356
|
-
*/ async function runDirectCall(instance, methodName, args, call) {
|
|
492
|
+
* Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves and types them, then
|
|
493
|
+
* the call with a pass every wrapper inside the one entered takes, so no guard runs twice.
|
|
494
|
+
*/ async function runDirectCall(instance, methodName, args, call, wrapper) {
|
|
357
495
|
const prototype = Object.getPrototypeOf(instance);
|
|
358
496
|
const controller = instance.constructor;
|
|
359
497
|
// The container that made the instance, or else the app that made the class or a class it extends: a subclass the
|
|
@@ -362,39 +500,22 @@ const wrapperCount = perHandler((prototype, methodName)=>{
|
|
|
362
500
|
if (!container) {
|
|
363
501
|
throw new Error(`${controller.name}.${methodName}: @UseGuard runs its guards through the app that made ${controller.name}, and this ` + `instance was made outside one. Inject ${controller.name}, or call it on an instance the app made.`);
|
|
364
502
|
}
|
|
503
|
+
const type = directCallType(prototype, methodName);
|
|
365
504
|
if (!await runGuards(handlerGuards(prototype, methodName), {
|
|
366
505
|
container,
|
|
367
506
|
controller,
|
|
368
507
|
methodName,
|
|
369
|
-
args
|
|
508
|
+
args,
|
|
509
|
+
type
|
|
370
510
|
})) return undefined;
|
|
371
|
-
|
|
372
|
-
if (inner <= 0) return call();
|
|
373
|
-
const key = markKey(args[0], instance);
|
|
374
|
-
let marks = dispatched.get(key);
|
|
375
|
-
if (!marks) dispatched.set(key, marks = new Map());
|
|
376
|
-
marks.set(methodName, inner);
|
|
377
|
-
try {
|
|
378
|
-
return await call();
|
|
379
|
-
} finally{
|
|
380
|
-
marks.delete(methodName);
|
|
381
|
-
}
|
|
511
|
+
return runWithPass(instance, methodName, args, call, wrapper);
|
|
382
512
|
}
|
|
383
513
|
/**
|
|
384
|
-
* Calls a handler dispatch has already guarded. Its wrappers let this one call through; the
|
|
385
|
-
*
|
|
514
|
+
* Calls a handler dispatch has already guarded. Its wrappers let this one call through; the pass
|
|
515
|
+
* closes afterwards, so a later direct call with the same (cached) object runs its guards.
|
|
386
516
|
*/ async function callGuardedHandler(instance, methodName, args) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const key = markKey(args[0], instance);
|
|
390
|
-
let marks = dispatched.get(key);
|
|
391
|
-
if (!marks) dispatched.set(key, marks = new Map());
|
|
392
|
-
marks.set(methodName, count);
|
|
393
|
-
try {
|
|
394
|
-
return await instance[methodName](...args);
|
|
395
|
-
} finally{
|
|
396
|
-
marks.delete(methodName);
|
|
397
|
-
}
|
|
517
|
+
if (!isWrapped(Object.getPrototypeOf(instance), methodName)) return instance[methodName](...args);
|
|
518
|
+
return runWithPass(instance, methodName, args, ()=>instance[methodName](...args));
|
|
398
519
|
}
|
|
399
520
|
|
|
400
|
-
export { callGuardedHandler, classChain, classLevelGuards,
|
|
521
|
+
export { applyGuards, callGuardedHandler, classChain, classLevelGuards, declaringPrototype, guardOwnHandlersWithBaseGuards, handlerGuards, handlerMethods, injectedTokens, isGuardWithParams, methodStages, nearestMethodStage, perHandler, prepareDirectCalls, recordGuards, runDirectCall, runGuards, singletonContextError, sourcePrototype, stageClasses, stageOwners, takeGuardedPass, undecoratedConstructor, untypedParameter };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ModalSubmitInteraction, ComponentType, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, ChannelSelectMenuInteraction, MentionableSelectMenuInteraction } from 'discord.js';
|
|
2
|
-
import { getCommandMap } from '../decorator/controller.decorator.js';
|
|
3
2
|
import { matchComponentRoute } from './component-routes.js';
|
|
4
|
-
import { hasCustomId, isCustomIdRouted, matchesHandler, resolveOptionParams, matchesCommandType } from '../util/interaction.util.js';
|
|
3
|
+
import { hasCustomId, isCustomIdRouted, matchesHandler, resolveOptionParams, describeInteraction, focusedOptionName, matchesCommandType } from '../util/interaction.util.js';
|
|
5
4
|
import { setOwn } from '../util/value.util.js';
|
|
6
5
|
import { commandRouteKeys, matchCommandRoute } from './command-routes.js';
|
|
6
|
+
import { matchAutocompleteRoute, buildAutocompleteRoutes } from './autocomplete-routes.js';
|
|
7
|
+
import { getCommandMap, getAutocompleteHandlers } from '../decorator/controller.decorator.js';
|
|
7
8
|
|
|
8
9
|
/** The entries of a discord.js Collection, or none for anything else, such as a test double's stub. */ const entriesOf = (collection)=>collection instanceof Map ? [
|
|
9
10
|
...collection.values()
|
|
@@ -143,5 +144,25 @@ import { commandRouteKeys, matchCommandRoute } from './command-routes.js';
|
|
|
143
144
|
if (winner) return `command '${keys[0]}' does not reach ${handler}: dispatch runs ${winner.controllerClass.name}.${winner.meta.methodName}.`;
|
|
144
145
|
return `command '${keys[0]}' does not match ${handler}'s route ${named.map(({ route })=>`'${route}'`).join(' or ')}.`;
|
|
145
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Why an autocomplete could not reach a handler, for a test calling the handler directly: the handler is checked
|
|
149
|
+
* against the one dispatch routes the autocomplete to among `controllers`, by its command path and focused option.
|
|
150
|
+
* `undefined` when it is that one, when the handler has no `@Autocomplete` route, or when the interaction is not an
|
|
151
|
+
* autocomplete or has no command name.
|
|
152
|
+
*/ function autocompleteMismatch(controllers, controller, methodName, interaction) {
|
|
153
|
+
if (!interaction.isAutocomplete() || typeof interaction.commandName !== 'string') return undefined;
|
|
154
|
+
const own = getAutocompleteHandlers(controller.prototype).filter((meta)=>meta.methodName === methodName);
|
|
155
|
+
if (own.length === 0) return undefined;
|
|
156
|
+
const winner = matchAutocompleteRoute(buildAutocompleteRoutes(controllers), interaction);
|
|
157
|
+
if (winner?.controllerClass === controller && winner.meta.methodName === methodName) return undefined;
|
|
158
|
+
const handler = `${controller.name}.${methodName}`;
|
|
159
|
+
const received = describeInteraction(interaction);
|
|
160
|
+
// A mock without a focused option reaches no option's handler, the commonest way to miss one
|
|
161
|
+
const option = focusedOptionName(interaction) === undefined ? own.find((meta)=>meta.optionName !== undefined)?.optionName : undefined;
|
|
162
|
+
const fix = option === undefined ? '' : ` Give the mock the focused option: options: createChatInputOptions({ focused: '${option}' }).`;
|
|
163
|
+
if (winner) return `${received} does not reach ${handler}: dispatch runs ${winner.controllerClass.name}.${winner.meta.methodName}.${fix}`;
|
|
164
|
+
const routes = own.map((meta)=>meta.optionName === undefined ? `'${meta.commandPath}'` : `'${meta.commandPath}' option '${meta.optionName}'`);
|
|
165
|
+
return `${received} does not match ${handler}'s route ${routes.join(' or ')}.${fix}`;
|
|
166
|
+
}
|
|
146
167
|
|
|
147
|
-
export { commandMismatch, componentRouteFor, handlerInput };
|
|
168
|
+
export { autocompleteMismatch, commandMismatch, componentRouteFor, handlerInput };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { refuse, startupError } from '../util/refusal.util.js';
|
|
2
|
+
import { refuse, startupError, collectStartupErrors, isRefusal, startupErrorsOf } from '../util/refusal.util.js';
|
|
3
3
|
import { setPresenter } from '../common/response/presenter.js';
|
|
4
4
|
import { responseOf } from '../common/response/response-state.js';
|
|
5
|
-
import { handlerDefer, nonInteractionHandler, deferMisuseError
|
|
6
|
-
import { perHandler, runGuards, callGuardedHandler,
|
|
7
|
-
import { prepareInterceptor,
|
|
5
|
+
import { handlerDefer, startDefer, nonInteractionHandler, deferMisuseError } from './defer.js';
|
|
6
|
+
import { perHandler, handlerGuards, runGuards, callGuardedHandler, isGuardWithParams } from './guard-runner.js';
|
|
7
|
+
import { prepareInterceptor, bindShared, interceptorClass, handlerInterceptors, runInterceptors } from './interceptor-runner.js';
|
|
8
8
|
import { inferContextType, HandlerExecutionContext, UnroutedExecutionContext } from '../common/execution-context.js';
|
|
9
|
-
import { prepareFilter,
|
|
10
|
-
import { handlerInputStages,
|
|
9
|
+
import { prepareFilter, filterClass, handlerFilterLevels, matchFilter, callFilter } from './filter-runner.js';
|
|
10
|
+
import { handlerInputStages, pipeClass, prepareHandlerArgs, preparePipe } from './input-runner.js';
|
|
11
11
|
import { hasObservers, notifyStart, outcomeOf, responsePhaseOf, notifyObservers } from './observer-runner.js';
|
|
12
12
|
import { handlerCooldowns, peekCooldowns, methodCooldowns } from './cooldown-runner.js';
|
|
13
13
|
import { Logger } from '../common/logger.js';
|
|
@@ -165,19 +165,35 @@ const stagesByGlobals = new WeakMap();
|
|
|
165
165
|
for (const entry of globals.filters)prepareFilter(container, entry);
|
|
166
166
|
for (const controller of controllers){
|
|
167
167
|
const prototype = controller.prototype;
|
|
168
|
-
for (const method of handlerMethods(prototype))
|
|
169
|
-
const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
|
|
170
|
-
if (kind) {
|
|
171
|
-
startupError(deferMisuseError(controller.name, method, kind));
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
|
|
175
|
-
for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
|
|
176
|
-
for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
|
|
177
|
-
}
|
|
168
|
+
for (const method of handlerMethods(prototype))prepareHandler(container, controller, method);
|
|
178
169
|
assertInputStagesOnInteractions(controller, prototype);
|
|
179
170
|
}
|
|
180
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Prepares one `@On` or `@Once` handler of a class the app reaches only as something it makes, as
|
|
174
|
+
* {@link prepareHandlerStages} prepares a controller's.
|
|
175
|
+
*
|
|
176
|
+
* @returns The errors a controller's handler would be refused with; none when the handler is ready to run.
|
|
177
|
+
*/ function prepareEventHandler(container, cls, method) {
|
|
178
|
+
try {
|
|
179
|
+
collectStartupErrors(()=>{
|
|
180
|
+
prepareHandler(container, cls, method);
|
|
181
|
+
assertInputStages(cls, cls.prototype, method, 'event');
|
|
182
|
+
});
|
|
183
|
+
return [];
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (!isRefusal(error)) throw error;
|
|
186
|
+
return startupErrorsOf(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Binds what one handler's interceptors, filters and pipes need, refusing `@Defer` where it cannot apply. */ function prepareHandler(container, controller, method) {
|
|
190
|
+
const prototype = controller.prototype;
|
|
191
|
+
const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
|
|
192
|
+
if (kind) return startupError(deferMisuseError(controller.name, method, kind));
|
|
193
|
+
for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
|
|
194
|
+
for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
|
|
195
|
+
for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
|
|
196
|
+
}
|
|
181
197
|
/** Records the app's theme for the handlers of `controllers`, once the startup checks have passed: it makes the theme cache. */ function prepareThemes(container, controllers) {
|
|
182
198
|
const globals = globalStagesOf(container);
|
|
183
199
|
configureThemes(container, globals.theme, controllers, globals.themeFor);
|
|
@@ -223,17 +239,17 @@ const stagesByGlobals = new WeakMap();
|
|
|
223
239
|
'event'
|
|
224
240
|
])
|
|
225
241
|
];
|
|
226
|
-
for (const [method, kind] of others)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
242
|
+
for (const [method, kind] of others)assertInputStages(controller, prototype, method, kind);
|
|
243
|
+
}
|
|
244
|
+
/** Refuses `@Validate` and `@UsePipe` on one handler of `kind` with no params to check, and `@Cooldown` where it never counts. */ function assertInputStages(controller, prototype, method, kind) {
|
|
245
|
+
const { schema, pipes } = handlerInputStages(prototype, method);
|
|
246
|
+
const handler = kind === 'message' ? 'a message handler without a pattern' : `${kind === 'autocomplete' || kind === 'event' ? 'an' : 'a'} ${kind} handler`;
|
|
247
|
+
if (schema || pipes.length > 0) {
|
|
248
|
+
return startupError(new Error(`${controller.name}.${method}: @Validate and @UsePipe are for interaction and patterned message handlers, ` + `whose options, customId params, modal fields and pattern params they check, and this is ${handler}.`));
|
|
249
|
+
}
|
|
250
|
+
// A controller's own @Cooldown skips these handlers; one on the method itself is a mistake.
|
|
251
|
+
if (kind !== 'message' && methodCooldowns(prototype, method).length > 0) {
|
|
252
|
+
startupError(new Error(`${controller.name}.${method}: @Cooldown is for interaction and message handlers, and this is ${handler}.`));
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
/**
|
|
@@ -481,4 +497,4 @@ const logger = new Logger('ExceptionFilter');
|
|
|
481
497
|
}, options);
|
|
482
498
|
}
|
|
483
499
|
|
|
484
|
-
export { appPresenterOf, appStages, bindAppPresenter, bindGlobalStages, classDecorators, globalStagesOf, handleUnroutedError, handlerStageClasses, handlerStages, observeUnclaimed, prepareAppPresenter, prepareHandlerStages, prepareThemes, runHandler, runInAppTheme };
|
|
500
|
+
export { appPresenterOf, appStages, bindAppPresenter, bindGlobalStages, classDecorators, globalStagesOf, handleUnroutedError, handlerStageClasses, handlerStages, observeUnclaimed, prepareAppPresenter, prepareEventHandler, prepareHandlerStages, prepareThemes, runHandler, runInAppTheme };
|
|
@@ -4,10 +4,10 @@ import { getCommandMap, getAutocompleteHandlers, getMessageHandlers, getReaction
|
|
|
4
4
|
import { getEventHandlers } from '../decorator/event.decorator.js';
|
|
5
5
|
import { CommandType } from '../enum/controller.enum.js';
|
|
6
6
|
import { buildMessageRoutes, messageStarts, afterStart, parseMessagePattern, commandWordsOf } from './message-routes.js';
|
|
7
|
-
import { computeMessageHelp, helpInvocation
|
|
7
|
+
import { computeMessageHelp, helpInvocation } from './message-help.js';
|
|
8
|
+
import { usageOf, effectiveScope } from './message-params.js';
|
|
8
9
|
import { registrationKey, SENT_AS, commandNameOf, serialise } from './command-registration.js';
|
|
9
10
|
import { textRenderer, messageLocale } from '../common/meocord-text.js';
|
|
10
|
-
import { usageOf } from './message-params.js';
|
|
11
11
|
|
|
12
12
|
const COMMAND_TYPES = new Set([
|
|
13
13
|
CommandType.SLASH,
|
|
@@ -65,6 +65,28 @@ const COMMAND_TYPES = new Set([
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
/** The message routes the app's dispatcher reads, by the app's registry, so its help and dispatch read one table. */ const dispatchedRoutes = new WeakMap();
|
|
68
|
+
/** What every entry has: its class and method, and the metadata on them. */ function entryBase(controller, method) {
|
|
69
|
+
const context = new HandlerExecutionContext({
|
|
70
|
+
controller,
|
|
71
|
+
methodName: method,
|
|
72
|
+
args: []
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
controller,
|
|
76
|
+
method,
|
|
77
|
+
get: context.get.bind(context),
|
|
78
|
+
getAll: context.getAll.bind(context)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const lateEventSources = new WeakMap();
|
|
82
|
+
// The late sources each registry's cached entries were read with, so a list made before they arrived is read again
|
|
83
|
+
const collectedWith = new WeakMap();
|
|
84
|
+
/**
|
|
85
|
+
* Has `registry` list the `@On` and `@Once` handlers that run on `sources`, the instances the app attaches them for,
|
|
86
|
+
* where they are on a class it does not list already.
|
|
87
|
+
*/ function listEventSources(registry, sources) {
|
|
88
|
+
lateEventSources.set(registry, sources);
|
|
89
|
+
}
|
|
68
90
|
/** Has `registry` work out message help from `routes`, the table the app's dispatcher routes messages with. */ function shareMessageRoutes(registry, routes) {
|
|
69
91
|
dispatchedRoutes.set(registry, routes);
|
|
70
92
|
}
|
|
@@ -105,7 +127,11 @@ const COMMAND_TYPES = new Set([
|
|
|
105
127
|
* @param filter - Narrows the list by kind, controller, or both.
|
|
106
128
|
* @returns The handlers, class by class in the order the app makes its classes, each after what it injects.
|
|
107
129
|
*/ list(filter = {}) {
|
|
108
|
-
|
|
130
|
+
const late = lateEventSources.get(this);
|
|
131
|
+
if (!this.entries || collectedWith.get(this) !== late) {
|
|
132
|
+
this.entries = this.collect();
|
|
133
|
+
collectedWith.set(this, late);
|
|
134
|
+
}
|
|
109
135
|
return this.entries.filter((entry)=>(filter.kind === undefined || entry.kind === filter.kind) && (filter.controller === undefined || entry.controller === filter.controller));
|
|
110
136
|
}
|
|
111
137
|
/**
|
|
@@ -129,8 +155,9 @@ const COMMAND_TYPES = new Set([
|
|
|
129
155
|
* }
|
|
130
156
|
* ```
|
|
131
157
|
*/ async messageHelp(message, query) {
|
|
158
|
+
// Until a dispatcher shares its table, the same one built from the same controllers, so help lists what dispatch reaches
|
|
132
159
|
const routes = dispatchedRoutes.get(this) ?? this.messageRoutes ?? (this.messageRoutes = buildMessageRoutes([
|
|
133
|
-
...this.
|
|
160
|
+
...this.controllers
|
|
134
161
|
], this.messages));
|
|
135
162
|
const starts = await messageStarts(this.messages, message, message.client?.user?.id);
|
|
136
163
|
const text = (message.content ?? '').trim();
|
|
@@ -160,19 +187,7 @@ const COMMAND_TYPES = new Set([
|
|
|
160
187
|
const entries = [];
|
|
161
188
|
for (const controller of this.classes){
|
|
162
189
|
const prototype = controller.prototype;
|
|
163
|
-
const base = (method)=>
|
|
164
|
-
const context = new HandlerExecutionContext({
|
|
165
|
-
controller,
|
|
166
|
-
methodName: method,
|
|
167
|
-
args: []
|
|
168
|
-
});
|
|
169
|
-
return {
|
|
170
|
-
controller,
|
|
171
|
-
method,
|
|
172
|
-
get: context.get.bind(context),
|
|
173
|
-
getAll: context.getAll.bind(context)
|
|
174
|
-
};
|
|
175
|
-
};
|
|
190
|
+
const base = (method)=>entryBase(controller, method);
|
|
176
191
|
for (const [name, metas] of Object.entries(getCommandMap(prototype) ?? {})){
|
|
177
192
|
for (const { methodName, type, builder } of metas){
|
|
178
193
|
if (COMMAND_TYPES.has(type)) {
|
|
@@ -235,18 +250,33 @@ const COMMAND_TYPES = new Set([
|
|
|
235
250
|
});
|
|
236
251
|
}
|
|
237
252
|
}
|
|
253
|
+
// The classes the app reaches only as something it makes, listed once each, with the handlers that run on them
|
|
254
|
+
const listed = new Set(this.classes);
|
|
255
|
+
for (const { cls, handlers } of lateEventSources.get(this) ?? []){
|
|
256
|
+
if (listed.has(cls)) continue;
|
|
257
|
+
listed.add(cls);
|
|
258
|
+
for (const { event, method, once } of handlers)entries.push({
|
|
259
|
+
...entryBase(cls, method),
|
|
260
|
+
kind: 'event',
|
|
261
|
+
name: event,
|
|
262
|
+
once
|
|
263
|
+
});
|
|
264
|
+
}
|
|
238
265
|
return entries;
|
|
239
266
|
}
|
|
240
267
|
/**
|
|
241
|
-
* @param classes - The app's classes to read handlers from. The factory fills the
|
|
242
|
-
* once the app is bound; entries are read on the first {@link list}.
|
|
268
|
+
* @param classes - The app's classes to read handlers from, controllers and services alike. The factory fills the
|
|
269
|
+
* list once the app is bound; entries are read on the first {@link list}.
|
|
243
270
|
* @param messages - The app's `messages` options, whose `caseSensitive` message entries' `matches` follows.
|
|
244
271
|
* @param translator - The app's translator, read when {@link messageHelp} words its labels; none for English.
|
|
245
|
-
|
|
272
|
+
* @param controllers - The app's controllers, the classes dispatch routes messages to, which {@link messageHelp}
|
|
273
|
+
* lists; `classes` when not given.
|
|
274
|
+
*/ constructor(classes, messages = {}, translator = ()=>undefined, controllers = classes){
|
|
246
275
|
this.classes = classes;
|
|
247
276
|
this.messages = messages;
|
|
248
277
|
this.translator = translator;
|
|
278
|
+
this.controllers = controllers;
|
|
249
279
|
}
|
|
250
280
|
}
|
|
251
281
|
|
|
252
|
-
export { HandlerRegistry, shareMessageRoutes };
|
|
282
|
+
export { HandlerRegistry, listEventSources, shareMessageRoutes };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { ValidationError } from '../common/errors.js';
|
|
3
3
|
import { prepareInterceptor } from './interceptor-runner.js';
|
|
4
|
-
import { perHandler,
|
|
4
|
+
import { perHandler, nearestMethodStage, methodStages } from './guard-runner.js';
|
|
5
5
|
import { handlerCooldowns, consumeCooldowns } from './cooldown-runner.js';
|
|
6
6
|
import { META } from '../util/metadata-keys.js';
|
|
7
7
|
|
|
@@ -15,17 +15,16 @@ function isPipeWithParams(entry) {
|
|
|
15
15
|
const asList = (entries)=>Array.isArray(entries) ? entries : [
|
|
16
16
|
entries
|
|
17
17
|
];
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const validation = Reflect.getOwnMetadata(META.methodValidation, source, methodName);
|
|
18
|
+
/**
|
|
19
|
+
* The validation and pipes that run on a handler's input: `@Validate`'s first, then `@UsePipe`'s. The `@Validate` is the
|
|
20
|
+
* handler's own, or else that of the nearest method it overrides that has one; `@UsePipe`'s are every one's, base first.
|
|
21
|
+
*/ const handlerInputStages = perHandler((prototype, methodName)=>{
|
|
22
|
+
const validation = nearestMethodStage(prototype, methodName, META.methodValidation);
|
|
24
23
|
const inline = Object.entries(validation?.pipes ?? {}).flatMap(([key, entries])=>asList(entries).map((entry)=>({
|
|
25
24
|
key,
|
|
26
25
|
entry
|
|
27
26
|
})));
|
|
28
|
-
const used =
|
|
27
|
+
const used = methodStages(prototype, methodName, META.methodPipes);
|
|
29
28
|
return {
|
|
30
29
|
schema: validation?.schema,
|
|
31
30
|
pipes: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { assertStartupChecked } from './startup-checked.js';
|
|
3
|
-
import {
|
|
3
|
+
import { injectedTokens, singletonContextError, perHandler, sourcePrototype, stageClasses, methodStages } from './guard-runner.js';
|
|
4
4
|
import { ExecutionContext } from '../common/execution-context.js';
|
|
5
5
|
import { makeInjectable } from '../util/injectable.util.js';
|
|
6
6
|
import { isAppClassToken } from './lifecycle-order.js';
|
|
@@ -24,13 +24,12 @@ function isInterceptorWithParams(entry) {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* The interceptors around a handler: each class's, base first, down to the controller it is dispatched on, then the
|
|
27
|
-
* method's.
|
|
27
|
+
* method's and its overrides', base first.
|
|
28
28
|
*/ const handlerInterceptors = perHandler((prototype, methodName)=>{
|
|
29
|
-
|
|
30
|
-
if (!source) return [];
|
|
29
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
31
30
|
return [
|
|
32
31
|
...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(META.classInterceptors, cls) ?? []),
|
|
33
|
-
...
|
|
32
|
+
...methodStages(prototype, methodName, META.methodInterceptors)
|
|
34
33
|
];
|
|
35
34
|
});
|
|
36
35
|
/** Binds `cls` and its unbound dependencies as singletons, refusing any that injects `ExecutionContext`. */ function bindShared(container, cls) {
|