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

barracks

Package Overview
Dependencies
Maintainers
1
Versions
63
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

barracks - npm Package Compare versions

Comparing version 6.0.6 to 7.0.0

258

index.js

@@ -1,5 +0,4 @@

// const debug = require('debug')('barracks')
const isFsa = require('flux-standard-action').isFSA
const series = require('run-series')
const mutate = require('xtend/mutable')
const assert = require('assert')
const xtend = require('xtend')

@@ -9,84 +8,123 @@ module.exports = dispatcher

// initialize a new barracks instance
// null -> obj
function dispatcher () {
const actions = {}
// obj -> obj
function dispatcher (handlers) {
handlers = handlers || {}
assert.equal(typeof handlers, 'object', 'barracks: handlers should be undefined or an object')
emit._actions = actions
emit.emit = emit
emit.on = on
return emit
const onError = handlers.onError || defaultOnError
const onAction = handlers.onAction
const onState = handlers.onState
// register a new action
// (str, fn) -> obj
function on (action, cb) {
assert.equal(typeof action, 'string')
assert.equal(typeof cb, 'function')
assert.equal(typeof onError, 'function', 'barracks: onError should be a function')
assert.ok(!onAction || typeof onAction === 'function', 'barracks: onAction should be undefined or a function')
assert.ok(!onState || typeof onState === 'function', 'barracks: onState should be undefined or a function')
actions[action] = actions[action] ? actions[action] : []
assert.equal(actions[action].length, 0, 'only one callback per action')
actions[action].push(cb)
var reducersCalled = false
var effectsCalled = false
var stateCalled = false
var subsCalled = false
return emit
const subscriptions = start._subscriptions = {}
const reducers = start._reducers = {}
const effects = start._effects = {}
const models = start._models = []
var _state = {}
start.model = setModel
start.state = getState
start.start = start
return start
// push a model to be initiated
// obj -> null
function setModel (model) {
assert.equal(typeof model, 'object', 'barracks.store.model: model should be an object')
models.push(model)
}
// call an action and
// execute the corresponding callback
// (str, obj?) -> prom
function emit (action, data) {
if (isFsa(action)) {
data = action
action = action.type
// get the current state from the store
// obj? -> obj
function getState (opts) {
opts = opts || {}
assert.equal(typeof opts, 'object', 'barracks.store.state: opts should be an object')
if (opts.state) {
const initialState = {}
const nsState = {}
models.forEach(function (model) {
const ns = model.namespace
if (ns) {
nsState[ns] = {}
apply(ns, model.state, nsState)
nsState[ns] = xtend(nsState[ns], opts.state[ns])
} else {
apply(model.namespace, model.state, initialState)
}
})
return xtend(_state, xtend(opts.state, nsState))
} else if (opts.noFreeze) {
return xtend(_state)
} else {
return Object.freeze(xtend(_state))
}
assert.ok(Array.isArray(actions[action]), 'action exists')
const fn = actions[action][0]
const stack = [action]
}
return fn(data, wait)
// initialize the store handlers, get the send() function
// obj? -> fn
function start (opts) {
opts = opts || {}
assert.equal(typeof opts, 'object', 'barracks.store.start: opts should be undefined or an object')
// internal 'wait()' function
// ([str], fn) -> prom
function wait (action, cb) {
action = Array.isArray(action) ? action : [action]
cb = cb || function () {}
// register values from the models
models.forEach(function (model) {
const ns = model.namespace
if (!stateCalled && model.state && !opts.noState) {
apply(ns, model.state, _state)
}
if (!reducersCalled && model.reducers && !opts.noReducers) {
apply(ns, model.reducers, reducers)
}
if (!effectsCalled && model.effects && !opts.noEffects) {
apply(ns, model.effects, effects)
}
if (!subsCalled && model.subscriptions && !opts.noSubscriptions) {
apply(ns, model.subscriptions, subscriptions, createSend, onError)
}
})
// 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)
})
if (!opts.noState) stateCalled = true
if (!opts.noReducers) reducersCalled = true
if (!opts.noEffects) effectsCalled = true
if (!opts.noSubs) subsCalled = true
return series(arr, cb)
return createSend
// 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)
// call an action from a view
// (str, bool?) -> (str, any?, fn?) -> null
function createSend (selfName, callOnError) {
assert.equal(typeof selfName, 'string', 'barracks.store.start.createSend: selfName should be a string')
assert.ok(!callOnError || typeof callOnError === 'boolean', 'barracks.store.start.send: callOnError should be undefined or a boolean')
if (fn.length === 2) return fn(data, retFn)
else {
fn(data)
stack.pop()
done()
}
return function send (name, data, cb) {
if (!cb && !callOnError) {
cb = data
data = null
}
data = (typeof data === 'undefined' ? null : data)
// execute `wait()` and `done()`
// to both delegate new `series()` calls
// handle cb's, and exit once done
// (str, fn) -> null
function retFn (action, cb) {
cb = cb || function () {}
if (action) return wait(action, endWrap)
endWrap()
assert.equal(typeof name, 'string', 'barracks.store.start.send: name should be a string')
assert.ok(!cb || typeof cb === 'function', 'barracks.store.start.send: cb should be a function')
function endWrap () {
cb()
stack.pop()
done()
}
const done = callOnError ? onErrorCallback : cb
_send(name, data, selfName, done)
function onErrorCallback (err) {
err = err || null
if (err) {
onError(err, _state, function createSend (selfName) {
return function send (name, data) {
assert.equal(typeof name, 'string', 'barracks.store.start.send: name should be a string')
data = (typeof data === 'undefined' ? null : data)
_send(name, data, selfName, done)
}
})
}

@@ -97,11 +135,77 @@ }

// emit an error
// any -> null
function emitErr (err) {
if (!actions.error) {
throw new Error("unhandled 'error' event")
}
actions.error[0](err)
// call an action
// (str, str, any, fn) -> null
function _send (name, data, caller, cb) {
assert.equal(typeof name, 'string', 'barracks._send: name should be a string')
assert.equal(typeof caller, 'string', 'barracks._send: caller should be a string')
assert.equal(typeof cb, 'function', 'barracks._send: cb should be a function')
process.nextTick(function () {
var reducersCalled = false
var effectsCalled = false
const newState = xtend(_state)
if (onAction) onAction(data, _state, name, caller, createSend)
// validate if a namespace exists. Namespaces are delimited by ':'.
var actionName = name
if (/:/.test(name)) {
const arr = name.split(':')
var ns = arr.shift()
actionName = arr.join(':')
}
const _reducers = ns ? reducers[ns] : reducers
if (_reducers && _reducers[actionName]) {
if (ns) {
const reducedState = _reducers[actionName](data, _state[ns])
mutate(newState[ns], xtend(_state[ns], reducedState))
} else {
mutate(newState, reducers[actionName](data, _state))
}
reducersCalled = true
if (onState) onState(data, newState, _state, actionName, createSend)
_state = newState
cb()
}
const _effects = ns ? effects[ns] : effects
if (!reducersCalled && _effects && _effects[actionName]) {
const send = createSend('effect: ' + name)
if (ns) _effects[actionName](data, _state[ns], send, cb)
else _effects[actionName](data, _state, send, cb)
effectsCalled = true
}
if (!reducersCalled && !effectsCalled) {
throw new Error('Could not find action ' + actionName)
}
})
}
}
}
// compose an object conditionally
// optionally contains a namespace
// which is used to nest properties.
// (str, obj, obj, fn?) -> null
function apply (ns, source, target, createSend, done) {
Object.keys(source).forEach(function (key) {
if (ns) {
if (!target[ns]) target[ns] = {}
target[ns][key] = source[key]
} else {
target[key] = source[key]
}
if (createSend && done) {
const send = createSend('subscription: ' + ns ? ns + ':' + key : key)
source[key](send, done)
}
})
}
// handle errors all the way at the top of the trace
// err? -> null
function defaultOnError (err) {
throw err
}
{
"name": "barracks",
"version": "6.0.6",
"version": "7.0.0",
"description": "Action dispatcher for unidirectional data flows",

@@ -8,3 +8,3 @@ "main": "index.js",

"test": "standard && NODE_ENV=test node test",
"test-cov": "standard && NODE_ENV=test istanbul cover test.js",
"test:cov": "standard && NODE_ENV=test istanbul cover test.js",
"watch": "watch 'npm t'"

@@ -25,6 +25,11 @@ },

"devDependencies": {
"bundle-collapser": "^1.2.1",
"coveralls": "~2.10.0",
"es2020": "^1.1.6",
"istanbul": "~0.2.10",
"noop2": "^2.0.0",
"standard": "^7.1.2",
"tape": "^3.0.3",
"uglifyify": "^3.0.2",
"unassertify": "^2.0.3",
"watch": "^0.16.0"

@@ -35,3 +40,5 @@ },

"flux-standard-action": "^0.6.0",
"run-series": "^1.1.2"
"noop2": "^2.0.0",
"run-series": "^1.1.2",
"xtend": "^4.0.1"
},

@@ -38,0 +45,0 @@ "files": [

@@ -7,11 +7,5 @@ # barracks

Action dispatcher for unidirectional data flows. Provides action composition
and checks for circular dependencies with a small interface of only 3
functions.
Action dispatcher for unidirectional data flows. Creates tiny models of data
that can be accessed through actions with an API of only 5 functions
## Installation
```sh
$ npm install barracks
```
## Usage

@@ -21,43 +15,257 @@ ````js

const dispatcher = barracks()
const store = []
const store = barracks({
onError: (err, state, createSend) => {
console.error(`error: ${err}`)
}),
onAction: (action, state, name, caller, createSend) => {
console.log(`action: ${action}`)
})
onState: (action, state, prev, caller, createSend) => {
console.log(`state: ${prev} -> ${state}`)
})
})
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.newName)
if (index !== -1) return wait('insert')
store[index] = data.newName
store.model({
namespace: 'cakes',
state: {},
effects: {},
reducers: {},
subscriptions: {}
})
dispatcher('insert', {name: 'Loki'})
dispatcher('upsert', {name: 'Loki', newName: 'Tobi'})
const createSend = store.start({ noSubscriptions: true })
const send = createSend('myDispatcher', true)
document.addEventListener('DOMContentLoaded', () => {
store.start()
const state = store.state()
send('foo:start', { name: 'Loki' })
})
````
## API
### dispatcher = barracks()
Initialize a new `barracks` instance.
### store = barracks(handlers)
Initialize a new `barracks` instance. Takes an optional object of handlers.
Handlers can be:
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(action, state, name, caller, createSend):__ called when an
`action` is fired.
- __onState(action, state, prev, caller, createSend):__ called after a reducer
changes the `state`.
### 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. Only
one callback can be added per action.
`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.
### dispatcher(action[, data])
Call an action and execute the corresponding callback. Alias:
`dispatcher.emit(action[, data])`. Supports strings and
[flux-standard-action](https://github.com/acdlite/flux-standard-action)s
([example](https://github.com/yoshuawuyts/barracks/blob/master/examples/flux-standard-action.js)).
Handlers should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the handlers.
## Events
### .on('error', cb(err))
Handle errors. Warns if circular dependencies exists.
### store.model()
Register a new model on the store. Models are optionally namespaced objects
with an initial `state`, and handlers for dealing with data:
- __namespace:__ namespace the model so that it cannot access any properties
and handlers in other models
- __state:__ initial values of `state` inside the model
- __reducers:__ synchronous operations that modify state. Triggered by `actions`
- __effects:__ asynchronous operations that don't modify state directly.
Triggered by `actions`, can call `actions`
- __subscriptions:__ asynchronous read-only operations that don't modify state
directly. Can call `actions`
`state` within handlers is immutable through `Object.freeze()` and thus cannot
be modified. Return data from `reducers` to modify `state`. See [handler
signatures](#handler-signatures) for more info on the handlers.
For debugging purposes internal references to values can be inspected through a
series of private accessors:
- `store._subscriptions`
- `store._reducers`
- `store._effects`
- `store._models`
### state = store.state(opts)
Get the current state from the store. Opts can take the following values:
- __noFreeze:__ default: false. Don't freeze returned state using
`Object.freeze()`. Useful for optimizing performance in production builds.
- __state:__ pass in a state object that will be merged with the state returned
from the store. Useful for rendering in Node.
### send = createSend(name) = store.start(opts)
Start the store and get a `createSend(name)` function. Pass a unique `name` to
`createSend()` to get a `send()` function. Opts can take the following values:
- __noSubscriptions:__ default: false. Don't register `subscriptions` when
starting the application. Useful to delay `init` functions until the DOM has
loaded.
- __noEffects:__ default: false. Don't register `effects` when
starting the application. Useful when only wanting the initial `state`
- __noReducers:__ default: false. Don't register `reducers` when
starting the application. Useful when only wanting the initial `state`
- __noFreeze:__ default: false. Don't freeze state in handlers using
`Object.freeze()`. Useful for optimizing performance in production builds.
### send(name, data?)
Send a new action to the models with optional data attached. Namespaced models
can be accessed by prefixing the name with the namespace separated with a `:`,
e.g. `namespace:name`.
## Handler signatures
These are the signatures for the properties that can be passed into a model.
### namespace
An optional string that causes `state`, `effects` and `reducers` to be
prefixed.
```js
app.model({
namespace: 'users'
})
```
### state
State can either be a value or an object of values that is used as the initial
state for the application. If namespaced the values will live under
`state[namespace]`.
```js
app.model({
namespace: 'hey',
state: { foo: 'bar' }
})
app.model({
namespace: 'there',
state: { bin: [ 'beep', 'boop' ] }
})
app.model({
namespace: 'people',
state: 'oi'
}})
```
### reducers
Reducers are synchronous functions that return a value syncrhonously. No
eventual values, just values that are relevant for the state. It takes two
arguments of `action` and `state`. `action` is the data that was emitted, and
`state` is the current state. Each action has a name that can be accessed
through `send(name)`, and when under a namespace can be accessed as
`send(namespace:name)`. When operating under a namespace, reducers only have
access to the state within the namespace.
```js
// some model
app.model({
namespace: 'plantcake',
state: {
enums: [ 'veggie', 'potato', 'lettuce' ]
paddie: 'veggie'
}
})
// so this model can't access anything in the 'plantcake' namespace
app.model({
namespace: 'burlybeardos',
state: { count: 1 },
reducers: {
feedPlantcake: (action, state) => {
return { count: state.count + 1 }
},
trimBeard: (action, state) => ({ count: state.count - 1 })
}
})
```
### effects
`effects` are asynchronous methods that can be triggered by `actions` in
`send()`. They never update the state directly, but can instead do thing
asyncrhonously, and then call `send()` again to trigger a `reducer` that can
update the state. `effects` can also trigger other `effects`, making them fully
composable. Generalyy it's recommended to only have `effects` without a
`namespace` call other `effects`, as to keep namespaced models as isolated as
possible.
When an `effect` is done executing, or encounters an error, it should call the
final `done(err)` callback. If the `effect` was called by another `effect` it
will call the callback of the caller. When an error propegates all the way to
the top, the `onError` handler will be called, registered in
`barracks(handlers)`. If no callback is registered, errors will `throw`.
Having callbacks in `effects` means that error handling can be formalized
without knowledge of the rest of the application leaking into the model. This
also causes `effects` to become fully composable, which smooths parallel
development in large teams, and keeps the mental overhead low when developing a
single model.
```js
const http = require('xhr')
const app = barracks({
onError: (action, state, prev, send) => send('app:error', action)
})
app.model({
namespace: 'app',
effects: {
error: (action, state, send, done) => {
// if doing http calls here be super sure not to get lost
// in a recursive error handling loop: remember this IS
// the error handler
console.error(action.message)
done()
}
}
})
app.model({
namespace: 'foo',
state: { foo: 1 },
reducers: {
moreFoo: (action, state) => ({ foo: state.foo + action.count })
}
effects: {
fetch: (action, state, send, done) => {
http('foobar.com', function (err, res, body) {
if (err || res.statusCode !== 200) {
return done(new Error({
message: 'error accessing server',
error: err
}))
} else {
send('moreFoo', { count: foo.count })
done()
}
})
}
}
})
```
### subscriptions
`subscriptions` are read-only sources of data. This means they cannot be
triggered by actions, but can emit actions themselves whenever they want. This
is useful for stuff like listening to keyboard events or incoming websocket
data. They should generally be started when the application is loaded, using
the `DOMContentLoaded` listener.
```js
app.model({
subscriptions: {
emitWoofs: (send, done) => {
// emit a woof every second
setInterval(() => send('print', { woof: 'meow?' }), 1000)
}
},
effects: {
printWoofs: (action, state) => console.log(action.woof)
}
})
```
`done()` is passed as the final argument so if an error occurs in a subscriber,
it can be communicated to the `onError` hook.
## 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
coupling 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

@@ -70,37 +278,34 @@ that updates the user's name for you. Now all the views that need to update a

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.
casually throw restrictions at users without having a clear architecture. I
don't like that. `barracks` is a package creates a clear flow of data within an
application, concerning itself with state, code separation, and data flow. I
believe that having strong opinions and being transparant in them makes for
architectures than sprinkles of opinions left and right, without a cohesive
story as to _why_.
### I want to use barracks, but I'm not sure where to start
That's fine, but it also means this readme needs to be improved. Would you mind
opening an [issue](https://github.com/yoshuawuyts/barracks/issues) and explain
what you're having difficulty with? 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.
### How is this different from choo?
`choo` is a framework that handles views, data and all problems related to
that. This is a package that only concerns itself with data flow, without being
explicitely tied to the DOM.
### Can I use flux standard actions with barracks?
[Yes you can](https://github.com/yoshuawuyts/barracks/blob/master/examples/flux-standard-action.js)
use [flux standard action](https://github.com/acdlite/flux-standard-action)s
with `barracks`. Just pass in an FSA compliant object into barracks, and it
will be parsed correctly.
### This looks like more than five functions!
Welllll, no. It's technically five functions with a high arity, hah. Nah,
you're right - but five functions _sounds_ good. Besides: you don't need to
know all options and toggles to get this working; that only relevant once you
start hitting edge cases like we did in `choo` :sparkles:
### Why didn't you include feature X?
An action dispatcher doesn't need 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.
## See Also
- [choo](https://github.com/yoshuawuyts/choo) - sturdy frontend framework
- [sheet-router](https://github.com/yoshuawuyts/wayfarer) - fast, modular
client-side router
- [yo-yo](https://github.com/maxogden/yo-yo) - template string based view
framework
- [send-action](https://github.com/sethdvincent/send-action) - unidirectional
action emitter
### 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.
## Installation
```sh
$ npm install barracks
```
## See Also
- [flux-standard-action](https://github.com/acdlite/flux-standard-action/) - human-friendly standard for Flux action objects
- [create-fsa](https://github.com/yoshuawuyts/create-fsa/) - create a flux-standard-action from a value
- [wayfarer](https://github.com/yoshuawuyts/wayfarer) - composable trie based router
## License

@@ -107,0 +312,0 @@ [MIT](https://tldrlegal.com/license/mit-license)

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