hono 0.0.3 → 0.0.4

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 CHANGED
@@ -15,11 +15,12 @@ app.fire()
15
15
  - Fast - the router is implemented with Trie-Tree structure.
16
16
  - Tiny - use only standard API.
17
17
  - Portable - zero dependencies.
18
- - Optimized for Cloudflare Workers.
18
+ - Flexible - you can make your own middlewares.
19
+ - Optimized - for Cloudflare Workers.
19
20
 
20
21
  ## Install
21
22
 
22
- ```sh
23
+ ```
23
24
  $ yarn add hono
24
25
  ```
25
26
 
@@ -29,21 +30,42 @@ or
29
30
  $ npm install hono
30
31
  ```
31
32
 
33
+ ## Methods
34
+
35
+ - app.**HTTP_METHOD**(path, callback)
36
+ - app.**all**(path, callback)
37
+ - app.**route**(path)
38
+ - app.**use**(path, middleware)
39
+
32
40
  ## Routing
33
41
 
34
42
  ### Basic
35
43
 
44
+ `app.HTTP_METHOD`
45
+
46
+ ```js
47
+ // HTTP Methods
48
+ app.get('/', () => new Response('GET /'))
49
+ app.post('/', () => new Response('POST /'))
50
+
51
+ // Wildcard
52
+ app.get('/wild/*/card', () => {
53
+ return new Reponse('GET /wild/*/card')
54
+ })
55
+ ```
56
+
57
+ `app.all`
58
+
36
59
  ```js
37
- app.get('/', () => 'GET /')
38
- app.post('/', () => 'POST /')
39
- app.get('/wild/*/card', () => 'GET /wild/*/card')
60
+ // Any HTTP methods
61
+ app.all('/hello', () => 'ALL Method /hello')
40
62
  ```
41
63
 
42
64
  ### Named Parameter
43
65
 
44
66
  ```js
45
- app.get('/user/:name', (req) => {
46
- const name = req.params('name')
67
+ app.get('/user/:name', (c) => {
68
+ const name = c.req.params('name')
47
69
  ...
48
70
  })
49
71
  ```
@@ -51,9 +73,9 @@ app.get('/user/:name', (req) => {
51
73
  ### Regexp
52
74
 
53
75
  ```js
54
- app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (req) => {
55
- const date = req.params('date')
56
- const title = req.params('title')
76
+ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
77
+ const date = c.req.params('date')
78
+ const title = c.req.params('title')
57
79
  ...
58
80
  ```
59
81
 
@@ -62,15 +84,77 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (req) => {
62
84
  ```js
63
85
  app
64
86
  .route('/api/book')
65
- .get(() => 'GET /api/book')
66
- .post(() => 'POST /api/book')
67
- .put(() => 'PUT /api/book')
87
+ .get(() => {...})
88
+ .post(() => {...})
89
+ .put(() => {...})
90
+ ```
91
+
92
+ ## Middleware
93
+
94
+ ```js
95
+ const logger = (c, next) => {
96
+ console.log(`[${c.req.method}] ${c.req.url}`)
97
+ next()
98
+ }
99
+
100
+ const addHeader = (c, next) => {
101
+ next()
102
+ c.res.headers.add('x-message', 'This is middleware!')
103
+ }
104
+
105
+ app = app.use('*', logger)
106
+ app = app.use('/message/*', addHeader)
107
+
108
+ app.get('/message/hello', () => 'Hello Middleware!')
109
+ ```
110
+
111
+ ## Context
112
+
113
+ ### req
114
+
115
+ ```js
116
+ app.get('/hello', (c) => {
117
+ const userAgent = c.req.headers('User-Agent')
118
+ ...
119
+ })
120
+ ```
121
+
122
+ ### res
123
+
124
+ ```js
125
+ app.use('/', (c, next) => {
126
+ next()
127
+ c.res.headers.append('X-Debug', 'Debug message')
128
+ })
129
+ ```
130
+
131
+ ## Request
132
+
133
+ ### query
134
+
135
+ ```js
136
+ app.get('/search', (c) => {
137
+ const query = c.req.query('q')
138
+ ...
139
+ })
140
+ ```
141
+
142
+ ### params
143
+
144
+ ```js
145
+ app.get('/entry/:id', (c) => {
146
+ const id = c.req.params('id')
147
+ ...
148
+ })
68
149
  ```
69
150
 
70
151
  ## Related projects
71
152
 
72
- - goblin <https://github.com/bmf-san/goblin>
153
+ - koa <https://github.com/koajs/koa>
154
+ - express <https://github.com/expressjs/express>
73
155
  - itty-router <https://github.com/kwhitley/itty-router>
156
+ - Sunder <https://github.com/SunderJS/sunder>
157
+ - goblin <https://github.com/bmf-san/goblin>
74
158
 
75
159
  ## Author
76
160
 
@@ -1,7 +1,28 @@
1
1
  const Hono = require('../../src/hono')
2
2
  const app = Hono()
3
3
 
4
- app.get('/', () => 'Hono!!')
5
- app.get('/hello', () => 'This is /hello')
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
+ }
6
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
7
28
  app.fire()
@@ -1,11 +1,11 @@
1
1
  {
2
- "name": "sandbox",
3
- "version": "1.0.0",
2
+ "name": "hono-example-basic",
3
+ "version": "0.0.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
9
  "author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
10
- "license": "ISC"
11
- }
10
+ "license": "MIT"
11
+ }
@@ -1,4 +1,4 @@
1
- name = "basic"
1
+ name = "hono-example-basic"
2
2
  type = "webpack"
3
3
  route = ''
4
4
  zone_id = ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Minimal web framework for Cloudflare Workers",
5
5
  "main": "src/hono.js",
6
6
  "scripts": {
@@ -16,9 +16,5 @@
16
16
  "devDependencies": {
17
17
  "jest": "^27.4.5",
18
18
  "node-fetch": "^2.6.6"
19
- },
20
- "dependencies": {
21
- "global": "^4.4.0",
22
- "wrangler": "^0.0.0-beta.6"
23
19
  }
24
20
  }
package/src/compose.js ADDED
@@ -0,0 +1,22 @@
1
+ // Based on the code in the MIT licensed `koa-compose` package.
2
+ const compose = (middleware) => {
3
+ return function (context, next) {
4
+ let index = -1
5
+ return dispatch(0)
6
+ function dispatch(i) {
7
+ if (i <= index)
8
+ return Promise.reject(new Error('next() called multiple times'))
9
+ index = i
10
+ let fn = middleware[i]
11
+ if (i === middleware.length) fn = next
12
+ if (!fn) return Promise.resolve()
13
+ try {
14
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
15
+ } catch (err) {
16
+ return Promise.reject(err)
17
+ }
18
+ }
19
+ }
20
+ }
21
+
22
+ module.exports = compose
@@ -0,0 +1,42 @@
1
+ const compose = require('./compose')
2
+
3
+ describe('compose middleware', () => {
4
+ const middleware = []
5
+
6
+ const a = (c, next) => {
7
+ c.req['log'] = 'log'
8
+ next()
9
+ }
10
+ middleware.push(a)
11
+
12
+ const b = (c, next) => {
13
+ next()
14
+ c.res['header'] = `${c.res.header}-custom-header`
15
+ }
16
+ middleware.push(b)
17
+
18
+ const handler = (c, next) => {
19
+ c.req['log'] = `${c.req.log} message`
20
+ next()
21
+ c.res = { message: 'new response' }
22
+ }
23
+ middleware.push(handler)
24
+
25
+ const request = {}
26
+ const response = {}
27
+
28
+ it('Request', () => {
29
+ const c = { req: request, res: response }
30
+ const composed = compose(middleware)
31
+ composed(c)
32
+ expect(c.req['log']).not.toBeNull()
33
+ expect(c.req['log']).toBe('log message')
34
+ })
35
+ it('Response', () => {
36
+ const c = { req: request, res: response }
37
+ const composed = compose(middleware)
38
+ composed(c)
39
+ expect(c.res['header']).not.toBeNull()
40
+ expect(c.res['message']).toBe('new response')
41
+ })
42
+ })
package/src/hono.js CHANGED
@@ -1,4 +1,8 @@
1
+ 'use strict'
2
+
1
3
  const Node = require('./node')
4
+ const compose = require('./compose')
5
+ const filter = require('./middleware/filter')
2
6
 
3
7
  class Router {
4
8
  constructor() {
@@ -6,98 +10,119 @@ class Router {
6
10
  this.tempPath = '/'
7
11
  }
8
12
 
9
- route(path) {
10
- this.tempPath = path
11
- return WrappedRouter(this)
13
+ add(method, path, ...handlers) {
14
+ this.node.insert(method, path, handlers)
12
15
  }
13
16
 
14
- addRoute(method, path, handler) {
15
- this.node.insert(method, path, handler)
16
- return WrappedRouter(this)
17
+ match(method, path) {
18
+ method = method.toLowerCase()
19
+ return this.node.search(method, path)
20
+ }
21
+ }
22
+
23
+ const proxyHandler = {
24
+ get:
25
+ (target, prop) =>
26
+ (...args) => {
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
+ },
36
+ }
37
+
38
+ class App {
39
+ constructor() {
40
+ this.router = new Router()
41
+ this.middlewareRouter = new Router()
42
+ this.middleware = []
43
+ }
44
+
45
+ addRoute(method, path, ...args) {
46
+ this.router.add(method, path, ...args)
47
+ return WrappedApp(this)
17
48
  }
18
49
 
19
50
  matchRoute(method, path) {
20
- method = method.toLowerCase()
21
- const res = this.node.search(method, path)
22
- return res
51
+ return this.router.match(method, path)
23
52
  }
24
53
 
25
- handle(event) {
26
- const result = this.dispatch(event.request)
27
- const response = this.filter(result)
28
- return event.respondWith(response)
54
+ route(path) {
55
+ this.router.tempPath = path
56
+ return WrappedApp(this)
29
57
  }
30
58
 
31
- filter(result) {
32
- if (result instanceof Response) {
33
- return result
59
+ use(path, middleware) {
60
+ middleware = [middleware]
61
+ const result = this.middlewareRouter.match('all', path)
62
+ if (result) {
63
+ middleware.push(...result.handler)
34
64
  }
35
- if (typeof result === 'object') {
36
- return new Response(JSON.stringify(result), {
37
- status: 200,
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- },
41
- })
42
- }
43
- if (typeof result === 'string') {
44
- return new Response(result, {
45
- status: 200,
46
- headers: {
47
- 'Content-Type': 'text/plain',
48
- },
49
- })
50
- }
51
- return this.notFound()
65
+ this.middlewareRouter.add('all', path, ...middleware)
52
66
  }
53
67
 
54
- dispatch(request) {
68
+ // XXX
69
+ createContext(req, res) {
70
+ return { req: req, res: res }
71
+ }
72
+
73
+ async dispatch(request, response) {
55
74
  const url = new URL(request.url)
56
- const path = url.pathname
57
- const method = request.method
58
- const res = this.matchRoute(method, path)
75
+ const [method, path] = [request.method, url.pathname]
76
+
77
+ const result = this.matchRoute(method, path)
78
+ if (!result) return this.notFound()
59
79
 
60
- if (!res) {
61
- return this.notFound()
80
+ request.params = (key) => result.params[key]
81
+
82
+ const middleware = [filter]
83
+ const mwResult = this.middlewareRouter.match('all', path)
84
+ if (mwResult) {
85
+ middleware.push(...mwResult.handler)
62
86
  }
63
87
 
64
- const handler = res.handler
65
- return handler(request)
88
+ let handler
89
+ for (const resultHandler of result.handler) {
90
+ if (resultHandler) {
91
+ handler = resultHandler
92
+ }
93
+ }
94
+
95
+ let wrappedHandler = (context, next) => {
96
+ context.res = handler(context)
97
+ next()
98
+ }
99
+
100
+ middleware.push(wrappedHandler)
101
+ const composed = compose(middleware)
102
+ const c = this.createContext(request, response)
103
+
104
+ composed(c)
105
+
106
+ return c.res
66
107
  }
67
108
 
68
- notFound() {
69
- return new Response('Not Found', {
70
- status: 404,
71
- headers: {
72
- 'content-type': 'text/plain',
73
- },
74
- })
109
+ async handleEvent(event) {
110
+ return await this.dispatch(event.request, new Response())
75
111
  }
76
112
 
77
113
  fire() {
78
114
  addEventListener('fetch', (event) => {
79
- this.handle(event)
115
+ event.respondWith(this.handleEvent(event))
80
116
  })
81
117
  }
82
- }
83
118
 
84
- const proxyHandler = {
85
- get:
86
- (target, prop) =>
87
- (...args) => {
88
- if (target.constructor.prototype.hasOwnProperty(prop)) {
89
- return target[prop](...args)
90
- } else {
91
- if (args.length === 1) {
92
- return target.addRoute(prop, target.tempPath, ...args)
93
- }
94
- return target.addRoute(prop, ...args)
95
- }
96
- },
119
+ notFound() {
120
+ return new Response('Not Found', { status: 404 })
121
+ }
97
122
  }
98
123
 
99
- const WrappedRouter = (router = new Router()) => {
124
+ const WrappedApp = (router = new App()) => {
100
125
  return new Proxy(router, proxyHandler)
101
126
  }
102
127
 
103
- module.exports = WrappedRouter
128
+ module.exports = WrappedApp
package/src/hono.test.js CHANGED
@@ -1,9 +1,8 @@
1
1
  const Hono = require('./hono')
2
2
  const fetch = require('node-fetch')
3
3
 
4
- const app = Hono()
5
-
6
- describe('GET match', () => {
4
+ describe('GET Request', () => {
5
+ const app = Hono()
7
6
  app.get('/hello', () => {
8
7
  return new fetch.Response('hello', {
9
8
  status: 200,
@@ -14,16 +13,76 @@ describe('GET match', () => {
14
13
  status: 404,
15
14
  })
16
15
  }
17
- it('GET /hello is ok', () => {
16
+ it('GET /hello is ok', async () => {
18
17
  let req = new fetch.Request('https://example.com/hello')
19
- let res = app.dispatch(req)
18
+ let res = await app.dispatch(req, new fetch.Response())
20
19
  expect(res).not.toBeNull()
21
20
  expect(res.status).toBe(200)
21
+ expect(await res.text()).toBe('hello')
22
22
  })
23
- it('GET / is not found', () => {
23
+ it('GET / is not found', async () => {
24
24
  let req = new fetch.Request('https://example.com/')
25
- let res = app.dispatch(req)
25
+ let res = await app.dispatch(req, new fetch.Response())
26
26
  expect(res).not.toBeNull()
27
27
  expect(res.status).toBe(404)
28
28
  })
29
29
  })
30
+
31
+ describe('params and query', () => {
32
+ const app = Hono()
33
+
34
+ app.get('/entry/:id', async (c) => {
35
+ const id = await c.req.params('id')
36
+ return new fetch.Response(`id is ${id}`, {
37
+ status: 200,
38
+ })
39
+ })
40
+
41
+ app.get('/search', async (c) => {
42
+ const name = await c.req.query('name')
43
+ return new fetch.Response(`name is ${name}`, {
44
+ status: 200,
45
+ })
46
+ })
47
+
48
+ it('params of /entry/:id is found', async () => {
49
+ let req = new fetch.Request('https://example.com/entry/123')
50
+ let res = await app.dispatch(req)
51
+ expect(res.status).toBe(200)
52
+ expect(await res.text()).toBe('id is 123')
53
+ })
54
+ it('query of /search?name=sam is found', async () => {
55
+ let req = new fetch.Request('https://example.com/search?name=sam')
56
+ let res = await app.dispatch(req)
57
+ expect(res.status).toBe(200)
58
+ expect(await res.text()).toBe('name is sam')
59
+ })
60
+ })
61
+
62
+ describe('Middleware', () => {
63
+ const app = Hono()
64
+
65
+ const logger = (c, next) => {
66
+ console.log(`${c.req.method} : ${c.req.url}`)
67
+ next()
68
+ }
69
+
70
+ const customHeader = (c, next) => {
71
+ next()
72
+ c.res.headers.append('x-message', 'custom-header')
73
+ }
74
+
75
+ app.use('*', logger)
76
+ app.use('/hello', customHeader)
77
+ app.get('/hello', () => {
78
+ return new fetch.Response('hello')
79
+ })
80
+
81
+ it('logging and custom header', async () => {
82
+ let req = new fetch.Request('https://example.com/hello')
83
+ let res = await app.dispatch(req)
84
+ expect(res.status).toBe(200)
85
+ expect(await res.text()).toBe('hello')
86
+ expect(await res.headers.get('x-message')).toBe('custom-header')
87
+ })
88
+ })
@@ -0,0 +1,19 @@
1
+ const filter = (c, next) => {
2
+ const url = new URL(c.req.url)
3
+ c.req.query = (key) => {
4
+ return url.searchParams.get(key)
5
+ }
6
+
7
+ next()
8
+
9
+ if (typeof c.res === 'string') {
10
+ c.res = new Response(c.res, {
11
+ status: 200,
12
+ headers: {
13
+ 'Content-Type': 'text/plain',
14
+ },
15
+ })
16
+ }
17
+ }
18
+
19
+ module.exports = filter
package/src/node.js CHANGED
@@ -2,7 +2,7 @@ const methodNameOfAll = 'all'
2
2
 
3
3
  class Result {
4
4
  constructor({ handler, params } = {}) {
5
- this.handler = handler
5
+ this.handler = handler || []
6
6
  this.params = params || {}
7
7
  }
8
8
  }
@@ -32,9 +32,7 @@ class Node {
32
32
  curNode = curNode.children[p]
33
33
  }
34
34
  }
35
- if (!curNode.method[method]) {
36
- curNode.method[method] = handler
37
- }
35
+ curNode.method[method] = handler
38
36
  }
39
37
 
40
38
  splitPath(path) {
@@ -66,11 +64,17 @@ class Node {
66
64
 
67
65
  search(method, path) {
68
66
  let curNode = this
69
- const params = {}
67
+ let [handler, params] = [, {}]
70
68
 
71
69
  if (path === '/') {
72
70
  const root = this.children['/']
73
- if (!root.method[method]) {
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]) {
74
78
  return this.noRoute()
75
79
  }
76
80
  }
@@ -109,14 +113,14 @@ class Node {
109
113
  }
110
114
  }
111
115
 
112
- let handler = curNode.method[methodNameOfAll] || curNode.method[method]
116
+ handler =
117
+ handler || curNode.method[methodNameOfAll] || curNode.method[method]
113
118
 
114
- if (handler) {
115
- const res = new Result({ handler: handler, params: params })
116
- return res
117
- } else {
119
+ if (!handler) {
118
120
  return this.noRoute()
119
121
  }
122
+ const res = new Result({ handler: handler, params: params })
123
+ return res
120
124
  }
121
125
 
122
126
  noRoute() {
package/src/node.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const Node = require('./node')
2
- const node = new Node()
3
2
 
4
3
  describe('Util Methods', () => {
4
+ const node = new Node()
5
5
  it('node.splitPath', () => {
6
6
  let ps = node.splitPath('/')
7
7
  expect(ps[0]).toBe('/')
@@ -11,7 +11,34 @@ describe('Util Methods', () => {
11
11
  })
12
12
  })
13
13
 
14
+ describe('Root Node', () => {
15
+ const node = new Node()
16
+ node.insert('get', '/', 'get root')
17
+ it('get /', () => {
18
+ expect(node.search('get', '/')).not.toBeNull()
19
+ expect(node.search('get', '/hello')).toBeNull()
20
+ })
21
+ })
22
+
23
+ describe('Root Node', () => {
24
+ const node = new Node()
25
+ node.insert('get', '/hello', 'get hello')
26
+ it('get /', () => {
27
+ expect(node.search('get', '/')).toBeNull()
28
+ })
29
+ })
30
+
31
+ describe('All', () => {
32
+ const node = new Node()
33
+ node.insert('get', '*', 'get all')
34
+ it('get /', () => {
35
+ expect(node.search('get', '/')).not.toBeNull()
36
+ expect(node.search('get', '/hello')).not.toBeNull()
37
+ })
38
+ })
39
+
14
40
  describe('Basic Usage', () => {
41
+ const node = new Node()
15
42
  node.insert('get', '/hello', 'get hello')
16
43
  node.insert('post', '/hello', 'post hello')
17
44
  node.insert('get', '/hello/foo', 'get hello foo')
@@ -38,6 +65,7 @@ describe('Basic Usage', () => {
38
65
  })
39
66
 
40
67
  describe('Name path', () => {
68
+ const node = new Node()
41
69
  it('get /entry/123', () => {
42
70
  node.insert('get', '/entry/:id', 'get entry')
43
71
  let res = node.search('get', '/entry/123')
@@ -65,6 +93,7 @@ describe('Name path', () => {
65
93
  })
66
94
 
67
95
  describe('Wildcard', () => {
96
+ const node = new Node()
68
97
  it('/wildcard-abc/xxxxxx/wildcard-efg', () => {
69
98
  node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')
70
99
  res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
@@ -74,6 +103,7 @@ describe('Wildcard', () => {
74
103
  })
75
104
 
76
105
  describe('Regexp', () => {
106
+ const node = new Node()
77
107
  node.insert(
78
108
  'get',
79
109
  '/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}',
@@ -97,6 +127,7 @@ describe('Regexp', () => {
97
127
  })
98
128
 
99
129
  describe('All', () => {
130
+ const node = new Node()
100
131
  node.insert('all', '/all-methods', 'all methods')
101
132
  it('/all-methods', () => {
102
133
  res = node.search('get', '/all-methods')
@@ -1,18 +1,19 @@
1
- const Router = require('./hono')
2
- let router = Router()
1
+ const App = require('./hono')
3
2
 
4
3
  describe('Basic Usage', () => {
4
+ let router = App()
5
+
5
6
  it('get, post hello', () => {
6
7
  router.get('/hello', 'get hello')
7
8
  router.post('/hello', 'post hello')
8
9
 
9
10
  let res = router.matchRoute('GET', '/hello')
10
11
  expect(res).not.toBeNull()
11
- expect(res.handler).toBe('get hello')
12
+ expect(res.handler[0]).toBe('get hello')
12
13
 
13
14
  res = router.matchRoute('POST', '/hello')
14
15
  expect(res).not.toBeNull()
15
- expect(res.handler).toBe('post hello')
16
+ expect(res.handler[0]).toBe('post hello')
16
17
 
17
18
  res = router.matchRoute('PUT', '/hello')
18
19
  expect(res).toBeNull()
@@ -23,24 +24,28 @@ describe('Basic Usage', () => {
23
24
  })
24
25
 
25
26
  describe('Complex', () => {
27
+ let router = App()
28
+
26
29
  it('Named Param', () => {
27
30
  router.get('/entry/:id', 'get entry')
28
31
  res = router.matchRoute('GET', '/entry/123')
29
32
  expect(res).not.toBeNull()
30
- expect(res.handler).toBe('get entry')
33
+ expect(res.handler[0]).toBe('get entry')
31
34
  expect(res.params['id']).toBe('123')
32
35
  })
36
+
33
37
  it('Wildcard', () => {
34
38
  router.get('/wild/*/card', 'get wildcard')
35
39
  res = router.matchRoute('GET', '/wild/xxx/card')
36
40
  expect(res).not.toBeNull()
37
- expect(res.handler).toBe('get wildcard')
41
+ expect(res.handler[0]).toBe('get wildcard')
38
42
  })
43
+
39
44
  it('Regexp', () => {
40
45
  router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')
41
46
  res = router.matchRoute('GET', '/post/20210101/hello')
42
47
  expect(res).not.toBeNull()
43
- expect(res.handler).toBe('get post')
48
+ expect(res.handler[0]).toBe('get post')
44
49
  expect(res.params['date']).toBe('20210101')
45
50
  expect(res.params['title']).toBe('hello')
46
51
  res = router.matchRoute('GET', '/post/onetwothree')
@@ -51,25 +56,28 @@ describe('Complex', () => {
51
56
  })
52
57
 
53
58
  describe('Chained Route', () => {
59
+ let router = App()
60
+
54
61
  it('Return rooter object', () => {
55
62
  router = router.patch('/hello', 'patch hello')
56
63
  expect(router).not.toBeNull()
57
64
  router = router.delete('/hello', 'delete hello')
58
65
  res = router.matchRoute('DELETE', '/hello')
59
66
  expect(res).not.toBeNull()
60
- expect(res.handler).toBe('delete hello')
67
+ expect(res.handler[0]).toBe('delete hello')
61
68
  })
69
+
62
70
  it('Chain with route method', () => {
63
71
  router.route('/api/book').get('get book').post('post book').put('put book')
64
72
  res = router.matchRoute('GET', '/api/book')
65
73
  expect(res).not.toBeNull()
66
- expect(res.handler).toBe('get book')
74
+ expect(res.handler[0]).toBe('get book')
67
75
  res = router.matchRoute('POST', '/api/book')
68
76
  expect(res).not.toBeNull()
69
- expect(res.handler).toBe('post book')
77
+ expect(res.handler[0]).toBe('post book')
70
78
  res = router.matchRoute('PUT', '/api/book')
71
79
  expect(res).not.toBeNull()
72
- expect(res.handler).toBe('put book')
80
+ expect(res.handler[0]).toBe('put book')
73
81
  res = router.matchRoute('DELETE', '/api/book')
74
82
  expect(res).toBeNull()
75
83
  })
package/hono.mini.js DELETED
@@ -1 +0,0 @@
1
- const Router=require("./router");class Route{constructor(t,e){this.method=t;this.handler=e}}class App{constructor(){this.router=new Router}addRoute(t,e,n){this.router.add(e,new Route(t,n))}handle(t){const e=this.dispatch(t.request);return t.respondWith(e)}dispatch(t){const e=new URL(t.url);const n=e.pathname;const r=this.router.match(n);if(!r){return this.notFound()}const o=t.method.toLowerCase();const s=r[0];if(s.method==o){const i=s.handler;return i(t)}return this.notFound()}notFound(){return new Response("Not Found",{status:404,headers:{"content-type":"text/plain"}})}fire(){addEventListener("fetch",t=>{this.handle(t)})}}const proxyHandler={get:(e,n)=>(...t)=>{if(e.constructor.prototype.hasOwnProperty(n)){return e[n](t[0])}else{e.addRoute(n,t[0],t[1]);return}}};const app=new App;function Hono(){return new Proxy(app,proxyHandler)}module.exports=Hono;class Router{constructor(){this.node=new Node({label:"/"})}add(t,e){this.node.insert(t,e)}match(t){return this.node.search(t)}}class Node{constructor({label:t,stuff:e,children:n}={}){this.label=t||"";this.stuff=e||{};this.children=n||[]}insert(t,e){let n=this;if(t=="/"){n.label=t;n.stuff=e}const r=this.splitPath(t);for(const o of r){let t=n.children[o];if(t){n=t}else{n.children[o]=new Node({label:o,stuff:e,children:[]});n=n.children[o]}}}splitPath(t){const e=[];for(const n of t.split("/")){if(n){e.push(n)}}return e}getPattern(t){const e=t.match(/^\:.+?\{(.+)\}$/);if(e){return"("+e[1]+")"}return"(.+)"}getParamName(t){const e=t.match(/^\:([^\{\}]+)/);if(e){return e[1]}}noRoute(){return null}search(t){let e=this;const n={};for(const r of this.splitPath(t)){const o=e.children[r];if(o){e=o;continue}if(Object.keys(e.children).length==0){if(e.label!=r){return this.noRoute()}break}let t=false;for(const s in e.children){if(s=="*"){e=e.children[s];t=true;break}if(s.match(/^:/)){const i=this.getPattern(s);const c=r.match(new RegExp(i));if(c){const h=this.getParamName(s);n[h]=c[0];e=e.children[s];t=true;break}return this.noRoute()}}if(t==false){return this.noRoute()}}return[e.stuff,n]}}module.exports=Router;
@@ -1,17 +0,0 @@
1
- const Node = require('./node')
2
-
3
- describe('Basic Usage', () => {
4
- const node = new Node()
5
- node.insert('get', '/', 'get root')
6
- it('get /', () => {
7
- expect(node.search('get', '/')).not.toBeNull()
8
- })
9
- })
10
-
11
- describe('Basic Usage', () => {
12
- const node = new Node()
13
- node.insert('get', '/hello', 'get hello')
14
- it('get /', () => {
15
- expect(node.search('get', '/')).toBeNull()
16
- })
17
- })