@itee/server 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @author [Tristan Valcke]{@link https://github.com/Itee}
3
+ * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause}
4
+ *
5
+ * @file Todo
6
+ *
7
+ * @example Todo
8
+ *
9
+ */
10
+
11
+ import express from 'express'
12
+ import http from 'node:http'
13
+ import https from 'node:https'
14
+ import {
15
+ DefaultLogger,
16
+ TAbstractObject
17
+ } from '@itee/core'
18
+ //todo: import Databases from '@itee/database'
19
+ import {
20
+ isArray,
21
+ isBlankString,
22
+ isDefined,
23
+ isEmptyString,
24
+ isNotArray,
25
+ isNotString,
26
+ isNull,
27
+ isUndefined
28
+ } from '@itee/validators'
29
+ import path from 'node:path'
30
+
31
+ class TBackendManager extends TAbstractObject {
32
+
33
+ constructor( parameters = {} ) {
34
+
35
+ const _parameters = {
36
+ ...{
37
+ logger: DefaultLogger,
38
+ rootPath: __dirname,
39
+ applications: [],
40
+ databases: [],
41
+ servers: []
42
+ },
43
+ ...parameters
44
+ }
45
+
46
+ super( _parameters )
47
+
48
+ this.logger = _parameters.logger
49
+ this.rootPath = _parameters.rootPath
50
+ this.applications = express()
51
+ this.router = express.Router
52
+ this.databases = new Map()
53
+ this.servers = new Map()
54
+ this.connections = []
55
+
56
+ this._initApplications( _parameters.applications )
57
+ this._initDatabases( _parameters.databases )
58
+ this._initServers( _parameters.servers )
59
+
60
+ }
61
+ get applications() {
62
+ return this._applications
63
+ }
64
+ set applications( value ) {
65
+ this._applications = value
66
+ }
67
+ get router() {
68
+ return this._router
69
+ }
70
+
71
+ // Todo remove middleware
72
+ set router( value ) {
73
+ this._router = value
74
+ }
75
+ get databases() {
76
+ return this._databases
77
+ }
78
+ set databases( value ) {
79
+ this._databases = value
80
+ }
81
+ get servers() {
82
+ return this._servers
83
+ }
84
+ set servers( value ) {
85
+ this._servers = value
86
+ }
87
+ get rootPath() {
88
+
89
+ return this._rootPath
90
+
91
+ }
92
+ set rootPath( value ) {
93
+
94
+ if ( isNull( value ) ) { throw new TypeError( 'Root path cannot be null ! Expect a non empty string.' ) }
95
+ if ( isUndefined( value ) ) { throw new TypeError( 'Root path cannot be undefined ! Expect a non empty string.' ) }
96
+ if ( isNotString( value ) ) { throw new TypeError( `Root path cannot be an instance of ${ value.constructor.name } ! Expect a non empty string.` ) }
97
+ if ( isEmptyString( value ) ) { throw new TypeError( 'Root path cannot be empty ! Expect a non empty string.' ) }
98
+ if ( isBlankString( value ) ) { throw new TypeError( 'Root path cannot contain only whitespace ! Expect a non empty string.' ) }
99
+
100
+ this._rootPath = value
101
+
102
+ }
103
+ setApplications( value ) {
104
+
105
+ this.applications = value
106
+ return this
107
+
108
+ }
109
+ addMiddleware( middleware ) {
110
+
111
+ this.applications.use( middleware )
112
+ return this
113
+
114
+ }
115
+ setRouter( value ) {
116
+
117
+ this.router = value
118
+ return this
119
+
120
+ }
121
+ setDatabases( value ) {
122
+
123
+ this.databases = value
124
+ return this
125
+
126
+ }
127
+ addDatabase( databaseName, database ) {
128
+
129
+ this._databases.set( databaseName, database )
130
+ return this
131
+
132
+ }
133
+ setServers( value ) {
134
+
135
+ this.servers = value
136
+ return this
137
+
138
+ }
139
+ setRootPath( value ) {
140
+
141
+ this.rootPath = value
142
+ return this
143
+
144
+ }
145
+
146
+ _initApplications( config ) {
147
+
148
+ if ( config.case_sensitive_routing ) { this.applications.set( 'case sensitive routing', config.case_sensitive_routing ) }
149
+ if ( config.env ) { this.applications.set( 'env', config.env ) }
150
+ if ( config.etag ) { this.applications.set( 'etag', config.etag ) }
151
+ if ( config.jsonp_callback_name ) { this.applications.set( 'jsonp callback name', config.jsonp_callback_name ) }
152
+ if ( config.jsonp_escape ) { this.applications.set( 'json escape', config.jsonp_escape ) }
153
+ if ( config.jsonp_replacer ) { this.applications.set( 'json replacer', config.jsonp_replacer ) }
154
+ if ( config.jsonp_spaces ) { this.applications.set( 'json spaces', config.jsonp_spaces ) }
155
+ if ( config.query_parser ) { this.applications.set( 'query parser', config.query_parser ) }
156
+ if ( config.strict_routing ) { this.applications.set( 'strict routing', config.strict_routing ) }
157
+ if ( config.subdomain_offset ) { this.applications.set( 'subdomain offset', config.subdomain_offset ) }
158
+ if ( config.trust_proxy ) { this.applications.set( 'trust proxy', config.trust_proxy ) }
159
+ if ( config.views ) { this.applications.set( 'views', config.views ) }
160
+ if ( config.view_cache ) { this.applications.set( 'view cache', config.view_cache ) }
161
+ if ( config.view_engine ) { this.applications.set( 'view engine', config.view_engine ) }
162
+ if ( config.x_powered_by ) { this.applications.set( 'x-powered-by', config.x_powered_by ) }
163
+
164
+ this._initMiddlewares( config.middlewares )
165
+ this._initRouters( config.routers )
166
+
167
+ }
168
+
169
+ _initMiddlewares( middlewaresConfig ) {
170
+
171
+ for ( let [ name, config ] of Object.entries( middlewaresConfig ) ) {
172
+
173
+ if ( isNotArray( config ) ) {
174
+ throw new TypeError( `Invalid middlware configuration for ${ name }, expecting an array of arguments to spread to middleware module, got ${ config.constructor.name }` )
175
+ }
176
+
177
+ if ( this._initPackageMiddleware( name, config ) ) {
178
+
179
+ this.logger.log( `Use ${ name } middleware from node_modules` )
180
+
181
+ } else if ( this._initLocalMiddleware( name, config ) ) {
182
+
183
+ this.logger.log( `Use ${ name } middleware from local folder` )
184
+
185
+ } else {
186
+
187
+ this.logger.error( `Unable to register the middleware ${ name } the package and/or local file doesn't seem to exist ! Skip it.` )
188
+
189
+ }
190
+
191
+ }
192
+
193
+ }
194
+
195
+ _initPackageMiddleware( name, config ) {
196
+
197
+ let success = false
198
+
199
+ try {
200
+
201
+ this.applications.use( require( name )( ...config ) )
202
+ success = true
203
+
204
+ } catch ( error ) {
205
+
206
+ if ( !error.code || error.code !== 'MODULE_NOT_FOUND' ) {
207
+
208
+ this.logger.error( `The middleware "${ name }" seems to encounter internal error.` )
209
+ this.logger.error( error )
210
+
211
+ }
212
+
213
+ }
214
+
215
+ return success
216
+
217
+ }
218
+
219
+ _initLocalMiddleware( name, config ) {
220
+
221
+ let success = false
222
+
223
+ try {
224
+
225
+ const localMiddlewaresPath = path.join( this.rootPath, 'middlewares', name )
226
+ this.applications.use( require( localMiddlewaresPath )( ...config ) )
227
+ success = true
228
+
229
+ } catch ( error ) {
230
+
231
+ this.logger.error( error )
232
+
233
+ }
234
+
235
+ return success
236
+
237
+ }
238
+
239
+ _initRouters( routers ) {
240
+
241
+ for ( let [ baseRoute, routerPath ] of Object.entries( routers ) ) {
242
+
243
+ if ( this._initPackageRouter( baseRoute, routerPath ) ) {
244
+
245
+ this.logger.log( `Use ${ routerPath } router from node_modules over base route: ${ baseRoute }` )
246
+
247
+ } else if ( this._initLocalRouter( baseRoute, routerPath ) ) {
248
+
249
+ this.logger.log( `Use ${ routerPath } router from local folder over base route: ${ baseRoute }` )
250
+
251
+ } else {
252
+
253
+ this.logger.error( `Unable to register the router ${ routerPath } the package and/or local file doesn't seem to exist ! Skip it.` )
254
+
255
+ }
256
+
257
+ }
258
+
259
+ }
260
+
261
+ _initPackageRouter( baseRoute, routerPath ) {
262
+
263
+ let success = false
264
+
265
+ try {
266
+
267
+ this.applications.use( baseRoute, require( routerPath ) )
268
+ success = true
269
+
270
+ } catch ( error ) {
271
+
272
+ if ( !error.code || error.code !== 'MODULE_NOT_FOUND' ) {
273
+
274
+ this.logger.error( `The router "${ baseRoute }" seems to encounter internal error.` )
275
+ this.logger.error( error )
276
+
277
+ }
278
+
279
+ }
280
+
281
+ return success
282
+
283
+ }
284
+
285
+ _initLocalRouter( baseRoute, routerPath ) {
286
+
287
+ let success = false
288
+
289
+ try {
290
+
291
+ const localRoutersPath = path.join( this.rootPath, 'routers', routerPath )
292
+ this.applications.use( baseRoute, require( localRoutersPath ) )
293
+ success = true
294
+
295
+ } catch ( error ) {
296
+
297
+ if ( error instanceof TypeError && error.message === 'Found non-callable @@iterator' ) {
298
+
299
+ this.logger.error( `The router "${ baseRoute }" seems to encounter error ! Are you using an object instead an array for router configuration ?` )
300
+
301
+ }
302
+
303
+ this.logger.error( error )
304
+
305
+ }
306
+
307
+ return success
308
+
309
+ }
310
+
311
+ _initDatabases( config ) {
312
+
313
+ for ( let configIndex = 0, numberOfDatabasesConfigs = config.length ; configIndex < numberOfDatabasesConfigs ; configIndex++ ) {
314
+
315
+ const databaseConfig = config[ configIndex ]
316
+ const dbType = databaseConfig.type
317
+ const dbFrom = databaseConfig.from
318
+ const dbName = `${ ( databaseConfig.name ) ? databaseConfig.name : `${ dbType }_${ configIndex }` }`
319
+
320
+ try {
321
+
322
+ let database = null
323
+
324
+ if ( isDefined( dbFrom ) ) {
325
+
326
+ // In case user specify a package where take the database of type...
327
+ const databasePackage = require( dbFrom )
328
+ database = new databasePackage[ dbType ]( {
329
+ ...{
330
+ application: this.applications,
331
+ router: this.router
332
+ },
333
+ ...databaseConfig
334
+ } )
335
+
336
+ } else {
337
+
338
+ // // Else try to use auto registered database
339
+ // database = new Databases[ dbType ]( {
340
+ // ...{
341
+ // application: this.applications,
342
+ // router: this.router
343
+ // },
344
+ // ...databaseConfig
345
+ // } )
346
+
347
+ }
348
+
349
+ // Todo move in start
350
+ database.connect()
351
+
352
+ this.databases.set( dbName, database )
353
+
354
+ } catch ( error ) {
355
+
356
+ this.logger.error( `Unable to create database of type ${ dbType } due to ${ error.name }` )
357
+ this.logger.error( error.message )
358
+ this.logger.error( error.stack )
359
+
360
+ }
361
+
362
+ }
363
+
364
+ }
365
+
366
+ _initServers( config ) {
367
+
368
+ const _config = ( isArray( config ) ) ? config : [ config ]
369
+
370
+ for ( let configId = 0, numberOfConfigs = _config.length ; configId < numberOfConfigs ; configId++ ) {
371
+
372
+ let configElement = _config[ configId ]
373
+ let server = null
374
+
375
+ if ( configElement.type === 'https' ) {
376
+
377
+ const options = {
378
+ pfx: configElement.pfx,
379
+ passphrase: configElement.passphrase
380
+ }
381
+
382
+ server = https.createServer( options, this.applications )
383
+
384
+ } else {
385
+
386
+ server = http.createServer( this.applications )
387
+
388
+ }
389
+
390
+ server.name = configElement.name || `${ ( configElement.name ) ? configElement.name : `Server_${ configId }` }`
391
+ server.maxHeadersCount = configElement.max_headers_count
392
+ server.timeout = configElement.timeout
393
+ server.type = configElement.type
394
+ server.host = configElement.host
395
+ server.port = configElement.port
396
+ server.env = configElement.env
397
+ server.listen( configElement.port, configElement.host, () => {
398
+ this.logger.log( `${ server.name } start listening on ${ server.type }://${ server.host }:${ server.port } at ${ new Date() } under ${ server.env } environment.` )
399
+ } )
400
+ server.on( 'connection', connection => {
401
+ this.connections.push( connection )
402
+ connection.on( 'close', () => {
403
+ this.connections = this.connections.filter( curr => curr !== connection )
404
+ } )
405
+ } )
406
+
407
+ this.servers.set( server.name, server )
408
+
409
+ }
410
+
411
+ }
412
+
413
+ /**
414
+ *
415
+ * @param databaseKey
416
+ * @param eventName
417
+ * @param callback
418
+ */
419
+ databaseOn( databaseKey, eventName, callback ) {} // eslint-disable-line no-unused-vars
420
+
421
+ serverOn( serverName, eventName, callback ) {
422
+
423
+ this.servers[ serverName ].on( eventName, callback )
424
+
425
+ }
426
+
427
+ serversOn( serverKey, eventName, callback ) {
428
+
429
+ //TODO: filter availaible events
430
+ // [ 'request', 'connection', 'close', 'timeout', 'checkContinue', 'connect', 'upgrade', 'clientError' ]
431
+ for ( let serverKey in this.servers ) {
432
+ this.serverOn( serverKey, eventName, callback )
433
+ }
434
+
435
+ }
436
+
437
+ start() {
438
+
439
+ }
440
+
441
+ stop( callback ) {
442
+
443
+ const numberOfServers = this.servers.size
444
+ const numberOfDatabases = this.databases.size
445
+ let shutDownServers = 0
446
+ let closedDatabases = 0
447
+
448
+ if ( allClosed() ) { return }
449
+
450
+ for ( const [ databaseName, database ] of this.databases ) {
451
+
452
+ database.close( () => {
453
+
454
+ closedDatabases++
455
+ this.logger.log( `Connection to ${ databaseName } is closed.` )
456
+
457
+ allClosed()
458
+
459
+ } )
460
+
461
+ }
462
+
463
+ for ( let connection of this.connections ) {
464
+ connection.end()
465
+ }
466
+
467
+ for ( const [ serverName, server ] of this.servers ) {
468
+
469
+ server.close( () => {
470
+
471
+ shutDownServers++
472
+ this.logger.log( `The ${ serverName } listening on ${ server.type }://${ server.host }:${ server.port } is shutted down.` )
473
+
474
+ allClosed()
475
+
476
+ } )
477
+
478
+ }
479
+
480
+ function allClosed() {
481
+
482
+ if ( shutDownServers < numberOfServers ) {
483
+ return false
484
+ }
485
+
486
+ if ( closedDatabases < numberOfDatabases ) {
487
+ return false
488
+ }
489
+
490
+ if ( callback ) { callback() }
491
+
492
+ }
493
+
494
+ }
495
+
496
+ closeServers() {
497
+
498
+ }
499
+
500
+ }
501
+
502
+ export { TBackendManager }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @author [Tristan Valcke]{@link https://github.com/Itee}
3
+ * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause}
4
+ *
5
+ * @file Todo
6
+ *
7
+ * @example Todo
8
+ *
9
+ */
10
+
11
+ export * from './TBackendManager.js'