barehttp 0.4.2 → 0.6.0
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 +35 -10
- package/lib/logger/index.js +6 -2
- package/lib/request.d.ts +8 -4
- package/lib/request.js +41 -32
- package/lib/schemas/custom-schema.d.ts +32 -0
- package/lib/schemas/custom-schema.js +69 -0
- package/lib/schemas/dirty-tsm.d.ts +1 -0
- package/lib/schemas/dirty-tsm.js +201 -0
- package/lib/schemas/generator.d.ts +7 -0
- package/lib/schemas/generator.js +186 -0
- package/lib/schemas/helpers.d.ts +27 -0
- package/lib/schemas/helpers.js +50 -0
- package/lib/schemas/json-schema.d.ts +2 -0
- package/lib/schemas/json-schema.js +52 -0
- package/lib/schemas/openami-schema.d.ts +2 -0
- package/lib/schemas/openami-schema.js +63 -0
- package/lib/schemas/project.d.ts +0 -0
- package/lib/schemas/project.js +1 -0
- package/lib/server.d.ts +44 -15
- package/lib/server.js +103 -62
- package/lib/websocket.js +3 -1
- package/package.json +14 -10
- package/lib/report.d.ts +0 -2
- package/lib/report.js +0 -20
package/lib/server.js
CHANGED
|
@@ -5,19 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BareHttp = exports.BareServer = void 0;
|
|
7
7
|
const find_my_way_1 = __importDefault(require("find-my-way"));
|
|
8
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
8
9
|
const request_1 = require("./request");
|
|
9
10
|
const logger_1 = require("./logger");
|
|
10
11
|
const context_1 = require("./context");
|
|
11
|
-
const report_1 = require("./report");
|
|
12
12
|
const utils_1 = require("./utils");
|
|
13
13
|
const cors_1 = require("./middlewares/cors/cors");
|
|
14
14
|
const websocket_1 = require("./websocket");
|
|
15
|
+
const generator_1 = require("./schemas/generator");
|
|
15
16
|
const dns_1 = __importDefault(require("dns"));
|
|
16
17
|
const http_1 = require("http");
|
|
17
18
|
class BareServer {
|
|
18
19
|
bareOptions;
|
|
19
20
|
server;
|
|
20
21
|
ws;
|
|
22
|
+
ajv;
|
|
23
|
+
route = {};
|
|
21
24
|
#middlewares = [];
|
|
22
25
|
#routes = new Map();
|
|
23
26
|
#routesLib = new Map();
|
|
@@ -26,26 +29,37 @@ class BareServer {
|
|
|
26
29
|
#corsInstance;
|
|
27
30
|
#port = 3000;
|
|
28
31
|
#host = '0.0.0.0';
|
|
29
|
-
#
|
|
32
|
+
#globalMiddlewaresRun = (_) => _;
|
|
33
|
+
#routeMiddlewaresStore = new Map();
|
|
34
|
+
#routeRuntimeSchemas = new Map();
|
|
30
35
|
constructor(bareOptions = {}) {
|
|
31
36
|
this.bareOptions = bareOptions;
|
|
32
37
|
// init
|
|
33
38
|
this.server = (0, http_1.createServer)(this.#listener.bind(this));
|
|
34
39
|
this.attachGracefulHandlers();
|
|
35
40
|
this.attachRoutesDeclarator();
|
|
36
|
-
this.
|
|
41
|
+
this.applyLaunchOptions();
|
|
42
|
+
this.loadRoutesSchemas();
|
|
37
43
|
return this;
|
|
38
44
|
}
|
|
39
45
|
#listener = (request, response) => {
|
|
40
|
-
const
|
|
41
|
-
const flow = new request_1.BareRequest(request, response, { logging, requestTimeFormat });
|
|
46
|
+
const flow = new request_1.BareRequest(request, response, this.bareOptions);
|
|
42
47
|
// init and attach request uuid to the context
|
|
43
48
|
if (this.bareOptions.context) {
|
|
44
49
|
(0, context_1.newContext)('request');
|
|
45
50
|
context_1.context.current?.store.set('id', flow.ID.code);
|
|
46
51
|
}
|
|
47
|
-
//
|
|
48
|
-
this.applyMiddlewares(flow)
|
|
52
|
+
// execute global middlewares on the request
|
|
53
|
+
this.applyMiddlewares(flow)
|
|
54
|
+
.catch((e) => {
|
|
55
|
+
this.#errorHandler(e, flow, 400);
|
|
56
|
+
})
|
|
57
|
+
.then(() => {
|
|
58
|
+
// if middlewares sent the response back, stop here
|
|
59
|
+
if (flow.sent)
|
|
60
|
+
return;
|
|
61
|
+
this.#router.lookup(flow._originalRequest, flow._originalResponse);
|
|
62
|
+
});
|
|
49
63
|
};
|
|
50
64
|
/**
|
|
51
65
|
* This function generates previously defined middlewares for the sequential execution
|
|
@@ -63,11 +77,16 @@ class BareServer {
|
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
const text = lines.join('\n');
|
|
66
|
-
this.#
|
|
80
|
+
this.#globalMiddlewaresRun = new AsyncFunction('flow', text);
|
|
67
81
|
};
|
|
68
|
-
|
|
82
|
+
applyLaunchOptions = () => {
|
|
69
83
|
const { bareOptions: bo } = this;
|
|
70
|
-
|
|
84
|
+
if (bo.setRandomPort) {
|
|
85
|
+
this.#port = undefined;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.#port = +(bo.serverPort || process.env.PORT || 3000);
|
|
89
|
+
}
|
|
71
90
|
this.#host = typeof bo.serverAddress === 'string' ? bo.serverAddress : '0.0.0.0';
|
|
72
91
|
// context setting
|
|
73
92
|
if (bo.context)
|
|
@@ -85,18 +104,15 @@ class BareServer {
|
|
|
85
104
|
this.#corsInstance = new cors_1.Cors(corsOpts);
|
|
86
105
|
}
|
|
87
106
|
this.#middlewares.push(...(bo.middlewares || []));
|
|
88
|
-
if (bo.statisticsReport)
|
|
89
|
-
this.registerReport();
|
|
90
107
|
};
|
|
91
108
|
async applyMiddlewares(flow) {
|
|
92
|
-
if (!flow) {
|
|
93
|
-
throw new Error(`No flow been found to apply middlewares for, theres a sync mistake in the server.`); // should NEVER happen
|
|
94
|
-
}
|
|
95
109
|
if (this.bareOptions.cors) {
|
|
96
110
|
this.resolveMiddleware(flow, 0, this.#corsInstance?.corsMiddleware.bind(this.#corsInstance));
|
|
97
111
|
}
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
if (this.bareOptions.doNotParseBody !== true) {
|
|
113
|
+
// invoke body stream consumption
|
|
114
|
+
await flow['readBody']();
|
|
115
|
+
}
|
|
100
116
|
// attach cookies middleware
|
|
101
117
|
if (this.bareOptions.cookies) {
|
|
102
118
|
flow['attachCookieManager'](this.bareOptions.cookiesOptions);
|
|
@@ -109,11 +125,7 @@ class BareServer {
|
|
|
109
125
|
flow['setRemoteClient'](remoteClient[0]);
|
|
110
126
|
}
|
|
111
127
|
if (this.#middlewares.length)
|
|
112
|
-
await this.#
|
|
113
|
-
// now route the request if middlewares did not send the response back
|
|
114
|
-
if (!flow.sent) {
|
|
115
|
-
this.#router.lookup(flow._originalRequest, flow._originalResponse);
|
|
116
|
-
}
|
|
128
|
+
await this.#globalMiddlewaresRun(flow);
|
|
117
129
|
}
|
|
118
130
|
/**
|
|
119
131
|
* This handler is used in async generated middlewares runtime function
|
|
@@ -129,15 +141,13 @@ class BareServer {
|
|
|
129
141
|
this.#errorHandler(e, flow);
|
|
130
142
|
}
|
|
131
143
|
}
|
|
132
|
-
setRoute(method, route,
|
|
144
|
+
setRoute(method, route, isRuntime, handler, opts) {
|
|
133
145
|
const encode = this.encodeRoute(method, route);
|
|
134
|
-
this.#routes.set(encode, { hits: 0, fails: 0, success: 0 });
|
|
135
146
|
const handleFn = (req, _, routeParams) => {
|
|
136
|
-
this
|
|
137
|
-
this.handleRoute(req, checkParams(routeParams), handler, encode, opts);
|
|
147
|
+
this.handleRoute(req, checkParams(routeParams), handler, opts);
|
|
138
148
|
};
|
|
139
149
|
this.#routesLib.set(encode, handleFn);
|
|
140
|
-
if (
|
|
150
|
+
if (isRuntime) {
|
|
141
151
|
this.#router.reset();
|
|
142
152
|
this.#routesLib.forEach((handlerFn, route) => {
|
|
143
153
|
const [m, r] = this.explodeRoute(route);
|
|
@@ -148,53 +158,63 @@ class BareServer {
|
|
|
148
158
|
this.#router.on(method, route, handleFn);
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
|
-
|
|
152
|
-
this.setRoute('GET', '/_report', false, (flow) => {
|
|
153
|
-
flow.setHeader('Content-Type', 'text/html');
|
|
154
|
-
flow.send((0, report_1.generateReport)(this.#routes));
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
handleRoute(req, routeParams, handle, encodedRoute, opts) {
|
|
161
|
+
handleRoute(req, routeParams, handle, routeOpts) {
|
|
158
162
|
const flow = req.flow;
|
|
159
163
|
if (!flow) {
|
|
160
164
|
throw new Error(`No flow been found to route this request, theres a sync mistake in the server.`); // should NEVER happen
|
|
161
165
|
}
|
|
162
|
-
// apply possible route options
|
|
163
|
-
if (opts) {
|
|
164
|
-
if (opts.disableCache)
|
|
165
|
-
flow.disableCache();
|
|
166
|
-
if (opts.cache)
|
|
167
|
-
flow.setCache(opts.cache);
|
|
168
|
-
if (opts.timeout)
|
|
169
|
-
flow['attachTimeout'](opts.timeout);
|
|
170
|
-
}
|
|
171
166
|
// populate with route params
|
|
172
167
|
if (routeParams)
|
|
173
168
|
flow['setParams'](routeParams);
|
|
174
|
-
//
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
});
|
|
169
|
+
// apply possible route options
|
|
170
|
+
if (routeOpts) {
|
|
171
|
+
if (routeOpts.disableCache)
|
|
172
|
+
flow.disableCache();
|
|
173
|
+
if (routeOpts.cache)
|
|
174
|
+
flow.setCache(routeOpts.cache);
|
|
175
|
+
if (routeOpts.timeout)
|
|
176
|
+
flow['attachTimeout'](routeOpts.timeout);
|
|
184
177
|
}
|
|
178
|
+
// TODO: implement per route middlewares!
|
|
185
179
|
try {
|
|
186
|
-
const routeReturn = handle
|
|
180
|
+
const routeReturn = handle(flow);
|
|
181
|
+
if (flow.sent)
|
|
182
|
+
return;
|
|
187
183
|
if (routeReturn instanceof Promise) {
|
|
188
|
-
routeReturn
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
routeReturn
|
|
185
|
+
.then((result) => this.resolveResponse(flow, result, req.url, req.method?.toLowerCase(), routeOpts?.builtInRuntime?.output))
|
|
186
|
+
.catch((e) => {
|
|
187
|
+
this.#errorHandler(e, flow);
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
192
190
|
}
|
|
191
|
+
this.resolveResponse(flow, routeReturn, req.url, req.method?.toLowerCase(), routeOpts?.builtInRuntime?.output);
|
|
193
192
|
}
|
|
194
193
|
catch (e) {
|
|
195
194
|
this.#errorHandler(e, flow);
|
|
196
195
|
}
|
|
197
196
|
}
|
|
197
|
+
resolveResponse(flow, response, url, method, builtInRuntime) {
|
|
198
|
+
if (!builtInRuntime || !method || !url) {
|
|
199
|
+
flow.send(response);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const schema = this.#routeRuntimeSchemas.get(`${method}-${url}`);
|
|
203
|
+
const check = schema?.compiled(response);
|
|
204
|
+
if ((schema && check) || !schema)
|
|
205
|
+
flow.send(response);
|
|
206
|
+
else {
|
|
207
|
+
logger_1.logMe.error('Response schema error!', {
|
|
208
|
+
method,
|
|
209
|
+
url,
|
|
210
|
+
errors: schema?.compiled.errors,
|
|
211
|
+
received: response,
|
|
212
|
+
});
|
|
213
|
+
flow
|
|
214
|
+
.status(500)
|
|
215
|
+
.send({ message: `Response schema error, please communicate to server administrator.` });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
198
218
|
encodeRoute(method, route) {
|
|
199
219
|
if (route.endsWith('/'))
|
|
200
220
|
route = route.slice(0, -1);
|
|
@@ -232,7 +252,7 @@ class BareServer {
|
|
|
232
252
|
}
|
|
233
253
|
attachRoutesDeclarator() {
|
|
234
254
|
for (const method of [...Object.keys(utils_1.HttpMethods), 'declare']) {
|
|
235
|
-
this[method] = (routeSetUp) => {
|
|
255
|
+
this.route[method] = (routeSetUp) => {
|
|
236
256
|
checkRouteSetUp(routeSetUp, method);
|
|
237
257
|
if (method === 'declare') {
|
|
238
258
|
for (const m of new Set(routeSetUp.methods))
|
|
@@ -298,8 +318,10 @@ class BareServer {
|
|
|
298
318
|
// }
|
|
299
319
|
if (!this.ws)
|
|
300
320
|
await this.stopWs();
|
|
321
|
+
if (!this.server?.listening)
|
|
322
|
+
return;
|
|
301
323
|
await new Promise((res, rej) => {
|
|
302
|
-
this.server
|
|
324
|
+
this.server.close((e) => {
|
|
303
325
|
if (e) {
|
|
304
326
|
rej(e);
|
|
305
327
|
cb?.(e);
|
|
@@ -311,6 +333,23 @@ class BareServer {
|
|
|
311
333
|
});
|
|
312
334
|
});
|
|
313
335
|
}
|
|
336
|
+
loadRoutesSchemas() {
|
|
337
|
+
if (!this.bareOptions.enableSchemaValidation) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (this.bareOptions.declaredRoutesPaths?.length) {
|
|
341
|
+
this.ajv = new ajv_1.default({ strict: true });
|
|
342
|
+
for (const path of this.bareOptions.declaredRoutesPaths) {
|
|
343
|
+
const schemas = (0, generator_1.generateRouteSchema)(path);
|
|
344
|
+
for (const schema of schemas) {
|
|
345
|
+
this.#routeRuntimeSchemas.set(`${schema.methodName}-${schema.route}`, {
|
|
346
|
+
raw: schema.jsonSchema,
|
|
347
|
+
compiled: this.ajv.compile(schema.jsonSchema),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
314
353
|
use(middleware) {
|
|
315
354
|
this.#middlewares.push(middleware);
|
|
316
355
|
return this;
|
|
@@ -321,11 +360,15 @@ class BareServer {
|
|
|
321
360
|
setCustomErrorHandler(eh) {
|
|
322
361
|
this.#errorHandler = eh;
|
|
323
362
|
}
|
|
363
|
+
getServerPort() {
|
|
364
|
+
return this.server.address().port;
|
|
365
|
+
}
|
|
324
366
|
getRoutes() {
|
|
325
367
|
return [...this.#routes.keys()];
|
|
326
368
|
}
|
|
327
369
|
}
|
|
328
370
|
exports.BareServer = BareServer;
|
|
371
|
+
exports.BareHttp = BareServer;
|
|
329
372
|
function checkRouteSetUp(routeSetUp, key) {
|
|
330
373
|
if (typeof routeSetUp.route !== 'string') {
|
|
331
374
|
throw new TypeError(`A route path for the method ${key} is not a a string`);
|
|
@@ -358,5 +401,3 @@ function checkParams(params) {
|
|
|
358
401
|
}
|
|
359
402
|
return params;
|
|
360
403
|
}
|
|
361
|
-
const BareHttp = BareServer;
|
|
362
|
-
exports.BareHttp = BareHttp;
|
package/lib/websocket.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "barehttp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Lightweight and fast Node.js web server",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"directories": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"lib"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "rm -rf ./lib && tsc",
|
|
14
|
+
"build": "rm -rf ./lib && tsc -p tsconfig.build.json",
|
|
15
15
|
"build:dev": "rm -rf ./dev-lib && tsc -p tsconfig.dev.json",
|
|
16
|
-
"test": "jest --
|
|
16
|
+
"test": "jest --coverage",
|
|
17
17
|
"lint": "eslint ./src --fix",
|
|
18
18
|
"release": "semantic-release -e ./.releaserc.json"
|
|
19
19
|
},
|
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
},
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"ajv": "^8.11.0",
|
|
36
37
|
"callsites": "^3.1.0",
|
|
37
|
-
"cookie": "^0.
|
|
38
|
-
"cookie-signature": "^1.
|
|
39
|
-
"find-my-way": "^5.
|
|
38
|
+
"cookie": "^0.5.0",
|
|
39
|
+
"cookie-signature": "^1.2.0",
|
|
40
|
+
"find-my-way": "^5.6.0",
|
|
40
41
|
"hyperid": "^2.3.1",
|
|
41
|
-
"
|
|
42
|
-
"pino
|
|
43
|
-
"
|
|
42
|
+
"lodash": "^4.17.21",
|
|
43
|
+
"pino": "^7.11.0",
|
|
44
|
+
"pino-pretty": "^7.6.1",
|
|
45
|
+
"ts-morph": "^14.0.0",
|
|
46
|
+
"ws": "^8.6.0"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@semantic-release/git": "^10.0.1",
|
|
@@ -61,11 +64,12 @@
|
|
|
61
64
|
"express": "^4.17.1",
|
|
62
65
|
"fastify": "^3.24.1",
|
|
63
66
|
"jest": "^27.4.3",
|
|
67
|
+
"prettier": "^2.5.1",
|
|
64
68
|
"semantic-release": "^18.0.1",
|
|
65
69
|
"supertest": "^6.1.3",
|
|
66
70
|
"ts-jest": "^27.0.7",
|
|
67
71
|
"ts-node-dev": "^1.1.6",
|
|
68
|
-
"typescript": "^4.
|
|
72
|
+
"typescript": "^4.6.4"
|
|
69
73
|
},
|
|
70
74
|
"optionalDependencies": {
|
|
71
75
|
"bufferutil": "^4.0.3",
|
package/lib/report.d.ts
DELETED
package/lib/report.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateReport = void 0;
|
|
4
|
-
function generateReport(routes) {
|
|
5
|
-
const lines = [];
|
|
6
|
-
lines.push('<!DOCTYPE html><html><head><title>Routes usage</title><meta charset="utf-8"></head><body><table style="border: 2px;border-style: ridge;border-radius: 5px;padding: 10px;"><tr><th>Route</th><th>Hits</th><th>Successes</th><th>Fails</th></tr>');
|
|
7
|
-
const sorted = [...routes].sort(([a], [b]) => {
|
|
8
|
-
if (a > b)
|
|
9
|
-
return 1;
|
|
10
|
-
else if (b > a)
|
|
11
|
-
return -1;
|
|
12
|
-
return 0;
|
|
13
|
-
});
|
|
14
|
-
sorted.forEach(([route, stats]) => {
|
|
15
|
-
lines.push(`<tr><td>${route}</td><td>${stats.hits}</td><td>${stats.success}</td><td>${stats.fails}</td></tr>`);
|
|
16
|
-
});
|
|
17
|
-
lines.push('</table></body></html>');
|
|
18
|
-
return lines.join('');
|
|
19
|
-
}
|
|
20
|
-
exports.generateReport = generateReport;
|