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 +1 -1
- package/src/cli/routes-command.js +6 -2
- package/src/controllers/routes/route-endpoint.d.ts +8 -0
- package/src/controllers/routes/route-endpoint.js +43 -6
- package/src/controllers/routes/route-scope-base.js +20 -3
- package/src/controllers/routes/route-scope.d.ts +1 -1
- package/src/controllers/routes/route-scope.js +5 -2
- package/src/http-server/http-server.js +26 -3
package/package.json
CHANGED
|
@@ -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}]${(
|
|
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 =
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
321
|
-
let application
|
|
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(
|
|
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);
|