-
Notifications
You must be signed in to change notification settings - Fork 0
/
actions.js
334 lines (306 loc) · 8.88 KB
/
actions.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
'use strict';
var assign = require('lodash.assign');
/**
*
* Create a pocket of actions as defined by initial optional actions and
* optional subscribers. Used to create new immutable action dispatchers.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myActions = actions.register(function double (i) { return i * 2; });
* var double2 = myActions.invoke('double', 2); //> 4
* var double = myActions.fn.double;
* ```
*
* @property {Object<String, Function>} actions attached to the dispatcher
* @property {Array<Function>} subscribers attached to the dispatcher
*
* @module actions
* @returns {Object}
* @api public
*/
function actions (initialActions, subscribers) {
var storedActions = (initialActions || {});
var storedSubscribers = (subscribers || []);
var methods = {
/**
* Create a pocket of actions as defined by initial optional actions and
* optional subscribers. Used to create new immutable action dispatchers.
*
* ### Example
*
* ```js
* var double2 = actions.invoke('double', 2); //> 4
* ```
*
* @param {Array<String>|String} actionName Invoke a named function
* @param {...Object} rest Optional arguments passed to action
*
* @module actions.invoke
* @returns {Any}
* @api public
*/
invoke: function (actionName) {
var actions = pickActions(storedActions, arrify(actionName));
var args = toArray(arguments).slice(1);
var result = Object.keys(actions).reduce(function (acc, name) {
acc[name] = actions[name].apply(null, args);
return acc;
}, {});
if (!Array.isArray(actionName)) {
triggerSubscribers(storedSubscribers, result[actionName]);
return result[actionName];
}
triggerSubscribers(storedSubscribers, result);
return result;
},
/**
* Add subscriber. Is triggered when any action is invoked. This returns
* a new actions dispatcher, as action dispatchers are immutable.
*
* ### Example
*
* ```js
* var newActionsWithSubscriber = actions.subscribe(function subscriber () {
* // ...
* });
* ```
*
* @param {Function} fn Subscriber function
*
* @module actions.subscribe
* @returns {actions}
* @api public
*/
subscribe: function (fn) {
return actions(
assign({}, storedActions),
assign([], storedSubscribers.concat(fn))
);
},
/**
* Remove subscriber. This returns a new actions dispatcher, as action
* dispatchers are immutable.
*
* ### Example
*
* ```js
* function subscriber () {
* // ...
* }
* var newActionsWithoutSubscriber = actions.unsubscribe(subscriber);
* ```
*
* @param {Function} fn Subscriber function
*
* @module actions.unsubscribe
* @returns {actions}
* @api public
*/
unsubscribe: function (fn) {
return actions(
assign({}, storedActions),
assign([], storedSubscribers.filter(function (fns) {
return fns !== fn;
}))
);
},
/**
* Register new action. This returns a new actions dispatcher, as action
* dispatchers are immutable.
*
* If not `actionName` is defined, it uses name of function passed in.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myActions = actions.register(function double (i) { return i * 2; });
* var double2 = myActions.invoke('double', 2); //> 4
* var double = myActions.fn.double;
*
* ```
*
* @param {String} [actionName] Name of action function
* @param {Function} fn Subscriber function
*
* @module actions.register
* @returns {actions}
* @api public
*/
register: function (actionName, action) {
var newActionMerger = {};
if (typeof actionName === 'function') {
action = actionName;
actionName = action.name;
}
if (!actionName) {
return methods;
}
newActionMerger[actionName] = action;
return actions(
assign({}, storedActions, newActionMerger),
assign([], storedSubscribers)
);
},
/**
* Remove existing action. This returns a new actions dispatcher, as action
* dispatchers are immutable.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myActions = actions.register(function double (i) { return i * 2; });
* myActions = myActions.remove('double');
* ```
*
* @param {Function} fn Subscriber function
*
* @module actions.remove
* @returns {actions}
* @api public
*/
remove: function (actionName) {
var actionNames = arrify(actionName);
return actions(Object.keys(storedActions).reduce(function (acc, key) {
if (actionNames.indexOf(key) !== -1) return acc;
acc[key] = storedActions[key];
return acc;
}, {}));
},
/**
* Create a composed invoker of two or more actions.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myActions = actions.register(function double (i) { return i * 2; });
* myActions = actions.register(function plus2 (i) { return i + 2; });
* var doublePlus2 = myActions.createComposedInvoker('double', 'plus2');
* ```
*
* @param {Array<String>} functions Functions names of actions to compose
*
* @module actions.createComposedInvoker
* @returns {Function}
* @api public
*/
createComposedInvoker: function (/* fns */) {
var actions = pickActions(storedActions, toArray(arguments));
return function composedInvoker (args) {
var outerContext = this;
var result = updateOrInvoke(actions, args, outerContext);
triggerSubscribers(storedSubscribers, result);
return result;
};
},
actions: storedActions,
subscribers: storedSubscribers,
/**
* Combine one or more action dispatchers. Returns a new action dispatcher
* which has all the actions and subscribers of the action dispatchers passed
* as input.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myAction1 = actions.register(function double (i) { return i * 2; });
* var myAction2 = actions.register(function plus2 (i) { return i + 2; });
* var myActions = myAction1.combine(myAction2);
* doublePlus2 = myActions.createComposedInvoker('double', 'plus2');
* ```
*
* @param {Array<String>} functions Functions names of actions to compose
*
* @module actions.combine
* @returns {Actions}
* @api public
*/
combine: function (/* other stores */) {
var newStoredActions = [storedActions]
.concat(
toArray(arguments)
.map(function (store) {
return store.actions;
})
);
var newSubscribers = storedSubscribers
.concat(
toArray(arguments)
.reduce(function (acc, store) {
return acc.concat(store.subscribers);
}, [])
);
return actions(
assign.apply(null, [{}].concat(newStoredActions)),
assign([], newSubscribers)
);
},
};
/**
* Property getting all functions added as actions. Will trigger subscribers
* but can be used as standalone functions.
*
* ### Example
*
* ```js
* var actions = require('immstruct-actions');
* var myAction1 = actions.register(function double (i) { return i * 2; });
* var double = myActions.fn.double;
* // or with destructuring
* var {double} = myActions.fn;
* ```
*/
Object.defineProperty(methods, 'fn', {
get: function () {
return Object.keys(storedActions).reduce(function (acc, fnName) {
acc[fnName] = methods.invoke.bind(methods, fnName);
return acc;
}, {});
},
enumerable: true,
configurable: false,
});
return methods;
};
module.exports = actions();
function updateOrInvoke (actions, args, context) {
var invoke = function (args) {
return Object.keys(actions).reduceRight(function (result, name) {
return actions[name].call(context, result);
}, args);
};
if (args && args.deref && args.groupedOperations) {
return args.groupedOperations(invoke);
}
return invoke(args);
}
function triggerSubscribers (subscribers, structure) {
subscribers.forEach(function (fn) {
fn(structure);
});
}
function arrify (input) {
if (Array.isArray(input)) {
return input;
}
return [input];
}
function toArray (input) {
return [].slice.apply(input);
}
function pickActions (actions, names) {
return names.reduce(function (acc, name) {
if (typeof name === 'function') {
acc[name.name] = name;
}
if (!actions[name]) return acc;
acc[name] = actions[name];
return acc;
}, {});
}