nodester 0.0.1 → 0.0.2

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 (68) hide show
  1. package/Readme.md +33 -39
  2. package/lib/application/index.js +110 -38
  3. package/lib/constants/Operations.js +23 -0
  4. package/lib/controllers/methods/index.js +194 -0
  5. package/lib/controllers/mixins/index.js +222 -0
  6. package/lib/database/connection.js +34 -0
  7. package/lib/database/migration.js +42 -0
  8. package/lib/database/utils.js +19 -0
  9. package/lib/enums/Enum.js +16 -0
  10. package/lib/facades/methods/index.js +173 -0
  11. package/lib/facades/mixins/index.js +111 -0
  12. package/lib/factories/errors/CustomError.js +7 -5
  13. package/lib/factories/responses/html.js +7 -2
  14. package/lib/factories/responses/rest.js +110 -0
  15. package/lib/http/codes/index.js +157 -0
  16. package/lib/{application/http → http}/request.js +6 -30
  17. package/lib/{application/http → http}/response.js +20 -53
  18. package/lib/middlewares/etag/index.js +62 -0
  19. package/lib/middlewares/ql/sequelize/index.js +34 -0
  20. package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +121 -0
  21. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +456 -0
  22. package/lib/models/associate.js +17 -0
  23. package/lib/models/define.js +56 -14
  24. package/lib/models/mixins.js +100 -78
  25. package/lib/params/Params.js +37 -0
  26. package/lib/queries/Colander.js +84 -0
  27. package/lib/queries/NodesterQueryParams.js +139 -0
  28. package/lib/queries/traverse.js +311 -0
  29. package/lib/router/handlers.util.js +61 -0
  30. package/lib/router/index.js +386 -0
  31. package/lib/router/route.js +124 -0
  32. package/lib/router/routes.util.js +66 -0
  33. package/lib/stacks/MarkersStack.js +35 -0
  34. package/lib/{application → stacks}/MiddlewareStack.js +47 -13
  35. package/lib/utils/path.util.js +3 -1
  36. package/lib/utils/types.util.js +51 -1
  37. package/lib/validators/dates.js +25 -0
  38. package/lib/validators/numbers.js +14 -0
  39. package/package.json +31 -4
  40. package/tests/index.test.js +7 -2
  41. package/tests/nql.test.js +277 -0
  42. package/docs/App.md +0 -13
  43. package/docs/Queries.md +0 -61
  44. package/docs/Readme.md +0 -2
  45. package/docs/Routing.md +0 -34
  46. package/examples/goal/index.js +0 -23
  47. package/examples/rest/index.js +0 -25
  48. package/examples/rest/node_modules/.package-lock.json +0 -40
  49. package/examples/rest/package-lock.json +0 -72
  50. package/examples/rest/package.json +0 -14
  51. package/lib/constants/ConstantsEnum.js +0 -13
  52. package/lib/controllers/Controller.js +0 -474
  53. package/lib/controllers/JWTController.js +0 -240
  54. package/lib/controllers/ServiceController.js +0 -109
  55. package/lib/controllers/WebController.js +0 -75
  56. package/lib/facades/Facade.js +0 -388
  57. package/lib/facades/FacadeParams.js +0 -11
  58. package/lib/facades/ServiceFacade.js +0 -17
  59. package/lib/facades/jwt.facade.js +0 -273
  60. package/lib/factories/responses/api.js +0 -90
  61. package/lib/models/DisabledRefreshToken.js +0 -68
  62. package/lib/models/Extractor.js +0 -320
  63. package/lib/routers/Default/index.js +0 -143
  64. package/lib/routers/Default/layer.js +0 -50
  65. package/lib/routers/Main/index.js +0 -10
  66. package/lib/routers/Roles/index.js +0 -81
  67. package/lib/utils/params.util.js +0 -19
  68. /package/lib/{application/http → http}/utils.js +0 -0
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Rest response factory.
3
+ */
4
+
5
+ const ResponseFormats = require('../../constants/ResponseFormats');
6
+
7
+
8
+ module.exports = {
9
+ createGenericResponse: _createGenericResponse,
10
+ createOKResponse: _createOKResponse,
11
+ createErrorResponse: _createErrorResponse,
12
+ }
13
+
14
+
15
+ /*
16
+ * Format for all API responses will be JSON
17
+ * {
18
+ * content: {...}
19
+ * error: {...}
20
+ * }
21
+ * Status code is sent in header.
22
+ *
23
+ * If error is not present, error should be null.
24
+ * If error is present, content can be null (But it's not required).
25
+ *
26
+ * @param {ServerResponse} res
27
+ * @param {Object} options
28
+ *
29
+ * @alias createGenericResponse
30
+ * @api public
31
+ */
32
+ function _createGenericResponse(
33
+ res,
34
+ options = {
35
+ status: 200,
36
+ content: {},
37
+ error: null,
38
+ format: ResponseFormats.JSON
39
+ }
40
+ ) {
41
+ try {
42
+ const data = {
43
+ content: options?.content ?? null,
44
+ error: options?.error ?? null
45
+ };
46
+
47
+ switch(options?.format) {
48
+ case ResponseFormats.JSON:
49
+ return options?.res.status(options?.status).json(data);
50
+ case ResponseFormats.XML:
51
+ // TODO: format data into XML.
52
+ return options?.res.status(options?.status).send(data);
53
+ break;
54
+ default: {
55
+ const err = new TypeError("No format specified.");
56
+ throw err;
57
+ }
58
+ }
59
+ }
60
+ catch(error) {
61
+ const err = new Error(`Could not create generic response: ${error.message}`);
62
+ err.name = error?.name;
63
+ err.code = error?.code;
64
+ throw err;
65
+ }
66
+ }
67
+
68
+
69
+ /**
70
+ * Sends response with status code 200.
71
+ * Should be called on all successful respones.
72
+ *
73
+ * @param {ServerResponse} res
74
+ * @param <Object> content
75
+ * @param <String> format
76
+ *
77
+ * @alias createOKResponse
78
+ * @api public
79
+ */
80
+ function _createOKResponse(res, options={}) {
81
+
82
+ return this.createGenericResponse(res, {
83
+ ...options,
84
+ status: 200,
85
+ format: options?.format ?? ResponseFormats.JSON
86
+ });
87
+ }
88
+
89
+
90
+ /**
91
+ * Sends response with provided error code.
92
+ * Should be called on all failed respones.
93
+ *
94
+ * @param {ServerResponse} res
95
+ * @param <Object> error
96
+ * @param <Object> content (optional)
97
+ * @param <Int> status
98
+ * @param <String> format
99
+ *
100
+ * @alias createErrorResponse
101
+ * @api public
102
+ */
103
+ function _createErrorResponse(res, options) {
104
+
105
+ return this.createGenericResponse(res, {
106
+ ...options,
107
+ status: options?.status ?? 500,
108
+ format: options?.format ?? ResponseFormats.JSON
109
+ });
110
+ }
@@ -0,0 +1,157 @@
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'),
7
+
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'),
19
+
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
+
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 = {
76
+ // Informational:
77
+ 100: 'Continue',
78
+ 101: 'Switching Protocols',
79
+ 102: 'Processing',
80
+ 103: 'Early Hints',
81
+
82
+ // 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',
93
+
94
+ // 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',
103
+
104
+ // 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',
134
+
135
+ // 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
+ }
@@ -7,14 +7,14 @@
7
7
  const accepts = require('accepts');
8
8
  const isIP = require('net').isIP;
9
9
  const typeis = require('type-is');
10
- const http = require('http');
10
+ const { IncomingMessage } = require('http');
11
11
  const fresh = require('fresh');
12
12
  const parseRange = require('range-parser');
13
13
  const parse = require('parseurl');
14
14
  const proxyaddr = require('proxy-addr');
15
15
 
16
16
 
17
- const req = http.IncomingMessage.prototype;
17
+ const req = IncomingMessage.prototype;
18
18
 
19
19
  module.exports = req;
20
20
 
@@ -355,39 +355,15 @@ defineGetter(req, 'path', function path() {
355
355
 
356
356
  /**
357
357
  * Parse the "Host" header field to a hostname.
358
- *
359
- * When the "trust proxy" setting trusts the socket
360
- * address, the "X-Forwarded-Host" header field will
361
- * be trusted.
358
+ * Will return "X-Forwarded-Host" if set,
359
+ * or "Host" as a fallback.
362
360
  *
363
361
  * @return {String}
364
362
  * @public
365
363
  */
366
364
  defineGetter(req, 'hostname', function hostname() {
367
- const trust = this.app.get('trust proxy fn');
368
- let host = this.get('X-Forwarded-Host');
369
-
370
- if (!host || !trust(this.connection.remoteAddress, 0)) {
371
- host = this.get('Host');
372
- }
373
- else if (host.indexOf(',') !== -1) {
374
- // Note: X-Forwarded-Host is normally only ever a
375
- // single value, but this is to be safe.
376
- host = host.substring(0, host.indexOf(',')).trimRight()
377
- }
378
-
379
- if (!host)
380
- return;
381
-
382
- // IPv6 literal support
383
- const offset = host[0] === '['
384
- ? host.indexOf(']') + 1
385
- : 0;
386
- const index = host.indexOf(':', offset);
387
-
388
- return index !== -1
389
- ? host.substring(0, index)
390
- : host;
365
+ const host = this.get('X-Forwarded-Host') ?? this.get('Host');
366
+ return host;
391
367
  });
392
368
 
393
369
 
@@ -12,7 +12,7 @@ const contentDisposition = require('content-disposition');
12
12
  const createError = require('http-errors')
13
13
  const encodeUrl = require('encodeurl');
14
14
  const escapeHtml = require('escape-html');
15
- const http = require('http');
15
+ const { ServerResponse } = require('http');
16
16
  const onFinished = require('on-finished');
17
17
  const statuses = require('statuses')
18
18
  const sign = require('cookie-signature').sign;
@@ -28,10 +28,10 @@ const {
28
28
  const path = require('path');
29
29
  const extname = path.extname;
30
30
  const resolve = path.resolve;
31
- const { isAbsolute } = require('../../utils/path.util');
31
+ const { isAbsolute } = require('../utils/path.util');
32
32
 
33
33
 
34
- const res = http.ServerResponse.prototype;
34
+ const res = ServerResponse.prototype;
35
35
  module.exports = res;
36
36
 
37
37
  /**
@@ -135,53 +135,26 @@ res.send = function send(body) {
135
135
  }
136
136
  }
137
137
 
138
- // determine if ETag should be generated
139
- const etagFn = app.get('etag fn')
140
- const generateETag = !this.get('ETag') && typeof etagFn === 'function'
141
-
142
- // populate Content-Length
143
- let bufferLength = undefined;
144
- if (chunk !== undefined) {
145
- if (Buffer.isBuffer(chunk)) {
146
- // Get length of the Buffer.
147
- bufferLength = chunk.length
148
- }
149
- else if (!generateETag && chunk.length < 1000) {
150
- // Just calculate length when no ETag + small chunk.
151
- bufferLength = Buffer.byteLength(chunk, encoding)
152
- }
153
- else {
154
- // Convert chunk to Buffer and calculate:
155
- chunk = Buffer.from(chunk, encoding)
156
- encoding = undefined;
157
- bufferLength = chunk.length
158
- }
159
-
160
- this.set('Content-Length', bufferLength);
161
- }
162
-
163
- // Populate ETag
164
- let etag = undefined;
165
- if (generateETag && bufferLength !== undefined) {
166
- if ((etag = etagFn(chunk, encoding))) {
167
- this.set('ETag', etag);
168
- }
169
- }
170
-
171
138
  // freshness:
172
- if (req.fresh) {
173
- this.statusCode = 304;
174
- }
175
-
176
- // strip irrelevant headers
177
- if (204 === this.statusCode || 304 === this.statusCode) {
139
+ // if (req.fresh) {
140
+ // this.statusCode = 304;
141
+ // }
142
+
143
+ // Strip irrelevant headers for:
144
+ // - 204 (No Content)
145
+ // - 303 (Not Modified)
146
+ if (
147
+ this.statusCode === 204
148
+ ||
149
+ this.statusCode === 304
150
+ ) {
178
151
  this.removeHeader('Content-Type');
179
152
  this.removeHeader('Content-Length');
180
153
  this.removeHeader('Transfer-Encoding');
181
154
  chunk = '';
182
155
  }
183
156
 
184
- // alter headers for 205
157
+ // Alter headers for 205 (Reset Content):
185
158
  if (this.statusCode === 205) {
186
159
  this.set('Content-Length', '0')
187
160
  this.removeHeader('Transfer-Encoding')
@@ -189,11 +162,11 @@ res.send = function send(body) {
189
162
  }
190
163
 
191
164
  if (req.method === 'HEAD') {
192
- // skip body for HEAD
165
+ // skip body for HEAD.
193
166
  this.end();
194
167
  }
195
168
  else {
196
- // respond
169
+ // Respond.
197
170
  this.end(chunk, encoding);
198
171
  }
199
172
 
@@ -214,19 +187,13 @@ res.send = function send(body) {
214
187
  * @public
215
188
  */
216
189
  res.json = function json(obj) {
217
- const app = this.app;
218
-
219
- // Settings:
220
- const escape = app.get('json escape')
221
- const replacer = app.get('json replacer');
222
- const spaces = app.get('json spaces');
223
- const body = stringify(obj, replacer, spaces, escape)
224
190
 
225
- // content-type
191
+ // If content-type not set:
226
192
  if (!this.get('Content-Type')) {
227
193
  this.set('Content-Type', 'application/json');
228
194
  }
229
195
 
196
+ const body = JSON.stringify(obj);
230
197
  return this.send(body);
231
198
  };
232
199
 
@@ -0,0 +1,62 @@
1
+ 'use strict'
2
+
3
+ const calculate = require('etag');
4
+ const Stream = require('stream');
5
+ const promisify = require('util').promisify;
6
+ const fs = require('fs');
7
+
8
+ const getFileStats = promisify(fs.stat);
9
+
10
+ /**
11
+ * Expose `etag` middleware.
12
+ *
13
+ * Add ETag header field.
14
+ * @param {object} [options] see https://github.com/jshttp/etag#options
15
+ * @param {boolean} [options.weak]
16
+ * @return {Function}
17
+ * @api public
18
+ */
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);
24
+ }
25
+ }
26
+
27
+
28
+ async function getResponseEntity (res) {
29
+ // If body is not defined:
30
+ const { body } = res;
31
+ if (!body || res.get('etag'))
32
+ return;
33
+
34
+ // Status code.
35
+ const status = res.status / 100 | 0;
36
+
37
+ // 2xx
38
+ if (status !== 2)
39
+ return;
40
+
41
+ if (body instanceof Stream) {
42
+ if (!body.path)
43
+ return;
44
+
45
+ const stats = await getFileStats(body.path);
46
+ return stats;
47
+ }
48
+ else if ((typeof body === 'string') || Buffer.isBuffer(body)) {
49
+ return body;
50
+ }
51
+ else {
52
+ return JSON.stringify(body);
53
+ }
54
+ }
55
+
56
+
57
+ function setEtag (res, entity, options) {
58
+ if (!entity)
59
+ return;
60
+
61
+ res.etag = calculate(entity, options);
62
+ }
@@ -0,0 +1,34 @@
1
+ const QueryLexer = require('./interpreter/QueryLexer');
2
+
3
+
4
+ module.exports = NodesterQL;
5
+
6
+ async function NodesterQL(req, res, next) {
7
+ // Object, which will be populated with parsed query.
8
+ req.nquery = {};
9
+
10
+ // Unwrap neccessary params.
11
+ const {
12
+ url
13
+ } = req;
14
+
15
+ // If no query, skip:
16
+ if (url.indexOf('?') === -1) {
17
+ return next();
18
+ }
19
+
20
+ try {
21
+ // Convert to URLSearchParams.
22
+ const queryString = req.url.split('?')[1];
23
+
24
+ const lexer = new QueryLexer(queryString);
25
+
26
+ // Go on!
27
+ req.nquery = lexer.query;
28
+ next();
29
+ }
30
+ catch(error) {
31
+ res.status(422);
32
+ res.json({ error: error.toString() });
33
+ }
34
+ }
@@ -0,0 +1,121 @@
1
+ const debug = require('debug')('nodester:interpreter:ModelsTree');
2
+
3
+
4
+ class ModelsTreeNode {
5
+ constructor(model, parent=null, opts={}) {
6
+ this.model = model;
7
+ this.parent = parent;
8
+ this.activeParam = null;
9
+ this.op = null;
10
+
11
+ // for override:
12
+ this.fields = [];
13
+ this._where = {};
14
+ this.skip = 0;
15
+ this.limit = -1; // No limit
16
+
17
+ this.includes = opts.includes ?? [];
18
+ this.order = opts.order ?? 'asc';
19
+ this.order_by = opts.order_by ?? 'id';
20
+ }
21
+
22
+ get hasParent() {
23
+ return this.parent !== null;
24
+ }
25
+
26
+ get includesCount() {
27
+ return Object.values(this.includes).length;
28
+ }
29
+
30
+ get hasIncludes() {
31
+ return this.includesCount > 0;
32
+ }
33
+
34
+ get where() {
35
+ return this._where;
36
+ }
37
+
38
+ resetActiveParam() {
39
+ this.activeParam = null;
40
+ }
41
+
42
+ resetOP() {
43
+ this.op = null;
44
+ }
45
+
46
+ addWhere(condition={}) {
47
+ this._where = {
48
+ ...this.where,
49
+ ...condition
50
+ }
51
+ }
52
+
53
+ include(modelTreeNode) {
54
+ modelTreeNode.parent = this;
55
+ this.includes.push(modelTreeNode);
56
+ return modelTreeNode;
57
+ }
58
+
59
+ toObject() {
60
+ return {
61
+ model: this.model,
62
+
63
+ where: this.where,
64
+ skip: this.skip,
65
+ limit: this.limit,
66
+ order: this.order,
67
+ order_by: this.order_by,
68
+
69
+ fields: this.fields,
70
+
71
+ includes: this.includes.map(i => i.toObject())
72
+ }
73
+ }
74
+ }
75
+
76
+ class ModelsTree {
77
+ constructor() {
78
+ this.root = new ModelsTreeNode('root', null);
79
+ this.node = this.root;
80
+ }
81
+
82
+ include(model, opts={}) {
83
+ debug('include', model);
84
+
85
+ const node = new ModelsTreeNode(model, this.node, opts);
86
+ this.node.include(node);
87
+ return this;
88
+ }
89
+
90
+ use(model) {
91
+ let foundOne = null;
92
+ // Dirty search:
93
+ for (const include of this.node.includes) {
94
+ if (include.model === model) {
95
+ foundOne = this.node = include;
96
+ break;
97
+ }
98
+ }
99
+
100
+ debug('use', model, !!foundOne ? '' : '-> failed.');
101
+
102
+ return foundOne;
103
+ }
104
+
105
+ up() {
106
+ if (this.node.hasParent) {
107
+ this.node = this.node.parent;
108
+ }
109
+
110
+ debug('go up to', this.node.model);
111
+ }
112
+
113
+ upToRoot() {
114
+ this.node = this.root;
115
+
116
+ debug('go up to root');
117
+ }
118
+ }
119
+
120
+ exports.ModelsTreeNode = ModelsTreeNode;
121
+ exports.ModelsTree = ModelsTree;