Comparing version 1.2.2 to 1.3.0
@@ -6,3 +6,3 @@ /// <reference types="@cloudflare/workers-types" /> | ||
export declare type Env = Record<string, any>; | ||
export declare class Context<RequestParamKeyType = string, E = Env> { | ||
export declare class Context<RequestParamKeyType extends string = string, E = Env> { | ||
req: Request<RequestParamKeyType>; | ||
@@ -9,0 +9,0 @@ res: Response; |
@@ -19,9 +19,27 @@ "use strict"; | ||
initRequest(req) { | ||
req.header = (name) => { | ||
return req.headers.get(name); | ||
}; | ||
req.query = (key) => { | ||
req.header = ((name) => { | ||
if (name) { | ||
return req.headers.get(name); | ||
} | ||
else { | ||
const result = {}; | ||
for (const [key, value] of req.headers) { | ||
result[key] = value; | ||
} | ||
return result; | ||
} | ||
}); | ||
req.query = ((key) => { | ||
const url = new URL(req.url); | ||
return url.searchParams.get(key); | ||
}; | ||
if (key) { | ||
return url.searchParams.get(key); | ||
} | ||
else { | ||
const result = {}; | ||
for (const [key, value] of url.searchParams) { | ||
result[key] = value; | ||
} | ||
return result; | ||
} | ||
}); | ||
return req; | ||
@@ -28,0 +46,0 @@ } |
@@ -5,11 +5,19 @@ /// <reference types="@cloudflare/workers-types" /> | ||
import type { Router } from './router'; | ||
import { METHOD_NAME_ALL_LOWERCASE } from './router'; | ||
declare global { | ||
interface Request<ParamKeyType = string> { | ||
param: (key: ParamKeyType) => string; | ||
query: (key: string) => string; | ||
header: (name: string) => string; | ||
interface Request<ParamKeyType extends string = string> { | ||
param: { | ||
(key: ParamKeyType): string; | ||
(): Record<ParamKeyType, string>; | ||
}; | ||
query: { | ||
(key: string): string; | ||
(): Record<string, string>; | ||
}; | ||
header: { | ||
(name: string): string; | ||
(): Record<string, string>; | ||
}; | ||
} | ||
} | ||
export declare type Handler<RequestParamKeyType = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | void | Promise<void>; | ||
export declare type Handler<RequestParamKeyType extends string = string, E = Env> = (c: Context<RequestParamKeyType, E>, next: Next) => Response | Promise<Response> | void | Promise<void>; | ||
export declare type NotFoundHandler<E = Env> = (c: Context<string, E>) => Response; | ||
@@ -27,25 +35,7 @@ export declare type ErrorHandler<E = Env> = (err: Error, c: Context<string, E>) => Response; | ||
} | ||
declare const methods: readonly ["get", "post", "put", "delete", "head", "options", "patch"]; | ||
declare type Methods = typeof methods[number] | typeof METHOD_NAME_ALL_LOWERCASE; | ||
interface Routing<E extends Env> { | ||
interface Route<E extends Env> { | ||
path: string; | ||
method: Methods; | ||
method: string; | ||
handler: Handler<string, E>; | ||
} | ||
declare const Route_base: new <E_1 extends Env, T extends string, U>() => { | ||
delete: HandlerInterface<T, E_1, U>; | ||
get: HandlerInterface<T, E_1, U>; | ||
all: HandlerInterface<T, E_1, U>; | ||
post: HandlerInterface<T, E_1, U>; | ||
put: HandlerInterface<T, E_1, U>; | ||
head: HandlerInterface<T, E_1, U>; | ||
options: HandlerInterface<T, E_1, U>; | ||
patch: HandlerInterface<T, E_1, U>; | ||
}; | ||
export declare class Route<E = Env, P extends string = ''> extends Route_base<E, P, Route<E, P>> { | ||
#private; | ||
routes: Routing<E>[]; | ||
constructor(); | ||
private add; | ||
} | ||
declare const Hono_base: new <E_1 extends Env, T extends string, U>() => { | ||
@@ -61,4 +51,3 @@ delete: HandlerInterface<T, E_1, U>; | ||
}; | ||
export declare class Hono<E = Env, P extends string = ''> extends Hono_base<E, P, Hono<E, P>> { | ||
#private; | ||
export declare class Hono<E = Env, P extends string = '/'> extends Hono_base<E, P, Hono<E, P>> { | ||
readonly routerClass: { | ||
@@ -68,7 +57,10 @@ new (): Router<any>; | ||
readonly strict: boolean; | ||
private _router; | ||
private _tempPath; | ||
private path; | ||
routes: Route<E>[]; | ||
constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>); | ||
private notFoundHandler; | ||
private errorHandler; | ||
route(path: string, route?: Route): Hono<E, P>; | ||
route(path: string, app?: Hono<any>): Hono<E, P>; | ||
use(path: string, ...middleware: Handler<string, E>[]): Hono<E, P>; | ||
@@ -75,0 +67,0 @@ use(...middleware: Handler<string, E>[]): Hono<E, P>; |
"use strict"; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
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"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
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"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _Route_path, _Hono_router, _Hono_tempPath; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Hono = exports.Route = void 0; | ||
exports.Hono = void 0; | ||
const compose_1 = require("./compose"); | ||
@@ -27,33 +15,2 @@ const context_1 = require("./context"); | ||
} | ||
class Route extends defineDynamicClass() { | ||
constructor() { | ||
super(); | ||
this.routes = []; | ||
_Route_path.set(this, ''); | ||
const allMethods = [...methods, router_2.METHOD_NAME_ALL_LOWERCASE]; | ||
allMethods.map((method) => { | ||
this[method] = (args1, ...args) => { | ||
if (typeof args1 === 'string') { | ||
__classPrivateFieldSet(this, _Route_path, args1, "f"); | ||
} | ||
else { | ||
this.add(method, __classPrivateFieldGet(this, _Route_path, "f"), args1); | ||
} | ||
args.map((handler) => { | ||
if (typeof handler !== 'string') { | ||
this.add(method, __classPrivateFieldGet(this, _Route_path, "f"), handler); | ||
} | ||
}); | ||
return this; | ||
}; | ||
}); | ||
} | ||
add(method, path, handler) { | ||
const r = { path: path, method: method, handler: handler }; | ||
this.routes.push(r); | ||
return this; | ||
} | ||
} | ||
exports.Route = Route; | ||
_Route_path = new WeakMap(); | ||
class Hono extends defineDynamicClass() { | ||
@@ -64,4 +21,4 @@ constructor(init = {}) { | ||
this.strict = true; // strict routing - default is true | ||
_Hono_router.set(this, void 0); | ||
_Hono_tempPath.set(this, void 0); | ||
this.path = '/'; | ||
this.routes = []; | ||
this.notFoundHandler = (c) => { | ||
@@ -94,15 +51,13 @@ const message = '404 Not Found'; | ||
Object.assign(this, init); | ||
__classPrivateFieldSet(this, _Hono_router, new this.routerClass(), "f"); | ||
__classPrivateFieldSet(this, _Hono_tempPath, null, "f"); | ||
this._router = new this.routerClass(); | ||
this._tempPath = null; | ||
} | ||
route(path, route) { | ||
const newHono = new Hono(); | ||
__classPrivateFieldSet(newHono, _Hono_tempPath, path, "f"); | ||
__classPrivateFieldSet(newHono, _Hono_router, __classPrivateFieldGet(this, _Hono_router, "f"), "f"); | ||
if (route) { | ||
route.routes.map((r) => { | ||
newHono.addRoute(r.method, r.path, r.handler); | ||
route(path, app) { | ||
this._tempPath = path; | ||
if (app) { | ||
app.routes.map((r) => { | ||
this.addRoute(r.method, r.path, r.handler); | ||
}); | ||
} | ||
return newHono; | ||
return this; | ||
} | ||
@@ -131,9 +86,11 @@ use(arg1, ...handlers) { | ||
method = method.toUpperCase(); | ||
if (__classPrivateFieldGet(this, _Hono_tempPath, "f")) { | ||
path = (0, url_1.mergePath)(__classPrivateFieldGet(this, _Hono_tempPath, "f"), path); | ||
if (this._tempPath) { | ||
path = (0, url_1.mergePath)(this._tempPath, path); | ||
} | ||
__classPrivateFieldGet(this, _Hono_router, "f").add(method, path, handler); | ||
this._router.add(method, path, handler); | ||
const r = { path: path, method: method, handler: handler }; | ||
this.routes.push(r); | ||
} | ||
async matchRoute(method, path) { | ||
return __classPrivateFieldGet(this, _Hono_router, "f").match(method, path); | ||
return this._router.match(method, path); | ||
} | ||
@@ -144,6 +101,12 @@ async dispatch(request, event, env) { | ||
const result = await this.matchRoute(method, path); | ||
request.param = (key) => { | ||
if (result) | ||
return result.params[key]; | ||
}; | ||
request.param = ((key) => { | ||
if (result) { | ||
if (key) { | ||
return result.params[key]; | ||
} | ||
else { | ||
return result.params; | ||
} | ||
} | ||
}); | ||
const handlers = result ? result.handlers : [this.notFoundHandler]; | ||
@@ -183,2 +146,1 @@ const c = new context_1.Context(request, { env: env, event: event, res: undefined }); | ||
exports.Hono = Hono; | ||
_Hono_router = new WeakMap(), _Hono_tempPath = new WeakMap(); |
@@ -155,41 +155,68 @@ "use strict"; | ||
describe('param and query', () => { | ||
const app = new hono_1.Hono(); | ||
app.get('/entry/:id', (c) => { | ||
const id = c.req.param('id'); | ||
return c.text(`id is ${id}`); | ||
const apps = {}; | ||
apps['get by name'] = (() => { | ||
const app = new hono_1.Hono(); | ||
app.get('/entry/:id', (c) => { | ||
const id = c.req.param('id'); | ||
return c.text(`id is ${id}`); | ||
}); | ||
app.get('/date/:date{[0-9]+}', (c) => { | ||
const date = c.req.param('date'); | ||
return c.text(`date is ${date}`); | ||
}); | ||
app.get('/search', (c) => { | ||
const name = c.req.query('name'); | ||
return c.text(`name is ${name}`); | ||
}); | ||
app.get('/add-header', (c) => { | ||
const bar = c.req.header('X-Foo'); | ||
return c.text(`foo is ${bar}`); | ||
}); | ||
return app; | ||
})(); | ||
apps['get all as an object'] = (() => { | ||
const app = new hono_1.Hono(); | ||
app.get('/entry/:id', (c) => { | ||
const { id } = c.req.param(); | ||
return c.text(`id is ${id}`); | ||
}); | ||
app.get('/date/:date{[0-9]+}', (c) => { | ||
const { date } = c.req.param(); | ||
return c.text(`date is ${date}`); | ||
}); | ||
app.get('/search', (c) => { | ||
const { name } = c.req.query(); | ||
return c.text(`name is ${name}`); | ||
}); | ||
app.get('/add-header', (c) => { | ||
const { 'x-foo': bar } = c.req.header(); | ||
return c.text(`foo is ${bar}`); | ||
}); | ||
return app; | ||
})(); | ||
describe.each(Object.keys(apps))('%s', (name) => { | ||
const app = apps[name]; | ||
it('param of /entry/:id is found', async () => { | ||
const res = await app.request('http://localhost/entry/123'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('id is 123'); | ||
}); | ||
it('param of /date/:date is found', async () => { | ||
const res = await app.request('http://localhost/date/0401'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('date is 0401'); | ||
}); | ||
it('query of /search?name=sam is found', async () => { | ||
const res = await app.request('http://localhost/search?name=sam'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('name is sam'); | ||
}); | ||
it('/add-header header - X-Foo is Bar', async () => { | ||
const req = new Request('http://localhost/add-header'); | ||
req.headers.append('X-Foo', 'Bar'); | ||
const res = await app.request(req); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('foo is Bar'); | ||
}); | ||
}); | ||
app.get('/date/:date{[0-9]+}', (c) => { | ||
const date = c.req.param('date'); | ||
return c.text(`date is ${date}`); | ||
}); | ||
app.get('/search', (c) => { | ||
const name = c.req.query('name'); | ||
return c.text(`name is ${name}`); | ||
}); | ||
app.get('/add-header', (c) => { | ||
const bar = c.req.header('X-Foo'); | ||
return c.text(`foo is ${bar}`); | ||
}); | ||
it('param of /entry/:id is found', async () => { | ||
const res = await app.request('http://localhost/entry/123'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('id is 123'); | ||
}); | ||
it('param of /date/:date is found', async () => { | ||
const res = await app.request('http://localhost/date/0401'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('date is 0401'); | ||
}); | ||
it('query of /search?name=sam is found', async () => { | ||
const res = await app.request('http://localhost/search?name=sam'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('name is sam'); | ||
}); | ||
it('/add-header header - X-Foo is Bar', async () => { | ||
const req = new Request('http://localhost/add-header'); | ||
req.headers.append('X-Foo', 'Bar'); | ||
const res = await app.request(req); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('foo is Bar'); | ||
}); | ||
}); | ||
@@ -455,30 +482,53 @@ describe('Middleware', () => { | ||
}); | ||
describe('`Route` with app.route', () => { | ||
const app = new hono_1.Hono(); | ||
describe('Hono with `app.route`', () => { | ||
describe('Basic', () => { | ||
const route = new hono_1.Route(); | ||
route.get('/post', (c) => c.text('GET /POST')); | ||
route.post('/post', (c) => c.text('POST /POST')); | ||
app.route('/v1', route); | ||
it('Should return 200 response - GET /v1/post', async () => { | ||
const res = await app.request('http://localhost/v1/post'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('GET /POST'); | ||
const app = new hono_1.Hono(); | ||
const api = new hono_1.Hono(); | ||
const middleware = new hono_1.Hono(); | ||
api.get('/posts', (c) => c.text('List')); | ||
api.post('/posts', (c) => c.text('Create')); | ||
api.get('/posts/:id', (c) => c.text(`GET ${c.req.param('id')}`)); | ||
api.use('*', async (c, next) => { | ||
await next(); | ||
c.res.headers.append('x-custom-a', 'a'); | ||
}); | ||
it('Should return 200 response - POST /v1/post', async () => { | ||
const res = await app.request('http://localhost/v1/post', { method: 'POST' }); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('POST /POST'); | ||
app.route('/api', api); | ||
middleware.use('*', async (c, next) => { | ||
await next(); | ||
c.res.headers.append('x-custom-b', 'b'); | ||
}); | ||
it('Should return 404 response - DELETE /v1/post', async () => { | ||
const res = await app.request('http://localhost/v1/post', { method: 'DELETE' }); | ||
app.route('/api', middleware); | ||
it('Should return not found response', async () => { | ||
const res = await app.request('http://localhost/'); | ||
expect(res.status).toBe(404); | ||
}); | ||
it('Should return 404 response - GET /post', async () => { | ||
const res = await app.request('http://localhost/post'); | ||
it('Should return not found response', async () => { | ||
const res = await app.request('http://localhost/posts'); | ||
expect(res.status).toBe(404); | ||
}); | ||
test('GET /api/posts', async () => { | ||
const res = await app.request('http://localhost/api/posts'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('List'); | ||
}); | ||
test('Custom header by middleware', async () => { | ||
const res = await app.request('http://localhost/api/posts'); | ||
expect(res.status).toBe(200); | ||
expect(res.headers.get('x-custom-a')).toBe('a'); | ||
expect(res.headers.get('x-custom-b')).toBe('b'); | ||
}); | ||
test('POST /api/posts', async () => { | ||
const res = await app.request('http://localhost/api/posts', { method: 'POST' }); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('Create'); | ||
}); | ||
test('GET /api/posts/123', async () => { | ||
const res = await app.request('http://localhost/api/posts/123'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('GET 123'); | ||
}); | ||
}); | ||
describe('Chaining', () => { | ||
const route = new hono_1.Route(); | ||
const app = new hono_1.Hono(); | ||
const route = new hono_1.Hono(); | ||
route.get('/post', (c) => c.text('GET /POST v2')).post((c) => c.text('POST /POST v2')); | ||
@@ -501,18 +551,32 @@ app.route('/v2', route); | ||
}); | ||
describe('Named parameter', () => { | ||
const route = new hono_1.Route(); | ||
route.get('/post/:id', (c) => { | ||
const id = c.req.param('id'); | ||
return c.text(`GET /post/${id} v3`); | ||
describe('Nested', () => { | ||
const app = new hono_1.Hono(); | ||
const api = new hono_1.Hono(); | ||
const book = new hono_1.Hono(); | ||
book.get('/', (c) => c.text('list books')); | ||
book.get('/:id', (c) => c.text(`book ${c.req.param('id')}`)); | ||
api.get('/', (c) => c.text('this is API')); | ||
api.route('/book', book); | ||
app.get('/', (c) => c.text('root')); | ||
app.route('/v2', api); | ||
it('Should return 200 response - GET /', async () => { | ||
const res = await app.request('http://localhost/'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('root'); | ||
}); | ||
app.route('/v3', route); | ||
it('Should return 200 response - GET /v3/post/1', async () => { | ||
const res = await app.request('http://localhost/v3/post/1'); | ||
it('Should return 200 response - GET /v2', async () => { | ||
const res = await app.request('http://localhost/v2'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('GET /post/1 v3'); | ||
expect(await res.text()).toBe('this is API'); | ||
}); | ||
it('Should return 404 response - GET /v3/post', async () => { | ||
const res = await app.request('http://localhost/v3/post/abc/def'); | ||
expect(res.status).toBe(404); | ||
it('Should return 200 response - GET /v2/book', async () => { | ||
const res = await app.request('http://localhost/v2/book'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('list books'); | ||
}); | ||
it('Should return 200 response - GET /v2/book/123', async () => { | ||
const res = await app.request('http://localhost/v2/book/123'); | ||
expect(res.status).toBe(200); | ||
expect(await res.text()).toBe('book 123'); | ||
}); | ||
}); | ||
@@ -519,0 +583,0 @@ }); |
@@ -1,4 +0,4 @@ | ||
export { Hono, Route } from './hono'; | ||
export { Hono } from './hono'; | ||
export type { Handler, Next } from './hono'; | ||
export { Context } from './context'; | ||
export type { Env } from './context'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Context = exports.Route = exports.Hono = void 0; | ||
exports.Context = exports.Hono = void 0; | ||
var hono_1 = require("./hono"); | ||
Object.defineProperty(exports, "Hono", { enumerable: true, get: function () { return hono_1.Hono; } }); | ||
Object.defineProperty(exports, "Route", { enumerable: true, get: function () { return hono_1.Route; } }); | ||
var context_1 = require("./context"); | ||
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } }); |
@@ -5,3 +5,6 @@ import type { Context } from '../../context'; | ||
interface Request { | ||
cookie: (name: string) => string; | ||
cookie: { | ||
(name: string): string; | ||
(): Record<string, string>; | ||
}; | ||
} | ||
@@ -8,0 +11,0 @@ } |
@@ -6,8 +6,13 @@ "use strict"; | ||
return async (c, next) => { | ||
c.req.cookie = (name) => { | ||
c.req.cookie = ((name) => { | ||
const cookie = c.req.headers.get('Cookie'); | ||
const obj = parse(cookie); | ||
const value = obj[name]; | ||
return value; | ||
}; | ||
if (name) { | ||
const value = obj[name]; | ||
return value; | ||
} | ||
else { | ||
return obj; | ||
} | ||
}); | ||
c.cookie = (name, value, opt) => { | ||
@@ -14,0 +19,0 @@ const cookie = serialize(name, value, opt); |
@@ -6,50 +6,74 @@ "use strict"; | ||
describe('Cookie Middleware', () => { | ||
const app = new hono_1.Hono(); | ||
app.use('/cookie', (0, _1.cookie)()); | ||
app.get('/cookie', (c) => { | ||
const yummyCookie = c.req.cookie('yummy_cookie'); | ||
const tastyCookie = c.req.cookie('tasty_cookie'); | ||
const res = new Response('Good cookie'); | ||
res.headers.set('Yummy-Cookie', yummyCookie); | ||
res.headers.set('Tasty-Cookie', tastyCookie); | ||
return res; | ||
describe('Parse cookie', () => { | ||
const apps = {}; | ||
apps['get by name'] = (() => { | ||
const app = new hono_1.Hono(); | ||
app.use('/cookie', (0, _1.cookie)()); | ||
app.get('/cookie', (c) => { | ||
const yummyCookie = c.req.cookie('yummy_cookie'); | ||
const tastyCookie = c.req.cookie('tasty_cookie'); | ||
const res = new Response('Good cookie'); | ||
res.headers.set('Yummy-Cookie', yummyCookie); | ||
res.headers.set('Tasty-Cookie', tastyCookie); | ||
return res; | ||
}); | ||
return app; | ||
})(); | ||
apps['get all as an object'] = (() => { | ||
const app = new hono_1.Hono(); | ||
app.use('/cookie', (0, _1.cookie)()); | ||
app.get('/cookie', (c) => { | ||
const { yummy_cookie: yummyCookie, tasty_cookie: tastyCookie } = c.req.cookie(); | ||
const res = new Response('Good cookie'); | ||
res.headers.set('Yummy-Cookie', yummyCookie); | ||
res.headers.set('Tasty-Cookie', tastyCookie); | ||
return res; | ||
}); | ||
return app; | ||
})(); | ||
describe.each(Object.keys(apps))('%s', (name) => { | ||
const app = apps[name]; | ||
it('Parse cookie on c.req.cookie', async () => { | ||
const req = new Request('http://localhost/cookie'); | ||
const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '; | ||
req.headers.set('Cookie', cookieString); | ||
const res = await app.request(req); | ||
expect(res.headers.get('Yummy-Cookie')).toBe('choco'); | ||
expect(res.headers.get('Tasty-Cookie')).toBe('strawberry'); | ||
}); | ||
}); | ||
}); | ||
it('Parse cookie on c.req.cookie', async () => { | ||
const req = new Request('http://localhost/cookie'); | ||
const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '; | ||
req.headers.set('Cookie', cookieString); | ||
const res = await app.request(req); | ||
expect(res.headers.get('Yummy-Cookie')).toBe('choco'); | ||
expect(res.headers.get('Tasty-Cookie')).toBe('strawberry'); | ||
}); | ||
app.use('/set-cookie', (0, _1.cookie)()); | ||
app.get('/set-cookie', (c) => { | ||
c.cookie('delicious_cookie', 'macha'); | ||
return c.text('Give cookie'); | ||
}); | ||
it('Set cookie on c.cookie', async () => { | ||
const res = await app.request('http://localhost/set-cookie'); | ||
expect(res.status).toBe(200); | ||
const header = res.headers.get('Set-Cookie'); | ||
expect(header).toBe('delicious_cookie=macha'); | ||
}); | ||
app.use('/set-cookie-complex', (0, _1.cookie)()); | ||
app.get('/set-cookie-complex', (c) => { | ||
c.cookie('great_cookie', 'banana', { | ||
path: '/', | ||
secure: true, | ||
domain: 'example.com', | ||
httpOnly: true, | ||
maxAge: 1000, | ||
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)), | ||
sameSite: 'Strict', | ||
describe('Set cookie', () => { | ||
const app = new hono_1.Hono(); | ||
app.use('/set-cookie', (0, _1.cookie)()); | ||
app.get('/set-cookie', (c) => { | ||
c.cookie('delicious_cookie', 'macha'); | ||
return c.text('Give cookie'); | ||
}); | ||
return c.text('Give cookie'); | ||
it('Set cookie on c.cookie', async () => { | ||
const res = await app.request('http://localhost/set-cookie'); | ||
expect(res.status).toBe(200); | ||
const header = res.headers.get('Set-Cookie'); | ||
expect(header).toBe('delicious_cookie=macha'); | ||
}); | ||
app.use('/set-cookie-complex', (0, _1.cookie)()); | ||
app.get('/set-cookie-complex', (c) => { | ||
c.cookie('great_cookie', 'banana', { | ||
path: '/', | ||
secure: true, | ||
domain: 'example.com', | ||
httpOnly: true, | ||
maxAge: 1000, | ||
expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)), | ||
sameSite: 'Strict', | ||
}); | ||
return c.text('Give cookie'); | ||
}); | ||
it('Complex pattern', async () => { | ||
const res = await app.request('http://localhost/set-cookie-complex'); | ||
expect(res.status).toBe(200); | ||
const header = res.headers.get('Set-Cookie'); | ||
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'); | ||
}); | ||
}); | ||
it('Complex pattern', async () => { | ||
const res = await app.request('http://localhost/set-cookie-complex'); | ||
expect(res.status).toBe(200); | ||
const header = res.headers.get('Set-Cookie'); | ||
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'); | ||
}); | ||
}); |
@@ -78,2 +78,6 @@ "use strict"; | ||
} | ||
/* ['/', '/'] => `/` */ | ||
if (path === '/' && p === '') { | ||
p = '/'; | ||
} | ||
} | ||
@@ -80,0 +84,0 @@ return p; |
@@ -86,3 +86,12 @@ "use strict"; | ||
}); | ||
it('Should be `/book`', () => { | ||
expect((0, url_1.mergePath)('/', 'book')).toBe('/book'); | ||
}); | ||
it('Should be `/book`', () => { | ||
expect((0, url_1.mergePath)('/', '/book')).toBe('/book'); | ||
}); | ||
it('Should be `/`', () => { | ||
expect((0, url_1.mergePath)('/', '/')).toBe('/'); | ||
}); | ||
}); | ||
}); |
{ | ||
"name": "hono", | ||
"version": "1.2.2", | ||
"version": "1.3.0", | ||
"description": "Ultrafast web framework for Cloudflare Workers.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
109
README.md
@@ -55,3 +55,3 @@ <div align="center"> | ||
- **TrieRouter**(default) - Implemented with Trie tree structure. | ||
- **RegExpRouter** - Match routes with one big Regex made before dispatching at once. | ||
- **RegExpRouter** - Match the route with using one big Regex made before dispatch. | ||
@@ -97,12 +97,10 @@ ## Hono in 1 minute | ||
```ts | ||
import { Hono, Route } from 'hono' | ||
import { cors } from 'hono/cors' | ||
import { Hono } from 'hono' | ||
import { basicAuth } from 'hono/basic-auth' | ||
const app = new Hono() | ||
const v1 = new Route() | ||
const v1 = new Hono() | ||
v1.get('/posts', (c) => { | ||
return c.text('list pots') | ||
}) | ||
.post('/posts', cors(), (c) => { | ||
.post(basicAuth({ username, password }), (c) => { | ||
return c.text('created!', 201) | ||
@@ -115,2 +113,3 @@ }) | ||
const app = new Hono() | ||
app.route('/v1', v1) | ||
@@ -146,3 +145,3 @@ ``` | ||
- app.**all**(\[path,\] handler|middleware...) | ||
- app.**route**(path, \[Route\]) | ||
- app.**route**(path, \[app\]) | ||
- app.**use**(\[path,\] middleware) | ||
@@ -163,2 +162,4 @@ - app.**notFound**(handler) | ||
app.post('/', (c) => c.text('POST /')) | ||
app.put('/', (c) => c.text('PUT /')) | ||
app.delete('/', (c) => c.text('DELETE /')) | ||
@@ -183,2 +184,11 @@ // Wildcard | ||
or all parameters at once: | ||
```ts | ||
app.get('/posts/:id/comment/:comment_id', (c) => { | ||
const { id, comment_id } = c.req.param() | ||
... | ||
}) | ||
``` | ||
### Regexp | ||
@@ -188,4 +198,3 @@ | ||
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => { | ||
const date = c.req.param('date') | ||
const title = c.req.param('title') | ||
const { date, title } = c.req.param() | ||
... | ||
@@ -229,8 +238,8 @@ }) | ||
## Route | ||
## Grouping | ||
`Route` object enables Nested route. | ||
Group the routes with `Hono` instance and add them to the main app with `route` method. | ||
```ts | ||
const book = new Route() | ||
const book = new Hono() | ||
@@ -245,2 +254,3 @@ book.get('/', (c) => c.text('List Books')) // GET /book | ||
const app = new Hono() | ||
app.route('/book', book) | ||
@@ -350,2 +360,8 @@ ``` | ||
// Get all params at once | ||
app.get('/search', (c) => { | ||
const { q, limit, offset } = c.req.query() | ||
... | ||
}) | ||
// Captured params | ||
@@ -440,4 +456,4 @@ app.get('/entry/:id', (c) => { | ||
// Response object | ||
app.use('/', (c, next) => { | ||
next() | ||
app.use('/', async (c, next) => { | ||
await next() | ||
c.res.headers.append('X-Debug', 'Debug message') | ||
@@ -451,7 +467,7 @@ }) | ||
// FetchEvent object | ||
app.use('*', async (c, next) => { | ||
app.get('/foo', async (c) => { | ||
c.event.waitUntil( | ||
... | ||
c.env.KV.put(key, data) | ||
) | ||
await next() | ||
... | ||
}) | ||
@@ -574,4 +590,59 @@ ``` | ||
## Examples | ||
## Practical Example | ||
How about writing web API with Hono? | ||
```ts | ||
import { Hono } from 'hono' | ||
import { cors } from 'hono/cors' | ||
import { basicAuth } from 'hono/basic-auth' | ||
import { prettyJSON } from 'hono/pretty-json' | ||
import { getPosts, getPosts, createPost } from './model' | ||
const app = new Hono() | ||
app.get('/', (c) => c.text('Pretty Blog API')) | ||
app.use('*', prettyJSON()) | ||
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) | ||
export interface Bindings { | ||
USERNAME: string | ||
PASSWORD: string | ||
} | ||
const api = new Hono<Bindings>() | ||
api.get('/posts', (c) => { | ||
const { limit, offset } = c.req.query() | ||
const posts = getPosts({ limit, offset }) | ||
return c.json({ posts }) | ||
}) | ||
api.get('/posts/:id', (c) => { | ||
const id = c.req.param('id') | ||
const post = getPost({ id }) | ||
return c.json({ post }) | ||
}) | ||
api.post( | ||
'/posts', | ||
async (c, next) => { | ||
const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD }) | ||
await auth(c, next) | ||
}, | ||
async (c) => { | ||
const post = await c.req.json<POST>() | ||
const ok = createPost({ post }) | ||
return c.json({ ok }) | ||
} | ||
) | ||
app.use('/posts/*', cors()) | ||
app.route('/api', api) | ||
export default app | ||
``` | ||
## Other Examples | ||
- Hono Examples - <https://github.com/honojs/examples> | ||
@@ -578,0 +649,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
258550
6301
670