@gramio/composer 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -521,6 +521,63 @@ command<TThis extends ComposerLike<TThis>>(
521
521
  ): TThis
522
522
  ```
523
523
 
524
+ #### `EventContextOf<T, E>` — extract context for a specific event (global + per-event derives)
525
+
526
+ Like `ContextOf<T>`, but also includes per-event derives registered via `derive(event, handler)`.
527
+
528
+ | | Includes |
529
+ |---|---|
530
+ | `ContextOf<TThis>` | `TOut` — global derives only |
531
+ | `EventContextOf<TThis, "message">` | `TOut & TDerives["message"]` — global **and** per-event derives |
532
+
533
+ **Why it matters:** when a custom method always routes to a specific event (e.g. `command` → `"message"`), its handler should see per-event derives too. With `ContextOf` alone, a `derive("message", ...)` plugin's types are invisible inside the handler even though the value is there at runtime.
534
+
535
+ ```ts
536
+ import { createComposer, defineComposerMethods, eventTypes } from "@gramio/composer";
537
+ import type { ComposerLike, EventContextOf, Middleware } from "@gramio/composer";
538
+
539
+ const methods = defineComposerMethods({
540
+ command<TThis extends ComposerLike<TThis>>(
541
+ this: TThis,
542
+ name: string,
543
+ // ↓ EventContextOf instead of ContextOf
544
+ handler: Middleware<MessageCtx & EventContextOf<TThis, "message">>,
545
+ ): TThis {
546
+ return this.on("message", (ctx: any, next: Next) => {
547
+ if (ctx.text === `/${name}`) return handler(ctx, next);
548
+ return next();
549
+ });
550
+ },
551
+ });
552
+
553
+ const { Composer } = createComposer<BaseCtx, { message: MessageCtx }, typeof methods>({
554
+ discriminator: (ctx) => ctx.updateType,
555
+ methods,
556
+ });
557
+
558
+ // Per-event plugin that adds `t` only for message events:
559
+ const i18nPlugin = new Composer({ name: "i18n" })
560
+ .derive("message", (ctx) => ({
561
+ t: i18n.buildT(ctx.from?.languageCode ?? "en"),
562
+ }))
563
+ .as("scoped");
564
+
565
+ new Composer()
566
+ .extend(i18nPlugin)
567
+ .command("start", (ctx, next) => {
568
+ ctx.t("Hello"); // ✅ typed — EventContextOf sees TDerives["message"]
569
+ ctx.text; // ✅ string | undefined — from MessageCtx
570
+ return next();
571
+ })
572
+ .on("message", (ctx, next) => {
573
+ ctx.t("Hi"); // ✅ also works here via ResolveEventCtx
574
+ return next();
575
+ });
576
+ ```
577
+
578
+ > [!NOTE]
579
+ > If the derive is registered globally (`.derive(() => ...)` without an event name), both `ContextOf` and `EventContextOf` will see it. Per-event derives (`derive("message", ...)`) are only visible through `EventContextOf` in custom method signatures, or directly inside `.on("message", ...)` handlers.
580
+
524
581
  #### `ComposerLike<T>` — minimal structural type for `this` constraints
525
582
 
526
583
  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.
package/dist/index.cjs CHANGED
@@ -313,7 +313,10 @@ class Composer {
313
313
  for (const key of preKeys) snapshot[key] = ctx[key];
314
314
  await chain(ctx, noopNext);
315
315
  for (const key of Object.keys(ctx)) {
316
- if (!preKeys.has(key)) delete ctx[key];
316
+ if (!preKeys.has(key)) {
317
+ const desc = Object.getOwnPropertyDescriptor(ctx, key);
318
+ if (desc?.configurable) delete ctx[key];
319
+ }
317
320
  }
318
321
  Object.assign(ctx, snapshot);
319
322
  return next();
@@ -355,7 +358,10 @@ class Composer {
355
358
  for (const key of preKeys) snapshot[key] = ctx[key];
356
359
  await chain(ctx, noopNext);
357
360
  for (const key of Object.keys(ctx)) {
358
- if (!preKeys.has(key)) delete ctx[key];
361
+ if (!preKeys.has(key)) {
362
+ const desc = Object.getOwnPropertyDescriptor(ctx, key);
363
+ if (desc?.configurable) delete ctx[key];
364
+ }
359
365
  }
360
366
  Object.assign(ctx, snapshot);
361
367
  return next();
package/dist/index.d.cts CHANGED
@@ -379,6 +379,40 @@ type ContextOf<T> = T extends {
379
379
  Out: infer O;
380
380
  };
381
381
  } ? O : never;
382
+ /**
383
+ * Extracts the context type for a specific event from an EventComposer instance,
384
+ * combining global `TOut` (like `ContextOf`) **and** per-event `TDerives[E]`.
385
+ *
386
+ * Use this in custom event-specific methods (e.g. `command`, `hears`) instead of
387
+ * `ContextOf<T>` so that handlers registered via `derive(event, handler)` are
388
+ * visible in the handler's type:
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * const methods = defineComposerMethods({
393
+ * command<TThis extends ComposerLike<TThis>>(
394
+ * this: TThis,
395
+ * name: string,
396
+ * handler: Middleware<MsgCtx & EventContextOf<TThis, "message">>,
397
+ * ): TThis {
398
+ * return this.on("message", (ctx, next) => {
399
+ * if (ctx.text === `/${name}`) return handler(ctx, next);
400
+ * return next();
401
+ * });
402
+ * },
403
+ * });
404
+ *
405
+ * new Composer()
406
+ * .derive("message", () => ({ t: (s: string) => s }))
407
+ * .command("start", (ctx) => ctx.t("Hi!")); // ✅ t is visible
408
+ * ```
409
+ */
410
+ type EventContextOf<T, E extends string> = T extends {
411
+ "~": {
412
+ Out: infer O extends object;
413
+ Derives: infer D extends Record<string, object>;
414
+ };
415
+ } ? O & (E extends keyof D ? D[E] : {}) : never;
382
416
  /**
383
417
  * Helper to define custom composer methods with full TypeScript inference.
384
418
  *
@@ -466,4 +500,4 @@ declare const skip: Middleware<any>;
466
500
  declare const stop: Middleware<any>;
467
501
 
468
502
  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 };
503
+ export type { CompatibleEvents, ComposedMiddleware, ComposerLike, ComposerOptions, ContextCallback, ContextOf, DeriveFromOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, EventContextOf, HandlerOptions, LazyFactory, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler, WithCtx };
package/dist/index.d.ts CHANGED
@@ -379,6 +379,40 @@ type ContextOf<T> = T extends {
379
379
  Out: infer O;
380
380
  };
381
381
  } ? O : never;
382
+ /**
383
+ * Extracts the context type for a specific event from an EventComposer instance,
384
+ * combining global `TOut` (like `ContextOf`) **and** per-event `TDerives[E]`.
385
+ *
386
+ * Use this in custom event-specific methods (e.g. `command`, `hears`) instead of
387
+ * `ContextOf<T>` so that handlers registered via `derive(event, handler)` are
388
+ * visible in the handler's type:
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * const methods = defineComposerMethods({
393
+ * command<TThis extends ComposerLike<TThis>>(
394
+ * this: TThis,
395
+ * name: string,
396
+ * handler: Middleware<MsgCtx & EventContextOf<TThis, "message">>,
397
+ * ): TThis {
398
+ * return this.on("message", (ctx, next) => {
399
+ * if (ctx.text === `/${name}`) return handler(ctx, next);
400
+ * return next();
401
+ * });
402
+ * },
403
+ * });
404
+ *
405
+ * new Composer()
406
+ * .derive("message", () => ({ t: (s: string) => s }))
407
+ * .command("start", (ctx) => ctx.t("Hi!")); // ✅ t is visible
408
+ * ```
409
+ */
410
+ type EventContextOf<T, E extends string> = T extends {
411
+ "~": {
412
+ Out: infer O extends object;
413
+ Derives: infer D extends Record<string, object>;
414
+ };
415
+ } ? O & (E extends keyof D ? D[E] : {}) : never;
382
416
  /**
383
417
  * Helper to define custom composer methods with full TypeScript inference.
384
418
  *
@@ -466,4 +500,4 @@ declare const skip: Middleware<any>;
466
500
  declare const stop: Middleware<any>;
467
501
 
468
502
  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 };
503
+ export type { CompatibleEvents, ComposedMiddleware, ComposerLike, ComposerOptions, ContextCallback, ContextOf, DeriveFromOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, EventContextOf, HandlerOptions, LazyFactory, MacroDef, MacroDefinitions, MacroDeriveType, MacroHooks, MacroOptionType, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler, WithCtx };
package/dist/index.js CHANGED
@@ -310,7 +310,10 @@ class Composer {
310
310
  for (const key of preKeys) snapshot[key] = ctx[key];
311
311
  await chain(ctx, noopNext);
312
312
  for (const key of Object.keys(ctx)) {
313
- if (!preKeys.has(key)) delete ctx[key];
313
+ if (!preKeys.has(key)) {
314
+ const desc = Object.getOwnPropertyDescriptor(ctx, key);
315
+ if (desc?.configurable) delete ctx[key];
316
+ }
314
317
  }
315
318
  Object.assign(ctx, snapshot);
316
319
  return next();
@@ -352,7 +355,10 @@ class Composer {
352
355
  for (const key of preKeys) snapshot[key] = ctx[key];
353
356
  await chain(ctx, noopNext);
354
357
  for (const key of Object.keys(ctx)) {
355
- if (!preKeys.has(key)) delete ctx[key];
358
+ if (!preKeys.has(key)) {
359
+ const desc = Object.getOwnPropertyDescriptor(ctx, key);
360
+ if (desc?.configurable) delete ctx[key];
361
+ }
356
362
  }
357
363
  Object.assign(ctx, snapshot);
358
364
  return next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gramio/composer",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "General-purpose, type-safe middleware composition library for TypeScript",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",