-
Notifications
You must be signed in to change notification settings - Fork 119
/
index.ts
161 lines (131 loc) · 3.8 KB
/
index.ts
1
2
3
4
5
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { parse } from "cookie";
const hasField = (
{
request,
url,
cookies,
}: { request: Request; url: URL; cookies: Record<string, string> },
has: Source["has"][0]
) => {
switch (has.type) {
case "host": {
// TODO: URL host, hostname or HTTP Header host?
return url.host === has.value;
}
case "header": {
if (has.value !== undefined) {
return request.headers.get(has.key)?.match(has.value);
}
return request.headers.has(has.key);
}
case "cookie": {
const cookie = cookies[has.key];
if (has.value !== undefined) {
return cookie?.match(has.value);
}
return cookie !== undefined;
}
case "query": {
if (has.value !== undefined) {
return url.searchParams.get(has.key)?.match(has.value);
}
return url.searchParams.has(has.key);
}
}
};
export const routesMatcher = (
{ request }: { request: Request },
routes?: Config["routes"]
): Config["routes"] => {
// https://vercel.com/docs/build-output-api/v3#build-output-configuration/supported-properties/routes
const url = new URL(request.url);
const cookies = parse(request.headers.get("cookie") || "");
const matchingRoutes: Config["routes"] = [];
for (const route of routes || []) {
// https://vercel.com/docs/build-output-api/v3#build-output-configuration/supported-properties/routes/source-route
// TODO: continue, check, locale, middlewarePath
if ("methods" in route) {
const requestMethod = request.method.toLowerCase();
const foundMatch = route.methods.find(
(method) => method.toLowerCase() === requestMethod
);
if (!foundMatch) {
continue;
}
}
if ("has" in route) {
const okay = route.has.every((has) =>
hasField({ request, url, cookies }, has)
);
if (!okay) {
continue;
}
}
if ("missing" in route) {
const notOkay = route.missing.find((has) =>
hasField({ request, url, cookies }, has)
);
if (notOkay) {
continue;
}
}
let caseSensitive = false;
if ("caseSensitive" in route && route.caseSensitive) {
caseSensitive = true;
}
if ("src" in route) {
const regExp = new RegExp(route.src, caseSensitive ? undefined : "i");
const match = url.pathname.match(regExp);
if (match) {
matchingRoutes.push(route);
// if (!("continue" in route) || !route.continue) return matchingRoutes;
}
} else {
matchingRoutes.push(route);
// TODO: route.handle
}
}
return matchingRoutes;
};
type EdgeFunction = {
default: (
request: Request,
context: ExecutionContext
) => Response | Promise<Response>;
};
type EdgeFunctions = {
matchers: { regexp: string }[];
entrypoint: EdgeFunction;
}[];
declare const __CONFIG__: Config;
declare const __FUNCTIONS__: EdgeFunctions;
declare const __MIDDLEWARE__: EdgeFunctions;
export default {
async fetch(request, env, context) {
const { pathname } = new URL(request.url);
const routes = routesMatcher({ request }, __CONFIG__.routes);
for (const route of routes) {
if ("middlewarePath" in route && route.middlewarePath in __MIDDLEWARE__) {
return await __MIDDLEWARE__[route.middlewarePath].entrypoint.default(
request,
context
);
}
}
for (const { matchers, entrypoint } of Object.values(__FUNCTIONS__)) {
let found = false;
for (const matcher of matchers) {
if (matcher.regexp) {
if (pathname.match(new RegExp(matcher?.regexp))) {
found = true;
break;
}
}
}
if (found) {
return entrypoint.default(request, context);
}
}
return env.ASSETS.fetch(request);
},
} as ExportedHandler<{ ASSETS: Fetcher }>;