nodester 0.1.0 → 0.1.5

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.
@@ -1,10 +1,19 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const { Op } = require('sequelize');
8
+ const NQueryError = require('../factories/errors/NodesterQueryError');
9
+ const httpCodes = require('nodester/http/codes');
2
10
 
3
11
 
4
12
  module.exports = traverse;
5
13
 
6
- function traverse(queryNode, colander=null, model) {
14
+ function traverse(queryNode, filter=null, model) {
7
15
 
16
+ const sequelize = model.sequelize;
8
17
  const fieldsAvailable = Object.keys(model.tableAttributes);
9
18
  const includesAvailable = model.getIncludesList();
10
19
 
@@ -18,15 +27,16 @@ function traverse(queryNode, colander=null, model) {
18
27
  where,
19
28
  includes,
20
29
  fields,
30
+ functions,
21
31
  clauses,
22
32
  } = _disassembleQueryNode(queryNode);
23
33
 
24
34
 
25
35
  // Fields:
26
36
  //
27
- // If Colander is not set,
37
+ // If Filter is not set,
28
38
  // use every available field:
29
- if (colander === null) {
39
+ if (filter === null) {
30
40
  for (let field of fieldsAvailable) {
31
41
  // If no query filter or field is requested:
32
42
  if (fields.length === 0 || fields.indexOf(field) > -1) {
@@ -35,20 +45,21 @@ function traverse(queryNode, colander=null, model) {
35
45
  }
36
46
  }
37
47
  }
38
- // Colander is present:
48
+ // Filter is present:
39
49
  else {
40
50
  // If no query fields were set,
41
- // use the ones from Colander,
51
+ // use the ones from Filter,
42
52
  // If query fields were set,
43
- // put them through Colander:
44
- for (let field of colander.fields) {
53
+ // put them through Filter:
54
+ for (let field of filter.fields) {
45
55
  if (fieldsAvailable.indexOf(field) === -1) {
46
- const err = new TypeError(`field ${ field } is not present in model.`);
56
+ const err = new TypeError(`Field '${ field }' is not present in model.`);
57
+ err.status = httpCodes.NOT_ACCEPTABLE;
47
58
  throw err;
48
59
  }
49
60
 
50
61
  // If field is not in available set:
51
- // if (colander.fields.indexOf(field) === -1) {
62
+ // if (filter.fields.indexOf(field) === -1) {
52
63
  // continue;
53
64
  // }
54
65
 
@@ -63,10 +74,61 @@ function traverse(queryNode, colander=null, model) {
63
74
  // At least 1 field is mandatory:
64
75
  if (newQuery.attributes.length === 0) {
65
76
  const err = new TypeError(`No fields were selected.`);
77
+ err.status = httpCodes.NOT_ACCEPTABLE;
66
78
  throw err;
67
79
  }
68
80
  // Fields\
69
81
 
82
+ // Functions:
83
+ for (const fnParams of functions) {
84
+
85
+ // If COUNT() is requested:
86
+ if (fnParams.fn === 'count') {
87
+ const countParams = fnParams.args;
88
+
89
+ const [ countTarget ] = countParams;
90
+ const RootModelName = model.options.name;
91
+ // Count can be requested for this model,
92
+ // or for any of the available uncludes.
93
+ const isForRootModel = countTarget === RootModelName.plural.toLowerCase();
94
+
95
+ // Compile request:
96
+ // Example:
97
+ // `(SELECT COUNT(*) FROM comments WHERE comments.morph_id=Morph.id)`
98
+
99
+ // Params for attribute:
100
+ let rawSQL = '(SELECT COUNT(*) FROM ';
101
+ let countAttribute = '_count';
102
+
103
+ // If request to count one of includes:
104
+ if (!isForRootModel) {
105
+ // Check if it's available:
106
+ if (
107
+ !filter
108
+ ||
109
+ !filter?.includes[countTarget]
110
+ ||
111
+ model.associations[countTarget] === undefined
112
+ ) {
113
+ const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
114
+ err.status = httpCodes.NOT_ACCEPTABLE;
115
+ throw err;
116
+ }
117
+
118
+ const {
119
+ foreignKey,
120
+ sourceKey
121
+ } = model.associations[countTarget];
122
+ rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ RootModelName.singular }.${ sourceKey })`;
123
+ countAttribute = `${ countTarget }_count`;
124
+ }
125
+
126
+ newQuery.attributes.push(
127
+ [sequelize.literal(rawSQL), countAttribute]
128
+ );
129
+ }
130
+ }
131
+ // Functions\
70
132
 
71
133
  // Clauses:
72
134
  const order = {};
@@ -74,8 +136,8 @@ function traverse(queryNode, colander=null, model) {
74
136
  const clausesEntries = Object.entries(clauses);
75
137
  for (let [clauseName, value] of clausesEntries) {
76
138
  // If clause is not available:
77
- if (colander != null) {
78
- if (colander.clauses.indexOf(clauseName) === -1)
139
+ if (filter != null) {
140
+ if (filter.clauses.indexOf(clauseName) === -1)
79
141
  continue;
80
142
  }
81
143
 
@@ -87,6 +149,7 @@ function traverse(queryNode, colander=null, model) {
87
149
 
88
150
  newQuery.limit = value;
89
151
  continue;
152
+
90
153
  case 'skip':
91
154
  // Do not set if 0:
92
155
  if (value === 0)
@@ -94,20 +157,27 @@ function traverse(queryNode, colander=null, model) {
94
157
 
95
158
  newQuery.offset = value;
96
159
  continue;
160
+
97
161
  case 'order':
98
162
  order.order = value;
99
163
  continue;
164
+
100
165
  case 'order_by':
101
166
  order.by = value;
102
167
  continue;
168
+
103
169
  default:
104
170
  continue;
105
171
  }
106
172
  }
107
173
 
108
174
  // "statics" override or set any query Clause:
109
- if (colander !== null) {
110
- for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
175
+ if (filter !== null) {
176
+ const staticClausesEntries = Object.entries(filter.statics.clauses);
177
+
178
+ for (let entry of staticClausesEntries) {
179
+ const [clauseName, staticClauseValue] = entry;
180
+
111
181
  switch(clauseName) {
112
182
  case 'limit':
113
183
  newQuery.limit = staticClauseValue;
@@ -130,7 +200,6 @@ function traverse(queryNode, colander=null, model) {
130
200
 
131
201
 
132
202
  // Order:
133
- const sequelize = model.sequelize;
134
203
  if ( ['rand', 'random'].indexOf(order.order) > -1) {
135
204
  newQuery.order = sequelize.random();
136
205
  }
@@ -150,6 +219,11 @@ function traverse(queryNode, colander=null, model) {
150
219
  break;
151
220
  // MAX/MIN\
152
221
 
222
+ case null:
223
+ case undefined:
224
+ newQuery.order = [ ['id', 'desc'] ];
225
+ break;
226
+
153
227
  default:
154
228
  newQuery.order = [ [order.by, order.order] ];
155
229
  break;
@@ -166,21 +240,22 @@ function traverse(queryNode, colander=null, model) {
166
240
 
167
241
  const includeIndex = leftIncludes.indexOf(includeName);
168
242
  if (includeIndex === -1) {
169
- const err = new TypeError(`No include named ${ includeName }`);
243
+ const err = new TypeError(`No include named '${ includeName }'`);
244
+ err.status = httpCodes.NOT_ACCEPTABLE;
170
245
  throw err;
171
246
  }
172
247
 
173
248
  leftIncludes.splice(includeIndex, 1);
174
249
  }
175
250
 
176
- _traverseIncludes(includes, model, colander, newQuery)
251
+ _traverseIncludes(includes, model, filter, newQuery)
177
252
  // Includes\
178
253
 
179
254
 
180
255
  // Where:
181
256
  const whereEntries = Object.entries(where);
182
257
  for (let [attribute, value] of whereEntries) {
183
- _parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
258
+ _parseWhereEntry(attribute, value, newQuery.where, filter.statics.attributes);
184
259
  }
185
260
 
186
261
  // If "where" was not set:
@@ -189,21 +264,21 @@ function traverse(queryNode, colander=null, model) {
189
264
  }
190
265
  // Where\
191
266
 
192
-
193
267
  return newQuery;
194
268
  }
195
269
 
196
270
 
197
- function _traverseIncludes(includes, model, colander, resultQuery) {
198
- // If no Colander:
199
- if (colander === null) {
271
+ function _traverseIncludes(includes, model, filter=null, resultQuery) {
272
+ // If no Filter:
273
+ if (filter === null) {
200
274
  for (let include of includes) {
201
275
  const includeName = include.model;
202
276
  const association = model.associations[includeName];
203
277
 
204
278
  // If no such association:
205
279
  if (!association) {
206
- const err = new TypeError(`No include ${ includeName }`);
280
+ const err = new TypeError(`No include '${ includeName }'`);
281
+ err.status = httpCodes.NOT_ACCEPTABLE;
207
282
  throw err;
208
283
  }
209
284
 
@@ -214,14 +289,15 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
214
289
  _addAssociationQuery(associationQuery, includeName, resultQuery);
215
290
  }
216
291
  }
217
- // Colander is present:
292
+ // Filter is present:
218
293
  else {
219
- const colanderIncludeEntries = Object.entries(colander.includes);
220
- for (let [includeName, includeColander] of colanderIncludeEntries) {
294
+ const filterIncludeEntries = Object.entries(filter.includes);
295
+ for (let [includeName, includeFilter] of filterIncludeEntries) {
221
296
  const association = model.associations[includeName];
222
297
  // If no such association:
223
298
  if (!association) {
224
299
  const err = new TypeError(`No include ${ includeName }`);
300
+ err.status = httpCodes.NOT_ACCEPTABLE;
225
301
  throw err;
226
302
  }
227
303
 
@@ -232,7 +308,7 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
232
308
 
233
309
  const includeModel = association.target;
234
310
  // Build query for this include.
235
- const associationQuery = traverse(include, colander.includes[includeName], includeModel);
311
+ const associationQuery = traverse(include, filter.includes[includeName], includeModel);
236
312
 
237
313
  _addAssociationQuery(associationQuery, includeName, resultQuery);
238
314
  }
@@ -252,9 +328,9 @@ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
252
328
 
253
329
  function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
254
330
  let _value = value;
255
- const static = staticAttributes[attribute];
331
+ const staticAttribute = staticAttributes[attribute];
256
332
 
257
- // If attribute is Op (like, or, not, etc.):
333
+ // If attribute is Op (not, like, or, etc.):
258
334
  if (attribute in Op) {
259
335
  // Parse value:
260
336
  _value = _parseValue(_value, attribute);
@@ -265,8 +341,8 @@ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
265
341
  }
266
342
 
267
343
  // Static value overrides any other:
268
- if (!!static) {
269
- whereHolder[attribute] = static;
344
+ if (!!staticAttribute) {
345
+ whereHolder[attribute] = staticAttribute;
270
346
  return;
271
347
  }
272
348
 
@@ -279,6 +355,7 @@ function _disassembleQueryNode(queryNode) {
279
355
  where,
280
356
  includes,
281
357
  fields,
358
+ functions,
282
359
  ...clauses
283
360
  } = queryNode;
284
361
  // delete queryNode.model;
@@ -287,6 +364,7 @@ function _disassembleQueryNode(queryNode) {
287
364
  where: where ?? {},
288
365
  includes: includes ?? [],
289
366
  fields: fields ?? [],
367
+ functions: functions ?? [],
290
368
  clauses: clauses ?? []
291
369
  };
292
370
  }
@@ -22,7 +22,7 @@ const Path = require('path');
22
22
  const fs = require('fs');
23
23
  const commonExtensions = require('common-js-file-extensions');
24
24
  // Debug & console:
25
- const consl = require('../logger/console');
25
+ const consl = require('nodester/loggers/console');
26
26
  const debug = require('debug')('nodester:router');
27
27
 
28
28
 
@@ -257,7 +257,7 @@ module.exports = class NodesterRouter {
257
257
  const handlerType = typeOf(handler);
258
258
 
259
259
  if (handlerType === 'Object' && !this.paths.controllers && !this.paths.providers) {
260
- const msg = `Please set "controllersPath" or "providersPath" during Router initialization.`;
260
+ const msg = `Please set 'controllersPath' or 'providersPath' during Router initialization.`;
261
261
  const err = new TypeError(msg);
262
262
  throw err;
263
263
  }
@@ -396,7 +396,7 @@ module.exports = class NodesterRouter {
396
396
  extend(key='', fnOrProperty) {
397
397
  const keys = Object.keys(this);
398
398
  if (keys.indexOf(key) > -1) {
399
- const err = new TypeError(`Key ${ key } is already present in Router instance`);
399
+ const err = new TypeError(`Key '${ key }' is already present in Router instance.`);
400
400
  throw err;
401
401
  }
402
402
 
@@ -88,7 +88,11 @@ function _wrapRouteHandler(routeInstance, handler) {
88
88
  await parsedHandler.before(req.nquery, req, res);
89
89
  }
90
90
 
91
- await providedAction(req, res);
91
+ // If response was not sent,
92
+ // perform action
93
+ if (res.headersSent === false) {
94
+ await providedAction(req, res);
95
+ }
92
96
  }
93
97
  };
94
98
 
@@ -1,4 +1,4 @@
1
- const consl = require('../logger/console');
1
+ const consl = require('nodester/loggers/console');
2
2
  const debug = require('debug')('nodester:MiddlewareStack');
3
3
 
4
4
 
@@ -1,5 +1,5 @@
1
1
  const finalhandler = require('finalhandler');
2
- const consl = require('../logger/console');
2
+ const consl = require('nodester/loggers/console');
3
3
  const debug = require('debug')('nodester:MiddlewareStack');
4
4
 
5
5
 
@@ -0,0 +1,14 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+
8
+ module.exports = {
9
+ isModel: _isModel
10
+ };
11
+
12
+ function _isModel(arg=null) {
13
+ return !!arg.tableName && typeof arg._schema === 'object';
14
+ }
package/package.json CHANGED
@@ -1,30 +1,54 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "A boilerplate framework for Node.js",
5
5
  "exports": {
6
6
  ".": "./lib/application/index.js",
7
+
8
+ "./body/extract": "./lib/body/extract.js",
9
+
10
+ "./constants/ErrorCodes": "./lib/constants/ErrorCodes.js",
11
+
7
12
  "./controllers/methods": "./lib/controllers/methods/index.js",
8
13
  "./controllers/mixins": "./lib/controllers/mixins/index.js",
14
+
9
15
  "./database/connection": "./lib/database/connection.js",
10
16
  "./database/migration": "./lib/database/migration.js",
11
17
  "./database/utils": "./lib/database/utils.js",
18
+
12
19
  "./enum": "./lib/enums/Enum.js",
20
+
13
21
  "./facades/methods": "./lib/facades/methods/index.js",
14
22
  "./facades/mixins": "./lib/facades/mixins/index.js",
23
+
15
24
  "./factories/errors": "./lib/factories/errors/index.js",
16
25
  "./factories/responses/rest": "./lib/factories/responses/rest/index.js",
26
+
27
+ "./filter": "./lib/filters/Filter.js",
28
+
17
29
  "./http/codes": "./lib/http/codes/index.js",
30
+ "./http/codes/descriptions": "./lib/http/codes/descriptions.js",
31
+ "./http/codes/symbols": "./lib/http/codes/symbols.js",
32
+
33
+ "./loggers/console": "./lib/loggers/console.js",
34
+ "./loggers/dev": "./lib/loggers/dev.js",
35
+
18
36
  "./middlewares/formidable": "./lib/middlewares/formidable/index.js",
37
+
19
38
  "./models/associate": "./lib/models/associate.js",
20
39
  "./models/define": "./lib/models/define.js",
40
+
21
41
  "./params": "./lib/params/Params.js",
42
+
22
43
  "./ql/sequelize": "./lib/middlewares/ql/sequelize",
23
- "./queries/Colander": "./lib/queries/Colander.js",
24
- "./queries/traverse": "./lib/queries/traverse.js",
44
+ "./query/traverse": "./lib/query/traverse.js",
45
+
25
46
  "./route": "./lib/router/route.js",
26
47
  "./router": "./lib/router/index.js",
27
- "./utils/strings": "./lib/utils/strings.util.js"
48
+
49
+ "./utils/sql": "./lib/utils/sql.util.js",
50
+ "./utils/strings": "./lib/utils/strings.util.js",
51
+ "./utils/sanitizations": "./lib/utils/sanitizations.util.js"
28
52
  },
29
53
  "directories": {
30
54
  "doc": "docs"
@@ -1,61 +0,0 @@
1
- // Constants.
2
- const VISITOR = 'visitor';
3
-
4
- // Custom error.
5
- const { Err } = require('nodester/factories/errors');
6
-
7
-
8
- module.exports = class BodyPreprocessor {
9
- constructor(
10
- availableParamsForRoles,
11
- staticParamsForRoles,
12
- customProcessFunction
13
- ) {
14
- this.availableParamsForRoles = availableParamsForRoles ?? {};
15
- this.staticParamsForRoles = staticParamsForRoles ?? {};
16
-
17
- this.customProcessFunction = customProcessFunction ? customProcessFunction : ()=>{};
18
- }
19
-
20
- async extract(
21
- req,
22
- role
23
- ) {
24
- try {
25
- const requestBody = req.body;
26
-
27
- if (!requestBody || typeof requestBody !== 'object') {
28
- const err = new Err();
29
- err.name = 'ValidationError';
30
- throw err;
31
- }
32
-
33
- // Get role or set "visitor"
34
- const _role = typeof role === 'string' && role.length > 1 ? role : VISITOR;
35
-
36
- const resultBody = {};
37
-
38
- const params = this.availableParamsForRoles[_role] ?? [];
39
- const staticValues = this.staticParamsForRoles[_role] ?? {};
40
-
41
- params.forEach((param) => {
42
- // If such param is set in body:
43
- if (!!requestBody[param]) {
44
- resultBody[param] = staticValues[param] ?? requestBody[param];
45
- }
46
- // If such param is not set, but we have a "static" for it:
47
- else if (!requestBody[param] && !!staticValues[param]) {
48
- resultBody[param] = staticValues[param];
49
- }
50
- });
51
-
52
- // Make further preprocessing using customly defined function.
53
- await this.customProcessFunction.call(this, req, role, resultBody);
54
-
55
- return Promise.resolve(resultBody);
56
- }
57
- catch(error) {
58
- return Promise.reject(error);
59
- }
60
- }
61
- }
@@ -1,55 +0,0 @@
1
- // Constants:
2
- const VISITOR = 'visitor';
3
- const DefaultAvailableParamsForRoles = { [VISITOR]: [] };
4
- const DefaultStaticParamsForRoles = { [VISITOR]: [] };
5
-
6
- // Custom error.
7
- const { Err } = require('nodester/factories/errors');
8
-
9
-
10
- module.exports = class IncludesPreprocessor {
11
-
12
- constructor(
13
- availableParamsForRoles,
14
- staticParamsForRoles,
15
- customProcessFunction=()=>{}
16
- ) {
17
- this.availableParamsForRoles = availableParamsForRoles ?? DefaultAvailableParamsForRoles;
18
- this.staticParamsForRoles = staticParamsForRoles ?? DefaultStaticParamsForRoles;
19
-
20
- this.customProcessFunction = customProcessFunction ? customProcessFunction : ()=>{};
21
- }
22
-
23
- async extract(
24
- req,
25
- role
26
- ) {
27
- const requestIncludes = req.query?.includes ?? [];
28
-
29
- if (!requestQuery || typeof requestQuery !== 'object') {
30
- const err = new Err();
31
- err.name = 'ValidationError';
32
- throw err;
33
- }
34
-
35
- // Get role or set "visitor"
36
- const _role = typeof role === 'string' && role.length > 1 ? role : VISITOR;
37
-
38
- const resultIncludes = [];
39
-
40
- const params = this.availableParamsForRoles[_role] ?? [];
41
- const staticValues = this.staticParamsForRoles[_role] ?? [];
42
-
43
- params.forEach((param) => {
44
- // If such param is set in query:
45
- if (requestIncludes.indexOf(param) !== -1) {
46
- resultIncludes.push(param);
47
- }
48
- });
49
-
50
- // Make further preprocessing using custom defined function.
51
- await this.customProcessFunction.call(this, req, role, resultIncludes);
52
-
53
- return resultIncludes;
54
- }
55
- }
@@ -1,64 +0,0 @@
1
- // Constants:
2
- const VISITOR = 'visitor';
3
- const DefaultAvailableParamsForRoles = { [VISITOR]: [ 'skip', 'limit', 'order' ] };
4
- const DefaultStaticParamsForRoles = { [VISITOR]: { limit: 50 } };
5
-
6
- // Custom error.
7
- const { Err } = require('nodester/factories/errors');
8
-
9
-
10
- module.exports = class QueryPreprocessor {
11
-
12
- constructor(
13
- availableParamsForRoles,
14
- staticParamsForRoles,
15
- customProcessFunction
16
- ) {
17
- this.availableParamsForRoles = availableParamsForRoles ?? DefaultAvailableParamsForRoles;
18
- this.staticParamsForRoles = staticParamsForRoles ?? DefaultStaticParamsForRoles;
19
-
20
- this.customProcessFunction = customProcessFunction ? customProcessFunction : ()=>{};
21
- }
22
-
23
- async extract(
24
- req,
25
- role
26
- ) {
27
- try {
28
- const requestQuery = req.query;
29
-
30
- if (!requestQuery || typeof requestQuery !== 'object') {
31
- const err = new Err();
32
- err.name = 'ValidationError';
33
- throw err;
34
- }
35
-
36
- // Get role or set "visitor"
37
- const _role = typeof role === 'string' && role.length > 1 ? role : [VISITOR];
38
-
39
- const resultQuery = {};
40
-
41
- const params = this.availableParamsForRoles[_role] ?? [];
42
- const staticValues = this.staticParamsForRoles[_role] ?? {};
43
-
44
- params.forEach((param) => {
45
- // If such param is set in query:
46
- if (!!requestQuery[param]) {
47
- resultQuery[param] = staticValues[param] ?? requestQuery[param];
48
- }
49
- // If such param is not set, but we have a "static" for it:
50
- else if (!requestQuery[param] && !!staticValues[param]) {
51
- resultQuery[param] = staticValues[param];
52
- }
53
- });
54
-
55
- // Make further preprocessing using customly defined function.
56
- await this.customProcessFunction.call(this, req, role, resultQuery);
57
-
58
- return Promise.resolve(resultQuery);
59
- }
60
- catch(error) {
61
- return Promise.reject(error);
62
- }
63
- }
64
- }