@gramio/composer 0.2.0 → 0.3.1
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/README.md +271 -6
- package/dist/index.cjs +95 -11
- package/dist/index.d.cts +256 -18
- package/dist/index.d.ts +256 -18
- package/dist/index.js +94 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,13 +119,21 @@ app.guard(
|
|
|
119
119
|
);
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
**Without handlers
|
|
122
|
+
**Without handlers (gate mode)** — if false, stop this composer's remaining middleware. When a type predicate is used, downstream context is narrowed:
|
|
123
123
|
|
|
124
124
|
```ts
|
|
125
125
|
// Only admin can reach subsequent middleware
|
|
126
126
|
app
|
|
127
127
|
.guard((ctx) => ctx.role === "admin")
|
|
128
128
|
.use(adminOnlyHandler); // skipped if not admin
|
|
129
|
+
|
|
130
|
+
// Type predicate narrows context for all downstream handlers
|
|
131
|
+
app
|
|
132
|
+
.guard((ctx): ctx is Ctx & { text: string } => "text" in ctx)
|
|
133
|
+
.on("message", (ctx, next) => {
|
|
134
|
+
ctx.text; // string (narrowed by guard)
|
|
135
|
+
return next();
|
|
136
|
+
});
|
|
129
137
|
```
|
|
130
138
|
|
|
131
139
|
When used inside an `extend()`-ed plugin, the guard stops the plugin's chain but the parent continues:
|
|
@@ -274,6 +282,32 @@ app.extend(limit100); // applied
|
|
|
274
282
|
app.extend(limit200); // applied (different seed)
|
|
275
283
|
```
|
|
276
284
|
|
|
285
|
+
> [!WARNING]
|
|
286
|
+
> **Dedup removes middleware at registration time — not at runtime per-request.**
|
|
287
|
+
>
|
|
288
|
+
> If a shared plugin (e.g. `withUser`) is extended only inside sub-composers, its
|
|
289
|
+
> `derive` runs inside each sub-composer's isolation group. When dedup removes the
|
|
290
|
+
> derive from the second sub-composer, `ctx.user` set in the first group is **not
|
|
291
|
+
> visible** in the second — TypeScript types are correct, runtime value is `undefined`.
|
|
292
|
+
>
|
|
293
|
+
> **Fix:** extend the shared composer at the level where its data must be available,
|
|
294
|
+
> and let sub-composers extend it only for type safety (dedup prevents double execution).
|
|
295
|
+
>
|
|
296
|
+
> ```ts
|
|
297
|
+
> // ✅ correct — withUser runs once on the real ctx, both routers see ctx.user
|
|
298
|
+
> app
|
|
299
|
+
> .extend(withUser) // derive on real ctx
|
|
300
|
+
> .extend(adminRouter) // withUser inside → deduped (skipped)
|
|
301
|
+
> .extend(chatRouter); // withUser inside → deduped (skipped)
|
|
302
|
+
>
|
|
303
|
+
> // ⚠️ risky — works only if routers are mutually exclusive (one handles per update)
|
|
304
|
+
> app
|
|
305
|
+
> .extend(adminRouter) // withUser runs in isolation group
|
|
306
|
+
> .extend(chatRouter); // withUser deduped → chatHandlers can't see ctx.user
|
|
307
|
+
> ```
|
|
308
|
+
>
|
|
309
|
+
> See [`docs/layered-composers.md`](./docs/layered-composers.md) for the full breakdown.
|
|
310
|
+
|
|
277
311
|
### `createComposer(config)` — Event System
|
|
278
312
|
|
|
279
313
|
Factory that creates a Composer class with `.on()` event discrimination.
|
|
@@ -305,6 +339,74 @@ const app = new Composer()
|
|
|
305
339
|
});
|
|
306
340
|
```
|
|
307
341
|
|
|
342
|
+
#### `.on()` with filters
|
|
343
|
+
|
|
344
|
+
**Filter-only (no event name)** — the 2-arg `on(filter, handler)` applies the filter to **all** events without discriminating by event type:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
// Type-narrowing filter — handler sees narrowed context across all compatible events
|
|
348
|
+
app.on(
|
|
349
|
+
(ctx): ctx is { text: string } => typeof (ctx as any).text === "string",
|
|
350
|
+
(ctx, next) => {
|
|
351
|
+
ctx.text; // string (narrowed)
|
|
352
|
+
return next();
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Boolean filter — no narrowing, handler gets base TOut
|
|
357
|
+
app.on(
|
|
358
|
+
(ctx) => ctx.updateType === "message",
|
|
359
|
+
(ctx, next) => {
|
|
360
|
+
// no type narrowing, full context
|
|
361
|
+
return next();
|
|
362
|
+
},
|
|
363
|
+
);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Event + filter** — the 3-arg `on(event, filter, handler)` supports both type-narrowing predicates and boolean filters:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// Type-narrowing filter — handler sees narrowed context
|
|
370
|
+
app.on(
|
|
371
|
+
"message",
|
|
372
|
+
(ctx): ctx is MessageCtx & { text: string } => ctx.text !== undefined,
|
|
373
|
+
(ctx, next) => {
|
|
374
|
+
ctx.text; // string (narrowed, not string | undefined)
|
|
375
|
+
return next();
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Boolean filter — no narrowing, handler sees full context
|
|
380
|
+
app.on(
|
|
381
|
+
"message",
|
|
382
|
+
(ctx) => ctx.text !== undefined,
|
|
383
|
+
(ctx, next) => {
|
|
384
|
+
ctx.text; // string | undefined (not narrowed)
|
|
385
|
+
return next();
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The 2-arg `on()` also accepts an optional `Patch` generic for context extensions (useful in custom methods):
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
app.on<"message", { args: string }>("message", (ctx, next) => {
|
|
394
|
+
ctx.args; // string — type-safe without casting
|
|
395
|
+
return next();
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`.use()` supports the same `Patch` generic — handy when a custom method enriches context before delegating to a user-provided handler:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
app.use<{ args: string }>((ctx, next) => {
|
|
403
|
+
ctx.args; // string — type-safe without casting
|
|
404
|
+
return next();
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`Patch` does not change `TOut` — it is a local escape hatch for one handler, not a permanent context extension. Use `derive()` when you want the addition to propagate to all downstream middleware.
|
|
409
|
+
|
|
308
410
|
#### `types` + `eventTypes()` — phantom type inference
|
|
309
411
|
|
|
310
412
|
TypeScript cannot partially infer type arguments, so when you need both `TEventMap` and `TMethods` inferred together, use the `types` phantom field with the `eventTypes()` helper instead of explicit type parameters:
|
|
@@ -322,7 +424,9 @@ const { Composer } = createComposer({
|
|
|
322
424
|
|
|
323
425
|
#### `methods` — custom prototype methods
|
|
324
426
|
|
|
325
|
-
Inject framework-specific DX sugar directly onto the Composer prototype
|
|
427
|
+
Inject framework-specific DX sugar directly onto the Composer prototype. Custom methods are preserved through **all** method chains (`on`, `use`, `derive`, `extend`, etc.). A runtime conflict check throws if a method name collides with a built-in.
|
|
428
|
+
|
|
429
|
+
**Simple methods** (no access to accumulated derives) work directly in `methods`:
|
|
326
430
|
|
|
327
431
|
```ts
|
|
328
432
|
const { Composer } = createComposer({
|
|
@@ -343,13 +447,92 @@ const { Composer } = createComposer({
|
|
|
343
447
|
},
|
|
344
448
|
},
|
|
345
449
|
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Methods that receive accumulated derives** require two steps. TypeScript cannot infer generic method signatures when `TMethods` is nested inside the return type of `createComposer`, so use `defineComposerMethods` first — its return type is directly `TMethods`, which preserves generic signatures. Then pass `typeof methods` as the 3rd type argument.
|
|
453
|
+
|
|
454
|
+
Use `ComposerLike<TThis>` as an F-bounded constraint so that `this.on(...)` is fully typed and returns `TThis` — no casts needed.
|
|
455
|
+
|
|
456
|
+
**Pattern: `this: TThis` + `ContextOf<TThis>` — zero annotation at the call site:**
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
import { createComposer, defineComposerMethods, eventTypes } from "@gramio/composer";
|
|
460
|
+
import type { ComposerLike, ContextOf, Middleware } from "@gramio/composer";
|
|
461
|
+
|
|
462
|
+
const methods = defineComposerMethods({
|
|
463
|
+
command<TThis extends ComposerLike<TThis>>(
|
|
464
|
+
this: TThis,
|
|
465
|
+
name: string,
|
|
466
|
+
handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
467
|
+
): TThis {
|
|
468
|
+
const inner: Middleware<MessageCtx & ContextOf<TThis>> = (ctx, next) => {
|
|
469
|
+
if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
470
|
+
return next();
|
|
471
|
+
};
|
|
472
|
+
return this.on("message", inner);
|
|
473
|
+
},
|
|
474
|
+
});
|
|
346
475
|
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
476
|
+
const { Composer } = createComposer<BaseCtx, { message: MessageCtx }, typeof methods>({
|
|
477
|
+
discriminator: (ctx) => ctx.updateType,
|
|
478
|
+
methods,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Derives flow into the handler automatically — no annotation needed:
|
|
482
|
+
new Composer()
|
|
483
|
+
.derive(() => ({ user: { id: 1, name: "Alice" } }))
|
|
484
|
+
.command("start", (ctx, next) => {
|
|
485
|
+
ctx.user.id; // ✅ typed — inferred from ContextOf<TThis>
|
|
486
|
+
ctx.text; // ✅ string | undefined — from MessageCtx
|
|
487
|
+
return next();
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
#### `ContextOf<T>` — extract the current context type
|
|
492
|
+
|
|
493
|
+
Extracts `TOut` (the fully accumulated context type after all `.derive()` and `.decorate()` calls) from a Composer or EventComposer instance type.
|
|
494
|
+
|
|
495
|
+
**Naming a plugin's context type for reuse:**
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
import type { ContextOf } from "@gramio/composer";
|
|
499
|
+
|
|
500
|
+
const withUser = new Composer()
|
|
501
|
+
.derive(async (ctx) => ({
|
|
502
|
+
user: await db.getUser(ctx.userId),
|
|
503
|
+
}));
|
|
504
|
+
|
|
505
|
+
// Extract the enriched context — no manual conditional type needed
|
|
506
|
+
export type WithUser = ContextOf<typeof withUser>;
|
|
507
|
+
// WithUser = { userId: string } & { user: User }
|
|
508
|
+
|
|
509
|
+
// Use it in standalone functions, other plugins, or type assertions:
|
|
510
|
+
function requireAdmin(ctx: WithUser) {
|
|
511
|
+
if (!ctx.user.isAdmin) throw new Error("Forbidden");
|
|
512
|
+
}
|
|
350
513
|
```
|
|
351
514
|
|
|
352
|
-
|
|
515
|
+
**In a custom method signature** — `ContextOf<TThis>` captures all derives accumulated at the call site:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
command<TThis extends ComposerLike<TThis>>(
|
|
519
|
+
this: TThis,
|
|
520
|
+
handler: Middleware<ContextOf<TThis>>,
|
|
521
|
+
): TThis
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
#### `ComposerLike<T>` — minimal structural type for `this` constraints
|
|
525
|
+
|
|
526
|
+
A minimal interface `{ on(event: any, handler: any): T }` used as an F-bounded constraint on `TThis`. Makes `this.on(...)` fully typed and return `TThis` without casts.
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
import type { ComposerLike } from "@gramio/composer";
|
|
530
|
+
|
|
531
|
+
// Constraint in a custom method:
|
|
532
|
+
command<TThis extends ComposerLike<TThis>>(this: TThis, ...): TThis {
|
|
533
|
+
return this.on("message", inner); // returns TThis — no `as TThis` needed
|
|
534
|
+
}
|
|
535
|
+
```
|
|
353
536
|
|
|
354
537
|
### `EventQueue`
|
|
355
538
|
|
|
@@ -370,6 +553,88 @@ queue.addBatch(events);
|
|
|
370
553
|
await queue.stop(5000);
|
|
371
554
|
```
|
|
372
555
|
|
|
556
|
+
### Macro System
|
|
557
|
+
|
|
558
|
+
Declarative handler options inspired by [Elysia macros](https://elysiajs.com/patterns/macro.md). Register reusable behaviors (guards, rate-limits, auth) as macros, then activate them via an options object on handler methods.
|
|
559
|
+
|
|
560
|
+
#### `macro(name, definition)` / `macro(definitions)`
|
|
561
|
+
|
|
562
|
+
Register macros on a Composer or EventComposer instance.
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
import { Composer, type MacroDef, type ContextCallback } from "@gramio/composer";
|
|
566
|
+
|
|
567
|
+
// Boolean shorthand macro — plain hooks object
|
|
568
|
+
const onlyAdmin: MacroDef<void, {}> = {
|
|
569
|
+
preHandler: (ctx, next) => {
|
|
570
|
+
if (ctx.role !== "admin") return; // stops chain
|
|
571
|
+
return next();
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// Parameterized macro — function receiving options
|
|
576
|
+
interface ThrottleOptions {
|
|
577
|
+
limit: number;
|
|
578
|
+
window?: number;
|
|
579
|
+
onLimit?: ContextCallback; // ← replaced with actual ctx type at call site
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const throttle: MacroDef<ThrottleOptions, {}> = (opts) => ({
|
|
583
|
+
preHandler: createThrottleMiddleware(opts),
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Macro with derive — enriches handler context
|
|
587
|
+
interface AuthDerived { user: { id: number; name: string } }
|
|
588
|
+
|
|
589
|
+
const auth: MacroDef<void, AuthDerived> = {
|
|
590
|
+
derive: async (ctx) => {
|
|
591
|
+
const user = await getUser(ctx.token);
|
|
592
|
+
if (!user) return; // void = stop chain (guard behavior)
|
|
593
|
+
return { user };
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const app = new Composer()
|
|
598
|
+
.macro("onlyAdmin", onlyAdmin)
|
|
599
|
+
.macro({ throttle, auth }); // batch registration
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
#### `buildFromOptions(macros, options, handler)`
|
|
603
|
+
|
|
604
|
+
Runtime helper that composes a handler with macro hooks. Used internally by frameworks to wire macros into handler methods.
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
import { buildFromOptions } from "@gramio/composer";
|
|
608
|
+
|
|
609
|
+
// Execution order:
|
|
610
|
+
// 1. options.preHandler[] (explicit guards — user controls order)
|
|
611
|
+
// 2. Per-macro in options property order:
|
|
612
|
+
// a. macro.preHandler (guard middleware)
|
|
613
|
+
// b. macro.derive (context enrichment; void = stop chain)
|
|
614
|
+
// 3. Main handler
|
|
615
|
+
const composed = buildFromOptions(
|
|
616
|
+
app["~"].macros,
|
|
617
|
+
{ auth: true, throttle: { limit: 5 } },
|
|
618
|
+
mainHandler,
|
|
619
|
+
);
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Macro Types
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
import type {
|
|
626
|
+
MacroDef, // Macro definition (function or hooks object)
|
|
627
|
+
MacroHooks, // { preHandler?, derive? }
|
|
628
|
+
MacroDefinitions, // Record<string, MacroDef<any, any>>
|
|
629
|
+
ContextCallback, // Marker type for context-aware callbacks
|
|
630
|
+
WithCtx, // Recursively replaces ContextCallback with real ctx type
|
|
631
|
+
HandlerOptions, // Builds the options parameter type for handler methods
|
|
632
|
+
DeriveFromOptions, // Collects derive types from activated macros
|
|
633
|
+
MacroOptionType, // Extracts option type from MacroDef
|
|
634
|
+
MacroDeriveType, // Extracts derive return type from MacroDef
|
|
635
|
+
} from "@gramio/composer";
|
|
636
|
+
```
|
|
637
|
+
|
|
373
638
|
### Utilities
|
|
374
639
|
|
|
375
640
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -79,7 +79,10 @@ class Composer {
|
|
|
79
79
|
name: void 0,
|
|
80
80
|
seed: void 0,
|
|
81
81
|
errorsDefinitions: {},
|
|
82
|
-
tracer: void 0
|
|
82
|
+
tracer: void 0,
|
|
83
|
+
macros: {},
|
|
84
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
85
|
+
Out: void 0
|
|
83
86
|
};
|
|
84
87
|
constructor(options) {
|
|
85
88
|
this["~"].name = options?.name;
|
|
@@ -88,6 +91,14 @@ class Composer {
|
|
|
88
91
|
invalidate() {
|
|
89
92
|
this["~"].compiled = null;
|
|
90
93
|
}
|
|
94
|
+
macro(nameOrDefs, definition) {
|
|
95
|
+
if (typeof nameOrDefs === "string") {
|
|
96
|
+
this["~"].macros[nameOrDefs] = definition;
|
|
97
|
+
} else {
|
|
98
|
+
Object.assign(this["~"].macros, nameOrDefs);
|
|
99
|
+
}
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
91
102
|
decorate(values, options) {
|
|
92
103
|
const mw = (ctx, next) => {
|
|
93
104
|
Object.assign(ctx, values);
|
|
@@ -99,6 +110,7 @@ class Composer {
|
|
|
99
110
|
this.invalidate();
|
|
100
111
|
return this;
|
|
101
112
|
}
|
|
113
|
+
// biome-ignore lint/suspicious/noExplicitAny: overload implementation signature
|
|
102
114
|
use(...middleware) {
|
|
103
115
|
for (const fn of middleware) {
|
|
104
116
|
this["~"].middlewares.push({ fn, scope: "local", type: "use", name: fn.name || void 0 });
|
|
@@ -296,8 +308,14 @@ class Composer {
|
|
|
296
308
|
fn(group);
|
|
297
309
|
const chain = compose(group["~"].middlewares.map((m) => m.fn));
|
|
298
310
|
const mw = async (ctx, next) => {
|
|
299
|
-
const
|
|
300
|
-
|
|
311
|
+
const preKeys = new Set(Object.keys(ctx));
|
|
312
|
+
const snapshot = {};
|
|
313
|
+
for (const key of preKeys) snapshot[key] = ctx[key];
|
|
314
|
+
await chain(ctx, noopNext);
|
|
315
|
+
for (const key of Object.keys(ctx)) {
|
|
316
|
+
if (!preKeys.has(key)) delete ctx[key];
|
|
317
|
+
}
|
|
318
|
+
Object.assign(ctx, snapshot);
|
|
301
319
|
return next();
|
|
302
320
|
};
|
|
303
321
|
nameMiddleware(mw, "group");
|
|
@@ -316,6 +334,7 @@ class Composer {
|
|
|
316
334
|
this["~"].extended.add(key);
|
|
317
335
|
}
|
|
318
336
|
Object.assign(this["~"].errorsDefinitions, other["~"].errorsDefinitions);
|
|
337
|
+
Object.assign(this["~"].macros, other["~"].macros);
|
|
319
338
|
this["~"].onErrors.push(...other["~"].onErrors);
|
|
320
339
|
const pluginName = other["~"].name;
|
|
321
340
|
const isNew = (m) => {
|
|
@@ -331,8 +350,14 @@ class Composer {
|
|
|
331
350
|
if (localMws.length > 0) {
|
|
332
351
|
const chain = compose(localMws.map((m) => m.fn));
|
|
333
352
|
const isolated = async (ctx, next) => {
|
|
334
|
-
const
|
|
335
|
-
|
|
353
|
+
const preKeys = new Set(Object.keys(ctx));
|
|
354
|
+
const snapshot = {};
|
|
355
|
+
for (const key of preKeys) snapshot[key] = ctx[key];
|
|
356
|
+
await chain(ctx, noopNext);
|
|
357
|
+
for (const key of Object.keys(ctx)) {
|
|
358
|
+
if (!preKeys.has(key)) delete ctx[key];
|
|
359
|
+
}
|
|
360
|
+
Object.assign(ctx, snapshot);
|
|
336
361
|
return next();
|
|
337
362
|
};
|
|
338
363
|
nameMiddleware(isolated, "extend", pluginName);
|
|
@@ -475,19 +500,36 @@ class EventQueue {
|
|
|
475
500
|
}
|
|
476
501
|
}
|
|
477
502
|
|
|
503
|
+
function defineComposerMethods(methods) {
|
|
504
|
+
return methods;
|
|
505
|
+
}
|
|
478
506
|
function eventTypes() {
|
|
479
507
|
return void 0;
|
|
480
508
|
}
|
|
481
509
|
function createComposer(config) {
|
|
482
510
|
class EventComposerImpl extends Composer {
|
|
483
|
-
on(
|
|
484
|
-
|
|
511
|
+
on(eventOrFilter, filterOrHandler, handler) {
|
|
512
|
+
if (typeof eventOrFilter === "function") {
|
|
513
|
+
const filter2 = eventOrFilter;
|
|
514
|
+
const actualHandler2 = filterOrHandler;
|
|
515
|
+
const filterLabel = filter2.name || "filter";
|
|
516
|
+
const mw2 = (ctx, next) => {
|
|
517
|
+
if (filter2(ctx)) return actualHandler2(ctx, next);
|
|
518
|
+
return next();
|
|
519
|
+
};
|
|
520
|
+
nameMiddleware(mw2, "on", filterLabel);
|
|
521
|
+
this["~"].middlewares.push({ fn: mw2, scope: "local", type: "on", name: filterLabel });
|
|
522
|
+
this.invalidate();
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
const events = Array.isArray(eventOrFilter) ? eventOrFilter : [eventOrFilter];
|
|
485
526
|
const eventLabel = events.join("|");
|
|
527
|
+
const actualHandler = handler ?? filterOrHandler;
|
|
528
|
+
const filter = handler ? filterOrHandler : void 0;
|
|
486
529
|
const mw = (ctx, next) => {
|
|
487
|
-
if (events.includes(config.discriminator(ctx)))
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return next();
|
|
530
|
+
if (!events.includes(config.discriminator(ctx))) return next();
|
|
531
|
+
if (filter && !filter(ctx)) return next();
|
|
532
|
+
return actualHandler(ctx, next);
|
|
491
533
|
};
|
|
492
534
|
nameMiddleware(mw, "on", eventLabel);
|
|
493
535
|
this["~"].middlewares.push({ fn: mw, scope: "local", type: "on", name: eventLabel });
|
|
@@ -535,10 +577,52 @@ function createComposer(config) {
|
|
|
535
577
|
};
|
|
536
578
|
}
|
|
537
579
|
|
|
580
|
+
function buildFromOptions(macros, options, handler) {
|
|
581
|
+
if (!options) return handler;
|
|
582
|
+
const chain = [];
|
|
583
|
+
const preHandler = options.preHandler;
|
|
584
|
+
if (preHandler) {
|
|
585
|
+
if (Array.isArray(preHandler)) {
|
|
586
|
+
chain.push(...preHandler);
|
|
587
|
+
} else {
|
|
588
|
+
chain.push(preHandler);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const key of Object.keys(options)) {
|
|
592
|
+
if (key === "preHandler") continue;
|
|
593
|
+
const value = options[key];
|
|
594
|
+
if (value === false || value == null) continue;
|
|
595
|
+
const def = macros[key];
|
|
596
|
+
if (!def) continue;
|
|
597
|
+
const hooks = typeof def === "function" ? def(value === true ? void 0 : value) : def;
|
|
598
|
+
if (hooks.preHandler) {
|
|
599
|
+
if (Array.isArray(hooks.preHandler)) {
|
|
600
|
+
chain.push(...hooks.preHandler);
|
|
601
|
+
} else {
|
|
602
|
+
chain.push(hooks.preHandler);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (hooks.derive) {
|
|
606
|
+
const deriveFn = hooks.derive;
|
|
607
|
+
chain.push(async (ctx, next) => {
|
|
608
|
+
const derived = await deriveFn(ctx);
|
|
609
|
+
if (derived == null) return;
|
|
610
|
+
Object.assign(ctx, derived);
|
|
611
|
+
return next();
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
chain.push(handler);
|
|
616
|
+
if (chain.length === 1) return chain[0];
|
|
617
|
+
return compose(chain);
|
|
618
|
+
}
|
|
619
|
+
|
|
538
620
|
exports.Composer = Composer;
|
|
539
621
|
exports.EventQueue = EventQueue;
|
|
622
|
+
exports.buildFromOptions = buildFromOptions;
|
|
540
623
|
exports.compose = compose;
|
|
541
624
|
exports.createComposer = createComposer;
|
|
625
|
+
exports.defineComposerMethods = defineComposerMethods;
|
|
542
626
|
exports.eventTypes = eventTypes;
|
|
543
627
|
exports.noopNext = noopNext;
|
|
544
628
|
exports.skip = skip;
|