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 +1 -1
- package/src/application.d.ts +3 -1
- package/src/application.js +7 -0
- package/src/cli/cli-utils.js +11 -0
- package/src/cli/deploy-command.js +4 -0
- package/src/cli/routes-command.js +10 -33
- package/src/controllers/controller-module.d.ts +0 -1
- package/src/controllers/controller-module.js +4 -13
- package/src/controllers/controller-utils.d.ts +0 -32
- package/src/controllers/controller-utils.js +1 -407
- package/src/controllers/generate-client-api-interface.js +44 -58
- package/src/controllers/index.d.ts +1 -0
- package/src/controllers/index.js +3 -10
- package/src/controllers/routes/index.d.ts +4 -0
- package/src/controllers/routes/index.js +31 -0
- package/src/controllers/routes/route-capture.d.ts +32 -0
- package/src/controllers/routes/route-capture.js +99 -0
- package/src/controllers/routes/route-endpoint.d.ts +26 -0
- package/src/controllers/routes/route-endpoint.js +59 -0
- package/src/controllers/routes/route-scope-base.d.ts +29 -0
- package/src/controllers/routes/route-scope-base.js +196 -0
- package/src/controllers/routes/route-scope.d.ts +10 -0
- package/src/controllers/routes/route-scope.js +48 -0
- package/src/http-server/http-errors.js +7 -7
- package/src/http-server/http-server.d.ts +3 -1
- package/src/http-server/http-server.js +24 -37
- package/src/models/model.js +1 -1
- package/src/http-server/stop.js +0 -528
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/* global Buffer, Utils, globalScope */
|
|
4
4
|
|
|
5
|
-
const Nife
|
|
6
|
-
const
|
|
7
|
-
const HTTPUtils = require('../utils/http-utils');
|
|
5
|
+
const Nife = require('nife');
|
|
6
|
+
const HTTPUtils = require('../utils/http-utils');
|
|
8
7
|
|
|
9
8
|
function tabIn(str, amount) {
|
|
10
9
|
var padding = ''.padStart((amount || 1) * 2, ' ');
|
|
@@ -20,11 +19,6 @@ function tabIn(str, amount) {
|
|
|
20
19
|
});
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function buildRoutes(httpServer, routes) {
|
|
24
|
-
var customParserTypes = this.getCustomRouteParserTypes(httpServer, routes);
|
|
25
|
-
return ControllerUtils.buildRoutes(routes, customParserTypes);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
function nodeRequestHandler(routeName, requestOptions) {
|
|
29
23
|
return new Promise((function(resolve, reject) {
|
|
30
24
|
if (!requestOptions || Utils.isEmpty(requestOptions.url)) {
|
|
@@ -163,15 +157,22 @@ function browserRequestHandler(routeName, requestOptions) {
|
|
|
163
157
|
var data = requestOptions.data;
|
|
164
158
|
var extraConfig = {};
|
|
165
159
|
var headers = Object.assign(Utils.keysToLowerCase(this.defaultHeaders || {}), Utils.keysToLowerCase(requestOptions.headers || {}));
|
|
160
|
+
var isFormData = (data && (data instanceof FormData || data.constructor.name === 'FormData'));
|
|
166
161
|
|
|
167
162
|
if (data) {
|
|
168
163
|
if (!method.match(/^(GET|HEAD)$/i)) {
|
|
169
|
-
if (
|
|
170
|
-
|
|
164
|
+
if (isFormData) {
|
|
165
|
+
extraConfig = {
|
|
166
|
+
body: data,
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
if ((headers['content-type'] || '').match(/application\/json/i))
|
|
170
|
+
data = JSON.stringify(data);
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
extraConfig = {
|
|
173
|
+
body: data,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
175
176
|
} else {
|
|
176
177
|
var queryString = Utils.dataToQueryString(data);
|
|
177
178
|
if (queryString)
|
|
@@ -193,6 +194,8 @@ function browserRequestHandler(routeName, requestOptions) {
|
|
|
193
194
|
);
|
|
194
195
|
|
|
195
196
|
delete options.data;
|
|
197
|
+
if (isFormData && options.headers)
|
|
198
|
+
delete options.headers['content-type'];
|
|
196
199
|
|
|
197
200
|
globalScope.fetch(url, Utils.cleanObjectProperties(options)).then(
|
|
198
201
|
async function(response) {
|
|
@@ -298,45 +301,31 @@ function generateUtils() {
|
|
|
298
301
|
`;
|
|
299
302
|
}
|
|
300
303
|
|
|
301
|
-
function generateRoutes(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (options.routeFilter) {
|
|
308
|
-
var routeFilter = options.routeFilter;
|
|
309
|
-
|
|
310
|
-
if (typeof routeFilter === 'function') {
|
|
311
|
-
routes = routes.filter(routeFilter);
|
|
312
|
-
} else if (routeFilter instanceof RegExp) {
|
|
313
|
-
routes = routes.filter((route) => {
|
|
314
|
-
return routeFilter.test(route.path);
|
|
315
|
-
});
|
|
316
|
-
} else if (Nife.instanceOf(routeFilter, 'string')) {
|
|
317
|
-
routes = routes.filter((route) => {
|
|
318
|
-
return (route.path.indexOf(routeFilter) >= 0);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
304
|
+
function generateRoutes(applicationRoutes, _options) {
|
|
305
|
+
let options = _options || {};
|
|
306
|
+
let methods = {};
|
|
307
|
+
let domain = options.domain;
|
|
308
|
+
let routeFilter = options.routeFilter;
|
|
322
309
|
|
|
323
310
|
if (Nife.isEmpty(domain))
|
|
324
311
|
domain = '';
|
|
325
312
|
else
|
|
326
313
|
domain = ('' + domain).replace(/\/+$/, '');
|
|
327
314
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
315
|
+
applicationRoutes.walkRoutes((context) => {
|
|
316
|
+
if (typeof routeFilter === 'function') {
|
|
317
|
+
if (!routeFilter(context))
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
333
320
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
321
|
+
let { endpoint } = context;
|
|
322
|
+
let methodName = endpoint.name;
|
|
323
|
+
if (Nife.isEmpty(methodName))
|
|
324
|
+
return;
|
|
337
325
|
|
|
326
|
+
let clientOptions = endpoint.clientOptions;
|
|
338
327
|
if (clientOptions == null) {
|
|
339
|
-
|
|
328
|
+
let contentType = endpoint.contentType;
|
|
340
329
|
if (Nife.isEmpty(contentType))
|
|
341
330
|
contentType = 'application/json';
|
|
342
331
|
else if (Array.isArray(contentType))
|
|
@@ -361,15 +350,13 @@ function generateRoutes(_routes, _options) {
|
|
|
361
350
|
return value;
|
|
362
351
|
});
|
|
363
352
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
methods[methodName] = { method: `function ${methodName}(_options) { var clientOptions = ${clientOptions}; var options = Object.assign({ url: '${url}', method: '${defaultMethod}' }, defaultRouteOptions, clientOptions, Object.assign({}, _options || {}, { headers: Object.assign({}, defaultRouteOptions.headers || {}, clientOptions.headers || {}, ((_options || {}).headers) || {}) })); options.url = Utils.injectURLParams('${methodName}', options); delete options.params; return makeRequest.call(this, '${methodName}', options); }`, route };
|
|
372
|
-
}
|
|
353
|
+
let defaultMethod = endpoint.methods[0];
|
|
354
|
+
let url = `${domain}/${endpoint.path.replace(/^\/+/, '')}`;
|
|
355
|
+
methods[methodName] = {
|
|
356
|
+
method: `function ${methodName}(_options) { var clientOptions = ${clientOptions}; var options = Object.assign({ url: '${url}', method: '${defaultMethod}' }, defaultRouteOptions, clientOptions, Object.assign({}, _options || {}, { headers: Object.assign({}, defaultRouteOptions.headers || {}, clientOptions.headers || {}, ((_options || {}).headers) || {}) })); options.url = Utils.injectURLParams('${methodName}', options); delete options.params; return makeRequest.call(this, '${methodName}', options); }`,
|
|
357
|
+
endpoint,
|
|
358
|
+
};
|
|
359
|
+
});
|
|
373
360
|
|
|
374
361
|
return methods;
|
|
375
362
|
}
|
|
@@ -498,10 +485,10 @@ function generateAPIInterface(routes, _options) {
|
|
|
498
485
|
var routeMethods = generateRoutes(routes, options);
|
|
499
486
|
var routeMethodNames = Object.keys(routeMethods).sort();
|
|
500
487
|
var injectMethodsStr = routeMethodNames.map((methodName) => {
|
|
501
|
-
var info
|
|
502
|
-
var method
|
|
503
|
-
var
|
|
504
|
-
var help
|
|
488
|
+
var info = routeMethods[methodName];
|
|
489
|
+
var method = info.method;
|
|
490
|
+
var endpoint = info.endpoint;
|
|
491
|
+
var help = endpoint.help;
|
|
505
492
|
|
|
506
493
|
if (!help || options.mode !== 'development')
|
|
507
494
|
help = '{}';
|
|
@@ -647,8 +634,7 @@ function generateAPIInterface(routes, _options) {
|
|
|
647
634
|
|
|
648
635
|
function generateClientAPIInterface(application, _options) {
|
|
649
636
|
var options = _options || {};
|
|
650
|
-
var
|
|
651
|
-
var routes = buildRoutes.call(application, httpServer, application.getRoutes());
|
|
637
|
+
var routes = application._getRoutes();
|
|
652
638
|
var globalName = (Object.prototype.hasOwnProperty.call(options, 'globalName')) ? options.globalName : '';
|
|
653
639
|
|
|
654
640
|
if (Nife.isNotEmpty(globalName))
|
package/src/controllers/index.js
CHANGED
|
@@ -5,22 +5,15 @@ const { ControllerModule } = require('./controller-module');
|
|
|
5
5
|
const { generateClientAPIInterface } = require('./generate-client-api-interface');
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
|
-
buildPatternMatcher,
|
|
9
|
-
buildMethodMatcher,
|
|
10
|
-
buildContentTypeMatcher,
|
|
11
|
-
buildPathMatcher,
|
|
12
|
-
buildRoutes,
|
|
13
8
|
defineController,
|
|
14
9
|
} = require('./controller-utils');
|
|
15
10
|
|
|
11
|
+
const Routes = require('./routes');
|
|
12
|
+
|
|
16
13
|
module.exports = {
|
|
17
14
|
ControllerBase,
|
|
18
15
|
ControllerModule,
|
|
19
|
-
|
|
20
|
-
buildMethodMatcher,
|
|
21
|
-
buildContentTypeMatcher,
|
|
22
|
-
buildPathMatcher,
|
|
23
|
-
buildRoutes,
|
|
16
|
+
Routes,
|
|
24
17
|
defineController,
|
|
25
18
|
generateClientAPIInterface,
|
|
26
19
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
RouteScopeBase,
|
|
5
|
+
} = require('./route-scope-base');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
RouteScope,
|
|
9
|
+
} = require('./route-scope');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
RouteCapture,
|
|
13
|
+
} = require('./route-capture');
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
RouteEndpoint,
|
|
17
|
+
} = require('./route-endpoint');
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
// Route scope base
|
|
21
|
+
RouteScopeBase,
|
|
22
|
+
|
|
23
|
+
// Route scope
|
|
24
|
+
RouteScope,
|
|
25
|
+
|
|
26
|
+
// Route capture
|
|
27
|
+
RouteCapture,
|
|
28
|
+
|
|
29
|
+
// Route endpoint
|
|
30
|
+
RouteEndpoint,
|
|
31
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GenericObject } from '../../interfaces/common';
|
|
2
|
+
import { RouteScopeBase } from './route-scope-base';
|
|
3
|
+
|
|
4
|
+
export declare interface RouteCaptureOptions {
|
|
5
|
+
type?: string;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare interface RouteCaptureHelperContext {
|
|
10
|
+
value: string;
|
|
11
|
+
request: Request;
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
contentType: string | undefined;
|
|
15
|
+
params: GenericObject;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare type RouteCaptureHelper = ((context: RouteCaptureHelperContext) => any) | RegExp;
|
|
19
|
+
|
|
20
|
+
export declare class RouteCapture {
|
|
21
|
+
declare _parentScope: RouteScopeBase;
|
|
22
|
+
declare _paramName: string;
|
|
23
|
+
declare _helper: RouteCaptureHelper | null;
|
|
24
|
+
declare _options: RouteCaptureOptions;
|
|
25
|
+
|
|
26
|
+
public constructor(parentScope: RouteScopeBase, paramName: string, _helperOrOptions?: RouteCaptureHelper | GenericObject, _options?: RouteCaptureOptions);
|
|
27
|
+
public getParentScope(): RouteScopeBase;
|
|
28
|
+
public getName(): string;
|
|
29
|
+
public isOptional(): boolean;
|
|
30
|
+
public clone(newOptions?: RouteCaptureOptions): RouteCapture;
|
|
31
|
+
public matches(context: RouteCaptureHelperContext): any;
|
|
32
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Nife = require('nife');
|
|
4
|
+
|
|
5
|
+
class RouteCapture {
|
|
6
|
+
constructor(parentScope, paramName, _helperOrOptions, _options) {
|
|
7
|
+
let helper;
|
|
8
|
+
let options;
|
|
9
|
+
|
|
10
|
+
if (!Nife.instanceOf(paramName, 'string'))
|
|
11
|
+
throw new TypeError('RouteCapture::constructor: "paramName" is required to be a string.');
|
|
12
|
+
|
|
13
|
+
if (typeof _helperOrOptions === 'function' || _helperOrOptions instanceof RegExp) {
|
|
14
|
+
helper = _helperOrOptions;
|
|
15
|
+
options = _options || {};
|
|
16
|
+
} else {
|
|
17
|
+
options = _helperOrOptions || {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Object.defineProperties(this, {
|
|
21
|
+
'_parentScope': {
|
|
22
|
+
writable: false,
|
|
23
|
+
enumerable: false,
|
|
24
|
+
configurable: false,
|
|
25
|
+
value: parentScope || null,
|
|
26
|
+
},
|
|
27
|
+
'_paramName': {
|
|
28
|
+
writable: false,
|
|
29
|
+
enumerable: false,
|
|
30
|
+
configurable: false,
|
|
31
|
+
value: paramName,
|
|
32
|
+
},
|
|
33
|
+
'_helper': {
|
|
34
|
+
writable: false,
|
|
35
|
+
enumerable: false,
|
|
36
|
+
configurable: false,
|
|
37
|
+
value: helper || null,
|
|
38
|
+
},
|
|
39
|
+
'_options': {
|
|
40
|
+
writable: false,
|
|
41
|
+
enumerable: false,
|
|
42
|
+
configurable: false,
|
|
43
|
+
value: options,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getParentScope = () => {
|
|
49
|
+
return this._parentScope;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
getName() {
|
|
53
|
+
return this._paramName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isOptional() {
|
|
57
|
+
return !!this._options.optional;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clone(newOptions) {
|
|
61
|
+
let options = Object.assign({}, this._options, newOptions || {});
|
|
62
|
+
|
|
63
|
+
if (this._helper)
|
|
64
|
+
return new this.constructor(this._parentScope, this._paramName, this._helper, options);
|
|
65
|
+
else
|
|
66
|
+
return new this.constructor(this._parentScope, this._paramName, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
matches(context) {
|
|
70
|
+
let helper = this._helper;
|
|
71
|
+
if (typeof helper === 'function') {
|
|
72
|
+
return helper(context);
|
|
73
|
+
} else if (helper instanceof RegExp) {
|
|
74
|
+
let result = context.value.match(helper);
|
|
75
|
+
if (!result)
|
|
76
|
+
return;
|
|
77
|
+
|
|
78
|
+
return (result && result.groups) ? result.groups : result[0];
|
|
79
|
+
} else {
|
|
80
|
+
if (!this.isOptional() && Nife.isEmpty(context.value))
|
|
81
|
+
return;
|
|
82
|
+
|
|
83
|
+
let options = this._options || {};
|
|
84
|
+
let type = options.type || 'string';
|
|
85
|
+
return Nife.coerceValue(context.value, type);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
toString() {
|
|
90
|
+
let paramName = this.getName();
|
|
91
|
+
let optional = this.isOptional();
|
|
92
|
+
|
|
93
|
+
return `<<${paramName}${(optional) ? '?' : ''}>>`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
RouteCapture,
|
|
99
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GenericObject } from '../../interfaces/common';
|
|
2
|
+
import { RouteScopeBase } from './route-scope-base';
|
|
3
|
+
|
|
4
|
+
export declare type EndpointMethods = 'GET' | 'PUT' | 'POST' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | '*';
|
|
5
|
+
|
|
6
|
+
export declare interface EndpointOptions {
|
|
7
|
+
name?: string;
|
|
8
|
+
methods?: Array<EndpointMethods> | EndpointMethods;
|
|
9
|
+
contentType?: Array<string | RegExp> | string | RegExp;
|
|
10
|
+
controller: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
help?: GenericObject;
|
|
13
|
+
queryParams?: GenericObject;
|
|
14
|
+
middleware?: Array<Function>;
|
|
15
|
+
[key: string | symbol]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare class RouteEndpoint implements EndpointOptions {
|
|
19
|
+
declare _parentScope: RouteScopeBase;
|
|
20
|
+
declare isDynamic: boolean;
|
|
21
|
+
declare controller: string;
|
|
22
|
+
declare methods: Array<EndpointMethods>;
|
|
23
|
+
|
|
24
|
+
public constructor(parentScope: RouteScopeBase, attributes: EndpointOptions);
|
|
25
|
+
public getParentScope(): RouteScopeBase;
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Nife = require('nife');
|
|
4
|
+
|
|
5
|
+
class RouteEndpoint {
|
|
6
|
+
constructor(parentScope, attributes) {
|
|
7
|
+
Object.defineProperties(this, {
|
|
8
|
+
'_parentScope': {
|
|
9
|
+
writable: false,
|
|
10
|
+
enumerable: false,
|
|
11
|
+
configurable: false,
|
|
12
|
+
value: parentScope || null,
|
|
13
|
+
},
|
|
14
|
+
'isDynamic': {
|
|
15
|
+
writable: true,
|
|
16
|
+
enumerable: false,
|
|
17
|
+
configurable: true,
|
|
18
|
+
value: false,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
Object.assign(
|
|
23
|
+
this,
|
|
24
|
+
{
|
|
25
|
+
methods: [ 'GET' ],
|
|
26
|
+
contentType: [ 'application/json', 'multipart/form-data' ],
|
|
27
|
+
},
|
|
28
|
+
attributes,
|
|
29
|
+
);
|
|
30
|
+
|
|
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
|
+
}));
|
|
37
|
+
|
|
38
|
+
this.contentType = Nife.arrayFlatten(Nife.toArray(this.contentType || []).filter(Boolean).map((contentType) => {
|
|
39
|
+
if (contentType === '*')
|
|
40
|
+
return [];
|
|
41
|
+
|
|
42
|
+
if (contentType instanceof RegExp)
|
|
43
|
+
return contentType;
|
|
44
|
+
|
|
45
|
+
return ('' + contentType).split(';')[0].trim().toLowerCase();
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
if (Nife.isEmpty(this.contentType))
|
|
49
|
+
this.contentType = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getParentScope = () => {
|
|
53
|
+
return this._parentScope;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
RouteEndpoint,
|
|
59
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GenericObject } from '../../interfaces/common';
|
|
2
|
+
import { RouteCapture } from './route-capture';
|
|
3
|
+
import { RouteEndpoint } from './route-endpoint';
|
|
4
|
+
|
|
5
|
+
export declare type PathPart = string | RouteCapture;
|
|
6
|
+
export declare type RoutePart = RouteScopeBase | RouteEndpoint;
|
|
7
|
+
|
|
8
|
+
export declare interface RouteCallbackContext {
|
|
9
|
+
endpoint: RouteEndpoint;
|
|
10
|
+
pathParts: Array<PathPart>;
|
|
11
|
+
stop: (result: any) => void;
|
|
12
|
+
scope: RouteScopeBase;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare type RouteCallback = (context: RouteCallbackContext) => void;
|
|
16
|
+
|
|
17
|
+
export declare class RouteScopeBase {
|
|
18
|
+
declare _parentScope: RouteScopeBase | null;
|
|
19
|
+
declare _pathParts: Array<PathPart>;
|
|
20
|
+
declare _routes: Map<PathPart, RoutePart>;
|
|
21
|
+
declare isDynamic: boolean;
|
|
22
|
+
|
|
23
|
+
public constructor(parentScope?: RouteScopeBase | null, pathParts?: Array<PathPart> | null);
|
|
24
|
+
public getParentScope(): RouteScopeBase | null;
|
|
25
|
+
public addRoute(pathPart: PathPart, scope: RoutePart): void;
|
|
26
|
+
public isDynamicPathPart(pathPart: PathPart): boolean;
|
|
27
|
+
public walkRoutes(callback: RouteCallback): any;
|
|
28
|
+
public findFirstMatchingRoute(request: Request): { endpoint?: RouteEndpoint, params?: GenericObject, error?: string };
|
|
29
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Nife = require('nife');
|
|
4
|
+
const { RouteCapture } = require('./route-capture');
|
|
5
|
+
const { RouteEndpoint } = require('./route-endpoint');
|
|
6
|
+
|
|
7
|
+
class RouteScopeBase {
|
|
8
|
+
constructor(parentScope, pathParts) {
|
|
9
|
+
Object.defineProperties(this, {
|
|
10
|
+
'_parentScope': {
|
|
11
|
+
writable: false,
|
|
12
|
+
enumerable: false,
|
|
13
|
+
configurable: false,
|
|
14
|
+
value: parentScope || null,
|
|
15
|
+
},
|
|
16
|
+
'_pathParts': {
|
|
17
|
+
writable: false,
|
|
18
|
+
enumerable: false,
|
|
19
|
+
configurable: false,
|
|
20
|
+
value: pathParts || [],
|
|
21
|
+
},
|
|
22
|
+
'_routes': {
|
|
23
|
+
writable: true,
|
|
24
|
+
enumerable: false,
|
|
25
|
+
configurable: true,
|
|
26
|
+
value: new Map(),
|
|
27
|
+
},
|
|
28
|
+
'_routeMatchCache': {
|
|
29
|
+
writable: true,
|
|
30
|
+
enumerable: false,
|
|
31
|
+
configurable: true,
|
|
32
|
+
value: new Map(),
|
|
33
|
+
},
|
|
34
|
+
'isDynamic': {
|
|
35
|
+
writable: true,
|
|
36
|
+
enumerable: false,
|
|
37
|
+
configurable: true,
|
|
38
|
+
value: false,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getParentScope = () => {
|
|
44
|
+
return this._parentScope;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
addRoute(pathPart, scope) {
|
|
48
|
+
let set = this._routes.get(pathPart);
|
|
49
|
+
if (!set) {
|
|
50
|
+
set = new Set();
|
|
51
|
+
this._routes.set(pathPart, set);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set.add(scope);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isDynamicPathPart(pathPart) {
|
|
58
|
+
if (pathPart instanceof RouteCapture)
|
|
59
|
+
return true;
|
|
60
|
+
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
walkRoutes(callback) {
|
|
65
|
+
const walk = (routeScope, pathParts) => {
|
|
66
|
+
for (let [ pathPart, children ] of routeScope._routes) {
|
|
67
|
+
if (isStopped)
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
let newPathParts = pathParts.concat(pathPart);
|
|
71
|
+
for (let child of children) {
|
|
72
|
+
if (child instanceof RouteEndpoint)
|
|
73
|
+
callback({ endpoint: child, pathParts: newPathParts, stop, scope: routeScope });
|
|
74
|
+
else
|
|
75
|
+
walk(child, newPathParts);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let finalResult;
|
|
81
|
+
let isStopped = false;
|
|
82
|
+
let stop = (result) => {
|
|
83
|
+
isStopped = true;
|
|
84
|
+
finalResult = result;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
walk(this, []);
|
|
88
|
+
|
|
89
|
+
return finalResult;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
findFirstMatchingRoute(request) {
|
|
93
|
+
let method = request.method.toUpperCase();
|
|
94
|
+
let contentType = Nife.get(request, 'headers.content-type');
|
|
95
|
+
let path = decodeURIComponent(request.path);
|
|
96
|
+
|
|
97
|
+
if (contentType) {
|
|
98
|
+
contentType = contentType.split(';')[0];
|
|
99
|
+
if (contentType)
|
|
100
|
+
contentType = contentType.trim().toLowerCase();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let cacheKey = `${method}:${contentType}:${path}`;
|
|
104
|
+
let routeMatch = this._routeMatchCache.get(cacheKey);
|
|
105
|
+
if (routeMatch)
|
|
106
|
+
return routeMatch;
|
|
107
|
+
|
|
108
|
+
const matchesPathParts = (pathParts) => {
|
|
109
|
+
let params = {};
|
|
110
|
+
|
|
111
|
+
for (let i = 0, il = pathParts.length; i < il; i++) {
|
|
112
|
+
let pathPart = pathParts[i];
|
|
113
|
+
let incomingPathPart = incomingPathParts[i] || '';
|
|
114
|
+
|
|
115
|
+
if (pathPart instanceof RouteCapture) {
|
|
116
|
+
let result = pathPart.matches({ value: incomingPathPart, request, method, path, contentType, params });
|
|
117
|
+
if (result == null) {
|
|
118
|
+
if (pathPart.isOptional())
|
|
119
|
+
continue;
|
|
120
|
+
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
params[pathPart.getName()] = result;
|
|
125
|
+
} else {
|
|
126
|
+
if (pathPart !== incomingPathPart)
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return params;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const contentTypeMatches = (endpointContentTypes) => {
|
|
135
|
+
if (!endpointContentTypes || !contentType)
|
|
136
|
+
return true;
|
|
137
|
+
|
|
138
|
+
for (let i = 0, il = endpointContentTypes.length; i < il; i++) {
|
|
139
|
+
let thisContentType = endpointContentTypes[i];
|
|
140
|
+
if ((thisContentType instanceof RegExp) && thisContentType.test(contentType))
|
|
141
|
+
return true;
|
|
142
|
+
else if (thisContentType === contentType)
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let incomingPathParts = path.trim().replace(/^\/+/, '').split('/');
|
|
150
|
+
let possibleMatch;
|
|
151
|
+
|
|
152
|
+
let result = this.walkRoutes(({ endpoint, pathParts, stop }) => {
|
|
153
|
+
// Does method match route?
|
|
154
|
+
if (endpoint.methods.indexOf(method) < 0)
|
|
155
|
+
return;
|
|
156
|
+
|
|
157
|
+
// Because of optional capture groups, it is possible
|
|
158
|
+
// that the length could deviate by one.
|
|
159
|
+
if (pathParts.length !== incomingPathParts.length) {
|
|
160
|
+
if (Math.abs(pathParts.length - incomingPathParts.length) > 1)
|
|
161
|
+
return;
|
|
162
|
+
|
|
163
|
+
let lastPathPart = pathParts[pathParts.length - 1];
|
|
164
|
+
if (!((lastPathPart instanceof RouteCapture) && lastPathPart.isOptional()))
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let params = matchesPathParts(pathParts);
|
|
169
|
+
if (params) {
|
|
170
|
+
// Does the contentType match?
|
|
171
|
+
if ((method !== 'GET' && method !== 'HEAD') && !contentTypeMatches(endpoint.contentType)) {
|
|
172
|
+
possibleMatch = endpoint;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return stop({ endpoint, params });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!result) {
|
|
181
|
+
if (possibleMatch)
|
|
182
|
+
return { endpoint: possibleMatch, error: 'BadContentType' };
|
|
183
|
+
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!result.endpoint.isDynamic)
|
|
188
|
+
this._routeMatchCache.set(cacheKey, result);
|
|
189
|
+
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
RouteScopeBase,
|
|
196
|
+
};
|