hono 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -19
- package/dist/context.d.ts +1 -1
- package/dist/context.js +24 -6
- package/dist/hono.d.ts +21 -29
- package/dist/hono.js +27 -65
- package/dist/hono.test.js +133 -69
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/middleware/cookie/index.d.ts +4 -1
- package/dist/middleware/cookie/index.js +9 -4
- package/dist/middleware/cookie/index.test.js +69 -45
- package/dist/utils/url.js +4 -0
- package/dist/utils/url.test.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Fastest is hono - regexp-router
|
|
|
52
52
|
Routers used in Hono are really smart.
|
|
53
53
|
|
|
54
54
|
- **TrieRouter**(default) - Implemented with Trie tree structure.
|
|
55
|
-
- **RegExpRouter** - Match
|
|
55
|
+
- **RegExpRouter** - Match the route with using one big Regex made before dispatch.
|
|
56
56
|
|
|
57
57
|
## Hono in 1 minute
|
|
58
58
|
|
|
@@ -93,16 +93,14 @@ app.use('*', etag(), logger())
|
|
|
93
93
|
And, the routing of Hono is so flexible. It's easy to construct large web applications.
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
|
-
import { Hono
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
const app = new Hono()
|
|
96
|
+
import { Hono } from 'hono'
|
|
97
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
100
98
|
|
|
101
|
-
const v1 = new
|
|
99
|
+
const v1 = new Hono()
|
|
102
100
|
v1.get('/posts', (c) => {
|
|
103
101
|
return c.text('list pots')
|
|
104
102
|
})
|
|
105
|
-
.post(
|
|
103
|
+
.post(basicAuth({ username, password }), (c) => {
|
|
106
104
|
return c.text('created!', 201)
|
|
107
105
|
})
|
|
108
106
|
.get('/posts/:id', (c) => {
|
|
@@ -110,6 +108,7 @@ v1.get('/posts', (c) => {
|
|
|
110
108
|
return c.text(`your id is ${id}`)
|
|
111
109
|
})
|
|
112
110
|
|
|
111
|
+
const app = new Hono()
|
|
113
112
|
app.route('/v1', v1)
|
|
114
113
|
```
|
|
115
114
|
|
|
@@ -140,7 +139,7 @@ An instance of `Hono` has these methods.
|
|
|
140
139
|
|
|
141
140
|
- app.**HTTP_METHOD**(\[path,\] handler|middleware...)
|
|
142
141
|
- app.**all**(\[path,\] handler|middleware...)
|
|
143
|
-
- app.**route**(path, \[
|
|
142
|
+
- app.**route**(path, \[app\])
|
|
144
143
|
- app.**use**(\[path,\] middleware)
|
|
145
144
|
- app.**notFound**(handler)
|
|
146
145
|
- app.**onError**(err, handler)
|
|
@@ -156,6 +155,8 @@ An instance of `Hono` has these methods.
|
|
|
156
155
|
// HTTP Methods
|
|
157
156
|
app.get('/', (c) => c.text('GET /'))
|
|
158
157
|
app.post('/', (c) => c.text('POST /'))
|
|
158
|
+
app.put('/', (c) => c.text('PUT /'))
|
|
159
|
+
app.delete('/', (c) => c.text('DELETE /'))
|
|
159
160
|
|
|
160
161
|
// Wildcard
|
|
161
162
|
app.get('/wild/*/card', (c) => {
|
|
@@ -175,12 +176,20 @@ app.get('/user/:name', (c) => {
|
|
|
175
176
|
})
|
|
176
177
|
```
|
|
177
178
|
|
|
179
|
+
or all parameters at once:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
app.get('/posts/:id/comment/:comment_id', (c) => {
|
|
183
|
+
const { id, comment_id } = c.req.param()
|
|
184
|
+
...
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
178
188
|
### Regexp
|
|
179
189
|
|
|
180
190
|
```ts
|
|
181
191
|
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
|
|
182
|
-
const date = c.req.param(
|
|
183
|
-
const title = c.req.param('title')
|
|
192
|
+
const { date, title } = c.req.param()
|
|
184
193
|
...
|
|
185
194
|
})
|
|
186
195
|
```
|
|
@@ -219,12 +228,12 @@ app.get('/fetch-url', async (c) => {
|
|
|
219
228
|
})
|
|
220
229
|
```
|
|
221
230
|
|
|
222
|
-
##
|
|
231
|
+
## Grouping
|
|
223
232
|
|
|
224
|
-
`
|
|
233
|
+
Group the routes with `Hono` instance and add them to the main app with `route` method.
|
|
225
234
|
|
|
226
235
|
```ts
|
|
227
|
-
const book = new
|
|
236
|
+
const book = new Hono()
|
|
228
237
|
|
|
229
238
|
book.get('/', (c) => c.text('List Books')) // GET /book
|
|
230
239
|
book.get('/:id', (c) => {
|
|
@@ -234,6 +243,7 @@ book.get('/:id', (c) => {
|
|
|
234
243
|
})
|
|
235
244
|
book.post('/', (c) => c.text('Create Book')) // POST /book
|
|
236
245
|
|
|
246
|
+
const app = new Hono()
|
|
237
247
|
app.route('/book', book)
|
|
238
248
|
```
|
|
239
249
|
|
|
@@ -338,6 +348,12 @@ app.get('/search', (c) => {
|
|
|
338
348
|
...
|
|
339
349
|
})
|
|
340
350
|
|
|
351
|
+
// Get all params at once
|
|
352
|
+
app.get('/search', (c) => {
|
|
353
|
+
const { q, limit, offset } = c.req.query()
|
|
354
|
+
...
|
|
355
|
+
})
|
|
356
|
+
|
|
341
357
|
// Captured params
|
|
342
358
|
app.get('/entry/:id', (c) => {
|
|
343
359
|
const id = c.req.param('id')
|
|
@@ -427,8 +443,8 @@ app.get('/redirect-permanently', (c) => c.redirect('/', 301))
|
|
|
427
443
|
|
|
428
444
|
```ts
|
|
429
445
|
// Response object
|
|
430
|
-
app.use('/', (c, next) => {
|
|
431
|
-
next()
|
|
446
|
+
app.use('/', async (c, next) => {
|
|
447
|
+
await next()
|
|
432
448
|
c.res.headers.append('X-Debug', 'Debug message')
|
|
433
449
|
})
|
|
434
450
|
```
|
|
@@ -437,11 +453,11 @@ app.use('/', (c, next) => {
|
|
|
437
453
|
|
|
438
454
|
```ts
|
|
439
455
|
// FetchEvent object
|
|
440
|
-
app.
|
|
456
|
+
app.get('/foo', async (c) => {
|
|
441
457
|
c.event.waitUntil(
|
|
442
|
-
|
|
458
|
+
c.env.KV.put(key, data)
|
|
443
459
|
)
|
|
444
|
-
|
|
460
|
+
...
|
|
445
461
|
})
|
|
446
462
|
```
|
|
447
463
|
|
|
@@ -559,7 +575,62 @@ To generate a project skelton, run this command.
|
|
|
559
575
|
npx create-cloudflare my-app https://github.com/honojs/hono-minimal
|
|
560
576
|
```
|
|
561
577
|
|
|
562
|
-
##
|
|
578
|
+
## Practical Example
|
|
579
|
+
|
|
580
|
+
How about writing web API with Hono?
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import { Hono } from 'hono'
|
|
584
|
+
import { cors } from 'hono/cors'
|
|
585
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
586
|
+
import { prettyJSON } from 'hono/pretty-json'
|
|
587
|
+
import { getPosts, getPosts, createPost } from './model'
|
|
588
|
+
|
|
589
|
+
const app = new Hono()
|
|
590
|
+
app.get('/', (c) => c.text('Pretty Blog API'))
|
|
591
|
+
app.use('*', prettyJSON())
|
|
592
|
+
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404))
|
|
593
|
+
|
|
594
|
+
export interface Bindings {
|
|
595
|
+
USERNAME: string
|
|
596
|
+
PASSWORD: string
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const api = new Hono<Bindings>()
|
|
600
|
+
|
|
601
|
+
api.get('/posts', (c) => {
|
|
602
|
+
const { limit, offset } = c.req.query()
|
|
603
|
+
const posts = getPosts({ limit, offset })
|
|
604
|
+
return c.json({ posts })
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
api.get('/posts/:id', (c) => {
|
|
608
|
+
const id = c.req.param('id')
|
|
609
|
+
const post = getPost({ id })
|
|
610
|
+
return c.json({ post })
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
api.post(
|
|
614
|
+
'/posts',
|
|
615
|
+
async (c, next) => {
|
|
616
|
+
const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD })
|
|
617
|
+
await auth(c, next)
|
|
618
|
+
},
|
|
619
|
+
async (c) => {
|
|
620
|
+
const post = await c.req.json<POST>()
|
|
621
|
+
const ok = createPost({ post })
|
|
622
|
+
return c.json({ ok })
|
|
623
|
+
}
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
app.use('/posts/*', cors())
|
|
627
|
+
|
|
628
|
+
app.route('/api', api)
|
|
629
|
+
|
|
630
|
+
export default app
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## Other Examples
|
|
563
634
|
|
|
564
635
|
- Hono Examples - <https://github.com/honojs/examples>
|
|
565
636
|
|
package/dist/context.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { StatusCode } from './utils/http-status';
|
|
|
3
3
|
declare type Headers = Record<string, string>;
|
|
4
4
|
declare type Data = string | ArrayBuffer | ReadableStream;
|
|
5
5
|
export declare type Env = Record<string, any>;
|
|
6
|
-
export declare class Context<RequestParamKeyType = string, E = Env> {
|
|
6
|
+
export declare class Context<RequestParamKeyType extends string = string, E = Env> {
|
|
7
7
|
req: Request<RequestParamKeyType>;
|
|
8
8
|
res: Response;
|
|
9
9
|
env: E;
|
package/dist/context.js
CHANGED
|
@@ -16,13 +16,31 @@ class Context {
|
|
|
16
16
|
Object.assign(this, opts);
|
|
17
17
|
}
|
|
18
18
|
initRequest(req) {
|
|
19
|
-
req.header = (name) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
req.header = ((name) => {
|
|
20
|
+
if (name) {
|
|
21
|
+
return req.headers.get(name);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const [key, value] of req.headers) {
|
|
26
|
+
result[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
req.query = ((key) => {
|
|
23
32
|
const url = new URL(req.url);
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
if (key) {
|
|
34
|
+
return url.searchParams.get(key);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const [key, value] of url.searchParams) {
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
26
44
|
return req;
|
|
27
45
|
}
|
|
28
46
|
header(name, value) {
|
package/dist/hono.d.ts
CHANGED
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
import { Context } from './context';
|
|
3
3
|
import type { Env } from './context';
|
|
4
4
|
import type { Router } from './router';
|
|
5
|
-
import { METHOD_NAME_ALL_LOWERCASE } from './router';
|
|
6
5
|
declare global {
|
|
7
|
-
interface Request<ParamKeyType = string> {
|
|
8
|
-
param:
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
interface Request<ParamKeyType extends string = string> {
|
|
7
|
+
param: {
|
|
8
|
+
(key: ParamKeyType): string;
|
|
9
|
+
(): Record<ParamKeyType, string>;
|
|
10
|
+
};
|
|
11
|
+
query: {
|
|
12
|
+
(key: string): string;
|
|
13
|
+
(): Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
header: {
|
|
16
|
+
(name: string): string;
|
|
17
|
+
(): Record<string, string>;
|
|
18
|
+
};
|
|
11
19
|
}
|
|
12
20
|
}
|
|
13
|
-
export declare type Handler<RequestParamKeyType = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | void | Promise<void>;
|
|
21
|
+
export declare type Handler<RequestParamKeyType extends string = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | void | Promise<void>;
|
|
14
22
|
export declare type NotFoundHandler<E = Env> = (c: Context<string, E>) => Response;
|
|
15
23
|
export declare type ErrorHandler<E = Env> = (err: Error, c: Context<string, E>) => Response;
|
|
16
24
|
export declare type Next = () => Promise<void>;
|
|
@@ -23,29 +31,11 @@ interface HandlerInterface<T extends string, E = Env, U = Hono<E, T>> {
|
|
|
23
31
|
<Path extends string>(...handlers: Handler<ParamKeys<Path> extends never ? string : ParamKeys<Path>, E>[]): U;
|
|
24
32
|
(...handlers: Handler<string, E>[]): U;
|
|
25
33
|
}
|
|
26
|
-
|
|
27
|
-
declare type Methods = typeof methods[number] | typeof METHOD_NAME_ALL_LOWERCASE;
|
|
28
|
-
interface Routing<E extends Env> {
|
|
34
|
+
interface Route<E extends Env> {
|
|
29
35
|
path: string;
|
|
30
|
-
method:
|
|
36
|
+
method: string;
|
|
31
37
|
handler: Handler<string, E>;
|
|
32
38
|
}
|
|
33
|
-
declare const Route_base: new <E_1 extends Env, T extends string, U>() => {
|
|
34
|
-
delete: HandlerInterface<T, E_1, U>;
|
|
35
|
-
get: HandlerInterface<T, E_1, U>;
|
|
36
|
-
all: HandlerInterface<T, E_1, U>;
|
|
37
|
-
post: HandlerInterface<T, E_1, U>;
|
|
38
|
-
put: HandlerInterface<T, E_1, U>;
|
|
39
|
-
head: HandlerInterface<T, E_1, U>;
|
|
40
|
-
options: HandlerInterface<T, E_1, U>;
|
|
41
|
-
patch: HandlerInterface<T, E_1, U>;
|
|
42
|
-
};
|
|
43
|
-
export declare class Route<E = Env, P extends string = ''> extends Route_base<E, P, Route<E, P>> {
|
|
44
|
-
#private;
|
|
45
|
-
routes: Routing<E>[];
|
|
46
|
-
constructor();
|
|
47
|
-
private add;
|
|
48
|
-
}
|
|
49
39
|
declare const Hono_base: new <E_1 extends Env, T extends string, U>() => {
|
|
50
40
|
delete: HandlerInterface<T, E_1, U>;
|
|
51
41
|
get: HandlerInterface<T, E_1, U>;
|
|
@@ -56,17 +46,19 @@ declare const Hono_base: new <E_1 extends Env, T extends string, U>() => {
|
|
|
56
46
|
options: HandlerInterface<T, E_1, U>;
|
|
57
47
|
patch: HandlerInterface<T, E_1, U>;
|
|
58
48
|
};
|
|
59
|
-
export declare class Hono<E = Env, P extends string = ''> extends Hono_base<E, P, Hono<E, P>> {
|
|
60
|
-
#private;
|
|
49
|
+
export declare class Hono<E = Env, P extends string = '/'> extends Hono_base<E, P, Hono<E, P>> {
|
|
61
50
|
readonly routerClass: {
|
|
62
51
|
new (): Router<any>;
|
|
63
52
|
};
|
|
64
53
|
readonly strict: boolean;
|
|
54
|
+
private _router;
|
|
55
|
+
private _tempPath;
|
|
65
56
|
private path;
|
|
57
|
+
routes: Route<E>[];
|
|
66
58
|
constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
|
|
67
59
|
private notFoundHandler;
|
|
68
60
|
private errorHandler;
|
|
69
|
-
route(path: string,
|
|
61
|
+
route(path: string, app?: Hono<any>): Hono<E, P>;
|
|
70
62
|
use(path: string, ...middleware: Handler<string, E>[]): Hono<E, P>;
|
|
71
63
|
use(...middleware: Handler<string, E>[]): Hono<E, P>;
|
|
72
64
|
onError(handler: ErrorHandler<E>): Hono<E, P>;
|
package/dist/hono.js
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
-
};
|
|
8
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
-
};
|
|
13
|
-
var _Route_path, _Hono_router, _Hono_tempPath;
|
|
14
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.Hono =
|
|
3
|
+
exports.Hono = void 0;
|
|
16
4
|
const compose_1 = require("./compose");
|
|
17
5
|
const context_1 = require("./context");
|
|
18
6
|
const router_1 = require("./router");
|
|
@@ -24,44 +12,13 @@ function defineDynamicClass() {
|
|
|
24
12
|
return class {
|
|
25
13
|
};
|
|
26
14
|
}
|
|
27
|
-
class Route extends defineDynamicClass() {
|
|
28
|
-
constructor() {
|
|
29
|
-
super();
|
|
30
|
-
this.routes = [];
|
|
31
|
-
_Route_path.set(this, '');
|
|
32
|
-
const allMethods = [...methods, router_2.METHOD_NAME_ALL_LOWERCASE];
|
|
33
|
-
allMethods.map((method) => {
|
|
34
|
-
this[method] = (args1, ...args) => {
|
|
35
|
-
if (typeof args1 === 'string') {
|
|
36
|
-
__classPrivateFieldSet(this, _Route_path, args1, "f");
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
this.add(method, __classPrivateFieldGet(this, _Route_path, "f"), args1);
|
|
40
|
-
}
|
|
41
|
-
args.map((handler) => {
|
|
42
|
-
if (typeof handler !== 'string') {
|
|
43
|
-
this.add(method, __classPrivateFieldGet(this, _Route_path, "f"), handler);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return this;
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
add(method, path, handler) {
|
|
51
|
-
const r = { path: path, method: method, handler: handler };
|
|
52
|
-
this.routes.push(r);
|
|
53
|
-
return this;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
exports.Route = Route;
|
|
57
|
-
_Route_path = new WeakMap();
|
|
58
15
|
class Hono extends defineDynamicClass() {
|
|
59
16
|
constructor(init = {}) {
|
|
60
17
|
super();
|
|
61
18
|
this.routerClass = trie_router_1.TrieRouter;
|
|
62
19
|
this.strict = true; // strict routing - default is true
|
|
63
|
-
|
|
64
|
-
|
|
20
|
+
this.path = '/';
|
|
21
|
+
this.routes = [];
|
|
65
22
|
this.notFoundHandler = (c) => {
|
|
66
23
|
const message = '404 Not Found';
|
|
67
24
|
return c.text(message, 404);
|
|
@@ -89,19 +46,17 @@ class Hono extends defineDynamicClass() {
|
|
|
89
46
|
};
|
|
90
47
|
});
|
|
91
48
|
Object.assign(this, init);
|
|
92
|
-
|
|
93
|
-
|
|
49
|
+
this._router = new this.routerClass();
|
|
50
|
+
this._tempPath = null;
|
|
94
51
|
}
|
|
95
|
-
route(path,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
route.routes.map((r) => {
|
|
101
|
-
newHono.addRoute(r.method, r.path, r.handler);
|
|
52
|
+
route(path, app) {
|
|
53
|
+
this._tempPath = path;
|
|
54
|
+
if (app) {
|
|
55
|
+
app.routes.map((r) => {
|
|
56
|
+
this.addRoute(r.method, r.path, r.handler);
|
|
102
57
|
});
|
|
103
58
|
}
|
|
104
|
-
return
|
|
59
|
+
return this;
|
|
105
60
|
}
|
|
106
61
|
use(arg1, ...handlers) {
|
|
107
62
|
if (typeof arg1 === 'string') {
|
|
@@ -125,22 +80,30 @@ class Hono extends defineDynamicClass() {
|
|
|
125
80
|
}
|
|
126
81
|
addRoute(method, path, handler) {
|
|
127
82
|
method = method.toUpperCase();
|
|
128
|
-
if (
|
|
129
|
-
path = (0, url_1.mergePath)(
|
|
83
|
+
if (this._tempPath) {
|
|
84
|
+
path = (0, url_1.mergePath)(this._tempPath, path);
|
|
130
85
|
}
|
|
131
|
-
|
|
86
|
+
this._router.add(method, path, handler);
|
|
87
|
+
const r = { path: path, method: method, handler: handler };
|
|
88
|
+
this.routes.push(r);
|
|
132
89
|
}
|
|
133
90
|
async matchRoute(method, path) {
|
|
134
|
-
return
|
|
91
|
+
return this._router.match(method, path);
|
|
135
92
|
}
|
|
136
93
|
async dispatch(request, event, env) {
|
|
137
94
|
const path = (0, url_1.getPathFromURL)(request.url, { strict: this.strict });
|
|
138
95
|
const method = request.method;
|
|
139
96
|
const result = await this.matchRoute(method, path);
|
|
140
|
-
request.param = (key) => {
|
|
141
|
-
if (result)
|
|
142
|
-
|
|
143
|
-
|
|
97
|
+
request.param = ((key) => {
|
|
98
|
+
if (result) {
|
|
99
|
+
if (key) {
|
|
100
|
+
return result.params[key];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return result.params;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
144
107
|
const handlers = result ? result.handlers : [this.notFoundHandler];
|
|
145
108
|
const c = new context_1.Context(request, { env: env, event: event, res: undefined });
|
|
146
109
|
c.notFound = () => this.notFoundHandler(c);
|
|
@@ -175,4 +138,3 @@ class Hono extends defineDynamicClass() {
|
|
|
175
138
|
}
|
|
176
139
|
}
|
|
177
140
|
exports.Hono = Hono;
|
|
178
|
-
_Hono_router = new WeakMap(), _Hono_tempPath = new WeakMap();
|
package/dist/hono.test.js
CHANGED
|
@@ -152,44 +152,71 @@ describe('Routing', () => {
|
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
describe('param and query', () => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
155
|
+
const apps = {};
|
|
156
|
+
apps['get by name'] = (() => {
|
|
157
|
+
const app = new hono_1.Hono();
|
|
158
|
+
app.get('/entry/:id', (c) => {
|
|
159
|
+
const id = c.req.param('id');
|
|
160
|
+
return c.text(`id is ${id}`);
|
|
161
|
+
});
|
|
162
|
+
app.get('/date/:date{[0-9]+}', (c) => {
|
|
163
|
+
const date = c.req.param('date');
|
|
164
|
+
return c.text(`date is ${date}`);
|
|
165
|
+
});
|
|
166
|
+
app.get('/search', (c) => {
|
|
167
|
+
const name = c.req.query('name');
|
|
168
|
+
return c.text(`name is ${name}`);
|
|
169
|
+
});
|
|
170
|
+
app.get('/add-header', (c) => {
|
|
171
|
+
const bar = c.req.header('X-Foo');
|
|
172
|
+
return c.text(`foo is ${bar}`);
|
|
173
|
+
});
|
|
174
|
+
return app;
|
|
175
|
+
})();
|
|
176
|
+
apps['get all as an object'] = (() => {
|
|
177
|
+
const app = new hono_1.Hono();
|
|
178
|
+
app.get('/entry/:id', (c) => {
|
|
179
|
+
const { id } = c.req.param();
|
|
180
|
+
return c.text(`id is ${id}`);
|
|
181
|
+
});
|
|
182
|
+
app.get('/date/:date{[0-9]+}', (c) => {
|
|
183
|
+
const { date } = c.req.param();
|
|
184
|
+
return c.text(`date is ${date}`);
|
|
185
|
+
});
|
|
186
|
+
app.get('/search', (c) => {
|
|
187
|
+
const { name } = c.req.query();
|
|
188
|
+
return c.text(`name is ${name}`);
|
|
189
|
+
});
|
|
190
|
+
app.get('/add-header', (c) => {
|
|
191
|
+
const { 'x-foo': bar } = c.req.header();
|
|
192
|
+
return c.text(`foo is ${bar}`);
|
|
193
|
+
});
|
|
194
|
+
return app;
|
|
195
|
+
})();
|
|
196
|
+
describe.each(Object.keys(apps))('%s', (name) => {
|
|
197
|
+
const app = apps[name];
|
|
198
|
+
it('param of /entry/:id is found', async () => {
|
|
199
|
+
const res = await app.request('http://localhost/entry/123');
|
|
200
|
+
expect(res.status).toBe(200);
|
|
201
|
+
expect(await res.text()).toBe('id is 123');
|
|
202
|
+
});
|
|
203
|
+
it('param of /date/:date is found', async () => {
|
|
204
|
+
const res = await app.request('http://localhost/date/0401');
|
|
205
|
+
expect(res.status).toBe(200);
|
|
206
|
+
expect(await res.text()).toBe('date is 0401');
|
|
207
|
+
});
|
|
208
|
+
it('query of /search?name=sam is found', async () => {
|
|
209
|
+
const res = await app.request('http://localhost/search?name=sam');
|
|
210
|
+
expect(res.status).toBe(200);
|
|
211
|
+
expect(await res.text()).toBe('name is sam');
|
|
212
|
+
});
|
|
213
|
+
it('/add-header header - X-Foo is Bar', async () => {
|
|
214
|
+
const req = new Request('http://localhost/add-header');
|
|
215
|
+
req.headers.append('X-Foo', 'Bar');
|
|
216
|
+
const res = await app.request(req);
|
|
217
|
+
expect(res.status).toBe(200);
|
|
218
|
+
expect(await res.text()).toBe('foo is Bar');
|
|
219
|
+
});
|
|
193
220
|
});
|
|
194
221
|
});
|
|
195
222
|
describe('Middleware', () => {
|
|
@@ -451,34 +478,57 @@ describe('Request methods with custom middleware', () => {
|
|
|
451
478
|
expect(res.headers.get('X-Header-2')).toBe('bar');
|
|
452
479
|
});
|
|
453
480
|
});
|
|
454
|
-
describe('
|
|
455
|
-
const app = new hono_1.Hono();
|
|
481
|
+
describe('Hono with `app.route`', () => {
|
|
456
482
|
describe('Basic', () => {
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
483
|
+
const app = new hono_1.Hono();
|
|
484
|
+
const api = new hono_1.Hono();
|
|
485
|
+
const middleware = new hono_1.Hono();
|
|
486
|
+
api.get('/posts', (c) => c.text('List'));
|
|
487
|
+
api.post('/posts', (c) => c.text('Create'));
|
|
488
|
+
api.get('/posts/:id', (c) => c.text(`GET ${c.req.param('id')}`));
|
|
489
|
+
api.use('*', async (c, next) => {
|
|
490
|
+
await next();
|
|
491
|
+
c.res.headers.append('x-custom-a', 'a');
|
|
465
492
|
});
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
493
|
+
app.route('/api', api);
|
|
494
|
+
middleware.use('*', async (c, next) => {
|
|
495
|
+
await next();
|
|
496
|
+
c.res.headers.append('x-custom-b', 'b');
|
|
470
497
|
});
|
|
471
|
-
|
|
472
|
-
|
|
498
|
+
app.route('/api', middleware);
|
|
499
|
+
it('Should return not found response', async () => {
|
|
500
|
+
const res = await app.request('http://localhost/');
|
|
473
501
|
expect(res.status).toBe(404);
|
|
474
502
|
});
|
|
475
|
-
it('Should return
|
|
476
|
-
const res = await app.request('http://localhost/
|
|
503
|
+
it('Should return not found response', async () => {
|
|
504
|
+
const res = await app.request('http://localhost/posts');
|
|
477
505
|
expect(res.status).toBe(404);
|
|
478
506
|
});
|
|
507
|
+
test('GET /api/posts', async () => {
|
|
508
|
+
const res = await app.request('http://localhost/api/posts');
|
|
509
|
+
expect(res.status).toBe(200);
|
|
510
|
+
expect(await res.text()).toBe('List');
|
|
511
|
+
});
|
|
512
|
+
test('Custom header by middleware', async () => {
|
|
513
|
+
const res = await app.request('http://localhost/api/posts');
|
|
514
|
+
expect(res.status).toBe(200);
|
|
515
|
+
expect(res.headers.get('x-custom-a')).toBe('a');
|
|
516
|
+
expect(res.headers.get('x-custom-b')).toBe('b');
|
|
517
|
+
});
|
|
518
|
+
test('POST /api/posts', async () => {
|
|
519
|
+
const res = await app.request('http://localhost/api/posts', { method: 'POST' });
|
|
520
|
+
expect(res.status).toBe(200);
|
|
521
|
+
expect(await res.text()).toBe('Create');
|
|
522
|
+
});
|
|
523
|
+
test('GET /api/posts/123', async () => {
|
|
524
|
+
const res = await app.request('http://localhost/api/posts/123');
|
|
525
|
+
expect(res.status).toBe(200);
|
|
526
|
+
expect(await res.text()).toBe('GET 123');
|
|
527
|
+
});
|
|
479
528
|
});
|
|
480
529
|
describe('Chaining', () => {
|
|
481
|
-
const
|
|
530
|
+
const app = new hono_1.Hono();
|
|
531
|
+
const route = new hono_1.Hono();
|
|
482
532
|
route.get('/post', (c) => c.text('GET /POST v2')).post((c) => c.text('POST /POST v2'));
|
|
483
533
|
app.route('/v2', route);
|
|
484
534
|
it('Should return 200 response - GET /v2/post', async () => {
|
|
@@ -496,21 +546,35 @@ describe('`Route` with app.route', () => {
|
|
|
496
546
|
expect(res.status).toBe(404);
|
|
497
547
|
});
|
|
498
548
|
});
|
|
499
|
-
describe('
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
549
|
+
describe('Nested', () => {
|
|
550
|
+
const app = new hono_1.Hono();
|
|
551
|
+
const api = new hono_1.Hono();
|
|
552
|
+
const book = new hono_1.Hono();
|
|
553
|
+
book.get('/', (c) => c.text('list books'));
|
|
554
|
+
book.get('/:id', (c) => c.text(`book ${c.req.param('id')}`));
|
|
555
|
+
api.get('/', (c) => c.text('this is API'));
|
|
556
|
+
api.route('/book', book);
|
|
557
|
+
app.get('/', (c) => c.text('root'));
|
|
558
|
+
app.route('/v2', api);
|
|
559
|
+
it('Should return 200 response - GET /', async () => {
|
|
560
|
+
const res = await app.request('http://localhost/');
|
|
561
|
+
expect(res.status).toBe(200);
|
|
562
|
+
expect(await res.text()).toBe('root');
|
|
504
563
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const res = await app.request('http://localhost/v3/post/1');
|
|
564
|
+
it('Should return 200 response - GET /v2', async () => {
|
|
565
|
+
const res = await app.request('http://localhost/v2');
|
|
508
566
|
expect(res.status).toBe(200);
|
|
509
|
-
expect(await res.text()).toBe('
|
|
567
|
+
expect(await res.text()).toBe('this is API');
|
|
510
568
|
});
|
|
511
|
-
it('Should return
|
|
512
|
-
const res = await app.request('http://localhost/
|
|
513
|
-
expect(res.status).toBe(
|
|
569
|
+
it('Should return 200 response - GET /v2/book', async () => {
|
|
570
|
+
const res = await app.request('http://localhost/v2/book');
|
|
571
|
+
expect(res.status).toBe(200);
|
|
572
|
+
expect(await res.text()).toBe('list books');
|
|
573
|
+
});
|
|
574
|
+
it('Should return 200 response - GET /v2/book/123', async () => {
|
|
575
|
+
const res = await app.request('http://localhost/v2/book/123');
|
|
576
|
+
expect(res.status).toBe(200);
|
|
577
|
+
expect(await res.text()).toBe('book 123');
|
|
514
578
|
});
|
|
515
579
|
});
|
|
516
580
|
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Context = exports.
|
|
3
|
+
exports.Context = exports.Hono = void 0;
|
|
4
4
|
var hono_1 = require("./hono");
|
|
5
5
|
Object.defineProperty(exports, "Hono", { enumerable: true, get: function () { return hono_1.Hono; } });
|
|
6
|
-
Object.defineProperty(exports, "Route", { enumerable: true, get: function () { return hono_1.Route; } });
|
|
7
6
|
var context_1 = require("./context");
|
|
8
7
|
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
|
|
@@ -2,7 +2,10 @@ import type { Context } from '../../context';
|
|
|
2
2
|
import type { Next } from '../../hono';
|
|
3
3
|
declare global {
|
|
4
4
|
interface Request {
|
|
5
|
-
cookie:
|
|
5
|
+
cookie: {
|
|
6
|
+
(name: string): string;
|
|
7
|
+
(): Record<string, string>;
|
|
8
|
+
};
|
|
6
9
|
}
|
|
7
10
|
}
|
|
8
11
|
declare module '../../context' {
|
|
@@ -3,12 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.cookie = void 0;
|
|
4
4
|
const cookie = () => {
|
|
5
5
|
return async (c, next) => {
|
|
6
|
-
c.req.cookie = (name) => {
|
|
6
|
+
c.req.cookie = ((name) => {
|
|
7
7
|
const cookie = c.req.headers.get('Cookie');
|
|
8
8
|
const obj = parse(cookie);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (name) {
|
|
10
|
+
const value = obj[name];
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return obj;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
12
17
|
c.cookie = (name, value, opt) => {
|
|
13
18
|
const cookie = serialize(name, value, opt);
|
|
14
19
|
c.header('Set-Cookie', cookie);
|
|
@@ -3,52 +3,76 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const hono_1 = require("../../hono");
|
|
4
4
|
const _1 = require(".");
|
|
5
5
|
describe('Cookie Middleware', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
|
|
44
|
-
sameSite: 'Strict',
|
|
6
|
+
describe('Parse cookie', () => {
|
|
7
|
+
const apps = {};
|
|
8
|
+
apps['get by name'] = (() => {
|
|
9
|
+
const app = new hono_1.Hono();
|
|
10
|
+
app.use('/cookie', (0, _1.cookie)());
|
|
11
|
+
app.get('/cookie', (c) => {
|
|
12
|
+
const yummyCookie = c.req.cookie('yummy_cookie');
|
|
13
|
+
const tastyCookie = c.req.cookie('tasty_cookie');
|
|
14
|
+
const res = new Response('Good cookie');
|
|
15
|
+
res.headers.set('Yummy-Cookie', yummyCookie);
|
|
16
|
+
res.headers.set('Tasty-Cookie', tastyCookie);
|
|
17
|
+
return res;
|
|
18
|
+
});
|
|
19
|
+
return app;
|
|
20
|
+
})();
|
|
21
|
+
apps['get all as an object'] = (() => {
|
|
22
|
+
const app = new hono_1.Hono();
|
|
23
|
+
app.use('/cookie', (0, _1.cookie)());
|
|
24
|
+
app.get('/cookie', (c) => {
|
|
25
|
+
const { yummy_cookie: yummyCookie, tasty_cookie: tastyCookie } = c.req.cookie();
|
|
26
|
+
const res = new Response('Good cookie');
|
|
27
|
+
res.headers.set('Yummy-Cookie', yummyCookie);
|
|
28
|
+
res.headers.set('Tasty-Cookie', tastyCookie);
|
|
29
|
+
return res;
|
|
30
|
+
});
|
|
31
|
+
return app;
|
|
32
|
+
})();
|
|
33
|
+
describe.each(Object.keys(apps))('%s', (name) => {
|
|
34
|
+
const app = apps[name];
|
|
35
|
+
it('Parse cookie on c.req.cookie', async () => {
|
|
36
|
+
const req = new Request('http://localhost/cookie');
|
|
37
|
+
const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry ';
|
|
38
|
+
req.headers.set('Cookie', cookieString);
|
|
39
|
+
const res = await app.request(req);
|
|
40
|
+
expect(res.headers.get('Yummy-Cookie')).toBe('choco');
|
|
41
|
+
expect(res.headers.get('Tasty-Cookie')).toBe('strawberry');
|
|
42
|
+
});
|
|
45
43
|
});
|
|
46
|
-
return c.text('Give cookie');
|
|
47
44
|
});
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
describe('Set cookie', () => {
|
|
46
|
+
const app = new hono_1.Hono();
|
|
47
|
+
app.use('/set-cookie', (0, _1.cookie)());
|
|
48
|
+
app.get('/set-cookie', (c) => {
|
|
49
|
+
c.cookie('delicious_cookie', 'macha');
|
|
50
|
+
return c.text('Give cookie');
|
|
51
|
+
});
|
|
52
|
+
it('Set cookie on c.cookie', async () => {
|
|
53
|
+
const res = await app.request('http://localhost/set-cookie');
|
|
54
|
+
expect(res.status).toBe(200);
|
|
55
|
+
const header = res.headers.get('Set-Cookie');
|
|
56
|
+
expect(header).toBe('delicious_cookie=macha');
|
|
57
|
+
});
|
|
58
|
+
app.use('/set-cookie-complex', (0, _1.cookie)());
|
|
59
|
+
app.get('/set-cookie-complex', (c) => {
|
|
60
|
+
c.cookie('great_cookie', 'banana', {
|
|
61
|
+
path: '/',
|
|
62
|
+
secure: true,
|
|
63
|
+
domain: 'example.com',
|
|
64
|
+
httpOnly: true,
|
|
65
|
+
maxAge: 1000,
|
|
66
|
+
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),
|
|
67
|
+
sameSite: 'Strict',
|
|
68
|
+
});
|
|
69
|
+
return c.text('Give cookie');
|
|
70
|
+
});
|
|
71
|
+
it('Complex pattern', async () => {
|
|
72
|
+
const res = await app.request('http://localhost/set-cookie-complex');
|
|
73
|
+
expect(res.status).toBe(200);
|
|
74
|
+
const header = res.headers.get('Set-Cookie');
|
|
75
|
+
expect(header).toBe('great_cookie=banana; Max-Age=1000; Domain=example.com; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict');
|
|
76
|
+
});
|
|
53
77
|
});
|
|
54
78
|
});
|
package/dist/utils/url.js
CHANGED
package/dist/utils/url.test.js
CHANGED
|
@@ -83,5 +83,14 @@ describe('url', () => {
|
|
|
83
83
|
expect((0, url_1.mergePath)('book', 'hey')).toBe('/book/hey');
|
|
84
84
|
expect((0, url_1.mergePath)('book', 'hey/')).toBe('/book/hey/');
|
|
85
85
|
});
|
|
86
|
+
it('Should be `/book`', () => {
|
|
87
|
+
expect((0, url_1.mergePath)('/', 'book')).toBe('/book');
|
|
88
|
+
});
|
|
89
|
+
it('Should be `/book`', () => {
|
|
90
|
+
expect((0, url_1.mergePath)('/', '/book')).toBe('/book');
|
|
91
|
+
});
|
|
92
|
+
it('Should be `/`', () => {
|
|
93
|
+
expect((0, url_1.mergePath)('/', '/')).toBe('/');
|
|
94
|
+
});
|
|
86
95
|
});
|
|
87
96
|
});
|