@vida-global/core 1.3.2 → 1.3.3

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.
@@ -22,7 +22,7 @@ jobs:
22
22
  - name: Setup Node
23
23
  uses: actions/setup-node@v4
24
24
  with:
25
- node-version: '20'
25
+ node-version: '24'
26
26
  registry-url: 'https://registry.npmjs.org'
27
27
 
28
28
  - name: Update npm
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Setup Node
17
17
  uses: actions/setup-node@v4
18
18
  with:
19
- node-version: '20'
19
+ node-version: '24'
20
20
  registry-url: 'https://registry.npmjs.org'
21
21
 
22
22
  - name: Install dependencies
@@ -106,40 +106,17 @@ class VidaServer {
106
106
 
107
107
  get loggingMiddleware() {
108
108
  return httpLogger({
109
- logger: this.middlewareLogger,
110
- customLogLevel: (res, err) => {
111
- if (res.statusCode >= 500) return 'error';
112
- if (res.statusCode >= 400) return 'warn';
113
- return 'info';
114
- },
115
- serializers: {
116
- // Format the request log as a string
117
- req: (req) => `Request: ${req.method} ${req.url}`,
118
- // Format the response log as a string
119
- res: (res) => `statusCode=${res.statusCode}, responseTime=${res.get('X-Response-Time')}`,
120
- },
121
- wrapSerializers: false,
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,
122
116
  })
123
117
  }
124
118
 
125
119
 
126
- get middlewareLogger() {
127
- return pino({
128
- level: process.env.HTTP_LOG_LEVEL || 'info',
129
- transport: {
130
- level: process.env.HTTP_LOG_LEVEL || 'info',
131
- target: 'pino-pretty',
132
- options: {
133
- colorize: true,
134
- ignore: 'pid,hostname',
135
- translateTime: 'SYS:standard',
136
- messageFormat: '{msg}',
137
- }
138
- },
139
- });
140
- }
141
-
142
-
143
120
  get staticFilesDirectory() {
144
121
  return `${process.cwd()}/static`;
145
122
  }
@@ -158,6 +135,53 @@ class VidaServer {
158
135
  use() { this.#expressServer.use(...arguments); }
159
136
 
160
137
 
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
+
161
185
  /***********************************************************************************************
162
186
  * SETTINGS
163
187
  ***********************************************************************************************/
@@ -226,8 +250,9 @@ class VidaServer {
226
250
 
227
251
  requestHandler(action, controllerCls) {
228
252
  return async function(request, response) {
229
- if (process.env.NODE_ENV != 'test') this.logger.info(`${controllerCls.name}#${action}`);
230
253
  const controllerInstance = this.buildController(controllerCls, request, response);
254
+ request.controller = controllerInstance;
255
+ request.action = action;
231
256
  await controllerInstance.performRequest(action);
232
257
  }.bind(this);
233
258
  }
@@ -133,9 +133,14 @@ class VidaServerController {
133
133
  await this.renderErrors(err.message, err.fields);
134
134
 
135
135
  } else {
136
- this.statusCode = 500;
137
- await this.render({error: err.message});
138
- if (process.env.NODE_ENV == 'development') console.log(err);
136
+ this._response.error = err;
137
+ let message;
138
+ if (typeof err == 'string') {
139
+ message = err;
140
+ } else {
141
+ message = err.message;
142
+ }
143
+ await this.renderServerErrorResponse(message);
139
144
  }
140
145
  }
141
146
 
@@ -148,7 +153,11 @@ class VidaServerController {
148
153
  this._response.send(body);
149
154
  } else {
150
155
  body = await this.processJSONBody(body, options);
151
- body = this.formatJSONBody(body, options);
156
+
157
+ const errors = body.errors || null;
158
+ delete body.errors;
159
+
160
+ body = this.formatJSONBody(body, errors, options);
152
161
  this._response.json(body);
153
162
  }
154
163
  this.#rendered = true;
@@ -177,27 +186,40 @@ class VidaServerController {
177
186
  }
178
187
 
179
188
 
189
+ async renderUnauthorizedResponse(message=null) {
190
+ return await this.#renderError(401, message);
191
+ }
192
+
193
+
194
+ async renderForbiddenResponse(message=null) {
195
+ return await this.#renderError(403, message);
196
+ }
197
+
198
+
180
199
  async renderNotFoundResponse(message=null) {
181
- this.statusCode = 404;
182
- await this.render({ message });
200
+ return await this.#renderError(404, message);
183
201
  }
184
202
 
185
203
 
186
- async renderUnauthorizedResponse(message=null) {
187
- this.statusCode = 401;
188
- await this.render({ message });
204
+ async renderServerErrorResponse(message=null) {
205
+ return await this.#renderError(500, message);
189
206
  }
190
207
 
191
208
 
192
- async renderForbiddenResponse(message=null) {
193
- this.statusCode = 403;
194
- await this.render({ message });
209
+ async #renderError(statusCode, message) {
210
+ this.statusCode = statusCode;
211
+ const body = {errors: {message: message || null}};
212
+ return await this.render(body);
195
213
  }
196
214
 
197
215
 
198
- formatJSONBody(body, options) {
216
+ formatJSONBody(body, errors, options) {
199
217
  if (options.standardize === false) return body;
200
- return {data: body, status: this.statusText};
218
+
219
+ const response = {data: body, status: this.statusText};
220
+ if (errors) response.errors = errors;
221
+
222
+ return response;
201
223
  }
202
224
 
203
225
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vida-global/core",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Core libraries for supporting Vida development",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -33,7 +33,7 @@
33
33
  "@vida-global/release": "^1.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "jest": "^30.0.0",
36
+ "jest": "^29.0.0",
37
37
  "jest-express": "^1.12.0",
38
38
  "jest-extended": "^7.0.0",
39
39
  "@vida-global/test-helpers": "^1.0.1"
@@ -8,7 +8,7 @@ const { VidaServer } = require('../../lib/server');
8
8
 
9
9
 
10
10
  jest.mock('express', () => {
11
- const express = require('jest-express');
11
+ const express = require('jest-express');
12
12
  const { Response } = require('jest-express/lib/response');
13
13
 
14
14
  // missing functions in mock express
@@ -8,7 +8,7 @@ const TestHelpers = require('@vida-global/test-helpers');
8
8
  const { VidaServerController } = require('../../lib/server');
9
9
 
10
10
  describe('VidaServerController', () => {
11
- describe('VidaServerController.actionNames', () => {
11
+ describe('.actionNames', () => {
12
12
  it ('returns actions based on method names', () => {
13
13
  const names = FooController.actionNames;
14
14
  const expected = ['postIndex',
@@ -23,7 +23,7 @@ describe('VidaServerController', () => {
23
23
  });
24
24
 
25
25
 
26
- describe('VidaServerController.autoLoadHelpers', () => {
26
+ describe('.autoLoadHelpers', () => {
27
27
  class AutoLoadTestController extends VidaServerController {
28
28
  static get autoLoadHelperPath() {
29
29
  return `${process.cwd()}/test/server/helpers/autoload/${this.name}.js`;
@@ -60,7 +60,7 @@ describe('VidaServerController', () => {
60
60
  });
61
61
 
62
62
 
63
- describe('VidaServerController._constructAction', () => {
63
+ describe('._constructAction', () => {
64
64
  class UsersController extends VidaServerController {
65
65
  static get routes() {
66
66
  return {getBazBan: '/baz/:id/ban'};
@@ -123,7 +123,7 @@ describe('VidaServerController', () => {
123
123
  });
124
124
 
125
125
 
126
- describe('VidaServerController.validateParameters', () => {
126
+ describe('.validateParameters', () => {
127
127
  const paramName1 = TestHelpers.Faker.Text.randomString();
128
128
  const paramName2 = TestHelpers.Faker.Text.randomString();
129
129
  const value1 = TestHelpers.Faker.Text.randomString();
@@ -217,7 +217,7 @@ describe('VidaServerController', () => {
217
217
  });
218
218
 
219
219
 
220
- describe('VidaServerController.validatePresence', () => {
220
+ describe('.validatePresence', () => {
221
221
  const controller = new FooController({}, {});
222
222
  it ('returns undefined when there is a value', () => {
223
223
  const result = controller.validatePresence(TestHelpers.Faker.Text.randomString());
@@ -231,7 +231,7 @@ describe('VidaServerController', () => {
231
231
  });
232
232
 
233
233
 
234
- describe('VidaServerController.validateIsInteger', () => {
234
+ describe('.validateIsInteger', () => {
235
235
  const controller = new FooController({}, {});
236
236
  it ('returns undefined when there is an integer', () => {
237
237
  const val = Math.ceil(Math.random() * 100);
@@ -316,7 +316,7 @@ describe('VidaServerController', () => {
316
316
  });
317
317
 
318
318
 
319
- describe('VidaServerController.validateIsString', () => {
319
+ describe('.validateIsString', () => {
320
320
  const controller = new FooController({}, {});
321
321
  it ('returns undefined when there is an string', () => {
322
322
  const result = controller.validateIsString('')
@@ -396,7 +396,7 @@ describe('VidaServerController', () => {
396
396
  });
397
397
 
398
398
 
399
- describe('VidaServerController.validateIsDateTime', () => {
399
+ describe('.validateIsDateTime', () => {
400
400
  const controller = new FooController({}, {});
401
401
  it ('returns undefined when passed a string of format YYYY-MM-DD', () => {
402
402
  const result = controller.validateIsDateTime('2025-12-17');
@@ -435,7 +435,7 @@ describe('VidaServerController', () => {
435
435
  });
436
436
 
437
437
 
438
- describe('VidaServerController.validateIsEnum', () => {
438
+ describe('.validateIsEnum', () => {
439
439
  const controller = new FooController({}, {});
440
440
  const enums = [Math.random(), Math.random(), Math.random()];
441
441
  const error = TestHelpers.Faker.Text.randomString();
@@ -452,7 +452,7 @@ describe('VidaServerController', () => {
452
452
  });
453
453
 
454
454
 
455
- describe('VidaServerController.validateFunction', () => {
455
+ describe('.validateFunction', () => {
456
456
  const controller = new FooController({}, {});
457
457
  const correctValue = Math.random();
458
458
  const error = TestHelpers.Faker.Text.randomString();
@@ -470,7 +470,7 @@ describe('VidaServerController', () => {
470
470
  });
471
471
 
472
472
 
473
- describe('VidaServerController#performRequest', () => {
473
+ describe('#performRequest', () => {
474
474
  const errorHandlers = [
475
475
  ['renderUnauthorizedResponse', 'AuthorizationError'],
476
476
  ['renderForbiddenResponse', 'ForbiddenError'],
@@ -505,7 +505,7 @@ describe('VidaServerController', () => {
505
505
  await controller.performRequest('testAction');
506
506
 
507
507
  expect(controller.statusCode).toEqual(500);
508
- expect(controller.render).toHaveBeenCalledWith({error: message});
508
+ expect(controller.render).toHaveBeenCalledWith({errors: { message }});
509
509
  });
510
510
  });
511
511
 
@@ -520,7 +520,7 @@ describe('VidaServerController', () => {
520
520
  });
521
521
 
522
522
 
523
- describe('VidaServerController#renderErrors', () => {
523
+ describe('#renderErrors', () => {
524
524
  it ('returns a 400 response', async () => {
525
525
  await controller.renderErrors();
526
526
  expect(response.statusCode).toBe(400);
@@ -577,7 +577,7 @@ describe('VidaServerController', () => {
577
577
  });
578
578
 
579
579
 
580
- describe('VidaServerController#renderUnauthorizedResponse', () => {
580
+ describe('#renderUnauthorizedResponse', () => {
581
581
  it ('returns a 401 response', async () => {
582
582
  await controller.renderUnauthorizedResponse();
583
583
  expect(response.statusCode).toBe(401);
@@ -586,19 +586,19 @@ describe('VidaServerController', () => {
586
586
  it ('includes a blank message by default', async () => {
587
587
  await controller.renderUnauthorizedResponse();
588
588
  expect(controller.render).toHaveBeenCalledTimes(1);
589
- expect(controller.render).toHaveBeenCalledWith({message: null});
589
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: null}});
590
590
  })
591
591
 
592
592
  it ('includes a provided message', async () => {
593
593
  const msg = TestHelpers.Faker.Text.randomString();
594
594
  await controller.renderUnauthorizedResponse(msg);
595
595
  expect(controller.render).toHaveBeenCalledTimes(1);
596
- expect(controller.render).toHaveBeenCalledWith({message: msg});
596
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: msg}});
597
597
  });
598
598
  });
599
599
 
600
600
 
601
- describe('VidaServerController#renderForbiddenResponse', () => {
601
+ describe('#renderForbiddenResponse', () => {
602
602
  it ('returns a 403 response', async () => {
603
603
  await controller.renderForbiddenResponse();
604
604
  expect(response.statusCode).toBe(403);
@@ -607,19 +607,19 @@ describe('VidaServerController', () => {
607
607
  it ('includes a blank message by default', async () => {
608
608
  await controller.renderForbiddenResponse();
609
609
  expect(controller.render).toHaveBeenCalledTimes(1);
610
- expect(controller.render).toHaveBeenCalledWith({message: null});
610
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: null}});
611
611
  })
612
612
 
613
613
  it ('includes a provided message', async () => {
614
614
  const msg = TestHelpers.Faker.Text.randomString();
615
615
  await controller.renderForbiddenResponse(msg);
616
616
  expect(controller.render).toHaveBeenCalledTimes(1);
617
- expect(controller.render).toHaveBeenCalledWith({message: msg});
617
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: msg}});
618
618
  });
619
619
  });
620
620
 
621
621
 
622
- describe('VidaServerController#renderNotFoundResponse', () => {
622
+ describe('#renderNotFoundResponse', () => {
623
623
  it ('returns a 404 response', async () => {
624
624
  await controller.renderNotFoundResponse();
625
625
  expect(response.statusCode).toBe(404);
@@ -628,14 +628,14 @@ describe('VidaServerController', () => {
628
628
  it ('includes a blank message by default', async () => {
629
629
  await controller.renderNotFoundResponse();
630
630
  expect(controller.render).toHaveBeenCalledTimes(1);
631
- expect(controller.render).toHaveBeenCalledWith({message: null});
631
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: null}});
632
632
  })
633
633
 
634
634
  it ('includes a provided message', async () => {
635
635
  const msg = TestHelpers.Faker.Text.randomString();
636
636
  await controller.renderNotFoundResponse(msg);
637
637
  expect(controller.render).toHaveBeenCalledTimes(1);
638
- expect(controller.render).toHaveBeenCalledWith({message: msg});
638
+ expect(controller.render).toHaveBeenCalledWith({errors: {message: msg}});
639
639
  });
640
640
  });
641
641
  });
@@ -653,7 +653,7 @@ describe('VidaServerController', () => {
653
653
  controller = new FooController(request, response);
654
654
  });
655
655
 
656
- describe('VidaServerController#requestHeaders', () => {
656
+ describe('#requestHeaders', () => {
657
657
  it ('returns a copy of the request headers', () => {
658
658
  expect(controller.requestHeaders).not.toBe(request.headers);
659
659
  expect(JSON.stringify(controller.requestHeaders)).toEqual(JSON.stringify(request.headers));
@@ -661,28 +661,28 @@ describe('VidaServerController', () => {
661
661
  });
662
662
 
663
663
 
664
- describe('VidaServerController#responseHeaders', () => {
664
+ describe('#responseHeaders', () => {
665
665
  it ('returns the request headers', () => {
666
666
  expect(controller.responseHeaders).toBe(response.headers);
667
667
  });
668
668
  });
669
669
 
670
670
 
671
- describe('VidaServerController#contentType', () => {
671
+ describe('#contentType', () => {
672
672
  it ('returns the content-type request header', () => {
673
673
  expect(controller.contentType).toEqual(request.headers['content-type']);
674
674
  });
675
675
  });
676
676
 
677
677
 
678
- describe('VidaServerController#logger', () => {
678
+ describe('#logger', () => {
679
679
  it ('uses the standard logger', () => {
680
680
  expect(controller.logger).toBe(logger);
681
681
  });
682
682
  });
683
683
 
684
684
 
685
- describe('VidaServerController#rendered', () => {
685
+ describe('#rendered', () => {
686
686
  it ('returns false if nothing has rendered', () => {
687
687
  expect(controller.rendered).toBeFalsy();
688
688
  });
@@ -701,7 +701,7 @@ describe('VidaServerController', () => {
701
701
  });
702
702
 
703
703
 
704
- describe('VidaServerController#statusCode', () => {
704
+ describe('#statusCode', () => {
705
705
  it ('returns the response status code', () => {
706
706
  expect(controller.statusCode).toEqual(response.statusCode);
707
707
  });
@@ -715,7 +715,7 @@ describe('VidaServerController', () => {
715
715
  });
716
716
 
717
717
 
718
- describe('ServerController#setupCallbacks', () => {
718
+ describe('#setupCallbacks', () => {
719
719
  it ('is only called once', () => {
720
720
  class CallbacksTestController extends VidaServerController {}
721
721
  CallbacksTestController.prototype.setupCallbacks = jest.fn();