@cfworker/web
Advanced tools
Comparing version 1.6.11 to 1.8.0
import { safeParse } from 'secure-json-parse'; | ||
import { Accepts } from './accepts.js'; | ||
import { HttpError } from './http-error.js'; | ||
export class Req { | ||
@@ -8,3 +9,13 @@ constructor(request) { | ||
this.url = new URL(request.url); | ||
this.url.pathname = decodeURIComponent(this.url.pathname); | ||
try { | ||
this.url.pathname = decodeURIComponent(this.url.pathname) | ||
.replace(/\/\/+/g, '/') | ||
.normalize(); | ||
} | ||
catch (err) { | ||
if (err instanceof URIError) { | ||
throw new HttpError(400, `Unable to decode pathname "${this.url.pathname}".`); | ||
} | ||
throw err; | ||
} | ||
this.headers = request.headers; | ||
@@ -11,0 +22,0 @@ this.params = Object.create(null); |
@@ -0,3 +1,5 @@ | ||
import { ParseOptions, TokensToRegexpOptions } from 'path-to-regexp'; | ||
import { Context } from './context.js'; | ||
import { Middleware } from './middleware.js'; | ||
export declare type PathToRegExpOptions = TokensToRegexpOptions & ParseOptions; | ||
export declare const Method: (method: string) => ({ req }: Context) => boolean; | ||
@@ -14,3 +16,3 @@ export declare const Get: ({ req }: Context) => boolean; | ||
export declare const Referer: (host: string) => ({ req }: Context) => boolean; | ||
export declare const Path: (pattern: string) => ({ req: { url, params } }: Context) => boolean; | ||
export declare const Path: (pattern: string, options?: PathToRegExpOptions | undefined) => ({ req: { url, params } }: Context) => boolean; | ||
export declare type RouteCondition = (context: Context) => boolean; | ||
@@ -21,5 +23,10 @@ export interface Route { | ||
} | ||
export interface RouterOptions { | ||
pathToRegExpOptions?: PathToRegExpOptions; | ||
} | ||
export declare const defaultRouterOptions: RouterOptions; | ||
export declare class Router { | ||
private readonly options; | ||
private readonly routes; | ||
constructor(); | ||
constructor(options?: RouterOptions); | ||
get(pathname: string, ...middleware: Middleware[]): this; | ||
@@ -31,3 +38,3 @@ post(pathname: string, ...middleware: Middleware[]): this; | ||
head(pathname: string, ...middleware: Middleware[]): this; | ||
all(...middleware: Middleware[]): this; | ||
all(pathname: string, ...middleware: Middleware[]): this; | ||
middleware: Middleware; | ||
@@ -34,0 +41,0 @@ compose(conditions: RouteCondition[], ...middleware: Middleware[]): this; |
import { pathToRegexp } from 'path-to-regexp'; | ||
import { HttpError } from './http-error.js'; | ||
import { composeMiddleware } from './middleware.js'; | ||
@@ -21,5 +20,5 @@ export const Method = (method) => { | ||
export const Referer = (host) => Header('referer', host); | ||
export const Path = (pattern) => { | ||
export const Path = (pattern, options) => { | ||
const keys = []; | ||
const regExp = pathToRegexp(pattern, keys); | ||
const regExp = pathToRegexp(pattern, keys, options); | ||
return ({ req: { url, params } }) => { | ||
@@ -34,4 +33,8 @@ const match = url.pathname.match(regExp); | ||
}; | ||
export const defaultRouterOptions = { | ||
pathToRegExpOptions: { strict: true } | ||
}; | ||
export class Router { | ||
constructor() { | ||
constructor(options = defaultRouterOptions) { | ||
this.options = options; | ||
this.middleware = async (ctx, next) => { | ||
@@ -49,21 +52,28 @@ const resolved = this.resolve(ctx); | ||
get(pathname, ...middleware) { | ||
return this.compose([Get, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Get, Path(pathname, opts)], ...middleware); | ||
} | ||
post(pathname, ...middleware) { | ||
return this.compose([Post, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Post, Path(pathname, opts)], ...middleware); | ||
} | ||
put(pathname, ...middleware) { | ||
return this.compose([Put, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Put, Path(pathname, opts)], ...middleware); | ||
} | ||
patch(pathname, ...middleware) { | ||
return this.compose([Patch, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Patch, Path(pathname, opts)], ...middleware); | ||
} | ||
delete(pathname, ...middleware) { | ||
return this.compose([Delete, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Delete, Path(pathname, opts)], ...middleware); | ||
} | ||
head(pathname, ...middleware) { | ||
return this.compose([Head, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Head, Path(pathname, opts)], ...middleware); | ||
} | ||
all(...middleware) { | ||
return this.compose([], ...middleware); | ||
all(pathname, ...middleware) { | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Path(pathname, opts)], ...middleware); | ||
} | ||
@@ -88,18 +98,4 @@ compose(conditions, ...middleware) { | ||
} | ||
params[name] = decodePathnameComponent(value); | ||
params[name] = decodeURIComponent(value); | ||
} | ||
} | ||
function decodePathnameComponent(component) { | ||
if (component.length === 0) { | ||
return component; | ||
} | ||
try { | ||
return decodeURIComponent(component); | ||
} | ||
catch (err) { | ||
if (err instanceof URIError) { | ||
throw new HttpError(400, `Unable to decode pathname component "${component}".`); | ||
} | ||
throw err; | ||
} | ||
} |
{ | ||
"name": "@cfworker/web", | ||
"version": "1.6.11", | ||
"version": "1.8.0", | ||
"description": "Web framework for Cloudflare Workers and service workers, inspired by Koa and fastify", | ||
@@ -36,4 +36,4 @@ "keywords": [ | ||
"dependencies": { | ||
"@cfworker/json-schema": "^1.6.11", | ||
"@cfworker/worker-types": "^1.6.11", | ||
"@cfworker/json-schema": "^1.8.0", | ||
"@cfworker/worker-types": "^1.8.0", | ||
"@cloudflare/workers-types": "^2.1.0", | ||
@@ -55,3 +55,3 @@ "@types/cookie": "^0.4.0", | ||
"devDependencies": { | ||
"@cfworker/dev": "^1.6.11", | ||
"@cfworker/dev": "^1.8.0", | ||
"@types/chai": "^4.2.16", | ||
@@ -64,3 +64,3 @@ "@types/mocha": "^8.2.2", | ||
}, | ||
"gitHead": "c1751f50dcc2bf75106545395fe3ec56ff426a57" | ||
"gitHead": "f338fb849e8eb8c0a355680c595a079d0107807e" | ||
} |
# @cfworker/web | ||
![](https://badgen.net/bundlephobia/minzip/@cfworker/web) | ||
![](https://badgen.net/bundlephobia/min/@cfworker/web) | ||
![](https://badgen.net/bundlephobia/dependency-count/@cfworker/web) | ||
![](https://badgen.net/bundlephobia/tree-shaking/@cfworker/web) | ||
![](https://badgen.net/npm/types/@cfworker/web?icon=typescript) | ||
Web framework for Cloudflare Workers and service workers, inspired by Koa and fastify. | ||
@@ -4,0 +10,0 @@ |
import { Reviver, safeParse } from 'secure-json-parse'; | ||
import { Accepts } from './accepts.js'; | ||
import { HttpError } from './http-error.js'; | ||
@@ -17,3 +18,15 @@ export class Req { | ||
this.url = new URL(request.url); | ||
this.url.pathname = decodeURIComponent(this.url.pathname); | ||
try { | ||
this.url.pathname = decodeURIComponent(this.url.pathname) | ||
.replace(/\/\/+/g, '/') | ||
.normalize(); | ||
} catch (err) { | ||
if (err instanceof URIError) { | ||
throw new HttpError( | ||
400, | ||
`Unable to decode pathname "${this.url.pathname}".` | ||
); | ||
} | ||
throw err; | ||
} | ||
this.headers = request.headers; | ||
@@ -20,0 +33,0 @@ this.params = Object.create(null); |
@@ -1,6 +0,12 @@ | ||
import { Key, pathToRegexp } from 'path-to-regexp'; | ||
import { | ||
Key, | ||
ParseOptions, | ||
pathToRegexp, | ||
TokensToRegexpOptions | ||
} from 'path-to-regexp'; | ||
import { Context } from './context.js'; | ||
import { HttpError } from './http-error.js'; | ||
import { composeMiddleware, Middleware } from './middleware.js'; | ||
export type PathToRegExpOptions = TokensToRegexpOptions & ParseOptions; | ||
export const Method = (method: string) => { | ||
@@ -25,5 +31,5 @@ method = method.toUpperCase(); | ||
export const Path = (pattern: string) => { | ||
export const Path = (pattern: string, options?: PathToRegExpOptions) => { | ||
const keys: Key[] = []; | ||
const regExp = pathToRegexp(pattern, keys); | ||
const regExp = pathToRegexp(pattern, keys, options); | ||
@@ -47,6 +53,14 @@ return ({ req: { url, params } }: Context) => { | ||
export interface RouterOptions { | ||
pathToRegExpOptions?: PathToRegExpOptions; | ||
} | ||
export const defaultRouterOptions: RouterOptions = { | ||
pathToRegExpOptions: { strict: true } | ||
}; | ||
export class Router { | ||
private readonly routes: Route[]; | ||
constructor() { | ||
constructor(private readonly options: RouterOptions = defaultRouterOptions) { | ||
this.routes = []; | ||
@@ -56,27 +70,34 @@ } | ||
public get(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Get, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Get, Path(pathname, opts)], ...middleware); | ||
} | ||
public post(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Post, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Post, Path(pathname, opts)], ...middleware); | ||
} | ||
public put(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Put, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Put, Path(pathname, opts)], ...middleware); | ||
} | ||
public patch(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Patch, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Patch, Path(pathname, opts)], ...middleware); | ||
} | ||
public delete(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Delete, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Delete, Path(pathname, opts)], ...middleware); | ||
} | ||
public head(pathname: string, ...middleware: Middleware[]) { | ||
return this.compose([Head, Path(pathname)], ...middleware); | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Head, Path(pathname, opts)], ...middleware); | ||
} | ||
public all(...middleware: Middleware[]) { | ||
return this.compose([], ...middleware); | ||
public all(pathname: string, ...middleware: Middleware[]) { | ||
const opts = this.options.pathToRegExpOptions; | ||
return this.compose([Path(pathname, opts)], ...middleware); | ||
} | ||
@@ -121,22 +142,4 @@ | ||
} | ||
params[name] = decodePathnameComponent(value); | ||
params[name] = decodeURIComponent(value); | ||
} | ||
} | ||
function decodePathnameComponent(component: string): string { | ||
if (component.length === 0) { | ||
return component; | ||
} | ||
try { | ||
return decodeURIComponent(component); | ||
} catch (err) { | ||
if (err instanceof URIError) { | ||
throw new HttpError( | ||
400, | ||
`Unable to decode pathname component "${component}".` | ||
); | ||
} | ||
throw err; | ||
} | ||
} |
50954
1418
82