@vida-global/core 1.4.5 → 2.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.
Files changed (55) hide show
  1. package/AGENTS.md +13 -0
  2. package/README.md +1 -0
  3. package/config/newrelic-config.js +118 -0
  4. package/index.js +9 -5
  5. package/lib/activeRecord/baseRecord.js +49 -3
  6. package/lib/apm/agent.js +22 -0
  7. package/lib/apm/index.js +54 -0
  8. package/lib/apm/utils.js +43 -0
  9. package/lib/http/client.js +16 -2
  10. package/lib/jobQueue/README.md +63 -0
  11. package/lib/jobQueue/abstractJob.js +103 -0
  12. package/lib/jobQueue/abstractJobComponent.js +37 -0
  13. package/lib/jobQueue/index.js +10 -0
  14. package/lib/jobQueue/jobImporter.js +19 -0
  15. package/lib/jobQueue/queue.js +161 -0
  16. package/lib/jobQueue/worker.js +273 -0
  17. package/lib/logger/README.md +33 -2
  18. package/lib/logger/index.js +117 -20
  19. package/lib/logger/serverMiddleware.js +41 -0
  20. package/lib/redis/redisClient.js +32 -24
  21. package/lib/server/controllerImporter.js +9 -47
  22. package/lib/server/server.js +34 -71
  23. package/lib/server/serverController.js +215 -23
  24. package/lib/utils/abstractAutoImporter.js +64 -0
  25. package/package.json +8 -6
  26. package/test/activeRecord/baseRecord.test.js +129 -0
  27. package/test/activeRecord/db/connection.test.js +4 -1
  28. package/test/activeRecord/db/connectionConfiguration.test.js +9 -3
  29. package/test/activeRecord/db/schema.test.js +217 -0
  30. package/test/activeRecord/helpers/baseRecordMocks.js +6 -4
  31. package/test/activeRecord/helpers/connection.js +0 -3
  32. package/test/activeRecord/helpers/connectionConfiguration.js +0 -8
  33. package/test/activeRecord/utils.test.js +72 -0
  34. package/test/apm/agent.test.js +56 -0
  35. package/test/apm/utils.test.js +121 -0
  36. package/test/helpers/env.js +33 -0
  37. package/test/http/client.test.js +3 -3
  38. package/test/jobQueue/abstractJob.test.js +307 -0
  39. package/test/jobQueue/abstractJobComponent.test.js +110 -0
  40. package/test/jobQueue/helpers/abstractJob.js +26 -0
  41. package/test/jobQueue/helpers/apmMock.js +20 -0
  42. package/test/jobQueue/helpers/bullmqMock.js +67 -0
  43. package/test/jobQueue/helpers/env.js +29 -0
  44. package/test/jobQueue/helpers/fixtureJobs/notAJob.js +8 -0
  45. package/test/jobQueue/helpers/fixtureJobs/testJobA.js +14 -0
  46. package/test/jobQueue/helpers/fixtureJobs/testJobB.js +14 -0
  47. package/test/jobQueue/helpers/loggerMock.js +43 -0
  48. package/test/jobQueue/helpers/worker.js +38 -0
  49. package/test/jobQueue/queue.test.js +320 -0
  50. package/test/jobQueue/worker.test.js +526 -0
  51. package/test/logger/index.test.js +61 -0
  52. package/test/logger/serverMiddleware.test.js +72 -0
  53. package/test/server/apiDocsGenerator.test.js +38 -0
  54. package/test/server/server.test.js +186 -4
  55. package/test/server/serverController.test.js +407 -5
@@ -3,40 +3,48 @@ const { logger } = require('../logger');
3
3
  const redis = require('redis');
4
4
 
5
5
 
6
- let redisSingleton;
6
+ const CERT_LOCATION = process.env.REDIS_CERT_LOCATION || `${process.cwd()}/redis_ca.pem`;
7
+ const clients = {};
8
+ logger.addScope('redis');
7
9
 
8
10
 
9
- async function initRedisClientSingleton() {
10
- if (redisSingleton) return;
11
+ function redisClientFactory({ host, port, password, subscriber=false } = {}) {
12
+ host = host || process.env.REDIS_HOST;
13
+ port = parseInt(port || process.env.REDIS_PORT);
14
+ password = password || process.env.REDIS_PASSWORD
15
+ const key = `${host}:${port}:${Boolean(subscriber)}`;
11
16
 
12
- const certLocation = process.env.REDIS_CERT_LOCATION || `${process.cwd()}/redis_ca.pem`;
13
- const cert = fs.readFileSync(certLocation)
17
+ if (clients[key]) return clients[key];
14
18
 
15
- redisSingleton = redis.createClient({
16
- socket: {
17
- host: process.env.REDIS_HOST,
18
- port: parseInt(process.env.REDIS_PORT),
19
- tls: true,
20
- ca: [cert]
21
- },
22
- password: process.env.REDIS_PASSWORD
23
- });
19
+ const client = createClient(host, port, password);
20
+ clients[key] = client;
21
+
22
+ configureClient(client)
23
+ return client;
24
+ }
24
25
 
25
- redisSingleton.on('ready', (err) => {
26
- logger.info('Redis Client Connected');
26
+
27
+ function createClient(host, port, password) {
28
+ const ca = fs.readFileSync(CERT_LOCATION)
29
+ const socket = { host, port, ca, tls: true, };
30
+ return redis.createClient({ socket, password });
31
+ }
32
+
33
+
34
+ function configureClient(client) {
35
+ client.on('ready', (err) => {
36
+ logger.redis.info('Connected');
27
37
  });
28
- redisSingleton .on('error', (err) => {
38
+ client.on('error', (err) => {
29
39
  const errno = err.errno ? ` (${err.errno})` : '';
30
- logger.error(`Redis: ${err.message}${errno}`);
40
+ logger.redis.error(`${err.message}${errno}`);
31
41
  });
32
42
 
33
- await redisSingleton.connect();
34
- }
35
-
36
43
 
37
- async function redisClientFactory() {
38
- await initRedisClientSingleton();
39
- return redisSingleton;
44
+ const connect = client.connect.bind(client);
45
+ client.connect = async function() {
46
+ if (!this.isReady) await connect();
47
+ }
40
48
  }
41
49
 
42
50
 
@@ -1,61 +1,23 @@
1
- const fs = require('fs');
1
+ const { AbstractAutoImporter } = require('../utils/abstractAutoImporter');
2
2
  const { VidaServerController } = require('./serverController');
3
3
 
4
4
 
5
- class ControllerImporter {
6
- #controllerDirectories;
7
- #controllerClasses;
8
-
9
-
10
- constructor(controllerDirectories) {
11
- if (!Array.isArray(controllerDirectories)) {
12
- controllerDirectories = [controllerDirectories];
13
- }
14
- this.#controllerDirectories = controllerDirectories;
15
- }
16
-
17
-
5
+ class ControllerImporter extends AbstractAutoImporter {
18
6
  get controllerClasses() {
19
- if (!this.#controllerClasses) {
20
- this.#importControllerClasses();
21
- }
22
- return this.#controllerClasses;
7
+ return this.imports;
23
8
  }
24
9
 
25
10
 
26
- #importControllerClasses() {
27
- this.#controllerClasses = [];
28
- this.#controllerDirectories.forEach(dir => {
29
- this.#importDirectory(dir, dir);
30
- });
11
+ shouldImport(obj) {
12
+ return obj.prototype instanceof VidaServerController;
31
13
  }
32
14
 
33
15
 
34
- #importDirectory(dir, topLevelDirectory) {
35
- fs.readdirSync(dir).forEach((f => {
36
- const filePath = `${dir}/${f}`;
37
- if (fs.lstatSync(filePath).isDirectory()) {
38
- this.#importDirectory(filePath, topLevelDirectory);
39
- } else if (f.endsWith('.js')) {
40
- this.#importFromFile(filePath, dir, topLevelDirectory);
41
- }
42
- }).bind(this));
16
+ processImport(obj, directoryPrefix) {
17
+ obj.directoryPrefix = directoryPrefix;
18
+ obj.autoLoadHelpers();
19
+ obj.autoLoadDocumentation();
43
20
  }
44
-
45
-
46
- #importFromFile(filePath, dir, topLevelDirectory) {
47
- const exports = Object.values(require(filePath));
48
- exports.forEach(_export => {
49
- if (_export.prototype instanceof VidaServerController) {
50
- const directoryPrefix = dir.replace(topLevelDirectory, '');
51
- _export.directoryPrefix = directoryPrefix;
52
- _export.autoLoadHelpers();
53
- _export.autoLoadDocumentation();
54
- this.#controllerClasses.push(_export);
55
- }
56
- });
57
- }
58
-
59
21
  }
60
22
 
61
23
 
@@ -1,13 +1,17 @@
1
+ const APM = require('../apm');
1
2
  const { ControllerImporter } = require('./controllerImporter');
2
3
  const express = require('express');
3
- const httpLogger = require('pino-http')
4
4
  const { logger } = require('../logger');
5
+ const loggingMiddleware = require('../logger/serverMiddleware');
5
6
  const mustacheExpress = require('mustache-express');
6
- const { pino } = require('pino');
7
7
  const responseTime = require('response-time');
8
8
  const IoServer = require("socket.io")
9
9
  const { Server } = require('http');
10
10
  const { SystemController } = require('./systemController');
11
+ const requestID = require( 'express-request-id');
12
+
13
+
14
+ logger.addScope('server');
11
15
 
12
16
 
13
17
  class VidaServer {
@@ -31,7 +35,7 @@ class VidaServer {
31
35
  await this.registerControllers();
32
36
 
33
37
  this.#httpServer.listen(this.#port, this.#host, () => {
34
- this.logger.info(`Server is running on port ${this.#port}`);
38
+ this.logger.info(`${this.constructor.name} running on port ${this.#port}`);
35
39
  if (callback) callback();
36
40
  });
37
41
  }
@@ -39,7 +43,7 @@ class VidaServer {
39
43
 
40
44
  get host() { return this.#host; }
41
45
  get port() { return this.#port; }
42
- get logger() { return logger; }
46
+ get logger() { return logger.server; }
43
47
 
44
48
 
45
49
  /***********************************************************************************************
@@ -72,11 +76,12 @@ class VidaServer {
72
76
  setupMiddleware() {
73
77
  this.use(this.jsonParsingMiddleware);
74
78
  this.use(this.octetStreamParsingMiddleware);
75
- this.use(responseTime())
79
+ this.use(responseTime({suffix: true}))
76
80
  this.use(express.static('public'))
77
81
  this.use(this.loggingMiddleware);
78
82
  this.use('/static', express.static(this.staticFilesDirectory));
79
83
  this.use(this.connectionAbortedMiddleware);
84
+ this.use(requestID());
80
85
 
81
86
  this.#expressServer.engine('html', mustacheExpress());
82
87
  }
@@ -105,15 +110,7 @@ class VidaServer {
105
110
 
106
111
 
107
112
  get loggingMiddleware() {
108
- return httpLogger({
109
- logger: this.middlewareLogger,
110
- customLogLevel: this.requestLogLevel.bind(this),
111
- customSuccessMessage: this.requestLogMessage.bind(this),
112
- customErrorMessage: this.requestLogMessage.bind(this),
113
- customErrorObject: this.requestLogDetails.bind(this),
114
- customSuccessObject: this.requestLogDetails.bind(this),
115
- wrapSerializers: false,
116
- })
113
+ return loggingMiddleware.middleware(this.logger);
117
114
  }
118
115
 
119
116
 
@@ -123,7 +120,7 @@ class VidaServer {
123
120
 
124
121
 
125
122
  get connectionAbortedMiddleware() {
126
- return async function (err, req, res, next) {
123
+ return async function connectionAborted(err, req, res, next) {
127
124
  if (err && err.code === 'ECONNABORTED') {
128
125
  return res.status(400).end(); // Don't process this error any further to avoid its logging
129
126
  }
@@ -135,53 +132,6 @@ class VidaServer {
135
132
  use() { this.#expressServer.use(...arguments); }
136
133
 
137
134
 
138
- /***********************************************************************************************
139
- * LOGGING
140
- ***********************************************************************************************/
141
- requestLogDetails(req, res, err) {
142
- const details = {
143
- req: `${req.method} ${req.url}`,
144
- res: `statusCode=${res.statusCode}, responseTime=${res.get('X-Response-Time')}`,
145
- };
146
-
147
- if (res.statusCode >= 500 && res.error) {
148
- details.error = this.requestLogErrorDetails(req, res, res.error);
149
- }
150
-
151
- return details;
152
- }
153
-
154
-
155
- requestLogErrorDetails(req, res, err) {
156
- if (typeof err == 'string') return {message: err};
157
- return {
158
- type: err.constructor.name,
159
- message: err.message,
160
- stack: err.stack
161
- }
162
- }
163
-
164
-
165
- requestLogMessage(req, res) {
166
- const prefix = '[VidaServer]';
167
- if (req.controller) {
168
- return `${prefix} ${req.controller.constructor.name}#${req.action}`;
169
- }
170
-
171
- return `${prefix} ${res.status >= 500 ? 'request errored' : 'request completed'}`;
172
- }
173
-
174
-
175
- requestLogLevel(req, res) {
176
- return 'debug';
177
- }
178
-
179
-
180
- get middlewareLogger() {
181
- return logger;
182
- }
183
-
184
-
185
135
  /***********************************************************************************************
186
136
  * SETTINGS
187
137
  ***********************************************************************************************/
@@ -234,7 +184,7 @@ class VidaServer {
234
184
  const method = action.method.toLowerCase();
235
185
  const requestHandler = this.requestHandler(action.action, controllerCls)
236
186
  if (process.env.NODE_ENV != 'test') {
237
- logger.info(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
187
+ this.logger.verbose(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
238
188
  }
239
189
  this['_'+method](action.path, requestHandler);
240
190
  }
@@ -249,24 +199,29 @@ class VidaServer {
249
199
 
250
200
 
251
201
  requestHandler(action, controllerCls) {
252
- return async function(request, response) {
253
- const controllerInstance = this.buildController(controllerCls, request, response);
254
- request.controller = controllerInstance;
255
- request.action = action;
256
- await controllerInstance.performRequest(action);
257
- }.bind(this);
202
+ // Implemented with this object access for APM segment tracking
203
+ const server = this;
204
+ const handler = {
205
+ async requestHandler(request, response) {
206
+ const controllerInstance = server.buildController(controllerCls, request, response);
207
+ request.controller = controllerInstance;
208
+ request.action = action;
209
+ await controllerInstance.performRequest(action);
210
+ }
211
+ }.requestHandler;
212
+ return handler;
258
213
  }
259
214
 
260
215
 
261
216
  buildController(controllerCls, request, response) {
262
- return new controllerCls(request, response);
217
+ return new controllerCls(request, response, this.logger);
263
218
  }
264
219
 
265
220
 
266
221
  get controllerClasses() {
267
222
  if (!this.#controllerClasses) {
268
223
  const controllerImporter = new ControllerImporter(this.controllerDirectories);
269
- this.#controllerClasses = controllerImporter.controllerClasses;
224
+ this.#controllerClasses = controllerImporter.controllerClasses.filter(cls => !cls.name.startsWith('Abstract'));
270
225
  }
271
226
 
272
227
  return this.#controllerClasses;
@@ -290,6 +245,14 @@ class VidaServer {
290
245
  }
291
246
 
292
247
 
248
+ /***********************************************************************************************
249
+ * APM
250
+ ***********************************************************************************************/
251
+ startApmSegment(name, record, handler) {
252
+ APM.startSegment(name, record, handler);
253
+ }
254
+
255
+
293
256
  /***********************************************************************************************
294
257
  * DEPRECATED PASS THROUGH METHODS
295
258
  ***********************************************************************************************/