nodester 0.0.8 → 0.1.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.
Files changed (37) hide show
  1. package/Readme.md +18 -59
  2. package/lib/application/index.js +28 -7
  3. package/lib/controllers/methods/index.js +34 -10
  4. package/lib/controllers/mixins/index.js +14 -5
  5. package/lib/database/connection.js +34 -0
  6. package/lib/database/migration.js +42 -0
  7. package/lib/database/utils.js +19 -0
  8. package/lib/facades/methods/index.js +173 -0
  9. package/lib/facades/mixins/index.js +111 -0
  10. package/lib/middlewares/formidable/index.js +37 -0
  11. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +2 -2
  12. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +3 -3
  13. package/lib/models/associate.js +17 -0
  14. package/lib/models/define.js +50 -1
  15. package/lib/models/mixins.js +81 -72
  16. package/lib/params/Params.js +10 -7
  17. package/lib/queries/Colander.js +84 -0
  18. package/lib/queries/traverse.js +311 -0
  19. package/lib/router/handlers.util.js +22 -2
  20. package/lib/router/index.js +96 -75
  21. package/lib/router/markers.js +78 -0
  22. package/lib/router/route.js +4 -4
  23. package/lib/router/routes.util.js +35 -5
  24. package/lib/router/utils.js +30 -0
  25. package/package.json +20 -7
  26. package/tests/nql.test.js +3 -3
  27. package/lib/_/n_controllers/Controller.js +0 -474
  28. package/lib/_/n_controllers/JWTController.js +0 -240
  29. package/lib/_/n_controllers/ServiceController.js +0 -109
  30. package/lib/_/n_controllers/WebController.js +0 -75
  31. package/lib/_facades/Facade.js +0 -388
  32. package/lib/_facades/FacadeParams.js +0 -11
  33. package/lib/_facades/ServiceFacade.js +0 -17
  34. package/lib/_facades/jwt.facade.js +0 -273
  35. package/lib/models/DisabledRefreshToken.js +0 -68
  36. package/lib/models/Extractor.js +0 -320
  37. package/lib/utils/forms.util.js +0 -22
@@ -0,0 +1,311 @@
1
+ const { Op } = require('sequelize');
2
+
3
+
4
+ module.exports = traverse;
5
+
6
+ function traverse(queryNode, colander=null, model) {
7
+
8
+ const fieldsAvailable = Object.keys(model.tableAttributes);
9
+ const includesAvailable = model.getIncludesList();
10
+
11
+ const newQuery = {
12
+ attributes: [],
13
+ where: {},
14
+ include: []
15
+ };
16
+
17
+ const {
18
+ where,
19
+ includes,
20
+ fields,
21
+ clauses,
22
+ } = _disassembleQueryNode(queryNode);
23
+
24
+
25
+ // Fields:
26
+ //
27
+ // If Colander is not set,
28
+ // use every available field:
29
+ if (colander === null) {
30
+ for (let field of fieldsAvailable) {
31
+ // If no query filter or field is requested:
32
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
33
+ newQuery.attributes.push(field);
34
+ continue;
35
+ }
36
+ }
37
+ }
38
+ // Colander is present:
39
+ else {
40
+ // If no query fields were set,
41
+ // use the ones from Colander,
42
+ // If query fields were set,
43
+ // put them through Colander:
44
+ for (let field of colander.fields) {
45
+ if (fieldsAvailable.indexOf(field) === -1) {
46
+ const err = new TypeError(`field ${ field } is not present in model.`);
47
+ throw err;
48
+ }
49
+
50
+ // If field is not in available set:
51
+ // if (colander.fields.indexOf(field) === -1) {
52
+ // continue;
53
+ // }
54
+
55
+ // If no query filter or field is requested:
56
+ if (fields.length === 0 || fields.indexOf(field) > -1) {
57
+ newQuery.attributes.push(field);
58
+ continue;
59
+ }
60
+ }
61
+ }
62
+
63
+ // At least 1 field is mandatory:
64
+ if (newQuery.attributes.length === 0) {
65
+ const err = new TypeError(`No fields were selected.`);
66
+ throw err;
67
+ }
68
+ // Fields\
69
+
70
+
71
+ // Clauses:
72
+ const order = {};
73
+
74
+ const clausesEntries = Object.entries(clauses);
75
+ for (let [clauseName, value] of clausesEntries) {
76
+ // If clause is not available:
77
+ if (colander != null) {
78
+ if (colander.clauses.indexOf(clauseName) === -1)
79
+ continue;
80
+ }
81
+
82
+ switch(clauseName) {
83
+ case 'limit':
84
+ // Do not set if -1:
85
+ if (value === -1)
86
+ continue;
87
+
88
+ newQuery.limit = value;
89
+ continue;
90
+ case 'skip':
91
+ // Do not set if 0:
92
+ if (value === 0)
93
+ continue;
94
+
95
+ newQuery.offset = value;
96
+ continue;
97
+ case 'order':
98
+ order.order = value;
99
+ continue;
100
+ case 'order_by':
101
+ order.by = value;
102
+ continue;
103
+ default:
104
+ continue;
105
+ }
106
+ }
107
+
108
+ // "statics" override or set any query Clause:
109
+ if (colander !== null) {
110
+ for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
111
+ switch(clauseName) {
112
+ case 'limit':
113
+ newQuery.limit = staticClauseValue;
114
+ continue;
115
+ case 'skip':
116
+ newQuery.offset = staticClauseValue;
117
+ continue;
118
+ case 'order':
119
+ order.order = staticClauseValue;
120
+ continue;
121
+ case 'order_by':
122
+ order.by = staticClauseValue;
123
+ continue;
124
+ default:
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ // Clauses\
130
+
131
+
132
+ // Order:
133
+ const sequelize = model.sequelize;
134
+ if ( ['rand', 'random'].indexOf(order.order) > -1) {
135
+ newQuery.order = sequelize.random();
136
+ }
137
+ else {
138
+ const column = sequelize.col( order.by );
139
+ switch (order.order) {
140
+ // MAX/MIN:
141
+ case 'max-asc':
142
+ case 'max':
143
+ case 'min-desc':
144
+ newQuery.order = sequelize.fn('max', column);
145
+ break;
146
+ case 'min':
147
+ case 'min-asc':
148
+ case 'max-desc':
149
+ newQuery.order = [ sequelize.fn('max', column), 'DESC' ];
150
+ break;
151
+ // MAX/MIN\
152
+
153
+ default:
154
+ newQuery.order = [ [order.by, order.order] ];
155
+ break;
156
+ }
157
+ }
158
+ // Order\
159
+
160
+
161
+ // Includes:
162
+ // If requested includes are not available:
163
+ const leftIncludes = includesAvailable.map(i => i.association);
164
+ for (let include of includes) {
165
+ const includeName = include.model;
166
+
167
+ const includeIndex = leftIncludes.indexOf(includeName);
168
+ if (includeIndex === -1) {
169
+ const err = new TypeError(`No include named ${ includeName }`);
170
+ throw err;
171
+ }
172
+
173
+ leftIncludes.splice(includeIndex, 1);
174
+ }
175
+
176
+ _traverseIncludes(includes, model, colander, newQuery)
177
+ // Includes\
178
+
179
+
180
+ // Where:
181
+ const whereEntries = Object.entries(where);
182
+ for (let [attribute, value] of whereEntries) {
183
+ _parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
184
+ }
185
+
186
+ // If "where" was not set:
187
+ if (whereEntries.length === 0) {
188
+ delete newQuery.where;
189
+ }
190
+ // Where\
191
+
192
+
193
+ return newQuery;
194
+ }
195
+
196
+
197
+ function _traverseIncludes(includes, model, colander, resultQuery) {
198
+ // If no Colander:
199
+ if (colander === null) {
200
+ for (let include of includes) {
201
+ const includeName = include.model;
202
+ const association = model.associations[includeName];
203
+
204
+ // If no such association:
205
+ if (!association) {
206
+ const err = new TypeError(`No include ${ includeName }`);
207
+ throw err;
208
+ }
209
+
210
+ const includeModel = association.target;
211
+ // Build query for this include.
212
+ const associationQuery = traverse(include, null, includeModel);
213
+
214
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
215
+ }
216
+ }
217
+ // Colander is present:
218
+ else {
219
+ const colanderIncludeEntries = Object.entries(colander.includes);
220
+ for (let [includeName, includeColander] of colanderIncludeEntries) {
221
+ const association = model.associations[includeName];
222
+ // If no such association:
223
+ if (!association) {
224
+ const err = new TypeError(`No include ${ includeName }`);
225
+ throw err;
226
+ }
227
+
228
+ // If include was not requested:
229
+ const include = includes.find(({ model }) => model === includeName);
230
+ if (!include)
231
+ continue;
232
+
233
+ const includeModel = association.target;
234
+ // Build query for this include.
235
+ const associationQuery = traverse(include, colander.includes[includeName], includeModel);
236
+
237
+ _addAssociationQuery(associationQuery, includeName, resultQuery);
238
+ }
239
+ }
240
+ }
241
+
242
+
243
+ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
244
+
245
+ // Add all association info into query.
246
+ resultQuery.include.push({
247
+ association: includeName,
248
+ ...associationQuery
249
+ });
250
+ }
251
+
252
+
253
+ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
254
+ let _value = value;
255
+ const static = staticAttributes[attribute];
256
+
257
+ // If attribute is Op (like, or, not, etc.):
258
+ if (attribute in Op) {
259
+ // Parse value:
260
+ _value = _parseValue(_value, attribute);
261
+
262
+ const op = Op[attribute];
263
+ whereHolder[op] = _value;
264
+ return;
265
+ }
266
+
267
+ // Static value overrides any other:
268
+ if (!!static) {
269
+ whereHolder[attribute] = static;
270
+ return;
271
+ }
272
+
273
+ whereHolder[attribute] = _parseValue(_value, attribute);
274
+ }
275
+
276
+ function _disassembleQueryNode(queryNode) {
277
+ // Disassemble current query node:
278
+ const {
279
+ where,
280
+ includes,
281
+ fields,
282
+ ...clauses
283
+ } = queryNode;
284
+ // delete queryNode.model;
285
+
286
+ return {
287
+ where: where ?? {},
288
+ includes: includes ?? [],
289
+ fields: fields ?? [],
290
+ clauses: clauses ?? []
291
+ };
292
+ }
293
+
294
+ function _parseValue(value, attribute) {
295
+ // If value is Object:
296
+ if (typeof value === 'object' && Array.isArray(value) === false) {
297
+ const [opKey, rawValue] = (Object.entries(value))[0];
298
+
299
+ // If operation is "in":
300
+ if (opKey === 'in') {
301
+ // Unwrap rawValue.
302
+ return rawValue[0][attribute];
303
+ }
304
+ else {
305
+ const op = Op[opKey];
306
+ return { [op]: rawValue };
307
+ }
308
+ }
309
+
310
+ return value;
311
+ }
@@ -26,19 +26,24 @@ function _parseRouteHandler(routeHandler={}) {
26
26
  }
27
27
 
28
28
  const result = {
29
+ actionName: undefined,
29
30
  before: null,
30
31
  controllerName: undefined,
31
- actionName: undefined,
32
+ providerName: undefined
32
33
  };
33
34
 
34
35
  const {
36
+ action,
35
37
  before,
36
38
 
37
39
  controller,
38
40
  controlledBy,
39
- action
41
+
42
+ provider,
43
+ providedBy,
40
44
  } = routeHandler;
41
45
 
46
+ // Controllers:
42
47
  if (!!controlledBy) {
43
48
  const parts = controlledBy.split('.');
44
49
  const controllerName = parts[0];
@@ -49,6 +54,21 @@ function _parseRouteHandler(routeHandler={}) {
49
54
  else if (!!controller) {
50
55
  result.controllerName = `${ controller }`;
51
56
  }
57
+ // Controllers\
58
+
59
+ // Providers:
60
+ else if (!!providedBy) {
61
+ const parts = providedBy.split('.');
62
+ const providerName = parts[0];
63
+ const actionName = parts[1];
64
+ result.providerName = providerName;
65
+ result.actionName = actionName;
66
+ }
67
+ else if (!!provider) {
68
+ result.providerName = `${ provider }`;
69
+ }
70
+ // Providers\
71
+
52
72
  else if (!!action) {
53
73
  result.actionName = `${ action }`;
54
74
  }
@@ -6,11 +6,17 @@
6
6
  'use strict';
7
7
 
8
8
  const MiddlewareStack = require('../stacks/MiddlewareStack');
9
- const MarkersStack = require('../stacks/MarkersStack');
10
9
  const Route = require('./route');
10
+ // Markers:
11
+ const MarkersStack = require('../stacks/MarkersStack');
12
+ const MarkerMethods = require('./markers');
11
13
  // Utils:
12
14
  const { typeOf } = require('../utils/types.util');
13
- const { wrapRouteHandler } = require('./routes.util');
15
+ const {
16
+ validateParsedRouteMethood,
17
+ wrapRouteHandler
18
+ } = require('./routes.util');
19
+ const { parseProviderFileNames } = require('./utils');
14
20
  // File system:
15
21
  const Path = require('path');
16
22
  const fs = require('fs');
@@ -34,14 +40,17 @@ module.exports = class NodesterRouter {
34
40
  * @api public
35
41
  */
36
42
  constructor(opts={}) {
43
+ // Reference to the controllers stack.
44
+ this._controllers = new Map();
45
+
37
46
  // Reference to middlewares stack.
38
47
  this._middlewares = new MiddlewareStack({ finalhandlerEnabled: !!opts.finalhandlerEnabled });
39
48
 
40
49
  // Reference to the markers stack.
41
50
  this._markers = new MarkersStack();
42
51
 
43
- // Reference to the controllers stack.
44
- this._controllers = new Map();
52
+ // Reference to the providers stack.
53
+ this._providers = new Map();
45
54
 
46
55
  this.codeFileExtensions = commonExtensions.code;
47
56
  this.paths = {};
@@ -55,29 +64,20 @@ module.exports = class NodesterRouter {
55
64
  this.codeFileExtensions = [...opts.codeFileExtensions];
56
65
  }
57
66
 
58
- // If "controllersPath" was set, cache all controllers in directory:
67
+ // If "controllersPath" was set,
68
+ // cache all controllers in directory:
59
69
  if (!!opts.controllersPath) {
60
70
  // Save path.
61
71
  this.paths.controllers = opts.controllersPath;
62
72
 
63
- const availableFileExtensions = this.codeFileExtensions;
64
73
  // Only get files, which have available file extensions:
74
+ const availableFileExtensions = this.codeFileExtensions;
65
75
  const fileNames = fs.readdirSync(this.paths.controllers);
66
- for (const fileName of fileNames) {
67
-
68
- const nameParts = fileName.split('.');
69
- const extension = nameParts.pop();
70
- if (availableFileExtensions.indexOf(extension) === -1) {
71
- continue;
72
- }
73
76
 
74
- // If the name format is <model>.<controller>,
75
- // but second part is not "controller":
76
- if (nameParts.length > 1 && nameParts[1] !== 'controller')
77
- continue;
77
+ const controllersNames = parseProviderFileNames(fileNames, availableFileExtensions, 'controller');
78
78
 
79
+ for (const { fileName, controllerName } of controllersNames) {
79
80
  const controller = require(Path.join(this.paths.controllers, fileName));
80
- const controllerName = nameParts[0];
81
81
  this.addController(controller, controllerName);
82
82
  }
83
83
  }
@@ -94,6 +94,37 @@ module.exports = class NodesterRouter {
94
94
  this.addController(controllerDefinition, controllerName);
95
95
  }
96
96
  }
97
+
98
+ // If "providersPath" was set,
99
+ // cache all providers in directory:
100
+ if (!!opts.providersPath) {
101
+ // Save path.
102
+ this.paths.providers = opts.providersPath;
103
+
104
+ // Only get files, which have available file extensions:
105
+ const availableFileExtensions = this.codeFileExtensions;
106
+ const fileNames = fs.readdirSync(this.paths.providers);
107
+
108
+ const providersNames = parseProviderFileNames(fileNames, availableFileExtensions, 'provider');
109
+
110
+ for (const { fileName, providerName } of providersNames) {
111
+ const provider = require(Path.join(this.paths.providers, fileName));
112
+ this.addProvider(provider, providerName);
113
+ }
114
+ }
115
+
116
+ // If "providers" were provided as an Object:
117
+ if (!!opts.providers) {
118
+ if (typeOf(opts.providers) !== 'Object') {
119
+ const err = new TypeError(`"providers" must be an Object.`);
120
+ throw err;
121
+ }
122
+
123
+ const entities = Object.entities(opts.providers);
124
+ for (const [providerName, providerDefinition] of entities) {
125
+ this.addProvider(providerDefinition, providerName);
126
+ }
127
+ }
97
128
  }
98
129
 
99
130
 
@@ -111,6 +142,7 @@ module.exports = class NodesterRouter {
111
142
  controller: this.addController.bind(this),
112
143
  middleware: this.addMiddleware.bind(this),
113
144
  marker: this.addMarker.bind(this),
145
+ provider: this.addProvider.bind(this),
114
146
  route: this.addRoute.bind(this),
115
147
  routes: this.addRoutes.bind(this),
116
148
  }
@@ -182,6 +214,33 @@ module.exports = class NodesterRouter {
182
214
  }
183
215
 
184
216
 
217
+ /*
218
+ * Adds new provider to the providers stack.
219
+ *
220
+ * @param {Function|Object} provider
221
+ * @param {String} providerName
222
+ *
223
+ * @api public
224
+ */
225
+ addProvider(fnOrObject, providerName=null) {
226
+ const providerType = typeOf(fnOrObject);
227
+ const name = providerName ?? fnOrObject?.name ?? fnOrObject.constructor.name;
228
+
229
+ // If provider was exported as Object:
230
+ if (providerType === 'Object') {
231
+ this._providers.set(name, fnOrObject);
232
+ }
233
+ // If provider was exported as a constructor function:
234
+ else if (providerType === 'function') {
235
+ this._providers.set(name, new fnOrObject());
236
+ }
237
+ else {
238
+ const err = new TypeError(`Please check how you exported ${ name }, it should be either Object or constructor function.`);
239
+ throw err;
240
+ }
241
+ }
242
+
243
+
185
244
  /*
186
245
  * Creates route middleware and adds it to the stack.
187
246
  *
@@ -191,21 +250,19 @@ module.exports = class NodesterRouter {
191
250
  * @api public
192
251
  */
193
252
  addRoute(route='', handler) {
194
- const parsed = new Route(route);
195
- const handlerType = typeOf(handler);
253
+ const parsedRoute = new Route(route);
254
+ // Will throw exception if not valid.
255
+ validateParsedRouteMethood(parsedRoute);
196
256
 
197
- // ToDo: move it to separate validator:
198
- if (parsed.method === undefined) {
199
- const err = new TypeError(`"route" should start with one of the following methods: [GET, POST, PUT, DELETE, QUERY, HEADER, OPTIONS]`);
200
- throw err;
201
- }
257
+ const handlerType = typeOf(handler);
202
258
 
203
- if (handlerType === 'Object' && !this.paths.controllers) {
204
- const err = new TypeError(`Please set "controllersPath" during Router initialization.`);
259
+ if (handlerType === 'Object' && !this.paths.controllers && !this.paths.providers) {
260
+ const msg = `Please set "controllersPath" or "providersPath" during Router initialization.`;
261
+ const err = new TypeError(msg);
205
262
  throw err;
206
263
  }
207
264
 
208
- const wrapped = wrapRouteHandler.call(this, parsed, handler);
265
+ const wrapped = wrapRouteHandler.call(this, parsedRoute, handler);
209
266
  return this.addMiddleware(wrapped);
210
267
  }
211
268
 
@@ -228,11 +285,18 @@ module.exports = class NodesterRouter {
228
285
  /*
229
286
  * Proxy to .add.middleware()
230
287
  *
231
- * @param {Function} fn
288
+ * @param {Function|NodesterRouter} fnOrRouter
232
289
  *
233
290
  * @api public
234
291
  */
235
- use(fn) {
292
+ use(fnOrRouter) {
293
+ let fn = fnOrRouter;
294
+
295
+ // If Router:
296
+ if (fnOrRouter instanceof NodesterRouter) {
297
+ fn = fnOrRouter.handle.bind(fnOrRouter);
298
+ }
299
+
236
300
  return this.add.middleware(fn);
237
301
  }
238
302
 
@@ -249,51 +313,8 @@ module.exports = class NodesterRouter {
249
313
  const markerFn = this._markers.get(markerName);
250
314
 
251
315
  return {
252
- route: (route, fn) => {
253
- const parsed = new Route(route);
254
- // ToDo: move it to separate validator:
255
- if (parsed.method === undefined) {
256
- const err = new TypeError(`"route" should start with one of the following methods: [GET, POST, PUT, DELETE, QUERY, HEADER, OPTIONS]`);
257
- throw err;
258
- }
259
-
260
- const wrapped = async (req, res, next) => {
261
- const matched = await markerFn.call(self, req, res);
262
- // Skip, if marker's condition was not matched:
263
- if (!matched) {
264
- return next();
265
- }
266
-
267
- // Wrap and call:
268
- const routeHandler = wrapRouteHandler.call(self, parsed, fn);
269
- await routeHandler.call(self, req, res, next);
270
-
271
- // If response was not sent,
272
- // go to next one:
273
- if (res.headersSent === false) {
274
- next();
275
- }
276
- };
277
- return self.add.route(wrapped);
278
- },
279
- use: (fn) => {
280
- const wrapped = async (req, res, next) => {
281
- const matched = await markerFn.call(self, req, res);
282
- // Skip, if marker's condition was not matched:
283
- if (!matched) {
284
- return next();
285
- }
286
-
287
- await fn.call(self, req, res, next);
288
-
289
- // If response was not sent,
290
- // go to next one:
291
- if (res.headersSent === false) {
292
- next();
293
- }
294
- };
295
- return self.use(wrapped);
296
- }
316
+ route: (route, fn) => MarkerMethods.onlyRoute.call(self, route, fn, markerFn),
317
+ use: (fnOrRouter) => MarkerMethods.onlyUse.call(self, fnOrRouter, markerFn),
297
318
  }
298
319
  }
299
320
 
@@ -0,0 +1,78 @@
1
+ const Route = require('./route');
2
+ // Utils.
3
+ const {
4
+ validateParsedRouteMethood,
5
+ wrapRouteHandler
6
+ } = require('./routes.util');
7
+
8
+
9
+ module.exports = {
10
+ onlyRoute: _onlyRoute,
11
+ onlyUse: _onlyUse
12
+ }
13
+
14
+ /*
15
+ *
16
+ * @param {String} route
17
+ * @param {Function} fn
18
+ * @param {Function} markerFn
19
+ *
20
+ */
21
+ function _onlyRoute(route, fn, markerFn) {
22
+ const parsedRoute = new Route(route);
23
+ // Will throw exception if not valid.
24
+ validateParsedRouteMethood(parsedRoute);
25
+
26
+ const wrapped = async (req, res, next) => {
27
+ const matched = await markerFn.call(this, req, res);
28
+ // Skip, if marker's condition was not matched:
29
+ if (!matched) {
30
+ return next();
31
+ }
32
+
33
+ // Wrap and call:
34
+ const routeHandler = wrapRouteHandler.call(this, parsedRoute, fn);
35
+ await routeHandler.call(this, req, res, next);
36
+
37
+ // If response was not sent,
38
+ // go to the next one:
39
+ if (res.headersSent === false) {
40
+ next();
41
+ }
42
+ };
43
+ return this.add.route(route, wrapped);
44
+ }
45
+
46
+
47
+ /*
48
+ *
49
+ * @param {Function} fnOrRouter
50
+ * @param {Function} markerFn
51
+ *
52
+ */
53
+ function _onlyUse(fnOrRouter, markerFn) {
54
+
55
+ const wrapped = async (req, res, next) => {
56
+ const matched = await markerFn.call(this, req, res);
57
+
58
+ // Skip, if marker's condition was not matched:
59
+ if (!matched) {
60
+ return next();
61
+ }
62
+
63
+ const isRouter = fnOrRouter.constructor.name === 'NodesterRouter';
64
+
65
+ const fn = isRouter ? fnOrRouter.handle.bind(fnOrRouter) : fnOrRouter;
66
+ await fn.call(this, req, res, next);
67
+
68
+ // If regular handler function:
69
+ if (!isRouter) {
70
+ // If response was not sent,
71
+ // go to the next one:
72
+ if (res.headersSent === false) {
73
+ next();
74
+ }
75
+ }
76
+ };
77
+ return this.use(wrapped);
78
+ }
@@ -40,17 +40,17 @@ module.exports = class NodesterRoute {
40
40
  }
41
41
 
42
42
  // Parse:
43
+ const collapsedSpaces = routeStringOrOpts.replace(/\s\s+/g, ' ');
43
44
  const parts = routeStringOrOpts.split(' ');
44
- const cleared = parts.filter(p => p.length > 0);
45
45
 
46
46
  // Set method:
47
- if (cleared[0].indexOf('/') === -1) {
48
- const method = cleared.shift().toUpperCase();
47
+ if (parts[0].indexOf('/') === -1) {
48
+ const method = parts.shift().toUpperCase();
49
49
  this.method = method;
50
50
  }
51
51
 
52
52
  // Build route one again and set it:
53
- const clearRoute = cleared.join('');
53
+ const clearRoute = parts.join('');
54
54
  this.route = clearRoute;
55
55
 
56
56
  // Parse path parts: