mythix 1.0.2 → 1.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythix",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Mythix is a NodeJS web-app framework",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -27,8 +27,9 @@
27
27
  "express-busboy": "^8.0.0",
28
28
  "inflection": "^1.13.1",
29
29
  "lodash": "^4.17.21",
30
- "nife": "^1.2.2",
30
+ "nife": "^1.5.0",
31
31
  "object-hash": "^2.2.0",
32
- "sequelize": "^6.13.0"
32
+ "sequelize": "^6.13.0",
33
+ "sqlite3": "^5.0.2"
33
34
  }
34
35
  }
@@ -135,7 +135,7 @@ describe('controller-utils', function() {
135
135
  },
136
136
  });
137
137
 
138
- console.log('ALL ROUTES: ', allRoutes);
138
+ // console.log('ALL ROUTES: ', allRoutes);
139
139
  });
140
140
  });
141
141
  });
@@ -14,7 +14,7 @@ const {
14
14
  } = require('./utils');
15
15
 
16
16
  function nowInSeconds() {
17
- return Math.floor(Date.now() / 1000);
17
+ return Date.now() / 1000;
18
18
  }
19
19
 
20
20
  // Trace what is requesting the application exit
@@ -58,6 +58,7 @@ class Application extends EventEmitter {
58
58
  autoReload: (process.env.NODE_ENV || 'development') === 'development',
59
59
  exitOnShutdown: null,
60
60
  runTasks: true,
61
+ testMode: false,
61
62
  }, _opts || {});
62
63
 
63
64
  Object.defineProperties(this, {
@@ -138,7 +139,7 @@ class Application extends EventEmitter {
138
139
  enumerable: false,
139
140
  configurable: true,
140
141
  value: this.createLogger(
141
- Object.assign({}, opts.logger || {}, this.getConfigValue('logger', {})),
142
+ Object.assign({}, this.getConfigValue('logger', {}), opts.logger || {}),
142
143
  Logger,
143
144
  ),
144
145
  },
@@ -777,12 +778,17 @@ class Application extends EventEmitter {
777
778
  }
778
779
 
779
780
  var sequelize = new Sequelize(databaseConfig);
781
+ var dbConnectionString;
782
+
783
+ if (Nife.instanceOf(databaseConfig, 'string'))
784
+ dbConnectionString = databaseConfig;
785
+ else
786
+ dbConnectionString = `${databaseConfig.dialect}://${databaseConfig.host}:${databaseConfig.port || '<default port>'}/${databaseConfig.database}`;
780
787
 
781
- var dbConnectionString = `${databaseConfig.dialect}://${databaseConfig.host}:${databaseConfig.port || '<default port>'}/${databaseConfig.database}`;
782
788
  try {
783
789
  await sequelize.authenticate();
784
790
 
785
- this.getLogger().log(`Connection to ${dbConnectionString} has been established successfully!`);
791
+ this.getLogger().info(`Connection to ${dbConnectionString} has been established successfully!`);
786
792
 
787
793
  return sequelize;
788
794
  } catch (error) {
@@ -803,6 +809,13 @@ class Application extends EventEmitter {
803
809
  return server;
804
810
  }
805
811
 
812
+ getDBTablePrefix(userSpecifiedPrefix) {
813
+ if (Nife.isNotEmpty(userSpecifiedPrefix))
814
+ return userSpecifiedPrefix;
815
+
816
+ return `${this.getApplicationName()}_`;
817
+ }
818
+
806
819
  async start() {
807
820
  var options = this.getOptions();
808
821
 
@@ -818,10 +831,13 @@ class Application extends EventEmitter {
818
831
  return;
819
832
  }
820
833
 
821
- databaseConfig.logging = (this.getLogger().isDebugLevel()) ? this.getLogger().log.bind(this.getLogger()) : false;
834
+ if (options.testMode) {
835
+ databaseConfig.logging = false;
836
+ } else {
837
+ databaseConfig.logging = (this.getLogger().isDebugLevel()) ? this.getLogger().log.bind(this.getLogger()) : false;
838
+ }
822
839
 
823
- if (Nife.isEmpty(databaseConfig.tablePrefix))
824
- databaseConfig.tablePrefix = `${this.getApplicationName()}_`;
840
+ databaseConfig.tablePrefix = this.getDBTablePrefix(databaseConfig.tablePrefix);
825
841
 
826
842
  this.dbConnection = await this.connectToDatabase(databaseConfig);
827
843
 
@@ -34,6 +34,11 @@ class CommandBase {
34
34
  var app = this.getApplication();
35
35
  return app.getLogger();
36
36
  }
37
+
38
+ getDBConnection() {
39
+ var application = this.getApplication();
40
+ return application.getDBConnection();
41
+ }
37
42
  }
38
43
 
39
44
  var loadingAllCommandsInProgress = false;
@@ -81,7 +81,7 @@ module.exports = defineCommand('migrate', ({ Parent }) => {
81
81
  var useTransaction = (args['transaction']) ? true : false;
82
82
  var rollback = args.rollback;
83
83
 
84
- console.log('USING TRANSACTION: ', useTransaction, args['no-transaction']);
84
+ console.log('USING TRANSACTION: ', useTransaction, args['transaction']);
85
85
 
86
86
  if (args.revision)
87
87
  migrationFiles = this.getMigrationFilesFromRevision(migrationFiles, args.revision);
@@ -0,0 +1,39 @@
1
+ const { defineCommand } = require('./cli-utils');
2
+ const { Logger } = require('../logger');
3
+
4
+ module.exports = defineCommand('routes', ({ Parent }) => {
5
+ return class RoutesCommand extends Parent {
6
+ static description = 'List application routes';
7
+ static applicationConfig = { logger: { level: Logger.ERROR } };
8
+
9
+ execute(args) {
10
+ const whitespaceOfLength = (len) => {
11
+ if (len < 0)
12
+ return '';
13
+
14
+ var parts = new Array(len);
15
+ for (var i = 0, il = parts.length; i < il; i++)
16
+ parts[i] = ' ';
17
+
18
+ return parts.join('');
19
+ };
20
+
21
+ const stringToLength = (str, len) => {
22
+ return `${str}${whitespaceOfLength(len - str.length)}`
23
+ };
24
+
25
+ var application = this.getApplication();
26
+ var routes = application.buildRoutes(null, application.getRoutes());
27
+
28
+ routes.forEach((route) => {
29
+ var methods = route.methods;
30
+ if (!methods === '*')
31
+ methods = [ '{ANY}' ];
32
+
33
+ methods.forEach((method) => {
34
+ console.log(`${stringToLength(method, 8)}${route.path} -> ${route.controller}`);
35
+ });
36
+ });
37
+ }
38
+ };
39
+ });
@@ -1,4 +1,5 @@
1
- const Nife = require('nife');
1
+ const Nife = require('nife');
2
+ const HTTPErrors = require('../http-server/http-errors');
2
3
 
3
4
  class ControllerBase {
4
5
  constructor(application, logger, request, response) {
@@ -27,6 +28,12 @@ class ControllerBase {
27
28
  configurable: true,
28
29
  value: logger,
29
30
  },
31
+ 'route': {
32
+ writable: true,
33
+ enumberable: false,
34
+ configurable: true,
35
+ value: null,
36
+ },
30
37
  });
31
38
  }
32
39
 
@@ -54,8 +61,31 @@ class ControllerBase {
54
61
  return application.getModels();
55
62
  }
56
63
 
57
- async handleIncomingRequest(request, response, { route, controller, controllerMethod, controllerInstance, startTime, params }) {
58
- return await this[controllerMethod].call(this, params, request.query || {}, request.body, this.getModels());
64
+ getDBConnection() {
65
+ var application = this.getApplication();
66
+ return application.getDBConnection();
67
+ }
68
+
69
+ throwNotFoundError(message) {
70
+ throw new HTTPErrors.HTTPNotFoundError(this.route, message);
71
+ }
72
+
73
+ throwBadRequestError(message) {
74
+ throw new HTTPErrors.HTTPBadRequestError(this.route, message);
75
+ }
76
+
77
+ throwUnauthorizedError(message) {
78
+ throw new HTTPErrors.HTTPUnauthorizedError(this.route, message);
79
+ }
80
+
81
+ throwInternalServerError(message) {
82
+ throw new HTTPErrors.HTTPInternalServerError(this.route, message);
83
+ }
84
+
85
+ async handleIncomingRequest(request, response, { route, controller, controllerMethod, controllerInstance, startTime, params, query }) {
86
+ this.route = route;
87
+
88
+ return await this[controllerMethod].call(this, { params, query, body: request.body, request, response, startTime }, this.getModels());
59
89
  }
60
90
 
61
91
  async handleOutgoingResponse(_controllerResult, request, response, { route, controller, controllerMethod, controllerInstance, startTime, params }) {
@@ -74,6 +104,7 @@ class ControllerBase {
74
104
  if (controllerResult == null)
75
105
  controllerResult = {};
76
106
 
107
+ response.header('Content-Type', 'application/json; charset=UTF-8');
77
108
  response.status(200).send(JSON.stringify(controllerResult));
78
109
  }
79
110
  }
@@ -20,8 +20,9 @@ const ROUTE_PROPERTIES = [
20
20
  'accept',
21
21
  'controller',
22
22
  'methods',
23
- 'middleWare',
23
+ 'middleware',
24
24
  'priority',
25
+ 'queryParams',
25
26
  ];
26
27
 
27
28
  function buildPatternMatcher(_patterns, _opts) {
@@ -281,6 +282,26 @@ function getRouteProperties(route) {
281
282
  function compileRoutes(routes, customParserTypes, _context) {
282
283
  const sortRoutes = (routes) => {
283
284
  return routes.sort((a, b) => {
285
+ var pathA = a.path;
286
+ var pathB = b.path;
287
+
288
+ if (Nife.arrayUnion(a.methods, b.methods).length > 0) {
289
+ // If the routes are at the same level
290
+ // but one of them has a capture parameter
291
+ // then the one without a capture parameter comes first.
292
+ // If we don't do this, then the capture parameter wild-card
293
+ // might match the route name of the non-parameter route
294
+ var lastForwardSlashIndexA = pathA.lastIndexOf('/');
295
+ var lastForwardSlashIndexB = pathB.lastIndexOf('/');
296
+
297
+ if (lastForwardSlashIndexA >= 0 && lastForwardSlashIndexB >= 0 && pathA.substring(0, lastForwardSlashIndexA) === pathB.substring(0, lastForwardSlashIndexB)) {
298
+ if (pathA.indexOf('<', lastForwardSlashIndexA) >= 0 && pathB.indexOf('<', lastForwardSlashIndexB) < 0)
299
+ return 1;
300
+ else if (pathA.indexOf('<', lastForwardSlashIndexA) < 0 && pathB.indexOf('<', lastForwardSlashIndexB) >= 0)
301
+ return -1;
302
+ }
303
+ }
304
+
284
305
  var x = a.priority;
285
306
  var y = b.priority;
286
307
 
@@ -349,9 +370,6 @@ function compileRoutes(routes, customParserTypes, _context) {
349
370
  if (!path)
350
371
  path = '/';
351
372
 
352
- if (routeName !== '/')
353
- path = `${(path)}/${routeName}`;
354
-
355
373
  path = path.replace(/\/{2,}/g, '/');
356
374
 
357
375
  var keys = Object.keys(routes);
@@ -370,10 +388,12 @@ function compileRoutes(routes, customParserTypes, _context) {
370
388
  continue;
371
389
 
372
390
  var thisRouteName = (isArray) ? routeName : key;
391
+ var newPath = (isArray) ? path : `${path}/${key}`;
392
+
373
393
  if (Nife.instanceOf(route, 'object', 'array')) {
374
394
  var subRoutes = compileRoutes(route, customParserTypes, {
375
395
  routeName: thisRouteName,
376
- path: path,
396
+ path: newPath,
377
397
  priority: i,
378
398
  depth: depth + 1,
379
399
  alreadyVisited,
@@ -25,10 +25,19 @@ class HTTPBadRequestError extends HTTPBaseError {
25
25
  constructor(route, message) {
26
26
  super(route, message, 400);
27
27
  }
28
+ }
29
+
30
+ class HTTPBadContentTypeError extends HTTPBaseError {
31
+ constructor(route, message) {
32
+ super(route, message, 400);
33
+ }
28
34
 
29
35
  getMessage() {
30
- var route = this.route;
31
- var accept = route.accept;
36
+ var route = this.route;
37
+ if (!route)
38
+ return this.message;
39
+
40
+ var accept = route.accept;
32
41
  if (!(accept instanceof Array))
33
42
  accept = [ accept ];
34
43
 
@@ -43,7 +52,10 @@ class HTTPBadRequestError extends HTTPBaseError {
43
52
  return `'${part}'`;
44
53
  });
45
54
 
46
- return `${this.message}: Accepted Content-Types are [ ${accept.join(', ')} ]`;
55
+ if (this.message)
56
+ return `${this.message}: Accepted Content-Types are [ ${accept.join(', ')} ]`;
57
+ else
58
+ return `Accepted Content-Types are [ ${accept.join(', ')} ]`;
47
59
  }
48
60
  }
49
61
 
@@ -63,6 +75,7 @@ module.exports = {
63
75
  HTTPBaseError,
64
76
  HTTPNotFoundError,
65
77
  HTTPBadRequestError,
78
+ HTTPBadContentTypeError,
66
79
  HTTPUnauthorizedError,
67
80
  HTTPInternalServerError,
68
81
  };
@@ -12,6 +12,7 @@ const {
12
12
  HTTPBaseError,
13
13
  HTTPNotFoundError,
14
14
  HTTPBadRequestError,
15
+ HTTPBadContentTypeError,
15
16
  HTTPInternalServerError,
16
17
  } = require('./http-errors');
17
18
 
@@ -23,7 +24,7 @@ class HTTPServer {
23
24
  constructor(application, _opts) {
24
25
  var appName = application.getApplicationName();
25
26
 
26
- var uploadPath = Path.resolve(OS.tmpdir(), 'appName', ('' + process.pid));
27
+ var uploadPath = Path.resolve(OS.tmpdir(), appName.replace(/[^\w-]/g, ''), ('' + process.pid));
27
28
 
28
29
  var opts = Nife.extend(true, {
29
30
  host: 'localhost',
@@ -102,46 +103,67 @@ class HTTPServer {
102
103
  this.routes = routes;
103
104
  }
104
105
 
105
- baseMiddleware(request, response, rootNext) {
106
- var middleware = this.middleware;
107
- if (!middleware || !middleware.length)
108
- return rootNext.call(this);
106
+ executeMiddleware(middleware, request, response) {
107
+ return new Promise((resolve, reject) => {
108
+ if (Nife.isEmpty(middleware))
109
+ return resolve();
109
110
 
110
- var application = this.getApplication();
111
- request.mythixApplication = application;
111
+ var application = this.getApplication();
112
+ if (!request.mythixApplication)
113
+ request.mythixApplication = application;
112
114
 
113
- var logger = request.mythixLogger = this.createRequestLogger(application, request);
115
+ var logger = request.mythixLogger;
116
+ if (!logger)
117
+ logger = request.mythixLogger = this.createRequestLogger(application, request);
114
118
 
115
- request.Sequelize = Sequelize;
119
+ if (!request.Sequelize)
120
+ request.Sequelize = Sequelize;
116
121
 
117
- var middlewareIndex = 0;
118
- const next = async () => {
119
- if (middlewareIndex >= middleware.length)
120
- return rootNext();
122
+ var middlewareIndex = 0;
123
+ const next = async () => {
124
+ if (middlewareIndex >= middleware.length)
125
+ return resolve();
121
126
 
122
- var middlewareFunc = middleware[middlewareIndex++];
127
+ var middlewareFunc = middleware[middlewareIndex++];
123
128
 
124
- try {
125
- await middlewareFunc.call(this, request, response, next);
126
- } catch (error) {
127
- var statusCode = error.statusCode || error.status_code || 500;
129
+ try {
130
+ await middlewareFunc.call(this, request, response, next);
131
+ } catch (error) {
132
+ var statusCode = error.statusCode || error.status_code || 500;
128
133
 
129
- if (error instanceof HTTPBaseError) {
130
- logger.log(`Error: ${statusCode} ${statusCodeToMessage(statusCode)}`);
131
- this.errorHandler(error.getMessage(), statusCode, response, request);
132
- } else {
133
- if (statusCode) {
134
+ if (error instanceof HTTPBaseError) {
134
135
  logger.log(`Error: ${statusCode} ${statusCodeToMessage(statusCode)}`);
135
- this.errorHandler(error.message, statusCode, response, request);
136
+ this.errorHandler(error.getMessage(), statusCode, response, request);
136
137
  } else {
137
- logger.log(`Error: ${error.message}`, error);
138
- this.errorHandler(error.message, 500, response, request);
138
+ if (statusCode) {
139
+ logger.log(`Error: ${statusCode} ${statusCodeToMessage(statusCode)}`);
140
+ this.errorHandler(error.message, statusCode, response, request);
141
+ } else {
142
+ logger.log(`Error: ${error.message}`, error);
143
+ this.errorHandler(error.message, 500, response, request);
144
+ }
139
145
  }
146
+
147
+ reject(error);
140
148
  }
141
- }
142
- };
149
+ };
143
150
 
144
- next();
151
+ next().catch(reject);
152
+ });
153
+ }
154
+
155
+ baseMiddleware(request, response, rootNext) {
156
+ var middleware = this.middleware;
157
+ if (Nife.isEmpty(middleware))
158
+ return rootNext();
159
+
160
+ this.executeMiddleware(middleware, request, response).then(
161
+ () => rootNext(),
162
+ (error) => {
163
+ if (!(error instanceof HTTPBaseError))
164
+ this.getApplication().error('Error in middleware: ', error);
165
+ }
166
+ );
145
167
  }
146
168
 
147
169
  findFirstMatchingRoute(request, _routes) {
@@ -160,7 +182,7 @@ class HTTPServer {
160
182
  return;
161
183
 
162
184
  if (typeof contentTypeMatcher === 'function' && !contentTypeMatcher(contentType))
163
- throw new HTTPBadRequestError(route);
185
+ throw new HTTPBadContentTypeError(route);
164
186
 
165
187
  return result;
166
188
  };
@@ -168,7 +190,7 @@ class HTTPServer {
168
190
  var routes = _routes || [];
169
191
  var method = request.method;
170
192
  var contentType = Nife.get(request, 'headers.content-type');
171
- var path = request.url;
193
+ var path = request.path;
172
194
 
173
195
  for (var i = 0, il = routes.length; i < il; i++) {
174
196
  var route = routes[i];
@@ -207,26 +229,81 @@ class HTTPServer {
207
229
 
208
230
  var logger = application.getLogger();
209
231
  var loggerMethod = ('' + request.method).toUpperCase();
210
- var loggerURL = ('' + request.url);
232
+ var loggerURL = ('' + request.path);
211
233
  var requestID = (Date.now() + Math.random()).toFixed(4);
212
234
  var ipAddress = Nife.get(request, 'client.remoteAddress', '<unknown IP address>');
213
235
 
214
236
  return logger.clone({ formatter: (output) => `{${ipAddress}} - [#${requestID} ${loggerMethod} ${loggerURL}]: ${output}`});
215
237
  }
216
238
 
217
- sendRequestToController(...args) {
239
+ validateQueryParam(route, query, paramName, queryValue, queryParams) {
240
+ var { validate } = queryParams;
241
+ if (!validate)
242
+ return true;
243
+
244
+ if (validate instanceof RegExp)
245
+ return !!('' + queryValue).match(validate);
246
+ else if (typeof validate === 'function')
247
+ return !!validate.call(route, queryValue, paramName, query, queryParams);
248
+
249
+ return true;
250
+ }
251
+
252
+ compileQueryParams(route, query, queryParams) {
253
+ var finalQuery = Object.assign({}, query || {});
254
+
255
+ var paramNames = Object.keys(queryParams || {});
256
+ for (var i = 0, il = paramNames.length; i < il; i++) {
257
+ var paramName = paramNames[i];
258
+ var queryParam = queryParams[paramName];
259
+ if (!queryParam)
260
+ continue;
261
+
262
+ var queryValue = finalQuery[paramName];
263
+ if (queryValue == null) {
264
+ if (queryParam.required)
265
+ throw new HTTPBadRequestError(route, `Query param "${paramName}" is required`);
266
+
267
+ if (queryParam.hasOwnProperty('defaultValue'))
268
+ finalQuery[paramName] = queryParam['defaultValue'];
269
+ } else {
270
+ if (!this.validateQueryParam(route, finalQuery, paramName, queryValue, queryParam))
271
+ throw new HTTPBadRequestError(route, `Query param "${paramName}" is invalid`);
272
+
273
+ if (queryParam.hasOwnProperty('type'))
274
+ finalQuery[paramName] = Nife.coerceValue(queryValue, queryParam['type']);
275
+ }
276
+ }
277
+
278
+ return finalQuery;
279
+ }
280
+
281
+ async sendRequestToController(...args) {
218
282
  var context = args[2];
219
283
  var controllerInstance = context.controllerInstance;
220
284
 
221
- return controllerInstance.handleIncomingRequest.apply(controllerInstance, args);
285
+ // Compile query params
286
+ context.query = this.compileQueryParams(context.route, context.query, (context.route && context.route.queryParams));
287
+
288
+ var route = context.route;
289
+
290
+ // Execute middleware if any exists
291
+ var middleware = (typeof controllerInstance.getMiddleware === 'function') ? controllerInstance.getMiddleware.call(controllerInstance, context) : [];
292
+ if (route && Nife.instanceOf(route.middleware, 'array') && Nife.isNotEmpty(route.middleware))
293
+ middleware = route.middleware.concat((middleware) ? middleware : []);
294
+
295
+ if (Nife.isNotEmpty(middleware))
296
+ await this.executeMiddleware(middleware, request, response);
297
+
298
+ return await controllerInstance.handleIncomingRequest.apply(controllerInstance, args);
222
299
  }
223
300
 
224
301
  async baseRouter(request, response, next) {
225
- var startTime = Nife.now();
302
+ var startTime = Nife.now();
303
+ var application = this.getApplication();
226
304
 
227
305
  try {
228
- var application = this.getApplication();
229
- var logger = this.createRequestLogger(application, request, { controller, controllerMethod });
306
+ var logger = this.createRequestLogger(application, request, { controller, controllerMethod });
230
307
 
231
308
  logger.log(`Starting request`);
232
309
 
@@ -250,6 +327,7 @@ class HTTPServer {
250
327
 
251
328
  var context = {
252
329
  params: request.params,
330
+ query: request.query,
253
331
  route,
254
332
  controller,
255
333
  controllerMethod,
@@ -259,13 +337,18 @@ class HTTPServer {
259
337
 
260
338
  var controllerResult = await this.sendRequestToController(request, response, context);
261
339
 
262
- if (!response.finished)
340
+ if (!(response.finished || response.statusMessage))
263
341
  await controllerInstance.handleOutgoingResponse(controllerResult, request, response, context);
342
+ else if (!response.finished)
343
+ response.end();
264
344
 
265
345
  var statusCode = response.statusCode || 200;
266
346
  var requestTime = Nife.now() - startTime;
267
347
  logger.log(`Completed request in ${requestTime.toFixed(3)}ms: ${statusCode} ${response.statusMessage || statusCodeToMessage(statusCode)}`);
268
348
  } catch (error) {
349
+ if ((error instanceof HTTPInternalServerError || !(error instanceof HTTPBaseError)) && application.getOptions().testMode)
350
+ console.error(error);
351
+
269
352
  try {
270
353
  var statusCode = error.statusCode || error.status_code || 500;
271
354
  var requestTime = Nife.now() - startTime;
@@ -305,14 +388,15 @@ class HTTPServer {
305
388
  }
306
389
 
307
390
  async start() {
308
- var options = this.getOptions();
309
- var app = this.createExpressApplication(options);
391
+ var options = this.getOptions();
392
+ var app = this.createExpressApplication(options);
393
+ var portString = (options.port) ? `:${options.port}` : '';
310
394
  var server;
311
395
 
312
396
  app.use(this.baseMiddleware.bind(this));
313
397
  app.all('*', this.baseRouter.bind(this));
314
398
 
315
- this.getLogger().log(`Starting ${(options.https) ? 'HTTPS' : 'HTTP'} server ${(options.https) ? 'https' : 'http'}://${options.host}:${options.port}...`);
399
+ this.getLogger().log(`Starting ${(options.https) ? 'HTTPS' : 'HTTP'} server ${(options.https) ? 'https' : 'http'}://${options.host}${portString}...`);
316
400
 
317
401
  if (options.https) {
318
402
  var credentials = await this.getHTTPSCredentials(options.https);
@@ -325,7 +409,8 @@ class HTTPServer {
325
409
 
326
410
  this.server = server;
327
411
 
328
- this.getLogger().log(`Web server listening at ${(options.https) ? 'https' : 'http'}://${options.host}:${options.port}`);
412
+ var listeningPort = server.address().port;
413
+ this.getLogger().log(`Web server listening at ${(options.https) ? 'https' : 'http'}://${options.host}:${listeningPort}`);
329
414
 
330
415
  return server;
331
416
  }
package/src/index.js CHANGED
@@ -8,22 +8,25 @@ const { Logger } = require('./logger');
8
8
  const Utils = require('./utils');
9
9
 
10
10
  module.exports = {
11
- defineCommand: CLIUtilsScope.defineCommand,
12
- defineController: ControllerScope.defineController,
13
- defineModel: Models.defineModel,
14
- defineTask: TasksScope.defineTask,
11
+ defineCommand: CLIUtilsScope.defineCommand,
12
+ defineController: ControllerScope.defineController,
13
+ defineModel: Models.defineModel,
14
+ defineTask: TasksScope.defineTask,
15
+ createTestApplication: Utils.TestUtils.createTestApplication,
15
16
 
16
- CLI: CLIUtilsScope,
17
- ControllerBase: ControllerScope.ControllerBase,
18
- Controllers: ControllerScope,
19
- CryptoUtils: Utils.CryptoUtils,
20
- HTTP: HTTPServerScope,
21
- HTTPErrors: HTTPServerScope.HTTPErrors,
22
- HTTPServer: HTTPServerScope.HTTPServer,
23
- HTTPUtils: Utils.HTTPUtils,
24
- Middleware: HTTPServerScope.Middleware,
25
- TaskBase: TasksScope.TaskBase,
26
- Tasks: TasksScope,
17
+ CLI: CLIUtilsScope,
18
+ ControllerBase: ControllerScope.ControllerBase,
19
+ Controllers: ControllerScope,
20
+ CryptoUtils: Utils.CryptoUtils,
21
+ HTTP: HTTPServerScope,
22
+ HTTPErrors: HTTPServerScope.HTTPErrors,
23
+ HTTPServer: HTTPServerScope.HTTPServer,
24
+ HTTPUtils: Utils.HTTPUtils,
25
+ Middleware: HTTPServerScope.Middleware,
26
+ TaskBase: TasksScope.TaskBase,
27
+ Tasks: TasksScope,
28
+ TestUtils: Utils.TestUtils,
29
+ Model: Models.Model,
27
30
 
28
31
  Application,
29
32
  Logger,
package/src/logger.js CHANGED
@@ -2,9 +2,10 @@ const Path = require('path');
2
2
  const FileSystem = require('fs');
3
3
 
4
4
  const LEVEL_ERROR = 1;
5
- const LEVEL_WARN = 2;
6
- const LEVEL_INFO = 3;
7
- const LEVEL_DEBUG = 4;
5
+ const LEVEL_LOG = 2;
6
+ const LEVEL_WARN = 3;
7
+ const LEVEL_INFO = 4;
8
+ const LEVEL_DEBUG = 5;
8
9
 
9
10
  function errorStackToString(rootPath, error) {
10
11
  return ('\n -> ' + error.stack.split(/\n+/).slice(1).map((part) => `${part.replace(/^\s+at\s+/, '')}\n`).join(' -> ')).trimEnd();
@@ -48,8 +49,11 @@ function logToWriter(type, ..._args) {
48
49
 
49
50
  try {
50
51
  arg = JSON.stringify(arg);
51
- } catch (e) {
52
- return '<LOGGER_ERROR>';
52
+ } catch (error) {
53
+ if (error.message = 'Converting circular structure to JSON')
54
+ return '<circular>';
55
+
56
+ return `<LOGGER_ERROR: ${error.message}>`;
53
57
  }
54
58
 
55
59
  return arg;
@@ -88,7 +92,7 @@ class Logger {
88
92
 
89
93
  Object.defineProperties(this, {
90
94
  '_level': {
91
- writable: false,
95
+ writable: true,
92
96
  enumerable: false,
93
97
  configurable: false,
94
98
  value: opts.level,
@@ -132,13 +136,18 @@ class Logger {
132
136
  });
133
137
  }
134
138
 
139
+ setLevel(level) {
140
+ this._level = level;
141
+ }
142
+
135
143
  clone(extraOpts) {
136
144
  return new this.constructor(Object.assign({
137
- level: this._level,
138
- writer: this._writer,
139
- pid: this._pid,
140
- formatter: this._formatter,
141
- rootPath: this._rootPath,
145
+ level: this._level,
146
+ writer: this._writer,
147
+ pid: this._pid,
148
+ formatter: this._formatter,
149
+ rootPath: this._rootPath,
150
+ errorStackFormatter: this._errorStackFormatter,
142
151
  }, extraOpts || {}));
143
152
  }
144
153
 
@@ -146,6 +155,10 @@ class Logger {
146
155
  return (this._level >= LEVEL_ERROR);
147
156
  }
148
157
 
158
+ isLogLevel() {
159
+ return (this._level >= LEVEL_LOG);
160
+ }
161
+
149
162
  isWarningLevel() {
150
163
  return (this._level >= LEVEL_WARN);
151
164
  }
@@ -179,7 +192,7 @@ class Logger {
179
192
  }
180
193
 
181
194
  log(...args) {
182
- if (this.isErrorLevel())
195
+ if (this.isLogLevel())
183
196
  logToWriter.call(this, 'log', ...args);
184
197
  }
185
198
 
@@ -201,6 +214,7 @@ class Logger {
201
214
 
202
215
  Object.assign(Logger, {
203
216
  ERROR: LEVEL_ERROR,
217
+ LOG: LEVEL_LOG,
204
218
  WARN: LEVEL_WARN,
205
219
  INFO: LEVEL_INFO,
206
220
  DEBUG: LEVEL_DEBUG,
@@ -34,14 +34,28 @@ function relationHelper(type) {
34
34
  const RELATION_HELPERS = {
35
35
  hasOne: relationHelper('hasOne'),
36
36
  belongsTo: relationHelper('belongsTo'),
37
- hasMany: relationHelper('belongsToMany'),
37
+ hasMany: relationHelper('hasMany'),
38
38
  belongsToMany: relationHelper('belongsToMany'),
39
39
  };
40
40
 
41
+ function preciseNow() {
42
+ var janFirst2022 = 1640995200000;
43
+ var now = Date.now() - janFirst2022;
44
+ var highResolutionNow = Nife.now();
45
+ var diff = Math.floor(highResolutionNow);
46
+
47
+ return Math.floor((now + (highResolutionNow - diff)) * 1000);
48
+ }
49
+
41
50
  function defineModel(modelName, definer, _parent) {
42
- function compileModelFields(Klass, DataTypes) {
51
+ function compileModelFields(Klass, DataTypes, application, connection) {
52
+ const createAutoIncrementor = () => {
53
+ return () => preciseNow();
54
+ };
55
+
43
56
  var fields = Klass.fields;
44
57
  var fieldNames = Object.keys(fields);
58
+ var isSQLIte = !!('' + Nife.get(connection, 'options.dialect')).match(/sqlite/);
45
59
 
46
60
  for (var i = 0, il = fieldNames.length; i < il; i++) {
47
61
  var fieldName = fieldNames[i];
@@ -52,6 +66,14 @@ function defineModel(modelName, definer, _parent) {
52
66
  field.field = columnName;
53
67
  }
54
68
 
69
+ // If using SQLite, which doesn't support autoincrement
70
+ // on anything except the primary key, then create our
71
+ // own auto-incrementor for this field
72
+ if (field.autoIncrement && isSQLIte && !field.primaryKey) {
73
+ application.getLogger().warn(`!Warning!: Using an auto-increment field in SQLite on a non-primary-key column "${field.field}"! Be aware that this functionality is now emulated using high resolution timestamps. This won't work unless the column is a BIGINT. You may run into serious problems with this emulation!`)
74
+ field.defaultValue = createAutoIncrementor();
75
+ }
76
+
55
77
  if (field.type === DataTypes.BIGINT) {
56
78
  if (!field.get) {
57
79
  field.get = function(name) {
@@ -80,15 +102,26 @@ function defineModel(modelName, definer, _parent) {
80
102
  return fields;
81
103
  }
82
104
 
83
- function cleanModelFields(Klass) {
105
+ function cleanModelFields(Klass, connection) {
84
106
  var finalFields = {};
85
107
  var fields = Klass.fields;
86
108
  var fieldNames = Object.keys(fields);
109
+ var isSQLIte = !!('' + Nife.get(connection, 'options.dialect')).match(/sqlite/);
87
110
 
88
111
  for (var i = 0, il = fieldNames.length; i < il; i++) {
89
112
  var fieldName = fieldNames[i];
90
113
  var field = fields[fieldName];
91
- var newField = Nife.extend(Nife.extend.FILTER, (key) => !key.match(/^(index)$/), {}, field);
114
+ var newField = Nife.extend(Nife.extend.FILTER, (key) => {
115
+ if (key.match(/^(index)$/))
116
+ return false;
117
+
118
+ // Strip "autoIncrement" if this is not the primary key
119
+ // and we are using sqlite for our dialect
120
+ if (key === 'autoIncrement' && isSQLIte && !field.primaryKey)
121
+ return false;
122
+
123
+ return true;
124
+ }, {}, field);
92
125
 
93
126
  finalFields[fieldName] = newField;
94
127
  }
@@ -135,7 +168,7 @@ function defineModel(modelName, definer, _parent) {
135
168
  }
136
169
 
137
170
  return function({ application, Sequelize, connection }) {
138
- var Klass = definer({
171
+ var definerArgs = {
139
172
  Parent: (_parent) ? _parent : Model,
140
173
  Type: Sequelize.DataTypes,
141
174
  Relation: RELATION_HELPERS,
@@ -143,19 +176,24 @@ function defineModel(modelName, definer, _parent) {
143
176
  connection,
144
177
  modelName,
145
178
  application,
146
- });
179
+ };
180
+
181
+ var Klass = definer(definerArgs);
147
182
 
148
183
  Klass.name = modelName;
149
184
 
185
+ if (typeof Klass.onModelClassCreate === 'function')
186
+ Klass = Klass.onModelClassCreate(Klass, definerArgs);
187
+
150
188
  var pluralName = (Klass.pluralName) ? Klass.pluralName : Inflection.pluralize(modelName);
151
189
  if (Klass.pluralName !== pluralName)
152
190
  Klass.pluralName = pluralName;
153
191
 
154
- Klass.fields = compileModelFields(Klass, Sequelize.DataTypes);
192
+ Klass.fields = compileModelFields(Klass, Sequelize.DataTypes, application, connection);
155
193
 
156
194
  var indexes = generateIndexes(Klass);
157
195
 
158
- Klass.fields = cleanModelFields(Klass);
196
+ Klass.fields = cleanModelFields(Klass, connection);
159
197
 
160
198
  var applicationOptions = application.getOptions();
161
199
  var tableName;
@@ -177,8 +215,8 @@ function defineModel(modelName, definer, _parent) {
177
215
  Klass.getPrimaryKeyField = getModelPrimaryKeyField.bind(this, Klass);
178
216
  Klass.getPrimaryKeyFieldName = () => (getModelPrimaryKeyField(Klass).field);
179
217
 
180
- if (typeof Klass.onModelClassCreate === 'function')
181
- Klass = Klass.onModelClassCreate(Klass);
218
+ if (typeof Klass.onModelClassFinalized === 'function')
219
+ Klass = Klass.onModelClassFinalized(Klass, definerArgs);
182
220
 
183
221
  return { [modelName]: Klass };
184
222
  };
@@ -11,6 +11,11 @@ class Model extends Sequelize.Model {
11
11
  return application.getLogger();
12
12
  }
13
13
 
14
+ getDBConnection() {
15
+ var application = this.getApplication();
16
+ return application.getDBConnection();
17
+ }
18
+
14
19
  getPrimaryKeyField() {
15
20
  return this.constructor.getPrimaryKeyField();
16
21
  }
@@ -20,12 +25,16 @@ class Model extends Sequelize.Model {
20
25
  }
21
26
 
22
27
  static prepareWhereStatement(conditions) {
23
- if (Nife.isEmpty(conditions))
28
+ if (Nife.isEmpty(conditions)) {
24
29
  return undefined;
30
+ }
31
+
32
+ if (conditions._mythixQuery)
33
+ return conditions;
25
34
 
26
35
  const Ops = Sequelize.Op;
27
36
  var finalQuery = {};
28
- var keys = Object.keys(conditions);
37
+ var keys = Object.keys(conditions).concat(Object.getOwnPropertySymbols(conditions));
29
38
 
30
39
  for (var i = 0, il = keys.length; i < il; i++) {
31
40
  var key = keys[i];
@@ -34,47 +43,134 @@ class Model extends Sequelize.Model {
34
43
  if (value === undefined)
35
44
  continue;
36
45
 
37
- if (value === null) {
38
- finalQuery[key] = { [Ops.is]: value };
46
+ var name = key;
47
+ var invert = false;
48
+
49
+ if (typeof name === 'string' && name.charAt(0) === '!') {
50
+ name = name.substring(1);
51
+ invert = true;
52
+ }
53
+
54
+ if (typeof key === 'symbol') {
55
+ finalQuery[key] = value;
56
+ } else if (value === null) {
57
+ finalQuery[name] = (invert) ? { [Ops.not]: value } : { [Ops.is]: value };
39
58
  } else if (Nife.instanceOf(value, 'number', 'string', 'boolean', 'bigint')) {
40
- finalQuery[key] = { [Ops.eq]: value };
59
+ finalQuery[name] = (invert) ? { [Ops.ne]: value } : { [Ops.eq]: value };
41
60
  } else if (Nife.instanceOf(value, 'array') && Nife.isNotEmpty(value)) {
42
- finalQuery[key] = { [Ops.in]: value };
61
+ finalQuery[name] = (invert) ? { [Ops.not]: { [Ops.in]: value } } : { [Ops.in]: value };
43
62
  } else if (Nife.isNotEmpty(value)) {
44
- finalQuery[key] = value;
63
+ if (invert)
64
+ throw new Error(`Model.prepareWhereStatement: Attempted to invert a custom matcher "${name}"`);
65
+
66
+ finalQuery[name] = value;
45
67
  }
46
68
  }
47
69
 
70
+ if (Nife.isEmpty(finalQuery))
71
+ return;
72
+
73
+ Object.defineProperties(finalQuery, {
74
+ '_mythixQuery': {
75
+ writable: false,
76
+ enumberable: false,
77
+ configurable: false,
78
+ value: true,
79
+ },
80
+ });
81
+
48
82
  return finalQuery;
49
83
  }
50
84
 
51
- static onModelClassCreate(Klass) {
52
- Klass.first = Klass.first.bind(this, Klass);
53
- Klass.last = Klass.last.bind(this, Klass);
85
+ static onModelClassFinalized(Klass) {
86
+ Klass.getDefaultOrderBy = Klass.getDefaultOrderBy.bind(this, Klass);
87
+ Klass.prepareQueryOptions = Klass.prepareQueryOptions.bind(this, Klass);
88
+ Klass.bulkUpdate = Klass.bulkUpdate.bind(this, Klass);
89
+ Klass.all = Klass.all.bind(this, Klass);
90
+ Klass.where = Klass.where.bind(this, Klass);
91
+ Klass.rowCount = Klass.rowCount.bind(this, Klass);
92
+ Klass.first = Klass.first.bind(this, Klass);
93
+ Klass.last = Klass.last.bind(this, Klass);
54
94
 
55
95
  return Klass;
56
96
  }
57
97
 
58
- static async first(Model, conditions, _order) {
59
- var options = {};
60
- var query = Model.prepareWhereStatement(conditions);
98
+ static getDefaultOrderBy(Model) {
99
+ return [ Model.getPrimaryKeyFieldName() ];
100
+ }
101
+
102
+ static prepareQueryOptions(Model, conditions, _order) {
103
+ const Ops = Sequelize.Op;
104
+ var options;
105
+ var query;
106
+
107
+ if (conditions && Nife.isNotEmpty(conditions.where)) {
108
+ query = conditions.where;
109
+ options = Object.assign({}, conditions);
110
+ } else if (conditions) {
111
+ query = Model.prepareWhereStatement(conditions);
112
+ }
113
+
114
+ var order = _order;
115
+ if (!options && Nife.instanceOf(order, 'object')) {
116
+ options = Object.assign({}, order);
117
+ order = undefined;
118
+ } else if (!options) {
119
+ options = {};
120
+ }
61
121
 
62
122
  if (Nife.isNotEmpty(query))
63
123
  options.where = query;
64
124
 
65
- var order = _order;
66
- if (!order)
67
- order = [ Model.getPrimaryKeyFieldName() ];
125
+ if (!order && options.defaultOrder !== false) {
126
+ if (options.order) {
127
+ order = options.order;
128
+ } else {
129
+ if (typeof Model.getDefaultOrderBy === 'function')
130
+ order = Model.getDefaultOrderBy();
131
+ else
132
+ order = [ Model.getPrimaryKeyFieldName() ];
133
+ }
134
+ }
68
135
 
69
136
  options.order = order;
137
+ if (!options.hasOwnProperty('distinct'))
138
+ options.distinct = true;
139
+
140
+ // If no "where" clause was specified, then grab everything
141
+ if (!options.where)
142
+ options.where = { [Model.getPrimaryKeyFieldName()]: { [Ops.not]: null } };
143
+
144
+ if (options.debug)
145
+ console.log('QUERY OPTIONS: ', options);
146
+
147
+ return options;
148
+ }
149
+
150
+ static where(Model, conditions) {
151
+ return Model.prepareWhereStatement(conditions);
152
+ }
153
+
154
+ static async rowCount(Model, conditions, options) {
155
+ return await Model.count(Model.prepareQueryOptions(conditions, options));
156
+ }
157
+
158
+ static async bulkUpdate(Model, attrs, conditions) {
159
+ return await Model.update(attrs, Model.prepareQueryOptions(conditions, { distinct: false }));
160
+ }
161
+
162
+ static async all(Model, conditions, order) {
163
+ return await Model.findAll(Model.prepareQueryOptions(conditions, order));
164
+ }
70
165
 
71
- return await Model.findOne(options);
166
+ static async first(Model, conditions, order) {
167
+ return await Model.findOne(Model.prepareQueryOptions(conditions, order));
72
168
  }
73
169
 
74
170
  static async last(Model, conditions, _order) {
75
171
  var order = _order;
76
172
  if (!order)
77
- order = [ Model.getPrimaryKeyFieldName(), 'DESC' ];
173
+ order = [ 'createdAt', 'DESC' ];
78
174
 
79
175
  return await Model.first(conditions, order);
80
176
  }
@@ -94,7 +94,7 @@ class TaskBase {
94
94
 
95
95
  if (workers > 1) {
96
96
  var shift = (frequency / workers);
97
- startDelay = Math.round(startDelay + (shift * taskIndex));
97
+ startDelay = startDelay + (shift * taskIndex);
98
98
  }
99
99
 
100
100
  return startDelay;
@@ -72,7 +72,7 @@ class TimeHelpers {
72
72
  }
73
73
 
74
74
  totalMilliseconds() {
75
- return this.totalSeconds() * 1000;
75
+ return Math.round(this.totalSeconds() * 1000);
76
76
  }
77
77
  }
78
78
 
@@ -59,9 +59,12 @@ function makeRequest(requestOptions) {
59
59
  var url = new URL(requestOptions.url);
60
60
  var data = (!method.match(/^(GET|HEAD)$/i) && requestOptions.data) ? requestOptions.data : undefined;
61
61
  var extraConfig = {};
62
+ var headers = Object.assign({
63
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
64
+ }, defaultHeaders || {}, requestOptions.headers || {});
62
65
 
63
66
  if (data) {
64
- if (Nife.get(requestOptions, 'headers.Content-Type').match(/application\/json/))
67
+ if (Nife.get(headers, 'Content-Type', '').match(/application\/json/))
65
68
  data = JSON.stringify(data);
66
69
 
67
70
  extraConfig = {
@@ -77,9 +80,7 @@ function makeRequest(requestOptions) {
77
80
  port: url.port,
78
81
  path: `${url.pathname}${url.search}`,
79
82
  method,
80
- headers: Object.assign({
81
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
82
- }, defaultHeaders || {}),
83
+ headers,
83
84
  }, requestOptions, extraConfig);
84
85
 
85
86
  delete options.data;
@@ -7,11 +7,13 @@ const {
7
7
 
8
8
  const HTTPUtils = require('./http-utils');
9
9
  const CryptoUtils = require('./crypto-utils');
10
+ const TestUtils = require('./test-utils');
10
11
 
11
12
  module.exports = {
12
13
  CryptoUtils,
13
14
  fileNameWithoutExtension,
14
15
  HTTPUtils,
16
+ TestUtils,
15
17
  walkDir,
16
18
  wrapConfig,
17
19
  };
@@ -0,0 +1,161 @@
1
+ const Nife = require('nife');
2
+ const { Logger } = require('../logger');
3
+ const HTTPUtils = require('./http-utils');
4
+
5
+ function createTestApplication(Application) {
6
+ const Klass = class TestApplication extends Application {
7
+ static APP_NAME = `${(Nife.isNotEmpty(Application.APP_NAME)) ? Application.APP_NAME : 'mythix'}_test`;
8
+
9
+ constructor(_opts) {
10
+ var opts = Nife.extend(true, {
11
+ autoReload: false,
12
+ runTasks: false,
13
+ testMode: true,
14
+ }, _opts || {});
15
+
16
+ super(opts);
17
+ }
18
+
19
+ getDBTablePrefix(userSpecifiedPrefix) {
20
+ var prefix = super.getDBTablePrefix(userSpecifiedPrefix);
21
+ return `${prefix.replace(/_test/g, '')}_test_`.replace(/_+/g, '_');
22
+ }
23
+
24
+ getTestingDatabaseConfig() {
25
+ return { dialect: 'sqlite', logging: false };
26
+ }
27
+
28
+ getTestingHTTPServerConfig(options) {
29
+ return Object.assign({}, options || {}, {
30
+ host: 'localhost',
31
+ port: 0, // Select a random port
32
+ https: false,
33
+ });
34
+ }
35
+
36
+ getTestingLoggerConfig(loggerOpts) {
37
+ return Object.assign({}, loggerOpts, {
38
+ level: Logger.ERROR,
39
+ writer: null,
40
+ });
41
+ }
42
+
43
+ createLogger(loggerOpts, Logger) {
44
+ return super.createLogger(this.getTestingLoggerConfig(loggerOpts), Logger);
45
+ }
46
+
47
+ async connectToDatabase() {
48
+ var connection = await super.connectToDatabase(this.getTestingDatabaseConfig());
49
+ return connection;
50
+ }
51
+
52
+ async createHTTPServer(options) {
53
+ var httpServerConfig = this.getTestingHTTPServerConfig(options);
54
+ var server = await super.createHTTPServer(httpServerConfig);
55
+
56
+ Object.defineProperties(this, {
57
+ 'host': {
58
+ writable: true,
59
+ enumberable: false,
60
+ configurable: true,
61
+ value: {
62
+ hostname: httpServerConfig.host,
63
+ port: server.server.address().port,
64
+ },
65
+ },
66
+ });
67
+
68
+ this.setDefaultURL(`http://${this.host.hostname}:${this.host.port}/`);
69
+
70
+ return server;
71
+ }
72
+
73
+ async start(...args) {
74
+ var result = await super.start(...args);
75
+
76
+ var dbConnection = this.getDBConnection();
77
+ await dbConnection.sync({ force: true, logging: false });
78
+
79
+ return result;
80
+ }
81
+
82
+ async truncateAllTables(exclude) {
83
+ var dbConnection = this.getDBConnection();
84
+ var models = this.getModels();
85
+ var modelNames = Object.keys(models);
86
+
87
+ await dbConnection.query('PRAGMA foreign_keys = OFF');
88
+
89
+ try {
90
+ for (var i = 0, il = modelNames.length; i < il; i++) {
91
+ var modelName = modelNames[i];
92
+ if (exclude && exclude.indexOf(modelName) >= 0)
93
+ continue;
94
+
95
+ var model = models[modelName];
96
+ await dbConnection.query(`DELETE FROM ${model.tableName}`);
97
+ }
98
+ } catch (error) {
99
+ await dbConnection.query('PRAGMA foreign_keys = ON');
100
+ }
101
+ }
102
+
103
+ getDefaultURL(...args) {
104
+ return HTTPUtils.getDefaultURL(...args);
105
+ }
106
+
107
+ setDefaultURL(...args) {
108
+ return HTTPUtils.setDefaultURL(...args);
109
+ }
110
+
111
+ async getDefaultHeader(...args) {
112
+ return HTTPUtils.getDefaultHeader(...args);
113
+ }
114
+
115
+ async getDefaultHeaders(...args) {
116
+ return HTTPUtils.getDefaultHeaders(...args);
117
+ }
118
+
119
+ async setDefaultHeader(...args) {
120
+ return HTTPUtils.setDefaultHeader(...args);
121
+ }
122
+
123
+ async setDefaultHeaders(...args) {
124
+ return HTTPUtils.setDefaultHeaders(...args);
125
+ }
126
+
127
+ async request(...args) {
128
+ return HTTPUtils.request(...args);
129
+ }
130
+
131
+ async get(...args) {
132
+ return HTTPUtils.get(...args);
133
+ }
134
+
135
+ async post(...args) {
136
+ return HTTPUtils.post(...args);
137
+ }
138
+
139
+ async put(...args) {
140
+ return HTTPUtils.put(...args);
141
+ }
142
+
143
+ async delete(...args) {
144
+ return HTTPUtils.delete(...args);
145
+ }
146
+
147
+ async head(...args) {
148
+ return HTTPUtils.head(...args);
149
+ }
150
+
151
+ async options(...args) {
152
+ return HTTPUtils.options(...args);
153
+ }
154
+ };
155
+
156
+ return Klass;
157
+ }
158
+
159
+ module.exports = {
160
+ createTestApplication,
161
+ };