Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Defines and executes functions with a common interface (services) configured in a directed acyclic graph
When asked why he went about with a lamp in broad daylight, Diogenes confessed, "I am looking for a [honest] man."
Diogenes defines and executes functions with a common interface (services) configured in a directed acyclic graph.
I define a "service" as a function with a specific interface. Its arguments are:
A service outputs a "dependency", this is identified with a name. Services are organized inside registries. The common interface allows to automate how the dependencies are resolved within the registry.
Let's say that you have a function returning an html page. You usually need to execute a certain number of steps (already incapsulated into functions):
decodeURL(url, function (id){
getDB(config, function (db){
getDataFromDB(id, function (obj){
retrieveTemplate("template.html", function (template){
renderTemplate(template, obj, function (html){
returnHTML(html)
});
});
});
});
});
I am sure you have already seen something like this. Well, I can see more than one issue here. The first one, usually called "the pyramid of doom", can be solved easily using promises (or other techniques). But there is a worst issue, you are designing how the components interact between them, in an imperative way. This is awkward as you'll either use the same patterns again and again, or you'll spend a lot of time refactoring the old code trying to avoid repetition.
With Diogenes you can describe the flow of informations in terms of services, describing the relations between them:
var Diogenes = require('diogenes');
var registry = Diogenes.getRegistry();
registry.add("id", decodeURL);
registry.add("db", getDB);
registry.add("data", ["db", "url"], getDataFromDB);// arrays define dependencies
registry.add("template", retrieveTemplate);
registry.add("html", ["template", "data"], renderTemplate);
and let the system do the job:
registry.run("html", configuration, returnHTML);
Diogenes resolves the whole dependency tree for you, executing the services in the right order (even in parallel when possible). Then it serves you the result on a silver platter.
The easiest way to import Diogenes is using commonjs:
var Diogenes = require('diogenes');
You can also import it as a global module. In that case you should take care of the dependencies (setImmediate and occamsrazor).
You can create a registry with:
var registry = Diogenes.getRegistry();
Without arguments you create a "local registry" that is reachable within the scope of the "registry" variable. If you pass a name to the constructor you create a global registry that is available everywhere:
var registry = Diogenes.getRegistry("myregistry");
A service is defined by a name (a string), a list of dependencies (an optional list of strings) and a function with a specific interface:
registry.add("text", function (config, deps, next) {
var text = ["Diogenes became notorious for his philosophical ",
"stunts such as carrying a lamp in the daytime, claiming to ",
"be looking for an honest man."].join();
next(undefined, text);
});
If the service is successful it passes undefined as the first argument and the result as second. The first argument will contain an exception if the service fails As an alternative it is also possible to return a value instead of using a callback. It will work anyway:
registry.add("text", function (config, deps) {
return ["Diogenes became notorious for his philosophical ",
"stunts such as carrying a lamp in the daytime, claiming to ",
"be looking for an honest man."].join();
});
In this case you can throw an exception in case of errors. Let's add another service:
registry.add("tokens", ['text'], function (config, deps, next) {
next(undefined, deps.text.split(' '));
});
The array specifies a list of dependencies. This service depends on the "text" service. The deps argument will contain an attribute for every dependency in this example: deps.text.
registry.add("count", ['tokens'], function (config, deps, next) {
next(undefined, deps.tokens.length);
});
registry.add("abstract", ['tokens'], function (config, deps, next) {
var len = config.abstractLen;
var ellipsis = config.abstractEllipsis;
next(undefined, deps.tokens.slice(0, len).join(' ') + ellipsis);
});
The "config" argument the same for all services. It is passed with the run method below.
registry.add("paragraph", ['text', 'abstract', 'count'],
function (config, deps, next) {
next(undefined, {
count: deps.count,
abstract: deps.abstract,
text: deps.text
});
});
This is how services relates each other:
You can call a service using the method "run" with the name and the configuration (the same one will be passed as argument to all services).
registry.run("paragraph", {abstractLen: 5, abstractEllipsis: "..."},
function (err, p){
if (err){
console.log("Something went wrong!");
}
else {
console.log("This paragraph is " + p.count + " words long");
console.log("The abstract is: " + p.anstract);
console.log("This is the original text:");
console.log(p.text);
}
});
p will be the output of the paragraph service. If any service throws, or returns an error, the "err" argument will contain the exception. If you need more than one service, you can pass a list of services:
registry.run(["count", "abstract"], {abstractLen: 5, abstractEllipsis: "..."},
function (err, deps){
...
});
In this case the second argument will contain an object with an attribute for any dependency (deps.count, deps.abstract). Using "run", Diogenes calls all services required to satisfy the dependencies tree. You can get the ordering using:
registry.getExecutionOrder("paragraph",
{abstractLen: 5, abstractEllipsis: "..."});
It will return an array: ["text", "tokens", "abstract", "count", "paragraph"] Diogenes does not strictly follow that order: "count", for example doesn't require to wait for "abstract" as it depends on "tokens" only.
A service can contain more than one function. The correct function will be chosen using the configuration and an occamsrazor validator. Diogenes.validator is a copy of occamsrazor.validator (for convenience). Let's say for example that you want to use a different way to get the abstract:
var useAlternativeClamp = Diogenes.validator().match({abstractClamp: "chars"});
registry.add("abstract", ['text'], useAlternativeClamp,
function (config, deps, next) {
var len = config.abstractLen;
var ellipsis = config.abstractEllipsis;
next(undefined, deps.text.slice(0, len) + ellipsis);
});
You should notice that specifying a validator you are also able to use a different set of dependencies.
registry.getExecutionOrder("paragraph",
{abstractLen: 5, abstractEllipsis: "...", abstractClamp: "chars"});
will output: ["text", "abstract", "tokens", "count", "paragraph"]. You can run the service as usual:
registry.run("paragraph",
{abstractLen: 5, abstractEllipsis: "...", abstractClamp: "chars"},
function (err, p){
if (err){
console.log("Something went wrong!");
}
else {
console.log("This paragraph is " + p.count + " words long");
console.log("The abstract is: " + p.anstract);
console.log("This is the original text:");
console.log(p.text);
}
});
The key point is that you just extended the system without changing the original code!
addValue is a short cut method you can use if a service returns always the same value (or a singleton object). And it doesn't need any configuration. For example the "text" service can become:
registry.addValue("text", ["Diogenes became notorious for his philosophical ",
"stunts such as carrying a lamp in the daytime, claiming to ",
"be looking for an honest man."].join());
If the result of a service depends on the configuration, or it is heavy to compute, you can cache it. You can enable the cache with cacheOn, empty the cache with cacheReset or disable with cacheOff. The cacheOn method takes an object as argument with 3 different arguments:
Note: a cache hit, will ever never return dependencies. After all if the service has a defined return value it doesn't need to relay on any other service. So for example:
registry.service('count').cacheOn({key: "abstractLen", maxAge: 1000});
If a service returns or throws an exception, this is propagated along the execution graph. Services depending on those are not executed. They are considered failed. While this is the default behaviour, it is also possible to configure a service to fallback on a default value:
registry.service('count').onErrorReturn(42);
Or on a function (the usual config is the only argument)
registry.service('count').onErrorExecute(function (config){
return config.defaultCount;
});
This function is called in these cases:
The event system allows to do something when a service is executed. You can listen to a service in this way:
registry.on('paragraph', function (name, dep, config){
// name is "paragraph"
// dep is the output of the "count" service
// config is the usual one used in the "run" method
});
The event system is implemented with occamsrazor (see the doc, especially the "mediator" example https://github.com/sithmel/occamsrazor.js#implementing-a-mediator-with-occamsrazor). So you can execute the function depending on the arguments (just pass as many validators you need).
registry.on(function (name, dep, config){
// this is executed for any service
});
registry.on("paragraph", isLessThan5, useAlternativeClamp, function (name, dep, config){
// this is executed for count service
// only if count is less than 5 and
// the config passes the "useAlternativeClamp" validator
});
registry.on(/count.*/, function (name, dep, config){
// this is executed for any service with the name that matches
// that regular expression
});
Be aware that events are suppressed for cached values and their dependencies! You can also handle the event once with "one" and remove the event handler with "off". If you need you can also emit your own custom events:
registry.on("my custom event", function (name, data1, data2){
//
});
registry.trigger("my custom event", data1, data2);
You can store any data related to a service with the metadata:
var service = registry.service("abstract")
service.metadata({abstractLen: 10});
registry.add("abstract", ['tokens'], function (config, deps, next) {
var len = this.service('abstract').metadata().abstractLen;
var ellipsis = config.abstractEllipsis;
next(undefined, deps.tokens.slice(0, len).join(' ') + ellipsis);
});
This can be practical if you want to save informations that are "service" specific.
You can attach a a description to a service. This will be used by the method "info" for giving an outline of the services available.
var service = registry.service("abstract")
service.description("This service returns the abstract of a paragraph.");
service.info({}); // I pass the configuration
abstract
========
This service returns the abstract of a paragraph.
Dependencies:
* text
* tokens
You can also use the method "info" of the registry to get all the services.
Diogenes depends on setimmediate and occamsrazor.
A lot of the things going on requires a bit of knowledge of occamsrazor (https://github.com/sithmel/occamsrazor.js). Basically a service is an occamsrazor adapter's registry (identified with a name). When you add a function you are adding an adapter to the registry. This adapter will return the function and the dependencies when called with the configuration as argument. When you try running a service the first thing that happen is that diogenes will perform a dfs within the services. The configuration will be used to unwrap a service when its adjancency is required. Doing this operation you have this cases:
The result will be a sorted list of adapters. At this point the system will start executing all the functions. Every time one of these function's callback returns a value I push this in an dependency map and try to execute all the functions that see their dependencies fulfilled. The last function should be the one I requested.
Create a registry of services:
var registry = Diogenes.getRegistry();
or
var registry = new Diogenes();
If you don't pass any argument this registry will be local. And you will need to reach the local variable "registry" to use its services. If you pass a string, the registry will use a global variable to store services:
var registry = Diogenes.getRegistry("myregistry");
or
var registry = new Diogenes("myregistry");
This is convenient if you want any application register its services to a specific registry.
Returns a single service. It creates the service if it doesn't exist.
registry.service("name");
It adds a service to a registry. It has different signatures:
registry.add(name, func);
registry.add(name, dependencies, func);
registry.add(name, dependencies, validator, func);
The function can have 2 different signatures: with callback (config, deps, next) or without (config, deps):
If you use the signature without "next" you can return the value using return, or throw an exception in case of errors. If you return a promise (A or A+) this will be automatically used:
registry.add("promise", function (config, deps) {
return new Promise(function (resolve, reject){
resolve("resolved!");
});
});
registry.run("promise", function (err, dep){
console.log(dep); // resolved!
})
It returns the registry.
It works the same as the add method but instead of adding a service It adds a value. This will be the dependency returned.
registry.add(name, value);
registry.add(name, dependencies, value);
registry.add(name, dependencies, validator, value);
Note: having a value you don't need dependencies. They are still part of the signature of the method for consistency.
It works the same as add but the service will be returned only one time.
registry.addOnce(name, func);
registry.addOnce(name, dependencies, func);
registry.addOnce(name, dependencies, validator, func);
It works the same as addValue but the service will be returned only one time.
registry.addValueOnce(name, value);
registry.addValueOnce(name, dependencies, value);
registry.addValueOnce(name, dependencies, validator, value);
It remove a service from the registry:
registry.remove(name);
It returns the registry.
It executes all the dependency tree required by the service and call the function. All the services are called using config:
registry.run(name, config, func);
The function takes 2 arguments:
You can also use the alternative syntax:
registry.run(names, config, func);
In this case "names" is an array of strings (the dependency you want to be returned). The callback will get as second argument an object with a property for any dependency returned.
The context (this) of this function is the registry itself.
It returns the registry.
It empties and disable the cache of all services.
It empties the cache of all services.
It pauses the cache of all services.
Resume the cache of all services.
Returns an array of services that should be executed with those arguments. The services are sorted by dependencies. It is not strictly the execution order as diogenes is able to execute services in parallel if possible. Also it will take into consideration what plugins match and caching (a cached items as no dependency!):
registry.getExecutionOrder(name, config);
Helper function. It runs a group of functions with the registry as "this". Useful for initializing the registry.
/*module1 fir example*/
module.exports = function (){
this.add('service1', ...);
};
/*main*/
var module1 = require('module1');
var module2 = require('module2');
registry.init([module1, module2]);
It runs a callback for any service registered.
registry.forEach(function (service, name){
// the service is also "this"
});
Attach an event handler. It triggers when an services gets a valid output. You can pass up to 3 validators and the function. The function takes 3 arguments:
registry.on([validators], function (name, dep, config){
...
});
The same as "on". The function is executed only once.
Remove an event handler. It takes the previously registered function.
registry.off(func);
Trigger an event. You can use trigger with a bunch of arguments and, all handlers registered with "on" and "one" compatible with those will be called.
It returns a documentation of all services. It requires a configuration to resolve the dependencies.
registry.info(config);
add (addValue, addValueOnce, addOnce), remove and run are chainable. So you can do for example:
registry.add("service1", service1)
.add("service1", service2);
.add("service3", ["service1", "service2"], myservice);
In the "add" and "run" callback the "this" is the registry itself. This simplify the case in which you want:
Example:
var c = 0;
registry.add('counter-button', function (config, deps, next){
var registry = this;
document.getElementById('inc').addEventListener("click", function (){
c++;
console.log(c);
});
registry.on("reset-event", function (){
c = 0;
});
next();
});
registry.add('reset-button', function (config, deps, next){
var registry = this;
document.getElementById('reset').addEventListener("click", function (){
registry.trigger("reset-event");
});
next();
});
registry.run(['counter-button', 'reset-button']);
You can get a service with the "service" method.
var service = registry.service("service1");
All the service methods returns a service instance so they can be chained.
The same as the add registry method:
service.add(func);
service.add(dependencies, func);
service.add(dependencies, validator, func);
The same as the addValue registry method:
service.addValue(value);
service.addValue(dependencies, value);
service.addValue(dependencies, validator, value);
The same as the addOnce registry method:
service.addOnce(func);
service.addOnce(dependencies, func);
service.addOnce(dependencies, validator, func);
The same as the addValueOnce registry method:
service.addValueOnce(value);
service.addValueOnce(dependencies, value);
service.addValueOnce(dependencies, validator, value);
The same as the remove registry method:
service.remove();
The same as the run registry method:
service.run(config, func);
If the service or one of the dependencies fails (thrown an exception) it returns "value" as fallback.
service.onErrorReturn(value);
If the service or one of the dependencies fails (thrown an exception) it uses the function to calculate a fallback.
service.onErrorExecute(function (config){
return ...;
});
It reverts to the default behaviour: on error it propagates the error.
service.onErrorThrown();
Set the cache for this service on. It takes as argument the cache configuration:
service.cacheOn(config);
The configuration contains 3 parameters:
It empties and disable the cache.
It empties the cache.
It pauses the cache. The cache will work as usual but the cached result won't be used
Resume the cache.
Manage event handlers. It is a alternate syntax to the registry ones.
registry.service(name).on([validators], function (name, dep, config){
...
});
registry.service(name).one([validators], function (name, dep, config){
...
});
registry.service(name).off(func);
Get/set metadata on the service.
registry.service(name).metadata(metadata); // set
registry.service(name).metadata(); // get
Get/set a service description.
registry.service(name).description(metadata); // set
registry.service(name).description(); // get
It returns a documentation of the service. It requires a configuration to resolve the dependencies.
registry.service(name).info(config);
The library is currently able to detect and throws exceptions in a few cases:
These 3 exceptions are thrown by "getExecutionOrder". So it is very useful using this method to check if something is wrong in the graph configuration.
Do not mutate the configuration argument! It is not meant to be changed during the execution. Instead you can apply side effects through a dependency. See the example of the expressjs middleware below.
If you need to run a service that depends on some variable defined in a closure you can use this trick: define a local registry containing the "local" dependencies, merge together the main and the local registry (a new merged registry will be generated), run the service. This is an example using an expressjs middleware:
var express = require('express');
var app = express();
var Diogenes = require('diogenes');
var registry = new Diogenes();
registry.add('hello', ['req', 'res'], function (config, deps, next){
var username = deps.req.query.username;
deps.res.send('hello ' + username);
next();
});
app.get('/', function(req, res){
var localReg = new Diogenes();
localReg.addValue('req', req);
localReg.addValue('res', res);
registry.merge(localReg).run('hello');
});
app.listen(3000);
A service can return a dependency that need to be disposed. In this case you can leverage the event system:
var registry = new Diogenes();
...
registry.add('database-connection', function (config, deps){
var connection = ..... I get the connection here
this.on('done', function (){
connection.dispose();
});
next();
});
registry.run('main-service', function (err, dep){
...
this.trigger('done');
});
FAQs
A dependency injection framework.
The npm package diogenes receives a total of 0 weekly downloads. As such, diogenes popularity was classified as not popular.
We found that diogenes demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.