@vida-global/core 2.0.1 → 2.0.4

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.
@@ -1,27 +1,16 @@
1
- const APM = require('../apm');
2
- const { camelize, singularize } = require('inflection');
3
- const Errors = require('./errors');
4
- const { logger } = require('../logger');
5
- const nodeUtil = require('util');
6
-
7
-
8
- const AUTH_CALLBACK_NAME = 'authenticateRequest';
9
- const actionStore = new WeakMap();
1
+ const APM = require('../apm');
2
+ const { camelize } = require('inflection');
3
+ const crypto = require('node:crypto');
4
+ const Errors = require('./errors');
5
+ const { logger } = require('../logger');
10
6
 
11
7
 
12
8
  class VidaServerController {
13
- #afterCallbacks = [];
14
- #afterCallbacksToSkip = [];
15
- #beforeCallbacks = [];
16
- #beforeCallbacksToSkip = [];
17
- #callbacksSetUp = false;
18
- #isStreaming = false;
19
9
  #logger;
20
10
  #params;
21
- #rendered = false;
11
+ #rendered = false;
22
12
  #request;
23
13
  #response;
24
- #streamAbortController;
25
14
 
26
15
 
27
16
  constructor(request, response, serverLogger) {
@@ -32,75 +21,18 @@ class VidaServerController {
32
21
  this.#response = response;
33
22
  this.#logger = (serverLogger || logger).child(this.requestId);
34
23
 
35
- this.#applyCallbacks();
36
- this.#setupStatusRenderers();
37
- }
38
-
39
-
40
- get _request() { return this.#request };
41
- get _response() { return this.#response };
42
-
43
- get params() {
44
- if (!this.#params) {
45
- const body = Buffer.isBuffer(this._request.body) ? {} : this.#processRequestData('body');
46
- this.#params = {...this._request.params, ...this._request.query, ...body};
47
- }
48
- return structuredClone(this.#params);
49
- }
50
-
51
- get requestId() { return this._request.id; }
52
- get requestHeaders() { return structuredClone(this._request.headers || {}); }
53
- get responseHeaders() { return this._response.headers; };
54
- get requestBody() { return this._request.body; }
55
- get requestMethod() { return this._request.method; }
56
- get requestIp() { return this._request.ip; }
57
- get url() { return this._request.originalUrl; }
58
- get contentType() { return this.requestHeaders['content-type']; }
59
- get userAgent() { return this.requestHeaders['user-agent']; }
60
-
61
- get statusCode() { return this._response.statusCode; }
62
- set statusCode(_status) { this._response.statusCode = _status; }
63
-
64
-
65
- get logger() {
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
- }
78
-
79
-
80
- #processRequestData(key) {
81
- const data = this._request[key] || {};
82
- const processedData = {};
83
- for (let [paramName, paramValue] of Object.entries(data)) {
84
- processedData[paramName] = this.#processedRequestDataValue(paramValue)
85
- }
86
-
87
- return processedData;
24
+ this._applyCallbacks();
25
+ this._setupStatusRenderers();
88
26
  }
89
27
 
90
28
 
91
- #processedRequestDataValue(value) {
92
- return value;
93
- try {
94
- if (!/^\[|{/.test(value)) return value;
95
- return JSON.parse(value);
96
- } catch(err) {
97
- return value;
98
- }
99
- }
29
+ get _request() { return this.#request }
30
+ get _response() { return this.#response }
31
+ get logger() { return this.#logger }
100
32
 
101
33
 
102
34
  /***********************************************************************************************
103
- * PERFORM ACTION
35
+ * PERFORM REQUEST
104
36
  ***********************************************************************************************/
105
37
  async performRequest(action) {
106
38
  try {
@@ -113,32 +45,30 @@ class VidaServerController {
113
45
 
114
46
 
115
47
  async #performRequest(action) {
116
- await APM.startSegment('setupRequestState', true, () => this.setupRequestState());
117
-
118
- if (this.rendered) {
119
- this.#response.end();
120
- return;
121
- }
48
+ await this.#setupRequestState()
49
+ if (this.rendered) return this.#response.end();
122
50
 
123
51
  const responseBody = await this.#performAction(action);
124
52
 
125
- if (!this.rendered) {
126
- await APM.startSegment('render', true, () => this.render(responseBody || {}));
127
- }
53
+ if (!this.rendered) await this.render(responseBody || {});
128
54
 
129
55
  this.#response.end();
130
56
  }
131
57
 
132
58
 
59
+ async #setupRequestState() {
60
+ return await APM.startSegment('setupRequestState', true, () => this.setupRequestState?.());
61
+ }
62
+
63
+
133
64
  async #performAction(action) {
134
- if (await APM.startSegment('beforeCallbacks', true, () => this.runBeforeCallbacks(action)) === false) return false;
65
+ if (!await this._runCallbacks('before', action)) return false;
135
66
 
136
- const parameters = this.constructor.parametersForAction(action);
137
- if (parameters) await APM.startSegment('validateParameters', true, () => this.validateParameters(parameters));
67
+ await APM.startSegment('validateParameters', true, () => this.validateAction(action));
138
68
 
139
69
  const responseBody = await APM.startSegment(`action:${action}`, true, () => this[action]());
140
70
 
141
- if (await APM.startSegment('afterCallbacks', true, () => this.runAfterCallbacks(action)) === false) return false;
71
+ if (!await this._runCallbacks('after', action)) return false;
142
72
 
143
73
  return responseBody;
144
74
  }
@@ -172,287 +102,41 @@ class VidaServerController {
172
102
  }
173
103
 
174
104
 
175
- reportError(err) {
176
- APM.reportError(err);
177
- }
178
-
179
-
180
- /***********************************************************************************************
181
- * RESPONSE RENDERING
182
- ***********************************************************************************************/
183
- async render(body, options={}) {
184
- if (this.rendered) return;
185
- if (this.#isStreaming) return;
186
-
187
- if (typeof body == 'string') {
188
- this._response.send(body);
189
- } else {
190
- body = await this.processJSONBody(body, options);
191
-
192
- const errors = body.errors || null;
193
- delete body.errors;
194
-
195
- body = this.formatJSONBody(body, errors, options);
196
- this._response.json(body);
197
- }
198
- this.markRendered();
199
- }
200
-
201
-
202
- async renderErrors(message, fields) {
203
- const errors = {message: null, fields: {}};
204
-
205
- if (message && !fields && typeof message == 'object') {
206
- fields = message;
207
- message = null;
208
- }
209
-
210
- if (fields) {
211
- for (const [field, values] of Object.entries(fields)) {
212
- if (!Array.isArray(values)) fields[field] = [values];
213
- }
214
- errors.fields = fields;
215
- }
216
-
217
- await this.renderBadRequest(message, { errors });
218
- }
219
-
220
-
221
- formatJSONBody(body, errors, options) {
222
- if (options.standardize === false) return body;
223
-
224
- const response = {data: body, status: this.statusText};
225
- if (errors) response.errors = errors;
226
-
227
- return response;
228
- }
229
-
230
-
231
- async processJSONBody(body, options) {
232
- if (body === undefined) return null;
233
- if (!body || typeof body != 'object') return body;
234
-
235
- let processedBody;
236
-
237
- if (body.toApiResponse) {
238
- const formattedBody = await body.toApiResponse(options);
239
- return await this.processJSONBody(formattedBody, options);
240
-
241
- } else if (Array.isArray(body)) {
242
- const processed = [];
243
- for (const idx in body) {
244
- processed.push(await this.processJSONBody(body[idx], options));
245
- }
246
- return processed;
247
-
248
- } else if (body.constructor == Date) {
249
- return body.getTime();
250
-
251
- } else {
252
- const processed = {};
253
- for (let [key, val] of Object.entries(body)) {
254
- processed[key] = await this.processJSONBody(val, options);
255
- }
256
- return processed;
257
- }
258
- }
259
-
260
-
261
- get statusText() {
262
- switch(this.statusCode) {
263
- case 200:
264
- return 'ok';
265
- case 201:
266
- return 'created';
267
- case 202:
268
- return 'accepted';
269
- case 204:
270
- return 'no content';
271
- case 301:
272
- return 'moved permanently';
273
- case 302:
274
- return 'found';
275
- case 304:
276
- return 'not modified';
277
- case 307:
278
- return 'temporary redirect';
279
- case 308:
280
- return 'permanent redirect';
281
- case 400:
282
- return 'bad request'
283
- case 401:
284
- return 'unauthorized';
285
- case 402:
286
- return 'payment required';
287
- case 403:
288
- return 'forbidden';
289
- case 404:
290
- return 'not found';
291
- case 405:
292
- return 'method not allowed';
293
- case 408:
294
- return 'request timeout';
295
- case 409:
296
- return 'conflict';
297
- case 410:
298
- return 'gone';
299
- case 413:
300
- return 'content too large';
301
- case 415:
302
- return 'unsupported media type';
303
- case 422:
304
- return 'unprocessable content';
305
- case 423:
306
- return 'locked';
307
- case 429:
308
- return 'too many requests';
309
- case 500:
310
- return 'server error';
311
- case 501:
312
- return 'not implemented';
313
- case 502:
314
- return 'bad gateway';
315
- case 503:
316
- return 'service unavailable';
317
- case 504:
318
- return 'gateway timeout';
319
- }
320
- }
321
-
322
-
323
- #setupStatusRenderers() {
324
- const successStatuses = {
325
- CreationSuccessful: 201,
326
- Accepted: 202,
327
- NoConent: 204,
328
- MovedPermanently: 301,
329
- Found: 302,
330
- NotModified: 304,
331
- TemporaryRedirect: 307,
332
- PermanentRedirect: 308,
333
- }
334
-
335
- for (let [type, statusCode] of Object.entries(Errors.Statuses)) {
336
- if (type == 'Server') type = 'ServerError';
337
- this[`render${type}`] = async function(message=null, body=null) {
338
- return await this.#renderError(statusCode, message, body);
339
- }
340
- }
341
-
342
- for (let [type, statusCode] of Object.entries(successStatuses)) {
343
- this[`render${type}`] = async function(body=null) {
344
- return await this.#renderSuccess(statusCode, body);
345
- }
346
- }
347
- }
348
-
349
-
350
- async #renderError(statusCode, message, body) {
351
- this.statusCode = statusCode;
352
- body = structuredClone(body || {});
353
- body.errors = body.errors || {};
354
- body.errors.message = message || null;
355
- return await this.render(body);
356
- }
357
-
358
-
359
- async #renderSuccess(statusCode, body) {
360
- this.statusCode = statusCode;
361
- return await this.render(body);
362
- }
363
-
364
-
365
- setHeader(header, value) {
366
- this.#response.setHeader(header, value);
367
- }
368
-
369
-
370
- flushHeaders() {
371
- this.#response.flushHeaders();
372
- }
373
-
374
-
375
105
  get rendered() { return this.#rendered; }
376
106
  markRendered() { this.#rendered = true; }
377
107
  get writableEnded() { return this.#response.writableEnded; }
108
+ get isStreaming() { return Boolean(this._isStreaming); }
378
109
 
379
110
 
380
111
  /***********************************************************************************************
381
- * STREAM
112
+ * MIXINS
382
113
  ***********************************************************************************************/
383
- async streamSseResponse(handler, errorHandler, abortController=null) {
384
- if (this.#isStreaming) throw new StreamInProgressError();
385
- this.#isStreaming = true;
386
-
387
- let closeHandler;
388
- if (abortController) {
389
- this.#streamAbortController = abortController;
390
- closeHandler = () => abortController.abort();
391
- this.#request.on('close', closeHandler);
392
- }
393
-
394
- this.#setSseHeaders();
395
- this.markRendered();
396
- try {
397
- await handler.bind(this)();
398
- } catch(err) {
399
- await errorHandler(err);
400
- throw err;
401
- } finally {
402
- this.writeStreamEvent(null, '[DONE]');
403
- this.#response.end();
404
-
405
- this.#isStreaming = false;
406
- this.#streamAbortController = null;
407
-
408
- if (closeHandler) this.#request.off('close', closeHandler);
409
- }
410
- }
411
-
412
-
413
- #setSseHeaders() {
414
- this.statusCode = 200;
415
- this.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
416
- this.setHeader('Cache-Control', 'no-cache');
417
- this.setHeader('Connection', 'keep-alive');
418
- this.setHeader('X-Accel-Buffering', 'no');
419
- this.flushHeaders();
420
- }
421
-
422
-
423
- writeStreamEvent(evt, data) {
424
- if (!this.#isStreaming) throw new NoActiveStreamError();
425
- if (this.#streamAbortController?.signal?.aborted || this.writableEnded) return;
426
-
427
- if (evt) {
428
- this.#response.write(`event: ${evt}\n`);
429
- }
430
-
431
- const msg = `data: ${data}\n\n`;
432
- this.#response.write(msg);
114
+ static autoLoadMixins() {
115
+ this.actions; // make sure that actions are cached before loading helpers
116
+ const mixin = this._autoLoadModule(this.mixinDirectory);
117
+ this.loadMixin(mixin);
433
118
  }
434
119
 
435
120
 
436
- /***********************************************************************************************
437
- * HELPERS
438
- ***********************************************************************************************/
439
- static autoLoadHelpers() {
440
- this.actions; // make sure that actions are cached before loading helpers
441
- const { StaticMethods, InstanceMethods, Accessors } = this._autoLoadModule(this.autoLoadHelperPath);
121
+ static loadMixin(mixin) {
122
+ if (typeof mixin == 'function') mixin = mixin();
123
+ const { InstanceMethods, StaticMethods, Accessors={}, StaticAccessors={} } = mixin;
442
124
 
443
125
  if (StaticMethods) Object.assign(this, StaticMethods);
444
126
  if (InstanceMethods) Object.assign(this.prototype, InstanceMethods);
445
127
 
446
- if (Accessors) {
447
- for (const [attrName, attrAccessors] of Object.entries(Accessors)) {
448
- Object.defineProperty(this.prototype, attrName, attrAccessors);
449
- }
128
+ for (const [attrName, attrAccessors] of Object.entries(Accessors)) {
129
+ Object.defineProperty(this.prototype, attrName, attrAccessors);
130
+ }
131
+
132
+ for (const [attrName, attrAccessors] of Object.entries(StaticAccessors)) {
133
+ Object.defineProperty(this, attrName, attrAccessors);
450
134
  }
451
135
  }
452
136
 
453
137
 
454
138
  static autoLoadDocumentation() {
455
- const docs = this._autoLoadModule(this.autoLoadDocumentationPath);
139
+ const docs = this._autoLoadModule(this.documentationDirectory);
456
140
  Object.assign(this, docs);
457
141
  }
458
142
 
@@ -469,103 +153,16 @@ class VidaServerController {
469
153
  }
470
154
 
471
155
 
472
- static get autoLoadHelperPath() {
156
+ static get mixinDirectory() {
473
157
  return `${process.cwd()}/lib/controllers${this.routePrefix}Controller`;
474
158
  }
475
159
 
476
160
 
477
- static get autoLoadDocumentationPath() {
161
+ static get documentationDirectory() {
478
162
  return `${process.cwd()}/lib/controllers/_docs${this.routePrefix}Controller`;
479
163
  }
480
164
 
481
165
 
482
- /***********************************************************************************************
483
- * CALLBACKS
484
- ***********************************************************************************************/
485
- async setupRequestState() {}
486
- setupCallbacks() {}
487
-
488
-
489
- #applyCallbacks() {
490
- // set up the callbacks when the instance is constructed and prevent it from being called again
491
- this.setupCallbacks();
492
- this.setupCallbacks = undefined;
493
- }
494
-
495
-
496
- beforeCallback(callback, options={}) {
497
- this.#addCallback(callback, options, this.#beforeCallbacks);
498
- }
499
-
500
-
501
- afterCallback(callback, options={}) {
502
- this.#addCallback(callback, options, this.#afterCallbacks);
503
- }
504
-
505
-
506
- skipBeforeCallback(callback, options={}) {
507
- this.#addCallback(callback, options, this.#beforeCallbacksToSkip);
508
- }
509
-
510
-
511
- skipAfterCallback(callback, options={}) {
512
- this.#addCallback(callback, options, this.#afterCallbacksToSkip);
513
- }
514
-
515
-
516
- #addCallback(callback, options, callbacksList) {
517
- callbacksList.push({ callback, options });
518
- }
519
-
520
-
521
- async runBeforeCallbacks(actionName) {
522
- return await this.#runCallbacks(this.#beforeCallbacks, this.#beforeCallbacksToSkip, actionName);
523
- }
524
-
525
-
526
- async runAfterCallbacks(actionName) {
527
- return await this.#runCallbacks(this.#afterCallbacks, this.#afterCallbacksToSkip, actionName);
528
- }
529
-
530
-
531
- async #runCallbacks(callbacksList, callbacksToSkipList, actionName) {
532
- let toSkip = callbacksToSkipList.filter(({ callback, options }) => {
533
- return this.#shouldRunCallback(actionName, options);
534
- });
535
- toSkip = toSkip.map(({ callback }) => callback);
536
-
537
- for (const { callback, options } of callbacksList) {
538
- if (!this.#shouldRunCallback(actionName, options)) continue;
539
- if (toSkip.includes(callback)) continue;
540
- const result = await this.#runCallback(callback)
541
- if (result === false) return false;
542
- }
543
-
544
- return true;
545
- }
546
-
547
-
548
- async #runCallback(callback) {
549
- if (typeof callback == 'function') return await callback.call(this);
550
- return await this[callback]();
551
- }
552
-
553
-
554
- #shouldRunCallback(actionName, options) {
555
- if (options.only) {
556
- if (Array.isArray(options.only)) return options.only.includes(actionName);
557
- return options.only == actionName;
558
- }
559
-
560
- if (options.except) {
561
- if (Array.isArray(options.except)) return !options.except.includes(actionName);
562
- return options.except != actionName;
563
- }
564
-
565
- return true;
566
- }
567
-
568
-
569
166
  /***********************************************************************************************
570
167
  * REQUEST PARAMETERS
571
168
  ***********************************************************************************************/
@@ -611,232 +208,6 @@ class VidaServerController {
611
208
  }
612
209
 
613
210
 
614
- /***********************************************************************************************
615
- * VALIDATIONS
616
- ***********************************************************************************************/
617
- async validateParameters(validations) {
618
- const errors = {};
619
- for (const [parameterName, parameterValidations] of Object.entries(validations)) {
620
- const parameterErrors = await this.validateParameter(this.params[parameterName], parameterValidations);
621
- if (parameterErrors.length) errors[parameterName] = parameterErrors;
622
- }
623
-
624
- if (Object.keys(errors).length) throw new Errors.ValidationError('', errors);
625
- }
626
-
627
-
628
- async validateParameter(value, parameterValidations) {
629
- const errors = [];
630
-
631
- if (parameterValidations?.optional && value === undefined) return errors;
632
-
633
- for (const [validationType, validationOptions] of Object.entries(parameterValidations)) {
634
- const validationMethod = `validate${camelize(validationType)}`;
635
- if (!this[validationMethod]) continue;
636
- const error = await this[validationMethod](value, validationOptions);
637
- if (error) errors.push(error);
638
- }
639
-
640
- return errors;
641
- }
642
-
643
-
644
- validatePresence(value) {
645
- if (!value) return 'required';
646
- }
647
-
648
-
649
- validateIsInteger(value, options) {
650
- const num = parseFloat(value);
651
- if (!/^-?\d+$/.test(value) || isNaN(num) || !Number.isInteger(num)) return 'must be an integer';
652
-
653
- if (options?.gte !== undefined) {
654
- if (num < options.gte) return `must be greater than or equal to ${options.gte}`;
655
- }
656
- if (options?.lte !== undefined) {
657
- if (num > options.lte) return `must be less than or equal to ${options.lte}`;
658
- }
659
- }
660
-
661
-
662
- validateIsString(value, options) {
663
- if (typeof value !== 'string') return 'must be a string';
664
-
665
- if (options?.length?.gte !== undefined) {
666
- if (value.length < options.length.gte) return `must be greater than or equal to ${options.length.gte} characters`;
667
- }
668
- if (options?.length?.lte !== undefined) {
669
- if (value.length > options.length.lte) return `must be fewer than or equal to ${options.length.lte} characters`;
670
- }
671
- if (options?.regex !== undefined) {
672
- if (!options.regex.test(value)) return `must match ${options.regex}`;
673
- }
674
- }
675
-
676
-
677
- validateIsDateTime(value) {
678
- if (new Date(value) == 'Invalid Date') return 'invalid date';
679
- }
680
-
681
-
682
- validateIsEnum(value, options) {
683
- if (!options.enums.includes(value)) {
684
- return options.error || `must be one of [${options.enums.filter(Boolean).join(', ')}]`;
685
- }
686
- }
687
-
688
-
689
- validateIsBoolean(value, bool) {
690
- if (typeof value == 'boolean' && value === bool) return;
691
- return `must be ${bool}`;
692
- }
693
-
694
-
695
- async validateFunction(value, fnc) {
696
- const error = await fnc.call(this, value);
697
- if (error) return error;
698
- }
699
-
700
-
701
- /***********************************************************************************************
702
- * ACTION SETUP
703
- ************************************************************************************************
704
- * The controller looks for any methods that fit the pattern of `{httpMethod}ActionName`
705
- * (e.g. getFooBar) and prepares server routes for them. By default, a method `getFooBar` on a
706
- * `BazController` would have an endpoint of `GET /baz/fooBar`. The server will also prepend
707
- * directory names, so if `bazController.js` is nested within the `ban` directory
708
- * (e.g. `ban/bazController.js`), the endpoint will be `GET /ban/baz/fooBar`.
709
- *
710
- * Default routing logic can be overriden by defining custom routes in the `routes` getter. To
711
- * provider a custom route for `getFooBar`, `routes` should return an object like:
712
- * {getFooBar: '/ban/:accountIdd/foo/:userId/bar'}`
713
- ***********************************************************************************************/
714
- static get actions() {
715
- if (actionStore.has(this)) return actionStore.get(this);
716
- const actionNames = this.actionNames;
717
- const actions = actionNames.map((actionName => {
718
- return this._constructAction(actionName);
719
- }).bind(this));
720
-
721
- actionStore.set(this, actions);
722
- return actions;
723
- }
724
-
725
-
726
- static _constructAction(actionName) {
727
- const routePrefix = this.routePrefix
728
- const action = {};
729
- const match = actionName.match(this.actionRegExp);
730
-
731
- action.method = match.groups.method.toUpperCase();
732
- action.action = actionName;
733
- action.path = this._routeForAction(action.method, match.groups.path, routePrefix, actionName);
734
- return action;
735
- }
736
-
737
-
738
- static _routeForAction(method, path, routePrefix, actionName) {
739
- if (this.routes[actionName]) return this.routes[actionName];
740
-
741
- // If the path starts with Record, use :id as a path parameter
742
- /*
743
- class UsersController {
744
- // Get /users
745
- async getIndex() {}
746
-
747
- // Get /user/:id
748
- async getRecord() {}
749
-
750
- // Get /user/:id/status
751
- async getRecordStatus() {}
752
- }
753
- */
754
- if (/^Record($|[A-Z])/.test(path)) {
755
- routePrefix = `${singularize(routePrefix)}/:id`;
756
- path = path == 'Record' ? 'Index' : path.replace(/^Record/, '');
757
- }
758
-
759
- if (path == 'Index') return routePrefix;
760
-
761
- path = path.charAt(0).toLowerCase() + path.slice(1);
762
- return `${routePrefix}/${path}`;
763
- }
764
-
765
-
766
- static get routePrefix() {
767
- const controllerName = this.name.replace(/Controller$/, '');
768
- const controllerPrefix = controllerName.charAt(0).toLowerCase() + controllerName.slice(1);
769
-
770
- return `${this.directoryPrefix || ''}/${controllerPrefix}`;
771
- }
772
-
773
-
774
- static get routes() {
775
- return {};
776
- }
777
-
778
-
779
- static get actionNames() {
780
- const methodNames = Object.getOwnPropertyNames(this.prototype);
781
- const nameRegExp = this.actionRegExp;
782
- return methodNames.filter(name => nameRegExp.test(name));
783
- }
784
-
785
-
786
- static get actionRegExp() {
787
- return new RegExp(/^(?<method>get|post|put|delete|patch|head)(?<path>[A-Z][a-zA-Z]+)$/);
788
- }
789
-
790
-
791
- /***********************************************************************************************
792
- * DOCUMENTATION
793
- ***********************************************************************************************/
794
- static parametersForAction(actionName) {
795
- const methodName = this.parametersMethodForAction(actionName);
796
- if (this[methodName]) return this[methodName]();
797
- return null;
798
- }
799
-
800
-
801
- static parametersMethodForAction(actionName) {
802
- return `parametersFor${camelize(actionName)}`;
803
- }
804
-
805
-
806
- static documentationForAction(actionName) {
807
- const methodName = this.documentationMethodForAction(actionName);
808
- if (this[methodName]) return this[methodName]();
809
- return null;
810
- }
811
-
812
-
813
- static documentationMethodForAction(actionName) {
814
- return `document${camelize(actionName)}`;
815
- }
816
-
817
-
818
- isAuthenticatedAction(actionName) {
819
- const { options } = this.#beforeCallbacks.find(({ callback, options }) => callback == AUTH_CALLBACK_NAME)
820
- return Boolean(options) && this.#shouldRunCallback(actionName, options);
821
- }
822
-
823
-
824
- static isAuthenticatedAction(actionName) {
825
- const controller = new this();
826
- return controller.isAuthenticatedAction(actionName);
827
- }
828
-
829
-
830
- static _authenticationDocumentation(actionName) {
831
- if (!this.isAuthenticatedAction(actionName)) return undefined;
832
- }
833
-
834
-
835
- static authenticationDocumentation(actionName) {
836
- return {apiKeyAuth: []};
837
- }
838
-
839
-
840
211
  /***********************************************************************************************
841
212
  * SECURITY
842
213
  ***********************************************************************************************/
@@ -864,11 +235,36 @@ class VidaServerController {
864
235
  startApmSegment(name, record, handler) {
865
236
  return APM.startSegment(name, record, handler);
866
237
  }
238
+
239
+
240
+ reportError(err) {
241
+ APM.reportError(err);
242
+ }
867
243
  }
868
244
 
869
245
 
870
- class StreamInProgressError extends Error {}
871
- class NoActiveStreamError extends Error {}
246
+ /***************************************************************************************************
247
+ * MIXINS
248
+ ***************************************************************************************************/
249
+ const CallbacksMixin = require('./controllerMixins/callbacks');
250
+ const DocumentationMixin = require('./controllerMixins/documentation');
251
+ const RendererMixin = require('./controllerMixins/renderer');
252
+ const RequestDetailsMixin = require('./controllerMixins/requestDetails');
253
+ const RoutingMixin = require('./controllerMixins/routing');
254
+ const ValidationMixin = require('./controllerMixins/validations');
255
+
256
+ VidaServerController.loadMixin(CallbacksMixin);
257
+ VidaServerController.loadMixin(DocumentationMixin);
258
+ VidaServerController.loadMixin(RequestDetailsMixin);
259
+ VidaServerController.loadMixin(RendererMixin);
260
+ VidaServerController.loadMixin(RoutingMixin);
261
+ VidaServerController.loadMixin(ValidationMixin);
262
+
263
+
264
+ /***************************************************************************************************
265
+ * ERRORS
266
+ ***************************************************************************************************/
267
+ const { NoActiveStreamError, StreamInProgressError } = RendererMixin;
872
268
 
873
269
  const errorClasses = Object.values(Errors).filter(cls => cls == Errors.ServerError || cls.prototype instanceof Errors.AbstractServerError);
874
270
  VidaServerController.prototype.Errors = Object.fromEntries(errorClasses.map(cls => {