nodester 0.0.1 → 0.0.6
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 +61 -26
- package/lib/application/index.js +86 -35
- package/lib/constants/Operations.js +23 -0
- package/lib/controllers/methods/index.js +170 -0
- package/lib/controllers/mixins/index.js +213 -0
- package/lib/enums/Enum.js +16 -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/define.js +6 -13
- package/lib/models/mixins.js +28 -15
- package/lib/params/Params.js +34 -0
- package/lib/queries/NodesterQueryParams.js +139 -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 +16 -2
- 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/factories/responses/api.js +0 -90
- 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/{controllers/Controller.js → _/n_controllers/Controller.js"} +0 -0
- /package/lib/{controllers/JWTController.js → _/n_controllers/JWTController.js"} +0 -0
- /package/lib/{controllers/ServiceController.js → _/n_controllers/ServiceController.js"} +0 -0
- /package/lib/{controllers/WebController.js → _/n_controllers/WebController.js"} +0 -0
- /package/lib/{facades → _facades}/Facade.js +0 -0
- /package/lib/{facades → _facades}/FacadeParams.js +0 -0
- /package/lib/{facades → _facades}/ServiceFacade.js +0 -0
- /package/lib/{facades → _facades}/jwt.facade.js +0 -0
- /package/lib/{application/http → http}/utils.js +0 -0
|
@@ -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
|
|
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('
|
|
31
|
+
const { isAbsolute } = require('../utils/path.util');
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
const res =
|
|
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
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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.orderBy = opts.orderBy ?? '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
|
+
orderBy: this.orderBy,
|
|
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;
|