@gramio/composer 0.2.0 → 0.3.0
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 +230 -6
- package/dist/index.cjs +79 -7
- package/dist/index.d.cts +256 -18
- package/dist/index.d.ts +256 -18
- package/dist/index.js +78 -8
- 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:
|
|
@@ -305,6 +313,74 @@ const app = new Composer()
|
|
|
305
313
|
});
|
|
306
314
|
```
|
|
307
315
|
|
|
316
|
+
#### `.on()` with filters
|
|
317
|
+
|
|
318
|
+
**Filter-only (no event name)** — the 2-arg `on(filter, handler)` applies the filter to **all** events without discriminating by event type:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// Type-narrowing filter — handler sees narrowed context across all compatible events
|
|
322
|
+
app.on(
|
|
323
|
+
(ctx): ctx is { text: string } => typeof (ctx as any).text === "string",
|
|
324
|
+
(ctx, next) => {
|
|
325
|
+
ctx.text; // string (narrowed)
|
|
326
|
+
return next();
|
|
327
|
+
},
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Boolean filter — no narrowing, handler gets base TOut
|
|
331
|
+
app.on(
|
|
332
|
+
(ctx) => ctx.updateType === "message",
|
|
333
|
+
(ctx, next) => {
|
|
334
|
+
// no type narrowing, full context
|
|
335
|
+
return next();
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Event + filter** — the 3-arg `on(event, filter, handler)` supports both type-narrowing predicates and boolean filters:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
// Type-narrowing filter — handler sees narrowed context
|
|
344
|
+
app.on(
|
|
345
|
+
"message",
|
|
346
|
+
(ctx): ctx is MessageCtx & { text: string } => ctx.text !== undefined,
|
|
347
|
+
(ctx, next) => {
|
|
348
|
+
ctx.text; // string (narrowed, not string | undefined)
|
|
349
|
+
return next();
|
|
350
|
+
},
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Boolean filter — no narrowing, handler sees full context
|
|
354
|
+
app.on(
|
|
355
|
+
"message",
|
|
356
|
+
(ctx) => ctx.text !== undefined,
|
|
357
|
+
(ctx, next) => {
|
|
358
|
+
ctx.text; // string | undefined (not narrowed)
|
|
359
|
+
return next();
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The 2-arg `on()` also accepts an optional `Patch` generic for context extensions (useful in custom methods):
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
app.on<"message", { args: string }>("message", (ctx, next) => {
|
|
368
|
+
ctx.args; // string — type-safe without casting
|
|
369
|
+
return next();
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`.use()` supports the same `Patch` generic — handy when a custom method enriches context before delegating to a user-provided handler:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
app.use<{ args: string }>((ctx, next) => {
|
|
377
|
+
ctx.args; // string — type-safe without casting
|
|
378
|
+
return next();
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`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.
|
|
383
|
+
|
|
308
384
|
#### `types` + `eventTypes()` — phantom type inference
|
|
309
385
|
|
|
310
386
|
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 +398,9 @@ const { Composer } = createComposer({
|
|
|
322
398
|
|
|
323
399
|
#### `methods` — custom prototype methods
|
|
324
400
|
|
|
325
|
-
Inject framework-specific DX sugar directly onto the Composer prototype
|
|
401
|
+
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.
|
|
402
|
+
|
|
403
|
+
**Simple methods** (no access to accumulated derives) work directly in `methods`:
|
|
326
404
|
|
|
327
405
|
```ts
|
|
328
406
|
const { Composer } = createComposer({
|
|
@@ -343,13 +421,77 @@ const { Composer } = createComposer({
|
|
|
343
421
|
},
|
|
344
422
|
},
|
|
345
423
|
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**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.
|
|
427
|
+
|
|
428
|
+
Use `ComposerLike<TThis>` as an F-bounded constraint so that `this.on(...)` is fully typed and returns `TThis` — no casts needed.
|
|
346
429
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
430
|
+
**Pattern: `this: TThis` + `ContextOf<TThis>` — zero annotation at the call site:**
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
import { createComposer, defineComposerMethods, eventTypes } from "@gramio/composer";
|
|
434
|
+
import type { ComposerLike, ContextOf, Middleware } from "@gramio/composer";
|
|
435
|
+
|
|
436
|
+
const methods = defineComposerMethods({
|
|
437
|
+
command<TThis extends ComposerLike<TThis>>(
|
|
438
|
+
this: TThis,
|
|
439
|
+
name: string,
|
|
440
|
+
handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
441
|
+
): TThis {
|
|
442
|
+
const inner: Middleware<MessageCtx & ContextOf<TThis>> = (ctx, next) => {
|
|
443
|
+
if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
444
|
+
return next();
|
|
445
|
+
};
|
|
446
|
+
return this.on("message", inner);
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const { Composer } = createComposer<BaseCtx, { message: MessageCtx }, typeof methods>({
|
|
451
|
+
discriminator: (ctx) => ctx.updateType,
|
|
452
|
+
methods,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Derives flow into the handler automatically — no annotation needed:
|
|
456
|
+
new Composer()
|
|
457
|
+
.derive(() => ({ user: { id: 1, name: "Alice" } }))
|
|
458
|
+
.command("start", (ctx, next) => {
|
|
459
|
+
ctx.user.id; // ✅ typed — inferred from ContextOf<TThis>
|
|
460
|
+
ctx.text; // ✅ string | undefined — from MessageCtx
|
|
461
|
+
return next();
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### `ContextOf<T>` — extract the current context type
|
|
466
|
+
|
|
467
|
+
Extracts `TOut` from a Composer or EventComposer instance type. Used as `ContextOf<TThis>` in custom method signatures to automatically capture all accumulated derives at the call site.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
import type { ContextOf } from "@gramio/composer";
|
|
471
|
+
|
|
472
|
+
// From a plain Composer:
|
|
473
|
+
type Ctx = ContextOf<Composer<{ a: number }, { a: number; b: string }>>;
|
|
474
|
+
// Ctx = { a: number; b: string }
|
|
475
|
+
|
|
476
|
+
// In a custom method — TThis is inferred from the caller instance:
|
|
477
|
+
command<TThis extends ComposerLike<TThis>>(
|
|
478
|
+
this: TThis,
|
|
479
|
+
handler: Middleware<ContextOf<TThis>>,
|
|
480
|
+
): TThis
|
|
350
481
|
```
|
|
351
482
|
|
|
352
|
-
|
|
483
|
+
#### `ComposerLike<T>` — minimal structural type for `this` constraints
|
|
484
|
+
|
|
485
|
+
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.
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import type { ComposerLike } from "@gramio/composer";
|
|
489
|
+
|
|
490
|
+
// Constraint in a custom method:
|
|
491
|
+
command<TThis extends ComposerLike<TThis>>(this: TThis, ...): TThis {
|
|
492
|
+
return this.on("message", inner); // returns TThis — no `as TThis` needed
|
|
493
|
+
}
|
|
494
|
+
```
|
|
353
495
|
|
|
354
496
|
### `EventQueue`
|
|
355
497
|
|
|
@@ -370,6 +512,88 @@ queue.addBatch(events);
|
|
|
370
512
|
await queue.stop(5000);
|
|
371
513
|
```
|
|
372
514
|
|
|
515
|
+
### Macro System
|
|
516
|
+
|
|
517
|
+
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.
|
|
518
|
+
|
|
519
|
+
#### `macro(name, definition)` / `macro(definitions)`
|
|
520
|
+
|
|
521
|
+
Register macros on a Composer or EventComposer instance.
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
import { Composer, type MacroDef, type ContextCallback } from "@gramio/composer";
|
|
525
|
+
|
|
526
|
+
// Boolean shorthand macro — plain hooks object
|
|
527
|
+
const onlyAdmin: MacroDef<void, {}> = {
|
|
528
|
+
preHandler: (ctx, next) => {
|
|
529
|
+
if (ctx.role !== "admin") return; // stops chain
|
|
530
|
+
return next();
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Parameterized macro — function receiving options
|
|
535
|
+
interface ThrottleOptions {
|
|
536
|
+
limit: number;
|
|
537
|
+
window?: number;
|
|
538
|
+
onLimit?: ContextCallback; // ← replaced with actual ctx type at call site
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const throttle: MacroDef<ThrottleOptions, {}> = (opts) => ({
|
|
542
|
+
preHandler: createThrottleMiddleware(opts),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Macro with derive — enriches handler context
|
|
546
|
+
interface AuthDerived { user: { id: number; name: string } }
|
|
547
|
+
|
|
548
|
+
const auth: MacroDef<void, AuthDerived> = {
|
|
549
|
+
derive: async (ctx) => {
|
|
550
|
+
const user = await getUser(ctx.token);
|
|
551
|
+
if (!user) return; // void = stop chain (guard behavior)
|
|
552
|
+
return { user };
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const app = new Composer()
|
|
557
|
+
.macro("onlyAdmin", onlyAdmin)
|
|
558
|
+
.macro({ throttle, auth }); // batch registration
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### `buildFromOptions(macros, options, handler)`
|
|
562
|
+
|
|
563
|
+
Runtime helper that composes a handler with macro hooks. Used internally by frameworks to wire macros into handler methods.
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
import { buildFromOptions } from "@gramio/composer";
|
|
567
|
+
|
|
568
|
+
// Execution order:
|
|
569
|
+
// 1. options.preHandler[] (explicit guards — user controls order)
|
|
570
|
+
// 2. Per-macro in options property order:
|
|
571
|
+
// a. macro.preHandler (guard middleware)
|
|
572
|
+
// b. macro.derive (context enrichment; void = stop chain)
|
|
573
|
+
// 3. Main handler
|
|
574
|
+
const composed = buildFromOptions(
|
|
575
|
+
app["~"].macros,
|
|
576
|
+
{ auth: true, throttle: { limit: 5 } },
|
|
577
|
+
mainHandler,
|
|
578
|
+
);
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### Macro Types
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import type {
|
|
585
|
+
MacroDef, // Macro definition (function or hooks object)
|
|
586
|
+
MacroHooks, // { preHandler?, derive? }
|
|
587
|
+
MacroDefinitions, // Record<string, MacroDef<any, any>>
|
|
588
|
+
ContextCallback, // Marker type for context-aware callbacks
|
|
589
|
+
WithCtx, // Recursively replaces ContextCallback with real ctx type
|
|
590
|
+
HandlerOptions, // Builds the options parameter type for handler methods
|
|
591
|
+
DeriveFromOptions, // Collects derive types from activated macros
|
|
592
|
+
MacroOptionType, // Extracts option type from MacroDef
|
|
593
|
+
MacroDeriveType, // Extracts derive return type from MacroDef
|
|
594
|
+
} from "@gramio/composer";
|
|
595
|
+
```
|
|
596
|
+
|
|
373
597
|
### Utilities
|
|
374
598
|
|
|
375
599
|
```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 });
|
|
@@ -316,6 +328,7 @@ class Composer {
|
|
|
316
328
|
this["~"].extended.add(key);
|
|
317
329
|
}
|
|
318
330
|
Object.assign(this["~"].errorsDefinitions, other["~"].errorsDefinitions);
|
|
331
|
+
Object.assign(this["~"].macros, other["~"].macros);
|
|
319
332
|
this["~"].onErrors.push(...other["~"].onErrors);
|
|
320
333
|
const pluginName = other["~"].name;
|
|
321
334
|
const isNew = (m) => {
|
|
@@ -475,19 +488,36 @@ class EventQueue {
|
|
|
475
488
|
}
|
|
476
489
|
}
|
|
477
490
|
|
|
491
|
+
function defineComposerMethods(methods) {
|
|
492
|
+
return methods;
|
|
493
|
+
}
|
|
478
494
|
function eventTypes() {
|
|
479
495
|
return void 0;
|
|
480
496
|
}
|
|
481
497
|
function createComposer(config) {
|
|
482
498
|
class EventComposerImpl extends Composer {
|
|
483
|
-
on(
|
|
484
|
-
|
|
499
|
+
on(eventOrFilter, filterOrHandler, handler) {
|
|
500
|
+
if (typeof eventOrFilter === "function") {
|
|
501
|
+
const filter2 = eventOrFilter;
|
|
502
|
+
const actualHandler2 = filterOrHandler;
|
|
503
|
+
const filterLabel = filter2.name || "filter";
|
|
504
|
+
const mw2 = (ctx, next) => {
|
|
505
|
+
if (filter2(ctx)) return actualHandler2(ctx, next);
|
|
506
|
+
return next();
|
|
507
|
+
};
|
|
508
|
+
nameMiddleware(mw2, "on", filterLabel);
|
|
509
|
+
this["~"].middlewares.push({ fn: mw2, scope: "local", type: "on", name: filterLabel });
|
|
510
|
+
this.invalidate();
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
const events = Array.isArray(eventOrFilter) ? eventOrFilter : [eventOrFilter];
|
|
485
514
|
const eventLabel = events.join("|");
|
|
515
|
+
const actualHandler = handler ?? filterOrHandler;
|
|
516
|
+
const filter = handler ? filterOrHandler : void 0;
|
|
486
517
|
const mw = (ctx, next) => {
|
|
487
|
-
if (events.includes(config.discriminator(ctx)))
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return next();
|
|
518
|
+
if (!events.includes(config.discriminator(ctx))) return next();
|
|
519
|
+
if (filter && !filter(ctx)) return next();
|
|
520
|
+
return actualHandler(ctx, next);
|
|
491
521
|
};
|
|
492
522
|
nameMiddleware(mw, "on", eventLabel);
|
|
493
523
|
this["~"].middlewares.push({ fn: mw, scope: "local", type: "on", name: eventLabel });
|
|
@@ -535,10 +565,52 @@ function createComposer(config) {
|
|
|
535
565
|
};
|
|
536
566
|
}
|
|
537
567
|
|
|
568
|
+
function buildFromOptions(macros, options, handler) {
|
|
569
|
+
if (!options) return handler;
|
|
570
|
+
const chain = [];
|
|
571
|
+
const preHandler = options.preHandler;
|
|
572
|
+
if (preHandler) {
|
|
573
|
+
if (Array.isArray(preHandler)) {
|
|
574
|
+
chain.push(...preHandler);
|
|
575
|
+
} else {
|
|
576
|
+
chain.push(preHandler);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (const key of Object.keys(options)) {
|
|
580
|
+
if (key === "preHandler") continue;
|
|
581
|
+
const value = options[key];
|
|
582
|
+
if (value === false || value == null) continue;
|
|
583
|
+
const def = macros[key];
|
|
584
|
+
if (!def) continue;
|
|
585
|
+
const hooks = typeof def === "function" ? def(value === true ? void 0 : value) : def;
|
|
586
|
+
if (hooks.preHandler) {
|
|
587
|
+
if (Array.isArray(hooks.preHandler)) {
|
|
588
|
+
chain.push(...hooks.preHandler);
|
|
589
|
+
} else {
|
|
590
|
+
chain.push(hooks.preHandler);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (hooks.derive) {
|
|
594
|
+
const deriveFn = hooks.derive;
|
|
595
|
+
chain.push(async (ctx, next) => {
|
|
596
|
+
const derived = await deriveFn(ctx);
|
|
597
|
+
if (derived == null) return;
|
|
598
|
+
Object.assign(ctx, derived);
|
|
599
|
+
return next();
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
chain.push(handler);
|
|
604
|
+
if (chain.length === 1) return chain[0];
|
|
605
|
+
return compose(chain);
|
|
606
|
+
}
|
|
607
|
+
|
|
538
608
|
exports.Composer = Composer;
|
|
539
609
|
exports.EventQueue = EventQueue;
|
|
610
|
+
exports.buildFromOptions = buildFromOptions;
|
|
540
611
|
exports.compose = compose;
|
|
541
612
|
exports.createComposer = createComposer;
|
|
613
|
+
exports.defineComposerMethods = defineComposerMethods;
|
|
542
614
|
exports.eventTypes = eventTypes;
|
|
543
615
|
exports.noopNext = noopNext;
|
|
544
616
|
exports.skip = skip;
|
package/dist/index.d.cts
CHANGED
|
@@ -19,7 +19,88 @@ type MaybeArray<T> = T | readonly T[];
|
|
|
19
19
|
/** Scope level for middleware propagation */
|
|
20
20
|
type Scope = "local" | "scoped" | "global";
|
|
21
21
|
/** Which method created a middleware entry */
|
|
22
|
-
type MiddlewareType = "use" | "derive" | "decorate" | "guard" | "branch" | "route" | "fork" | "tap" | "lazy" | "group" | "extend" | "on";
|
|
22
|
+
type MiddlewareType = "use" | "derive" | "decorate" | "guard" | "branch" | "route" | "fork" | "tap" | "lazy" | "group" | "extend" | "on" | "macro";
|
|
23
|
+
/** Brand symbol for ContextCallback marker type */
|
|
24
|
+
declare const ContextCallbackBrand: unique symbol;
|
|
25
|
+
/**
|
|
26
|
+
* Marker type for context-aware callbacks in macro options.
|
|
27
|
+
* The framework replaces this with the actual handler context type at the call site.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* interface ThrottleOptions {
|
|
32
|
+
* limit: number;
|
|
33
|
+
* onLimit?: ContextCallback; // ← framework substitutes the real ctx type
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
interface ContextCallback {
|
|
38
|
+
readonly [ContextCallbackBrand]: true;
|
|
39
|
+
(ctx: never): unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Recursively replaces all `ContextCallback` occurrences in `T`
|
|
43
|
+
* with `(ctx: TCtx) => unknown`.
|
|
44
|
+
*/
|
|
45
|
+
type WithCtx<T, TCtx> = T extends ContextCallback ? (ctx: TCtx) => unknown : T extends (...args: any[]) => any ? T : T extends object ? {
|
|
46
|
+
[K in keyof T]: WithCtx<T[K], TCtx>;
|
|
47
|
+
} : T;
|
|
48
|
+
/** What a macro can return when activated */
|
|
49
|
+
interface MacroHooks<TDerive extends object = {}> {
|
|
50
|
+
/** Middleware to run before the main handler */
|
|
51
|
+
preHandler?: Middleware<any> | Middleware<any>[];
|
|
52
|
+
/**
|
|
53
|
+
* Context enrichment — return type gets merged into the handler's context.
|
|
54
|
+
* Return `void` / `undefined` to stop the middleware chain (acts as a guard).
|
|
55
|
+
*/
|
|
56
|
+
derive?: (ctx: any) => TDerive | void | Promise<TDerive | void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A macro definition: either a function accepting options, or a plain hooks object (boolean shorthand).
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* // Parameterized macro
|
|
64
|
+
* const throttle: MacroDef<{ limit: number }, {}> = (opts) => ({
|
|
65
|
+
* preHandler: createThrottleMiddleware(opts),
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Boolean shorthand
|
|
69
|
+
* const onlyAdmin: MacroDef<void, {}> = {
|
|
70
|
+
* preHandler: checkAdminMiddleware,
|
|
71
|
+
* };
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
type MacroDef<TOptions = void, TDerive extends object = {}> = ((opts: TOptions) => MacroHooks<TDerive>) | MacroHooks<TDerive>;
|
|
75
|
+
/** Registry of named macro definitions */
|
|
76
|
+
type MacroDefinitions = Record<string, MacroDef<any, any>>;
|
|
77
|
+
/** Extract the options type a macro accepts (boolean for shorthand macros) */
|
|
78
|
+
type MacroOptionType<M> = M extends (opts: infer O) => any ? O : boolean;
|
|
79
|
+
/** Extract the derive (context enrichment) type from a macro */
|
|
80
|
+
type MacroDeriveType<M> = M extends (opts: any) => {
|
|
81
|
+
derive: (...a: any) => infer R;
|
|
82
|
+
} ? Exclude<Awaited<R>, void> : M extends {
|
|
83
|
+
derive: (...a: any) => infer R;
|
|
84
|
+
} ? Exclude<Awaited<R>, void> : {};
|
|
85
|
+
/**
|
|
86
|
+
* Builds the `options` parameter type for handler methods.
|
|
87
|
+
* Includes `preHandler` plus all registered macro option types
|
|
88
|
+
* with ContextCallback markers replaced by `TBaseCtx`.
|
|
89
|
+
*/
|
|
90
|
+
type HandlerOptions<TBaseCtx, Macros extends MacroDefinitions> = {
|
|
91
|
+
preHandler?: Middleware<TBaseCtx> | Middleware<TBaseCtx>[];
|
|
92
|
+
} & {
|
|
93
|
+
[K in keyof Macros]?: WithCtx<MacroOptionType<Macros[K]>, TBaseCtx>;
|
|
94
|
+
};
|
|
95
|
+
/** Helper: converts a union to an intersection */
|
|
96
|
+
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
97
|
+
/**
|
|
98
|
+
* Collects all derive types from macros that are activated in `TOptions`.
|
|
99
|
+
* The result is intersected into the handler's context type.
|
|
100
|
+
*/
|
|
101
|
+
type DeriveFromOptions<Macros extends MacroDefinitions, TOptions> = UnionToIntersection<{
|
|
102
|
+
[K in keyof TOptions & keyof Macros]: MacroDeriveType<Macros[K]>;
|
|
103
|
+
}[keyof TOptions & keyof Macros]>;
|
|
23
104
|
/** Read-only projection of a middleware entry for inspect()/trace() */
|
|
24
105
|
interface MiddlewareInfo {
|
|
25
106
|
index: number;
|
|
@@ -51,7 +132,7 @@ interface ComposerOptions {
|
|
|
51
132
|
declare function compose<T>(middlewares: Middleware<T>[]): ComposedMiddleware<T>;
|
|
52
133
|
|
|
53
134
|
/** Route handler: single middleware, array, or Composer instance */
|
|
54
|
-
type RouteHandler<T extends object> = Middleware<T> | Middleware<T>[] | Composer<any, any, any>;
|
|
135
|
+
type RouteHandler<T extends object> = Middleware<T> | Middleware<T>[] | Composer<any, any, any, any>;
|
|
55
136
|
/** Route builder passed to the builder-callback overload of route() */
|
|
56
137
|
interface RouteBuilder<T extends object, K extends string> {
|
|
57
138
|
/** Register a route. Returns a pre-typed Composer for chaining derive/use/guard etc. */
|
|
@@ -59,7 +140,7 @@ interface RouteBuilder<T extends object, K extends string> {
|
|
|
59
140
|
/** Fallback when router returns undefined or key has no handler */
|
|
60
141
|
otherwise(...middleware: Middleware<T>[]): void;
|
|
61
142
|
}
|
|
62
|
-
declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed extends object = {}> {
|
|
143
|
+
declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed extends object = {}, TMacros extends MacroDefinitions = {}> {
|
|
63
144
|
"~": {
|
|
64
145
|
middlewares: ScopedMiddleware<any>[];
|
|
65
146
|
onErrors: ErrorHandler<any>[];
|
|
@@ -72,13 +153,22 @@ declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed
|
|
|
72
153
|
prototype: Error;
|
|
73
154
|
}>;
|
|
74
155
|
tracer: TraceHandler | undefined;
|
|
156
|
+
macros: Record<string, MacroDef<any, any>>;
|
|
157
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
158
|
+
Out: TOut;
|
|
75
159
|
};
|
|
76
160
|
constructor(options?: ComposerOptions);
|
|
77
161
|
invalidate(): void;
|
|
162
|
+
/** Register a single named macro */
|
|
163
|
+
macro<const Name extends string, TDef extends MacroDef<any, any>>(name: Name, definition: TDef): Composer<TIn, TOut, TExposed, TMacros & Record<Name, TDef>>;
|
|
164
|
+
/** Register multiple macros at once */
|
|
165
|
+
macro<const TDefs extends Record<string, MacroDef<any, any>>>(definitions: TDefs): Composer<TIn, TOut, TExposed, TMacros & TDefs>;
|
|
78
166
|
decorate<D extends object>(values: D): Composer<TIn, TOut & D, TExposed>;
|
|
79
167
|
decorate<D extends object>(values: D, options: {
|
|
80
168
|
as: "scoped" | "global";
|
|
81
169
|
}): Composer<TIn, TOut & D, TExposed & D>;
|
|
170
|
+
use(handler: Middleware<TOut>): this;
|
|
171
|
+
use<Patch extends object>(handler: Middleware<TOut & Patch>): this;
|
|
82
172
|
use(...middleware: Middleware<TOut>[]): Composer<TIn, TOut, TExposed>;
|
|
83
173
|
derive<D extends object>(handler: DeriveHandler<TOut, D>): Composer<TIn, TOut & D, TExposed>;
|
|
84
174
|
derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
|
|
@@ -99,7 +189,7 @@ declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed
|
|
|
99
189
|
}): this;
|
|
100
190
|
as(scope: "scoped" | "global"): Composer<TIn, TOut, TOut>;
|
|
101
191
|
group(fn: (composer: Composer<TOut, TOut, {}>) => void): Composer<TIn, TOut, TExposed>;
|
|
102
|
-
extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): Composer<TIn, TOut & UExposed, TExposed>;
|
|
192
|
+
extend<UIn extends object, UOut extends UIn, UExposed extends object, UMacros extends MacroDefinitions = {}>(other: Composer<UIn, UOut, UExposed, UMacros>): Composer<TIn, TOut & UExposed, TExposed, TMacros & UMacros>;
|
|
103
193
|
inspect(): MiddlewareInfo[];
|
|
104
194
|
trace(handler: TraceHandler): this;
|
|
105
195
|
compose(): ComposedMiddleware<TIn>;
|
|
@@ -136,10 +226,47 @@ declare class EventQueue<T> {
|
|
|
136
226
|
* Array of events → discriminated union where each branch has correct derives.
|
|
137
227
|
*/
|
|
138
228
|
type ResolveEventCtx<TOut extends object, TEventMap extends Record<string, any>, TDerives extends Record<string, object>, E extends string> = E extends any ? TOut & (E extends keyof TEventMap ? TEventMap[E] : {}) & (E extends keyof TDerives ? TDerives[E] : {}) : never;
|
|
229
|
+
/**
|
|
230
|
+
* Given an event map and a Narrowing type, yields the union of event names
|
|
231
|
+
* whose context type contains all keys from Narrowing.
|
|
232
|
+
*/
|
|
233
|
+
type CompatibleEvents<TEventMap extends Record<string, any>, Narrowing> = {
|
|
234
|
+
[E in keyof TEventMap & string]: keyof Narrowing & string extends keyof TEventMap[E] ? E : never;
|
|
235
|
+
}[keyof TEventMap & string];
|
|
236
|
+
/**
|
|
237
|
+
* Minimal structural type for constraining `this` in custom composer methods.
|
|
238
|
+
*
|
|
239
|
+
* Use as an F-bounded constraint on `TThis` so that `this.on(...)` is typed
|
|
240
|
+
* and returns `TThis` without requiring `as any`:
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* command<TThis extends ComposerLike<TThis>>(
|
|
245
|
+
* this: TThis,
|
|
246
|
+
* name: string,
|
|
247
|
+
* handler: Middleware<MsgCtx & ContextOf<TThis>>,
|
|
248
|
+
* ): TThis {
|
|
249
|
+
* const inner: Middleware<MsgCtx & ContextOf<TThis>> = (ctx, next) => {
|
|
250
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
251
|
+
* return next();
|
|
252
|
+
* };
|
|
253
|
+
* return this.on("message", inner); // typed — no cast needed
|
|
254
|
+
* }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
type ComposerLike<T = unknown> = {
|
|
258
|
+
on(event: any, handler: any): T;
|
|
259
|
+
};
|
|
139
260
|
/** EventComposer interface — Composer + .on() + per-event derive tracking + custom methods */
|
|
140
|
-
interface EventComposer<TBase extends object, TEventMap extends Record<string, TBase>, TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}> {
|
|
141
|
-
on<
|
|
261
|
+
interface EventComposer<TBase extends object, TEventMap extends Record<string, TBase>, TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}, TMacros extends MacroDefinitions = {}> {
|
|
262
|
+
on<Narrowing>(filter: (ctx: any) => ctx is Narrowing, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, CompatibleEvents<TEventMap, Narrowing>> & Narrowing>): this;
|
|
263
|
+
on(filter: (ctx: TOut) => boolean, handler: Middleware<TOut>): this;
|
|
264
|
+
on<E extends keyof TEventMap & string, Narrowing>(event: MaybeArray<E>, filter: (ctx: any) => ctx is Narrowing, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E> & Narrowing>): this;
|
|
265
|
+
on<E extends keyof TEventMap & string>(event: MaybeArray<E>, filter: (ctx: ResolveEventCtx<TOut, TEventMap, TDerives, E>) => boolean, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): this;
|
|
266
|
+
on<E extends keyof TEventMap & string, Patch extends object = {}>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E> & Patch>): this;
|
|
267
|
+
use<Patch extends object>(handler: Middleware<TOut & Patch>): this;
|
|
142
268
|
use(...middleware: Middleware<TOut>[]): this;
|
|
269
|
+
guard<Narrowing>(predicate: (context: any) => context is Narrowing): EventComposer<TBase, TEventMap, TIn, TOut & Narrowing, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
143
270
|
guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): this;
|
|
144
271
|
branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): this;
|
|
145
272
|
route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, (composer: Composer<TOut, TOut, {}>) => Composer<any, any, any>>>, fallback?: (composer: Composer<TOut, TOut, {}>) => Composer<any, any, any>): this;
|
|
@@ -154,21 +281,25 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
|
|
|
154
281
|
prototype: Error;
|
|
155
282
|
}): this;
|
|
156
283
|
group(fn: (composer: Composer<TOut, TOut, {}>) => void): this;
|
|
157
|
-
decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods> & TMethods;
|
|
284
|
+
decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
158
285
|
decorate<D extends object>(values: D, options: {
|
|
159
286
|
as: "scoped" | "global";
|
|
160
|
-
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
|
|
161
|
-
derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods> & TMethods;
|
|
287
|
+
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods, TMacros> & TMethods;
|
|
288
|
+
derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
162
289
|
derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
|
|
163
290
|
as: "scoped" | "global";
|
|
164
|
-
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
|
|
291
|
+
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods, TMacros> & TMethods;
|
|
165
292
|
derive<E extends keyof TEventMap & string, D extends object>(event: MaybeArray<E>, handler: DeriveHandler<ResolveEventCtx<TOut, TEventMap, TDerives, E>, D>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives & {
|
|
166
293
|
[K in E]: D;
|
|
167
|
-
}, TMethods> & TMethods;
|
|
168
|
-
when<UOut extends TOut>(condition: boolean, fn: (composer: Composer<TOut, TOut, {}>) => Composer<TOut, UOut, any>): EventComposer<TBase, TEventMap, TIn, TOut & Partial<Omit<UOut, keyof TOut>>, TExposed, TDerives, TMethods> & TMethods;
|
|
169
|
-
as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives, TMethods> & TMethods;
|
|
170
|
-
extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object
|
|
171
|
-
extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives, TMethods> & TMethods;
|
|
294
|
+
}, TMethods, TMacros> & TMethods;
|
|
295
|
+
when<UOut extends TOut>(condition: boolean, fn: (composer: Composer<TOut, TOut, {}>) => Composer<TOut, UOut, any>): EventComposer<TBase, TEventMap, TIn, TOut & Partial<Omit<UOut, keyof TOut>>, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
296
|
+
as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives, TMethods, TMacros> & TMethods;
|
|
297
|
+
extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object>, UMacros extends MacroDefinitions = {}>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives, any, UMacros>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives, TMethods, TMacros & UMacros> & TMethods;
|
|
298
|
+
extend<UIn extends object, UOut extends UIn, UExposed extends object, UMacros extends MacroDefinitions = {}>(other: Composer<UIn, UOut, UExposed, UMacros>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives, TMethods, TMacros & UMacros> & TMethods;
|
|
299
|
+
/** Register a single named macro */
|
|
300
|
+
macro<const Name extends string, TDef extends MacroDef<any, any>>(name: Name, definition: TDef): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros & Record<Name, TDef>> & TMethods;
|
|
301
|
+
/** Register multiple macros at once */
|
|
302
|
+
macro<const TDefs extends Record<string, MacroDef<any, any>>>(definitions: TDefs): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros & TDefs> & TMethods;
|
|
172
303
|
inspect(): MiddlewareInfo[];
|
|
173
304
|
trace(handler: TraceHandler): this;
|
|
174
305
|
compose(): ComposedMiddleware<TIn>;
|
|
@@ -185,13 +316,108 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
|
|
|
185
316
|
prototype: Error;
|
|
186
317
|
}>;
|
|
187
318
|
tracer: TraceHandler | undefined;
|
|
319
|
+
macros: Record<string, MacroDef<any, any>>;
|
|
188
320
|
Derives: TDerives;
|
|
321
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
322
|
+
Out: TOut;
|
|
189
323
|
};
|
|
190
324
|
invalidate(): void;
|
|
191
325
|
}
|
|
192
326
|
interface EventComposerConstructor<TBase extends object, TEventMap extends Record<string, TBase>, TMethods extends Record<string, (...args: any[]) => any> = {}> {
|
|
193
|
-
new <TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}>(options?: ComposerOptions): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods> & TMethods;
|
|
327
|
+
new <TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMacros extends MacroDefinitions = {}>(options?: ComposerOptions): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
194
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Extracts the current accumulated context type (`TOut`) from an EventComposer
|
|
331
|
+
* or Composer instance type.
|
|
332
|
+
*
|
|
333
|
+
* Use with a `this: TThis` parameter in custom methods to get automatic
|
|
334
|
+
* derive type inference — the handler receives all types from previous `.derive()` calls
|
|
335
|
+
* without requiring explicit annotation at the call site.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* const { Composer } = createComposer({
|
|
340
|
+
* methods: {
|
|
341
|
+
* // Automatic: caller's derives are inferred into the handler
|
|
342
|
+
* command<TThis>(
|
|
343
|
+
* this: TThis,
|
|
344
|
+
* name: string,
|
|
345
|
+
* handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
346
|
+
* ) {
|
|
347
|
+
* return (this as any).on("message", (ctx: any, next: any) => {
|
|
348
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
349
|
+
* return next();
|
|
350
|
+
* }) as TThis;
|
|
351
|
+
* },
|
|
352
|
+
*
|
|
353
|
+
* // Manual: caller declares which derives they need via Patch generic
|
|
354
|
+
* hears<Patch extends object = {}>(
|
|
355
|
+
* trigger: string,
|
|
356
|
+
* handler: Middleware<MessageCtx & Patch>,
|
|
357
|
+
* ) {
|
|
358
|
+
* return this.on<"message", Patch>("message", (ctx, next) => {
|
|
359
|
+
* if (ctx.text === trigger) return handler(ctx, next);
|
|
360
|
+
* return next();
|
|
361
|
+
* });
|
|
362
|
+
* },
|
|
363
|
+
* },
|
|
364
|
+
* });
|
|
365
|
+
*
|
|
366
|
+
* // With ContextOf — no type annotation needed at call site:
|
|
367
|
+
* new Composer()
|
|
368
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
369
|
+
* .command("start", (ctx) => ctx.user.id); // ✅ inferred automatically
|
|
370
|
+
*
|
|
371
|
+
* // With Patch — caller declares what they need:
|
|
372
|
+
* new Composer()
|
|
373
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
374
|
+
* .hears<{ user: { id: number } }>("hello", (ctx) => ctx.user.id); // ✅
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
type ContextOf<T> = T extends {
|
|
378
|
+
"~": {
|
|
379
|
+
Out: infer O;
|
|
380
|
+
};
|
|
381
|
+
} ? O : never;
|
|
382
|
+
/**
|
|
383
|
+
* Helper to define custom composer methods with full TypeScript inference.
|
|
384
|
+
*
|
|
385
|
+
* TypeScript cannot infer generic method signatures when they're passed directly
|
|
386
|
+
* inside `createComposer({ methods: { ... } })` because `TMethods` is buried
|
|
387
|
+
* inside the nested return type `{ Composer: EventComposerConstructor<..., TMethods> }`.
|
|
388
|
+
*
|
|
389
|
+
* This helper has return type `TMethods` directly — which lets TypeScript preserve
|
|
390
|
+
* generic method signatures. Pass the result (via `typeof`) as the 3rd type argument
|
|
391
|
+
* to `createComposer`:
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* const methods = defineComposerMethods({
|
|
396
|
+
* // Pattern: `this: TThis` + `ContextOf<TThis>` — zero annotation at call site
|
|
397
|
+
* command<TThis>(
|
|
398
|
+
* this: TThis,
|
|
399
|
+
* name: string,
|
|
400
|
+
* handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
401
|
+
* ): TThis {
|
|
402
|
+
* return (this as any).on("message", (ctx: any, next: any) => {
|
|
403
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
404
|
+
* return next();
|
|
405
|
+
* }) as TThis;
|
|
406
|
+
* },
|
|
407
|
+
* });
|
|
408
|
+
*
|
|
409
|
+
* const { Composer } = createComposer<Base, EventMap, typeof methods>({
|
|
410
|
+
* discriminator: (ctx) => ctx.updateType,
|
|
411
|
+
* methods,
|
|
412
|
+
* });
|
|
413
|
+
*
|
|
414
|
+
* // No annotation needed — derives flow in automatically:
|
|
415
|
+
* new Composer()
|
|
416
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
417
|
+
* .command("start", (ctx) => ctx.user.id); // ✅
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare function defineComposerMethods<TMethods extends Record<string, (...args: any[]) => any>>(methods: TMethods): TMethods;
|
|
195
421
|
/**
|
|
196
422
|
* Phantom type carrier for event map inference.
|
|
197
423
|
* Returns `undefined` at runtime — exists purely for type-level inference
|
|
@@ -220,6 +446,18 @@ declare function createComposer<TBase extends object, TEventMap extends Record<s
|
|
|
220
446
|
EventQueue: typeof EventQueue;
|
|
221
447
|
};
|
|
222
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Composes a handler with macro hooks and preHandlers from an options object.
|
|
451
|
+
*
|
|
452
|
+
* Execution order:
|
|
453
|
+
* 1. `options.preHandler` array (explicit guards — user controls order)
|
|
454
|
+
* 2. Per-macro in options property order:
|
|
455
|
+
* a. macro.preHandler (guard middleware)
|
|
456
|
+
* b. macro.derive (context enrichment; void return = stop chain)
|
|
457
|
+
* 3. Main handler
|
|
458
|
+
*/
|
|
459
|
+
declare function buildFromOptions<TCtx>(macros: Record<string, MacroDef<any, any>>, options: Record<string, unknown> | undefined, handler: Middleware<TCtx>): Middleware<TCtx>;
|
|
460
|
+
|
|
223
461
|
/** No-op next function: () => Promise.resolve() */
|
|
224
462
|
declare const noopNext: Next;
|
|
225
463
|
/** Pass-through middleware: calls next() immediately */
|
|
@@ -227,5 +465,5 @@ declare const skip: Middleware<any>;
|
|
|
227
465
|
/** Terminal middleware: does NOT call next() */
|
|
228
466
|
declare const stop: Middleware<any>;
|
|
229
467
|
|
|
230
|
-
export { Composer, EventQueue, compose, createComposer, eventTypes, noopNext, skip, stop };
|
|
231
|
-
export type { ComposedMiddleware, ComposerOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, LazyFactory, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler };
|
|
468
|
+
export { Composer, EventQueue, buildFromOptions, compose, createComposer, defineComposerMethods, eventTypes, noopNext, skip, stop };
|
|
469
|
+
export type { CompatibleEvents, ComposedMiddleware, ComposerLike, ComposerOptions, ContextCallback, ContextOf, DeriveFromOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, HandlerOptions, LazyFactory, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler, WithCtx };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,88 @@ type MaybeArray<T> = T | readonly T[];
|
|
|
19
19
|
/** Scope level for middleware propagation */
|
|
20
20
|
type Scope = "local" | "scoped" | "global";
|
|
21
21
|
/** Which method created a middleware entry */
|
|
22
|
-
type MiddlewareType = "use" | "derive" | "decorate" | "guard" | "branch" | "route" | "fork" | "tap" | "lazy" | "group" | "extend" | "on";
|
|
22
|
+
type MiddlewareType = "use" | "derive" | "decorate" | "guard" | "branch" | "route" | "fork" | "tap" | "lazy" | "group" | "extend" | "on" | "macro";
|
|
23
|
+
/** Brand symbol for ContextCallback marker type */
|
|
24
|
+
declare const ContextCallbackBrand: unique symbol;
|
|
25
|
+
/**
|
|
26
|
+
* Marker type for context-aware callbacks in macro options.
|
|
27
|
+
* The framework replaces this with the actual handler context type at the call site.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* interface ThrottleOptions {
|
|
32
|
+
* limit: number;
|
|
33
|
+
* onLimit?: ContextCallback; // ← framework substitutes the real ctx type
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
interface ContextCallback {
|
|
38
|
+
readonly [ContextCallbackBrand]: true;
|
|
39
|
+
(ctx: never): unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Recursively replaces all `ContextCallback` occurrences in `T`
|
|
43
|
+
* with `(ctx: TCtx) => unknown`.
|
|
44
|
+
*/
|
|
45
|
+
type WithCtx<T, TCtx> = T extends ContextCallback ? (ctx: TCtx) => unknown : T extends (...args: any[]) => any ? T : T extends object ? {
|
|
46
|
+
[K in keyof T]: WithCtx<T[K], TCtx>;
|
|
47
|
+
} : T;
|
|
48
|
+
/** What a macro can return when activated */
|
|
49
|
+
interface MacroHooks<TDerive extends object = {}> {
|
|
50
|
+
/** Middleware to run before the main handler */
|
|
51
|
+
preHandler?: Middleware<any> | Middleware<any>[];
|
|
52
|
+
/**
|
|
53
|
+
* Context enrichment — return type gets merged into the handler's context.
|
|
54
|
+
* Return `void` / `undefined` to stop the middleware chain (acts as a guard).
|
|
55
|
+
*/
|
|
56
|
+
derive?: (ctx: any) => TDerive | void | Promise<TDerive | void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A macro definition: either a function accepting options, or a plain hooks object (boolean shorthand).
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* // Parameterized macro
|
|
64
|
+
* const throttle: MacroDef<{ limit: number }, {}> = (opts) => ({
|
|
65
|
+
* preHandler: createThrottleMiddleware(opts),
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Boolean shorthand
|
|
69
|
+
* const onlyAdmin: MacroDef<void, {}> = {
|
|
70
|
+
* preHandler: checkAdminMiddleware,
|
|
71
|
+
* };
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
type MacroDef<TOptions = void, TDerive extends object = {}> = ((opts: TOptions) => MacroHooks<TDerive>) | MacroHooks<TDerive>;
|
|
75
|
+
/** Registry of named macro definitions */
|
|
76
|
+
type MacroDefinitions = Record<string, MacroDef<any, any>>;
|
|
77
|
+
/** Extract the options type a macro accepts (boolean for shorthand macros) */
|
|
78
|
+
type MacroOptionType<M> = M extends (opts: infer O) => any ? O : boolean;
|
|
79
|
+
/** Extract the derive (context enrichment) type from a macro */
|
|
80
|
+
type MacroDeriveType<M> = M extends (opts: any) => {
|
|
81
|
+
derive: (...a: any) => infer R;
|
|
82
|
+
} ? Exclude<Awaited<R>, void> : M extends {
|
|
83
|
+
derive: (...a: any) => infer R;
|
|
84
|
+
} ? Exclude<Awaited<R>, void> : {};
|
|
85
|
+
/**
|
|
86
|
+
* Builds the `options` parameter type for handler methods.
|
|
87
|
+
* Includes `preHandler` plus all registered macro option types
|
|
88
|
+
* with ContextCallback markers replaced by `TBaseCtx`.
|
|
89
|
+
*/
|
|
90
|
+
type HandlerOptions<TBaseCtx, Macros extends MacroDefinitions> = {
|
|
91
|
+
preHandler?: Middleware<TBaseCtx> | Middleware<TBaseCtx>[];
|
|
92
|
+
} & {
|
|
93
|
+
[K in keyof Macros]?: WithCtx<MacroOptionType<Macros[K]>, TBaseCtx>;
|
|
94
|
+
};
|
|
95
|
+
/** Helper: converts a union to an intersection */
|
|
96
|
+
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
97
|
+
/**
|
|
98
|
+
* Collects all derive types from macros that are activated in `TOptions`.
|
|
99
|
+
* The result is intersected into the handler's context type.
|
|
100
|
+
*/
|
|
101
|
+
type DeriveFromOptions<Macros extends MacroDefinitions, TOptions> = UnionToIntersection<{
|
|
102
|
+
[K in keyof TOptions & keyof Macros]: MacroDeriveType<Macros[K]>;
|
|
103
|
+
}[keyof TOptions & keyof Macros]>;
|
|
23
104
|
/** Read-only projection of a middleware entry for inspect()/trace() */
|
|
24
105
|
interface MiddlewareInfo {
|
|
25
106
|
index: number;
|
|
@@ -51,7 +132,7 @@ interface ComposerOptions {
|
|
|
51
132
|
declare function compose<T>(middlewares: Middleware<T>[]): ComposedMiddleware<T>;
|
|
52
133
|
|
|
53
134
|
/** Route handler: single middleware, array, or Composer instance */
|
|
54
|
-
type RouteHandler<T extends object> = Middleware<T> | Middleware<T>[] | Composer<any, any, any>;
|
|
135
|
+
type RouteHandler<T extends object> = Middleware<T> | Middleware<T>[] | Composer<any, any, any, any>;
|
|
55
136
|
/** Route builder passed to the builder-callback overload of route() */
|
|
56
137
|
interface RouteBuilder<T extends object, K extends string> {
|
|
57
138
|
/** Register a route. Returns a pre-typed Composer for chaining derive/use/guard etc. */
|
|
@@ -59,7 +140,7 @@ interface RouteBuilder<T extends object, K extends string> {
|
|
|
59
140
|
/** Fallback when router returns undefined or key has no handler */
|
|
60
141
|
otherwise(...middleware: Middleware<T>[]): void;
|
|
61
142
|
}
|
|
62
|
-
declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed extends object = {}> {
|
|
143
|
+
declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed extends object = {}, TMacros extends MacroDefinitions = {}> {
|
|
63
144
|
"~": {
|
|
64
145
|
middlewares: ScopedMiddleware<any>[];
|
|
65
146
|
onErrors: ErrorHandler<any>[];
|
|
@@ -72,13 +153,22 @@ declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed
|
|
|
72
153
|
prototype: Error;
|
|
73
154
|
}>;
|
|
74
155
|
tracer: TraceHandler | undefined;
|
|
156
|
+
macros: Record<string, MacroDef<any, any>>;
|
|
157
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
158
|
+
Out: TOut;
|
|
75
159
|
};
|
|
76
160
|
constructor(options?: ComposerOptions);
|
|
77
161
|
invalidate(): void;
|
|
162
|
+
/** Register a single named macro */
|
|
163
|
+
macro<const Name extends string, TDef extends MacroDef<any, any>>(name: Name, definition: TDef): Composer<TIn, TOut, TExposed, TMacros & Record<Name, TDef>>;
|
|
164
|
+
/** Register multiple macros at once */
|
|
165
|
+
macro<const TDefs extends Record<string, MacroDef<any, any>>>(definitions: TDefs): Composer<TIn, TOut, TExposed, TMacros & TDefs>;
|
|
78
166
|
decorate<D extends object>(values: D): Composer<TIn, TOut & D, TExposed>;
|
|
79
167
|
decorate<D extends object>(values: D, options: {
|
|
80
168
|
as: "scoped" | "global";
|
|
81
169
|
}): Composer<TIn, TOut & D, TExposed & D>;
|
|
170
|
+
use(handler: Middleware<TOut>): this;
|
|
171
|
+
use<Patch extends object>(handler: Middleware<TOut & Patch>): this;
|
|
82
172
|
use(...middleware: Middleware<TOut>[]): Composer<TIn, TOut, TExposed>;
|
|
83
173
|
derive<D extends object>(handler: DeriveHandler<TOut, D>): Composer<TIn, TOut & D, TExposed>;
|
|
84
174
|
derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
|
|
@@ -99,7 +189,7 @@ declare class Composer<TIn extends object = {}, TOut extends TIn = TIn, TExposed
|
|
|
99
189
|
}): this;
|
|
100
190
|
as(scope: "scoped" | "global"): Composer<TIn, TOut, TOut>;
|
|
101
191
|
group(fn: (composer: Composer<TOut, TOut, {}>) => void): Composer<TIn, TOut, TExposed>;
|
|
102
|
-
extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): Composer<TIn, TOut & UExposed, TExposed>;
|
|
192
|
+
extend<UIn extends object, UOut extends UIn, UExposed extends object, UMacros extends MacroDefinitions = {}>(other: Composer<UIn, UOut, UExposed, UMacros>): Composer<TIn, TOut & UExposed, TExposed, TMacros & UMacros>;
|
|
103
193
|
inspect(): MiddlewareInfo[];
|
|
104
194
|
trace(handler: TraceHandler): this;
|
|
105
195
|
compose(): ComposedMiddleware<TIn>;
|
|
@@ -136,10 +226,47 @@ declare class EventQueue<T> {
|
|
|
136
226
|
* Array of events → discriminated union where each branch has correct derives.
|
|
137
227
|
*/
|
|
138
228
|
type ResolveEventCtx<TOut extends object, TEventMap extends Record<string, any>, TDerives extends Record<string, object>, E extends string> = E extends any ? TOut & (E extends keyof TEventMap ? TEventMap[E] : {}) & (E extends keyof TDerives ? TDerives[E] : {}) : never;
|
|
229
|
+
/**
|
|
230
|
+
* Given an event map and a Narrowing type, yields the union of event names
|
|
231
|
+
* whose context type contains all keys from Narrowing.
|
|
232
|
+
*/
|
|
233
|
+
type CompatibleEvents<TEventMap extends Record<string, any>, Narrowing> = {
|
|
234
|
+
[E in keyof TEventMap & string]: keyof Narrowing & string extends keyof TEventMap[E] ? E : never;
|
|
235
|
+
}[keyof TEventMap & string];
|
|
236
|
+
/**
|
|
237
|
+
* Minimal structural type for constraining `this` in custom composer methods.
|
|
238
|
+
*
|
|
239
|
+
* Use as an F-bounded constraint on `TThis` so that `this.on(...)` is typed
|
|
240
|
+
* and returns `TThis` without requiring `as any`:
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* command<TThis extends ComposerLike<TThis>>(
|
|
245
|
+
* this: TThis,
|
|
246
|
+
* name: string,
|
|
247
|
+
* handler: Middleware<MsgCtx & ContextOf<TThis>>,
|
|
248
|
+
* ): TThis {
|
|
249
|
+
* const inner: Middleware<MsgCtx & ContextOf<TThis>> = (ctx, next) => {
|
|
250
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
251
|
+
* return next();
|
|
252
|
+
* };
|
|
253
|
+
* return this.on("message", inner); // typed — no cast needed
|
|
254
|
+
* }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
type ComposerLike<T = unknown> = {
|
|
258
|
+
on(event: any, handler: any): T;
|
|
259
|
+
};
|
|
139
260
|
/** EventComposer interface — Composer + .on() + per-event derive tracking + custom methods */
|
|
140
|
-
interface EventComposer<TBase extends object, TEventMap extends Record<string, TBase>, TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}> {
|
|
141
|
-
on<
|
|
261
|
+
interface EventComposer<TBase extends object, TEventMap extends Record<string, TBase>, TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}, TMacros extends MacroDefinitions = {}> {
|
|
262
|
+
on<Narrowing>(filter: (ctx: any) => ctx is Narrowing, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, CompatibleEvents<TEventMap, Narrowing>> & Narrowing>): this;
|
|
263
|
+
on(filter: (ctx: TOut) => boolean, handler: Middleware<TOut>): this;
|
|
264
|
+
on<E extends keyof TEventMap & string, Narrowing>(event: MaybeArray<E>, filter: (ctx: any) => ctx is Narrowing, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E> & Narrowing>): this;
|
|
265
|
+
on<E extends keyof TEventMap & string>(event: MaybeArray<E>, filter: (ctx: ResolveEventCtx<TOut, TEventMap, TDerives, E>) => boolean, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): this;
|
|
266
|
+
on<E extends keyof TEventMap & string, Patch extends object = {}>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E> & Patch>): this;
|
|
267
|
+
use<Patch extends object>(handler: Middleware<TOut & Patch>): this;
|
|
142
268
|
use(...middleware: Middleware<TOut>[]): this;
|
|
269
|
+
guard<Narrowing>(predicate: (context: any) => context is Narrowing): EventComposer<TBase, TEventMap, TIn, TOut & Narrowing, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
143
270
|
guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): this;
|
|
144
271
|
branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): this;
|
|
145
272
|
route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, (composer: Composer<TOut, TOut, {}>) => Composer<any, any, any>>>, fallback?: (composer: Composer<TOut, TOut, {}>) => Composer<any, any, any>): this;
|
|
@@ -154,21 +281,25 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
|
|
|
154
281
|
prototype: Error;
|
|
155
282
|
}): this;
|
|
156
283
|
group(fn: (composer: Composer<TOut, TOut, {}>) => void): this;
|
|
157
|
-
decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods> & TMethods;
|
|
284
|
+
decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
158
285
|
decorate<D extends object>(values: D, options: {
|
|
159
286
|
as: "scoped" | "global";
|
|
160
|
-
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
|
|
161
|
-
derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods> & TMethods;
|
|
287
|
+
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods, TMacros> & TMethods;
|
|
288
|
+
derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
162
289
|
derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
|
|
163
290
|
as: "scoped" | "global";
|
|
164
|
-
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
|
|
291
|
+
}): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods, TMacros> & TMethods;
|
|
165
292
|
derive<E extends keyof TEventMap & string, D extends object>(event: MaybeArray<E>, handler: DeriveHandler<ResolveEventCtx<TOut, TEventMap, TDerives, E>, D>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives & {
|
|
166
293
|
[K in E]: D;
|
|
167
|
-
}, TMethods> & TMethods;
|
|
168
|
-
when<UOut extends TOut>(condition: boolean, fn: (composer: Composer<TOut, TOut, {}>) => Composer<TOut, UOut, any>): EventComposer<TBase, TEventMap, TIn, TOut & Partial<Omit<UOut, keyof TOut>>, TExposed, TDerives, TMethods> & TMethods;
|
|
169
|
-
as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives, TMethods> & TMethods;
|
|
170
|
-
extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object
|
|
171
|
-
extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives, TMethods> & TMethods;
|
|
294
|
+
}, TMethods, TMacros> & TMethods;
|
|
295
|
+
when<UOut extends TOut>(condition: boolean, fn: (composer: Composer<TOut, TOut, {}>) => Composer<TOut, UOut, any>): EventComposer<TBase, TEventMap, TIn, TOut & Partial<Omit<UOut, keyof TOut>>, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
296
|
+
as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives, TMethods, TMacros> & TMethods;
|
|
297
|
+
extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object>, UMacros extends MacroDefinitions = {}>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives, any, UMacros>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives, TMethods, TMacros & UMacros> & TMethods;
|
|
298
|
+
extend<UIn extends object, UOut extends UIn, UExposed extends object, UMacros extends MacroDefinitions = {}>(other: Composer<UIn, UOut, UExposed, UMacros>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives, TMethods, TMacros & UMacros> & TMethods;
|
|
299
|
+
/** Register a single named macro */
|
|
300
|
+
macro<const Name extends string, TDef extends MacroDef<any, any>>(name: Name, definition: TDef): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros & Record<Name, TDef>> & TMethods;
|
|
301
|
+
/** Register multiple macros at once */
|
|
302
|
+
macro<const TDefs extends Record<string, MacroDef<any, any>>>(definitions: TDefs): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros & TDefs> & TMethods;
|
|
172
303
|
inspect(): MiddlewareInfo[];
|
|
173
304
|
trace(handler: TraceHandler): this;
|
|
174
305
|
compose(): ComposedMiddleware<TIn>;
|
|
@@ -185,13 +316,108 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
|
|
|
185
316
|
prototype: Error;
|
|
186
317
|
}>;
|
|
187
318
|
tracer: TraceHandler | undefined;
|
|
319
|
+
macros: Record<string, MacroDef<any, any>>;
|
|
188
320
|
Derives: TDerives;
|
|
321
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
322
|
+
Out: TOut;
|
|
189
323
|
};
|
|
190
324
|
invalidate(): void;
|
|
191
325
|
}
|
|
192
326
|
interface EventComposerConstructor<TBase extends object, TEventMap extends Record<string, TBase>, TMethods extends Record<string, (...args: any[]) => any> = {}> {
|
|
193
|
-
new <TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}>(options?: ComposerOptions): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods> & TMethods;
|
|
327
|
+
new <TIn extends TBase = TBase, TOut extends TIn = TIn, TExposed extends object = {}, TDerives extends Record<string, object> = {}, TMacros extends MacroDefinitions = {}>(options?: ComposerOptions): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives, TMethods, TMacros> & TMethods;
|
|
194
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Extracts the current accumulated context type (`TOut`) from an EventComposer
|
|
331
|
+
* or Composer instance type.
|
|
332
|
+
*
|
|
333
|
+
* Use with a `this: TThis` parameter in custom methods to get automatic
|
|
334
|
+
* derive type inference — the handler receives all types from previous `.derive()` calls
|
|
335
|
+
* without requiring explicit annotation at the call site.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* const { Composer } = createComposer({
|
|
340
|
+
* methods: {
|
|
341
|
+
* // Automatic: caller's derives are inferred into the handler
|
|
342
|
+
* command<TThis>(
|
|
343
|
+
* this: TThis,
|
|
344
|
+
* name: string,
|
|
345
|
+
* handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
346
|
+
* ) {
|
|
347
|
+
* return (this as any).on("message", (ctx: any, next: any) => {
|
|
348
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
349
|
+
* return next();
|
|
350
|
+
* }) as TThis;
|
|
351
|
+
* },
|
|
352
|
+
*
|
|
353
|
+
* // Manual: caller declares which derives they need via Patch generic
|
|
354
|
+
* hears<Patch extends object = {}>(
|
|
355
|
+
* trigger: string,
|
|
356
|
+
* handler: Middleware<MessageCtx & Patch>,
|
|
357
|
+
* ) {
|
|
358
|
+
* return this.on<"message", Patch>("message", (ctx, next) => {
|
|
359
|
+
* if (ctx.text === trigger) return handler(ctx, next);
|
|
360
|
+
* return next();
|
|
361
|
+
* });
|
|
362
|
+
* },
|
|
363
|
+
* },
|
|
364
|
+
* });
|
|
365
|
+
*
|
|
366
|
+
* // With ContextOf — no type annotation needed at call site:
|
|
367
|
+
* new Composer()
|
|
368
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
369
|
+
* .command("start", (ctx) => ctx.user.id); // ✅ inferred automatically
|
|
370
|
+
*
|
|
371
|
+
* // With Patch — caller declares what they need:
|
|
372
|
+
* new Composer()
|
|
373
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
374
|
+
* .hears<{ user: { id: number } }>("hello", (ctx) => ctx.user.id); // ✅
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
type ContextOf<T> = T extends {
|
|
378
|
+
"~": {
|
|
379
|
+
Out: infer O;
|
|
380
|
+
};
|
|
381
|
+
} ? O : never;
|
|
382
|
+
/**
|
|
383
|
+
* Helper to define custom composer methods with full TypeScript inference.
|
|
384
|
+
*
|
|
385
|
+
* TypeScript cannot infer generic method signatures when they're passed directly
|
|
386
|
+
* inside `createComposer({ methods: { ... } })` because `TMethods` is buried
|
|
387
|
+
* inside the nested return type `{ Composer: EventComposerConstructor<..., TMethods> }`.
|
|
388
|
+
*
|
|
389
|
+
* This helper has return type `TMethods` directly — which lets TypeScript preserve
|
|
390
|
+
* generic method signatures. Pass the result (via `typeof`) as the 3rd type argument
|
|
391
|
+
* to `createComposer`:
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* const methods = defineComposerMethods({
|
|
396
|
+
* // Pattern: `this: TThis` + `ContextOf<TThis>` — zero annotation at call site
|
|
397
|
+
* command<TThis>(
|
|
398
|
+
* this: TThis,
|
|
399
|
+
* name: string,
|
|
400
|
+
* handler: Middleware<MessageCtx & ContextOf<TThis>>,
|
|
401
|
+
* ): TThis {
|
|
402
|
+
* return (this as any).on("message", (ctx: any, next: any) => {
|
|
403
|
+
* if (ctx.text === `/${name}`) return handler(ctx, next);
|
|
404
|
+
* return next();
|
|
405
|
+
* }) as TThis;
|
|
406
|
+
* },
|
|
407
|
+
* });
|
|
408
|
+
*
|
|
409
|
+
* const { Composer } = createComposer<Base, EventMap, typeof methods>({
|
|
410
|
+
* discriminator: (ctx) => ctx.updateType,
|
|
411
|
+
* methods,
|
|
412
|
+
* });
|
|
413
|
+
*
|
|
414
|
+
* // No annotation needed — derives flow in automatically:
|
|
415
|
+
* new Composer()
|
|
416
|
+
* .derive(() => ({ user: { id: 1 } }))
|
|
417
|
+
* .command("start", (ctx) => ctx.user.id); // ✅
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare function defineComposerMethods<TMethods extends Record<string, (...args: any[]) => any>>(methods: TMethods): TMethods;
|
|
195
421
|
/**
|
|
196
422
|
* Phantom type carrier for event map inference.
|
|
197
423
|
* Returns `undefined` at runtime — exists purely for type-level inference
|
|
@@ -220,6 +446,18 @@ declare function createComposer<TBase extends object, TEventMap extends Record<s
|
|
|
220
446
|
EventQueue: typeof EventQueue;
|
|
221
447
|
};
|
|
222
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Composes a handler with macro hooks and preHandlers from an options object.
|
|
451
|
+
*
|
|
452
|
+
* Execution order:
|
|
453
|
+
* 1. `options.preHandler` array (explicit guards — user controls order)
|
|
454
|
+
* 2. Per-macro in options property order:
|
|
455
|
+
* a. macro.preHandler (guard middleware)
|
|
456
|
+
* b. macro.derive (context enrichment; void return = stop chain)
|
|
457
|
+
* 3. Main handler
|
|
458
|
+
*/
|
|
459
|
+
declare function buildFromOptions<TCtx>(macros: Record<string, MacroDef<any, any>>, options: Record<string, unknown> | undefined, handler: Middleware<TCtx>): Middleware<TCtx>;
|
|
460
|
+
|
|
223
461
|
/** No-op next function: () => Promise.resolve() */
|
|
224
462
|
declare const noopNext: Next;
|
|
225
463
|
/** Pass-through middleware: calls next() immediately */
|
|
@@ -227,5 +465,5 @@ declare const skip: Middleware<any>;
|
|
|
227
465
|
/** Terminal middleware: does NOT call next() */
|
|
228
466
|
declare const stop: Middleware<any>;
|
|
229
467
|
|
|
230
|
-
export { Composer, EventQueue, compose, createComposer, eventTypes, noopNext, skip, stop };
|
|
231
|
-
export type { ComposedMiddleware, ComposerOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, LazyFactory, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler };
|
|
468
|
+
export { Composer, EventQueue, buildFromOptions, compose, createComposer, defineComposerMethods, eventTypes, noopNext, skip, stop };
|
|
469
|
+
export type { CompatibleEvents, ComposedMiddleware, ComposerLike, ComposerOptions, ContextCallback, ContextOf, DeriveFromOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, HandlerOptions, LazyFactory, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler, WithCtx };
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,10 @@ class Composer {
|
|
|
76
76
|
name: void 0,
|
|
77
77
|
seed: void 0,
|
|
78
78
|
errorsDefinitions: {},
|
|
79
|
-
tracer: void 0
|
|
79
|
+
tracer: void 0,
|
|
80
|
+
macros: {},
|
|
81
|
+
/** Phantom type accessor — never set at runtime, used by `ContextOf<T>` */
|
|
82
|
+
Out: void 0
|
|
80
83
|
};
|
|
81
84
|
constructor(options) {
|
|
82
85
|
this["~"].name = options?.name;
|
|
@@ -85,6 +88,14 @@ class Composer {
|
|
|
85
88
|
invalidate() {
|
|
86
89
|
this["~"].compiled = null;
|
|
87
90
|
}
|
|
91
|
+
macro(nameOrDefs, definition) {
|
|
92
|
+
if (typeof nameOrDefs === "string") {
|
|
93
|
+
this["~"].macros[nameOrDefs] = definition;
|
|
94
|
+
} else {
|
|
95
|
+
Object.assign(this["~"].macros, nameOrDefs);
|
|
96
|
+
}
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
88
99
|
decorate(values, options) {
|
|
89
100
|
const mw = (ctx, next) => {
|
|
90
101
|
Object.assign(ctx, values);
|
|
@@ -96,6 +107,7 @@ class Composer {
|
|
|
96
107
|
this.invalidate();
|
|
97
108
|
return this;
|
|
98
109
|
}
|
|
110
|
+
// biome-ignore lint/suspicious/noExplicitAny: overload implementation signature
|
|
99
111
|
use(...middleware) {
|
|
100
112
|
for (const fn of middleware) {
|
|
101
113
|
this["~"].middlewares.push({ fn, scope: "local", type: "use", name: fn.name || void 0 });
|
|
@@ -313,6 +325,7 @@ class Composer {
|
|
|
313
325
|
this["~"].extended.add(key);
|
|
314
326
|
}
|
|
315
327
|
Object.assign(this["~"].errorsDefinitions, other["~"].errorsDefinitions);
|
|
328
|
+
Object.assign(this["~"].macros, other["~"].macros);
|
|
316
329
|
this["~"].onErrors.push(...other["~"].onErrors);
|
|
317
330
|
const pluginName = other["~"].name;
|
|
318
331
|
const isNew = (m) => {
|
|
@@ -472,19 +485,36 @@ class EventQueue {
|
|
|
472
485
|
}
|
|
473
486
|
}
|
|
474
487
|
|
|
488
|
+
function defineComposerMethods(methods) {
|
|
489
|
+
return methods;
|
|
490
|
+
}
|
|
475
491
|
function eventTypes() {
|
|
476
492
|
return void 0;
|
|
477
493
|
}
|
|
478
494
|
function createComposer(config) {
|
|
479
495
|
class EventComposerImpl extends Composer {
|
|
480
|
-
on(
|
|
481
|
-
|
|
496
|
+
on(eventOrFilter, filterOrHandler, handler) {
|
|
497
|
+
if (typeof eventOrFilter === "function") {
|
|
498
|
+
const filter2 = eventOrFilter;
|
|
499
|
+
const actualHandler2 = filterOrHandler;
|
|
500
|
+
const filterLabel = filter2.name || "filter";
|
|
501
|
+
const mw2 = (ctx, next) => {
|
|
502
|
+
if (filter2(ctx)) return actualHandler2(ctx, next);
|
|
503
|
+
return next();
|
|
504
|
+
};
|
|
505
|
+
nameMiddleware(mw2, "on", filterLabel);
|
|
506
|
+
this["~"].middlewares.push({ fn: mw2, scope: "local", type: "on", name: filterLabel });
|
|
507
|
+
this.invalidate();
|
|
508
|
+
return this;
|
|
509
|
+
}
|
|
510
|
+
const events = Array.isArray(eventOrFilter) ? eventOrFilter : [eventOrFilter];
|
|
482
511
|
const eventLabel = events.join("|");
|
|
512
|
+
const actualHandler = handler ?? filterOrHandler;
|
|
513
|
+
const filter = handler ? filterOrHandler : void 0;
|
|
483
514
|
const mw = (ctx, next) => {
|
|
484
|
-
if (events.includes(config.discriminator(ctx)))
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return next();
|
|
515
|
+
if (!events.includes(config.discriminator(ctx))) return next();
|
|
516
|
+
if (filter && !filter(ctx)) return next();
|
|
517
|
+
return actualHandler(ctx, next);
|
|
488
518
|
};
|
|
489
519
|
nameMiddleware(mw, "on", eventLabel);
|
|
490
520
|
this["~"].middlewares.push({ fn: mw, scope: "local", type: "on", name: eventLabel });
|
|
@@ -532,4 +562,44 @@ function createComposer(config) {
|
|
|
532
562
|
};
|
|
533
563
|
}
|
|
534
564
|
|
|
535
|
-
|
|
565
|
+
function buildFromOptions(macros, options, handler) {
|
|
566
|
+
if (!options) return handler;
|
|
567
|
+
const chain = [];
|
|
568
|
+
const preHandler = options.preHandler;
|
|
569
|
+
if (preHandler) {
|
|
570
|
+
if (Array.isArray(preHandler)) {
|
|
571
|
+
chain.push(...preHandler);
|
|
572
|
+
} else {
|
|
573
|
+
chain.push(preHandler);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const key of Object.keys(options)) {
|
|
577
|
+
if (key === "preHandler") continue;
|
|
578
|
+
const value = options[key];
|
|
579
|
+
if (value === false || value == null) continue;
|
|
580
|
+
const def = macros[key];
|
|
581
|
+
if (!def) continue;
|
|
582
|
+
const hooks = typeof def === "function" ? def(value === true ? void 0 : value) : def;
|
|
583
|
+
if (hooks.preHandler) {
|
|
584
|
+
if (Array.isArray(hooks.preHandler)) {
|
|
585
|
+
chain.push(...hooks.preHandler);
|
|
586
|
+
} else {
|
|
587
|
+
chain.push(hooks.preHandler);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (hooks.derive) {
|
|
591
|
+
const deriveFn = hooks.derive;
|
|
592
|
+
chain.push(async (ctx, next) => {
|
|
593
|
+
const derived = await deriveFn(ctx);
|
|
594
|
+
if (derived == null) return;
|
|
595
|
+
Object.assign(ctx, derived);
|
|
596
|
+
return next();
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
chain.push(handler);
|
|
601
|
+
if (chain.length === 1) return chain[0];
|
|
602
|
+
return compose(chain);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export { Composer, EventQueue, buildFromOptions, compose, createComposer, defineComposerMethods, eventTypes, noopNext, skip, stop };
|