routup 5.2.0 → 6.0.0-beta.1
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/README.md +68 -47
- package/dist/bun.d.mts +3 -3
- package/dist/bun.mjs +4 -4
- package/dist/bun.mjs.map +1 -1
- package/dist/cloudflare.d.mts +3 -3
- package/dist/cloudflare.mjs +4 -4
- package/dist/cloudflare.mjs.map +1 -1
- package/dist/deno.d.mts +3 -3
- package/dist/deno.mjs +4 -4
- package/dist/deno.mjs.map +1 -1
- package/dist/generic.d.mts +3 -3
- package/dist/generic.mjs +4 -4
- package/dist/generic.mjs.map +1 -1
- package/dist/index-B80OpXdo.d.mts +1844 -0
- package/dist/node.d.mts +4 -4
- package/dist/node.mjs +6 -6
- package/dist/node.mjs.map +1 -1
- package/dist/service-worker.d.mts +3 -3
- package/dist/service-worker.mjs +4 -4
- package/dist/service-worker.mjs.map +1 -1
- package/dist/src-DaK6SZc0.mjs +2694 -0
- package/dist/src-DaK6SZc0.mjs.map +1 -0
- package/package.json +6 -4
- package/dist/index-DdsCL8RI.d.mts +0 -1159
- package/dist/src-DX0rndew.mjs +0 -1993
- package/dist/src-DX0rndew.mjs.map +0 -1
package/dist/src-DX0rndew.mjs
DELETED
|
@@ -1,1993 +0,0 @@
|
|
|
1
|
-
import { FastURL } from "srvx";
|
|
2
|
-
import { hasInstanceof, markInstanceof } from "@ebec/core";
|
|
3
|
-
import { HTTPError, isHTTPError } from "@ebec/http";
|
|
4
|
-
import { subtle } from "uncrypto";
|
|
5
|
-
import { merge } from "smob";
|
|
6
|
-
import { compile } from "proxy-addr";
|
|
7
|
-
import { get, getType } from "mime-explorer";
|
|
8
|
-
import Negotiator from "negotiator";
|
|
9
|
-
import { pathToRegexp } from "path-to-regexp";
|
|
10
|
-
//#region src/constants.ts
|
|
11
|
-
const MethodName = {
|
|
12
|
-
GET: "GET",
|
|
13
|
-
POST: "POST",
|
|
14
|
-
PUT: "PUT",
|
|
15
|
-
PATCH: "PATCH",
|
|
16
|
-
DELETE: "DELETE",
|
|
17
|
-
OPTIONS: "OPTIONS",
|
|
18
|
-
HEAD: "HEAD"
|
|
19
|
-
};
|
|
20
|
-
const HeaderName = {
|
|
21
|
-
ACCEPT: "accept",
|
|
22
|
-
ACCEPT_CHARSET: "accept-charset",
|
|
23
|
-
ACCEPT_ENCODING: "accept-encoding",
|
|
24
|
-
ACCEPT_LANGUAGE: "accept-language",
|
|
25
|
-
ACCEPT_RANGES: "accept-ranges",
|
|
26
|
-
ALLOW: "allow",
|
|
27
|
-
CACHE_CONTROL: "cache-control",
|
|
28
|
-
CONTENT_DISPOSITION: "content-disposition",
|
|
29
|
-
CONTENT_ENCODING: "content-encoding",
|
|
30
|
-
CONTENT_LENGTH: "content-length",
|
|
31
|
-
CONTENT_RANGE: "content-range",
|
|
32
|
-
CONTENT_TYPE: "content-type",
|
|
33
|
-
CONNECTION: "connection",
|
|
34
|
-
COOKIE: "cookie",
|
|
35
|
-
ETag: "etag",
|
|
36
|
-
HOST: "host",
|
|
37
|
-
IF_MODIFIED_SINCE: "if-modified-since",
|
|
38
|
-
IF_NONE_MATCH: "if-none-match",
|
|
39
|
-
LAST_MODIFIED: "last-modified",
|
|
40
|
-
LOCATION: "location",
|
|
41
|
-
RANGE: "range",
|
|
42
|
-
RATE_LIMIT_LIMIT: "ratelimit-limit",
|
|
43
|
-
RATE_LIMIT_REMAINING: "ratelimit-remaining",
|
|
44
|
-
RATE_LIMIT_RESET: "ratelimit-reset",
|
|
45
|
-
RETRY_AFTER: "retry-after",
|
|
46
|
-
SET_COOKIE: "set-cookie",
|
|
47
|
-
TRANSFER_ENCODING: "transfer-encoding",
|
|
48
|
-
X_ACCEL_BUFFERING: "x-accel-buffering",
|
|
49
|
-
X_FORWARDED_HOST: "x-forwarded-host",
|
|
50
|
-
X_FORWARDED_FOR: "x-forwarded-for",
|
|
51
|
-
X_FORWARDED_PROTO: "x-forwarded-proto"
|
|
52
|
-
};
|
|
53
|
-
//#endregion
|
|
54
|
-
//#region src/response/helpers/cache.ts
|
|
55
|
-
function setResponseCacheHeaders(event, options) {
|
|
56
|
-
options = options || {};
|
|
57
|
-
const cacheControls = ["public"].concat(options.cacheControls || []);
|
|
58
|
-
if (options.maxAge !== void 0) cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
|
|
59
|
-
if (options.modifiedTime) {
|
|
60
|
-
const modifiedTime = typeof options.modifiedTime === "string" ? new Date(options.modifiedTime) : options.modifiedTime;
|
|
61
|
-
event.response.headers.set("last-modified", modifiedTime.toUTCString());
|
|
62
|
-
}
|
|
63
|
-
event.response.headers.set("cache-control", cacheControls.join(", "));
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
//#region src/error/module.ts
|
|
67
|
-
const ErrorSymbol = Symbol.for("RoutupError");
|
|
68
|
-
var RoutupError = class extends HTTPError {
|
|
69
|
-
constructor(input = {}) {
|
|
70
|
-
super(input);
|
|
71
|
-
this.name = "RoutupError";
|
|
72
|
-
markInstanceof(this, ErrorSymbol);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
//#endregion
|
|
76
|
-
//#region src/response/helpers/event-stream/utils.ts
|
|
77
|
-
function stripNewlines(value) {
|
|
78
|
-
return value.replace(/[\r\n]/g, "");
|
|
79
|
-
}
|
|
80
|
-
function serializeEventStreamMessage(message) {
|
|
81
|
-
let result = "";
|
|
82
|
-
if (message.id) result += `id: ${stripNewlines(message.id)}\n`;
|
|
83
|
-
if (message.event) result += `event: ${stripNewlines(message.event)}\n`;
|
|
84
|
-
if (typeof message.retry === "number" && Number.isInteger(message.retry)) result += `retry: ${message.retry}\n`;
|
|
85
|
-
const lines = message.data.replace(/\r/g, "").split("\n");
|
|
86
|
-
for (const line of lines) result += `data: ${line}\n`;
|
|
87
|
-
result += "\n";
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/response/helpers/event-stream/module.ts
|
|
92
|
-
function createEventStream(event, options) {
|
|
93
|
-
if (options?.maxMessageSize !== void 0) {
|
|
94
|
-
if (!Number.isInteger(options.maxMessageSize) || options.maxMessageSize < 0) throw new RoutupError("maxMessageSize must be a non-negative integer.");
|
|
95
|
-
}
|
|
96
|
-
let controller;
|
|
97
|
-
let closed = false;
|
|
98
|
-
const encoder = new TextEncoder();
|
|
99
|
-
const stream = new ReadableStream({
|
|
100
|
-
start(ctrl) {
|
|
101
|
-
controller = ctrl;
|
|
102
|
-
},
|
|
103
|
-
cancel() {
|
|
104
|
-
closed = true;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
const headers = new Headers(event.response.headers);
|
|
108
|
-
headers.set(HeaderName.CONTENT_TYPE, "text/event-stream");
|
|
109
|
-
headers.set(HeaderName.CACHE_CONTROL, "private, no-cache, no-store, no-transform, must-revalidate, max-age=0");
|
|
110
|
-
headers.set(HeaderName.X_ACCEL_BUFFERING, "no");
|
|
111
|
-
headers.set(HeaderName.CONNECTION, "keep-alive");
|
|
112
|
-
const handle = {
|
|
113
|
-
write(message) {
|
|
114
|
-
if (closed) return false;
|
|
115
|
-
if (typeof message === "string") return handle.write({ data: message });
|
|
116
|
-
const serialized = serializeEventStreamMessage(message);
|
|
117
|
-
if (options?.maxMessageSize !== void 0) {
|
|
118
|
-
if (encoder.encode(serialized).byteLength > options.maxMessageSize) return false;
|
|
119
|
-
}
|
|
120
|
-
controller.enqueue(encoder.encode(serialized));
|
|
121
|
-
return true;
|
|
122
|
-
},
|
|
123
|
-
end() {
|
|
124
|
-
if (closed) return;
|
|
125
|
-
closed = true;
|
|
126
|
-
controller.close();
|
|
127
|
-
},
|
|
128
|
-
response: new Response(stream, {
|
|
129
|
-
status: event.response.status,
|
|
130
|
-
headers
|
|
131
|
-
})
|
|
132
|
-
};
|
|
133
|
-
return handle;
|
|
134
|
-
}
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/utils/header.ts
|
|
137
|
-
function sanitizeHeaderValue(value) {
|
|
138
|
-
return value.replace(/[\r\n]/g, "");
|
|
139
|
-
}
|
|
140
|
-
//#endregion
|
|
141
|
-
//#region src/utils/etag/module.ts
|
|
142
|
-
async function sha1(str) {
|
|
143
|
-
const enc = new TextEncoder();
|
|
144
|
-
const hash = await subtle.digest("SHA-1", enc.encode(str));
|
|
145
|
-
return btoa(String.fromCharCode(...new Uint8Array(hash)));
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Generate an ETag.
|
|
149
|
-
*/
|
|
150
|
-
async function generateETag(input) {
|
|
151
|
-
if (input.length === 0) return "\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"";
|
|
152
|
-
const hash = await sha1(input);
|
|
153
|
-
return `"${input.length.toString(16)}-${hash.substring(0, 27)}"`;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Create a simple ETag.
|
|
157
|
-
*/
|
|
158
|
-
async function createEtag(input, options = {}) {
|
|
159
|
-
const tag = await generateETag(input);
|
|
160
|
-
return options.weak ? `W/${tag}` : tag;
|
|
161
|
-
}
|
|
162
|
-
//#endregion
|
|
163
|
-
//#region src/utils/object.ts
|
|
164
|
-
function isObject(item) {
|
|
165
|
-
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
166
|
-
}
|
|
167
|
-
//#endregion
|
|
168
|
-
//#region src/utils/etag/utils.ts
|
|
169
|
-
const textEncoder = /* @__PURE__ */ new TextEncoder();
|
|
170
|
-
function buildEtagFn(input) {
|
|
171
|
-
if (typeof input === "function") return input;
|
|
172
|
-
input = input ?? true;
|
|
173
|
-
if (input === false) return () => Promise.resolve(void 0);
|
|
174
|
-
let options = { weak: true };
|
|
175
|
-
if (isObject(input)) options = merge(input, options);
|
|
176
|
-
return async (body, size) => {
|
|
177
|
-
if (typeof options.threshold !== "undefined") {
|
|
178
|
-
if ((size ?? textEncoder.encode(body).byteLength) <= options.threshold) return;
|
|
179
|
-
}
|
|
180
|
-
return createEtag(body, options);
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
//#endregion
|
|
184
|
-
//#region src/utils/trust-proxy/module.ts
|
|
185
|
-
function buildTrustProxyFn(input) {
|
|
186
|
-
if (typeof input === "function") return input;
|
|
187
|
-
if (input === true) return () => true;
|
|
188
|
-
if (typeof input === "number") return (_address, hop) => hop < input;
|
|
189
|
-
if (typeof input === "string") input = input.split(",").map((value) => value.trim());
|
|
190
|
-
return compile(input || []);
|
|
191
|
-
}
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/utils/mime.ts
|
|
194
|
-
function getMimeType(type) {
|
|
195
|
-
if (type.includes("/")) return type;
|
|
196
|
-
return getType(type);
|
|
197
|
-
}
|
|
198
|
-
function getCharsetForMimeType(type) {
|
|
199
|
-
if (/^text\/|^application\/(javascript|json)/.test(type)) return "utf-8";
|
|
200
|
-
const meta = get(type);
|
|
201
|
-
if (meta && meta.charset) return meta.charset.toLowerCase();
|
|
202
|
-
}
|
|
203
|
-
//#endregion
|
|
204
|
-
//#region src/utils/method.ts
|
|
205
|
-
function toMethodName(input, alt) {
|
|
206
|
-
if (input) return input.toUpperCase();
|
|
207
|
-
return alt;
|
|
208
|
-
}
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/utils/path.ts
|
|
211
|
-
/**
|
|
212
|
-
* Based on https://github.com/unjs/pathe v1.1.1 (055f50a6f1131f4e5c56cf259dd8816168fba329)
|
|
213
|
-
*/
|
|
214
|
-
function normalizeWindowsPath(input = "") {
|
|
215
|
-
if (!input || !input.includes("\\")) return input;
|
|
216
|
-
return input.replace(/\\/g, "/");
|
|
217
|
-
}
|
|
218
|
-
const EXTNAME_RE = /.(\.[^./]+)$/;
|
|
219
|
-
function extname(input) {
|
|
220
|
-
const match = EXTNAME_RE.exec(normalizeWindowsPath(input));
|
|
221
|
-
return match && match[1] || "";
|
|
222
|
-
}
|
|
223
|
-
function basename(input, extension) {
|
|
224
|
-
const lastSegment = normalizeWindowsPath(input).split("/").pop();
|
|
225
|
-
if (!lastSegment) return input;
|
|
226
|
-
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
|
|
227
|
-
}
|
|
228
|
-
//#endregion
|
|
229
|
-
//#region src/utils/url.ts
|
|
230
|
-
const TRAILING_SLASH_RE = /\/$|\/\?/;
|
|
231
|
-
function hasTrailingSlash(input = "", queryParams = false) {
|
|
232
|
-
if (!queryParams) return input.endsWith("/");
|
|
233
|
-
return TRAILING_SLASH_RE.test(input);
|
|
234
|
-
}
|
|
235
|
-
function withoutTrailingSlash(input = "", queryParams = false) {
|
|
236
|
-
if (!queryParams) return (hasTrailingSlash(input) ? input.slice(0, -1) : input) || "/";
|
|
237
|
-
if (!hasTrailingSlash(input, true)) return input || "/";
|
|
238
|
-
const [s0, ...s] = input.split("?");
|
|
239
|
-
return (s0.slice(0, -1) || "/") + (s.length ? `?${s.join("?")}` : "");
|
|
240
|
-
}
|
|
241
|
-
function hasLeadingSlash(input = "") {
|
|
242
|
-
return input.startsWith("/");
|
|
243
|
-
}
|
|
244
|
-
function withLeadingSlash(input = "") {
|
|
245
|
-
return hasLeadingSlash(input) ? input : `/${input}`;
|
|
246
|
-
}
|
|
247
|
-
function cleanDoubleSlashes(input = "") {
|
|
248
|
-
if (input.includes("://")) return input.split("://").map((str) => cleanDoubleSlashes(str)).join("://");
|
|
249
|
-
return input.replace(/\/+/g, "/");
|
|
250
|
-
}
|
|
251
|
-
//#endregion
|
|
252
|
-
//#region src/response/helpers/header.ts
|
|
253
|
-
function appendResponseHeader(event, name, value) {
|
|
254
|
-
const { headers } = event.response;
|
|
255
|
-
if (Array.isArray(value)) for (const v of value) headers.append(name, sanitizeHeaderValue(v));
|
|
256
|
-
else headers.append(name, sanitizeHeaderValue(value));
|
|
257
|
-
}
|
|
258
|
-
function appendResponseHeaderDirective(event, name, value) {
|
|
259
|
-
const { headers } = event.response;
|
|
260
|
-
const existing = headers.get(name);
|
|
261
|
-
if (!existing) {
|
|
262
|
-
if (Array.isArray(value)) headers.set(name, sanitizeHeaderValue(value.join("; ")));
|
|
263
|
-
else headers.set(name, sanitizeHeaderValue(value));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const directives = existing.split("; ");
|
|
267
|
-
if (Array.isArray(value)) directives.push(...value);
|
|
268
|
-
else directives.push(value);
|
|
269
|
-
const unique = [...new Set(directives)];
|
|
270
|
-
headers.set(name, sanitizeHeaderValue(unique.join("; ")));
|
|
271
|
-
}
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/response/helpers/utils.ts
|
|
274
|
-
function setResponseContentTypeByFileName(event, fileName) {
|
|
275
|
-
const ext = extname(fileName);
|
|
276
|
-
if (ext) {
|
|
277
|
-
let type = getMimeType(ext.substring(1));
|
|
278
|
-
if (type) {
|
|
279
|
-
const charset = getCharsetForMimeType(type);
|
|
280
|
-
if (charset) type += `; charset=${charset}`;
|
|
281
|
-
event.response.headers.set(HeaderName.CONTENT_TYPE, type);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region src/response/helpers/header-disposition.ts
|
|
287
|
-
const ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;
|
|
288
|
-
const NON_ASCII_REGEXP = /[^\x20-\x7e]/g;
|
|
289
|
-
const QUOTE_REGEXP = /[\\"]/g;
|
|
290
|
-
const HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/;
|
|
291
|
-
const ASCII_TEXT_REGEXP = /^[\x20-\x7e]+$/;
|
|
292
|
-
const TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/;
|
|
293
|
-
function pencode(char) {
|
|
294
|
-
return `%${char.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
295
|
-
}
|
|
296
|
-
function quoteString(value) {
|
|
297
|
-
return `"${value.replace(QUOTE_REGEXP, "\\$&")}"`;
|
|
298
|
-
}
|
|
299
|
-
function getAscii(value) {
|
|
300
|
-
return value.replace(NON_ASCII_REGEXP, "?");
|
|
301
|
-
}
|
|
302
|
-
function encodeExtended(value) {
|
|
303
|
-
return encodeURIComponent(value).replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode);
|
|
304
|
-
}
|
|
305
|
-
function formatFilename(value) {
|
|
306
|
-
if (TOKEN_REGEXP.test(value)) return `filename=${value}`;
|
|
307
|
-
return `filename=${quoteString(value)}`;
|
|
308
|
-
}
|
|
309
|
-
function setDisposition(event, type, filename) {
|
|
310
|
-
let disposition = type;
|
|
311
|
-
if (typeof filename === "string") {
|
|
312
|
-
setResponseContentTypeByFileName(event, filename);
|
|
313
|
-
if (ASCII_TEXT_REGEXP.test(filename) && !HEX_ESCAPE_REGEXP.test(filename)) disposition += `; ${formatFilename(filename)}`;
|
|
314
|
-
else {
|
|
315
|
-
disposition += `; ${formatFilename(getAscii(filename))}`;
|
|
316
|
-
disposition += `; filename*=UTF-8''${encodeExtended(filename)}`;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
event.response.headers.set(HeaderName.CONTENT_DISPOSITION, disposition);
|
|
320
|
-
}
|
|
321
|
-
function setResponseHeaderAttachment(event, filename) {
|
|
322
|
-
setDisposition(event, "attachment", filename);
|
|
323
|
-
}
|
|
324
|
-
function setResponseHeaderInline(event, filename) {
|
|
325
|
-
setDisposition(event, "inline", filename);
|
|
326
|
-
}
|
|
327
|
-
//#endregion
|
|
328
|
-
//#region src/response/helpers/header-content-type.ts
|
|
329
|
-
function setResponseHeaderContentType(event, input, ifNotExists) {
|
|
330
|
-
if (ifNotExists) {
|
|
331
|
-
if (event.response.headers.get(HeaderName.CONTENT_TYPE)) return;
|
|
332
|
-
}
|
|
333
|
-
const contentType = getMimeType(input);
|
|
334
|
-
if (contentType) event.response.headers.set(HeaderName.CONTENT_TYPE, contentType);
|
|
335
|
-
}
|
|
336
|
-
//#endregion
|
|
337
|
-
//#region src/error/is.ts
|
|
338
|
-
function isError(input) {
|
|
339
|
-
return hasInstanceof(input, ErrorSymbol);
|
|
340
|
-
}
|
|
341
|
-
//#endregion
|
|
342
|
-
//#region src/error/create.ts
|
|
343
|
-
function isNativeError(input) {
|
|
344
|
-
return isObject(input) && typeof input.message === "string" && typeof input.name === "string";
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Create an internal error object by
|
|
348
|
-
* - an existing RoutupError (returned as-is)
|
|
349
|
-
* - an HTTPError (wrapped into a RoutupError preserving status)
|
|
350
|
-
* - an Error (wrapped preserving message and cause)
|
|
351
|
-
* - an options object (status, message, etc.)
|
|
352
|
-
* - a message string
|
|
353
|
-
*
|
|
354
|
-
* @param input
|
|
355
|
-
*/
|
|
356
|
-
function createError(input) {
|
|
357
|
-
if (isError(input)) return input;
|
|
358
|
-
if (typeof input === "string") return new RoutupError(input);
|
|
359
|
-
if (isHTTPError(input)) return new RoutupError({
|
|
360
|
-
message: input.message,
|
|
361
|
-
code: input.code,
|
|
362
|
-
status: input.status,
|
|
363
|
-
redirectURL: input.redirectURL,
|
|
364
|
-
cause: input
|
|
365
|
-
});
|
|
366
|
-
if (isNativeError(input)) return new RoutupError({
|
|
367
|
-
message: input.message,
|
|
368
|
-
cause: input
|
|
369
|
-
});
|
|
370
|
-
if (!isObject(input)) return new RoutupError();
|
|
371
|
-
const options = { ...input };
|
|
372
|
-
if (options.cause === void 0) options.cause = input;
|
|
373
|
-
return new RoutupError(options);
|
|
374
|
-
}
|
|
375
|
-
//#endregion
|
|
376
|
-
//#region src/response/to-response.ts
|
|
377
|
-
function stripWeakPrefix(etag) {
|
|
378
|
-
return etag.startsWith("W/") ? etag.slice(2) : etag;
|
|
379
|
-
}
|
|
380
|
-
async function applyEtag(body, event, headers) {
|
|
381
|
-
const etagFn = event.routerOptions.etag;
|
|
382
|
-
if (!etagFn) return void 0;
|
|
383
|
-
const etag = await etagFn(body);
|
|
384
|
-
if (!etag) return void 0;
|
|
385
|
-
headers.set("etag", etag);
|
|
386
|
-
const ifNoneMatch = event.headers.get("if-none-match");
|
|
387
|
-
if (ifNoneMatch && (ifNoneMatch === "*" || ifNoneMatch.split(",").some((t) => stripWeakPrefix(t.trim()) === stripWeakPrefix(etag)))) return new Response(null, {
|
|
388
|
-
status: 304,
|
|
389
|
-
headers
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
async function toResponse(value, event) {
|
|
393
|
-
if (value === void 0) return;
|
|
394
|
-
if (value === null) return new Response(null, {
|
|
395
|
-
status: event.response.status,
|
|
396
|
-
headers: event.response.headers
|
|
397
|
-
});
|
|
398
|
-
if (value instanceof Response) return value;
|
|
399
|
-
const { status, headers } = event.response;
|
|
400
|
-
if (typeof value === "string") {
|
|
401
|
-
if (!headers.has("content-type")) headers.set("content-type", "text/plain; charset=utf-8");
|
|
402
|
-
const cached = await applyEtag(value, event, headers);
|
|
403
|
-
if (cached) return cached;
|
|
404
|
-
return new Response(value, {
|
|
405
|
-
status,
|
|
406
|
-
headers
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
410
|
-
if (!headers.has("content-type")) headers.set("content-type", "application/octet-stream");
|
|
411
|
-
return new Response(value, {
|
|
412
|
-
status,
|
|
413
|
-
headers
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
if (value instanceof ReadableStream) return new Response(value, {
|
|
417
|
-
status,
|
|
418
|
-
headers
|
|
419
|
-
});
|
|
420
|
-
if (value instanceof Blob) {
|
|
421
|
-
if (!headers.has("content-type")) headers.set("content-type", value.type || "application/octet-stream");
|
|
422
|
-
return new Response(value, {
|
|
423
|
-
status,
|
|
424
|
-
headers
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
|
428
|
-
let json;
|
|
429
|
-
try {
|
|
430
|
-
json = JSON.stringify(value);
|
|
431
|
-
} catch (e) {
|
|
432
|
-
throw createError({
|
|
433
|
-
message: "JSON serialization failed",
|
|
434
|
-
status: 500,
|
|
435
|
-
cause: e
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
const cached = await applyEtag(json, event, headers);
|
|
439
|
-
if (cached) return cached;
|
|
440
|
-
return new Response(json, {
|
|
441
|
-
status,
|
|
442
|
-
headers
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
//#endregion
|
|
446
|
-
//#region src/response/helpers/send-accepted.ts
|
|
447
|
-
async function sendAccepted(event, data) {
|
|
448
|
-
event.response.status = 202;
|
|
449
|
-
return await toResponse(data ?? "", event);
|
|
450
|
-
}
|
|
451
|
-
//#endregion
|
|
452
|
-
//#region src/response/helpers/send-created.ts
|
|
453
|
-
async function sendCreated(event, data) {
|
|
454
|
-
event.response.status = 201;
|
|
455
|
-
return await toResponse(data ?? "", event);
|
|
456
|
-
}
|
|
457
|
-
//#endregion
|
|
458
|
-
//#region src/response/helpers/send-file.ts
|
|
459
|
-
async function sendFile(event, options) {
|
|
460
|
-
let stats;
|
|
461
|
-
if (typeof options.stats === "function") stats = await options.stats();
|
|
462
|
-
else stats = options.stats;
|
|
463
|
-
const name = options.name || stats.name;
|
|
464
|
-
const { headers } = event.response;
|
|
465
|
-
const disposition = options.disposition ?? (options.attachment ? "attachment" : void 0);
|
|
466
|
-
if (name) {
|
|
467
|
-
const fileName = basename(name);
|
|
468
|
-
if (disposition) {
|
|
469
|
-
if (!headers.get(HeaderName.CONTENT_DISPOSITION)) if (disposition === "inline") setResponseHeaderInline(event, fileName);
|
|
470
|
-
else setResponseHeaderAttachment(event, fileName);
|
|
471
|
-
} else setResponseContentTypeByFileName(event, fileName);
|
|
472
|
-
}
|
|
473
|
-
const contentOptions = {};
|
|
474
|
-
let statusCode = event.response.status;
|
|
475
|
-
if (stats.size) {
|
|
476
|
-
const rangeHeader = event.headers.get(HeaderName.RANGE);
|
|
477
|
-
if (rangeHeader) {
|
|
478
|
-
const [x, y] = rangeHeader.replace("bytes=", "").split("-");
|
|
479
|
-
const parsedStart = Number.parseInt(x, 10);
|
|
480
|
-
const parsedEnd = Number.parseInt(y, 10);
|
|
481
|
-
contentOptions.start = Number.isFinite(parsedStart) && parsedStart >= 0 ? parsedStart : 0;
|
|
482
|
-
contentOptions.end = Number.isFinite(parsedEnd) && parsedEnd >= 0 ? Math.min(parsedEnd, stats.size - 1) : stats.size - 1;
|
|
483
|
-
if (contentOptions.start >= stats.size || contentOptions.start > contentOptions.end) {
|
|
484
|
-
const rangeHeaders = new Headers(headers);
|
|
485
|
-
rangeHeaders.set(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
|
|
486
|
-
return new Response(null, {
|
|
487
|
-
status: 416,
|
|
488
|
-
headers: rangeHeaders
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
headers.set(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
|
|
492
|
-
headers.set(HeaderName.CONTENT_LENGTH, `${contentOptions.end - contentOptions.start + 1}`);
|
|
493
|
-
statusCode = 206;
|
|
494
|
-
} else headers.set(HeaderName.CONTENT_LENGTH, `${stats.size}`);
|
|
495
|
-
headers.set(HeaderName.ACCEPT_RANGES, "bytes");
|
|
496
|
-
if (stats.mtime) {
|
|
497
|
-
const mtime = new Date(stats.mtime);
|
|
498
|
-
headers.set(HeaderName.LAST_MODIFIED, mtime.toUTCString());
|
|
499
|
-
headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const content = await options.content(contentOptions);
|
|
503
|
-
return new Response(content, {
|
|
504
|
-
status: statusCode,
|
|
505
|
-
headers
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
//#endregion
|
|
509
|
-
//#region src/request/helpers/header.ts
|
|
510
|
-
function getRequestHeader(event, name) {
|
|
511
|
-
return event.headers.get(name);
|
|
512
|
-
}
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/request/helpers/negotiator.ts
|
|
515
|
-
const NEGOTIATOR_KEY = Symbol.for("routup:negotiator");
|
|
516
|
-
function headersToPlainObject(headers) {
|
|
517
|
-
const result = {};
|
|
518
|
-
headers.forEach((value, key) => {
|
|
519
|
-
result[key] = value;
|
|
520
|
-
});
|
|
521
|
-
return result;
|
|
522
|
-
}
|
|
523
|
-
function useRequestNegotiator(event) {
|
|
524
|
-
let value = event.store[NEGOTIATOR_KEY];
|
|
525
|
-
if (value) return value;
|
|
526
|
-
value = new Negotiator({ headers: headersToPlainObject(event.headers) });
|
|
527
|
-
event.store[NEGOTIATOR_KEY] = value;
|
|
528
|
-
return value;
|
|
529
|
-
}
|
|
530
|
-
//#endregion
|
|
531
|
-
//#region src/request/helpers/header-accept.ts
|
|
532
|
-
function getRequestAcceptableContentTypes(event) {
|
|
533
|
-
return useRequestNegotiator(event).mediaTypes();
|
|
534
|
-
}
|
|
535
|
-
function getRequestAcceptableContentType(event, input) {
|
|
536
|
-
input = input || [];
|
|
537
|
-
const items = Array.isArray(input) ? input : [input];
|
|
538
|
-
if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
|
|
539
|
-
if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
|
|
540
|
-
let polluted = false;
|
|
541
|
-
const mimeTypes = [];
|
|
542
|
-
for (const item of items) {
|
|
543
|
-
const mimeType = getMimeType(item);
|
|
544
|
-
if (mimeType) mimeTypes.push(mimeType);
|
|
545
|
-
else polluted = true;
|
|
546
|
-
}
|
|
547
|
-
const matches = useRequestNegotiator(event).mediaTypes(mimeTypes);
|
|
548
|
-
if (matches.length > 0) {
|
|
549
|
-
if (polluted) return items[0];
|
|
550
|
-
return items[mimeTypes.indexOf(matches[0])];
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
//#endregion
|
|
554
|
-
//#region src/response/helpers/send-format.ts
|
|
555
|
-
function sendFormat(event, input) {
|
|
556
|
-
const { default: formatDefault, ...formats } = input;
|
|
557
|
-
const contentTypes = Object.keys(formats);
|
|
558
|
-
if (contentTypes.length === 0) return formatDefault();
|
|
559
|
-
const contentType = getRequestAcceptableContentType(event, contentTypes);
|
|
560
|
-
if (contentType && formats[contentType]) return formats[contentType]();
|
|
561
|
-
return formatDefault();
|
|
562
|
-
}
|
|
563
|
-
//#endregion
|
|
564
|
-
//#region src/response/helpers/send-redirect.ts
|
|
565
|
-
function escapeHtml(str) {
|
|
566
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
567
|
-
}
|
|
568
|
-
function isAllowedRedirectUrl(location) {
|
|
569
|
-
if (location.startsWith("//")) return false;
|
|
570
|
-
if (location.startsWith("/") || location.startsWith(".")) return true;
|
|
571
|
-
try {
|
|
572
|
-
const url = new URL(location);
|
|
573
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
574
|
-
} catch {
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
function sendRedirect(event, location, statusCode = 302) {
|
|
579
|
-
if (!isAllowedRedirectUrl(location)) throw new RoutupError({
|
|
580
|
-
status: 400,
|
|
581
|
-
message: "Invalid redirect URL scheme."
|
|
582
|
-
});
|
|
583
|
-
const sanitizedLocation = sanitizeHeaderValue(location);
|
|
584
|
-
const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${escapeHtml(location)}"></head></html>`;
|
|
585
|
-
const headers = new Headers(event.response.headers);
|
|
586
|
-
headers.set("location", sanitizedLocation);
|
|
587
|
-
headers.set("content-type", "text/html; charset=utf-8");
|
|
588
|
-
headers.delete("content-length");
|
|
589
|
-
return new Response(html, {
|
|
590
|
-
status: statusCode,
|
|
591
|
-
headers
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
//#endregion
|
|
595
|
-
//#region src/response/helpers/send-stream.ts
|
|
596
|
-
function sendStream(event, stream) {
|
|
597
|
-
const { status, headers } = event.response;
|
|
598
|
-
return new Response(stream, {
|
|
599
|
-
status,
|
|
600
|
-
headers
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
//#endregion
|
|
604
|
-
//#region src/event/module.ts
|
|
605
|
-
var RoutupEvent = class {
|
|
606
|
-
request;
|
|
607
|
-
params;
|
|
608
|
-
path;
|
|
609
|
-
method;
|
|
610
|
-
mountPath;
|
|
611
|
-
headers;
|
|
612
|
-
searchParams;
|
|
613
|
-
response;
|
|
614
|
-
store;
|
|
615
|
-
signal;
|
|
616
|
-
_context;
|
|
617
|
-
_routerOptions;
|
|
618
|
-
_nextCalled = false;
|
|
619
|
-
_nextResult;
|
|
620
|
-
_nextCalledDeferred;
|
|
621
|
-
constructor(context) {
|
|
622
|
-
this._context = context;
|
|
623
|
-
this.request = context.request;
|
|
624
|
-
this.params = context.params;
|
|
625
|
-
this.path = context.path;
|
|
626
|
-
this.method = context.method;
|
|
627
|
-
this.mountPath = context.mountPath;
|
|
628
|
-
this.headers = context.headers;
|
|
629
|
-
this.searchParams = context.searchParams;
|
|
630
|
-
this.response = context.response;
|
|
631
|
-
this.store = context.store;
|
|
632
|
-
this.signal = context.signal;
|
|
633
|
-
}
|
|
634
|
-
get routerOptions() {
|
|
635
|
-
if (!this._routerOptions) this._routerOptions = this._context.routerOptions();
|
|
636
|
-
return this._routerOptions;
|
|
637
|
-
}
|
|
638
|
-
get nextCalled() {
|
|
639
|
-
return this._nextCalled;
|
|
640
|
-
}
|
|
641
|
-
get nextResult() {
|
|
642
|
-
return this._nextResult;
|
|
643
|
-
}
|
|
644
|
-
whenNextCalled() {
|
|
645
|
-
if (!this._nextCalledDeferred) {
|
|
646
|
-
let resolve;
|
|
647
|
-
const promise = new Promise((r) => {
|
|
648
|
-
resolve = r;
|
|
649
|
-
});
|
|
650
|
-
this._nextCalledDeferred = {
|
|
651
|
-
promise,
|
|
652
|
-
resolve
|
|
653
|
-
};
|
|
654
|
-
if (this._nextCalled) resolve();
|
|
655
|
-
}
|
|
656
|
-
return this._nextCalledDeferred.promise;
|
|
657
|
-
}
|
|
658
|
-
async next(error) {
|
|
659
|
-
if (this._nextCalled) return this._nextResult;
|
|
660
|
-
this._nextCalled = true;
|
|
661
|
-
this._nextResult = this._context.next(this, error);
|
|
662
|
-
if (this._nextCalledDeferred) this._nextCalledDeferred.resolve();
|
|
663
|
-
return this._nextResult;
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
//#endregion
|
|
667
|
-
//#region src/dispatcher/module.ts
|
|
668
|
-
var DispatcherEvent = class {
|
|
669
|
-
request;
|
|
670
|
-
params;
|
|
671
|
-
path;
|
|
672
|
-
method;
|
|
673
|
-
/**
|
|
674
|
-
* Collected allowed methods (for OPTIONS).
|
|
675
|
-
*/
|
|
676
|
-
methodsAllowed;
|
|
677
|
-
mountPath;
|
|
678
|
-
error;
|
|
679
|
-
routerPath;
|
|
680
|
-
_dispatched;
|
|
681
|
-
_response;
|
|
682
|
-
_store;
|
|
683
|
-
/**
|
|
684
|
-
* Cached parsed URL (avoids double-parsing).
|
|
685
|
-
*/
|
|
686
|
-
_url;
|
|
687
|
-
/**
|
|
688
|
-
* Continuation function for middleware onion model.
|
|
689
|
-
*/
|
|
690
|
-
_next;
|
|
691
|
-
_signal;
|
|
692
|
-
_signalCleanup;
|
|
693
|
-
/**
|
|
694
|
-
* Whether _next has already been called (guard against double-invocation).
|
|
695
|
-
*/
|
|
696
|
-
_nextCalled;
|
|
697
|
-
/**
|
|
698
|
-
* The cached result of the next handler.
|
|
699
|
-
*/
|
|
700
|
-
_nextResult;
|
|
701
|
-
constructor(request) {
|
|
702
|
-
this.request = request;
|
|
703
|
-
this._url = new FastURL(request.url);
|
|
704
|
-
this.method = request.method;
|
|
705
|
-
this.path = this._url.pathname;
|
|
706
|
-
this.mountPath = "/";
|
|
707
|
-
this.params = {};
|
|
708
|
-
this.routerPath = [];
|
|
709
|
-
this.methodsAllowed = /* @__PURE__ */ new Set();
|
|
710
|
-
this._dispatched = false;
|
|
711
|
-
this._nextCalled = false;
|
|
712
|
-
}
|
|
713
|
-
get response() {
|
|
714
|
-
if (!this._response) this._response = {
|
|
715
|
-
status: 200,
|
|
716
|
-
headers: new Headers()
|
|
717
|
-
};
|
|
718
|
-
return this._response;
|
|
719
|
-
}
|
|
720
|
-
get signal() {
|
|
721
|
-
if (!this._signal) this._signal = this.request.signal;
|
|
722
|
-
return this._signal;
|
|
723
|
-
}
|
|
724
|
-
set signal(value) {
|
|
725
|
-
if (this._signalCleanup) {
|
|
726
|
-
this._signalCleanup();
|
|
727
|
-
this._signalCleanup = void 0;
|
|
728
|
-
}
|
|
729
|
-
if (value === this.request.signal) {
|
|
730
|
-
this._signal = value;
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const controller = new AbortController();
|
|
734
|
-
const abort = (e) => {
|
|
735
|
-
const reason = e?.target instanceof AbortSignal ? e.target.reason : void 0;
|
|
736
|
-
this.request.signal.removeEventListener("abort", abort);
|
|
737
|
-
value.removeEventListener("abort", abort);
|
|
738
|
-
controller.abort(reason);
|
|
739
|
-
};
|
|
740
|
-
if (this.request.signal.aborted || value.aborted) {
|
|
741
|
-
const reason = this.request.signal.aborted ? this.request.signal.reason : value.reason;
|
|
742
|
-
controller.abort(reason);
|
|
743
|
-
} else {
|
|
744
|
-
this.request.signal.addEventListener("abort", abort, { once: true });
|
|
745
|
-
value.addEventListener("abort", abort, { once: true });
|
|
746
|
-
this._signalCleanup = () => {
|
|
747
|
-
this.request.signal.removeEventListener("abort", abort);
|
|
748
|
-
value.removeEventListener("abort", abort);
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
this._signal = controller.signal;
|
|
752
|
-
}
|
|
753
|
-
get dispatched() {
|
|
754
|
-
return this._dispatched;
|
|
755
|
-
}
|
|
756
|
-
set dispatched(value) {
|
|
757
|
-
this._dispatched = value;
|
|
758
|
-
}
|
|
759
|
-
async next(event, error) {
|
|
760
|
-
if (this._nextCalled) return this._nextResult;
|
|
761
|
-
this._nextCalled = true;
|
|
762
|
-
if (this._next) this._nextResult = this._next(event, error);
|
|
763
|
-
return this._nextResult;
|
|
764
|
-
}
|
|
765
|
-
setNext(fn) {
|
|
766
|
-
if (fn) this._next = async (event, error) => {
|
|
767
|
-
return toResponse(await fn(error), event);
|
|
768
|
-
};
|
|
769
|
-
else this._next = void 0;
|
|
770
|
-
this._nextCalled = false;
|
|
771
|
-
this._nextResult = void 0;
|
|
772
|
-
}
|
|
773
|
-
build(signal) {
|
|
774
|
-
return new RoutupEvent({
|
|
775
|
-
request: this.request,
|
|
776
|
-
params: this.params,
|
|
777
|
-
path: this.path,
|
|
778
|
-
method: this.method,
|
|
779
|
-
mountPath: this.mountPath,
|
|
780
|
-
headers: this.request.headers,
|
|
781
|
-
searchParams: new URLSearchParams(this._url.search),
|
|
782
|
-
response: this.response,
|
|
783
|
-
store: this.store,
|
|
784
|
-
signal: signal ?? this.signal,
|
|
785
|
-
routerOptions: () => this.resolveOptions(),
|
|
786
|
-
next: (event, error) => this.next(event, error)
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
get store() {
|
|
790
|
-
if (!this._store) this._store = Object.create(null);
|
|
791
|
-
return this._store;
|
|
792
|
-
}
|
|
793
|
-
resolveOptions() {
|
|
794
|
-
const resolved = {
|
|
795
|
-
trustProxy: () => false,
|
|
796
|
-
subdomainOffset: 2,
|
|
797
|
-
etag: buildEtagFn(),
|
|
798
|
-
proxyIpMax: 0
|
|
799
|
-
};
|
|
800
|
-
for (let i = 0; i < this.routerPath.length; i++) {
|
|
801
|
-
const node = this.routerPath[i];
|
|
802
|
-
const entries = Object.entries(node.options);
|
|
803
|
-
for (const entry of entries) {
|
|
804
|
-
const [key, value] = entry;
|
|
805
|
-
if (typeof value !== "undefined") resolved[key] = value;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
return resolved;
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
//#endregion
|
|
812
|
-
//#region src/handler/constants.ts
|
|
813
|
-
const HandlerType = {
|
|
814
|
-
CORE: "core",
|
|
815
|
-
ERROR: "error"
|
|
816
|
-
};
|
|
817
|
-
const HandlerSymbol = Symbol.for("Handler");
|
|
818
|
-
//#endregion
|
|
819
|
-
//#region src/hook/constants.ts
|
|
820
|
-
const HookName = {
|
|
821
|
-
REQUEST: "request",
|
|
822
|
-
RESPONSE: "response",
|
|
823
|
-
ERROR: "error",
|
|
824
|
-
CHILD_MATCH: "childMatch",
|
|
825
|
-
CHILD_DISPATCH_BEFORE: "childDispatchBefore",
|
|
826
|
-
CHILD_DISPATCH_AFTER: "childDispatchAfter"
|
|
827
|
-
};
|
|
828
|
-
//#endregion
|
|
829
|
-
//#region src/hook/module.ts
|
|
830
|
-
var Hooks = class Hooks {
|
|
831
|
-
items;
|
|
832
|
-
constructor() {
|
|
833
|
-
this.items = {};
|
|
834
|
-
}
|
|
835
|
-
addListener(name, fn, priority = 0) {
|
|
836
|
-
this.items[name] = this.items[name] || [];
|
|
837
|
-
const entry = {
|
|
838
|
-
fn,
|
|
839
|
-
priority
|
|
840
|
-
};
|
|
841
|
-
let i = 0;
|
|
842
|
-
while (i < this.items[name].length && this.items[name][i].priority >= priority) i++;
|
|
843
|
-
this.items[name].splice(i, 0, entry);
|
|
844
|
-
return () => {
|
|
845
|
-
this.removeListener(name, fn);
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
removeListener(name, fn) {
|
|
849
|
-
if (!this.items[name]) return;
|
|
850
|
-
if (typeof fn === "undefined") {
|
|
851
|
-
delete this.items[name];
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
if (typeof fn === "function") {
|
|
855
|
-
const index = this.items[name].findIndex((entry) => entry.fn === fn);
|
|
856
|
-
if (index !== -1) this.items[name].splice(index, 1);
|
|
857
|
-
}
|
|
858
|
-
if (this.items[name].length === 0) delete this.items[name];
|
|
859
|
-
}
|
|
860
|
-
/**
|
|
861
|
-
* Create a new `Hooks` instance seeded with the same listeners as this
|
|
862
|
-
* one.
|
|
863
|
-
*
|
|
864
|
-
* Listener functions are shared by reference; priority and ordering are
|
|
865
|
-
* preserved. Future mutations on the returned instance do not affect this
|
|
866
|
-
* one (and vice versa).
|
|
867
|
-
*/
|
|
868
|
-
clone() {
|
|
869
|
-
const next = new Hooks();
|
|
870
|
-
const names = Object.keys(this.items);
|
|
871
|
-
for (const name of names) {
|
|
872
|
-
const entries = this.items[name];
|
|
873
|
-
for (const entry of entries) next.addListener(name, entry.fn, entry.priority);
|
|
874
|
-
}
|
|
875
|
-
return next;
|
|
876
|
-
}
|
|
877
|
-
async trigger(name, event) {
|
|
878
|
-
if (!this.items[name] || this.items[name].length === 0) return;
|
|
879
|
-
try {
|
|
880
|
-
for (let i = 0; i < this.items[name].length; i++) {
|
|
881
|
-
const { fn } = this.items[name][i];
|
|
882
|
-
await this.triggerListener(name, event, fn);
|
|
883
|
-
if (event.dispatched) {
|
|
884
|
-
if (event.error) event.error = void 0;
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
} catch (e) {
|
|
889
|
-
if (!event.error) event.error = createError(e);
|
|
890
|
-
if (!this.isErrorListenerHook(name)) {
|
|
891
|
-
await this.trigger(HookName.ERROR, event);
|
|
892
|
-
if (event.dispatched) {
|
|
893
|
-
if (event.error) event.error = void 0;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
triggerListener(name, event, listener) {
|
|
899
|
-
if (this.isErrorListenerHook(name)) {
|
|
900
|
-
if (event.error) return listener(event);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
return listener(event);
|
|
904
|
-
}
|
|
905
|
-
isErrorListenerHook(input) {
|
|
906
|
-
return input === HookName.ERROR;
|
|
907
|
-
}
|
|
908
|
-
};
|
|
909
|
-
//#endregion
|
|
910
|
-
//#region src/path/matcher.ts
|
|
911
|
-
function decodeParam(val) {
|
|
912
|
-
/* istanbul ignore next */
|
|
913
|
-
if (typeof val !== "string" || val.length === 0) return val;
|
|
914
|
-
try {
|
|
915
|
-
return decodeURIComponent(val);
|
|
916
|
-
} catch {
|
|
917
|
-
return val;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
var PathMatcher = class {
|
|
921
|
-
path;
|
|
922
|
-
regexp;
|
|
923
|
-
regexpKeys = [];
|
|
924
|
-
regexpOptions;
|
|
925
|
-
constructor(path, options) {
|
|
926
|
-
this.path = path;
|
|
927
|
-
this.regexpOptions = options || {};
|
|
928
|
-
const regexp = pathToRegexp(path, options);
|
|
929
|
-
this.regexp = regexp.regexp;
|
|
930
|
-
this.regexpKeys = regexp.keys;
|
|
931
|
-
}
|
|
932
|
-
test(path) {
|
|
933
|
-
return this.regexp.test(path);
|
|
934
|
-
}
|
|
935
|
-
exec(path) {
|
|
936
|
-
if (this.path === "/" && this.regexpOptions.end === false) return {
|
|
937
|
-
path: "/",
|
|
938
|
-
params: Object.create(null)
|
|
939
|
-
};
|
|
940
|
-
const match = this.regexp.exec(path);
|
|
941
|
-
if (!match) return;
|
|
942
|
-
const params = Object.create(null);
|
|
943
|
-
for (let i = 1; i < match.length; i++) {
|
|
944
|
-
const key = this.regexpKeys[i - 1];
|
|
945
|
-
if (!key) continue;
|
|
946
|
-
const prop = key.name;
|
|
947
|
-
const val = decodeParam(match[i]);
|
|
948
|
-
if (typeof val !== "undefined") params[prop] = val;
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
951
|
-
path: match[0],
|
|
952
|
-
params
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
//#endregion
|
|
957
|
-
//#region src/path/utils.ts
|
|
958
|
-
function isPath(input) {
|
|
959
|
-
return typeof input === "string";
|
|
960
|
-
}
|
|
961
|
-
//#endregion
|
|
962
|
-
//#region src/handler/utils.ts
|
|
963
|
-
/**
|
|
964
|
-
* Build a `PathMatcher` for a handler-side path.
|
|
965
|
-
*
|
|
966
|
-
* Returns `undefined` when no path is supplied. The `end` flag controls
|
|
967
|
-
* whether the matcher requires a full match (`true` for method handlers
|
|
968
|
-
* matching exact routes) or accepts a prefix (`false` for middleware).
|
|
969
|
-
*/
|
|
970
|
-
function buildHandlerPathMatcher(path, end) {
|
|
971
|
-
if (typeof path === "undefined") return;
|
|
972
|
-
return new PathMatcher(typeof path === "string" ? withLeadingSlash(path) : path, { end });
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Match a request method against a handler's bound method.
|
|
976
|
-
*
|
|
977
|
-
* - When the handler has no method bound, matches every request method.
|
|
978
|
-
* - Otherwise matches when the request method is the same.
|
|
979
|
-
* - HEAD requests additionally match GET handlers.
|
|
980
|
-
*/
|
|
981
|
-
function matchHandlerMethod(handlerMethod, requestMethod) {
|
|
982
|
-
return !handlerMethod || requestMethod === handlerMethod || requestMethod === MethodName.HEAD && handlerMethod === MethodName.GET;
|
|
983
|
-
}
|
|
984
|
-
//#endregion
|
|
985
|
-
//#region src/handler/module.ts
|
|
986
|
-
var Handler = class {
|
|
987
|
-
config;
|
|
988
|
-
hooks;
|
|
989
|
-
pathMatcher;
|
|
990
|
-
method;
|
|
991
|
-
constructor(handler) {
|
|
992
|
-
this.config = handler;
|
|
993
|
-
this.hooks = new Hooks();
|
|
994
|
-
this.mountHooks();
|
|
995
|
-
if (typeof handler.path === "string") this.config.path = withLeadingSlash(handler.path);
|
|
996
|
-
this.pathMatcher = buildHandlerPathMatcher(this.config.path, !!this.config.method);
|
|
997
|
-
this.method = this.config.method ? toMethodName(this.config.method) : void 0;
|
|
998
|
-
markInstanceof(this, HandlerSymbol);
|
|
999
|
-
}
|
|
1000
|
-
get type() {
|
|
1001
|
-
return this.config.type;
|
|
1002
|
-
}
|
|
1003
|
-
get path() {
|
|
1004
|
-
return this.config.path;
|
|
1005
|
-
}
|
|
1006
|
-
async dispatch(event) {
|
|
1007
|
-
if (this.pathMatcher) {
|
|
1008
|
-
const pathMatch = this.pathMatcher.exec(event.path);
|
|
1009
|
-
if (pathMatch) event.params = {
|
|
1010
|
-
...event.params,
|
|
1011
|
-
...pathMatch.params
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
await this.hooks.trigger(HookName.CHILD_DISPATCH_BEFORE, event);
|
|
1015
|
-
if (event.dispatched) return;
|
|
1016
|
-
let response;
|
|
1017
|
-
try {
|
|
1018
|
-
let result;
|
|
1019
|
-
const previewEvent = event.build();
|
|
1020
|
-
const effectiveTimeout = this.resolveTimeout(previewEvent.routerOptions);
|
|
1021
|
-
let childController;
|
|
1022
|
-
let cleanupParentListener;
|
|
1023
|
-
let handlerEvent = previewEvent;
|
|
1024
|
-
if (effectiveTimeout) {
|
|
1025
|
-
const parentSignal = event.signal;
|
|
1026
|
-
childController = new AbortController();
|
|
1027
|
-
if (parentSignal.aborted) childController.abort(parentSignal.reason);
|
|
1028
|
-
else {
|
|
1029
|
-
const onAbort = () => childController.abort(parentSignal.reason);
|
|
1030
|
-
parentSignal.addEventListener("abort", onAbort, { once: true });
|
|
1031
|
-
cleanupParentListener = () => parentSignal.removeEventListener("abort", onAbort);
|
|
1032
|
-
}
|
|
1033
|
-
handlerEvent = event.build(childController.signal);
|
|
1034
|
-
}
|
|
1035
|
-
try {
|
|
1036
|
-
if (this.config.type === HandlerType.ERROR) {
|
|
1037
|
-
if (event.error) {
|
|
1038
|
-
const { fn } = this.config;
|
|
1039
|
-
const { error } = event;
|
|
1040
|
-
result = await this.executeWithTimeout(() => this.resolveHandlerResult(fn(error, handlerEvent), handlerEvent), handlerEvent.routerOptions, childController);
|
|
1041
|
-
}
|
|
1042
|
-
} else {
|
|
1043
|
-
const { fn } = this.config;
|
|
1044
|
-
result = await this.executeWithTimeout(() => this.resolveHandlerResult(fn(handlerEvent), handlerEvent), handlerEvent.routerOptions, childController);
|
|
1045
|
-
}
|
|
1046
|
-
} finally {
|
|
1047
|
-
if (cleanupParentListener) cleanupParentListener();
|
|
1048
|
-
}
|
|
1049
|
-
response = await toResponse(result, handlerEvent);
|
|
1050
|
-
if (response) event.dispatched = true;
|
|
1051
|
-
} catch (e) {
|
|
1052
|
-
event.error = isError(e) ? e : createError(e);
|
|
1053
|
-
await this.hooks.trigger(HookName.ERROR, event);
|
|
1054
|
-
if (event.dispatched) event.error = void 0;
|
|
1055
|
-
else throw event.error;
|
|
1056
|
-
}
|
|
1057
|
-
await this.hooks.trigger(HookName.CHILD_DISPATCH_AFTER, event);
|
|
1058
|
-
return response;
|
|
1059
|
-
}
|
|
1060
|
-
matchPath(path) {
|
|
1061
|
-
if (!this.pathMatcher) return true;
|
|
1062
|
-
return this.pathMatcher.test(path);
|
|
1063
|
-
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Resolve a handler's return value into the final value handed to `toResponse`.
|
|
1066
|
-
*
|
|
1067
|
-
* Contract:
|
|
1068
|
-
* - non-undefined value → return as-is (becomes the response)
|
|
1069
|
-
* - `undefined` + `event.next()` was called → forward downstream result
|
|
1070
|
-
* - `undefined` + `event.next()` not yet called → wait until either `next()` is
|
|
1071
|
-
* invoked (e.g. from an async callback) or `signal` aborts. A global or
|
|
1072
|
-
* per-handler timeout aborts `signal` and surfaces as 408. With no timeout
|
|
1073
|
-
* configured and no eventual `next()` call, the request hangs by design.
|
|
1074
|
-
*/
|
|
1075
|
-
async resolveHandlerResult(invocation, handlerEvent) {
|
|
1076
|
-
const value = await invocation;
|
|
1077
|
-
if (typeof value !== "undefined") return value;
|
|
1078
|
-
if (handlerEvent.nextCalled) return handlerEvent.nextResult;
|
|
1079
|
-
const { signal } = handlerEvent;
|
|
1080
|
-
if (signal.aborted) throw createError({
|
|
1081
|
-
status: 408,
|
|
1082
|
-
message: "Request Timeout"
|
|
1083
|
-
});
|
|
1084
|
-
return new Promise((resolve, reject) => {
|
|
1085
|
-
const onAbort = () => {
|
|
1086
|
-
signal.removeEventListener("abort", onAbort);
|
|
1087
|
-
reject(createError({
|
|
1088
|
-
status: 408,
|
|
1089
|
-
message: "Request Timeout"
|
|
1090
|
-
}));
|
|
1091
|
-
};
|
|
1092
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1093
|
-
handlerEvent.whenNextCalled().then(() => {
|
|
1094
|
-
signal.removeEventListener("abort", onAbort);
|
|
1095
|
-
resolve(handlerEvent.nextResult);
|
|
1096
|
-
});
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
async executeWithTimeout(fn, routerOptions, controller) {
|
|
1100
|
-
const effectiveTimeout = this.resolveTimeout(routerOptions);
|
|
1101
|
-
if (!effectiveTimeout) return fn();
|
|
1102
|
-
let timerId;
|
|
1103
|
-
try {
|
|
1104
|
-
return await Promise.race([fn(), new Promise((_, reject) => {
|
|
1105
|
-
timerId = setTimeout(() => {
|
|
1106
|
-
if (controller) controller.abort();
|
|
1107
|
-
reject(createError({
|
|
1108
|
-
status: 408,
|
|
1109
|
-
message: "Request Timeout"
|
|
1110
|
-
}));
|
|
1111
|
-
}, effectiveTimeout);
|
|
1112
|
-
})]);
|
|
1113
|
-
} finally {
|
|
1114
|
-
clearTimeout(timerId);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
resolveTimeout(routerOptions) {
|
|
1118
|
-
const routerDefault = routerOptions.handlerTimeout;
|
|
1119
|
-
const handlerOverride = this.config.timeout;
|
|
1120
|
-
if (!routerDefault && !handlerOverride) return;
|
|
1121
|
-
if (!routerDefault) return handlerOverride;
|
|
1122
|
-
if (!handlerOverride) return routerDefault;
|
|
1123
|
-
if (routerOptions.handlerTimeoutOverridable) return handlerOverride;
|
|
1124
|
-
return Math.min(routerDefault, handlerOverride);
|
|
1125
|
-
}
|
|
1126
|
-
mountHooks() {
|
|
1127
|
-
if (this.config.onBefore) this.hooks.addListener(HookName.CHILD_DISPATCH_BEFORE, this.config.onBefore);
|
|
1128
|
-
if (this.config.onAfter) this.hooks.addListener(HookName.CHILD_DISPATCH_AFTER, this.config.onAfter);
|
|
1129
|
-
if (this.config.onError) this.hooks.addListener(HookName.ERROR, this.config.onError);
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
//#endregion
|
|
1133
|
-
//#region src/handler/core/define.ts
|
|
1134
|
-
function defineCoreHandler(input) {
|
|
1135
|
-
if (typeof input === "function") return new Handler({
|
|
1136
|
-
type: HandlerType.CORE,
|
|
1137
|
-
fn: input
|
|
1138
|
-
});
|
|
1139
|
-
return new Handler({
|
|
1140
|
-
type: HandlerType.CORE,
|
|
1141
|
-
...input
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1144
|
-
//#endregion
|
|
1145
|
-
//#region src/handler/error/define.ts
|
|
1146
|
-
function defineErrorHandler(input) {
|
|
1147
|
-
if (typeof input === "function") return new Handler({
|
|
1148
|
-
type: HandlerType.ERROR,
|
|
1149
|
-
fn: input
|
|
1150
|
-
});
|
|
1151
|
-
return new Handler({
|
|
1152
|
-
type: HandlerType.ERROR,
|
|
1153
|
-
...input
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
//#endregion
|
|
1157
|
-
//#region src/handler/adapters/node/define.ts
|
|
1158
|
-
const kHandled = /* @__PURE__ */ Symbol("handled");
|
|
1159
|
-
function callHandler(handler, req, res) {
|
|
1160
|
-
return new Promise((resolve, reject) => {
|
|
1161
|
-
let settled = false;
|
|
1162
|
-
const onClose = () => settle(kHandled);
|
|
1163
|
-
const onFinish = () => settle(kHandled);
|
|
1164
|
-
const onError = (error) => fail(error);
|
|
1165
|
-
function cleanup() {
|
|
1166
|
-
res.removeListener("close", onClose);
|
|
1167
|
-
res.removeListener("finish", onFinish);
|
|
1168
|
-
res.removeListener("error", onError);
|
|
1169
|
-
}
|
|
1170
|
-
function settle(value) {
|
|
1171
|
-
if (settled) return;
|
|
1172
|
-
settled = true;
|
|
1173
|
-
cleanup();
|
|
1174
|
-
resolve(value);
|
|
1175
|
-
}
|
|
1176
|
-
function fail(error) {
|
|
1177
|
-
if (settled) return;
|
|
1178
|
-
settled = true;
|
|
1179
|
-
cleanup();
|
|
1180
|
-
reject(error);
|
|
1181
|
-
}
|
|
1182
|
-
res.once("close", onClose);
|
|
1183
|
-
res.once("finish", onFinish);
|
|
1184
|
-
res.once("error", onError);
|
|
1185
|
-
try {
|
|
1186
|
-
Promise.resolve(handler(req, res)).then(() => settle(kHandled)).catch(fail);
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
fail(error);
|
|
1189
|
-
}
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
function callMiddleware(handler, req, res) {
|
|
1193
|
-
return new Promise((resolve, reject) => {
|
|
1194
|
-
let settled = false;
|
|
1195
|
-
const onClose = () => settle(kHandled);
|
|
1196
|
-
const onFinish = () => settle(kHandled);
|
|
1197
|
-
const onError = (error) => fail(error);
|
|
1198
|
-
function cleanup() {
|
|
1199
|
-
res.removeListener("close", onClose);
|
|
1200
|
-
res.removeListener("finish", onFinish);
|
|
1201
|
-
res.removeListener("error", onError);
|
|
1202
|
-
}
|
|
1203
|
-
function settle(value) {
|
|
1204
|
-
if (settled) return;
|
|
1205
|
-
settled = true;
|
|
1206
|
-
cleanup();
|
|
1207
|
-
resolve(value);
|
|
1208
|
-
}
|
|
1209
|
-
function fail(error) {
|
|
1210
|
-
if (settled) return;
|
|
1211
|
-
settled = true;
|
|
1212
|
-
cleanup();
|
|
1213
|
-
reject(error);
|
|
1214
|
-
}
|
|
1215
|
-
res.once("close", onClose);
|
|
1216
|
-
res.once("finish", onFinish);
|
|
1217
|
-
res.once("error", onError);
|
|
1218
|
-
try {
|
|
1219
|
-
Promise.resolve(handler(req, res, (error) => {
|
|
1220
|
-
if (error) fail(error);
|
|
1221
|
-
else settle(res.writableEnded || res.destroyed ? kHandled : void 0);
|
|
1222
|
-
})).catch(fail);
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
fail(error);
|
|
1225
|
-
}
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
function createNodeBridge(handler, isMiddleware) {
|
|
1229
|
-
if (typeof handler !== "function") throw new RoutupError("fromNodeHandler/fromNodeMiddleware expects a function.");
|
|
1230
|
-
return defineCoreHandler({ fn: (async (event) => {
|
|
1231
|
-
const node = event.request.runtime?.node;
|
|
1232
|
-
if (!node?.req || !node?.res) throw new RoutupError("fromNodeHandler/fromNodeMiddleware requires a Node.js runtime.");
|
|
1233
|
-
const req = node.req;
|
|
1234
|
-
const res = node.res;
|
|
1235
|
-
if ((isMiddleware ? await callMiddleware(handler, req, res) : await callHandler(handler, req, res)) === kHandled) return null;
|
|
1236
|
-
return event.next();
|
|
1237
|
-
}) });
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* Wraps a Node.js `(req, res)` handler for use in the routup pipeline.
|
|
1241
|
-
*
|
|
1242
|
-
* @example
|
|
1243
|
-
* ```typescript
|
|
1244
|
-
* import { fromNodeHandler } from 'routup/node';
|
|
1245
|
-
*
|
|
1246
|
-
* router.use(fromNodeHandler((req, res) => {
|
|
1247
|
-
* res.end('Hello');
|
|
1248
|
-
* }));
|
|
1249
|
-
* ```
|
|
1250
|
-
*/
|
|
1251
|
-
function fromNodeHandler(handler) {
|
|
1252
|
-
return createNodeBridge(handler, false);
|
|
1253
|
-
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Wraps a Node.js `(req, res, next)` middleware for use in the routup pipeline.
|
|
1256
|
-
*
|
|
1257
|
-
* @example
|
|
1258
|
-
* ```typescript
|
|
1259
|
-
* import cors from 'cors';
|
|
1260
|
-
* import { fromNodeMiddleware } from 'routup/node';
|
|
1261
|
-
*
|
|
1262
|
-
* router.use(fromNodeMiddleware(cors()));
|
|
1263
|
-
* ```
|
|
1264
|
-
*/
|
|
1265
|
-
function fromNodeMiddleware(handler) {
|
|
1266
|
-
return createNodeBridge(handler, true);
|
|
1267
|
-
}
|
|
1268
|
-
//#endregion
|
|
1269
|
-
//#region src/handler/adapters/web/is.ts
|
|
1270
|
-
function isWebHandlerProvider(input) {
|
|
1271
|
-
return isObject(input) && typeof input.fetch === "function";
|
|
1272
|
-
}
|
|
1273
|
-
function isWebHandler(input) {
|
|
1274
|
-
return typeof input === "function";
|
|
1275
|
-
}
|
|
1276
|
-
//#endregion
|
|
1277
|
-
//#region src/handler/adapters/web/define.ts
|
|
1278
|
-
function fromWebHandler(input) {
|
|
1279
|
-
if (isWebHandlerProvider(input)) return fromWebHandler(input.fetch.bind(input));
|
|
1280
|
-
if (typeof input !== "function") throw new RoutupError("fromWebHandler expects a function or an object with a fetch method.");
|
|
1281
|
-
return defineCoreHandler({ fn: (event) => input(event.request) });
|
|
1282
|
-
}
|
|
1283
|
-
//#endregion
|
|
1284
|
-
//#region src/handler/is.ts
|
|
1285
|
-
function isHandlerOptions(input) {
|
|
1286
|
-
return isObject(input) && typeof input.fn === "function" && typeof input.type === "string";
|
|
1287
|
-
}
|
|
1288
|
-
function isHandler(input) {
|
|
1289
|
-
return hasInstanceof(input, HandlerSymbol);
|
|
1290
|
-
}
|
|
1291
|
-
//#endregion
|
|
1292
|
-
//#region src/request/helpers/cache.ts
|
|
1293
|
-
function isRequestCacheable(event, modifiedTime) {
|
|
1294
|
-
const modifiedSince = event.headers.get(HeaderName.IF_MODIFIED_SINCE);
|
|
1295
|
-
if (!modifiedSince) return false;
|
|
1296
|
-
modifiedTime = typeof modifiedTime === "string" ? new Date(modifiedTime) : modifiedTime;
|
|
1297
|
-
const sinceDate = new Date(modifiedSince);
|
|
1298
|
-
if (Number.isNaN(sinceDate.getTime()) || Number.isNaN(modifiedTime.getTime())) return false;
|
|
1299
|
-
return sinceDate >= modifiedTime;
|
|
1300
|
-
}
|
|
1301
|
-
//#endregion
|
|
1302
|
-
//#region src/request/helpers/header-accept-charset.ts
|
|
1303
|
-
function getRequestAcceptableCharsets(event) {
|
|
1304
|
-
return useRequestNegotiator(event).charsets();
|
|
1305
|
-
}
|
|
1306
|
-
function getRequestAcceptableCharset(event, input) {
|
|
1307
|
-
input = input || [];
|
|
1308
|
-
const items = Array.isArray(input) ? input : [input];
|
|
1309
|
-
if (items.length === 0) return getRequestAcceptableCharsets(event).shift();
|
|
1310
|
-
return useRequestNegotiator(event).charsets(items).shift() || void 0;
|
|
1311
|
-
}
|
|
1312
|
-
//#endregion
|
|
1313
|
-
//#region src/request/helpers/header-accept-encoding.ts
|
|
1314
|
-
function getRequestAcceptableEncodings(event) {
|
|
1315
|
-
return useRequestNegotiator(event).encodings();
|
|
1316
|
-
}
|
|
1317
|
-
function getRequestAcceptableEncoding(event, input) {
|
|
1318
|
-
input = input || [];
|
|
1319
|
-
const items = Array.isArray(input) ? input : [input];
|
|
1320
|
-
if (items.length === 0) return getRequestAcceptableEncodings(event).shift();
|
|
1321
|
-
return useRequestNegotiator(event).encodings(items).shift() || void 0;
|
|
1322
|
-
}
|
|
1323
|
-
//#endregion
|
|
1324
|
-
//#region src/request/helpers/header-accept-language.ts
|
|
1325
|
-
function getRequestAcceptableLanguages(event) {
|
|
1326
|
-
return useRequestNegotiator(event).languages();
|
|
1327
|
-
}
|
|
1328
|
-
function getRequestAcceptableLanguage(event, input) {
|
|
1329
|
-
input = input || [];
|
|
1330
|
-
const items = Array.isArray(input) ? input : [input];
|
|
1331
|
-
if (items.length === 0) return getRequestAcceptableLanguages(event).shift();
|
|
1332
|
-
return useRequestNegotiator(event).languages(items).shift() || void 0;
|
|
1333
|
-
}
|
|
1334
|
-
//#endregion
|
|
1335
|
-
//#region src/request/helpers/header-content-type.ts
|
|
1336
|
-
function matchRequestContentType(event, contentType) {
|
|
1337
|
-
const header = getRequestHeader(event, HeaderName.CONTENT_TYPE);
|
|
1338
|
-
if (!header) return true;
|
|
1339
|
-
return header.split(";")[0].trim() === getMimeType(contentType);
|
|
1340
|
-
}
|
|
1341
|
-
//#endregion
|
|
1342
|
-
//#region src/request/helpers/hostname.ts
|
|
1343
|
-
function getRequestHostName(event, options = {}) {
|
|
1344
|
-
let trustProxy;
|
|
1345
|
-
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1346
|
-
else trustProxy = event.routerOptions.trustProxy;
|
|
1347
|
-
let hostname = event.headers.get(HeaderName.X_FORWARDED_HOST);
|
|
1348
|
-
if (!hostname || !event.request.ip || !trustProxy(event.request.ip, 0)) hostname = event.headers.get(HeaderName.HOST);
|
|
1349
|
-
else if (hostname && hostname.includes(",")) hostname = hostname.substring(0, hostname.indexOf(",")).trimEnd();
|
|
1350
|
-
if (!hostname) return;
|
|
1351
|
-
const offset = hostname[0] === "[" ? hostname.indexOf("]") + 1 : 0;
|
|
1352
|
-
const index = hostname.indexOf(":", offset);
|
|
1353
|
-
const result = index !== -1 ? hostname.substring(0, index) : hostname;
|
|
1354
|
-
if (/[\x00-\x1F\x7F\s/@\\]/.test(result)) return;
|
|
1355
|
-
return result;
|
|
1356
|
-
}
|
|
1357
|
-
//#endregion
|
|
1358
|
-
//#region src/request/helpers/ip.ts
|
|
1359
|
-
/**
|
|
1360
|
-
* Get the client IP address from the request.
|
|
1361
|
-
*
|
|
1362
|
-
* When `trustProxy` is configured, walks the `X-Forwarded-For` chain
|
|
1363
|
-
* and returns the rightmost untrusted address (the actual client IP).
|
|
1364
|
-
* Falls back to `event.request.ip` (the direct connection IP).
|
|
1365
|
-
*/
|
|
1366
|
-
function getRequestIP(event, options = {}) {
|
|
1367
|
-
let trustProxy;
|
|
1368
|
-
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1369
|
-
else trustProxy = event.routerOptions.trustProxy;
|
|
1370
|
-
const socketAddr = event.request.ip;
|
|
1371
|
-
if (!socketAddr) return;
|
|
1372
|
-
const forwarded = event.headers.get(HeaderName.X_FORWARDED_FOR);
|
|
1373
|
-
const addrs = [socketAddr];
|
|
1374
|
-
if (forwarded) {
|
|
1375
|
-
const parts = forwarded.split(",");
|
|
1376
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1377
|
-
const addr = parts[i].trim();
|
|
1378
|
-
if (addr) addrs.push(addr);
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
for (let i = 0; i < addrs.length - 1; i++) if (!trustProxy(addrs[i], i)) return addrs[i];
|
|
1382
|
-
return addrs[addrs.length - 1];
|
|
1383
|
-
}
|
|
1384
|
-
//#endregion
|
|
1385
|
-
//#region src/request/helpers/protocol.ts
|
|
1386
|
-
function getRequestProtocol(event, options = {}) {
|
|
1387
|
-
let trustProxy;
|
|
1388
|
-
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1389
|
-
else trustProxy = event.routerOptions.trustProxy;
|
|
1390
|
-
let protocol;
|
|
1391
|
-
try {
|
|
1392
|
-
if (new URL(event.request.url).protocol === "https:") protocol = "https";
|
|
1393
|
-
else protocol = "http";
|
|
1394
|
-
} catch {
|
|
1395
|
-
protocol = options.default || "http";
|
|
1396
|
-
}
|
|
1397
|
-
if (!event.request.ip || !trustProxy(event.request.ip, 0)) return protocol;
|
|
1398
|
-
const header = event.headers.get(HeaderName.X_FORWARDED_PROTO);
|
|
1399
|
-
if (!header) return protocol;
|
|
1400
|
-
const index = header.indexOf(",");
|
|
1401
|
-
const forwarded = index !== -1 ? header.substring(0, index).trim().toLowerCase() : header.trim().toLowerCase();
|
|
1402
|
-
if (forwarded === "http" || forwarded === "https") return forwarded;
|
|
1403
|
-
return protocol;
|
|
1404
|
-
}
|
|
1405
|
-
//#endregion
|
|
1406
|
-
//#region src/plugin/error/constants.ts
|
|
1407
|
-
const PluginErrorCode = {
|
|
1408
|
-
PLUGIN: "PLUGIN",
|
|
1409
|
-
NOT_INSTALLED: "PLUGIN_NOT_INSTALLED",
|
|
1410
|
-
ALREADY_INSTALLED: "PLUGIN_ALREADY_INSTALLED",
|
|
1411
|
-
INSTALL: "PLUGIN_INSTALL"
|
|
1412
|
-
};
|
|
1413
|
-
//#endregion
|
|
1414
|
-
//#region src/plugin/error/is.ts
|
|
1415
|
-
const PLUGIN_ERROR_CODES = new Set(Object.values(PluginErrorCode));
|
|
1416
|
-
function isPluginError(input) {
|
|
1417
|
-
if (!isError(input)) return false;
|
|
1418
|
-
return PLUGIN_ERROR_CODES.has(input.code);
|
|
1419
|
-
}
|
|
1420
|
-
//#endregion
|
|
1421
|
-
//#region src/plugin/error/module.ts
|
|
1422
|
-
var PluginError = class extends RoutupError {
|
|
1423
|
-
constructor(input = {}) {
|
|
1424
|
-
const options = typeof input === "string" ? { message: input } : { ...input };
|
|
1425
|
-
if (!("code" in options) || !options.code) options.code = PluginErrorCode.PLUGIN;
|
|
1426
|
-
super(options);
|
|
1427
|
-
this.name = "PluginError";
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
//#endregion
|
|
1431
|
-
//#region src/plugin/error/sub/already-installed.ts
|
|
1432
|
-
var PluginAlreadyInstalledError = class extends PluginError {
|
|
1433
|
-
pluginName;
|
|
1434
|
-
constructor(pluginName) {
|
|
1435
|
-
super({
|
|
1436
|
-
message: `Plugin "${pluginName}" is already installed on this router.`,
|
|
1437
|
-
code: PluginErrorCode.ALREADY_INSTALLED
|
|
1438
|
-
});
|
|
1439
|
-
this.name = "PluginAlreadyInstalledError";
|
|
1440
|
-
this.pluginName = pluginName;
|
|
1441
|
-
}
|
|
1442
|
-
};
|
|
1443
|
-
//#endregion
|
|
1444
|
-
//#region src/plugin/error/sub/install.ts
|
|
1445
|
-
var PluginInstallError = class extends PluginError {
|
|
1446
|
-
pluginName;
|
|
1447
|
-
constructor(pluginName, cause) {
|
|
1448
|
-
super({
|
|
1449
|
-
message: `Failed to install plugin "${pluginName}".`,
|
|
1450
|
-
code: PluginErrorCode.INSTALL,
|
|
1451
|
-
cause
|
|
1452
|
-
});
|
|
1453
|
-
this.name = "PluginInstallError";
|
|
1454
|
-
this.pluginName = pluginName;
|
|
1455
|
-
}
|
|
1456
|
-
};
|
|
1457
|
-
//#endregion
|
|
1458
|
-
//#region src/plugin/error/sub/not-installed.ts
|
|
1459
|
-
var PluginNotInstalledError = class extends PluginError {
|
|
1460
|
-
pluginName;
|
|
1461
|
-
helperName;
|
|
1462
|
-
constructor(pluginName, helperName) {
|
|
1463
|
-
super({
|
|
1464
|
-
message: `${helperName}() requires the "${pluginName}" plugin. Register it with: router.use(${pluginName}())`,
|
|
1465
|
-
code: PluginErrorCode.NOT_INSTALLED
|
|
1466
|
-
});
|
|
1467
|
-
this.name = "PluginNotInstalledError";
|
|
1468
|
-
this.pluginName = pluginName;
|
|
1469
|
-
this.helperName = helperName;
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1472
|
-
//#endregion
|
|
1473
|
-
//#region src/plugin/is.ts
|
|
1474
|
-
function isPlugin(input) {
|
|
1475
|
-
if (!isObject(input)) return false;
|
|
1476
|
-
if (typeof input.name !== "undefined" && typeof input.name !== "string") return false;
|
|
1477
|
-
return typeof input.install === "function" && input.install.length === 1;
|
|
1478
|
-
}
|
|
1479
|
-
//#endregion
|
|
1480
|
-
//#region src/router/options.ts
|
|
1481
|
-
function normalizeRouterOptions(input) {
|
|
1482
|
-
if (typeof input.etag !== "undefined") input.etag = buildEtagFn(input.etag);
|
|
1483
|
-
if (typeof input.trustProxy !== "undefined") input.trustProxy = buildTrustProxyFn(input.trustProxy);
|
|
1484
|
-
if (typeof input.timeout !== "undefined") {
|
|
1485
|
-
if (typeof input.timeout !== "number" || !Number.isFinite(input.timeout) || input.timeout <= 0) delete input.timeout;
|
|
1486
|
-
}
|
|
1487
|
-
if (typeof input.handlerTimeout !== "undefined") {
|
|
1488
|
-
if (typeof input.handlerTimeout !== "number" || !Number.isFinite(input.handlerTimeout) || input.handlerTimeout <= 0) delete input.handlerTimeout;
|
|
1489
|
-
}
|
|
1490
|
-
return input;
|
|
1491
|
-
}
|
|
1492
|
-
//#endregion
|
|
1493
|
-
//#region src/router/constants.ts
|
|
1494
|
-
const RouterSymbol = Symbol.for("Router");
|
|
1495
|
-
const RouterPipelineStep = {
|
|
1496
|
-
START: 0,
|
|
1497
|
-
LOOKUP: 1,
|
|
1498
|
-
CHILD_BEFORE: 2,
|
|
1499
|
-
CHILD_DISPATCH: 3,
|
|
1500
|
-
CHILD_AFTER: 4,
|
|
1501
|
-
FINISH: 5
|
|
1502
|
-
};
|
|
1503
|
-
const RouterStackEntryType = {
|
|
1504
|
-
ROUTER: "router",
|
|
1505
|
-
HANDLER: "handler"
|
|
1506
|
-
};
|
|
1507
|
-
//#endregion
|
|
1508
|
-
//#region src/router/utils.ts
|
|
1509
|
-
function isRouterInstance(input) {
|
|
1510
|
-
return hasInstanceof(input, RouterSymbol);
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Build a non-terminal `PathMatcher` for a router mount path.
|
|
1514
|
-
*
|
|
1515
|
-
* Returns `undefined` when the path is the root (`/`) or omitted entirely —
|
|
1516
|
-
* a router mounted at the root has no intrinsic path filter.
|
|
1517
|
-
*/
|
|
1518
|
-
function buildRouterPathMatcher(value) {
|
|
1519
|
-
if (value === "/" || typeof value === "undefined") return;
|
|
1520
|
-
return new PathMatcher(withLeadingSlash(withoutTrailingSlash(`${value}`)), { end: false });
|
|
1521
|
-
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Check if the request accepts JSON responses.
|
|
1524
|
-
* Matches application/json and +json suffixes (e.g. application/vnd.api+json).
|
|
1525
|
-
* Returns true if no Accept header is present (API-first default).
|
|
1526
|
-
*/
|
|
1527
|
-
function acceptsJson(request) {
|
|
1528
|
-
const accept = request.headers.get("accept");
|
|
1529
|
-
if (!accept) return true;
|
|
1530
|
-
return accept.includes("application/json") || accept.includes("+json") || accept.includes("*/*");
|
|
1531
|
-
}
|
|
1532
|
-
//#endregion
|
|
1533
|
-
//#region src/router/module.ts
|
|
1534
|
-
const METHOD_TO_REGISTER = {
|
|
1535
|
-
[MethodName.GET]: "get",
|
|
1536
|
-
[MethodName.POST]: "post",
|
|
1537
|
-
[MethodName.PUT]: "put",
|
|
1538
|
-
[MethodName.PATCH]: "patch",
|
|
1539
|
-
[MethodName.DELETE]: "delete",
|
|
1540
|
-
[MethodName.HEAD]: "head",
|
|
1541
|
-
[MethodName.OPTIONS]: "options"
|
|
1542
|
-
};
|
|
1543
|
-
var Router = class Router {
|
|
1544
|
-
/**
|
|
1545
|
-
* A label for the router instance.
|
|
1546
|
-
*/
|
|
1547
|
-
name;
|
|
1548
|
-
/**
|
|
1549
|
-
* Array of mounted layers, routes & routers, each tagged by kind so the
|
|
1550
|
-
* dispatch loop can discriminate without `isRouterInstance`/`isHandler`
|
|
1551
|
-
* runtime checks.
|
|
1552
|
-
*
|
|
1553
|
-
* @protected
|
|
1554
|
-
*/
|
|
1555
|
-
stack = [];
|
|
1556
|
-
/**
|
|
1557
|
-
* Path matcher for the current mount path.
|
|
1558
|
-
*
|
|
1559
|
-
* @protected
|
|
1560
|
-
*/
|
|
1561
|
-
pathMatcher;
|
|
1562
|
-
/**
|
|
1563
|
-
* Lifecycle hook registry.
|
|
1564
|
-
*
|
|
1565
|
-
* @protected
|
|
1566
|
-
*/
|
|
1567
|
-
hooks;
|
|
1568
|
-
/**
|
|
1569
|
-
* Normalized options for this router instance.
|
|
1570
|
-
*/
|
|
1571
|
-
_options;
|
|
1572
|
-
/**
|
|
1573
|
-
* Registry of installed plugins (name → version) on this router.
|
|
1574
|
-
*
|
|
1575
|
-
* @protected
|
|
1576
|
-
*/
|
|
1577
|
-
plugins = /* @__PURE__ */ new Map();
|
|
1578
|
-
constructor(input = {}) {
|
|
1579
|
-
this.name = input.name;
|
|
1580
|
-
const { hooks = new Hooks(), plugins = /* @__PURE__ */ new Map(), ...options } = input;
|
|
1581
|
-
this.hooks = hooks;
|
|
1582
|
-
this.plugins = new Map(plugins);
|
|
1583
|
-
this._options = normalizeRouterOptions(options);
|
|
1584
|
-
this.pathMatcher = buildRouterPathMatcher(options.path);
|
|
1585
|
-
markInstanceof(this, RouterSymbol);
|
|
1586
|
-
}
|
|
1587
|
-
matchPath(path) {
|
|
1588
|
-
if (this.pathMatcher) return this.pathMatcher.test(path);
|
|
1589
|
-
return true;
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Public entry point — creates a DispatcherEvent from the request,
|
|
1593
|
-
* runs the pipeline, and returns a Response (with 404/500 fallbacks).
|
|
1594
|
-
*/
|
|
1595
|
-
async fetch(request) {
|
|
1596
|
-
const event = new DispatcherEvent(request);
|
|
1597
|
-
let response;
|
|
1598
|
-
try {
|
|
1599
|
-
const timeoutMs = this._options.timeout;
|
|
1600
|
-
if (timeoutMs) {
|
|
1601
|
-
const controller = new AbortController();
|
|
1602
|
-
event.signal = controller.signal;
|
|
1603
|
-
let timerId;
|
|
1604
|
-
try {
|
|
1605
|
-
response = await Promise.race([this.dispatch(event), new Promise((_, reject) => {
|
|
1606
|
-
timerId = setTimeout(() => {
|
|
1607
|
-
controller.abort();
|
|
1608
|
-
reject(createError({
|
|
1609
|
-
status: 408,
|
|
1610
|
-
message: "Request Timeout"
|
|
1611
|
-
}));
|
|
1612
|
-
}, timeoutMs);
|
|
1613
|
-
})]);
|
|
1614
|
-
} finally {
|
|
1615
|
-
clearTimeout(timerId);
|
|
1616
|
-
}
|
|
1617
|
-
} else response = await this.dispatch(event);
|
|
1618
|
-
} catch (e) {
|
|
1619
|
-
event.error = createError(e);
|
|
1620
|
-
}
|
|
1621
|
-
if (response) return response;
|
|
1622
|
-
if (event.error) return this.buildFallbackResponse(request, event, event.error.status || 500, event.error.message);
|
|
1623
|
-
return this.buildFallbackResponse(request, event, 404, "Not Found");
|
|
1624
|
-
}
|
|
1625
|
-
buildFallbackResponse(request, event, status, message) {
|
|
1626
|
-
const headers = new Headers(event.response.headers);
|
|
1627
|
-
if (acceptsJson(request)) {
|
|
1628
|
-
headers.set("content-type", "application/json; charset=utf-8");
|
|
1629
|
-
return new Response(JSON.stringify({
|
|
1630
|
-
status,
|
|
1631
|
-
message
|
|
1632
|
-
}), {
|
|
1633
|
-
status,
|
|
1634
|
-
headers
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
headers.set("content-type", "text/plain; charset=utf-8");
|
|
1638
|
-
return new Response(message, {
|
|
1639
|
-
status,
|
|
1640
|
-
headers
|
|
1641
|
-
});
|
|
1642
|
-
}
|
|
1643
|
-
async executePipelineStep(context) {
|
|
1644
|
-
while (context.step !== RouterPipelineStep.FINISH) switch (context.step) {
|
|
1645
|
-
case RouterPipelineStep.START:
|
|
1646
|
-
await this.executePipelineStepStart(context);
|
|
1647
|
-
break;
|
|
1648
|
-
case RouterPipelineStep.LOOKUP:
|
|
1649
|
-
await this.executePipelineStepLookup(context);
|
|
1650
|
-
break;
|
|
1651
|
-
case RouterPipelineStep.CHILD_BEFORE:
|
|
1652
|
-
await this.executePipelineStepChildBefore(context);
|
|
1653
|
-
break;
|
|
1654
|
-
case RouterPipelineStep.CHILD_DISPATCH:
|
|
1655
|
-
await this.executePipelineStepChildDispatch(context);
|
|
1656
|
-
break;
|
|
1657
|
-
case RouterPipelineStep.CHILD_AFTER:
|
|
1658
|
-
await this.executePipelineStepChildAfter(context);
|
|
1659
|
-
break;
|
|
1660
|
-
default:
|
|
1661
|
-
context.step = RouterPipelineStep.FINISH;
|
|
1662
|
-
break;
|
|
1663
|
-
}
|
|
1664
|
-
await this.executePipelineStepFinish(context);
|
|
1665
|
-
}
|
|
1666
|
-
async executePipelineStepStart(context) {
|
|
1667
|
-
await this.hooks.trigger(HookName.REQUEST, context.event);
|
|
1668
|
-
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1669
|
-
else context.step = RouterPipelineStep.LOOKUP;
|
|
1670
|
-
}
|
|
1671
|
-
async executePipelineStepLookup(context) {
|
|
1672
|
-
while (!context.event.dispatched && context.stackIndex < this.stack.length) {
|
|
1673
|
-
const entry = this.stack[context.stackIndex];
|
|
1674
|
-
if (entry.type === RouterStackEntryType.HANDLER) {
|
|
1675
|
-
const handler = entry.data;
|
|
1676
|
-
if (context.event.error && handler.type === HandlerType.CORE || !context.event.error && handler.type === HandlerType.ERROR) {
|
|
1677
|
-
context.stackIndex++;
|
|
1678
|
-
continue;
|
|
1679
|
-
}
|
|
1680
|
-
if (entry.pathMatcher ? entry.pathMatcher.test(context.event.path) : handler.matchPath(context.event.path)) {
|
|
1681
|
-
const method = entry.method ?? handler.method;
|
|
1682
|
-
if (method) context.event.methodsAllowed.add(method);
|
|
1683
|
-
if (matchHandlerMethod(method, context.event.method)) {
|
|
1684
|
-
await this.hooks.trigger(HookName.CHILD_MATCH, context.event);
|
|
1685
|
-
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1686
|
-
else context.step = RouterPipelineStep.CHILD_BEFORE;
|
|
1687
|
-
return;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
context.stackIndex++;
|
|
1691
|
-
continue;
|
|
1692
|
-
}
|
|
1693
|
-
if (entry.pathMatcher ? entry.pathMatcher.test(context.event.path) : entry.data.matchPath(context.event.path)) {
|
|
1694
|
-
await this.hooks.trigger(HookName.CHILD_MATCH, context.event);
|
|
1695
|
-
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1696
|
-
else context.step = RouterPipelineStep.CHILD_BEFORE;
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
context.stackIndex++;
|
|
1700
|
-
}
|
|
1701
|
-
context.step = RouterPipelineStep.FINISH;
|
|
1702
|
-
}
|
|
1703
|
-
async executePipelineStepChildBefore(context) {
|
|
1704
|
-
await this.hooks.trigger(HookName.CHILD_DISPATCH_BEFORE, context.event);
|
|
1705
|
-
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1706
|
-
else context.step = RouterPipelineStep.CHILD_DISPATCH;
|
|
1707
|
-
}
|
|
1708
|
-
async executePipelineStepChildAfter(context) {
|
|
1709
|
-
await this.hooks.trigger(HookName.CHILD_DISPATCH_AFTER, context.event);
|
|
1710
|
-
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1711
|
-
else context.step = RouterPipelineStep.LOOKUP;
|
|
1712
|
-
}
|
|
1713
|
-
async executePipelineStepChildDispatch(context) {
|
|
1714
|
-
const entry = this.stack[context.stackIndex];
|
|
1715
|
-
if (context.event.dispatched || typeof entry === "undefined") {
|
|
1716
|
-
context.step = RouterPipelineStep.FINISH;
|
|
1717
|
-
return;
|
|
1718
|
-
}
|
|
1719
|
-
const { event } = context;
|
|
1720
|
-
const savedPath = event.path;
|
|
1721
|
-
const savedMountPath = event.mountPath;
|
|
1722
|
-
const savedParams = event.params;
|
|
1723
|
-
if (entry.type === RouterStackEntryType.ROUTER && entry.pathMatcher) {
|
|
1724
|
-
const output = entry.pathMatcher.exec(event.path);
|
|
1725
|
-
if (typeof output !== "undefined") {
|
|
1726
|
-
event.mountPath = cleanDoubleSlashes(`${event.mountPath}/${output.path}`);
|
|
1727
|
-
if (event.path === output.path) event.path = "/";
|
|
1728
|
-
else event.path = withLeadingSlash(event.path.substring(output.path.length));
|
|
1729
|
-
event.params = {
|
|
1730
|
-
...event.params,
|
|
1731
|
-
...output.params
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
|
-
} else if (entry.type === RouterStackEntryType.HANDLER && entry.pathMatcher) {
|
|
1735
|
-
const output = entry.pathMatcher.exec(event.path);
|
|
1736
|
-
if (typeof output !== "undefined") event.params = {
|
|
1737
|
-
...event.params,
|
|
1738
|
-
...output.params
|
|
1739
|
-
};
|
|
1740
|
-
}
|
|
1741
|
-
try {
|
|
1742
|
-
event.setNext(async (error) => {
|
|
1743
|
-
if (error) event.error = createError(error);
|
|
1744
|
-
const nextContext = {
|
|
1745
|
-
step: RouterPipelineStep.LOOKUP,
|
|
1746
|
-
event,
|
|
1747
|
-
stackIndex: context.stackIndex + 1,
|
|
1748
|
-
response: void 0
|
|
1749
|
-
};
|
|
1750
|
-
await this.executePipelineStep(nextContext);
|
|
1751
|
-
return nextContext.response;
|
|
1752
|
-
});
|
|
1753
|
-
const response = await entry.data.dispatch(event);
|
|
1754
|
-
if (response) {
|
|
1755
|
-
context.response = response;
|
|
1756
|
-
event.dispatched = true;
|
|
1757
|
-
}
|
|
1758
|
-
} catch (e) {
|
|
1759
|
-
event.error = createError(e);
|
|
1760
|
-
await this.hooks.trigger(HookName.ERROR, event);
|
|
1761
|
-
}
|
|
1762
|
-
if (!event.dispatched) {
|
|
1763
|
-
event.path = savedPath;
|
|
1764
|
-
event.mountPath = savedMountPath;
|
|
1765
|
-
event.params = savedParams;
|
|
1766
|
-
}
|
|
1767
|
-
context.stackIndex++;
|
|
1768
|
-
context.step = RouterPipelineStep.CHILD_AFTER;
|
|
1769
|
-
}
|
|
1770
|
-
async executePipelineStepFinish(context) {
|
|
1771
|
-
if (context.event.error || context.event.dispatched) return this.hooks.trigger(HookName.RESPONSE, context.event);
|
|
1772
|
-
if (!context.event.dispatched && context.event.routerPath.length === 1 && context.event.method && context.event.method === MethodName.OPTIONS) {
|
|
1773
|
-
if (context.event.methodsAllowed.has(MethodName.GET)) context.event.methodsAllowed.add(MethodName.HEAD);
|
|
1774
|
-
const options = [...context.event.methodsAllowed].map((key) => key.toUpperCase()).join(",");
|
|
1775
|
-
const optionsHeaders = new Headers(context.event.response.headers);
|
|
1776
|
-
optionsHeaders.set(HeaderName.ALLOW, options);
|
|
1777
|
-
context.response = new Response(options, {
|
|
1778
|
-
status: context.event.response.status || 200,
|
|
1779
|
-
headers: optionsHeaders
|
|
1780
|
-
});
|
|
1781
|
-
context.event.dispatched = true;
|
|
1782
|
-
}
|
|
1783
|
-
return this.hooks.trigger(HookName.RESPONSE, context.event);
|
|
1784
|
-
}
|
|
1785
|
-
async dispatch(event) {
|
|
1786
|
-
const savedPath = event.path;
|
|
1787
|
-
const savedMountPath = event.mountPath;
|
|
1788
|
-
const savedParams = event.params;
|
|
1789
|
-
if (this.pathMatcher) {
|
|
1790
|
-
const output = this.pathMatcher.exec(event.path);
|
|
1791
|
-
if (typeof output !== "undefined") {
|
|
1792
|
-
event.mountPath = cleanDoubleSlashes(`${event.mountPath}/${output.path}`);
|
|
1793
|
-
if (event.path === output.path) event.path = "/";
|
|
1794
|
-
else event.path = withLeadingSlash(event.path.substring(output.path.length));
|
|
1795
|
-
event.params = {
|
|
1796
|
-
...event.params,
|
|
1797
|
-
...output.params
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
const context = {
|
|
1802
|
-
step: RouterPipelineStep.START,
|
|
1803
|
-
event,
|
|
1804
|
-
stackIndex: 0
|
|
1805
|
-
};
|
|
1806
|
-
event.routerPath.push({
|
|
1807
|
-
name: this.name,
|
|
1808
|
-
options: this._options
|
|
1809
|
-
});
|
|
1810
|
-
try {
|
|
1811
|
-
await this.executePipelineStep(context);
|
|
1812
|
-
} finally {
|
|
1813
|
-
event.routerPath.pop();
|
|
1814
|
-
if (!event.dispatched) {
|
|
1815
|
-
event.path = savedPath;
|
|
1816
|
-
event.mountPath = savedMountPath;
|
|
1817
|
-
event.params = savedParams;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
return context.response;
|
|
1821
|
-
}
|
|
1822
|
-
delete(...input) {
|
|
1823
|
-
this.useForMethod(MethodName.DELETE, ...input);
|
|
1824
|
-
return this;
|
|
1825
|
-
}
|
|
1826
|
-
get(...input) {
|
|
1827
|
-
this.useForMethod(MethodName.GET, ...input);
|
|
1828
|
-
return this;
|
|
1829
|
-
}
|
|
1830
|
-
post(...input) {
|
|
1831
|
-
this.useForMethod(MethodName.POST, ...input);
|
|
1832
|
-
return this;
|
|
1833
|
-
}
|
|
1834
|
-
put(...input) {
|
|
1835
|
-
this.useForMethod(MethodName.PUT, ...input);
|
|
1836
|
-
return this;
|
|
1837
|
-
}
|
|
1838
|
-
patch(...input) {
|
|
1839
|
-
this.useForMethod(MethodName.PATCH, ...input);
|
|
1840
|
-
return this;
|
|
1841
|
-
}
|
|
1842
|
-
head(...input) {
|
|
1843
|
-
this.useForMethod(MethodName.HEAD, ...input);
|
|
1844
|
-
return this;
|
|
1845
|
-
}
|
|
1846
|
-
options(...input) {
|
|
1847
|
-
this.useForMethod(MethodName.OPTIONS, ...input);
|
|
1848
|
-
return this;
|
|
1849
|
-
}
|
|
1850
|
-
useForMethod(method, ...input) {
|
|
1851
|
-
let path;
|
|
1852
|
-
for (const element of input) {
|
|
1853
|
-
if (isPath(element)) {
|
|
1854
|
-
path = element;
|
|
1855
|
-
continue;
|
|
1856
|
-
}
|
|
1857
|
-
let handler;
|
|
1858
|
-
if (isHandlerOptions(element)) handler = new Handler({
|
|
1859
|
-
...element,
|
|
1860
|
-
method,
|
|
1861
|
-
path: path ?? element.path
|
|
1862
|
-
});
|
|
1863
|
-
else if (isHandler(element)) handler = element;
|
|
1864
|
-
else continue;
|
|
1865
|
-
this.stack.push({
|
|
1866
|
-
type: RouterStackEntryType.HANDLER,
|
|
1867
|
-
data: handler,
|
|
1868
|
-
method,
|
|
1869
|
-
path,
|
|
1870
|
-
pathMatcher: buildHandlerPathMatcher(path ?? handler.path, true)
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
use(...input) {
|
|
1875
|
-
let path;
|
|
1876
|
-
for (const item of input) {
|
|
1877
|
-
if (isPath(item)) {
|
|
1878
|
-
path = withLeadingSlash(item);
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
if (isRouterInstance(item)) {
|
|
1882
|
-
this.stack.push({
|
|
1883
|
-
type: RouterStackEntryType.ROUTER,
|
|
1884
|
-
data: item,
|
|
1885
|
-
path,
|
|
1886
|
-
pathMatcher: buildRouterPathMatcher(path)
|
|
1887
|
-
});
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
if (isHandlerOptions(item)) {
|
|
1891
|
-
const handler = new Handler({
|
|
1892
|
-
...item,
|
|
1893
|
-
path: path ?? item.path
|
|
1894
|
-
});
|
|
1895
|
-
this.stack.push({
|
|
1896
|
-
type: RouterStackEntryType.HANDLER,
|
|
1897
|
-
data: handler,
|
|
1898
|
-
path
|
|
1899
|
-
});
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
if (isHandler(item)) {
|
|
1903
|
-
this.stack.push({
|
|
1904
|
-
type: RouterStackEntryType.HANDLER,
|
|
1905
|
-
data: item,
|
|
1906
|
-
path,
|
|
1907
|
-
pathMatcher: buildHandlerPathMatcher(path, !!item.method)
|
|
1908
|
-
});
|
|
1909
|
-
continue;
|
|
1910
|
-
}
|
|
1911
|
-
if (isPlugin(item)) if (path) this.install(item, { path });
|
|
1912
|
-
else this.install(item);
|
|
1913
|
-
}
|
|
1914
|
-
return this;
|
|
1915
|
-
}
|
|
1916
|
-
/**
|
|
1917
|
-
* Check if a plugin with the given name is installed on this router.
|
|
1918
|
-
*/
|
|
1919
|
-
hasPlugin(name) {
|
|
1920
|
-
return this.plugins.has(name);
|
|
1921
|
-
}
|
|
1922
|
-
/**
|
|
1923
|
-
* Get the version of an installed plugin by name on this router,
|
|
1924
|
-
* or `undefined` if the plugin is not installed here.
|
|
1925
|
-
*/
|
|
1926
|
-
getPluginVersion(name) {
|
|
1927
|
-
return this.plugins.get(name);
|
|
1928
|
-
}
|
|
1929
|
-
install(plugin, context = {}) {
|
|
1930
|
-
if (this.plugins.has(plugin.name)) throw new PluginAlreadyInstalledError(plugin.name);
|
|
1931
|
-
const router = new Router({ name: plugin.name });
|
|
1932
|
-
plugin.install(router);
|
|
1933
|
-
if (context.path) this.use(context.path, router);
|
|
1934
|
-
else this.use(router);
|
|
1935
|
-
this.plugins.set(plugin.name, plugin.version);
|
|
1936
|
-
return this;
|
|
1937
|
-
}
|
|
1938
|
-
/**
|
|
1939
|
-
* Return a new `Router` that mirrors this one but owns independent
|
|
1940
|
-
* mountable state.
|
|
1941
|
-
*
|
|
1942
|
-
* The new router has:
|
|
1943
|
-
* - a fresh `stack` array of shallow-copied entries (handlers and child
|
|
1944
|
-
* routers are shared by reference; only the wrapping entries are new)
|
|
1945
|
-
* - the same `pathMatcher` reference (it is stateless)
|
|
1946
|
-
* - a fresh `Hooks` instance seeded with the current listeners
|
|
1947
|
-
* - a shallow copy of `_options`
|
|
1948
|
-
* - a fresh `plugins` map with the same entries
|
|
1949
|
-
*
|
|
1950
|
-
* Use this when the same logical router needs to be mounted under
|
|
1951
|
-
* multiple paths — each mount can receive its own clone so subsequent
|
|
1952
|
-
* mutations on one mount do not bleed into the others.
|
|
1953
|
-
*/
|
|
1954
|
-
clone() {
|
|
1955
|
-
const next = new Router({
|
|
1956
|
-
...this._options,
|
|
1957
|
-
hooks: this.hooks.clone(),
|
|
1958
|
-
plugins: this.plugins
|
|
1959
|
-
});
|
|
1960
|
-
for (const entry of this.stack) {
|
|
1961
|
-
if (entry.type === RouterStackEntryType.ROUTER) {
|
|
1962
|
-
const data = entry.data.clone();
|
|
1963
|
-
if (entry.path) next.use(entry.path, data);
|
|
1964
|
-
else next.use(data);
|
|
1965
|
-
continue;
|
|
1966
|
-
}
|
|
1967
|
-
if (entry.method) {
|
|
1968
|
-
const register = METHOD_TO_REGISTER[entry.method];
|
|
1969
|
-
if (entry.path) next[register](entry.path, entry.data);
|
|
1970
|
-
else next[register](entry.data);
|
|
1971
|
-
continue;
|
|
1972
|
-
}
|
|
1973
|
-
if (entry.path) next.use(entry.path, entry.data);
|
|
1974
|
-
else next.use(entry.data);
|
|
1975
|
-
}
|
|
1976
|
-
return next;
|
|
1977
|
-
}
|
|
1978
|
-
on(name, fn, priority) {
|
|
1979
|
-
return this.hooks.addListener(name, fn, priority);
|
|
1980
|
-
}
|
|
1981
|
-
off(name, fn) {
|
|
1982
|
-
if (typeof fn === "undefined") {
|
|
1983
|
-
this.hooks.removeListener(name);
|
|
1984
|
-
return this;
|
|
1985
|
-
}
|
|
1986
|
-
this.hooks.removeListener(name, fn);
|
|
1987
|
-
return this;
|
|
1988
|
-
}
|
|
1989
|
-
};
|
|
1990
|
-
//#endregion
|
|
1991
|
-
export { setResponseHeaderAttachment as $, Handler as A, sendRedirect as B, fromWebHandler as C, fromNodeMiddleware as D, fromNodeHandler as E, HandlerSymbol as F, getRequestHeader as G, getRequestAcceptableContentType as H, HandlerType as I, sendAccepted as J, sendFile as K, DispatcherEvent as L, matchHandlerMethod as M, isPath as N, defineErrorHandler as O, PathMatcher as P, setResponseHeaderContentType as Q, RoutupEvent as R, isHandlerOptions as S, isWebHandlerProvider as T, getRequestAcceptableContentTypes as U, sendFormat as V, useRequestNegotiator as W, createError as X, toResponse as Y, isError as Z, getRequestAcceptableEncodings as _, PluginInstallError as a, serializeEventStreamMessage as at, isRequestCacheable as b, isPluginError as c, setResponseCacheHeaders as ct, getRequestIP as d, setResponseHeaderInline as et, getRequestHostName as f, getRequestAcceptableEncoding as g, getRequestAcceptableLanguages as h, PluginNotInstalledError as i, createEventStream as it, buildHandlerPathMatcher as j, defineCoreHandler as k, PluginErrorCode as l, HeaderName as lt, getRequestAcceptableLanguage as m, normalizeRouterOptions as n, appendResponseHeader as nt, PluginAlreadyInstalledError as o, ErrorSymbol as ot, matchRequestContentType as p, sendCreated as q, isPlugin as r, appendResponseHeaderDirective as rt, PluginError as s, RoutupError as st, Router as t, setResponseContentTypeByFileName as tt, getRequestProtocol as u, MethodName as ut, getRequestAcceptableCharset as v, isWebHandler as w, isHandler as x, getRequestAcceptableCharsets as y, sendStream as z };
|
|
1992
|
-
|
|
1993
|
-
//# sourceMappingURL=src-DX0rndew.mjs.map
|