@gramio/composer 0.1.1 → 0.2.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 CHANGED
@@ -305,6 +305,52 @@ const app = new Composer()
305
305
  });
306
306
  ```
307
307
 
308
+ #### `types` + `eventTypes()` — phantom type inference
309
+
310
+ 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:
311
+
312
+ ```ts
313
+ import { createComposer, eventTypes } from "@gramio/composer";
314
+
315
+ // eventTypes<T>() returns undefined at runtime — purely for inference
316
+ const { Composer } = createComposer({
317
+ discriminator: (ctx: BaseCtx) => ctx.updateType,
318
+ types: eventTypes<{ message: MessageCtx; callback_query: CallbackCtx }>(),
319
+ });
320
+ // TBase inferred from discriminator, TEventMap inferred from types
321
+ ```
322
+
323
+ #### `methods` — custom prototype methods
324
+
325
+ Inject framework-specific DX sugar directly onto the Composer prototype via the `methods` config option. Method bodies receive `this` typed as the full `EventComposer`, giving access to `.on()`, `.use()`, `.derive()`, etc.
326
+
327
+ ```ts
328
+ const { Composer } = createComposer({
329
+ discriminator: (ctx: BaseCtx) => ctx.updateType,
330
+ types: eventTypes<{ message: MessageCtx }>(),
331
+ methods: {
332
+ hears(trigger: RegExp | string, handler: (ctx: MessageCtx) => unknown) {
333
+ return this.on("message", (ctx, next) => {
334
+ const text = ctx.text;
335
+ if (
336
+ (typeof trigger === "string" && text === trigger) ||
337
+ (trigger instanceof RegExp && text && trigger.test(text))
338
+ ) {
339
+ return handler(ctx);
340
+ }
341
+ return next();
342
+ });
343
+ },
344
+ },
345
+ });
346
+
347
+ const bot = new Composer();
348
+ bot.hears(/hello/, handler); // custom method
349
+ bot.on("message", h).hears(/hi/, h2); // chaining works — TMethods preserved
350
+ ```
351
+
352
+ 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 (e.g. `on`, `use`, `derive`).
353
+
308
354
  ### `EventQueue`
309
355
 
310
356
  Concurrent event queue with graceful shutdown.
package/dist/index.cjs CHANGED
@@ -475,6 +475,9 @@ class EventQueue {
475
475
  }
476
476
  }
477
477
 
478
+ function eventTypes() {
479
+ return void 0;
480
+ }
478
481
  function createComposer(config) {
479
482
  class EventComposerImpl extends Composer {
480
483
  on(event, handler) {
@@ -512,6 +515,19 @@ function createComposer(config) {
512
515
  return this;
513
516
  }
514
517
  }
518
+ if (config.methods) {
519
+ for (const [name, fn] of Object.entries(config.methods)) {
520
+ if (name in EventComposerImpl.prototype) {
521
+ throw new Error(`Custom method "${name}" conflicts with built-in method`);
522
+ }
523
+ Object.defineProperty(EventComposerImpl.prototype, name, {
524
+ value: fn,
525
+ writable: true,
526
+ configurable: true,
527
+ enumerable: false
528
+ });
529
+ }
530
+ }
515
531
  return {
516
532
  Composer: EventComposerImpl,
517
533
  compose,
@@ -523,6 +539,7 @@ exports.Composer = Composer;
523
539
  exports.EventQueue = EventQueue;
524
540
  exports.compose = compose;
525
541
  exports.createComposer = createComposer;
542
+ exports.eventTypes = eventTypes;
526
543
  exports.noopNext = noopNext;
527
544
  exports.skip = skip;
528
545
  exports.stop = stop;
package/dist/index.d.cts CHANGED
@@ -136,39 +136,39 @@ declare class EventQueue<T> {
136
136
  * Array of events → discriminated union where each branch has correct derives.
137
137
  */
138
138
  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;
139
- /** EventComposer interface — Composer + .on() + per-event derive tracking */
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> = {}> {
141
- on<E extends keyof TEventMap & string>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
142
- decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives>;
139
+ /** 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<E extends keyof TEventMap & string>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): this;
142
+ use(...middleware: Middleware<TOut>[]): this;
143
+ guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): this;
144
+ branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): this;
145
+ 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;
146
+ route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, builder: (route: RouteBuilder<TOut, K>) => void): this;
147
+ route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>>>, fallback?: Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>): this;
148
+ fork(...middleware: Middleware<TOut>[]): this;
149
+ tap(...middleware: Middleware<TOut>[]): this;
150
+ lazy(factory: LazyFactory<TOut>): this;
151
+ onError(handler: ErrorHandler<TOut>): this;
152
+ error(kind: string, errorClass: {
153
+ new (...args: any): any;
154
+ prototype: Error;
155
+ }): this;
156
+ 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;
143
158
  decorate<D extends object>(values: D, options: {
144
159
  as: "scoped" | "global";
145
- }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives>;
146
- use(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
147
- derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives>;
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;
148
162
  derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
149
163
  as: "scoped" | "global";
150
- }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives>;
164
+ }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
151
165
  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 & {
152
166
  [K in E]: D;
153
- }>;
154
- guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
155
- branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
156
- 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>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
157
- route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, builder: (route: RouteBuilder<TOut, K>) => void): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
158
- route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>>>, fallback?: Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
159
- fork(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
160
- tap(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
161
- lazy(factory: LazyFactory<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
162
- onError(handler: ErrorHandler<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
163
- 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>;
164
- error(kind: string, errorClass: {
165
- new (...args: any): any;
166
- prototype: Error;
167
- }): this;
168
- as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives>;
169
- group(fn: (composer: Composer<TOut, TOut, {}>) => void): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
170
- extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object>>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives>;
171
- extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives>;
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>>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives, any>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives, TMethods> & TMethods;
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;
172
172
  inspect(): MiddlewareInfo[];
173
173
  trace(handler: TraceHandler): this;
174
174
  compose(): ComposedMiddleware<TIn>;
@@ -189,16 +189,33 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
189
189
  };
190
190
  invalidate(): void;
191
191
  }
192
- interface EventComposerConstructor<TBase extends object, TEventMap extends Record<string, TBase>> {
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>;
192
+ 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;
194
194
  }
195
+ /**
196
+ * Phantom type carrier for event map inference.
197
+ * Returns `undefined` at runtime — exists purely for type-level inference
198
+ * so that `TEventMap` can be inferred from the `types` config field.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * const { Composer } = createComposer({
203
+ * discriminator: (ctx: BaseCtx) => ctx.updateType,
204
+ * types: eventTypes<EventMap>(),
205
+ * methods: { hears(trigger) { return this.on("message", ...); } },
206
+ * });
207
+ * ```
208
+ */
209
+ declare function eventTypes<TEventMap extends Record<string, any>>(): TEventMap;
195
210
  /**
196
211
  * Creates a configured Composer class with type-safe .on() event discrimination.
197
212
  */
198
- declare function createComposer<TBase extends object, TEventMap extends Record<string, TBase> = {}>(config: {
213
+ declare function createComposer<TBase extends object, TEventMap extends Record<string, TBase> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}>(config: {
199
214
  discriminator: (context: TBase) => string;
215
+ types?: TEventMap;
216
+ methods?: TMethods & ThisType<EventComposer<TBase, TEventMap, TBase, TBase, {}, {}, TMethods> & TMethods>;
200
217
  }): {
201
- Composer: EventComposerConstructor<TBase, TEventMap>;
218
+ Composer: EventComposerConstructor<TBase, TEventMap, TMethods>;
202
219
  compose: typeof compose;
203
220
  EventQueue: typeof EventQueue;
204
221
  };
@@ -210,5 +227,5 @@ declare const skip: Middleware<any>;
210
227
  /** Terminal middleware: does NOT call next() */
211
228
  declare const stop: Middleware<any>;
212
229
 
213
- export { Composer, EventQueue, compose, createComposer, noopNext, skip, stop };
230
+ export { Composer, EventQueue, compose, createComposer, eventTypes, noopNext, skip, stop };
214
231
  export type { ComposedMiddleware, ComposerOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, LazyFactory, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler };
package/dist/index.d.ts CHANGED
@@ -136,39 +136,39 @@ declare class EventQueue<T> {
136
136
  * Array of events → discriminated union where each branch has correct derives.
137
137
  */
138
138
  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;
139
- /** EventComposer interface — Composer + .on() + per-event derive tracking */
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> = {}> {
141
- on<E extends keyof TEventMap & string>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
142
- decorate<D extends object>(values: D): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives>;
139
+ /** 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<E extends keyof TEventMap & string>(event: MaybeArray<E>, handler: Middleware<ResolveEventCtx<TOut, TEventMap, TDerives, E>>): this;
142
+ use(...middleware: Middleware<TOut>[]): this;
143
+ guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): this;
144
+ branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): this;
145
+ 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;
146
+ route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, builder: (route: RouteBuilder<TOut, K>) => void): this;
147
+ route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>>>, fallback?: Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>): this;
148
+ fork(...middleware: Middleware<TOut>[]): this;
149
+ tap(...middleware: Middleware<TOut>[]): this;
150
+ lazy(factory: LazyFactory<TOut>): this;
151
+ onError(handler: ErrorHandler<TOut>): this;
152
+ error(kind: string, errorClass: {
153
+ new (...args: any): any;
154
+ prototype: Error;
155
+ }): this;
156
+ 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;
143
158
  decorate<D extends object>(values: D, options: {
144
159
  as: "scoped" | "global";
145
- }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives>;
146
- use(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
147
- derive<D extends object>(handler: DeriveHandler<TOut, D>): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed, TDerives>;
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;
148
162
  derive<D extends object>(handler: DeriveHandler<TOut, D>, options: {
149
163
  as: "scoped" | "global";
150
- }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives>;
164
+ }): EventComposer<TBase, TEventMap, TIn, TOut & D, TExposed & D, TDerives, TMethods> & TMethods;
151
165
  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 & {
152
166
  [K in E]: D;
153
- }>;
154
- guard<S extends TOut>(predicate: ((context: TOut) => context is S) | ((context: TOut) => boolean | Promise<boolean>), ...middleware: Middleware<any>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
155
- branch(predicate: ((context: TOut) => boolean | Promise<boolean>) | boolean, onTrue: Middleware<TOut>, onFalse?: Middleware<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
156
- 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>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
157
- route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, builder: (route: RouteBuilder<TOut, K>) => void): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
158
- route<K extends string>(router: (context: TOut) => K | undefined | Promise<K | undefined>, cases: Partial<Record<K, Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>>>, fallback?: Middleware<TOut> | Middleware<TOut>[] | Composer<any, any, any>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
159
- fork(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
160
- tap(...middleware: Middleware<TOut>[]): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
161
- lazy(factory: LazyFactory<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
162
- onError(handler: ErrorHandler<TOut>): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
163
- 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>;
164
- error(kind: string, errorClass: {
165
- new (...args: any): any;
166
- prototype: Error;
167
- }): this;
168
- as(scope: "scoped" | "global"): EventComposer<TBase, TEventMap, TIn, TOut, TOut, TDerives>;
169
- group(fn: (composer: Composer<TOut, TOut, {}>) => void): EventComposer<TBase, TEventMap, TIn, TOut, TExposed, TDerives>;
170
- extend<UIn extends TBase, UOut extends UIn, UExposed extends object, UDerives extends Record<string, object>>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives>;
171
- extend<UIn extends object, UOut extends UIn, UExposed extends object>(other: Composer<UIn, UOut, UExposed>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives>;
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>>(other: EventComposer<TBase, TEventMap, UIn, UOut, UExposed, UDerives, any>): EventComposer<TBase, TEventMap, TIn, TOut & UExposed, TExposed, TDerives & UDerives, TMethods> & TMethods;
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;
172
172
  inspect(): MiddlewareInfo[];
173
173
  trace(handler: TraceHandler): this;
174
174
  compose(): ComposedMiddleware<TIn>;
@@ -189,16 +189,33 @@ interface EventComposer<TBase extends object, TEventMap extends Record<string, T
189
189
  };
190
190
  invalidate(): void;
191
191
  }
192
- interface EventComposerConstructor<TBase extends object, TEventMap extends Record<string, TBase>> {
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>;
192
+ 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;
194
194
  }
195
+ /**
196
+ * Phantom type carrier for event map inference.
197
+ * Returns `undefined` at runtime — exists purely for type-level inference
198
+ * so that `TEventMap` can be inferred from the `types` config field.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * const { Composer } = createComposer({
203
+ * discriminator: (ctx: BaseCtx) => ctx.updateType,
204
+ * types: eventTypes<EventMap>(),
205
+ * methods: { hears(trigger) { return this.on("message", ...); } },
206
+ * });
207
+ * ```
208
+ */
209
+ declare function eventTypes<TEventMap extends Record<string, any>>(): TEventMap;
195
210
  /**
196
211
  * Creates a configured Composer class with type-safe .on() event discrimination.
197
212
  */
198
- declare function createComposer<TBase extends object, TEventMap extends Record<string, TBase> = {}>(config: {
213
+ declare function createComposer<TBase extends object, TEventMap extends Record<string, TBase> = {}, TMethods extends Record<string, (...args: any[]) => any> = {}>(config: {
199
214
  discriminator: (context: TBase) => string;
215
+ types?: TEventMap;
216
+ methods?: TMethods & ThisType<EventComposer<TBase, TEventMap, TBase, TBase, {}, {}, TMethods> & TMethods>;
200
217
  }): {
201
- Composer: EventComposerConstructor<TBase, TEventMap>;
218
+ Composer: EventComposerConstructor<TBase, TEventMap, TMethods>;
202
219
  compose: typeof compose;
203
220
  EventQueue: typeof EventQueue;
204
221
  };
@@ -210,5 +227,5 @@ declare const skip: Middleware<any>;
210
227
  /** Terminal middleware: does NOT call next() */
211
228
  declare const stop: Middleware<any>;
212
229
 
213
- export { Composer, EventQueue, compose, createComposer, noopNext, skip, stop };
230
+ export { Composer, EventQueue, compose, createComposer, eventTypes, noopNext, skip, stop };
214
231
  export type { ComposedMiddleware, ComposerOptions, DeriveHandler, ErrorHandler, EventComposer, EventComposerConstructor, LazyFactory, MaybeArray, Middleware, MiddlewareInfo, MiddlewareType, Next, RouteBuilder, RouteHandler, Scope, TraceHandler };
package/dist/index.js CHANGED
@@ -472,6 +472,9 @@ class EventQueue {
472
472
  }
473
473
  }
474
474
 
475
+ function eventTypes() {
476
+ return void 0;
477
+ }
475
478
  function createComposer(config) {
476
479
  class EventComposerImpl extends Composer {
477
480
  on(event, handler) {
@@ -509,6 +512,19 @@ function createComposer(config) {
509
512
  return this;
510
513
  }
511
514
  }
515
+ if (config.methods) {
516
+ for (const [name, fn] of Object.entries(config.methods)) {
517
+ if (name in EventComposerImpl.prototype) {
518
+ throw new Error(`Custom method "${name}" conflicts with built-in method`);
519
+ }
520
+ Object.defineProperty(EventComposerImpl.prototype, name, {
521
+ value: fn,
522
+ writable: true,
523
+ configurable: true,
524
+ enumerable: false
525
+ });
526
+ }
527
+ }
512
528
  return {
513
529
  Composer: EventComposerImpl,
514
530
  compose,
@@ -516,4 +532,4 @@ function createComposer(config) {
516
532
  };
517
533
  }
518
534
 
519
- export { Composer, EventQueue, compose, createComposer, noopNext, skip, stop };
535
+ export { Composer, EventQueue, compose, createComposer, eventTypes, noopNext, skip, stop };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gramio/composer",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "General-purpose, type-safe middleware composition library for TypeScript",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",