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. So it doesn't really searching for a man, just an [honest] service.
A service is a function that takes a configuration and a list of dependencies (other services). The configuration is common to all services.
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, the pyramid of doom, can be solved easily using promises (or other techniques). But there is a worst issue, you are designing the workflow, 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); // the array defines the 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 all the 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 I pass undefined as the first argument and the result as second:
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 is passed to all services.
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 getService 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 services throws, or returns an error. This function will be executed anyway and the "err" argument will contain the exception. Diogenes calls all the services in order. 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 follow that order exactly: "count", for example doesn't require to wait for abstract as it depends on tokens only.
A registry can have more than one service with the same name (and a different set of dependencies). The correct service 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"});
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());
Some time you may want to execute a service only once and then use a more generic one:
registry.addOnce("paragraph", [], Diogenes.validator().important(), function (config, deps, next) {
next(undefined, "This runs only once");
});
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:
registry.service('count').cacheOn();
registry.service('count').cacheReset();
registry.service('count').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.
Diogenes depends on setimmediate and occamsrazor.
A lot of the things going on requires a bit of knowedge of occamsrazor (https://github.com/sithmel/occamsrazor.js). Basically a service is a 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 try to unwrap all the services for discovering what function/dependencies use. This is what can happen:
With all the services unwrapped, a dfs (depth first search) will be performed. The result will be a sorted list of services. There will be included only a subset of the services. At this point the system will start executing all the functions without dependencies. 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 will have these arguments (config, deps, next):
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:
It returns the registry.
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);
add (addValue, addValueOnce, addOnce), remove and run are chainable. So you can do for example:
registry.add("service1", service1)
.add("service1", service2);
.add("myservice", ["service1", "service2"], myservice);
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);
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.
The library is currently able to detect and throws exceptions in a few cases:
The first 3 exceptions can be thrown by getExecutionOrder and getService. The last one only using getService.
Do not mutate the config and deps arguments! It will lead to unpredictable bugs. Clone the object instead.
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.