slower 2.1.7 → 2.1.9

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/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
- "dependencies": {
3
- "path-to-regexp": "^6.2.2"
4
- },
5
- "name": "slower",
6
- "version": "2.1.7",
7
- "main": "index.js",
8
- "devDependencies": {},
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "keywords": [],
13
- "author": "Tomás Luchesi <no.mad.devtech@gmail.com>",
14
- "license": "MIT",
15
- "description": "A package for simple HTTP server routing."
16
- }
2
+ "name": "slower",
3
+ "version": "2.1.9",
4
+ "main": "index.js",
5
+ "devDependencies": {},
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "dependencies": {
10
+ "path-to-regexp": "^6.2.2"
11
+ },
12
+ "keywords": [],
13
+ "author": "Tomás Luchesi <no.mad.devtech@gmail.com>",
14
+ "license": "MIT",
15
+ "description": "A package for simple HTTP server routing."
16
+ }
package/src/decorators.js CHANGED
@@ -4,14 +4,14 @@ const utils = require('./utils');
4
4
 
5
5
  /**
6
6
  * Receives and configures the HTTP Response object
7
- * @param {http.ServerResponse} response
8
- * @returns {http.ServerResponse}
7
+ * @param {http.ServerResponse} response
8
+ * @returns {http.ServerResponse}
9
9
  * @exposes .status()
10
10
  * @exposes .send()
11
11
  * @exposes .json()
12
12
  * @exposes .file()
13
13
  */
14
- function setupResponse (response) {
14
+ function setupResponse(response, request) {
15
15
  /**
16
16
  * Sets the response status code
17
17
  * @chainable
@@ -27,10 +27,10 @@ function setupResponse (response) {
27
27
  * Sets a response header to a specified value
28
28
  * @chainable
29
29
  * @overload
30
- * @param {string} header
31
- * @param {string} value
30
+ * @param {string} header
31
+ * @param {string} value
32
32
  * @returns {http.ServerResponse}
33
- * @info Pass in an object of "header":"value" entries
33
+ * @info Pass in an object of "header":"value" entries
34
34
  * to set multiple headers at once
35
35
  * @example
36
36
  * res.set('Content-Length', 1000)
@@ -45,22 +45,19 @@ function setupResponse (response) {
45
45
  * res.set({ 'Content-Length':1000, 'Content-Type':'text/plain' })
46
46
  */
47
47
  response.set = function (header, value) {
48
- if (typeof header === 'string')
49
- response.setHeader(header, value);
50
- else
51
- for (let prop of header)
52
- response.setHeader(prop, header[prop]);
48
+ if (typeof header === 'string') response.setHeader(header, value);
49
+ else for (let prop of header) response.setHeader(prop, header[prop]);
53
50
  return response;
54
- }
51
+ };
55
52
 
56
53
  /**
57
54
  * An alias for 'res.getHeader()'.
58
- * @param {string} header
55
+ * @param {string} header
59
56
  * @returns {string}
60
57
  */
61
- response.get = function(header) {
58
+ response.get = function (header) {
62
59
  return response.getHeader(header);
63
- }
60
+ };
64
61
 
65
62
  /**
66
63
  * Sets the Content-Type header with a specific MIME type.
@@ -70,32 +67,32 @@ function setupResponse (response) {
70
67
  * @info And if no type is specified, binary type is used (application/octet-stream)
71
68
  */
72
69
  response.type = function (mime) {
73
- let mimetype = MIME_TABLE[mime] || mime || MIME_TABLE[extension] || MIME_TABLE['default'];
70
+ let mimetype = MIME_TABLE[mime] || mime || MIME_TABLE['default'];
74
71
  response.setHeader('Content-type', mimetype);
75
72
  return response;
76
- }
73
+ };
77
74
 
78
75
  /**
79
76
  * Sends a string or buffer as JSON and ends the response
80
- * @param {string|Buffer} data
77
+ * @param {string|Buffer} data
81
78
  * @returns {undefined}
82
79
  */
83
- response.json = function json (data) {
80
+ response.json = function json(data) {
84
81
  if (response.statusCode === undefined) response.status(200);
85
82
  response.setHeader('Content-type', 'application/json');
86
83
  response.write(JSON.stringify(data));
87
84
  response.end();
88
85
  return undefined;
89
- }
86
+ };
90
87
 
91
88
  /**
92
89
  * Sends a string or buffer as HTML data and ends the response
93
- * @param {string|Buffer} data
90
+ * @param {string|Buffer} data
94
91
  * @returns {undefined}
95
92
  */
96
93
  response.send = function (data) {
97
94
  if (response.statusCode === undefined) response.status(200);
98
- if (!response.getHeader('Content-Type'))
95
+ if (!response.getHeader('Content-Type'))
99
96
  response.setHeader('Content-Type', 'text/html');
100
97
  response.write(data);
101
98
  response.end();
@@ -110,41 +107,46 @@ function setupResponse (response) {
110
107
  response.file = function (filename) {
111
108
  if (response.statusCode === undefined) response.status(200);
112
109
  if (!response.getHeader('Content-Type')) {
113
- let extension = (filename||'').split('.').slice(-1)[0];
114
- response.setHeader('Content-Type', MIME_TABLE[extension] || MIME_TABLE['default']);
110
+ let extension = (filename || '').split('.').slice(-1)[0];
111
+ response.setHeader(
112
+ 'Content-Type',
113
+ MIME_TABLE[extension] || MIME_TABLE['default']
114
+ );
115
115
  }
116
116
  const stream = fs.createReadStream(filename);
117
117
  stream.pipe(response);
118
118
  return undefined;
119
- }
119
+ };
120
120
 
121
121
  /**
122
- * Renders an HTML document and sends it to client
123
- * @param {string} filename The file to use as rendering View
124
- * @param {object} locals The properties to replace in the View
125
- * @info The locals object may contain either strings or functions or objects with 'toString' method.
126
- * @returns {undefined}
127
- * @example
128
- * // In 'home.html:
129
- * <h2>{{user}}</h2> has <h2>{{count}}</h2> items
130
- * // In server.js: (generates a new random item for each request)
131
- * res.render('./views/home.html', { user: 'Mike', count: () => randomInt() });
132
- */
122
+ * Renders an HTML document and sends it to client
123
+ * @param {string} filename The file to use as rendering View
124
+ * @param {object} locals The properties to replace in the View
125
+ * @info The locals object may contain either strings or functions or objects with 'toString' method.
126
+ * @returns {undefined}
127
+ * @example
128
+ * // In 'home.html:
129
+ * <h2>{{user}}</h2> has <h2>{{count}}</h2> items
130
+ * // In server.js: (generates a new random item for each request)
131
+ * res.render('./views/home.html', { user: 'Mike', count: () => randomInt() });
132
+ */
133
133
  response.render = function (filename, locals = {}) {
134
134
  let html = fs.readFileSync(filename, 'utf-8');
135
135
  for (let item in locals) {
136
136
  html = html.replace(
137
- new RegExp('{{'+item+'}}', 'gim'),
138
- typeof locals[item] === 'function' ?
139
- locals[item]() : locals[item]?.toString() || ''
137
+ new RegExp('{{' + item + '}}', 'gim'),
138
+ typeof locals[item] === 'function'
139
+ ? locals[item]()
140
+ : locals[item]?.toString() || ''
140
141
  );
141
142
  }
142
- response.setHeader('Content-type', 'text/html');
143
+ if (!response.getHeader('Content-Type'))
144
+ response.setHeader('Content-type', 'text/html');
143
145
  response.write(html);
144
146
  response.end();
145
147
  return undefined;
146
- }
147
-
148
+ };
149
+
148
150
  /**
149
151
  * Sets the 'Location' header and redirects to a given path, URL, or link.
150
152
  * @param {string} location An URL, link, path, or the string 'back' to set the target
@@ -160,50 +162,53 @@ function setupResponse (response) {
160
162
  * res.status(300).redirect('http://example.com');
161
163
  */
162
164
  response.redirect = function (location) {
163
- response.setHeader('Location', location === 'back' ? request.getHeader('Referrer') : location || '/');
165
+ response.setHeader(
166
+ 'Location',
167
+ location === 'back'
168
+ ? request.getHeader('Referrer')
169
+ : location || '/'
170
+ );
164
171
  response.end();
165
172
  return undefined;
166
- }
167
-
173
+ };
168
174
 
169
175
  return response;
170
176
  }
171
177
 
172
178
  /**
173
179
  * Receives and configures the HTTP Request object
174
- * @param {http.IncomingMessage} request
180
+ * @param {http.IncomingMessage} request
175
181
  * @returns {http.IncomingMessage}
176
182
  * @exposes req.body
177
183
  * @exposes req.urlParts
178
184
  * @exposes req.query
179
185
  * @exposes req.session = { port, rport, host, rhost }
180
186
  */
181
- function setupRequest (request) {
187
+ function setupRequest(request) {
182
188
  /**
183
189
  * @property
184
190
  * Holds the request body data as a buffer
185
191
  */
186
- return new Promise (resolve => {
192
+ return new Promise((resolve, reject) => {
187
193
  // Set classical socket locals
188
194
  request.session = {
189
195
  port: request.socket.localPort,
190
196
  rport: request.socket.remotePort,
191
197
  host: utils.normalizeAddress(request.socket.localAddress),
192
- rhost: utils.normalizeAddress(request.socket.remoteAddress)
198
+ rhost: utils.normalizeAddress(request.socket.remoteAddress),
193
199
  };
194
-
195
- // Set express-compatible locals
196
- request.ip = request.session.rhost;
197
200
 
198
-
201
+ // Set express-compatible locals
202
+ request.ip = request.session.rhost;
203
+
199
204
  /**
200
205
  * An alias for 'req.getHeader()'.
201
- * @param {string} header
206
+ * @param {string} header
202
207
  * @returns {string}
203
208
  */
204
- request.get = function(header) {
209
+ request.get = function (header) {
205
210
  return request.getHeader(header);
206
- }
211
+ };
207
212
 
208
213
  // Add req.params placeholder, it is added in the main router, not here
209
214
  request.params = undefined;
@@ -215,17 +220,17 @@ function setupRequest (request) {
215
220
  request.body = [];
216
221
  request.on('timeout', () => reject(new Error('Timeout')));
217
222
  request.on('data', chunk => request.body.push(chunk));
218
- request.on('error', (err) => reject(err));
223
+ request.on('error', err => reject(err));
219
224
  request.on('end', () => {
220
225
  let tmp = Buffer.concat(request.body);
221
226
  request.body = {
222
227
  buffer: tmp,
223
228
  text: () => tmp.toString(),
224
- json: () => JSON.parse(tmp)
225
- }
229
+ json: () => JSON.parse(tmp),
230
+ };
226
231
  return resolve(request);
227
232
  });
228
233
  });
229
234
  }
230
235
 
231
- module.exports = { setupRequest, setupResponse };
236
+ module.exports = { setupRequest, setupResponse };
package/src/slower.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const http = require('node:http');
3
2
  const https = require('node:https');
4
3
  const path = require('node:path');
@@ -20,16 +19,16 @@ class SlowerRouter {
20
19
 
21
20
  // You can create it with HTTPS options here
22
21
  /**
23
- * Use HTTPS server instead of HTTP.
22
+ * Use HTTPS server instead of HTTP.
24
23
  * Pass in all regular HTTPS options as parameters.
25
24
  * 'key' and 'cert' options are required for HTTPS.
26
- * @param {object} options
25
+ * @param {object} options
27
26
  * @returns {http.Server|https.Server}
28
27
  * @example
29
28
  * SlowerRouter({https:true, key:'...', cert:'...'}); // Create HTTPS server
30
29
  * SlowerRouter(); // Create regular HTTP
31
30
  */
32
- constructor (options = {}) {
31
+ constructor(options = {}) {
33
32
  this.METHODS = HTTP_VERBS;
34
33
  this.layers = new Map();
35
34
 
@@ -42,7 +41,7 @@ class SlowerRouter {
42
41
  * or
43
42
  * app.get({handler}) -> apply handlers for any route for a specific method
44
43
  * app.get({handler}, {handler}, ...) -> apply handlers for any route for a specific method
45
- *
44
+ *
46
45
  */
47
46
  for (let verb of HTTP_VERBS) {
48
47
  this.layers.set(verb, new Map());
@@ -55,22 +54,24 @@ class SlowerRouter {
55
54
  };
56
55
  }
57
56
 
58
- if (options.https)
59
- this.#server = https.createServer(options);
60
- else
61
- this.#server = http.createServer(options);
57
+ if (options.https) this.#server = https.createServer(options);
58
+ else this.#server = http.createServer(options);
62
59
 
63
60
  this.#server.on('request', this.#requestHandlerWrapper(this));
64
61
  }
65
-
66
- #requestHandlerWrapper () {
62
+
63
+ #requestHandlerWrapper() {
67
64
  // Save the 'this' scope
68
65
  // Inside the requestHandler function, 'this' corresponds to the http.Server instance
69
66
  const self = this;
70
67
 
71
- return (async function requestHandler (req, res) {
68
+ return async function requestHandler(req, res) {
72
69
  // Get all routes that match the URL and join with middlewares to cycle
73
- let foundRoutes = utils.getMatchingRoute(req.url, req.method, self.layers);
70
+ let foundRoutes = utils.getMatchingRoute(
71
+ req.url,
72
+ req.method,
73
+ self.layers
74
+ );
74
75
  let layers = foundRoutes;
75
76
  // Add an Error:404 special layer to the end of the layers list
76
77
  // This prevents accidental requests hanging
@@ -78,33 +79,46 @@ class SlowerRouter {
78
79
 
79
80
  // Set properties on request and response objects
80
81
  req = await setupRequest(req);
81
- res = await setupResponse(res);
82
+ res = await setupResponse(res, req);
82
83
 
83
84
  // Cycle throught all middlewares and proper routes and call with 'next()' as third argument
84
- ;(async function cycleMatching (routes) {
85
+ (async function cycleMatching(routes) {
85
86
  if (routes.length === 0) return;
86
87
  let route = routes[0];
87
88
  if (route.params) req.params = route.params;
88
89
  if (route.callback) route = route.callback;
89
90
  route(req, res, async () => cycleMatching(routes.slice(1)));
90
91
  })(layers);
91
- });
92
+ };
92
93
  }
93
94
 
94
- listen (...v) { return this.#server.listen(...v); }
95
- close (callback) { return this.#server.close(callback); }
95
+ listen(...v) {
96
+ return this.#server.listen(...v);
97
+ }
98
+ close(callback) {
99
+ return this.#server.close(callback);
100
+ }
96
101
 
97
102
  // Add any type of route
98
- #setRoute (method, path, handler) {
99
- if (typeof method !== 'string')
100
- throw new Error('<SlowerRouter>.route :: "method" parameter must be of type String');
101
- if (typeof path !== 'string' && typeof path !== 'function' && path?.constructor?.name !== 'RegExp')
102
- throw new Error('<SlowerRouter>.route :: "path" parameter must be of type Function, String or RegExp');
103
+ #setRoute(method, path, handler) {
104
+ if (typeof method !== 'string')
105
+ throw new Error(
106
+ '<SlowerRouter>.route :: "method" parameter must be of type String'
107
+ );
108
+ if (
109
+ typeof path !== 'string' &&
110
+ typeof path !== 'function' &&
111
+ path?.constructor?.name !== 'RegExp'
112
+ )
113
+ throw new Error(
114
+ '<SlowerRouter>.route :: "path" parameter must be of type Function, String or RegExp'
115
+ );
103
116
  if (typeof handler !== 'function')
104
- throw new Error('<SlowerRouter>.route :: "handler" parameter must be of type Function');
105
- if (!this.layers.get(method))
106
- this.layers.set(method, new Map());
107
- if (typeof path === 'string' || path?.constructor?.name !== 'RegExp')
117
+ throw new Error(
118
+ '<SlowerRouter>.route :: "handler" parameter must be of type Function'
119
+ );
120
+ if (!this.layers.get(method)) this.layers.set(method, new Map());
121
+ if (typeof path === 'string' || path?.constructor?.name !== 'RegExp')
108
122
  path = match(path, { decode: decodeURIComponent }); // 'path' is a function now
109
123
  this.layers.get(method).set(path, handler);
110
124
  return this;
@@ -114,7 +128,7 @@ class SlowerRouter {
114
128
  /**
115
129
  * Create a middleware for all HTTP methods, for a specific path
116
130
  * @overload
117
- * @param {String} path
131
+ * @param {String} path
118
132
  * @param {...Function} handlers
119
133
  * @returns {SlowerRouter}
120
134
  * @example Applies a middleware for all methods, for a specific path
@@ -123,7 +137,7 @@ class SlowerRouter {
123
137
  /**
124
138
  * Create a global middleware for a specific verb (all paths for that method)
125
139
  * @info This is the same as using "app[method]({callback})", without a path. Like: app.get(() => {});
126
- * @overload
140
+ * @overload
127
141
  * @param {string} verb (one of the HTTP verbs)
128
142
  * @param {...Function} handlers
129
143
  * @returns {SlowerRouter}
@@ -132,44 +146,44 @@ class SlowerRouter {
132
146
  */
133
147
  /**
134
148
  * Create a global middleware (all paths and all HTTP methods)
135
- * @overload
136
- * @param {...Function} handlers
149
+ * @overload
150
+ * @param {...Function} handlers
137
151
  * @returns {SlowerRouter}
138
152
  * @example Applies a middleware for all HTTP requests (of any method)
139
153
  * app.all((req, res, next) => {console.log(...); next()})
140
154
  */
141
- all (path, ...handlers) {
142
-
155
+ all(path, ...handlers) {
143
156
  // function signature: app.all({METHOD}, ...{HANDLERS})
144
157
  // example: app.all('post', () => {});
145
158
  if (typeof path === 'string' && HTTP_VERBS.includes(path.toLowerCase()))
146
159
  for (let handler of handlers)
147
160
  this.#setRoute(path, MATCH_ANY, handler);
148
-
149
-
150
161
  // function signature: app.all({PATH}, ...{HANDLERS})
151
162
  // example: app.all('/api', () => {});
152
163
  else if (typeof path === 'string')
153
164
  for (let handler of handlers)
154
- for (let verb of HTTP_VERBS)
165
+ for (let verb of HTTP_VERBS)
155
166
  this.#setRoute(verb, path, handler);
156
-
157
167
  // function signature: app.all(...{HANDLERS})
158
- // example: app.all(() => {});
168
+ // example: app.all(() => {});
159
169
  else if (typeof path === 'function')
160
170
  for (let verb of HTTP_VERBS) {
161
171
  this.#setRoute(verb, MATCH_ANY, path);
162
172
  for (let handler of handlers)
163
173
  this.#setRoute(verb, MATCH_ANY, handler);
164
174
  }
165
-
166
-
167
- else throw new Error('<SlowerRouter>.use :: "handler" parameter must be of type Function');
175
+ else
176
+ throw new Error(
177
+ '<SlowerRouter>.use :: "handler" parameter must be of type Function'
178
+ );
168
179
 
169
180
  return this;
170
181
  }
171
182
  // Just a more comprehensive call to app.all for defining middlewares
172
- use (...b) { this.all(...b); return this; };
183
+ use(...b) {
184
+ this.all(...b);
185
+ return this;
186
+ }
173
187
 
174
188
  /**
175
189
  * Serve static files from a directory
@@ -179,33 +193,46 @@ class SlowerRouter {
179
193
  * @example
180
194
  * // Using a mounting poing:
181
195
  * app.static('./public', '/files') // Access with 'GET /files/{filename}'
182
- *
196
+ *
183
197
  * // Not using a mounting point:
184
198
  * app.static('./public') // Access with 'GET /public/{filename}'
185
- *
199
+ *
186
200
  * // Using root ('/') as mounting point:
187
201
  * app.static('./public', '/') // Access with 'GET /{filename}'
188
202
  */
189
- static (directoryPath, mountPath = '') {
203
+ static(directoryPath, mountPath = '') {
190
204
  const absoluteDir = path.resolve(directoryPath);
191
205
  if (!fs.existsSync(absoluteDir))
192
- throw new Error(`Invalid directory provided for [SlowerRouter].static(): [${directoryPath}]`);
206
+ throw new Error(
207
+ `Invalid directory provided for [SlowerRouter].static(): [${directoryPath}]`
208
+ );
193
209
 
194
210
  for (const file of utils.getFiles(absoluteDir)) {
195
211
  // Get only the file name from the absolute file path 'c:\u\a.txt' -> 'a.txt'
196
- let fileAsURLPath = file.replaceAll(/\\/g, '/').split('/').slice(-1)[0];
212
+ let fileAsURLPath = file
213
+ .replaceAll(/\\/g, '/')
214
+ .split('/')
215
+ .slice(-1)[0];
197
216
  // If there is a mount path, merge it with the file and remove duplicate bars '//'
198
- if (!mountPath) mountPath = directoryPath.replaceAll(/\\/g, '/').split('/').slice(-1)[0];
199
- fileAsURLPath = ('/' + mountPath + '/' + fileAsURLPath).replaceAll(/\/+/g,'/');
200
-
201
-
202
- async function staticfhandle (req, res, next) {
217
+ if (!mountPath)
218
+ mountPath = directoryPath
219
+ .replaceAll(/\\/g, '/')
220
+ .split('/')
221
+ .slice(-1)[0];
222
+ fileAsURLPath = ('/' + mountPath + '/' + fileAsURLPath).replaceAll(
223
+ /\/+/g,
224
+ '/'
225
+ );
226
+
227
+ async function staticfhandle(req, res /*next*/) {
203
228
  try {
204
229
  const fileStream = createReadStream(file);
205
- res.setHeader('Content-Type', MIME_TABLE[extension] || MIME_TABLE['default']);
230
+ res.setHeader(
231
+ 'Content-Type',
232
+ MIME_TABLE[extension] || MIME_TABLE['default']
233
+ );
206
234
  return await pipeline(fileStream, res);
207
-
208
- } catch (err) {
235
+ } catch {
209
236
  // In case the file does not exist, return a 404 and prevent it from breaking
210
237
  res.status(404).end();
211
238
  }
@@ -216,7 +243,7 @@ class SlowerRouter {
216
243
  // If the file is an html file, also add a path for using it without the '.html' extension
217
244
  // both 'GET /files/somefile.html' and 'GET /files/somefile' would work
218
245
  const extension = utils.getFileExtension(file);
219
- if (extension === 'html')
246
+ if (extension === 'html')
220
247
  this.get(fileAsURLPath.replace('.html', ''), staticfhandle);
221
248
  }
222
249
  return this;
@@ -225,13 +252,14 @@ class SlowerRouter {
225
252
  /**
226
253
  * Import and use another SlowerRouter, mounted at a specific path
227
254
  * @param {string} mountPath Mount the router at a specific path
228
- * @param {SlowerRouter} SlowerRouterInstance
255
+ * @param {SlowerRouter} SlowerRouterInstance
229
256
  */
230
- useRouter (SlowerSubRouterInstance, mountPath = '/') {
231
- const mount = p => mountPath ? (mountPath + '/' + p).replace(/\/{2,}/g, '/') : p;
257
+ useRouter(SlowerSubRouterInstance, mountPath = '/') {
258
+ const mount = p =>
259
+ mountPath ? (mountPath + '/' + p).replace(/\/{2,}/g, '/') : p;
232
260
  const layers = SlowerSubRouterInstance.layers;
233
261
  for (let layer of layers) {
234
- if (!!layer.static) {
262
+ if (layer.static) {
235
263
  this.static(layer.directoryPath, mount(layer.mountPath));
236
264
  continue;
237
265
  }
@@ -243,38 +271,36 @@ class SlowerRouter {
243
271
  /**
244
272
  * Creates a temporary synced mini-router for a specific path
245
273
  * Allows to declare multiple handlers for methods of a specific path
246
- * @param {string} path
274
+ * @param {string} path
247
275
  * @returns {SlowerMicroRouter}
248
- * @example
276
+ * @example
249
277
  * app.route('/books')
250
278
  * .get((req, res) => console.log('you retrieved a book with GET'))
251
279
  * .post((req, res) => console.log('you added a book with POST'))
252
280
  * ;
253
281
  */
254
- route (path) {
282
+ route(path) {
255
283
  return new SlowerMicroRouter(path, this.layers);
256
284
  }
257
285
  }
258
286
 
259
-
260
287
  /**
261
288
  * Used as the class for modular routers
262
289
  * used in "slower.Router()"
263
290
  */
264
291
  class SlowerSubRouter {
265
- constructor () {
292
+ constructor() {
266
293
  this.METHODS = HTTP_VERBS;
267
294
  this.layers = new Array();
268
295
 
269
296
  // OLD version: (Do not delete until new version is properly tested)
270
- // Create basic route shortcuts
271
- // get(), post(), put(), delete()
272
- // for (let verb of HTTP_VERBS) {
273
- // this[verb] = function (path, callback) {
274
- // return this.#setRoute(verb, path, callback);
275
- // };
276
- // }
277
-
297
+ // Create basic route shortcuts
298
+ // get(), post(), put(), delete()
299
+ // for (let verb of HTTP_VERBS) {
300
+ // this[verb] = function (path, callback) {
301
+ // return this.#setRoute(verb, path, callback);
302
+ // };
303
+ // }
278
304
 
279
305
  // Create basic route shortcuts
280
306
  // get(), post(), put(), delete(), ...
@@ -285,7 +311,7 @@ class SlowerSubRouter {
285
311
  * or
286
312
  * app.get({handler}) -> apply handlers for any route for a specific method
287
313
  * app.get({handler}, {handler}, ...) -> apply handlers for any route for a specific method
288
- *
314
+ *
289
315
  */
290
316
  for (let verb of HTTP_VERBS) {
291
317
  this[verb] = function (path, ...callbacks) {
@@ -298,13 +324,19 @@ class SlowerSubRouter {
298
324
  }
299
325
  }
300
326
 
301
- #setRoute (method, path, handler) {
302
- if (typeof method !== 'string')
303
- throw new Error('<SlowerSubRouter>.route :: "method" parameter must be of type String');
327
+ #setRoute(method, path, handler) {
328
+ if (typeof method !== 'string')
329
+ throw new Error(
330
+ '<SlowerSubRouter>.route :: "method" parameter must be of type String'
331
+ );
304
332
  if (typeof path !== 'string' && path?.constructor?.name !== 'RegExp')
305
- throw new Error('<SlowerSubRouter>.route :: "path" parameter must be of type Function, String or RegExp');
333
+ throw new Error(
334
+ '<SlowerSubRouter>.route :: "path" parameter must be of type Function, String or RegExp'
335
+ );
306
336
  if (typeof handler !== 'function')
307
- throw new Error('<SlowerSubRouter>.route :: "handler" parameter must be of type Function');
337
+ throw new Error(
338
+ '<SlowerSubRouter>.route :: "handler" parameter must be of type Function'
339
+ );
308
340
  this.layers.push({ method, path, handler });
309
341
  return this;
310
342
  }
@@ -313,8 +345,8 @@ class SlowerSubRouter {
313
345
  /**
314
346
  * Create a middleware for all HTTP methods, for a specific path
315
347
  * @overload
316
- * @param {String} path
317
- * @param {...Function} handlers
348
+ * @param {String} path
349
+ * @param {...Function} handlers
318
350
  * @returns {SlowerRouter}
319
351
  * @example Applies a middleware for all methods, for a specific path
320
352
  * app.all('/img/:id', (req, res, next) => {console.log(...); next()})
@@ -322,7 +354,7 @@ class SlowerSubRouter {
322
354
  /**
323
355
  * Create a global middleware for a specific verb (all paths for that method)
324
356
  * @info This is the same as using "app[method]({callback})", without a path. Like: app.get(() => {});
325
- * @overload
357
+ * @overload
326
358
  * @param {string} verb (one of the HTTP verbs)
327
359
  * @param {...Function} handlers
328
360
  * @returns {SlowerRouter}
@@ -331,46 +363,46 @@ class SlowerSubRouter {
331
363
  */
332
364
  /**
333
365
  * Create a global middleware (all paths and all HTTP methods)
334
- * @overload
366
+ * @overload
335
367
  * @param {...Function} handlers
336
368
  * @returns {SlowerRouter}
337
369
  * @example Applies a middleware for all HTTP requests (of any method)
338
370
  * app.all((req, res, next) => {console.log(...); next()})
339
371
  */
340
- all (path, ...handlers) {
341
-
372
+ all(path, ...handlers) {
342
373
  // function signature: app.all({METHOD}, ...{HANDLERS})
343
374
  // example: app.all('post', () => {});
344
375
  if (typeof path === 'string' && HTTP_VERBS.includes(path.toLowerCase()))
345
376
  for (let handler of handlers)
346
377
  this.#setRoute(path, MATCH_ANY, handler);
347
-
348
-
349
378
  // function signature: app.all({PATH}, ...{HANDLERS})
350
379
  // example: app.all('/api', () => {});
351
380
  else if (typeof path === 'string')
352
381
  for (let handler of handlers)
353
- for (let verb of HTTP_VERBS)
382
+ for (let verb of HTTP_VERBS)
354
383
  this.#setRoute(verb, path, handler);
355
-
356
384
  // function signature: app.all(...{HANDLERS})
357
- // example: app.all(() => {});
385
+ // example: app.all(() => {});
358
386
  else if (typeof path === 'function')
359
387
  for (let verb of HTTP_VERBS) {
360
388
  this.#setRoute(verb, MATCH_ANY, path);
361
389
  for (let handler of handlers)
362
390
  this.#setRoute(verb, MATCH_ANY, handler);
363
391
  }
364
-
365
-
366
- else throw new Error('<SlowerSubRouter>.use :: "handler" parameter must be of type Function');
392
+ else
393
+ throw new Error(
394
+ '<SlowerSubRouter>.use :: "handler" parameter must be of type Function'
395
+ );
367
396
 
368
397
  return this;
369
398
  }
370
399
  // Just a more comprehensive call to app.all for defining middlewares
371
- use (...b) { this.all(...b); return this; };
400
+ use(...b) {
401
+ this.all(...b);
402
+ return this;
403
+ }
372
404
 
373
- static (directoryPath, mountPath = '') {
405
+ static(directoryPath, mountPath = '') {
374
406
  this.layers.push({ static: true, directoryPath, mountPath });
375
407
  return this;
376
408
  }
@@ -378,31 +410,29 @@ class SlowerSubRouter {
378
410
  /**
379
411
  * Creates a temporary synced mini-router for a specific path
380
412
  * Allows to declare multiple handlers for methods of a specific path
381
- * @param {string} path
413
+ * @param {string} path
382
414
  * @returns {SlowerMicroRouter}
383
- * @example
415
+ * @example
384
416
  * app.route('/books')
385
417
  * .get((req, res) => console.log('you retrieved a book with GET'))
386
418
  * .post((req, res) => console.log('you added a book with POST'))
387
419
  * ;
388
420
  */
389
- route (path) {
421
+ route(path) {
390
422
  return new SlowerMicroRouter(path, this.layers);
391
423
  }
392
424
  }
393
425
 
394
-
395
-
396
426
  /**
397
427
  * Used as the class for micro-routing with "app.route"
398
- *
428
+ *
399
429
  * only method acessors (.get(), .post(), .put(), ...)
400
430
  * and .all() and .use()
401
- *
431
+ *
402
432
  * .static() is not allowed
403
433
  */
404
434
  class SlowerMicroRouter {
405
- constructor (path, layerPool) {
435
+ constructor(path, layerPool) {
406
436
  this.path = path;
407
437
  this.layers = layerPool;
408
438
 
@@ -410,21 +440,26 @@ class SlowerMicroRouter {
410
440
  // get(), post(), put(), delete()
411
441
  for (let verb of HTTP_VERBS) {
412
442
  this[verb] = function (...callbacks) {
413
- for (let callback of callbacks)
414
- this.#setRoute(verb, callback);
415
- return this;
443
+ for (let callback of callbacks) this.#setRoute(verb, callback);
444
+ return this;
416
445
  };
417
446
  }
418
447
  }
419
448
 
420
- #setRoute (method, handler) {
449
+ #setRoute(method, handler) {
421
450
  const path = this.path;
422
- if (typeof method !== 'string')
423
- throw new Error('<SlowerMicroRouter>.route :: "method" parameter must be of type String');
451
+ if (typeof method !== 'string')
452
+ throw new Error(
453
+ '<SlowerMicroRouter>.route :: "method" parameter must be of type String'
454
+ );
424
455
  if (typeof path !== 'string' && path?.constructor?.name !== 'RegExp')
425
- throw new Error('<SlowerMicroRouter>.route :: "path" parameter must be of type Function, String or RegExp');
456
+ throw new Error(
457
+ '<SlowerMicroRouter>.route :: "path" parameter must be of type Function, String or RegExp'
458
+ );
426
459
  if (typeof handler !== 'function')
427
- throw new Error('<SlowerMicroRouter>.route :: "handler" parameter must be of type Function');
460
+ throw new Error(
461
+ '<SlowerMicroRouter>.route :: "handler" parameter must be of type Function'
462
+ );
428
463
  this.layers.push({ method, path, handler });
429
464
  return this;
430
465
  }
@@ -433,8 +468,8 @@ class SlowerMicroRouter {
433
468
  /**
434
469
  * Create a middleware for all HTTP methods, for a specific path
435
470
  * @overload
436
- * @param {String} path
437
- * @param {...Function} handlers
471
+ * @param {String} path
472
+ * @param {...Function} handlers
438
473
  * @returns {SlowerRouter}
439
474
  * @example Applies a middleware for all methods, for a specific path
440
475
  * app.all('/img/:id', (req, res, next) => {console.log(...); next()})
@@ -442,63 +477,63 @@ class SlowerMicroRouter {
442
477
  /**
443
478
  * Create a global middleware for a specific verb (all paths for that method)
444
479
  * @info This is the same as using "app[method]({callback})", without a path. Like: app.get(() => {});
445
- * @overload
480
+ * @overload
446
481
  * @param {string} verb (one of the HTTP verbs)
447
- * @param {...Function} handlers
482
+ * @param {...Function} handlers
448
483
  * @returns {SlowerRouter}
449
484
  * @example Applies a middleware for all GET requests
450
485
  * app.all('get', (req, res, next) => {console.log(...); next()})
451
486
  */
452
487
  /**
453
488
  * Create a global middleware (all paths and all HTTP methods)
454
- * @overload
455
- * @param {...Function} handlers
489
+ * @overload
490
+ * @param {...Function} handlers
456
491
  * @returns {SlowerRouter}
457
492
  * @example Applies a middleware for all HTTP requests (of any method)
458
493
  * app.all((req, res, next) => {console.log(...); next()})
459
494
  */
460
- all (path, ...handlers) {
461
-
495
+ all(path, ...handlers) {
462
496
  // function signature: app.all({METHOD}, ...{HANDLERS})
463
497
  // example: app.all('post', () => {});
464
498
  if (typeof path === 'string' && HTTP_VERBS.includes(path.toLowerCase()))
465
499
  for (let handler of handlers)
466
500
  this.#setRoute(path, MATCH_ANY, handler);
467
-
468
-
469
501
  // function signature: app.all({PATH}, ...{HANDLERS})
470
502
  // example: app.all('/api', () => {});
471
503
  else if (typeof path === 'string')
472
504
  for (let handler of handlers)
473
- for (let verb of HTTP_VERBS)
505
+ for (let verb of HTTP_VERBS)
474
506
  this.#setRoute(verb, path, handler);
475
-
476
507
  // function signature: app.all(...{HANDLERS})
477
- // example: app.all(() => {});
508
+ // example: app.all(() => {});
478
509
  else if (typeof path === 'function')
479
510
  for (let verb of HTTP_VERBS) {
480
511
  this.#setRoute(verb, MATCH_ANY, path);
481
512
  for (let handler of handlers)
482
513
  this.#setRoute(verb, MATCH_ANY, handler);
483
514
  }
515
+ else
516
+ throw new Error(
517
+ '<SlowerMicroRouter>.use :: "handler" parameter must be of type Function'
518
+ );
484
519
 
485
-
486
- else throw new Error('<SlowerMicroRouter>.use :: "handler" parameter must be of type Function');
487
-
488
520
  return this;
489
521
  }
490
522
  // Just a more comprehensive call to app.all for defining middlewares
491
- use (...b) { this.all(...b); return this; };
523
+ use(...b) {
524
+ this.all(...b);
525
+ return this;
526
+ }
492
527
  }
493
528
 
494
-
495
529
  // Create basic main routing creation
496
- function slower (options) {
530
+ function slower(options) {
497
531
  return new SlowerRouter(options);
498
532
  }
533
+
499
534
  // Add the subrouter class
500
535
  slower.Router = function () {
501
536
  return new SlowerSubRouter();
502
- }
537
+ };
503
538
 
504
539
  module.exports = slower;
package/src/utils.js CHANGED
@@ -1,17 +1,15 @@
1
-
2
- const { statSync, readdirSync } = require('node:fs')
1
+ const { statSync, readdirSync } = require('node:fs');
3
2
  const { join } = require('node:path');
4
3
  const { parse } = require('node:querystring');
5
4
 
6
5
  function* getFiles(folder) {
7
6
  const files = readdirSync(folder);
8
7
  for (const file of files) {
9
- const absolutePath = join(folder, file)
8
+ const absolutePath = join(folder, file);
10
9
  if (statSync(absolutePath).isDirectory()) {
11
- yield* getFiles(absolutePath)
12
- }
13
- else {
14
- yield absolutePath.replaceAll('..\\','')
10
+ yield* getFiles(absolutePath);
11
+ } else {
12
+ yield absolutePath.replaceAll('..\\', '');
15
13
  }
16
14
  }
17
15
  }
@@ -22,44 +20,48 @@ const getMatchingRoute = (url, method, layers) => {
22
20
  // Get only the layers from the proper HTTP verb
23
21
  let routes = layers.get(method) || new Map();
24
22
  // Iterate through all routes, and get the one that match
25
- for (let [ pathFn, callback ] of routes) {
23
+ for (let [pathFn, callback] of routes) {
26
24
  let params = pathFn(getURLPathBody(url));
27
25
  // Return the matching route
28
- if (!!params) {
26
+ if (params) {
29
27
  // add to list
30
- let built = ({ callback });
28
+ let built = { callback };
31
29
  if (params.params && params.params['0'] === undefined)
32
30
  built['params'] = params.params;
33
31
  list.push(built);
34
32
  }
35
33
  }
36
34
  return list;
37
- }
35
+ };
38
36
 
39
- const normalizeAddress = addr => addr.startsWith('::') ? addr : addr.substring(addr.indexOf(':',2)+1);
37
+ const normalizeAddress = addr =>
38
+ addr.startsWith('::') ? addr : addr.substring(addr.indexOf(':', 2) + 1);
40
39
 
41
- const getFileExtension = (fname) => fname.split('.').filter(Boolean).slice(-1)[0];
40
+ const getFileExtension = fname => fname.split('.').filter(Boolean).slice(-1)[0];
42
41
 
43
42
  // /page?foo=bar&abc=123 -> /page
44
43
  const getURLPathBody = (urlPath = '') => urlPath.split('?')[0] || '';
45
44
 
46
45
  // /page?foo=bar&abc=123 -> { foo: 'bar', abc: '123' }
47
- const getURLQueryString = (urlPath = '') => parse((urlPath.split('?')[1] || '').split('#')[0]);
48
-
49
- const noLayersFoundFallback = (req, res) =>
50
- res.status(404).send(
51
- `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">` +
52
- `<title>Error</title></head><body>` +
53
- `<pre>Cannot ${req.method.toUpperCase()} ${req.url}</pre></body></html>`
54
- )
55
- ;
46
+ const getURLQueryString = (urlPath = '') =>
47
+ parse((urlPath.split('?')[1] || '').split('#')[0]);
56
48
 
57
- module.exports = {
58
- getMatchingRoute,
59
- getFiles,
60
- normalizeAddress,
61
- getFileExtension,
62
- getURLPathBody,
49
+ const noLayersFoundFallback = (req, res) =>
50
+ res
51
+ .status(404)
52
+ .send(
53
+ `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">` +
54
+ `<title>Error</title></head><body>` +
55
+ `<pre>Cannot ${req.method.toUpperCase()} ${
56
+ req.url
57
+ }</pre></body></html>`
58
+ );
59
+ module.exports = {
60
+ getMatchingRoute,
61
+ getFiles,
62
+ normalizeAddress,
63
+ getFileExtension,
64
+ getURLPathBody,
63
65
  getURLQueryString,
64
- noLayersFoundFallback
65
- };
66
+ noLayersFoundFallback,
67
+ };