@pirxpilot/router 1.0.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/layer.js ADDED
@@ -0,0 +1,225 @@
1
+ /*!
2
+ * router
3
+ * Copyright(c) 2013 Roman Shtylman
4
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
5
+ * MIT Licensed
6
+ */
7
+
8
+ 'use strict'
9
+
10
+ /**
11
+ * Module dependencies.
12
+ * @private
13
+ */
14
+
15
+ const pathRegexp = require('path-to-regexp')
16
+
17
+ /**
18
+ * Module variables.
19
+ * @private
20
+ */
21
+
22
+ const TRAILING_SLASH_REGEXP = /\/+$/
23
+ const MATCHING_GROUP_REGEXP = /\((?:\?<(.*?)>)?(?!\?)/g
24
+
25
+ /**
26
+ * Expose `Layer`.
27
+ */
28
+
29
+ module.exports = Layer
30
+
31
+ function Layer (path, options, fn) {
32
+ if (!(this instanceof Layer)) {
33
+ return new Layer(path, options, fn)
34
+ }
35
+
36
+ const opts = options || {}
37
+
38
+ this.handle = fn
39
+ this.keys = []
40
+ this.name = fn.name || '<anonymous>'
41
+ this.params = undefined
42
+ this.path = undefined
43
+ this.slash = path === '/' && opts.end === false
44
+ this.matchers = Array.isArray(path) ? path.map(p => matcher(p, opts)) : [matcher(path, opts)]
45
+ }
46
+
47
+ function matcher (path, { sensitive, end, strict }) {
48
+ return path instanceof RegExp
49
+ ? createRegexMatcher(path)
50
+ : pathRegexp.match((strict ? path : loosen(path)), {
51
+ sensitive,
52
+ end,
53
+ trailing: !strict,
54
+ decode: decodeParam
55
+ })
56
+ }
57
+
58
+ /**
59
+ * Handle the error for the layer.
60
+ *
61
+ * @param {Error} error
62
+ * @param {Request} req
63
+ * @param {Response} res
64
+ * @param {function} next
65
+ * @api private
66
+ */
67
+
68
+ Layer.prototype.handleError = function handleError (error, req, res, next) {
69
+ const fn = this.handle
70
+
71
+ if (fn.length !== 4) {
72
+ // not a standard error handler
73
+ return next(error)
74
+ }
75
+
76
+ try {
77
+ // invoke function
78
+ const ret = fn(error, req, res, next)
79
+
80
+ // wait for returned promise
81
+ if (ret instanceof Promise) {
82
+ ret.catch((error = new Error('Rejected promise')) => next(error))
83
+ }
84
+ } catch (err) {
85
+ next(err)
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Handle the request for the layer.
91
+ *
92
+ * @param {Request} req
93
+ * @param {Response} res
94
+ * @param {function} next
95
+ * @api private
96
+ */
97
+
98
+ Layer.prototype.handleRequest = function handleRequest (req, res, next) {
99
+ const fn = this.handle
100
+
101
+ if (fn.length > 3) {
102
+ // not a standard request handler
103
+ return next()
104
+ }
105
+
106
+ try {
107
+ // invoke function
108
+ const ret = fn(req, res, next)
109
+
110
+ // wait for returned promise
111
+ if (ret instanceof Promise) {
112
+ ret.catch((error = new Error('Rejected promise')) => next(error))
113
+ }
114
+ } catch (err) {
115
+ next(err)
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if this route matches `path`, if so
121
+ * populate `.params`.
122
+ *
123
+ * @param {String} path
124
+ * @return {Boolean}
125
+ * @api private
126
+ */
127
+
128
+ Layer.prototype.match = function match (path) {
129
+ if (path != null) {
130
+ // fast path non-ending match for / (any path matches)
131
+ if (this.slash) {
132
+ this.params = {}
133
+ this.path = ''
134
+ return true
135
+ }
136
+
137
+ for (const matcher of this.matchers) {
138
+ const matched = matcher(path)
139
+ if (matched) {
140
+ // store values
141
+ this.params = matched.params
142
+ this.path = matched.path
143
+ this.keys = Object.keys(matched.params)
144
+ return true
145
+ }
146
+ }
147
+ }
148
+
149
+ this.params = undefined
150
+ this.path = undefined
151
+ return false
152
+ }
153
+
154
+ function createRegexMatcher (path) {
155
+ const keys = []
156
+ let name = 0
157
+
158
+ for (const m of path.source.matchAll(MATCHING_GROUP_REGEXP)) {
159
+ keys.push({
160
+ name: m[1] || name++,
161
+ offset: m.index
162
+ })
163
+ }
164
+
165
+ return function regexpMatcher (p) {
166
+ const match = path.exec(p)
167
+ if (!match) {
168
+ return false
169
+ }
170
+
171
+ const params = {}
172
+ for (let i = 1; i < match.length; i++) {
173
+ const key = keys[i - 1]
174
+ const prop = key.name
175
+ const val = decodeParam(match[i])
176
+
177
+ if (val !== undefined) {
178
+ params[prop] = val
179
+ }
180
+ }
181
+
182
+ return {
183
+ params,
184
+ path: match[0]
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Decode param value.
191
+ *
192
+ * @param {string} val
193
+ * @return {string}
194
+ * @private
195
+ */
196
+
197
+ function decodeParam (val) {
198
+ if (typeof val !== 'string' || val.length === 0) {
199
+ return val
200
+ }
201
+
202
+ try {
203
+ return decodeURIComponent(val)
204
+ } catch (err) {
205
+ if (err instanceof URIError) {
206
+ err.message = 'Failed to decode param \'' + val + '\''
207
+ err.status = 400
208
+ }
209
+
210
+ throw err
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Loosens the given path for path-to-regexp matching.
216
+ */
217
+ function loosen (path) {
218
+ if (path instanceof RegExp || path === '/') {
219
+ return path
220
+ }
221
+
222
+ return Array.isArray(path)
223
+ ? path.map(function (p) { return loosen(p) })
224
+ : String(path).replace(TRAILING_SLASH_REGEXP, '')
225
+ }
package/lib/route.js ADDED
@@ -0,0 +1,235 @@
1
+ /*!
2
+ * router
3
+ * Copyright(c) 2013 Roman Shtylman
4
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
5
+ * MIT Licensed
6
+ */
7
+
8
+ 'use strict'
9
+
10
+ /**
11
+ * Module dependencies.
12
+ * @private
13
+ */
14
+
15
+ const Layer = require('./layer')
16
+ const methods = require('methods')
17
+
18
+ /**
19
+ * Module variables.
20
+ * @private
21
+ */
22
+
23
+ /**
24
+ * Expose `Route`.
25
+ */
26
+
27
+ module.exports = Route
28
+
29
+ /**
30
+ * Initialize `Route` with the given `path`,
31
+ *
32
+ * @param {String} path
33
+ * @api private
34
+ */
35
+
36
+ function Route (path) {
37
+ this.path = path
38
+ this.stack = []
39
+
40
+ // route handlers for various http methods
41
+ this.methods = Object.create(null)
42
+ }
43
+
44
+ /**
45
+ * @private
46
+ */
47
+
48
+ Route.prototype._handlesMethod = function _handlesMethod (method) {
49
+ if (this.methods._all) {
50
+ return true
51
+ }
52
+
53
+ // normalize name
54
+ let name = typeof method === 'string'
55
+ ? method.toLowerCase()
56
+ : method
57
+
58
+ if (name === 'head' && !this.methods.head) {
59
+ name = 'get'
60
+ }
61
+
62
+ return Boolean(this.methods[name])
63
+ }
64
+
65
+ /**
66
+ * @return {array} supported HTTP methods
67
+ * @private
68
+ */
69
+
70
+ Route.prototype._methods = function _methods () {
71
+ const methods = Object.keys(this.methods)
72
+
73
+ // append automatic head
74
+ if (this.methods.get && !this.methods.head) {
75
+ methods.push('head')
76
+ }
77
+
78
+ for (let i = 0; i < methods.length; i++) {
79
+ // make upper case
80
+ methods[i] = methods[i].toUpperCase()
81
+ }
82
+
83
+ return methods
84
+ }
85
+
86
+ /**
87
+ * dispatch req, res into this route
88
+ *
89
+ * @private
90
+ */
91
+
92
+ Route.prototype.dispatch = function dispatch (req, res, done) {
93
+ let idx = 0
94
+ const stack = this.stack
95
+ let sync = 0
96
+
97
+ if (stack.length === 0) {
98
+ return done()
99
+ }
100
+
101
+ let method = typeof req.method === 'string'
102
+ ? req.method.toLowerCase()
103
+ : req.method
104
+
105
+ if (method === 'head' && !this.methods.head) {
106
+ method = 'get'
107
+ }
108
+
109
+ req.route = this
110
+
111
+ next()
112
+
113
+ function next (err) {
114
+ // signal to exit route
115
+ if (err && err === 'route') {
116
+ return done()
117
+ }
118
+
119
+ // signal to exit router
120
+ if (err && err === 'router') {
121
+ return done(err)
122
+ }
123
+
124
+ // no more matching layers
125
+ if (idx >= stack.length) {
126
+ return done(err)
127
+ }
128
+
129
+ // max sync stack
130
+ if (++sync > 100) {
131
+ return setImmediate(next, err)
132
+ }
133
+
134
+ let layer
135
+ let match
136
+
137
+ // find next matching layer
138
+ while (idx < stack.length) {
139
+ layer = stack[idx++]
140
+ match = !layer.method || layer.method === method
141
+ if (match) {
142
+ break
143
+ }
144
+ }
145
+
146
+ // no match
147
+ if (match !== true) {
148
+ return done(err)
149
+ }
150
+
151
+ if (err) {
152
+ layer.handleError(err, req, res, next)
153
+ } else {
154
+ layer.handleRequest(req, res, next)
155
+ }
156
+
157
+ sync = 0
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Add a handler for all HTTP verbs to this route.
163
+ *
164
+ * Behaves just like middleware and can respond or call `next`
165
+ * to continue processing.
166
+ *
167
+ * You can use multiple `.all` call to add multiple handlers.
168
+ *
169
+ * function check_something(req, res, next){
170
+ * next()
171
+ * }
172
+ *
173
+ * function validate_user(req, res, next){
174
+ * next()
175
+ * }
176
+ *
177
+ * route
178
+ * .all(validate_user)
179
+ * .all(check_something)
180
+ * .get(function(req, res, next){
181
+ * res.send('hello world')
182
+ * })
183
+ *
184
+ * @param {array|function} handler
185
+ * @return {Route} for chaining
186
+ * @api public
187
+ */
188
+
189
+ Route.prototype.all = function all (...args) {
190
+ const callbacks = args.flat(Infinity)
191
+
192
+ if (callbacks.length === 0) {
193
+ throw new TypeError('argument handler is required')
194
+ }
195
+
196
+ for (const fn of callbacks) {
197
+ if (typeof fn !== 'function') {
198
+ throw new TypeError('argument handler must be a function')
199
+ }
200
+
201
+ const layer = Layer('/', {}, fn)
202
+ layer.method = undefined
203
+
204
+ this.methods._all = true
205
+ this.stack.push(layer)
206
+ }
207
+
208
+ return this
209
+ }
210
+
211
+ methods.forEach(function (method) {
212
+ Route.prototype[method] = function (...args) {
213
+ const callbacks = args.flat(Infinity)
214
+
215
+ if (callbacks.length === 0) {
216
+ throw new TypeError('argument handler is required')
217
+ }
218
+
219
+ for (let i = 0; i < callbacks.length; i++) {
220
+ const fn = callbacks[i]
221
+
222
+ if (typeof fn !== 'function') {
223
+ throw new TypeError('argument handler must be a function')
224
+ }
225
+
226
+ const layer = Layer('/', {}, fn)
227
+ layer.method = method
228
+
229
+ this.methods[method] = true
230
+ this.stack.push(layer)
231
+ }
232
+
233
+ return this
234
+ }
235
+ })
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@pirxpilot/router",
3
+ "description": "Simple middleware-style router",
4
+ "version": "1.0.0",
5
+ "author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
6
+ "contributors": [
7
+ "Blake Embrey <hello@blakeembrey.com>"
8
+ ],
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/pirxpilot/router.git"
13
+ },
14
+ "dependencies": {
15
+ "methods": "~1",
16
+ "parseurl": "~1",
17
+ "path-to-regexp": "~8"
18
+ },
19
+ "devDependencies": {
20
+ "finalhandler": "~1",
21
+ "run-series": "~1",
22
+ "standard": "~17",
23
+ "supertest": "~7"
24
+ },
25
+ "files": [
26
+ "lib/",
27
+ "LICENSE",
28
+ "HISTORY.md",
29
+ "README.md",
30
+ "SECURITY.md",
31
+ "index.js"
32
+ ],
33
+ "engines": {
34
+ "node": ">= 0.10"
35
+ },
36
+ "scripts": {
37
+ "test": "make check"
38
+ }
39
+ }