Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

diogenes

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

diogenes - npm Package Compare versions

Comparing version 5.0.0 to 6.0.0

src/lib/compose.js

3

package.json
{
"name": "diogenes",
"version": "5.0.0",
"version": "6.0.0",
"description": "A dependency injection framework.",

@@ -37,5 +37,4 @@ "main": "src/index.js",

"dependencies": {
"es6-promisify": "^6.0.0",
"uuid": "^3.2.1"
}
}

@@ -53,8 +53,7 @@ Diogenes

-----------------
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.
A service is a unit of code with a name. It can be a simple value, a synchronous function (returning a value) 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.
Services are organised inside a registry. The common interface allows to automate how the dependencies are resolved within the registry.

@@ -75,3 +74,3 @@ A step by step example

```js
const registry = Diogenes.getRegistry(); // of new Diogenes()
const registry = Diogenes.getRegistry(); // or new Diogenes()
```

@@ -104,11 +103,4 @@

```
or you can use a callback:
```js
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?
As you can see, Diogenes allows to mix sync and async functions.
Let's add other services:

@@ -150,73 +142,127 @@ ```js

```
p will be the output of the paragraph service. You can alternatively pass a callback to "run".
p will be the output of the paragraph service.
When resolving a dependency graph, diogenes takes care of executing every service at most once.
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.
Docstring
=========
A docstring is the description of the service. That may help using diogenes-lantern, a tool that shows your registry with a graph.
You can set a docstring like this:
```js
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.abstract);
console.log("This is the original text:");
console.log(p.text);
registry
.service('service1')
.doc('this is some helpful information')
```
And you can retrieve a docString with:
```js
registry
.service('service1')
.doc()
```
registry-runner
===============
Registry runner is an object that takes care of running services. This adds many features to a simple registry. You can create a runner like this:
```js
const registryRunner = Diogenes.getRegistryRunner()
```
then you can run a service with:
```js
registryRunner.run(registry, 'myservice')
```
Registry runner allows to use callbacks:
```js
registryRunner.run(registry, 'myservice', (err, myservice) => {
...
})
```
The callback uses the node.js convention, the error is the first argument.
If you need more than one service, you can pass a list of services:
Another feature allows to execute multiple services efficiently using an array or a regular expression:
```js
registry.run(["count", "abstract"])
.then({ count, paragraph } => {
...
})
registryRunner.run(registry, /myservice[1-3]/)
```
In this case the result will be an object with an attribute for each dependency (deps.count, deps.abstract).
or the equivalent:
```js
registryRunner.run(registry, ['myservice1', 'myservice2', 'myservice3'])
```
The result will be an object with an attribute for every dependency.
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.
Using this feature is different to:
```js
Promise.all([registry.run('myservice1'), registry.run('myservice2'), registry.run('myservice3')])
```
Because it ensures that every service is executed at most once.
Using function references
=========================
"service" and "dependsOn" allow to use function references instead of strings (the functions should have a name!):
You can also use the same method to add services without dependencies, without changing the original registry:
```js
registry
.service(function service2 (deps) {
// ... function implementation ...
})
.dependsOn([service2]);
registryRunner.run(registry, 'myservice', { times: 3 })
```
is equivalent to:
So if a service depends on "times", it will get 3. This can be useful for testing (injecting a mock in the dependency graph).
It is also useful to give an "execution context" that is different every time (think for example the request data in a web application).
The registry runner keeps track of all pending execution so is able to gracefully shutdown:
```js
registry
.service('service2')
.provides((deps) => {
// ... function implementation ...
registryRunner.shutdown()
.then(() => {
console.log('We can shutdown')
})
.dependsOn(['service2']);
registryRunner.run('myservice1') // this return a promise rejection because the registry is not accepting new tasks
```
Docstring
=========
You can define a docstring in 2 ways:
Registry and decorators
=======================
[The decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern) can be very useful to enhance a service. For example adding a caching layer, logging or to convert a callback based service to use a promise (promisify is a decorator).
The method "provides" includes a shortcut to add decorators to the service. If you pass an array or more than one argument, to the method. In the next example I am able to add a service that uses a callback instead of promises:
```js
registry
.service('service1')
.doc('this is some helpful information')
registry.service('myservice')
.provides([
promisify,
(deps, next) => {
.. do something
next(null, result)
}])
```
or
In the next example I use a decorator to ensure a service is executed only once:
```js
registry
.service(function service1(deps) {
/**
this is some helpful information
**/
})
// define my decorator
const onlyOnce = (func) => {
let cache
return (deps) => {
if (typeof cache === 'undefined') {
cache = func(deps)
}
return cache
}
}
registry.service('myservice')
.provides([
onlyOnce,
(deps) => {
...
}])
```
And you can retrieve a docString with:
You can add multiple decorators:
```js
registry
.service('service1')
.doc()
registry.service('myservice')
.provides([logger, onlyOnce, myservice])
```
Docstrings can be used to store info about services.
This is the equivalent of:
```js
registry.service('myservice')
.provides(logger(onlyOnce(myservice)))
```
You can find many examples of what you can do with decorators on [async-deco](https://github.com/sithmel/async-deco) and on [diogenes-utils](https://github.com/sithmel/diogenes-utils). This one in particular, contains a decorator that caches a service.
```js
const cacheService = require('diogenes-utils').cacheService
registry.service('myservice')
.provides([
cacheService({ len: 3, ttl: 10000 }),
myservice
])
```

@@ -237,2 +283,9 @@ Syntax

Diogenes.getRegistryRunner
--------------------
Create a registry runner instance:
```js
const registry = Diogenes.getRegistryRunner();
```
Registry

@@ -247,7 +300,2 @@ ========

```
You can also pass a function (a named function!):
```js
registry.service(name);
```
Passing a function is equivalent to both defining a service and providing an implementation.

@@ -277,32 +325,3 @@ init

```
It can also use a callback:
```js
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:
```js
registry.run(['service1', 'service2'])
.then(({ service1, service2 }) => {
...
})
```
or using a regular expression:
```js
registry.run(/service[0-9]?/)
.then(({ service1, service2 }) => {
...
})
```
You can also pass an object with some extra dependencies to be used for this execution:
```js
registry.run('service2', { service1: 'hello' })
.then(({ service1, service2 }) => {
...
})
```
merge/clone

@@ -314,3 +333,2 @@ -----------

```
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.

@@ -343,4 +361,8 @@

```
As an optional argument you can pass an object with some extra dependencies.
missingDeps
-----------
This method returns an array of service names that are not in the registry, but are dependencies of another service.
This can be useful for debugging.
getMetadata

@@ -368,19 +390,3 @@ ------------

```
As an optional argument you can pass an object with some extra dependencies.
shutdown
--------
The purpose of this method is allow all asynchronous call to be terminated before a system shutdown.
After calling this method the service won't execute the "run" method anymore (It will return an exception). The method returns a promise (or a callback). This will be fulfilled when all previous "run" has been fulfilled of rejected.
```js
const A = registry.run('A')
const C = registry.run('C')
registry.shutdown()
.then(() => {
// "A" and "C" are fulfilled
registry.run('B') // rejected with DiogenesShutdownError
})
```
Service

@@ -396,8 +402,18 @@ =======

---------
It defines the dependencies of a service. It may be an array or a function returning an array of strings (service names) or an array of functions:
It defines the dependencies of a service. It may be an array or an object:
```js
service.dependsOn(array);
service.dependsOn([...]);
service.dependsOn(func);
service.dependsOn({...});
```
Using an object you can use the dependencies under different names. For example, this are equivalent:
```js
service.dependsOn(['A', 'B']);
service.dependsOn({ A: 'A', B: 'B' });
```
You can use the object like this:
```js
service.dependsOn({ value: 'A' })
.provides(({ value }) => return value * 2);
```

@@ -414,16 +430,16 @@ provides

```
A synchronous function:
Or a synchronous function:
```js
service.provides((deps) => deps.something * 2);
```
Or callback:
If you pass an array or more than one argument, the first arguments are used to decorate the others:
```js
service.provides((deps, callback) => callback(null, deps.something * 2));
service.provides(arg1, arg2, arg3, arg4);
// is the equivalent of
service.provides(arg1(arg2(arg3(arg4))));
```
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.
doc
---
set/get the documentation string.
get/set the documentation string.
```js

@@ -454,5 +470,49 @@ service.doc(); // returns documentation string

Registry Runner
===============
This object runs services, keeping track of their execution.
run
---
This method runs one or more services:
```js
registryRunner.run(service, 'servicename')
```
by default it returns a promise but can also use a callback (using the node convention):
```js
registryRunner.run(service, 'servicename', (err, res) => { ... })
```
you can run multiple services using a regular expression or an array of names.
You can also pass an object with some extra dependencies to be used for this execution:
```js
registry.run('service2', { service1: 'hello' })
.then(({ service2 }) => {
...
})
```
The extra dependencies won't be added to the original registry.
shutdown
--------
The purpose of this method is to allow all asynchronous call to be terminated before a system shutdown.
After calling this method the registry runner won't execute the "run" method anymore (It will return an exception). The method returns a promise (or uses a callback). This will be fulfilled when all previous "run" has been fulfilled of rejected.
```js
registryRunner.run(registry, 'A')
registryRunner.run(registry, 'C')
registry.shutdown()
.then(() => {
// "A" and "C" are fulfilled
})
registryRunner.run(registry, 'A') // rejected with DiogenesShutdownError
```
flush
-----
Flush runs a shutdown and then restore the registry to its normal state.
Compatibility
=============
Diogenes is written is ES6. Please transpile it for using with old browsers/node.js. Also provide a polyfill for Promises and WeakMaps.
Diogenes is written is ES6. Please transpile it for using with old browsers/node.js. Also provide a polyfill for Promises, WeakMaps and Sets.

@@ -459,0 +519,0 @@ Acknowledgements

@@ -1,6 +0,5 @@

const uuid = require('uuid/v1')
const Service = require('./service')
const DiogenesError = require('./lib/diogenes-error')
const DiogenesShutdownError = require('./lib/diogenes-shutdown')
const getName = require('./lib/get-name')
const uuid = require('uuid/v1')
const RegistryRunner = require('./registry-runner')

@@ -13,4 +12,2 @@ /*

this.services = {}
this.running = {}
this._isShuttingDown = false
}

@@ -22,14 +19,18 @@

Registry.prototype.has = function registryHas (nameOrFunc) {
return getName(nameOrFunc) in this.services
Registry.getRegistryRunner = function registryGetRegistryRunner () {
return new RegistryRunner()
}
Registry.prototype.set = function registrySet (nameOrFunc, service) {
this.services[getName(nameOrFunc)] = service
Registry.prototype.has = function registryHas (name) {
return name in this.services
}
Registry.prototype.get = function registryGet (nameOrFunc) {
return this.services[getName(nameOrFunc)]
Registry.prototype.set = function registrySet (name, service) {
this.services[name] = service
}
Registry.prototype.get = function registryGet (name) {
return this.services[name]
}
Registry.prototype.init = function registryInit (funcs) {

@@ -41,12 +42,12 @@ for (const func of funcs) {

Registry.prototype.service = function registryService (nameOrFunc) {
if (typeof nameOrFunc !== 'string' && typeof nameOrFunc !== 'function') {
throw new DiogenesError('Diogenes: the service should be a string or a function')
Registry.prototype.service = function registryService (name) {
if (typeof name !== 'string') {
throw new DiogenesError('Diogenes: the service name should be a string')
}
if (!this.has(nameOrFunc)) {
this.set(nameOrFunc, new Service(nameOrFunc))
if (!this.has(name)) {
this.set(name, new Service(name))
}
return this.get(nameOrFunc)
return this.get(name)
}

@@ -62,33 +63,33 @@

Registry.prototype.addDeps = function registryAddDeps (deps) {
let reg = this.clone()
Object.keys(deps)
.forEach((serviceName) => {
reg.service(serviceName).provides(() => deps[serviceName])
})
return reg
Registry.prototype.getAdjList = function registryGetAdjList () {
return this.map((service) => service.depsArray())
}
Registry.prototype.getAdjList = function registryGetAdjList (deps) {
const reg = deps ? this.addDeps(deps) : this
return reg.map((service) => service.deps())
Registry.prototype.missingDeps = function registryMissingDeps () {
const adjList = this.getAdjList()
const deps = Object.keys(adjList)
.reduce((accum, key) => {
return accum.concat(adjList[key])
}, [])
return Array.from(new Set(deps))
.filter((dep) => !(dep in adjList))
}
Registry.prototype.getMetadata = function registryGetMetadata (deps) {
const reg = deps ? this.addDeps(deps) : this
return reg.map(function (service) { return service.getMetadata() })
Registry.prototype.getMetadata = function registryGetMetadata () {
return this.map(function (service) { return service.getMetadata() })
}
Registry.prototype._run = function registryRun (name, runId) {
Registry.prototype.run = function registryRun (name, runId) {
runId = runId || uuid()
const cache = {}
let c = 0
const getPromiseFromStr = (nameOfFunc) => {
const getPromiseFromStr = (name) => {
if (c++ > 1000) {
throw new DiogenesError('Diogenes: circular dependency')
}
const service = this.get(nameOfFunc)
const service = this.get(name)
if (!service) {
return Promise.reject(new DiogenesError(`Diogenes: missing dependency: ${getName(nameOfFunc)}`))
return Promise.reject(new DiogenesError(`Diogenes: missing dependency: ${name}`))
}

@@ -101,8 +102,9 @@

const deps = service.deps()
const depsArray = service.depsArray()
if (deps.length === 0) {
cache[service.name] = service._run(runId, {})
if (depsArray.length === 0) {
cache[service.name] = service.run(runId, {})
} else {
cache[service.name] = getPromisesFromStrArray(deps)
.then((d) => service._run(runId, d))
cache[service.name] = getPromisesFromDeps(depsArray, deps)
.then((d) => service.run(runId, d))
}

@@ -112,9 +114,17 @@ return cache[service.name]

const getPromisesFromStrArray = (strArray) =>
Promise.all(strArray.map(getPromiseFromStr))
const getPromisesFromDeps = (depsArray, depsObj) =>
Promise.all(depsArray.map(getPromiseFromStr))
.then(function (results) {
// map dependencies on an object
const depMap = {}
for (let i = 0; i < depsArray.length; i++) {
depMap[depsArray[i]] = results[i]
}
// map object on another object values
const out = {}
for (var i = 0; i < strArray.length; i++) {
out[strArray[i]] = results[i]
}
Object.keys(depsObj).forEach((key) => {
const value = depMap[depsObj[key]]
out[key] = value
})
return out

@@ -131,54 +141,8 @@ })

// clone registry and run. BEWARE: "_run" runs in a different registry (the cloned one)
Registry.prototype.run = function registryRun (name, deps, done) {
done = typeof deps === 'function' ? deps : done
deps = typeof deps === 'object' ? deps : {}
const runId = uuid()
let promise
if (this._isShuttingDown) {
promise = Promise.reject(new DiogenesShutdownError('Diogenes: shutting down'))
} else {
const tempreg = this.addDeps(deps)
if (typeof name === 'string' || typeof name === 'function') {
promise = tempreg._run(getName(name), runId)
} else {
if (name instanceof RegExp) {
name = Object.keys(this.services).filter(RegExp.prototype.test.bind(name))
} else if (Array.isArray(name)) {
name = name.map(getName)
}
tempreg.service('__temp__').dependsOn(name)
.provides(function (deps) {
return Promise.resolve(deps)
})
promise = tempreg._run('__temp__', runId)
}
}
const promiseWithCleanUp = promise
.then((res) => {
delete this.running[runId]
return Promise.resolve(res)
Registry.prototype.addDeps = function registryAddDeps (deps) {
Object.keys(deps)
.forEach((serviceName) => {
this.service(serviceName).provides(deps[serviceName])
})
.catch((err) => {
delete this.running[runId]
return Promise.reject(err)
})
this.running[runId] = promiseWithCleanUp
if (done) {
promiseWithCleanUp
.then((res) => {
done(null, res)
})
.catch((err) => {
done(err)
})
return this
} else {
return promiseWithCleanUp
}
return this
}

@@ -200,43 +164,2 @@

Registry.prototype.shutdown = function registryShutdown (done) {
this._isShuttingDown = true
var promise = Promise.all(Object.keys(this.running)
.map((key) => this.running[key].catch(() => Promise.resolve(null))))
if (done) {
promise
.then(function (res) {
done(null, res)
})
.catch(function (err) {
done(err)
})
return this
} else {
return promise
}
}
Registry.prototype.flush = function registryFlush (done) {
const promise = this.shutdown()
.then(() => {
this._isShuttingDown = false
return Promise.resolve(null)
})
if (done) {
promise
.then(function (res) {
done(null, res)
})
.catch(function (err) {
done(err)
})
return this
} else {
return promise
}
}
module.exports = Registry

@@ -1,4 +0,3 @@

const promisify = require('es6-promisify').promisify
const getName = require('./lib/get-name')
const DiogenesError = require('./lib/diogenes-error')
const compose = require('./lib/compose')

@@ -30,24 +29,6 @@ /*

function extractDocString (f) {
const str = f.toString()
const re = /\/\*\*(.+?)\*\*\//
const match = re.exec(str)
if (match) {
return match[1]
}
}
function Service (nameOrFunc) {
function Service (name) {
this._doc = ''
this._deps = []
this.name = getName(nameOrFunc)
if (!this.name) {
throw new DiogenesError('The service must have a name. Use either a string or a named function')
}
if (typeof nameOrFunc === 'function') {
this._debugInfo = getDebugInfo(nameOrFunc, 3)
this._provides(nameOrFunc)
this.doc(extractDocString(nameOrFunc))
}
this._deps = {}
this.name = name
}

@@ -64,9 +45,13 @@

Service.prototype.deps = function serviceDeps () {
return this._deps.map(getName)
return this._deps
}
Service.prototype.depsArray = function serviceDepsArray () {
return Array.from(new Set(Object.values(this._deps)))
}
Service.prototype.getMetadata = function serviceGetMetadata () {
return {
name: this.name,
deps: this.deps(),
deps: this.depsArray(),
doc: this.doc(),

@@ -78,3 +63,14 @@ debugInfo: this._debugInfo

Service.prototype.dependsOn = function serviceDependsOn (deps) {
this._deps = deps
if (Array.isArray(deps)) {
this._deps = deps.reduce((acc, value) => {
acc[value] = value
return acc
}, {})
} else if (typeof deps === 'object') {
this._deps = deps
} else if (typeof deps === 'undefined') {
this._deps = {}
} else {
throw new DiogenesError('Dependency can be an array, an object or undefined')
}
return this

@@ -84,27 +80,16 @@ }

Service.prototype.provides = function serviceProvides (func) {
this._debugInfo = getDebugInfo(func, 2)
return this._provides(func)
}
Service.prototype._provides = function serviceProvides (func) {
if (this._func) {
throw new DiogenesError(`You already defined a function for ${this.name}`)
func = arguments.length > 1 ? Array.prototype.slice.call(arguments, 0) : func
let originalFunction, resultingFunction
if (Array.isArray(func)) {
originalFunction = func[func.length - 1]
resultingFunction = compose(func.slice(0, -1))(originalFunction)
} else {
originalFunction = func
resultingFunction = func
}
if (typeof func !== 'function') {
this._debugInfo = getDebugInfo(originalFunction, 2)
if (typeof resultingFunction !== 'function') {
this._func = function () { return 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)
}
}
this._func = resultingFunction
}

@@ -114,7 +99,8 @@ return this

Service.prototype._run = function serviceRun (id, deps) {
Service.prototype.run = function serviceRun (id, deps) {
const context = { id: id, service: this }
return this._func.call(context, deps)
return Promise.resolve()
.then(() => this._func.call(context, deps))
}
module.exports = Service
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc