nodester 0.1.5 → 0.2.1

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 (60) hide show
  1. package/Readme.md +25 -61
  2. package/lib/application/index.js +185 -63
  3. package/lib/body/extract.js +15 -4
  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 +10 -9
  12. package/lib/facades/mixins/index.js +67 -13
  13. package/lib/factories/responses/rest.js +25 -13
  14. package/lib/http/{request.js → request/index.js} +53 -75
  15. package/lib/http/request/utils.js +27 -0
  16. package/lib/http/response/headers.js +138 -0
  17. package/lib/http/response/index.js +248 -0
  18. package/lib/http/response/utils.js +38 -0
  19. package/lib/middlewares/SearchParams/index.js +25 -0
  20. package/lib/middlewares/cookies/index.js +44 -0
  21. package/lib/middlewares/etag/index.js +32 -15
  22. package/lib/middlewares/formidable/index.js +30 -25
  23. package/lib/middlewares/ql/sequelize/index.js +11 -2
  24. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +10 -2
  25. package/lib/middlewares/render/index.js +62 -0
  26. package/lib/models/associate.js +25 -1
  27. package/lib/models/define.js +19 -15
  28. package/lib/models/mixins.js +7 -0
  29. package/lib/query/traverse.js +97 -63
  30. package/lib/router/handlers.util.js +1 -0
  31. package/lib/router/index.js +193 -98
  32. package/lib/router/markers.js +7 -0
  33. package/lib/router/route.js +5 -0
  34. package/lib/router/routes.util.js +12 -14
  35. package/lib/router/utils.js +7 -0
  36. package/lib/stacks/MarkersStack.js +41 -3
  37. package/lib/stacks/MiddlewaresStack.js +200 -0
  38. package/lib/structures/Enum.js +46 -0
  39. package/lib/structures/Filter.js +157 -0
  40. package/lib/structures/Params.js +55 -0
  41. package/lib/tools/sql.tool.js +7 -0
  42. package/lib/utils/objects.util.js +31 -24
  43. package/lib/utils/sanitizations.util.js +10 -4
  44. package/lib/validators/arguments.js +68 -0
  45. package/lib/validators/dates.js +7 -0
  46. package/lib/validators/numbers.js +7 -0
  47. package/package.json +11 -8
  48. package/tests/index.test.js +4 -4
  49. package/tests/nql.test.js +18 -1
  50. package/lib/database/utils.js +0 -19
  51. package/lib/enums/Enum.js +0 -16
  52. package/lib/filters/Filter.js +0 -109
  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/services/includes.service.js +0 -79
  59. package/lib/services/jwt.service.js +0 -147
  60. package/lib/stacks/MiddlewareStack.js +0 -159
@@ -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,10 +1,19 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
  const QueryLexer = require('./interpreter/QueryLexer');
2
9
  const httpCodes = require('nodester/http/codes');
3
10
 
4
11
 
5
- module.exports = NodesterQL;
12
+ module.exports = function initNodesterQL() {
13
+ return nqlHandle;
14
+ };
6
15
 
7
- async function NodesterQL(req, res, next) {
16
+ async function nqlHandle(req, res, next) {
8
17
  // Object, which will be populated with parsed query.
9
18
  req.nquery = {};
10
19
 
@@ -2,10 +2,11 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
- 'use strict';
6
5
 
6
+ 'use strict';
7
7
 
8
8
  const Enum = require('nodester/enum');
9
+
9
10
  const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
10
11
  const util = require('util');
11
12
  const debug = require('debug')('nodester:interpreter:QueryLexer');
@@ -221,6 +222,13 @@ module.exports = class QueryLexer {
221
222
  const model = token;
222
223
  tree.use(model) ?? tree.include(model);
223
224
 
225
+ // Last token (model) was included,
226
+ // now jump to root and proceed to collect next token (model).
227
+ tree.node.resetActiveParam();
228
+ tree.upToRoot();
229
+
230
+ tree.node.activeParam = 'includes';
231
+
224
232
  token = '';
225
233
  continue;
226
234
  }
@@ -402,7 +410,7 @@ module.exports = class QueryLexer {
402
410
  const err = MissingCharError(i+1, ')');
403
411
  throw err;
404
412
  }
405
-
413
+
406
414
  this.setNodeParam(tree.node, token, value);
407
415
 
408
416
  // If end of subquery:
@@ -0,0 +1,62 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+
9
+ module.exports = function initRenderMiddleware() {
10
+ return handle;
11
+ }
12
+
13
+
14
+ function handle(req, res, next) {
15
+ const context = { req, res, next };
16
+ res.render = _render.bind(context);
17
+ next();
18
+ }
19
+
20
+
21
+ /**
22
+ * Render `view` with the given `options` and optional callback `fn`.
23
+ * When a callback function is given a response will _not_ be made
24
+ * automatically, otherwise a response of _200_ and _text/html_ is given.
25
+ *
26
+ * Options:
27
+ *
28
+ * - `cache` boolean hinting to the engine it should cache
29
+ * - `filename` filename of the view being rendered
30
+ *
31
+ * @alias render
32
+ * @api public
33
+ */
34
+ function _render(view, options, callback) {
35
+ const app = this.req.app;
36
+ let done = callback;
37
+ let opts = options || {};
38
+
39
+ const req = this.req;
40
+ const res = this.res;
41
+ const next = this.next;
42
+
43
+ // support callback function as second arg
44
+ if (typeof options === 'function') {
45
+ done = options;
46
+ opts = {};
47
+ }
48
+
49
+ // merge res.locals
50
+ opts._locals = res.locals;
51
+
52
+ // default callback to respond
53
+ done = done || function (err, str) {
54
+ if (err)
55
+ return next(err);
56
+
57
+ res.send(str);
58
+ };
59
+
60
+ // render
61
+ app.render(view, opts, done);
62
+ };
@@ -1,5 +1,17 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
1
7
 
2
- module.exports = async function associateModels(databaseConnection) {
8
+
9
+ module.exports = {
10
+ associateModels: _associateModels,
11
+ associateModelsSync: _associateModelsSync
12
+ }
13
+
14
+ async function _associateModels(databaseConnection) {
3
15
  try {
4
16
  const models = databaseConnection.models;
5
17
 
@@ -15,3 +27,15 @@ module.exports = async function associateModels(databaseConnection) {
15
27
  return Promise.reject(error);
16
28
  }
17
29
  }
30
+
31
+ function _associateModelsSync(databaseConnection) {
32
+ const models = databaseConnection.models;
33
+
34
+ const modelNames = Object.keys(models);
35
+
36
+ for (let modelName of modelNames) {
37
+ models[modelName].associate(models);
38
+ }
39
+
40
+ return models;
41
+ }
@@ -2,6 +2,7 @@
2
2
  * /nodester
3
3
  * MIT Licensed
4
4
  */
5
+
5
6
  'use strict';
6
7
 
7
8
  // CRUD mixins.
@@ -18,7 +19,7 @@ module.exports = defineModel;
18
19
  * @param {Function} definition
19
20
  * @param {Object} options
20
21
  * - ... Sequilize model options
21
- * - noCRUD (Bool)
22
+ * - @param {Boolean} noCRUD
22
23
  */
23
24
  function defineModel(
24
25
  databaseConnection,
@@ -30,16 +31,24 @@ function defineModel(
30
31
  const _options = {
31
32
  // Set snake-cased table name.
32
33
  // tableName: underscore( pluralize(modelName) ),
34
+
33
35
  // Set snake-case.
34
36
  underscored: true,
37
+
35
38
  // Enable automatic 'created_at' and 'updated_at' fields.
36
39
  timestamps: true,
37
40
 
38
- // The only way to get snake-cased timestamps (issue: https://github.com/sequelize/sequelize/issues/10857)
41
+ // The only way to get snake-cased timestamps:
42
+ // (issue: https://github.com/sequelize/sequelize/issues/10857)
39
43
  createdAt: 'created_at',
40
44
  updatedAt: 'updated_at',
41
45
  deletedAt: 'deleted_at',
42
46
 
47
+ // Configs related to nodester:
48
+ nodester: {
49
+ output: 'underscored'
50
+ },
51
+
43
52
  // Add user-defined options (they can override upper ones).
44
53
  ...options
45
54
  };
@@ -76,11 +85,8 @@ function _getIncludesList(facadeData=null) {
76
85
  const associations = this.associations;
77
86
  const associationEntries = Object.entries(associations);
78
87
 
79
- associationEntries.forEach(([
80
- associationName,
81
- associationDefinition
82
- ]) => {
83
- const a = { association: associationName };
88
+ for (const [ associationName, associationDefinition ] of associationEntries) {
89
+ const formatted = { association: associationName };
84
90
 
85
91
  if (!!facadeData) {
86
92
  // If facade data is set, go deeper:
@@ -88,19 +94,17 @@ function _getIncludesList(facadeData=null) {
88
94
  if (keys.indexOf(associationName) > 0) {
89
95
  const associationModel = associationDefinition.target;
90
96
 
91
- const a = { association: associationName };
92
97
  if (Object.entries(associationModel.associations).length > 0) {
93
98
  const deepData = facadeData[ associationName ];
94
- a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
99
+ formatted.include = associationModel.getIncludesList(
100
+ Array.isArray(deepData) ? deepData[0] : deepData
101
+ );
95
102
  }
96
-
97
- result.push( a );
98
103
  }
99
104
  }
100
- else {
101
- result.push( a );
102
- }
103
- });
105
+
106
+ result.push( formatted );
107
+ }
104
108
 
105
109
  return result;
106
110
  }
@@ -1,3 +1,10 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
1
8
  /*
2
9
  * CRUD mixins for any model:
3
10
  */