The sty coloration library is currently hard coded inside the Broadcast class. Because Channel is a core class which uses Broadcast that makes core opinionated about how color should work. This makes it impossible to customize if you:
- Don't want color
- Don't like the
sty syntax
- Aren't using ANSI color
My high level concept is to create a system of customizable StreamDecorators that can be attached to TransportStreams in such a a way that a developer wouldn't have to modify core code AND wouldn't have to modify the transport bundle's (like telnet-networking code).
The difficulty is that TransportStream types aren't registered anywhere in the way that EntityLoaders are registered. This means there is currently no good central place to create the Stream+Decorators bindings.
Possible solutions
Bundle-centric
In this approach the transport bundle (e.g., telnet-networking) would entirely be in charge of this: they'd handle the initialization, customization, and management of decorators.
Pros
Cons
- Little to no inter-bundle portability as every bundle might have a different configuration scheme, different decorator interfaces, etc.
- All the work is foisted on the bundle dev
- Likely to unintentionally imply that decorators should be hard coded into a transport bundle
Core-centric
In this approach there would be a base TransportDecorator class in core in the same way there is a base TransportStream class. Additionally the core would have to decide upon and own the configuration/binding strategy for Stream+Decorators
Pros
- Easier on bundle developers
- Easier for bundle users
- Portable decorators
- Unified configuration style
Cons
- Less control for stream bundle developers since decorator configuration/binding will happen outside of their code
- Having core do the configuration/binding means that the scheme will have to be more abstract and thereby potentially more complex than if it were up to the bundle itself
For me the core-centric approach seems like the obvious choice. The pieces of that puzzle look something like this:
TransportStream
TransportStream will need some way to identify themselves in such a way that configuration could link a transport stream to its decorators, my initial idea is that the class should get a new identifier getter, for example:
class TelnetStream extends TransportStream
{
static get identifier() {
return 'telnet';
}
// ...
}
TransportDecoratorRegistry
My idea for how to actually bind decorators to a stream involves creating a new TransportDecoratorRegistry. This class will be in charge of holding the configuration from ranvier.json which will look similar to DataLoader configuration:
// ranvier.json
{
// register available decorators
"transportDecorators": {
"ansi": {
// value follows same rules as DataLoader require config
"require": "./lib/MyAnsiDecorator",
// Serves as the default configuration for this decorator
// which can be overridden by 'config' in the binding below
"config": {
"someDecoratorOption": 25
}
}
},
"transportDecoration": {
"telnet": [
{
"decorator": "ansi",
"config": {
// additional configuration specific to this telnet:ansi binding
"anotherOption": "foobar",
// override of default config from registration
"someDecoratorOption": 12,
}
}
]
}
}
This is really verbose with all the configs but in practice it will look more like this:
{
"streamDecorators": {
"ansi": {
"require": "cool-ansi-decorator"
}
},
"streamDecoration": {
"telnet": [
{ "decorator": "ansi" }
]
}
}
To actually facilitate the usage of these decorators TransportDecoratorRegistry will have a decorate(TransportStream streamConstructor) method. This method will be used by transport bundle developers which means if a bundle developer so chooses they can not allow applying decorators:
class TransportDecoratorRegistry {
decorate(streamConstructor) {
const decorators = this.getDecorators(streamConstructor.identifier);
return class extends streamConstructor {
write(message, encoding) {
message = decorators.reduce((acc, d) => d.decorate(acc), message);
super.write(message, encoding);
}
};
},
}
Writing a decorator
To write a new decorator, similar to DataLoader, there is no base class to extend. Instead you just need to follow the prescribed API:
const sty = require('sty');
class MyAnsiDecorator {
// required configure(object config)
configure(config = {}) {
this.config = config;
},
// required decorate(string message)
decorate(message) {
return sty.parse(message);
}
}
This class can either be an npm module or just a local file.
Changes for transport bundle devs
All that is in core. For the actual transport bundle developer they would write the following:
// inside their server-event script, e.g., server-events/telnet-server.js
// current:
const stream = new TelnetStream();
stream.attach(telnetSocket);
// new:
const stream = state.TransportDecoratorRegistry.decorate(TelnetStream);
stream.attach(telnetSocket);
The
stycoloration library is currently hard coded inside theBroadcastclass. BecauseChannelis a core class which usesBroadcastthat makescoreopinionated about how color should work. This makes it impossible to customize if you:stysyntaxMy high level concept is to create a system of customizable
StreamDecoratorsthat can be attached toTransportStreamsin such a a way that a developer wouldn't have to modify core code AND wouldn't have to modify the transport bundle's (liketelnet-networkingcode).The difficulty is that
TransportStreamtypes aren't registered anywhere in the way thatEntityLoadersare registered. This means there is currently no good central place to create theStream+Decoratorsbindings.Possible solutions
Bundle-centric
In this approach the transport bundle (e.g.,
telnet-networking) would entirely be in charge of this: they'd handle the initialization, customization, and management of decorators.Pros
Cons
Core-centric
In this approach there would be a base
TransportDecoratorclass in core in the same way there is a baseTransportStreamclass. Additionally the core would have to decide upon and own the configuration/binding strategy for Stream+DecoratorsPros
Cons
For me the core-centric approach seems like the obvious choice. The pieces of that puzzle look something like this:
TransportStream
TransportStreamwill need some way to identify themselves in such a way that configuration could link a transport stream to its decorators, my initial idea is that the class should get a new identifier getter, for example:TransportDecoratorRegistry
My idea for how to actually bind decorators to a stream involves creating a new
TransportDecoratorRegistry. This class will be in charge of holding the configuration fromranvier.jsonwhich will look similar toDataLoaderconfiguration:This is really verbose with all the configs but in practice it will look more like this:
To actually facilitate the usage of these decorators
TransportDecoratorRegistrywill have adecorate(TransportStream streamConstructor)method. This method will be used by transport bundle developers which means if a bundle developer so chooses they can not allow applying decorators:Writing a decorator
To write a new decorator, similar to DataLoader, there is no base class to extend. Instead you just need to follow the prescribed API:
This class can either be an npm module or just a local file.
Changes for transport bundle devs
All that is in core. For the actual transport bundle developer they would write the following: