Comparing version 5.0.4 to 6.0.0
199
index.js
@@ -1,145 +0,100 @@ | ||
const debug = require('debug')('barracks') | ||
const assert = require('assertf') | ||
// const debug = require('debug')('barracks') | ||
const series = require('run-series') | ||
const assert = require('assert') | ||
module.exports = Dispatcher | ||
module.exports = dispatcher | ||
// initialize the dispatcher with actions | ||
// obj -> fn | ||
function Dispatcher (actions) { | ||
if (!(this instanceof Dispatcher)) return new Dispatcher(actions) | ||
// initialize a new barracks instance | ||
// null -> obj | ||
function dispatcher () { | ||
const actions = {} | ||
assert(actions, "an 'actions' object should be passed as an argument") | ||
assert.equal(typeof actions, 'object', 'actions should be an object') | ||
_assertActionsObject(actions) | ||
emit._actions = actions | ||
emit.emit = emit | ||
emit.on = on | ||
return emit | ||
this.locals = {} | ||
this.payload = null | ||
// register a new action | ||
// (str, fn) -> obj | ||
function on (action, cb) { | ||
assert.equal(typeof action, 'string') | ||
assert.equal(typeof cb, 'function') | ||
this._current = [] | ||
this._isPending = {} | ||
this._isHandled = {} | ||
this._actions = actions | ||
this._isDispatching = false | ||
actions[action] = actions[action] ? actions[action] : [] | ||
actions[action].push(cb) | ||
return this.dispatch.bind(this) | ||
} | ||
return emit | ||
} | ||
// dispatch event to stores | ||
// str, obj|[obj] -> fn | ||
Dispatcher.prototype.dispatch = function (action, payload) { | ||
assert.equal(typeof action, 'string', "action '%s' should be a string", action) | ||
assert(!this._isDispatching, "cannot dispatch '%s' in the middle of a dispatch", action) | ||
// call an action and | ||
// execute the corresponding callback | ||
// (str, obj?) -> prom | ||
function emit (action, data) { | ||
assert.ok(Array.isArray(actions[action]), 'action exists') | ||
const fn = actions[action][0] | ||
const stack = [action] | ||
this._current.push(action) | ||
this._isDispatching = true | ||
return fn(data, wait) | ||
this._isPending = {} | ||
this._isPending[action] = true | ||
// internal 'wait()' function | ||
// ([str], fn) -> prom | ||
function wait (action, cb) { | ||
action = Array.isArray(action) ? action : [action] | ||
cb = cb || function () {} | ||
this._isHandled = {} | ||
this._isHandled[action] = false | ||
// retrieve actions | ||
const arr = action.map(function (name) { | ||
const actExists = Array.isArray(actions[name]) | ||
assert.ok(actExists, 'action ' + name + ' does not exist') | ||
const fn = actions[name][0] | ||
return createDone(name, fn) | ||
}) | ||
this.locals = {} | ||
this.payload = payload | ||
return series(arr, cb) | ||
try { | ||
var fn = _getAction.call(this, action) | ||
} catch (e) { | ||
_stopDispatching.call(this) | ||
throw e | ||
} | ||
// wrap an action with a `done()` method | ||
// for usage in `series()` | ||
// (str, fn(any, (str, fn)) -> fn | ||
function createDone (name, fn) { | ||
return function (done) { | ||
const index = stack.indexOf(name) | ||
if (index !== -1) return emitErr('circular dependency detected') | ||
stack.push(name) | ||
debug("dispatch '%s'", action) | ||
fn.call(this, _stopDispatching.bind(this)) | ||
} | ||
if (fn.length === 2) return fn(data, retFn) | ||
fn(data) | ||
end() | ||
// expose a delegation method to the registered | ||
// actions. Calls `run-series()` under the hood | ||
// str|[str], fn -> null | ||
Dispatcher.prototype.waitFor = function (actions, done) { | ||
done = done || function () {} | ||
assert.equal(typeof done, 'function', 'callback should be a function') | ||
// execute `wait()` and `done()` | ||
// to both delegate new `series()` calls | ||
// handle cb's, and exit once done | ||
// (str, fn) -> null | ||
function retFn (action, cb) { | ||
if (action) return wait(action, endWrap) | ||
endWrap() | ||
actions = Array.isArray(actions) ? actions : [actions] | ||
const ctx = this | ||
function endWrap () { | ||
if (cb) cb() | ||
end() | ||
} | ||
} | ||
const arr = actions.map(function (action) { | ||
const fn = _getAction.call(ctx, action) | ||
const nwFn = _thunkify.call(ctx, fn, action) | ||
return nwFn.bind(ctx) | ||
}) | ||
const nwArr = arr.concat(done.bind(this)) | ||
series(nwArr) | ||
} | ||
// deep assert the actions object | ||
// obj -> null | ||
function _assertActionsObject (actions) { | ||
Object.keys(actions).forEach(function (key) { | ||
const action = actions[key] | ||
if (typeof action === 'object') return _assertActionsObject(action) | ||
assert.equal(typeof action, 'function', 'action should be a function') | ||
}) | ||
} | ||
// wrap function to set | ||
// `this._isHandled[action]` on end | ||
// fn, fn -> fn | ||
function _thunkify (fn, action) { | ||
return function (done) { | ||
try { | ||
assert.equal(typeof action, 'string', '.waitFor(): requires a string or array of strings') | ||
if (this._isPending[action]) { | ||
assert(this._isHandled[action], "circular dependency detected while waiting for '%s'", action) | ||
// pop name from stack | ||
// and exit series call | ||
// null -> null | ||
function end () { | ||
stack.pop() | ||
done() | ||
} | ||
} | ||
} | ||
} catch(e) { | ||
_stopDispatching.call(this) | ||
throw e | ||
} | ||
this._isPending[action] = true | ||
this._isHandled[action] = false | ||
function fin () { | ||
this._current.pop() | ||
this._isHandled[action] = true | ||
done() | ||
// emit an error | ||
// any -> null | ||
function emitErr (err) { | ||
const emitter = actions.error ? actions.error[0] : function () {} | ||
emitter(err) | ||
} | ||
this._current.push(action) | ||
debug("'%s' -> '%s'", this._current[this._current.length - 2], action) | ||
fn(fin.bind(this)) | ||
} | ||
} | ||
// get the dispatched action recursively | ||
// [str], str -> fn | ||
function _getAction (action, arr, index) { | ||
arr = arr || action.split('_') | ||
index = index || 0 | ||
const val = arr[index] | ||
if (typeof val === 'object') return _getAction.call(this, action, arr, index++) | ||
var fn = this._actions | ||
arr.forEach(function (obj, i) { | ||
assert(fn[arr[i]], "action '%s' is not registered", action) | ||
fn = fn[arr[i]] | ||
}) | ||
assert.equal(typeof fn, 'function', "action '%s' is not registered", action) | ||
return fn.bind(this) | ||
} | ||
// reset internal state | ||
// null -> null | ||
function _stopDispatching () { | ||
this._isDispatching = false | ||
this._isPending = {} | ||
this._isHandled = {} | ||
this.payload = null | ||
this._current = [] | ||
this.locals = {} | ||
} |
{ | ||
"name": "barracks", | ||
"version": "5.0.4", | ||
"description": "An event dispatcher for the flux architecture", | ||
"version": "6.0.0", | ||
"description": "Action dispatcher for unidirectional data flows", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint .", | ||
"test": "NODE_ENV=test node test | colortape", | ||
"test-cov": "NODE_ENV=test istanbul cover test.js" | ||
"test": "standard && NODE_ENV=test node test", | ||
"test-cov": "standard && NODE_ENV=test istanbul cover test.js", | ||
"watch": "watch 'npm t'" | ||
}, | ||
@@ -15,2 +15,6 @@ "repository": "yoshuawuyts/barracks", | ||
"flux", | ||
"action", | ||
"minimal", | ||
"emitter", | ||
"event", | ||
"react", | ||
@@ -21,11 +25,9 @@ "react-component" | ||
"devDependencies": { | ||
"colortape": "^0.1.1", | ||
"coveralls": "~2.10.0", | ||
"eslint": "^0.11.0", | ||
"istanbul": "~0.2.10", | ||
"make-lint": "^1.0.1", | ||
"tape": "^3.0.3" | ||
"standard": "^4.5.4", | ||
"tape": "^3.0.3", | ||
"watch": "^0.16.0" | ||
}, | ||
"dependencies": { | ||
"assertf": "^1.0.0", | ||
"debug": "^2.0.0", | ||
@@ -35,3 +37,3 @@ "run-series": "^1.1.2" | ||
"files": [ | ||
"HISTORY.md", | ||
"README.md", | ||
"LICENSE", | ||
@@ -38,0 +40,0 @@ "index.js" |
202
README.md
@@ -7,163 +7,87 @@ # barracks | ||
Event dispatcher for the [flux architecture][flux]. Provides event composition | ||
through `this.waitFor()` and checks for circular dependencies with a small | ||
interface of only 3 functions. | ||
Action dispatcher for unidirectional data flows. Provides action composition | ||
and checks for circular dependencies with a small interface of only 3 | ||
functions. | ||
``` | ||
╔═════╗ ╔════════════╗ ╔════════╗ ╔═════════════════╗ | ||
║ API ║<──────>║ Middleware ║──────>║ Stores ║──────>║ View Components ║ | ||
╚═════╝ ╚════════════╝ ╚════════╝ ╚═════════════════╝ | ||
^ │ | ||
│ │ | ||
╔════════════╗ │ | ||
║ Dispatcher ║ │ | ||
╚════════════╝ │ | ||
^ │ | ||
└────────────────────────────────────────┘ | ||
``` | ||
## Installation | ||
```sh | ||
npm install barracks | ||
$ npm install barracks | ||
``` | ||
## Overview | ||
## Usage | ||
````js | ||
var barracks = require('barracks'); | ||
const barracks = require('barracks') | ||
// Initialize dispatcher. | ||
const dispatcher = barracks() | ||
const store = [] | ||
var dispatcher = barracks({ | ||
users: { | ||
add: function(next) { | ||
console.log(user + ' got added'); | ||
next(); | ||
} | ||
}, | ||
courses: { | ||
get: function(next) { | ||
console.log('Get ' + this.payload); | ||
next(); | ||
}, | ||
set: function(next) { | ||
console.log('Set ' + this.payload); | ||
next(); | ||
} | ||
} | ||
}); | ||
dispatcher.on('error', err => console.log(err)) | ||
dispatcher.on('insert', data => store.push(data.name)) | ||
dispatcher.on('upsert', (data, wait) => { | ||
const index = store.indexOf(data.prevName) | ||
if (index !== -1) return wait('insert') | ||
store[index] = data.newName | ||
}) | ||
// Dispatch an event. | ||
dispatcher('users_add', 'Loki'); | ||
// => 'Loki got added' | ||
dispatcher('insert', {name: 'Loki'}) | ||
dispatcher('upsert', {name: 'Loki', newName: 'Tobi'}) | ||
```` | ||
## API | ||
#### dispatcher = barracks(actions) | ||
Initialize a new `barracks` instance. Returns a function. | ||
```js | ||
// Initialize without namespaces. | ||
### dispatcher = barracks() | ||
Initialize a new `barracks` instance. | ||
var dispatcher = barracks({ | ||
user: function() {}, | ||
group: function() {} | ||
}); | ||
### dispatcher.on(action, cb(data, wait)) | ||
Register a new action. Checks for circular dependencies when dispatching. The | ||
callback receives the passed in data and a `wait(actions[, cb])` function that | ||
can be used to call other actions internally. `wait()` accepts a single action | ||
or an array of actions and an optional callback as the final argument. | ||
// Initialize with namespaces. | ||
### dispatcher(event[, data]) | ||
Call an action and execute the corresponding callback. Alias: | ||
`dispatcher.emit(event[, data])`. | ||
var dispatcher = barracks({ | ||
users: { | ||
add: function() {}, | ||
remove: function() {} | ||
}, | ||
courses: { | ||
get: function() {}, | ||
put: function() {} | ||
} | ||
}); | ||
``` | ||
## Events | ||
### .on('error', cb(err)) | ||
Handle errors. Warns if circular dependencies exists. | ||
#### dispatcher(action, data) | ||
`barracks()` returns a dispatcher function which can be called to dispatch an | ||
action. By dispatching an action you call the corresponding function from | ||
the dispatcher and pass it data. You can think of it as just calling a | ||
function. | ||
## FAQ | ||
### What is an "action dispatcher"? | ||
An action dispatcher gets data from one place to another without tightly | ||
coupling the code. The best known use case for this is in the `flux` pattern. | ||
Say you want to update a piece of data (for example a user's name), instead of | ||
directly calling the update logic inside the view the action calls a function | ||
that updates the user's name for you. Now all the views that need to update a | ||
user's name can call the same action and pass in the relevant data. This | ||
pattern tends to make views more robust and easier to maintain. | ||
In order to access namespaced functions you can delimit your string with | ||
underscores. So to access `courses.get` you'd dispatch the string `courses_get`. | ||
````js | ||
// Call a non-namespaced action. | ||
dispatcher('group', [123, 'hello']); | ||
### Why did you build this? | ||
Passing messages around should not be complicated. Many `flux` implementations | ||
casually throw around framework specific terminology making new users feel | ||
silly for not following along. I don't like that. `barracks` is a package that | ||
takes node's familiar `EventEmitter` interface and adapts it for use as an | ||
action dispatcher. | ||
// Call a namespaced action. | ||
dispatcher('users_add', {foo: 'bar'}); | ||
```` | ||
### I want to start using barracks, but I'm not sure how to use it | ||
That's fine, that means this readme needs to be improved. Would you mind | ||
opening an [issue](https://github.com/yoshuawuyts/barracks/issues) and explain | ||
what you don't understand? I want `barracks` to be comprehensive for | ||
developers of any skill level, so don't hesitate to ask questions if you're | ||
unsure about something. | ||
#### ctx.waitFor(action) | ||
Execute another function within the dispatcher before proceeding. Registered | ||
callbacks are always bound to the scope of the dispatcher, so you can just | ||
call `this.waitFor` to access the function from within a registered callback. | ||
```js | ||
var dispatcher = barracks({ | ||
init: function(next) { | ||
console.log('1'); | ||
this.waitFor(['add', 'listen'], function() { | ||
console.log('4'); | ||
next(); | ||
}); | ||
}, | ||
add: function(next) { | ||
setTimeout(function() { | ||
console.log('2'); | ||
done(); | ||
}, 10); | ||
}, | ||
listen: function(next) { | ||
console.log('3'); | ||
next(); | ||
} | ||
}); | ||
### Why didn't you include feature X? | ||
An action dispatcher doesn't a lot of features to pass a message from A to B. | ||
`barracks` was built for flexibility. If you feel you're repeating yourself a | ||
lot with `barracks` or are missing a feature, feel free to wrap and extend it | ||
however you like. | ||
dispatcher('init'); | ||
// => 1 2 3 | ||
``` | ||
### What data store do you recommend using with barracks? | ||
In flux it's common to store your application state in a data store.I think a | ||
data store should be immutable, single-instance and allow data access through | ||
cursors / lenses. At the moment of writing I haven't found a data store I'm | ||
pleased with, so I'll probably end up writing one in the near future. | ||
#### ctx.payload | ||
`this.payload` contains the data provided by `dispatcher()`. | ||
```js | ||
var dispatcher = barracks({ | ||
init: function(next) { | ||
console.log(this.payload); | ||
} | ||
}); | ||
## See Also | ||
- [wayfarer](https://github.com/yoshuawuyts/wayfarer) - composable trie based route | ||
dispatcher('init', 'fooBar'); | ||
// => 'fooBar' | ||
``` | ||
#### ctx.locals= | ||
`this.locals` is shared between all (delegated) function calls and acts as the | ||
location to share data between function calls. For example when you retrieve | ||
a token from a store and want to make it available to all subsequent functions. | ||
The payload provided by `dispatcher()` is available under `this.locals.payload`. | ||
```js | ||
var dispatcher = barracks({ | ||
add: function(next) { | ||
this.locals.token = 'asdf12345'; | ||
next(); | ||
}); | ||
}, | ||
fetch: function(next) { | ||
this.waitFor(['add'], function() { | ||
console.log(this.locals.token); | ||
next(); | ||
}); | ||
} | ||
}); | ||
dispatcher('fetch'); | ||
// => 'asdf12345' | ||
``` | ||
## License | ||
@@ -170,0 +94,0 @@ [MIT](https://tldrlegal.com/license/mit-license) |
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
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
2
5
8767
83
106
- Removedassertf@^1.0.0
- Removedassertf@1.0.0(transitive)
- Removedbeforefn@2.3.1(transitive)
- Removedsliced@0.0.5(transitive)