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.
- package/.github/workflows/ci.yml +24 -0
- package/README.md +31 -6
- package/package.json +3 -3
- package/src/hono.d.ts +47 -0
- package/src/hono.js +65 -51
- package/src/hono.test.js +34 -7
- package/src/methods.js +30 -0
- package/src/middleware/{filter.js → defaultFilter.js} +5 -5
- package/src/middleware/poweredBy.js +6 -0
- package/src/middleware/poweredBy.test.js +17 -0
- package/src/middleware.js +7 -0
- package/src/middleware.test.js +17 -0
- package/src/node.js +68 -102
- package/src/node.test.js +18 -23
- package/src/router.test.js +28 -24
- 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
|
@@ -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
|
|
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
|
|
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
|
|
106
|
-
app
|
|
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
|
-
"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
|
|
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
|
|
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
|
|
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
|
-
},
|
|
26
|
+
const getPathFromURL = (url) => {
|
|
27
|
+
// XXX
|
|
28
|
+
const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/)
|
|
29
|
+
return match[5]
|
|
36
30
|
}
|
|
37
31
|
|
|
38
|
-
class
|
|
32
|
+
class Hono {
|
|
39
33
|
constructor() {
|
|
40
34
|
this.router = new Router()
|
|
41
|
-
this.
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
this.
|
|
47
|
-
return WrappedApp(this)
|
|
44
|
+
all(...args) {
|
|
45
|
+
this.addRoute('ALL', ...args)
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
return this.router
|
|
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
|
|
64
|
+
return this
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
use(path, middleware) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
// Default Export
|
|
137
|
+
module.exports = Hono
|
|
138
|
+
exports = module.exports
|
|
127
139
|
|
|
128
|
-
|
|
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
|
|
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
|
|
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
|
|
@@ -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,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
|
-
|
|
1
|
+
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
if (match) {
|
|
61
|
-
return match[1]
|
|
62
|
-
}
|
|
63
|
-
}
|
|
43
|
+
const params = {}
|
|
44
|
+
const parts = splitPath(path)
|
|
64
45
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
79
|
+
if (isWildcard) {
|
|
80
|
+
break
|
|
81
|
+
}
|
|
118
82
|
|
|
119
|
-
if (
|
|
120
|
-
return
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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('
|
|
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()
|
package/src/router.test.js
CHANGED
|
@@ -1,84 +1,88 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { Hono } = require('./hono')
|
|
2
2
|
|
|
3
3
|
describe('Basic Usage', () => {
|
|
4
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 } = 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
|
-
}
|