@vida-global/core 1.3.1 → 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.
@@ -16,13 +16,23 @@ jobs:
16
16
  publish:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
- - uses: actions/checkout@v4
20
- - uses: actions/setup-node@v4
19
+ - name: Checkout code
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Setup Node
23
+ uses: actions/setup-node@v4
21
24
  with:
22
25
  node-version: '24'
23
26
  registry-url: 'https://registry.npmjs.org'
24
- - run: npm ci
25
- env:
26
- NODE_AUTH_TOKEN: ${{ secrets.NPM_READ_TOKEN }}
27
- - run: npm test
28
- - run: npm publish
27
+
28
+ - name: Update npm
29
+ run: npm install -g npm@latest
30
+
31
+ - name: Install dependencies
32
+ run: npm ci
33
+
34
+ - name: Run tests
35
+ run: npm test
36
+
37
+ - name: Publish
38
+ run: npm publish
@@ -1,3 +1,6 @@
1
+ # This workflow will run tests using node
2
+
3
+
1
4
  name: npm test
2
5
 
3
6
  on:
@@ -6,7 +9,6 @@ on:
6
9
  jobs:
7
10
  test:
8
11
  runs-on: ubuntu-latest
9
-
10
12
  steps:
11
13
  - name: Checkout code
12
14
  uses: actions/checkout@v4
@@ -14,8 +16,8 @@ jobs:
14
16
  - name: Setup Node
15
17
  uses: actions/setup-node@v4
16
18
  with:
17
- node-version: 20
18
- cache: 'npm'
19
+ node-version: '24'
20
+ registry-url: 'https://registry.npmjs.org'
19
21
 
20
22
  - name: Install dependencies
21
23
  run: npm ci
package/index.js CHANGED
@@ -1,19 +1,14 @@
1
- const { HttpClient, HttpError } = require('./lib/http/client');
2
- const { AuthorizationError,
3
- VidaServer,
4
- VidaServerController } = require('./lib/server');
5
- const { logger } = require('./lib/logger');
6
- const ActiveRecord = require('./lib/activeRecord');
7
- const { redisClientFactory } = require('./lib/redis');
1
+ const httpLibs = require('./lib/http/client');
2
+ const serverLibs = require('./lib/server');
3
+ const { logger } = require('./lib/logger');
4
+ const ActiveRecord = require('./lib/activeRecord');
5
+ const redisLibs = require('./lib/redis');
8
6
 
9
7
 
10
8
  module.exports = {
11
9
  ActiveRecord,
12
- AuthorizationError,
13
- HttpClient,
14
- HttpError,
10
+ ...httpLibs,
15
11
  logger,
16
- redisClientFactory,
17
- VidaServer,
18
- VidaServerController,
12
+ ...redisLibs,
13
+ ...serverLibs
19
14
  };
@@ -1,5 +1,5 @@
1
1
  const { redisClientFactory } = require('./redisClient');
2
2
 
3
3
  module.exports = {
4
- redisClientFactory
4
+ redisClientFactory
5
5
  }
@@ -194,5 +194,5 @@ titleUniqueness(title) {
194
194
  - `{isInteger: true}`, `{isInteger: {gte: 0, lte: 100}}`
195
195
  - `{isDateTime}`
196
196
  - `{isEnum: {enums: [a, b, c]}}`
197
- - `{regex: /foo/}`
197
+ - `{isString: true}`, `{isString: {length: {gte: 10, lte: 100}, regex: /foo/}}
198
198
  - `{function: someFunction}` If the function returns a string, that is considered an error and returned as the validation message
@@ -3,7 +3,7 @@ const { ControllerImporter } = require('./controllerImporter');
3
3
 
4
4
  class ApiDocsGenerator {
5
5
  #controllerDirectories
6
- #controllers;
6
+ #controllerClasses;
7
7
  #docs = {};
8
8
 
9
9
  constructor(server) {
@@ -13,29 +13,60 @@ class ApiDocsGenerator {
13
13
 
14
14
  generateDocs() {
15
15
  console.log("\n\n");
16
- this.controllers.forEach(controller => {
17
- controller.actions.forEach(action => {
18
- this.generateDocsForAction(action);
16
+ this.controllerClasses.forEach(controllerClass => {
17
+ controllerClass.actions.forEach(action => {
18
+ this.generateDocsForAction(action, controllerClass);
19
19
  });
20
20
  });
21
- console.log("\n\n");
21
+ console.log("\n");
22
22
  }
23
23
 
24
24
 
25
- generateDocsForAction(action) {
26
- const endpoint = this.formatEndpoint(action);
25
+ generateDocsForAction(action, controllerClass) {
26
+ const documentationDetails = controllerClass.documentationForAction(action.action);
27
+ if (!documentationDetails?.description) return;
28
+
29
+ const components = this.collectComponents(controllerClass, documentationDetails, action);
30
+
31
+ const endpoint = components.endpoint;
27
32
  this.#docs[endpoint] = this.#docs[endpoint] || {};
28
- const actionDocs = this.#docs[endpoint][action.method.toLowerCase()] = {foo: 1};
29
- console.log(actionDocs);
33
+ const actionDocs = this.#docs[endpoint][action.method.toLowerCase()] = {};
34
+
35
+ }
36
+
37
+
38
+ collectComponents(controllerClass, documentationDetails, action) {
39
+ const components = {...documentationDetails};
40
+ components.endpoint = this.formatEndpoint(action);
41
+ components.method = action.method;
42
+
43
+ const pathParamNames = components.endpoint.match(/{[^}]+}/g).map(p => {
44
+ return p.replace(/[{}]/g, '')
45
+ });
46
+
47
+ const parameters = Object.entries(controllerClass.parametersForAction(action.action));
48
+ const pathParams = parameters.filter(([param, _]) => pathParamNames.includes(param));
49
+ const otherParams = parameters.filter(([param, _]) => !pathParamNames.includes(param));
50
+ console.log(components.endpoint);
51
+ console.log(pathParams);
52
+ console.log(components.method);
53
+ console.log(parameters);
54
+ console.log(components.description);
55
+ console.log(components.summary);
56
+ console.log("\n");
57
+ //const auth = controllerClass.authenticationDocumentation(action.action);
58
+ //const method = action.method;
59
+
60
+ return components;
30
61
  }
31
62
 
32
63
 
33
- get controllers() {
34
- if (!this.#controllers) {
64
+ get controllerClasses() {
65
+ if (!this.#controllerClasses) {
35
66
  const controllerImporter = new ControllerImporter(this.#controllerDirectories);
36
- this.#controllers = controllerImporter.controllers;
67
+ this.#controllerClasses = controllerImporter.controllerClasses;
37
68
  }
38
- return this.#controllers;
69
+ return [...this.#controllerClasses];
39
70
  }
40
71
 
41
72
 
@@ -4,7 +4,7 @@ const { VidaServerController } = require('./serverController');
4
4
 
5
5
  class ControllerImporter {
6
6
  #controllerDirectories;
7
- #controllers;
7
+ #controllerClasses;
8
8
 
9
9
 
10
10
  constructor(controllerDirectories) {
@@ -15,16 +15,16 @@ class ControllerImporter {
15
15
  }
16
16
 
17
17
 
18
- get controllers() {
19
- if (!this.#controllers) {
20
- this.#importControllers();
18
+ get controllerClasses() {
19
+ if (!this.#controllerClasses) {
20
+ this.#importControllerClasses();
21
21
  }
22
- return this.#controllers;
22
+ return this.#controllerClasses;
23
23
  }
24
24
 
25
25
 
26
- #importControllers() {
27
- this.#controllers = [];
26
+ #importControllerClasses() {
27
+ this.#controllerClasses = [];
28
28
  this.#controllerDirectories.forEach(dir => {
29
29
  this.#importDirectory(dir, dir);
30
30
  });
@@ -44,12 +44,14 @@ class ControllerImporter {
44
44
 
45
45
 
46
46
  #importFromFile(filePath, dir, topLevelDirectory) {
47
- const exports = Object.values(require(filePath));
47
+ const exports = Object.values(require(filePath));
48
48
  exports.forEach(_export => {
49
49
  if (_export.prototype instanceof VidaServerController) {
50
50
  const directoryPrefix = dir.replace(topLevelDirectory, '');
51
51
  _export.directoryPrefix = directoryPrefix;
52
- this.#controllers.push(_export);
52
+ _export.autoLoadHelpers();
53
+ _export.autoLoadDocumentation();
54
+ this.#controllerClasses.push(_export);
53
55
  }
54
56
  });
55
57
  }
@@ -1,8 +1,10 @@
1
1
  const { VidaServer } = require('./server');
2
2
  const { VidaServerController } = require('./serverController');
3
3
  const ERRORS = require('./errors');
4
+ const { ApiDocsGenerator } = require('./apiDocsGenerator');
4
5
 
5
6
  module.exports = {
7
+ ApiDocsGenerator,
6
8
  ...ERRORS,
7
9
  VidaServer,
8
10
  VidaServerController,
@@ -1,5 +1,5 @@
1
+ const { ControllerImporter } = require('./controllerImporter');
1
2
  const express = require('express');
2
- const fs = require('fs');
3
3
  const httpLogger = require('pino-http')
4
4
  const { logger } = require('../logger');
5
5
  const mustacheExpress = require('mustache-express');
@@ -8,11 +8,6 @@ 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 { AuthorizationError,
12
- ForbiddenError,
13
- NotFoundError,
14
- ValidationError,
15
- VidaServerController } = require('./serverController');
16
11
 
17
12
 
18
13
  class VidaServer {
@@ -32,11 +27,12 @@ class VidaServer {
32
27
  }
33
28
 
34
29
 
35
- async listen() {
30
+ async listen(callback) {
36
31
  await this.registerControllers();
37
32
 
38
33
  this.#httpServer.listen(this.#port, this.#host, () => {
39
34
  this.logger.info(`Server is running on port ${this.#port}`);
35
+ if (callback) callback();
40
36
  });
41
37
  }
42
38
 
@@ -75,6 +71,7 @@ class VidaServer {
75
71
  ***********************************************************************************************/
76
72
  setupMiddleware() {
77
73
  this.use(this.jsonParsingMiddleware);
74
+ this.use(this.octetStreamParsingMiddleware);
78
75
  this.use(responseTime())
79
76
  this.use(express.static('public'))
80
77
  this.use(this.loggingMiddleware);
@@ -97,39 +94,26 @@ class VidaServer {
97
94
  }
98
95
 
99
96
 
100
- get loggingMiddleware() {
101
- return httpLogger({
102
- logger: this.middlewareLogger,
103
- customLogLevel: (res, err) => {
104
- if (res.statusCode >= 500) return 'error';
105
- if (res.statusCode >= 400) return 'warn';
106
- return 'info';
107
- },
108
- serializers: {
109
- // Format the request log as a string
110
- req: (req) => `Request: ${req.method} ${req.url}`,
111
- // Format the response log as a string
112
- res: (res) => `statusCode=${res.statusCode}, responseTime=${res.get('X-Response-Time')}`,
113
- },
114
- wrapSerializers: false,
115
- })
97
+ get octetStreamParsingMiddleware() {
98
+ return express.raw({ type: 'application/octet-stream', limit: this.octetStreamLimit });
116
99
  }
117
100
 
118
101
 
119
- get middlewareLogger() {
120
- return pino({
121
- level: process.env.HTTP_LOG_LEVEL || 'info',
122
- transport: {
123
- level: process.env.HTTP_LOG_LEVEL || 'info',
124
- target: 'pino-pretty',
125
- options: {
126
- colorize: true,
127
- ignore: 'pid,hostname',
128
- translateTime: 'SYS:standard',
129
- messageFormat: '{msg}',
130
- }
131
- },
132
- });
102
+ get octetStreamLimit() {
103
+ return '128mb';
104
+ }
105
+
106
+
107
+ 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
+ })
133
117
  }
134
118
 
135
119
 
@@ -151,6 +135,53 @@ class VidaServer {
151
135
  use() { this.#expressServer.use(...arguments); }
152
136
 
153
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
+
154
185
  /***********************************************************************************************
155
186
  * SETTINGS
156
187
  ***********************************************************************************************/
@@ -193,7 +224,6 @@ class VidaServer {
193
224
 
194
225
 
195
226
  #registerController(controllerCls) {
196
- controllerCls.autoLoadHelpers();
197
227
  controllerCls.actions.forEach((action => {
198
228
  this.#registerAction(action, controllerCls)
199
229
  }).bind(this));
@@ -215,12 +245,14 @@ class VidaServer {
215
245
  _put() { this.#expressServer.put(...arguments); }
216
246
  _delete() { this.#expressServer.delete(...arguments); }
217
247
  _patch() { this.#expressServer.patch(...arguments); }
248
+ _head() { this.#expressServer.head(...arguments); }
218
249
 
219
250
 
220
251
  requestHandler(action, controllerCls) {
221
252
  return async function(request, response) {
222
- if (process.env.NODE_ENV != 'test') this.logger.info(`${controllerCls.name}#${action}`);
223
253
  const controllerInstance = this.buildController(controllerCls, request, response);
254
+ request.controller = controllerInstance;
255
+ request.action = action;
224
256
  await controllerInstance.performRequest(action);
225
257
  }.bind(this);
226
258
  }
@@ -232,41 +264,15 @@ class VidaServer {
232
264
 
233
265
 
234
266
  get controllerClasses() {
235
- if (this.#controllerClasses) return this.#controllerClasses;
236
- this.#controllerClasses = [];
237
-
238
- this.controllerDirectories.forEach(dir => {
239
- this.#importControllers(dir, dir);
240
- });
267
+ if (!this.#controllerClasses) {
268
+ const controllerImporter = new ControllerImporter(this.controllerDirectories);
269
+ this.#controllerClasses = controllerImporter.controllerClasses;
270
+ }
241
271
 
242
272
  return this.#controllerClasses;
243
273
  }
244
274
 
245
275
 
246
- #importControllers(dir, topLevelDirectory) {
247
- fs.readdirSync(dir).forEach((f => {
248
- const filePath = `${dir}/${f}`;
249
- if (fs.lstatSync(filePath).isDirectory()) {
250
- this.#importControllers(filePath, topLevelDirectory);
251
- } else if (f.endsWith('.js')) {
252
- this.#importControllersFromFile(filePath, dir, topLevelDirectory);
253
- }
254
- }).bind(this));
255
- }
256
-
257
-
258
- #importControllersFromFile(filePath, dir, topLevelDirectory) {
259
- const exports = Object.values(require(filePath));
260
- exports.forEach(_export => {
261
- if (_export.prototype instanceof VidaServerController) {
262
- const directoryPrefix = dir.replace(topLevelDirectory, '');
263
- _export.directoryPrefix = directoryPrefix;
264
- this.#controllerClasses.push(_export);
265
- }
266
- });
267
- }
268
-
269
-
270
276
  get controllerDirectories() {
271
277
  return [`${process.cwd()}/controllers`];
272
278
  }
@@ -291,6 +297,7 @@ class VidaServer {
291
297
  post() { this.handleDeprecatedRoutingCall('post', arguments); }
292
298
  put() { this.handleDeprecatedRoutingCall('put', arguments); }
293
299
  patch() { this.handleDeprecatedRoutingCall('patch', arguments); }
300
+ head() { this.handleDeprecatedRoutingCall('head', arguments); }
294
301
  delete() { this.handleDeprecatedRoutingCall('delete', arguments); }
295
302
 
296
303