mythix 2.9.0 → 2.10.0

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": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Mythix is a NodeJS web-app framework",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -1,5 +1,6 @@
1
1
  import { ConnectionBase, ModelClass, Models } from 'mythix-orm';
2
2
  import { ControllerClass } from './controllers/controller-base';
3
+ import { RouteScope } from './controllers/routes/route-scope';
3
4
  import { HTTPServer } from './http-server/http-server';
4
5
  import { GenericObject } from './interfaces/common';
5
6
  import { Logger, LoggerClass, LoggerOptions } from './logger';
@@ -54,7 +55,8 @@ export declare class Application {
54
55
  public getConfig(): GenericObject;
55
56
  public setConfig(options: GenericObject): Application;
56
57
  public getApplicationName(): string;
57
- public getRoutes(): GenericObject;
58
+ public _getRoutes(): RouteScope;
59
+ public getRoutes(context: RouteScope): void;
58
60
  public getCustomRouteParserTypes(): { [key: string]: (value: string, param: GenericObject, index?: number) => any };
59
61
  public createLogger(loggerOptions: LoggerOptions, LoggerClass: LoggerClass): Logger;
60
62
  public getLogger(): Logger;
@@ -15,6 +15,7 @@ const { ControllerModule } = require('./controllers/controller-module');
15
15
  const { TaskModule } = require('./tasks/task-module');
16
16
  const { FileWatcherModule } = require('./modules/file-watcher-module');
17
17
  const { wrapConfig } = require('./utils');
18
+ const ControllerRoutes = require('./controllers/routes');
18
19
 
19
20
  // Trace what is requesting the application exit
20
21
 
@@ -272,6 +273,12 @@ class Application extends EventEmitter {
272
273
  return this;
273
274
  }
274
275
 
276
+ _getRoutes() {
277
+ let routeScope = new ControllerRoutes.RouteScope();
278
+ this.getRoutes(routeScope);
279
+ return routeScope;
280
+ }
281
+
275
282
  getRoutes() {
276
283
  throw new Error('Error: child application expected to implement "getRoutes" method');
277
284
  }
@@ -78,6 +78,17 @@ class CommandBase {
78
78
  });
79
79
 
80
80
  childProcess.on('error', (error) => {
81
+ if (options && options.ignoreExitCode) {
82
+ resolve({
83
+ stdout: Buffer.concat(output).toString('utf8'),
84
+ stderr: Buffer.concat(errors).toString('utf8'),
85
+ code: 0,
86
+ error,
87
+ });
88
+
89
+ return;
90
+ }
91
+
81
92
  reject({
82
93
  stdout: Buffer.concat(output).toString('utf8'),
83
94
  stderr: Buffer.concat(errors).toString('utf8'),
@@ -68,6 +68,10 @@ module.exports = defineCommand('deploy', ({ Parent }) => {
68
68
  try {
69
69
  return await super.spawnCommand(command, args, options);
70
70
  } catch (error) {
71
+ console.error('Error with command: ', { dryRun, command, args, options }, error);
72
+ if (error.stderr)
73
+ console.error(error.stderr);
74
+
71
75
  if (error instanceof Error)
72
76
  throw error;
73
77
 
@@ -1,10 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ const Nife = require('nife');
3
4
  const { defineCommand } = require('./cli-utils');
4
5
  const { Logger } = require('../logger');
5
- const { buildRoutes } = require('../controllers/controller-utils');
6
-
7
- const TAB_SIZE = 8;
8
6
 
9
7
  module.exports = defineCommand('routes', ({ Parent }) => {
10
8
  return class RoutesCommand extends Parent {
@@ -19,40 +17,19 @@ module.exports = defineCommand('routes', ({ Parent }) => {
19
17
  };
20
18
  }
21
19
 
22
- buildRoutes(httpServer, routes) {
23
- let application = this.getApplication();
24
- let customParserTypes = application.getCustomRouteParserTypes(httpServer, routes);
25
-
26
- return buildRoutes(routes, customParserTypes);
27
- }
28
-
29
20
  execute() {
30
- const whitespaceOfLength = (len) => {
31
- if (len < 0)
32
- return '';
33
-
34
- let parts = new Array(len);
35
- for (let i = 0, il = parts.length; i < il; i++)
36
- parts[i] = ' ';
37
-
38
- return parts.join('');
39
- };
40
-
41
- const stringToLength = (str, len) => {
42
- return `${str}${whitespaceOfLength(len - str.length)}`;
43
- };
44
-
45
21
  let application = this.getApplication();
46
- let routes = this.buildRoutes(null, application.getRoutes());
22
+ let routes = application._getRoutes();
23
+ let whitespace = ' ';
47
24
 
48
- routes.forEach((route) => {
49
- let methods = route.methods;
50
- if (!methods === '*')
51
- methods = [ '{ANY}' ];
25
+ console.log(`${application.getApplicationName()} routes:`);
52
26
 
53
- methods.forEach((method) => {
54
- console.log(`${stringToLength(method, TAB_SIZE)}${route.path} -> ${route.controller}`);
55
- });
27
+ routes.walkRoutes(({ endpoint }) => {
28
+ let methods = endpoint.methods;
29
+ for (let i = 0, il = methods.length; i < il; i++) {
30
+ let method = methods[i];
31
+ console.log(` ${method}${whitespace.substring(0, whitespace.length - method.length)}/${endpoint.path} -> [${endpoint.controller}]${(endpoint.isDynamic) ? ' (dynamic)' : ''}`);
32
+ }
56
33
  });
57
34
  }
58
35
  };
@@ -7,5 +7,4 @@ export class ControllerModule extends BaseModule {
7
7
  public getControllerFilePaths(controllersPath: string): Array<string>;
8
8
  public loadControllers(controllersPath: string): ControllerClasses;
9
9
  public getController(name: string): { controller: ControllerClass, controllerMethod: string | undefined };
10
- public buildRoutes(httpServer: HTTPServer, routes: Array<GenericObject>): Array<GenericObject>;
11
10
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const Nife = require('nife');
4
4
  const { BaseModule } = require('../modules/base-module');
5
- const { buildRoutes } = require('./controller-utils');
6
5
  const {
7
6
  fileNameWithoutExtension,
8
7
  walkDir,
@@ -108,22 +107,14 @@ class ControllerModule extends BaseModule {
108
107
  };
109
108
  }
110
109
 
111
- buildRoutes(httpServer, routes) {
112
- let application = this.getApplication();
113
- let customParserTypes = application.getCustomRouteParserTypes(httpServer, routes);
114
-
115
- return buildRoutes(routes, customParserTypes);
116
- }
117
-
118
110
  async start(options) {
119
- let application = this.getApplication();
120
- let httpServer = (typeof application.getHTTPServer === 'function') ? application.getHTTPServer() : null;
121
- let controllers = await this.loadControllers(options.controllersPath);
111
+ let application = this.getApplication();
112
+ let httpServer = (typeof application.getHTTPServer === 'function') ? application.getHTTPServer() : null;
113
+ let controllers = await this.loadControllers(options.controllersPath);
122
114
 
123
115
  this.controllers = controllers;
124
116
 
125
- let routes = await this.buildRoutes(httpServer, application.getRoutes());
126
- httpServer.setRoutes(routes);
117
+ httpServer.setRoutes(application._getRoutes());
127
118
  }
128
119
 
129
120
  async stop() {
@@ -1,34 +1,7 @@
1
1
  import { Application } from '../application';
2
2
  import { HTTPServer } from '../http-server';
3
- import { GenericObject } from '../interfaces/common';
4
3
  import { ControllerClass } from './controller-base';
5
4
 
6
- export declare interface BuildPatternMatcherOptions {
7
- strict?: boolean;
8
- sanitize?: (value: string) => string;
9
- flags?: string;
10
- }
11
-
12
- export declare interface PatternMatcherMethod {
13
- (value: string): boolean;
14
- regexp: RegExp;
15
- directPatterns: Array<RegExp>;
16
- }
17
-
18
- export declare interface PathMatcherMethod {
19
- (pathPart: string): GenericObject | undefined;
20
- regexp: RegExp;
21
- params: Array<string>;
22
- sanitizedPath: string;
23
- }
24
-
25
- export declare type RoutePatterns = string | RegExp | Array<string | RegExp>;
26
-
27
- export declare function buildPatternMatcher(
28
- patterns: RoutePatterns,
29
- options?: BuildPatternMatcherOptions
30
- ): PatternMatcherMethod;
31
-
32
5
  export declare interface DefineControllerContext<T = ControllerClass> {
33
6
  Parent: T;
34
7
  application: Application;
@@ -36,11 +9,6 @@ export declare interface DefineControllerContext<T = ControllerClass> {
36
9
  controllerName: string;
37
10
  }
38
11
 
39
- export declare function buildMethodMatcher(patterns: RoutePatterns): PatternMatcherMethod;
40
- export declare function buildContentTypeMatcher(patterns: RoutePatterns): PatternMatcherMethod;
41
- export declare function buildPathMatcher(routeName: string, customParserTypes: GenericObject): PathMatcherMethod;
42
- export declare function buildRoutes(routes: GenericObject | Array<GenericObject>, customParserTypes?: GenericObject): Array<GenericObject>;
43
-
44
12
  export declare function defineController<T = ControllerClass>(
45
13
  controllerName: string,
46
14
  definer: (context: DefineControllerContext<T>) => ControllerClass,
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
- const { ControllerBase } = require('./controller-base');
3
+ const { ControllerBase } = require('./controller-base');
5
4
 
6
5
  function defineController(controllerName, definer, _parent) {
7
6
  let parentKlass = _parent || ControllerBase;
@@ -20,411 +19,6 @@ function defineController(controllerName, definer, _parent) {
20
19
  };
21
20
  }
22
21
 
23
- const ROUTE_PROPERTIES = [
24
- 'name',
25
- 'accept',
26
- 'controller',
27
- 'methods',
28
- 'middleware',
29
- 'priority',
30
- 'queryParams',
31
- 'clientOptions',
32
- ];
33
-
34
- function buildPatternMatcher(_patterns, _opts) {
35
- let opts = _opts || {};
36
- let patterns = _patterns;
37
- let sanitizeFunc = opts.sanitize;
38
- let strict = opts.strict;
39
- let flags = (Nife.instanceOf(opts.flags, 'string') && Nife.isNotEmpty(opts.flags)) ? opts.flags : 'i';
40
- let matchRE;
41
- let matchFunc;
42
-
43
- if (Nife.instanceOf(patterns, 'array'))
44
- patterns = Nife.uniq(patterns);
45
-
46
- if (patterns === '*' || (Nife.instanceOf(patterns, 'array') && patterns.indexOf('*') >= 0) || patterns instanceof RegExp) {
47
- matchRE = (patterns instanceof RegExp) ? patterns : /.*/i;
48
-
49
- matchFunc = function patternMatcher(value) {
50
- if (!value || !Nife.instanceOf(value, 'string'))
51
- return true;
52
-
53
- return !!value.match(matchRE);
54
- };
55
-
56
- Object.defineProperties(matchFunc, {
57
- 'regexp': {
58
- writable: false,
59
- enumerable: false,
60
- configurable: false,
61
- value: matchRE,
62
- },
63
- 'directPatterns': {
64
- writable: false,
65
- enumerable: false,
66
- configurable: false,
67
- value: [],
68
- },
69
- });
70
-
71
- return matchFunc;
72
- }
73
-
74
- if (!Nife.instanceOf(patterns, 'array'))
75
- patterns = [ patterns ];
76
-
77
- let parts = [];
78
- let directPatterns = [];
79
-
80
- for (let i = 0, il = patterns.length; i < il; i++) {
81
- let part = patterns[i];
82
-
83
- if (part instanceof RegExp) {
84
- directPatterns.push(part);
85
- continue;
86
- }
87
-
88
- if (typeof sanitizeFunc === 'function') {
89
- part = sanitizeFunc(part);
90
- } else {
91
- part = part.replace(/\*/g, '@@@WILD_MATCH@@@');
92
- part = Nife.regexpEscape(part);
93
- part = part.replace(/@@@WILD_MATCH@@@/g, '.*?');
94
- }
95
-
96
- parts.push(part);
97
- }
98
-
99
- if (parts && parts.length)
100
- matchRE = new RegExp((strict) ? `^(${parts.join('|')})$` : `(${parts.join('|')})`, flags);
101
-
102
- matchFunc = function patternMatcher(value) {
103
- if (!value || !Nife.instanceOf(value, 'string'))
104
- return false;
105
-
106
- if (directPatterns && directPatterns.length) {
107
- for (let j = 0, jl = directPatterns.length; j < jl; j++) {
108
- let pattern = directPatterns[j];
109
- if (value.match(pattern))
110
- return true;
111
- }
112
- }
113
-
114
- if (!matchRE)
115
- return false;
116
-
117
- return !!value.match(matchRE);
118
- };
119
-
120
- Object.defineProperties(matchFunc, {
121
- 'regexp': {
122
- writable: false,
123
- enumerable: false,
124
- configurable: false,
125
- value: matchRE,
126
- },
127
- 'directPatterns': {
128
- writable: false,
129
- enumerable: false,
130
- configurable: false,
131
- value: directPatterns,
132
- },
133
- });
134
-
135
- return matchFunc;
136
- }
137
-
138
- function buildMethodMatcher(methods) {
139
- return buildPatternMatcher(methods, { strict: true });
140
- }
141
-
142
- function buildContentTypeMatcher(contentTypePatterns) {
143
- return buildPatternMatcher(contentTypePatterns);
144
- }
145
-
146
- function buildPathMatcher(routeName, customParserTypes) {
147
- let params = [];
148
- let parts = [];
149
- let lastOffset = 0;
150
-
151
- let sanitizedPath = routeName.replace(/<\s*([^\s:]+?\??)\s*(:\w+?)?\s*(=\s*[^>]+)?>/g, function(m, _name, _type, _defaultValue, offset, str) {
152
- if (offset > lastOffset) {
153
- parts.push(str.substring(lastOffset, offset));
154
- lastOffset = offset + m.length;
155
- }
156
-
157
- let defaultValue = _defaultValue;
158
- let type = _type;
159
- let optional = false;
160
- let name = _name;
161
-
162
- if (name.match(/\?$/)) {
163
- optional = true;
164
- name = name.substring(0, name.length - 1);
165
- }
166
-
167
- if (type)
168
- type = type.replace(/\W/g, '');
169
-
170
- if (defaultValue) {
171
- defaultValue = defaultValue.trim().replace(/^=\s*/, '');
172
- defaultValue = Nife.coerceValue(defaultValue, type);
173
- }
174
-
175
- let matcher;
176
-
177
- if (type === 'number' || type === 'int' || type === 'integer' || type === 'bigint')
178
- matcher = '([\\d.e-]+)';
179
- else if (type === 'boolean' || type === 'bool')
180
- matcher = '(true|True|TRUE|false|False|FALSE)';
181
- else
182
- matcher = (optional) ? '(.*?)' : '(.+?)';
183
-
184
- if (optional)
185
- matcher = matcher + '?';
186
-
187
- let param = {
188
- startOffset: offset,
189
- endOffset: offset + m.length,
190
- name,
191
- type,
192
- defaultValue,
193
- matcher,
194
- optional,
195
- };
196
-
197
- params.push(param);
198
- parts.push(param);
199
-
200
- return `<<${name}${(optional) ? '?' : ''}>>`;
201
- });
202
-
203
- if (lastOffset < routeName.length)
204
- parts.push(routeName.substring(lastOffset));
205
-
206
- let finalRegExpStr = parts.reduce((items, _item, index) => {
207
- let item = _item;
208
-
209
- if (typeof item === 'string') {
210
- item = item.replace(/\?/g, '@@@CHAR_MATCH@@@').replace(/\*/g, '@@@WILD_MATCH@@@').replace(/\/+/g, '@@@FORWARD_SLASH@@@');
211
- item = Nife.regexpEscape(item);
212
- item = item.replace(/@@@CHAR_MATCH@@@/g, '.').replace(/@@@WILD_MATCH@@@/g, '.*?').replace(/@@@FORWARD_SLASH@@@/g, '/');
213
-
214
- if (item.match(/\/$/)) {
215
- let nextItem = parts[index + 1];
216
- if (nextItem && typeof nextItem !== 'string' && nextItem.optional)
217
- item = item + '?';
218
- }
219
-
220
- items.push(item);
221
- } else {
222
- items.push(item.matcher);
223
- }
224
-
225
- return items;
226
- }, []).join('');
227
-
228
- let matcherRE = new RegExp(`^${finalRegExpStr}$`);
229
- let matchFunc = function routeMatcher(pathPart) {
230
- let match = pathPart.match(matcherRE);
231
- if (!match)
232
- return;
233
-
234
- let result = {};
235
- for (let i = 1, il = match.length; i < il; i++) {
236
- let part = match[i];
237
- if (!part)
238
- continue;
239
-
240
- let paramIndex = i - 1;
241
- let param = params[paramIndex];
242
- if (!param)
243
- continue;
244
-
245
- if (customParserTypes && Object.prototype.hasOwnProperty.call(customParserTypes, param.type))
246
- result[param.name] = customParserTypes[param.type](part, param, paramIndex);
247
- else
248
- result[param.name] = Nife.coerceValue(part, param.type);
249
- }
250
-
251
- return result;
252
- };
253
-
254
- Object.defineProperties(matchFunc, {
255
- 'regexp': {
256
- writable: false,
257
- enumerable: false,
258
- configurable: false,
259
- value: matcherRE,
260
- },
261
- 'params': {
262
- writable: false,
263
- enumerable: false,
264
- configurable: false,
265
- value: params,
266
- },
267
- 'sanitizedPath': {
268
- writable: false,
269
- enumerable: false,
270
- configurable: false,
271
- value: sanitizedPath,
272
- },
273
- });
274
-
275
- return matchFunc;
276
- }
277
-
278
- function compileRoutes(routes, customParserTypes, _context) {
279
- const addRoute = (theseRoutes, route, path, priority) => {
280
- let newRoute = Object.assign(
281
- {
282
- methods: 'GET',
283
- accept: '*',
284
- priority,
285
- },
286
- route || {},
287
- {
288
- path: path.replace(/\/{2,}/g, '/'),
289
- },
290
- );
291
-
292
- if (Nife.instanceOf(newRoute.methods, 'array')) {
293
- newRoute.methods = Nife
294
- .uniq(newRoute.methods)
295
- .filter((method) => ((typeof method === 'string' || method instanceof String) && Nife.isNotEmpty(method)))
296
- .map((method) => method.toUpperCase());
297
-
298
- if (newRoute.methods.indexOf('*') >= 0)
299
- newRoute.methods = '*';
300
- } else {
301
- if (Nife.instanceOf(newRoute.methods, 'string') || Nife.isEmpty(newRoute.methods))
302
- newRoute.methods = 'GET';
303
-
304
- newRoute.methods = newRoute.methods.toUpperCase();
305
- }
306
-
307
- theseRoutes.push(newRoute);
308
- };
309
-
310
- let context = _context || {};
311
- let theseRoutes = [];
312
- let isArray = (routes instanceof Array);
313
- let {
314
- routeName,
315
- path,
316
- alreadyVisited,
317
- depth,
318
- } = context;
319
-
320
- if (!alreadyVisited)
321
- alreadyVisited = new Map();
322
-
323
- if (alreadyVisited.get(routes))
324
- return [];
325
-
326
- alreadyVisited.set(routes, true);
327
-
328
- if (!depth)
329
- depth = 0;
330
-
331
- if (Nife.isEmpty(routeName))
332
- routeName = '/';
333
-
334
- if (!path)
335
- path = '/';
336
-
337
- path = path.replace(/\/{2,}/g, '/');
338
-
339
- // eslint-disable-next-line no-magic-numbers
340
- let basePriority = 1000000 - (depth * 1000);
341
- let keys = Object.keys(routes);
342
-
343
- for (let i = 0, il = keys.length; i < il; i++) {
344
- let key = keys[i];
345
- let route = routes[key];
346
-
347
- if (route && Nife.isNotEmpty(route.controller)) {
348
- let newPath = (isArray) ? path : `${path}/${key}`;
349
- addRoute(theseRoutes, route, newPath, basePriority + i);
350
- }
351
-
352
- if (ROUTE_PROPERTIES.indexOf(key) >= 0 || Nife.instanceOf(route, 'boolean', 'string', 'number', 'bigint'))
353
- continue;
354
-
355
- let thisRouteName = (isArray) ? routeName : key;
356
- let newPath = (isArray) ? path : `${path}/${key}`;
357
-
358
- if (Nife.instanceOf(route, 'object', 'array')) {
359
- let subRoutes = compileRoutes(route, customParserTypes, {
360
- routeName: thisRouteName,
361
- path: newPath,
362
- priority: i,
363
- depth: depth + 1,
364
- alreadyVisited,
365
- });
366
-
367
- if (!subRoutes || !subRoutes.length)
368
- continue;
369
-
370
- theseRoutes = [].concat(subRoutes, theseRoutes);
371
- }
372
- }
373
-
374
- return theseRoutes;
375
- }
376
-
377
- function buildRoutes(_routes, customParserTypes) {
378
- const sortRoutes = (routesToSort) => {
379
- return routesToSort.sort((a, b) => {
380
- // We convert "<" to "{" and ">" to "}"
381
- // to get the desired sort order...
382
- // This is so that capture parameters
383
- // always come last.
384
- const mangle = (str) => {
385
- return str.replace(/</g, '{').replace(/>/g, '}');
386
- };
387
-
388
- // We pad the priority number so as not to get:
389
- // 100
390
- // 10
391
- // 1
392
- // sort order funkiness
393
-
394
- // eslint-disable-next-line no-magic-numbers
395
- let pathA = mangle(`${a.path}${('' + a.priority).padStart(12, '0')}`);
396
- // eslint-disable-next-line no-magic-numbers
397
- let pathB = mangle(`${b.path}${('' + b.priority).padStart(12, '0')}`);
398
-
399
- if (pathA === pathB)
400
- return 0;
401
-
402
- return (pathA < pathB) ? -1 : 1;
403
- });
404
- };
405
-
406
- let routes = compileRoutes(_routes, customParserTypes);
407
-
408
- routes = sortRoutes(routes);
409
-
410
- return routes.map((route) => {
411
- // Filter out priority key
412
- let thisRoute = Nife.extend(Nife.extend.FILTER, (key) => !key.match(/^(priority)$/), {}, route);
413
-
414
- // Inject route matchers
415
- thisRoute.methodMatcher = buildMethodMatcher(thisRoute.methods || '*');
416
- thisRoute.contentTypeMatcher = buildContentTypeMatcher(thisRoute.accept || '*');
417
- thisRoute.pathMatcher = buildPathMatcher(thisRoute.path, customParserTypes);
418
-
419
- return thisRoute;
420
- });
421
- }
422
-
423
22
  module.exports = {
424
- buildPatternMatcher,
425
- buildMethodMatcher,
426
- buildContentTypeMatcher,
427
- buildPathMatcher,
428
- buildRoutes,
429
23
  defineController,
430
24
  };