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.
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,78 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const httpCodes = require('nodester/http/codes');
8
+
9
+ const Sanitizations = require('nodester/utils/sanitizations');
10
+
11
+
12
+ module.exports = extract;
13
+
14
+ function extract(body, filter=null, model) {
15
+
16
+ const sequelize = model.sequelize;
17
+ const modelFields = Object.keys(model.tableAttributes);
18
+ const availableIncludes = Object.keys(model.associations);
19
+
20
+ const bodyEntries = Object.entries(body);
21
+
22
+ const { statics } = filter;
23
+
24
+
25
+ // Result object.
26
+ const newBody = {};
27
+
28
+ for (const [key, value] of bodyEntries) {
29
+ const isInclude = availableIncludes.indexOf(key) > -1;
30
+ const isField = modelFields.indexOf(key) > -1;
31
+
32
+ if ((!isField || filter.fields.indexOf(key) === -1) && !isInclude) {
33
+ const err = new Error(`Field '${ key }' is not available.`);
34
+ err.status = httpCodes.NOT_ACCEPTABLE;
35
+ throw err;
36
+ }
37
+
38
+ if (isField) {
39
+ const column = model.rawAttributes[key];
40
+ const typeName = column.type.constructor.name;
41
+ // Optional validation.
42
+ const { validation } = column;
43
+
44
+ const sanitizationOptions = {
45
+ fallback: column.defaultValue ?? undefined,
46
+ min: validation?.min ?? undefined,
47
+ max: validation?.max ?? undefined
48
+ }
49
+ const sanitized = Sanitizations[typeName](value, sanitizationOptions);
50
+
51
+ newBody[key] = sanitized;
52
+
53
+ continue;
54
+ }
55
+
56
+ if (isInclude) {
57
+ const filterIncludes = Object.keys(filter.includes);
58
+
59
+ if (filterIncludes.indexOf(key) === -1) {
60
+ const err = new Error(`Include '${ key }' is not available.`);
61
+ err.status = httpCodes.NOT_ACCEPTABLE;
62
+ throw err;
63
+ }
64
+
65
+ const association = model.associations[key];
66
+
67
+ newBody[key] = extract(value[0], filter.includes[key], association.target);
68
+
69
+ continue;
70
+ }
71
+
72
+ const err = new Error(`Unknown field '${ key }'.`);
73
+ err.status = httpCodes.NOT_ACCEPTABLE;
74
+ throw err;
75
+ }
76
+
77
+ return newBody;
78
+ }
@@ -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,13 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const Params = require('nodester/params');
2
8
 
9
+ const log = require('nodester/loggers/dev');
10
+
3
11
 
4
12
  module.exports = {
5
13
  getOne: _getOne,
@@ -29,11 +37,12 @@ async function _getOne(params) {
29
37
 
30
38
  const result = {
31
39
  [this.modelName.singular]: instance,
32
- count: 0 + instance !== null
40
+ count: 0 + (instance !== null)
33
41
  }
34
42
  return Promise.resolve(result);
35
43
  }
36
44
  catch(error) {
45
+ log.error(`${ [this.modelName.singular] }Facade.getOne error:`, error);
37
46
  return Promise.reject(error);
38
47
  }
39
48
  }
@@ -63,6 +72,7 @@ async function _getMany(params) {
63
72
  return Promise.resolve(result);
64
73
  }
65
74
  catch(error) {
75
+ log.error(`${ [this.modelName.singular] }Facade.getMany error:`, error);
66
76
  return Promise.reject(error);
67
77
  }
68
78
  }
@@ -79,24 +89,17 @@ async function _createOne(params) {
79
89
  try {
80
90
  const {
81
91
  data,
82
- includes,
83
92
  } = Params(params, {
84
93
  data: null,
85
- includes: null,
86
94
  });
87
95
 
88
96
  const instance = await this.model.create({ ...data }, {
89
97
  include: this.model.getIncludesList(data)
90
98
  });
91
99
 
92
- // If includes are set, "find" this record with includes:
93
- if (!!includes && includes?.length > 0) {
94
- await instance.reload({ include: includes });
95
- }
96
-
97
100
  const result = {
98
101
  [this.modelName.singular]: instance,
99
- count: instance === null ? 0 : 1
102
+ count: 0 + (instance !== null)
100
103
  }
101
104
 
102
105
  // Call after create.
@@ -105,6 +108,7 @@ async function _createOne(params) {
105
108
  return Promise.resolve(result);
106
109
  }
107
110
  catch(error) {
111
+ log.error(`${ [this.modelName.singular] }Facade.createOne error:`, error);
108
112
  return Promise.reject(error);
109
113
  }
110
114
  }
@@ -134,11 +138,12 @@ async function _updateOne(params) {
134
138
  const result = {
135
139
  success: isNewRecord === false,
136
140
  [this.modelName.singular]: instance,
137
- count: !!instance ? 1 : 0
141
+ count: 0 + (instance !== null)
138
142
  }
139
143
  return Promise.resolve(result);
140
144
  }
141
145
  catch(error) {
146
+ log.error(`${ [this.modelName.singular] }Facade.updateOne error:`, error);
142
147
  return Promise.reject(error);
143
148
  }
144
149
  }
@@ -168,6 +173,7 @@ async function _deleteOne(params) {
168
173
  return Promise.resolve(result);
169
174
  }
170
175
  catch(error) {
176
+ log.error(`${ [this.modelName.singular] }Facade.deleteOne error:`, error);
171
177
  return Promise.reject(error);
172
178
  }
173
179
  }
@@ -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
  }
@@ -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
- module.exports = class Colander {
12
+ module.exports = class Filter {
5
13
 
6
14
  /*
7
15
  *
8
- * @param {Object|Model} optsOrModelDefinition
16
+ * @param {Model} model
17
+ * @param {Object} opts
9
18
  * - @param {Array} fields
10
19
  * - @param {Array} clauses
11
20
  * - @param {Object} includes
@@ -13,33 +22,49 @@ 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(model=null, opts=null) {
29
+ this._model = model;
30
+
18
31
  this._fields = [];
19
32
  this._clauses = [];
20
33
  this._includes = {};
21
34
 
22
35
  this._statics = {
23
36
  attributes: {},
24
- clauses: {
25
- limit: 3
26
- }
37
+ clauses: {}
27
38
  }
28
39
 
29
- // If model:
30
- if (!!optsOrModelDefinition.tableName && typeof optsOrModelDefinition._schema === 'object') {
31
- this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
40
+ // noLimit=false
41
+ // if (noLimit === true) {
42
+ // delete this._statics.clauses.limit;
43
+ // }
44
+
45
+ // If model is present:
46
+ if (isModel(this._model)) {
47
+ this._fields = Object.keys(this._model.tableAttributes);
32
48
  this._clauses = CLAUSES;
49
+
50
+ // ...and no options are provided,
51
+ // set default statics:
52
+ if (!opts) {
53
+ this.statics.clauses.limit = 3;
54
+ }
33
55
  }
34
- // If options:
35
- else {
56
+
57
+ // If options are defined:
58
+ if (!!opts) {
36
59
  const {
37
60
  fields,
38
61
  clauses,
39
62
  includes,
40
63
  statics,
41
- } = optsOrModelDefinition;
64
+ } = opts;
65
+
42
66
 
67
+ // If fields are array:
43
68
  if (Array.isArray(fields)) {
44
69
  this._fields = fields;
45
70
  }
@@ -0,0 +1,82 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const Enum = require('nodester/enum');
8
+
9
+
10
+ module.exports = new Enum({
11
+ // Informational:
12
+ 100: 'Continue',
13
+ 101: 'Switching Protocols',
14
+ 102: 'Processing',
15
+ 103: 'Early Hints',
16
+
17
+ // Success:
18
+ 200: 'OK',
19
+ 201: 'Created',
20
+ 202: 'Accepted',
21
+ 203: 'Non-Authoritative Information',
22
+ 204: 'No Content',
23
+ 205: 'Reset Content',
24
+ 206: 'Partial Content',
25
+ 207: 'Multi-Status',
26
+ 208: 'Already Reported',
27
+ 226: 'IM Used',
28
+
29
+ // Redirections:
30
+ 300: 'Multiple Choices',
31
+ 301: 'Moved Permanently',
32
+ 302: 'Found',
33
+ 303: 'See Other',
34
+ 304: 'Not Modified',
35
+ 305: 'Use Proxy',
36
+ 307: 'Temporary Redirect',
37
+ 308: 'Permanent Redirect',
38
+
39
+ // Client Errors:
40
+ 400: 'Bad Request',
41
+ 401: 'Unauthorized',
42
+ 402: 'Payment Required',
43
+ 403: 'Forbidden',
44
+ 404: 'Not Found',
45
+ 405: 'Method Not Allowed',
46
+ 406: 'Not Acceptable',
47
+ 407: 'Proxy Authentication Required',
48
+ 408: 'Request Timeout',
49
+ 409: 'Conflict',
50
+ 410: 'Gone',
51
+ 411: 'Length Required',
52
+ 412: 'Precondition Failed',
53
+ 413: 'Payload Too Large',
54
+ 414: 'URI Too Long',
55
+ 415: 'Unsupported Media Type',
56
+ 416: 'Range Not Satisfiable',
57
+ 417: 'Expectation Failed',
58
+ 418: 'I\'m a teapot',
59
+ 421: 'Misdirected Request',
60
+ 422: 'Unprocessable Entity',
61
+ 423: 'Locked',
62
+ 424: 'Failed Dependency',
63
+ 425: 'Too Early',
64
+ 426: 'Upgrade Required',
65
+ 428: 'Precondition Required',
66
+ 429: 'Too Many Requests',
67
+ 431: 'Request Header Fields Too Large',
68
+ 451: 'Unavailable For Legal Reasons',
69
+
70
+ // Server Errors:
71
+ 500: 'Internal Server Error',
72
+ 501: 'Not Implemented',
73
+ 502: 'Bad Gateway',
74
+ 503: 'Service Unavailable',
75
+ 504: 'Gateway Timeout',
76
+ 505: 'HTTP Version Not Supported',
77
+ 506: 'Variant Also Negotiates',
78
+ 507: 'Insufficient Storage',
79
+ 508: 'Loop Detected',
80
+ 510: 'Not Extended',
81
+ 511: 'Network Authentication Required'
82
+ });