Comparing version 2.0.0 to 3.0.0
{ | ||
"name": "diogenes", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "A dependency injection framework.", | ||
@@ -9,3 +9,3 @@ "main": "src/index.js", | ||
"watch": "npm run test -- -w", | ||
"lint": "./node_modules/.bin/eslint --ext .js ./src ./tests", | ||
"lint": "eslint --fix --ext .js ./src ./tests", | ||
"release:major": "./node_modules/.bin/npm-release major", | ||
@@ -26,16 +26,17 @@ "release:minor": "./node_modules/.bin/npm-release minor", | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"eslint": "^1.10.3", | ||
"husky": "^0.11.8", | ||
"mocha": "^3.0.2", | ||
"chai": "^4.1.2", | ||
"eslint": "^4.18.1", | ||
"eslint-config-standard": "^11.0.0", | ||
"eslint-plugin-import": "^2.9.0", | ||
"eslint-plugin-node": "^6.0.1", | ||
"eslint-plugin-promise": "^3.6.0", | ||
"eslint-plugin-standard": "^3.0.1", | ||
"husky": "^0.14.3", | ||
"mocha": "^5.0.1", | ||
"npm-release": "^1.0.0" | ||
}, | ||
"dependencies": { | ||
"async-deco": "^7.0.0", | ||
"little-ds-toolkit": "^0.3.0", | ||
"object-assign": "^4.1.0", | ||
"occamsrazor": "^5.0.1", | ||
"occamsrazor-validator": "^6.0.0", | ||
"setimmediate": "^1.0.5" | ||
"es6-promisify": "^6.0.0", | ||
"object-assign": "^4.1.1" | ||
} | ||
} |
517
README.md
@@ -31,10 +31,10 @@ Diogenes | ||
```js | ||
var Diogenes = require('diogenes'); | ||
var registry = Diogenes.getRegistry(); | ||
const Diogenes = require('diogenes'); | ||
const registry = Diogenes.getRegistry(); | ||
registry.service("database").provides(getDB); | ||
registry.service("passwordHashing").provides(getPasswordHashing); | ||
registry.service('database').provides(getDB); | ||
registry.service('passwordHashing').provides(getPasswordHashing); | ||
registry.service("users") | ||
.dependsOn(["database", "passwordHashing"]) | ||
registry.service('users') | ||
.dependsOn(['database', 'passwordHashing']) | ||
.provides(getUsers); | ||
@@ -45,6 +45,6 @@ ``` | ||
registry | ||
.instance(config) | ||
.run("users", function (err, users) { | ||
.run('users') | ||
.then((users) => { | ||
... | ||
}); | ||
}) | ||
``` | ||
@@ -56,8 +56,5 @@ 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. | ||
As a function, it has a specific interface. Its arguments are: | ||
It takes as argument an object containing the dependencies (output of other services). | ||
Optionally you can pass a callback. | ||
* a configuration, common to all services | ||
* an object containing the dependencies (output of other services) | ||
* an optional callback (if the function needs it) | ||
A service outputs a "dependency", this is identified with the service name. | ||
@@ -88,24 +85,30 @@ Services are organized inside a registry. The common interface allows to automate how the dependencies are resolved within the registry. | ||
registry.service("text") | ||
.returnsValue(["Diogenes became notorious for his philosophical ", | ||
"stunts such as carrying a lamp in the daytime, claiming to ", | ||
"be looking for an honest man."].join()); | ||
.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: | ||
```js | ||
registry.service("text").returns(function (config, deps) { | ||
var text = fs.readFileSync(config.path, {encoding: 'utf8'}); | ||
return text; | ||
}); | ||
registry | ||
.service("text") | ||
.provides((deps) => fs.readFileSync(deps.config.path, {encoding: 'utf8'})); | ||
``` | ||
The "config" argument is a generic configuration used for all services. | ||
The "returns" method can be used for synchronous functions, but it works even if you return promises! | ||
You can also define a service using a callback: | ||
If the function is asynchronous you can return a promise. It will work transparently: | ||
```js | ||
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: | ||
```js | ||
registry.service("text") | ||
.provides(function (config, deps, next) { | ||
fs.readFile(config.path, {encoding: 'utf8'}, next); | ||
}); | ||
.provides((deps, next) => { | ||
fs.readFile('diogenes.txt', {encoding: 'utf8'}, next); | ||
}); | ||
``` | ||
The callback uses 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. | ||
For synchronous functions you can throw an exception in case of errors as usual. | ||
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? | ||
@@ -116,31 +119,17 @@ Let's add other services: | ||
.dependsOn(['text']) | ||
.returns(function (config, deps) { | ||
return deps.text.split(' '); | ||
}); | ||
.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 will contain an attribute for every dependency, in this example: deps.text. | ||
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. | ||
```js | ||
registry.service("count") | ||
.dependsOn(['tokens']) | ||
.returns(function (config, deps) { | ||
return deps.tokens.length; | ||
}); | ||
.provides(({ tokens }) => tokens.length); | ||
registry.service("abstract") | ||
.dependsOn(['tokens']) | ||
.returns(function (config, deps) { | ||
var len = config.abstractLen; | ||
var ellipsis = config.abstractEllipsis; | ||
return deps.tokens.slice(0, len).join(' ') + ellipsis; | ||
}); | ||
.provides(({ tokens }) => tokens.slice(0, 20).join(' ') + '...'); | ||
registry.service("paragraph") | ||
.dependsOn(['text', 'abstract', 'count']) | ||
.returns(function (config, deps) { | ||
return { | ||
count: deps.count, | ||
abstract: deps.abstract, | ||
text: deps.text | ||
}; | ||
}); | ||
.provides(({ text, abstract, count }) => ({text, abstract, count})); | ||
``` | ||
@@ -152,11 +141,6 @@ This is how services relates each other: | ||
----------------- | ||
You can call a service using the method "run" on a registry instance. The "instance" method returns a registry instance using a specific configuration. | ||
You can call a service using the method "run" on a registry. | ||
```js | ||
var registryInstance = registry.instance({abstractLen: 5, abstractEllipsis: "..."}); | ||
registryInstance.run("paragraph", function (err, p){ | ||
if (err){ | ||
console.log("Something went wrong!"); | ||
} | ||
else { | ||
registry.run('paragraph') | ||
.then((p) => { | ||
console.log("This paragraph is " + p.count + " words long"); | ||
@@ -166,91 +150,32 @@ console.log("The abstract is: " + p.anstract); | ||
console.log(p.text); | ||
} | ||
}); | ||
}) | ||
.catch((err) => console.log(err.message)) | ||
``` | ||
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: | ||
p will be the output of the paragraph service. You can alternatively pass a callback to "run". | ||
```js | ||
registryInstance.run(["count", "abstract"], function (err, deps){ | ||
... | ||
}); | ||
``` | ||
In this case the second argument will contain an object with an attribute for each dependency (deps.count, deps.abstract). | ||
Using "run", Diogenes calls all services required to satisfy the dependencies tree. You can get the ordering using: | ||
```js | ||
var dependencies = registryInstance.getExecutionOrder("paragraph"); | ||
``` | ||
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. | ||
Plugins | ||
------- | ||
A service can contain more than one function, and more than one set of dependencies. | ||
Let's say for example that you want to use a different way to get the abstract: | ||
```js | ||
var validator = require('occamsrazor-validator'); | ||
var useAlternativeClamp = validator().match({abstractClamp: "chars"}); | ||
registry.service("abstract") | ||
.dependsOn(useAlternativeClamp, ['text']) | ||
.provides(useAlternativeClamp, function (config, deps, next) { | ||
var len = config.abstractLen; | ||
var ellipsis = config.abstractEllipsis; | ||
next(undefined, deps.text.slice(0, len) + ellipsis); | ||
}); | ||
``` | ||
"useAlternativeClamp" is an [occamsrazor validator](https://github.com/sithmel/occamsrazor-validator). | ||
A validator is a function that identifies if a value matches some criteria and assigns a score to the match. This helps to choose what dependencies and what service use. | ||
The "dependsOn" method can take one validator. If it matches the config, this different set of dependencies will be used. | ||
The "provides", "returns" and "returnsValue" methods can take 2 validators. The first one will match the config, the second the dependencies. | ||
So you can change on the fly which function to use depending on the arguments (config and deps). | ||
![Registry as graph](https://cloud.githubusercontent.com/assets/460811/11994528/0fade84a-aa38-11e5-92d2-4f4d8f60dc4d.png) | ||
```js | ||
var registryInstance = registry.instance({abstractLen: 5, abstractEllipsis: "...", abstractClamp: "chars"}); | ||
var dependencies = registryInstance.getExecutionOrder("paragraph"); | ||
``` | ||
will output: ["text", "abstract", "tokens", "count", "paragraph"]. | ||
You can run the service as usual: | ||
```js | ||
registryInstance.run("paragraph", function (err, p){ | ||
if (err){ | ||
console.log("Something went wrong!"); | ||
registry.run('paragraph', (err, p) => { | ||
if (err) { | ||
console.log(err.message) | ||
return; | ||
} | ||
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); | ||
} | ||
}); | ||
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); | ||
}) | ||
``` | ||
You have just extended the system without changing the original code! | ||
Caching a service | ||
----------------- | ||
If the result of a service depends only on the configuration, you can cache it. | ||
The "cache" method takes an object as argument with 3 different attributes: | ||
* key: (a function or a string) it generates the key to use as cache key. It default to a single key (it will store a single value). It can also be a string with the name of a config attribute to use as a cache key | ||
* maxAge: the time in ms for preserving the cache. Default to infinity. | ||
* maxLen: the length of the cache. Default to infinity | ||
Note: a cached service, when using the cached value, it will return no dependencies. After all if the service has a defined return value it doesn't need to relay on any other service. | ||
So for example: | ||
If you need more than one service, you can pass a list of services: | ||
```js | ||
registry.service('count') | ||
.cache({ | ||
key: function (config){ | ||
return config.abstractLen; | ||
}, | ||
maxAge: 1000 | ||
}); | ||
registry.run(["count", "abstract"]) | ||
.then({ count, paragraph } => { | ||
... | ||
}) | ||
``` | ||
Lastly, using "cache" without options you will cache the first value forever. | ||
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. | ||
Errors | ||
====== | ||
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. They will propagate the exception to the services depending on them. | ||
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. | ||
@@ -271,8 +196,5 @@ Syntax | ||
Registry's attributes | ||
===================== | ||
Registry | ||
======== | ||
Registry's methods | ||
================== | ||
service | ||
@@ -285,106 +207,94 @@ ------- | ||
instance | ||
-------- | ||
Returns a an registryInstance object. It is a registry with a configuration and it is used to run services. | ||
```js | ||
registry.instance(config, options); | ||
``` | ||
The config argument will be passed to all services (calling the run method). The only option available is: | ||
* limit: limit the number of services executed in parallel (defaults to Infinity) | ||
remove | ||
------ | ||
It remove a service from the registry: | ||
```js | ||
registry.remove(name); | ||
``` | ||
It returns the registry. | ||
init | ||
---- | ||
Helper function. It runs a group of functions with the registry as "this". Useful for initializing the registry. | ||
Helper function. It runs a group of functions with the registry as first argument. Useful for initializing the registry. | ||
```js | ||
/*module1 fir example*/ | ||
module.exports = function (){ | ||
this.service('service1').provides(...); | ||
/* module1 for example */ | ||
module.exports = (registry) => { | ||
registry.service('service1').provides(...); | ||
}; | ||
/*main*/ | ||
var module1 = require('module1'); | ||
var module2 = require('module2'); | ||
/* main */ | ||
const module1 = require('module1'); | ||
const module2 = require('module2'); | ||
registry.init([module1, module2]); | ||
``` | ||
forEach | ||
------- | ||
It runs a callback for any service registered. | ||
run | ||
--- | ||
It executes all the dependency tree required by a service and return a promise that will resolve to the service itself. | ||
```js | ||
registry.forEach(function (service, name){ | ||
// the service is also "this" | ||
registry.run(serviceName) | ||
.then((service) => { | ||
... | ||
}); | ||
``` | ||
It can also use a callback: | ||
```js | ||
registry.run(serviceName, (err, service) => { | ||
... | ||
}); | ||
``` | ||
The callback uses the node convention (error as first argument). | ||
Service's attributes | ||
==================== | ||
* name: the name of the service (cannot be changed) | ||
Service's methods | ||
================== | ||
You can get a service from the registry with the "service" method. | ||
You can also execute more than one service passing an array of names: | ||
```js | ||
var service = registry.service("service1"); | ||
registry.run(['service1', 'service2']) | ||
.then(({ service1, service2 }) => { | ||
... | ||
}) | ||
``` | ||
All the service methods returns a service instance so they can be chained. | ||
or using a regular expression: | ||
```js | ||
registry.run(/service[0-9]?/) | ||
.then(({ service1, service2 }) => { | ||
... | ||
}) | ||
``` | ||
provides | ||
-------- | ||
It adds a function to the service. This function uses a callback following the node.js convention: | ||
merge/clone | ||
----------- | ||
It allows to create a new registry, merging services of different registries: | ||
```js | ||
service.provides(func); | ||
service.provides(configValidator, func); | ||
service.provides(configValidator, dependenciesValidator, func); | ||
const registry4 = registry1.merge(registry2, registry3) | ||
``` | ||
The function has this signature: (config, deps, next). | ||
* "config" is a value passed to all services when "run" is invoked | ||
* "deps" is an object. It has as many properties as the dependencies of this service. The attributes of deps have the same name of the respective dependency. | ||
* "next" is the function called with the output of this service: next(undefined, output). | ||
* If something goes wrong you can pass the error as first argument: next(new Error('Something wrong!')). | ||
If you use the signature without "next" you can return the value using return, or throw an exception in case of errors. | ||
"configValidator" and "dependencyValidator" are [occamsrazor validators](https://github.com/sithmel/occamsrazor-validator). You can also pass a value as explained in the ["match" validator](https://github.com/sithmel/occamsrazor-validator#validatormatch). | ||
They matches respectively the first and second argument of the function. | ||
"this" will be the service itself. | ||
Optionally is possible to define an array instead of a function. See the section "decorators" above for further explanations. | ||
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. | ||
returns | ||
------- | ||
It adds a function to the service. This function returns its output with "return" | ||
getAdjList | ||
---------- | ||
It returns the adjacency list in the following format: | ||
```js | ||
service.returns(func); | ||
/* | ||
A ----> B | ||
| / | | ||
| / | | ||
| / | | ||
| / | | ||
| / | | ||
VV V | ||
C ----> D | ||
*/ | ||
service.returns(configValidator, func); | ||
service.returns(configValidator, dependenciesValidator, func); | ||
registry.getAdjList(); | ||
/* returns | ||
{ | ||
'A': [], | ||
'B': ['A'], | ||
'C': ['A', 'B'], | ||
'D': ['B', 'C'] | ||
} | ||
*/ | ||
``` | ||
The function has this signature: (config, deps). | ||
* "config" is a value passed to all services when "run" is invoked | ||
* "deps" is an object. It has as many properties as the dependencies of this service. The attributes of deps have the same name of the respective dependency. | ||
"configValidator" and "dependencyValidator" works the same as in the "provides" method. | ||
returnsValue | ||
------------ | ||
It works the same as the previous ones but instead of adds a function it adds a value. This will be the dependency returned. | ||
Service | ||
======= | ||
You can get a service from the registry with the "service" method. | ||
```js | ||
service.returnsValue(value); | ||
service.returnsValue(configValidator, value); | ||
service.returnsValue(configValidator, dependencyValidator, value); | ||
const service = registry.service('service1'); | ||
``` | ||
All the service methods returns a service instance so they can be chained. | ||
dependsOn | ||
--------- | ||
It defines the dependencies of a service. It may be an array or a function returning an array. The function takes "config" as argument: | ||
It defines the dependencies of a service. It may be an array or a function returning an array of strings (service names): | ||
```js | ||
@@ -394,172 +304,33 @@ service.dependsOn(array); | ||
service.dependsOn(func); | ||
service.dependsOn(configValidator, array); | ||
service.dependsOn(configValidator, func); | ||
``` | ||
cache | ||
----- | ||
Set the cache for this service. It takes as argument the cache configuration: | ||
provides | ||
-------- | ||
You can pass a function taking a dependencies object as argument, and returning a promise. | ||
```js | ||
service.cache(config); | ||
service.provides((deps) => ...); | ||
``` | ||
The configuration contains 3 parameters: | ||
* key: (a string/an array or a function) it generates the key to use as cache key. You can specify an attribute of the configuration (string), or use a custom function running on the configuration. It default to a single key (it will store a single value) | ||
* maxAge: the time in ms for preserving the cache. Default to infinity. | ||
* maxLen: the length of the cache. Default to infinity | ||
RegistryInstance's methods | ||
======================= | ||
This object is returned with the "instance" registry method. | ||
getExecutionOrder | ||
----------------- | ||
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 the caching (a cached dependency has no dependency!): | ||
You can also pass any "non function" argument: | ||
```js | ||
registryInstance.getExecutionOrder(name); | ||
service.provides(42); // Any non function argument | ||
``` | ||
run | ||
--- | ||
It executes all the dependency tree required by the service and call the function. All the services are called using the configuration used in the method "instance": | ||
A synchronous function: | ||
```js | ||
registryInstance.run(name, func); | ||
service.provides((deps) => deps.something * 2); | ||
``` | ||
The function takes 2 arguments: | ||
* an error | ||
* the value of the service required | ||
You can also ask to execute more than one service passing an array of names: | ||
Or callback: | ||
```js | ||
registryInstance.run(names, func); | ||
service.provides((deps, callback) => callback(null, deps.something * 2)); | ||
``` | ||
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. | ||
If you pass a regular expression you will execute every service with the name matching the regexp: | ||
```js | ||
registryInstance.run(regexp, func); | ||
``` | ||
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. | ||
The context (this) of this function is the registry itself. | ||
Compatibility | ||
============= | ||
Diogenes is written is ES5 but it requires "Promise" support. Please provide a polyfill in the global namespace if promises are not supported. | ||
It returns the registry instance. | ||
runP | ||
---- | ||
This method is equivalent to "run" but it returns a promise instead of using a callback. | ||
Errors in the services graph | ||
============================ | ||
The library is currently able to detect and throws exceptions in a few cases: | ||
* circular dependencies | ||
* missing dependencies (or incompatible plugin) | ||
* more than one plug-in matches | ||
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. | ||
Tricks and tips | ||
=============== | ||
Where to apply side effects | ||
--------------------------- | ||
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. | ||
Run a service defined in a closure | ||
---------------------------------- | ||
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: | ||
```js | ||
var express = require('express'); | ||
var app = express(); | ||
var Diogenes = require('diogenes'); | ||
var registry = new Diogenes(); | ||
registry.service('hello') | ||
.dependsOn(['req', 'res']) | ||
.provides(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.service('req').returns(req); | ||
localReg.service('res').returns(res); | ||
registry.merge(localReg).instance(config).run('hello'); | ||
}); | ||
app.listen(3000); | ||
``` | ||
Using events for intra service communication | ||
-------------------------------------------- | ||
You can use an event bus, such as one generated by [occamsrazor](https://github.com/sithmel/occamsrazor.js) to make one service communicate with another: | ||
```js | ||
var c = 0; | ||
var or = require('occamsrazor'); | ||
registry | ||
.service('events').returnsValue(or()); | ||
registry | ||
.dependsOn(['events']) | ||
.service('counter-button').provides(function (config, deps, next){ | ||
document.getElementById('inc').addEventListener("click", function (){ | ||
c++; | ||
console.log(c); | ||
}); | ||
events.on("reset-event", function (){ | ||
c = 0; | ||
}); | ||
next(); | ||
}); | ||
registry | ||
.dependsOn(['events']) | ||
.service('reset-button').provides(function (config, deps, next){ | ||
document.getElementById('reset').addEventListener("click", function (){ | ||
events.trigger("reset-event"); | ||
}); | ||
next(); | ||
}); | ||
registry.run(['counter-button', 'reset-button']); | ||
``` | ||
Another example: a service requires to do some clean up after its execution. In this case you can leverage the event system: | ||
```js | ||
var or = require('occamsrazor'); | ||
registry | ||
.service('events').returnsValue(or()); | ||
var registry = new Diogenes(); | ||
... | ||
registry | ||
.dependsOn(['events']) | ||
.service('database-connection').provides(function (config, deps){ | ||
var connection = ..... I get the connection here | ||
events.on('done', function (){ | ||
connection.dispose(); | ||
}); | ||
next(); | ||
}); | ||
registry.run(['main-service', 'events'], function (err, dep){ | ||
... | ||
events.trigger('done'); | ||
}); | ||
``` | ||
Acknowledgements | ||
================ | ||
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: | ||
* the great explanation of the Depth First Search (DFS) in the Stanford algorithm online course on Coursera. | ||
* the [zope component architecture](http://muthukadan.net/docs/zca.html) for showing how to build an extensible system | ||
* [architect](https://github.com/c9/architect) for introducing the idea of initialising a system using the dependency injection pattern | ||
* [electrician](https://github.com/tes/electrician) that explored a way to wrap the code in "components" having a common interface, that is a prerequisite of having them working together. | ||
* [electrician](https://github.com/tes/electrician) that explored a way to wrap the code in "components" having a common interface, that is a prerequisite of having them working together |
@@ -1,3 +0,2 @@ | ||
require('setimmediate'); | ||
var Registry = require('./registry'); | ||
module.exports = Registry; | ||
var Registry = require('./registry') | ||
module.exports = Registry |
@@ -1,10 +0,10 @@ | ||
function DiogenesError(message) { | ||
this.name = 'DiogenesError'; | ||
this.message = message || 'DiogenesError'; | ||
this.stack = (new Error()).stack; | ||
function DiogenesError (message) { | ||
this.name = 'DiogenesError' | ||
this.message = message || 'DiogenesError' | ||
this.stack = (new Error()).stack | ||
} | ||
DiogenesError.prototype = Object.create(Error.prototype); | ||
DiogenesError.prototype.constructor = DiogenesError; | ||
DiogenesError.prototype = Object.create(Error.prototype) | ||
DiogenesError.prototype.constructor = DiogenesError | ||
module.exports = DiogenesError; | ||
module.exports = DiogenesError |
@@ -1,73 +0,130 @@ | ||
var assign = require('object-assign'); | ||
var or = require('occamsrazor'); | ||
var assign = require('object-assign') | ||
var Service = require('./service') | ||
var DiogenesError = require('./lib/diogenes-error') | ||
var Service = require('./service'); | ||
var RegistryInstance = require('./registry-instance'); | ||
var DiogenesError = require('./lib/diogenes-error'); | ||
/* | ||
Registry object | ||
*/ | ||
function Registry() { | ||
this.services = {}; | ||
function Registry () { | ||
this.services = {} | ||
} | ||
Registry.getRegistry = function registry_getRegistry() { | ||
return new Registry(); | ||
}; | ||
Registry.getRegistry = function registryGetRegistry () { | ||
return new Registry() | ||
} | ||
Registry.prototype.init = function registry_init(funcs) { | ||
Registry.prototype.init = function registryInit (funcs) { | ||
for (var i = 0; i < funcs.length; i++) { | ||
funcs[i].apply(this); | ||
funcs[i].call(this, this) | ||
} | ||
}; | ||
} | ||
Registry.prototype.forEach = function registry_forEach(callback) { | ||
for (var name in this.services) { | ||
callback.call(this.services[name], this.services[name], name); | ||
Registry.prototype.service = function registryService (name) { | ||
if (typeof name !== 'string') { | ||
throw new DiogenesError('Diogenes: the name of the service should be a string') | ||
} | ||
}; | ||
Registry.prototype.merge = function registry_merge() { | ||
var registry = new Registry(); | ||
if (!(name in this.services)) { | ||
this.services[name] = new Service(name, this) | ||
} | ||
var services = Array.prototype.map.call(arguments, function (reg) { | ||
return reg.services; | ||
}); | ||
return this.services[name] | ||
} | ||
services.unshift(this.services); | ||
services.unshift({}); | ||
Registry.prototype.getAdjList = function registryInstanceGetAdjList () { | ||
var adjList = {} | ||
Object.keys(this.services) | ||
.map(this.service.bind(this)) | ||
.forEach(function (service) { | ||
adjList[service.name] = service._deps() | ||
}) | ||
return adjList | ||
} | ||
registry.services = assign.apply(null, services); | ||
return registry; | ||
}; | ||
Registry.prototype._run = function registryRun (name) { | ||
var registry = this | ||
var c = 0 | ||
Registry.prototype.clone = function registry_clone() { | ||
return this.merge(); | ||
}; | ||
function getPromiseFromStr (str) { | ||
if (c++ > 1000) { | ||
throw new DiogenesError('Diogenes: circular dependency') | ||
} | ||
if (!(str in registry.services)) { | ||
return Promise.reject(new DiogenesError('Diogenes: missing dependency: ' + str)) | ||
} | ||
Registry.prototype.service = function registry_service(name) { | ||
if (typeof name !== 'string') { | ||
throw new DiogenesError('Diogenes: the name of the service should be a string'); | ||
var deps = registry.services[str]._getDeps() | ||
if (deps.length === 0) { | ||
return registry.services[str]._run({}) | ||
} | ||
return getPromisesFromStrArray(deps) | ||
.then(registry.services[str]._run.bind(registry.services[str])) | ||
} | ||
if (!(name in this.services)) { | ||
this.services[name] = new Service(name, this); | ||
function getPromisesFromStrArray (strArray) { | ||
return Promise.all(strArray.map(getPromiseFromStr)) | ||
.then(function (results) { | ||
var out = {} | ||
for (var i = 0; i < strArray.length; i++) { | ||
out[strArray[i]] = results[i] | ||
} | ||
return out | ||
}) | ||
} | ||
return this.services[name]; | ||
}; | ||
try { | ||
return getPromiseFromStr(name) | ||
} catch (e) { | ||
return Promise.reject(e) | ||
} | ||
} | ||
Registry.prototype.remove = function registry_remove(name) { | ||
delete this.services[name]; | ||
return this; | ||
}; | ||
Registry.prototype.run = function registryRun (name, done) { | ||
var promise | ||
if (typeof name === 'string') { | ||
promise = this._run(name, done) | ||
} else { | ||
if (name instanceof RegExp) { | ||
name = Object.keys(this.services).filter(RegExp.prototype.test.bind(name)) | ||
} | ||
Registry.prototype.instance = function registry_instance(config, options) { | ||
return new RegistryInstance(this, config, options); | ||
}; | ||
var tempreg = this.clone() | ||
module.exports = Registry; | ||
tempreg.service('__temp__').dependsOn(name) | ||
.provides(function (deps) { | ||
return Promise.resolve(deps) | ||
}) | ||
promise = tempreg.run('__temp__') | ||
} | ||
if (done) { | ||
promise | ||
.then(function (res) { | ||
done(null, res) | ||
}) | ||
.catch(function (err) { | ||
done(err) | ||
}) | ||
return this | ||
} else { | ||
return promise | ||
} | ||
} | ||
Registry.prototype.merge = Registry.prototype.clone = function registryMerge () { | ||
var registry = new Registry() | ||
var services = Array.prototype.map.call(arguments, function (reg) { | ||
return reg.services | ||
}) | ||
services.unshift(this.services) | ||
services.unshift({}) | ||
registry.services = assign.apply(null, services) | ||
return registry | ||
} | ||
module.exports = Registry |
@@ -1,149 +0,62 @@ | ||
var or = require('occamsrazor'); | ||
var validator = require('occamsrazor-validator'); | ||
var Cache = require('./lib/cache'); | ||
var DiogenesError = require('./lib/diogenes-error'); | ||
var callbackifyDecorator = require('async-deco/utils/callbackify'); | ||
var proxyDecorator = require('async-deco/callback/proxy'); | ||
var decorate = require('async-deco/utils/decorate'); | ||
var depsHasError = proxyDecorator(function depsHasError(config, deps, next) { | ||
var depsList = Object.keys(deps); | ||
for (var i = 0; i < depsList.length; i++) { | ||
if (deps[depsList[i]] instanceof Error) { | ||
return next(deps[depsList[i]]); // one of the deps is an error | ||
} | ||
} | ||
next(); | ||
}); | ||
function getValidator(v) { | ||
return (typeof v === 'function' && 'score' in v) ? v : validator().match(v); | ||
} | ||
/* | ||
Service object | ||
*/ | ||
var promisify = require('es6-promisify').promisify | ||
function Service(name, registry) { | ||
this.name = name; | ||
this._registry = registry; // backreference | ||
this._funcs = or(); | ||
this._deps = or().add(function () { | ||
return []; | ||
}); | ||
this._mainCache = undefined; // caching | ||
function Service (name, registry) { | ||
this.name = name | ||
this._registry = registry // backreference | ||
this._deps = function () { return [] } | ||
this._func = function () { return Promise.resolve() } | ||
this._cache = undefined | ||
} | ||
Service.prototype.registry = function service_registry() { | ||
return this._registry; | ||
}; | ||
Service.prototype.registry = function serviceRegistry () { | ||
return this._registry | ||
} | ||
Service.prototype.dependsOn = function service_dependsOn() { | ||
var deps = arguments[arguments.length - 1]; | ||
var depsFunc = typeof deps === 'function' ? deps : function () {return deps;}; | ||
if (arguments.length > 1) { | ||
this._deps.add(getValidator(arguments[0]), depsFunc); | ||
} | ||
else { | ||
this._deps.add(validator(), depsFunc); | ||
} | ||
return this; | ||
}; | ||
Service.prototype.dependsOn = function serviceDependsOn (deps) { | ||
this._deps = typeof deps === 'function' ? deps : function () { return deps } | ||
return this | ||
} | ||
Service.prototype._returns = function service__returns(isSync) { | ||
var func = decorate(depsHasError, isSync ? callbackifyDecorator(arguments[arguments.length - 1]) : arguments[arguments.length - 1]); | ||
var adapter = function () { | ||
return { func: func }; | ||
}; | ||
if (arguments.length > 3) { | ||
this._funcs.add(getValidator(arguments[1]), | ||
getValidator(arguments[2]), adapter); | ||
Service.prototype.provides = function serviceProvides (func) { | ||
if (typeof func !== 'function') { | ||
this._func = function () { Promise.resolve(func) } // plain value | ||
} else if (func.length > 1) { | ||
this._func = promisify(func) // callback function | ||
} else { | ||
this._func = function (deps) { // sync function or return promise | ||
try { | ||
var res = func(deps) | ||
} catch (e) { | ||
return Promise.reject(e) | ||
} | ||
if (res instanceof Object && 'then' in res) { | ||
return res | ||
} else { | ||
return Promise.resolve(res) | ||
} | ||
} | ||
} | ||
else if (arguments.length > 2) { | ||
this._funcs.add(getValidator(arguments[1]), adapter); | ||
} | ||
else { | ||
this._funcs.add(validator(), adapter); | ||
} | ||
return this; | ||
}; | ||
return this | ||
} | ||
Service.prototype.provides = function service_provides() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
args.unshift(false); // isSync | ||
return this._returns.apply(this, args); | ||
}; | ||
Service.prototype.returns = function service_returns() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
args.unshift(true); // isSync | ||
return this._returns.apply(this, args); | ||
}; | ||
Service.prototype.returnsValue = function service_returnsValue() { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
var value = args[args.length - 1]; | ||
args[args.length - 1] = function (conf, deps) { | ||
return value; | ||
}; | ||
args.unshift(true); // isSync | ||
return this._returns.apply(this, args); | ||
}; | ||
Service.prototype._getFunc = function service__getFunc(config, deps, context, callback) { | ||
var obj = this._funcs(config, deps); | ||
var service = this; | ||
var func = obj.func; | ||
return function () { | ||
func.call(context, config, deps, function (err, dep) { | ||
var d = err ? err : dep; | ||
return callback(service.name, d); | ||
}); | ||
}; | ||
}; | ||
Service.prototype._getDeps = function service__getDeps(config) { | ||
var service = this; | ||
var cacheState = {}; | ||
if (this._mainCache) { // cache check here !!! | ||
cacheState.cached = this._mainCache.has(config); | ||
if (cacheState.cached) { | ||
cacheState.hit = this._mainCache.get(config); | ||
} | ||
Service.prototype._run = function serviceRun (deps) { | ||
var service = this | ||
if (service._cache) { | ||
return service._cache | ||
} | ||
else { | ||
cacheState.cached = false; | ||
} | ||
if (cacheState.cached) { | ||
// cache hit! | ||
return { | ||
name: service.name, | ||
deps: [], // no dependencies needed for cached values | ||
cached: cacheState.hit | ||
}; | ||
} | ||
else { | ||
return { | ||
name: service.name, | ||
deps: service._deps(config) | ||
}; | ||
} | ||
}; | ||
service._cache = service._func(deps) | ||
.catch(function (err) { | ||
service._cache = undefined | ||
return Promise.reject(err) | ||
}) | ||
return service._cache | ||
} | ||
Service.prototype.cache = function service_cache(opts) { | ||
this._mainCache = new Cache(opts); | ||
return this; | ||
}; | ||
Service.prototype._getDeps = function serviceGetDeps () { | ||
return this._cache ? [] : this._deps() | ||
} | ||
Service.prototype._cachePush = function service__cachePush(config, output) { | ||
if (this._mainCache) { | ||
this._mainCache.set(config, output); | ||
} | ||
}; | ||
module.exports = Service; | ||
module.exports = Service |
@@ -1,6 +0,7 @@ | ||
var Diogenes = require('../src'); | ||
var assert = require('chai').assert; | ||
/* eslint-env node, mocha */ | ||
var Diogenes = require('../src') | ||
var assert = require('chai').assert | ||
describe('async parallel execution', function (done) { | ||
var str, registry; | ||
var str, registry | ||
@@ -21,43 +22,34 @@ beforeEach(function () { | ||
registry = Diogenes.getRegistry(); | ||
registry = Diogenes.getRegistry() | ||
str = ''; | ||
str = '' | ||
registry.service('A').provides(function (config, deps, next) { | ||
registry.service('A').provides(function (deps, next) { | ||
setTimeout(function () { | ||
str += 'A'; | ||
next(undefined, 'A'); | ||
}, 50); | ||
}); | ||
str += 'A' | ||
next(undefined, 'A') | ||
}, 50) | ||
}) | ||
registry.service('B').provides(function (config, deps, next) { | ||
registry.service('B').provides(function (deps, next) { | ||
setTimeout(function () { | ||
str += 'B'; | ||
next(undefined, 'B'); | ||
}, 20); | ||
}); | ||
str += 'B' | ||
next(undefined, 'B') | ||
}, 20) | ||
}) | ||
registry.service('C').dependsOn(['A', 'B']).provides(function (config, deps, next) { | ||
str += 'C'; | ||
next(undefined, deps.A + deps.B + 'C'); | ||
}); | ||
registry.service('C').dependsOn(['A', 'B']).provides(function (deps, next) { | ||
str += 'C' | ||
next(undefined, deps.A + deps.B + 'C') | ||
}) | ||
}) | ||
}); | ||
it('must run service asynchronously', function (done) { | ||
registry.instance({}).run('C', function (err, dep) { | ||
assert.equal(str, 'BAC'); | ||
assert.equal(dep, 'ABC'); | ||
done(); | ||
}); | ||
}); | ||
it('must run service synchronously', function (done) { | ||
registry.instance({}, {limit: 1}).run('C', function (err, dep) { | ||
assert.equal(str, 'ABC'); | ||
assert.equal(dep, 'ABC'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
registry.run('C', function (err, dep) { | ||
if (err) return | ||
assert.equal(str, 'BAC') | ||
assert.equal(dep, 'ABC') | ||
done() | ||
}) | ||
}) | ||
}) |
@@ -1,6 +0,7 @@ | ||
var Diogenes = require('../src'); | ||
var assert = require('chai').assert; | ||
/* eslint-env node, mocha */ | ||
var Diogenes = require('../src') | ||
var assert = require('chai').assert | ||
describe('dfs: 4 functions', function (done) { | ||
var registry; | ||
var registry | ||
@@ -20,117 +21,93 @@ beforeEach(function () { | ||
*/ | ||
registry = Diogenes.getRegistry(); | ||
registry.service('A').provides(function (config, deps, next) { | ||
next(undefined, 'A'); | ||
}); | ||
registry = Diogenes.getRegistry() | ||
registry.service('A').provides(function (deps, next) { | ||
next(undefined, 'A') | ||
}) | ||
registry.service('B').dependsOn(['A']).provides(function (config, deps, next) { | ||
next(undefined, deps['A'] + 'B'); | ||
}); | ||
registry.service('B').dependsOn(['A']).provides(function (deps, next) { | ||
next(undefined, deps['A'] + 'B') | ||
}) | ||
registry.service('C').dependsOn(['A', 'B']).provides(function (config, deps, next) { | ||
next(undefined, deps['A'] + deps['B'] + 'C'); | ||
}); | ||
registry.service('C').dependsOn(['A', 'B']).provides(function (deps, next) { | ||
next(undefined, deps['A'] + deps['B'] + 'C') | ||
}) | ||
registry.service('D').dependsOn(['B', 'C']).provides(function (config, deps, next) { | ||
next(undefined, deps['B'] + deps['C'] + 'D'); | ||
}); | ||
}); | ||
registry.service('D').dependsOn(['B', 'C']).provides(function (deps, next) { | ||
next(undefined, deps['B'] + deps['C'] + 'D') | ||
}) | ||
}) | ||
it('must return leftmost service', function (done) { | ||
registry.instance({}).run('A', function (err, dep) { | ||
assert.deepEqual(dep, 'A'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('A', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'A') | ||
done() | ||
}) | ||
}) | ||
it('must return middle service (1)', function (done) { | ||
registry.instance({}).run('B', function (err, dep) { | ||
assert.deepEqual(dep, 'AB'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('B', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'AB') | ||
done() | ||
}) | ||
}) | ||
it('must return middle service (2)', function (done) { | ||
registry.instance({}).run('C', function (err, dep) { | ||
assert.deepEqual(dep, 'AABC'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('C', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'AABC') | ||
done() | ||
}) | ||
}) | ||
it('must return rightmost service', function (done) { | ||
registry.instance({}).run('D', function (err, dep) { | ||
assert.deepEqual(dep, 'ABAABCD'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('D', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'ABAABCD') | ||
done() | ||
}) | ||
}) | ||
it('must return execution order', function () { | ||
var list = registry.instance({}).getExecutionOrder('D'); | ||
assert.deepEqual(list, [ 'A', 'B', 'C', 'D' ]); | ||
}); | ||
it('must return adjList', function () { | ||
assert.deepEqual(registry.instance({}).getAdjList(), | ||
assert.deepEqual(registry.getAdjList(), | ||
{ | ||
'A': [], | ||
'B': ['A'], | ||
'C': ['A','B'], | ||
'D': ['B','C'] | ||
}); | ||
}); | ||
'C': ['A', 'B'], | ||
'D': ['B', 'C'] | ||
}) | ||
}) | ||
it('must replace node', function () { | ||
registry.remove('D'); | ||
registry.service('D').dependsOn(['A']).provides(function (config, deps, next) { | ||
next(undefined, deps['A'] + 'D'); | ||
}); | ||
var list = registry.instance({}).getExecutionOrder('D'); | ||
assert.deepEqual(list, [ 'A', 'D' ]); | ||
}); | ||
it('must run without config', function (done) { | ||
registry.instance().run('D', function (err, dep) { | ||
assert.deepEqual(dep, 'ABAABCD'); | ||
done(); | ||
}); | ||
}); | ||
it('must run without config and callback', function (done) { | ||
registry.instance().run('D'); | ||
it('must run without callback', function (done) { | ||
registry.run('D') | ||
setTimeout(function () { | ||
done(); | ||
}, 20); | ||
}); | ||
done() | ||
}, 20) | ||
}) | ||
it('must run more than one service', function (done) { | ||
registry.instance({}).run(['A', 'D'], function (err, deps) { | ||
assert.deepEqual(deps.A, 'A'); | ||
assert.deepEqual(deps.D, 'ABAABCD'); | ||
done(); | ||
}); | ||
}); | ||
registry.run(['A', 'D'], function (err, deps) { | ||
if (err) return | ||
assert.deepEqual(deps.A, 'A') | ||
assert.deepEqual(deps.D, 'ABAABCD') | ||
done() | ||
}) | ||
}) | ||
it('must run more than one service, no config', function (done) { | ||
registry.instance().run(['A', 'D'], function (err, deps) { | ||
assert.deepEqual(deps.A, 'A'); | ||
assert.deepEqual(deps.D, 'ABAABCD'); | ||
done(); | ||
}); | ||
}); | ||
it('must run more than one service, no config, no callback', function (done) { | ||
registry.instance().run(['A', 'D']); | ||
registry.run(['A', 'D']) | ||
setTimeout(function () { | ||
done(); | ||
}, 20); | ||
}); | ||
done() | ||
}, 20) | ||
}) | ||
it('must run more than one service using regexp', function (done) { | ||
registry.instance({}).run(/(A|B)/, function (err, deps) { | ||
assert.deepEqual(deps.A, 'A'); | ||
assert.deepEqual(deps.B, 'AB'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
registry.run(/(A|B)/, function (err, deps) { | ||
if (err) return | ||
assert.deepEqual(deps.A, 'A') | ||
assert.deepEqual(deps.B, 'AB') | ||
done() | ||
}) | ||
}) | ||
}) |
@@ -1,36 +0,33 @@ | ||
var Diogenes = require('../src'); | ||
var assert = require('chai').assert; | ||
var DiogenesError = require('../src/lib/diogenes-error'); | ||
var validator = require('occamsrazor-validator'); | ||
/* eslint-env node, mocha */ | ||
var Diogenes = require('../src') | ||
var assert = require('chai').assert | ||
var DiogenesError = require('../src/lib/diogenes-error') | ||
describe('diogenes merge registries', function () { | ||
var registry1, registry2, registry3 | ||
var registry1, registry2, registry3; | ||
beforeEach(function () { | ||
registry1 = Diogenes.getRegistry(); | ||
registry2 = Diogenes.getRegistry(); | ||
registry1.service('answer').returnsValue(42); | ||
registry2.service('question').returnsValue('the answer to life the universe and everything'); | ||
registry3 = registry1.merge(registry2); | ||
}); | ||
registry1 = Diogenes.getRegistry() | ||
registry2 = Diogenes.getRegistry() | ||
registry1.service('answer').provides(42) | ||
registry2.service('question').provides('the answer to life the universe and everything') | ||
registry3 = registry1.merge(registry2) | ||
}) | ||
it('must be different from previous registries', function () { | ||
assert.notEqual(registry1, registry3); | ||
assert.notEqual(registry2, registry3); | ||
}); | ||
assert.notEqual(registry1, registry3) | ||
assert.notEqual(registry2, registry3) | ||
}) | ||
it('must copy the services', function () { | ||
assert.equal(Object.keys(registry3.services).length, 2); | ||
}); | ||
}); | ||
assert.equal(Object.keys(registry3.services).length, 2) | ||
}) | ||
}) | ||
describe('registry', function () { | ||
var registry, | ||
isAnything; | ||
var registry | ||
beforeEach(function () { | ||
registry = Diogenes.getRegistry(); | ||
isAnything = validator(); | ||
}); | ||
registry = Diogenes.getRegistry() | ||
}) | ||
@@ -40,454 +37,145 @@ describe('init', function () { | ||
registry.init([function () { | ||
assert.equal(registry, this); | ||
}]); | ||
}); | ||
}); | ||
assert.equal(registry, this) | ||
}]) | ||
}) | ||
}) | ||
it('must return a service in a simple case (1 function)', function (done) { | ||
registry.service('hello').provides(function (config, deps, next) { | ||
assert.equal(registry.service('hello'), this); | ||
assert.deepEqual(deps, {}); | ||
next(undefined, 'hello'); | ||
}); | ||
registry.service('hello').provides(function (deps, next) { | ||
assert.deepEqual(deps, {}) | ||
next(undefined, 'hello') | ||
}) | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.equal(registry, this); | ||
assert.deepEqual(dep, 'hello'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('hello', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'hello') | ||
done() | ||
}) | ||
}) | ||
it('must return undefined (1 function)', function (done) { | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.equal(registry, this); | ||
assert.equal(err.message, 'Diogenes: missing dependency: hello'); | ||
assert.instanceOf(err, DiogenesError); | ||
done(); | ||
}); | ||
}); | ||
registry.run('hello', function (err, dep) { | ||
assert.equal(err.message, 'Diogenes: missing dependency: hello') | ||
assert.instanceOf(err, DiogenesError) | ||
done() | ||
}) | ||
}) | ||
it('must return an exception if the function fails', function (done) { | ||
registry.service('hello').returns(function (config, deps) { | ||
throw new Error('broken'); | ||
return 'hello'; | ||
}); | ||
registry.service('hello').provides(function (deps) { | ||
throw new Error('broken') | ||
}) | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.equal(registry, this); | ||
assert.instanceOf(err, Error); | ||
assert.equal(err.message, 'broken'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('hello', function (err, dep) { | ||
assert.instanceOf(err, Error) | ||
assert.equal(err.message, 'broken') | ||
done() | ||
}) | ||
}) | ||
it('must return a service in a simple case (2 functions)', function (done) { | ||
registry.service('hello').provides(function (config, deps, next) { | ||
assert.deepEqual(deps, {}); | ||
next(undefined, 'hello '); | ||
}); | ||
registry.service('hello').provides(function (deps, next) { | ||
assert.deepEqual(deps, {}) | ||
next(undefined, 'hello ') | ||
}) | ||
registry.service('world').dependsOn(['hello']).provides(function (config, deps, next) { | ||
assert.deepEqual(deps, {hello: 'hello '}); | ||
next(undefined, deps.hello + 'world!'); | ||
}); | ||
registry.service('world').dependsOn(['hello']).provides(function (deps, next) { | ||
assert.deepEqual(deps, {hello: 'hello '}) | ||
next(undefined, deps.hello + 'world!') | ||
}) | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'hello world!'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('world', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'hello world!') | ||
done() | ||
}) | ||
}) | ||
it('must return an exception if the callback fires twice', function (done) { | ||
registry.service('hello').provides(function (config, deps, next) { | ||
next(undefined, 'hello '); | ||
next(undefined, 'hello '); | ||
}); | ||
registry.service('world').dependsOn(['hello']).provides(function (config, deps, next) { | ||
next(undefined, deps.hello + 'world!'); | ||
}); | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError); | ||
assert.equal(err.message, 'Diogenes: a callback has been firing more than once'); | ||
done(); | ||
}); | ||
}); | ||
it('must return a service in a simple case (2 functions) not using next', function (done) { | ||
registry.service('hello').returns(function (config, deps) { | ||
assert.deepEqual(deps, {}); | ||
return 'hello '; | ||
}); | ||
registry.service('hello').provides(function (deps) { | ||
assert.deepEqual(deps, {}) | ||
return 'hello ' | ||
}) | ||
registry.service('world').dependsOn(['hello']).returns(function (config, deps) { | ||
assert.deepEqual(deps, {hello: 'hello '}); | ||
return deps.hello + 'world!'; | ||
}); | ||
registry.service('world').dependsOn(['hello']).provides(function (deps) { | ||
assert.deepEqual(deps, { hello: 'hello ' }) | ||
return deps.hello + 'world!' | ||
}) | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'hello world!'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('world', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'hello world!') | ||
done() | ||
}) | ||
}) | ||
it('must return a service in a simple case (2 functions) using promises', function (done) { | ||
registry.service('hello').returns(function (config, deps) { | ||
assert.deepEqual(deps, {}); | ||
var p = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
resolve('hello '); | ||
}, 10); | ||
}); | ||
return p; | ||
}); | ||
registry.service('world').dependsOn(['hello']).returns(function (config, deps) { | ||
assert.deepEqual(deps, {hello: 'hello '}); | ||
var p = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
resolve(deps.hello + 'world!'); | ||
}, 10); | ||
}); | ||
return p; | ||
}); | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'hello world!'); | ||
done(); | ||
}); | ||
}); | ||
it('must unwrap promises automatically', function (done) { | ||
var getPromise = function (ret) { | ||
return p = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
resolve(ret); | ||
}, 10); | ||
}); | ||
}; | ||
registry.service('hello').returns(function (config, deps) { | ||
assert.deepEqual(deps, {}); | ||
return getPromise(getPromise('hello')); | ||
}); | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.deepEqual(dep, 'hello'); | ||
done(); | ||
}); | ||
}); | ||
it('must propagate an error using promises', function (done) { | ||
registry.service('hello').returns(function (config, deps) { | ||
assert.deepEqual(deps, {}); | ||
var p = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
reject(new Error('broken')); | ||
}, 10); | ||
}); | ||
return p; | ||
}); | ||
registry.service('world').dependsOn(['hello']).returns(function (config, deps) { | ||
assert.deepEqual(deps, {hello: 'hello '}); | ||
var p = new Promise(function (resolve, reject) { | ||
setTimeout(function () { | ||
resolve(deps.hello + 'world!'); | ||
}, 10); | ||
}); | ||
return p; | ||
}); | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.instanceOf(err, Error); | ||
assert.equal(err.message, 'broken'); | ||
done(); | ||
}); | ||
}); | ||
it('must return a service in a simple case (2 functions), dependencies are a function', function (done) { | ||
registry.service('hello').provides(function (config, deps, next) { | ||
assert.deepEqual(deps, {}); | ||
next(undefined, 'hello '); | ||
}); | ||
registry.service('hello').provides(function (deps, next) { | ||
assert.deepEqual(deps, {}) | ||
next(undefined, 'hello ') | ||
}) | ||
var getDeps = function (config) { | ||
assert.deepEqual(config, {test: 1}); | ||
return ['hello']; | ||
}; | ||
var getDeps = function () { | ||
return ['hello'] | ||
} | ||
registry.service('world').dependsOn(getDeps).provides(function (config, deps, next) { | ||
assert.deepEqual(deps, {hello: 'hello '}); | ||
next(undefined, deps.hello + 'world!'); | ||
}); | ||
registry.service('world').dependsOn(getDeps).provides(function (deps, next) { | ||
assert.deepEqual(deps, {hello: 'hello '}) | ||
next(undefined, deps.hello + 'world!') | ||
}) | ||
registry.instance({test: 1}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'hello world!'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('world', function (err, dep) { | ||
if (err) return | ||
assert.deepEqual(dep, 'hello world!') | ||
done() | ||
}) | ||
}) | ||
it('must recognize a circular dependency', function (done) { | ||
registry.service('hello').dependsOn(['world']).provides(function (config, deps, next) { | ||
next(undefined, 'hello '); | ||
}); | ||
registry.service('hello').dependsOn(['world']).provides(function (deps, next) { | ||
next(undefined, 'hello ') | ||
}) | ||
registry.service('world').dependsOn(['hello']).provides(function (config, deps, next) { | ||
next(undefined, 'world!'); | ||
}); | ||
registry.service('world').dependsOn(['hello']).provides(function (deps, next) { | ||
next(undefined, 'world!') | ||
}) | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError); | ||
assert.equal(err.message, 'Diogenes: circular dependency: hello'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('hello', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError) | ||
assert.equal(err.message, 'Diogenes: circular dependency') | ||
done() | ||
}) | ||
}) | ||
it('must recognize a circular dependency (3 services)', function (done) { | ||
registry.service('A').dependsOn(['C']).provides(function (config, deps, next) { | ||
next(undefined, undefined); | ||
}); | ||
registry.service('A').dependsOn(['C']).provides(function (deps, next) { | ||
next(undefined, undefined) | ||
}) | ||
registry.service('B').dependsOn(['A']).provides(function (config, deps, next) { | ||
next(undefined, undefined); | ||
}); | ||
registry.service('B').dependsOn(['A']).provides(function (deps, next) { | ||
next(undefined, undefined) | ||
}) | ||
registry.service('C').dependsOn(['B']).provides(function (config, deps, next) { | ||
next(undefined, undefined); | ||
}); | ||
registry.service('C').dependsOn(['B']).provides(function (deps, next) { | ||
next(undefined, undefined) | ||
}) | ||
registry.instance({}).run('C', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError); | ||
assert.equal(err.message, 'Diogenes: circular dependency: C'); | ||
done(); | ||
}); | ||
}); | ||
registry.run('C', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError) | ||
assert.equal(err.message, 'Diogenes: circular dependency') | ||
done() | ||
}) | ||
}) | ||
it('must throw an exception when missing dependency', function (done) { | ||
registry.service('hello').dependsOn(['world']).provides(function (config, deps, next) { | ||
next(undefined, 'hello '); | ||
}); | ||
registry.service('hello').dependsOn(['world']).provides(function (deps, next) { | ||
next(undefined, 'hello ') | ||
}) | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError); | ||
assert.equal(err.message, 'Diogenes: missing dependency: world'); | ||
done(); | ||
}); | ||
}); | ||
it('must throw an exception when more than one plugin matches', function (done) { | ||
registry.service('hello').provides(function (config, deps, next) { | ||
next(undefined, 'hello1'); | ||
}); | ||
registry.service('hello').provides(function (config, deps, next) { | ||
next(undefined, 'hello2'); | ||
}); | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.instanceOf(err, Error); | ||
assert.equal(err.message, 'Occamsrazor (get): More than one adapter fits'); | ||
done(); | ||
}); | ||
}); | ||
it('must add a value', function (done) { | ||
registry.service('hello').returnsValue('hello'); | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.equal(dep, 'hello'); | ||
done(); | ||
}); | ||
}); | ||
describe('plugins', function () { | ||
beforeEach(function () { | ||
registry.service('hello').returnsValue('hello') | ||
.provides({greetings: undefined}, function (cfg, deps) { | ||
return cfg.greetings; | ||
}); | ||
registry.service('world').dependsOn(['hello']).returns(function (cfg, deps) { | ||
return deps.hello + ' world'; | ||
}) | ||
.provides({who: /mars/gi}, function (cfg, deps) { | ||
return deps.hello + ' martians'; | ||
}) | ||
.provides({who: /mars/gi}, {hello: /morning/}, function (cfg, deps) { | ||
return 'good day martians'; | ||
}); | ||
}); | ||
it('must use right function', function () { | ||
registry.instance().run('world', function (err, dep) { | ||
assert.equal(dep, 'hello world'); | ||
}); | ||
}); | ||
it('must use right function using a validator for the config', function () { | ||
registry.instance({who: 'mars'}).run('world', function (err, dep) { | ||
assert.equal(dep, 'hello martians'); | ||
}); | ||
}); | ||
it('must use right function using a validator for the config and deps', function () { | ||
registry.instance({who: 'mars', greetings: 'good morning'}).run('world', function (err, dep) { | ||
assert.equal(dep, 'good day martians'); | ||
}); | ||
}); | ||
}); | ||
describe('correct services in the correct order (using the config/plugin)', function () { | ||
var isReversed; | ||
beforeEach(function () { | ||
isReversed = isAnything.has('reverse'); | ||
registry.service('hello') | ||
.provides(function (config, deps, next) { | ||
next(undefined, 'hello '); | ||
}) | ||
.dependsOn(isReversed, ['world']) | ||
.provides(isReversed, function (config, deps, next) { | ||
next(undefined, deps.world + 'hello!'); | ||
}); | ||
registry.service('world') | ||
.dependsOn(['hello']) | ||
.provides(function (config, deps, next) { | ||
next(undefined, deps.hello + 'world!'); | ||
}) | ||
.dependsOn(isReversed, []) | ||
.provides(isReversed, function (config, deps, next) { | ||
next(undefined, 'world '); | ||
}); | ||
}); | ||
it('must extract the rightmost service', function (done) { | ||
registry.instance({}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'hello world!'); | ||
done(); | ||
}); | ||
}); | ||
it('must extract the leftmost service', function (done) { | ||
registry.instance({}).run('hello', function (err, dep) { | ||
assert.deepEqual(dep, 'hello '); | ||
done(); | ||
}); | ||
}); | ||
it('must extract the rightmost inverted service', function (done) { | ||
registry.instance({reverse: true}).run('hello', function (err, dep) { | ||
assert.deepEqual(dep, 'world hello!'); | ||
done(); | ||
}); | ||
}); | ||
it('must extract the leftmost inverted service', function (done) { | ||
registry.instance({reverse: true}).run('world', function (err, dep) { | ||
assert.deepEqual(dep, 'world '); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('from the readme', function (done) { | ||
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(); | ||
beforeEach(function () { | ||
registry.service('text').returnsValue(text); | ||
registry.service('tokens').dependsOn(['text']).provides(function (config, deps, next) { | ||
next(undefined, deps.text.split(' ')); | ||
}); | ||
registry.service('count').dependsOn(['tokens']).provides(function (config, deps, next) { | ||
next(undefined, deps.tokens.length); | ||
}); | ||
registry.service('abstract').dependsOn(['tokens']).provides(function (config, deps, next) { | ||
var len = config.abstractLen; | ||
var ellipsis = config.abstractEllipsis; | ||
next(undefined, deps.tokens.slice(0, len).join(' ') + ellipsis); | ||
}); | ||
registry.service('paragraph').dependsOn(['text', 'abstract', 'count']) | ||
.provides(function (config, deps, next) { | ||
next(undefined, { | ||
count: deps.count, | ||
abstract: deps.abstract, | ||
text: deps.text | ||
}); | ||
}); | ||
var useAlternativeClamp = validator().match({abstractClamp: 'chars'}); | ||
registry.service('abstract') | ||
.dependsOn(useAlternativeClamp, ['text']) | ||
.provides(useAlternativeClamp, function (config, deps, next) { | ||
var len = config.abstractLen; | ||
var ellipsis = config.abstractEllipsis; | ||
next(undefined, deps.text.slice(0, len) + ellipsis); | ||
}); | ||
}); | ||
it('must return a correct order (readme example)', function () { | ||
var a = registry.instance({abstractLen: 5, abstractEllipsis: '...'}) | ||
.getExecutionOrder('paragraph'); | ||
assert.deepEqual(a, ['text', 'tokens', 'abstract', 'count', 'paragraph']); | ||
}); | ||
it('must work (readme example)', function (done) { | ||
registry.instance({abstractLen: 5, abstractEllipsis: '...'}).run('paragraph', function (err, p) { | ||
assert.equal(p.count, 23); | ||
assert.equal(p.abstract, 'Diogenes became notorious for his...'); | ||
assert.equal(p.text, text); | ||
done(); | ||
}); | ||
}); | ||
it('must return a correct order (readme example) - alternate version', function () { | ||
var a = registry.instance({abstractLen: 5, abstractEllipsis: '...', abstractClamp: 'chars'}) | ||
.getExecutionOrder('paragraph'); | ||
assert.deepEqual(a, ['text', 'abstract', 'tokens', 'count', 'paragraph']); | ||
}); | ||
it('must work (readme example) - alternate version', function (done) { | ||
registry.instance({abstractLen: 5, abstractEllipsis: '...', abstractClamp: 'chars'}).run('paragraph', function (err, p) { | ||
assert.equal(p.count, 23); | ||
assert.equal(p.abstract, 'Dioge...'); | ||
assert.equal(p.text, text); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('services cache', function () { | ||
var cached; | ||
beforeEach(function () { | ||
var c = 0; | ||
cached = registry.service('cached').provides(function (config, deps, next) { | ||
next(undefined, 'hello ' + c++); | ||
}); | ||
}); | ||
it('must run only once', function (done) { | ||
cached.cache(); | ||
cached.registry().instance({}).run('cached', function (err, dep) { | ||
assert.equal(dep, 'hello 0'); | ||
cached.registry().instance({}).run('cached', function (err, dep) { | ||
assert.equal(dep, 'hello 0'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
registry.run('hello', function (err, dep) { | ||
assert.instanceOf(err, DiogenesError) | ||
assert.equal(err.message, 'Diogenes: missing dependency: world') | ||
done() | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2
149527
10
16
463
327
1
+ Addedes6-promisify@^6.0.0
+ Addedes6-promisify@6.1.1(transitive)
- Removedasync-deco@^7.0.0
- Removedlittle-ds-toolkit@^0.3.0
- Removedoccamsrazor@^5.0.1
- Removedoccamsrazor-validator@^6.0.0
- Removedsetimmediate@^1.0.5
- Removedasync-deco@7.6.0(transitive)
- Removedes6-promise@3.3.1(transitive)
- Removedes6-promisify@3.0.0(transitive)
- Removedlittle-ds-toolkit@0.2.10.3.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedmd5-o-matic@0.1.1(transitive)
- Removedmemoize-cache-utils@0.0.2(transitive)
- Removedoccamsrazor@5.1.1(transitive)
- Removedoccamsrazor-match@4.1.1(transitive)
- Removedoccamsrazor-validator@6.0.0(transitive)
- Removedrequire-all@2.2.0(transitive)
- Removedsetimmediate@1.0.5(transitive)
- Removedsizeof@1.0.0(transitive)
- Removeduuid@2.0.3(transitive)
Updatedobject-assign@^4.1.1