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.
When asked why he went about with a lamp in broad daylight, Diogenes confessed, "I am looking for a [honest] man."
Diogenes helps to use the dependency injection pattern to split your application into reusable and testable components.
The dependency injection pattern is a widely used design pattern. Simply put, allows to build complicated abstractions composed by simpler abstractions. The composition happens when you "inject" one or more dependency into a function:
const database = getDB(config.db);
const passwordHashing = getPasswordHashing(config.secret);
const users = getUsers(database, passwordHashing);
I call this progressively complex objects "services" as they provide a specific functionality.
While this is a very nice way to build an application, it will leave the developer with a lot of annoying problems:
Diogenes lets you design how these components interact between them, in an declarative way.
You declare your "services" and their dependencies:
const Diogenes = require('diogenes');
const registry = Diogenes.getRegistry();
registry.service('database').provides(getDB);
registry.service('passwordHashing').provides(getPasswordHashing);
registry.service('users')
.dependsOn(['database', 'passwordHashing'])
.provides(getUsers);
and then get the service you need:
registry
.run('users')
.then((users) => {
...
})
Diogenes figures out the execution order, manages error propagation, deals with synchronous and asynchronous functions transparently and much more.
A service is a unit of code with a name. It can be a simple value, a synchronous function (returning a value), an asynchronous function using a callback or an asynchronous function returning a promise. It takes as argument an object containing the dependencies (output of other services). Optionally you can pass a callback.
A service outputs a "dependency", this is identified with the service name. Services are organized inside a registry. The common interface allows to automate how the dependencies are resolved within the registry.
You can import Diogenes using commonjs:
const Diogenes = require('diogenes');
You can create a registry with:
const registry = Diogenes.getRegistry(); // of new Diogenes()
A service is defined by a name (a string) and it can be as simple as a value:
registry.service("text")
.provides(`Diogenes became notorious for his philosophical
stunts such as carrying a lamp in the daytime, claiming to
be looking for an honest man.`);
most of the time you will define a service as a function:
registry
.service("text")
.provides((deps) => fs.readFileSync(deps.config.path, {encoding: 'utf8'}));
If the function is asynchronous you can return a promise. It will work transparently:
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
registry
.service("text")
.provides((deps) => readFile('diogenes.txt', {encoding: 'utf8'}));
or you can use a callback:
registry.service("text")
.provides((deps, next) => {
fs.readFile('diogenes.txt', {encoding: 'utf8'}, next);
});
The callback should use the node.js convention: the first argument is the error instance (or null if there isn't any) and the second is the value returned. As you can see, Diogenes allows to mix sync and async (callback and promise based) functions. How cool is that? Let's add other services:
registry.service("tokens")
.dependsOn(['text'])
.provides(({ text }) => text.split(' '));
The method "dependsOn" allows to specify a list of dependencies. For example this service depends on the "text" service. The deps argument is an object containing an attribute for every dependency, in this example: deps.text.
registry.service("count")
.dependsOn(['tokens'])
.provides(({ tokens }) => tokens.length);
registry.service("abstract")
.dependsOn(['tokens'])
.provides(({ tokens }) => tokens.slice(0, 20).join(' ') + '...');
registry.service("paragraph")
.dependsOn(['text', 'abstract', 'count'])
.provides(({ text, abstract, count }) => ({text, abstract, count}));
This is how services relates each other:
You can call a service using the method "run" on a registry.
registry.run('paragraph')
.then((p) => {
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);
})
.catch((err) => console.log(err.message))
p will be the output of the paragraph service. You can alternatively pass a callback to "run".
registry.run('paragraph', (err, p) => {
if (err) {
console.log(err.message)
return;
}
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);
})
If you need more than one service, you can pass a list of services:
registry.run(["count", "abstract"])
.then({ count, paragraph } => {
...
})
In this case the result will be an object with an attribute for each dependency (deps.count, deps.abstract). Once a service has been executed, the result is cached forever.
If a service returns or throws an exception, this is propagated along the execution graph. Services getting an exception as one of the dependencies, are not executed. When a service gets an exception, its state is not cached. So it can be executed again.
Create a registry of services:
const registry = Diogenes.getRegistry();
or
const registry = new Diogenes();
Returns a single service. It creates the service if it doesn't exist.
registry.service("name");
Helper function. It runs a group of functions with the registry as first argument. Useful for initializing the registry.
/* module1 for example */
module.exports = (registry) => {
registry.service('service1').provides(...);
};
/* main */
const module1 = require('module1');
const module2 = require('module2');
registry.init([module1, module2]);
It executes all the dependency tree required by a service and return a promise that will resolve to the service itself.
registry.run(serviceName)
.then((service) => {
...
});
It can also use a callback:
registry.run(serviceName, (err, service) => {
...
});
The callback uses the node convention (error as first argument).
You can also execute more than one service passing an array of names:
registry.run(['service1', 'service2'])
.then(({ service1, service2 }) => {
...
})
or using a regular expression:
registry.run(/service[0-9]?/)
.then(({ service1, service2 }) => {
...
})
It allows to create a new registry, merging services of different registries:
const registry4 = registry1.merge(registry2, registry3)
The state of of services will be preserved. So for example, if a service has already been successfully executed, this won't be executed again. Calling merge without argument, creates a clone.
It returns the adjacency list in the following format:
/*
A ----> B
| / |
| / |
| / |
| / |
| / |
VV V
C ----> D
*/
registry.getAdjList();
/* returns
{
'A': [],
'B': ['A'],
'C': ['A', 'B'],
'D': ['B', 'C']
}
*/
You can get a service from the registry with the "service" method.
const service = registry.service('service1');
All the service methods returns a service instance so they can be chained.
It defines the dependencies of a service. It may be an array or a function returning an array of strings (service names):
service.dependsOn(array);
service.dependsOn(func);
You can pass a function taking a dependencies object as argument, and returning a promise.
service.provides((deps) => ...);
You can also pass any "non function" argument:
service.provides(42); // Any non function argument
A synchronous function:
service.provides((deps) => deps.something * 2);
Or callback:
service.provides((deps, callback) => callback(null, deps.something * 2));
The "callback" behaviour is triggered by the extra argument "callback". Do not add that argument if you are not using the callback. Callbacks use the node convention of having the error as first argument and the result as second. When you pass a function to "provides", the first argument of this function is always a object with an attribute for every dependency.
Diogenes is written is ES5 but it requires "Promise" support. Please provide a polyfill in the global namespace if promises are not supported.
Diogenes won't be possible without the work of many others. The inspiration came from many different patterns/libraries. In particular I want to thank you:
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.