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": [ |
337
README.md
@@ -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) |
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
20969
182
322
5
10
1
+ Addednoop2@^2.0.0
+ Addedxtend@^4.0.1
+ Addednoop2@2.0.0(transitive)
+ Addedxtend@4.0.2(transitive)