nodester 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/Readme.md +16 -55
  2. package/lib/application/index.js +174 -63
  3. package/lib/body/extract.js +89 -0
  4. package/lib/constants/Bounds.js +15 -0
  5. package/lib/constants/Clauses.js +13 -0
  6. package/lib/constants/ResponseFormats.js +2 -2
  7. package/lib/controllers/methods/index.js +7 -0
  8. package/lib/controllers/mixins/index.js +36 -36
  9. package/lib/database/connection.js +6 -0
  10. package/lib/database/migration.js +14 -4
  11. package/lib/facades/methods/index.js +16 -16
  12. package/lib/facades/mixins/index.js +67 -13
  13. package/lib/factories/responses/rest.js +25 -13
  14. package/lib/http/codes/descriptions.js +82 -0
  15. package/lib/http/codes/index.js +70 -145
  16. package/lib/http/codes/symbols.js +82 -0
  17. package/lib/http/{request.js → request/index.js} +53 -75
  18. package/lib/http/request/utils.js +27 -0
  19. package/lib/http/response/headers.js +138 -0
  20. package/lib/http/response/index.js +248 -0
  21. package/lib/http/response/utils.js +38 -0
  22. package/lib/middlewares/SearchParams/index.js +25 -0
  23. package/lib/middlewares/cookies/index.js +44 -0
  24. package/lib/middlewares/etag/index.js +32 -15
  25. package/lib/middlewares/formidable/index.js +30 -25
  26. package/lib/middlewares/ql/sequelize/index.js +13 -4
  27. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +4 -3
  28. package/lib/middlewares/render/index.js +62 -0
  29. package/lib/models/associate.js +25 -1
  30. package/lib/models/define.js +26 -19
  31. package/lib/models/mixins.js +8 -1
  32. package/lib/{queries → query}/traverse.js +118 -77
  33. package/lib/router/handlers.util.js +1 -0
  34. package/lib/router/index.js +194 -99
  35. package/lib/router/markers.js +7 -0
  36. package/lib/router/route.js +5 -0
  37. package/lib/router/routes.util.js +16 -14
  38. package/lib/router/utils.js +7 -0
  39. package/lib/stacks/MarkersStack.js +41 -3
  40. package/lib/stacks/MiddlewaresStack.js +200 -0
  41. package/lib/structures/Enum.js +46 -0
  42. package/lib/structures/Filter.js +156 -0
  43. package/lib/structures/Params.js +55 -0
  44. package/lib/tools/sql.tool.js +7 -0
  45. package/lib/utils/objects.util.js +31 -24
  46. package/lib/utils/sanitizations.util.js +10 -4
  47. package/lib/validators/arguments.js +68 -0
  48. package/lib/validators/dates.js +7 -0
  49. package/lib/validators/numbers.js +7 -0
  50. package/package.json +20 -10
  51. package/lib/database/utils.js +0 -19
  52. package/lib/enums/Enum.js +0 -16
  53. package/lib/http/response.js +0 -1074
  54. package/lib/http/utils.js +0 -254
  55. package/lib/params/Params.js +0 -37
  56. package/lib/policies/Role.js +0 -77
  57. package/lib/policies/RoleExtracting.js +0 -97
  58. package/lib/preprocessors/BodyPreprocessor.js +0 -61
  59. package/lib/queries/Colander.js +0 -107
  60. package/lib/queries/NodesterQueryParams.js +0 -145
  61. package/lib/services/includes.service.js +0 -79
  62. package/lib/services/jwt.service.js +0 -147
  63. package/lib/stacks/MiddlewareStack.js +0 -159
@@ -0,0 +1,138 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ // Constants.
9
+ const CHARSET_REGEX = /;\s*charset\s*=/;
10
+
11
+ // Utils:
12
+ const mime = require('mime');
13
+
14
+
15
+ module.exports = {
16
+ setHeader: _setHeader,
17
+ appendHeader: _appendHeader,
18
+ getHeader: _getHeader,
19
+ removeHeader: _removeHeader,
20
+ }
21
+
22
+ /**
23
+ * Set header `field` to `value`, or pass
24
+ * an object of header fields.
25
+ *
26
+ * Examples:
27
+ *
28
+ * res.set('Foo', ['bar', 'baz']);
29
+ * res.set('Accept', 'application/json');
30
+ * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
31
+ *
32
+ * Aliased as `res.header()`.
33
+ *
34
+ * @param {String|Object} keyOrObject
35
+ * @param {String|Array} value
36
+ *
37
+ * @return {ServerResponse} for chaining
38
+ *
39
+ * @alias setHeader
40
+ * @api public
41
+ */
42
+ function _setHeader(keyOrObject, value) {
43
+ // As a pair:
44
+ if (arguments.length === 2) {
45
+ let _value = Array.isArray(value) ? value.map(String) : String(value);
46
+
47
+ // Add charset to content-type:
48
+ if (keyOrObject.toLowerCase() === 'content-type') {
49
+
50
+ if (Array.isArray(_value)) {
51
+ throw new TypeError('Content-Type cannot be set to an Array');
52
+ }
53
+
54
+ if (!CHARSET_REGEX.test(_value)) {
55
+ const charset = mime.charsets.getType(_value.split(';')[0]);
56
+
57
+ if (charset) {
58
+ _value += '; charset=' + charset.toLowerCase();
59
+ }
60
+ }
61
+ }
62
+
63
+ this.setHeader(keyOrObject, _value);
64
+ }
65
+ // As an object
66
+ else if (typeof keyOrObject === 'object') {
67
+ for (let key in keyOrObject) {
68
+ this.setHeader(key, keyOrObject[key]);
69
+ }
70
+ }
71
+ else {
72
+ const err = new TypeError(`'keyOrObject' must be of type String|Object.`);
73
+ Error.captureStackTrace(err, _setHeader);
74
+ throw err;
75
+ }
76
+
77
+ return this;
78
+ };
79
+
80
+
81
+ /**
82
+ * Append additional header `field` with value `val`.
83
+ *
84
+ * Example:
85
+ *
86
+ * res.append.header('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
87
+ * res.append.header('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
88
+ * res.append.header('Warning', '199 Miscellaneous warning');
89
+ *
90
+ * @param {String} field
91
+ * @param {String|Array} value
92
+ * @return {ServerResponse} for chaining
93
+ *
94
+ * @alias appendHeader
95
+ * @api public
96
+ */
97
+ function _appendHeader(field, value) {
98
+ const prev = this.get(field);
99
+ let _value = value;
100
+
101
+ if (prev) {
102
+ // concat the new and prev values:
103
+ _value = Array.isArray(prev) ? prev.concat(value)
104
+ : Array.isArray(value) ? [prev].concat(value)
105
+ : [prev, value]
106
+ }
107
+
108
+ return this.setHeader(field, _value);
109
+ };
110
+
111
+
112
+ /**
113
+ * Get value for header `key`.
114
+ *
115
+ * @param {String} key
116
+ *
117
+ * @return {String}
118
+ *
119
+ * @alias getHeader
120
+ * @api public
121
+ */
122
+ function _getHeader(key) {
123
+ return this.getHeader(key);
124
+ };
125
+
126
+ /**
127
+ * Remove value for header `key`.
128
+ *
129
+ * @param {String} key
130
+ *
131
+ * @return {String}
132
+ *
133
+ * @alias removeHeader
134
+ * @api public
135
+ */
136
+ function _removeHeader(key) {
137
+ return this.removeHeader(key);
138
+ };
@@ -0,0 +1,248 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ServerResponse } = require('http');
9
+
10
+ const {
11
+ setHeader,
12
+ appendHeader,
13
+ getHeader,
14
+ removeHeader,
15
+ } = require('./headers');
16
+
17
+ const calculateEtag = require('etag');
18
+
19
+ // Utils:
20
+ const statuses = require('statuses');
21
+ const mime = require('mime');
22
+ const {
23
+ setCharset
24
+ } = require('./utils');
25
+
26
+
27
+ const response = ServerResponse.prototype;
28
+
29
+ // Mixin custom methods:
30
+ response.status = _status;
31
+ response.sendStatus = _sendStatus;
32
+
33
+ response.send = _send;
34
+ response.json = _json;
35
+
36
+ // Headers:
37
+ response.set = setHeader;
38
+ response.append = appendHeader;
39
+ response.get = getHeader;
40
+ response.remove = removeHeader;
41
+
42
+ // ContentType:
43
+ response.setContentType = _setContentType;
44
+ response.getContentType = _getContentType;
45
+ // Mixins\
46
+
47
+ module.exports = response;
48
+
49
+ /**
50
+ * Set status `code`.
51
+ *
52
+ * @param {Number} code
53
+ *
54
+ * @return {ServerResponse}
55
+ *
56
+ * @alias status
57
+ * @api public
58
+ */
59
+ function _status(code) {
60
+ this.statusCode = code;
61
+ return this;
62
+ };
63
+
64
+
65
+ /**
66
+ * Send given HTTP status code.
67
+ *
68
+ * Sets the response status to `statusCode` and the body of the
69
+ * response to the standard description from node's http.STATUS_CODES
70
+ * or the statusCode number if no description.
71
+ *
72
+ * Examples:
73
+ *
74
+ * res.sendStatus(200);
75
+ *
76
+ * @param {number} statusCode
77
+ *
78
+ * @alias _sendStatus
79
+ * @api public
80
+ */
81
+ function _sendStatus(statusCode) {
82
+ const body = statuses.message[statusCode] || String(statusCode);
83
+
84
+ this.statusCode = statusCode;
85
+ this.setContentType('txt');
86
+
87
+ return this.send(body);
88
+ };
89
+
90
+ /**
91
+ * Sends a response.
92
+ *
93
+ * Examples:
94
+ *
95
+ * res.send(Buffer.from('wahoo'));
96
+ * res.send({ some: 'json' });
97
+ * res.send('<p>some html</p>');
98
+ *
99
+ * @param {string|number|boolean|object|Buffer} body
100
+ *
101
+ * @alias send
102
+ * @api public
103
+ */
104
+ function _send(body) {
105
+ const req = this.req;
106
+ const app = this.app;
107
+
108
+ let chunk = body;
109
+ let encoding;
110
+ let type;
111
+
112
+ switch (typeof chunk) {
113
+ // strings defaulting to html:
114
+ case 'string':
115
+ if (!this.getContentType()) {
116
+ this.setContentType('html');
117
+ }
118
+ break;
119
+ case 'boolean':
120
+ case 'number':
121
+ case 'object':
122
+ if (chunk === null) {
123
+ chunk = '';
124
+ }
125
+ else if (Buffer.isBuffer(chunk)) {
126
+ if (!this.getContentType()) {
127
+ this.setContentType('bin');
128
+ }
129
+ }
130
+ else {
131
+ return this.json(chunk);
132
+ }
133
+ break;
134
+ }
135
+
136
+ // Write strings in utf-8:
137
+ if (typeof chunk === 'string') {
138
+ encoding = 'utf8';
139
+ type = this.getContentType();
140
+
141
+ // reflect this in content-type
142
+ if (typeof type === 'string') {
143
+ this.setContentType( setCharset(type, 'utf-8') );
144
+ }
145
+ }
146
+
147
+ // Freshness of the REQUEST:
148
+ if (req.fresh) {
149
+ this.statusCode = 304;
150
+ }
151
+
152
+ // Strip irrelevant headers for:
153
+ // - 204 (No Content)
154
+ // - 303 (Not Modified)
155
+ if (
156
+ this.statusCode === 204
157
+ ||
158
+ this.statusCode === 304
159
+ ) {
160
+ this.remove('Content-Type');
161
+ this.remove('Content-Length');
162
+ this.remove('Transfer-Encoding');
163
+ chunk = '';
164
+ }
165
+
166
+ // Alter headers for 205 (Reset Content):
167
+ if (this.statusCode === 205) {
168
+ this.set('Content-Length', '0');
169
+ this.remove('Transfer-Encoding');
170
+ chunk = ''
171
+ }
172
+
173
+ if (req.method === 'HEAD') {
174
+ // skip body for HEAD.
175
+ this.end();
176
+ }
177
+ else {
178
+ // Calculate etag:
179
+ const etag = calculateEtag(chunk);
180
+ this.set('etag', etag);
181
+
182
+ // Respond.
183
+ this.end(chunk, encoding);
184
+ }
185
+
186
+ return this;
187
+ };
188
+
189
+
190
+ /**
191
+ * Send JSON response.
192
+ *
193
+ * @param {string|number|boolean|object} obj
194
+ *
195
+ * @alias json
196
+ * @api public
197
+ */
198
+ function _json(obj) {
199
+
200
+ // Ensure content-type:
201
+ if (!this.getContentType()) {
202
+ this.setContentType('application/json');
203
+ }
204
+
205
+ const body = JSON.stringify(obj);
206
+ return this.send(body);
207
+ };
208
+
209
+ /**
210
+ * Set _Content-Type_ response header with `type` through `mime.getType()`
211
+ * when it does not contain "/", or set the Content-Type to `type` otherwise.
212
+ *
213
+ * Examples:
214
+ *
215
+ * res.type('.html');
216
+ * res.type('html');
217
+ * res.type('json');
218
+ * res.type('application/json');
219
+ * res.type('png');
220
+ *
221
+ * @param {String} type
222
+ *
223
+ * @return {ServerResponse} for chaining
224
+ *
225
+ * @alias setContentType
226
+ * @api public
227
+ */
228
+ function _setContentType(type) {
229
+ const contentType = type.indexOf('/') === -1 ?
230
+ mime.getType(type)
231
+ :
232
+ type;
233
+
234
+ this.setHeader('Content-Type', contentType);
235
+ return this;
236
+ };
237
+
238
+ /**
239
+ * Returns _Content-Type_ header.
240
+ *
241
+ * @return {String} type
242
+ *
243
+ * @alias getContentType
244
+ * @api public
245
+ */
246
+ function _getContentType() {
247
+ return this.getHeader('Content-Type');
248
+ };
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ // Utils:
9
+ const contentType = require('content-type');
10
+
11
+ module.exports = {
12
+ setCharset: _setCharset
13
+ }
14
+
15
+ /**
16
+ * Set the charset in a given Content-Type string.
17
+ *
18
+ * @param {String} type
19
+ * @param {String} charset
20
+ *
21
+ * @return {String}
22
+ *
23
+ * @api private
24
+ */
25
+ function _setCharset(type, charset) {
26
+ if (!type || !charset) {
27
+ return type;
28
+ }
29
+
30
+ // parse type
31
+ const parsed = contentType.parse(type);
32
+
33
+ // set charset
34
+ parsed.parameters.charset = charset;
35
+
36
+ // format type
37
+ return contentType.format(parsed);
38
+ }
@@ -0,0 +1,25 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = function initSearchParamsMiddleware() {
10
+ return handle;
11
+ }
12
+
13
+ function handle(req, res, next) {
14
+ // If no query, skip:
15
+ if (req.url.indexOf('?') === -1) {
16
+ return next();
17
+ }
18
+
19
+ const querystring = req.url.split('?')[1];
20
+ const params = new URLSearchParams(querystring);
21
+
22
+ req.searchParams = params;
23
+
24
+ next();
25
+ }
@@ -0,0 +1,44 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const cookie = require('cookie');
9
+ const cookieSignature = require('cookie-signature');
10
+
11
+
12
+ module.exports = function initCookiesMiddleware(options={}) {
13
+ const context = {
14
+ options
15
+ }
16
+ return cookiesHandle.bind(context);
17
+ };
18
+
19
+ function cookiesHandle(req, res, next) {
20
+ try {
21
+ if (req.headers.cookie === undefined) {
22
+ req.cookies = {};
23
+ return next();
24
+ }
25
+
26
+ const cookies = cookie.parse(req.headers.cookie);
27
+ req.cookies = cookies;
28
+
29
+ next();
30
+ }
31
+ catch(error) {
32
+ console.error(error);
33
+
34
+ const statusCode = error.status || 406;
35
+ res.status(statusCode);
36
+ res.json({
37
+ error: {
38
+ message: error.message,
39
+ code: statusCode
40
+ },
41
+ status: statusCode
42
+ });
43
+ }
44
+ }
@@ -1,38 +1,54 @@
1
- 'use strict'
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
2
7
 
3
8
  const calculate = require('etag');
4
9
  const Stream = require('stream');
10
+
11
+ // Utils:
5
12
  const promisify = require('util').promisify;
6
13
  const fs = require('fs');
7
-
8
14
  const getFileStats = promisify(fs.stat);
9
15
 
16
+
10
17
  /**
11
- * Expose `etag` middleware.
18
+ * Initialize `etag` middleware.
19
+ *
20
+ * @param {Object} options
21
+ * @param {Boolean} options.weak
12
22
  *
13
- * Add ETag header field.
14
- * @param {object} [options] see https://github.com/jshttp/etag#options
15
- * @param {boolean} [options.weak]
16
23
  * @return {Function}
24
+ *
17
25
  * @api public
18
26
  */
19
- module.exports = function etag (options) {
20
- return async function(req, res, next) {
21
- await next()
22
- const entity = await getResponseEntity(req, res)
23
- setEtag(res, entity, options);
27
+ module.exports = function initETagMiddleware(options) {
28
+ const context = {
29
+ options
24
30
  }
31
+ return handle.bind(context);
25
32
  }
26
33
 
34
+ /*
35
+ * Add ETag header field.
36
+ */
37
+ function handle(req, res, next) {
38
+ // console.log('e', req.headers);
39
+ next();
40
+ }
27
41
 
28
- async function getResponseEntity (res) {
42
+ async function _getResponseEntity(res) {
29
43
  // If body is not defined:
30
44
  const { body } = res;
31
45
  if (!body || res.get('etag'))
32
46
  return;
33
47
 
34
48
  // Status code.
35
- const status = res.status / 100 | 0;
49
+ const status = res.statusCode / 100 | 0;
50
+
51
+ console.log('getResponseEntity', status, { tag: res.get('etag') });
36
52
 
37
53
  // 2xx
38
54
  if (status !== 2)
@@ -45,7 +61,7 @@ async function getResponseEntity (res) {
45
61
  const stats = await getFileStats(body.path);
46
62
  return stats;
47
63
  }
48
- else if ((typeof body === 'string') || Buffer.isBuffer(body)) {
64
+ else if (typeof body === 'string' || Buffer.isBuffer(body)) {
49
65
  return body;
50
66
  }
51
67
  else {
@@ -53,8 +69,9 @@ async function getResponseEntity (res) {
53
69
  }
54
70
  }
55
71
 
72
+ function _setEtag(res, entity, options) {
73
+ console.log('setEtag', typeof res, entity);
56
74
 
57
- function setEtag (res, entity, options) {
58
75
  if (!entity)
59
76
  return;
60
77
 
@@ -2,36 +2,41 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
+
5
6
  'use strict';
6
7
 
7
8
  const { formidable } = require('formidable');
8
9
 
9
10
 
10
- module.exports = function FormidableMiddleware(formidableOptions={}) {
11
- return async function(req, res, next) {
12
- try {
13
- const form = formidable(formidableOptions);
14
- const [fields, files] = await form.parse(req);
15
-
16
- // Add to request:
17
- req.form = {
18
- fields,
19
- files
20
- };
21
-
22
- next();
23
- }
24
- catch(error) {
25
- const statusCode = error.status || 406;
26
- res.status(statusCode);
27
- res.json({
28
- error: {
29
- message: error.message,
30
- code: statusCode
31
- },
32
- status: statusCode
33
- });
34
- }
11
+ module.exports = function initFormidableMiddleware(formidableOptions={}) {
12
+ const context = {
13
+ formidableOptions
35
14
  }
15
+ return formidableHandle.bind(context);
36
16
  };
37
17
 
18
+ async function formidableHandle(req, res, next) {
19
+ try {
20
+ const form = formidable(this.formidableOptions);
21
+ const [fields, files] = await form.parse(req);
22
+
23
+ // Add to request:
24
+ req.form = {
25
+ fields,
26
+ files
27
+ };
28
+
29
+ next();
30
+ }
31
+ catch(error) {
32
+ const statusCode = error.status || 406;
33
+ res.status(statusCode);
34
+ res.json({
35
+ error: {
36
+ message: error.message,
37
+ code: statusCode
38
+ },
39
+ status: statusCode
40
+ });
41
+ }
42
+ }
@@ -1,9 +1,19 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
  const QueryLexer = require('./interpreter/QueryLexer');
9
+ const httpCodes = require('nodester/http/codes');
2
10
 
3
11
 
4
- module.exports = NodesterQL;
12
+ module.exports = function initNodesterQL() {
13
+ return nqlHandle;
14
+ };
5
15
 
6
- async function NodesterQL(req, res, next) {
16
+ async function nqlHandle(req, res, next) {
7
17
  // Object, which will be populated with parsed query.
8
18
  req.nquery = {};
9
19
 
@@ -18,7 +28,6 @@ async function NodesterQL(req, res, next) {
18
28
  }
19
29
 
20
30
  try {
21
- // Convert to URLSearchParams.
22
31
  const queryString = req.url.split('?')[1];
23
32
 
24
33
  const lexer = new QueryLexer(queryString);
@@ -28,7 +37,7 @@ async function NodesterQL(req, res, next) {
28
37
  next();
29
38
  }
30
39
  catch(error) {
31
- res.status(422);
40
+ res.status(error.status ?? httpCodes.UNPROCESSABLE_ENTITY);
32
41
  res.json({ error: error.toString() });
33
42
  }
34
43
  }
@@ -2,10 +2,11 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
+
5
6
  'use strict';
6
7
 
8
+ const Enum = require('nodester/enum');
7
9
 
8
- const Enum = require('../../../../enums/Enum');
9
10
  const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
10
11
  const util = require('util');
11
12
  const debug = require('debug')('nodester:interpreter:QueryLexer');
@@ -60,7 +61,7 @@ module.exports = class QueryLexer {
60
61
  const isSubQuery = tree.node.model !== 'root';
61
62
  debug({ isSubQuery, startAt });
62
63
 
63
- // Token is accumulated char-by-char.
64
+ // Token is String, accumulated char-by-char.
64
65
  let token = '';
65
66
  // Value of param ('id=10' OR 'fields=id,text').
66
67
  let value = [];
@@ -375,7 +376,7 @@ module.exports = class QueryLexer {
375
376
  const param = this.parseParamFromToken(token);
376
377
 
377
378
  if (isSubQuery === true && param === 'includes') {
378
- const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model' or 'model.model+model'.`);
379
+ const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model1' or 'model.model1+model2'.`);
379
380
  throw err;
380
381
  }
381
382