@depup/h3 2.0.1-depup.0
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/LICENSE +21 -0
- package/README.md +25 -0
- package/bin/h3.mjs +36 -0
- package/changes.json +5 -0
- package/dist/THIRD-PARTY-LICENSES.md +70 -0
- package/dist/_entries/bun.d.mts +6 -0
- package/dist/_entries/bun.mjs +16 -0
- package/dist/_entries/cloudflare.d.mts +6 -0
- package/dist/_entries/cloudflare.mjs +16 -0
- package/dist/_entries/deno.d.mts +6 -0
- package/dist/_entries/deno.mjs +16 -0
- package/dist/_entries/generic.d.mts +6 -0
- package/dist/_entries/generic.mjs +16 -0
- package/dist/_entries/node.d.mts +10 -0
- package/dist/_entries/node.mjs +19 -0
- package/dist/_entries/service-worker.d.mts +6 -0
- package/dist/_entries/service-worker.mjs +16 -0
- package/dist/_utils.mjs +240 -0
- package/dist/cache.mjs +599 -0
- package/dist/cache2.mjs +50 -0
- package/dist/cors.mjs +292 -0
- package/dist/docs/0.guide/0.index/index.md +117 -0
- package/dist/docs/0.guide/1.basics/0.lifecycle.md +68 -0
- package/dist/docs/0.guide/1.basics/1.routing.md +167 -0
- package/dist/docs/0.guide/1.basics/2.middleware.md +97 -0
- package/dist/docs/0.guide/1.basics/3.handler.md +165 -0
- package/dist/docs/0.guide/1.basics/4.response.md +171 -0
- package/dist/docs/0.guide/1.basics/5.error.md +117 -0
- package/dist/docs/0.guide/1.basics/6.nested-apps.md +57 -0
- package/dist/docs/0.guide/2.rules.md +698 -0
- package/dist/docs/0.guide/3.api/0.h3.md +144 -0
- package/dist/docs/0.guide/3.api/1.h3event.md +160 -0
- package/dist/docs/0.guide/4.advanced/0.plugins.md +50 -0
- package/dist/docs/0.guide/4.advanced/1.websocket.md +176 -0
- package/dist/docs/0.guide/4.advanced/2.nightly.md +13 -0
- package/dist/docs/1.utils/0.index/index.md +46 -0
- package/dist/docs/1.utils/1.request.md +447 -0
- package/dist/docs/1.utils/2.response.md +172 -0
- package/dist/docs/1.utils/3.cookie.md +33 -0
- package/dist/docs/1.utils/4.security.md +175 -0
- package/dist/docs/1.utils/5.proxy.md +57 -0
- package/dist/docs/1.utils/6.mcp.md +75 -0
- package/dist/docs/1.utils/7.more.md +117 -0
- package/dist/docs/1.utils/8.community.md +48 -0
- package/dist/docs/2.examples/0.index/index.md +17 -0
- package/dist/docs/2.examples/1.handle-cookie.md +67 -0
- package/dist/docs/2.examples/2.handle-query.md +76 -0
- package/dist/docs/2.examples/3.handle-session.md +210 -0
- package/dist/docs/2.examples/4.serve-static-assets.md +66 -0
- package/dist/docs/2.examples/5.stream-response.md +76 -0
- package/dist/docs/2.examples/6.validate-data.md +193 -0
- package/dist/docs/3.migration/0.index/index.md +204 -0
- package/dist/docs/README.md +37 -0
- package/dist/h3.d.mts +1669 -0
- package/dist/h3.mjs +1809 -0
- package/dist/index.d.mts +1634 -0
- package/dist/match.d.mts +123 -0
- package/dist/middleware.mjs +123 -0
- package/dist/normalize.mjs +645 -0
- package/dist/path.mjs +42 -0
- package/dist/proxy.mjs +254 -0
- package/dist/response.mjs +465 -0
- package/dist/rules/cache.d.mts +29 -0
- package/dist/rules/cache.mjs +163 -0
- package/dist/rules/compiler.d.mts +94 -0
- package/dist/rules/compiler.mjs +173 -0
- package/dist/rules/index.d.mts +77 -0
- package/dist/rules/index.mjs +34 -0
- package/dist/rules/proxy.d.mts +3 -0
- package/dist/rules/proxy.mjs +14 -0
- package/dist/tracing.d.mts +33 -0
- package/dist/tracing.mjs +89 -0
- package/package.json +148 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { NullProtoObj as EmptyObject } from "rou3";
|
|
2
|
+
import { FastResponse, FastURL } from "srvx";
|
|
3
|
+
function withoutTrailingSlash(path) {
|
|
4
|
+
if (!path || path === "/") return "/";
|
|
5
|
+
return path[path.length - 1] === "/" ? path.slice(0, -1) : path;
|
|
6
|
+
}
|
|
7
|
+
function withLeadingSlash(path) {
|
|
8
|
+
return !path ? "/" : path[0] === "/" ? path : "/" + path;
|
|
9
|
+
}
|
|
10
|
+
function joinURL(base, path) {
|
|
11
|
+
if (!path || path === "/") return base || "/";
|
|
12
|
+
if (!base) return path;
|
|
13
|
+
const segment = path.replace(JOIN_LEADING_SLASH_RE, "");
|
|
14
|
+
return (base[base.length - 1] === "/" ? base : base + "/") + segment;
|
|
15
|
+
}
|
|
16
|
+
const JOIN_LEADING_SLASH_RE = /^\//;
|
|
17
|
+
function stripBase(pathname, base) {
|
|
18
|
+
if (pathname === base || pathname.startsWith(base + "/") || pathname.startsWith(base + "?")) return "/" + pathname.slice(base.length).replace(/^\/+/, "");
|
|
19
|
+
return pathname;
|
|
20
|
+
}
|
|
21
|
+
function hasEmptySegmentAfterBase(pathname, base) {
|
|
22
|
+
return pathname.startsWith(base + "//");
|
|
23
|
+
}
|
|
24
|
+
function withoutBase(input = "", base = "") {
|
|
25
|
+
if (!base || base === "/") return input;
|
|
26
|
+
return stripBase(input, withoutTrailingSlash(base));
|
|
27
|
+
}
|
|
28
|
+
const AUTHORITY_RE = /^(?:[a-z][a-z\d+.-]*:)?\/\//i;
|
|
29
|
+
function getURLPathname(input) {
|
|
30
|
+
const path = AUTHORITY_RE.test(input) ? input.replace(AUTHORITY_RE, "").replace(/^[^/?#]*/, "") : input;
|
|
31
|
+
const end = path.search(/[#?]/);
|
|
32
|
+
return end === -1 ? path : path.slice(0, end);
|
|
33
|
+
}
|
|
34
|
+
const NEEDLESS_ESCAPE_SRC = String.raw`%(?:2[146-9A-E]|3[0-9ABD]|4[0-9A-F]|5[0-9ABDF]|6[1-9A-F]|7[0-9ACE])`;
|
|
35
|
+
const NEEDLESS_ESCAPE_RE = /* @__PURE__ */ new RegExp(NEEDLESS_ESCAPE_SRC, "i");
|
|
36
|
+
const NEEDLESS_ESCAPE_RE_G = /* @__PURE__ */ new RegExp(NEEDLESS_ESCAPE_SRC, "gi");
|
|
37
|
+
function isNonCanonicalPathname(pathname) {
|
|
38
|
+
return NEEDLESS_ESCAPE_RE.test(pathname);
|
|
39
|
+
}
|
|
40
|
+
function canonicalPathname(pathname) {
|
|
41
|
+
return pathname.replace(NEEDLESS_ESCAPE_RE_G, (m) => String.fromCharCode(Number.parseInt(m.slice(1), 16)));
|
|
42
|
+
}
|
|
43
|
+
const ABSOLUTE_URL_RE = /^[a-z][a-z\d+\-.]*:\/\//i;
|
|
44
|
+
const ROUTE_ENCODE_RE = /[\u0000-\u0020"#<>\u0060]|[^\u0000-\u007E]/gu;
|
|
45
|
+
function normalizeRoute(route) {
|
|
46
|
+
if (ABSOLUTE_URL_RE.test(route)) throw new Error(`Route patterns are pathnames, received URL: ${route}`);
|
|
47
|
+
if (route.charCodeAt(0) !== 47) route = `/${route}`;
|
|
48
|
+
route = canonicalPathname(route.replace(ROUTE_ENCODE_RE, encodeURIComponent));
|
|
49
|
+
return route.includes("/.") ? resolveDotSegments(route) : route;
|
|
50
|
+
}
|
|
51
|
+
function resolveDotSegments(pathname) {
|
|
52
|
+
const out = [];
|
|
53
|
+
let dot = false;
|
|
54
|
+
for (const segment of pathname.split("/")) {
|
|
55
|
+
dot = segment === "." || segment === "..";
|
|
56
|
+
if (!dot) out.push(segment);
|
|
57
|
+
else if (segment.length === 2 && out.length > 1) out.pop();
|
|
58
|
+
}
|
|
59
|
+
if (dot) out.push("");
|
|
60
|
+
return out.join("/");
|
|
61
|
+
}
|
|
62
|
+
function decodePathname(pathname) {
|
|
63
|
+
try {
|
|
64
|
+
return decodeURI(pathname);
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const ENCODED_SEP_RE_G = /%(?:25)*(?:2f|5c)/gi;
|
|
70
|
+
const ENCODED_SEP_FLAT_RE_G = /%(?:2f|5c)/gi;
|
|
71
|
+
function decodePreservingSeparators(value, opts) {
|
|
72
|
+
if (!value.includes("%")) return value;
|
|
73
|
+
const decode = opts?.decode || decodeURIComponent;
|
|
74
|
+
const re = opts?.nested === false ? ENCODED_SEP_FLAT_RE_G : ENCODED_SEP_RE_G;
|
|
75
|
+
let result = "";
|
|
76
|
+
let lastIndex = 0;
|
|
77
|
+
re.lastIndex = 0;
|
|
78
|
+
for (let m; m = re.exec(value);) {
|
|
79
|
+
result += decode(value.slice(lastIndex, m.index)) + m[0];
|
|
80
|
+
lastIndex = m.index + m[0].length;
|
|
81
|
+
}
|
|
82
|
+
return result + decode(value.slice(lastIndex));
|
|
83
|
+
}
|
|
84
|
+
const kEventNS = "h3.internal.event.";
|
|
85
|
+
const kEventRes = /* @__PURE__ */ Symbol.for(`${kEventNS}res`);
|
|
86
|
+
const kEventResHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.headers`);
|
|
87
|
+
const kEventResErrHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.err.headers`);
|
|
88
|
+
const kMalformedURL = /* @__PURE__ */ Symbol.for(`${kEventNS}malformed`);
|
|
89
|
+
var H3Event = class {
|
|
90
|
+
app;
|
|
91
|
+
req;
|
|
92
|
+
url;
|
|
93
|
+
context;
|
|
94
|
+
static __is_event__ = true;
|
|
95
|
+
constructor(req, context, app) {
|
|
96
|
+
this.context = req.context = context || req.context || new EmptyObject();
|
|
97
|
+
this.req = req;
|
|
98
|
+
this.app = app;
|
|
99
|
+
const _url = req._url;
|
|
100
|
+
let url = _url && _url instanceof URL ? _url : new FastURL(req.url);
|
|
101
|
+
const pathname = url.pathname;
|
|
102
|
+
if (pathname.includes("%")) {
|
|
103
|
+
if (decodePathname(pathname) === void 0) this[kMalformedURL] = true;
|
|
104
|
+
else if (isNonCanonicalPathname(pathname)) url = new FastURL(`${url.protocol}//${url.host}${canonicalPathname(pathname)}${url.search}`);
|
|
105
|
+
}
|
|
106
|
+
this.url = url;
|
|
107
|
+
}
|
|
108
|
+
get res() {
|
|
109
|
+
return this[kEventRes] ||= new H3EventResponse();
|
|
110
|
+
}
|
|
111
|
+
get runtime() {
|
|
112
|
+
return this.req.runtime;
|
|
113
|
+
}
|
|
114
|
+
waitUntil(promise) {
|
|
115
|
+
this.req.waitUntil?.(promise);
|
|
116
|
+
}
|
|
117
|
+
toString() {
|
|
118
|
+
return `[${this.req.method}] ${this.req.url}`;
|
|
119
|
+
}
|
|
120
|
+
toJSON() {
|
|
121
|
+
return this.toString();
|
|
122
|
+
}
|
|
123
|
+
get node() {
|
|
124
|
+
return this.req.runtime?.node;
|
|
125
|
+
}
|
|
126
|
+
get headers() {
|
|
127
|
+
return this.req.headers;
|
|
128
|
+
}
|
|
129
|
+
get path() {
|
|
130
|
+
return this.url.pathname + this.url.search;
|
|
131
|
+
}
|
|
132
|
+
get method() {
|
|
133
|
+
return this.req.method;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var H3EventResponse = class {
|
|
137
|
+
status;
|
|
138
|
+
statusText;
|
|
139
|
+
get headers() {
|
|
140
|
+
return this[kEventResHeaders] ||= new Headers();
|
|
141
|
+
}
|
|
142
|
+
get errHeaders() {
|
|
143
|
+
return this[kEventResErrHeaders] ||= new Headers();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g;
|
|
147
|
+
function sanitizeStatusMessage(statusMessage = "") {
|
|
148
|
+
return statusMessage.replace(DISALLOWED_STATUS_CHARS, "");
|
|
149
|
+
}
|
|
150
|
+
function sanitizeStatusCode(statusCode, defaultStatusCode = 200) {
|
|
151
|
+
if (!statusCode) return defaultStatusCode;
|
|
152
|
+
if (typeof statusCode === "string") statusCode = +statusCode;
|
|
153
|
+
if (!Number.isInteger(statusCode) || statusCode < 100 || statusCode > 599) return defaultStatusCode;
|
|
154
|
+
return statusCode;
|
|
155
|
+
}
|
|
156
|
+
var HTTPError = class HTTPError extends Error {
|
|
157
|
+
get name() {
|
|
158
|
+
return "HTTPError";
|
|
159
|
+
}
|
|
160
|
+
status;
|
|
161
|
+
statusText;
|
|
162
|
+
headers;
|
|
163
|
+
cause;
|
|
164
|
+
data;
|
|
165
|
+
body;
|
|
166
|
+
unhandled;
|
|
167
|
+
static isError(input) {
|
|
168
|
+
return input instanceof Error && input?.name === "HTTPError" && input.status > 99;
|
|
169
|
+
}
|
|
170
|
+
static status(status, statusText, details) {
|
|
171
|
+
return new HTTPError({
|
|
172
|
+
...details,
|
|
173
|
+
statusText,
|
|
174
|
+
status
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
constructor(arg1, arg2) {
|
|
178
|
+
let messageInput;
|
|
179
|
+
let details;
|
|
180
|
+
if (typeof arg1 === "string") {
|
|
181
|
+
messageInput = arg1;
|
|
182
|
+
details = arg2;
|
|
183
|
+
} else details = arg1;
|
|
184
|
+
const status = sanitizeStatusCode(details?.status || details?.statusCode || (details?.cause)?.status || (details?.cause)?.statusCode, 500);
|
|
185
|
+
const statusText = sanitizeStatusMessage(details?.statusText || details?.statusMessage || (details?.cause)?.statusText || (details?.cause)?.statusMessage);
|
|
186
|
+
const message = messageInput || details?.message || (details?.cause)?.message || details?.statusText || details?.statusMessage || [
|
|
187
|
+
"HTTPError",
|
|
188
|
+
status,
|
|
189
|
+
statusText
|
|
190
|
+
].filter(Boolean).join(" ");
|
|
191
|
+
super(message, { cause: details });
|
|
192
|
+
this.cause = details;
|
|
193
|
+
this.status = status;
|
|
194
|
+
this.statusText = statusText || void 0;
|
|
195
|
+
const rawHeaders = details?.headers || (details?.cause)?.headers;
|
|
196
|
+
this.headers = rawHeaders ? new Headers(rawHeaders) : void 0;
|
|
197
|
+
this.unhandled = details?.unhandled ?? (details?.cause)?.unhandled ?? void 0;
|
|
198
|
+
this.data = details?.data;
|
|
199
|
+
this.body = details?.body;
|
|
200
|
+
}
|
|
201
|
+
get statusCode() {
|
|
202
|
+
return this.status;
|
|
203
|
+
}
|
|
204
|
+
get statusMessage() {
|
|
205
|
+
return this.statusText;
|
|
206
|
+
}
|
|
207
|
+
toJSON() {
|
|
208
|
+
const unhandled = this.unhandled;
|
|
209
|
+
return {
|
|
210
|
+
status: this.status,
|
|
211
|
+
statusText: this.statusText,
|
|
212
|
+
unhandled,
|
|
213
|
+
message: unhandled ? "HTTPError" : this.message,
|
|
214
|
+
data: unhandled ? void 0 : this.data,
|
|
215
|
+
...unhandled ? void 0 : this.body
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
function hasProp(obj, prop) {
|
|
220
|
+
try {
|
|
221
|
+
return prop in obj;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function isJSONSerializable(value, _type) {
|
|
227
|
+
if (value === null || value === void 0) return true;
|
|
228
|
+
if (_type !== "object") return _type === "boolean" || _type === "number" || _type === "string";
|
|
229
|
+
if (typeof value.toJSON === "function") return true;
|
|
230
|
+
if (Array.isArray(value)) return true;
|
|
231
|
+
if (typeof value.pipe === "function" || typeof value.pipeTo === "function") return false;
|
|
232
|
+
if (value instanceof EmptyObject) return true;
|
|
233
|
+
const proto = Object.getPrototypeOf(value);
|
|
234
|
+
return proto === Object.prototype || proto === null;
|
|
235
|
+
}
|
|
236
|
+
const kEventDispose = /* @__PURE__ */ Symbol.for("h3.internal.event.dispose");
|
|
237
|
+
function onDispose(event, cb) {
|
|
238
|
+
let state = event[kEventDispose];
|
|
239
|
+
if (!state) {
|
|
240
|
+
const _state = {
|
|
241
|
+
callbacks: [],
|
|
242
|
+
observe: (response, val) => observeResponse(response, val, event, _state)
|
|
243
|
+
};
|
|
244
|
+
state = event[kEventDispose] = _state;
|
|
245
|
+
}
|
|
246
|
+
if (state.disposed) invokeDisposeCallbacks(event, [cb], state.reason);
|
|
247
|
+
else state.callbacks.push(cb);
|
|
248
|
+
}
|
|
249
|
+
function observeResponse(response, val, event, state) {
|
|
250
|
+
if (state.observing || state.disposed) return response;
|
|
251
|
+
state.observing = true;
|
|
252
|
+
const nodeRes = event.runtime?.node?.res;
|
|
253
|
+
if (nodeRes) {
|
|
254
|
+
if (nodeRes.closed || nodeRes.destroyed) {
|
|
255
|
+
fireDispose(event, state, nodeRes.errored ?? abortError());
|
|
256
|
+
return response;
|
|
257
|
+
}
|
|
258
|
+
nodeRes.once("close", () => {
|
|
259
|
+
fireDispose(event, state, nodeRes.errored ?? (nodeRes.writableFinished ? void 0 : abortError()));
|
|
260
|
+
});
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
if (!isStreamBody(val) || !response.body) {
|
|
264
|
+
fireDispose(event, state, void 0);
|
|
265
|
+
return response;
|
|
266
|
+
}
|
|
267
|
+
const body = response.body;
|
|
268
|
+
const { readable, writable } = new TransformStream();
|
|
269
|
+
body.pipeTo(writable).then(() => fireDispose(event, state, void 0), (reason) => fireDispose(event, state, reason === void 0 ? abortError() : reason));
|
|
270
|
+
return new FastResponse(readable, {
|
|
271
|
+
status: response.status,
|
|
272
|
+
statusText: response.statusText,
|
|
273
|
+
headers: response.headers
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function fireDispose(event, state, reason) {
|
|
277
|
+
if (state.disposed) return;
|
|
278
|
+
state.disposed = true;
|
|
279
|
+
state.reason = reason;
|
|
280
|
+
const callbacks = state.callbacks;
|
|
281
|
+
state.callbacks = [];
|
|
282
|
+
invokeDisposeCallbacks(event, callbacks, reason);
|
|
283
|
+
}
|
|
284
|
+
function invokeDisposeCallbacks(event, callbacks, reason) {
|
|
285
|
+
const pending = [];
|
|
286
|
+
for (const cb of callbacks) try {
|
|
287
|
+
const res = cb(reason);
|
|
288
|
+
if (typeof res?.then === "function") pending.push(Promise.resolve(res).catch((error) => reportDisposeError(event, error)));
|
|
289
|
+
} catch (error) {
|
|
290
|
+
reportDisposeError(event, error);
|
|
291
|
+
}
|
|
292
|
+
if (pending.length > 0) event.waitUntil(Promise.all(pending));
|
|
293
|
+
}
|
|
294
|
+
function isStreamBody(val) {
|
|
295
|
+
return val instanceof ReadableStream || val instanceof Blob || val instanceof Response || val?.body instanceof ReadableStream;
|
|
296
|
+
}
|
|
297
|
+
function abortError() {
|
|
298
|
+
return new DOMException("Connection closed prematurely.", "AbortError");
|
|
299
|
+
}
|
|
300
|
+
function reportDisposeError(event, error) {
|
|
301
|
+
if (!event.app?.config.silent) console.error("[h3] onDispose:", error);
|
|
302
|
+
}
|
|
303
|
+
const kNotFound = /* @__PURE__ */ Symbol.for("h3.notFound");
|
|
304
|
+
const kHandled = /* @__PURE__ */ Symbol.for("h3.handled");
|
|
305
|
+
function toResponse(val, event, config = {}) {
|
|
306
|
+
if (typeof val?.then === "function") return val.then((resolvedVal) => toResponse(resolvedVal, event, config), (r) => toResponse(toError(r), event, config));
|
|
307
|
+
let response;
|
|
308
|
+
try {
|
|
309
|
+
response = prepareResponse(val, event, config);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return toResponse(toError(error), event, config);
|
|
312
|
+
}
|
|
313
|
+
if (typeof response?.then === "function") return toResponse(response, event, config);
|
|
314
|
+
const { onResponse } = config;
|
|
315
|
+
if (onResponse) return Promise.resolve().then(() => onResponse(response, event)).catch((error) => {
|
|
316
|
+
if (!config.silent) console.error(error);
|
|
317
|
+
}).then(() => event[kEventDispose]?.observe(response, val) ?? response);
|
|
318
|
+
return event[kEventDispose]?.observe(response, val) ?? response;
|
|
319
|
+
}
|
|
320
|
+
function toError(value) {
|
|
321
|
+
if (value === kNotFound || value === kHandled || value instanceof Error) return value;
|
|
322
|
+
if (typeof value === "number") return new HTTPError({ status: value });
|
|
323
|
+
const error = new HTTPError({
|
|
324
|
+
status: 500,
|
|
325
|
+
unhandled: true
|
|
326
|
+
});
|
|
327
|
+
error.cause = value;
|
|
328
|
+
return error;
|
|
329
|
+
}
|
|
330
|
+
const kHTTPResponse = /* @__PURE__ */ Symbol.for("h3.HTTPResponse");
|
|
331
|
+
var HTTPResponse = class {
|
|
332
|
+
#headers;
|
|
333
|
+
#init;
|
|
334
|
+
body;
|
|
335
|
+
constructor(body, init) {
|
|
336
|
+
this.body = body;
|
|
337
|
+
this.#init = init;
|
|
338
|
+
}
|
|
339
|
+
get status() {
|
|
340
|
+
return this.#init?.status;
|
|
341
|
+
}
|
|
342
|
+
get statusText() {
|
|
343
|
+
return this.#init?.statusText;
|
|
344
|
+
}
|
|
345
|
+
get headers() {
|
|
346
|
+
return this.#headers ||= new Headers(this.#init?.headers);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
HTTPResponse.prototype[kHTTPResponse] = true;
|
|
350
|
+
function prepareResponse(val, event, config, nested) {
|
|
351
|
+
if (val === kHandled) return new FastResponse(null);
|
|
352
|
+
if (val === kNotFound) val = new HTTPError({
|
|
353
|
+
status: 404,
|
|
354
|
+
message: `Cannot find any route matching [${event.req.method}] ${event.url}`
|
|
355
|
+
});
|
|
356
|
+
if (val && val instanceof Error) {
|
|
357
|
+
const isHTTPError = HTTPError.isError(val);
|
|
358
|
+
const error = isHTTPError ? val : new HTTPError(val);
|
|
359
|
+
if (!isHTTPError) {
|
|
360
|
+
error.unhandled = true;
|
|
361
|
+
if (val?.stack) error.stack = val.stack;
|
|
362
|
+
}
|
|
363
|
+
if (error.unhandled && !config.silent) console.error(error);
|
|
364
|
+
const { onError } = config;
|
|
365
|
+
const errHeaders = event[kEventRes]?.[kEventResErrHeaders];
|
|
366
|
+
if (onError && !nested) return Promise.resolve().then(() => onError(error, event)).catch(toError).then((newVal) => prepareResponse(newVal ?? val, event, config, true));
|
|
367
|
+
event[kEventRes] = void 0;
|
|
368
|
+
return errorResponse(error, config.debug, errHeaders);
|
|
369
|
+
}
|
|
370
|
+
const preparedRes = event[kEventRes];
|
|
371
|
+
let preparedHeaders = preparedRes?.[kEventResHeaders];
|
|
372
|
+
event[kEventRes] = void 0;
|
|
373
|
+
if (!(val instanceof Response)) {
|
|
374
|
+
const res = prepareResponseBody(val, event, config);
|
|
375
|
+
const rawStatus = res.status || preparedRes?.status;
|
|
376
|
+
const status = rawStatus ? sanitizeStatusCode(rawStatus) : void 0;
|
|
377
|
+
const rawStatusText = res.statusText || preparedRes?.statusText;
|
|
378
|
+
return new FastResponse(nullBody(event.req.method, status) ? null : res.body, {
|
|
379
|
+
status,
|
|
380
|
+
statusText: rawStatusText === void 0 ? void 0 : sanitizeStatusMessage(rawStatusText),
|
|
381
|
+
headers: res.headers && preparedHeaders ? mergeHeaders(res.headers, preparedHeaders) : res.headers || preparedHeaders
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (val.status >= 400) preparedHeaders = preparedRes?.[kEventResErrHeaders];
|
|
385
|
+
if (preparedHeaders && !nested && !preparedHeaders.keys().next().done) return new FastResponse(nullBody(event.req.method, val.status) ? null : val.body, {
|
|
386
|
+
status: val.status,
|
|
387
|
+
statusText: val.statusText,
|
|
388
|
+
headers: mergeHeaders(val.headers, preparedHeaders)
|
|
389
|
+
});
|
|
390
|
+
return event.req.method === "HEAD" && val.body !== null ? new FastResponse(null, {
|
|
391
|
+
status: val.status,
|
|
392
|
+
statusText: val.statusText,
|
|
393
|
+
headers: val.headers
|
|
394
|
+
}) : val;
|
|
395
|
+
}
|
|
396
|
+
function mergeHeaders(base, overrides, target = new Headers(base)) {
|
|
397
|
+
for (const [name, value] of overrides) if (name === "set-cookie") target.append(name, value);
|
|
398
|
+
else target.set(name, value);
|
|
399
|
+
return target;
|
|
400
|
+
}
|
|
401
|
+
const frozen = (name) => (...args) => {
|
|
402
|
+
throw new Error(`Headers are frozen (${name} ${args.join(", ")})`);
|
|
403
|
+
};
|
|
404
|
+
var FrozenHeaders = class extends Headers {
|
|
405
|
+
set = frozen("set");
|
|
406
|
+
append = frozen("append");
|
|
407
|
+
delete = frozen("delete");
|
|
408
|
+
};
|
|
409
|
+
const emptyHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-length": "0" });
|
|
410
|
+
const jsonHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-type": "application/json;charset=UTF-8" });
|
|
411
|
+
function prepareResponseBody(val, event, config) {
|
|
412
|
+
if (val === null || val === void 0) return {
|
|
413
|
+
body: "",
|
|
414
|
+
headers: emptyHeaders
|
|
415
|
+
};
|
|
416
|
+
const valType = typeof val;
|
|
417
|
+
if (valType === "string") return { body: val };
|
|
418
|
+
if (val instanceof Uint8Array) return {
|
|
419
|
+
body: val,
|
|
420
|
+
headers: new Headers({ "content-length": val.byteLength.toString() })
|
|
421
|
+
};
|
|
422
|
+
if (val instanceof HTTPResponse || val?.[kHTTPResponse] === true) return val;
|
|
423
|
+
if (isJSONSerializable(val, valType)) return {
|
|
424
|
+
body: JSON.stringify(val, void 0, config.debug ? 2 : void 0),
|
|
425
|
+
headers: jsonHeaders
|
|
426
|
+
};
|
|
427
|
+
if (valType === "bigint") return {
|
|
428
|
+
body: val.toString(),
|
|
429
|
+
headers: jsonHeaders
|
|
430
|
+
};
|
|
431
|
+
if (val instanceof Blob) {
|
|
432
|
+
const headers = new Headers({
|
|
433
|
+
"content-type": val.type,
|
|
434
|
+
"content-length": val.size.toString()
|
|
435
|
+
});
|
|
436
|
+
let filename = val.name;
|
|
437
|
+
if (filename) {
|
|
438
|
+
filename = encodeURIComponent(filename);
|
|
439
|
+
headers.set("content-disposition", `filename="${filename}"; filename*=UTF-8''${filename}`);
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
body: val.stream(),
|
|
443
|
+
headers
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (valType === "symbol") return { body: val.toString() };
|
|
447
|
+
if (valType === "function") return { body: `${val.name}()` };
|
|
448
|
+
return { body: val };
|
|
449
|
+
}
|
|
450
|
+
function nullBody(method, status) {
|
|
451
|
+
return method === "HEAD" || status === 100 || status === 101 || status === 102 || status === 204 || status === 205 || status === 304;
|
|
452
|
+
}
|
|
453
|
+
function errorResponse(error, debug, errHeaders) {
|
|
454
|
+
let headers = error.headers ? mergeHeaders(jsonHeaders, error.headers) : new Headers(jsonHeaders);
|
|
455
|
+
if (errHeaders) headers = mergeHeaders(headers, errHeaders);
|
|
456
|
+
return new FastResponse(JSON.stringify({
|
|
457
|
+
...error.toJSON(),
|
|
458
|
+
stack: debug && error.stack ? error.stack.split("\n").map((l) => l.trim()) : void 0
|
|
459
|
+
}, void 0, debug ? 2 : void 0), {
|
|
460
|
+
status: error.status,
|
|
461
|
+
statusText: error.statusText,
|
|
462
|
+
headers
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
export { EmptyObject, H3Event, HTTPError, HTTPResponse, decodePreservingSeparators, getURLPathname, hasEmptySegmentAfterBase, hasProp, isNonCanonicalPathname, joinURL, kHandled, kMalformedURL, kNotFound, normalizeRoute, onDispose, sanitizeStatusCode, sanitizeStatusMessage, stripBase, toError, toResponse, withLeadingSlash, withoutBase, withoutTrailingSlash };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RuleHandler } from "../h3.mjs";
|
|
2
|
+
import { CachedEventHandlerOptions, StorageOption } from "ocache";
|
|
3
|
+
|
|
4
|
+
export interface OcacheRuleHandlerOptions {
|
|
5
|
+
/**
|
|
6
|
+
* ocache storage instance, or a factory resolved on first use. Shared by every
|
|
7
|
+
* cache rule this handler serves. Defaults to one lazily created memory store.
|
|
8
|
+
*/
|
|
9
|
+
storage?: StorageOption;
|
|
10
|
+
/**
|
|
11
|
+
* Default ocache options. Rule options take precedence.
|
|
12
|
+
*
|
|
13
|
+
* A `shouldBypassCache` hook passed here is resolved inside the cache, so a
|
|
14
|
+
* request it bypasses is still dispatched by the rule and does not continue
|
|
15
|
+
* the middleware chain the way an uncacheable method or `Range` request does.
|
|
16
|
+
*/
|
|
17
|
+
defaults?: CachedEventHandlerOptions;
|
|
18
|
+
/** Stable cache-key scope. Set this when sharing persistent storage across processes. */
|
|
19
|
+
id?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create an ocache-backed `cache` rule handler.
|
|
23
|
+
*
|
|
24
|
+
* Every rule this handler serves shares one storage instance: the supplied
|
|
25
|
+
* `storage`, or a memory store shared by every handler with the same `id`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createOcacheRuleHandler(opts?: OcacheRuleHandlerOptions): RuleHandler<"cache">;
|
|
28
|
+
/** Shared default handler imported by compiled matchers and registered explicitly at runtime. */
|
|
29
|
+
export declare const cache: RuleHandler<"cache">;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { HTTPError, HTTPResponse, toResponse } from "../response.mjs";
|
|
2
|
+
import { defineHandler, handleCacheHeaders } from "../cache.mjs";
|
|
3
|
+
import { createCacheRuleHandler } from "../cache2.mjs";
|
|
4
|
+
import { createMemoryStorage, defineCachedHandler } from "ocache";
|
|
5
|
+
const VOLATILE_CORS_HEADERS = ["access-control-allow-origin", "access-control-allow-credentials"];
|
|
6
|
+
function getSetCookies(headers) {
|
|
7
|
+
if (typeof headers.getSetCookie === "function") return headers.getSetCookie();
|
|
8
|
+
const value = headers.get("set-cookie");
|
|
9
|
+
return value === null ? [] : [value];
|
|
10
|
+
}
|
|
11
|
+
const RE_VARY_ORIGIN = /(?:^|,)\s*origin\s*(?:,|$)/i;
|
|
12
|
+
function withoutVaryOrigin(vary) {
|
|
13
|
+
const rest = vary.split(",").map((name) => name.trim()).filter((name) => name && name.toLowerCase() !== "origin");
|
|
14
|
+
return rest.length > 0 ? rest.join(", ") : void 0;
|
|
15
|
+
}
|
|
16
|
+
function moveVolatileHeaders(res, event) {
|
|
17
|
+
if (event.req.method !== "GET" && event.req.method !== "HEAD") return res;
|
|
18
|
+
const moved = [];
|
|
19
|
+
for (const name of VOLATILE_CORS_HEADERS) {
|
|
20
|
+
const value = res.headers.get(name);
|
|
21
|
+
if (value !== null) moved.push([name, value]);
|
|
22
|
+
}
|
|
23
|
+
const cookies = getSetCookies(res.headers);
|
|
24
|
+
const vary = event.context.routeRules?.cors ? res.headers.get("vary") : null;
|
|
25
|
+
const dropVaryOrigin = vary !== null && RE_VARY_ORIGIN.test(vary);
|
|
26
|
+
const varyRest = dropVaryOrigin ? withoutVaryOrigin(vary) : void 0;
|
|
27
|
+
if (moved.length === 0 && cookies.length === 0 && !dropVaryOrigin) return res;
|
|
28
|
+
const strip = (headers) => {
|
|
29
|
+
for (const [name] of moved) headers.delete(name);
|
|
30
|
+
if (cookies.length > 0) headers.delete("set-cookie");
|
|
31
|
+
if (dropVaryOrigin) {
|
|
32
|
+
if (varyRest === void 0) headers.delete("vary");
|
|
33
|
+
else headers.set("vary", varyRest);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
strip(res.headers);
|
|
38
|
+
} catch {
|
|
39
|
+
res = new Response(res.body, res);
|
|
40
|
+
strip(res.headers);
|
|
41
|
+
}
|
|
42
|
+
for (const [name, value] of moved) event.res.headers.set(name, value);
|
|
43
|
+
for (const cookie of cookies) event.res.headers.append("set-cookie", cookie);
|
|
44
|
+
if (dropVaryOrigin) event.res.headers.set("vary", vary);
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
function isBypassedRequest(event) {
|
|
48
|
+
const method = event.req.method;
|
|
49
|
+
return method !== "GET" && method !== "HEAD" || event.req.headers.has("range");
|
|
50
|
+
}
|
|
51
|
+
const CREDENTIAL_HEADERS = ["authorization", "proxy-authorization"];
|
|
52
|
+
function setRequestHeaders(event, names, values) {
|
|
53
|
+
const apply = (headers) => {
|
|
54
|
+
for (let i = 0; i < names.length; i++) {
|
|
55
|
+
const value = values[i];
|
|
56
|
+
if (value == null) headers.delete(names[i]);
|
|
57
|
+
else headers.set(names[i], value);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const applied = (headers) => names.every((name, i) => headers.get(name) === (values[i] ?? null));
|
|
61
|
+
try {
|
|
62
|
+
if (applied(event.req.headers)) return true;
|
|
63
|
+
apply(event.req.headers);
|
|
64
|
+
if (applied(event.req.headers)) return true;
|
|
65
|
+
} catch {}
|
|
66
|
+
try {
|
|
67
|
+
const original = event.req;
|
|
68
|
+
const headers = new Headers(original.headers);
|
|
69
|
+
apply(headers);
|
|
70
|
+
const req = new Request(original.url, {
|
|
71
|
+
method: original.method,
|
|
72
|
+
headers
|
|
73
|
+
});
|
|
74
|
+
req.context = original.context;
|
|
75
|
+
if (original.runtime) req.runtime = original.runtime;
|
|
76
|
+
event.req = req;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return applied(event.req.headers);
|
|
81
|
+
}
|
|
82
|
+
const savedHeaders = /* @__PURE__ */ new WeakMap();
|
|
83
|
+
const servedCacheControl = /* @__PURE__ */ new WeakMap();
|
|
84
|
+
const RE_PRIVATE = /(?:^|,)\s*(?:private|no-store)(?:\s*=|\s*,|\s*$)/i;
|
|
85
|
+
function withPreservedCacheControl(event, conditions, sendCacheControl) {
|
|
86
|
+
const existing = servedCacheControl.get(event) ?? event.res.headers.get("cache-control");
|
|
87
|
+
const preserve = !sendCacheControl || existing !== null && RE_PRIVATE.test(existing);
|
|
88
|
+
const matched = handleCacheHeaders(event, conditions);
|
|
89
|
+
if (preserve) {
|
|
90
|
+
if (existing) event.res.headers.set("cache-control", existing);
|
|
91
|
+
else event.res.headers.delete("cache-control");
|
|
92
|
+
}
|
|
93
|
+
return matched;
|
|
94
|
+
}
|
|
95
|
+
function withRequestHeaderFilter(handler, strip, restore) {
|
|
96
|
+
const names = [...strip, ...restore];
|
|
97
|
+
const stripped = strip.map(() => null);
|
|
98
|
+
return (event) => {
|
|
99
|
+
if (names.length > 0 && (event.req.method === "GET" || event.req.method === "HEAD")) {
|
|
100
|
+
const saved = savedHeaders.get(event);
|
|
101
|
+
const values = saved ? [...stripped, ...saved] : [...stripped, ...restore.map(() => null)];
|
|
102
|
+
if (!setRequestHeaders(event, names, values)) {
|
|
103
|
+
for (const name of strip) if (event.req.headers.get(name) !== null) throw new HTTPError({
|
|
104
|
+
status: 500,
|
|
105
|
+
message: "Cache rule could not strip the credential headers from the request before a cached dispatch."
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return handler(event);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function variableHeaderNames(opts, allowCredentials) {
|
|
113
|
+
const allowsCookies = (opts.allowCookies ?? []).some((name) => name?.trim());
|
|
114
|
+
const varies = allowCredentials ? [...opts.varies ?? [], ...CREDENTIAL_HEADERS] : opts.varies ?? [];
|
|
115
|
+
return [...new Set(varies.filter(Boolean).map((name) => name.toLowerCase()))].filter((name) => !(allowsCookies && name === "cookie"));
|
|
116
|
+
}
|
|
117
|
+
const idStorages = /* @__PURE__ */ new Map();
|
|
118
|
+
function idStorage(id) {
|
|
119
|
+
let storage = idStorages.get(id);
|
|
120
|
+
if (!storage) {
|
|
121
|
+
storage = createMemoryStorage();
|
|
122
|
+
idStorages.set(id, storage);
|
|
123
|
+
}
|
|
124
|
+
return storage;
|
|
125
|
+
}
|
|
126
|
+
function createOcacheRuleHandler(opts) {
|
|
127
|
+
let memoryStorage;
|
|
128
|
+
const id = opts?.id;
|
|
129
|
+
const storage = opts?.storage ?? (id === void 0 ? () => memoryStorage ??= createMemoryStorage() : () => idStorage(id));
|
|
130
|
+
return createCacheRuleHandler({
|
|
131
|
+
shouldBypass: isBypassedRequest,
|
|
132
|
+
defineCachedHandler: (handler, cachedOpts) => {
|
|
133
|
+
const allowCredentials = cachedOpts.allowAuthorization === true;
|
|
134
|
+
const strip = allowCredentials ? [] : CREDENTIAL_HEADERS;
|
|
135
|
+
const restore = variableHeaderNames(cachedOpts, allowCredentials).filter((name) => !strip.includes(name));
|
|
136
|
+
const ocacheHandler = defineCachedHandler(cachedOpts.headersOnly ? handler : withRequestHeaderFilter(handler, strip, restore), {
|
|
137
|
+
toResponse: async (value, event) => {
|
|
138
|
+
const res = moveVolatileHeaders(await toResponse(value, event), event);
|
|
139
|
+
const cacheControl = res.headers.get("cache-control");
|
|
140
|
+
if (cacheControl) servedCacheControl.set(event, cacheControl);
|
|
141
|
+
return res;
|
|
142
|
+
},
|
|
143
|
+
handleCacheHeaders: (event, conditions) => withPreservedCacheControl(event, conditions, cachedOpts.sendCacheControl !== false),
|
|
144
|
+
storage,
|
|
145
|
+
...cachedOpts,
|
|
146
|
+
...allowCredentials && { varies: [...cachedOpts.varies ?? [], ...CREDENTIAL_HEADERS] }
|
|
147
|
+
});
|
|
148
|
+
return defineHandler(async (event) => {
|
|
149
|
+
if (restore.length > 0) savedHeaders.set(event, restore.map((name) => event.req.headers.get(name)));
|
|
150
|
+
const res = await ocacheHandler(event);
|
|
151
|
+
if (res instanceof Response && res.status === 304) return new HTTPResponse(null, {
|
|
152
|
+
status: 304,
|
|
153
|
+
headers: res.headers
|
|
154
|
+
});
|
|
155
|
+
return res;
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
defaults: opts?.defaults,
|
|
159
|
+
id: opts?.id
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const cache = /* @__PURE__ */ createOcacheRuleHandler();
|
|
163
|
+
export { cache, createOcacheRuleHandler };
|