hono 0.0.4 → 0.0.5
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 +15 -3
- package/package.json +3 -3
- package/src/hono.js +65 -49
- package/src/middleware/{filter.js → defaultFilter.js} +5 -5
- package/src/node.js +63 -103
- package/src/node.test.js +17 -22
- package/src/router.test.js +25 -21
- package/src/util.js +26 -0
- package/src/util.test.js +29 -0
- package/example/basic/index.js +0 -28
- package/example/basic/package-lock.json +0 -13
- package/example/basic/package.json +0 -11
- package/example/basic/wrangler.toml +0 -8
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Hono [炎] - Tiny web framework for Cloudflare Workers and others.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
|
+
const Hono = require('Hono')
|
|
6
7
|
const app = Hono()
|
|
7
8
|
|
|
8
9
|
app.get('/', () => new Response('Hono!!'))
|
|
@@ -16,7 +17,17 @@ app.fire()
|
|
|
16
17
|
- Tiny - use only standard API.
|
|
17
18
|
- Portable - zero dependencies.
|
|
18
19
|
- Flexible - you can make your own middlewares.
|
|
19
|
-
- Optimized - for Cloudflare Workers.
|
|
20
|
+
- Optimized - for Cloudflare Workers and Fastly Compute@Edge.
|
|
21
|
+
|
|
22
|
+
## Benchmark
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
hono x 813,001 ops/sec ±2.96% (75 runs sampled)
|
|
26
|
+
itty-router x 160,415 ops/sec ±3.31% (85 runs sampled)
|
|
27
|
+
sundar x 307,438 ops/sec ±4.77% (73 runs sampled)
|
|
28
|
+
Fastest is hono
|
|
29
|
+
✨ Done in 37.03s.
|
|
30
|
+
```
|
|
20
31
|
|
|
21
32
|
## Install
|
|
22
33
|
|
|
@@ -50,7 +61,7 @@ app.post('/', () => new Response('POST /'))
|
|
|
50
61
|
|
|
51
62
|
// Wildcard
|
|
52
63
|
app.get('/wild/*/card', () => {
|
|
53
|
-
return new
|
|
64
|
+
return new Response('GET /wild/*/card')
|
|
54
65
|
})
|
|
55
66
|
```
|
|
56
67
|
|
|
@@ -58,7 +69,7 @@ app.get('/wild/*/card', () => {
|
|
|
58
69
|
|
|
59
70
|
```js
|
|
60
71
|
// Any HTTP methods
|
|
61
|
-
app.all('/hello', () => 'ALL Method /hello')
|
|
72
|
+
app.all('/hello', () => new Response('ALL Method /hello'))
|
|
62
73
|
```
|
|
63
74
|
|
|
64
75
|
### Named Parameter
|
|
@@ -152,6 +163,7 @@ app.get('/entry/:id', (c) => {
|
|
|
152
163
|
|
|
153
164
|
- koa <https://github.com/koajs/koa>
|
|
154
165
|
- express <https://github.com/expressjs/express>
|
|
166
|
+
- oak <https://github.com/oakserver/oak>
|
|
155
167
|
- itty-router <https://github.com/kwhitley/itty-router>
|
|
156
168
|
- Sunder <https://github.com/SunderJS/sunder>
|
|
157
169
|
- goblin <https://github.com/bmf-san/goblin>
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Minimal web framework for Cloudflare Workers",
|
|
5
5
|
"main": "src/hono.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "jest
|
|
7
|
+
"test": "jest"
|
|
8
8
|
},
|
|
9
9
|
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
|
10
10
|
"license": "MIT",
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
"jest": "^27.4.5",
|
|
18
18
|
"node-fetch": "^2.6.6"
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
}
|
package/src/hono.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Node = require('./node')
|
|
4
4
|
const compose = require('./compose')
|
|
5
|
-
const
|
|
5
|
+
const defaultFilter = require('./middleware/defaultFilter')
|
|
6
6
|
|
|
7
7
|
class Router {
|
|
8
8
|
constructor() {
|
|
@@ -15,91 +15,107 @@ class Router {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
match(method, path) {
|
|
18
|
-
method = method.toLowerCase()
|
|
19
18
|
return this.node.search(method, path)
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (target.constructor.prototype.hasOwnProperty(prop)) {
|
|
28
|
-
return target[prop](...args)
|
|
29
|
-
} else {
|
|
30
|
-
if (args.length == 1) {
|
|
31
|
-
return target.addRoute(prop, target.router.tempPath, ...args)
|
|
32
|
-
}
|
|
33
|
-
return target.addRoute(prop, ...args)
|
|
34
|
-
}
|
|
35
|
-
},
|
|
22
|
+
const getPathFromURL = (url) => {
|
|
23
|
+
// XXX
|
|
24
|
+
const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/)
|
|
25
|
+
return match[5]
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
class
|
|
28
|
+
class Hono {
|
|
39
29
|
constructor() {
|
|
40
30
|
this.router = new Router()
|
|
41
31
|
this.middlewareRouter = new Router()
|
|
42
|
-
this.
|
|
32
|
+
this.middlewareRouters = []
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
|
|
35
|
+
get(...args) {
|
|
36
|
+
return this.addRoute('GET', ...args)
|
|
37
|
+
}
|
|
38
|
+
post(...args) {
|
|
39
|
+
return this.addRoute('POST', ...args)
|
|
40
|
+
}
|
|
41
|
+
put(...args) {
|
|
42
|
+
return this.addRoute('PUT', ...args)
|
|
43
|
+
}
|
|
44
|
+
delete(...args) {
|
|
45
|
+
return this.addRoute('DELETE', ...args)
|
|
46
|
+
}
|
|
47
|
+
patch(...args) {
|
|
48
|
+
return this.addRoute('PATCH', ...args)
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
addRoute(method, ...args) {
|
|
52
|
+
method = method.toUpperCase()
|
|
53
|
+
if (args.length === 1) {
|
|
54
|
+
this.router.add(method, this.router.tempPath, ...args)
|
|
55
|
+
} else {
|
|
56
|
+
this.router.add(method, ...args)
|
|
57
|
+
}
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getRouter() {
|
|
62
|
+
return this.router
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
route(path) {
|
|
55
66
|
this.router.tempPath = path
|
|
56
|
-
return
|
|
67
|
+
return this
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
use(path, middleware) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
const router = new Router()
|
|
72
|
+
router.add('all', path, middleware)
|
|
73
|
+
this.middlewareRouters.push(router)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async matchRoute(method, path) {
|
|
77
|
+
const res = this.router.match(method, path)
|
|
78
|
+
return res
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
// XXX
|
|
69
|
-
createContext(req, res) {
|
|
70
|
-
return {
|
|
82
|
+
async createContext(req, res) {
|
|
83
|
+
return {
|
|
84
|
+
req: req,
|
|
85
|
+
res: res,
|
|
86
|
+
newResponse: (params) => {
|
|
87
|
+
return new Response(params)
|
|
88
|
+
},
|
|
89
|
+
}
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
async dispatch(request, response) {
|
|
74
|
-
const
|
|
75
|
-
const [method, path] = [request.method, url.pathname]
|
|
93
|
+
const [method, path] = [request.method, getPathFromURL(request.url)]
|
|
76
94
|
|
|
77
|
-
const result = this.matchRoute(method, path)
|
|
95
|
+
const result = await this.matchRoute(method, path)
|
|
78
96
|
if (!result) return this.notFound()
|
|
79
97
|
|
|
80
98
|
request.params = (key) => result.params[key]
|
|
81
99
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
middleware.push(...mwResult.handler)
|
|
86
|
-
}
|
|
100
|
+
let handler = result.handler[0] // XXX
|
|
101
|
+
|
|
102
|
+
const middleware = [defaultFilter] // add defaultFilter later
|
|
87
103
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
handler
|
|
104
|
+
for (const mr of this.middlewareRouters) {
|
|
105
|
+
const mwResult = mr.match('all', path)
|
|
106
|
+
if (mwResult) {
|
|
107
|
+
middleware.push(...mwResult.handler)
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
|
|
95
|
-
let wrappedHandler = (context, next) => {
|
|
111
|
+
let wrappedHandler = async (context, next) => {
|
|
96
112
|
context.res = handler(context)
|
|
97
113
|
next()
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
middleware.push(wrappedHandler)
|
|
101
117
|
const composed = compose(middleware)
|
|
102
|
-
const c = this.createContext(request, response)
|
|
118
|
+
const c = await this.createContext(request, response)
|
|
103
119
|
|
|
104
120
|
composed(c)
|
|
105
121
|
|
|
@@ -107,7 +123,7 @@ class App {
|
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
async handleEvent(event) {
|
|
110
|
-
return
|
|
126
|
+
return this.dispatch(event.request, {}) // XXX
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
fire() {
|
|
@@ -121,8 +137,8 @@ class App {
|
|
|
121
137
|
}
|
|
122
138
|
}
|
|
123
139
|
|
|
124
|
-
const
|
|
125
|
-
return new
|
|
140
|
+
const CreateApp = () => {
|
|
141
|
+
return new Hono()
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
module.exports =
|
|
144
|
+
module.exports = CreateApp
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
const
|
|
2
|
-
const url = new URL(c.req.url)
|
|
1
|
+
const defaultFilter = (c, next) => {
|
|
3
2
|
c.req.query = (key) => {
|
|
3
|
+
const url = new URL(c.req.url)
|
|
4
4
|
return url.searchParams.get(key)
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
next()
|
|
8
8
|
|
|
9
9
|
if (typeof c.res === 'string') {
|
|
10
|
-
c.res = new
|
|
10
|
+
c.res = new Reponse(c.res, {
|
|
11
11
|
status: 200,
|
|
12
12
|
headers: {
|
|
13
|
-
'
|
|
13
|
+
'Conten-Type': 'text/plain',
|
|
14
14
|
},
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
module.exports =
|
|
19
|
+
module.exports = defaultFilter
|
package/src/node.js
CHANGED
|
@@ -1,131 +1,91 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
constructor({ handler, params } = {}) {
|
|
5
|
-
this.handler = handler || []
|
|
6
|
-
this.params = params || {}
|
|
7
|
-
}
|
|
8
|
-
}
|
|
3
|
+
const { splitPath, getPattern } = require('./util')
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
constructor({ method, label, handler, children } = {}) {
|
|
12
|
-
this.label = label || ''
|
|
13
|
-
this.children = children || []
|
|
14
|
-
this.method = {}
|
|
15
|
-
if (method && handler) {
|
|
16
|
-
this.method[method] = handler
|
|
17
|
-
}
|
|
18
|
-
}
|
|
5
|
+
const METHOD_NAME_OF_ALL = 'all'
|
|
19
6
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
for (const p of ps) {
|
|
24
|
-
let nextNode = curNode.children[p]
|
|
25
|
-
if (nextNode) {
|
|
26
|
-
curNode = nextNode
|
|
27
|
-
} else {
|
|
28
|
-
curNode.children[p] = new Node({
|
|
29
|
-
label: p,
|
|
30
|
-
handler: handler,
|
|
31
|
-
})
|
|
32
|
-
curNode = curNode.children[p]
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
curNode.method[method] = handler
|
|
36
|
-
}
|
|
7
|
+
const createResult = (handler, params) => {
|
|
8
|
+
return { handler: handler, params: params }
|
|
9
|
+
}
|
|
37
10
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (p) {
|
|
42
|
-
ps.push(p)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return ps
|
|
46
|
-
}
|
|
11
|
+
const noRoute = () => {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
47
14
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return '(' + match[1] + ')'
|
|
54
|
-
}
|
|
55
|
-
return '(.+)'
|
|
15
|
+
function Node(method, handler, children) {
|
|
16
|
+
this.children = children || {}
|
|
17
|
+
this.method = {}
|
|
18
|
+
if (method && handler) {
|
|
19
|
+
this.method[method] = handler
|
|
56
20
|
}
|
|
21
|
+
}
|
|
57
22
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
23
|
+
Node.prototype.insert = function (method, path, handler) {
|
|
24
|
+
let curNode = this
|
|
25
|
+
const parts = splitPath(path)
|
|
26
|
+
for (let i = 0; i < parts.length; i++) {
|
|
27
|
+
const p = parts[i]
|
|
28
|
+
if (Object.keys(curNode.children).includes(p)) {
|
|
29
|
+
curNode = curNode.children[p]
|
|
30
|
+
continue
|
|
62
31
|
}
|
|
32
|
+
curNode.children[p] = new Node(method, handler)
|
|
33
|
+
curNode = curNode.children[p]
|
|
63
34
|
}
|
|
35
|
+
curNode.method[method] = handler
|
|
36
|
+
return curNode
|
|
37
|
+
}
|
|
64
38
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
let [handler, params] = [, {}]
|
|
39
|
+
Node.prototype.search = function (method, path) {
|
|
40
|
+
let curNode = this
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!root) return this.noRoute()
|
|
72
|
-
// app.get('*', () => 'All')
|
|
73
|
-
const rootAsterisk = root.children['*']
|
|
74
|
-
if (rootAsterisk) {
|
|
75
|
-
handler =
|
|
76
|
-
rootAsterisk.method[method] || rootAsterisk.method[methodNameOfAll]
|
|
77
|
-
} else if (!root.method[method]) {
|
|
78
|
-
return this.noRoute()
|
|
79
|
-
}
|
|
80
|
-
}
|
|
42
|
+
const params = {}
|
|
43
|
+
const parts = splitPath(path)
|
|
81
44
|
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
for (let i = 0; i < parts.length; i++) {
|
|
46
|
+
const p = parts[i]
|
|
47
|
+
const nextNode = curNode.children[p]
|
|
48
|
+
if (nextNode) {
|
|
49
|
+
curNode = nextNode
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
84
52
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
53
|
+
let isParamMatch = false
|
|
54
|
+
const keys = Object.keys(curNode.children)
|
|
55
|
+
for (let j = 0; j < keys.length; j++) {
|
|
56
|
+
const key = keys[j]
|
|
57
|
+
// Wildcard
|
|
58
|
+
if (key === '*') {
|
|
59
|
+
curNode = curNode.children['*']
|
|
60
|
+
isParamMatch = true
|
|
61
|
+
break
|
|
88
62
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
63
|
+
const pattern = getPattern(key)
|
|
64
|
+
if (pattern) {
|
|
65
|
+
const match = p.match(new RegExp(pattern[1]))
|
|
66
|
+
if (match) {
|
|
67
|
+
const k = pattern[0]
|
|
68
|
+
params[k] = match[1]
|
|
94
69
|
curNode = curNode.children[key]
|
|
95
70
|
isParamMatch = true
|
|
96
71
|
break
|
|
97
72
|
}
|
|
98
|
-
|
|
99
|
-
const pattern = this.getPattern(key)
|
|
100
|
-
const match = p.match(new RegExp(pattern))
|
|
101
|
-
if (match) {
|
|
102
|
-
const k = this.getParamName(key)
|
|
103
|
-
params[k] = match[0]
|
|
104
|
-
curNode = curNode.children[key]
|
|
105
|
-
isParamMatch = true
|
|
106
|
-
break
|
|
107
|
-
}
|
|
108
|
-
return this.noRoute()
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (isParamMatch == false) {
|
|
112
|
-
return this.noRoute()
|
|
73
|
+
return noRoute()
|
|
113
74
|
}
|
|
114
75
|
}
|
|
115
76
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!handler) {
|
|
120
|
-
return this.noRoute()
|
|
77
|
+
if (isParamMatch === false) {
|
|
78
|
+
return noRoute()
|
|
121
79
|
}
|
|
122
|
-
const res = new Result({ handler: handler, params: params })
|
|
123
|
-
return res
|
|
124
80
|
}
|
|
125
81
|
|
|
126
|
-
|
|
127
|
-
|
|
82
|
+
const handler = curNode.method[METHOD_NAME_OF_ALL] || curNode.method[method]
|
|
83
|
+
|
|
84
|
+
if (!handler) {
|
|
85
|
+
return noRoute()
|
|
128
86
|
}
|
|
87
|
+
|
|
88
|
+
return createResult(handler, params)
|
|
129
89
|
}
|
|
130
90
|
|
|
131
91
|
module.exports = Node
|
package/src/node.test.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
const Node = require('./node')
|
|
2
2
|
|
|
3
|
-
describe('Util Methods', () => {
|
|
4
|
-
const node = new Node()
|
|
5
|
-
it('node.splitPath', () => {
|
|
6
|
-
let ps = node.splitPath('/')
|
|
7
|
-
expect(ps[0]).toBe('/')
|
|
8
|
-
ps = node.splitPath('/hello')
|
|
9
|
-
expect(ps[0]).toBe('/')
|
|
10
|
-
expect(ps[1]).toBe('hello')
|
|
11
|
-
})
|
|
12
|
-
})
|
|
13
|
-
|
|
14
3
|
describe('Root Node', () => {
|
|
15
4
|
const node = new Node()
|
|
16
5
|
node.insert('get', '/', 'get root')
|
|
17
6
|
it('get /', () => {
|
|
18
|
-
|
|
7
|
+
let res = node.search('get', '/')
|
|
8
|
+
expect(res).not.toBeNull()
|
|
9
|
+
expect(res.handler).toBe('get root')
|
|
19
10
|
expect(node.search('get', '/hello')).toBeNull()
|
|
20
11
|
})
|
|
21
12
|
})
|
|
22
13
|
|
|
23
|
-
describe('Root Node', () => {
|
|
14
|
+
describe('Root Node id not defined', () => {
|
|
24
15
|
const node = new Node()
|
|
25
16
|
node.insert('get', '/hello', 'get hello')
|
|
26
17
|
it('get /', () => {
|
|
@@ -28,7 +19,7 @@ describe('Root Node', () => {
|
|
|
28
19
|
})
|
|
29
20
|
})
|
|
30
21
|
|
|
31
|
-
describe('All', () => {
|
|
22
|
+
describe('All with *', () => {
|
|
32
23
|
const node = new Node()
|
|
33
24
|
node.insert('get', '*', 'get all')
|
|
34
25
|
it('get /', () => {
|
|
@@ -78,25 +69,33 @@ describe('Name path', () => {
|
|
|
78
69
|
|
|
79
70
|
it('get /entry/456/comment', () => {
|
|
80
71
|
node.insert('get', '/entry/:id', 'get entry')
|
|
81
|
-
res = node.search('get', '/entry/456/comment')
|
|
72
|
+
let res = node.search('get', '/entry/456/comment')
|
|
82
73
|
expect(res).toBeNull()
|
|
83
74
|
})
|
|
84
75
|
|
|
85
76
|
it('get /entry/789/comment/123', () => {
|
|
86
77
|
node.insert('get', '/entry/:id/comment/:comment_id', 'get comment')
|
|
87
|
-
res = node.search('get', '/entry/789/comment/123')
|
|
78
|
+
let res = node.search('get', '/entry/789/comment/123')
|
|
88
79
|
expect(res).not.toBeNull()
|
|
89
80
|
expect(res.handler).toBe('get comment')
|
|
90
81
|
expect(res.params['id']).toBe('789')
|
|
91
82
|
expect(res.params['comment_id']).toBe('123')
|
|
92
83
|
})
|
|
84
|
+
|
|
85
|
+
it('get /map/:location/events', () => {
|
|
86
|
+
node.insert('get', '/map/:location/events', 'get events')
|
|
87
|
+
let res = node.search('get', '/map/yokohama/events')
|
|
88
|
+
expect(res).not.toBeNull()
|
|
89
|
+
expect(res.handler).toBe('get events')
|
|
90
|
+
expect(res.params['location']).toBe('yokohama')
|
|
91
|
+
})
|
|
93
92
|
})
|
|
94
93
|
|
|
95
94
|
describe('Wildcard', () => {
|
|
96
95
|
const node = new Node()
|
|
97
96
|
it('/wildcard-abc/xxxxxx/wildcard-efg', () => {
|
|
98
97
|
node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')
|
|
99
|
-
res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
|
|
98
|
+
let res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
|
|
100
99
|
expect(res).not.toBeNull()
|
|
101
100
|
expect(res.handler).toBe('wildcard')
|
|
102
101
|
})
|
|
@@ -104,11 +103,7 @@ describe('Wildcard', () => {
|
|
|
104
103
|
|
|
105
104
|
describe('Regexp', () => {
|
|
106
105
|
const node = new Node()
|
|
107
|
-
node.insert(
|
|
108
|
-
'get',
|
|
109
|
-
'/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}',
|
|
110
|
-
'regexp'
|
|
111
|
-
)
|
|
106
|
+
node.insert('get', '/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}', 'regexp')
|
|
112
107
|
it('/regexp-abc/123/comment/abc', () => {
|
|
113
108
|
res = node.search('get', '/regex-abc/123/comment/abc')
|
|
114
109
|
expect(res).not.toBeNull()
|
package/src/router.test.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
const App = require('./hono')
|
|
2
2
|
|
|
3
3
|
describe('Basic Usage', () => {
|
|
4
|
-
|
|
4
|
+
const router = App()
|
|
5
5
|
|
|
6
|
-
it('get, post hello', () => {
|
|
6
|
+
it('get, post hello', async () => {
|
|
7
7
|
router.get('/hello', 'get hello')
|
|
8
8
|
router.post('/hello', 'post hello')
|
|
9
9
|
|
|
10
|
-
let res = router.matchRoute('GET', '/hello')
|
|
10
|
+
let res = await router.matchRoute('GET', '/hello')
|
|
11
11
|
expect(res).not.toBeNull()
|
|
12
12
|
expect(res.handler[0]).toBe('get hello')
|
|
13
13
|
|
|
14
|
-
res = router.matchRoute('POST', '/hello')
|
|
14
|
+
res = await router.matchRoute('POST', '/hello')
|
|
15
15
|
expect(res).not.toBeNull()
|
|
16
16
|
expect(res.handler[0]).toBe('post hello')
|
|
17
17
|
|
|
18
|
-
res = router.matchRoute('PUT', '/hello')
|
|
18
|
+
res = await router.matchRoute('PUT', '/hello')
|
|
19
19
|
expect(res).toBeNull()
|
|
20
20
|
|
|
21
|
-
res = router.matchRoute('GET', '/')
|
|
21
|
+
res = await router.matchRoute('GET', '/')
|
|
22
22
|
expect(res).toBeNull()
|
|
23
23
|
})
|
|
24
24
|
})
|
|
@@ -26,31 +26,31 @@ describe('Basic Usage', () => {
|
|
|
26
26
|
describe('Complex', () => {
|
|
27
27
|
let router = App()
|
|
28
28
|
|
|
29
|
-
it('Named Param', () => {
|
|
29
|
+
it('Named Param', async () => {
|
|
30
30
|
router.get('/entry/:id', 'get entry')
|
|
31
|
-
res = router.matchRoute('GET', '/entry/123')
|
|
31
|
+
res = await router.matchRoute('GET', '/entry/123')
|
|
32
32
|
expect(res).not.toBeNull()
|
|
33
33
|
expect(res.handler[0]).toBe('get entry')
|
|
34
34
|
expect(res.params['id']).toBe('123')
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
it('Wildcard', () => {
|
|
37
|
+
it('Wildcard', async () => {
|
|
38
38
|
router.get('/wild/*/card', 'get wildcard')
|
|
39
|
-
res = router.matchRoute('GET', '/wild/xxx/card')
|
|
39
|
+
res = await router.matchRoute('GET', '/wild/xxx/card')
|
|
40
40
|
expect(res).not.toBeNull()
|
|
41
41
|
expect(res.handler[0]).toBe('get wildcard')
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
it('Regexp', () => {
|
|
44
|
+
it('Regexp', async () => {
|
|
45
45
|
router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')
|
|
46
|
-
res = router.matchRoute('GET', '/post/20210101/hello')
|
|
46
|
+
res = await router.matchRoute('GET', '/post/20210101/hello')
|
|
47
47
|
expect(res).not.toBeNull()
|
|
48
48
|
expect(res.handler[0]).toBe('get post')
|
|
49
49
|
expect(res.params['date']).toBe('20210101')
|
|
50
50
|
expect(res.params['title']).toBe('hello')
|
|
51
|
-
res = router.matchRoute('GET', '/post/onetwothree')
|
|
51
|
+
res = await router.matchRoute('GET', '/post/onetwothree')
|
|
52
52
|
expect(res).toBeNull()
|
|
53
|
-
res = router.matchRoute('GET', '/post/123/123')
|
|
53
|
+
res = await router.matchRoute('GET', '/post/123/123')
|
|
54
54
|
expect(res).toBeNull()
|
|
55
55
|
})
|
|
56
56
|
})
|
|
@@ -58,27 +58,31 @@ describe('Complex', () => {
|
|
|
58
58
|
describe('Chained Route', () => {
|
|
59
59
|
let router = App()
|
|
60
60
|
|
|
61
|
-
it('Return rooter object', () => {
|
|
61
|
+
it('Return rooter object', async () => {
|
|
62
62
|
router = router.patch('/hello', 'patch hello')
|
|
63
63
|
expect(router).not.toBeNull()
|
|
64
64
|
router = router.delete('/hello', 'delete hello')
|
|
65
|
-
res = router.matchRoute('DELETE', '/hello')
|
|
65
|
+
res = await router.matchRoute('DELETE', '/hello')
|
|
66
66
|
expect(res).not.toBeNull()
|
|
67
67
|
expect(res.handler[0]).toBe('delete hello')
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
it('Chain with route method', () => {
|
|
70
|
+
it('Chain with route method', async () => {
|
|
71
71
|
router.route('/api/book').get('get book').post('post book').put('put book')
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
res = await router.matchRoute('GET', '/api/book')
|
|
73
74
|
expect(res).not.toBeNull()
|
|
74
75
|
expect(res.handler[0]).toBe('get book')
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
res = await router.matchRoute('POST', '/api/book')
|
|
76
78
|
expect(res).not.toBeNull()
|
|
77
79
|
expect(res.handler[0]).toBe('post book')
|
|
78
|
-
|
|
80
|
+
|
|
81
|
+
res = await router.matchRoute('PUT', '/api/book')
|
|
79
82
|
expect(res).not.toBeNull()
|
|
80
83
|
expect(res.handler[0]).toBe('put book')
|
|
81
|
-
|
|
84
|
+
|
|
85
|
+
res = await router.matchRoute('DELETE', '/api/book')
|
|
82
86
|
expect(res).toBeNull()
|
|
83
87
|
})
|
|
84
88
|
})
|
package/src/util.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const splitPath = (path) => {
|
|
2
|
+
path = path.split(/\//) // faster than path.split('/')
|
|
3
|
+
if (path[0] === '') {
|
|
4
|
+
path.shift()
|
|
5
|
+
}
|
|
6
|
+
return path
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getPattern = (label) => {
|
|
10
|
+
// :id{[0-9]+} => ([0-9]+)
|
|
11
|
+
// :id => (.+)
|
|
12
|
+
//const name = ''
|
|
13
|
+
const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/)
|
|
14
|
+
if (match) {
|
|
15
|
+
if (match[2]) {
|
|
16
|
+
return [match[1], '(' + match[2] + ')']
|
|
17
|
+
} else {
|
|
18
|
+
return [match[1], '(.+)']
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
splitPath,
|
|
25
|
+
getPattern,
|
|
26
|
+
}
|
package/src/util.test.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { splitPath, getPattern, getParamName } = require('./util')
|
|
2
|
+
|
|
3
|
+
describe('Utility methods', () => {
|
|
4
|
+
it('splitPath', () => {
|
|
5
|
+
let ps = splitPath('/')
|
|
6
|
+
expect(ps[0]).toBe('')
|
|
7
|
+
ps = splitPath('/hello')
|
|
8
|
+
expect(ps[0]).toBe('hello')
|
|
9
|
+
ps = splitPath('*')
|
|
10
|
+
expect(ps[0]).toBe('*')
|
|
11
|
+
ps = splitPath('/wildcard-abc/*/wildcard-efg')
|
|
12
|
+
expect(ps[0]).toBe('wildcard-abc')
|
|
13
|
+
expect(ps[1]).toBe('*')
|
|
14
|
+
expect(ps[2]).toBe('wildcard-efg')
|
|
15
|
+
ps = splitPath('/map/:location/events')
|
|
16
|
+
expect(ps[0]).toBe('map')
|
|
17
|
+
expect(ps[1]).toBe(':location')
|
|
18
|
+
expect(ps[2]).toBe('events')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('getPattern', () => {
|
|
22
|
+
let res = getPattern(':id')
|
|
23
|
+
expect(res[0]).toBe('id')
|
|
24
|
+
expect(res[1]).toBe('(.+)')
|
|
25
|
+
res = getPattern(':id{[0-9]+}')
|
|
26
|
+
expect(res[0]).toBe('id')
|
|
27
|
+
expect(res[1]).toBe('([0-9]+)')
|
|
28
|
+
})
|
|
29
|
+
})
|
package/example/basic/index.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const Hono = require('../../src/hono')
|
|
2
|
-
const app = Hono()
|
|
3
|
-
|
|
4
|
-
// Middleware
|
|
5
|
-
const logger = (c, next) => {
|
|
6
|
-
console.log(`[${c.req.method}] ${c.req.url}`)
|
|
7
|
-
next()
|
|
8
|
-
}
|
|
9
|
-
const addHeader = (c, next) => {
|
|
10
|
-
next()
|
|
11
|
-
c.res.headers.append('X-message', 'This is addHeader middleware!')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Mount middleware
|
|
15
|
-
app.use('*', logger)
|
|
16
|
-
app.use('/hello', addHeader)
|
|
17
|
-
|
|
18
|
-
// Routing
|
|
19
|
-
app.get('/', () => new Response('Hono!!'))
|
|
20
|
-
app.get('/hello', () => new Response('This is /hello'))
|
|
21
|
-
|
|
22
|
-
app.get('/entry/:id', (c) => {
|
|
23
|
-
const id = c.req.params('id')
|
|
24
|
-
return new Response(`Your ID is ${id}`)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
// addEventListener
|
|
28
|
-
app.fire()
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "hono-example-basic",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
|
10
|
-
"license": "MIT"
|
|
11
|
-
}
|