@cfworker/web 5.0.0 → 5.0.2
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/dist/{types → commonjs}/normalize-pathname.d.ts +1 -1
- package/dist/{cjs → commonjs}/normalize-pathname.js +2 -2
- package/dist/commonjs/package.json +3 -0
- package/dist/esm/accepts.d.ts +12 -0
- package/dist/esm/application.d.ts +11 -0
- package/dist/esm/context.d.ts +15 -0
- package/dist/esm/cookies.d.ts +8 -0
- package/dist/esm/http-error.d.ts +7 -0
- package/dist/esm/middleware.d.ts +3 -0
- package/dist/esm/normalize-pathname.d.ts +3 -0
- package/dist/esm/normalize-pathname.js +1 -1
- package/dist/esm/package.json +3 -0
- package/dist/esm/req.d.ts +24 -0
- package/dist/esm/response-builder.d.ts +22 -0
- package/dist/esm/router.d.ts +39 -0
- package/dist/esm/validate.d.ts +21 -0
- package/package.json +34 -30
- package/dist/tsconfig.cjs.tsbuildinfo +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/accepts.ts +0 -87
- package/src/application.ts +0 -55
- package/src/context.ts +0 -27
- package/src/cookies.ts +0 -30
- package/src/declarations.d.ts +0 -10
- package/src/http-error.ts +0 -36
- package/src/middleware.ts +0 -34
- package/src/normalize-pathname.ts +0 -22
- package/src/req.ts +0 -59
- package/src/response-builder.ts +0 -158
- package/src/router.ts +0 -146
- package/src/validate.ts +0 -121
- /package/dist/{types → commonjs}/accepts.d.ts +0 -0
- /package/dist/{cjs → commonjs}/accepts.js +0 -0
- /package/dist/{types → commonjs}/application.d.ts +0 -0
- /package/dist/{cjs → commonjs}/application.js +0 -0
- /package/dist/{types → commonjs}/context.d.ts +0 -0
- /package/dist/{cjs → commonjs}/context.js +0 -0
- /package/dist/{types → commonjs}/cookies.d.ts +0 -0
- /package/dist/{cjs → commonjs}/cookies.js +0 -0
- /package/dist/{types → commonjs}/http-error.d.ts +0 -0
- /package/dist/{cjs → commonjs}/http-error.js +0 -0
- /package/dist/{types → commonjs}/index.d.ts +0 -0
- /package/dist/{cjs → commonjs}/index.js +0 -0
- /package/dist/{types → commonjs}/middleware.d.ts +0 -0
- /package/dist/{cjs → commonjs}/middleware.js +0 -0
- /package/dist/{types → commonjs}/req.d.ts +0 -0
- /package/dist/{cjs → commonjs}/req.js +0 -0
- /package/dist/{types → commonjs}/response-builder.d.ts +0 -0
- /package/dist/{cjs → commonjs}/response-builder.js +0 -0
- /package/dist/{types → commonjs}/router.d.ts +0 -0
- /package/dist/{cjs → commonjs}/router.js +0 -0
- /package/dist/{types → commonjs}/validate.d.ts +0 -0
- /package/dist/{cjs → commonjs}/validate.js +0 -0
- /package/{src/index.ts → dist/esm/index.d.ts} +0 -0
package/src/cookies.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
parse as parseCookie,
|
|
3
|
-
serialize as serializeCookie,
|
|
4
|
-
SerializeOptions
|
|
5
|
-
} from 'cookie';
|
|
6
|
-
|
|
7
|
-
const noCookies = Object.create(null);
|
|
8
|
-
|
|
9
|
-
export class Cookies {
|
|
10
|
-
private readonly requestCookies: Record<string, string>;
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
requestHeaders: Headers,
|
|
14
|
-
private readonly responseHeaders: Headers
|
|
15
|
-
) {
|
|
16
|
-
const cookie = requestHeaders.get('cookie');
|
|
17
|
-
this.requestCookies = cookie ? parseCookie(cookie) : noCookies;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public get(name: string): string | null {
|
|
21
|
-
return this.requestCookies[name] || null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public set(name: string, val: string, options?: SerializeOptions): void {
|
|
25
|
-
this.responseHeaders.append(
|
|
26
|
-
'Set-Cookie',
|
|
27
|
-
serializeCookie(name, val, options)
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/declarations.d.ts
DELETED
package/src/http-error.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import statuses from 'statuses';
|
|
2
|
-
|
|
3
|
-
export class HttpError extends Error {
|
|
4
|
-
public readonly status: number;
|
|
5
|
-
public readonly statusText: string;
|
|
6
|
-
public readonly body: string | object;
|
|
7
|
-
|
|
8
|
-
constructor(status: number, body: string | object) {
|
|
9
|
-
const statusText = statuses.message[status]!;
|
|
10
|
-
|
|
11
|
-
super(statusText);
|
|
12
|
-
|
|
13
|
-
this.name = this.constructor.name;
|
|
14
|
-
if (typeof Error.captureStackTrace === 'function') {
|
|
15
|
-
Error.captureStackTrace(this, this.constructor);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
this.status = status;
|
|
19
|
-
this.statusText = statusText;
|
|
20
|
-
this.body = body;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public toResponse(): Response {
|
|
24
|
-
let body = this.body || this.statusText;
|
|
25
|
-
let contentType: string;
|
|
26
|
-
if (typeof body === 'string') {
|
|
27
|
-
contentType = 'text/plain';
|
|
28
|
-
} else {
|
|
29
|
-
contentType = 'application/json';
|
|
30
|
-
body = JSON.stringify(body);
|
|
31
|
-
}
|
|
32
|
-
const { status, statusText } = this;
|
|
33
|
-
const headers = { 'content-type': contentType };
|
|
34
|
-
return new Response(body, { status, statusText, headers });
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/middleware.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Context } from './context.js';
|
|
2
|
-
|
|
3
|
-
export type Middleware = (
|
|
4
|
-
context: Context,
|
|
5
|
-
next: () => Promise<void>
|
|
6
|
-
) => Promise<void> | void;
|
|
7
|
-
|
|
8
|
-
export function composeMiddleware(middleware: Middleware[]): Middleware {
|
|
9
|
-
if (!Array.isArray(middleware))
|
|
10
|
-
throw new TypeError('Middleware stack must be an array!');
|
|
11
|
-
for (const fn of middleware) {
|
|
12
|
-
if (typeof fn !== 'function')
|
|
13
|
-
throw new TypeError('Middleware must be composed of functions!');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return function (context, next) {
|
|
17
|
-
// last called middleware #
|
|
18
|
-
let index = -1;
|
|
19
|
-
return dispatch(0);
|
|
20
|
-
function dispatch(i: number): Promise<void> {
|
|
21
|
-
if (i <= index)
|
|
22
|
-
return Promise.reject(new Error('next() called multiple times'));
|
|
23
|
-
index = i;
|
|
24
|
-
let fn = middleware[i];
|
|
25
|
-
if (i === middleware.length) fn = next;
|
|
26
|
-
if (!fn) return Promise.resolve();
|
|
27
|
-
try {
|
|
28
|
-
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
|
|
29
|
-
} catch (err) {
|
|
30
|
-
return Promise.reject(err);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { HttpError } from './http-error';
|
|
2
|
-
import { Middleware } from './middleware';
|
|
3
|
-
|
|
4
|
-
export function normalizePathname(pathname: string): string {
|
|
5
|
-
return decodeURIComponent(pathname).replace(/\/\/+/g, '/').normalize();
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const normalizePathnameMiddleware: Middleware = async (
|
|
9
|
-
{ req },
|
|
10
|
-
next
|
|
11
|
-
) => {
|
|
12
|
-
const pathname = req.url.pathname;
|
|
13
|
-
try {
|
|
14
|
-
req.url.pathname = normalizePathname(pathname);
|
|
15
|
-
} catch (err) {
|
|
16
|
-
if (err instanceof URIError) {
|
|
17
|
-
throw new HttpError(400, `Unable to decode pathname "${pathname}".`);
|
|
18
|
-
}
|
|
19
|
-
throw err;
|
|
20
|
-
}
|
|
21
|
-
await next();
|
|
22
|
-
};
|
package/src/req.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { Reviver, safeParse } from 'secure-json-parse';
|
|
2
|
-
import { Accepts } from './accepts.js';
|
|
3
|
-
|
|
4
|
-
export class Req {
|
|
5
|
-
public readonly raw: Request;
|
|
6
|
-
public readonly method: string;
|
|
7
|
-
public readonly url: URL;
|
|
8
|
-
public readonly headers: Headers;
|
|
9
|
-
public readonly params: Record<string, string>;
|
|
10
|
-
public readonly accepts: Accepts;
|
|
11
|
-
public readonly body: ReqBody;
|
|
12
|
-
|
|
13
|
-
constructor(request: Request) {
|
|
14
|
-
this.raw = request;
|
|
15
|
-
this.method = request.method;
|
|
16
|
-
this.url = new URL(request.url);
|
|
17
|
-
this.headers = request.headers;
|
|
18
|
-
this.params = Object.create(null);
|
|
19
|
-
this.accepts = new Accepts(request.headers);
|
|
20
|
-
this.body = new ReqBody(request);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class ReqBody {
|
|
25
|
-
private _arrayBuffer: Promise<ArrayBuffer> | undefined;
|
|
26
|
-
private _formData: Promise<FormData> | undefined;
|
|
27
|
-
private _json: Promise<any> | undefined;
|
|
28
|
-
private _text: Promise<string> | undefined;
|
|
29
|
-
|
|
30
|
-
constructor(private readonly request: Request) {}
|
|
31
|
-
|
|
32
|
-
public arrayBuffer(): Promise<ArrayBuffer> {
|
|
33
|
-
if (!this._arrayBuffer) {
|
|
34
|
-
this._arrayBuffer = this.request.arrayBuffer();
|
|
35
|
-
}
|
|
36
|
-
return this._arrayBuffer;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public formData(): Promise<FormData> {
|
|
40
|
-
if (!this._formData) {
|
|
41
|
-
this._formData = this.request.formData();
|
|
42
|
-
}
|
|
43
|
-
return this._formData;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
public json(reviver?: Reviver): Promise<any> {
|
|
47
|
-
if (!this._json) {
|
|
48
|
-
this._json = this.text().then(text => safeParse(text, reviver));
|
|
49
|
-
}
|
|
50
|
-
return this._json;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public text(): Promise<string> {
|
|
54
|
-
if (!this._text) {
|
|
55
|
-
this._text = this.request.text();
|
|
56
|
-
}
|
|
57
|
-
return this._text;
|
|
58
|
-
}
|
|
59
|
-
}
|
package/src/response-builder.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import statuses from 'statuses';
|
|
2
|
-
|
|
3
|
-
export type ExtendedBodyInit =
|
|
4
|
-
| BodyInit
|
|
5
|
-
| boolean
|
|
6
|
-
| Date
|
|
7
|
-
| number
|
|
8
|
-
| object
|
|
9
|
-
| null;
|
|
10
|
-
|
|
11
|
-
export class ResponseBuilder {
|
|
12
|
-
public readonly headers: Headers = new Headers();
|
|
13
|
-
private _status = 404;
|
|
14
|
-
private _explicitStatus = false;
|
|
15
|
-
private _implicitType = false;
|
|
16
|
-
private _body: ExtendedBodyInit = null;
|
|
17
|
-
private _stringifyBody = false;
|
|
18
|
-
|
|
19
|
-
get status(): number {
|
|
20
|
-
return this._status;
|
|
21
|
-
}
|
|
22
|
-
set status(value) {
|
|
23
|
-
this._explicitStatus = true;
|
|
24
|
-
this._status = value;
|
|
25
|
-
if (this.body && statuses.empty[value]) {
|
|
26
|
-
this.body = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get statusText(): string {
|
|
31
|
-
return statuses.message[this._status]!;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
get body() {
|
|
35
|
-
return this._body;
|
|
36
|
-
}
|
|
37
|
-
set body(value: ExtendedBodyInit | null) {
|
|
38
|
-
this._body = value;
|
|
39
|
-
|
|
40
|
-
// no content
|
|
41
|
-
if (value === null) {
|
|
42
|
-
if (!statuses.empty[this.status]) {
|
|
43
|
-
this._status = 204;
|
|
44
|
-
}
|
|
45
|
-
this.headers.delete('content-type');
|
|
46
|
-
this.headers.delete('content-length');
|
|
47
|
-
this.headers.delete('transfer-encoding');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// set the status
|
|
52
|
-
if (!this._explicitStatus) {
|
|
53
|
-
this._status = 200;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// set Content-Type
|
|
57
|
-
if (
|
|
58
|
-
// Response will automatically use Blob's type.
|
|
59
|
-
value instanceof Blob ||
|
|
60
|
-
// Response will automatically use multipart/form-data.
|
|
61
|
-
value instanceof FormData ||
|
|
62
|
-
// Response will automatically use application/x-www-form-urlencoded.
|
|
63
|
-
value instanceof URLSearchParams ||
|
|
64
|
-
// Cannot determine Content-Type.
|
|
65
|
-
ArrayBuffer.isView(value) ||
|
|
66
|
-
value instanceof ArrayBuffer ||
|
|
67
|
-
value instanceof ReadableStream
|
|
68
|
-
) {
|
|
69
|
-
this._stringifyBody = false;
|
|
70
|
-
if (this._implicitType) {
|
|
71
|
-
this._implicitType = false;
|
|
72
|
-
this.headers.delete('content-type');
|
|
73
|
-
}
|
|
74
|
-
} else if (typeof value === 'string') {
|
|
75
|
-
this._stringifyBody = false;
|
|
76
|
-
if (!this.headers.has('content-type') || this._implicitType) {
|
|
77
|
-
this._implicitType = true;
|
|
78
|
-
if (/^\s*</.test(value)) {
|
|
79
|
-
this.headers.set('content-type', 'text/html;charset=UTF-8');
|
|
80
|
-
} else {
|
|
81
|
-
this.headers.set('content-type', 'text/plain;charset=UTF-8');
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
this._stringifyBody = true;
|
|
86
|
-
this._implicitType = true;
|
|
87
|
-
this.headers.set('content-type', 'application/json;charset=UTF-8');
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
redirect(url: string | URL): void {
|
|
92
|
-
if (url instanceof URL) {
|
|
93
|
-
url = url.href;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
this.headers.set('location', url);
|
|
97
|
-
|
|
98
|
-
if (!statuses.redirect[this.status]) {
|
|
99
|
-
this.status = 302;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
this.type = 'text/plain;charset=UTF-8';
|
|
103
|
-
this.body = `Redirecting to ${url}.`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get type(): string {
|
|
107
|
-
const type = this.headers.get('content-type');
|
|
108
|
-
if (!type) {
|
|
109
|
-
return '';
|
|
110
|
-
}
|
|
111
|
-
return type.split(';', 1)[0];
|
|
112
|
-
}
|
|
113
|
-
set type(value) {
|
|
114
|
-
this._implicitType = false;
|
|
115
|
-
if (value) {
|
|
116
|
-
this.headers.set('content-type', value);
|
|
117
|
-
} else {
|
|
118
|
-
this.headers.delete('content-type');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
get lastModified() {
|
|
123
|
-
const date = this.headers.get('last-modified');
|
|
124
|
-
return date ? new Date(date) : null;
|
|
125
|
-
}
|
|
126
|
-
set lastModified(value: string | Date | null) {
|
|
127
|
-
if (value === null) {
|
|
128
|
-
this.headers.delete('last-modified');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (typeof value === 'string') {
|
|
132
|
-
value = new Date(value);
|
|
133
|
-
}
|
|
134
|
-
this.headers.set('last-modified', value.toUTCString());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get etag(): string | null {
|
|
138
|
-
return this.headers.get('etag');
|
|
139
|
-
}
|
|
140
|
-
set etag(value) {
|
|
141
|
-
if (value) {
|
|
142
|
-
if (!/^(W\/)?"/.test(value)) {
|
|
143
|
-
value = `"${value}"`;
|
|
144
|
-
}
|
|
145
|
-
this.headers.set('etag', value);
|
|
146
|
-
} else {
|
|
147
|
-
this.headers.delete('etag');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
create(): Response {
|
|
152
|
-
const { body: rawBody, status, statusText, headers } = this;
|
|
153
|
-
const body = this._stringifyBody
|
|
154
|
-
? JSON.stringify(rawBody)
|
|
155
|
-
: (rawBody as BodyInit);
|
|
156
|
-
return new Response(body, { status, statusText, headers });
|
|
157
|
-
}
|
|
158
|
-
}
|
package/src/router.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { Key, pathToRegexp, PathToRegexpOptions } from 'path-to-regexp';
|
|
2
|
-
import { Context } from './context.js';
|
|
3
|
-
import { composeMiddleware, Middleware } from './middleware.js';
|
|
4
|
-
|
|
5
|
-
export type Predicate = (context: Context) => boolean;
|
|
6
|
-
|
|
7
|
-
export const Method: (method: string) => Predicate = (method: string) => {
|
|
8
|
-
method = method.toUpperCase();
|
|
9
|
-
return ({ req }: Context) => req.method === method;
|
|
10
|
-
};
|
|
11
|
-
export const Get: Predicate = Method('get');
|
|
12
|
-
export const Post: Predicate = Method('post');
|
|
13
|
-
export const Put: Predicate = Method('put');
|
|
14
|
-
export const Patch: Predicate = Method('patch');
|
|
15
|
-
export const Delete: Predicate = Method('delete');
|
|
16
|
-
export const Head: Predicate = Method('head');
|
|
17
|
-
export const Options: Predicate = Method('options');
|
|
18
|
-
|
|
19
|
-
export const Header: (header: string, value: string) => Predicate = (
|
|
20
|
-
header: string,
|
|
21
|
-
value: string
|
|
22
|
-
) => {
|
|
23
|
-
value = value.toLowerCase();
|
|
24
|
-
return ({ req }: Context) => req.headers.get(header) === value;
|
|
25
|
-
};
|
|
26
|
-
export const Host: (host: string) => Predicate = (host: string) =>
|
|
27
|
-
Header('host', host);
|
|
28
|
-
|
|
29
|
-
export const Path: (
|
|
30
|
-
pattern: string,
|
|
31
|
-
options?: PathToRegexpOptions
|
|
32
|
-
) => Predicate = (pattern: string, options?: PathToRegexpOptions) => {
|
|
33
|
-
const regexp = pathToRegexp(pattern, options);
|
|
34
|
-
|
|
35
|
-
return ({ req: { url, params } }: Context) => {
|
|
36
|
-
const match = url.pathname.match(regexp);
|
|
37
|
-
if (!match) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
collectParameters(regexp.keys, match, params);
|
|
41
|
-
return true;
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type RouteCondition = (context: Context) => boolean;
|
|
46
|
-
|
|
47
|
-
export interface Route {
|
|
48
|
-
conditions: RouteCondition[];
|
|
49
|
-
middleware: Middleware;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface RouterOptions {
|
|
53
|
-
pathToRegExpOptions?: PathToRegexpOptions;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const defaultRouterOptions: RouterOptions = {
|
|
57
|
-
pathToRegExpOptions: {
|
|
58
|
-
strict: true
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export class Router {
|
|
63
|
-
private readonly routes: Route[];
|
|
64
|
-
|
|
65
|
-
constructor(private readonly options: RouterOptions = defaultRouterOptions) {
|
|
66
|
-
this.routes = [];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public get(pathname: string, ...middleware: Middleware[]): Router {
|
|
70
|
-
const opts = this.options.pathToRegExpOptions;
|
|
71
|
-
return this.compose([Get, Path(pathname, opts)], ...middleware);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public post(pathname: string, ...middleware: Middleware[]): Router {
|
|
75
|
-
const opts = this.options.pathToRegExpOptions;
|
|
76
|
-
return this.compose([Post, Path(pathname, opts)], ...middleware);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public put(pathname: string, ...middleware: Middleware[]): Router {
|
|
80
|
-
const opts = this.options.pathToRegExpOptions;
|
|
81
|
-
return this.compose([Put, Path(pathname, opts)], ...middleware);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public patch(pathname: string, ...middleware: Middleware[]): Router {
|
|
85
|
-
const opts = this.options.pathToRegExpOptions;
|
|
86
|
-
return this.compose([Patch, Path(pathname, opts)], ...middleware);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public delete(pathname: string, ...middleware: Middleware[]): Router {
|
|
90
|
-
const opts = this.options.pathToRegExpOptions;
|
|
91
|
-
return this.compose([Delete, Path(pathname, opts)], ...middleware);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public head(pathname: string, ...middleware: Middleware[]): Router {
|
|
95
|
-
const opts = this.options.pathToRegExpOptions;
|
|
96
|
-
return this.compose([Head, Path(pathname, opts)], ...middleware);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public all(pathname: string, ...middleware: Middleware[]): Router {
|
|
100
|
-
const opts = this.options.pathToRegExpOptions;
|
|
101
|
-
return this.compose([Path(pathname, opts)], ...middleware);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public middleware: Middleware = async (ctx, next) => {
|
|
105
|
-
const resolved = this.resolve(ctx);
|
|
106
|
-
|
|
107
|
-
if (resolved) {
|
|
108
|
-
await resolved.middleware(ctx, next);
|
|
109
|
-
} else {
|
|
110
|
-
await next();
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
public compose(
|
|
115
|
-
conditions: RouteCondition[],
|
|
116
|
-
...middleware: Middleware[]
|
|
117
|
-
): Router {
|
|
118
|
-
this.routes.push({
|
|
119
|
-
conditions,
|
|
120
|
-
middleware: composeMiddleware(middleware)
|
|
121
|
-
});
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public resolve(ctx: Context): Route | undefined {
|
|
126
|
-
return this.routes.find(
|
|
127
|
-
({ conditions }) =>
|
|
128
|
-
conditions.length === 0 || conditions.every(c => c(ctx))
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function collectParameters(
|
|
134
|
-
keys: Key[],
|
|
135
|
-
match: RegExpMatchArray,
|
|
136
|
-
params: Record<string, string>
|
|
137
|
-
) {
|
|
138
|
-
for (let i = 1; i < match.length; i++) {
|
|
139
|
-
const name = keys[i - 1].name;
|
|
140
|
-
const value = match[i];
|
|
141
|
-
if (!value) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
params[name] = decodeURIComponent(value);
|
|
145
|
-
}
|
|
146
|
-
}
|
package/src/validate.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dereference,
|
|
3
|
-
OutputUnit,
|
|
4
|
-
Schema,
|
|
5
|
-
validate as schemaValidate
|
|
6
|
-
} from '@cfworker/json-schema';
|
|
7
|
-
import { HttpError } from './http-error.js';
|
|
8
|
-
import { Middleware } from './middleware.js';
|
|
9
|
-
|
|
10
|
-
export interface RequestSchemas {
|
|
11
|
-
body?: Schema;
|
|
12
|
-
headers?: Schema;
|
|
13
|
-
params?: Schema;
|
|
14
|
-
search?: Schema;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type RequestPart = keyof RequestSchemas;
|
|
18
|
-
|
|
19
|
-
export type RequestParser = (data: URLSearchParams | FormData) => any;
|
|
20
|
-
|
|
21
|
-
const draft = '2019-09';
|
|
22
|
-
|
|
23
|
-
const hasBody: Record<string, true> = {
|
|
24
|
-
POST: true,
|
|
25
|
-
PUT: true,
|
|
26
|
-
DELETE: true,
|
|
27
|
-
PATCH: true
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function middlewareFactory(
|
|
31
|
-
schemas: RequestSchemas,
|
|
32
|
-
parser: RequestParser = Object.fromEntries,
|
|
33
|
-
lookup: Record<string, Schema | boolean> = Object.create(null)
|
|
34
|
-
): Middleware {
|
|
35
|
-
const {
|
|
36
|
-
headers: $headers,
|
|
37
|
-
search: $search,
|
|
38
|
-
params: $params,
|
|
39
|
-
body: $body
|
|
40
|
-
} = schemas;
|
|
41
|
-
|
|
42
|
-
if ($headers) {
|
|
43
|
-
dereference($headers, { ...lookup });
|
|
44
|
-
}
|
|
45
|
-
if ($search) {
|
|
46
|
-
dereference($search, { ...lookup });
|
|
47
|
-
}
|
|
48
|
-
if ($params) {
|
|
49
|
-
dereference($params, { ...lookup });
|
|
50
|
-
}
|
|
51
|
-
if ($body) {
|
|
52
|
-
dereference($body, { ...lookup });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return async ({ req }, next) => {
|
|
56
|
-
if ($params) {
|
|
57
|
-
validateRequestPart('params', req.params, $params, lookup);
|
|
58
|
-
}
|
|
59
|
-
if ($headers) {
|
|
60
|
-
const headers = Object.fromEntries(req.headers);
|
|
61
|
-
validateRequestPart('headers', headers, $headers, lookup);
|
|
62
|
-
}
|
|
63
|
-
if ($search) {
|
|
64
|
-
const search = parser(req.url.searchParams);
|
|
65
|
-
validateRequestPart('search', search, $search, lookup);
|
|
66
|
-
}
|
|
67
|
-
if ($body && hasBody[req.method]) {
|
|
68
|
-
let body: any;
|
|
69
|
-
const contentType = req.headers.get('content-type') || '';
|
|
70
|
-
if (
|
|
71
|
-
contentType === 'application/x-www-form-urlencoded' ||
|
|
72
|
-
contentType.startsWith('application/x-www-form-urlencoded;') ||
|
|
73
|
-
contentType.startsWith('multipart/form-data;')
|
|
74
|
-
) {
|
|
75
|
-
const form = await req.body.formData();
|
|
76
|
-
body = parser(form);
|
|
77
|
-
} else {
|
|
78
|
-
body = await req.body.json();
|
|
79
|
-
}
|
|
80
|
-
validateRequestPart('body', body, $body, lookup);
|
|
81
|
-
}
|
|
82
|
-
await next();
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export const messages: Record<RequestPart, string> = {
|
|
87
|
-
params: 'Parameters in request path are invalid.',
|
|
88
|
-
headers: 'Headers are invalid.',
|
|
89
|
-
search: 'Query string parameters are invalid.',
|
|
90
|
-
body: 'Body is invalid.'
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export interface RequestValidationErrorBody {
|
|
94
|
-
status: 400;
|
|
95
|
-
part: RequestPart;
|
|
96
|
-
message: string;
|
|
97
|
-
errors: OutputUnit[];
|
|
98
|
-
schema: Schema;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function validateRequestPart(
|
|
102
|
-
part: RequestPart,
|
|
103
|
-
instance: any,
|
|
104
|
-
schema: Schema,
|
|
105
|
-
lookup: Record<string, boolean | Schema>
|
|
106
|
-
) {
|
|
107
|
-
const result = schemaValidate(instance, schema, draft, lookup);
|
|
108
|
-
if (result.valid) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const errorBody: RequestValidationErrorBody = {
|
|
112
|
-
status: 400,
|
|
113
|
-
part,
|
|
114
|
-
message: messages[part],
|
|
115
|
-
errors: result.errors,
|
|
116
|
-
schema
|
|
117
|
-
};
|
|
118
|
-
throw new HttpError(400, errorBody);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export const validate: typeof middlewareFactory = middlewareFactory;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|