barehttp 0.4.2 → 0.5.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/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
- #runMiddlewaresSequence = (_) => _;
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.mainOptionsSetter();
41
+ this.applyLaunchOptions();
42
+ this.loadRoutesSchemas();
37
43
  return this;
38
44
  }
39
45
  #listener = (request, response) => {
40
- const { requestTimeFormat, logging } = this.bareOptions;
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
- // attach a flow to the flow memory storage
48
- this.applyMiddlewares(flow).catch((e) => this.#errorHandler(e, flow, 400));
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.#runMiddlewaresSequence = new AsyncFunction('flow', text);
80
+ this.#globalMiddlewaresRun = new AsyncFunction('flow', text);
67
81
  };
68
- mainOptionsSetter = () => {
82
+ applyLaunchOptions = () => {
69
83
  const { bareOptions: bo } = this;
70
- this.#port = +(bo.serverPort || process.env.PORT || 3000);
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
- // invoke body stream consumption
99
- await flow['readBody']();
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.#runMiddlewaresSequence(flow);
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, runtime, handler, opts) {
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.#routes.get(encode).hits++;
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 (runtime) {
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
- registerReport() {
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
- // attach a general statistic reports counter
175
- if (this.bareOptions.statisticsReport) {
176
- flow._originalRequest.on('close', () => {
177
- if (flow.statusToSend < 300 && flow.statusToSend >= 200) {
178
- this.#routes.get(encodedRoute).success++;
179
- }
180
- else {
181
- this.#routes.get(encodedRoute).fails++;
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.bind(undefined)(flow);
180
+ const routeReturn = handle(flow);
181
+ if (flow.sent)
182
+ return;
187
183
  if (routeReturn instanceof Promise) {
188
- routeReturn.then((result) => flow.send(result)).catch((e) => this.#errorHandler(e, flow));
189
- }
190
- else {
191
- flow.send(routeReturn);
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?.close((e) => {
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
@@ -49,7 +49,9 @@ class WebSocketServer {
49
49
  }
50
50
  }
51
51
  catch (e) {
52
- this.rejectUpgrade(request, socket, e?.message, e);
52
+ if (e instanceof Error) {
53
+ this.rejectUpgrade(request, socket, e?.message, e);
54
+ }
53
55
  }
54
56
  });
55
57
  this.customUpgradeDone = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "barehttp",
3
- "version": "0.4.2",
3
+ "version": "0.5.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 --runInBand --coverage",
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.4.1",
38
- "cookie-signature": "^1.1.0",
39
- "find-my-way": "^5.0.0",
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
- "pino": "^7.5.1",
42
- "pino-pretty": "^7.2.0",
43
- "ws": "^8.3.0"
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.5.2"
72
+ "typescript": "^4.6.4"
69
73
  },
70
74
  "optionalDependencies": {
71
75
  "bufferutil": "^4.0.3",
package/lib/report.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { RouteReport } from './server';
2
- export declare function generateReport(routes: Map<string, RouteReport>): string;
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;