nodester 0.0.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +125 -0
  3. package/docs/App.md +13 -0
  4. package/docs/Queries.md +61 -0
  5. package/docs/Readme.md +2 -0
  6. package/docs/Routing.md +34 -0
  7. package/examples/goal/index.js +23 -0
  8. package/examples/rest/index.js +25 -0
  9. package/examples/rest/node_modules/.package-lock.json +40 -0
  10. package/examples/rest/package-lock.json +72 -0
  11. package/examples/rest/package.json +14 -0
  12. package/lib/application/MiddlewareStack.js +125 -0
  13. package/lib/application/http/request.js +462 -0
  14. package/lib/application/http/response.js +1107 -0
  15. package/lib/application/http/utils.js +254 -0
  16. package/lib/application/index.js +292 -0
  17. package/lib/constants/ConstantsEnum.js +13 -0
  18. package/lib/constants/ResponseFormats.js +7 -0
  19. package/lib/controllers/Controller.js +474 -0
  20. package/lib/controllers/JWTController.js +240 -0
  21. package/lib/controllers/ServiceController.js +109 -0
  22. package/lib/controllers/WebController.js +75 -0
  23. package/lib/facades/Facade.js +388 -0
  24. package/lib/facades/FacadeParams.js +11 -0
  25. package/lib/facades/ServiceFacade.js +17 -0
  26. package/lib/facades/jwt.facade.js +273 -0
  27. package/lib/factories/errors/CustomError.js +22 -0
  28. package/lib/factories/errors/index.js +9 -0
  29. package/lib/factories/responses/api.js +90 -0
  30. package/lib/factories/responses/html.js +55 -0
  31. package/lib/logger/console.js +24 -0
  32. package/lib/models/DisabledRefreshToken.js +68 -0
  33. package/lib/models/Extractor.js +320 -0
  34. package/lib/models/define.js +62 -0
  35. package/lib/models/mixins.js +369 -0
  36. package/lib/policies/Role.js +77 -0
  37. package/lib/policies/RoleExtracting.js +97 -0
  38. package/lib/preprocessors/BodyPreprocessor.js +61 -0
  39. package/lib/preprocessors/IncludesPreprocessor.js +55 -0
  40. package/lib/preprocessors/QueryPreprocessor.js +64 -0
  41. package/lib/routers/Default/index.js +143 -0
  42. package/lib/routers/Default/layer.js +50 -0
  43. package/lib/routers/Main/index.js +10 -0
  44. package/lib/routers/Roles/index.js +81 -0
  45. package/lib/services/includes.service.js +79 -0
  46. package/lib/services/jwt.service.js +147 -0
  47. package/lib/tools/sql.tool.js +82 -0
  48. package/lib/utils/dates.util.js +23 -0
  49. package/lib/utils/forms.util.js +22 -0
  50. package/lib/utils/json.util.js +49 -0
  51. package/lib/utils/mappers/Routes/index.js +100 -0
  52. package/lib/utils/mappers/Routes/utils.js +20 -0
  53. package/lib/utils/modelAssociations.util.js +44 -0
  54. package/lib/utils/objects.util.js +69 -0
  55. package/lib/utils/params.util.js +19 -0
  56. package/lib/utils/path.util.js +26 -0
  57. package/lib/utils/queries.util.js +240 -0
  58. package/lib/utils/sanitizations.util.js +111 -0
  59. package/lib/utils/sql.util.js +78 -0
  60. package/lib/utils/strings.util.js +43 -0
  61. package/lib/utils/types.util.js +26 -0
  62. package/package.json +63 -0
  63. package/tests/index.test.js +35 -0
@@ -0,0 +1,254 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+ 'use strict';
6
+
7
+ const contentType = require('content-type');
8
+ const mime = require('send').mime;
9
+ const etag = require('etag');
10
+ const proxyaddr = require('proxy-addr');
11
+ const qs = require('qs');
12
+ const querystring = require('querystring');
13
+
14
+
15
+ exports = module.exports = {
16
+ acceptParams: _acceptParams,
17
+ normalizeType: _normalizeType,
18
+ normalizeTypes: _normalizeTypes,
19
+ compileETag: _compileETag,
20
+ setCharset: _setCharset
21
+ }
22
+
23
+
24
+ /**
25
+ * Parse accept params `str` returning an
26
+ * object with `.value`, `.quality` and `.params`.
27
+ * also includes `.originalIndex` for stable sorting
28
+ *
29
+ * @param {String} str
30
+ * @param {Number} index
31
+ * @return {Object}
32
+ *
33
+ * @api private
34
+ */
35
+ function _acceptParams(str, index) {
36
+ const parts = str.split(/ *; */);
37
+ let result = { value: parts[0], quality: 1, params: {}, originalIndex: index };
38
+
39
+ for (let i = 1; i < parts.length; i++) {
40
+ const pms = parts[i].split(/ *= */);
41
+ if ('q' === pms[0]) {
42
+ result.quality = parseFloat(pms[1]);
43
+ } else {
44
+ result.params[pms[0]] = pms[1];
45
+ }
46
+ }
47
+
48
+ return result;
49
+ }
50
+
51
+
52
+ /**
53
+ * Create an ETag generator function, generating ETags with
54
+ * the given options.
55
+ *
56
+ * @param {object} options
57
+ * @return {function}
58
+ *
59
+ * @private
60
+ */
61
+ function _createETagGenerator (options) {
62
+ return function generateETag (body, encoding) {
63
+ const buf = !Buffer.isBuffer(body)
64
+ ? Buffer.from(body, encoding)
65
+ : body
66
+
67
+ return etag(buf, options)
68
+ }
69
+ }
70
+
71
+
72
+ /**
73
+ * Normalize the given `type`, for example "html" becomes "text/html".
74
+ *
75
+ * @param {String} type
76
+ * @return {Object}
77
+ *
78
+ * @api private
79
+ */
80
+ function _normalizeType(type){
81
+ return ~type.indexOf('/')
82
+ ? acceptParams(type)
83
+ : { value: mime.lookup(type), params: {} };
84
+ };
85
+
86
+
87
+ /**
88
+ * Normalize `types`, for example "html" becomes "text/html".
89
+ *
90
+ * @param {Array} types
91
+ * @return {Array}
92
+ *
93
+ * @api private
94
+ */
95
+ function _normalizeTypes(types){
96
+ const ret = [];
97
+
98
+ for (let i = 0; i < types.length; ++i) {
99
+ ret.push(
100
+ _normalizeType(types[i])
101
+ );
102
+ }
103
+
104
+ return ret;
105
+ };
106
+
107
+
108
+ /**
109
+ * Compile "etag" value to function.
110
+ *
111
+ * @param {Boolean|String|Function} val
112
+ * @return {Function}
113
+ *
114
+ * @alias compileETag
115
+ * @api public
116
+ */
117
+ function _compileETag(val) {
118
+ let fn;
119
+
120
+ if (typeof val === 'function') {
121
+ return val;
122
+ }
123
+
124
+ switch (val) {
125
+ case true:
126
+ case 'weak':
127
+ fn = _createETagGenerator({ weak: true });
128
+ break;
129
+ case false:
130
+ break;
131
+ case 'strong':
132
+ fn = _createETagGenerator({ weak: false });
133
+ break;
134
+ default:
135
+ throw new TypeError('unknown value for etag function: ' + val);
136
+ }
137
+
138
+ return fn;
139
+ }
140
+
141
+ /**
142
+ * Compile "query parser" value to function.
143
+ *
144
+ * @param {String|Function} val
145
+ * @return {Function}
146
+ *
147
+ * @api private
148
+ */
149
+
150
+ exports.compileQueryParser = function compileQueryParser(val) {
151
+ let fn;
152
+
153
+ if (typeof val === 'function') {
154
+ return val;
155
+ }
156
+
157
+ switch (val) {
158
+ case true:
159
+ case 'simple':
160
+ fn = querystring.parse;
161
+ break;
162
+ case false:
163
+ fn = newObject;
164
+ break;
165
+ case 'extended':
166
+ fn = parseExtendedQueryString;
167
+ break;
168
+ default:
169
+ throw new TypeError('unknown value for query parser function: ' + val);
170
+ }
171
+
172
+ return fn;
173
+ }
174
+
175
+ /**
176
+ * Compile "proxy trust" value to function.
177
+ *
178
+ * @param {Boolean|String|Number|Array|Function} val
179
+ * @return {Function}
180
+ *
181
+ * @api private
182
+ */
183
+ exports.compileTrust = function(val) {
184
+ if (typeof val === 'function') return val;
185
+
186
+ if (val === true) {
187
+ // Support plain true/false
188
+ return function(){ return true };
189
+ }
190
+
191
+ if (typeof val === 'number') {
192
+ // Support trusting hop count
193
+ return function(a, i){ return i < val };
194
+ }
195
+
196
+ if (typeof val === 'string') {
197
+ // Support comma-separated values
198
+ val = val.split(',')
199
+ .map(function (v) { return v.trim() })
200
+ }
201
+
202
+ return proxyaddr.compile(val || []);
203
+ }
204
+
205
+
206
+ /**
207
+ * Set the charset in a given Content-Type string.
208
+ *
209
+ * @param {String} type
210
+ * @param {String} charset
211
+ * @return {String}
212
+ *
213
+ * @api private
214
+ */
215
+ function _setCharset(type, charset) {
216
+ if (!type || !charset) {
217
+ return type;
218
+ }
219
+
220
+ // parse type
221
+ const parsed = contentType.parse(type);
222
+
223
+ // set charset
224
+ parsed.parameters.charset = charset;
225
+
226
+ // format type
227
+ return contentType.format(parsed);
228
+ };
229
+
230
+
231
+ /**
232
+ * Parse an extended query string with qs.
233
+ *
234
+ * @return {Object}
235
+ * @private
236
+ */
237
+ function parseExtendedQueryString(str) {
238
+ return qs.parse(str, {
239
+ allowPrototypes: true
240
+ });
241
+ }
242
+
243
+
244
+ /**
245
+ * Return new empty object.
246
+ *
247
+ * @return {Object}
248
+ *
249
+ * @api private
250
+ */
251
+ function newObject() {
252
+ return {};
253
+ }
254
+
@@ -0,0 +1,292 @@
1
+ /*!
2
+ * /nodester
3
+ * MIT Licensed
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Emitter = require('events');
9
+ const MiddlewareStack = require('./MiddlewareStack');
10
+ // Server:
11
+ const http = require('http');
12
+ const request = require('./http/request');
13
+ const response = require('./http/response');
14
+ // Utils:
15
+ const { typeOf } = require('../utils/types.util');
16
+ const { merge } = require('../utils/objects.util');
17
+ const consl = require('../logger/console');
18
+ const debug = require('debug')('nodester:application');
19
+
20
+
21
+ module.exports = class Application extends Emitter {
22
+
23
+ /**
24
+ * Initialize a new `Application`.
25
+ *
26
+ * @api public
27
+ */
28
+ constructor(opts={}) {
29
+ super();
30
+
31
+ // Fallback port.
32
+ this.port = opts?.port ?? 8080;
33
+
34
+ // Reference to middlewares stack.
35
+ this._middlewares = new MiddlewareStack();
36
+
37
+ // Reference to the database connection.
38
+ this.database = null;
39
+
40
+ // Reference to the http(s) server,
41
+ this.server = null;
42
+
43
+ this._hooks = {
44
+ beforeStart: ()=>{}
45
+ };
46
+
47
+ // Indicatorors.
48
+ this.isListening = false;
49
+ }
50
+
51
+ /*
52
+ * Expose the prototype that will get set on requests.
53
+ *
54
+ * @api public
55
+ */
56
+ get request() {
57
+ return Object.create(request, {
58
+ app: { configurable: true, enumerable: true, writable: true, value: app }
59
+ })
60
+ }
61
+
62
+
63
+ /*
64
+ * Expose the prototype that will get set on responses.
65
+ *
66
+ * @api public
67
+ */
68
+ get response() {
69
+ return Object.create(response, {
70
+ app: { configurable: true, enumerable: true, writable: true, value: app }
71
+ })
72
+ }
73
+
74
+
75
+ /*
76
+ * Sets (or overrides):
77
+ * - (database) main database of the application and tries to make connection
78
+ * - (router) default Router
79
+ *
80
+ * @api public
81
+ */
82
+ get set() {
83
+ return {
84
+ database: this.setDatabase.bind(this),
85
+ router: this.setRouter.bind(this)
86
+ }
87
+ }
88
+
89
+
90
+ /**
91
+ * Sets (or overrides) main database of the application and tries to make connection.
92
+ * @param {Sequilize} sequilizeConnection
93
+ * @param {Boolean} crashOnError
94
+ * @return {sequilizeConnection.authenticate}
95
+ *
96
+ * @public
97
+ */
98
+ setDatabase(sequilizeConnection, crashOnError=true) {
99
+ try {
100
+ if (!sequilizeConnection) {
101
+ const err = new Error('Connection to database (Sequilize) can not be null or undefined.');
102
+ throw err;
103
+ }
104
+
105
+ const result = sequilizeConnection.authenticate();
106
+ this.database = sequilizeConnection;
107
+
108
+ return result;
109
+ }
110
+ catch(error) {
111
+ if (crashOnError === true) {
112
+ throw error;
113
+ }
114
+ else {
115
+ consl.error(error);
116
+ }
117
+ }
118
+ }
119
+
120
+
121
+ /**
122
+ * Overrides default Router.
123
+ *
124
+ * @public
125
+ */
126
+ setRouter(newRouter) {
127
+ this.router = newRouter.init(this);
128
+ }
129
+
130
+
131
+ /*
132
+ * Adds:
133
+ * - (middleware) new middleware to the stack;
134
+ *
135
+ * @api public
136
+ */
137
+ get add() {
138
+ return {
139
+ middleware: this.addMiddleware.bind(this)
140
+ }
141
+ }
142
+
143
+
144
+ /*
145
+ * Adds new middleware to the stack.
146
+ *
147
+ * @api public
148
+ */
149
+ addMiddleware(fn) {
150
+ if (this._middlewares.isLocked === true) {
151
+ const err = new Error(`Can't add more middlewares after application has been started.`);
152
+ throw err;
153
+ }
154
+
155
+ this._middlewares.add(fn);
156
+ }
157
+
158
+
159
+ /*
160
+ * Proxy to .add().
161
+ *
162
+ * @param {Function} fn
163
+ *
164
+ * @api public
165
+ */
166
+ use(fn) {
167
+ return this.add(fn);
168
+ }
169
+
170
+
171
+ /**
172
+ * Adds to hooks stack (beforeStart).
173
+ *
174
+ * @api public
175
+ */
176
+ beforeStart(fn) {
177
+ if (typeOf(fn) !== 'function') {
178
+ const err = new TypeError('"fn" argument must be a function');
179
+ throw err;
180
+ }
181
+ this._hooks.beforeStart = fn;
182
+
183
+ return this._hooks.beforeStart;
184
+ }
185
+
186
+
187
+ /**
188
+ * Return a request handler callback
189
+ * for node's native http server.
190
+ *
191
+ * @return {Function}
192
+ *
193
+ * @api public
194
+ */
195
+ start() {
196
+ try {
197
+ this._hooks.beforeStart.call(this);
198
+ }
199
+ catch(error) {
200
+ console.error('Application did not start due to error.');
201
+ consl.error(error);
202
+ return;
203
+ }
204
+
205
+ // Lock middlewares stack.
206
+ this._middlewares.lock();
207
+
208
+ return this.handle.bind(this);
209
+ }
210
+
211
+
212
+ /**
213
+ * Shorthand for:
214
+ *
215
+ * http.createServer(app.start()).listen(...)
216
+ *
217
+ * @param {Int} port
218
+ * @param {Mixed} ...
219
+ * @return {import('http').Server}
220
+ *
221
+ * @api public
222
+ */
223
+ listen(port, ...args) {
224
+ // Remember port:
225
+ const _port = port ?? this.port;
226
+ this.port = _port;
227
+
228
+ debug(`listen on port ${ this.port }`);
229
+
230
+ if (!this.server) {
231
+ this.server = http.createServer(this.start());
232
+ }
233
+
234
+ this.isListening = true;
235
+ return this.server.listen(_port, ...args);
236
+ }
237
+
238
+
239
+ /**
240
+ * Handles server request.
241
+ *
242
+ * @api public
243
+ */
244
+ handle(req, res) {
245
+ // Req & res with mixins:
246
+ merge(req, this.request);
247
+ merge(res, this.response);
248
+
249
+ // Exchange references:
250
+ req.res = res;
251
+ res.req = req;
252
+
253
+ return this._middlewares.process(req, res)
254
+ }
255
+
256
+
257
+ /**
258
+ * Extends Application & makes sure, that "key" param is not present already.
259
+ * @param {String} key
260
+ * @param {Any} fnOrProperty
261
+ * @return {Any} fnOrProperty in Application
262
+ *
263
+ * @api public
264
+ */
265
+ extend(key='', fnOrProperty) {
266
+ const keys = Object.keys(this);
267
+ if (keys.indexOf(key) > -1) {
268
+ const err = new TypeError(`Key ${ key } is already present in Application instance`);
269
+ throw err;
270
+ }
271
+
272
+ this[key] = fnOrProperty;
273
+
274
+ return this[key];
275
+ }
276
+
277
+
278
+ /**
279
+ * Stops server
280
+ *
281
+ * @api public
282
+ */
283
+ stop() {
284
+ if (this.isListening !== true) {
285
+ console.warn('Nothing to stop. Server is not listening.');
286
+ return;
287
+ }
288
+
289
+ this.server.close();
290
+ this.isListening = false;
291
+ }
292
+ }
@@ -0,0 +1,13 @@
1
+
2
+ module.exports = function ConstantsEnum(constantsList = {}) {
3
+ // Set list.
4
+ this.list = constantsList;
5
+
6
+ // Set getters.
7
+ Object.keys(constantsList).forEach(key => {
8
+ this[key] = constantsList[key];
9
+ });
10
+
11
+ // Set constants in static array.
12
+ this.asArray = Object.values(constantsList);
13
+ }
@@ -0,0 +1,7 @@
1
+ const ConstantsEnum = require('nodester/constants/ConstantsEnum');
2
+
3
+
4
+ module.exports = new ConstantsEnum({
5
+ JSON: 'JSON',
6
+ XML: 'XML',
7
+ });