routup 5.0.0-beta.2 → 5.0.0-beta.4
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/bun.d.mts +2 -2
- package/dist/bun.mjs +2 -2
- package/dist/cloudflare.d.mts +2 -2
- package/dist/cloudflare.mjs +2 -2
- package/dist/deno.d.mts +2 -2
- package/dist/deno.mjs +2 -2
- package/dist/generic.d.mts +2 -2
- package/dist/generic.mjs +2 -2
- package/dist/{index-BBvBVzPm.d.mts → index-doDmTYzg.d.mts} +385 -246
- package/dist/node.d.mts +2 -2
- package/dist/node.mjs +2 -2
- package/dist/service-worker.d.mts +2 -2
- package/dist/service-worker.mjs +2 -2
- package/dist/{src-CNoRH9eg.mjs → src-Ck8GklBr.mjs} +864 -719
- package/dist/src-Ck8GklBr.mjs.map +1 -0
- package/package.json +12 -17
- package/dist/bun.cjs +0 -69
- package/dist/bun.cjs.map +0 -1
- package/dist/bun.d.cts +0 -8
- package/dist/cloudflare.cjs +0 -69
- package/dist/cloudflare.cjs.map +0 -1
- package/dist/cloudflare.d.cts +0 -8
- package/dist/deno.cjs +0 -69
- package/dist/deno.cjs.map +0 -1
- package/dist/deno.d.cts +0 -8
- package/dist/generic.cjs +0 -69
- package/dist/generic.cjs.map +0 -1
- package/dist/generic.d.cts +0 -8
- package/dist/index-eUBZJk6a.d.cts +0 -848
- package/dist/node.cjs +0 -73
- package/dist/node.cjs.map +0 -1
- package/dist/node.d.cts +0 -11
- package/dist/service-worker.cjs +0 -69
- package/dist/service-worker.cjs.map +0 -1
- package/dist/service-worker.d.cts +0 -10
- package/dist/src-BTv-fDqY.cjs +0 -1974
- package/dist/src-BTv-fDqY.cjs.map +0 -1
- package/dist/src-CNoRH9eg.mjs.map +0 -1
|
@@ -1,57 +1,137 @@
|
|
|
1
|
+
import { FastURL } from "srvx";
|
|
1
2
|
import { HTTPError, isHTTPError } from "@ebec/http";
|
|
2
3
|
import { Buffer } from "node:buffer";
|
|
3
4
|
import { subtle } from "uncrypto";
|
|
4
|
-
import {
|
|
5
|
+
import { merge } from "smob";
|
|
5
6
|
import { compile } from "proxy-addr";
|
|
6
7
|
import { get, getType } from "mime-explorer";
|
|
7
|
-
import { FastURL } from "srvx";
|
|
8
|
-
import { pathToRegexp } from "path-to-regexp";
|
|
9
8
|
import Negotiator from "negotiator";
|
|
9
|
+
import { pathToRegexp } from "path-to-regexp";
|
|
10
10
|
//#region src/constants.ts
|
|
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
|
-
|
|
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
|
+
"@instanceof" = ErrorSymbol;
|
|
70
|
+
constructor(input = {}) {
|
|
71
|
+
super(input);
|
|
72
|
+
this.name = "RoutupError";
|
|
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
|
+
}
|
|
55
135
|
//#endregion
|
|
56
136
|
//#region src/utils/header.ts
|
|
57
137
|
function sanitizeHeaderValue(value) {
|
|
@@ -193,20 +273,77 @@ function cleanDoubleSlashes(input = "") {
|
|
|
193
273
|
return input.replace(/\/+/g, "/");
|
|
194
274
|
}
|
|
195
275
|
//#endregion
|
|
276
|
+
//#region src/response/helpers/header.ts
|
|
277
|
+
function appendResponseHeader(event, name, value) {
|
|
278
|
+
const { headers } = event.response;
|
|
279
|
+
if (Array.isArray(value)) for (const v of value) headers.append(name, sanitizeHeaderValue(v));
|
|
280
|
+
else headers.append(name, sanitizeHeaderValue(value));
|
|
281
|
+
}
|
|
282
|
+
function appendResponseHeaderDirective(event, name, value) {
|
|
283
|
+
const { headers } = event.response;
|
|
284
|
+
const existing = headers.get(name);
|
|
285
|
+
if (!existing) {
|
|
286
|
+
if (Array.isArray(value)) headers.set(name, sanitizeHeaderValue(value.join("; ")));
|
|
287
|
+
else headers.set(name, sanitizeHeaderValue(value));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const directives = existing.split("; ");
|
|
291
|
+
if (Array.isArray(value)) directives.push(...value);
|
|
292
|
+
else directives.push(value);
|
|
293
|
+
const unique = [...new Set(directives)];
|
|
294
|
+
headers.set(name, sanitizeHeaderValue(unique.join("; ")));
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/response/helpers/utils.ts
|
|
298
|
+
function setResponseContentTypeByFileName(event, fileName) {
|
|
299
|
+
const ext = extname(fileName);
|
|
300
|
+
if (ext) {
|
|
301
|
+
let type = getMimeType(ext.substring(1));
|
|
302
|
+
if (type) {
|
|
303
|
+
const charset = getCharsetForMimeType(type);
|
|
304
|
+
if (charset) type += `; charset=${charset}`;
|
|
305
|
+
event.response.headers.set(HeaderName.CONTENT_TYPE, type);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/response/helpers/header-attachment.ts
|
|
311
|
+
function sanitizeFilename(filename) {
|
|
312
|
+
return filename.replace(/[\r\n]/g, "");
|
|
313
|
+
}
|
|
314
|
+
function toAsciiFilename(filename) {
|
|
315
|
+
return filename.replace(/[^\x20-\x7E]/g, "").replace(/"/g, "\\\"");
|
|
316
|
+
}
|
|
317
|
+
function encodeRfc5987(filename) {
|
|
318
|
+
return encodeURIComponent(filename).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/\*/g, "%2A");
|
|
319
|
+
}
|
|
320
|
+
function setResponseHeaderAttachment(event, filename) {
|
|
321
|
+
if (typeof filename === "string") setResponseContentTypeByFileName(event, filename);
|
|
322
|
+
let disposition = "attachment";
|
|
323
|
+
if (filename) {
|
|
324
|
+
const sanitized = sanitizeFilename(filename);
|
|
325
|
+
const ascii = toAsciiFilename(sanitized);
|
|
326
|
+
disposition += `; filename="${ascii}"`;
|
|
327
|
+
disposition += `; filename*=UTF-8''${encodeRfc5987(sanitized)}`;
|
|
328
|
+
}
|
|
329
|
+
event.response.headers.set(HeaderName.CONTENT_DISPOSITION, disposition);
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/response/helpers/header-content-type.ts
|
|
333
|
+
function setResponseHeaderContentType(event, input, ifNotExists) {
|
|
334
|
+
if (ifNotExists) {
|
|
335
|
+
if (event.response.headers.get(HeaderName.CONTENT_TYPE)) return;
|
|
336
|
+
}
|
|
337
|
+
const contentType = getMimeType(input);
|
|
338
|
+
if (contentType) event.response.headers.set(HeaderName.CONTENT_TYPE, contentType);
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
196
341
|
//#region src/error/is.ts
|
|
197
342
|
function isError(input) {
|
|
198
343
|
if (!isHTTPError(input)) return false;
|
|
199
|
-
return input
|
|
344
|
+
return isInstance(input, ErrorSymbol);
|
|
200
345
|
}
|
|
201
346
|
//#endregion
|
|
202
|
-
//#region src/error/module.ts
|
|
203
|
-
var RoutupError = class extends HTTPError {
|
|
204
|
-
constructor(input = {}) {
|
|
205
|
-
super(input);
|
|
206
|
-
this.name = "RoutupError";
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
//#endregion
|
|
210
347
|
//#region src/error/create.ts
|
|
211
348
|
function isNativeError(input) {
|
|
212
349
|
return isObject(input) && typeof input.message === "string" && typeof input.name === "string";
|
|
@@ -216,7 +353,7 @@ function isNativeError(input) {
|
|
|
216
353
|
* - an existing RoutupError (returned as-is)
|
|
217
354
|
* - an HTTPError (wrapped into a RoutupError preserving status)
|
|
218
355
|
* - an Error (wrapped preserving message and cause)
|
|
219
|
-
* - an options object (
|
|
356
|
+
* - an options object (status, message, etc.)
|
|
220
357
|
* - a message string
|
|
221
358
|
*
|
|
222
359
|
* @param input
|
|
@@ -227,8 +364,7 @@ function createError(input) {
|
|
|
227
364
|
if (isHTTPError(input)) return new RoutupError({
|
|
228
365
|
message: input.message,
|
|
229
366
|
code: input.code,
|
|
230
|
-
|
|
231
|
-
statusMessage: input.statusMessage,
|
|
367
|
+
status: input.status,
|
|
232
368
|
redirectURL: input.redirectURL,
|
|
233
369
|
cause: input
|
|
234
370
|
});
|
|
@@ -242,440 +378,72 @@ function createError(input) {
|
|
|
242
378
|
return new RoutupError(options);
|
|
243
379
|
}
|
|
244
380
|
//#endregion
|
|
245
|
-
//#region src/
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
_nextResult;
|
|
278
|
-
constructor(request) {
|
|
279
|
-
this.request = request;
|
|
280
|
-
this._url = new FastURL(request.url);
|
|
281
|
-
this.method = request.method;
|
|
282
|
-
this.path = this._url.pathname;
|
|
283
|
-
this.mountPath = "/";
|
|
284
|
-
this.params = {};
|
|
285
|
-
this.routerPath = [];
|
|
286
|
-
this.methodsAllowed = [];
|
|
287
|
-
this.store = Object.create(null);
|
|
288
|
-
this._dispatched = false;
|
|
289
|
-
this._nextCalled = false;
|
|
290
|
-
}
|
|
291
|
-
get headers() {
|
|
292
|
-
return this.request.headers;
|
|
381
|
+
//#region src/response/to-response.ts
|
|
382
|
+
function stripWeakPrefix(etag) {
|
|
383
|
+
return etag.startsWith("W/") ? etag.slice(2) : etag;
|
|
384
|
+
}
|
|
385
|
+
async function applyEtag(body, event, headers) {
|
|
386
|
+
const etagFn = event.routerOptions.etag;
|
|
387
|
+
if (!etagFn) return void 0;
|
|
388
|
+
const etag = await etagFn(body);
|
|
389
|
+
if (!etag) return void 0;
|
|
390
|
+
headers.set("etag", etag);
|
|
391
|
+
const ifNoneMatch = event.headers.get("if-none-match");
|
|
392
|
+
if (ifNoneMatch && (ifNoneMatch === "*" || ifNoneMatch.split(",").some((t) => stripWeakPrefix(t.trim()) === stripWeakPrefix(etag)))) return new Response(null, {
|
|
393
|
+
status: 304,
|
|
394
|
+
headers
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async function toResponse(value, event) {
|
|
398
|
+
if (value === void 0) return;
|
|
399
|
+
if (value === null) return new Response(null, {
|
|
400
|
+
status: event.response.status,
|
|
401
|
+
headers: event.response.headers
|
|
402
|
+
});
|
|
403
|
+
if (value instanceof Response) return value;
|
|
404
|
+
const { status, headers } = event.response;
|
|
405
|
+
if (typeof value === "string") {
|
|
406
|
+
if (!headers.has("content-type")) headers.set("content-type", "text/plain; charset=utf-8");
|
|
407
|
+
const cached = await applyEtag(value, event, headers);
|
|
408
|
+
if (cached) return cached;
|
|
409
|
+
return new Response(value, {
|
|
410
|
+
status,
|
|
411
|
+
headers
|
|
412
|
+
});
|
|
293
413
|
}
|
|
294
|
-
|
|
295
|
-
if (!
|
|
296
|
-
return
|
|
414
|
+
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
415
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/octet-stream");
|
|
416
|
+
return new Response(value, {
|
|
417
|
+
status,
|
|
418
|
+
headers
|
|
419
|
+
});
|
|
297
420
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
421
|
+
if (value instanceof ReadableStream) return new Response(value, {
|
|
422
|
+
status,
|
|
423
|
+
headers
|
|
424
|
+
});
|
|
425
|
+
if (value instanceof Blob) {
|
|
426
|
+
if (!headers.has("content-type")) headers.set("content-type", value.type || "application/octet-stream");
|
|
427
|
+
return new Response(value, {
|
|
428
|
+
status,
|
|
429
|
+
headers
|
|
430
|
+
});
|
|
304
431
|
}
|
|
305
|
-
|
|
306
|
-
|
|
432
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
|
433
|
+
let json;
|
|
434
|
+
try {
|
|
435
|
+
json = JSON.stringify(value);
|
|
436
|
+
} catch (e) {
|
|
437
|
+
throw createError({
|
|
438
|
+
message: "JSON serialization failed",
|
|
439
|
+
status: 500,
|
|
440
|
+
cause: e
|
|
441
|
+
});
|
|
307
442
|
}
|
|
308
|
-
set dispatched(value) {
|
|
309
|
-
this._dispatched = value;
|
|
310
|
-
}
|
|
311
|
-
async next() {
|
|
312
|
-
if (this._nextCalled) return this._nextResult;
|
|
313
|
-
this._nextCalled = true;
|
|
314
|
-
if (this._next) this._nextResult = this._next();
|
|
315
|
-
return this._nextResult;
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region src/handler/constants.ts
|
|
320
|
-
let HandlerType = /* @__PURE__ */ function(HandlerType) {
|
|
321
|
-
HandlerType["CORE"] = "core";
|
|
322
|
-
HandlerType["ERROR"] = "error";
|
|
323
|
-
return HandlerType;
|
|
324
|
-
}({});
|
|
325
|
-
const HandlerSymbol = /* @__PURE__ */ Symbol.for("Handler");
|
|
326
|
-
//#endregion
|
|
327
|
-
//#region src/hook/constants.ts
|
|
328
|
-
let HookName = /* @__PURE__ */ function(HookName) {
|
|
329
|
-
HookName["REQUEST"] = "request";
|
|
330
|
-
HookName["RESPONSE"] = "response";
|
|
331
|
-
HookName["ERROR"] = "error";
|
|
332
|
-
HookName["CHILD_MATCH"] = "childMatch";
|
|
333
|
-
HookName["CHILD_DISPATCH_BEFORE"] = "childDispatchBefore";
|
|
334
|
-
HookName["CHILD_DISPATCH_AFTER"] = "childDispatchAfter";
|
|
335
|
-
return HookName;
|
|
336
|
-
}({});
|
|
337
|
-
//#endregion
|
|
338
|
-
//#region src/hook/module.ts
|
|
339
|
-
var HookManager = class {
|
|
340
|
-
items;
|
|
341
|
-
constructor() {
|
|
342
|
-
this.items = {};
|
|
343
|
-
}
|
|
344
|
-
addListener(name, fn) {
|
|
345
|
-
this.items[name] = this.items[name] || [];
|
|
346
|
-
this.items[name].push(fn);
|
|
347
|
-
return () => {
|
|
348
|
-
this.removeListener(name, fn);
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
removeListener(name, fn) {
|
|
352
|
-
if (!this.items[name]) return;
|
|
353
|
-
if (typeof fn === "undefined") {
|
|
354
|
-
delete this.items[name];
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (typeof fn === "function") {
|
|
358
|
-
const index = this.items[name].indexOf(fn);
|
|
359
|
-
if (index !== -1) this.items[name].splice(index, 1);
|
|
360
|
-
}
|
|
361
|
-
if (this.items[name].length === 0) delete this.items[name];
|
|
362
|
-
}
|
|
363
|
-
async trigger(name, event) {
|
|
364
|
-
if (!this.items[name] || this.items[name].length === 0) return;
|
|
365
|
-
try {
|
|
366
|
-
for (let i = 0; i < this.items[name].length; i++) {
|
|
367
|
-
const listener = this.items[name][i];
|
|
368
|
-
await this.triggerListener(name, event, listener);
|
|
369
|
-
if (event.dispatched) {
|
|
370
|
-
if (event.error) event.error = void 0;
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch (e) {
|
|
375
|
-
event.error = e;
|
|
376
|
-
if (!this.isErrorListenerHook(name)) {
|
|
377
|
-
await this.trigger(HookName.ERROR, event);
|
|
378
|
-
if (event.dispatched) {
|
|
379
|
-
if (event.error) event.error = void 0;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
triggerListener(name, event, listener) {
|
|
385
|
-
if (this.isErrorListenerHook(name)) {
|
|
386
|
-
if (event.error) return listener(event);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
return listener(event);
|
|
390
|
-
}
|
|
391
|
-
isErrorListenerHook(input) {
|
|
392
|
-
return input === HookName.ERROR;
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/path/matcher.ts
|
|
397
|
-
function decodeParam(val) {
|
|
398
|
-
/* istanbul ignore next */
|
|
399
|
-
if (typeof val !== "string" || val.length === 0) return val;
|
|
400
|
-
try {
|
|
401
|
-
return decodeURIComponent(val);
|
|
402
|
-
} catch {
|
|
403
|
-
return val;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
var PathMatcher = class {
|
|
407
|
-
path;
|
|
408
|
-
regexp;
|
|
409
|
-
regexpKeys = [];
|
|
410
|
-
regexpOptions;
|
|
411
|
-
constructor(path, options) {
|
|
412
|
-
this.path = path;
|
|
413
|
-
this.regexpOptions = options || {};
|
|
414
|
-
const regexp = pathToRegexp(path, options);
|
|
415
|
-
this.regexp = regexp.regexp;
|
|
416
|
-
this.regexpKeys = regexp.keys;
|
|
417
|
-
}
|
|
418
|
-
test(path) {
|
|
419
|
-
return this.regexp.test(path);
|
|
420
|
-
}
|
|
421
|
-
exec(path) {
|
|
422
|
-
if (this.path === "/" && this.regexpOptions.end === false) return {
|
|
423
|
-
path: "/",
|
|
424
|
-
params: Object.create(null)
|
|
425
|
-
};
|
|
426
|
-
const match = this.regexp.exec(path);
|
|
427
|
-
if (!match) return;
|
|
428
|
-
const params = Object.create(null);
|
|
429
|
-
for (let i = 1; i < match.length; i++) {
|
|
430
|
-
const key = this.regexpKeys[i - 1];
|
|
431
|
-
if (!key) continue;
|
|
432
|
-
const prop = key.name;
|
|
433
|
-
const val = decodeParam(match[i]);
|
|
434
|
-
if (typeof val !== "undefined") params[prop] = val;
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
path: match[0],
|
|
438
|
-
params
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
//#endregion
|
|
443
|
-
//#region src/path/utils.ts
|
|
444
|
-
function isPath(input) {
|
|
445
|
-
return typeof input === "string";
|
|
446
|
-
}
|
|
447
|
-
//#endregion
|
|
448
|
-
//#region src/response/helpers/cache.ts
|
|
449
|
-
function setResponseCacheHeaders(event, options) {
|
|
450
|
-
options = options || {};
|
|
451
|
-
const cacheControls = ["public"].concat(options.cacheControls || []);
|
|
452
|
-
if (options.maxAge !== void 0) cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
|
|
453
|
-
if (options.modifiedTime) {
|
|
454
|
-
const modifiedTime = typeof options.modifiedTime === "string" ? new Date(options.modifiedTime) : options.modifiedTime;
|
|
455
|
-
event.response.headers.set("last-modified", modifiedTime.toUTCString());
|
|
456
|
-
}
|
|
457
|
-
event.response.headers.set("cache-control", cacheControls.join(", "));
|
|
458
|
-
}
|
|
459
|
-
//#endregion
|
|
460
|
-
//#region src/response/helpers/event-stream/utils.ts
|
|
461
|
-
function stripNewlines(value) {
|
|
462
|
-
return value.replace(/[\r\n]/g, "");
|
|
463
|
-
}
|
|
464
|
-
function serializeEventStreamMessage(message) {
|
|
465
|
-
let result = "";
|
|
466
|
-
if (message.id) result += `id: ${stripNewlines(message.id)}\n`;
|
|
467
|
-
if (message.event) result += `event: ${stripNewlines(message.event)}\n`;
|
|
468
|
-
if (typeof message.retry === "number" && Number.isInteger(message.retry)) result += `retry: ${message.retry}\n`;
|
|
469
|
-
const lines = message.data.replace(/\r/g, "").split("\n");
|
|
470
|
-
for (const line of lines) result += `data: ${line}\n`;
|
|
471
|
-
result += "\n";
|
|
472
|
-
return result;
|
|
473
|
-
}
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/response/helpers/event-stream/module.ts
|
|
476
|
-
function createEventStream(event, options) {
|
|
477
|
-
if (options?.maxMessageSize !== void 0) {
|
|
478
|
-
if (!Number.isInteger(options.maxMessageSize) || options.maxMessageSize < 0) throw new RoutupError("maxMessageSize must be a non-negative integer.");
|
|
479
|
-
}
|
|
480
|
-
let controller;
|
|
481
|
-
let closed = false;
|
|
482
|
-
const encoder = new TextEncoder();
|
|
483
|
-
const stream = new ReadableStream({
|
|
484
|
-
start(ctrl) {
|
|
485
|
-
controller = ctrl;
|
|
486
|
-
},
|
|
487
|
-
cancel() {
|
|
488
|
-
closed = true;
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
const headers = new Headers(event.response.headers);
|
|
492
|
-
headers.set(HeaderName.CONTENT_TYPE, "text/event-stream");
|
|
493
|
-
headers.set(HeaderName.CACHE_CONTROL, "private, no-cache, no-store, no-transform, must-revalidate, max-age=0");
|
|
494
|
-
headers.set(HeaderName.X_ACCEL_BUFFERING, "no");
|
|
495
|
-
headers.set(HeaderName.CONNECTION, "keep-alive");
|
|
496
|
-
const handle = {
|
|
497
|
-
write(message) {
|
|
498
|
-
if (closed) return;
|
|
499
|
-
if (typeof message === "string") {
|
|
500
|
-
handle.write({ data: message });
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
const serialized = serializeEventStreamMessage(message);
|
|
504
|
-
if (options?.maxMessageSize !== void 0) {
|
|
505
|
-
if (encoder.encode(serialized).byteLength > options.maxMessageSize) return;
|
|
506
|
-
}
|
|
507
|
-
controller.enqueue(encoder.encode(serialized));
|
|
508
|
-
},
|
|
509
|
-
end() {
|
|
510
|
-
if (closed) return;
|
|
511
|
-
closed = true;
|
|
512
|
-
controller.close();
|
|
513
|
-
},
|
|
514
|
-
response: new Response(stream, {
|
|
515
|
-
status: event.response.status,
|
|
516
|
-
statusText: event.response.statusText,
|
|
517
|
-
headers
|
|
518
|
-
})
|
|
519
|
-
};
|
|
520
|
-
return handle;
|
|
521
|
-
}
|
|
522
|
-
//#endregion
|
|
523
|
-
//#region src/response/helpers/gone.ts
|
|
524
|
-
function isResponseGone(event) {
|
|
525
|
-
return event.dispatched;
|
|
526
|
-
}
|
|
527
|
-
function setResponseGone(event) {
|
|
528
|
-
event.dispatched = true;
|
|
529
|
-
}
|
|
530
|
-
//#endregion
|
|
531
|
-
//#region src/response/helpers/header.ts
|
|
532
|
-
function appendResponseHeader(event, name, value) {
|
|
533
|
-
const { headers } = event.response;
|
|
534
|
-
if (Array.isArray(value)) for (const v of value) headers.append(name, sanitizeHeaderValue(v));
|
|
535
|
-
else headers.append(name, sanitizeHeaderValue(value));
|
|
536
|
-
}
|
|
537
|
-
function appendResponseHeaderDirective(event, name, value) {
|
|
538
|
-
const { headers } = event.response;
|
|
539
|
-
const existing = headers.get(name);
|
|
540
|
-
if (!existing) {
|
|
541
|
-
if (Array.isArray(value)) headers.set(name, sanitizeHeaderValue(value.join("; ")));
|
|
542
|
-
else headers.set(name, sanitizeHeaderValue(value));
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const directives = existing.split("; ");
|
|
546
|
-
if (Array.isArray(value)) directives.push(...value);
|
|
547
|
-
else directives.push(value);
|
|
548
|
-
const unique = [...new Set(directives)];
|
|
549
|
-
headers.set(name, sanitizeHeaderValue(unique.join("; ")));
|
|
550
|
-
}
|
|
551
|
-
//#endregion
|
|
552
|
-
//#region src/response/helpers/utils.ts
|
|
553
|
-
function setResponseContentTypeByFileName(event, fileName) {
|
|
554
|
-
const ext = extname(fileName);
|
|
555
|
-
if (ext) {
|
|
556
|
-
let type = getMimeType(ext.substring(1));
|
|
557
|
-
if (type) {
|
|
558
|
-
const charset = getCharsetForMimeType(type);
|
|
559
|
-
if (charset) type += `; charset=${charset}`;
|
|
560
|
-
event.response.headers.set(HeaderName.CONTENT_TYPE, type);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
//#endregion
|
|
565
|
-
//#region src/response/helpers/header-attachment.ts
|
|
566
|
-
function sanitizeFilename(filename) {
|
|
567
|
-
return filename.replace(/[\r\n]/g, "");
|
|
568
|
-
}
|
|
569
|
-
function toAsciiFilename(filename) {
|
|
570
|
-
return filename.replace(/[^\x20-\x7E]/g, "").replace(/"/g, "\\\"");
|
|
571
|
-
}
|
|
572
|
-
function encodeRfc5987(filename) {
|
|
573
|
-
return encodeURIComponent(filename).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/\*/g, "%2A");
|
|
574
|
-
}
|
|
575
|
-
function setResponseHeaderAttachment(event, filename) {
|
|
576
|
-
if (typeof filename === "string") setResponseContentTypeByFileName(event, filename);
|
|
577
|
-
let disposition = "attachment";
|
|
578
|
-
if (filename) {
|
|
579
|
-
const sanitized = sanitizeFilename(filename);
|
|
580
|
-
const ascii = toAsciiFilename(sanitized);
|
|
581
|
-
disposition += `; filename="${ascii}"`;
|
|
582
|
-
disposition += `; filename*=UTF-8''${encodeRfc5987(sanitized)}`;
|
|
583
|
-
}
|
|
584
|
-
event.response.headers.set(HeaderName.CONTENT_DISPOSITION, disposition);
|
|
585
|
-
}
|
|
586
|
-
//#endregion
|
|
587
|
-
//#region src/response/helpers/header-content-type.ts
|
|
588
|
-
function setResponseHeaderContentType(event, input, ifNotExists) {
|
|
589
|
-
if (ifNotExists) {
|
|
590
|
-
if (event.response.headers.get(HeaderName.CONTENT_TYPE)) return;
|
|
591
|
-
}
|
|
592
|
-
const contentType = getMimeType(input);
|
|
593
|
-
if (contentType) event.response.headers.set(HeaderName.CONTENT_TYPE, contentType);
|
|
594
|
-
}
|
|
595
|
-
//#endregion
|
|
596
|
-
//#region src/router-options/module.ts
|
|
597
|
-
const defaults = {
|
|
598
|
-
trustProxy: () => false,
|
|
599
|
-
subdomainOffset: 2,
|
|
600
|
-
etag: buildEtagFn(),
|
|
601
|
-
proxyIpMax: 0
|
|
602
|
-
};
|
|
603
|
-
const instances = {};
|
|
604
|
-
function setRouterOptions(id, input) {
|
|
605
|
-
instances[id] = input;
|
|
606
|
-
}
|
|
607
|
-
function findRouterOption(key, path) {
|
|
608
|
-
if (!path || path.length === 0) return defaults[key];
|
|
609
|
-
if (path.length > 0) for (let i = path.length; i >= 0; i--) {
|
|
610
|
-
const segment = path[i];
|
|
611
|
-
if (segment !== void 0 && hasOwnProperty(instances, segment) && typeof instances[segment][key] !== "undefined") return instances[segment][key];
|
|
612
|
-
}
|
|
613
|
-
return defaults[key];
|
|
614
|
-
}
|
|
615
|
-
//#endregion
|
|
616
|
-
//#region src/response/to-response.ts
|
|
617
|
-
function stripWeakPrefix(etag) {
|
|
618
|
-
return etag.startsWith("W/") ? etag.slice(2) : etag;
|
|
619
|
-
}
|
|
620
|
-
async function applyEtag(body, event, headers) {
|
|
621
|
-
const etagFn = findRouterOption("etag", event.routerPath);
|
|
622
|
-
if (!etagFn) return void 0;
|
|
623
|
-
const etag = await etagFn(body);
|
|
624
|
-
if (!etag) return void 0;
|
|
625
|
-
headers.set("etag", etag);
|
|
626
|
-
const ifNoneMatch = event.headers.get("if-none-match");
|
|
627
|
-
if (ifNoneMatch && (ifNoneMatch === "*" || ifNoneMatch.split(",").some((t) => stripWeakPrefix(t.trim()) === stripWeakPrefix(etag)))) return new Response(null, {
|
|
628
|
-
status: 304,
|
|
629
|
-
headers
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
async function toResponse(value, event) {
|
|
633
|
-
if (value === void 0) return;
|
|
634
|
-
if (value === null) return new Response(null, {
|
|
635
|
-
status: event.response.status,
|
|
636
|
-
statusText: event.response.statusText,
|
|
637
|
-
headers: event.response.headers
|
|
638
|
-
});
|
|
639
|
-
if (value instanceof Response) return value;
|
|
640
|
-
const { status, headers, statusText } = event.response;
|
|
641
|
-
if (typeof value === "string") {
|
|
642
|
-
if (!headers.has("content-type")) headers.set("content-type", "text/plain; charset=utf-8");
|
|
643
|
-
const cached = await applyEtag(value, event, headers);
|
|
644
|
-
if (cached) return cached;
|
|
645
|
-
return new Response(value, {
|
|
646
|
-
status,
|
|
647
|
-
statusText,
|
|
648
|
-
headers
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
652
|
-
if (!headers.has("content-type")) headers.set("content-type", "application/octet-stream");
|
|
653
|
-
return new Response(value, {
|
|
654
|
-
status,
|
|
655
|
-
statusText,
|
|
656
|
-
headers
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
if (value instanceof ReadableStream) return new Response(value, {
|
|
660
|
-
status,
|
|
661
|
-
statusText,
|
|
662
|
-
headers
|
|
663
|
-
});
|
|
664
|
-
if (value instanceof Blob) {
|
|
665
|
-
if (!headers.has("content-type")) headers.set("content-type", value.type || "application/octet-stream");
|
|
666
|
-
return new Response(value, {
|
|
667
|
-
status,
|
|
668
|
-
statusText,
|
|
669
|
-
headers
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
|
673
|
-
const json = JSON.stringify(value);
|
|
674
443
|
const cached = await applyEtag(json, event, headers);
|
|
675
444
|
if (cached) return cached;
|
|
676
445
|
return new Response(json, {
|
|
677
446
|
status,
|
|
678
|
-
statusText,
|
|
679
447
|
headers
|
|
680
448
|
});
|
|
681
449
|
}
|
|
@@ -683,16 +451,12 @@ async function toResponse(value, event) {
|
|
|
683
451
|
//#region src/response/helpers/send-accepted.ts
|
|
684
452
|
async function sendAccepted(event, data) {
|
|
685
453
|
event.response.status = 202;
|
|
686
|
-
event.response.statusText = "Accepted";
|
|
687
|
-
event.dispatched = true;
|
|
688
454
|
return await toResponse(data ?? "", event);
|
|
689
455
|
}
|
|
690
456
|
//#endregion
|
|
691
457
|
//#region src/response/helpers/send-created.ts
|
|
692
458
|
async function sendCreated(event, data) {
|
|
693
459
|
event.response.status = 201;
|
|
694
|
-
event.response.statusText = "Created";
|
|
695
|
-
event.dispatched = true;
|
|
696
460
|
return await toResponse(data ?? "", event);
|
|
697
461
|
}
|
|
698
462
|
//#endregion
|
|
@@ -718,133 +482,438 @@ async function sendFile(event, options) {
|
|
|
718
482
|
contentOptions.start = Number.isFinite(parsedStart) && parsedStart >= 0 ? parsedStart : 0;
|
|
719
483
|
contentOptions.end = Number.isFinite(parsedEnd) && parsedEnd >= 0 ? Math.min(parsedEnd, stats.size - 1) : stats.size - 1;
|
|
720
484
|
if (contentOptions.start >= stats.size || contentOptions.start > contentOptions.end) {
|
|
721
|
-
event.dispatched = true;
|
|
722
485
|
const rangeHeaders = new Headers(headers);
|
|
723
486
|
rangeHeaders.set(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
|
|
724
487
|
return new Response(null, {
|
|
725
488
|
status: 416,
|
|
726
|
-
statusText: event.response.statusText,
|
|
727
489
|
headers: rangeHeaders
|
|
728
490
|
});
|
|
729
491
|
}
|
|
730
|
-
headers.set(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
|
|
731
|
-
headers.set(HeaderName.CONTENT_LENGTH, `${contentOptions.end - contentOptions.start + 1}`);
|
|
732
|
-
statusCode = 206;
|
|
733
|
-
} else headers.set(HeaderName.CONTENT_LENGTH, `${stats.size}`);
|
|
734
|
-
headers.set(HeaderName.ACCEPT_RANGES, "bytes");
|
|
735
|
-
if (stats.mtime) {
|
|
736
|
-
const mtime = new Date(stats.mtime);
|
|
737
|
-
headers.set(HeaderName.LAST_MODIFIED, mtime.toUTCString());
|
|
738
|
-
headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
|
|
492
|
+
headers.set(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
|
|
493
|
+
headers.set(HeaderName.CONTENT_LENGTH, `${contentOptions.end - contentOptions.start + 1}`);
|
|
494
|
+
statusCode = 206;
|
|
495
|
+
} else headers.set(HeaderName.CONTENT_LENGTH, `${stats.size}`);
|
|
496
|
+
headers.set(HeaderName.ACCEPT_RANGES, "bytes");
|
|
497
|
+
if (stats.mtime) {
|
|
498
|
+
const mtime = new Date(stats.mtime);
|
|
499
|
+
headers.set(HeaderName.LAST_MODIFIED, mtime.toUTCString());
|
|
500
|
+
headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const content = await options.content(contentOptions);
|
|
504
|
+
return new Response(content, {
|
|
505
|
+
status: statusCode,
|
|
506
|
+
headers
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/request/helpers/header.ts
|
|
511
|
+
function getRequestHeader(event, name) {
|
|
512
|
+
return event.headers.get(name);
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/request/helpers/negotiator.ts
|
|
516
|
+
const NEGOTIATOR_KEY = Symbol.for("routup:negotiator");
|
|
517
|
+
function headersToPlainObject(headers) {
|
|
518
|
+
const result = {};
|
|
519
|
+
headers.forEach((value, key) => {
|
|
520
|
+
result[key] = value;
|
|
521
|
+
});
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
function useRequestNegotiator(event) {
|
|
525
|
+
let value = event.store[NEGOTIATOR_KEY];
|
|
526
|
+
if (value) return value;
|
|
527
|
+
value = new Negotiator({ headers: headersToPlainObject(event.headers) });
|
|
528
|
+
event.store[NEGOTIATOR_KEY] = value;
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/request/helpers/header-accept.ts
|
|
533
|
+
function getRequestAcceptableContentTypes(event) {
|
|
534
|
+
return useRequestNegotiator(event).mediaTypes();
|
|
535
|
+
}
|
|
536
|
+
function getRequestAcceptableContentType(event, input) {
|
|
537
|
+
input = input || [];
|
|
538
|
+
const items = Array.isArray(input) ? input : [input];
|
|
539
|
+
if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
|
|
540
|
+
if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
|
|
541
|
+
let polluted = false;
|
|
542
|
+
const mimeTypes = [];
|
|
543
|
+
for (const item of items) {
|
|
544
|
+
const mimeType = getMimeType(item);
|
|
545
|
+
if (mimeType) mimeTypes.push(mimeType);
|
|
546
|
+
else polluted = true;
|
|
547
|
+
}
|
|
548
|
+
const matches = useRequestNegotiator(event).mediaTypes(mimeTypes);
|
|
549
|
+
if (matches.length > 0) {
|
|
550
|
+
if (polluted) return items[0];
|
|
551
|
+
return items[mimeTypes.indexOf(matches[0])];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/response/helpers/send-format.ts
|
|
556
|
+
function sendFormat(event, input) {
|
|
557
|
+
const { default: formatDefault, ...formats } = input;
|
|
558
|
+
const contentTypes = Object.keys(formats);
|
|
559
|
+
if (contentTypes.length === 0) return formatDefault();
|
|
560
|
+
const contentType = getRequestAcceptableContentType(event, contentTypes);
|
|
561
|
+
if (contentType && formats[contentType]) return formats[contentType]();
|
|
562
|
+
return formatDefault();
|
|
563
|
+
}
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/response/helpers/send-redirect.ts
|
|
566
|
+
function escapeHtml(str) {
|
|
567
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
568
|
+
}
|
|
569
|
+
function isAllowedRedirectUrl(location) {
|
|
570
|
+
if (location.startsWith("//")) return false;
|
|
571
|
+
if (location.startsWith("/") || location.startsWith(".")) return true;
|
|
572
|
+
try {
|
|
573
|
+
const url = new URL(location);
|
|
574
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
575
|
+
} catch {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function sendRedirect(event, location, statusCode = 302) {
|
|
580
|
+
if (!isAllowedRedirectUrl(location)) throw new RoutupError({
|
|
581
|
+
status: 400,
|
|
582
|
+
message: "Invalid redirect URL scheme."
|
|
583
|
+
});
|
|
584
|
+
const sanitizedLocation = sanitizeHeaderValue(location);
|
|
585
|
+
const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${escapeHtml(location)}"></head></html>`;
|
|
586
|
+
const headers = new Headers(event.response.headers);
|
|
587
|
+
headers.set("location", sanitizedLocation);
|
|
588
|
+
headers.set("content-type", "text/html; charset=utf-8");
|
|
589
|
+
headers.delete("content-length");
|
|
590
|
+
return new Response(html, {
|
|
591
|
+
status: statusCode,
|
|
592
|
+
headers
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/response/helpers/send-stream.ts
|
|
597
|
+
function sendStream(event, stream) {
|
|
598
|
+
const { status, headers } = event.response;
|
|
599
|
+
return new Response(stream, {
|
|
600
|
+
status,
|
|
601
|
+
headers
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/event/module.ts
|
|
606
|
+
var RoutupEvent = class {
|
|
607
|
+
request;
|
|
608
|
+
params;
|
|
609
|
+
path;
|
|
610
|
+
method;
|
|
611
|
+
mountPath;
|
|
612
|
+
headers;
|
|
613
|
+
searchParams;
|
|
614
|
+
response;
|
|
615
|
+
store;
|
|
616
|
+
signal;
|
|
617
|
+
_context;
|
|
618
|
+
_routerOptions;
|
|
619
|
+
constructor(context) {
|
|
620
|
+
this._context = context;
|
|
621
|
+
this.request = context.request;
|
|
622
|
+
this.params = context.params;
|
|
623
|
+
this.path = context.path;
|
|
624
|
+
this.method = context.method;
|
|
625
|
+
this.mountPath = context.mountPath;
|
|
626
|
+
this.headers = context.headers;
|
|
627
|
+
this.searchParams = context.searchParams;
|
|
628
|
+
this.response = context.response;
|
|
629
|
+
this.store = context.store;
|
|
630
|
+
this.signal = context.signal;
|
|
631
|
+
}
|
|
632
|
+
get routerOptions() {
|
|
633
|
+
if (!this._routerOptions) this._routerOptions = this._context.routerOptions();
|
|
634
|
+
return this._routerOptions;
|
|
635
|
+
}
|
|
636
|
+
async next(error) {
|
|
637
|
+
return this._context.next(this, error);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region src/dispatcher/module.ts
|
|
642
|
+
var DispatcherEvent = class {
|
|
643
|
+
request;
|
|
644
|
+
params;
|
|
645
|
+
path;
|
|
646
|
+
method;
|
|
647
|
+
/**
|
|
648
|
+
* Collected allowed methods (for OPTIONS).
|
|
649
|
+
*/
|
|
650
|
+
methodsAllowed;
|
|
651
|
+
mountPath;
|
|
652
|
+
error;
|
|
653
|
+
routerPath;
|
|
654
|
+
_dispatched;
|
|
655
|
+
_response;
|
|
656
|
+
_store;
|
|
657
|
+
/**
|
|
658
|
+
* Cached parsed URL (avoids double-parsing).
|
|
659
|
+
*/
|
|
660
|
+
_url;
|
|
661
|
+
/**
|
|
662
|
+
* Continuation function for middleware onion model.
|
|
663
|
+
*/
|
|
664
|
+
_next;
|
|
665
|
+
_signal;
|
|
666
|
+
_signalCleanup;
|
|
667
|
+
/**
|
|
668
|
+
* Whether _next has already been called (guard against double-invocation).
|
|
669
|
+
*/
|
|
670
|
+
_nextCalled;
|
|
671
|
+
/**
|
|
672
|
+
* The cached result of the next handler.
|
|
673
|
+
*/
|
|
674
|
+
_nextResult;
|
|
675
|
+
constructor(request) {
|
|
676
|
+
this.request = request;
|
|
677
|
+
this._url = new FastURL(request.url);
|
|
678
|
+
this.method = request.method;
|
|
679
|
+
this.path = this._url.pathname;
|
|
680
|
+
this.mountPath = "/";
|
|
681
|
+
this.params = {};
|
|
682
|
+
this.routerPath = [];
|
|
683
|
+
this.methodsAllowed = /* @__PURE__ */ new Set();
|
|
684
|
+
this._dispatched = false;
|
|
685
|
+
this._nextCalled = false;
|
|
686
|
+
}
|
|
687
|
+
get response() {
|
|
688
|
+
if (!this._response) this._response = {
|
|
689
|
+
status: 200,
|
|
690
|
+
headers: new Headers()
|
|
691
|
+
};
|
|
692
|
+
return this._response;
|
|
693
|
+
}
|
|
694
|
+
get signal() {
|
|
695
|
+
if (!this._signal) this._signal = this.request.signal;
|
|
696
|
+
return this._signal;
|
|
697
|
+
}
|
|
698
|
+
set signal(value) {
|
|
699
|
+
if (this._signalCleanup) {
|
|
700
|
+
this._signalCleanup();
|
|
701
|
+
this._signalCleanup = void 0;
|
|
702
|
+
}
|
|
703
|
+
if (value === this.request.signal) {
|
|
704
|
+
this._signal = value;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const controller = new AbortController();
|
|
708
|
+
const abort = (e) => {
|
|
709
|
+
const reason = e?.target instanceof AbortSignal ? e.target.reason : void 0;
|
|
710
|
+
this.request.signal.removeEventListener("abort", abort);
|
|
711
|
+
value.removeEventListener("abort", abort);
|
|
712
|
+
controller.abort(reason);
|
|
713
|
+
};
|
|
714
|
+
if (this.request.signal.aborted || value.aborted) {
|
|
715
|
+
const reason = this.request.signal.aborted ? this.request.signal.reason : value.reason;
|
|
716
|
+
controller.abort(reason);
|
|
717
|
+
} else {
|
|
718
|
+
this.request.signal.addEventListener("abort", abort, { once: true });
|
|
719
|
+
value.addEventListener("abort", abort, { once: true });
|
|
720
|
+
this._signalCleanup = () => {
|
|
721
|
+
this.request.signal.removeEventListener("abort", abort);
|
|
722
|
+
value.removeEventListener("abort", abort);
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
this._signal = controller.signal;
|
|
726
|
+
}
|
|
727
|
+
get dispatched() {
|
|
728
|
+
return this._dispatched;
|
|
729
|
+
}
|
|
730
|
+
set dispatched(value) {
|
|
731
|
+
this._dispatched = value;
|
|
732
|
+
}
|
|
733
|
+
async next(event, error) {
|
|
734
|
+
if (this._nextCalled) return this._nextResult;
|
|
735
|
+
this._nextCalled = true;
|
|
736
|
+
if (this._next) this._nextResult = this._next(event, error);
|
|
737
|
+
return this._nextResult;
|
|
738
|
+
}
|
|
739
|
+
setNext(fn) {
|
|
740
|
+
if (fn) this._next = async (event, error) => {
|
|
741
|
+
return toResponse(await fn(error), event);
|
|
742
|
+
};
|
|
743
|
+
else this._next = void 0;
|
|
744
|
+
this._nextCalled = false;
|
|
745
|
+
this._nextResult = void 0;
|
|
746
|
+
}
|
|
747
|
+
build(signal) {
|
|
748
|
+
return new RoutupEvent({
|
|
749
|
+
request: this.request,
|
|
750
|
+
params: this.params,
|
|
751
|
+
path: this.path,
|
|
752
|
+
method: this.method,
|
|
753
|
+
mountPath: this.mountPath,
|
|
754
|
+
headers: this.request.headers,
|
|
755
|
+
searchParams: new URLSearchParams(this._url.search),
|
|
756
|
+
response: this.response,
|
|
757
|
+
store: this.store,
|
|
758
|
+
signal: signal ?? this.signal,
|
|
759
|
+
routerOptions: () => this.resolveOptions(),
|
|
760
|
+
next: (event, error) => this.next(event, error)
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
get store() {
|
|
764
|
+
if (!this._store) this._store = Object.create(null);
|
|
765
|
+
return this._store;
|
|
766
|
+
}
|
|
767
|
+
resolveOptions() {
|
|
768
|
+
const resolved = {
|
|
769
|
+
trustProxy: () => false,
|
|
770
|
+
subdomainOffset: 2,
|
|
771
|
+
etag: buildEtagFn(),
|
|
772
|
+
proxyIpMax: 0
|
|
773
|
+
};
|
|
774
|
+
for (let i = 0; i < this.routerPath.length; i++) {
|
|
775
|
+
const node = this.routerPath[i];
|
|
776
|
+
const entries = Object.entries(node.options);
|
|
777
|
+
for (const entry of entries) {
|
|
778
|
+
const [key, value] = entry;
|
|
779
|
+
if (typeof value !== "undefined") resolved[key] = value;
|
|
780
|
+
}
|
|
739
781
|
}
|
|
782
|
+
return resolved;
|
|
740
783
|
}
|
|
741
|
-
|
|
742
|
-
event.dispatched = true;
|
|
743
|
-
return new Response(content, {
|
|
744
|
-
status: statusCode,
|
|
745
|
-
statusText: event.response.statusText,
|
|
746
|
-
headers
|
|
747
|
-
});
|
|
748
|
-
}
|
|
784
|
+
};
|
|
749
785
|
//#endregion
|
|
750
|
-
//#region src/
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
786
|
+
//#region src/handler/constants.ts
|
|
787
|
+
const HandlerType = {
|
|
788
|
+
CORE: "core",
|
|
789
|
+
ERROR: "error"
|
|
790
|
+
};
|
|
791
|
+
const HandlerSymbol = Symbol.for("Handler");
|
|
754
792
|
//#endregion
|
|
755
|
-
//#region src/
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
function useRequestNegotiator(event) {
|
|
765
|
-
let value = event.store[NEGOTIATOR_KEY];
|
|
766
|
-
if (value) return value;
|
|
767
|
-
value = new Negotiator({ headers: headersToPlainObject(event.headers) });
|
|
768
|
-
event.store[NEGOTIATOR_KEY] = value;
|
|
769
|
-
return value;
|
|
770
|
-
}
|
|
793
|
+
//#region src/hook/constants.ts
|
|
794
|
+
const HookName = {
|
|
795
|
+
REQUEST: "request",
|
|
796
|
+
RESPONSE: "response",
|
|
797
|
+
ERROR: "error",
|
|
798
|
+
CHILD_MATCH: "childMatch",
|
|
799
|
+
CHILD_DISPATCH_BEFORE: "childDispatchBefore",
|
|
800
|
+
CHILD_DISPATCH_AFTER: "childDispatchAfter"
|
|
801
|
+
};
|
|
771
802
|
//#endregion
|
|
772
|
-
//#region src/
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
input = input || [];
|
|
778
|
-
const items = Array.isArray(input) ? input : [input];
|
|
779
|
-
if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
|
|
780
|
-
if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
|
|
781
|
-
let polluted = false;
|
|
782
|
-
const mimeTypes = [];
|
|
783
|
-
for (const item of items) {
|
|
784
|
-
const mimeType = getMimeType(item);
|
|
785
|
-
if (mimeType) mimeTypes.push(mimeType);
|
|
786
|
-
else polluted = true;
|
|
803
|
+
//#region src/hook/module.ts
|
|
804
|
+
var HookManager = class {
|
|
805
|
+
items;
|
|
806
|
+
constructor() {
|
|
807
|
+
this.items = {};
|
|
787
808
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
809
|
+
addListener(name, fn, priority = 0) {
|
|
810
|
+
this.items[name] = this.items[name] || [];
|
|
811
|
+
const entry = {
|
|
812
|
+
fn,
|
|
813
|
+
priority
|
|
814
|
+
};
|
|
815
|
+
let i = 0;
|
|
816
|
+
while (i < this.items[name].length && this.items[name][i].priority >= priority) i++;
|
|
817
|
+
this.items[name].splice(i, 0, entry);
|
|
818
|
+
return () => {
|
|
819
|
+
this.removeListener(name, fn);
|
|
820
|
+
};
|
|
792
821
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
822
|
+
removeListener(name, fn) {
|
|
823
|
+
if (!this.items[name]) return;
|
|
824
|
+
if (typeof fn === "undefined") {
|
|
825
|
+
delete this.items[name];
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (typeof fn === "function") {
|
|
829
|
+
const index = this.items[name].findIndex((entry) => entry.fn === fn);
|
|
830
|
+
if (index !== -1) this.items[name].splice(index, 1);
|
|
831
|
+
}
|
|
832
|
+
if (this.items[name].length === 0) delete this.items[name];
|
|
833
|
+
}
|
|
834
|
+
async trigger(name, event) {
|
|
835
|
+
if (!this.items[name] || this.items[name].length === 0) return;
|
|
836
|
+
try {
|
|
837
|
+
for (let i = 0; i < this.items[name].length; i++) {
|
|
838
|
+
const { fn } = this.items[name][i];
|
|
839
|
+
await this.triggerListener(name, event, fn);
|
|
840
|
+
if (event.dispatched) {
|
|
841
|
+
if (event.error) event.error = void 0;
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
} catch (e) {
|
|
846
|
+
if (!event.error) event.error = createError(e);
|
|
847
|
+
if (!this.isErrorListenerHook(name)) {
|
|
848
|
+
await this.trigger(HookName.ERROR, event);
|
|
849
|
+
if (event.dispatched) {
|
|
850
|
+
if (event.error) event.error = void 0;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
triggerListener(name, event, listener) {
|
|
856
|
+
if (this.isErrorListenerHook(name)) {
|
|
857
|
+
if (event.error) return listener(event);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
return listener(event);
|
|
861
|
+
}
|
|
862
|
+
isErrorListenerHook(input) {
|
|
863
|
+
return input === HookName.ERROR;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
804
866
|
//#endregion
|
|
805
|
-
//#region src/
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
function isAllowedRedirectUrl(location) {
|
|
810
|
-
if (location.startsWith("//")) return false;
|
|
811
|
-
if (location.startsWith("/") || location.startsWith(".")) return true;
|
|
867
|
+
//#region src/path/matcher.ts
|
|
868
|
+
function decodeParam(val) {
|
|
869
|
+
/* istanbul ignore next */
|
|
870
|
+
if (typeof val !== "string" || val.length === 0) return val;
|
|
812
871
|
try {
|
|
813
|
-
|
|
814
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
872
|
+
return decodeURIComponent(val);
|
|
815
873
|
} catch {
|
|
816
|
-
return
|
|
874
|
+
return val;
|
|
817
875
|
}
|
|
818
876
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
877
|
+
var PathMatcher = class {
|
|
878
|
+
path;
|
|
879
|
+
regexp;
|
|
880
|
+
regexpKeys = [];
|
|
881
|
+
regexpOptions;
|
|
882
|
+
constructor(path, options) {
|
|
883
|
+
this.path = path;
|
|
884
|
+
this.regexpOptions = options || {};
|
|
885
|
+
const regexp = pathToRegexp(path, options);
|
|
886
|
+
this.regexp = regexp.regexp;
|
|
887
|
+
this.regexpKeys = regexp.keys;
|
|
888
|
+
}
|
|
889
|
+
test(path) {
|
|
890
|
+
return this.regexp.test(path);
|
|
891
|
+
}
|
|
892
|
+
exec(path) {
|
|
893
|
+
if (this.path === "/" && this.regexpOptions.end === false) return {
|
|
894
|
+
path: "/",
|
|
895
|
+
params: Object.create(null)
|
|
896
|
+
};
|
|
897
|
+
const match = this.regexp.exec(path);
|
|
898
|
+
if (!match) return;
|
|
899
|
+
const params = Object.create(null);
|
|
900
|
+
for (let i = 1; i < match.length; i++) {
|
|
901
|
+
const key = this.regexpKeys[i - 1];
|
|
902
|
+
if (!key) continue;
|
|
903
|
+
const prop = key.name;
|
|
904
|
+
const val = decodeParam(match[i]);
|
|
905
|
+
if (typeof val !== "undefined") params[prop] = val;
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
path: match[0],
|
|
909
|
+
params
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
};
|
|
838
913
|
//#endregion
|
|
839
|
-
//#region src/
|
|
840
|
-
function
|
|
841
|
-
|
|
842
|
-
const { status, statusText, headers } = event.response;
|
|
843
|
-
return new Response(stream, {
|
|
844
|
-
status,
|
|
845
|
-
statusText,
|
|
846
|
-
headers
|
|
847
|
-
});
|
|
914
|
+
//#region src/path/utils.ts
|
|
915
|
+
function isPath(input) {
|
|
916
|
+
return typeof input === "string";
|
|
848
917
|
}
|
|
849
918
|
//#endregion
|
|
850
919
|
//#region src/handler/module.ts
|
|
@@ -884,10 +953,37 @@ var Handler = class {
|
|
|
884
953
|
let response;
|
|
885
954
|
try {
|
|
886
955
|
let result;
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
956
|
+
const previewEvent = event.build();
|
|
957
|
+
const effectiveTimeout = this.resolveTimeout(previewEvent.routerOptions);
|
|
958
|
+
let childController;
|
|
959
|
+
let cleanupParentListener;
|
|
960
|
+
let handlerEvent = previewEvent;
|
|
961
|
+
if (effectiveTimeout) {
|
|
962
|
+
const parentSignal = event.signal;
|
|
963
|
+
childController = new AbortController();
|
|
964
|
+
if (parentSignal.aborted) childController.abort(parentSignal.reason);
|
|
965
|
+
else {
|
|
966
|
+
const onAbort = () => childController.abort(parentSignal.reason);
|
|
967
|
+
parentSignal.addEventListener("abort", onAbort, { once: true });
|
|
968
|
+
cleanupParentListener = () => parentSignal.removeEventListener("abort", onAbort);
|
|
969
|
+
}
|
|
970
|
+
handlerEvent = event.build(childController.signal);
|
|
971
|
+
}
|
|
972
|
+
try {
|
|
973
|
+
if (this.config.type === HandlerType.ERROR) {
|
|
974
|
+
if (event.error) {
|
|
975
|
+
const { fn } = this.config;
|
|
976
|
+
const { error } = event;
|
|
977
|
+
result = await this.executeWithTimeout(() => fn(error, handlerEvent), handlerEvent.routerOptions, childController);
|
|
978
|
+
}
|
|
979
|
+
} else {
|
|
980
|
+
const { fn } = this.config;
|
|
981
|
+
result = await this.executeWithTimeout(() => fn(handlerEvent), handlerEvent.routerOptions, childController);
|
|
982
|
+
}
|
|
983
|
+
} finally {
|
|
984
|
+
if (cleanupParentListener) cleanupParentListener();
|
|
985
|
+
}
|
|
986
|
+
response = await toResponse(result, handlerEvent);
|
|
891
987
|
if (response) event.dispatched = true;
|
|
892
988
|
} catch (e) {
|
|
893
989
|
event.error = isError(e) ? e : createError(e);
|
|
@@ -919,6 +1015,33 @@ var Handler = class {
|
|
|
919
1015
|
this.config.method = method;
|
|
920
1016
|
this._method = method;
|
|
921
1017
|
}
|
|
1018
|
+
async executeWithTimeout(fn, routerOptions, controller) {
|
|
1019
|
+
const effectiveTimeout = this.resolveTimeout(routerOptions);
|
|
1020
|
+
if (!effectiveTimeout) return fn();
|
|
1021
|
+
let timerId;
|
|
1022
|
+
try {
|
|
1023
|
+
return await Promise.race([fn(), new Promise((_, reject) => {
|
|
1024
|
+
timerId = setTimeout(() => {
|
|
1025
|
+
if (controller) controller.abort();
|
|
1026
|
+
reject(createError({
|
|
1027
|
+
status: 408,
|
|
1028
|
+
message: "Request Timeout"
|
|
1029
|
+
}));
|
|
1030
|
+
}, effectiveTimeout);
|
|
1031
|
+
})]);
|
|
1032
|
+
} finally {
|
|
1033
|
+
clearTimeout(timerId);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
resolveTimeout(routerOptions) {
|
|
1037
|
+
const routerDefault = routerOptions.handlerTimeout;
|
|
1038
|
+
const handlerOverride = this.config.timeout;
|
|
1039
|
+
if (!routerDefault && !handlerOverride) return;
|
|
1040
|
+
if (!routerDefault) return handlerOverride;
|
|
1041
|
+
if (!handlerOverride) return routerDefault;
|
|
1042
|
+
if (routerOptions.handlerTimeoutOverridable) return handlerOverride;
|
|
1043
|
+
return Math.min(routerDefault, handlerOverride);
|
|
1044
|
+
}
|
|
922
1045
|
mountHooks() {
|
|
923
1046
|
if (this.config.onBefore) this.hookManager.addListener(HookName.CHILD_DISPATCH_BEFORE, this.config.onBefore);
|
|
924
1047
|
if (this.config.onAfter) this.hookManager.addListener(HookName.CHILD_DISPATCH_AFTER, this.config.onAfter);
|
|
@@ -1028,7 +1151,7 @@ function createNodeBridge(handler, isMiddleware) {
|
|
|
1028
1151
|
if (!node?.req || !node?.res) throw new RoutupError("fromNodeHandler/fromNodeMiddleware requires a Node.js runtime.");
|
|
1029
1152
|
const req = node.req;
|
|
1030
1153
|
const res = node.res;
|
|
1031
|
-
if ((isMiddleware ? await callMiddleware(handler, req, res) : await callHandler(handler, req, res)) === kHandled)
|
|
1154
|
+
if ((isMiddleware ? await callMiddleware(handler, req, res) : await callHandler(handler, req, res)) === kHandled) return null;
|
|
1032
1155
|
}) });
|
|
1033
1156
|
}
|
|
1034
1157
|
/**
|
|
@@ -1084,47 +1207,6 @@ function isHandler(input) {
|
|
|
1084
1207
|
return isInstance(input, HandlerSymbol);
|
|
1085
1208
|
}
|
|
1086
1209
|
//#endregion
|
|
1087
|
-
//#region src/request/helpers/body.ts
|
|
1088
|
-
const BODY_KEY = /* @__PURE__ */ Symbol.for("routup:body");
|
|
1089
|
-
/**
|
|
1090
|
-
* Read and parse the request body.
|
|
1091
|
-
*
|
|
1092
|
-
* - `application/x-www-form-urlencoded` → plain object (duplicate keys become arrays)
|
|
1093
|
-
* - `application/json` or other → attempts JSON parse, returns undefined on failure
|
|
1094
|
-
*
|
|
1095
|
-
* The result is cached on the event store — calling `readBody()` multiple
|
|
1096
|
-
* times returns the same parsed result.
|
|
1097
|
-
*
|
|
1098
|
-
* For binary or streaming access, use `event.request.arrayBuffer()`,
|
|
1099
|
-
* `event.request.blob()`, or `event.request.body` directly.
|
|
1100
|
-
*
|
|
1101
|
-
* @experimental
|
|
1102
|
-
*/
|
|
1103
|
-
async function readBody(event) {
|
|
1104
|
-
if (BODY_KEY in event.store) return event.store[BODY_KEY];
|
|
1105
|
-
const text = await event.request.text();
|
|
1106
|
-
let result;
|
|
1107
|
-
if ((event.headers.get("content-type") || "").includes("application/x-www-form-urlencoded")) result = parseURLEncodedBody(text);
|
|
1108
|
-
else try {
|
|
1109
|
-
result = JSON.parse(text);
|
|
1110
|
-
} catch {
|
|
1111
|
-
result = void 0;
|
|
1112
|
-
}
|
|
1113
|
-
event.store[BODY_KEY] = result;
|
|
1114
|
-
return result;
|
|
1115
|
-
}
|
|
1116
|
-
function parseURLEncodedBody(body) {
|
|
1117
|
-
const form = new URLSearchParams(body);
|
|
1118
|
-
const parsed = Object.create(null);
|
|
1119
|
-
for (const [key, value] of form.entries()) {
|
|
1120
|
-
const existing = parsed[key];
|
|
1121
|
-
if (existing !== void 0) if (Array.isArray(existing)) existing.push(value);
|
|
1122
|
-
else parsed[key] = [existing, value];
|
|
1123
|
-
else parsed[key] = value;
|
|
1124
|
-
}
|
|
1125
|
-
return parsed;
|
|
1126
|
-
}
|
|
1127
|
-
//#endregion
|
|
1128
1210
|
//#region src/request/helpers/cache.ts
|
|
1129
1211
|
function isRequestCacheable(event, modifiedTime) {
|
|
1130
1212
|
const modifiedSince = event.headers.get(HeaderName.IF_MODIFIED_SINCE);
|
|
@@ -1179,7 +1261,7 @@ function matchRequestContentType(event, contentType) {
|
|
|
1179
1261
|
function getRequestHostName(event, options = {}) {
|
|
1180
1262
|
let trustProxy;
|
|
1181
1263
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1182
|
-
else trustProxy =
|
|
1264
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1183
1265
|
let hostname = event.headers.get(HeaderName.X_FORWARDED_HOST);
|
|
1184
1266
|
if (!hostname || !event.request.ip || !trustProxy(event.request.ip, 0)) hostname = event.headers.get(HeaderName.HOST);
|
|
1185
1267
|
else if (hostname && hostname.includes(",")) hostname = hostname.substring(0, hostname.indexOf(",")).trimEnd();
|
|
@@ -1202,7 +1284,7 @@ function getRequestHostName(event, options = {}) {
|
|
|
1202
1284
|
function getRequestIP(event, options = {}) {
|
|
1203
1285
|
let trustProxy;
|
|
1204
1286
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1205
|
-
else trustProxy =
|
|
1287
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1206
1288
|
const socketAddr = event.request.ip;
|
|
1207
1289
|
if (!socketAddr) return;
|
|
1208
1290
|
const forwarded = event.headers.get(HeaderName.X_FORWARDED_FOR);
|
|
@@ -1222,7 +1304,7 @@ function getRequestIP(event, options = {}) {
|
|
|
1222
1304
|
function getRequestProtocol(event, options = {}) {
|
|
1223
1305
|
let trustProxy;
|
|
1224
1306
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1225
|
-
else trustProxy =
|
|
1307
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1226
1308
|
let protocol;
|
|
1227
1309
|
try {
|
|
1228
1310
|
if (new URL(event.request.url).protocol === "https:") protocol = "https";
|
|
@@ -1239,6 +1321,59 @@ function getRequestProtocol(event, options = {}) {
|
|
|
1239
1321
|
return protocol;
|
|
1240
1322
|
}
|
|
1241
1323
|
//#endregion
|
|
1324
|
+
//#region src/plugin/error/constants.ts
|
|
1325
|
+
const PluginErrorCode = {
|
|
1326
|
+
PLUGIN: "PLUGIN",
|
|
1327
|
+
NOT_INSTALLED: "PLUGIN_NOT_INSTALLED",
|
|
1328
|
+
INSTALL: "PLUGIN_INSTALL"
|
|
1329
|
+
};
|
|
1330
|
+
//#endregion
|
|
1331
|
+
//#region src/plugin/error/is.ts
|
|
1332
|
+
const PLUGIN_ERROR_CODES = new Set(Object.values(PluginErrorCode));
|
|
1333
|
+
function isPluginError(input) {
|
|
1334
|
+
if (!isError(input)) return false;
|
|
1335
|
+
return PLUGIN_ERROR_CODES.has(input.code);
|
|
1336
|
+
}
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/plugin/error/module.ts
|
|
1339
|
+
var PluginError = class extends RoutupError {
|
|
1340
|
+
constructor(input = {}) {
|
|
1341
|
+
const options = typeof input === "string" ? { message: input } : { ...input };
|
|
1342
|
+
if (!("code" in options) || !options.code) options.code = PluginErrorCode.PLUGIN;
|
|
1343
|
+
super(options);
|
|
1344
|
+
this.name = "PluginError";
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/plugin/error/sub/install.ts
|
|
1349
|
+
var PluginInstallError = class extends PluginError {
|
|
1350
|
+
pluginName;
|
|
1351
|
+
constructor(pluginName, cause) {
|
|
1352
|
+
super({
|
|
1353
|
+
message: `Failed to install plugin "${pluginName}".`,
|
|
1354
|
+
code: PluginErrorCode.INSTALL,
|
|
1355
|
+
cause
|
|
1356
|
+
});
|
|
1357
|
+
this.name = "PluginInstallError";
|
|
1358
|
+
this.pluginName = pluginName;
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/plugin/error/sub/not-installed.ts
|
|
1363
|
+
var PluginNotInstalledError = class extends PluginError {
|
|
1364
|
+
pluginName;
|
|
1365
|
+
helperName;
|
|
1366
|
+
constructor(pluginName, helperName) {
|
|
1367
|
+
super({
|
|
1368
|
+
message: `${helperName}() requires the "${pluginName}" plugin. Register it with: router.use(${pluginName}())`,
|
|
1369
|
+
code: PluginErrorCode.NOT_INSTALLED
|
|
1370
|
+
});
|
|
1371
|
+
this.name = "PluginNotInstalledError";
|
|
1372
|
+
this.pluginName = pluginName;
|
|
1373
|
+
this.helperName = helperName;
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
//#endregion
|
|
1242
1377
|
//#region src/plugin/is.ts
|
|
1243
1378
|
function isPlugin(input) {
|
|
1244
1379
|
if (!isObject(input)) return false;
|
|
@@ -1246,30 +1381,31 @@ function isPlugin(input) {
|
|
|
1246
1381
|
return typeof input.install === "function" && input.install.length === 1;
|
|
1247
1382
|
}
|
|
1248
1383
|
//#endregion
|
|
1249
|
-
//#region src/router
|
|
1384
|
+
//#region src/router/options.ts
|
|
1250
1385
|
function normalizeRouterOptions(input) {
|
|
1251
1386
|
if (typeof input.etag !== "undefined") input.etag = buildEtagFn(input.etag);
|
|
1252
1387
|
if (typeof input.trustProxy !== "undefined") input.trustProxy = buildTrustProxyFn(input.trustProxy);
|
|
1388
|
+
if (typeof input.timeout !== "undefined") {
|
|
1389
|
+
if (typeof input.timeout !== "number" || !Number.isFinite(input.timeout) || input.timeout <= 0) delete input.timeout;
|
|
1390
|
+
}
|
|
1391
|
+
if (typeof input.handlerTimeout !== "undefined") {
|
|
1392
|
+
if (typeof input.handlerTimeout !== "number" || !Number.isFinite(input.handlerTimeout) || input.handlerTimeout <= 0) delete input.handlerTimeout;
|
|
1393
|
+
}
|
|
1253
1394
|
return input;
|
|
1254
1395
|
}
|
|
1255
1396
|
//#endregion
|
|
1256
1397
|
//#region src/router/constants.ts
|
|
1257
|
-
const RouterSymbol =
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
}({});
|
|
1398
|
+
const RouterSymbol = Symbol.for("Router");
|
|
1399
|
+
const RouterPipelineStep = {
|
|
1400
|
+
START: 0,
|
|
1401
|
+
LOOKUP: 1,
|
|
1402
|
+
CHILD_BEFORE: 2,
|
|
1403
|
+
CHILD_DISPATCH: 3,
|
|
1404
|
+
CHILD_AFTER: 4,
|
|
1405
|
+
FINISH: 5
|
|
1406
|
+
};
|
|
1267
1407
|
//#endregion
|
|
1268
1408
|
//#region src/router/utils.ts
|
|
1269
|
-
let nextId = 0;
|
|
1270
|
-
function generateRouterID() {
|
|
1271
|
-
return ++nextId;
|
|
1272
|
-
}
|
|
1273
1409
|
function isRouterInstance(input) {
|
|
1274
1410
|
return isInstance(input, RouterSymbol);
|
|
1275
1411
|
}
|
|
@@ -1288,10 +1424,6 @@ function acceptsJson(request) {
|
|
|
1288
1424
|
var Router = class Router {
|
|
1289
1425
|
"@instanceof" = RouterSymbol;
|
|
1290
1426
|
/**
|
|
1291
|
-
* An identifier for the router instance.
|
|
1292
|
-
*/
|
|
1293
|
-
id;
|
|
1294
|
-
/**
|
|
1295
1427
|
* A label for the router instance.
|
|
1296
1428
|
*/
|
|
1297
1429
|
name;
|
|
@@ -1313,12 +1445,15 @@ var Router = class Router {
|
|
|
1313
1445
|
* @protected
|
|
1314
1446
|
*/
|
|
1315
1447
|
hookManager;
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1448
|
+
/**
|
|
1449
|
+
* Normalized options for this router instance.
|
|
1450
|
+
*/
|
|
1451
|
+
_options;
|
|
1452
|
+
constructor(input = {}) {
|
|
1453
|
+
this.name = input.name;
|
|
1319
1454
|
this.hookManager = new HookManager();
|
|
1320
|
-
this.
|
|
1321
|
-
|
|
1455
|
+
this._options = normalizeRouterOptions(input);
|
|
1456
|
+
this.setPath(input.path);
|
|
1322
1457
|
}
|
|
1323
1458
|
matchPath(path) {
|
|
1324
1459
|
if (this.pathMatcher) return this.pathMatcher.test(path);
|
|
@@ -1332,23 +1467,37 @@ var Router = class Router {
|
|
|
1332
1467
|
this.pathMatcher = new PathMatcher(withLeadingSlash(withoutTrailingSlash(`${value}`)), { end: false });
|
|
1333
1468
|
}
|
|
1334
1469
|
/**
|
|
1335
|
-
* Public entry point — creates a
|
|
1470
|
+
* Public entry point — creates a DispatcherEvent from the request,
|
|
1336
1471
|
* runs the pipeline, and returns a Response (with 404/500 fallbacks).
|
|
1337
1472
|
*/
|
|
1338
1473
|
async fetch(request) {
|
|
1339
|
-
const event = new
|
|
1474
|
+
const event = new DispatcherEvent(request);
|
|
1340
1475
|
let response;
|
|
1341
1476
|
try {
|
|
1342
|
-
|
|
1477
|
+
const timeoutMs = this._options.timeout;
|
|
1478
|
+
if (timeoutMs) {
|
|
1479
|
+
const controller = new AbortController();
|
|
1480
|
+
event.signal = controller.signal;
|
|
1481
|
+
let timerId;
|
|
1482
|
+
try {
|
|
1483
|
+
response = await Promise.race([this.dispatch(event), new Promise((_, reject) => {
|
|
1484
|
+
timerId = setTimeout(() => {
|
|
1485
|
+
controller.abort();
|
|
1486
|
+
reject(createError({
|
|
1487
|
+
status: 408,
|
|
1488
|
+
message: "Request Timeout"
|
|
1489
|
+
}));
|
|
1490
|
+
}, timeoutMs);
|
|
1491
|
+
})]);
|
|
1492
|
+
} finally {
|
|
1493
|
+
clearTimeout(timerId);
|
|
1494
|
+
}
|
|
1495
|
+
} else response = await this.dispatch(event);
|
|
1343
1496
|
} catch (e) {
|
|
1344
1497
|
event.error = createError(e);
|
|
1345
1498
|
}
|
|
1346
1499
|
if (response) return response;
|
|
1347
|
-
if (event.error)
|
|
1348
|
-
const status = event.error.statusCode || 500;
|
|
1349
|
-
const message = event.error.statusMessage || "Internal Server Error";
|
|
1350
|
-
return this.buildFallbackResponse(request, event, status, message);
|
|
1351
|
-
}
|
|
1500
|
+
if (event.error) return this.buildFallbackResponse(request, event, event.error.status || 500, event.error.message);
|
|
1352
1501
|
return this.buildFallbackResponse(request, event, 404, "Not Found");
|
|
1353
1502
|
}
|
|
1354
1503
|
buildFallbackResponse(request, event, status, message) {
|
|
@@ -1360,33 +1509,42 @@ var Router = class Router {
|
|
|
1360
1509
|
message
|
|
1361
1510
|
}), {
|
|
1362
1511
|
status,
|
|
1363
|
-
statusText: message,
|
|
1364
1512
|
headers
|
|
1365
1513
|
});
|
|
1366
1514
|
}
|
|
1367
1515
|
headers.set("content-type", "text/plain; charset=utf-8");
|
|
1368
1516
|
return new Response(message, {
|
|
1369
1517
|
status,
|
|
1370
|
-
statusText: message,
|
|
1371
1518
|
headers
|
|
1372
1519
|
});
|
|
1373
1520
|
}
|
|
1374
1521
|
async executePipelineStep(context) {
|
|
1375
|
-
switch (context.step) {
|
|
1376
|
-
case RouterPipelineStep.START:
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
case RouterPipelineStep.
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1522
|
+
while (context.step !== RouterPipelineStep.FINISH) switch (context.step) {
|
|
1523
|
+
case RouterPipelineStep.START:
|
|
1524
|
+
await this.executePipelineStepStart(context);
|
|
1525
|
+
break;
|
|
1526
|
+
case RouterPipelineStep.LOOKUP:
|
|
1527
|
+
await this.executePipelineStepLookup(context);
|
|
1528
|
+
break;
|
|
1529
|
+
case RouterPipelineStep.CHILD_BEFORE:
|
|
1530
|
+
await this.executePipelineStepChildBefore(context);
|
|
1531
|
+
break;
|
|
1532
|
+
case RouterPipelineStep.CHILD_DISPATCH:
|
|
1533
|
+
await this.executePipelineStepChildDispatch(context);
|
|
1534
|
+
break;
|
|
1535
|
+
case RouterPipelineStep.CHILD_AFTER:
|
|
1536
|
+
await this.executePipelineStepChildAfter(context);
|
|
1537
|
+
break;
|
|
1538
|
+
default:
|
|
1539
|
+
context.step = RouterPipelineStep.FINISH;
|
|
1540
|
+
break;
|
|
1383
1541
|
}
|
|
1542
|
+
await this.executePipelineStepFinish(context);
|
|
1384
1543
|
}
|
|
1385
1544
|
async executePipelineStepStart(context) {
|
|
1386
1545
|
await this.hookManager.trigger(HookName.REQUEST, context.event);
|
|
1387
1546
|
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1388
1547
|
else context.step = RouterPipelineStep.LOOKUP;
|
|
1389
|
-
return this.executePipelineStep(context);
|
|
1390
1548
|
}
|
|
1391
1549
|
async executePipelineStepLookup(context) {
|
|
1392
1550
|
while (!context.event.dispatched && context.stackIndex < this.stack.length) {
|
|
@@ -1397,12 +1555,12 @@ var Router = class Router {
|
|
|
1397
1555
|
continue;
|
|
1398
1556
|
}
|
|
1399
1557
|
if (item.matchPath(context.event.path)) {
|
|
1400
|
-
if (item.method) context.event.methodsAllowed.
|
|
1558
|
+
if (item.method) context.event.methodsAllowed.add(item.method);
|
|
1401
1559
|
if (item.matchMethod(context.event.method)) {
|
|
1402
1560
|
await this.hookManager.trigger(HookName.CHILD_MATCH, context.event);
|
|
1403
1561
|
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1404
1562
|
else context.step = RouterPipelineStep.CHILD_BEFORE;
|
|
1405
|
-
return
|
|
1563
|
+
return;
|
|
1406
1564
|
}
|
|
1407
1565
|
}
|
|
1408
1566
|
context.stackIndex++;
|
|
@@ -1412,51 +1570,41 @@ var Router = class Router {
|
|
|
1412
1570
|
await this.hookManager.trigger(HookName.CHILD_MATCH, context.event);
|
|
1413
1571
|
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1414
1572
|
else context.step = RouterPipelineStep.CHILD_BEFORE;
|
|
1415
|
-
return
|
|
1573
|
+
return;
|
|
1416
1574
|
}
|
|
1417
1575
|
context.stackIndex++;
|
|
1418
1576
|
}
|
|
1419
1577
|
context.step = RouterPipelineStep.FINISH;
|
|
1420
|
-
return this.executePipelineStep(context);
|
|
1421
1578
|
}
|
|
1422
1579
|
async executePipelineStepChildBefore(context) {
|
|
1423
1580
|
await this.hookManager.trigger(HookName.CHILD_DISPATCH_BEFORE, context.event);
|
|
1424
1581
|
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1425
1582
|
else context.step = RouterPipelineStep.CHILD_DISPATCH;
|
|
1426
|
-
return this.executePipelineStep(context);
|
|
1427
1583
|
}
|
|
1428
1584
|
async executePipelineStepChildAfter(context) {
|
|
1429
1585
|
await this.hookManager.trigger(HookName.CHILD_DISPATCH_AFTER, context.event);
|
|
1430
1586
|
if (context.event.dispatched) context.step = RouterPipelineStep.FINISH;
|
|
1431
1587
|
else context.step = RouterPipelineStep.LOOKUP;
|
|
1432
|
-
return this.executePipelineStep(context);
|
|
1433
1588
|
}
|
|
1434
1589
|
async executePipelineStepChildDispatch(context) {
|
|
1435
1590
|
if (context.event.dispatched || typeof this.stack[context.stackIndex] === "undefined") {
|
|
1436
1591
|
context.step = RouterPipelineStep.FINISH;
|
|
1437
|
-
return
|
|
1592
|
+
return;
|
|
1438
1593
|
}
|
|
1439
1594
|
const item = this.stack[context.stackIndex];
|
|
1440
1595
|
const { event } = context;
|
|
1441
|
-
const savedNext = event._next;
|
|
1442
|
-
const savedNextCalled = event._nextCalled;
|
|
1443
1596
|
try {
|
|
1444
|
-
event.
|
|
1445
|
-
|
|
1597
|
+
event.setNext(async (error) => {
|
|
1598
|
+
if (error) event.error = createError(error);
|
|
1446
1599
|
const nextContext = {
|
|
1447
1600
|
step: RouterPipelineStep.LOOKUP,
|
|
1448
1601
|
event,
|
|
1449
1602
|
stackIndex: context.stackIndex + 1,
|
|
1450
1603
|
response: void 0
|
|
1451
1604
|
};
|
|
1452
|
-
|
|
1453
|
-
try {
|
|
1454
|
-
await this.executePipelineStep(nextContext);
|
|
1455
|
-
} finally {
|
|
1456
|
-
event.routerPath.pop();
|
|
1457
|
-
}
|
|
1605
|
+
await this.executePipelineStep(nextContext);
|
|
1458
1606
|
return nextContext.response;
|
|
1459
|
-
};
|
|
1607
|
+
});
|
|
1460
1608
|
const response = await item.dispatch(event);
|
|
1461
1609
|
if (response) {
|
|
1462
1610
|
context.response = response;
|
|
@@ -1465,25 +1613,19 @@ var Router = class Router {
|
|
|
1465
1613
|
} catch (e) {
|
|
1466
1614
|
event.error = createError(e);
|
|
1467
1615
|
await this.hookManager.trigger(HookName.ERROR, event);
|
|
1468
|
-
} finally {
|
|
1469
|
-
event._next = savedNext;
|
|
1470
|
-
event._nextCalled = savedNextCalled;
|
|
1471
1616
|
}
|
|
1472
1617
|
context.stackIndex++;
|
|
1473
1618
|
context.step = RouterPipelineStep.CHILD_AFTER;
|
|
1474
|
-
return this.executePipelineStep(context);
|
|
1475
1619
|
}
|
|
1476
1620
|
async executePipelineStepFinish(context) {
|
|
1477
1621
|
if (context.event.error || context.event.dispatched) return this.hookManager.trigger(HookName.RESPONSE, context.event);
|
|
1478
1622
|
if (!context.event.dispatched && context.event.routerPath.length === 1 && context.event.method && context.event.method === MethodName.OPTIONS) {
|
|
1479
|
-
if (context.event.methodsAllowed.
|
|
1480
|
-
|
|
1481
|
-
const options = context.event.methodsAllowed.map((key) => key.toUpperCase()).join(",");
|
|
1623
|
+
if (context.event.methodsAllowed.has(MethodName.GET)) context.event.methodsAllowed.add(MethodName.HEAD);
|
|
1624
|
+
const options = [...context.event.methodsAllowed].map((key) => key.toUpperCase()).join(",");
|
|
1482
1625
|
const optionsHeaders = new Headers(context.event.response.headers);
|
|
1483
1626
|
optionsHeaders.set(HeaderName.ALLOW, options);
|
|
1484
1627
|
context.response = new Response(options, {
|
|
1485
1628
|
status: context.event.response.status || 200,
|
|
1486
|
-
statusText: context.event.response.statusText,
|
|
1487
1629
|
headers: optionsHeaders
|
|
1488
1630
|
});
|
|
1489
1631
|
context.event.dispatched = true;
|
|
@@ -1508,7 +1650,10 @@ var Router = class Router {
|
|
|
1508
1650
|
event,
|
|
1509
1651
|
stackIndex: 0
|
|
1510
1652
|
};
|
|
1511
|
-
event.routerPath.push(
|
|
1653
|
+
event.routerPath.push({
|
|
1654
|
+
name: this.name,
|
|
1655
|
+
options: this._options
|
|
1656
|
+
});
|
|
1512
1657
|
try {
|
|
1513
1658
|
await this.executePipelineStep(context);
|
|
1514
1659
|
} finally {
|
|
@@ -1598,8 +1743,8 @@ var Router = class Router {
|
|
|
1598
1743
|
else this.use(router);
|
|
1599
1744
|
return this;
|
|
1600
1745
|
}
|
|
1601
|
-
on(name, fn) {
|
|
1602
|
-
return this.hookManager.addListener(name, fn);
|
|
1746
|
+
on(name, fn, priority) {
|
|
1747
|
+
return this.hookManager.addListener(name, fn, priority);
|
|
1603
1748
|
}
|
|
1604
1749
|
off(name, fn) {
|
|
1605
1750
|
if (typeof fn === "undefined") {
|
|
@@ -1611,6 +1756,6 @@ var Router = class Router {
|
|
|
1611
1756
|
}
|
|
1612
1757
|
};
|
|
1613
1758
|
//#endregion
|
|
1614
|
-
export {
|
|
1759
|
+
export { appendResponseHeaderDirective as $, isPath as A, getRequestAcceptableContentTypes as B, isWebHandler as C, defineErrorHandler as D, fromNodeMiddleware as E, RoutupEvent as F, sendAccepted as G, getRequestHeader as H, sendStream as I, isError as J, toResponse as K, sendRedirect as L, HandlerSymbol as M, HandlerType as N, defineCoreHandler as O, DispatcherEvent as P, appendResponseHeader as Q, sendFormat as R, fromWebHandler as S, fromNodeHandler as T, sendFile as U, useRequestNegotiator as V, sendCreated as W, setResponseHeaderAttachment as X, setResponseHeaderContentType as Y, setResponseContentTypeByFileName as Z, getRequestAcceptableCharset as _, PluginInstallError as a, HeaderName as at, isHandler as b, PluginErrorCode as c, getRequestHostName as d, createEventStream as et, matchRequestContentType as f, getRequestAcceptableEncodings as g, getRequestAcceptableEncoding as h, PluginNotInstalledError as i, setResponseCacheHeaders as it, PathMatcher as j, Handler as k, getRequestProtocol as l, getRequestAcceptableLanguages as m, normalizeRouterOptions as n, ErrorSymbol as nt, PluginError as o, MethodName as ot, getRequestAcceptableLanguage as p, createError as q, isPlugin as r, RoutupError as rt, isPluginError as s, Router as t, serializeEventStreamMessage as tt, getRequestIP as u, getRequestAcceptableCharsets as v, isWebHandlerProvider as w, isHandlerOptions as x, isRequestCacheable as y, getRequestAcceptableContentType as z };
|
|
1615
1760
|
|
|
1616
|
-
//# sourceMappingURL=src-
|
|
1761
|
+
//# sourceMappingURL=src-Ck8GklBr.mjs.map
|