@@ -89,7 +89,7 @@ export type NamespacedName<Namespace extends string = string> =
89
89
type DelegatedMessenger <
90
90
Action extends ActionConstraint ,
91
91
Event extends EventConstraint ,
92
- Namespace extends string ,
92
+ Namespace extends string = string ,
93
93
> = Pick <
94
94
Messenger < Action , Event , Namespace > ,
95
95
| 'publishDelegated'
@@ -121,15 +121,21 @@ export class Messenger<
121
121
122
122
readonly #events = new Map < Event [ 'type' ] , EventSubscriptionMap < Event > > ( ) ;
123
123
124
- readonly #delegatedEventSubscriptions = new Map <
124
+ /**
125
+ * The set of messengers we've delegated events to, by event type.
126
+ */
127
+ readonly #subscriptionDelegationTargets = new Map <
125
128
Event [ 'type' ] ,
126
129
Map <
127
130
DelegatedMessenger < Action , Event > ,
128
131
ExtractEventHandler < Event , Event [ 'type' ] >
129
132
>
130
133
> ( ) ;
131
134
132
- readonly #delegatedActionHandlers = new Map <
135
+ /**
136
+ * The set of messengers we've delegated actions to, by action type.
137
+ */
138
+ readonly #actionDelegationTargets = new Map <
133
139
Action [ 'type' ] ,
134
140
Set < DelegatedMessenger < Action , Event > >
135
141
> ( ) ;
@@ -172,7 +178,6 @@ export class Messenger<
172
178
* @throws Will throw when a handler has been registered for this action type already.
173
179
* @template ActionType - A type union of Action type strings.
174
180
*/
175
-
176
181
registerActionHandler <
177
182
ActionType extends Action [ 'type' ] & NamespacedName < Namespace > ,
178
183
> ( actionType : ActionType , handler : ActionHandler < Action , ActionType > ) {
@@ -260,14 +265,14 @@ export class Messenger<
260
265
actionType : ActionType ,
261
266
) {
262
267
this . #actions. delete ( actionType ) ;
263
- const delegatedMessengers = this . #delegatedActionHandlers . get ( actionType ) ;
264
- if ( ! delegatedMessengers ) {
268
+ const delegationTargets = this . #actionDelegationTargets . get ( actionType ) ;
269
+ if ( ! delegationTargets ) {
265
270
return ;
266
271
}
267
- for ( const messenger of delegatedMessengers ) {
272
+ for ( const messenger of delegationTargets ) {
268
273
messenger . unregisterDelegatedActionHandler ( actionType ) ;
269
274
}
270
- this . #delegatedActionHandlers . delete ( actionType ) ;
275
+ this . #actionDelegationTargets . delete ( actionType ) ;
271
276
}
272
277
273
278
/**
@@ -317,7 +322,49 @@ export class Messenger<
317
322
* @param args.eventType - The event type to register a payload for.
318
323
* @param args.getPayload - A function for retrieving the event payload.
319
324
*/
320
- registerInitialEventPayload < EventType extends Event [ 'type' ] > ( {
325
+ registerInitialEventPayload <
326
+ EventType extends Event [ 'type' ] & NamespacedName < Namespace > ,
327
+ > ( {
328
+ eventType,
329
+ getPayload,
330
+ } : {
331
+ eventType : EventType ;
332
+ getPayload : ( ) => ExtractEventPayload < Event , EventType > ;
333
+ } ) {
334
+ /* istanbul ignore if */ // Branch unreachable with valid types
335
+ if ( ! this . #isInCurrentNamespace( eventType ) ) {
336
+ throw new Error (
337
+ `Only allowed registering initial payloads for events prefixed by '${
338
+ this . #namespace
339
+ } :'`,
340
+ ) ;
341
+ }
342
+ this . #registerInitialEventPayload( { eventType, getPayload } ) ;
343
+ }
344
+
345
+ /**
346
+ * Register a function for getting the initial payload for a delegated event.
347
+ *
348
+ * This is used for events that represent a state change, where the payload is the state.
349
+ * Registering a function for getting the payload allows event selectors to have a point of
350
+ * comparison the first time state changes.
351
+ *
352
+ * @deprecated Do not call this directly, instead use the `delegate` method.
353
+ * @param args - The arguments to this function
354
+ * @param args.eventType - The event type to register a payload for.
355
+ * @param args.getPayload - A function for retrieving the event payload.
356
+ */
357
+ registerDelegatedInitialEventPayload < EventType extends Event [ 'type' ] > ( {
358
+ eventType,
359
+ getPayload,
360
+ } : {
361
+ eventType : EventType ;
362
+ getPayload : ( ) => ExtractEventPayload < Event , EventType > ;
363
+ } ) {
364
+ this . #registerInitialEventPayload( { eventType, getPayload } ) ;
365
+ }
366
+
367
+ #registerInitialEventPayload< EventType extends Event [ 'type' ] > ( {
321
368
eventType,
322
369
getPayload,
323
370
} : {
@@ -341,6 +388,40 @@ export class Messenger<
341
388
* @template EventType - A type union of Event type strings.
342
389
*/
343
390
publish < EventType extends Event [ 'type' ] > (
391
+ eventType : EventType & NamespacedName < Namespace > ,
392
+ ...payload : ExtractEventPayload < Event , EventType >
393
+ ) {
394
+ /* istanbul ignore if */ // Branch unreachable with valid types
395
+ if ( ! this . #isInCurrentNamespace( eventType ) ) {
396
+ throw new Error (
397
+ `Only allowed publishing events prefixed by '${ this . #namespace} :'` ,
398
+ ) ;
399
+ }
400
+ this . #publish( eventType , ...payload ) ;
401
+ }
402
+
403
+ /**
404
+ * Publish a delegated event.
405
+ *
406
+ * Publishes the given payload to all subscribers of the given event type.
407
+ *
408
+ * Note that this method should never throw directly. Any errors from
409
+ * subscribers are captured and re-thrown in a timeout handler.
410
+ *
411
+ * @deprecated Do not call this directly, instead use the `delegate` method.
412
+ * @param eventType - The event type. This is a unique identifier for this event.
413
+ * @param payload - The event payload. The type of the parameters for each event handler must
414
+ * match the type of this payload.
415
+ * @template EventType - A type union of Event type strings.
416
+ */
417
+ publishDelegated < EventType extends Event [ 'type' ] > (
418
+ eventType : EventType ,
419
+ ...payload : ExtractEventPayload < Event , EventType >
420
+ ) {
421
+ this . #publish( eventType , ...payload ) ;
422
+ }
423
+
424
+ #publish< EventType extends Event [ 'type' ] > (
344
425
eventType : EventType ,
345
426
...payload : ExtractEventPayload < Event , EventType >
346
427
) {
@@ -483,63 +564,79 @@ export class Messenger<
483
564
}
484
565
485
566
/**
486
- * Get a restricted messenger
487
- *
488
- * Returns a wrapper around the messenger instance that restricts access to actions and events.
489
- * The provided allowlists grant the ability to call the listed actions and subscribe to the
490
- * listed events. The "name" provided grants ownership of any actions and events under that
491
- * namespace. Ownership allows registering actions and publishing events, as well as
492
- * unregistering actions and clearing event subscriptions.
493
- *
494
- * @param options - Messenger options.
495
- * @param options.name - The name of the thing this messenger will be handed to (e.g. the
496
- * controller name). This grants "ownership" of actions and events under this namespace to the
497
- * restricted messenger returned.
498
- * @param options.allowedActions - The list of actions that this restricted messenger should be
499
- * allowed to call.
500
- * @param options.allowedEvents - The list of events that this restricted messenger should be
501
- * allowed to subscribe to.
502
- * @template Namespace - The namespace for this messenger. Typically this is the name of the
503
- * module that this messenger has been created for. The authority to publish events and register
504
- * actions under this namespace is granted to this restricted messenger instance.
505
- * @template AllowedAction - A type union of the 'type' string for any allowed actions.
506
- * This must not include internal actions that are in the messenger's namespace.
507
- * @template AllowedEvent - A type union of the 'type' string for any allowed events.
508
- * This must not include internal events that are in the messenger's namespace.
509
- * @returns The restricted messenger.
567
+ * Delegate actions and/or events to another messenger.
568
+ *
569
+ * @param args - Arguments.
570
+ * @param args.actions - The action types to delegate.
571
+ * @param args.events - The event types to delegate.
572
+ * @param args.messenger - The messenger to delegate to.
510
573
*/
511
- getRestricted <
512
- Namespace extends string ,
513
- AllowedAction extends NotNamespacedBy < Namespace , Action [ 'type' ] > = never ,
514
- AllowedEvent extends NotNamespacedBy < Namespace , Event [ 'type' ] > = never ,
515
- > ( {
516
- name,
517
- allowedActions,
518
- allowedEvents,
574
+ delegate < DelegatedAction extends Action , DelegatedEvent extends Event > ( {
575
+ actions = [ ] ,
576
+ events = [ ] ,
577
+ messenger,
519
578
} : {
520
- name : Namespace ;
521
- allowedActions : NotNamespacedBy <
522
- Namespace ,
523
- Extract < Action [ 'type' ] , AllowedAction >
524
- > [ ] ;
525
- allowedEvents : NotNamespacedBy <
526
- Namespace ,
527
- Extract < Event [ 'type' ] , AllowedEvent >
528
- > [ ] ;
529
- } ) : RestrictedMessenger <
530
- Namespace ,
531
- | NarrowToNamespace < Action , Namespace >
532
- | NarrowToAllowed < Action , AllowedAction > ,
533
- NarrowToNamespace < Event , Namespace > | NarrowToAllowed < Event , AllowedEvent > ,
534
- AllowedAction ,
535
- AllowedEvent
536
- > {
537
- return new RestrictedMessenger ( {
538
- messenger : this ,
539
- name,
540
- allowedActions,
541
- allowedEvents,
542
- } ) ;
579
+ actions ?: DelegatedAction [ 'type' ] [ ] ;
580
+ events ?: DelegatedEvent [ 'type' ] [ ] ;
581
+ messenger : DelegatedMessenger < DelegatedAction , DelegatedEvent > ;
582
+ } ) {
583
+ for ( const actionType of actions ) {
584
+ const delegatedActionHandler = (
585
+ ...args : ExtractActionParameters < DelegatedAction , typeof actionType >
586
+ ) => {
587
+ // TODO: Explain cast
588
+ const actionHandler = this . #actions. get ( actionType ) as
589
+ | ActionHandler < DelegatedAction , typeof actionType >
590
+ | undefined ;
591
+ if ( ! actionHandler ) {
592
+ throw new Error (
593
+ `Cannot call '${ actionType } ', action not registered.` ,
594
+ ) ;
595
+ }
596
+ return actionHandler ( ...args ) ;
597
+ } ;
598
+ let delegationTargets = this . #actionDelegationTargets. get ( actionType ) ;
599
+ if ( ! delegationTargets ) {
600
+ delegationTargets = new Set < DelegatedMessenger < Action , Event > > ( ) ;
601
+ this . #actionDelegationTargets. set ( actionType , delegationTargets ) ;
602
+ }
603
+ delegationTargets . add ( messenger ) ;
604
+
605
+ messenger . registerDelegatedActionHandler (
606
+ actionType ,
607
+ delegatedActionHandler ,
608
+ ) ;
609
+ }
610
+ for ( const eventType of events ) {
611
+ const untypedSubscriber = (
612
+ ...payload : ExtractEventPayload < DelegatedEvent , typeof eventType >
613
+ ) => {
614
+ messenger . publishDelegated ( eventType , ...payload ) ;
615
+ } ;
616
+ // TODO: Explain cast
617
+ const subscriber = untypedSubscriber as ExtractEventHandler <
618
+ DelegatedEvent ,
619
+ typeof eventType
620
+ > ;
621
+ let delegatedEventSubscriptions =
622
+ this . #delegatedEventSubscriptions. get ( eventType ) ;
623
+ if ( ! delegatedEventSubscriptions ) {
624
+ delegatedEventSubscriptions = new Map ( ) ;
625
+ this . #delegatedEventSubscriptions. set (
626
+ eventType ,
627
+ delegatedEventSubscriptions ,
628
+ ) ;
629
+ }
630
+ delegatedEventSubscriptions . set ( messenger , subscriber ) ;
631
+ const getPayload = this . #initialEventPayloadGetters. get ( eventType ) ;
632
+ if ( getPayload ) {
633
+ messenger . registerDelegatedInitialEventPayload ( {
634
+ eventType,
635
+ getPayload,
636
+ } ) ;
637
+ }
638
+ this . subscribe ( eventType , subscriber ) ;
639
+ }
543
640
}
544
641
545
642
/**
0 commit comments