@vida-global/core 1.2.5 → 1.3.1

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.
Files changed (48) hide show
  1. package/.github/workflows/npm-test.yml +24 -0
  2. package/index.js +1 -1
  3. package/lib/{active_record → activeRecord}/README.md +3 -3
  4. package/lib/{active_record → activeRecord}/baseRecord.js +11 -2
  5. package/lib/{active_record → activeRecord}/db/schema.js +1 -1
  6. package/lib/http/README.md +2 -2
  7. package/lib/server/README.md +181 -20
  8. package/lib/server/apiDocsGenerator.js +55 -0
  9. package/lib/server/controllerImporter.js +62 -0
  10. package/lib/server/errors.js +28 -0
  11. package/lib/server/index.js +3 -3
  12. package/lib/server/server.js +7 -53
  13. package/lib/server/serverController.js +147 -20
  14. package/package.json +1 -1
  15. package/scripts/{active_record → activeRecord}/migrate.js +1 -1
  16. package/test/{active_record → activeRecord}/baseRecord.test.js +46 -90
  17. package/test/activeRecord/db/connection.test.js +149 -0
  18. package/test/activeRecord/db/connectionConfiguration.test.js +128 -0
  19. package/test/activeRecord/db/migrator.test.js +144 -0
  20. package/test/activeRecord/db/queryInterface.test.js +48 -0
  21. package/test/activeRecord/helpers/baseRecord.js +32 -0
  22. package/test/activeRecord/helpers/baseRecordMocks.js +59 -0
  23. package/test/activeRecord/helpers/connection.js +28 -0
  24. package/test/activeRecord/helpers/connectionConfiguration.js +32 -0
  25. package/test/activeRecord/helpers/fixtures.js +39 -0
  26. package/test/activeRecord/helpers/migrator.js +78 -0
  27. package/test/activeRecord/helpers/queryInterface.js +29 -0
  28. package/test/http/client.test.js +61 -239
  29. package/test/http/error.test.js +23 -47
  30. package/test/http/helpers/client.js +80 -0
  31. package/test/http/helpers/error.js +31 -0
  32. package/test/server/helpers/autoload/TmpWithHelpersController.js +17 -0
  33. package/test/server/helpers/serverController.js +13 -0
  34. package/test/server/serverController.test.js +319 -6
  35. package/test/active_record/db/connection.test.js +0 -221
  36. package/test/active_record/db/connectionConfiguration.test.js +0 -184
  37. package/test/active_record/db/migrator.test.js +0 -266
  38. package/test/active_record/db/queryInterface.test.js +0 -66
  39. /package/lib/{active_record → activeRecord}/db/connection.js +0 -0
  40. /package/lib/{active_record → activeRecord}/db/connectionConfiguration.js +0 -0
  41. /package/lib/{active_record → activeRecord}/db/importSchema.js +0 -0
  42. /package/lib/{active_record → activeRecord}/db/migration.js +0 -0
  43. /package/lib/{active_record → activeRecord}/db/migrationTemplate.js +0 -0
  44. /package/lib/{active_record → activeRecord}/db/migrationVersion.js +0 -0
  45. /package/lib/{active_record → activeRecord}/db/migrator.js +0 -0
  46. /package/lib/{active_record → activeRecord}/db/queryInterface.js +0 -0
  47. /package/lib/{active_record → activeRecord}/index.js +0 -0
  48. /package/lib/{active_record → activeRecord}/utils.js +0 -0
@@ -0,0 +1,24 @@
1
+ name: npm test
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - name: Checkout code
12
+ uses: actions/checkout@v4
13
+
14
+ - name: Setup Node
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ cache: 'npm'
19
+
20
+ - name: Install dependencies
21
+ run: npm ci
22
+
23
+ - name: Run tests
24
+ run: npm test
package/index.js CHANGED
@@ -3,7 +3,7 @@ const { AuthorizationError,
3
3
  VidaServer,
4
4
  VidaServerController } = require('./lib/server');
5
5
  const { logger } = require('./lib/logger');
6
- const ActiveRecord = require('./lib/active_record');
6
+ const ActiveRecord = require('./lib/activeRecord');
7
7
  const { redisClientFactory } = require('./lib/redis');
8
8
 
9
9
 
@@ -82,9 +82,9 @@ development: {
82
82
  # Migrations
83
83
  To prepare your project to run database migrations, add the following lines to the `scripts` section of your `package.json`:
84
84
  ```
85
- "db:create_migration": "node node_modules/@vida-global/core/scripts/active_record/migrate.js create_migration",
86
- "db:migrate": "node node_modules/@vida-global/core/scripts/active_record/migrate.js migrate",
87
- "db:rollback": "node node_modules/@vida-global/core/scripts/active_record/migrate.js rollback"
85
+ "db:create_migration": "node node_modules/@vida-global/core/scripts/activeRecord/migrate.js create_migration",
86
+ "db:migrate": "node node_modules/@vida-global/core/scripts/activeRecord/migrate.js migrate",
87
+ "db:rollback": "node node_modules/@vida-global/core/scripts/activeRecord/migrate.js rollback"
88
88
  ```
89
89
 
90
90
  To generate a new migration file, run: `npm run db:create_migration createUsers`. This will automatically generate a migration file. Your migration can create tables, create indexes, and add, remove, or update columns.
@@ -98,7 +98,8 @@ class BaseRecord extends Model {
98
98
 
99
99
  static initializeHooks() {
100
100
  if (this.isCacheable) {
101
- this.addHook('afterSave', this._afterSaveCacheHook);
101
+ this.addHook('afterSave', this._afterSaveCacheHook);
102
+ this.addHook('afterDestroy', this._afterDestroyCacheHook);
102
103
  }
103
104
  }
104
105
 
@@ -202,7 +203,9 @@ class BaseRecord extends Model {
202
203
  }
203
204
 
204
205
  const client = await this._getRedisClient();
205
- await client.mSet(toCache);
206
+ if (Object.keys(toCache).length) {
207
+ await client.mSet(toCache);
208
+ }
206
209
  this.debugLog('Cache Set', Object.keys(fetchedData).join(', '));
207
210
  }
208
211
 
@@ -273,6 +276,12 @@ class BaseRecord extends Model {
273
276
  }
274
277
 
275
278
 
279
+ static async _afterDestroyCacheHook(record, options) {
280
+ await record.clearSelfCache();
281
+ await record.updateCache(options);
282
+ }
283
+
284
+
276
285
  /***********************************************************************************************
277
286
  * MISC
278
287
  ***********************************************************************************************/
@@ -59,7 +59,7 @@ function resolveAutoIncrementColumns(details) {
59
59
  if (!details.defaultValue) return;
60
60
 
61
61
  const regExp = /^nextval\(\w+_seq::regclass\)$/;
62
- if (details.defaultValue.match(regExp)) {
62
+ if (regExp.test(details.defaultValue)) {
63
63
  delete details.defaultValue;
64
64
  delete details.allowNull;
65
65
  details.autoIncrement = true;
@@ -26,7 +26,7 @@ const client = new MyApiClient(true, '123abctoken');
26
26
  const requestParameters = {baz: 1, ban: 2};
27
27
  const response = await client.get("/foo/bar", {requestParameters});
28
28
 
29
- const requestBody = {baz: 1, ban: 2};
30
- const response = await client.post("/foo/bar", {requestBody});
29
+ const body = {baz: 1, ban: 2};
30
+ const response = await client.post("/foo/bar", { body });
31
31
  ```
32
32
 
@@ -1,37 +1,198 @@
1
1
  # VidaServer
2
2
  The `VidaServer` is a general purpose wrapper around an `express` server. It initializes standard middleware that we want running on all Vida application servers and leaves hooks for adding additional middleware on subclasses for specific use cases.
3
- The `VidaServer` works in conjunction with the `VidaServerController`. When a `VidaServer` begins listening, it searches its directory structure (as defined in the `controllerDirectories` getter) for subclasses of `VidaServerController`. It then creates routes based on all methods of the controller that are prefixed with `get`, `post`, `put`, `patch`, or `delete`.
3
+
4
+ ## Controllers
5
+ The `VidaServer` works in conjunction with the `VidaServerController`. When a `VidaServer` begins listening, it searches its directory structure (as defined in the `controllerDirectories` getter) for subclasses of `VidaServerController` and autoloads their paths.
6
+
7
+
8
+ ## Routing
9
+ When a controller is auto loaded, it generates routes based on all methods of the controller that are prefixed with `get`, `post`, `put`, `patch`, or `delete`.
4
10
  The paths for these routes are defined by the directory structure of the controller, the name of the controller, and the name of the method. For example, a method `getFoo` defined on a `BarController` in the directory `/controllers/baz`, will create the route `GET /baz/bar/foo`. “Index” will yield an empty path (e.g. `getIndex` in the previously described controller generates `GET /baz/bar`). However, the default paths can be overridden by defining the `routes` getter.
5
11
  Each controller method will have direct access to request and response variables and a logger. Calling `render` will set the response body. It accepts either a string or a JSON object.
6
12
  ```
13
+ // defined in the /api/v2 directory
7
14
  class UsersController extends ServerController {
8
- // GET /users
9
- getIndex() {
10
- const users = getUsers(this.query.pageNumber, this.query.pageSize);
11
- this.render(users.toJson());
15
+
16
+ // GET /api/v2/users
17
+ getIndex() {}
18
+
19
+ // POST /api/v2/user/:id
20
+ postRecord() {
21
+ const user = User.find(this.params.id);
12
22
  }
13
23
 
14
- // PUT /users/foo
15
- putFoo() {
16
- this.logger.info("Putting Foo");
17
- this.render("hello world");
24
+ // PUT /api/v2/users/foo
25
+ putFoo() {}
26
+
27
+ // Delete /foo/bar/baz
28
+ deleteSomething() {}
29
+
30
+ static get routes() {
31
+ return {deleteSomething: '/foo/bar/baz'};
18
32
  }
33
+ }
34
+ ```
35
+
36
+
37
+ ## Helpers 🚨IMPORTANT🚨
38
+ Controller actions should be kept slim with most of the logic in helper files. Helper files that follow the same directory structure as the controller, but in the `/lib` directory will be auto loaded and can add instance methods and custom accessors.
39
+ ```
40
+ // ./controllers/api/v2/fooController.js
41
+ class FooController extends ServerController {
42
+ async getBar() {
43
+ await this.validateParameters({playerNumber: {isInteger: true}});
44
+ return this.doTheThing();
45
+ }
46
+ }
19
47
 
20
- // POST /user/:id
21
- postUpdateUser() {
22
- const user = getUser(this.params.id);
23
- if (!user) {
24
- this.status = 404;
25
- this.render({error: "No user found"});
26
- return;
48
+ // ./lib/controllers/api/v2/fooController.js
49
+ const InstanceMethods = {
50
+ doTheThing() {
51
+ const user = this.getTheThing();
52
+ user.name = this.playerName;
53
+ return user;
54
+ },
55
+
56
+ getTheThing() {
57
+ return new User();
58
+ }
59
+ }
60
+
61
+ const Accessors = {
62
+ playerName: {
63
+ get() {
64
+ return `Player ${this.params.playerNumber}`;
27
65
  }
28
- user.update(this.body.user);
29
- this.render({success: true});
30
66
  }
67
+ }
31
68
 
32
- static get routes() {
33
- return {postUpdateUser: '/user/:id'};
69
+
70
+ module.exports = {
71
+ InstanceMethods,
72
+ Accessors
73
+ }
74
+ ```
75
+
76
+
77
+ ## Request Properties
78
+ Within an action, the controller has access to:
79
+ - `this.params` includes:
80
+ - any parameters from the route (e.g. `/foo/:bar/:baz` would provide `this.params.bar` and `this.params.baz`)
81
+ - any values from a JSON formatted request body
82
+ - any URL query parameters
83
+ - `this.requestHeaders` the headers sent with the request
84
+ - `this.responseHeaders` the headers to be sent with the response (can be updated)
85
+ - `this.contentType`
86
+
87
+
88
+ ## Rendering a response
89
+ By default, the controller will render the value returned from the action. It recursively searches the result for any objects with a `toApiResponse` and renders that. For example...
90
+ ```
91
+ class User {
92
+ toApiResponse(opts) {
93
+ const data = {name: this.name, email: this.email}
94
+ if (opts.includeTitle) data.title = this.title;
95
+ return data;
34
96
  }
35
97
  }
98
+
99
+ async getFoo() {
100
+ const user1 = new User("Bruce", "bwayne@wayne-enterprises.inc");
101
+ const user2 = new User("Babs", "bgordon@wayne-enterprises.inc");
102
+ const response = {bar: 1, baz: [user1, user2]};
103
+ return response;
104
+ }
105
+ ```
106
+ ...will generate the response
107
+ ```
108
+ {data: {bar: 1,
109
+ baz: [
110
+ {name: "Bruce", email: "bwayne@wayne-enterprises.inc"},
111
+ {name: "Babs", email: "bgordon@wayne-enterprises.inc"],
112
+ ]
113
+ }, status: "ok"}
114
+ ```
115
+
116
+ Additional options can be passed to `toApiResponse` by explicitly calling `render`
117
+ ```
118
+ await this.render(response, {includeTitle: true});
119
+ ```
120
+
121
+
122
+ ## Error handling
123
+ There is no need to set `statusCode` directly as there are helper methods for all statuses.
124
+ HTTP 400
125
+ ```
126
+ await this.renderErrors("Something bad happened", {password: ['must be > 8 characters', 'must include numbers']})
127
+ // renders {data: {errors: {message: "Soemthing bad happened", fields: {password: [...]}}}, status: "bad request"}
128
+
129
+ await this.renderErrors("Something bad happened"})
130
+ // renders {data: {errors: {message: "Soemthing bad happened"}}, status: "bad request"}
131
+
132
+ await this.renderErrors({password: ['must be > 8 characters', 'must include numbers']}
133
+ // renders {data: {errors: {fields: {password: [...]}}}, status: "bad request"}
134
+ ```
135
+ HTTP 401
136
+ ```
137
+ await this.renderUnauthorizedResponse("Nope")
138
+ // renders {data: {message: "Nope"}, status: "unauthorized"}
139
+ ```
140
+ HTTP 403
141
+ ```
142
+ await this.renderForbiddenResponse("You shall not pass")
143
+ // renders {data: {message: "You shall not pass"}, status: "forbidden"}
144
+ ```
145
+ HTTP 404
146
+ ```
147
+ await this.renderNotFoundResponse("Whoops")
148
+ // renders {data: {message: "Whoops"}, status: "not found"}
149
+ ```
150
+ Throwing any of `this.Errors.Authorization(msg)`, `this.Errors.Forbidden(msg)`, `this.Errors.NotFound(msg)`, `this.Errors.Validation(msg, errors)` from anywhere in the code will trigger the corresponding error handler. All other errors will generate a 500 error.
151
+
152
+
153
+ ## Callbacks
154
+ Callbacks are methods that will run before or after an action has performed. They can be used for authentication, action setup, etc. If a callback returns false, execution is stopped and no other callbacks or the action are run.
155
+ ```
156
+ // This will run on all actions except for getIndex and will halt execution if the environment is production (e.g. development routes)
157
+ this.beforeCallback(() => process.env.NODE_ENV != 'production', {except: 'getIndex'});
158
+
159
+ // This will run an instance method, `setUpSomeStuff` before every action
160
+ this.beforeCallback('setUpSomeStuff');
161
+
162
+ // This will run an instance method, 'cleanupRequest', after `getFoo` and `getBar`
163
+ this.afterCallback('cleanupRequest', {only: ['getFoo', 'getBar']})
164
+ ```
165
+
166
+ ## Validations
167
+ The `validateParameters` method can be called to automatically validate parameters based on given criteria. If validation fails, execution is stopped and a response is sent with a 400 code and body `{data: {errors: {field1: [errorMsg]}}, status: "bad request"}`
168
+ ```
169
+ async getFoo() {
170
+ // validates that the pageSize parameter is an integer greater than or equal to one
171
+ await this.validateParameters({pageSize: {isInteger: true, gte: 1}});
172
+ }
173
+
174
+ async postBar() {
175
+ await this.validateParameters(this.barValidations);
176
+ }
177
+
178
+ get barValidations() {
179
+ return {
180
+ name: {presence: true},
181
+ role: {isEnum: {enums: ['CEO', 'COO', 'other']}},
182
+ email: {regex: /\w@\w\.com/},
183
+ title: {function: this.titleUniqueness}
184
+ }
185
+ }
186
+
187
+ titleUniqueness(title) {
188
+ if (User.findByTitle(title)) return 'must be unique';
189
+ }
36
190
  ```
37
191
 
192
+ ### Supported Validations
193
+ - `{presence: true}`
194
+ - `{isInteger: true}`, `{isInteger: {gte: 0, lte: 100}}`
195
+ - `{isDateTime}`
196
+ - `{isEnum: {enums: [a, b, c]}}`
197
+ - `{regex: /foo/}`
198
+ - `{function: someFunction}` If the function returns a string, that is considered an error and returned as the validation message
@@ -0,0 +1,55 @@
1
+ const { ControllerImporter } = require('./controllerImporter');
2
+
3
+
4
+ class ApiDocsGenerator {
5
+ #controllerDirectories
6
+ #controllers;
7
+ #docs = {};
8
+
9
+ constructor(server) {
10
+ this.#controllerDirectories = server.controllerDirectories;
11
+ }
12
+
13
+
14
+ generateDocs() {
15
+ console.log("\n\n");
16
+ this.controllers.forEach(controller => {
17
+ controller.actions.forEach(action => {
18
+ this.generateDocsForAction(action);
19
+ });
20
+ });
21
+ console.log("\n\n");
22
+ }
23
+
24
+
25
+ generateDocsForAction(action) {
26
+ const endpoint = this.formatEndpoint(action);
27
+ this.#docs[endpoint] = this.#docs[endpoint] || {};
28
+ const actionDocs = this.#docs[endpoint][action.method.toLowerCase()] = {foo: 1};
29
+ console.log(actionDocs);
30
+ }
31
+
32
+
33
+ get controllers() {
34
+ if (!this.#controllers) {
35
+ const controllerImporter = new ControllerImporter(this.#controllerDirectories);
36
+ this.#controllers = controllerImporter.controllers;
37
+ }
38
+ return this.#controllers;
39
+ }
40
+
41
+
42
+ /***********************************************************************************************
43
+ * OPENAPI COMPONENTS
44
+ ***********************************************************************************************/
45
+ formatEndpoint(action) {
46
+ const endpoint = action.path.replace(/:([^/]+)/g, '{$1}');
47
+ return endpoint;
48
+ }
49
+
50
+ }
51
+
52
+
53
+ module.exports = {
54
+ ApiDocsGenerator
55
+ }
@@ -0,0 +1,62 @@
1
+ const fs = require('fs');
2
+ const { VidaServerController } = require('./serverController');
3
+
4
+
5
+ class ControllerImporter {
6
+ #controllerDirectories;
7
+ #controllers;
8
+
9
+
10
+ constructor(controllerDirectories) {
11
+ if (!Array.isArray(controllerDirectories)) {
12
+ controllerDirectories = [controllerDirectories];
13
+ }
14
+ this.#controllerDirectories = controllerDirectories;
15
+ }
16
+
17
+
18
+ get controllers() {
19
+ if (!this.#controllers) {
20
+ this.#importControllers();
21
+ }
22
+ return this.#controllers;
23
+ }
24
+
25
+
26
+ #importControllers() {
27
+ this.#controllers = [];
28
+ this.#controllerDirectories.forEach(dir => {
29
+ this.#importDirectory(dir, dir);
30
+ });
31
+ }
32
+
33
+
34
+ #importDirectory(dir, topLevelDirectory) {
35
+ fs.readdirSync(dir).forEach((f => {
36
+ const filePath = `${dir}/${f}`;
37
+ if (fs.lstatSync(filePath).isDirectory()) {
38
+ this.#importDirectory(filePath, topLevelDirectory);
39
+ } else if (f.endsWith('.js')) {
40
+ this.#importFromFile(filePath, dir, topLevelDirectory);
41
+ }
42
+ }).bind(this));
43
+ }
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
+ this.#controllers.push(_export);
53
+ }
54
+ });
55
+ }
56
+
57
+ }
58
+
59
+
60
+ module.exports = {
61
+ ControllerImporter
62
+ }
@@ -0,0 +1,28 @@
1
+ class ServerError extends Error {
2
+ #message;
3
+ constructor(message) {
4
+ super();
5
+ this.#message = message;
6
+ }
7
+ get message() { return this.#message; };
8
+ }
9
+ class AuthorizationError extends ServerError {}
10
+ class ForbiddenError extends ServerError {}
11
+ class NotFoundError extends ServerError {}
12
+ class ValidationError extends ServerError {
13
+ #fields;
14
+ constructor(message, fields) {
15
+ super(message);
16
+ this.#fields = fields;
17
+ }
18
+ get fields() { return structuredClone(this.#fields) };
19
+ }
20
+
21
+
22
+ module.exports = {
23
+ AuthorizationError,
24
+ ForbiddenError,
25
+ NotFoundError,
26
+ ServerError,
27
+ ValidationError
28
+ }
@@ -1,9 +1,9 @@
1
1
  const { VidaServer } = require('./server');
2
- const { AuthorizationError,
3
- VidaServerController } = require('./serverController');
2
+ const { VidaServerController } = require('./serverController');
3
+ const ERRORS = require('./errors');
4
4
 
5
5
  module.exports = {
6
- AuthorizationError,
6
+ ...ERRORS,
7
7
  VidaServer,
8
8
  VidaServerController,
9
9
  }
@@ -9,6 +9,8 @@ const IoServer = require("socket.io")
9
9
  const { Server } = require('http');
10
10
  const { SystemController } = require('./systemController');
11
11
  const { AuthorizationError,
12
+ ForbiddenError,
13
+ NotFoundError,
12
14
  ValidationError,
13
15
  VidaServerController } = require('./serverController');
14
16
 
@@ -191,6 +193,7 @@ class VidaServer {
191
193
 
192
194
 
193
195
  #registerController(controllerCls) {
196
+ controllerCls.autoLoadHelpers();
194
197
  controllerCls.actions.forEach((action => {
195
198
  this.#registerAction(action, controllerCls)
196
199
  }).bind(this));
@@ -200,7 +203,9 @@ class VidaServer {
200
203
  #registerAction(action, controllerCls) {
201
204
  const method = action.method.toLowerCase();
202
205
  const requestHandler = this.requestHandler(action.action, controllerCls)
203
- logger.info(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
206
+ if (process.env.NODE_ENV != 'test') {
207
+ logger.info(`ROUTE: ${method.toUpperCase().padEnd(6)} ${action.path}`);
208
+ }
204
209
  this['_'+method](action.path, requestHandler);
205
210
  }
206
211
 
@@ -216,62 +221,11 @@ class VidaServer {
216
221
  return async function(request, response) {
217
222
  if (process.env.NODE_ENV != 'test') this.logger.info(`${controllerCls.name}#${action}`);
218
223
  const controllerInstance = this.buildController(controllerCls, request, response);
219
- await controllerInstance.setupRequestState();
220
-
221
- if (controllerInstance.rendered) {
222
- response.end();
223
- return;
224
- }
225
-
226
- controllerInstance.setupCallbacks();
227
-
228
- const responseBody = await this.performRequest(controllerInstance, action);
229
-
230
- if (!controllerInstance.rendered) {
231
- await controllerInstance.render(responseBody || {});
232
- }
233
-
234
- response.end();
224
+ await controllerInstance.performRequest(action);
235
225
  }.bind(this);
236
226
  }
237
227
 
238
228
 
239
- async performRequest(controllerInstance, action) {
240
- try {
241
- return await this._performRequest(controllerInstance, action);
242
- } catch(err) {
243
- await this.handleError(err, controllerInstance);
244
- }
245
- }
246
-
247
-
248
- async handleError(err, controllerInstance) {
249
- if (err.constructor == AuthorizationError) {
250
- await controllerInstance.renderUnauthorizedResponse();
251
- return;
252
-
253
- } else if (err.constructor == ValidationError) {
254
- await controllerInstance.renderErrors(err.errors);
255
- return;
256
-
257
- } else {
258
- controllerInstance.statusCode = 500;
259
- await controllerInstance.render({error: err.message});
260
- }
261
-
262
- if (process.env.NODE_ENV == 'development') console.log(err);
263
- }
264
-
265
-
266
- async _performRequest(controllerInstance, action) {
267
- if (await controllerInstance.runBeforeCallbacks(action) === false) return false;
268
- const responseBody = await controllerInstance[action]();
269
- if (await controllerInstance.runAfterCallbacks(action) === false) return false;
270
-
271
- return responseBody;
272
- }
273
-
274
-
275
229
  buildController(controllerCls, request, response) {
276
230
  return new controllerCls(request, response);
277
231
  }