Comparing version 0.4.0 to 0.4.1
@@ -1,1 +0,1 @@ | ||
export declare const compose: <T>(middleware: Function[]) => (context: T, next?: Function) => Promise<void | object>; | ||
export declare const compose: <T>(middleware: Function[], onError?: Function) => (context: T, next?: Function) => Promise<void | object>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.compose = void 0; | ||
const context_1 = require("./context"); | ||
// Based on the code in the MIT licensed `koa-compose` package. | ||
const compose = (middleware) => { | ||
const compose = (middleware, onError) => { | ||
const errors = []; | ||
@@ -22,3 +23,8 @@ return function (context, next) { | ||
errors.push(e); | ||
throw errors[0]; // XXX | ||
if (onError && context instanceof context_1.Context) { | ||
context.res = onError(errors[0], context); | ||
} | ||
else { | ||
throw errors[0]; | ||
} | ||
}); | ||
@@ -25,0 +31,0 @@ } |
@@ -15,2 +15,3 @@ /// <reference types="@cloudflare/workers-types" /> | ||
render: (template: string, params?: object, options?: object) => Promise<Response>; | ||
notFound: () => Response; | ||
constructor(req: Request<RequestParamKeyType>, opts?: { | ||
@@ -17,0 +18,0 @@ res: Response; |
/// <reference types="@cloudflare/workers-types" /> | ||
import type { Result } from './node'; | ||
import { Node } from './node'; | ||
import { Context } from './context'; | ||
import type { Env } from './context'; | ||
import type { Result } from './router'; | ||
import { Router } from './router'; | ||
declare global { | ||
@@ -19,3 +20,3 @@ interface Request<ParamKeyType = string> { | ||
declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>; | ||
export declare class Router<T> { | ||
export declare class TrieRouter<T> extends Router<T> { | ||
node: Node<T>; | ||
@@ -26,23 +27,23 @@ constructor(); | ||
} | ||
declare type Init = { | ||
strict?: boolean; | ||
}; | ||
export declare class Hono { | ||
router: Router<Handler[]>; | ||
routerClass: { | ||
new (): Router<any>; | ||
}; | ||
strict: boolean; | ||
router: Router<Handler>; | ||
middlewareRouters: Router<MiddlewareHandler>[]; | ||
tempPath: string; | ||
strict: boolean; | ||
constructor(init?: Init); | ||
get<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
post<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
put<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
head<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
delete<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
options<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
patch<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
all<Path extends string>(path: Path, ...args: Handler<ParamKeys<Path>>[]): Hono; | ||
constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>); | ||
get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
head<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
delete<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
options<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
patch<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono; | ||
route(path: string): Hono; | ||
use(path: string, middleware: MiddlewareHandler): void; | ||
addRoute(method: string, path: string, ...args: Handler[]): Hono; | ||
matchRoute(method: string, path: string): Promise<Result<Handler[]>>; | ||
addRoute(method: string, path: string, handler: Handler): Hono; | ||
matchRoute(method: string, path: string): Promise<Result<Handler>>; | ||
dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>; | ||
@@ -52,5 +53,5 @@ handleEvent(event: FetchEvent): Promise<Response>; | ||
fire(): void; | ||
onError(err: Error): Response; | ||
notFound(): Response; | ||
onError(err: Error, c: Context): Response; | ||
notFound(c: Context): Response; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Hono = exports.Router = void 0; | ||
exports.Hono = exports.TrieRouter = void 0; | ||
const node_1 = require("./node"); | ||
@@ -8,4 +8,6 @@ const compose_1 = require("./compose"); | ||
const context_1 = require("./context"); | ||
class Router { | ||
const router_1 = require("./router"); | ||
class TrieRouter extends router_1.Router { | ||
constructor() { | ||
super(); | ||
this.node = new node_1.Node(); | ||
@@ -20,33 +22,35 @@ } | ||
} | ||
exports.Router = Router; | ||
exports.TrieRouter = TrieRouter; | ||
class Hono { | ||
constructor(init = { strict: true }) { | ||
this.router = new Router(); | ||
constructor(init = {}) { | ||
this.routerClass = TrieRouter; | ||
this.strict = true; // strict routing - default is true | ||
Object.assign(this, init); | ||
this.router = new this.routerClass(); | ||
this.middlewareRouters = []; | ||
this.tempPath = null; | ||
this.strict = init.strict; // strict routing - default is true | ||
} | ||
get(path, ...args) { | ||
return this.addRoute('get', path, ...args); | ||
get(path, handler) { | ||
return this.addRoute('get', path, handler); | ||
} | ||
post(path, ...args) { | ||
return this.addRoute('post', path, ...args); | ||
post(path, handler) { | ||
return this.addRoute('post', path, handler); | ||
} | ||
put(path, ...args) { | ||
return this.addRoute('put', path, ...args); | ||
put(path, handler) { | ||
return this.addRoute('put', path, handler); | ||
} | ||
head(path, ...args) { | ||
return this.addRoute('head', path, ...args); | ||
head(path, handler) { | ||
return this.addRoute('head', path, handler); | ||
} | ||
delete(path, ...args) { | ||
return this.addRoute('delete', path, ...args); | ||
delete(path, handler) { | ||
return this.addRoute('delete', path, handler); | ||
} | ||
options(path, ...args) { | ||
return this.addRoute('options', path, ...args); | ||
options(path, handler) { | ||
return this.addRoute('options', path, handler); | ||
} | ||
patch(path, ...args) { | ||
return this.addRoute('patch', path, ...args); | ||
patch(path, handler) { | ||
return this.addRoute('patch', path, handler); | ||
} | ||
all(path, ...args) { | ||
return this.addRoute('all', path, ...args); | ||
all(path, handler) { | ||
return this.addRoute('all', path, handler); | ||
} | ||
@@ -63,8 +67,8 @@ route(path) { | ||
} | ||
const router = new Router(); | ||
router.add(node_1.METHOD_NAME_OF_ALL, path, middleware); | ||
const router = new this.routerClass(); | ||
router.add(router_1.METHOD_NAME_OF_ALL, path, middleware); | ||
this.middlewareRouters.push(router); | ||
} | ||
// addRoute('get', '/', handler) | ||
addRoute(method, path, ...args) { | ||
addRoute(method, path, handler) { | ||
method = method.toUpperCase(); | ||
@@ -74,3 +78,3 @@ if (this.tempPath) { | ||
} | ||
this.router.add(method, path, args); | ||
this.router.add(method, path, handler); | ||
return this; | ||
@@ -98,6 +102,6 @@ } | ||
}; | ||
const handler = result ? result.handler[0] : this.notFound; // XXX | ||
const handler = result ? result.handler : this.notFound; | ||
const middleware = []; | ||
for (const mr of this.middlewareRouters) { | ||
const mwResult = mr.match(node_1.METHOD_NAME_OF_ALL, path); | ||
const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path); | ||
if (mwResult) { | ||
@@ -116,4 +120,5 @@ middleware.push(mwResult.handler); | ||
middleware.push(wrappedHandler); | ||
const composed = (0, compose_1.compose)(middleware); | ||
const composed = (0, compose_1.compose)(middleware, this.onError); | ||
const c = new context_1.Context(request, { env: env, event: event, res: null }); | ||
c.notFound = () => this.notFound(c); | ||
await composed(c); | ||
@@ -123,10 +128,6 @@ return c.res; | ||
async handleEvent(event) { | ||
return this.dispatch(event.request, {}, event).catch((err) => { | ||
return this.onError(err); | ||
}); | ||
return this.dispatch(event.request, {}, event); | ||
} | ||
async fetch(request, env, event) { | ||
return this.dispatch(request, env, event).catch((err) => { | ||
return this.onError(err); | ||
}); | ||
return this.dispatch(request, env, event); | ||
} | ||
@@ -138,22 +139,14 @@ fire() { | ||
} | ||
onError(err) { | ||
console.error(`${err}`); | ||
// Default error Response | ||
onError(err, c) { | ||
console.error(`${err.message}`); | ||
const message = 'Internal Server Error'; | ||
return new Response(message, { | ||
status: 500, | ||
headers: { | ||
'Content-Length': message.length.toString(), | ||
}, | ||
}); | ||
return c.text(message, 500); | ||
} | ||
notFound() { | ||
// Default 404 not found Response | ||
notFound(c) { | ||
const message = 'Not Found'; | ||
return new Response(message, { | ||
status: 404, | ||
headers: { | ||
'Content-Length': message.length.toString(), | ||
}, | ||
}); | ||
return c.text(message, 404); | ||
} | ||
} | ||
exports.Hono = Hono; |
@@ -15,5 +15,3 @@ "use strict"; | ||
const delta = Date.now() - start; | ||
return humanize([ | ||
delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's', | ||
]); | ||
return humanize([delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's']); | ||
}; | ||
@@ -49,15 +47,5 @@ const LogPrefix = { | ||
const start = Date.now(); | ||
try { | ||
await next(); | ||
} | ||
catch (e) { | ||
log(fn, LogPrefix.Error, method, path, c.res.status || 500, time(start)); | ||
throw e; | ||
} | ||
await next(); | ||
const len = parseFloat(c.res.headers.get('Content-Length')); | ||
const contentLength = isNaN(len) | ||
? '0' | ||
: len < 1024 | ||
? `${len}b` | ||
: `${len / 1024}kB`; | ||
const contentLength = isNaN(len) ? '0' : len < 1024 ? `${len}b` : `${len / 1024}kB`; | ||
log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start), contentLength); | ||
@@ -64,0 +52,0 @@ }; |
@@ -12,3 +12,7 @@ "use strict"; | ||
const url = new URL(c.req.url); | ||
const path = (0, cloudflare_1.getKVFilePath)({ filename: url.pathname, root: opt.root, defaultDocument: DEFAULT_DOCUMENT }); | ||
const path = (0, cloudflare_1.getKVFilePath)({ | ||
filename: url.pathname, | ||
root: opt.root, | ||
defaultDocument: DEFAULT_DOCUMENT, | ||
}); | ||
const content = await (0, cloudflare_1.getContentFromKVAsset)(path); | ||
@@ -15,0 +19,0 @@ if (content) { |
@@ -1,7 +0,3 @@ | ||
export declare const METHOD_NAME_OF_ALL = "ALL"; | ||
export declare class Result<T> { | ||
handler: T; | ||
params: Record<string, string>; | ||
constructor(handler: T, params: Record<string, string>); | ||
} | ||
import type { Pattern } from './utils/url'; | ||
import { Result } from './router'; | ||
export declare class Node<T> { | ||
@@ -12,2 +8,3 @@ method: Record<string, T>; | ||
middlewares: []; | ||
patterns: Pattern[]; | ||
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>); | ||
@@ -14,0 +11,0 @@ insert(method: string, path: string, handler: T): Node<T>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Node = exports.Result = exports.METHOD_NAME_OF_ALL = void 0; | ||
exports.Node = void 0; | ||
const url_1 = require("./utils/url"); | ||
exports.METHOD_NAME_OF_ALL = 'ALL'; | ||
class Result { | ||
constructor(handler, params) { | ||
this.handler = handler; | ||
this.params = params; | ||
} | ||
} | ||
exports.Result = Result; | ||
const router_1 = require("./router"); | ||
const noRoute = () => { | ||
@@ -24,2 +17,3 @@ return null; | ||
this.middlewares = []; | ||
this.patterns = []; | ||
} | ||
@@ -37,2 +31,6 @@ insert(method, path, handler) { | ||
curNode.children[p] = new Node(); | ||
const pattern = (0, url_1.getPattern)(p); | ||
if (pattern) { | ||
curNode.patterns.push(pattern); | ||
} | ||
curNode = curNode.children[p]; | ||
@@ -69,8 +67,7 @@ } | ||
let isParamMatch = false; | ||
const keys = Object.keys(curNode.children); | ||
for (let j = 0, len = keys.length; j < len; j++) { | ||
const key = keys[j]; | ||
for (let j = 0, len = curNode.patterns.length; j < len; j++) { | ||
const pattern = curNode.patterns[j]; | ||
// Wildcard | ||
// '/hello/*/foo' => match /hello/bar/foo | ||
if (key === '*') { | ||
if (pattern === '*') { | ||
curNode = curNode.children['*']; | ||
@@ -80,15 +77,11 @@ isWildcard = true; | ||
} | ||
const pattern = (0, url_1.getPattern)(key); | ||
// Named match | ||
if (pattern) { | ||
const match = p.match(new RegExp(pattern[1])); | ||
if (match) { | ||
const k = pattern[0]; | ||
params[k] = match[1]; | ||
curNode = curNode.children[key]; | ||
isParamMatch = true; | ||
break; | ||
} | ||
return noRoute(); | ||
const [key, name, matcher] = pattern; | ||
if (p !== '' && (matcher === true || matcher.test(p))) { | ||
params[name] = p; | ||
curNode = curNode.children[key]; | ||
isParamMatch = true; | ||
break; | ||
} | ||
return noRoute(); | ||
} | ||
@@ -102,9 +95,9 @@ if (isWildcard && i === len - 1) { | ||
} | ||
const handler = curNode.method[exports.METHOD_NAME_OF_ALL] || curNode.method[method]; | ||
const handler = curNode.method[router_1.METHOD_NAME_OF_ALL] || curNode.method[method]; | ||
if (!handler) { | ||
return noRoute(); | ||
} | ||
return new Result(handler, params); | ||
return new router_1.Result(handler, params); | ||
} | ||
} | ||
exports.Node = Node; |
@@ -49,3 +49,5 @@ "use strict"; | ||
}, new TextEncoder().encode(String(a))); | ||
const hash = Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join(''); | ||
const hash = Array.prototype.map | ||
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)) | ||
.join(''); | ||
return hash; | ||
@@ -52,0 +54,0 @@ } |
@@ -0,3 +1,4 @@ | ||
export declare type Pattern = readonly [string, string, RegExp | true] | '*'; | ||
export declare const splitPath: (path: string) => string[]; | ||
export declare const getPattern: (label: string) => string[] | null; | ||
export declare const getPattern: (label: string) => Pattern | null; | ||
declare type Params = { | ||
@@ -4,0 +5,0 @@ strict: boolean; |
@@ -13,14 +13,22 @@ "use strict"; | ||
exports.splitPath = splitPath; | ||
const patternCache = {}; | ||
const getPattern = (label) => { | ||
// * => wildcard | ||
// :id{[0-9]+} => ([0-9]+) | ||
// :id => (.+) | ||
//const name = '' | ||
if (label === '*') { | ||
return '*'; | ||
} | ||
const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/); | ||
if (match) { | ||
if (match[2]) { | ||
return [match[1], '(' + match[2] + ')']; | ||
if (!patternCache[label]) { | ||
if (match[2]) { | ||
patternCache[label] = [label, match[1], new RegExp('^' + match[2] + '$')]; | ||
} | ||
else { | ||
patternCache[label] = [label, match[1], true]; | ||
} | ||
} | ||
else { | ||
return [match[1], '(.+)']; | ||
} | ||
return patternCache[label]; | ||
} | ||
@@ -27,0 +35,0 @@ return null; |
{ | ||
"name": "hono", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "[炎] Ultrafast web framework for Cloudflare Workers.", | ||
@@ -53,3 +53,3 @@ "main": "dist/index.js", | ||
"test": "jest", | ||
"lint": "eslint --ext js,ts src .eslintrc.js test", | ||
"lint": "eslint --ext js,ts src .eslintrc.js test && prettier --check src", | ||
"build": "rimraf dist && tsc", | ||
@@ -86,3 +86,3 @@ "watch": "tsc -w", | ||
"eslint": "^7.26.0", | ||
"eslint-config-prettier": "^8.1.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-define-config": "^1.2.1", | ||
@@ -94,3 +94,2 @@ "eslint-import-resolver-typescript": "^2.0.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"form-data": "^4.0.0", | ||
@@ -100,2 +99,3 @@ "jest": "^27.4.5", | ||
"mustache": "^4.2.0", | ||
"prettier": "^2.5.1", | ||
"rimraf": "^3.0.2", | ||
@@ -102,0 +102,0 @@ "ts-jest": "^27.1.2", |
@@ -26,8 +26,8 @@ # Hono | ||
```plain | ||
hono x 708,671 ops/sec ±2.58% (58 runs sampled) | ||
itty-router x 159,610 ops/sec ±2.86% (87 runs sampled) | ||
sunder x 322,846 ops/sec ±2.24% (86 runs sampled) | ||
worktop x 223,625 ops/sec ±2.01% (95 runs sampled) | ||
hono x 779,197 ops/sec ±6.55% (78 runs sampled) | ||
itty-router x 161,813 ops/sec ±3.87% (87 runs sampled) | ||
sunder x 334,096 ops/sec ±1.33% (93 runs sampled) | ||
worktop x 212,661 ops/sec ±4.40% (81 runs sampled) | ||
Fastest is hono | ||
✨ Done in 57.83s. | ||
✨ Done in 58.29s. | ||
``` | ||
@@ -125,12 +125,2 @@ | ||
### Custom 404 Response | ||
You can customize 404 Not Found response: | ||
```js | ||
app.get('*', (c) => { | ||
return c.text('Custom 404 Error', 404) | ||
}) | ||
``` | ||
### no strict | ||
@@ -148,3 +138,3 @@ | ||
## async/await | ||
### async/await | ||
@@ -197,15 +187,2 @@ ```js | ||
### Handling Error | ||
```js | ||
app.use('*', async (c, next) => { | ||
try { | ||
await next() | ||
} catch (err) { | ||
console.error(`${err}`) | ||
c.res = c.text('Custom Error Message', { status: 500 }) | ||
} | ||
}) | ||
``` | ||
## Context | ||
@@ -298,2 +275,12 @@ | ||
### c.notFound() | ||
Return the default `404 Not Found` Response: | ||
```js | ||
app.get('/notfound', (c) => { | ||
return c.notFound() | ||
}) | ||
``` | ||
### c.redirect() | ||
@@ -340,2 +327,22 @@ | ||
## Not Found | ||
If you want, you can set the default `404 Not Found` Response: | ||
```js | ||
app.notFound = (c) => { | ||
return c.text('This is default 404 Not Found', 404) | ||
} | ||
``` | ||
## Error handling | ||
You can handle errors in your way: | ||
```js | ||
app.onError = (err, c) => { | ||
return c.text(`This is error message: ${err.mssage}`, 500) | ||
} | ||
``` | ||
## fire | ||
@@ -342,0 +349,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
66667
49
1549
472