|
|
![logo][logo-url] |
|
|
|
|
|
# steed |
|
|
|
|
|
[![npm version][npm-badge]][npm-url] |
|
|
[![Build Status][travis-badge]][travis-url] |
|
|
[![Coverage Status][coveralls-badge]][coveralls-url] |
|
|
[![Dependency Status][david-badge]][david-url] |
|
|
|
|
|
Horsepower for your modules. |
|
|
|
|
|
__Steed__ is an alternative to [async](http://npm.im/async) that is |
|
|
~50-100% faster. It is not currently on-par with async in term of features. |
|
|
Please help us! |
|
|
|
|
|
* <a href="#install">Installation</a> |
|
|
* <a href="#api">API</a> |
|
|
* <a href="#caveats">Caveats</a> |
|
|
* <a href="#why">Why it is so fast?</a> |
|
|
* <a href="#acknowledgements">Acknowledgements</a> |
|
|
* <a href="#licence">Licence & copyright</a> |
|
|
|
|
|
[![js-standard-style](https://raw.githubusercontent.com/feross/standard/master/badge.png)](https://github.com/feross/standard) |
|
|
|
|
|
Watch Matteo presenting Steed at Node.js Interactive 2015: https://www.youtube.com/watch?v=_0W_822Dijg. |
|
|
|
|
|
## Install |
|
|
|
|
|
`npm i steed --save` |
|
|
|
|
|
## API |
|
|
|
|
|
* <a href="#steed"><code><b>steed()</b></code></a> |
|
|
* <a href="#parallel"><code>steed#<b>parallel()</b></code></a> |
|
|
* <a href="#series"><code>steed#<b>series()</b></code></a> |
|
|
* <a href="#waterfall"><code>steed#<b>waterfall()</b></code></a> |
|
|
* <a href="#each"><code>steed#<b>each()</b></code></a> |
|
|
* <a href="#eachSeries"><code>steed#<b>eachSeries()</b></code></a> |
|
|
* <a href="#map"><code>steed#<b>map()</b></code></a> |
|
|
* <a href="#mapSeries"><code>steed#<b>mapSeries()</b></code></a> |
|
|
* <a href="#queue"><code>steed#<b>queue()</b></code></a> |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="steed"></a> |
|
|
### steed() |
|
|
|
|
|
Build an instance of steed, this step is not needed but welcomed for |
|
|
greater performance. Each steed utility likes being used for the same |
|
|
purpose. |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="parallel"></a> |
|
|
### steed.parallel([that,] tasks[, done(err, results)]) |
|
|
|
|
|
Executes a series of tasks in parallel. |
|
|
|
|
|
`tasks` can either be an array of functions, or an object where each |
|
|
property is a function. `done` will be called with the results. |
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
Uses [fastparallel](http://npm.im/fastparallel). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
steed.parallel([ |
|
|
function a (cb){ |
|
|
cb(null, 'a'); |
|
|
}, |
|
|
function b (cb){ |
|
|
cb(null, 'b'); |
|
|
} |
|
|
], function(err, results){ |
|
|
// results is ['a', 'b'] |
|
|
}) |
|
|
|
|
|
|
|
|
// an example using an object instead of an array |
|
|
steed.parallel({ |
|
|
a: function a1 (cb){ |
|
|
cb(null, 1) |
|
|
}, |
|
|
b: function b1 (cb){ |
|
|
cb(null, 2) |
|
|
} |
|
|
}, function(err, results) { |
|
|
// results is { a: 1, b: 2} |
|
|
}) |
|
|
|
|
|
// an example using that parameter |
|
|
// preferred form for max speed |
|
|
function run (prefix, a, b, cb) { |
|
|
steed.parallel(new State(prefix, a, b, cb), [aT, bT], doneT) |
|
|
} |
|
|
|
|
|
// can be optimized by V8 using an hidden class |
|
|
function State (prefix, a, b, cb) { |
|
|
this.a = a |
|
|
this.b = b |
|
|
this.cb = cb |
|
|
this.prefix = prefix |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function aT (cb){ |
|
|
cb(null, this.a); |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function bT (cb){ |
|
|
cb(null, this.b); |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function doneT (err, results) { |
|
|
if (results) { |
|
|
results.unshift(this.prefix) |
|
|
results = results.join(' ') |
|
|
} |
|
|
this.cb(err, results) |
|
|
} |
|
|
|
|
|
run('my name is', 'matteo', 'collina', console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 1781ms |
|
|
* `async.parallel`: 3484ms |
|
|
* `neoAsync.parallel`: 2162ms |
|
|
* `insync.parallel`: 10252ms |
|
|
* `items.parallel`: 3725ms |
|
|
* `parallelize`: 2928ms |
|
|
* `fastparallel` with results: 2139ms |
|
|
|
|
|
These benchmarks where taken on node v4.1.0, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="series"></a> |
|
|
### steed.series([that,] tasks[, done(err, results)]) |
|
|
|
|
|
Executes a series of tasks in series. |
|
|
|
|
|
`tasks` can either be an array of functions, or an object where each |
|
|
property is a function. `done` will be called with the results. |
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
Uses [fastseries](http://npm.im/fastseries). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
steed.series([ |
|
|
function a (cb){ |
|
|
cb(null, 'a'); |
|
|
}, |
|
|
function b (cb){ |
|
|
cb(null, 'b'); |
|
|
} |
|
|
], function(err, results){ |
|
|
// results is ['a', 'b'] |
|
|
}) |
|
|
|
|
|
|
|
|
// an example using an object instead of an array |
|
|
steed.series({ |
|
|
a: function a (cb){ |
|
|
cb(null, 1) |
|
|
}, |
|
|
b: function b (cb){ |
|
|
cb(null, 2) |
|
|
} |
|
|
}, function(err, results) { |
|
|
// results is { a: 1, b: 2} |
|
|
}) |
|
|
|
|
|
// an example using that parameter |
|
|
// preferred form for max speed |
|
|
function run (prefix, a, b, cb) { |
|
|
steed.series(new State(prefix, a, b, cb), [aT, bT], doneT) |
|
|
} |
|
|
|
|
|
// can be optimized by V8 using an hidden class |
|
|
function State (prefix, a, b, cb) { |
|
|
this.a = a |
|
|
this.b = b |
|
|
this.cb = cb |
|
|
this.prefix = prefix |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function aT (cb){ |
|
|
cb(null, this.a); |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function bT (cb){ |
|
|
cb(null, this.b); |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function doneT (err, results) { |
|
|
if (results) { |
|
|
results.unshift(this.prefix) |
|
|
results = results.join(' ') |
|
|
} |
|
|
this.cb(err, results) |
|
|
} |
|
|
|
|
|
run('my name is', 'matteo', 'collina', console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 3887ms |
|
|
* `async.series`: 5981ms |
|
|
* `neoAsync.series`: 4338ms |
|
|
* `fastseries` with results: 4096ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="waterfall"></a> |
|
|
### steed.waterfall(tasks[, done(err, ...)]) |
|
|
|
|
|
Runs the functions in `tasks` in series, each passing their result to |
|
|
the next task in the array. Quits early if any of the tasks errors. |
|
|
|
|
|
Uses [fastfall](http://npm.im/fastfall). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
steed.waterfall([ |
|
|
function a (cb) { |
|
|
console.log('called a') |
|
|
cb(null, 'a') |
|
|
}, |
|
|
function b (a, cb) { |
|
|
console.log('called b with:', a) |
|
|
cb(null, 'a', 'b') |
|
|
}, |
|
|
function c (a, b, cb) { |
|
|
console.log('called c with:', a, b) |
|
|
cb(null, 'a', 'b', 'c') |
|
|
}], function result (err, a, b, c) { |
|
|
console.log('result arguments', arguments) |
|
|
}) |
|
|
|
|
|
// preferred version for maximum speed |
|
|
function run (word, cb) { |
|
|
steed.waterfall(new State(cb), [ |
|
|
aT, bT, cT, |
|
|
], cb) |
|
|
} |
|
|
|
|
|
// can be optimized by V8 using an hidden class |
|
|
function State (value) { |
|
|
this.value = value |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function aT (cb) { |
|
|
console.log(this.value) |
|
|
console.log('called a') |
|
|
cb(null, 'a') |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function bT (a, cb) { |
|
|
console.log('called b with:', a) |
|
|
cb(null, 'a', 'b') |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function cT (a, b, cb) { |
|
|
console.log('called c with:', a, b) |
|
|
cb(null, 'a', 'b', 'c') |
|
|
} |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 100 thousands times: |
|
|
|
|
|
* non-reusable setImmediate: 418ms |
|
|
* `async.waterfall`: 1174ms |
|
|
* `run-waterfall`: 1432ms |
|
|
* `insync.wasterfall`: 1174ms |
|
|
* `neo-async.wasterfall`: 469ms |
|
|
* `waterfallize`: 749ms |
|
|
* `fastfall`: 452ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="each"></a> |
|
|
### steed.each([that,] array, iterator(item, cb), [, done()]) |
|
|
|
|
|
Iterate over all elements of the given array asynchronosly and in |
|
|
parallel. |
|
|
Calls `iterator` with an item and a callback. Calls `done` when all have |
|
|
been processed. |
|
|
|
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
`each` does not handle errors, if you need errors, use [`map`](#map). |
|
|
|
|
|
Uses [fastparallel](http://npm.im/fastparallel). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
var input = [1, 2, 3] |
|
|
var factor = 2 |
|
|
|
|
|
steed.each(input, function (num, cb) { |
|
|
console.log(num * factor) |
|
|
setImmediate(cb) |
|
|
}, function () { |
|
|
console.log('done') |
|
|
}) |
|
|
|
|
|
// preferred version for max speed |
|
|
function run (factor, args, cb) { |
|
|
steed.each(new State(factor), work, cb) |
|
|
} |
|
|
|
|
|
// can be optimizied by V8 using an hidden class |
|
|
function State (factor) { |
|
|
this.factor = factor |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function work (num, cb) { |
|
|
console.log(num * this.factor) |
|
|
cb() |
|
|
} |
|
|
|
|
|
run(factor, input, console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 1781ms |
|
|
* `async.each`: 2621ms |
|
|
* `neoAsync.each`: 2156ms |
|
|
* `insync.parallel`: 10252ms |
|
|
* `insync.each`: 2397ms |
|
|
* `fastparallel` each: 1941ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="eachSeries"></a> |
|
|
### steed.eachSeries([that,] array, iterator(item, cb), [, done(err)]) |
|
|
|
|
|
Iterate over all elements of the given array asynchronously and in |
|
|
series. |
|
|
Calls `iterator` with an item and a callback. Calls `done` when all have |
|
|
been processed. |
|
|
|
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
`eachSeries` does not handle errors, if you need errors, use [`mapSeries`](#mapSeries). |
|
|
|
|
|
Uses [fastseries](http://npm.im/fastseries). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
var input = [1, 2, 3] |
|
|
var factor = 2 |
|
|
|
|
|
steed.eachSeries(input, function (num, cb) { |
|
|
console.log(num * factor) |
|
|
setImmediate(cb) |
|
|
}, function (err) { |
|
|
console.log(err) |
|
|
}) |
|
|
|
|
|
// preferred version for max speed |
|
|
function run (factor, args, cb) { |
|
|
steed.eachSeries(new State(factor), work, cb) |
|
|
} |
|
|
|
|
|
// can be optimizied by V8 using an hidden class |
|
|
function State (factor) { |
|
|
this.factor = factor |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function work (num, cb) { |
|
|
console.log(num * this.factor) |
|
|
cb() |
|
|
} |
|
|
|
|
|
run(factor, input, console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 3887ms |
|
|
* `async.mapSeries`: 5540ms |
|
|
* `neoAsync.eachSeries`: 4195ms |
|
|
* `fastseries` each: 4168ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="map"></a> |
|
|
### steed.map([that,] array, iterator(item, cb), [, done(err, results)]) |
|
|
|
|
|
Performs a map operation over all elements of the given array asynchronously and in |
|
|
parallel. The result is an a array where all items have been replaced by |
|
|
the result of `iterator`. |
|
|
|
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
Calls `iterator` with an item and a callback. Calls `done` when all have |
|
|
been processed. |
|
|
|
|
|
Uses [fastparallel](http://npm.im/fastparallel). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
var input = [1, 2, 3] |
|
|
var factor = 2 |
|
|
|
|
|
steed.map(input, function (num, cb) { |
|
|
setImmediate(cb, null, num * factor) |
|
|
}, function (err, results) { |
|
|
if (err) { throw err } |
|
|
|
|
|
console.log(results.reduce(sum)) |
|
|
}) |
|
|
|
|
|
function sum (acc, num) { |
|
|
return acc + num |
|
|
} |
|
|
|
|
|
// preferred version for max speed |
|
|
function run (factor, args, cb) { |
|
|
steed.map(new State(factor, cb), args, work, done) |
|
|
} |
|
|
|
|
|
// can be optimizied by V8 using an hidden class |
|
|
function State (factor, cb) { |
|
|
this.factor = factor |
|
|
this.cb = cb |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function work (num, cb) { |
|
|
setImmediate(cb, null, num * this.factor) |
|
|
} |
|
|
|
|
|
function done (err, results) { |
|
|
results = results || [] |
|
|
this.cb(err, results.reduce(sum)) |
|
|
} |
|
|
|
|
|
run(2, [1, 2, 3], console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 1781ms |
|
|
* `async.map`: 3054ms |
|
|
* `neoAsync.map`: 2080ms |
|
|
* `insync.map`: 9700ms |
|
|
* `fastparallel` map: 2102ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="mapSeries"></a> |
|
|
### steed.mapSeries([that,] array, iterator(item, cb), [, done(err, results)]) |
|
|
|
|
|
Performs a map operation over all elements of the given array asynchronosly and in |
|
|
series. The result is an a array where all items have been replaced by |
|
|
the result of `iterator`. |
|
|
|
|
|
Calls `iterator` with an item and a callback. Calls `done` when all have |
|
|
been processed. |
|
|
|
|
|
The `that` argument will set `this` for each task and `done` callback. |
|
|
|
|
|
Uses [fastseries](http://npm.im/fastseries). |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
var input = [1, 2, 3] |
|
|
var factor = 2 |
|
|
|
|
|
steed.mapSeries(input, function (num, cb) { |
|
|
setImmediate(cb, null, num * factor) |
|
|
}, function (err, results) { |
|
|
if (err) { throw err } |
|
|
|
|
|
console.log(results.reduce(sum)) |
|
|
}) |
|
|
|
|
|
function sum (acc, num) { |
|
|
return acc + num |
|
|
} |
|
|
|
|
|
// preferred version for max speed |
|
|
function run (factor, args, cb) { |
|
|
steed.mapSeries(new State(factor, cb), args, work, done) |
|
|
} |
|
|
|
|
|
// can be optimizied by V8 using an hidden class |
|
|
function State (factor, cb) { |
|
|
this.factor = factor |
|
|
this.cb = cb |
|
|
} |
|
|
|
|
|
// because it is not a closure inside run() |
|
|
// v8 can optimize this function |
|
|
function work (num, cb) { |
|
|
setImmediate(cb, null, num * this.factor) |
|
|
} |
|
|
|
|
|
function done (err, results) { |
|
|
results = results || [] |
|
|
this.cb(err, results.reduce(sum)) |
|
|
} |
|
|
|
|
|
run(2, [1, 2, 3], console.log) |
|
|
``` |
|
|
|
|
|
Benchmark for doing 3 calls `setImmediate` 1 million times: |
|
|
|
|
|
* non-reusable `setImmediate`: 3887ms |
|
|
* `async.mapSeries`: 5540ms |
|
|
* `neoAsync.mapSeries`: 4237ms |
|
|
* `fastseries` map: 4032ms |
|
|
|
|
|
These benchmarks where taken on node v4.2.2, on a MacBook |
|
|
Pro Retina Mid 2014 (i7, 16GB of RAM). |
|
|
|
|
|
------------------------------------------------------- |
|
|
<a name="queue"></a> |
|
|
### steed.queue(worker, concurrency) |
|
|
|
|
|
Creates a new queue. See [fastq](http://npm.im/fastq) for full API. |
|
|
|
|
|
Arguments: |
|
|
|
|
|
* `worker`, worker function, it would be called with `that` as `this`, |
|
|
if that is specified. |
|
|
* `concurrency`, number of concurrent tasks that could be executed in |
|
|
parallel. |
|
|
|
|
|
Example: |
|
|
|
|
|
```js |
|
|
var steed = require('steed')() |
|
|
// or |
|
|
// var steed = require('steed') |
|
|
|
|
|
var queue = steed.queue(worker, 1) |
|
|
|
|
|
queue.push(42, function (err, result) { |
|
|
if (err) { throw err } |
|
|
console.log('the result is', result) |
|
|
}) |
|
|
|
|
|
function worker (arg, cb) { |
|
|
cb(null, arg * 2) |
|
|
} |
|
|
``` |
|
|
|
|
|
Benchmarks (1 million tasks): |
|
|
|
|
|
* setImmedidate: 1313ms |
|
|
* fastq: 1462ms |
|
|
* async.queue: 3989ms |
|
|
|
|
|
Obtained on node 4.2.2, on a MacBook Pro 2014 (i7, 16GB of RAM). |
|
|
|
|
|
## Caveats |
|
|
|
|
|
This library works by caching the latest used function, so that running a new parallel |
|
|
does not cause **any memory allocations**. |
|
|
|
|
|
The `done` function will be called only once, even if more than one error happen. |
|
|
|
|
|
__Steed__ has no safety checks: you should be responsible to avoid sync |
|
|
functions and so on. Also arguments type checks are not included, so be |
|
|
careful in what you pass. |
|
|
|
|
|
<a name="why"></a> |
|
|
## Why it is so fast? |
|
|
|
|
|
1. This library is caching functions a lot. We invented a technique to |
|
|
do so, and packaged it in a module: [reusify](http://npm.im/reusify). |
|
|
|
|
|
2. V8 optimizations: thanks to caching, the functions can be optimized by V8 |
|
|
(if they are optimizable, and we took great care of making them so). |
|
|
|
|
|
3. Don't use arrays if you just need a queue. A linked list implemented via |
|
|
objects is much faster if you do not need to access elements in between. |
|
|
|
|
|
## Acknowledgements |
|
|
|
|
|
Steed is sponsored by [nearForm](http://nearform.com). |
|
|
|
|
|
The steed logo was created, with thanks, by [Dean McDonnell](https://github.com/mcdonnelldean) |
|
|
|
|
|
## License |
|
|
|
|
|
MIT |
|
|
|
|
|
[logo-url]: https://raw.githubusercontent.com/mcollina/steed/master/assets/banner.png |
|
|
[npm-badge]: https://badge.fury.io/js/steed.svg |
|
|
[npm-url]: https://badge.fury.io/js/steed |
|
|
[travis-badge]: https://api.travis-ci.org/mcollina/steed.svg |
|
|
[travis-url]: https://travis-ci.org/mcollina/steed |
|
|
[coveralls-badge]:https://coveralls.io/repos/mcollina/steed/badge.svg?branch=master&service=github |
|
|
[coveralls-url]: https://coveralls.io/github/mcollina/steed?branch=master |
|
|
[david-badge]: https://david-dm.org/mcollina/steed.svg |
|
|
[david-url]: https://david-dm.org/mcollina/steed
|
|
|
|