@vida-global/core 1.4.6 → 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.
- package/README.md +1 -0
- package/config/newrelic-config.js +118 -0
- package/index.js +9 -5
- package/lib/activeRecord/baseRecord.js +49 -3
- package/lib/apm/agent.js +22 -0
- package/lib/apm/index.js +54 -0
- package/lib/apm/utils.js +43 -0
- package/lib/http/client.js +16 -2
- package/lib/jobQueue/README.md +63 -0
- package/lib/jobQueue/abstractJob.js +103 -0
- package/lib/jobQueue/abstractJobComponent.js +37 -0
- package/lib/jobQueue/index.js +10 -0
- package/lib/jobQueue/jobImporter.js +19 -0
- package/lib/jobQueue/queue.js +161 -0
- package/lib/jobQueue/worker.js +273 -0
- package/lib/logger/README.md +1 -1
- package/lib/logger/index.js +33 -15
- package/lib/logger/serverMiddleware.js +14 -12
- package/lib/redis/redisClient.js +32 -24
- package/lib/server/controllerImporter.js +9 -47
- package/lib/server/server.js +30 -13
- package/lib/server/serverController.js +93 -23
- package/lib/utils/abstractAutoImporter.js +64 -0
- package/package.json +4 -2
- package/test/activeRecord/baseRecord.test.js +129 -0
- package/test/activeRecord/db/connection.test.js +4 -1
- package/test/activeRecord/db/connectionConfiguration.test.js +9 -3
- package/test/activeRecord/helpers/baseRecordMocks.js +6 -4
- package/test/activeRecord/helpers/connection.js +0 -3
- package/test/activeRecord/helpers/connectionConfiguration.js +0 -8
- package/test/apm/agent.test.js +56 -0
- package/test/apm/utils.test.js +121 -0
- package/test/helpers/env.js +33 -0
- package/test/http/client.test.js +3 -3
- package/test/jobQueue/abstractJob.test.js +307 -0
- package/test/jobQueue/abstractJobComponent.test.js +110 -0
- package/test/jobQueue/helpers/abstractJob.js +26 -0
- package/test/jobQueue/helpers/apmMock.js +20 -0
- package/test/jobQueue/helpers/bullmqMock.js +67 -0
- package/test/jobQueue/helpers/env.js +29 -0
- package/test/jobQueue/helpers/fixtureJobs/notAJob.js +8 -0
- package/test/jobQueue/helpers/fixtureJobs/testJobA.js +14 -0
- package/test/jobQueue/helpers/fixtureJobs/testJobB.js +14 -0
- package/test/jobQueue/helpers/loggerMock.js +43 -0
- package/test/jobQueue/helpers/worker.js +38 -0
- package/test/jobQueue/queue.test.js +320 -0
- package/test/jobQueue/worker.test.js +526 -0
- package/test/logger/index.test.js +61 -0
- package/test/logger/serverMiddleware.test.js +6 -7
- package/test/server/serverController.test.js +180 -10
|
@@ -1,61 +1,23 @@
|
|
|
1
|
-
const
|
|
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
|
-
|
|
20
|
-
this.#importControllerClasses();
|
|
21
|
-
}
|
|
22
|
-
return this.#controllerClasses;
|
|
7
|
+
return this.imports;
|
|
23
8
|
}
|
|
24
9
|
|
|
25
10
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
package/lib/server/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const APM = require('../apm');
|
|
1
2
|
const { ControllerImporter } = require('./controllerImporter');
|
|
2
3
|
const express = require('express');
|
|
3
4
|
const { logger } = require('../logger');
|
|
@@ -10,6 +11,9 @@ const { SystemController } = require('./systemController');
|
|
|
10
11
|
const requestID = require( 'express-request-id');
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
logger.addScope('server');
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class VidaServer {
|
|
14
18
|
#controllerClasses;
|
|
15
19
|
#_expressServer;
|
|
@@ -31,7 +35,7 @@ class VidaServer {
|
|
|
31
35
|
await this.registerControllers();
|
|
32
36
|
|
|
33
37
|
this.#httpServer.listen(this.#port, this.#host, () => {
|
|
34
|
-
logger.info(
|
|
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
|
/***********************************************************************************************
|
|
@@ -106,7 +110,7 @@ class VidaServer {
|
|
|
106
110
|
|
|
107
111
|
|
|
108
112
|
get loggingMiddleware() {
|
|
109
|
-
|
|
113
|
+
return loggingMiddleware.middleware(this.logger);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
|
|
@@ -116,7 +120,7 @@ class VidaServer {
|
|
|
116
120
|
|
|
117
121
|
|
|
118
122
|
get connectionAbortedMiddleware() {
|
|
119
|
-
return async function (err, req, res, next) {
|
|
123
|
+
return async function connectionAborted(err, req, res, next) {
|
|
120
124
|
if (err && err.code === 'ECONNABORTED') {
|
|
121
125
|
return res.status(400).end(); // Don't process this error any further to avoid its logging
|
|
122
126
|
}
|
|
@@ -180,7 +184,7 @@ class VidaServer {
|
|
|
180
184
|
const method = action.method.toLowerCase();
|
|
181
185
|
const requestHandler = this.requestHandler(action.action, controllerCls)
|
|
182
186
|
if (process.env.NODE_ENV != 'test') {
|
|
183
|
-
logger.verbose(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
|
|
187
|
+
this.logger.verbose(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
|
|
184
188
|
}
|
|
185
189
|
this['_'+method](action.path, requestHandler);
|
|
186
190
|
}
|
|
@@ -195,24 +199,29 @@ class VidaServer {
|
|
|
195
199
|
|
|
196
200
|
|
|
197
201
|
requestHandler(action, controllerCls) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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;
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
|
|
207
216
|
buildController(controllerCls, request, response) {
|
|
208
|
-
return new controllerCls(request, response);
|
|
217
|
+
return new controllerCls(request, response, this.logger);
|
|
209
218
|
}
|
|
210
219
|
|
|
211
220
|
|
|
212
221
|
get controllerClasses() {
|
|
213
222
|
if (!this.#controllerClasses) {
|
|
214
223
|
const controllerImporter = new ControllerImporter(this.controllerDirectories);
|
|
215
|
-
this.#controllerClasses = controllerImporter.controllerClasses;
|
|
224
|
+
this.#controllerClasses = controllerImporter.controllerClasses.filter(cls => !cls.name.startsWith('Abstract'));
|
|
216
225
|
}
|
|
217
226
|
|
|
218
227
|
return this.#controllerClasses;
|
|
@@ -236,6 +245,14 @@ class VidaServer {
|
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
|
|
248
|
+
/***********************************************************************************************
|
|
249
|
+
* APM
|
|
250
|
+
***********************************************************************************************/
|
|
251
|
+
startApmSegment(name, record, handler) {
|
|
252
|
+
APM.startSegment(name, record, handler);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
239
256
|
/***********************************************************************************************
|
|
240
257
|
* DEPRECATED PASS THROUGH METHODS
|
|
241
258
|
***********************************************************************************************/
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
const
|
|
1
|
+
const APM = require('../apm');
|
|
2
2
|
const { camelize, singularize } = require('inflection');
|
|
3
3
|
const Errors = require('./errors');
|
|
4
|
+
const { logger } = require('../logger');
|
|
4
5
|
const nodeUtil = require('util');
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
const AUTH_CALLBACK_NAME = 'authenticateRequest';
|
|
9
|
+
const actionStore = new WeakMap();
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class VidaServerController {
|
|
@@ -22,12 +24,13 @@ class VidaServerController {
|
|
|
22
24
|
#streamAbortController;
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
constructor(request, response) {
|
|
27
|
+
constructor(request, response, serverLogger) {
|
|
26
28
|
if (this.constructor == VidaServerController) {
|
|
27
29
|
throw new Error("ServerControllers must be subclasses of VidaServerController");
|
|
28
30
|
}
|
|
29
31
|
this.#request = request;
|
|
30
32
|
this.#response = response;
|
|
33
|
+
this.#logger = (serverLogger || logger).child(this.requestId);
|
|
31
34
|
|
|
32
35
|
this.#applyCallbacks();
|
|
33
36
|
this.#setupStatusRenderers();
|
|
@@ -59,11 +62,8 @@ class VidaServerController {
|
|
|
59
62
|
set statusCode(_status) { this._response.statusCode = _status; }
|
|
60
63
|
|
|
61
64
|
|
|
62
|
-
get logger() {
|
|
63
|
-
|
|
64
|
-
this.#logger = logger.http.createChild(this.requestId);
|
|
65
|
-
}
|
|
66
|
-
return this.#logger;
|
|
65
|
+
get logger() {
|
|
66
|
+
return this.#logger;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
|
|
@@ -104,6 +104,7 @@ class VidaServerController {
|
|
|
104
104
|
***********************************************************************************************/
|
|
105
105
|
async performRequest(action) {
|
|
106
106
|
try {
|
|
107
|
+
APM.setControllerName(this.constructor.name, action);
|
|
107
108
|
await this.#performRequest(action)
|
|
108
109
|
} catch(err) {
|
|
109
110
|
await this.#handleError(err);
|
|
@@ -112,7 +113,7 @@ class VidaServerController {
|
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
async #performRequest(action) {
|
|
115
|
-
await this.setupRequestState();
|
|
116
|
+
await APM.startSegment('setupRequestState', true, () => this.setupRequestState());
|
|
116
117
|
|
|
117
118
|
if (this.rendered) {
|
|
118
119
|
this.#response.end();
|
|
@@ -122,7 +123,7 @@ class VidaServerController {
|
|
|
122
123
|
const responseBody = await this.#performAction(action);
|
|
123
124
|
|
|
124
125
|
if (!this.rendered) {
|
|
125
|
-
await this.render(responseBody || {});
|
|
126
|
+
await APM.startSegment('render', true, () => this.render(responseBody || {}));
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
this.#response.end();
|
|
@@ -130,13 +131,14 @@ class VidaServerController {
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
async #performAction(action) {
|
|
133
|
-
if (await this.runBeforeCallbacks(action) === false) return false;
|
|
134
|
+
if (await APM.startSegment('beforeCallbacks', true, () => this.runBeforeCallbacks(action)) === false) return false;
|
|
134
135
|
|
|
135
136
|
const parameters = this.constructor.parametersForAction(action);
|
|
136
|
-
if (parameters) await this.validateParameters(parameters);
|
|
137
|
+
if (parameters) await APM.startSegment('validateParameters', true, () => this.validateParameters(parameters));
|
|
137
138
|
|
|
138
|
-
const responseBody = await this[action]();
|
|
139
|
-
|
|
139
|
+
const responseBody = await APM.startSegment(`action:${action}`, true, () => this[action]());
|
|
140
|
+
|
|
141
|
+
if (await APM.startSegment('afterCallbacks', true, () => this.runAfterCallbacks(action)) === false) return false;
|
|
140
142
|
|
|
141
143
|
return responseBody;
|
|
142
144
|
}
|
|
@@ -164,10 +166,17 @@ class VidaServerController {
|
|
|
164
166
|
message = err.message;
|
|
165
167
|
}
|
|
166
168
|
await this.renderServerError(message);
|
|
169
|
+
|
|
170
|
+
this.reportError(err);
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
|
|
175
|
+
reportError(err) {
|
|
176
|
+
APM.reportError(err);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
171
180
|
/***********************************************************************************************
|
|
172
181
|
* RESPONSE RENDERING
|
|
173
182
|
***********************************************************************************************/
|
|
@@ -223,29 +232,29 @@ class VidaServerController {
|
|
|
223
232
|
if (body === undefined) return null;
|
|
224
233
|
if (!body || typeof body != 'object') return body;
|
|
225
234
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
body = structuredClone(body);
|
|
235
|
+
let processedBody;
|
|
229
236
|
|
|
230
237
|
if (body.toApiResponse) {
|
|
231
238
|
const formattedBody = await body.toApiResponse(options);
|
|
232
239
|
return await this.processJSONBody(formattedBody, options);
|
|
233
240
|
|
|
234
241
|
} else if (Array.isArray(body)) {
|
|
242
|
+
const processed = [];
|
|
235
243
|
for (const idx in body) {
|
|
236
|
-
|
|
244
|
+
processed.push(await this.processJSONBody(body[idx], options));
|
|
237
245
|
}
|
|
246
|
+
return processed;
|
|
238
247
|
|
|
239
248
|
} else if (body.constructor == Date) {
|
|
240
249
|
return body.getTime();
|
|
241
250
|
|
|
242
251
|
} else {
|
|
252
|
+
const processed = {};
|
|
243
253
|
for (let [key, val] of Object.entries(body)) {
|
|
244
|
-
|
|
254
|
+
processed[key] = await this.processJSONBody(val, options);
|
|
245
255
|
}
|
|
256
|
+
return processed;
|
|
246
257
|
}
|
|
247
|
-
|
|
248
|
-
return body;
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
|
|
@@ -428,6 +437,7 @@ class VidaServerController {
|
|
|
428
437
|
* HELPERS
|
|
429
438
|
***********************************************************************************************/
|
|
430
439
|
static autoLoadHelpers() {
|
|
440
|
+
this.actions; // make sure that actions are cached before loading helpers
|
|
431
441
|
const { StaticMethods, InstanceMethods, Accessors } = this._autoLoadModule(this.autoLoadHelperPath);
|
|
432
442
|
|
|
433
443
|
if (StaticMethods) Object.assign(this, StaticMethods);
|
|
@@ -460,12 +470,12 @@ class VidaServerController {
|
|
|
460
470
|
|
|
461
471
|
|
|
462
472
|
static get autoLoadHelperPath() {
|
|
463
|
-
return `${process.cwd()}/lib/controllers${this.routePrefix}Controller
|
|
473
|
+
return `${process.cwd()}/lib/controllers${this.routePrefix}Controller`;
|
|
464
474
|
}
|
|
465
475
|
|
|
466
476
|
|
|
467
477
|
static get autoLoadDocumentationPath() {
|
|
468
|
-
return `${process.cwd()}/lib/controllers/_docs${this.routePrefix}Controller
|
|
478
|
+
return `${process.cwd()}/lib/controllers/_docs${this.routePrefix}Controller`;
|
|
469
479
|
}
|
|
470
480
|
|
|
471
481
|
|
|
@@ -556,6 +566,44 @@ class VidaServerController {
|
|
|
556
566
|
}
|
|
557
567
|
|
|
558
568
|
|
|
569
|
+
/***********************************************************************************************
|
|
570
|
+
* REQUEST PARAMETERS
|
|
571
|
+
***********************************************************************************************/
|
|
572
|
+
hasParam(name) {
|
|
573
|
+
return Object.prototype.hasOwnProperty.call(this.params, name);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
coerceBoolean(value, { fallback } = {}) {
|
|
578
|
+
if (typeof value === 'boolean') return value;
|
|
579
|
+
if (typeof value !== 'string') return fallback;
|
|
580
|
+
const normalized = value.trim().toLowerCase();
|
|
581
|
+
if (!normalized) return fallback;
|
|
582
|
+
if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') return true;
|
|
583
|
+
if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') return false;
|
|
584
|
+
return fallback;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
coerceInteger(value, { min, max, fallback, nonNegative } = {}) {
|
|
589
|
+
if (value === null || value === undefined) return fallback;
|
|
590
|
+
if (typeof value === 'string' && value.trim() === '') return fallback;
|
|
591
|
+
const parsed = Number(value);
|
|
592
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
593
|
+
let next = Math.floor(parsed);
|
|
594
|
+
if (nonNegative && next < 0) return fallback;
|
|
595
|
+
if (Number.isFinite(min)) next = Math.max(min, next);
|
|
596
|
+
if (Number.isFinite(max)) next = Math.min(max, next);
|
|
597
|
+
return next;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
coerceObject(value, { fallback = {} } = {}) {
|
|
602
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
|
603
|
+
return fallback;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
|
|
559
607
|
/***********************************************************************************************
|
|
560
608
|
* VALIDATIONS
|
|
561
609
|
***********************************************************************************************/
|
|
@@ -657,10 +705,14 @@ class VidaServerController {
|
|
|
657
705
|
* {getFooBar: '/ban/:accountIdd/foo/:userId/bar'}`
|
|
658
706
|
***********************************************************************************************/
|
|
659
707
|
static get actions() {
|
|
708
|
+
if (actionStore.has(this)) return actionStore.get(this);
|
|
660
709
|
const actionNames = this.actionNames;
|
|
661
|
-
|
|
710
|
+
const actions = actionNames.map((actionName => {
|
|
662
711
|
return this._constructAction(actionName);
|
|
663
712
|
}).bind(this));
|
|
713
|
+
|
|
714
|
+
actionStore.set(this, actions);
|
|
715
|
+
return actions;
|
|
664
716
|
}
|
|
665
717
|
|
|
666
718
|
|
|
@@ -787,6 +839,24 @@ class VidaServerController {
|
|
|
787
839
|
if (aBuf.length !== bBuf.length) return false;
|
|
788
840
|
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
789
841
|
}
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
/***********************************************************************************************
|
|
845
|
+
* APM
|
|
846
|
+
***********************************************************************************************/
|
|
847
|
+
addApmAttribute(key, value) {
|
|
848
|
+
APM.addCustomAttribute(key, value)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
setApmUserId(id) {
|
|
853
|
+
APM.setUserId(id)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
startApmSegment(name, record, handler) {
|
|
858
|
+
return APM.startSegment(name, record, handler);
|
|
859
|
+
}
|
|
790
860
|
}
|
|
791
861
|
|
|
792
862
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AbstractAutoImporter {
|
|
5
|
+
#directories;
|
|
6
|
+
#imports;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
constructor(directories) {
|
|
10
|
+
if (!Array.isArray(directories)) {
|
|
11
|
+
directories = [directories];
|
|
12
|
+
}
|
|
13
|
+
this.#directories = directories;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
get imports() {
|
|
18
|
+
if (!this.#imports) {
|
|
19
|
+
this.#runImport();
|
|
20
|
+
}
|
|
21
|
+
return this.#imports;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#runImport() {
|
|
26
|
+
this.#imports = [];
|
|
27
|
+
this.#directories.forEach(dir => {
|
|
28
|
+
this.#importDirectory(dir, dir);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#importDirectory(dir, topLevelDirectory) {
|
|
34
|
+
fs.readdirSync(dir).forEach((f => {
|
|
35
|
+
const filePath = `${dir}/${f}`;
|
|
36
|
+
if (fs.lstatSync(filePath).isDirectory()) {
|
|
37
|
+
this.#importDirectory(filePath, topLevelDirectory);
|
|
38
|
+
} else if (f.endsWith('.js')) {
|
|
39
|
+
this.#importFromFile(filePath, dir, topLevelDirectory);
|
|
40
|
+
}
|
|
41
|
+
}).bind(this));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#importFromFile(filePath, dir, topLevelDirectory) {
|
|
46
|
+
const exports = Object.values(require(filePath));
|
|
47
|
+
exports.forEach(obj => {
|
|
48
|
+
if (this.shouldImport(obj)) {
|
|
49
|
+
const directoryPrefix = dir.replace(topLevelDirectory, '');
|
|
50
|
+
this.processImport(obj, directoryPrefix);
|
|
51
|
+
this.#imports.push(obj);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
shouldImport(obj) {}
|
|
58
|
+
processImport(obj) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
AbstractAutoImporter
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vida-global/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Core libraries for supporting Vida development",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "ISC",
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
"setupFilesAfterEnv": ["jest-extended/all"]
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"bullmq": "^5.0.0",
|
|
21
22
|
"commander": "^13.1.0",
|
|
22
23
|
"express": "^4.21.2",
|
|
23
24
|
"express-request-id": "1.4.1",
|
|
24
25
|
"express-winston": "^4.0.0",
|
|
25
26
|
"mustache-express": "^1.2.8",
|
|
27
|
+
"newrelic": "^13.0.0",
|
|
26
28
|
"pg": "^8.16.3",
|
|
27
|
-
"redis": "^
|
|
29
|
+
"redis": "^4.0.0",
|
|
28
30
|
"response-time": "^2.3.3",
|
|
29
31
|
"sequelize": "^6.37.7",
|
|
30
32
|
"sequelize-cli": "^6.6.3",
|
|
@@ -126,6 +126,135 @@ describe('BaseRecord', () => {
|
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
describe('BaseRecord.configureVirtualAccessors', () => {
|
|
130
|
+
it ('adds a VIRTUAL column when a _virtualGet method is defined on the subclass', () => {
|
|
131
|
+
const getter = jest.fn();
|
|
132
|
+
class User extends BaseRecord {}
|
|
133
|
+
User.prototype._virtualGetFullName = getter;
|
|
134
|
+
User.initialize();
|
|
135
|
+
|
|
136
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
137
|
+
expect.objectContaining({
|
|
138
|
+
fullName: { type: Sequelize.DataTypes.VIRTUAL, get: getter }
|
|
139
|
+
}),
|
|
140
|
+
expect.anything()
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it ('adds a VIRTUAL column when a _virtualSet method is defined on the subclass', () => {
|
|
145
|
+
const setter = jest.fn();
|
|
146
|
+
class User extends BaseRecord {}
|
|
147
|
+
User.prototype._virtualSetFullName = setter;
|
|
148
|
+
User.initialize();
|
|
149
|
+
|
|
150
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
151
|
+
expect.objectContaining({
|
|
152
|
+
fullName: { type: Sequelize.DataTypes.VIRTUAL, set: setter }
|
|
153
|
+
}),
|
|
154
|
+
expect.anything()
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it ('merges _virtualGet and _virtualSet into the same VIRTUAL column', () => {
|
|
159
|
+
const getter = jest.fn();
|
|
160
|
+
const setter = jest.fn();
|
|
161
|
+
class User extends BaseRecord {}
|
|
162
|
+
User.prototype._virtualGetFullName = getter;
|
|
163
|
+
User.prototype._virtualSetFullName = setter;
|
|
164
|
+
User.initialize();
|
|
165
|
+
|
|
166
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
fullName: { type: Sequelize.DataTypes.VIRTUAL, get: getter, set: setter }
|
|
169
|
+
}),
|
|
170
|
+
expect.anything()
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it ('picks up virtual accessors defined on a parent class', () => {
|
|
175
|
+
const getter = jest.fn();
|
|
176
|
+
class Person extends BaseRecord {}
|
|
177
|
+
Person.prototype._virtualGetDisplayName = getter;
|
|
178
|
+
class Employee extends Person {}
|
|
179
|
+
Employee.initialize();
|
|
180
|
+
|
|
181
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
182
|
+
expect.objectContaining({
|
|
183
|
+
displayName: { type: Sequelize.DataTypes.VIRTUAL, get: getter }
|
|
184
|
+
}),
|
|
185
|
+
expect.anything()
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it ('lets a subclass override a virtual accessor defined on a parent class', () => {
|
|
190
|
+
const parentGetter = jest.fn();
|
|
191
|
+
const childGetter = jest.fn();
|
|
192
|
+
class Person extends BaseRecord {}
|
|
193
|
+
Person.prototype._virtualGetDisplayName = parentGetter;
|
|
194
|
+
class Employee extends Person {}
|
|
195
|
+
Employee.prototype._virtualGetDisplayName = childGetter;
|
|
196
|
+
Employee.initialize();
|
|
197
|
+
|
|
198
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({
|
|
200
|
+
displayName: { type: Sequelize.DataTypes.VIRTUAL, get: childGetter }
|
|
201
|
+
}),
|
|
202
|
+
expect.anything()
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it ('merges a parent _virtualGet with a child _virtualSet on the same property', () => {
|
|
207
|
+
const parentGetter = jest.fn();
|
|
208
|
+
const childSetter = jest.fn();
|
|
209
|
+
class Person extends BaseRecord {}
|
|
210
|
+
Person.prototype._virtualGetDisplayName = parentGetter;
|
|
211
|
+
class Employee extends Person {}
|
|
212
|
+
Employee.prototype._virtualSetDisplayName = childSetter;
|
|
213
|
+
Employee.initialize();
|
|
214
|
+
|
|
215
|
+
expect(Model.init).toHaveBeenCalledWith(
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
displayName: { type: Sequelize.DataTypes.VIRTUAL, get: parentGetter, set: childSetter }
|
|
218
|
+
}),
|
|
219
|
+
expect.anything()
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it ('does not pull in non-virtual methods from any ancestor', () => {
|
|
224
|
+
class Person extends BaseRecord {
|
|
225
|
+
someOtherMethod() {}
|
|
226
|
+
}
|
|
227
|
+
class Employee extends Person {}
|
|
228
|
+
Employee.initialize();
|
|
229
|
+
|
|
230
|
+
const [schemaArg] = Model.init.mock.calls[0];
|
|
231
|
+
expect(schemaArg).not.toHaveProperty('someOtherMethod');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it ('does not walk past Model.prototype when collecting accessors', () => {
|
|
235
|
+
class User extends BaseRecord {}
|
|
236
|
+
Model.prototype._virtualGetShouldBeIgnored = jest.fn();
|
|
237
|
+
User.initialize();
|
|
238
|
+
|
|
239
|
+
const [schemaArg] = Model.init.mock.calls[0];
|
|
240
|
+
expect(schemaArg).not.toHaveProperty('shouldBeIgnored');
|
|
241
|
+
|
|
242
|
+
delete Model.prototype._virtualGetShouldBeIgnored;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it ('leaves existing schema columns untouched while adding the virtual entry', () => {
|
|
246
|
+
const getter = jest.fn();
|
|
247
|
+
class User extends BaseRecord {}
|
|
248
|
+
User.prototype._virtualGetFullName = getter;
|
|
249
|
+
User.initialize();
|
|
250
|
+
|
|
251
|
+
const [schemaArg] = Model.init.mock.calls[0];
|
|
252
|
+
expect(schemaArg[Helpers.colName]).toEqual({ type: Sequelize.DataTypes[Helpers.dataTypeKey2] });
|
|
253
|
+
expect(schemaArg.fullName).toEqual({ type: Sequelize.DataTypes.VIRTUAL, get: getter });
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
|
|
129
258
|
describe('BaseRecord.tableName', () => {
|
|
130
259
|
it ('defaults to the puralized version of the class name', () => {
|
|
131
260
|
class User extends BaseRecord {}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const { Connection } = require('../../../lib/activeRecord/db/connection');
|
|
2
2
|
const { ConnectionConfiguration } = require('../../../lib/activeRecord/db/connectionConfiguration');
|
|
3
3
|
const Helpers = require('../helpers/connection');
|
|
4
|
+
const { snapshotEnv } = require('../../helpers/env');
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
let fetchConfigsSpy;
|
|
8
|
+
let restoreEnv;
|
|
7
9
|
|
|
8
10
|
jest.mock('sequelize', () => {
|
|
9
11
|
const { MockSequelize } = require('../helpers/fixtures');
|
|
@@ -11,6 +13,7 @@ jest.mock('sequelize', () => {
|
|
|
11
13
|
});
|
|
12
14
|
|
|
13
15
|
beforeEach(() => {
|
|
16
|
+
restoreEnv = snapshotEnv(['NODE_ENV']);
|
|
14
17
|
fetchConfigsSpy = jest.spyOn(ConnectionConfiguration, '_fetchAllConfigs');
|
|
15
18
|
fetchConfigsSpy.mockImplementation(Helpers.buildConfigs);
|
|
16
19
|
});
|
|
@@ -18,7 +21,7 @@ beforeEach(() => {
|
|
|
18
21
|
afterEach(() => {
|
|
19
22
|
fetchConfigsSpy.mockRestore();
|
|
20
23
|
Connection.clearConnectionsCache();
|
|
21
|
-
|
|
24
|
+
restoreEnv();
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
|