1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 |
1
1
1
1
1
1
1
1
1
1
6
6
6
6
6
1
5
5
5
13
8
3
3
5
5
4
4
1
1
5
1
5
5
1
5
5
5
15
4
4
15
9
2
9
2
1
1
9
8
8
5
1
5
5
1
22
12
22
22
21
13
28
19
6
5
5
12
12
1
30
1
36
1
1
28
6
22
22
| 'use strict';
var BEGIN = 'BEGIN';
var COMMIT = 'COMMIT';
var REVERT = 'REVERT';
// Array({transactionID: string or null, beforeState: {object}, action: {object}}
var INITIAL_OPTIMIST = [];
module.exports = optimist;
module.exports.BEGIN = BEGIN;
module.exports.COMMIT = COMMIT;
module.exports.REVERT = REVERT;
function optimist(fn) {
function beginReducer(state, action) {
let {optimist, innerState} = separateState(state);
optimist = optimist.concat([{beforeState: innerState, action}]);
innerState = fn(innerState, action);
validateState(innerState, action);
return {optimist, ...innerState};
}
function commitReducer(state, action) {
let {optimist, innerState} = separateState(state);
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 Eif (
entry.beforeState &&
matchesTransaction(entry.action, action.optimist.id)
) {
committed = true;
}
});
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) {
let {optimist, innerState} = separateState(state);
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');
}
optimist = newOptimist;
return baseReducer(optimist, currentState, action);
}
function baseReducer(optimist, innerState, action) {
if (optimist.length) {
optimist = optimist.concat([{action}]);
}
innerState = fn(innerState, action);
validateState(innerState, action);
return {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);
}
}
let separated = separateState(state);
return baseReducer(separated.optimist, separated.innerState, action);
};
}
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.'
);
}
}
function separateState(state) {
if (!state) {
return {optimist: INITIAL_OPTIMIST, innerState: state};
} else {
let {optimist = INITIAL_OPTIMIST, ...innerState} = state;
return {optimist, innerState};
}
}
|