@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
package/dist/cache.mjs
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { EmptyObject, H3Event, HTTPError, decodePreservingSeparators, hasEmptySegmentAfterBase, kMalformedURL, kNotFound, normalizeRoute, stripBase, toError, toResponse } from "./response.mjs";
|
|
2
|
+
import { callMiddleware, composeHandler, composeMiddleware, normalizeMiddleware } from "./middleware.mjs";
|
|
3
|
+
import { addRoute, createRouter, findRoute } from "rou3";
|
|
4
|
+
const plusRegex = /\+/g;
|
|
5
|
+
function parseQuery(input) {
|
|
6
|
+
const params = new EmptyObject();
|
|
7
|
+
if (!input || input === "?") return params;
|
|
8
|
+
const inputLength = input.length;
|
|
9
|
+
let key = "";
|
|
10
|
+
let value = "";
|
|
11
|
+
let startingIndex = -1;
|
|
12
|
+
let equalityIndex = -1;
|
|
13
|
+
let shouldDecodeKey = false;
|
|
14
|
+
let shouldDecodeValue = false;
|
|
15
|
+
let keyHasPlus = false;
|
|
16
|
+
let valueHasPlus = false;
|
|
17
|
+
let hasBothKeyValuePair = false;
|
|
18
|
+
let c = 0;
|
|
19
|
+
for (let i = 0; i < inputLength + 1; i++) {
|
|
20
|
+
c = i === inputLength ? 38 : input.charCodeAt(i);
|
|
21
|
+
switch (c) {
|
|
22
|
+
case 38:
|
|
23
|
+
hasBothKeyValuePair = equalityIndex > startingIndex;
|
|
24
|
+
if (!hasBothKeyValuePair) equalityIndex = i;
|
|
25
|
+
key = input.slice(startingIndex + 1, equalityIndex);
|
|
26
|
+
if (hasBothKeyValuePair || key.length > 0) {
|
|
27
|
+
if (keyHasPlus) key = key.replace(plusRegex, " ");
|
|
28
|
+
if (shouldDecodeKey) try {
|
|
29
|
+
key = decodeURIComponent(key);
|
|
30
|
+
} catch {}
|
|
31
|
+
if (hasBothKeyValuePair) {
|
|
32
|
+
value = input.slice(equalityIndex + 1, i);
|
|
33
|
+
if (valueHasPlus) value = value.replace(plusRegex, " ");
|
|
34
|
+
if (shouldDecodeValue) try {
|
|
35
|
+
value = decodeURIComponent(value);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
const currentValue = params[key];
|
|
39
|
+
if (currentValue === void 0) params[key] = value;
|
|
40
|
+
else if (Array.isArray(currentValue)) currentValue.push(value);
|
|
41
|
+
else params[key] = [currentValue, value];
|
|
42
|
+
}
|
|
43
|
+
value = "";
|
|
44
|
+
startingIndex = i;
|
|
45
|
+
equalityIndex = i;
|
|
46
|
+
shouldDecodeKey = false;
|
|
47
|
+
shouldDecodeValue = false;
|
|
48
|
+
keyHasPlus = false;
|
|
49
|
+
valueHasPlus = false;
|
|
50
|
+
break;
|
|
51
|
+
case 61:
|
|
52
|
+
if (equalityIndex <= startingIndex) equalityIndex = i;
|
|
53
|
+
else shouldDecodeValue = true;
|
|
54
|
+
break;
|
|
55
|
+
case 43:
|
|
56
|
+
if (equalityIndex > startingIndex) valueHasPlus = true;
|
|
57
|
+
else keyHasPlus = true;
|
|
58
|
+
break;
|
|
59
|
+
case 37: if (equalityIndex > startingIndex) shouldDecodeValue = true;
|
|
60
|
+
else shouldDecodeKey = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return params;
|
|
64
|
+
}
|
|
65
|
+
const VALIDATION_FAILED = "Validation failed";
|
|
66
|
+
async function validateData(data, fn, options) {
|
|
67
|
+
if ("~standard" in fn) {
|
|
68
|
+
const result = await fn["~standard"].validate(data);
|
|
69
|
+
if (result.issues) throw createValidationError(options?.onError?.(result) || {
|
|
70
|
+
message: VALIDATION_FAILED,
|
|
71
|
+
issues: result.issues
|
|
72
|
+
});
|
|
73
|
+
return result.value;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const res = await fn(data);
|
|
77
|
+
if (res === false) throw createValidationError(options?.onError?.({ issues: [{ message: VALIDATION_FAILED }] }) || { message: VALIDATION_FAILED });
|
|
78
|
+
if (res === true) return data;
|
|
79
|
+
return res ?? data;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw createValidationError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const reqBodyKeys = /* @__PURE__ */ new Set([
|
|
85
|
+
"body",
|
|
86
|
+
"text",
|
|
87
|
+
"formData",
|
|
88
|
+
"arrayBuffer"
|
|
89
|
+
]);
|
|
90
|
+
async function validatedRequest(req, validate) {
|
|
91
|
+
if (validate.headers) {
|
|
92
|
+
const validatedheaders = await validateSource("headers", Object.fromEntries(req.headers.entries()), validate.headers, validate.onError);
|
|
93
|
+
for (const [key, value] of Object.entries(validatedheaders)) req.headers.set(key, value);
|
|
94
|
+
}
|
|
95
|
+
if (!validate.body) return req;
|
|
96
|
+
return new Proxy(req, { get(_target, prop) {
|
|
97
|
+
if (prop === "json") return function _validatedJson() {
|
|
98
|
+
return req.json().catch((error) => {
|
|
99
|
+
if (HTTPError.isError(error)) throw error;
|
|
100
|
+
throw new HTTPError({
|
|
101
|
+
status: 400,
|
|
102
|
+
statusText: "Bad Request",
|
|
103
|
+
message: "Invalid JSON body"
|
|
104
|
+
});
|
|
105
|
+
}).then((data) => validate.body["~standard"].validate(data)).then((result) => {
|
|
106
|
+
if (result.issues) throw createValidationError(validate.onError?.({
|
|
107
|
+
_source: "body",
|
|
108
|
+
...result
|
|
109
|
+
}) || {
|
|
110
|
+
message: VALIDATION_FAILED,
|
|
111
|
+
issues: result.issues
|
|
112
|
+
});
|
|
113
|
+
return result.value;
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
else if (reqBodyKeys.has(prop)) throw new TypeError(`Cannot access .${prop} on request with JSON validation enabled. Use .json() instead.`);
|
|
117
|
+
return Reflect.get(req, prop);
|
|
118
|
+
} });
|
|
119
|
+
}
|
|
120
|
+
async function validatedURL(url, validate) {
|
|
121
|
+
if (!validate.query) return url;
|
|
122
|
+
const query = new EmptyObject();
|
|
123
|
+
for (const [key, value] of url.searchParams) {
|
|
124
|
+
const prev = query[key];
|
|
125
|
+
if (prev === void 0) query[key] = value;
|
|
126
|
+
else if (Array.isArray(prev)) prev.push(value);
|
|
127
|
+
else query[key] = [prev, value];
|
|
128
|
+
}
|
|
129
|
+
const validatedQuery = await validateSource("query", query, validate.query, validate.onError);
|
|
130
|
+
for (const [key, value] of Object.entries(validatedQuery)) if (Array.isArray(value)) {
|
|
131
|
+
url.searchParams.delete(key);
|
|
132
|
+
for (const item of value) url.searchParams.append(key, String(item));
|
|
133
|
+
} else url.searchParams.set(key, String(value));
|
|
134
|
+
return url;
|
|
135
|
+
}
|
|
136
|
+
async function validateSource(source, data, fn, onError) {
|
|
137
|
+
const result = await fn["~standard"].validate(data);
|
|
138
|
+
if (result.issues) throw createValidationError(onError?.({
|
|
139
|
+
_source: source,
|
|
140
|
+
...result
|
|
141
|
+
}) || {
|
|
142
|
+
message: VALIDATION_FAILED,
|
|
143
|
+
issues: result.issues
|
|
144
|
+
});
|
|
145
|
+
return result.value;
|
|
146
|
+
}
|
|
147
|
+
function createValidationError(cause) {
|
|
148
|
+
return HTTPError.isError(cause) ? cause : new HTTPError({
|
|
149
|
+
cause,
|
|
150
|
+
status: cause?.status || 400,
|
|
151
|
+
statusText: cause?.statusText || VALIDATION_FAILED,
|
|
152
|
+
message: cause?.message || VALIDATION_FAILED,
|
|
153
|
+
data: {
|
|
154
|
+
issues: cause?.issues,
|
|
155
|
+
message: cause instanceof Error ? VALIDATION_FAILED : cause?.message || VALIDATION_FAILED
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function isEvent(input) {
|
|
160
|
+
return input instanceof H3Event || input?.constructor?.__is_event__;
|
|
161
|
+
}
|
|
162
|
+
function isHTTPEvent(input) {
|
|
163
|
+
return input?.req instanceof Request;
|
|
164
|
+
}
|
|
165
|
+
function getEventContext(event) {
|
|
166
|
+
if (event.context) return event.context;
|
|
167
|
+
event.req.context ??= {};
|
|
168
|
+
return event.req.context;
|
|
169
|
+
}
|
|
170
|
+
function mockEvent(_request, options) {
|
|
171
|
+
let request;
|
|
172
|
+
if (options?.body && !options.duplex) options.duplex = "half";
|
|
173
|
+
if (typeof _request === "string") {
|
|
174
|
+
let url = _request;
|
|
175
|
+
if (url[0] === "/") url = `http://localhost${url}`;
|
|
176
|
+
request = new Request(url, options);
|
|
177
|
+
} else if (options || _request instanceof URL) request = new Request(_request, options);
|
|
178
|
+
else request = _request;
|
|
179
|
+
return new H3Event(request);
|
|
180
|
+
}
|
|
181
|
+
function requestWithURL(req, url) {
|
|
182
|
+
const cache = new EmptyObject();
|
|
183
|
+
cache.url = url;
|
|
184
|
+
cache._url = void 0;
|
|
185
|
+
return new Proxy(req, {
|
|
186
|
+
get(target, prop) {
|
|
187
|
+
if (prop in cache) return cache[prop];
|
|
188
|
+
const value = Reflect.get(target, prop);
|
|
189
|
+
if (prop === "bodyUsed") return value;
|
|
190
|
+
cache[prop] = typeof value === "function" && prop !== "constructor" ? value.bind(target) : value;
|
|
191
|
+
return cache[prop];
|
|
192
|
+
},
|
|
193
|
+
set(target, prop, value) {
|
|
194
|
+
if (prop !== "url" && prop !== "_url") delete cache[prop];
|
|
195
|
+
return Reflect.set(target, prop, value);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function requestWithBaseURL(req, base, options = {}) {
|
|
200
|
+
const url = new URL(options.url || req.url);
|
|
201
|
+
url.pathname = stripBase(url.pathname, base);
|
|
202
|
+
return requestWithURL(req, url.href);
|
|
203
|
+
}
|
|
204
|
+
function toRequest(input, options) {
|
|
205
|
+
if (typeof input === "string") {
|
|
206
|
+
let url = input;
|
|
207
|
+
if (url[0] === "/") url = `http://${safeHost((options?.headers ? new Headers(options.headers) : void 0)?.get("host"))}${url}`;
|
|
208
|
+
return new Request(url, options);
|
|
209
|
+
} else if (options || input instanceof URL) return new Request(input, options);
|
|
210
|
+
return input;
|
|
211
|
+
}
|
|
212
|
+
function getQuery(event) {
|
|
213
|
+
return parseQuery((event.url || new URL(event.req.url)).search.slice(1));
|
|
214
|
+
}
|
|
215
|
+
function getValidatedQuery(event, validate, options) {
|
|
216
|
+
return validateData(getQuery(event), validate, options);
|
|
217
|
+
}
|
|
218
|
+
function getRouterParams(event, opts = {}) {
|
|
219
|
+
let params = getEventContext(event).params || {};
|
|
220
|
+
if (opts.decode) {
|
|
221
|
+
params = { ...params };
|
|
222
|
+
for (const key in params) params[key] = decodePreservingSeparators(params[key]);
|
|
223
|
+
}
|
|
224
|
+
return params;
|
|
225
|
+
}
|
|
226
|
+
function getValidatedRouterParams(event, validate, options = {}) {
|
|
227
|
+
const { decode, ...opts } = options;
|
|
228
|
+
return validateData(getRouterParams(event, { decode }), validate, opts);
|
|
229
|
+
}
|
|
230
|
+
function getRouterParam(event, name, opts = {}) {
|
|
231
|
+
return getRouterParams(event, opts)[name];
|
|
232
|
+
}
|
|
233
|
+
function isMethod(event, expected, allowHead) {
|
|
234
|
+
const method = event.req.method.toUpperCase();
|
|
235
|
+
if (allowHead && method === "HEAD") return true;
|
|
236
|
+
if (typeof expected === "string") {
|
|
237
|
+
if (method === expected) return true;
|
|
238
|
+
} else if (expected.includes(method)) return true;
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
function assertMethod(event, expected, allowHead) {
|
|
242
|
+
if (!isMethod(event, expected, allowHead)) {
|
|
243
|
+
const allowed = Array.isArray(expected) ? expected : [expected];
|
|
244
|
+
throw new HTTPError({
|
|
245
|
+
status: 405,
|
|
246
|
+
headers: { Allow: allowHead ? [...allowed, "HEAD"].join(", ") : allowed.join(", ") }
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function getRequestHost(event, opts = {}) {
|
|
251
|
+
if (opts.xForwardedHost) {
|
|
252
|
+
const xForwardedHost = (event.req.headers.get("x-forwarded-host") || "").split(",").shift()?.trim();
|
|
253
|
+
if (xForwardedHost) return xForwardedHost;
|
|
254
|
+
}
|
|
255
|
+
return event.req.headers.get("host") || "";
|
|
256
|
+
}
|
|
257
|
+
function getRequestProtocol(event, opts = {}) {
|
|
258
|
+
if (opts.xForwardedProto) {
|
|
259
|
+
const forwardedProto = (event.req.headers.get("x-forwarded-proto") || "").split(",")[0].trim();
|
|
260
|
+
if (forwardedProto === "https") return "https";
|
|
261
|
+
if (forwardedProto === "http") return "http";
|
|
262
|
+
}
|
|
263
|
+
return (event.url || new URL(event.req.url)).protocol.slice(0, -1);
|
|
264
|
+
}
|
|
265
|
+
function getRequestURL(event, opts = {}) {
|
|
266
|
+
const url = new URL(event.url || event.req.url);
|
|
267
|
+
url.protocol = getRequestProtocol(event, opts);
|
|
268
|
+
if (opts.xForwardedHost) {
|
|
269
|
+
const host = getRequestHost(event, opts);
|
|
270
|
+
if (host) applyForwardedHost(url, host);
|
|
271
|
+
}
|
|
272
|
+
return url;
|
|
273
|
+
}
|
|
274
|
+
function getRequestIP(event, opts = {}) {
|
|
275
|
+
if (opts.xForwardedFor) {
|
|
276
|
+
const _header = event.req.headers.get("x-forwarded-for");
|
|
277
|
+
if (_header) {
|
|
278
|
+
const xForwardedFor = _header.split(",")[0].trim();
|
|
279
|
+
if (xForwardedFor) return xForwardedFor;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return event.req.context?.clientAddress || event.req.ip || void 0;
|
|
283
|
+
}
|
|
284
|
+
function applyForwardedHost(url, host) {
|
|
285
|
+
const sep = host.lastIndexOf(":");
|
|
286
|
+
const hasPort = sep > host.lastIndexOf("]");
|
|
287
|
+
const hostname = hasPort ? host.slice(0, sep) : host;
|
|
288
|
+
const prevHostname = url.hostname;
|
|
289
|
+
url.hostname = hostname;
|
|
290
|
+
if (url.hostname === prevHostname && hostname.toLowerCase() !== prevHostname) return;
|
|
291
|
+
const port = hasPort ? host.slice(sep + 1) : "";
|
|
292
|
+
url.port = /^\d{1,5}$/.test(port) && +port < 65536 ? port : "";
|
|
293
|
+
}
|
|
294
|
+
function safeHost(host) {
|
|
295
|
+
return host && !/[/\\?#@\s]/.test(host) ? host : "localhost";
|
|
296
|
+
}
|
|
297
|
+
function defineHandler(input) {
|
|
298
|
+
if (typeof input === "function") return handlerWithFetch(input);
|
|
299
|
+
const handler = input.handler || (input.fetch ? function _fetchHandler(event) {
|
|
300
|
+
return input.fetch(event.req);
|
|
301
|
+
} : NoHandler);
|
|
302
|
+
const composed = input.middleware?.length && composeHandler(input.middleware, handler);
|
|
303
|
+
const eventHandler = handlerWithFetch(composed || handler);
|
|
304
|
+
return Object.assign(eventHandler, input, composed && { fetch: eventHandler.fetch });
|
|
305
|
+
}
|
|
306
|
+
function defineValidatedHandler(def) {
|
|
307
|
+
if (!def.validate) return defineHandler(def);
|
|
308
|
+
return defineHandler({
|
|
309
|
+
...def,
|
|
310
|
+
handler: async function _validatedHandler(event) {
|
|
311
|
+
event.req = await validatedRequest(event.req, def.validate);
|
|
312
|
+
event.url = await validatedURL(event.url, def.validate);
|
|
313
|
+
return def.handler(event);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function handlerWithFetch(handler) {
|
|
318
|
+
if ("fetch" in handler) return handler;
|
|
319
|
+
return Object.assign(handler, { fetch: (req) => {
|
|
320
|
+
if (typeof req === "string") req = new URL(req, "http://_");
|
|
321
|
+
if (req instanceof URL) req = new Request(req);
|
|
322
|
+
const event = new H3Event(req);
|
|
323
|
+
try {
|
|
324
|
+
return Promise.resolve(toResponse(handler(event), event));
|
|
325
|
+
} catch (error) {
|
|
326
|
+
return Promise.resolve(toResponse(toError(error), event));
|
|
327
|
+
}
|
|
328
|
+
} });
|
|
329
|
+
}
|
|
330
|
+
function dynamicEventHandler(initial) {
|
|
331
|
+
let current = toEventHandler(initial);
|
|
332
|
+
return Object.assign(defineHandler(function _dynamicEventHandler(event) {
|
|
333
|
+
return current?.(event);
|
|
334
|
+
}), { set: (handler) => {
|
|
335
|
+
current = toEventHandler(handler);
|
|
336
|
+
} });
|
|
337
|
+
}
|
|
338
|
+
function defineLazyEventHandler(loader) {
|
|
339
|
+
let handler;
|
|
340
|
+
let promise;
|
|
341
|
+
return defineHandler(function lazyHandler(event) {
|
|
342
|
+
return handler ? handler(event) : (promise ??= Promise.resolve(loader()).then(function resolveLazyHandler(r) {
|
|
343
|
+
handler = toEventHandler(r) || toEventHandler(r.default);
|
|
344
|
+
if (typeof handler !== "function") throw new TypeError("Invalid lazy handler", { cause: { resolved: r } });
|
|
345
|
+
return handler;
|
|
346
|
+
})).then((r) => r(event));
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function toEventHandler(handler) {
|
|
350
|
+
if (typeof handler === "function") return handler;
|
|
351
|
+
if (typeof handler?.handler === "function" && handler.constructor?.["~h3"]) return handler.handler;
|
|
352
|
+
if (typeof handler?.fetch === "function") return function _fetchHandler(event) {
|
|
353
|
+
return handler.fetch(event.req);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const NoHandler = () => kNotFound;
|
|
357
|
+
var H3Core = class {
|
|
358
|
+
static "~h3" = true;
|
|
359
|
+
config;
|
|
360
|
+
"~middleware";
|
|
361
|
+
"~routes" = [];
|
|
362
|
+
"~dispatch";
|
|
363
|
+
"~composed";
|
|
364
|
+
constructor(config = {}) {
|
|
365
|
+
this["~middleware"] = [];
|
|
366
|
+
this.config = config;
|
|
367
|
+
this.fetch = this.fetch.bind(this);
|
|
368
|
+
this.handler = this.handler.bind(this);
|
|
369
|
+
}
|
|
370
|
+
fetch(request) {
|
|
371
|
+
return this["~request"](request);
|
|
372
|
+
}
|
|
373
|
+
handler(event) {
|
|
374
|
+
const route = this["~findRoute"](event);
|
|
375
|
+
if (route) {
|
|
376
|
+
event.context.params = route.params;
|
|
377
|
+
event.context.matchedRoute = route.data;
|
|
378
|
+
}
|
|
379
|
+
return (this["~dispatch"] ??= createDispatcher(this))(event, route);
|
|
380
|
+
}
|
|
381
|
+
"~request"(request, context) {
|
|
382
|
+
const event = new H3Event(request, context, this);
|
|
383
|
+
let handlerRes;
|
|
384
|
+
try {
|
|
385
|
+
if (event[kMalformedURL] && !this.config.allowMalformedURL) throw new HTTPError({
|
|
386
|
+
status: 400,
|
|
387
|
+
message: "Bad Request"
|
|
388
|
+
});
|
|
389
|
+
if (this.config.onRequest) {
|
|
390
|
+
const hookRes = this.config.onRequest(event);
|
|
391
|
+
handlerRes = typeof hookRes?.then === "function" ? hookRes.then(() => this.handler(event)) : this.handler(event);
|
|
392
|
+
} else handlerRes = this.handler(event);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
handlerRes = Promise.reject(error);
|
|
395
|
+
}
|
|
396
|
+
return toResponse(handlerRes, event, this.config);
|
|
397
|
+
}
|
|
398
|
+
"~findRoute"(_event) {}
|
|
399
|
+
"~addRoute"(_route) {
|
|
400
|
+
this["~routes"].push(_route);
|
|
401
|
+
}
|
|
402
|
+
"~getMiddleware"(_event, _route) {
|
|
403
|
+
return this["~middleware"];
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
function createDispatcher(app) {
|
|
407
|
+
if (app["~getMiddleware"] !== H3Core.prototype["~getMiddleware"]) return (event, route) => callMiddleware(event, app["~getMiddleware"](event, route || void 0), routeHandler(route));
|
|
408
|
+
const middleware = app["~middleware"];
|
|
409
|
+
if (middleware.length === 0) return (event, route) => routeHandler(route)(event);
|
|
410
|
+
const composed = app["~composed"] ??= composeMiddleware(middleware);
|
|
411
|
+
return (event, route) => composed(event, routeHandler(route));
|
|
412
|
+
}
|
|
413
|
+
function routeHandler(route) {
|
|
414
|
+
const data = route?.data;
|
|
415
|
+
if (!data) return NoHandler;
|
|
416
|
+
return data.middleware?.length ? data["~composed"] ??= composeHandler(data.middleware, data.handler) : data.handler;
|
|
417
|
+
}
|
|
418
|
+
const H3 = /* @__PURE__ */ (() => {
|
|
419
|
+
class H3 extends H3Core {
|
|
420
|
+
"~rou3";
|
|
421
|
+
constructor(config = {}) {
|
|
422
|
+
super(config);
|
|
423
|
+
this["~rou3"] = createRouter();
|
|
424
|
+
this.request = this.request.bind(this);
|
|
425
|
+
config.plugins?.forEach((plugin) => plugin(this));
|
|
426
|
+
}
|
|
427
|
+
register(plugin) {
|
|
428
|
+
plugin(this);
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
request(_req, _init, context) {
|
|
432
|
+
return this["~request"](toRequest(_req, _init), context);
|
|
433
|
+
}
|
|
434
|
+
mount(base, input) {
|
|
435
|
+
base = !base || base === "/" ? "" : normalizeRoute(base).replace(/\/$/, "");
|
|
436
|
+
if ("handler" in input) {
|
|
437
|
+
if (input["~middleware"].length > 0) {
|
|
438
|
+
this["~middleware"].push((event, next) => {
|
|
439
|
+
const originalPathname = event.url.pathname;
|
|
440
|
+
if (!originalPathname.startsWith(base) || originalPathname.length > base.length && originalPathname[base.length] !== "/") return next();
|
|
441
|
+
if (hasEmptySegmentAfterBase(originalPathname, base)) throw new HTTPError({ status: 404 });
|
|
442
|
+
event.url.pathname = stripBase(originalPathname, base);
|
|
443
|
+
const restore = () => {
|
|
444
|
+
event.url.pathname = originalPathname;
|
|
445
|
+
};
|
|
446
|
+
try {
|
|
447
|
+
const result = (input["~composed"] ??= composeMiddleware(input["~middleware"]))(event, () => {
|
|
448
|
+
restore();
|
|
449
|
+
return next();
|
|
450
|
+
});
|
|
451
|
+
if (typeof result?.then === "function") return Promise.resolve(result).finally(restore);
|
|
452
|
+
restore();
|
|
453
|
+
return result;
|
|
454
|
+
} catch (err) {
|
|
455
|
+
restore();
|
|
456
|
+
throw err;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
this["~dispatch"] = this["~composed"] = void 0;
|
|
460
|
+
}
|
|
461
|
+
for (const r of input["~routes"]) this["~addRoute"]({
|
|
462
|
+
...r,
|
|
463
|
+
route: base + r.route
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
const fetchHandler = "fetch" in input ? input.fetch : input;
|
|
467
|
+
this.all(`${base}/**`, function _mountedMiddleware(event) {
|
|
468
|
+
if (hasEmptySegmentAfterBase(event.url.pathname, base)) throw new HTTPError({ status: 404 });
|
|
469
|
+
return fetchHandler(requestWithBaseURL(event.req, base, { url: event.url }));
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
on(method, route, handler, opts) {
|
|
475
|
+
const _method = (method || "").toUpperCase();
|
|
476
|
+
route = normalizeRoute(route);
|
|
477
|
+
this["~addRoute"]({
|
|
478
|
+
method: _method,
|
|
479
|
+
route,
|
|
480
|
+
handler: toEventHandler(handler),
|
|
481
|
+
middleware: opts?.middleware,
|
|
482
|
+
meta: {
|
|
483
|
+
...handler.meta,
|
|
484
|
+
...opts?.meta
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
all(route, handler, opts) {
|
|
490
|
+
return this.on("", route, handler, opts);
|
|
491
|
+
}
|
|
492
|
+
"~findRoute"(_event) {
|
|
493
|
+
const match = findRoute(this["~rou3"], _event.req.method, _event.url.pathname);
|
|
494
|
+
if (match === void 0 && _event.req.method === "HEAD") return findRoute(this["~rou3"], "GET", _event.url.pathname);
|
|
495
|
+
return match;
|
|
496
|
+
}
|
|
497
|
+
"~addRoute"(_route) {
|
|
498
|
+
addRoute(this["~rou3"], _route.method, _route.route, _route);
|
|
499
|
+
super["~addRoute"](_route);
|
|
500
|
+
}
|
|
501
|
+
use(arg1, arg2, arg3) {
|
|
502
|
+
let route;
|
|
503
|
+
let fn;
|
|
504
|
+
let opts;
|
|
505
|
+
if (typeof arg1 === "string") {
|
|
506
|
+
route = arg1;
|
|
507
|
+
fn = arg2;
|
|
508
|
+
opts = arg3;
|
|
509
|
+
} else {
|
|
510
|
+
fn = arg1;
|
|
511
|
+
opts = arg2;
|
|
512
|
+
}
|
|
513
|
+
if (typeof fn !== "function" && "handler" in fn) return this.mount(route || "", fn);
|
|
514
|
+
this["~middleware"].push(normalizeMiddleware(fn, {
|
|
515
|
+
...opts,
|
|
516
|
+
route
|
|
517
|
+
}));
|
|
518
|
+
this["~dispatch"] = this["~composed"] = void 0;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
for (const method of [
|
|
523
|
+
"GET",
|
|
524
|
+
"POST",
|
|
525
|
+
"PUT",
|
|
526
|
+
"DELETE",
|
|
527
|
+
"PATCH",
|
|
528
|
+
"HEAD",
|
|
529
|
+
"OPTIONS",
|
|
530
|
+
"CONNECT",
|
|
531
|
+
"TRACE",
|
|
532
|
+
"QUERY"
|
|
533
|
+
]) H3Core.prototype[method.toLowerCase()] = function(route, handler, opts) {
|
|
534
|
+
return this.on(method, route, handler, opts);
|
|
535
|
+
};
|
|
536
|
+
return H3;
|
|
537
|
+
})();
|
|
538
|
+
function matchETag(ifNoneMatch, etag) {
|
|
539
|
+
if (ifNoneMatch.trim() === "*") return true;
|
|
540
|
+
const target = opaqueTag(etag);
|
|
541
|
+
return splitETags(ifNoneMatch).some((tag) => opaqueTag(tag.trim()) === target);
|
|
542
|
+
}
|
|
543
|
+
function isCacheMatch(headers, validators) {
|
|
544
|
+
const ifNoneMatch = validators.ifNoneMatch ?? headers.get("if-none-match");
|
|
545
|
+
if (ifNoneMatch) return !!validators.etag && matchETag(ifNoneMatch, validators.etag);
|
|
546
|
+
if (validators.lastModified) {
|
|
547
|
+
const ifModifiedSince = validators.ifModifiedSince ?? headers.get("if-modified-since");
|
|
548
|
+
return !!ifModifiedSince && new Date(ifModifiedSince) >= validators.lastModified;
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
function opaqueTag(tag) {
|
|
553
|
+
return tag.startsWith("W/") ? tag.slice(2) : tag;
|
|
554
|
+
}
|
|
555
|
+
function splitETags(value) {
|
|
556
|
+
const tags = [];
|
|
557
|
+
let current = "";
|
|
558
|
+
let inQuotes = false;
|
|
559
|
+
for (const ch of value) if (ch === "\"") {
|
|
560
|
+
inQuotes = !inQuotes;
|
|
561
|
+
current += ch;
|
|
562
|
+
} else if (ch === "," && !inQuotes) {
|
|
563
|
+
tags.push(current);
|
|
564
|
+
current = "";
|
|
565
|
+
} else current += ch;
|
|
566
|
+
tags.push(current);
|
|
567
|
+
return tags;
|
|
568
|
+
}
|
|
569
|
+
const RE_PRIVATE = /(?:^|,)\s*(?:private|no-store)(?:\s*=|\s*,|\s*$)/i;
|
|
570
|
+
const RE_PUBLIC = /(?:^|,)\s*public(?:\s*=|\s*,|\s*$)/i;
|
|
571
|
+
function handleCacheHeaders(event, opts) {
|
|
572
|
+
const cacheControls = [...opts.cacheControls || []];
|
|
573
|
+
const joined = cacheControls.join(",");
|
|
574
|
+
const isPrivate = RE_PRIVATE.test(joined);
|
|
575
|
+
if (!isPrivate && !RE_PUBLIC.test(joined)) cacheControls.unshift("public");
|
|
576
|
+
if (opts.maxAge !== void 0) {
|
|
577
|
+
cacheControls.push(`max-age=${+opts.maxAge}`);
|
|
578
|
+
if (!isPrivate) cacheControls.push(`s-maxage=${+opts.maxAge}`);
|
|
579
|
+
}
|
|
580
|
+
if (opts.etag) event.res.headers.set("etag", opts.etag);
|
|
581
|
+
let lastModified;
|
|
582
|
+
if (opts.modifiedTime) {
|
|
583
|
+
lastModified = new Date(opts.modifiedTime);
|
|
584
|
+
lastModified.setMilliseconds(0);
|
|
585
|
+
event.res.headers.set("last-modified", lastModified.toUTCString());
|
|
586
|
+
}
|
|
587
|
+
event.res.headers.set("cache-control", cacheControls.join(", "));
|
|
588
|
+
if (isCacheMatch(event.req.headers, {
|
|
589
|
+
etag: opts.etag,
|
|
590
|
+
lastModified,
|
|
591
|
+
ifNoneMatch: opts.ifNoneMatch,
|
|
592
|
+
ifModifiedSince: opts.ifModifiedSince
|
|
593
|
+
})) {
|
|
594
|
+
event.res.status = 304;
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
export { H3, H3Core, assertMethod, defineHandler, defineLazyEventHandler, defineValidatedHandler, dynamicEventHandler, getEventContext, getQuery, getRequestHost, getRequestIP, getRequestProtocol, getRequestURL, getRouterParam, getRouterParams, getValidatedQuery, getValidatedRouterParams, handleCacheHeaders, isCacheMatch, isEvent, isHTTPEvent, isMethod, mockEvent, requestWithBaseURL, requestWithURL, toEventHandler, toRequest, validateData };
|
package/dist/cache2.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { HTTPResponse } from "./response.mjs";
|
|
2
|
+
const CACHE_GROUP = "h3/route-rules";
|
|
3
|
+
let dispatching;
|
|
4
|
+
let scopeCounter = 0;
|
|
5
|
+
function createCacheRuleHandler(opts) {
|
|
6
|
+
const defineCached = opts.defineCachedHandler;
|
|
7
|
+
const shouldBypass = opts.shouldBypass;
|
|
8
|
+
const defaults = opts.defaults;
|
|
9
|
+
const id = opts.id;
|
|
10
|
+
const cachedHandlers = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
return {
|
|
12
|
+
order: 3,
|
|
13
|
+
handler: (m) => function cacheRouteRule(event, next) {
|
|
14
|
+
if (shouldBypass?.(event)) return next();
|
|
15
|
+
const matchedRoute = event.context.matchedRoute;
|
|
16
|
+
if (!matchedRoute) return next();
|
|
17
|
+
const handler = matchedRoute["~composed"] ?? matchedRoute.handler;
|
|
18
|
+
const method = event.req.method;
|
|
19
|
+
const key = `${method === "GET" || method === "HEAD" ? method : "*"}:${m.route}:${matchedRoute.route}`;
|
|
20
|
+
let entry = cachedHandlers.get(handler);
|
|
21
|
+
if (!entry) {
|
|
22
|
+
entry = {
|
|
23
|
+
scope: id ?? `#${++scopeCounter}`,
|
|
24
|
+
byRoute: /* @__PURE__ */ new Map()
|
|
25
|
+
};
|
|
26
|
+
cachedHandlers.set(handler, entry);
|
|
27
|
+
}
|
|
28
|
+
let cachedHandler = entry.byRoute.get(key);
|
|
29
|
+
if (!cachedHandler) {
|
|
30
|
+
cachedHandler = defineCached(handler, {
|
|
31
|
+
group: CACHE_GROUP,
|
|
32
|
+
name: `${entry.scope}:${key}`,
|
|
33
|
+
...defaults,
|
|
34
|
+
...m.options
|
|
35
|
+
});
|
|
36
|
+
entry.byRoute.set(key, cachedHandler);
|
|
37
|
+
}
|
|
38
|
+
let active = dispatching?.get(event);
|
|
39
|
+
if (active?.has(handler)) return next();
|
|
40
|
+
if (!active) (dispatching ??= /* @__PURE__ */ new WeakMap()).set(event, active = /* @__PURE__ */ new Set());
|
|
41
|
+
active.add(handler);
|
|
42
|
+
const res = cachedHandler(event);
|
|
43
|
+
return typeof res?.then === "function" ? res.then(normalizeResult) : normalizeResult(res);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function normalizeResult(res) {
|
|
48
|
+
return res === void 0 ? new HTTPResponse(null) : res;
|
|
49
|
+
}
|
|
50
|
+
export { createCacheRuleHandler };
|