Fly your application with an event-driven model inside rich contexts!
Birbs is a tool to help you deal with events in a semantic declarative fashion. With it, it is possible to decouple your application without losing track of the whole process as a business perspective, while also keeping the code organized.
Whenever programming or designing any application, we need to setup communication and data transferring between multiple parts of it. The problem comes when a system meets the barrier all systems face at some point: The manageability of complex Interfaces, the increasing number of classes and how to maintain them decoupled. As long as that codebase keeps enlarging, we know it gets harder to keep it SOLID.
Although Node's plain events can be more than enough to get that desired decoupling, it could also do better! Birbs provides the funcionality of Node's events with a sweet sweet interface that requires almost no extra work to be done!
And this comes bundled with typescript out of the box too :)
Birbs gives context and extensibility to Node's events.
This library proposes that an Event and the effect it has in your application should be self contained. This means that your Event represents the modification to the context and to the state of your application.
For instance, let's say we have a Supermarket Application. We would need a MeatDepartment
domain and a FruitDepartment
domain; They would be Contexts of our application. For these both, we need a function or method called weightProduct()
. With Birbs we can have a Procedure
for this task, and execute it in any of the contexts.
The Procedure of wheighting the products makes a modification to the context they're in. For example, it could set a property of the context measuredProductsWeight
, used when the checkout is wanted
npm i birbs --save
Let's say here we got to do a chat Application. We would like to have the features of different Rooms and message notifications!
For the sake of simplicity, we will not create all the servers and routes, just execute it in Node directly. Take a look on the linked folder above or here.
Before we get started, let's get to know our entities. There's Context
, Procedure
, Pipeline
, and EventManager
.
When saying
Birbable
, it means either a Procedure or a Pipeline.
If your IDE or editor shows you properties or methods which are not documented, it means that they are private, and you should not use them directly.
A Context defines a group of informations. It is available to a Procedure or Pipeline when it gets executed. It may have any Bibrbable instance signed on it.
The context instance has a contextState
property which holds its current state. If you're using typescript you may pass the type of this property as the generic type of the class when using it. Example: Context<{ name: string }>
. If omitted, the context will accept any object as a state.
As for version 0.8+, The context has the hability to handle errors thrown anywhere during the execution of your procedures. For more information, check the "Handling Promise Rejections" section.
The constructor takes an option object for the only parameter.
ContextOptions : {
identifier : string | symbol;
errorHandler ?: HandlerFunction
}
Sets or updates a value of information on the context. T
is of the
context.setContextState(information: T):
Identifier of the context.
Triggers the execution of a signed Birbable
. Accepts as an argument an options object, and extra information to be passed to the birbable to be triggered.
The TriggerOptions interface:
TriggerOptions = {
context ?: symbol | string;
birbable : string;
errorHandler ?: HandlerFunction;
};
context.trigger(options: TriggerOptions, descriptable?: any);
Signs a Birbable in this context. Accepts a Birbable as an argument.
context.sign(BirbableEntity);
Removes a Birbable from this context. Accepts a Birbable as an argument.
context.unsign(BirbableEntity);
Is the unit that contains the instructions to change data or a state of the application. Must be executed in a context through the .trigger()
method.
This class is supposed to be extended so you can add your own implementation of the .execute()
method.
Type of birbable. Used internally to do assertions.
Lifetime of the Birbable, can be either "DURABLE"
OR "SINGLE"
. "DURABLE"
means the Birbable will be able to be used on a context until it gets manually removed with .unisgn()
; "SINGLE"
lifetime birbables can only be executed once per sign()
.
Procedures and Pipelines can have this property set at the constructor. If a Birbable with this property set is triggered, all of the other belonging with the same group get removed as well.
Wether this Birbable belongs to a group or not.
Is the method that contains the change to be made when the Birbable gets triggered.
Context is the context in which the procedure was triggered, while the descriptable is an optional argument containing extra data to be used in that execution.
Attention! - This method requires to be declared and implemented by you.
Procedure.execute(context, descriptable?);
Contains a sequential set of instructions. Must be executed in a context through the .trigger()
method.
This class is supposed to be extended, but you MUST NOT override its execute()
method.
The constructor accepts as arguments:
- options: The options of the Birbable
- onFinish: Callback executed when the pipeline finishes, which receives as argument the context.
new Pipeline(option, onFinish?, );
Type of birbable. Used internally to do assertions.
Lifetime of the Birbable, can be either "DURABLE"
OR "SINGLE"
. "DURABLE"
means the Birbable will be able to be used on a context until it gets manually removed with .unisgn()
; "SINGLE"
lifetime birbables can only be executed once per sign()
.
Pipelines and Pipelines can have this property set at the constructor. If a Birbable with this property set is triggered, all of the other belonging with the same group get removed as well.
Wether this Birbable belongs to a group or not.
Adds a Birbable to be executed in the pipeline. They are executed in the order they were added.
Pipeline.addStep(BirbableEntity);
It is the method that triggers the pipeline.
Context is the context in which the Pipeline was triggered, while the descriptable is an optional argument containing extra data to be used in that execution.
Pipeline.execute(context, descriptable?);
It is the entity that you can use to trigger a Procedure in a Context. It is also possible to use it as a context group.
This special entity should be used whenever possible to broadcast birbables since it enables to trigger them without a reference. Also, you may record and dump the history of broadcasts by plugging in a BroadcastsRecorder
.
Adds a context to an EventManager. Returns the EventManager
EventManager.addContext(context: Context);
Removes a context from an EventManager. Returns the EventManager
EventManager.removeContext(context: string);
Triggers a Procedure execution. Returns the EventManager
The context argument is optional. If ommited, the manager will try triggering the Procedure in all of its Contexts
EventManager.broadcast(
options: TriggerOptions
descriptable ?: any
);
Adds a Birbable to a Context. Returns the EventManager
EventManager.addBirbable(
birbable : Birbable,
context : Context | symbol
);
Removes a Birbable from a Context. Returns the EventManager
EventManager.removeProcedure(
birbable : string,
context : Context | symbol
);
May be plugged in a EventManager
to record the history of broadcasts made by it.
The BroadcastsRecorder
class emits three events: "read"
, "write"
and "dump"
which can be listened to using .on()
method, and have their listeners removed using .off()
. All these events recieve as an argument an Array of Broadcasts
.
interface Broadcast { procedureName : string; contextState : Context & any; data ?: any; }
The total amount of stored undumped broadcasts. Returns a number.
BroadcastsRecorder.size;
The next offset that the method .read()
will use. Returns a number.
BroadcastsRecorder.nextOffset;
Read an entry of the undumped Broadcasts records. Emits a "read"
event.
If executed with no arugments, it will read the next entry each execution.
BroadcastsRecorder.read(index ?: number);
Writes the state to the undumped broadcast list. Returns void
. Emits a "write"
event.
Beware of using this method yourself, it will make this instance record a change that may not have happened!
BroadcastsRecorder.writeState(
procedureName : string,
contextState : Context,
data ?: any
);
Reads the list of recorded broadcasts and clears it. Emits a "dump"
event.
BroadcastsRecorder.dump();
In order to understand how can we handle errors in Birbs, it is required that you provide to the context what it is supposed to do in case of a failure; you can do it so through either the .broadcast
or the .trigger
methods, by setting the property on the context, or during runtime. The function recieves an Error
as the only argument.