@vida-global/core 1.4.5 → 1.4.6
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/AGENTS.md +13 -0
- package/lib/logger/README.md +33 -2
- package/lib/logger/index.js +99 -20
- package/lib/logger/serverMiddleware.js +39 -0
- package/lib/server/server.js +8 -62
- package/lib/server/serverController.js +129 -7
- package/package.json +5 -5
- package/test/activeRecord/db/schema.test.js +217 -0
- package/test/activeRecord/utils.test.js +72 -0
- package/test/logger/serverMiddleware.test.js +73 -0
- package/test/server/apiDocsGenerator.test.js +38 -0
- package/test/server/server.test.js +186 -4
- package/test/server/serverController.test.js +237 -5
package/AGENTS.md
ADDED
package/lib/logger/README.md
CHANGED
|
@@ -1,2 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
A
|
|
1
|
+
# Logger
|
|
2
|
+
A simple logger that support multiple scopes and log levels. Set the `LOG_LEVEL` environment variable to determine what log levels are output.
|
|
3
|
+
|
|
4
|
+
## Scopes
|
|
5
|
+
```
|
|
6
|
+
logger.addScope('foo');
|
|
7
|
+
logger.addScope('bar');
|
|
8
|
+
|
|
9
|
+
logger.foo.debug('my log');
|
|
10
|
+
// [2026-04-30 16:16:12.797 -0700][DEBUG][FOO] my log
|
|
11
|
+
|
|
12
|
+
logger.bar.verbose('my log');
|
|
13
|
+
// [2026-04-30 16:16:12.797 -0700][VERBOSE][BAR] my log
|
|
14
|
+
|
|
15
|
+
logger.bar.info('my log');
|
|
16
|
+
// [2026-04-30 16:16:12.797 -0700][INFO][BAR] my log
|
|
17
|
+
|
|
18
|
+
logger.bar.warn('my log');
|
|
19
|
+
// [2026-04-30 16:16:12.797 -0700][WARN][BAR] my log
|
|
20
|
+
|
|
21
|
+
logger.bar.error('my log');
|
|
22
|
+
// [2026-04-30 16:16:12.797 -0700][ERROR][BAR] my log
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## IDs
|
|
26
|
+
```
|
|
27
|
+
logger.addScope('http');
|
|
28
|
+
const requestId = 123;
|
|
29
|
+
const requestLogger = logger.http.createChild(requestId);
|
|
30
|
+
|
|
31
|
+
requestLogger.debug('my log');
|
|
32
|
+
// [2026-04-30 16:16:12.797 -0700][DEBUG][HTTP][123] my log
|
|
33
|
+
``
|
package/lib/logger/index.js
CHANGED
|
@@ -1,25 +1,104 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
const winston = require('winston');
|
|
2
|
+
const { colorize, combine, printf, timestamp } = winston.format;
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Logger {
|
|
6
|
+
#id;
|
|
7
|
+
#logger;
|
|
8
|
+
#scope;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
constructor(scope, id) {
|
|
12
|
+
this.#id = id;
|
|
13
|
+
this.#scope = scope || 'API';
|
|
14
|
+
this.#logger = winston.createLogger(this.loggerOptions);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
get scope() { return this.#scope; }
|
|
19
|
+
get id() { return this.#id; }
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
get loggerOptions() {
|
|
23
|
+
const format = combine(...this.formatters);
|
|
24
|
+
const level = this.level;
|
|
25
|
+
const transports = this.transports;
|
|
26
|
+
|
|
27
|
+
return { format, level, transports };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
get formatters() {
|
|
32
|
+
const colors = {
|
|
33
|
+
debug: 'magenta',
|
|
34
|
+
verbose: 'green',
|
|
35
|
+
info: 'cyan',
|
|
36
|
+
warn: 'yellow',
|
|
37
|
+
error: 'red',
|
|
38
|
+
}
|
|
39
|
+
const messageFormatter = winston.format(this.messageFormatter.bind(this));
|
|
40
|
+
return [
|
|
41
|
+
timestamp({format: 'YYYY-MM-DD HH:mm:ss.SSS ZZ'}),
|
|
42
|
+
messageFormatter(),
|
|
43
|
+
colorize({all: true, colors}),
|
|
44
|
+
printf(({ message }) => message)
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
messageFormatter(info) {
|
|
50
|
+
const level = info.level.toUpperCase()
|
|
51
|
+
|
|
52
|
+
let prefix = `[${info.timestamp}][${level}][${this.scope.toUpperCase()}]`;
|
|
53
|
+
if (this.id) prefix = `${prefix}[${this.id}]`;
|
|
54
|
+
|
|
55
|
+
info.message = `${prefix} ${info.message}`;
|
|
56
|
+
return info;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
get level() {
|
|
61
|
+
return process.env.LOG_LEVEL || 'info';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
get transports() {
|
|
66
|
+
const transports = [ new winston.transports.Console() ];
|
|
67
|
+
return transports;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
addScope(scope) {
|
|
72
|
+
const existing = this[scope];
|
|
73
|
+
if (existing && !existing instanceof Logger) {
|
|
74
|
+
throw `Property already exists at ${scope}`;
|
|
18
75
|
}
|
|
19
|
-
|
|
76
|
+
|
|
77
|
+
const logger = new Logger(scope, this.id);
|
|
78
|
+
Object.defineProperty(this, scope, { get: () => logger });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
createChild(id) {
|
|
83
|
+
return new Logger(this.scope, id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
debug(msg) { this.#logger.debug(msg) }
|
|
88
|
+
error(msg) { this.#logger.error(msg) }
|
|
89
|
+
info(msg) { this.#logger.info(msg) }
|
|
90
|
+
silly(msg) { this.#logger.silly(msg) }
|
|
91
|
+
verbose(msg) { this.#logger.verbose(msg) }
|
|
92
|
+
warn(msg) { this.#logger.warn(msg) }
|
|
93
|
+
|
|
94
|
+
get _winstonLogger() {
|
|
95
|
+
this.warn('Directly accessing the underlying winston logger should be avoided unless necessary');
|
|
96
|
+
return this.#logger
|
|
97
|
+
}
|
|
20
98
|
}
|
|
21
99
|
|
|
22
|
-
|
|
100
|
+
|
|
101
|
+
const logger = new Logger();
|
|
23
102
|
|
|
24
103
|
|
|
25
|
-
module.exports = { logger };
|
|
104
|
+
module.exports = { logger, Logger };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { logger } = require('./index');
|
|
2
|
+
const expressWinston = require('express-winston');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const msgFormatter = (req, res) => {
|
|
6
|
+
let msg;
|
|
7
|
+
if (req.controller) {
|
|
8
|
+
msg = `${req.controller.constructor.name}#${req.action}`;
|
|
9
|
+
} else {
|
|
10
|
+
msg = res.statusCode >= 500 ? 'request errored' : 'request completed';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const headerPcs = res._header.split("\n").map(h => h.split(/:(.*)/));
|
|
14
|
+
const headers = Object.fromEntries(headerPcs);
|
|
15
|
+
const responseTime = headers['X-Response-Time'];
|
|
16
|
+
|
|
17
|
+
const requestDetails = `req: ${req.method} ${req.url.split('?')[0]} for ${req.ip}`;
|
|
18
|
+
const responseDetails = `res: statusCode=${res.statusCode} responseTime=${responseTime}`;
|
|
19
|
+
|
|
20
|
+
msg = `${msg}\n ${requestDetails}\n ${responseDetails}`;
|
|
21
|
+
if (res.error) {
|
|
22
|
+
msg = `${msg}\n${res.error.stack}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return msg;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
logger.addScope('http');
|
|
30
|
+
const middleware = expressWinston.logger({
|
|
31
|
+
level: 'debug',
|
|
32
|
+
msg: msgFormatter,
|
|
33
|
+
winstonInstance: logger.http._winstonLogger,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
middleware
|
|
39
|
+
}
|
package/lib/server/server.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const { ControllerImporter } = require('./controllerImporter');
|
|
2
2
|
const express = require('express');
|
|
3
|
-
const httpLogger = require('pino-http')
|
|
4
3
|
const { logger } = require('../logger');
|
|
4
|
+
const loggingMiddleware = require('../logger/serverMiddleware');
|
|
5
5
|
const mustacheExpress = require('mustache-express');
|
|
6
|
-
const { pino } = require('pino');
|
|
7
6
|
const responseTime = require('response-time');
|
|
8
7
|
const IoServer = require("socket.io")
|
|
9
8
|
const { Server } = require('http');
|
|
10
9
|
const { SystemController } = require('./systemController');
|
|
10
|
+
const requestID = require( 'express-request-id');
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class VidaServer {
|
|
@@ -31,7 +31,7 @@ class VidaServer {
|
|
|
31
31
|
await this.registerControllers();
|
|
32
32
|
|
|
33
33
|
this.#httpServer.listen(this.#port, this.#host, () => {
|
|
34
|
-
|
|
34
|
+
logger.info(`Server is running on port ${this.#port}`);
|
|
35
35
|
if (callback) callback();
|
|
36
36
|
});
|
|
37
37
|
}
|
|
@@ -39,7 +39,7 @@ class VidaServer {
|
|
|
39
39
|
|
|
40
40
|
get host() { return this.#host; }
|
|
41
41
|
get port() { return this.#port; }
|
|
42
|
-
get logger() { return logger; }
|
|
42
|
+
get logger() { return logger.http; }
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
/***********************************************************************************************
|
|
@@ -72,11 +72,12 @@ class VidaServer {
|
|
|
72
72
|
setupMiddleware() {
|
|
73
73
|
this.use(this.jsonParsingMiddleware);
|
|
74
74
|
this.use(this.octetStreamParsingMiddleware);
|
|
75
|
-
this.use(responseTime())
|
|
75
|
+
this.use(responseTime({suffix: true}))
|
|
76
76
|
this.use(express.static('public'))
|
|
77
77
|
this.use(this.loggingMiddleware);
|
|
78
78
|
this.use('/static', express.static(this.staticFilesDirectory));
|
|
79
79
|
this.use(this.connectionAbortedMiddleware);
|
|
80
|
+
this.use(requestID());
|
|
80
81
|
|
|
81
82
|
this.#expressServer.engine('html', mustacheExpress());
|
|
82
83
|
}
|
|
@@ -105,15 +106,7 @@ class VidaServer {
|
|
|
105
106
|
|
|
106
107
|
|
|
107
108
|
get loggingMiddleware() {
|
|
108
|
-
|
|
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
|
-
})
|
|
109
|
+
return loggingMiddleware.middleware
|
|
117
110
|
}
|
|
118
111
|
|
|
119
112
|
|
|
@@ -135,53 +128,6 @@ class VidaServer {
|
|
|
135
128
|
use() { this.#expressServer.use(...arguments); }
|
|
136
129
|
|
|
137
130
|
|
|
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
131
|
/***********************************************************************************************
|
|
186
132
|
* SETTINGS
|
|
187
133
|
***********************************************************************************************/
|
|
@@ -234,7 +180,7 @@ class VidaServer {
|
|
|
234
180
|
const method = action.method.toLowerCase();
|
|
235
181
|
const requestHandler = this.requestHandler(action.action, controllerCls)
|
|
236
182
|
if (process.env.NODE_ENV != 'test') {
|
|
237
|
-
logger.
|
|
183
|
+
logger.verbose(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
|
|
238
184
|
}
|
|
239
185
|
this['_'+method](action.path, requestHandler);
|
|
240
186
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { logger } = require('../logger');
|
|
2
2
|
const { camelize, singularize } = require('inflection');
|
|
3
3
|
const Errors = require('./errors');
|
|
4
|
+
const nodeUtil = require('util');
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
const AUTH_CALLBACK_NAME = 'authenticateRequest';
|
|
@@ -12,10 +13,13 @@ class VidaServerController {
|
|
|
12
13
|
#beforeCallbacks = [];
|
|
13
14
|
#beforeCallbacksToSkip = [];
|
|
14
15
|
#callbacksSetUp = false;
|
|
16
|
+
#isStreaming = false;
|
|
17
|
+
#logger;
|
|
15
18
|
#params;
|
|
16
19
|
#rendered = false;
|
|
17
20
|
#request;
|
|
18
21
|
#response;
|
|
22
|
+
#streamAbortController;
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
constructor(request, response) {
|
|
@@ -41,16 +45,36 @@ class VidaServerController {
|
|
|
41
45
|
return structuredClone(this.#params);
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
get requestId() { return this._request.id; }
|
|
44
49
|
get requestHeaders() { return structuredClone(this._request.headers || {}); }
|
|
45
50
|
get responseHeaders() { return this._response.headers; };
|
|
46
51
|
get requestBody() { return this._request.body; }
|
|
52
|
+
get requestMethod() { return this._request.method; }
|
|
53
|
+
get requestIp() { return this._request.ip; }
|
|
54
|
+
get url() { return this._request.originalUrl; }
|
|
47
55
|
get contentType() { return this.requestHeaders['content-type']; }
|
|
48
|
-
get
|
|
49
|
-
get rendered() { return this.#rendered }
|
|
50
|
-
markRendered() { this.#rendered = true; }
|
|
56
|
+
get userAgent() { return this.requestHeaders['user-agent']; }
|
|
51
57
|
|
|
52
|
-
get statusCode()
|
|
53
|
-
set statusCode(_status)
|
|
58
|
+
get statusCode() { return this._response.statusCode; }
|
|
59
|
+
set statusCode(_status) { this._response.statusCode = _status; }
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
get logger() {
|
|
63
|
+
if (!this.#logger) {
|
|
64
|
+
this.#logger = logger.http.createChild(this.requestId);
|
|
65
|
+
}
|
|
66
|
+
return this.#logger;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
get bearerToken() {
|
|
71
|
+
const auth = this.requestHeaders.authorization;
|
|
72
|
+
if (!auth) return null;
|
|
73
|
+
|
|
74
|
+
const match = /^\s*bearer\s+(.+)$/i.exec(auth);
|
|
75
|
+
if (!match) return null;
|
|
76
|
+
return match[1].trim();
|
|
77
|
+
}
|
|
54
78
|
|
|
55
79
|
|
|
56
80
|
#processRequestData(key) {
|
|
@@ -148,6 +172,9 @@ class VidaServerController {
|
|
|
148
172
|
* RESPONSE RENDERING
|
|
149
173
|
***********************************************************************************************/
|
|
150
174
|
async render(body, options={}) {
|
|
175
|
+
if (this.rendered) return;
|
|
176
|
+
if (this.#isStreaming) return;
|
|
177
|
+
|
|
151
178
|
if (typeof body == 'string') {
|
|
152
179
|
this._response.send(body);
|
|
153
180
|
} else {
|
|
@@ -159,7 +186,7 @@ class VidaServerController {
|
|
|
159
186
|
body = this.formatJSONBody(body, errors, options);
|
|
160
187
|
this._response.json(body);
|
|
161
188
|
}
|
|
162
|
-
this
|
|
189
|
+
this.markRendered();
|
|
163
190
|
}
|
|
164
191
|
|
|
165
192
|
|
|
@@ -196,6 +223,8 @@ class VidaServerController {
|
|
|
196
223
|
if (body === undefined) return null;
|
|
197
224
|
if (!body || typeof body != 'object') return body;
|
|
198
225
|
|
|
226
|
+
if (nodeUtil.types.isProxy(body)) body = {...body};
|
|
227
|
+
|
|
199
228
|
body = structuredClone(body);
|
|
200
229
|
|
|
201
230
|
if (body.toApiResponse) {
|
|
@@ -324,6 +353,77 @@ class VidaServerController {
|
|
|
324
353
|
}
|
|
325
354
|
|
|
326
355
|
|
|
356
|
+
setHeader(header, value) {
|
|
357
|
+
this.#response.setHeader(header, value);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
flushHeaders() {
|
|
362
|
+
this.#response.flushHeaders();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
get rendered() { return this.#rendered; }
|
|
367
|
+
markRendered() { this.#rendered = true; }
|
|
368
|
+
get writableEnded() { return this.#response.writableEnded; }
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
/***********************************************************************************************
|
|
372
|
+
* STREAM
|
|
373
|
+
***********************************************************************************************/
|
|
374
|
+
async streamSseResponse(handler, errorHandler, abortController=null) {
|
|
375
|
+
if (this.#isStreaming) throw new StreamInProgressError();
|
|
376
|
+
this.#isStreaming = true;
|
|
377
|
+
|
|
378
|
+
let closeHandler;
|
|
379
|
+
if (abortController) {
|
|
380
|
+
this.#streamAbortController = abortController;
|
|
381
|
+
closeHandler = () => abortController.abort();
|
|
382
|
+
this.#request.on('close', closeHandler);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.#setSseHeaders();
|
|
386
|
+
this.markRendered();
|
|
387
|
+
try {
|
|
388
|
+
await handler.bind(this)();
|
|
389
|
+
} catch(err) {
|
|
390
|
+
await errorHandler(err);
|
|
391
|
+
throw err;
|
|
392
|
+
} finally {
|
|
393
|
+
this.writeStreamEvent(null, '[DONE]');
|
|
394
|
+
this.#response.end();
|
|
395
|
+
|
|
396
|
+
this.#isStreaming = false;
|
|
397
|
+
this.#streamAbortController = null;
|
|
398
|
+
|
|
399
|
+
if (closeHandler) this.#request.off('close', closeHandler);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
#setSseHeaders() {
|
|
405
|
+
this.statusCode = 200;
|
|
406
|
+
this.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
407
|
+
this.setHeader('Cache-Control', 'no-cache');
|
|
408
|
+
this.setHeader('Connection', 'keep-alive');
|
|
409
|
+
this.setHeader('X-Accel-Buffering', 'no');
|
|
410
|
+
this.flushHeaders();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
writeStreamEvent(evt, data) {
|
|
415
|
+
if (!this.#isStreaming) throw new NoActiveStreamError();
|
|
416
|
+
if (this.#streamAbortController?.signal?.aborted || this.writableEnded) return;
|
|
417
|
+
|
|
418
|
+
if (evt) {
|
|
419
|
+
this.#response.write(`event: ${evt}\n`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const msg = `data: ${data}\n\n`;
|
|
423
|
+
this.#response.write(msg);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
327
427
|
/***********************************************************************************************
|
|
328
428
|
* HELPERS
|
|
329
429
|
***********************************************************************************************/
|
|
@@ -531,6 +631,12 @@ class VidaServerController {
|
|
|
531
631
|
}
|
|
532
632
|
|
|
533
633
|
|
|
634
|
+
validateIsBoolean(value, bool) {
|
|
635
|
+
if (typeof value == 'boolean' && value === bool) return;
|
|
636
|
+
return `must be ${bool}`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
|
|
534
640
|
async validateFunction(value, fnc) {
|
|
535
641
|
const error = await fnc.call(this, value);
|
|
536
642
|
if (error) return error;
|
|
@@ -670,15 +776,31 @@ class VidaServerController {
|
|
|
670
776
|
static authenticationDocumentation(actionName) {
|
|
671
777
|
return {apiKeyAuth: []};
|
|
672
778
|
}
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
/***********************************************************************************************
|
|
782
|
+
* SECURITY
|
|
783
|
+
***********************************************************************************************/
|
|
784
|
+
secureCompare(a, b) {
|
|
785
|
+
const aBuf = Buffer.from(a || '', 'utf8');
|
|
786
|
+
const bBuf = Buffer.from(b || '', 'utf8');
|
|
787
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
788
|
+
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
789
|
+
}
|
|
673
790
|
}
|
|
674
791
|
|
|
675
792
|
|
|
793
|
+
class StreamInProgressError extends Error {}
|
|
794
|
+
class NoActiveStreamError extends Error {}
|
|
795
|
+
|
|
676
796
|
const errorClasses = Object.values(Errors).filter(cls => cls == Errors.ServerError || cls.prototype instanceof Errors.AbstractServerError);
|
|
677
797
|
VidaServerController.prototype.Errors = Object.fromEntries(errorClasses.map(cls => {
|
|
678
798
|
return [cls.name.replace('Error', ''), cls]
|
|
679
799
|
}));
|
|
680
800
|
|
|
681
801
|
|
|
682
|
-
module.exports = {
|
|
802
|
+
module.exports = {
|
|
803
|
+
NoActiveStreamError,
|
|
804
|
+
StreamInProgressError,
|
|
683
805
|
VidaServerController
|
|
684
806
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vida-global/core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"description": "Core libraries for supporting Vida development",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "ISC",
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"commander": "^13.1.0",
|
|
22
22
|
"express": "^4.21.2",
|
|
23
|
+
"express-request-id": "1.4.1",
|
|
24
|
+
"express-winston": "^4.0.0",
|
|
23
25
|
"mustache-express": "^1.2.8",
|
|
24
26
|
"pg": "^8.16.3",
|
|
25
|
-
"pino": "^9.6.0",
|
|
26
|
-
"pino-http": "^10.4.0",
|
|
27
|
-
"pino-pretty": "^13.0.0",
|
|
28
27
|
"redis": "^5.0.0",
|
|
29
28
|
"response-time": "^2.3.3",
|
|
30
29
|
"sequelize": "^6.37.7",
|
|
31
30
|
"sequelize-cli": "^6.6.3",
|
|
32
31
|
"socket.io": "^4.4.0",
|
|
33
|
-
"@vida-global/release": "^1.0.0"
|
|
32
|
+
"@vida-global/release": "^1.0.0",
|
|
33
|
+
"winston": "^3.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"jest": "^29.0.0",
|