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.
- package/Readme.md +33 -39
- package/lib/application/index.js +110 -38
- package/lib/constants/Operations.js +23 -0
- package/lib/controllers/methods/index.js +194 -0
- package/lib/controllers/mixins/index.js +222 -0
- package/lib/database/connection.js +34 -0
- package/lib/database/migration.js +42 -0
- package/lib/database/utils.js +19 -0
- package/lib/enums/Enum.js +16 -0
- package/lib/facades/methods/index.js +173 -0
- package/lib/facades/mixins/index.js +111 -0
- package/lib/factories/errors/CustomError.js +7 -5
- package/lib/factories/responses/html.js +7 -2
- package/lib/factories/responses/rest.js +110 -0
- package/lib/http/codes/index.js +157 -0
- package/lib/{application/http → http}/request.js +6 -30
- package/lib/{application/http → http}/response.js +20 -53
- package/lib/middlewares/etag/index.js +62 -0
- package/lib/middlewares/ql/sequelize/index.js +34 -0
- package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +121 -0
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +456 -0
- package/lib/models/associate.js +17 -0
- package/lib/models/define.js +56 -14
- package/lib/models/mixins.js +100 -78
- package/lib/params/Params.js +37 -0
- package/lib/queries/Colander.js +84 -0
- package/lib/queries/NodesterQueryParams.js +139 -0
- package/lib/queries/traverse.js +311 -0
- package/lib/router/handlers.util.js +61 -0
- package/lib/router/index.js +386 -0
- package/lib/router/route.js +124 -0
- package/lib/router/routes.util.js +66 -0
- package/lib/stacks/MarkersStack.js +35 -0
- package/lib/{application → stacks}/MiddlewareStack.js +47 -13
- package/lib/utils/path.util.js +3 -1
- package/lib/utils/types.util.js +51 -1
- package/lib/validators/dates.js +25 -0
- package/lib/validators/numbers.js +14 -0
- package/package.json +31 -4
- package/tests/index.test.js +7 -2
- package/tests/nql.test.js +277 -0
- package/docs/App.md +0 -13
- package/docs/Queries.md +0 -61
- package/docs/Readme.md +0 -2
- package/docs/Routing.md +0 -34
- package/examples/goal/index.js +0 -23
- package/examples/rest/index.js +0 -25
- package/examples/rest/node_modules/.package-lock.json +0 -40
- package/examples/rest/package-lock.json +0 -72
- package/examples/rest/package.json +0 -14
- package/lib/constants/ConstantsEnum.js +0 -13
- package/lib/controllers/Controller.js +0 -474
- package/lib/controllers/JWTController.js +0 -240
- package/lib/controllers/ServiceController.js +0 -109
- package/lib/controllers/WebController.js +0 -75
- package/lib/facades/Facade.js +0 -388
- package/lib/facades/FacadeParams.js +0 -11
- package/lib/facades/ServiceFacade.js +0 -17
- package/lib/facades/jwt.facade.js +0 -273
- package/lib/factories/responses/api.js +0 -90
- package/lib/models/DisabledRefreshToken.js +0 -68
- package/lib/models/Extractor.js +0 -320
- package/lib/routers/Default/index.js +0 -143
- package/lib/routers/Default/layer.js +0 -50
- package/lib/routers/Main/index.js +0 -10
- package/lib/routers/Roles/index.js +0 -81
- package/lib/utils/params.util.js +0 -19
- /package/lib/{application/http → http}/utils.js +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
const Enum = require('../../../../enums/Enum');
|
|
2
|
+
const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
|
|
3
|
+
const util = require('util');
|
|
4
|
+
const debug = require('debug')('nodester:interpreter:QueryLexer');
|
|
5
|
+
|
|
6
|
+
const OP_TOKENS = new Enum({
|
|
7
|
+
AND: 'and',
|
|
8
|
+
BETWEEN: 'between',
|
|
9
|
+
NOT_BETWEEN: 'notBetween',
|
|
10
|
+
BETWEEN_MARK: '~',
|
|
11
|
+
OR: 'or',
|
|
12
|
+
OR_MARK: '|',
|
|
13
|
+
XOR: 'xor',
|
|
14
|
+
NOT: 'not',
|
|
15
|
+
NOT_MARK: '!',
|
|
16
|
+
NOT_IN: 'notIn',
|
|
17
|
+
LIKE: 'like',
|
|
18
|
+
GREATER: 'gt',
|
|
19
|
+
GREATER_OR_EQUAL: 'gte',
|
|
20
|
+
LOWER: 'lt',
|
|
21
|
+
LOWER_OR_EQUAL: 'lte'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module.exports = class QueryLexer {
|
|
26
|
+
constructor(queryString='') {
|
|
27
|
+
this.tree = new ModelsTree();
|
|
28
|
+
this.query = !!queryString ?
|
|
29
|
+
this.parse(queryString).toObject()
|
|
30
|
+
:
|
|
31
|
+
{};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parse(queryString='', tree=this.tree) {
|
|
35
|
+
if (typeof queryString !== 'string') {
|
|
36
|
+
const err = new TypeError(`Invalid 'queryString'.`);
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// You never know if it's encoded or not.
|
|
41
|
+
const decoded = decodeURI(queryString);
|
|
42
|
+
|
|
43
|
+
this.parseIsolatedQuery(decoded, 0, this.tree);
|
|
44
|
+
|
|
45
|
+
return this.tree.root;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
parseIsolatedQuery(queryString='', startAt=0, tree) {
|
|
49
|
+
const isSubQuery = tree.node.model !== 'root';
|
|
50
|
+
debug({ isSubQuery, startAt });
|
|
51
|
+
|
|
52
|
+
// Token is accumulated char-by-char.
|
|
53
|
+
let token = '';
|
|
54
|
+
// Value of param ('id=10' OR 'fields=id,text').
|
|
55
|
+
let value = [];
|
|
56
|
+
// Model, that was active before cursor went up in the tree.
|
|
57
|
+
let previousActive = null;
|
|
58
|
+
|
|
59
|
+
for (let i=startAt; i < queryString.length; i++) {
|
|
60
|
+
const char = queryString[i];
|
|
61
|
+
|
|
62
|
+
// ( can mean params of OP token,
|
|
63
|
+
// or subquery of a model:
|
|
64
|
+
if (char === '(') {
|
|
65
|
+
debug('char', char, { token, node: tree.node });
|
|
66
|
+
|
|
67
|
+
// If OP token:
|
|
68
|
+
if (OP_TOKENS.asArray.indexOf(token) > -1) {
|
|
69
|
+
// Set operation token.
|
|
70
|
+
tree.node.op = this.parseOP(token);
|
|
71
|
+
token = '';
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// If model subquery:
|
|
75
|
+
else {
|
|
76
|
+
const model = token;
|
|
77
|
+
tree.use(model) ?? tree.include(model).use(model);
|
|
78
|
+
token = '';
|
|
79
|
+
|
|
80
|
+
// Process subquery:
|
|
81
|
+
i++;
|
|
82
|
+
const [ charsCount ] = this.parseIsolatedQuery(queryString, i, tree);
|
|
83
|
+
i += charsCount;
|
|
84
|
+
|
|
85
|
+
previousActive = model;
|
|
86
|
+
tree.up();
|
|
87
|
+
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ) can mean end of OP token params,
|
|
93
|
+
// or end of subquery:
|
|
94
|
+
if (char === ')') {
|
|
95
|
+
debug('char', char, { token, node: tree.node });
|
|
96
|
+
|
|
97
|
+
// If end of OP token:
|
|
98
|
+
if (!!tree.node.op) {
|
|
99
|
+
|
|
100
|
+
// If token is empty, error:
|
|
101
|
+
if (token === '') {
|
|
102
|
+
const err = UnexpectedCharError(i, char);
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Structure of a value depends on OP:
|
|
107
|
+
let fullOp = {};
|
|
108
|
+
switch (tree.node.op) {
|
|
109
|
+
case 'not':
|
|
110
|
+
case 'like':
|
|
111
|
+
fullOp = { [tree.node.activeParam]: { [tree.node.op]: [token] } };
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
value.push({ [tree.node.activeParam]: [token] });
|
|
115
|
+
fullOp = { [tree.node.op]: value };
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
tree.node.addWhere(fullOp);
|
|
120
|
+
|
|
121
|
+
// Reset:
|
|
122
|
+
tree.node.resetOP();
|
|
123
|
+
tree.node.activeParam = 'includes';
|
|
124
|
+
token = '';
|
|
125
|
+
value = [];
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If end of subquery:
|
|
130
|
+
if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
|
|
131
|
+
// Set value.
|
|
132
|
+
this.setNodeParam(tree.node, token, value);
|
|
133
|
+
// Reset:
|
|
134
|
+
tree.node.resetActiveParam();
|
|
135
|
+
tree.node.resetOP();
|
|
136
|
+
}
|
|
137
|
+
const numberOfProcessedChars = i - startAt;
|
|
138
|
+
return [ numberOfProcessedChars ];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// , can mean n-th value in value array,
|
|
142
|
+
// or horizontal include:
|
|
143
|
+
if (char === ',') {
|
|
144
|
+
debug('char', char, { token, node: tree.node });
|
|
145
|
+
|
|
146
|
+
// If OP token:
|
|
147
|
+
if (!!tree.node.op) {
|
|
148
|
+
value.push({
|
|
149
|
+
[tree.node.activeParam]: [token]
|
|
150
|
+
});
|
|
151
|
+
token = '';
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If param value:
|
|
156
|
+
if (tree.node.activeParam !== 'includes') {
|
|
157
|
+
value.push(token);
|
|
158
|
+
token = '';
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Just quit from subquery:
|
|
163
|
+
if (token.length === 0) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Horizontal include:
|
|
168
|
+
if (tree.node.activeParam === 'includes') {
|
|
169
|
+
const model = token;
|
|
170
|
+
tree.use(model) ?? tree.include(model);
|
|
171
|
+
|
|
172
|
+
token = '';
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const err = UnexpectedCharError(i, char);
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// . can mean vertical include
|
|
181
|
+
// or it can be a part of param for "where":
|
|
182
|
+
if (char === '.') {
|
|
183
|
+
debug('char', char, { token, node: tree.node });
|
|
184
|
+
|
|
185
|
+
// Vertical include:
|
|
186
|
+
if (!!previousActive) {
|
|
187
|
+
tree.use(previousActive);
|
|
188
|
+
tree.node.activeParam = 'includes';
|
|
189
|
+
token = '';
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// If include of new model:
|
|
194
|
+
if (token.length > 0) {
|
|
195
|
+
const model = token;
|
|
196
|
+
tree.use(model) ?? tree.include(model).use(model);
|
|
197
|
+
|
|
198
|
+
// Prepare for more includes:
|
|
199
|
+
tree.node.activeParam = 'includes';
|
|
200
|
+
|
|
201
|
+
token = '';
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const err = UnexpectedCharError(i, char);
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// + can only mean horizontal include:
|
|
210
|
+
if (char === '+') {
|
|
211
|
+
debug('char', char, { token, node: tree.node });
|
|
212
|
+
|
|
213
|
+
// If include of new model:
|
|
214
|
+
if (token.length > 0) {
|
|
215
|
+
const model = token;
|
|
216
|
+
// Include, but do not use:
|
|
217
|
+
tree.use(model) ?? tree.include(model).use(model);
|
|
218
|
+
tree.up();
|
|
219
|
+
|
|
220
|
+
// Prepare for more includes:
|
|
221
|
+
tree.node.activeParam = 'includes';
|
|
222
|
+
|
|
223
|
+
token = '';
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (tree.node.hasParent === false) {
|
|
228
|
+
const err = UnexpectedCharError(i, char);
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
tree.up();
|
|
233
|
+
tree.node.activeParam = 'includes';
|
|
234
|
+
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// & can mean the end of key=value pair,
|
|
239
|
+
// or the end of subincludes:
|
|
240
|
+
if (char === '&') {
|
|
241
|
+
debug('char', char, { token, node: tree.node });
|
|
242
|
+
|
|
243
|
+
// If any OP at all:
|
|
244
|
+
if (!!tree.node.op) {
|
|
245
|
+
const err = MissingCharError(i+1, ')');
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// If end of key=value pair:
|
|
250
|
+
if (!!tree.node.activeParam && tree.node.activeParam !== 'includes') {
|
|
251
|
+
// Set value.
|
|
252
|
+
this.setNodeParam(tree.node, token, value);
|
|
253
|
+
// Reset:
|
|
254
|
+
tree.node.resetActiveParam();
|
|
255
|
+
token = '';
|
|
256
|
+
value = [];
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
else if (tree.node.activeParam === 'includes') {
|
|
260
|
+
// If include of new model:
|
|
261
|
+
if (token.length > 0) {
|
|
262
|
+
const model = token;
|
|
263
|
+
// Just include, no use.
|
|
264
|
+
tree.include(model);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Then jump to root.
|
|
268
|
+
tree.upToRoot();
|
|
269
|
+
|
|
270
|
+
// Reset:
|
|
271
|
+
token = '';
|
|
272
|
+
value = [];
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// If end of subquery:
|
|
277
|
+
if (tree.node.hasParent === true) {
|
|
278
|
+
tree.up();
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// If root:
|
|
282
|
+
else {
|
|
283
|
+
// Reset:
|
|
284
|
+
tree.node.resetActiveParam();
|
|
285
|
+
token = '';
|
|
286
|
+
value = [];
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Unknown case:
|
|
291
|
+
const err = UnexpectedCharError(i, char);
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// [ can only mean start of 'in':
|
|
296
|
+
if (char === '[') {
|
|
297
|
+
tree.node.op = 'in';
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ] can only mean end if 'in':
|
|
302
|
+
if (char === ']') {
|
|
303
|
+
// User missed first '[' :
|
|
304
|
+
if (tree.node.op !== 'in') {
|
|
305
|
+
const err = UnexpectedCharError(i, char);
|
|
306
|
+
throw err;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
tree.node.addWhere({
|
|
310
|
+
[tree.node.activeParam]: {
|
|
311
|
+
[tree.node.op]: value
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
// Reset:
|
|
315
|
+
tree.node.resetOP();
|
|
316
|
+
value = [];
|
|
317
|
+
token = '';
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// = can only mean the end of param name:
|
|
322
|
+
if (char === '=') {
|
|
323
|
+
const param = this.parseParamFromToken(token);
|
|
324
|
+
|
|
325
|
+
if (isSubQuery === true && param === 'includes') {
|
|
326
|
+
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model' or 'model.model+model'.`);
|
|
327
|
+
throw err;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
tree.node.activeParam = param;
|
|
331
|
+
token = '';
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Continue accumulating token.
|
|
336
|
+
token += char;
|
|
337
|
+
|
|
338
|
+
// If last char:
|
|
339
|
+
if (i === queryString.length-1) {
|
|
340
|
+
debug('last char', { token, node: tree.node });
|
|
341
|
+
|
|
342
|
+
// haven't up from 'in':
|
|
343
|
+
if (tree.node.op === 'in') {
|
|
344
|
+
const err = MissingCharError(i+1, ']');
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// If any OP at all:
|
|
349
|
+
if (!!tree.node.op) {
|
|
350
|
+
const err = MissingCharError(i+1, ')');
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.setNodeParam(tree.node, token, value);
|
|
355
|
+
|
|
356
|
+
// If end of subquery:
|
|
357
|
+
if (isSubQuery === true) {
|
|
358
|
+
const numberOfProcessedChars = i+1 - startAt;
|
|
359
|
+
return [ numberOfProcessedChars ];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return [ queryString.length - startAt ];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
parseParamFromToken(token) {
|
|
368
|
+
switch(token) {
|
|
369
|
+
case 'limit':
|
|
370
|
+
case 'l':
|
|
371
|
+
return 'limit';
|
|
372
|
+
case 'skip':
|
|
373
|
+
case 's':
|
|
374
|
+
case 'offset':
|
|
375
|
+
return 'skip';
|
|
376
|
+
case 'order':
|
|
377
|
+
case 'o':
|
|
378
|
+
return 'order';
|
|
379
|
+
case 'order_by':
|
|
380
|
+
case 'o_by':
|
|
381
|
+
return 'order_by';
|
|
382
|
+
case 'fields':
|
|
383
|
+
case 'f':
|
|
384
|
+
return 'fields';
|
|
385
|
+
case 'includes':
|
|
386
|
+
case 'in':
|
|
387
|
+
return 'includes';
|
|
388
|
+
default:
|
|
389
|
+
return token;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
setNodeParam(treeNode, token, value) {
|
|
394
|
+
const param = treeNode.activeParam;
|
|
395
|
+
|
|
396
|
+
debug(`set param ${ param }`, { token, value });
|
|
397
|
+
|
|
398
|
+
switch(param) {
|
|
399
|
+
case 'limit':
|
|
400
|
+
treeNode.limit = parseInt(token);
|
|
401
|
+
break;
|
|
402
|
+
case 'skip':
|
|
403
|
+
case 'offset':
|
|
404
|
+
treeNode.skip = parseInt(token);
|
|
405
|
+
break;
|
|
406
|
+
case 'order':
|
|
407
|
+
treeNode.order = token;
|
|
408
|
+
break;
|
|
409
|
+
case 'order_by':
|
|
410
|
+
treeNode.order_by = token;
|
|
411
|
+
break;
|
|
412
|
+
case 'fields':
|
|
413
|
+
if (token)
|
|
414
|
+
value.push(token);
|
|
415
|
+
treeNode.fields = value;
|
|
416
|
+
break;
|
|
417
|
+
case 'includes':
|
|
418
|
+
const node = new ModelsTreeNode(token);
|
|
419
|
+
treeNode.include(node);
|
|
420
|
+
break;
|
|
421
|
+
default:
|
|
422
|
+
if (token)
|
|
423
|
+
value.push(token);
|
|
424
|
+
treeNode.addWhere({ [param]: value });
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
parseOP(opToken) {
|
|
430
|
+
switch(opToken) {
|
|
431
|
+
case '|':
|
|
432
|
+
case 'or':
|
|
433
|
+
return 'or';
|
|
434
|
+
case 'not':
|
|
435
|
+
case '!':
|
|
436
|
+
return 'not';
|
|
437
|
+
default:
|
|
438
|
+
return opToken;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
[util.inspect.custom](depth, opts) {
|
|
443
|
+
return this.tree.root;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
function UnexpectedCharError(index, char) {
|
|
449
|
+
const err = new TypeError(`Unexpected ${ char } at position ${ index }`);
|
|
450
|
+
return err;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function MissingCharError(index, char) {
|
|
454
|
+
const err = new TypeError(`Missing ${ char } at position ${ index }`);
|
|
455
|
+
return err;
|
|
456
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = async function associateModels(databaseConnection) {
|
|
3
|
+
try {
|
|
4
|
+
const models = databaseConnection.models;
|
|
5
|
+
|
|
6
|
+
const modelNames = Object.keys(models);
|
|
7
|
+
|
|
8
|
+
for (let modelName of modelNames) {
|
|
9
|
+
await models[modelName].associate(models);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return Promise.resolve(models);
|
|
13
|
+
}
|
|
14
|
+
catch(error) {
|
|
15
|
+
return Promise.reject(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/lib/models/define.js
CHANGED
|
@@ -2,26 +2,21 @@
|
|
|
2
2
|
const { implementsCRUD } = require('./mixins');
|
|
3
3
|
// ORM.
|
|
4
4
|
const { DataTypes } = require('sequelize');
|
|
5
|
+
// NQL.
|
|
6
|
+
const Colander = require('../queries/Colander');
|
|
5
7
|
|
|
6
|
-
// Utils.
|
|
7
|
-
// const {
|
|
8
|
-
// pluralize,
|
|
9
|
-
// underscore
|
|
10
|
-
// } = require('../utils/strings.util');
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
module.exports = _defineModel;
|
|
9
|
+
module.exports = defineModel;
|
|
14
10
|
|
|
15
11
|
/*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* - options (Object)
|
|
12
|
+
* @param {SequilizeConnection} databaseConnection
|
|
13
|
+
* @param {String} modelName
|
|
14
|
+
* @param {Function} definition
|
|
15
|
+
* @param {Object} options
|
|
21
16
|
* - ... Sequilize model options
|
|
22
17
|
* - noCRUD (Bool)
|
|
23
18
|
*/
|
|
24
|
-
function
|
|
19
|
+
function defineModel(
|
|
25
20
|
databaseConnection,
|
|
26
21
|
modelName='',
|
|
27
22
|
definition=()=>{},
|
|
@@ -44,13 +39,23 @@ function _defineModel(
|
|
|
44
39
|
// Add user-defined options (they can override upper ones).
|
|
45
40
|
...options
|
|
46
41
|
};
|
|
42
|
+
|
|
47
43
|
const model = databaseConnection.define(modelName, definitionObject, _options);
|
|
48
44
|
|
|
49
45
|
if (options.noCRUD !== true) {
|
|
50
|
-
// Add
|
|
46
|
+
// Add:
|
|
47
|
+
// - createWithIncludes;
|
|
48
|
+
// - findById;
|
|
49
|
+
// - updateById;
|
|
50
|
+
// - deleteById;
|
|
51
|
+
// - etc.
|
|
51
52
|
implementsCRUD(model);
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// Association helpers:
|
|
56
|
+
model.associate = (models) => {};
|
|
57
|
+
model.getIncludesList = _getIncludesList.bind(model);
|
|
58
|
+
|
|
54
59
|
// Instance methods:
|
|
55
60
|
model.prototype.toJSON = function() {
|
|
56
61
|
const values = { ...this.get() };
|
|
@@ -60,3 +65,40 @@ function _defineModel(
|
|
|
60
65
|
|
|
61
66
|
return model;
|
|
62
67
|
}
|
|
68
|
+
|
|
69
|
+
/* Association mixins: */
|
|
70
|
+
function _getIncludesList(facadeData=null) {
|
|
71
|
+
const result = [];
|
|
72
|
+
|
|
73
|
+
const associations = this.associations;
|
|
74
|
+
const associationEntries = Object.entries(associations);
|
|
75
|
+
|
|
76
|
+
associationEntries.forEach(([
|
|
77
|
+
associationName,
|
|
78
|
+
associationDefinition
|
|
79
|
+
]) => {
|
|
80
|
+
const a = { association: associationName };
|
|
81
|
+
|
|
82
|
+
if (!!facadeData) {
|
|
83
|
+
// If facade data is set, go deeper:
|
|
84
|
+
const keys = Object.keys( facadeData );
|
|
85
|
+
if (keys.indexOf(associationName) > 0) {
|
|
86
|
+
const associationModel = associationDefinition.target;
|
|
87
|
+
|
|
88
|
+
const a = { association: associationName };
|
|
89
|
+
if (Object.entries(associationModel.associations).length > 0) {
|
|
90
|
+
const deepData = facadeData[ associationName ];
|
|
91
|
+
a.include = associationModel.getIncludesList(Array.isArray(deepData) ? deepData[0] : deepData);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
result.push( a );
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
result.push( a );
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/* Association mixins\ */
|