nodester 0.1.0 → 0.1.4

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/Readme.md CHANGED
@@ -30,12 +30,13 @@ app.listen(8080, function() {
30
30
 
31
31
 
32
32
  ### Core concepts
33
- [Go to core concepts documentaion](docs/CoreConcepts.md)
33
+ [Core concepts documentation ➡️](docs/CoreConcepts.md)
34
+
34
35
 
35
36
  ### Queries & Querying - Nodester Query Language (NQR)
36
37
  One of the main power points of nodester is it's query language. It's an extension of a REST API syntaxis for a broader integration with a database SQL. Read more about it in the documentation:
37
38
 
38
- [Go to NQR documentaion](docs/Queries.md)
39
+ [NQR documentaion ➡️](docs/Queries.md)
39
40
 
40
41
 
41
42
  ### Database
@@ -27,7 +27,7 @@ const {
27
27
  } = require('../database/utils');
28
28
 
29
29
  const { merge } = require('../utils/objects.util');
30
- const consl = require('../logger/console');
30
+ const consl = require('nodester/loggers/console');
31
31
  const debug = require('debug')('nodester:application');
32
32
 
33
33
 
@@ -0,0 +1,19 @@
1
+ const Enum = require('nodester/enum');
2
+
3
+ const NODESTER_QUERY_ERROR = 'NodesterQueryError';
4
+ const UNAUTHORIZED_ERROR = 'Unauthorized';
5
+ const NOT_FOUND_ERROR = 'NotFound';
6
+ const VALIDATION_ERROR = 'ValidationError';
7
+ const CONFLICT_ERROR = 'ConflictError';
8
+ const SEQUELIZE_UNIQUE_CONSTRAINT_ERROR = 'SequelizeUniqueConstraintError';
9
+ const INTERNAL_VALIDATION_ERROR = 'InternalValidationError';
10
+
11
+ module.exports = new Enum({
12
+ NODESTER_QUERY_ERROR,
13
+ UNAUTHORIZED_ERROR,
14
+ NOT_FOUND_ERROR,
15
+ VALIDATION_ERROR,
16
+ CONFLICT_ERROR,
17
+ SEQUELIZE_UNIQUE_CONSTRAINT_ERROR,
18
+ INTERNAL_VALIDATION_ERROR,
19
+ });
@@ -1,4 +1,4 @@
1
- const Enum = require('../enums/Enum');
1
+ const Enum = require('nodester/enum');
2
2
 
3
3
 
4
4
  module.exports = new Enum({
@@ -1,3 +1,13 @@
1
+ const {
2
+ NODESTER_QUERY_ERROR,
3
+ UNAUTHORIZED_ERROR,
4
+ NOT_FOUND_ERROR,
5
+ VALIDATION_ERROR,
6
+ CONFLICT_ERROR,
7
+ SEQUELIZE_UNIQUE_CONSTRAINT_ERROR,
8
+ INTERNAL_VALIDATION_ERROR
9
+ } = require('nodester/constants/ErrorCodes');
10
+
1
11
  const {
2
12
  getOne,
3
13
  getMany,
@@ -10,6 +20,8 @@ const {
10
20
  module.exports = {
11
21
  withDefaultCRUD: _withDefaultCRUD,
12
22
  withDefaultErrorProcessing: _withDefaultErrorProcessing,
23
+
24
+ setFacade: _setFacade,
13
25
  setMethod: _setMethod
14
26
  }
15
27
 
@@ -42,18 +54,7 @@ function _withDefaultCRUD(controller, opts={}) {
42
54
  throw err;
43
55
  }
44
56
 
45
- if (!facade) {
46
- const err = new TypeError(`'opts.facade' argument is invalid.`);
47
- throw err;
48
- }
49
-
50
- // Set main facade:
51
- if (facade.constructor.name === 'Function') {
52
- controller.facade = new facade();
53
- }
54
- else {
55
- controller.facade = facade;
56
- }
57
+ _setFacade(controller, facade);
57
58
 
58
59
  // Set model info:
59
60
  const model = facade.model;
@@ -143,34 +144,39 @@ function _withDefaultErrorProcessing(controller, opts={}) {
143
144
  let errorResponse = {};
144
145
 
145
146
  switch(error.name) {
146
- case('Unauthorized'): {
147
+ case UNAUTHORIZED_ERROR: {
147
148
  statusCode = 401;
148
149
  errorResponse.details = { message: 'Unauthorized' };
149
150
  break;
150
151
  }
151
- case('NotFound'): {
152
+ case NOT_FOUND_ERROR: {
152
153
  statusCode = 404;
153
154
  errorResponse.details = { message: errorMessage };
154
155
  break;
155
156
  }
156
- case('ValidationError'): {
157
- statusCode = 406;
157
+ case NODESTER_QUERY_ERROR: {
158
+ statusCode = 422;
159
+ errorResponse.details = { message: errorMessage };
160
+ break;
161
+ }
162
+ case VALIDATION_ERROR: {
163
+ statusCode = 422;
158
164
  errorResponse.details = error?.details;
159
165
  break;
160
166
  }
161
- case('ConflictError'): {
167
+ case CONFLICT_ERROR: {
162
168
  statusCode = 409;
163
169
  errorResponse.details = error?.details ?? error?.message;
164
170
  break;
165
171
  }
166
- case('SequelizeUniqueConstraintError'): {
172
+ case SEQUELIZE_UNIQUE_CONSTRAINT_ERROR: {
167
173
  statusCode = 409;
168
174
  errorResponse.details = error?.errors;
169
175
  break;
170
176
  }
171
- case('InternalValidationError'): {
177
+ case INTERNAL_VALIDATION_ERROR: {
172
178
  statusCode = 500;
173
- errorResponse.details = { message: 'Error' };
179
+ errorResponse.details = { message: errorMessage };
174
180
  break;
175
181
  }
176
182
  default: {
@@ -201,6 +207,39 @@ function _withDefaultErrorProcessing(controller, opts={}) {
201
207
  }
202
208
 
203
209
 
210
+ /**
211
+ * Sets one of or all of CRUD methods to Controller.
212
+ *
213
+ * @param {Function|Object} controller
214
+ * @param {Function|Object} facade
215
+ * @param {String} facadeName
216
+ *
217
+ * @return {Function|Object} controller
218
+ *
219
+ * @api public
220
+ * @alias setFacade
221
+ */
222
+ function _setFacade(controller, facade, facadeName=null) {
223
+ if (!controller) {
224
+ const err = new TypeError(`'controller' argument is not provided.`);
225
+ throw err;
226
+ }
227
+
228
+ if (!facade) {
229
+ const err = new TypeError(`'facade' argument is invalid.`);
230
+ throw err;
231
+ }
232
+
233
+ // Set main facade:
234
+ if (facade.constructor.name === 'Function') {
235
+ controller[facadeName ?? 'facade'] = new facade();
236
+ }
237
+ else {
238
+ controller[facadeName ?? 'facade'] = facade;
239
+ }
240
+ }
241
+
242
+
204
243
  /**
205
244
  * Sets one of CRUD methods to Controller.
206
245
  *
@@ -1,5 +1,7 @@
1
1
  const Params = require('nodester/params');
2
2
 
3
+ const log = require('nodester/loggers/dev');
4
+
3
5
 
4
6
  module.exports = {
5
7
  getOne: _getOne,
@@ -29,11 +31,12 @@ async function _getOne(params) {
29
31
 
30
32
  const result = {
31
33
  [this.modelName.singular]: instance,
32
- count: 0 + instance !== null
34
+ count: 0 + (instance !== null)
33
35
  }
34
36
  return Promise.resolve(result);
35
37
  }
36
38
  catch(error) {
39
+ log.error(`${ [this.modelName.singular] }Facade.getOne error:`, error);
37
40
  return Promise.reject(error);
38
41
  }
39
42
  }
@@ -63,6 +66,7 @@ async function _getMany(params) {
63
66
  return Promise.resolve(result);
64
67
  }
65
68
  catch(error) {
69
+ log.error(`${ [this.modelName.singular] }Facade.getMany error:`, error);
66
70
  return Promise.reject(error);
67
71
  }
68
72
  }
@@ -96,7 +100,7 @@ async function _createOne(params) {
96
100
 
97
101
  const result = {
98
102
  [this.modelName.singular]: instance,
99
- count: instance === null ? 0 : 1
103
+ count: 0 + (instance !== null)
100
104
  }
101
105
 
102
106
  // Call after create.
@@ -105,6 +109,7 @@ async function _createOne(params) {
105
109
  return Promise.resolve(result);
106
110
  }
107
111
  catch(error) {
112
+ log.error(`${ [this.modelName.singular] }Facade.createOne error:`, error);
108
113
  return Promise.reject(error);
109
114
  }
110
115
  }
@@ -134,11 +139,12 @@ async function _updateOne(params) {
134
139
  const result = {
135
140
  success: isNewRecord === false,
136
141
  [this.modelName.singular]: instance,
137
- count: !!instance ? 1 : 0
142
+ count: 0 + (instance !== null)
138
143
  }
139
144
  return Promise.resolve(result);
140
145
  }
141
146
  catch(error) {
147
+ log.error(`${ [this.modelName.singular] }Facade.updateOne error:`, error);
142
148
  return Promise.reject(error);
143
149
  }
144
150
  }
@@ -168,6 +174,7 @@ async function _deleteOne(params) {
168
174
  return Promise.resolve(result);
169
175
  }
170
176
  catch(error) {
177
+ log.error(`${ [this.modelName.singular] }Facade.deleteOne error:`, error);
171
178
  return Promise.reject(error);
172
179
  }
173
180
  }
@@ -1,3 +1,10 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
 
2
9
  module.exports = class CustomError extends Error {
3
10
  constructor(message) {
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const {
9
+ NODESTER_QUERY_ERROR,
10
+ } = require('nodester/constants/ErrorCodes');
11
+
12
+
13
+ module.exports = class NodesterQueryError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+
17
+ this.name = NODESTER_QUERY_ERROR;
18
+ this.status = 422;
19
+
20
+ // Remove constructor info from stack.
21
+ Error.captureStackTrace(this, this.constructor);
22
+ }
23
+ }
@@ -1,9 +1,16 @@
1
- /*
2
- * Add all your custom errors in this file.
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
3
4
  */
5
+
6
+ 'use strict';
7
+
8
+
4
9
  const Err = require('./CustomError');
10
+ const NodesterQueryError = require('./NodesterQueryError');
5
11
 
6
12
 
7
13
  module.exports = {
8
- Err
14
+ Err,
15
+ NodesterQueryError
9
16
  }
@@ -0,0 +1,28 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ exports = module.exports = {
10
+ error: _error
11
+ }
12
+
13
+ /**
14
+ * Log error using console.error.
15
+ *
16
+ * @param {Array} args
17
+ *
18
+ * @alias error
19
+ * @public
20
+ */
21
+
22
+ function _error(...args) {
23
+ const activeEnv = process.env.NODE_ENV
24
+
25
+ if (activeEnv === 'development' || activeEnv === 'testing') {
26
+ console.error(...args);
27
+ }
28
+ }
@@ -1,3 +1,9 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const debug = require('debug')('nodester:interpreter:ModelsTree');
2
8
 
3
9
 
@@ -7,10 +13,12 @@ class ModelsTreeNode {
7
13
  this.parent = parent;
8
14
  this.activeParam = null;
9
15
  this.op = null;
16
+ this.fn = null;
10
17
 
11
18
  // for override:
12
19
  this.fields = [];
13
20
  this._where = {};
21
+ this._functions = [];
14
22
  this.skip = 0;
15
23
  this.limit = -1; // No limit
16
24
 
@@ -35,6 +43,10 @@ class ModelsTreeNode {
35
43
  return this._where;
36
44
  }
37
45
 
46
+ get functions() {
47
+ return this._functions;
48
+ }
49
+
38
50
  resetActiveParam() {
39
51
  this.activeParam = null;
40
52
  }
@@ -43,6 +55,10 @@ class ModelsTreeNode {
43
55
  this.op = null;
44
56
  }
45
57
 
58
+ resetFN() {
59
+ this.fn = null;
60
+ }
61
+
46
62
  addWhere(condition={}) {
47
63
  this._where = {
48
64
  ...this.where,
@@ -50,6 +66,10 @@ class ModelsTreeNode {
50
66
  }
51
67
  }
52
68
 
69
+ addFunction(fnParams={}) {
70
+ this._functions.push(fnParams);
71
+ }
72
+
53
73
  include(modelTreeNode) {
54
74
  modelTreeNode.parent = this;
55
75
  this.includes.push(modelTreeNode);
@@ -60,13 +80,16 @@ class ModelsTreeNode {
60
80
  return {
61
81
  model: this.model,
62
82
 
83
+ fields: this.fields,
84
+ functions: this.functions,
85
+
63
86
  where: this.where,
87
+
64
88
  skip: this.skip,
65
89
  limit: this.limit,
66
90
  order: this.order,
67
91
  order_by: this.order_by,
68
92
 
69
- fields: this.fields,
70
93
 
71
94
  includes: this.includes.map(i => i.toObject())
72
95
  }
@@ -1,3 +1,10 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+
1
8
  const Enum = require('../../../../enums/Enum');
2
9
  const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
3
10
  const util = require('util');
@@ -21,6 +28,10 @@ const OP_TOKENS = new Enum({
21
28
  LOWER_OR_EQUAL: 'lte'
22
29
  });
23
30
 
31
+ const FN_TOKENS = new Enum({
32
+ COUNT: 'count',
33
+ });
34
+
24
35
 
25
36
  module.exports = class QueryLexer {
26
37
  constructor(queryString='') {
@@ -71,22 +82,29 @@ module.exports = class QueryLexer {
71
82
  token = '';
72
83
  continue;
73
84
  }
74
- // If model subquery:
75
- else {
76
- const model = token;
77
- tree.use(model) ?? tree.include(model).use(model);
85
+
86
+ // If FN token:
87
+ if (FN_TOKENS.asArray.indexOf(token) > -1) {
88
+ // Set function token.
89
+ tree.node.fn = token;
78
90
  token = '';
91
+ continue;
92
+ }
79
93
 
80
- // Process subquery:
81
- i++;
82
- const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
83
- i += charsCount;
94
+ // If model subquery:
95
+ const model = token;
96
+ tree.use(model) ?? tree.include(model).use(model);
97
+ token = '';
84
98
 
85
- previousActive = model;
86
- tree.up();
99
+ // Process subquery:
100
+ i++;
101
+ const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
102
+ i += charsCount;
87
103
 
88
- continue;
89
- }
104
+ previousActive = model;
105
+ tree.up();
106
+
107
+ continue;
90
108
  }
91
109
 
92
110
  // ) can mean end of OP token params,
@@ -126,6 +144,40 @@ module.exports = class QueryLexer {
126
144
  continue;
127
145
  }
128
146
 
147
+ // If end of FN token:
148
+ if (!!tree.node.fn) {
149
+ // If token is empty, error:
150
+ if (token === '') {
151
+ const err = UnexpectedCharError(i, char);
152
+ throw err;
153
+ }
154
+
155
+ let fnParams = {};
156
+ switch (tree.node.fn) {
157
+ case 'count':
158
+ fnParams = {
159
+ fn: 'count',
160
+ args: [token]
161
+ };
162
+ break;
163
+ default:
164
+ fnParams = {
165
+ fn: [tree.node.fn],
166
+ args: [token]
167
+ };
168
+ break;
169
+ }
170
+
171
+ tree.node.addFunction(fnParams);
172
+
173
+ // Reset:
174
+ tree.node.resetFN();
175
+ tree.node.activeParam = 'includes';
176
+ token = '';
177
+ value = [];
178
+ continue;
179
+ }
180
+
129
181
  // If end of subquery:
130
182
  if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
131
183
  // Set value.
@@ -1,11 +1,20 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
2
8
 
9
+ const { isModel } = require('../utils/models');
10
+
3
11
 
4
12
  module.exports = class Colander {
5
13
 
6
14
  /*
7
15
  *
8
16
  * @param {Object|Model} optsOrModelDefinition
17
+ @ - @param {Model} model
9
18
  * - @param {Array} fields
10
19
  * - @param {Array} clauses
11
20
  * - @param {Object} includes
@@ -13,8 +22,10 @@ module.exports = class Colander {
13
22
  * -- @param {Object} attributes
14
23
  * -- @param {Object} clauses
15
24
  *
25
+ * @param {Boolean} noLimit
26
+ *
16
27
  */
17
- constructor(optsOrModelDefinition) {
28
+ constructor(optsOrModelDefinition, noLimit=false) {
18
29
  this._fields = [];
19
30
  this._clauses = [];
20
31
  this._includes = {};
@@ -25,24 +36,36 @@ module.exports = class Colander {
25
36
  limit: 3
26
37
  }
27
38
  }
39
+ if (noLimit === true) {
40
+ delete this._statics.clauses.limit;
41
+ }
28
42
 
29
43
  // If model:
30
- if (!!optsOrModelDefinition.tableName && typeof optsOrModelDefinition._schema === 'object') {
44
+ if (isModel(optsOrModelDefinition)) {
31
45
  this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
32
46
  this._clauses = CLAUSES;
33
47
  }
34
48
  // If options:
35
49
  else {
36
50
  const {
51
+ model,
37
52
  fields,
38
53
  clauses,
39
54
  includes,
40
55
  statics,
41
56
  } = optsOrModelDefinition;
42
57
 
58
+
59
+ // If fields are array:
43
60
  if (Array.isArray(fields)) {
44
61
  this._fields = fields;
45
62
  }
63
+ // If fields were not provided,
64
+ // but we have full model definition:
65
+ else if (isModel(model)) {
66
+ this._fields = Object.keys(model.tableAttributes);
67
+ }
68
+
46
69
 
47
70
  if (Array.isArray(clauses)) {
48
71
  this._clauses = clauses;
@@ -1,3 +1,9 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  // Dictionary of unsafe characters:
2
8
  const NOT_ALLOWED = [
3
9
  '{',
@@ -1,10 +1,18 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const { Op } = require('sequelize');
8
+ const NQueryError = require('../factories/errors/NodesterQueryError');
2
9
 
3
10
 
4
11
  module.exports = traverse;
5
12
 
6
13
  function traverse(queryNode, colander=null, model) {
7
14
 
15
+ const sequelize = model.sequelize;
8
16
  const fieldsAvailable = Object.keys(model.tableAttributes);
9
17
  const includesAvailable = model.getIncludesList();
10
18
 
@@ -18,6 +26,7 @@ function traverse(queryNode, colander=null, model) {
18
26
  where,
19
27
  includes,
20
28
  fields,
29
+ functions,
21
30
  clauses,
22
31
  } = _disassembleQueryNode(queryNode);
23
32
 
@@ -67,6 +76,55 @@ function traverse(queryNode, colander=null, model) {
67
76
  }
68
77
  // Fields\
69
78
 
79
+ // Functions:
80
+ for (const fnParams of functions) {
81
+
82
+ // If COUNT() is requested:
83
+ if (fnParams.fn === 'count') {
84
+ const countParams = fnParams.args;
85
+
86
+ const [ countTarget ] = countParams;
87
+ const RootModelName = model.options.name;
88
+ // Count can be requested for this model,
89
+ // or for any of the available uncludes.
90
+ const isForRootModel = countTarget === RootModelName.plural.toLowerCase();
91
+
92
+ // Compile request:
93
+ // Example:
94
+ // `(SELECT COUNT(*) FROM comments WHERE comments.morph_id=Morph.id)`
95
+
96
+ // Params for attribute:
97
+ let rawSQL = '(SELECT COUNT(*) FROM ';
98
+ let countAttribute = '_count';
99
+
100
+ // If request to count one of includes:
101
+ if (!isForRootModel) {
102
+ // Check if it's available:
103
+ if (
104
+ !colander
105
+ ||
106
+ !colander?.includes[countTarget]
107
+ ||
108
+ model.associations[countTarget] === undefined
109
+ ) {
110
+ const err = new NQueryError(`Count for ${ countTarget } is not available.`);
111
+ throw err;
112
+ }
113
+
114
+ const {
115
+ foreignKey,
116
+ sourceKey
117
+ } = model.associations[countTarget];
118
+ rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ RootModelName.singular }.${ sourceKey })`;
119
+ countAttribute = `${ countTarget }_count`;
120
+ }
121
+
122
+ newQuery.attributes.push(
123
+ [sequelize.literal(rawSQL), countAttribute]
124
+ );
125
+ }
126
+ }
127
+ // Functions\
70
128
 
71
129
  // Clauses:
72
130
  const order = {};
@@ -87,6 +145,7 @@ function traverse(queryNode, colander=null, model) {
87
145
 
88
146
  newQuery.limit = value;
89
147
  continue;
148
+
90
149
  case 'skip':
91
150
  // Do not set if 0:
92
151
  if (value === 0)
@@ -94,12 +153,15 @@ function traverse(queryNode, colander=null, model) {
94
153
 
95
154
  newQuery.offset = value;
96
155
  continue;
156
+
97
157
  case 'order':
98
158
  order.order = value;
99
159
  continue;
160
+
100
161
  case 'order_by':
101
162
  order.by = value;
102
163
  continue;
164
+
103
165
  default:
104
166
  continue;
105
167
  }
@@ -107,7 +169,10 @@ function traverse(queryNode, colander=null, model) {
107
169
 
108
170
  // "statics" override or set any query Clause:
109
171
  if (colander !== null) {
110
- for (let [clauseName, staticClauseValue] of Object.entries(colander.statics.clauses)) {
172
+ const staticClausesEntries = Object.entries(colander.statics.clauses);
173
+ for (let entry of staticClausesEntries) {
174
+ const [clauseName, staticClauseValue] = entry;
175
+
111
176
  switch(clauseName) {
112
177
  case 'limit':
113
178
  newQuery.limit = staticClauseValue;
@@ -130,7 +195,6 @@ function traverse(queryNode, colander=null, model) {
130
195
 
131
196
 
132
197
  // Order:
133
- const sequelize = model.sequelize;
134
198
  if ( ['rand', 'random'].indexOf(order.order) > -1) {
135
199
  newQuery.order = sequelize.random();
136
200
  }
@@ -150,6 +214,11 @@ function traverse(queryNode, colander=null, model) {
150
214
  break;
151
215
  // MAX/MIN\
152
216
 
217
+ case null:
218
+ case undefined:
219
+ newQuery.order = [ ['id', 'desc'] ];
220
+ break;
221
+
153
222
  default:
154
223
  newQuery.order = [ [order.by, order.order] ];
155
224
  break;
@@ -189,7 +258,6 @@ function traverse(queryNode, colander=null, model) {
189
258
  }
190
259
  // Where\
191
260
 
192
-
193
261
  return newQuery;
194
262
  }
195
263
 
@@ -252,9 +320,9 @@ function _addAssociationQuery(associationQuery, includeName, resultQuery) {
252
320
 
253
321
  function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
254
322
  let _value = value;
255
- const static = staticAttributes[attribute];
323
+ const staticAttribute = staticAttributes[attribute];
256
324
 
257
- // If attribute is Op (like, or, not, etc.):
325
+ // If attribute is Op (not, like, or, etc.):
258
326
  if (attribute in Op) {
259
327
  // Parse value:
260
328
  _value = _parseValue(_value, attribute);
@@ -265,8 +333,8 @@ function _parseWhereEntry(attribute, value, whereHolder, staticAttributes) {
265
333
  }
266
334
 
267
335
  // Static value overrides any other:
268
- if (!!static) {
269
- whereHolder[attribute] = static;
336
+ if (!!staticAttribute) {
337
+ whereHolder[attribute] = staticAttribute;
270
338
  return;
271
339
  }
272
340
 
@@ -279,6 +347,7 @@ function _disassembleQueryNode(queryNode) {
279
347
  where,
280
348
  includes,
281
349
  fields,
350
+ functions,
282
351
  ...clauses
283
352
  } = queryNode;
284
353
  // delete queryNode.model;
@@ -287,6 +356,7 @@ function _disassembleQueryNode(queryNode) {
287
356
  where: where ?? {},
288
357
  includes: includes ?? [],
289
358
  fields: fields ?? [],
359
+ functions: functions ?? [],
290
360
  clauses: clauses ?? []
291
361
  };
292
362
  }
@@ -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
 
@@ -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,29 +1,46 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "A boilerplate framework for Node.js",
5
5
  "exports": {
6
6
  ".": "./lib/application/index.js",
7
+
8
+ "./constants/ErrorCodes": "./lib/constants/ErrorCodes.js",
9
+
7
10
  "./controllers/methods": "./lib/controllers/methods/index.js",
8
11
  "./controllers/mixins": "./lib/controllers/mixins/index.js",
12
+
9
13
  "./database/connection": "./lib/database/connection.js",
10
14
  "./database/migration": "./lib/database/migration.js",
11
15
  "./database/utils": "./lib/database/utils.js",
16
+
12
17
  "./enum": "./lib/enums/Enum.js",
18
+
13
19
  "./facades/methods": "./lib/facades/methods/index.js",
14
20
  "./facades/mixins": "./lib/facades/mixins/index.js",
21
+
15
22
  "./factories/errors": "./lib/factories/errors/index.js",
16
23
  "./factories/responses/rest": "./lib/factories/responses/rest/index.js",
24
+
17
25
  "./http/codes": "./lib/http/codes/index.js",
26
+ "./loggers/console": "./lib/loggers/console.js",
27
+ "./loggers/dev": "./lib/loggers/dev.js",
28
+
18
29
  "./middlewares/formidable": "./lib/middlewares/formidable/index.js",
30
+
19
31
  "./models/associate": "./lib/models/associate.js",
20
32
  "./models/define": "./lib/models/define.js",
33
+
21
34
  "./params": "./lib/params/Params.js",
35
+
22
36
  "./ql/sequelize": "./lib/middlewares/ql/sequelize",
23
37
  "./queries/Colander": "./lib/queries/Colander.js",
24
38
  "./queries/traverse": "./lib/queries/traverse.js",
39
+
25
40
  "./route": "./lib/router/route.js",
26
41
  "./router": "./lib/router/index.js",
42
+
43
+ "./utils/sql": "./lib/utils/sql.util.js",
27
44
  "./utils/strings": "./lib/utils/strings.util.js"
28
45
  },
29
46
  "directories": {
@@ -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
- }
File without changes