-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
388 lines (349 loc) · 7.71 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
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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
'use strict'
const mongoose = require('mongoose')
const express = require('express')
const _ = require('lodash')
const EventEmitter = require('events').EventEmitter
const TypeLoader = require('./lib/type-loader')
const DefinitionLoader = require('./lib/definition-loader')
const CollectionFactory = require('./collection-factory')
const Authentication = require('./authentication')
const defaultConfig = require('./default-config')
const defaultLogger = require('./console-logger')
const Errors = require('./errors')
const Meta = require('./lib/meta')
const utils = require('./lib/utils')
global.$logger = defaultLogger
/**
* Sevr
*
* Core Sevr library
*
* @class Sevr
*/
class Sevr {
constructor(config) {
this._config = _.mergeWith({}, defaultConfig, config)
this._server = express()
this._plugins = []
this._auth = new Authentication(this._config.secret)
this._events = new EventEmitter()
}
/**
* The Sevr config object
*
* @readonly
*/
get config() {
return Object.assign({}, this._config)
}
/**
* Ttype definitions
*
* @readonly
*/
get types() {
return Object.assign({}, this._types)
}
/**
* Collection definitions
*
* @readonly
*/
get definitions() {
return Object.assign({}, this._definitions)
}
/**
* Collection factory
*
* @readonly
*/
get factory() {
return this._collectionFactory
}
/**
* Loaded collection instances
*
* @readonly
*/
get collections() {
return this._collectionFactory.collections
}
/**
* Database connection
*
* @readonly
*/
get connection() {
return this._db
}
/**
* Express web server
*
* @readonly
*/
get server() {
return this._server
}
/**
* Sevr logger object
*
* @readonly
*/
get logger() {
return Sevr.logger
}
/**
* Authentication instance
*
* @readonly
*/
get authentication() {
return this._auth
}
/**
* Sevr event emitter
*
* @readonly
*/
get events() {
return this._events
}
/**
* Set the logger object used by Sevr
*
* object must implement the following methods:
* - `verbose`
* - `info`
* - `warning`
* - `error`
* - `critical`
*
* @static
* @param {Object} logger
*/
static setLogger(logger) {
['verbose', 'info', 'warning', 'error', 'critical'].forEach((type, i, all) => {
if (!logger.hasOwnProperty(type)) {
throw new Error(`Logger must have all methods: ${all.join(', ')}`)
}
})
Sevr.logger = logger
global.$logger = logger
}
/**
* Attach a plugin
*
* @example
* sevr.attach(PluginClass, {}, 'sevr.plugin')
*
* @param {Function} plugin The plugin class or constructor function
* @param {Object} config Configuration to be passed to the plugin
* @param {String} namespace The plugin's namespace'
* @return {Sevr}
*/
attach(plugin, config, namespace) {
if (typeof plugin !== 'function') {
throw new Error('Plugin must be a function')
}
this._plugins.push({
klass: plugin,
config,
namespace
})
return this
}
/**
* Connect to the MongoDB database
*
* Returns a promise that resolves once the connection is open.
* It also emits a `db-ready` event the connection is open
*
* @return {Promise}
*/
connect() {
const dbConfig = this._config.connection
const connUri = `mongodb://${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`
return new Promise((res, rej) => {
this._db = mongoose.createConnection(connUri, {
user: dbConfig.username,
pass: dbConfig.password
})
this._db.once('error', err => { rej(err) })
this._db.once('open', () => {
this.events.emit('db-ready')
res()
})
})
}
/**
* Start the Sevr service and begin the lifecyle
*
* Creates the collections and types, initializes authentication,
* and executes plugin lifecycle methods.
*
* Emits a `ready` event once the lifecycle is complete
*
* @return {Promise}
*/
start() {
return this.connect()
.then(() => {
// Create the meta collection
// Initialize plugins
Meta.createModel(this._db)
return this._initPlugins()
})
.then(() => {
// Load the types
// Allow plugins to register additional types
this._types = TypeLoader(this.config.types)
return this._pluginsCall('registerTypes', true)
})
.then(() => {
// Load the collections
// Allow plugins to register additional collections
this._definitions = DefinitionLoader(this.config.collections, this.types)
this._collectionFactory = new CollectionFactory(this._definitions, this._db)
return this._pluginsCall('registerCollections', true)
})
.then(() => {
// Intialize authentication
return Meta.getInstance('sevr-auth')
.then(meta => {
this._auth.setMeta(meta)
})
})
.then(() => {
// Plugin.didInitialize lifecycle method
return this._pluginsCall('didInitialize', true)
})
.then(() => {
// Plugin.willRun lifecycle method
return this._pluginsCall('willRun', true)
})
.then(() => {
this._events.emit('ready')
// Plugin.run lifecycle method
return this._pluginsRun()
})
}
/**
* Wait for the connection to be ready
*
* Attaches a callback function to the `ready` event
*
* @param {Function} fn Callback function
* @return {Sevr}
*/
ready(fn) {
this.events.on('ready', fn)
return this
}
/**
* Start the express web server
*
* Returns a promise that resolves once the web server is
* ready to accept connections.
*
* @return {Promise}
*/
startServer() {
const serverConfig = this._config.server
return new Promise((res, rej) => {
this._server.listen(serverConfig.port, serverConfig.host, (err) => {
if (err) return rej(err)
Sevr.logger.info(`Sevr listening on ${serverConfig.host}:${serverConfig.port}`)
res()
})
})
}
/**
* Trigger a reset
*
* Emits the `reset` event, triggers an authentication reset,
* and calls all plugin reset methods
*
* @return {Promise}
*/
reset() {
this.events.emit('reset')
this.authentication.reset()
return this._pluginsCall('reset')
}
/**
* Destroy the collection factory.
* For testing purposes only.
* @private
*/
static _destroyFactory() {
CollectionFactory._destroy()
}
/**
* Initialize the array of plugins
* @return {Promise}
* @private
*/
_initPlugins() {
const promises = []
this._plugins.forEach(plugin => {
promises.push(
Meta.getInstance(plugin.namespace).then(meta => {
plugin.instance = new plugin.klass(this, plugin.config, meta)
})
)
})
return Promise.all(promises)
}
/**
* Execute a particular method for all plugins.
* Methods can be called either concurrently or in sequence.
* @param {any} method
* @return {Promise}
* @private
*/
_pluginsCall(method, seq) {
const methods = []
this._plugins.forEach(plugin => {
if (typeof plugin.instance[method] !== 'function') return
methods.push(
// Bind the plugin method to it's instance, ensuring
// that `this` is available within the plugin class
plugin.instance[method].bind(plugin.instance)
)
})
if (seq) {
// Run each method in sequence
return utils.createPromiseSequence(methods)
} else {
// Run each method concurrently
return Promise.all(methods.map(fn => { return fn() }))
}
}
/**
* Execute the `run` lifecycle method for all plugins
* @return {Sevr}
* @private
*/
_pluginsRun() {
this._plugins.forEach(plugin => {
if (typeof plugin.instance.run !== 'function') return
plugin.instance.run()
})
return this
}
}
/**
* The Sevr logger object
*
* @static
* @memberOf Sevr
*/
Sevr.logger = defaultLogger
/**
* Error classes
*
* @static
* @memberOf Sevr
*/
Sevr.Errors = Errors
module.exports = Sevr