New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

redux-optimist

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redux-optimist - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

coverage/coverage.json

173

lib/index.js

@@ -7,8 +7,7 @@ 'use strict';

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var BEGIN = 'BEGIN';
var COMMIT = 'COMMIT';
var REVERT = 'REVERT';
var INITIAL_OPTIMIST = {};
// Array({transactionID: string or null, beforeState: {object}, action: {object}}
var INITIAL_OPTIMIST = [];

@@ -20,74 +19,130 @@ module.exports = optimist;

function optimist(fn) {
return function (state, action) {
var _ref = state || {};
function beginReducer(state, action) {
var _separateState = separateState(state);
var _ref$optimist = _ref.optimist;
var optimist = _ref$optimist === undefined ? INITIAL_OPTIMIST : _ref$optimist;
var optimist = _separateState.optimist;
var innerState = _separateState.innerState;
var oldState = _objectWithoutProperties(_ref, ['optimist']);
optimist = optimist.concat([{ beforeState: innerState, action: action }]);
innerState = fn(innerState, action);
validateState(innerState, action);
return _extends({ optimist: optimist }, innerState);
}
function commitReducer(state, action) {
var _separateState2 = separateState(state);
var oldOptimist = optimist;
if (!state) oldState = undefined;
if (action.optimist && (action.optimist.type === COMMIT || action.optimist.type === REVERT)) {
var _optimist = optimist;
var transaction = _optimist[action.optimist.id];
var optimist = _separateState2.optimist;
var innerState = _separateState2.innerState;
var transactions = _objectWithoutProperties(_optimist, [action.optimist.id]);
if (!transaction) {
console.error('Cannot ' + action.optimist.type + ' transaction with id "' + action.optimist.id + '" because it does not exist');
var newOptimist = [],
started = false,
committed = false;
optimist.forEach(function (entry) {
if (started) {
if (entry.beforeState && matchesTransaction(entry.action, action.optimist.id)) {
committed = true;
newOptimist.push({ action: entry.action });
} else {
newOptimist.push(entry);
}
} else if (entry.beforeState && !matchesTransaction(entry.action, action.optimist.id)) {
started = true;
newOptimist.push(entry);
} else if (entry.beforeState && matchesTransaction(entry.action, action.optimist.id)) {
committed = true;
}
optimist = transactions;
if (transaction && action.optimist.type === REVERT) {
(function () {
var state = transaction.state;
var actions = transaction.actions;
});
if (!committed) {
console.error('Cannot commit transaction with id "my-transaction" because it does not exist');
}
optimist = newOptimist;
return baseReducer(optimist, innerState, action);
}
function revertReducer(state, action) {
var _separateState3 = separateState(state);
actions.forEach(function (action) {
state = fn(state, action);
});
oldState = state;
})();
var optimist = _separateState3.optimist;
var innerState = _separateState3.innerState;
var newOptimist = [],
started = false,
gotInitialState = false,
currentState = innerState;
optimist.forEach(function (entry) {
if (entry.beforeState && matchesTransaction(entry.action, action.optimist.id)) {
currentState = entry.beforeState;
gotInitialState = true;
}
if (!matchesTransaction(entry.action, action.optimist.id)) {
if (entry.beforeState) {
started = true;
}
if (started) {
if (gotInitialState && entry.beforeState) {
newOptimist.push({
beforeState: currentState,
action: entry.action
});
} else {
newOptimist.push(entry);
}
}
if (gotInitialState) {
currentState = fn(currentState, entry.action);
validateState(innerState, action);
}
}
});
if (!gotInitialState) {
console.error('Cannot revert transaction with id "my-transaction" because it does not exist');
}
if (Object.keys(optimist).length) {
(function () {
var newOptimist = {};
Object.keys(optimist).forEach(function (key) {
newOptimist[key] = { state: optimist[key].state, actions: optimist[key].actions.concat([action]) };
});
optimist = newOptimist;
})();
optimist = newOptimist;
return baseReducer(optimist, currentState, action);
}
function baseReducer(optimist, innerState, action) {
if (optimist.length) {
optimist = optimist.concat([{ action: action }]);
}
if (action.optimist && action.optimist.type === BEGIN) {
if (action.optimist.id in optimist) {
console.error('Implicitly committing transaction with id "' + action.optimist.id + '" because it already exists, and you are starting' + ' it again.');
innerState = fn(innerState, action);
validateState(innerState, action);
return _extends({ optimist: optimist }, innerState);
}
return function (state, action) {
if (action.optimist) {
switch (action.optimist.type) {
case BEGIN:
return beginReducer(state, action);
case COMMIT:
return commitReducer(state, action);
case REVERT:
return revertReducer(state, action);
}
if (!state) {
console.error('You should never begin an optimistic transaction before initializing your store.' + ' You would have nothing to revert to!');
}
optimist = _extends({}, optimist, _defineProperty({}, action.optimist.id, { state: oldState, actions: [] }));
}
var newState = fn(oldState, action);
if (!newState || typeof newState !== 'object' || Array.isArray(newState)) {
throw new TypeError('Error while handling "' + action.type + '": Optimist requires that state is always a plain object.');
}
if (oldOptimist !== optimist || !equal(newState, oldState)) return _extends({ optimist: optimist }, newState);else return state;
var separated = separateState(state);
return baseReducer(separated.optimist, separated.innerState, action);
};
}
function equal(newState, oldState) {
if (newState === oldState) return true;
if (!(newState && oldState)) return false;
for (var key in newState) {
if (newState[key] !== oldState[key]) {
return false;
}
function matchesTransaction(action, id) {
return action.optimist && action.optimist.id === id;
}
function validateState(newState, action) {
if (!newState || typeof newState !== 'object' || Array.isArray(newState)) {
throw new TypeError('Error while handling "' + action.type + '": Optimist requires that state is always a plain object.');
}
for (var key in oldState) {
if (newState[key] !== oldState[key]) {
return false;
}
}
function separateState(state) {
if (!state) {
return { optimist: INITIAL_OPTIMIST, innerState: state };
} else {
var _state$optimist = state.optimist;
var _optimist = _state$optimist === undefined ? INITIAL_OPTIMIST : _state$optimist;
var innerState = _objectWithoutProperties(state, ['optimist']);
return { optimist: _optimist, innerState: innerState };
}
return true;
}
{
"name": "redux-optimist",
"version": "0.0.1",
"version": "0.0.2",
"description": "Optimistically apply actions that can be later commited or reverted.",

@@ -9,2 +9,5 @@ "keywords": [],

"babel": "^5.8.23",
"babel-istanbul": "^0.3.20",
"chalk": "^1.1.1",
"diff": "^2.1.2",
"testit": "^2.0.2"

@@ -15,3 +18,4 @@ },

"build": "babel src --out-dir lib",
"test": "babel-node test/index.js"
"test": "babel-node test/index.js",
"coverage": "babel-node node_modules/.bin/babel-istanbul cover test/index.js"
},

@@ -18,0 +22,0 @@ "repository": {

@@ -5,4 +5,18 @@ 'use strict';

import test from 'testit';
import {diffLines} from 'diff';
import chalk from 'chalk';
import optimist from '../src';
function deepEqual(expected, actual) {
expected = JSON.stringify(expected, null, ' ');
actual = JSON.stringify(actual, null, ' ');
if (expected !== actual) {
var diff = diffLines(actual, expected);
var err = '';
diff.forEach(function (chunk) {
err += chunk.added ? chalk.red(chunk.value) : chunk.removed ? chalk.green(chunk.value) : chunk.value;
});
throw err;
}
}
function getWarnings(fn) {

@@ -17,42 +31,16 @@ var warnings = [];

test('with a non-object return type it throws', () => {
try {
optimist(function () { })(undefined, {type: 'foo'});
} catch (ex) {
assert(ex instanceof TypeError);
assert(ex.message === 'Error while handling "foo": Optimist requires that state is always a plain object.');
return;
}
throw new Error('Optimist should have thrown an exception');
});
test('with an object it mixes in the initial state', () => {
let action = {type: 'foo'};
let res = optimist(function (state, a) {
assert(state === undefined);
assert(a === action);
return {lastAction: a};
})(undefined, action);
assert.deepEqual(res, {optimist: {}, lastAction: action});
});
test('when you attempt to commit a non-existent transaction it warns', () => {
let action = {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}};
let res;
let warnings = getWarnings(() => {
res = optimist(function (state, a) {
assert(state === undefined);
assert(a === action);
return {lastAction: a};
})(undefined, action);
test('errors and warnings', () => {
test('with a non-object return type it throws', () => {
try {
optimist(function () { })(undefined, {type: 'foo'});
} catch (ex) {
assert(ex instanceof TypeError);
assert(ex.message === 'Error while handling "foo": Optimist requires that state is always a plain object.');
return;
}
throw new Error('Optimist should have thrown an exception');
});
assert.deepEqual(
warnings,
['Cannot COMMIT transaction with id "my-transaction" because it does not exist']
);
assert.deepEqual(res, {optimist: {}, lastAction: action});
});
test('when you attempt to revert a non-existent transaction it warns', () => {
let action = {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}};
let res;
let warnings = getWarnings(() => {
res = optimist(function (state, a) {
test('with an object it mixes in the initial state', () => {
let action = {type: 'foo'};
let res = optimist(function (state, a) {
assert(state === undefined);

@@ -62,128 +50,220 @@ assert(a === action);

})(undefined, action);
assert.deepEqual(res, {optimist: [], lastAction: action});
});
assert.deepEqual(
warnings,
['Cannot REVERT transaction with id "my-transaction" because it does not exist']
);
assert.deepEqual(res, {optimist: {}, lastAction: action});
test('when you attempt to commit a non-existent transaction it warns', () => {
let action = {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}};
let res;
let warnings = getWarnings(() => {
res = optimist(function (state, a) {
assert(state === undefined);
assert(a === action);
return {lastAction: a};
})(undefined, action);
});
assert.deepEqual(
warnings,
['Cannot commit transaction with id "my-transaction" because it does not exist']
);
assert.deepEqual(res, {optimist: {}, lastAction: action});
});
test('when you attempt to revert a non-existent transaction it warns', () => {
let action = {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}};
let res;
let warnings = getWarnings(() => {
res = optimist(function (state, a) {
assert(state === undefined);
assert(a === action);
return {lastAction: a};
})(undefined, action);
});
assert.deepEqual(
warnings,
['Cannot revert transaction with id "my-transaction" because it does not exist']
);
assert.deepEqual(res, {optimist: {}, lastAction: action});
});
});
test('beginning a transaction', () => {
let action = {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}};
let res = optimist(function (state, a) {
assert.deepEqual(state, {initial: 'state'});
assert(a === action);
return {lastAction: a};
})({initial: 'state'}, action);
assert.deepEqual(
res,
{
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [] }
basic('beginning a transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
}
],
lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
}
});
basic('within a transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
}
],
lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
action: {type: 'foo'},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
lastAction: action
}
);
{action: {type: 'foo'}}
],
lastAction: {type: 'foo'}
}
});
test('within a transaction', () => {
let action = {type: 'foo'};
let initialState = {
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [] }
},
lastAction: {type: 'bar'}
};
let res = optimist(function (state, a) {
assert.deepEqual(state, {lastAction: {type: 'bar'}});
assert(a === action);
return {lastAction: a};
})(initialState, action);
assert.deepEqual(
res,
{
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [action] }
basic('nest a transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
}
],
lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
lastAction: action
}
);
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
],
lastAction: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
}
});
test('revert a transaction', () => {
let action = {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}};
let initialState = {
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [] }
},
lastAction: {type: 'bar'}
};
let res = optimist(function (state, a) {
assert.deepEqual(state, {initial: 'state'});
assert(a === action);
return {lastAction: a};
})(initialState, action);
assert.deepEqual(
res,
{
optimist: {},
lastAction: action
}
);
basic('revert a transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
],
lastAction: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
action: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
{
action: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}},
}
],
lastAction: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}},
}
});
test('revert a transaction with intermediate actions', () => {
let action = {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-transaction'}};
let initialState = {
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [{type: 'bar'}] }
},
lastAction: {type: 'bar'}
};
var call = 0;
let res = optimist(function (state, a) {
call++;
if (call === 1) {
assert.deepEqual(state, {initial: 'state'});
assert.deepEqual(a, {type: 'bar'});
}
if (call === 2) {
assert.deepEqual(state, {lastAction: {type: 'bar'}});
assert.deepEqual(a, action);
}
if (call > 2) {
throw new Error('Expected only 2 calls');
}
return {lastAction: a};
})(initialState, action);
assert.deepEqual(
res,
{
optimist: {},
lastAction: action
}
);
basic('revert other transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
],
lastAction: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
action: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-other-transaction'}},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
action: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-other-transaction'}},
}
],
lastAction: {type: 'foo', optimist: {type: optimist.REVERT, id: 'my-other-transaction'}},
}
});
test('commit a transaction', () => {
let action = {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}};
let initialState = {
optimist: {
'my-transaction': { state: {initial: 'state'}, actions: [] }
},
lastAction: {type: 'bar'}
};
let res = optimist(function (state, a) {
assert.deepEqual(state, {lastAction: {type: 'bar'}});
assert(a === action);
return {lastAction: a};
})(initialState, action);
assert.deepEqual(
res,
{
optimist: {},
lastAction: action
}
);
basic('commit a transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
],
lastAction: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
action: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}},
after: {
optimist: [
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
{
action: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}},
}
],
lastAction: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-transaction'}},
}
});
basic('commit other transaction', {
reducer: (state, a) => ({lastAction: a}),
before: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
beforeState: {lastAction: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}},
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
],
lastAction: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
action: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-other-transaction'}},
after: {
optimist: [
{
beforeState: {initial: 'state'},
action: {type: 'foo', optimist: {type: optimist.BEGIN, id: 'my-transaction'}}
},
{
action: {type: 'bar', optimist: {type: optimist.BEGIN, id: 'my-other-transaction'}}
},
{
action: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-other-transaction'}},
}
],
lastAction: {type: 'foo', optimist: {type: optimist.COMMIT, id: 'my-other-transaction'}},
}
});

@@ -246,3 +326,86 @@

});
assert.deepEqual(state, {optimist: {}, value: 4});
deepEqual(state, {optimist: [], value: 4});
});
test('real world example 2', () => {
function originalReducer(state = {value: 0}, action) {
switch (action.type) {
case 'SET':
return {value: action.value};
case 'INCREMENT':
return {value: state.value + 1};
case 'INCREMENT_IF_EVEN':
return state.value % 2 === 0 ? {value: state.value + 1} : state;
default:
return state;
}
}
let reducer = optimist(originalReducer);
let actionCreators = {
set(value, transactionID) {
return {
type: 'SET',
value: value,
optimist: transactionID ? {id: transactionID} : undefined
};
},
increment(transactionID) {
return {
type: 'INCREMENT',
optimist: transactionID ? {id: transactionID} : undefined
};
},
incrementIfEven(transactionID) {
return {
type: 'INCREMENT_IF_EVEN',
optimist: transactionID ? {id: transactionID} : undefined
};
},
begin(transactionID) {
return {type: 'BEGIN', optimist: {type: optimist.BEGIN, id: transactionID}};
},
commit(transactionID) {
return {type: 'COMMIT', optimist: {type: optimist.COMMIT, id: transactionID}};
},
revert(transactionID) {
return {type: 'REVERT', optimist: {type: optimist.REVERT, id: transactionID}};
},
};
let actions = [
{action: {type: '@@init'}, value: 0},
{action: actionCreators.set(2), value: 2},
{action: actionCreators.begin('start-at-1'), value: 2},
{action: actionCreators.set(1, 'start-at-1'), value: 1},
{action: actionCreators.incrementIfEven(), value: 1},
{action: actionCreators.increment('start-at-1'), value: 2},
{action: actionCreators.begin('inc'), value: 2},
{action: actionCreators.increment('inc'), value: 3},
{action: actionCreators.commit('inc'), value: 3},
{action: actionCreators.revert('start-at-1'), value: 4},
];
let state;
actions.forEach(({action, value}) => {
state = reducer(state, action);
assert(state.value === value);
});
assert.deepEqual(state, {optimist: [], value: 4});
});
function basic(name, {reducer, before, action, after}) {
test(name, () => {
let res = optimist(function (state, a) {
/*
if (before) {
let {optimist, ...filteredBefore} = before;
assert.deepEqual(state, filteredBefore);
} else {
assert(state === before);
}
assert(a === action, 'action should be passed through to reducer');
*/
return reducer(state, a);
})(before, action);
deepEqual(res, after);
});
}
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