nodester 0.1.4 → 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.
@@ -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
+ }
@@ -1,3 +1,9 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  const Params = require('nodester/params');
2
8
 
3
9
  const log = require('nodester/loggers/dev');
@@ -83,21 +89,14 @@ async function _createOne(params) {
83
89
  try {
84
90
  const {
85
91
  data,
86
- includes,
87
92
  } = Params(params, {
88
93
  data: null,
89
- includes: null,
90
94
  });
91
95
 
92
96
  const instance = await this.model.create({ ...data }, {
93
97
  include: this.model.getIncludesList(data)
94
98
  });
95
99
 
96
- // If includes are set, "find" this record with includes:
97
- if (!!includes && includes?.length > 0) {
98
- await instance.reload({ include: includes });
99
- }
100
-
101
100
  const result = {
102
101
  [this.modelName.singular]: instance,
103
102
  count: 0 + (instance !== null)
@@ -9,12 +9,12 @@ const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
9
9
  const { isModel } = require('../utils/models');
10
10
 
11
11
 
12
- module.exports = class Colander {
12
+ module.exports = class Filter {
13
13
 
14
14
  /*
15
15
  *
16
- * @param {Object|Model} optsOrModelDefinition
17
- @ - @param {Model} model
16
+ * @param {Model} model
17
+ * @param {Object} opts
18
18
  * - @param {Array} fields
19
19
  * - @param {Array} clauses
20
20
  * - @param {Object} includes
@@ -25,47 +25,49 @@ module.exports = class Colander {
25
25
  * @param {Boolean} noLimit
26
26
  *
27
27
  */
28
- constructor(optsOrModelDefinition, noLimit=false) {
28
+ constructor(model=null, opts=null) {
29
+ this._model = model;
30
+
29
31
  this._fields = [];
30
32
  this._clauses = [];
31
33
  this._includes = {};
32
34
 
33
35
  this._statics = {
34
36
  attributes: {},
35
- clauses: {
36
- limit: 3
37
- }
38
- }
39
- if (noLimit === true) {
40
- delete this._statics.clauses.limit;
37
+ clauses: {}
41
38
  }
42
39
 
43
- // If model:
44
- if (isModel(optsOrModelDefinition)) {
45
- 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);
46
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
+ }
47
55
  }
48
- // If options:
49
- else {
56
+
57
+ // If options are defined:
58
+ if (!!opts) {
50
59
  const {
51
- model,
52
60
  fields,
53
61
  clauses,
54
62
  includes,
55
63
  statics,
56
- } = optsOrModelDefinition;
64
+ } = opts;
57
65
 
58
66
 
59
67
  // If fields are array:
60
68
  if (Array.isArray(fields)) {
61
69
  this._fields = fields;
62
70
  }
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
-
69
71
 
70
72
  if (Array.isArray(clauses)) {
71
73
  this._clauses = clauses;
@@ -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
+ });
@@ -1,157 +1,82 @@
1
- const symbolsByTag = {
2
- // Informational:
3
- CONTINUE: Symbol('100: Continue'),
4
- SWITCHING_PROTOCOLS: Symbol('101: Switching Protocols'),
5
- PROCESSING: Symbol('102: Processing'),
6
- EARLY_HINTS: Symbol('103: Early Hints'),
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
7
6
 
8
- // Success:
9
- OK: Symbol('200: OK'),
10
- CREATED: Symbol('201: Created'),
11
- ACCEPTED: Symbol('202: Accepted'),
12
- NON_AUTHORITATIVE_INFORMATION: Symbol('203: Non-Authoritative Information'),
13
- NO_CONTENT: Symbol('204: No Content'),
14
- RESET_CONTENT: Symbol('205: Reset Content'),
15
- PARTIAL_CONTENT: Symbol('206: Partial Content'),
16
- MULTI_STATUS: Symbol('207: Multi-Status'),
17
- ALREADY_REPORTED: Symbol('208: Already Reported'),
18
- IM_USED: Symbol('226: IM Used'),
7
+ const Enum = require('nodester/enum');
19
8
 
20
- // Redirections:
21
- MULTIPLE_CHOICES: Symbol('300: Multiple Choices'),
22
- MOVED_PERMANENTLY: Symbol('301: Moved Permanently'),
23
- FOUND: Symbol('302: Found'),
24
- SEE_OTHER: Symbol('303: See Other'),
25
- NOT_MODIFIED: Symbol('304: Not Modified'),
26
- USE_PROXY: Symbol('305: Use Proxy'),
27
- TEMPORARY_REDIRECT: Symbol('307: Temporary Redirect'),
28
- PERMANENT_REDIRECT: Symbol('308: Permanent Redirect'),
29
-
30
- // Client Errors:
31
- BAD_REQUEST: Symbol('400: Bad Request'),
32
- UNAUTHORIZED: Symbol('401: Unauthorized'),
33
- PAYMENT_REQUIRED: Symbol('402: Payment Required'),
34
- FORBIDDEN: Symbol('403: Forbidden'),
35
- NOT_FOUND: Symbol('404: Not Found'),
36
- METHOD_NOT_ALLOWED: Symbol('405: Method Not Allowed'),
37
- NOT_ACCEPTABLE: Symbol('406: Not Acceptable'),
38
- PROXY_AUTHENTICATION_REQUIRED: Symbol('407: Proxy Authentication Required'),
39
- REQUEST_TIMEOUT: Symbol('408: Request Timeout'),
40
- CONFLICT: Symbol('409: Conflict'),
41
- GONE: Symbol('410: Gone'),
42
- LENGTH_REQUIRED: Symbol('411: Length Required'),
43
- PRECONDITION_FAILED: Symbol('412: Precondition Failed'),
44
- PAYLOAD_TOO_LARGE: Symbol('413: Payload Too Large'),
45
- URI_TOO_LONG: Symbol('414: URI Too Long'),
46
- UNSUPPORTED_MEDIA_TYPE: Symbol('415: Unsupported Media Type'),
47
- RANGE_NOT_SATISFIABLE: Symbol('416: Range Not Satisfiable'),
48
- EXPECTATION_FAILED: Symbol('417: Expectation Failed'),
49
- IM_A_TEAPOT: Symbol("418: I'm a teapot"),
50
- MISDIRECTED_REQUEST: Symbol('421: Misdirected Request'),
51
- UNPROCESSABLE_ENTITY: Symbol('422: Unprocessable Entity'),
52
- LOCKED: Symbol('423: Locked'),
53
- FAILED_DEPENDENCY: Symbol('424: Failed Dependency'),
54
- TOO_EARLY: Symbol('425: Too Early'),
55
- UPGRADE_REQUIRED: Symbol('426: Upgrade Required'),
56
- PRECONDITION_REQUIRED: Symbol('428: Precondition Required'),
57
- TOO_MANY_REQUESTS: Symbol('429: Too Many Requests'),
58
- REQUEST_HEADER_FIELDS_TOO_LARGE: Symbol('431: Request Header Fields Too Large'),
59
- UNAVAILABLE_FOR_LEGAL_REASONS: Symbol('451: Unavailable For Legal Reasons'),
60
9
 
61
- // Server Errors:
62
- INTERNAL_SERVER_ERROR: Symbol('500: Internal Server Error'),
63
- NOT_IMPLEMENTED: Symbol('501: Not Implemented'),
64
- BAD_GATEWAY: Symbol('502: Bad Gateway'),
65
- SERVICE_UNAVAILABLE: Symbol('503: Service Unavailable'),
66
- GATEWAY_TIMEOUT: Symbol('504: Gateway Timeout'),
67
- HTTP_VERSION_NOT_SUPPORTED: Symbol('505: HTTP Version Not Supported'),
68
- VARIANT_ALSO_NEGOTIATES: Symbol('506: Variant Also Negotiates'),
69
- INSUFFICIENT_STORAGE: Symbol('507: Insufficient Storage'),
70
- LOOP_DETECTED: Symbol('508: Loop Detected'),
71
- NOT_EXTENDED: Symbol('510: Not Extended'),
72
- NETWORK_AUTHENTICATION_REQUIRED: Symbol('511: Network Authentication Required')
73
- }
74
-
75
- const descriptionsByCode = {
10
+ module.exports = new Enum({
76
11
  // Informational:
77
- 100: 'Continue',
78
- 101: 'Switching Protocols',
79
- 102: 'Processing',
80
- 103: 'Early Hints',
12
+ CONTINUE: 100,
13
+ SWITCHING_PROTOCOLS: 101,
14
+ PROCESSING: 102,
15
+ EARLY_HINTS: 103,
81
16
 
82
17
  // Success:
83
- 200: 'OK',
84
- 201: 'Created',
85
- 202: 'Accepted',
86
- 203: 'Non-Authoritative Information',
87
- 204: 'No Content',
88
- 205: 'Reset Content',
89
- 206: 'Partial Content',
90
- 207: 'Multi-Status',
91
- 208: 'Already Reported',
92
- 226: 'IM Used',
18
+ OK: 200,
19
+ CREATED: 201,
20
+ ACCEPTED: 202,
21
+ NON_AUTHORITATIVE_INFORMATION: 203,
22
+ NO_CONTENT: 204,
23
+ RESET_CONTENT: 205,
24
+ PARTIAL_CONTENT: 206,
25
+ MULTI_STATUS: 207,
26
+ ALREADY_REPORTED: 208,
27
+ IM_USED: 226,
93
28
 
94
29
  // Redirections:
95
- 300: 'Multiple Choices',
96
- 301: 'Moved Permanently',
97
- 302: 'Found',
98
- 303: 'See Other',
99
- 304: 'Not Modified',
100
- 305: 'Use Proxy',
101
- 307: 'Temporary Redirect',
102
- 308: 'Permanent Redirect',
30
+ MULTIPLE_CHOICES: 300,
31
+ MOVED_PERMANENTLY: 301,
32
+ FOUND: 302,
33
+ SEE_OTHER: 303,
34
+ NOT_MODIFIED: 304,
35
+ USE_PROXY: 305,
36
+ TEMPORARY_REDIRECT: 307,
37
+ PERMANENT_REDIRECT: 308,
103
38
 
104
39
  // Client Errors:
105
- 400: 'Bad Request',
106
- 401: 'Unauthorized',
107
- 402: 'Payment Required',
108
- 403: 'Forbidden',
109
- 404: 'Not Found',
110
- 405: 'Method Not Allowed',
111
- 406: 'Not Acceptable',
112
- 407: 'Proxy Authentication Required',
113
- 408: 'Request Timeout',
114
- 409: 'Conflict',
115
- 410: 'Gone',
116
- 411: 'Length Required',
117
- 412: 'Precondition Failed',
118
- 413: 'Payload Too Large',
119
- 414: 'URI Too Long',
120
- 415: 'Unsupported Media Type',
121
- 416: 'Range Not Satisfiable',
122
- 417: 'Expectation Failed',
123
- 418: 'I\'m a teapot',
124
- 421: 'Misdirected Request',
125
- 422: 'Unprocessable Entity',
126
- 423: 'Locked',
127
- 424: 'Failed Dependency',
128
- 425: 'Too Early',
129
- 426: 'Upgrade Required',
130
- 428: 'Precondition Required',
131
- 429: 'Too Many Requests',
132
- 431: 'Request Header Fields Too Large',
133
- 451: 'Unavailable For Legal Reasons',
40
+ BAD_REQUEST: 400,
41
+ UNAUTHORIZED: 401,
42
+ PAYMENT_REQUIRED: 402,
43
+ FORBIDDEN: 403,
44
+ NOT_FOUND: 404,
45
+ METHOD_NOT_ALLOWED: 405,
46
+ NOT_ACCEPTABLE: 406,
47
+ PROXY_AUTHENTICATION_REQUIRED: 407,
48
+ REQUEST_TIMEOUT: 408,
49
+ CONFLICT: 409,
50
+ GONE: 410,
51
+ LENGTH_REQUIRED: 411,
52
+ PRECONDITION_FAILED: 412,
53
+ PAYLOAD_TOO_LARGE: 413,
54
+ URI_TOO_LONG: 414,
55
+ UNSUPPORTED_MEDIA_TYPE: 415,
56
+ RANGE_NOT_SATISFIABLE: 416,
57
+ EXPECTATION_FAILED: 417,
58
+ IM_A_TEAPOT: 418,
59
+ MISDIRECTED_REQUEST: 421,
60
+ UNPROCESSABLE_ENTITY: 422,
61
+ LOCKED: 423,
62
+ FAILED_DEPENDENCY: 424,
63
+ TOO_EARLY: 425,
64
+ UPGRADE_REQUIRED: 426,
65
+ PRECONDITION_REQUIRED: 428,
66
+ TOO_MANY_REQUESTS: 429,
67
+ REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
68
+ UNAVAILABLE_FOR_LEGAL_REASONS: 451,
134
69
 
135
70
  // Server Errors:
136
- 500: 'Internal Server Error',
137
- 501: 'Not Implemented',
138
- 502: 'Bad Gateway',
139
- 503: 'Service Unavailable',
140
- 504: 'Gateway Timeout',
141
- 505: 'HTTP Version Not Supported',
142
- 506: 'Variant Also Negotiates',
143
- 507: 'Insufficient Storage',
144
- 508: 'Loop Detected',
145
- 510: 'Not Extended',
146
- 511: 'Network Authentication Required'
147
- };
148
-
149
- module.exports = {
150
- descriptions: {
151
- byCode: descriptionsByCode
152
- },
153
-
154
- symbols: {
155
- byTag: symbolsByTag
156
- }
157
- }
71
+ INTERNAL_SERVER_ERROR: 500,
72
+ NOT_IMPLEMENTED: 501,
73
+ BAD_GATEWAY: 502,
74
+ SERVICE_UNAVAILABLE: 503,
75
+ GATEWAY_TIMEOUT: 504,
76
+ HTTP_VERSION_NOT_SUPPORTED: 505,
77
+ VARIANT_ALSO_NEGOTIATES: 506,
78
+ INSUFFICIENT_STORAGE: 507,
79
+ LOOP_DETECTED: 508,
80
+ NOT_EXTENDED: 510,
81
+ NETWORK_AUTHENTICATION_REQUIRED: 511,
82
+ });
@@ -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
+ CONTINUE: Symbol('100: Continue'),
13
+ SWITCHING_PROTOCOLS: Symbol('101: Switching Protocols'),
14
+ PROCESSING: Symbol('102: Processing'),
15
+ EARLY_HINTS: Symbol('103: Early Hints'),
16
+
17
+ // Success:
18
+ OK: Symbol('200: OK'),
19
+ CREATED: Symbol('201: Created'),
20
+ ACCEPTED: Symbol('202: Accepted'),
21
+ NON_AUTHORITATIVE_INFORMATION: Symbol('203: Non-Authoritative Information'),
22
+ NO_CONTENT: Symbol('204: No Content'),
23
+ RESET_CONTENT: Symbol('205: Reset Content'),
24
+ PARTIAL_CONTENT: Symbol('206: Partial Content'),
25
+ MULTI_STATUS: Symbol('207: Multi-Status'),
26
+ ALREADY_REPORTED: Symbol('208: Already Reported'),
27
+ IM_USED: Symbol('226: IM Used'),
28
+
29
+ // Redirections:
30
+ MULTIPLE_CHOICES: Symbol('300: Multiple Choices'),
31
+ MOVED_PERMANENTLY: Symbol('301: Moved Permanently'),
32
+ FOUND: Symbol('302: Found'),
33
+ SEE_OTHER: Symbol('303: See Other'),
34
+ NOT_MODIFIED: Symbol('304: Not Modified'),
35
+ USE_PROXY: Symbol('305: Use Proxy'),
36
+ TEMPORARY_REDIRECT: Symbol('307: Temporary Redirect'),
37
+ PERMANENT_REDIRECT: Symbol('308: Permanent Redirect'),
38
+
39
+ // Client Errors:
40
+ BAD_REQUEST: Symbol('400: Bad Request'),
41
+ UNAUTHORIZED: Symbol('401: Unauthorized'),
42
+ PAYMENT_REQUIRED: Symbol('402: Payment Required'),
43
+ FORBIDDEN: Symbol('403: Forbidden'),
44
+ NOT_FOUND: Symbol('404: Not Found'),
45
+ METHOD_NOT_ALLOWED: Symbol('405: Method Not Allowed'),
46
+ NOT_ACCEPTABLE: Symbol('406: Not Acceptable'),
47
+ PROXY_AUTHENTICATION_REQUIRED: Symbol('407: Proxy Authentication Required'),
48
+ REQUEST_TIMEOUT: Symbol('408: Request Timeout'),
49
+ CONFLICT: Symbol('409: Conflict'),
50
+ GONE: Symbol('410: Gone'),
51
+ LENGTH_REQUIRED: Symbol('411: Length Required'),
52
+ PRECONDITION_FAILED: Symbol('412: Precondition Failed'),
53
+ PAYLOAD_TOO_LARGE: Symbol('413: Payload Too Large'),
54
+ URI_TOO_LONG: Symbol('414: URI Too Long'),
55
+ UNSUPPORTED_MEDIA_TYPE: Symbol('415: Unsupported Media Type'),
56
+ RANGE_NOT_SATISFIABLE: Symbol('416: Range Not Satisfiable'),
57
+ EXPECTATION_FAILED: Symbol('417: Expectation Failed'),
58
+ IM_A_TEAPOT: Symbol("418: I'm a teapot"),
59
+ MISDIRECTED_REQUEST: Symbol('421: Misdirected Request'),
60
+ UNPROCESSABLE_ENTITY: Symbol('422: Unprocessable Entity'),
61
+ LOCKED: Symbol('423: Locked'),
62
+ FAILED_DEPENDENCY: Symbol('424: Failed Dependency'),
63
+ TOO_EARLY: Symbol('425: Too Early'),
64
+ UPGRADE_REQUIRED: Symbol('426: Upgrade Required'),
65
+ PRECONDITION_REQUIRED: Symbol('428: Precondition Required'),
66
+ TOO_MANY_REQUESTS: Symbol('429: Too Many Requests'),
67
+ REQUEST_HEADER_FIELDS_TOO_LARGE: Symbol('431: Request Header Fields Too Large'),
68
+ UNAVAILABLE_FOR_LEGAL_REASONS: Symbol('451: Unavailable For Legal Reasons'),
69
+
70
+ // Server Errors:
71
+ INTERNAL_SERVER_ERROR: Symbol('500: Internal Server Error'),
72
+ NOT_IMPLEMENTED: Symbol('501: Not Implemented'),
73
+ BAD_GATEWAY: Symbol('502: Bad Gateway'),
74
+ SERVICE_UNAVAILABLE: Symbol('503: Service Unavailable'),
75
+ GATEWAY_TIMEOUT: Symbol('504: Gateway Timeout'),
76
+ HTTP_VERSION_NOT_SUPPORTED: Symbol('505: HTTP Version Not Supported'),
77
+ VARIANT_ALSO_NEGOTIATES: Symbol('506: Variant Also Negotiates'),
78
+ INSUFFICIENT_STORAGE: Symbol('507: Insufficient Storage'),
79
+ LOOP_DETECTED: Symbol('508: Loop Detected'),
80
+ NOT_EXTENDED: Symbol('510: Not Extended'),
81
+ NETWORK_AUTHENTICATION_REQUIRED: Symbol('511: Network Authentication Required')
82
+ });
@@ -1,4 +1,5 @@
1
1
  const QueryLexer = require('./interpreter/QueryLexer');
2
+ const httpCodes = require('nodester/http/codes');
2
3
 
3
4
 
4
5
  module.exports = NodesterQL;
@@ -18,7 +19,6 @@ async function NodesterQL(req, res, next) {
18
19
  }
19
20
 
20
21
  try {
21
- // Convert to URLSearchParams.
22
22
  const queryString = req.url.split('?')[1];
23
23
 
24
24
  const lexer = new QueryLexer(queryString);
@@ -28,7 +28,7 @@ async function NodesterQL(req, res, next) {
28
28
  next();
29
29
  }
30
30
  catch(error) {
31
- res.status(422);
31
+ res.status(error.status ?? httpCodes.UNPROCESSABLE_ENTITY);
32
32
  res.json({ error: error.toString() });
33
33
  }
34
34
  }
@@ -5,7 +5,7 @@
5
5
  'use strict';
6
6
 
7
7
 
8
- const Enum = require('../../../../enums/Enum');
8
+ const Enum = require('nodester/enum');
9
9
  const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
10
10
  const util = require('util');
11
11
  const debug = require('debug')('nodester:interpreter:QueryLexer');
@@ -60,7 +60,7 @@ module.exports = class QueryLexer {
60
60
  const isSubQuery = tree.node.model !== 'root';
61
61
  debug({ isSubQuery, startAt });
62
62
 
63
- // Token is accumulated char-by-char.
63
+ // Token is String, accumulated char-by-char.
64
64
  let token = '';
65
65
  // Value of param ('id=10' OR 'fields=id,text').
66
66
  let value = [];
@@ -375,7 +375,7 @@ module.exports = class QueryLexer {
375
375
  const param = this.parseParamFromToken(token);
376
376
 
377
377
  if (isSubQuery === true && param === 'includes') {
378
- const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model' or 'model.model+model'.`);
378
+ const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model1' or 'model.model1+model2'.`);
379
379
  throw err;
380
380
  }
381
381
 
@@ -1,9 +1,13 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
1
7
  // CRUD mixins.
2
8
  const { implementsCRUD } = require('./mixins');
3
9
  // ORM.
4
10
  const { DataTypes } = require('sequelize');
5
- // NQL.
6
- const Colander = require('../queries/Colander');
7
11
 
8
12
 
9
13
  module.exports = defineModel;
@@ -52,7 +56,7 @@ function defineModel(
52
56
  implementsCRUD(model);
53
57
  }
54
58
 
55
- // Association helpers:
59
+ // Associations:
56
60
  model.associate = (models) => {};
57
61
  model.getIncludesList = _getIncludesList.bind(model);
58
62
 
@@ -61,7 +65,6 @@ function defineModel(
61
65
  const values = { ...this.get() };
62
66
  return values;
63
67
  }
64
- // Instance methods\
65
68
 
66
69
  return model;
67
70
  }
@@ -10,7 +10,7 @@ const {
10
10
 
11
11
  // Nodester query:
12
12
  const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
13
- const traverseNQuery = require('../queries/traverse');
13
+ const traverseNQuery = require('nodester/query/traverse');
14
14
 
15
15
 
16
16
  module.exports = {
@@ -6,11 +6,12 @@
6
6
 
7
7
  const { Op } = require('sequelize');
8
8
  const NQueryError = require('../factories/errors/NodesterQueryError');
9
+ const httpCodes = require('nodester/http/codes');
9
10
 
10
11
 
11
12
  module.exports = traverse;
12
13
 
13
- function traverse(queryNode, colander=null, model) {
14
+ function traverse(queryNode, filter=null, model) {
14
15
 
15
16
  const sequelize = model.sequelize;
16
17
  const fieldsAvailable = Object.keys(model.tableAttributes);
@@ -33,9 +34,9 @@ function traverse(queryNode, colander=null, model) {
33
34
 
34
35
  // Fields:
35
36
  //
36
- // If Colander is not set,
37
+ // If Filter is not set,
37
38
  // use every available field:
38
- if (colander === null) {
39
+ if (filter === null) {
39
40
  for (let field of fieldsAvailable) {
40
41
  // If no query filter or field is requested:
41
42
  if (fields.length === 0 || fields.indexOf(field) > -1) {
@@ -44,20 +45,21 @@ function traverse(queryNode, colander=null, model) {
44
45
  }
45
46
  }
46
47
  }
47
- // Colander is present:
48
+ // Filter is present:
48
49
  else {
49
50
  // If no query fields were set,
50
- // use the ones from Colander,
51
+ // use the ones from Filter,
51
52
  // If query fields were set,
52
- // put them through Colander:
53
- for (let field of colander.fields) {
53
+ // put them through Filter:
54
+ for (let field of filter.fields) {
54
55
  if (fieldsAvailable.indexOf(field) === -1) {
55
- 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;
56
58
  throw err;
57
59
  }
58
60
 
59
61
  // If field is not in available set:
60
- // if (colander.fields.indexOf(field) === -1) {
62
+ // if (filter.fields.indexOf(field) === -1) {
61
63
  // continue;
62
64
  // }
63
65
 
@@ -72,6 +74,7 @@ function traverse(queryNode, colander=null, model) {
72
74
  // At least 1 field is mandatory:
73
75
  if (newQuery.attributes.length === 0) {
74
76
  const err = new TypeError(`No fields were selected.`);
77
+ err.status = httpCodes.NOT_ACCEPTABLE;
75
78
  throw err;
76
79
  }
77
80
  // Fields\
@@ -101,13 +104,14 @@ function traverse(queryNode, colander=null, model) {
101
104
  if (!isForRootModel) {
102
105
  // Check if it's available:
103
106
  if (
104
- !colander
107
+ !filter
105
108
  ||
106
- !colander?.includes[countTarget]
109
+ !filter?.includes[countTarget]
107
110
  ||
108
111
  model.associations[countTarget] === undefined
109
112
  ) {
110
- const err = new NQueryError(`Count for ${ countTarget } is not available.`);
113
+ const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
114
+ err.status = httpCodes.NOT_ACCEPTABLE;
111
115
  throw err;
112
116
  }
113
117
 
@@ -132,8 +136,8 @@ function traverse(queryNode, colander=null, model) {
132
136
  const clausesEntries = Object.entries(clauses);
133
137
  for (let [clauseName, value] of clausesEntries) {
134
138
  // If clause is not available:
135
- if (colander != null) {
136
- if (colander.clauses.indexOf(clauseName) === -1)
139
+ if (filter != null) {
140
+ if (filter.clauses.indexOf(clauseName) === -1)
137
141
  continue;
138
142
  }
139
143
 
@@ -168,8 +172,9 @@ function traverse(queryNode, colander=null, model) {
168
172
  }
169
173
 
170
174
  // "statics" override or set any query Clause:
171
- if (colander !== null) {
172
- const staticClausesEntries = Object.entries(colander.statics.clauses);
175
+ if (filter !== null) {
176
+ const staticClausesEntries = Object.entries(filter.statics.clauses);
177
+
173
178
  for (let entry of staticClausesEntries) {
174
179
  const [clauseName, staticClauseValue] = entry;
175
180
 
@@ -235,21 +240,22 @@ function traverse(queryNode, colander=null, model) {
235
240
 
236
241
  const includeIndex = leftIncludes.indexOf(includeName);
237
242
  if (includeIndex === -1) {
238
- const err = new TypeError(`No include named ${ includeName }`);
243
+ const err = new TypeError(`No include named '${ includeName }'`);
244
+ err.status = httpCodes.NOT_ACCEPTABLE;
239
245
  throw err;
240
246
  }
241
247
 
242
248
  leftIncludes.splice(includeIndex, 1);
243
249
  }
244
250
 
245
- _traverseIncludes(includes, model, colander, newQuery)
251
+ _traverseIncludes(includes, model, filter, newQuery)
246
252
  // Includes\
247
253
 
248
254
 
249
255
  // Where:
250
256
  const whereEntries = Object.entries(where);
251
257
  for (let [attribute, value] of whereEntries) {
252
- _parseWhereEntry(attribute, value, newQuery.where, colander.statics.attributes);
258
+ _parseWhereEntry(attribute, value, newQuery.where, filter.statics.attributes);
253
259
  }
254
260
 
255
261
  // If "where" was not set:
@@ -262,16 +268,17 @@ function traverse(queryNode, colander=null, model) {
262
268
  }
263
269
 
264
270
 
265
- function _traverseIncludes(includes, model, colander, resultQuery) {
266
- // If no Colander:
267
- if (colander === null) {
271
+ function _traverseIncludes(includes, model, filter=null, resultQuery) {
272
+ // If no Filter:
273
+ if (filter === null) {
268
274
  for (let include of includes) {
269
275
  const includeName = include.model;
270
276
  const association = model.associations[includeName];
271
277
 
272
278
  // If no such association:
273
279
  if (!association) {
274
- const err = new TypeError(`No include ${ includeName }`);
280
+ const err = new TypeError(`No include '${ includeName }'`);
281
+ err.status = httpCodes.NOT_ACCEPTABLE;
275
282
  throw err;
276
283
  }
277
284
 
@@ -282,14 +289,15 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
282
289
  _addAssociationQuery(associationQuery, includeName, resultQuery);
283
290
  }
284
291
  }
285
- // Colander is present:
292
+ // Filter is present:
286
293
  else {
287
- const colanderIncludeEntries = Object.entries(colander.includes);
288
- for (let [includeName, includeColander] of colanderIncludeEntries) {
294
+ const filterIncludeEntries = Object.entries(filter.includes);
295
+ for (let [includeName, includeFilter] of filterIncludeEntries) {
289
296
  const association = model.associations[includeName];
290
297
  // If no such association:
291
298
  if (!association) {
292
299
  const err = new TypeError(`No include ${ includeName }`);
300
+ err.status = httpCodes.NOT_ACCEPTABLE;
293
301
  throw err;
294
302
  }
295
303
 
@@ -300,7 +308,7 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
300
308
 
301
309
  const includeModel = association.target;
302
310
  // Build query for this include.
303
- const associationQuery = traverse(include, colander.includes[includeName], includeModel);
311
+ const associationQuery = traverse(include, filter.includes[includeName], includeModel);
304
312
 
305
313
  _addAssociationQuery(associationQuery, includeName, resultQuery);
306
314
  }
@@ -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
 
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A boilerplate framework for Node.js",
5
5
  "exports": {
6
6
  ".": "./lib/application/index.js",
7
7
 
8
+ "./body/extract": "./lib/body/extract.js",
9
+
8
10
  "./constants/ErrorCodes": "./lib/constants/ErrorCodes.js",
9
11
 
10
12
  "./controllers/methods": "./lib/controllers/methods/index.js",
@@ -22,7 +24,12 @@
22
24
  "./factories/errors": "./lib/factories/errors/index.js",
23
25
  "./factories/responses/rest": "./lib/factories/responses/rest/index.js",
24
26
 
27
+ "./filter": "./lib/filters/Filter.js",
28
+
25
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
+
26
33
  "./loggers/console": "./lib/loggers/console.js",
27
34
  "./loggers/dev": "./lib/loggers/dev.js",
28
35
 
@@ -34,14 +41,14 @@
34
41
  "./params": "./lib/params/Params.js",
35
42
 
36
43
  "./ql/sequelize": "./lib/middlewares/ql/sequelize",
37
- "./queries/Colander": "./lib/queries/Colander.js",
38
- "./queries/traverse": "./lib/queries/traverse.js",
44
+ "./query/traverse": "./lib/query/traverse.js",
39
45
 
40
46
  "./route": "./lib/router/route.js",
41
47
  "./router": "./lib/router/index.js",
42
48
 
43
49
  "./utils/sql": "./lib/utils/sql.util.js",
44
- "./utils/strings": "./lib/utils/strings.util.js"
50
+ "./utils/strings": "./lib/utils/strings.util.js",
51
+ "./utils/sanitizations": "./lib/utils/sanitizations.util.js"
45
52
  },
46
53
  "directories": {
47
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,145 +0,0 @@
1
- /*!
2
- * /nodester
3
- * MIT Licensed
4
- */
5
- 'use strict';
6
-
7
- // Dictionary of unsafe characters:
8
- const NOT_ALLOWED = [
9
- '{',
10
- '}',
11
- // `\`,
12
- '^',
13
- '~',
14
- '[',
15
- ']',
16
- '`'
17
- ];
18
-
19
- const util = require('util');
20
-
21
- /*
22
- * NodesterQueryParams is a ready-to-use replacement for URLSearchParams.
23
- * The only difference is that NodesterQueryParams
24
- * respects nested "&" during parsing.
25
- */
26
- module.exports = class NodesterQueryParams {
27
- constructor(queryString='') {
28
- // Type validateion:
29
- if (typeof queryString !== 'string') {
30
- const err = new TypeError(`'query' must be a String.`);
31
- throw err;
32
- }
33
-
34
- // You never know if it's encoded or not.
35
- const decoded = decodeURI(queryString);
36
-
37
- // Indicates, how deep the char is inside different ().
38
- let deep = 0;
39
-
40
- const paramLevels = {};
41
-
42
- // Current query parameter.
43
- let param = '';
44
-
45
- // Current query token.
46
- let token = '';
47
-
48
- this._map = new Map();
49
-
50
- for (let i=0; i < decoded.length; i++) {
51
- const char = decoded[i];
52
-
53
- // Validate char:
54
- if (NOT_ALLOWED.indexOf(char) > -1) {
55
- const err = new TypeError(`Invalid query token at ${ i }: '${ char }'`);
56
- throw err;
57
- }
58
-
59
- if (char === '(') {
60
- // Error If there is nothing behind:
61
- if (param.length === 0) {
62
- const err = new TypeError(`Invalid query token at ${ i }: '${ char }'`);
63
- throw err;
64
- }
65
-
66
- // If not special token, go deeper:
67
- if (['and', 'or', 'xor', 'not', '!', '|', 'like'].indexOf(token) === -1) {
68
- this.append(param, token);
69
- deep++;
70
- }
71
-
72
- // will set ( in token later.
73
- }
74
-
75
- if (char === ')') {
76
- // If sub-level:
77
- if (deep > 0) {
78
- deep--
79
- }
80
- }
81
-
82
- // & can mean the end of key=value pair:
83
- if (char === '&') {
84
- // If top-level:
85
- if (deep === 0) {
86
- this.append(param, token);
87
- param = '';
88
- token = '';
89
- continue;
90
- }
91
-
92
- // If sub-level do nothing.
93
- }
94
-
95
- // = can mean the end of param name:
96
- if (char === '=') {
97
- // If top-level:
98
- if (deep === 0) {
99
- param = token;
100
- token = '';
101
- }
102
- }
103
-
104
- // Continue building token:
105
- if (char !== '=' || deep > 0 ) {
106
- token += char;
107
- }
108
-
109
- // If last char:
110
- if (i === decoded.length-1) {
111
- // Validate:
112
- if (deep > 0) {
113
- const err = new TypeError(`Missing ')' at ${ i }`);
114
- throw err;
115
- }
116
-
117
- this.append(param, token);
118
- }
119
- }
120
- }
121
-
122
- append(...args) {
123
- return this._map.set(...args);
124
- }
125
-
126
- get(...args) {
127
- return this._map.get(...args);
128
- }
129
-
130
- delete(...args) {
131
- return this._map.delete(...args);
132
- }
133
-
134
- entries(...args) {
135
- return this._map.entries(...args);
136
- }
137
-
138
- toString() {
139
- return this._map.toString();
140
- }
141
-
142
- [util.inspect.custom](depth, opts) {
143
- return this._map;
144
- }
145
- }