hono 0.0.4 → 0.0.8

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.
@@ -0,0 +1,24 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+
8
+ jobs:
9
+ ci:
10
+ runs-on: ubuntu-latest
11
+
12
+ strategy:
13
+ matrix:
14
+ node-version: [16.x]
15
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Use Node.js ${{ matrix.node-version }}
20
+ uses: actions/setup-node@v2
21
+ with:
22
+ node-version: ${{ matrix.node-version }}
23
+ - run: yarn install --frozen-lockfile
24
+ - run: npm test
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  Hono [炎] - Tiny web framework for Cloudflare Workers and others.
4
4
 
5
5
  ```js
6
- const app = Hono()
6
+ const { Hono } = require('hono')
7
+ const app = new Hono()
7
8
 
8
9
  app.get('/', () => new Response('Hono!!'))
9
10
 
@@ -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
+ sunder 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 Reponse('GET /wild/*/card')
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
@@ -91,6 +102,19 @@ app
91
102
 
92
103
  ## Middleware
93
104
 
105
+ ### Builtin Middleware
106
+
107
+ ```js
108
+ const { Hono, Middleware } = require('hono')
109
+
110
+ ...
111
+
112
+ app.use('*', Middleware.poweredBy)
113
+
114
+ ```
115
+
116
+ ### Custom Middleware
117
+
94
118
  ```js
95
119
  const logger = (c, next) => {
96
120
  console.log(`[${c.req.method}] ${c.req.url}`)
@@ -102,8 +126,8 @@ const addHeader = (c, next) => {
102
126
  c.res.headers.add('x-message', 'This is middleware!')
103
127
  }
104
128
 
105
- app = app.use('*', logger)
106
- app = app.use('/message/*', addHeader)
129
+ app.use('*', logger)
130
+ app.use('/message/*', addHeader)
107
131
 
108
132
  app.get('/message/hello', () => 'Hello Middleware!')
109
133
  ```
@@ -152,6 +176,7 @@ app.get('/entry/:id', (c) => {
152
176
 
153
177
  - koa <https://github.com/koajs/koa>
154
178
  - express <https://github.com/expressjs/express>
179
+ - oak <https://github.com/oakserver/oak>
155
180
  - itty-router <https://github.com/kwhitley/itty-router>
156
181
  - Sunder <https://github.com/SunderJS/sunder>
157
182
  - 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.4",
4
- "description": "Minimal web framework for Cloudflare Workers",
3
+ "version": "0.0.8",
4
+ "description": "Minimal web framework for Cloudflare Workers and Fastly Compute@Edge",
5
5
  "main": "src/hono.js",
6
6
  "scripts": {
7
- "test": "jest --verbose"
7
+ "test": "jest"
8
8
  },
9
9
  "author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
10
10
  "license": "MIT",
package/src/hono.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ declare class FetchEvent {}
2
+ declare class Request {}
3
+ declare class Response {}
4
+ declare class Context {}
5
+
6
+ declare class Node {}
7
+
8
+ declare class Router {
9
+ tempPath: string
10
+ node: Node
11
+
12
+ add(method: string, path: string, handlers: any[]): Node
13
+ match(method: string, path: string): Node
14
+ }
15
+
16
+ export class Hono {
17
+ router: Router
18
+ middlewareRouters: Router[]
19
+
20
+ getRouter(): Router
21
+ addRoute(method: string, args: any[]): Hono
22
+ matchRoute(method: string, path: string): Node
23
+ createContext(req: Request, res: Response): Context
24
+ dispatch(req: Request, res: Response): Response
25
+ handleEvent(event: FetchEvent): Response
26
+ fire(): void
27
+
28
+ notFound(): Response
29
+
30
+ route(path: string): Hono
31
+
32
+ use(path: string, middleware: any): void
33
+
34
+ all(path: string, handler: any): Hono
35
+ get(path: string, handler: any): Hono
36
+ post(path: string, handler: any): Hono
37
+ put(path: string, handler: any): Hono
38
+ head(path: string, handler: any): Hono
39
+ delete(path: string, handler: any): Hono
40
+ }
41
+
42
+ // XXX
43
+ declare interface BuiltinMiddleware {}
44
+
45
+ export class Middleware {
46
+ static poweredBy: BuiltinMiddleware
47
+ }
package/src/hono.js CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  const Node = require('./node')
4
4
  const compose = require('./compose')
5
- const filter = require('./middleware/filter')
5
+ const methods = require('./methods')
6
+ const defaultFilter = require('./middleware/defaultFilter')
7
+ const Middleware = require('./middleware')
8
+
9
+ const METHOD_NAME_OF_ALL = 'ALL'
6
10
 
7
11
  class Router {
8
12
  constructor() {
@@ -15,91 +19,99 @@ class Router {
15
19
  }
16
20
 
17
21
  match(method, path) {
18
- method = method.toLowerCase()
19
22
  return this.node.search(method, path)
20
23
  }
21
24
  }
22
25
 
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
- },
26
+ const getPathFromURL = (url) => {
27
+ // XXX
28
+ const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/)
29
+ return match[5]
36
30
  }
37
31
 
38
- class App {
32
+ class Hono {
39
33
  constructor() {
40
34
  this.router = new Router()
41
- this.middlewareRouter = new Router()
42
- this.middleware = []
35
+ this.middlewareRouters = []
36
+
37
+ for (const method of methods) {
38
+ this[method] = (...args) => {
39
+ return this.addRoute(method, ...args)
40
+ }
41
+ }
43
42
  }
44
43
 
45
- addRoute(method, path, ...args) {
46
- this.router.add(method, path, ...args)
47
- return WrappedApp(this)
44
+ all(...args) {
45
+ this.addRoute('ALL', ...args)
48
46
  }
49
47
 
50
- matchRoute(method, path) {
51
- return this.router.match(method, path)
48
+ getRouter() {
49
+ return this.router
50
+ }
51
+
52
+ addRoute(method, ...args) {
53
+ method = method.toUpperCase()
54
+ if (args.length === 1) {
55
+ this.router.add(method, this.router.tempPath, ...args)
56
+ } else {
57
+ this.router.add(method, ...args)
58
+ }
59
+ return this
52
60
  }
53
61
 
54
62
  route(path) {
55
63
  this.router.tempPath = path
56
- return WrappedApp(this)
64
+ return this
57
65
  }
58
66
 
59
67
  use(path, middleware) {
60
- middleware = [middleware]
61
- const result = this.middlewareRouter.match('all', path)
62
- if (result) {
63
- middleware.push(...result.handler)
64
- }
65
- this.middlewareRouter.add('all', path, ...middleware)
68
+ const router = new Router()
69
+ router.add(METHOD_NAME_OF_ALL, path, middleware)
70
+ this.middlewareRouters.push(router)
71
+ }
72
+
73
+ async matchRoute(method, path) {
74
+ return this.router.match(method, path)
66
75
  }
67
76
 
68
77
  // XXX
69
- createContext(req, res) {
70
- return { req: req, res: res }
78
+ async createContext(req, res) {
79
+ return {
80
+ req: req,
81
+ res: res,
82
+ newResponse: (params) => {
83
+ return new Response(params)
84
+ },
85
+ }
71
86
  }
72
87
 
73
88
  async dispatch(request, response) {
74
- const url = new URL(request.url)
75
- const [method, path] = [request.method, url.pathname]
89
+ const [method, path] = [request.method, getPathFromURL(request.url)]
76
90
 
77
- const result = this.matchRoute(method, path)
91
+ const result = await this.matchRoute(method, path)
78
92
  if (!result) return this.notFound()
79
93
 
80
94
  request.params = (key) => result.params[key]
81
95
 
82
- const middleware = [filter]
83
- const mwResult = this.middlewareRouter.match('all', path)
84
- if (mwResult) {
85
- middleware.push(...mwResult.handler)
86
- }
96
+ let handler = result.handler[0] // XXX
87
97
 
88
- let handler
89
- for (const resultHandler of result.handler) {
90
- if (resultHandler) {
91
- handler = resultHandler
98
+ const middleware = [defaultFilter] // add defaultFilter later
99
+
100
+ for (const mr of this.middlewareRouters) {
101
+ const mwResult = mr.match(METHOD_NAME_OF_ALL, path)
102
+ if (mwResult) {
103
+ middleware.push(...mwResult.handler)
92
104
  }
93
105
  }
94
106
 
95
- let wrappedHandler = (context, next) => {
107
+ let wrappedHandler = async (context, next) => {
96
108
  context.res = handler(context)
97
109
  next()
98
110
  }
99
111
 
100
112
  middleware.push(wrappedHandler)
101
113
  const composed = compose(middleware)
102
- const c = this.createContext(request, response)
114
+ const c = await this.createContext(request, response)
103
115
 
104
116
  composed(c)
105
117
 
@@ -107,7 +119,7 @@ class App {
107
119
  }
108
120
 
109
121
  async handleEvent(event) {
110
- return await this.dispatch(event.request, new Response())
122
+ return this.dispatch(event.request, {}) // XXX
111
123
  }
112
124
 
113
125
  fire() {
@@ -121,8 +133,10 @@ class App {
121
133
  }
122
134
  }
123
135
 
124
- const WrappedApp = (router = new App()) => {
125
- return new Proxy(router, proxyHandler)
126
- }
136
+ // Default Export
137
+ module.exports = Hono
138
+ exports = module.exports
127
139
 
128
- module.exports = WrappedApp
140
+ // Named Export
141
+ exports.Hono = Hono
142
+ exports.Middleware = Middleware
package/src/hono.test.js CHANGED
@@ -1,8 +1,9 @@
1
- const Hono = require('./hono')
2
1
  const fetch = require('node-fetch')
2
+ const { Hono } = require('./hono')
3
3
 
4
4
  describe('GET Request', () => {
5
- const app = Hono()
5
+ const app = new Hono()
6
+
6
7
  app.get('/hello', () => {
7
8
  return new fetch.Response('hello', {
8
9
  status: 200,
@@ -29,7 +30,7 @@ describe('GET Request', () => {
29
30
  })
30
31
 
31
32
  describe('params and query', () => {
32
- const app = Hono()
33
+ const app = new Hono()
33
34
 
34
35
  app.get('/entry/:id', async (c) => {
35
36
  const id = await c.req.params('id')
@@ -60,29 +61,55 @@ describe('params and query', () => {
60
61
  })
61
62
 
62
63
  describe('Middleware', () => {
63
- const app = Hono()
64
+ const app = new Hono()
64
65
 
65
- const logger = (c, next) => {
66
+ const logger = async (c, next) => {
66
67
  console.log(`${c.req.method} : ${c.req.url}`)
67
68
  next()
68
69
  }
69
70
 
70
- const customHeader = (c, next) => {
71
+ const rootHeader = async (c, next) => {
72
+ next()
73
+ await c.res.headers.append('x-custom', 'root')
74
+ }
75
+
76
+ const customHeader = async (c, next) => {
77
+ next()
78
+ await c.res.headers.append('x-message', 'custom-header')
79
+ }
80
+ const customHeader2 = async (c, next) => {
71
81
  next()
72
- c.res.headers.append('x-message', 'custom-header')
82
+ await c.res.headers.append('x-message-2', 'custom-header-2')
73
83
  }
74
84
 
75
85
  app.use('*', logger)
86
+ app.use('*', rootHeader)
76
87
  app.use('/hello', customHeader)
88
+ app.use('/hello/*', customHeader2)
77
89
  app.get('/hello', () => {
78
90
  return new fetch.Response('hello')
79
91
  })
92
+ app.get('/hello/:message', (c) => {
93
+ const message = c.req.params('message')
94
+ return new fetch.Response(`${message}`)
95
+ })
80
96
 
81
97
  it('logging and custom header', async () => {
82
98
  let req = new fetch.Request('https://example.com/hello')
83
99
  let res = await app.dispatch(req)
84
100
  expect(res.status).toBe(200)
85
101
  expect(await res.text()).toBe('hello')
102
+ expect(await res.headers.get('x-custom')).toBe('root')
86
103
  expect(await res.headers.get('x-message')).toBe('custom-header')
104
+ expect(await res.headers.get('x-message-2')).toBe('custom-header-2')
105
+ })
106
+
107
+ it('logging and custom header with named params', async () => {
108
+ let req = new fetch.Request('https://example.com/hello/message')
109
+ let res = await app.dispatch(req)
110
+ expect(res.status).toBe(200)
111
+ expect(await res.text()).toBe('message')
112
+ expect(await res.headers.get('x-custom')).toBe('root')
113
+ expect(await res.headers.get('x-message-2')).toBe('custom-header-2')
87
114
  })
88
115
  })
package/src/methods.js ADDED
@@ -0,0 +1,30 @@
1
+ const methods = [
2
+ 'get',
3
+ 'post',
4
+ 'put',
5
+ 'head',
6
+ 'delete',
7
+ 'options',
8
+ 'trace',
9
+ 'copy',
10
+ 'lock',
11
+ 'mkcol',
12
+ 'move',
13
+ 'patch',
14
+ 'purge',
15
+ 'propfind',
16
+ 'proppatch',
17
+ 'unlock',
18
+ 'report',
19
+ 'mkactivity',
20
+ 'checkout',
21
+ 'merge',
22
+ 'm-search',
23
+ 'notify',
24
+ 'subscribe',
25
+ 'unsubscribe',
26
+ 'search',
27
+ 'connect',
28
+ ]
29
+
30
+ module.exports = methods
@@ -1,19 +1,19 @@
1
- const filter = (c, next) => {
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 Response(c.res, {
10
+ c.res = new Reponse(c.res, {
11
11
  status: 200,
12
12
  headers: {
13
- 'Content-Type': 'text/plain',
13
+ 'Conten-Type': 'text/plain',
14
14
  },
15
15
  })
16
16
  }
17
17
  }
18
18
 
19
- module.exports = filter
19
+ module.exports = defaultFilter
@@ -0,0 +1,6 @@
1
+ const poweredBy = async (c, next) => {
2
+ next()
3
+ await c.res.headers.append('X-Powered-By', 'Hono')
4
+ }
5
+
6
+ module.exports = poweredBy
@@ -0,0 +1,17 @@
1
+ const fetch = require('node-fetch')
2
+ const { Hono, Middleware } = require('../hono')
3
+
4
+ describe('Powered by Middleware', () => {
5
+ const app = new Hono()
6
+
7
+ app.use('*', Middleware.poweredBy)
8
+ app.get('/', () => new fetch.Response('root'))
9
+
10
+ it('Response headers include X-Powered-By', async () => {
11
+ let req = new fetch.Request('https://example.com/')
12
+ let res = await app.dispatch(req, new fetch.Response())
13
+ expect(res).not.toBeNull()
14
+ expect(res.status).toBe(200)
15
+ expect(res.headers.get('X-Powered-By')).toBe('Hono')
16
+ })
17
+ })
@@ -0,0 +1,7 @@
1
+ const poweredBy = require('./middleware/poweredBy')
2
+
3
+ function Middleware() {}
4
+
5
+ Middleware.poweredBy = poweredBy
6
+
7
+ module.exports = Middleware
@@ -0,0 +1,17 @@
1
+ const fetch = require('node-fetch')
2
+ const { Hono, Middleware } = require('./hono')
3
+
4
+ describe('Builtin Middleware', () => {
5
+ const app = new Hono()
6
+
7
+ app.use('*', Middleware.poweredBy)
8
+ app.get('/', () => new fetch.Response('root'))
9
+
10
+ it('Builtin Powered By Middleware', async () => {
11
+ let req = new fetch.Request('https://example.com/')
12
+ let res = await app.dispatch(req, new fetch.Response())
13
+ expect(res).not.toBeNull()
14
+ expect(res.status).toBe(200)
15
+ expect(res.headers.get('X-Powered-By')).toBe('Hono')
16
+ })
17
+ })
package/src/node.js CHANGED
@@ -1,131 +1,97 @@
1
- const methodNameOfAll = 'all'
1
+ 'use strict'
2
2
 
3
- class Result {
4
- constructor({ handler, params } = {}) {
5
- this.handler = handler || []
6
- this.params = params || {}
7
- }
3
+ const { splitPath, getPattern } = require('./util')
4
+
5
+ const METHOD_NAME_OF_ALL = 'ALL'
6
+
7
+ const createResult = (handler, params) => {
8
+ return { handler: handler, params: params }
8
9
  }
9
10
 
10
- class Node {
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
- }
11
+ const noRoute = () => {
12
+ return null
13
+ }
19
14
 
20
- insert(method, path, handler) {
21
- let curNode = this
22
- const ps = this.splitPath(path)
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
15
+ function Node(method, handler, children) {
16
+ this.children = children || {}
17
+ this.method = {}
18
+ if (method && handler) {
19
+ this.method[method] = handler
36
20
  }
21
+ this.middlewares = []
22
+ }
37
23
 
38
- splitPath(path) {
39
- let ps = ['/']
40
- for (const p of path.split('/')) {
41
- if (p) {
42
- ps.push(p)
43
- }
24
+ Node.prototype.insert = function (method, path, handler) {
25
+ let curNode = this
26
+ const parts = splitPath(path)
27
+ for (let i = 0; i < parts.length; i++) {
28
+ const p = parts[i]
29
+ if (Object.keys(curNode.children).includes(p)) {
30
+ curNode = curNode.children[p]
31
+ continue
44
32
  }
45
- return ps
33
+ curNode.children[p] = new Node(method, handler)
34
+ curNode = curNode.children[p]
46
35
  }
36
+ curNode.method[method] = handler
37
+ return curNode
38
+ }
47
39
 
48
- getPattern(label) {
49
- // :id{[0-9]+} [0-9]+$
50
- // :id → (.+)
51
- const match = label.match(/^\:.+?\{(.+)\}$/)
52
- if (match) {
53
- return '(' + match[1] + ')'
54
- }
55
- return '(.+)'
56
- }
40
+ Node.prototype.search = function (method, path) {
41
+ let curNode = this
57
42
 
58
- getParamName(label) {
59
- const match = label.match(/^\:([^\{\}]+)/)
60
- if (match) {
61
- return match[1]
62
- }
63
- }
43
+ const params = {}
44
+ const parts = splitPath(path)
64
45
 
65
- search(method, path) {
66
- let curNode = this
67
- let [handler, params] = [, {}]
68
-
69
- if (path === '/') {
70
- const root = this.children['/']
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
- }
46
+ for (let i = 0; i < parts.length; i++) {
47
+ const p = parts[i]
48
+ const nextNode = curNode.children[p]
49
+ if (nextNode) {
50
+ curNode = nextNode
51
+ continue
80
52
  }
81
53
 
82
- for (const p of this.splitPath(path)) {
83
- let nextNode = curNode.children[p]
84
-
85
- if (nextNode) {
86
- curNode = nextNode
87
- continue
54
+ let isWildcard = false
55
+ let isParamMatch = false
56
+ const keys = Object.keys(curNode.children)
57
+ for (let j = 0; j < keys.length; j++) {
58
+ const key = keys[j]
59
+ // Wildcard
60
+ if (key === '*') {
61
+ curNode = curNode.children['*']
62
+ isWildcard = true
63
+ break
88
64
  }
89
-
90
- let isParamMatch = false
91
- for (const key in curNode.children) {
92
- if (key === '*') {
93
- // Wildcard
65
+ const pattern = getPattern(key)
66
+ if (pattern) {
67
+ const match = p.match(new RegExp(pattern[1]))
68
+ if (match) {
69
+ const k = pattern[0]
70
+ params[k] = match[1]
94
71
  curNode = curNode.children[key]
95
72
  isParamMatch = true
96
73
  break
97
74
  }
98
- if (key.match(/^:/)) {
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()
75
+ return noRoute()
113
76
  }
114
77
  }
115
78
 
116
- handler =
117
- handler || curNode.method[methodNameOfAll] || curNode.method[method]
79
+ if (isWildcard) {
80
+ break
81
+ }
118
82
 
119
- if (!handler) {
120
- return this.noRoute()
83
+ if (isParamMatch === false) {
84
+ return noRoute()
121
85
  }
122
- const res = new Result({ handler: handler, params: params })
123
- return res
124
86
  }
125
87
 
126
- noRoute() {
127
- return null
88
+ const handler = curNode.method[METHOD_NAME_OF_ALL] || curNode.method[method]
89
+
90
+ if (!handler) {
91
+ return noRoute()
128
92
  }
93
+
94
+ return createResult(handler, params)
129
95
  }
130
96
 
131
97
  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
- expect(node.search('get', '/')).not.toBeNull()
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()
@@ -128,7 +123,7 @@ describe('Regexp', () => {
128
123
 
129
124
  describe('All', () => {
130
125
  const node = new Node()
131
- node.insert('all', '/all-methods', 'all methods')
126
+ node.insert('ALL', '/all-methods', 'all methods') // ALL
132
127
  it('/all-methods', () => {
133
128
  res = node.search('get', '/all-methods')
134
129
  expect(res).not.toBeNull()
@@ -1,84 +1,88 @@
1
- const App = require('./hono')
1
+ const { Hono } = require('./hono')
2
2
 
3
3
  describe('Basic Usage', () => {
4
- let router = App()
4
+ const router = new Hono()
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
  })
25
25
 
26
26
  describe('Complex', () => {
27
- let router = App()
27
+ let router = new Hono()
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
  })
57
57
 
58
58
  describe('Chained Route', () => {
59
- let router = App()
59
+ let router = new Hono()
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
- res = router.matchRoute('GET', '/api/book')
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
- res = router.matchRoute('POST', '/api/book')
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
- res = router.matchRoute('PUT', '/api/book')
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
- res = router.matchRoute('DELETE', '/api/book')
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
+ }
@@ -0,0 +1,29 @@
1
+ const { splitPath, getPattern } = 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
+ })
@@ -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,13 +0,0 @@
1
- {
2
- "name": "sandbox",
3
- "version": "1.0.0",
4
- "lockfileVersion": 2,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "sandbox",
9
- "version": "1.0.0",
10
- "license": "ISC"
11
- }
12
- }
13
- }
@@ -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
- }
@@ -1,8 +0,0 @@
1
- name = "hono-example-basic"
2
- type = "webpack"
3
- route = ''
4
- zone_id = ''
5
- usage_model = ''
6
- compatibility_flags = []
7
- workers_dev = true
8
- compatibility_date = "2021-12-15"