routup 5.0.0-beta.2 → 5.0.0-beta.3
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-DCtnUoGL.d.mts} +211 -155
- 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-Tr9cHQaV.mjs} +574 -551
- package/dist/src-Tr9cHQaV.mjs.map +1 -0
- package/package.json +10 -15
- 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,12 +1,12 @@
|
|
|
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 { distinctArray,
|
|
5
|
+
import { distinctArray, 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
11
|
let MethodName = /* @__PURE__ */ function(MethodName) {
|
|
12
12
|
MethodName["GET"] = "GET";
|
|
@@ -53,6 +53,89 @@ let HeaderName = /* @__PURE__ */ function(HeaderName) {
|
|
|
53
53
|
return HeaderName;
|
|
54
54
|
}({});
|
|
55
55
|
//#endregion
|
|
56
|
+
//#region src/response/helpers/cache.ts
|
|
57
|
+
function setResponseCacheHeaders(event, options) {
|
|
58
|
+
options = options || {};
|
|
59
|
+
const cacheControls = ["public"].concat(options.cacheControls || []);
|
|
60
|
+
if (options.maxAge !== void 0) cacheControls.push(`max-age=${+options.maxAge}`, `s-maxage=${+options.maxAge}`);
|
|
61
|
+
if (options.modifiedTime) {
|
|
62
|
+
const modifiedTime = typeof options.modifiedTime === "string" ? new Date(options.modifiedTime) : options.modifiedTime;
|
|
63
|
+
event.response.headers.set("last-modified", modifiedTime.toUTCString());
|
|
64
|
+
}
|
|
65
|
+
event.response.headers.set("cache-control", cacheControls.join(", "));
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/error/module.ts
|
|
69
|
+
var RoutupError = class extends HTTPError {
|
|
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;
|
|
115
|
+
if (typeof message === "string") {
|
|
116
|
+
handle.write({ data: message });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const serialized = serializeEventStreamMessage(message);
|
|
120
|
+
if (options?.maxMessageSize !== void 0) {
|
|
121
|
+
if (encoder.encode(serialized).byteLength > options.maxMessageSize) return;
|
|
122
|
+
}
|
|
123
|
+
controller.enqueue(encoder.encode(serialized));
|
|
124
|
+
},
|
|
125
|
+
end() {
|
|
126
|
+
if (closed) return;
|
|
127
|
+
closed = true;
|
|
128
|
+
controller.close();
|
|
129
|
+
},
|
|
130
|
+
response: new Response(stream, {
|
|
131
|
+
status: event.response.status,
|
|
132
|
+
statusText: event.response.statusText,
|
|
133
|
+
headers
|
|
134
|
+
})
|
|
135
|
+
};
|
|
136
|
+
return handle;
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
56
139
|
//#region src/utils/header.ts
|
|
57
140
|
function sanitizeHeaderValue(value) {
|
|
58
141
|
return value.replace(/[\r\n]/g, "");
|
|
@@ -193,432 +276,77 @@ function cleanDoubleSlashes(input = "") {
|
|
|
193
276
|
return input.replace(/\/+/g, "/");
|
|
194
277
|
}
|
|
195
278
|
//#endregion
|
|
196
|
-
//#region src/
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
279
|
+
//#region src/response/helpers/header.ts
|
|
280
|
+
function appendResponseHeader(event, name, value) {
|
|
281
|
+
const { headers } = event.response;
|
|
282
|
+
if (Array.isArray(value)) for (const v of value) headers.append(name, sanitizeHeaderValue(v));
|
|
283
|
+
else headers.append(name, sanitizeHeaderValue(value));
|
|
284
|
+
}
|
|
285
|
+
function appendResponseHeaderDirective(event, name, value) {
|
|
286
|
+
const { headers } = event.response;
|
|
287
|
+
const existing = headers.get(name);
|
|
288
|
+
if (!existing) {
|
|
289
|
+
if (Array.isArray(value)) headers.set(name, sanitizeHeaderValue(value.join("; ")));
|
|
290
|
+
else headers.set(name, sanitizeHeaderValue(value));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const directives = existing.split("; ");
|
|
294
|
+
if (Array.isArray(value)) directives.push(...value);
|
|
295
|
+
else directives.push(value);
|
|
296
|
+
const unique = [...new Set(directives)];
|
|
297
|
+
headers.set(name, sanitizeHeaderValue(unique.join("; ")));
|
|
200
298
|
}
|
|
201
299
|
//#endregion
|
|
202
|
-
//#region src/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
300
|
+
//#region src/response/helpers/utils.ts
|
|
301
|
+
function setResponseContentTypeByFileName(event, fileName) {
|
|
302
|
+
const ext = extname(fileName);
|
|
303
|
+
if (ext) {
|
|
304
|
+
let type = getMimeType(ext.substring(1));
|
|
305
|
+
if (type) {
|
|
306
|
+
const charset = getCharsetForMimeType(type);
|
|
307
|
+
if (charset) type += `; charset=${charset}`;
|
|
308
|
+
event.response.headers.set(HeaderName.CONTENT_TYPE, type);
|
|
309
|
+
}
|
|
207
310
|
}
|
|
208
|
-
}
|
|
311
|
+
}
|
|
209
312
|
//#endregion
|
|
210
|
-
//#region src/
|
|
211
|
-
function
|
|
212
|
-
return
|
|
313
|
+
//#region src/response/helpers/header-attachment.ts
|
|
314
|
+
function sanitizeFilename(filename) {
|
|
315
|
+
return filename.replace(/[\r\n]/g, "");
|
|
213
316
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* - an existing RoutupError (returned as-is)
|
|
217
|
-
* - an HTTPError (wrapped into a RoutupError preserving status)
|
|
218
|
-
* - an Error (wrapped preserving message and cause)
|
|
219
|
-
* - an options object (statusCode, statusMessage, etc.)
|
|
220
|
-
* - a message string
|
|
221
|
-
*
|
|
222
|
-
* @param input
|
|
223
|
-
*/
|
|
224
|
-
function createError(input) {
|
|
225
|
-
if (isError(input)) return input;
|
|
226
|
-
if (typeof input === "string") return new RoutupError(input);
|
|
227
|
-
if (isHTTPError(input)) return new RoutupError({
|
|
228
|
-
message: input.message,
|
|
229
|
-
code: input.code,
|
|
230
|
-
statusCode: input.statusCode,
|
|
231
|
-
statusMessage: input.statusMessage,
|
|
232
|
-
redirectURL: input.redirectURL,
|
|
233
|
-
cause: input
|
|
234
|
-
});
|
|
235
|
-
if (isNativeError(input)) return new RoutupError({
|
|
236
|
-
message: input.message,
|
|
237
|
-
cause: input
|
|
238
|
-
});
|
|
239
|
-
if (!isObject(input)) return new RoutupError();
|
|
240
|
-
const options = { ...input };
|
|
241
|
-
if (options.cause === void 0) options.cause = input;
|
|
242
|
-
return new RoutupError(options);
|
|
317
|
+
function toAsciiFilename(filename) {
|
|
318
|
+
return filename.replace(/[^\x20-\x7E]/g, "").replace(/"/g, "\\\"");
|
|
243
319
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* Collected allowed methods (for OPTIONS).
|
|
256
|
-
*/
|
|
257
|
-
methodsAllowed;
|
|
258
|
-
store;
|
|
259
|
-
_dispatched;
|
|
260
|
-
_response;
|
|
261
|
-
/**
|
|
262
|
-
* Cached parsed URL (avoids double-parsing).
|
|
263
|
-
*/
|
|
264
|
-
_url;
|
|
265
|
-
_searchParams;
|
|
266
|
-
/**
|
|
267
|
-
* Continuation function for middleware onion model.
|
|
268
|
-
*/
|
|
269
|
-
_next;
|
|
270
|
-
/**
|
|
271
|
-
* Whether _next has already been called (guard against double-invocation).
|
|
272
|
-
*/
|
|
273
|
-
_nextCalled;
|
|
274
|
-
/**
|
|
275
|
-
* The cached result of the next handler.
|
|
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;
|
|
293
|
-
}
|
|
294
|
-
get searchParams() {
|
|
295
|
-
if (!this._searchParams) this._searchParams = new URLSearchParams(this._url.search);
|
|
296
|
-
return this._searchParams;
|
|
297
|
-
}
|
|
298
|
-
get response() {
|
|
299
|
-
if (!this._response) this._response = {
|
|
300
|
-
status: 200,
|
|
301
|
-
headers: new Headers()
|
|
302
|
-
};
|
|
303
|
-
return this._response;
|
|
304
|
-
}
|
|
305
|
-
get dispatched() {
|
|
306
|
-
return this._dispatched;
|
|
307
|
-
}
|
|
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;
|
|
320
|
+
function encodeRfc5987(filename) {
|
|
321
|
+
return encodeURIComponent(filename).replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/\*/g, "%2A");
|
|
322
|
+
}
|
|
323
|
+
function setResponseHeaderAttachment(event, filename) {
|
|
324
|
+
if (typeof filename === "string") setResponseContentTypeByFileName(event, filename);
|
|
325
|
+
let disposition = "attachment";
|
|
326
|
+
if (filename) {
|
|
327
|
+
const sanitized = sanitizeFilename(filename);
|
|
328
|
+
const ascii = toAsciiFilename(sanitized);
|
|
329
|
+
disposition += `; filename="${ascii}"`;
|
|
330
|
+
disposition += `; filename*=UTF-8''${encodeRfc5987(sanitized)}`;
|
|
316
331
|
}
|
|
317
|
-
|
|
332
|
+
event.response.headers.set(HeaderName.CONTENT_DISPOSITION, disposition);
|
|
333
|
+
}
|
|
318
334
|
//#endregion
|
|
319
|
-
//#region src/
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
}
|
|
335
|
+
//#region src/response/helpers/header-content-type.ts
|
|
336
|
+
function setResponseHeaderContentType(event, input, ifNotExists) {
|
|
337
|
+
if (ifNotExists) {
|
|
338
|
+
if (event.response.headers.get(HeaderName.CONTENT_TYPE)) return;
|
|
339
|
+
}
|
|
340
|
+
const contentType = getMimeType(input);
|
|
341
|
+
if (contentType) event.response.headers.set(HeaderName.CONTENT_TYPE, contentType);
|
|
342
|
+
}
|
|
615
343
|
//#endregion
|
|
616
344
|
//#region src/response/to-response.ts
|
|
617
345
|
function stripWeakPrefix(etag) {
|
|
618
346
|
return etag.startsWith("W/") ? etag.slice(2) : etag;
|
|
619
347
|
}
|
|
620
348
|
async function applyEtag(body, event, headers) {
|
|
621
|
-
const etagFn =
|
|
349
|
+
const etagFn = event.routerOptions.etag;
|
|
622
350
|
if (!etagFn) return void 0;
|
|
623
351
|
const etag = await etagFn(body);
|
|
624
352
|
if (!etag) return void 0;
|
|
@@ -684,7 +412,6 @@ async function toResponse(value, event) {
|
|
|
684
412
|
async function sendAccepted(event, data) {
|
|
685
413
|
event.response.status = 202;
|
|
686
414
|
event.response.statusText = "Accepted";
|
|
687
|
-
event.dispatched = true;
|
|
688
415
|
return await toResponse(data ?? "", event);
|
|
689
416
|
}
|
|
690
417
|
//#endregion
|
|
@@ -692,7 +419,6 @@ async function sendAccepted(event, data) {
|
|
|
692
419
|
async function sendCreated(event, data) {
|
|
693
420
|
event.response.status = 201;
|
|
694
421
|
event.response.statusText = "Created";
|
|
695
|
-
event.dispatched = true;
|
|
696
422
|
return await toResponse(data ?? "", event);
|
|
697
423
|
}
|
|
698
424
|
//#endregion
|
|
@@ -718,7 +444,6 @@ async function sendFile(event, options) {
|
|
|
718
444
|
contentOptions.start = Number.isFinite(parsedStart) && parsedStart >= 0 ? parsedStart : 0;
|
|
719
445
|
contentOptions.end = Number.isFinite(parsedEnd) && parsedEnd >= 0 ? Math.min(parsedEnd, stats.size - 1) : stats.size - 1;
|
|
720
446
|
if (contentOptions.start >= stats.size || contentOptions.start > contentOptions.end) {
|
|
721
|
-
event.dispatched = true;
|
|
722
447
|
const rangeHeaders = new Headers(headers);
|
|
723
448
|
rangeHeaders.set(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
|
|
724
449
|
return new Response(null, {
|
|
@@ -738,113 +463,422 @@ async function sendFile(event, options) {
|
|
|
738
463
|
headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
|
|
739
464
|
}
|
|
740
465
|
}
|
|
741
|
-
const content = await options.content(contentOptions);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
466
|
+
const content = await options.content(contentOptions);
|
|
467
|
+
return new Response(content, {
|
|
468
|
+
status: statusCode,
|
|
469
|
+
statusText: event.response.statusText,
|
|
470
|
+
headers
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/request/helpers/header.ts
|
|
475
|
+
function getRequestHeader(event, name) {
|
|
476
|
+
return event.headers.get(name);
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/request/helpers/negotiator.ts
|
|
480
|
+
const NEGOTIATOR_KEY = /* @__PURE__ */ Symbol.for("routup:negotiator");
|
|
481
|
+
function headersToPlainObject(headers) {
|
|
482
|
+
const result = {};
|
|
483
|
+
headers.forEach((value, key) => {
|
|
484
|
+
result[key] = value;
|
|
485
|
+
});
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
function useRequestNegotiator(event) {
|
|
489
|
+
let value = event.store[NEGOTIATOR_KEY];
|
|
490
|
+
if (value) return value;
|
|
491
|
+
value = new Negotiator({ headers: headersToPlainObject(event.headers) });
|
|
492
|
+
event.store[NEGOTIATOR_KEY] = value;
|
|
493
|
+
return value;
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/request/helpers/header-accept.ts
|
|
497
|
+
function getRequestAcceptableContentTypes(event) {
|
|
498
|
+
return useRequestNegotiator(event).mediaTypes();
|
|
499
|
+
}
|
|
500
|
+
function getRequestAcceptableContentType(event, input) {
|
|
501
|
+
input = input || [];
|
|
502
|
+
const items = Array.isArray(input) ? input : [input];
|
|
503
|
+
if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
|
|
504
|
+
if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
|
|
505
|
+
let polluted = false;
|
|
506
|
+
const mimeTypes = [];
|
|
507
|
+
for (const item of items) {
|
|
508
|
+
const mimeType = getMimeType(item);
|
|
509
|
+
if (mimeType) mimeTypes.push(mimeType);
|
|
510
|
+
else polluted = true;
|
|
511
|
+
}
|
|
512
|
+
const matches = useRequestNegotiator(event).mediaTypes(mimeTypes);
|
|
513
|
+
if (matches.length > 0) {
|
|
514
|
+
if (polluted) return items[0];
|
|
515
|
+
return items[mimeTypes.indexOf(matches[0])];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/response/helpers/send-format.ts
|
|
520
|
+
function sendFormat(event, input) {
|
|
521
|
+
const { default: formatDefault, ...formats } = input;
|
|
522
|
+
const contentTypes = Object.keys(formats);
|
|
523
|
+
if (contentTypes.length === 0) return formatDefault();
|
|
524
|
+
const contentType = getRequestAcceptableContentType(event, contentTypes);
|
|
525
|
+
if (contentType && formats[contentType]) return formats[contentType]();
|
|
526
|
+
return formatDefault();
|
|
527
|
+
}
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/response/helpers/send-redirect.ts
|
|
530
|
+
function escapeHtml(str) {
|
|
531
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
532
|
+
}
|
|
533
|
+
function isAllowedRedirectUrl(location) {
|
|
534
|
+
if (location.startsWith("//")) return false;
|
|
535
|
+
if (location.startsWith("/") || location.startsWith(".")) return true;
|
|
536
|
+
try {
|
|
537
|
+
const url = new URL(location);
|
|
538
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
539
|
+
} catch {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function sendRedirect(event, location, statusCode = 302) {
|
|
544
|
+
if (!isAllowedRedirectUrl(location)) throw new RoutupError({
|
|
545
|
+
statusCode: 400,
|
|
546
|
+
statusMessage: "Invalid redirect URL scheme."
|
|
547
|
+
});
|
|
548
|
+
const sanitizedLocation = sanitizeHeaderValue(location);
|
|
549
|
+
const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${escapeHtml(location)}"></head></html>`;
|
|
550
|
+
const headers = new Headers(event.response.headers);
|
|
551
|
+
headers.set("location", sanitizedLocation);
|
|
552
|
+
headers.set("content-type", "text/html; charset=utf-8");
|
|
553
|
+
headers.delete("content-length");
|
|
554
|
+
return new Response(html, {
|
|
555
|
+
status: statusCode,
|
|
556
|
+
statusText: event.response.statusText,
|
|
557
|
+
headers
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/response/helpers/send-stream.ts
|
|
562
|
+
function sendStream(event, stream) {
|
|
563
|
+
const { status, statusText, headers } = event.response;
|
|
564
|
+
return new Response(stream, {
|
|
565
|
+
status,
|
|
566
|
+
statusText,
|
|
567
|
+
headers
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/event/module.ts
|
|
572
|
+
var RoutupEvent = class {
|
|
573
|
+
request;
|
|
574
|
+
params;
|
|
575
|
+
path;
|
|
576
|
+
method;
|
|
577
|
+
mountPath;
|
|
578
|
+
headers;
|
|
579
|
+
searchParams;
|
|
580
|
+
response;
|
|
581
|
+
store;
|
|
582
|
+
_context;
|
|
583
|
+
_routerOptions;
|
|
584
|
+
constructor(context) {
|
|
585
|
+
this._context = context;
|
|
586
|
+
this.request = context.request;
|
|
587
|
+
this.params = context.params;
|
|
588
|
+
this.path = context.path;
|
|
589
|
+
this.method = context.method;
|
|
590
|
+
this.mountPath = context.mountPath;
|
|
591
|
+
this.headers = context.headers;
|
|
592
|
+
this.searchParams = context.searchParams;
|
|
593
|
+
this.response = context.response;
|
|
594
|
+
this.store = context.store;
|
|
595
|
+
}
|
|
596
|
+
get routerOptions() {
|
|
597
|
+
if (!this._routerOptions) this._routerOptions = this._context.routerOptions();
|
|
598
|
+
return this._routerOptions;
|
|
599
|
+
}
|
|
600
|
+
async next(error) {
|
|
601
|
+
return this._context.next(this, error);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/dispatcher/module.ts
|
|
606
|
+
var DispatcherEvent = class {
|
|
607
|
+
request;
|
|
608
|
+
params;
|
|
609
|
+
path;
|
|
610
|
+
method;
|
|
611
|
+
/**
|
|
612
|
+
* Collected allowed methods (for OPTIONS).
|
|
613
|
+
*/
|
|
614
|
+
methodsAllowed;
|
|
615
|
+
mountPath;
|
|
616
|
+
error;
|
|
617
|
+
routerPath;
|
|
618
|
+
_dispatched;
|
|
619
|
+
_response;
|
|
620
|
+
_store;
|
|
621
|
+
/**
|
|
622
|
+
* Cached parsed URL (avoids double-parsing).
|
|
623
|
+
*/
|
|
624
|
+
_url;
|
|
625
|
+
/**
|
|
626
|
+
* Continuation function for middleware onion model.
|
|
627
|
+
*/
|
|
628
|
+
_next;
|
|
629
|
+
/**
|
|
630
|
+
* Whether _next has already been called (guard against double-invocation).
|
|
631
|
+
*/
|
|
632
|
+
_nextCalled;
|
|
633
|
+
/**
|
|
634
|
+
* The cached result of the next handler.
|
|
635
|
+
*/
|
|
636
|
+
_nextResult;
|
|
637
|
+
constructor(request) {
|
|
638
|
+
this.request = request;
|
|
639
|
+
this._url = new FastURL(request.url);
|
|
640
|
+
this.method = request.method;
|
|
641
|
+
this.path = this._url.pathname;
|
|
642
|
+
this.mountPath = "/";
|
|
643
|
+
this.params = {};
|
|
644
|
+
this.routerPath = [];
|
|
645
|
+
this.methodsAllowed = [];
|
|
646
|
+
this._dispatched = false;
|
|
647
|
+
this._nextCalled = false;
|
|
648
|
+
}
|
|
649
|
+
get response() {
|
|
650
|
+
if (!this._response) this._response = {
|
|
651
|
+
status: 200,
|
|
652
|
+
headers: new Headers()
|
|
653
|
+
};
|
|
654
|
+
return this._response;
|
|
655
|
+
}
|
|
656
|
+
get dispatched() {
|
|
657
|
+
return this._dispatched;
|
|
658
|
+
}
|
|
659
|
+
set dispatched(value) {
|
|
660
|
+
this._dispatched = value;
|
|
661
|
+
}
|
|
662
|
+
async next(event, error) {
|
|
663
|
+
if (this._nextCalled) return this._nextResult;
|
|
664
|
+
this._nextCalled = true;
|
|
665
|
+
if (this._next) this._nextResult = this._next(event, error);
|
|
666
|
+
return this._nextResult;
|
|
667
|
+
}
|
|
668
|
+
setNext(fn) {
|
|
669
|
+
if (fn) this._next = async (event, error) => {
|
|
670
|
+
return toResponse(await fn(error), event);
|
|
671
|
+
};
|
|
672
|
+
else this._next = void 0;
|
|
673
|
+
this._nextCalled = false;
|
|
674
|
+
this._nextResult = void 0;
|
|
675
|
+
}
|
|
676
|
+
build() {
|
|
677
|
+
return new RoutupEvent({
|
|
678
|
+
request: this.request,
|
|
679
|
+
params: this.params,
|
|
680
|
+
path: this.path,
|
|
681
|
+
method: this.method,
|
|
682
|
+
mountPath: this.mountPath,
|
|
683
|
+
headers: this.request.headers,
|
|
684
|
+
searchParams: new URLSearchParams(this._url.search),
|
|
685
|
+
response: this.response,
|
|
686
|
+
store: this.store,
|
|
687
|
+
routerOptions: () => this.resolveOptions(),
|
|
688
|
+
next: (event, error) => this.next(event, error)
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
get store() {
|
|
692
|
+
if (!this._store) this._store = Object.create(null);
|
|
693
|
+
return this._store;
|
|
694
|
+
}
|
|
695
|
+
resolveOptions() {
|
|
696
|
+
const resolved = {
|
|
697
|
+
trustProxy: () => false,
|
|
698
|
+
subdomainOffset: 2,
|
|
699
|
+
etag: buildEtagFn(),
|
|
700
|
+
proxyIpMax: 0
|
|
701
|
+
};
|
|
702
|
+
for (let i = 0; i < this.routerPath.length; i++) {
|
|
703
|
+
const node = this.routerPath[i];
|
|
704
|
+
const entries = Object.entries(node.options);
|
|
705
|
+
for (const entry of entries) {
|
|
706
|
+
const [key, value] = entry;
|
|
707
|
+
if (typeof value !== "undefined") resolved[key] = value;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return resolved;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
749
713
|
//#endregion
|
|
750
|
-
//#region src/
|
|
751
|
-
function
|
|
752
|
-
|
|
714
|
+
//#region src/error/is.ts
|
|
715
|
+
function isError(input) {
|
|
716
|
+
if (!isHTTPError(input)) return false;
|
|
717
|
+
return input.name === "RoutupError";
|
|
753
718
|
}
|
|
754
719
|
//#endregion
|
|
755
|
-
//#region src/
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const result = {};
|
|
759
|
-
headers.forEach((value, key) => {
|
|
760
|
-
result[key] = value;
|
|
761
|
-
});
|
|
762
|
-
return result;
|
|
720
|
+
//#region src/error/create.ts
|
|
721
|
+
function isNativeError(input) {
|
|
722
|
+
return isObject(input) && typeof input.message === "string" && typeof input.name === "string";
|
|
763
723
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
724
|
+
/**
|
|
725
|
+
* Create an internal error object by
|
|
726
|
+
* - an existing RoutupError (returned as-is)
|
|
727
|
+
* - an HTTPError (wrapped into a RoutupError preserving status)
|
|
728
|
+
* - an Error (wrapped preserving message and cause)
|
|
729
|
+
* - an options object (statusCode, statusMessage, etc.)
|
|
730
|
+
* - a message string
|
|
731
|
+
*
|
|
732
|
+
* @param input
|
|
733
|
+
*/
|
|
734
|
+
function createError(input) {
|
|
735
|
+
if (isError(input)) return input;
|
|
736
|
+
if (typeof input === "string") return new RoutupError(input);
|
|
737
|
+
if (isHTTPError(input)) return new RoutupError({
|
|
738
|
+
message: input.message,
|
|
739
|
+
code: input.code,
|
|
740
|
+
statusCode: input.statusCode,
|
|
741
|
+
statusMessage: input.statusMessage,
|
|
742
|
+
redirectURL: input.redirectURL,
|
|
743
|
+
cause: input
|
|
744
|
+
});
|
|
745
|
+
if (isNativeError(input)) return new RoutupError({
|
|
746
|
+
message: input.message,
|
|
747
|
+
cause: input
|
|
748
|
+
});
|
|
749
|
+
if (!isObject(input)) return new RoutupError();
|
|
750
|
+
const options = { ...input };
|
|
751
|
+
if (options.cause === void 0) options.cause = input;
|
|
752
|
+
return new RoutupError(options);
|
|
770
753
|
}
|
|
771
754
|
//#endregion
|
|
772
|
-
//#region src/
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
755
|
+
//#region src/handler/constants.ts
|
|
756
|
+
let HandlerType = /* @__PURE__ */ function(HandlerType) {
|
|
757
|
+
HandlerType["CORE"] = "core";
|
|
758
|
+
HandlerType["ERROR"] = "error";
|
|
759
|
+
return HandlerType;
|
|
760
|
+
}({});
|
|
761
|
+
const HandlerSymbol = /* @__PURE__ */ Symbol.for("Handler");
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/hook/constants.ts
|
|
764
|
+
let HookName = /* @__PURE__ */ function(HookName) {
|
|
765
|
+
HookName["REQUEST"] = "request";
|
|
766
|
+
HookName["RESPONSE"] = "response";
|
|
767
|
+
HookName["ERROR"] = "error";
|
|
768
|
+
HookName["CHILD_MATCH"] = "childMatch";
|
|
769
|
+
HookName["CHILD_DISPATCH_BEFORE"] = "childDispatchBefore";
|
|
770
|
+
HookName["CHILD_DISPATCH_AFTER"] = "childDispatchAfter";
|
|
771
|
+
return HookName;
|
|
772
|
+
}({});
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region src/hook/module.ts
|
|
775
|
+
var HookManager = class {
|
|
776
|
+
items;
|
|
777
|
+
constructor() {
|
|
778
|
+
this.items = {};
|
|
787
779
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
return
|
|
780
|
+
addListener(name, fn) {
|
|
781
|
+
this.items[name] = this.items[name] || [];
|
|
782
|
+
this.items[name].push(fn);
|
|
783
|
+
return () => {
|
|
784
|
+
this.removeListener(name, fn);
|
|
785
|
+
};
|
|
792
786
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
787
|
+
removeListener(name, fn) {
|
|
788
|
+
if (!this.items[name]) return;
|
|
789
|
+
if (typeof fn === "undefined") {
|
|
790
|
+
delete this.items[name];
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (typeof fn === "function") {
|
|
794
|
+
const index = this.items[name].indexOf(fn);
|
|
795
|
+
if (index !== -1) this.items[name].splice(index, 1);
|
|
796
|
+
}
|
|
797
|
+
if (this.items[name].length === 0) delete this.items[name];
|
|
798
|
+
}
|
|
799
|
+
async trigger(name, event) {
|
|
800
|
+
if (!this.items[name] || this.items[name].length === 0) return;
|
|
801
|
+
try {
|
|
802
|
+
for (let i = 0; i < this.items[name].length; i++) {
|
|
803
|
+
const listener = this.items[name][i];
|
|
804
|
+
await this.triggerListener(name, event, listener);
|
|
805
|
+
if (event.dispatched) {
|
|
806
|
+
if (event.error) event.error = void 0;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch (e) {
|
|
811
|
+
event.error = e;
|
|
812
|
+
if (!this.isErrorListenerHook(name)) {
|
|
813
|
+
await this.trigger(HookName.ERROR, event);
|
|
814
|
+
if (event.dispatched) {
|
|
815
|
+
if (event.error) event.error = void 0;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
triggerListener(name, event, listener) {
|
|
821
|
+
if (this.isErrorListenerHook(name)) {
|
|
822
|
+
if (event.error) return listener(event);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
return listener(event);
|
|
826
|
+
}
|
|
827
|
+
isErrorListenerHook(input) {
|
|
828
|
+
return input === HookName.ERROR;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
804
831
|
//#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;
|
|
832
|
+
//#region src/path/matcher.ts
|
|
833
|
+
function decodeParam(val) {
|
|
834
|
+
/* istanbul ignore next */
|
|
835
|
+
if (typeof val !== "string" || val.length === 0) return val;
|
|
812
836
|
try {
|
|
813
|
-
|
|
814
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
837
|
+
return decodeURIComponent(val);
|
|
815
838
|
} catch {
|
|
816
|
-
return
|
|
839
|
+
return val;
|
|
817
840
|
}
|
|
818
841
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
842
|
+
var PathMatcher = class {
|
|
843
|
+
path;
|
|
844
|
+
regexp;
|
|
845
|
+
regexpKeys = [];
|
|
846
|
+
regexpOptions;
|
|
847
|
+
constructor(path, options) {
|
|
848
|
+
this.path = path;
|
|
849
|
+
this.regexpOptions = options || {};
|
|
850
|
+
const regexp = pathToRegexp(path, options);
|
|
851
|
+
this.regexp = regexp.regexp;
|
|
852
|
+
this.regexpKeys = regexp.keys;
|
|
853
|
+
}
|
|
854
|
+
test(path) {
|
|
855
|
+
return this.regexp.test(path);
|
|
856
|
+
}
|
|
857
|
+
exec(path) {
|
|
858
|
+
if (this.path === "/" && this.regexpOptions.end === false) return {
|
|
859
|
+
path: "/",
|
|
860
|
+
params: Object.create(null)
|
|
861
|
+
};
|
|
862
|
+
const match = this.regexp.exec(path);
|
|
863
|
+
if (!match) return;
|
|
864
|
+
const params = Object.create(null);
|
|
865
|
+
for (let i = 1; i < match.length; i++) {
|
|
866
|
+
const key = this.regexpKeys[i - 1];
|
|
867
|
+
if (!key) continue;
|
|
868
|
+
const prop = key.name;
|
|
869
|
+
const val = decodeParam(match[i]);
|
|
870
|
+
if (typeof val !== "undefined") params[prop] = val;
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
path: match[0],
|
|
874
|
+
params
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
};
|
|
838
878
|
//#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
|
-
});
|
|
879
|
+
//#region src/path/utils.ts
|
|
880
|
+
function isPath(input) {
|
|
881
|
+
return typeof input === "string";
|
|
848
882
|
}
|
|
849
883
|
//#endregion
|
|
850
884
|
//#region src/handler/module.ts
|
|
@@ -884,10 +918,11 @@ var Handler = class {
|
|
|
884
918
|
let response;
|
|
885
919
|
try {
|
|
886
920
|
let result;
|
|
921
|
+
const handlerEvent = event.build();
|
|
887
922
|
if (this.config.type === HandlerType.ERROR) {
|
|
888
|
-
if (event.error) result = await this.config.fn(event.error,
|
|
889
|
-
} else result = await this.config.fn(
|
|
890
|
-
response = await toResponse(result,
|
|
923
|
+
if (event.error) result = await this.config.fn(event.error, handlerEvent);
|
|
924
|
+
} else result = await this.config.fn(handlerEvent);
|
|
925
|
+
response = await toResponse(result, handlerEvent);
|
|
891
926
|
if (response) event.dispatched = true;
|
|
892
927
|
} catch (e) {
|
|
893
928
|
event.error = isError(e) ? e : createError(e);
|
|
@@ -1028,7 +1063,7 @@ function createNodeBridge(handler, isMiddleware) {
|
|
|
1028
1063
|
if (!node?.req || !node?.res) throw new RoutupError("fromNodeHandler/fromNodeMiddleware requires a Node.js runtime.");
|
|
1029
1064
|
const req = node.req;
|
|
1030
1065
|
const res = node.res;
|
|
1031
|
-
if ((isMiddleware ? await callMiddleware(handler, req, res) : await callHandler(handler, req, res)) === kHandled)
|
|
1066
|
+
if ((isMiddleware ? await callMiddleware(handler, req, res) : await callHandler(handler, req, res)) === kHandled) return null;
|
|
1032
1067
|
}) });
|
|
1033
1068
|
}
|
|
1034
1069
|
/**
|
|
@@ -1179,7 +1214,7 @@ function matchRequestContentType(event, contentType) {
|
|
|
1179
1214
|
function getRequestHostName(event, options = {}) {
|
|
1180
1215
|
let trustProxy;
|
|
1181
1216
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1182
|
-
else trustProxy =
|
|
1217
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1183
1218
|
let hostname = event.headers.get(HeaderName.X_FORWARDED_HOST);
|
|
1184
1219
|
if (!hostname || !event.request.ip || !trustProxy(event.request.ip, 0)) hostname = event.headers.get(HeaderName.HOST);
|
|
1185
1220
|
else if (hostname && hostname.includes(",")) hostname = hostname.substring(0, hostname.indexOf(",")).trimEnd();
|
|
@@ -1202,7 +1237,7 @@ function getRequestHostName(event, options = {}) {
|
|
|
1202
1237
|
function getRequestIP(event, options = {}) {
|
|
1203
1238
|
let trustProxy;
|
|
1204
1239
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1205
|
-
else trustProxy =
|
|
1240
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1206
1241
|
const socketAddr = event.request.ip;
|
|
1207
1242
|
if (!socketAddr) return;
|
|
1208
1243
|
const forwarded = event.headers.get(HeaderName.X_FORWARDED_FOR);
|
|
@@ -1222,7 +1257,7 @@ function getRequestIP(event, options = {}) {
|
|
|
1222
1257
|
function getRequestProtocol(event, options = {}) {
|
|
1223
1258
|
let trustProxy;
|
|
1224
1259
|
if (typeof options.trustProxy !== "undefined") trustProxy = buildTrustProxyFn(options.trustProxy);
|
|
1225
|
-
else trustProxy =
|
|
1260
|
+
else trustProxy = event.routerOptions.trustProxy;
|
|
1226
1261
|
let protocol;
|
|
1227
1262
|
try {
|
|
1228
1263
|
if (new URL(event.request.url).protocol === "https:") protocol = "https";
|
|
@@ -1246,7 +1281,7 @@ function isPlugin(input) {
|
|
|
1246
1281
|
return typeof input.install === "function" && input.install.length === 1;
|
|
1247
1282
|
}
|
|
1248
1283
|
//#endregion
|
|
1249
|
-
//#region src/router
|
|
1284
|
+
//#region src/router/options.ts
|
|
1250
1285
|
function normalizeRouterOptions(input) {
|
|
1251
1286
|
if (typeof input.etag !== "undefined") input.etag = buildEtagFn(input.etag);
|
|
1252
1287
|
if (typeof input.trustProxy !== "undefined") input.trustProxy = buildTrustProxyFn(input.trustProxy);
|
|
@@ -1266,10 +1301,6 @@ let RouterPipelineStep = /* @__PURE__ */ function(RouterPipelineStep) {
|
|
|
1266
1301
|
}({});
|
|
1267
1302
|
//#endregion
|
|
1268
1303
|
//#region src/router/utils.ts
|
|
1269
|
-
let nextId = 0;
|
|
1270
|
-
function generateRouterID() {
|
|
1271
|
-
return ++nextId;
|
|
1272
|
-
}
|
|
1273
1304
|
function isRouterInstance(input) {
|
|
1274
1305
|
return isInstance(input, RouterSymbol);
|
|
1275
1306
|
}
|
|
@@ -1288,10 +1319,6 @@ function acceptsJson(request) {
|
|
|
1288
1319
|
var Router = class Router {
|
|
1289
1320
|
"@instanceof" = RouterSymbol;
|
|
1290
1321
|
/**
|
|
1291
|
-
* An identifier for the router instance.
|
|
1292
|
-
*/
|
|
1293
|
-
id;
|
|
1294
|
-
/**
|
|
1295
1322
|
* A label for the router instance.
|
|
1296
1323
|
*/
|
|
1297
1324
|
name;
|
|
@@ -1313,12 +1340,15 @@ var Router = class Router {
|
|
|
1313
1340
|
* @protected
|
|
1314
1341
|
*/
|
|
1315
1342
|
hookManager;
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1343
|
+
/**
|
|
1344
|
+
* Normalized options for this router instance.
|
|
1345
|
+
*/
|
|
1346
|
+
_options;
|
|
1347
|
+
constructor(input = {}) {
|
|
1348
|
+
this.name = input.name;
|
|
1319
1349
|
this.hookManager = new HookManager();
|
|
1320
|
-
this.
|
|
1321
|
-
|
|
1350
|
+
this._options = normalizeRouterOptions(input);
|
|
1351
|
+
this.setPath(input.path);
|
|
1322
1352
|
}
|
|
1323
1353
|
matchPath(path) {
|
|
1324
1354
|
if (this.pathMatcher) return this.pathMatcher.test(path);
|
|
@@ -1332,11 +1362,11 @@ var Router = class Router {
|
|
|
1332
1362
|
this.pathMatcher = new PathMatcher(withLeadingSlash(withoutTrailingSlash(`${value}`)), { end: false });
|
|
1333
1363
|
}
|
|
1334
1364
|
/**
|
|
1335
|
-
* Public entry point — creates a
|
|
1365
|
+
* Public entry point — creates a DispatcherEvent from the request,
|
|
1336
1366
|
* runs the pipeline, and returns a Response (with 404/500 fallbacks).
|
|
1337
1367
|
*/
|
|
1338
1368
|
async fetch(request) {
|
|
1339
|
-
const event = new
|
|
1369
|
+
const event = new DispatcherEvent(request);
|
|
1340
1370
|
let response;
|
|
1341
1371
|
try {
|
|
1342
1372
|
response = await this.dispatch(event);
|
|
@@ -1438,25 +1468,18 @@ var Router = class Router {
|
|
|
1438
1468
|
}
|
|
1439
1469
|
const item = this.stack[context.stackIndex];
|
|
1440
1470
|
const { event } = context;
|
|
1441
|
-
const savedNext = event._next;
|
|
1442
|
-
const savedNextCalled = event._nextCalled;
|
|
1443
1471
|
try {
|
|
1444
|
-
event.
|
|
1445
|
-
|
|
1472
|
+
event.setNext(async (error) => {
|
|
1473
|
+
if (error) event.error = createError(error);
|
|
1446
1474
|
const nextContext = {
|
|
1447
1475
|
step: RouterPipelineStep.LOOKUP,
|
|
1448
1476
|
event,
|
|
1449
1477
|
stackIndex: context.stackIndex + 1,
|
|
1450
1478
|
response: void 0
|
|
1451
1479
|
};
|
|
1452
|
-
|
|
1453
|
-
try {
|
|
1454
|
-
await this.executePipelineStep(nextContext);
|
|
1455
|
-
} finally {
|
|
1456
|
-
event.routerPath.pop();
|
|
1457
|
-
}
|
|
1480
|
+
await this.executePipelineStep(nextContext);
|
|
1458
1481
|
return nextContext.response;
|
|
1459
|
-
};
|
|
1482
|
+
});
|
|
1460
1483
|
const response = await item.dispatch(event);
|
|
1461
1484
|
if (response) {
|
|
1462
1485
|
context.response = response;
|
|
@@ -1465,9 +1488,6 @@ var Router = class Router {
|
|
|
1465
1488
|
} catch (e) {
|
|
1466
1489
|
event.error = createError(e);
|
|
1467
1490
|
await this.hookManager.trigger(HookName.ERROR, event);
|
|
1468
|
-
} finally {
|
|
1469
|
-
event._next = savedNext;
|
|
1470
|
-
event._nextCalled = savedNextCalled;
|
|
1471
1491
|
}
|
|
1472
1492
|
context.stackIndex++;
|
|
1473
1493
|
context.step = RouterPipelineStep.CHILD_AFTER;
|
|
@@ -1508,7 +1528,10 @@ var Router = class Router {
|
|
|
1508
1528
|
event,
|
|
1509
1529
|
stackIndex: 0
|
|
1510
1530
|
};
|
|
1511
|
-
event.routerPath.push(
|
|
1531
|
+
event.routerPath.push({
|
|
1532
|
+
name: this.name,
|
|
1533
|
+
options: this._options
|
|
1534
|
+
});
|
|
1512
1535
|
try {
|
|
1513
1536
|
await this.executePipelineStep(context);
|
|
1514
1537
|
} finally {
|
|
@@ -1611,6 +1634,6 @@ var Router = class Router {
|
|
|
1611
1634
|
}
|
|
1612
1635
|
};
|
|
1613
1636
|
//#endregion
|
|
1614
|
-
export {
|
|
1637
|
+
export { setResponseCacheHeaders as $, createError as A, getRequestHeader as B, defineErrorHandler as C, PathMatcher as D, isPath as E, sendRedirect as F, setResponseHeaderContentType as G, sendCreated as H, sendFormat as I, appendResponseHeader as J, setResponseHeaderAttachment as K, getRequestAcceptableContentType as L, DispatcherEvent as M, RoutupEvent as N, HandlerSymbol as O, sendStream as P, RoutupError as Q, getRequestAcceptableContentTypes as R, fromNodeMiddleware as S, Handler as T, sendAccepted as U, sendFile as V, toResponse as W, createEventStream as X, appendResponseHeaderDirective as Y, serializeEventStreamMessage as Z, isHandlerOptions as _, getRequestIP as a, isWebHandlerProvider as b, getRequestAcceptableLanguage as c, getRequestAcceptableEncodings as d, HeaderName as et, getRequestAcceptableCharset as f, isHandler as g, readBody as h, getRequestProtocol as i, isError as j, HandlerType as k, getRequestAcceptableLanguages as l, isRequestCacheable as m, normalizeRouterOptions as n, getRequestHostName as o, getRequestAcceptableCharsets as p, setResponseContentTypeByFileName as q, isPlugin as r, matchRequestContentType as s, Router as t, MethodName as tt, getRequestAcceptableEncoding as u, fromWebHandler as v, defineCoreHandler as w, fromNodeHandler as x, isWebHandler as y, useRequestNegotiator as z };
|
|
1615
1638
|
|
|
1616
|
-
//# sourceMappingURL=src-
|
|
1639
|
+
//# sourceMappingURL=src-Tr9cHQaV.mjs.map
|