mythix 2.11.0 → 2.11.2

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.11.0",
3
+ "version": "2.11.2",
4
4
  "description": "Mythix is a NodeJS web-app framework",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const Nife = require('nife');
4
3
  const { defineCommand } = require('./cli-utils');
5
4
  const { Logger } = require('../logger');
6
5
 
@@ -26,9 +25,14 @@ module.exports = defineCommand('routes', ({ Parent }) => {
26
25
 
27
26
  routes.walkRoutes(({ endpoint }) => {
28
27
  let methods = endpoint.methods;
28
+ let flags = [
29
+ (endpoint.isDynamic) ? 'dynamic' : '',
30
+ (endpoint.cors) ? 'COR' : '',
31
+ ].filter(Boolean).join(', ');
32
+
29
33
  for (let i = 0, il = methods.length; i < il; i++) {
30
34
  let method = methods[i];
31
- console.log(` ${method}${whitespace.substring(0, whitespace.length - method.length)}/${endpoint.path} -> [${endpoint.controller}]${(endpoint.isDynamic) ? ' (dynamic)' : ''}`);
35
+ console.log(` ${method}${whitespace.substring(0, whitespace.length - method.length)}/${endpoint.path} -> [${endpoint.controller}]${(flags) ? ` (${flags})` : ''}`);
32
36
  }
33
37
  });
34
38
  }
@@ -3,6 +3,13 @@ import { RouteScopeBase } from './route-scope-base';
3
3
 
4
4
  export declare type EndpointMethods = 'GET' | 'PUT' | 'POST' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | '*';
5
5
 
6
+ export declare interface EndpointCORsOptions {
7
+ allowOrigin: string;
8
+ allowMethods?: string | Array<string>;
9
+ allowHeaders?: string | Array<string>;
10
+ maxAge?: number;
11
+ }
12
+
6
13
  export declare interface EndpointOptions {
7
14
  name?: string;
8
15
  methods?: Array<EndpointMethods> | EndpointMethods;
@@ -12,6 +19,7 @@ export declare interface EndpointOptions {
12
19
  help?: GenericObject;
13
20
  queryParams?: GenericObject;
14
21
  middleware?: Array<Function>;
22
+ cors?: boolean | EndpointCORsOptions;
15
23
  [key: string | symbol]: any;
16
24
  }
17
25
 
@@ -4,6 +4,15 @@ const Nife = require('nife');
4
4
 
5
5
  class RouteEndpoint {
6
6
  constructor(parentScope, attributes) {
7
+ const mapMethods = (methods) => {
8
+ return Nife.arrayFlatten(Nife.toArray(methods || []).filter(Boolean).map((method) => {
9
+ if (method === '*')
10
+ return [ 'GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'HEAD' ];
11
+
12
+ return ('' + method).toUpperCase();
13
+ }));
14
+ };
15
+
7
16
  Object.defineProperties(this, {
8
17
  '_parentScope': {
9
18
  writable: false,
@@ -28,12 +37,8 @@ class RouteEndpoint {
28
37
  attributes,
29
38
  );
30
39
 
31
- this.methods = Nife.arrayFlatten(Nife.toArray(this.methods || []).filter(Boolean).map((method) => {
32
- if (method === '*')
33
- return [ 'GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS' ];
34
-
35
- return ('' + method).toUpperCase();
36
- }));
40
+ this.methods = mapMethods(this.methods);
41
+ this.methods = Nife.uniq(this.methods.concat('OPTIONS'));
37
42
 
38
43
  this.contentType = Nife.arrayFlatten(Nife.toArray(this.contentType || []).filter(Boolean).map((contentType) => {
39
44
  if (contentType === '*')
@@ -47,6 +52,38 @@ class RouteEndpoint {
47
52
 
48
53
  if (Nife.isEmpty(this.contentType))
49
54
  this.contentType = null;
55
+
56
+ let cors = this.cors;
57
+ if (cors) {
58
+ if (cors === true)
59
+ cors = this.cors = {};
60
+
61
+ if (cors.allowOrigin == null)
62
+ cors.allowOrigin = '*';
63
+
64
+ if (cors.allowMethods == null)
65
+ cors.allowMethods = this.methods;
66
+
67
+ if (cors.allowHeaders == null) {
68
+ cors.allowHeaders = [
69
+ 'DNT',
70
+ 'User-Agent',
71
+ 'X-Requested-With',
72
+ 'If-Modified-Since',
73
+ 'Cache-Control',
74
+ 'Content-Type',
75
+ 'Range',
76
+ ];
77
+ }
78
+
79
+ if (cors.maxAge == null)
80
+ cors.maxAge = 86400;
81
+
82
+ cors.allowMethods = mapMethods(cors.allowMethods).join(',');
83
+
84
+ if (Array.isArray(cors.allowHeaders))
85
+ cors.allowHeaders = cors.allowHeaders.filter(Boolean).join(',');
86
+ }
50
87
  }
51
88
 
52
89
  getParentScope = () => {
@@ -5,7 +5,7 @@ const { RouteCapture } = require('./route-capture');
5
5
  const { RouteEndpoint } = require('./route-endpoint');
6
6
 
7
7
  class RouteScopeBase {
8
- constructor(parentScope, pathParts) {
8
+ constructor(parentScope, pathParts, options) {
9
9
  Object.defineProperties(this, {
10
10
  '_parentScope': {
11
11
  writable: false,
@@ -19,6 +19,12 @@ class RouteScopeBase {
19
19
  configurable: false,
20
20
  value: pathParts || [],
21
21
  },
22
+ '_options': {
23
+ writable: true,
24
+ enumerable: false,
25
+ configurable: true,
26
+ value: Nife.extend(true, {}, ((parentScope && parentScope.getOptions()) || {}), (options || {})),
27
+ },
22
28
  '_routes': {
23
29
  writable: true,
24
30
  enumerable: false,
@@ -44,6 +50,15 @@ class RouteScopeBase {
44
50
  return this._parentScope;
45
51
  };
46
52
 
53
+ getOptions = () => {
54
+ return this._options;
55
+ };
56
+
57
+ updateOptions = (newOptions) => {
58
+ let options = this.getOptions();
59
+ this._options = Nife.extend(true, {}, (newOptions || {}));
60
+ };
61
+
47
62
  addRoute(pathPart, route) {
48
63
  let set = this._routes.get(pathPart);
49
64
  if (!set) {
@@ -60,8 +75,10 @@ class RouteScopeBase {
60
75
  return;
61
76
 
62
77
  let firstRoute = Array.from(set.values()).find((route) => {
63
- if (route instanceof RouteScopeBase)
64
- return true;
78
+ if (route instanceof RouteScopeBase) {
79
+ if (route._pathParts && route._pathParts[route._pathParts.length - 1] === pathPart)
80
+ return true;
81
+ }
65
82
 
66
83
  return false;
67
84
  });
@@ -4,7 +4,7 @@ import { EndpointOptions } from './route-endpoint';
4
4
  import { PathPart, RouteScopeBase } from './route-scope-base';
5
5
 
6
6
  export declare class RouteScope extends RouteScopeBase {
7
- public path(pathPart: PathPart, callback: (context: RouteScope) => void): void;
7
+ public path(pathPart: PathPart, callback: (context: RouteScope) => void, options?: GenericObject): void;
8
8
  public endpoint(pathPart: PathPart, options: EndpointOptions | string): void;
9
9
  public capture(paramName: string, _helperOrOptions?: RouteCaptureHelper | GenericObject, _options?: RouteCaptureOptions): RouteCapture;
10
10
  }
@@ -6,7 +6,7 @@ const { RouteScopeBase } = require('./route-scope-base');
6
6
  const { RouteEndpoint } = require('./route-endpoint');
7
7
 
8
8
  class RouteScope extends RouteScopeBase {
9
- path = (_pathPart, callback) => {
9
+ path = (_pathPart, callback, options) => {
10
10
  let pathPart = _pathPart;
11
11
  if (!Nife.instanceOf(pathPart, 'string') && !(pathPart instanceof RouteCapture))
12
12
  throw new TypeError('RouteScope::path: First argument must be a string or a capture.');
@@ -16,9 +16,11 @@ class RouteScope extends RouteScopeBase {
16
16
 
17
17
  let routeScope = this.getPathRoute(pathPart);
18
18
  if (!routeScope) {
19
- routeScope = new RouteScope(this, this._pathParts.concat(pathPart));
19
+ routeScope = new RouteScope(this, this._pathParts.concat(pathPart), options);
20
20
  routeScope.isDynamic = this.isDynamic || this.isDynamicPathPart(pathPart);
21
21
  this.addRoute(pathPart, routeScope);
22
+ } else if (options) {
23
+ routeScope.updateOptions(options);
22
24
  }
23
25
 
24
26
  callback(routeScope);
@@ -32,6 +34,7 @@ class RouteScope extends RouteScopeBase {
32
34
  throw new TypeError('RouteScope::endpoint: Endpoint "options" required.');
33
35
 
34
36
  let options = (Nife.instanceOf(_options, 'string')) ? { controller: ('' + _options) } : { ..._options };
37
+ options = Nife.extend(true, {}, (this.getOptions() || {}).endpointDefaults || {}, options);
35
38
  options.path = this._pathParts.concat(pathPart).join('/');
36
39
 
37
40
  let endpoint = new RouteEndpoint(this, options);
@@ -317,17 +317,40 @@ class HTTPServer {
317
317
  }
318
318
 
319
319
  async baseRouter(request, response, next) {
320
- let startTime = Nife.now();
321
- let application = this.getApplication();
320
+ let startTime = Nife.now();
321
+ let application = this.getApplication();
322
+ let requestMethod = ('' + request.method).toUpperCase();
322
323
  let controllerInstance;
323
324
  let logger;
324
325
 
325
326
  try {
326
327
  logger = this.createRequestLogger(application, request);
327
- logger.info('Starting request');
328
+ logger.info(`Starting request: ${requestMethod} ${decodeURIComponent(request.path)}`);
328
329
 
329
330
  let { endpoint, params } = (this.findFirstMatchingRoute(request, this.routes) || {});
330
331
 
332
+ // CORS
333
+ let cors = endpoint.cors;
334
+ if (!cors && requestMethod === 'OPTIONS') {
335
+ response.header('Content-Type', 'text/plain; charset=UTF-8');
336
+ response.status(403);
337
+ response.end();
338
+
339
+ let requestTime = Nife.now() - startTime;
340
+ logger.log(`Completed request in ${requestTime.toFixed(REQUEST_TIME_RESOLUTION)}ms: 403 Forbidden`);
341
+
342
+ return;
343
+ }
344
+
345
+ if (cors) {
346
+ response.header('Access-Control-Allow-Origin', cors.allowOrigin);
347
+ response.header('Access-Control-Allow-Methods', cors.allowMethods);
348
+ response.header('Access-Control-Allow-Headers', cors.allowHeaders);
349
+
350
+ if (cors.maxAge != null)
351
+ response.header('Access-Control-Max-Age', cors.maxAge);
352
+ }
353
+
331
354
  request.params = params || {};
332
355
 
333
356
  let _controller = this.getRouteController(endpoint.controller, endpoint, params, request);