-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
221 lines (205 loc) · 6.7 KB
/
index.js
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Generated by CoffeeScript 2.3.2
// 
// # Statue
// ###### 0.4 kb Art of functional State
// ## Installation
// ```sh
// npm install --save @playframe/statue
// ```
// ## Description
// Statue is a [Redux](https://github.com/reduxjs/redux)
// like functional state manegement library that lets you define
// and access actions right inside of your state. So you could
// describe a deeply nested state tree with nested actions
// in one simple object.
// To update state, action could return a new object
// that has one or more properties.
// This will produce a new state with those properties updated.
// Another option is to mutate a state object passed to your function.
// Mutation will be detected and this will produce a new state as well.
// Returned value is ignored in this case.
// If you are writing a performance demanding reducer,
// please use this mutation trick and define the hottest property as
// the first one in your initial state for faster checking.
// If you don't want to update state, please make sure you action
// returns something
// [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)
// or the same state that was passed to it
// To work with state directly you can do
// ```js
// // to update the state
// state._(updated)
// // to read the latest state
// state = state._()
// // to turn a function into a reducer
// state._.new_action = state._( (x, state)=> state.x = x )
// ```
// Statue also cares not to send too many updates
// from child to parent branches
// ## Usage
// ```js
// import statue from '@playframe/statue'
// const state = statue({
// i: 0,
// _: {
// increment: (x, state)=> i: state.i + 1,
// incrementBy: (n)=>(event, state)=> state.i += n // mutating
// },
// subState: {
// i: 0,
// _: {
// increment: (x, state)=> i: state.i + 1
// }
// }
// },
// setTimeout,
// (newState)=> {
// console.log('Counter is:', newState.i)
// console.log('Subcounter is:', newState.subState.i)
// }
// )
// for(let j = 0; j < 100; j++){
// state.subState._.increment()
// state.subState._.increment()
// state._.increment()
// }
// // Logs only once
// //> Counter is: 100
// //> Subcounter is: 200
// // Will increment by 10 and log in console on every click
// $('button').click( state._.incrementBy(10) )
// ```
// ## Annotated Source
// Caching static functions from `Object`
var assign, create, is_function, keys, statue,
hasProp = {}.hasOwnProperty;
({assign, create, keys} = Object);
is_function = (f) => {
return typeof f === 'function';
};
// Let's define a function that takes a takes `state_actions`
// as an initial state and its reducers (actions)
// defined under `_` (underscore) property.
// Our function also takes `level_up` or 'subscribe' function
// that will be called when state updates. Most sertainly we don't
// want to update the whole state tree every time nested leaf updates,
// so we also pass a `delayed` function that will help debounce
// parent update
module.exports = statue = (state_actions, delayed, level_up) => {
/* state._.actions
]_[.state._.actions
[_] ]_[.
[_] [_]
*/
var _nested, _scheduled, _state, action, actions, delay_nested, inject_state, k, reset, save_state, schedule, update_parent, update_state, v;
actions = state_actions._;
_state = state_actions; // _closure
_scheduled = false; // _closure
_nested = reset = () => {
return _nested = reset; // reset _nested
};
// delaying child state updates
delay_nested = (f) => {
_nested = ((_nested) => {
return () => {
_nested();
f();
};
})(_nested);
schedule();
};
// recursive statue if there nested actions
for (k in state_actions) {
v = state_actions[k];
if (v && v._) {
_state[k] = statue(v, delay_nested, ((k) => {
return (sub_state) => { // closure for k
return _state[k] = sub_state; // executes as _nested
};
})(k));
}
}
// saving new state in closure
save_state = (state) => {
schedule();
return _state = state;
};
schedule = () => {
if (!_scheduled) {
_scheduled = true;
// lazy parent update
delayed(update_parent);
}
};
update_parent = () => {
var proto_state;
_scheduled = false;
proto_state = _state;
_state = create(null);
for (k in proto_state) {
// merging prototype chain state
_state[k] = proto_state[k];
}
_nested();
level_up(_state);
};
// This function is a little overloaded, it's a getter/setter but
// also is a function wrapper.
// You can access it like this `yourState._(updated)`.
// The wrapper will return a twin of your function
// that calls yours and passes a copy of latest state
// as the second argument. State will update, if the copy is mutated
// or a new object returned. If your function
// returns a new function, it will be wrapped in the same manner.
// So we support state updates for deeply curried functions.
// curry down or make state
_state._ = update_state = (arg) => {
if (is_function(arg)) {
return inject_state(arg);
} else {
if (arg) {
return save_state(assign(create(_state), arg));
} else {
return _state;
}
}
};
// `inject_state` is a higher order function that does the magic.
// It produces a cheap clone if current state by setting current
// state as its prototype. Please note that such a clone has no
// own properties, and all property accees falls back to its
// prototype. If we mutate such a clone, new properties are
// easily detected
inject_state = (f) => {
return (x) => {
var cloned, mutated, y;
// _state as prototype of cloned
cloned = create(_state);
y = f(x, cloned);
for (k in cloned) {
if (!hasProp.call(cloned, k)) continue;
// mutation detected
mutated = true;
save_state(cloned);
break;
}
if (is_function(y)) {
// recursevely currying down
return inject_state(y);
} else {
if (y && !mutated && y !== cloned && !y.then) { // not promise
save_state(assign(cloned, y));
}
return y;
}
};
};
// Now let's wrap all of your actions and set under the `_` property.
// That's it! You new state machine is ready!
// bind actions to state
for (k in actions) {
action = actions[k];
update_state[k] = inject_state(action);
}
return _state;
};