forked from cliftonc/calipso
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Event.js
320 lines (253 loc) · 8.94 KB
/
Event.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
/*!
* Calipso Module Event Library
* Copyright(c) 2011 Clifton Cunningham
* MIT Licensed
*
* This library provides an event emitter for modules that is created on each request,
* to provide the ability for module dependencies to be managed, as well as enable modules
* to ensure that they run after all other modules have emitted certain events (e.g. menu rendering).
*
* This library additionally introduces the Calipso Hook.io Wrapper, allowing certain calipso events to be
* broadcast to a hook.io cloud.
*
*/
/**
* Includes
*/
var rootpath = process.cwd() + '/',
path = require('path'),
sys = require('sys'),
util = require('util'),
events = require('events'),
calipso = require(path.join(rootpath, 'lib/calipso')),
CalipsoHook = require(path.join(rootpath, 'lib/Hook')).CalipsoHook;
exports = module.exports = {
CalipsoEventEmitter: CalipsoEventEmitter,
RequestEventListener: RequestEventListener,
addModuleEventListener: addModuleEventListener,
// Module & Routing Event constants
ROUTE_START: 'route_s',
ROUTE_FINISH: 'route_f',
INIT_START: 'init_s',
INIT_FINISH: 'init_f'
};
/**
* Calipso event emitter, object that enables calipso to emit events.
* Events are always triggered at server scope, and cannot be used to
* Execute functions in request scope
*/
function CalipsoEventEmitter(options) {
var self = this;
// Initialise options
var options = options || {};
// Create a hook.io emitter
var hookOptions = options.hook || {
name: 'calipso-hook',
port: 9001,
debug: false
};
this.hook = new CalipsoHook(hookOptions);
this.hook.start();
// Create an emitter to drive events
this.emitter = new events.EventEmitter();
this.emitter.setMaxListeners(500);
// Holder for events, enable debugging of module events
this.events = {};
// Wrapper for event emitter, enable turn on / off
this.addEvent = function(event, options) {
var options = options || {enabled:true};
this.events[event] = options;
// Enable tracking of attached listeners for debugging purposes
this.events[event].preListeners = {'#':0};
this.events[event].postListeners = {'#':0};
this.events[event].customListeners = {'#':0};
}
// Pre and post event prefixes
var pre_prefix = 'PRE_',post_prefix='POST_';
// Register a pre listener
this.pre = function(event,listener,fn) {
self.emitter.on(pre_prefix + event,fn);
this.events[event].preListeners[listener] = this.events[event].preListeners[listener] || [];
this.events[event].preListeners[listener].push(fn.name);
this.events[event].preListeners['#'] += 1;
}
// Register a post listener
this.post = function(event,listener,fn) {
self.emitter.on(post_prefix + event,fn);
this.events[event].postListeners[listener] = this.events[event].postListeners[listener] || [];
this.events[event].postListeners[listener].push(fn.name);
this.events[event].postListeners['#'] += 1;
}
// Register a custom event listener
this.custom = function(event,key,listener,fn) {
self.emitter.on(key + "_" + event,fn);
this.events[event].customListeners[listener] = this.events[event].customListeners[listener] || [];
this.events[event].customListeners[listener].push(fn.name);
this.events[event].customListeners['#'] += 1;
}
// Emit a pre event
this.pre_emit = function(event,data,next) {
// Create a callback to track completion of all events (only if next exists)
if(typeof next === "function") {
var cb = createCallback(this.events[event].preListeners['#'],data,next);
} else {
var cb = function() {};
}
if(this.events[event] && this.events[event].enabled) {
self.emitter.emit(pre_prefix + event,pre_prefix + event,data,cb);
}
}
// Emit a post event
this.post_emit = function(event,data,next) {
// Create a callback to track completion of all events (only if next exists)
if(typeof next === "function") {
var cb = createCallback(this.events[event].postListeners['#'],data,next);
} else {
var cb = function() {};
}
if(this.events[event] && this.events[event].enabled) {
self.emitter.emit(post_prefix + event,post_prefix + event,data,cb);
}
}
// Emit a custom event
this.custom_emit = function(event,key,data,next) {
// Create a callback to track completion of all events (only if next exists)
if(typeof next === "function") {
var cb = createCallback(this.events[event].customListeners['#'],data,next);
} else {
var cb = function() {};
}
if(this.events[event] && this.events[event].enabled) {
self.emitter.emit(key + "_" + event,key + "_" + event,data,cb);
}
}
// Create a curried callback function for use in the emit code
function createCallback(total,data,callback) {
this.count = 0;
this.total = total;
this.outputStack = [];
if(data)
this.outputStack.push(data);
// No listeners, so callback immediately
if(total === 0) {
callback(data);
return;
}
return function(data) {
this.count += 1;
if(data)
this.outputStack.push(data);
// Merge the outputs from the stack
if(this.count === this.total) {
callback(mergeArray(this.outputStack));
}
}
}
}
/**
* Module event emitter, object that enables modules to emit events.
* This contains both server and request scope event emitters, though clearly
* an instance of an object only emits one or the other depending on
* where it is instantiated.
*/
function ModuleEventEmitter(moduleName) {
var self = this;
this.moduleName = moduleName;
this.setMaxListeners(100);
this.init_start = function(options) {
self.emit(exports.INIT_START,self.moduleName,options);
};
this.init_finish = function(options) {
self.emit(exports.INIT_FINISH,self.moduleName,options);
};
this.route_start = function(options) {
self.emit(exports.ROUTE_START,self.moduleName,options);
};
this.route_finish = function(options) {
self.emit(exports.ROUTE_FINISH,self.moduleName,options);
};
}
/**
* Event listener linked to the module itself
* This is for server events (e.g. init, reload)
* No events here can sit within the request context as
* they will apply to all requests
*/
function addModuleEventListener(module) {
var calipso = require(path.join(rootpath, 'lib/calipso'));
var moduleEventEmitter = module.event = new ModuleEventEmitter(module.name);
// Link events
moduleEventEmitter.on(exports.INIT_START, function(moduleName, options) {
// Check for dependent modules, init them
});
moduleEventEmitter.on(exports.INIT_FINISH, function(moduleName, options) {
// Check for dependent modules, init them
calipso.notifyDependenciesOfInit(moduleName, options);
});
}
/**
* Event listener linked to the request object
* This is the object that will listen to each module event emitter
* and call other modules or perform other defined functions
*/
function RequestEventListener() {
// Register a module, listen to its events
var self = this;
var calipso = require(path.join(rootpath, 'lib/calipso'));
// Local hash of module event emitters, used to track routing status
this.modules = {};
// Register a module
this.registerModule = function(req, res, moduleName) {
// Register event emitter
var moduleEventEmitter = self.modules[moduleName] = new ModuleEventEmitter(moduleName);
// Configure event listener
self.modules[moduleName].routed = false; // Is it done
self.modules[moduleName].check = {}; // Hash of dependent modules to check if initialised
self.req = req; // Request
self.res = res; // Response
// Register depends on parent
if(calipso.modules[moduleName].fn && calipso.modules[moduleName].fn.depends) {
calipso.modules[moduleName].fn.depends.forEach(function(dependentModule) {
self.modules[moduleName].check[dependentModule] = false;
});
}
// Start
moduleEventEmitter.on(exports.ROUTE_START, function(moduleName, options) {
self.modules[moduleName].start = new Date();
});
// Finish
moduleEventEmitter.on(exports.ROUTE_FINISH, function(moduleName, options) {
self.modules[moduleName].finish = new Date();
self.modules[moduleName].duration = self.modules[moduleName].finish - self.modules[moduleName].start;
self.modules[moduleName].routed = true;
// Callback to Calipso to notify dependent objects of route
calipso.notifyDependenciesOfRoute(self.req, self.res, moduleName, self.modules);
});
};
}
/**
* Inherits
*/
sys.inherits(ModuleEventEmitter, events.EventEmitter);
/**
* Helper functions TODO CONSOLIDATE!
*/
function mergeArray(arr,first) {
var output = {};
arr.forEach(function(value,key) {
if(first) {
output = merge(value,output);
} else {
output = merge(output,value);
}
});
return output;
}
function merge(a, b){
if (a && b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
};