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

cerebral

Package Overview
Dependencies
Maintainers
1
Versions
638
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cerebral - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0

src/core/createRefMethods.js

114

API.md

@@ -22,4 +22,6 @@ # API

- [ref](#ref)
- [getByRef](#getbyref)
- [create](#create)
- [update](#update)
- [get](#update)
- [remove](#update)
- [getMemories](#getmemories)

@@ -478,16 +480,110 @@ - [remember](#remember)

### ref
Cerebral has a reference implementation. The reason it has this implementation is for you to easily do optimistic updates, relational data and general lookups in the cerebral. Lets us first look at the four methods and then look at a scenario.
#### create
```js
let todo = {
ref: cerebral.ref(),
title: 'foo'
let ref = cerebral.ref.create();
```
Returns a reference value created by the cerebral.
```js
let ref = cerebral.ref.create(foo.id);
```
Returns a reference value that is now linked with the id of foo.
#### update
```js
let ref = cerebral.ref.create();
cerebral.ref.update(ref, foo.id);
```
Links an id to the ref.
#### get
```js
let ref = cerebral.ref.get(foo.id);
```
Return the ref related to the id of foo.
#### remove
```js
cerebral.ref.remove(ref);
cerebral.ref.remove(foo.id);
```
Both these removes the ref and linked id.
**So why use this?**. When you download and update data in the client you should put that data in a map (object) and use arrays to display that data. The reason is that these gives you a data storage of sorts. Displaying data will be used by referencing this source data. To give complete control of all this referencing you can use cerebrals implementation.
Let us optimistically update our application with a new todo.
```js
let addTodo = function (cerebral) {
let ref = cerebral.ref.create();
let todo = {
$isSaving: true,
title: cerebral.get('newTodoTitle'),
completed: false
};
cerebral.set(['todos', ref], todo);
return ref;
};
```
Use Cerebral refs to create unique IDs in the client. It is important that you use Cerebrals internal reference implementation as the IDs will be created chronologically.
We create an action that adds a new todo to our projects map using a created ref. Then it returns that ref. Our next action will now save that todo.
```js
let saveTodo = function (cerebral, ref) {
let todo = cerebral.get(['todos', ref]);
return ajax.post('/todos', {
title: todo.title,
completed: todo.completed
})
.success(function (id) {
cerebral.ref.update(todo.ref, id);
return id;
});
};
```
Our action now grabs the todo using the ref and declares exactly what properties to send to the server. This has two benefits. You can see in your code exactly what you pass to the server and you avoid sending unnecessary data, like the client side $isSaving property. When the server responds with an ID we update our reference and link it to the new id. Then we return the id for the next action which will actually set the id on our todo.
### getByRef
```js
let todo = cerebral.getByRef('todos', todo.$ref);
let setTodoId = function (cerebral, id) {
let ref = cerebral.ref.get(id);
let todo = cerebral.get(['todos', ref]);
cerebral.set([todo, 'id'], id);
};
```
Values returned from cerebral are immutable!
We can now use the id to get our ref. The ref is used to grab the todo and we set the id to it.
What we have achieved with this implementation is to remove the need for ids completely. The client, with cerebral, is in full control of all objects and you can use both refs and ids to find what you need. This implementation becomes especially useful when mapping relational data.
```js
let cerebral = Cerebral({
projects: {},
users: {},
projectRows = function () {
return {
value: [],
deps: ['projects', 'users'],
get(cerebral, deps, projectRefs) {
return projectRefs.map(function (ref) {
let project = deps.projects[ref].toJS();
let userRef = cerebral.ref.get(project.authorId);
if (userRef) {
project.author = deps.users[userRef];
} else {
project.author = {
$notFound: true
};
}
return project;
});
}
};
}
})
```
### toJS

@@ -494,0 +590,0 @@ ```js

9

demo/actions/addTodo.js
let addTodo = function(cerebral) {
let ref = cerebral.ref.create();
let todo = {
$ref: cerebral.ref(),
$isSaving: true,

@@ -8,7 +9,9 @@ title: cerebral.get('newTodoTitle'),

};
cerebral.push('todos', todo);
cerebral.set(['todos', ref], todo);
cerebral.set('newTodoTitle', '');
return todo;
return ref;
};
export default addTodo;
let clearCompleted = function (cerebral) {
cerebral.set('todos', cerebral.get('todos').filter(function (todo) {
return !todo.completed && !todo.$isSaving;
}));
let todos = cerebral.get('todos');
Object.keys(todos).forEach(function (key) {
if (todos[key].completed && !todos[key].$isSaving) {
cerebral.unset('todos', key);
}
});
};
export default clearCompleted;

@@ -1,8 +0,12 @@

let setEditedTodo = function (cerebral, todo) {
todo = cerebral.getByRef('todos', todo.$ref);
cerebral.merge(todo, {
let editTodo = function (cerebral, id) {
let ref = cerebral.ref.get(id);
let todo = cerebral.get(['todos', ref]);
cerebral.merge(['todos', ref], {
$isEditing: !todo.$isSaving && true
});
};
export default setEditedTodo;
export default editTodo;

@@ -1,7 +0,7 @@

let removeTodo = function(cerebral, todo) {
var path = todo.getPath();
var index = path.pop();
cerebral.splice(path, index, 1);
let removeTodo = function(cerebral, id) {
let ref = cerebral.ref.get(id);
cerebral.unset('todos', ref);
cerebral.ref.remove(id);
};
export default removeTodo;

@@ -1,8 +0,11 @@

let saveTodo = function (cerebral, todo) {
let saveTodo = function (cerebral, ref) {
return new Promise(function (resolve, reject) {
let todo = cerebral.get('todos', ref);
// Simulating posting the todo.data and get an ID from
// the server. We resolve with the new id and the ref
setTimeout(function () {
resolve({
$ref: todo.$ref,
$isSaving: false
ref: ref,
id: Date.now() + parseInt(Math.random() * 1000)
});

@@ -9,0 +12,0 @@ }, 3000);

let setAllChecked = function(cerebral, value) {
let visibleTodos = cerebral.get('visibleTodos');
cerebral.set('isAllChecked', visibleTodos.filter(function(todo) {
return !todo.completed;
}).length === 0 && visibleTodos.length !== 0);
return value;
};
export default setAllChecked;
let setCounters = function(cerebral, value) {
let counts = cerebral.get('todos').reduce(function(counts, todo) {
let todos = cerebral.get('todos');
let counts = Object.keys(todos).reduce(function(counts, key) {
let todo = todos[key];
if (todo.completed) {

@@ -9,3 +13,5 @@ counts.completedCount++;

}
return counts;
}, {

@@ -12,0 +18,0 @@ completedCount: 0,

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

let setTodoNewTitle = function (cerebral, todo, title) {
todo = cerebral.get('todos', todo.$ref);
cerebral.merge(todo, {
let setTodoNewTitle = function (cerebral, id, title) {
let ref = cerebral.ref.get(id);
cerebral.merge(['todos', ref], {
$newTitle: title

@@ -5,0 +5,0 @@ });

@@ -5,3 +5,6 @@ let setVisibleTodos = function(cerebral, value) {

let filter = cerebral.get('filter');
let visibleTodos = todos.filter(function(todo) {
let visibleTodos = Object.keys(todos).filter(function(key) {
let todo = todos[key];
return (

@@ -12,6 +15,5 @@ filter === 'all' ||

);
})
.map(function (todo) {
return todo.$ref;
});
cerebral.set('visibleTodos', visibleTodos);

@@ -18,0 +20,0 @@

@@ -1,4 +0,6 @@

let stopEditingTodo = function (cerebral, todo) {
todo = cerebral.getByRef('todos', todo.$ref);
let stopEditingTodo = function (cerebral, id) {
let ref = cerebral.ref.get(id);
let todo = cerebral.get(['todos', ref]);
if (!todo.$newTitle) {

@@ -5,0 +7,0 @@ return;

@@ -1,6 +0,11 @@

let toggleAllChecked = function(cerebral, todo) {
var isCompleted = !cerebral.get('isAllChecked');
cerebral.get('todos').forEach(function (todo) {
let toggleAllChecked = function(cerebral) {
let isCompleted = !cerebral.get('isAllChecked');
let todos = cerebral.get('todos');
Object.keys(todos).forEach(function (key) {
let todo = todos[key];
cerebral.set([todo, 'completed'], isCompleted);
});
cerebral.set('isAllChecked', isCompleted);

@@ -7,0 +12,0 @@ };

@@ -1,2 +0,4 @@

let toggleTodoCompleted = function(cerebral, todo) {
let toggleTodoCompleted = function(cerebral, id) {
let ref = cerebral.ref.get(id);
let todo = cerebral.get(['todos', ref]);
cerebral.set([todo, 'completed'], !todo.completed);

@@ -3,0 +5,0 @@ };

@@ -1,6 +0,13 @@

let updateTodo = function(cerebral, updatedTodo) {
let todo = cerebral.getByRef('todos', updatedTodo.$ref);
cerebral.merge(todo, updatedTodo);
let updateTodo = function(cerebral, result) {
let todo = cerebral.get('todos', result.ref);
cerebral.ref.update(result.ref, result.id);
cerebral.merge(todo, {
id: result.id,
$isSaving: false
});
};
export default updateTodo;

@@ -22,3 +22,3 @@ import React from 'react';

{this.state.visibleTodos.length ? <TodosList/> : null}
{this.state.todos.length ? <TodosFooter/> : null}
{Object.keys(this.state.todos).length ? <TodosFooter/> : null}
</section>

@@ -25,0 +25,0 @@ <footer id="info">

import Cerebral from './../src/Cerebral.js';
var state = localStorage.store ? JSON.parse(localStorage.store) : {
todos: [],
visibleTodos: function () {
todos: {},
visibleTodos: function() {
return {
value: [],
deps: ['todos'],
get: function (cerebral, deps, $refs) {
return $refs.map(function ($ref) {
return deps.todos.filter(function (todo) {
return todo.$ref === $ref;
}).pop();
get: function(cerebral, deps, refs) {
return refs.map(function(ref) {
return deps.todos[ref];
});

@@ -15,0 +13,0 @@ }

@@ -7,6 +7,2 @@ import React from 'react';

toggleCompleted() {
actions.toggleCompleted(this.props.todo);
}
edit() {

@@ -18,3 +14,3 @@

this.signals.todoDoubleClicked(this.props.todo);
this.signals.todoDoubleClicked(this.props.todo.id);

@@ -30,3 +26,3 @@ // FOCUS fix

onNewTitleChanged(event) {
this.signals.newTitleChanged(this.props.todo, event.target.value);
this.signals.newTitleChanged(this.props.todo.id, event.target.value);
}

@@ -36,3 +32,3 @@

event.preventDefault();
actions.saveEdit(this.props.todo, this.state.newTitle);
actions.saveEdit(this.props.todo.id, this.state.newTitle);
}

@@ -62,6 +58,11 @@

disabled={this.props.todo.$isSaving}
onChange={this.signals.toggleCompletedChanged.bind(this, this.props.todo)}
onChange={this.signals.toggleCompletedChanged.bind(null, this.props.todo.id)}
checked={this.props.todo.completed}/>
}
<label onDoubleClick={this.edit.bind(this)}>{this.props.todo.title} {this.props.todo.$isSaving ? <small>(saving)</small> : null}</label>
<label onDoubleClick={this.edit.bind(this)}>
{this.props.todo.title} {this.props.todo.$isSaving ?
<small>(saving)</small> :
null
}
</label>
{

@@ -72,3 +73,3 @@ this.props.todo.$isSaving ?

className="destroy"
onClick={this.signals.removeTodoClicked.bind(this, this.props.todo)}/>
onClick={this.signals.removeTodoClicked.bind(null, this.props.todo.id)}/>
}

@@ -81,3 +82,3 @@ </div>

value={this.props.todo.$newTitle || this.props.todo.title}
onBlur={this.signals.newTitleSubmitted.bind(null, this.props.todo)}
onBlur={this.signals.newTitleSubmitted.bind(null, this.props.todo.id)}
onChange={this.onNewTitleChanged.bind(this)}

@@ -84,0 +85,0 @@ />

{
"name": "cerebral",
"version": "0.5.0",
"version": "0.6.0",
"description": "An intelligent react application framework",

@@ -5,0 +5,0 @@ "main": "src/Cerebral.js",

@@ -23,2 +23,3 @@ 'use strict';

var createMutationMethods = require('./core/createMutationMethods.js');
var createRefMethods = require('./core/createRefMethods.js');
var CerebralDebugger = React.createFactory(require('./Debugger.js'));

@@ -135,2 +136,4 @@ var createStore = require('./core/createStore.js');

helpers.nextSignal = 0;
helpers.refs = [];
helpers.ids = [];
refIds = {};

@@ -167,27 +170,4 @@ return helpers.eventStore.travel(index, helpers.currentState);

// The ref method creates a reference that can be used by objects without ID.
// It is chronological as required by EventStore
cerebral.ref = function(id) {
cerebral.ref = createRefMethods(helpers);
// Will map an ID to a ref to allow optimistic updates very easily
if (id && id in refIds) {
return refIds[id];
} else if (id) {
return refIds[id] = helpers.nextRef++;
} else {
return helpers.nextRef++;
}
};
// Allows to quickly grab an object from en array using the $ref
cerebral.getByRef = function(path, $ref) {
var items = this.get(path);
for (var x = 0; x < items.length; x++) {
if (items[x].$ref === $ref) {
return items[x];
}
}
};
// Resets any helpers state and resets the EventStore

@@ -198,2 +178,4 @@ cerebral.reset = function() {

helpers.asyncCallbacks = {};
helpers.refs = [];
helpers.ids = [];
refIds = {};

@@ -200,0 +182,0 @@ helpers.eventStore.reset(helpers.currentState);

@@ -22,2 +22,4 @@ "use strict";

nextRef: 0,
refs: [],
ids: [],
currentSignal: eventStore.currentIndex,

@@ -24,0 +26,0 @@

@@ -124,3 +124,3 @@ var React = require('react');

color: '#555',
fontSize: '1.25em'
fontSize: '1em'
}

@@ -254,3 +254,3 @@ }, (index + 1) + '. ' + action.name,

marginBottom: '25px',
fontSize: '1.5em'
fontSize: '1.25em'
}

@@ -257,0 +257,0 @@ }, signal ? DOM.span(null, signal.name, this.renderFPS(signal.duration)) : null),

"use strict";
/*
EventStore will keep track of all signals triggered on the cerebral. It keeps an array of signals with
actions and mutations related to that signal. It will also track any async signals processing. The EventStore
is able to reset state and travel to a "specific point in time" by playing back the signals up to a certain
signal.
*/
var utils = require('./utils.js');
var EventStore = function(state, store) {
var EventStore = function(state, cerebral) {
// We grab the signals stored in localStorage, if any
var signals = utils.hasLocalStorage() && localStorage.getItem('cerebral_signals') ?
JSON.parse(localStorage.getItem('cerebral_signals')) : [];
// The initial state is used to reset the cerebral
this.initialState = state;
// Indicates if signals should be stored or replaced. Grabs from localStorage if available
this.willKeepState = utils.hasLocalStorage() && localStorage.getItem('cerebral_keepState') ?
JSON.parse(localStorage.getItem('cerebral_keepState')) : true;
this.signals = signals;
this.asyncSignals = [];
// Flag used to evaluate that new signals should be stored, as they will trigger when "travelling"
this.hasExecutingSignals = false;
// Flag used by debugger to evaluate if travelling should be possible. Should not travel while executing
// signals
this.hasExecutingAsyncSignals = false;
// -1 means "no"
this.currentIndex = -1;
this.store = store;
// We keep a reference to the cerebral
this.cerebral = cerebral;
// When travelling it should not trigger any events
// TODO: Might make more sense to put this on the cerebral
this.isSilent = false;

@@ -23,5 +47,7 @@

// Flips flag of storing signals or replacing them. Needs to trigger a special
// event to notify Debugger about the change
EventStore.prototype.toggleKeepState = function() {
this.willKeepState = !this.willKeepState;
this.store.emit('eventStoreUpdate');
this.cerebral.emit('eventStoreUpdate');
};

@@ -31,3 +57,6 @@

// If it is an ending async signal, remove it from the list by comparing
// the start time. TODO: Should also check name of signal?
if (signal.end) {
var startSignal = this.asyncSignals.filter(function(existingSignal) {

@@ -37,12 +66,20 @@ return existingSignal.start === existingSignal.start;

this.asyncSignals.splice(this.asyncSignals.indexOf(startSignal), 1);
// Or add it to the list
} else {
this.asyncSignals.push(signal);
// If it is the first signal we should flip flag and notify with an event
if (this.asyncSignals.length === 1) {
this.hasExecutingAsyncSignals = true;
this.store.emit('eventStoreUpdate');
this.cerebral.emit('eventStoreUpdate');
}
}
// When no more signals flip flag and notify with event
if (!this.asyncSignals.length) {
this.hasExecutingAsyncSignals = false;
this.store.emit('eventStoreUpdate');
this.cerebral.emit('eventStoreUpdate');
}

@@ -52,18 +89,29 @@

// Adds signals triggered on the cerebral
EventStore.prototype.addSignal = function(signal) {
// When executing signals in EventStore, do not add them again
if (this.hasExecutingSignals) {
return;
}
// If we are not keeping the state around reset the signals to just
// keep the latest one
if (!this.willKeepState) {
this.signals = [];
}
if (this.currentIndex < this.signals.length - 1) {
this.signals.splice(this.currentIndex + 1, this.signals.length - this.currentIndex);
// If we have travelled back and start adding new signals the signals not triggered should
// be removed. This effectively "changes history"
if (this.currentIndex < this.signals.length) {
this.signals.splice(this.currentIndex, this.signals.length - this.currentIndex);
}
}
// Add signal and set the current signal to be the recently added signal
this.signals.push(signal);
this.currentIndex = this.signals.length - 1;
};
// Adds an action to the signal, unless travelling
EventStore.prototype.addAction = function(action) {

@@ -76,2 +124,3 @@ if (this.hasExecutingSignals) {

// Adds a mutation to an action inside a signal, unless travelling
EventStore.prototype.addMutation = function(mutation) {

@@ -85,2 +134,4 @@ if (this.hasExecutingSignals) {

// TODO: What names to use, remember or just signals?
// This is used when loading up the app and producing the last known state
EventStore.prototype.rememberNow = function(state) {

@@ -92,3 +143,8 @@

// Silently travels (does not trigger events) before the
// app is loaded
this.isSilent = true;
/*
TODO: Is this needed? As it only runs when starting app. Maybe when using react-hot-loader?
if (this.hasExecutingAsyncSignals) {

@@ -101,2 +157,3 @@ var lastAsyncSignal = this.asyncSignals.sort(function(a, b) {

}
*/
this.travel(this.signals.length - 1, state);

@@ -106,2 +163,4 @@ this.isSilent = false;

// Will reset the EventStore and cerebral completely
// TODO: Should be in the cerebral really? Mostly cerebral related code here
EventStore.prototype.reset = function(state) {

@@ -112,6 +171,8 @@ if (!this.hasExecutingAsyncSignals) {

this.store.removeAllListeners('mapUpdate');
// Remove all mapUpdate listeners as map functions will be added again
// TODO: Make sure added map functions are removed from helpers
this.cerebral.removeAllListeners('mapUpdate');
// First remove all state, as there might have been some
// additions
// First remove all state, as some code might have added
// new props
Object.keys(state).forEach(function (key) {

@@ -121,3 +182,4 @@ state = state.unset(key);

// Make sure we do not trigger any events
// Now set the initial state
// TODO: Should this do the same object diffing?
Object.keys(this.initialState).forEach(function(key) {

@@ -129,3 +191,4 @@ state = state.set(key, this.initialState[key]);

this.store.emit('update');
// Update app with new data
this.cerebral.emit('update');

@@ -137,10 +200,13 @@ }

var store = this.store;
var cerebral = this.cerebral;
// Create new store with initial state
// Flag that we are travelling.
// TODO: Should it be named remember instead?
// TODO: Is not hasExecutingSignals and isRemembering the same?
// TODO: Remove mapUpdate here too? Maybe have some resetToInitialState method?
this.hasExecutingSignals = true;
store.isRemembering = true;
store.removeAllListeners('mapUpdate');
cerebral.isRemembering = true;
cerebral.removeAllListeners('mapUpdate');
// Make sure we do not trigger any events
// TODO: This should also be part of general reset?
Object.keys(this.initialState).forEach(function(key) {

@@ -150,3 +216,3 @@ state = state.set(key, this.initialState[key]);

// If no event, just update the app
// If going back to initial state, just return and update
if (index === -1) {

@@ -156,10 +222,12 @@

this.hasExecutingSignals = false;
store.isRemembering = false;
store.emit('update');
return store;
cerebral.isRemembering = false;
cerebral.emit('update');
return cerebral;
} else {
// Run through events
// Start from beginning
this.currentIndex = -1;
// Go through signals
for (var x = 0; x <= index; x++) {

@@ -172,15 +240,18 @@

store.signals[signal.name].apply(store, signal.args);
// Trigger signal and then set what has become the current signal
cerebral.signals[signal.name].apply(cerebral, signal.args);
this.currentIndex = x;
}
}
// Reset flags and emit event to Debugger
this.hasExecutingSignals = false;
store.isRemembering = false;
store.emit('eventStoreUpdate');
cerebral.isRemembering = false;
cerebral.emit('eventStoreUpdate');
return store;
return cerebral;
};
module.exports = EventStore;
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