-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preliminary Scopes Implementation #102
base: main
Are you sure you want to change the base?
Changes from all commits
cbb6b90
67c01ed
e376e31
75fe872
0389a8b
22422cd
5a36780
3e5b2bc
5622030
795a10a
12a6b90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,19 @@ export interface IMessage<T = any> { | |
* Represents a messagebroker and provides access to the core features which includes publishing/subscribing to messages and RSVP. | ||
*/ | ||
export interface IMessageBroker<T> { | ||
/** | ||
* A unique identifier for this instance of the MessageBroker. This is useful for identifying an instance within a tree of scopes. | ||
*/ | ||
readonly name: string; | ||
/** | ||
* A reference to the parent scope if this is not the root node in the tree of scopes. If this is the root, it's undefined. | ||
*/ | ||
readonly parent?: IMessageBroker<T>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the parent may have different generic params. We are assuming all the tree is uniform. Its fine but something to considerr. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm understand right, do you mean supporting something like this? const broker1 = messagebroker<Contract1>();
const broker2 = broker1.createScope<Contract2>('my-scope'); in this case, would Contract2 be required to extend Contract 1? Otherwise a message sent to broker1 could not necessarily be passed down to broker2. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah so I think this is governed by the propergation logic you plan to implement. For example say create scope and that scope extends the parents channels type, then it would mean my scope would essentially be a superset of the parent and messages subsribed or publishes essentially propergate upwards. interface Contract1{
"foo": boolean;
}
const broker1 = messagebroker<Contract1>();
const broker2 = broker1.createScope<Contract2 extends Contract1>('my-scope');
broker2.publish('foo') //Works However if you do not extend the parents type then you could it this way const broker1 = messagebroker<Contract1>();
const broker2 = broker1.createScope<Contract2 extends Contract1>('my-scope');
broker2.publish('foo') //Compile error
broker2.parent.publish('foo') //Works I prefer the extended types option as it makes more sense that channels delegate upwards |
||
/** | ||
* A list of all child scopes that have been created on this instance of the broker. | ||
*/ | ||
children: IMessageBroker<T>[]; | ||
|
||
/** | ||
* Creates a new channel with the provided channelName. An optional config object can be passed that specifies how many messages to cache. | ||
* No caching is set by default | ||
|
@@ -96,6 +109,27 @@ export interface IMessageBroker<T> { | |
* This RSVP function is used by responders and is analogous to the 'Get' function. Responders when invoked must return the required response value type. | ||
*/ | ||
rsvp<K extends keyof RSVPOf<T>>(channelName: K, handler: RSVPHandler<T>): IResponderRef; | ||
|
||
/** | ||
* Creates a new scope with the given scopeName with this instance of the MessageBroker as its parent. | ||
* If a scope with this name already exists, it returns that instance instead of creating a new one. | ||
* @param scopeName The name to use for the scope to create | ||
* @returns An instance of the messagebroker that matches the scopeName provided | ||
*/ | ||
createScope(scopeName: string): IMessageBroker<T>; | ||
|
||
/* | ||
* Destroys all children scopes, disposes of all message channels on | ||
* this instance and removes itself from its parents children. | ||
*/ | ||
destroy(): void; | ||
|
||
/** | ||
* Returns true if this is root node of the tree of MessageBrokers. | ||
* The root MessageBroker will not have a parent MessageBroker. | ||
* @returns A boolean indicating whether this is the root or not | ||
*/ | ||
isRoot(): boolean; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
order: 5 | ||
title: Scopes | ||
--- | ||
|
||
Message scoping is a mechanism for restricting which subscribers will receive a given message. | ||
You can create a new scope on a messagebroker by calling `createScope`. | ||
|
||
```typescript | ||
const parent: IMessageBroker<IContract> | ||
= messagebroker<IContract>(); | ||
|
||
const child: IMessageBroker<IContract> | ||
= parent.createScope('my-scope'); | ||
|
||
parent.children.contains(child) // expect: true | ||
``` | ||
|
||
A scope is just another instance of an `IMessageBroker` on which you can perform all of the same operations that you'd expect on the base messagebroker. | ||
The main thing to note about this feature is how messages are shared across scopes. | ||
|
||
### Scope Hierarchies | ||
|
||
Any message that is published to a broker is also published down through the hierarchy of children scopes belonging that broker. | ||
|
||
```typescript | ||
parent.get('x').subscribe(message => console.log('parent received')); | ||
child.get('x').subscribe(message => console.log('child received')); | ||
|
||
parent.create('x').publish({}); | ||
|
||
// expect: child received | ||
// expect: parent received | ||
``` | ||
|
||
However messages are not sent **up** the hierarchy to the parent of that broker. | ||
|
||
```typescript | ||
parent.get('x').subscribe(message => console.log('parent received')); | ||
child.get('x').subscribe(message => console.log('child received')); | ||
|
||
child.create('x').publish({}); | ||
|
||
// expect: child received | ||
``` | ||
|
||
Messages are also not published to "sibling" scopes, where the brokers share a parent. | ||
|
||
```typescript | ||
const sibling: IMessageBroker<IContract> | ||
= parent.createScope('sibling-scope'); | ||
|
||
parent.get('x').subscribe(message => console.log('parent received')); | ||
child.get('x').subscribe(message => console.log('child received')); | ||
sibling.get('x').subscribe(message => console.log('sibling received')); | ||
|
||
sibling.create('x').publish({}); | ||
|
||
// expect: sibling received | ||
``` | ||
|
||
### Scope Depth | ||
|
||
Scope hierarchies can be arbitrarily deep, and messages will make their way all the way down to the bottom. | ||
|
||
```typescript | ||
const distantChild = parent | ||
.createScope('scope1') | ||
.createScope('scope2') | ||
... | ||
.createScope('scopeX'); | ||
|
||
distantChild.get('x').subscribe(message => console.log('child received')); | ||
|
||
parent.create('x').publish({}); | ||
|
||
// expect: child received | ||
``` | ||
|
||
### Naming | ||
|
||
Scopes under the same parent cannot have the same name. | ||
An attempt to create a scope with a name that already exists on a broker will just return the original scope. | ||
|
||
If they are different parts of the hierarchy (i.e. don't share a parent), then you can have multiple scopes with the same name. | ||
|
||
### Disposal | ||
|
||
Disposing of a channel in a broker will also dispose of that channel in its children scopes. | ||
|
||
### Destroy | ||
|
||
A MessageBroker instance can be destroyed. | ||
Destroying a MessageBroker will first destroy all of its children, it will dispose of all its channels, and finally remove itself from its parent. | ||
|
||
```typescript | ||
child.destroy(); | ||
parent.children.contains(child); // expect: false | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've introduced the name property on the messagebroker instance itself to keep track. The root broker has the name 'root'