@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,94 @@
|
|
|
1
|
+
import { RouteRuleConfig } from "../h3.mjs";
|
|
2
|
+
import { MatcherMemoizeOptions } from "../match.mjs";
|
|
3
|
+
|
|
4
|
+
export declare const DEFAULT_RUNTIME_RULES: Readonly<Record<string, RuntimeRuleImport>>;
|
|
5
|
+
/** Module and optional named export for a generated rule-handler import. */
|
|
6
|
+
type RuntimeRuleImport = string | RuntimeRuleImportSpec;
|
|
7
|
+
interface RuntimeRuleImportSpec {
|
|
8
|
+
source: string;
|
|
9
|
+
/**
|
|
10
|
+
* Named export within `source`; must be a valid JS identifier (becomes an
|
|
11
|
+
* import binding in generated code).
|
|
12
|
+
* @default the rule key
|
|
13
|
+
*/
|
|
14
|
+
export?: string;
|
|
15
|
+
}
|
|
16
|
+
interface CompileRouteRulesOptions {
|
|
17
|
+
/** Base URL prefix for all rule patterns (trailing slash trimmed). */
|
|
18
|
+
baseURL?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Identifier prefix for imported handlers in generated code (handler `name`
|
|
21
|
+
* binds as `<prefix>$<name>`).
|
|
22
|
+
* @default "__ruleHandlers__"
|
|
23
|
+
*/
|
|
24
|
+
handlersImportName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Runtime rules keyed by rule name, merged **over** `DEFAULT_RUNTIME_RULES`
|
|
27
|
+
* (list only additions/overrides). Keys bind as JS identifiers in generated
|
|
28
|
+
* code.
|
|
29
|
+
* @default DEFAULT_RUNTIME_RULES
|
|
30
|
+
*/
|
|
31
|
+
runtimeRules?: Record<string, RuntimeRuleImport>;
|
|
32
|
+
/**
|
|
33
|
+
* Pre-merge each pattern's subsumption chain at compile time (exact, but
|
|
34
|
+
* requires a **chain-clean** rule set). Unlike the runtime matcher, the
|
|
35
|
+
* compiler is fail-safe: a non-chain-clean set emits a `console.warn` and
|
|
36
|
+
* falls back to plain compilation instead of throwing.
|
|
37
|
+
*/
|
|
38
|
+
preMerge?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Optional generated matcher export. A string sets its name; object form can
|
|
42
|
+
* also enable memoization.
|
|
43
|
+
*/
|
|
44
|
+
type MatcherExport = boolean | string | {
|
|
45
|
+
name?: string;
|
|
46
|
+
memoize?: boolean | MatcherMemoizeOptions;
|
|
47
|
+
};
|
|
48
|
+
/** Options for compiling a complete route-rules module. */
|
|
49
|
+
interface CompileModuleOptions extends CompileRouteRulesOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Also emit a ready-to-use matcher export. See {@link MatcherExport}.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
matcher?: MatcherExport;
|
|
55
|
+
}
|
|
56
|
+
/** Compiled module source, also split into composable imports and body. */
|
|
57
|
+
interface CompiledRouteRules {
|
|
58
|
+
/**
|
|
59
|
+
* Handler import statements ({@link compileHandlersImport} output); empty
|
|
60
|
+
* for a data-only rule set. Includes the matcher infra import when one is
|
|
61
|
+
* requested.
|
|
62
|
+
*/
|
|
63
|
+
imports: string;
|
|
64
|
+
/**
|
|
65
|
+
* `findRouteRules` export declaration (no imports), plus the matcher
|
|
66
|
+
* declaration when requested. References bindings {@link imports} brings
|
|
67
|
+
* into scope.
|
|
68
|
+
*/
|
|
69
|
+
body: string;
|
|
70
|
+
/** The complete module source — {@link imports} then {@link body}. Same as `toString()`. */
|
|
71
|
+
code: string;
|
|
72
|
+
/** The complete module source ({@link code}), so the result interpolates as a string. */
|
|
73
|
+
toString(): string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Compile a rule set into a complete ESM module exporting `findRouteRules`
|
|
77
|
+
* (and, with `matcher`, a ready-to-use matcher). Input is normalized
|
|
78
|
+
* internally — pass authored config or already-normalized rules.
|
|
79
|
+
*/
|
|
80
|
+
export declare function compileRouteRules(config: Record<string, RouteRuleConfig>, opts?: CompileModuleOptions): CompiledRouteRules;
|
|
81
|
+
/**
|
|
82
|
+
* Compile a rule set into the source of a `findRouteRules(method, pathname)`
|
|
83
|
+
* function expression. Entries reference handler constructors as
|
|
84
|
+
* `<handlersImportName>$<name>` bindings — pair with
|
|
85
|
+
* {@link compileHandlersImport} (which imports exactly those names).
|
|
86
|
+
*/
|
|
87
|
+
export declare function compileFindRouteRules(config: Record<string, RouteRuleConfig>, opts?: CompileRouteRulesOptions): string;
|
|
88
|
+
/**
|
|
89
|
+
* Import statement for exactly the handlers the rule set references (empty
|
|
90
|
+
* string if none), keeping unused handlers' deps (e.g. ocache) tree-shakeable.
|
|
91
|
+
* Input is normalized internally, so e.g. an `swr` shortcut counts as `cache`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function compileHandlersImport(config: Record<string, RouteRuleConfig>, opts?: CompileRouteRulesOptions): string;
|
|
94
|
+
export type { CompileModuleOptions, CompileRouteRulesOptions, CompiledRouteRules, MatcherExport, RuntimeRuleImport, RuntimeRuleImportSpec };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createRulesRouter, normalizeRouteRules, parseRouteKey } from "../normalize.mjs";
|
|
2
|
+
import { compareRoutes } from "rou3";
|
|
3
|
+
import { compileRouterToString } from "rou3/compiler";
|
|
4
|
+
const DEFAULT_RUNTIME_RULES = Object.freeze({
|
|
5
|
+
headers: "h3/rules",
|
|
6
|
+
redirect: "h3/rules",
|
|
7
|
+
proxy: "h3/rules/proxy",
|
|
8
|
+
cache: "h3/rules/cache",
|
|
9
|
+
cors: "h3/rules"
|
|
10
|
+
});
|
|
11
|
+
function resolveRuntimeRules(runtimeRules) {
|
|
12
|
+
return runtimeRules ? {
|
|
13
|
+
...DEFAULT_RUNTIME_RULES,
|
|
14
|
+
...runtimeRules
|
|
15
|
+
} : DEFAULT_RUNTIME_RULES;
|
|
16
|
+
}
|
|
17
|
+
function isRuntimeRule(name, runtimeRules) {
|
|
18
|
+
return Object.hasOwn(runtimeRules, name);
|
|
19
|
+
}
|
|
20
|
+
function resolveRuntimeRule(name, runtimeRules) {
|
|
21
|
+
const entry = runtimeRules[name];
|
|
22
|
+
return typeof entry === "string" ? {
|
|
23
|
+
source: entry,
|
|
24
|
+
export: name
|
|
25
|
+
} : {
|
|
26
|
+
source: entry.source,
|
|
27
|
+
export: entry.export ?? name
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function serializePreMergedRouteRules(data, ns, runtimeRules) {
|
|
31
|
+
const resets = data.resets?.length ? `,resets:${JSON.stringify(data.resets)}` : "";
|
|
32
|
+
return `{route:${JSON.stringify(data.route)},rank:${data.rank}${resets},rules:${serializeRouteRuleEntries(data.rules, ns, runtimeRules)}}`;
|
|
33
|
+
}
|
|
34
|
+
function serializeRouteRuleEntries(entries, ns, runtimeRules) {
|
|
35
|
+
return `[${entries.map((entry) => {
|
|
36
|
+
const runtime = isRuntimeRule(entry.name, runtimeRules);
|
|
37
|
+
if (runtime) assertHandlerBinding(entry.name, "runtime rule name");
|
|
38
|
+
assertSerializableOptions(entry.options, entry, "options");
|
|
39
|
+
return [
|
|
40
|
+
`name:${JSON.stringify(entry.name)}`,
|
|
41
|
+
`route:${JSON.stringify(entry.route)}`,
|
|
42
|
+
runtime && `handler:${ns}$${entry.name}`,
|
|
43
|
+
`options:${JSON.stringify(entry.options)}`,
|
|
44
|
+
entry.rank && `rank:${entry.rank}`,
|
|
45
|
+
entry.paramRoutes && `paramRoutes:${JSON.stringify(entry.paramRoutes)}`
|
|
46
|
+
].filter(Boolean).join(",");
|
|
47
|
+
}).map((fields) => `{${fields}}`).join(",")}]`;
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_MATCHER_EXPORT_NAME = "matcher";
|
|
50
|
+
function compileMatcherExport(matcher, overridePredicate) {
|
|
51
|
+
if (!matcher) return null;
|
|
52
|
+
const spec = typeof matcher === "string" ? { name: matcher } : matcher === true ? {} : matcher;
|
|
53
|
+
const name = spec.name || DEFAULT_MATCHER_EXPORT_NAME;
|
|
54
|
+
assertHandlerBinding(name, "matcher export name");
|
|
55
|
+
const { memoize } = spec;
|
|
56
|
+
let expr = `createMatcherFromFind(findRouteRules, ${overridePredicate})`;
|
|
57
|
+
if (memoize) {
|
|
58
|
+
const max = memoize === true ? void 0 : memoize.max;
|
|
59
|
+
expr = max === void 0 ? `memoizeRouteRulesMatcher(${expr})` : `memoizeRouteRulesMatcher(${expr}, { max: ${JSON.stringify(max)} })`;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
imports: `import { createMatcherFromFind${memoize ? ", memoizeRouteRulesMatcher" : ""} } from "h3/rules";`,
|
|
63
|
+
body: `export const ${name} = ${expr};\n`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function compileOverridePredicate(routes) {
|
|
67
|
+
const sorted = [...routes].sort();
|
|
68
|
+
const allowed = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const current of sorted) for (const incoming of sorted) {
|
|
70
|
+
if (current === incoming) continue;
|
|
71
|
+
const rel = compareRoutes(current, incoming);
|
|
72
|
+
if (rel === "superset" || rel === "equal") {
|
|
73
|
+
let list = allowed.get(current);
|
|
74
|
+
if (!list) allowed.set(current, list = []);
|
|
75
|
+
list.push(incoming);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (allowed.size === 0) return "(a, b) => a === b";
|
|
79
|
+
return `/* @__PURE__ */ (() => { const t = new Map([${[...allowed].map(([current, list]) => `[${JSON.stringify(current)}, new Set(${JSON.stringify(list)})]`).join(", ")}]); return (a, b) => a === b || (t.get(a)?.has(b) ?? false); })()`;
|
|
80
|
+
}
|
|
81
|
+
function assertHandlerBinding(name, what) {
|
|
82
|
+
if (!JS_IDENTIFIER_RE.test(name)) throw new Error(`[h3] rules: compiler: ${what} \`${name}\` is not a valid JS identifier — it is used as a binding in generated code`);
|
|
83
|
+
}
|
|
84
|
+
const JS_IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
|
|
85
|
+
function assertSerializableOptions(value, entry, path) {
|
|
86
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return;
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
for (const [i, item] of value.entries()) assertSerializableOptions(item, entry, `${path}[${i}]`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const proto = typeof value === "object" ? Object.getPrototypeOf(value) : void 0;
|
|
92
|
+
if (proto === Object.prototype || proto === null) {
|
|
93
|
+
for (const [key, item] of Object.entries(value)) {
|
|
94
|
+
if (key === "__proto__") throw new Error(`[h3] rules: compiler: \`${entry.name}\` rule for \`${entry.route}\` has a \`__proto__\` key at \`${path}\` — it cannot be embedded as a JS object literal without changing the object's prototype (its JSON.stringify output would diverge from the runtime matcher); rename or remove it`);
|
|
95
|
+
assertSerializableOptions(item, entry, `${path}.${key}`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const kind = typeof value === "object" ? value.constructor?.name || "object" : typeof value;
|
|
100
|
+
throw new Error(`[h3] rules: compiler: \`${entry.name}\` rule for \`${entry.route}\` has a non-JSON-serializable value at \`${path}\` (${kind}) — compiled rule options must survive a JSON round-trip`);
|
|
101
|
+
}
|
|
102
|
+
function compileRouteRules(config, opts = {}) {
|
|
103
|
+
const ctx = resolveCompileCtx(config, opts);
|
|
104
|
+
const handlerImports = emitHandlersImport(ctx);
|
|
105
|
+
const matcherExport = opts.matcher ? compileMatcherExport(opts.matcher, compileOverridePredicate(collectRoutes(ctx))) : null;
|
|
106
|
+
const imports = [handlerImports, matcherExport?.imports].filter(Boolean).join("\n");
|
|
107
|
+
const find = `export const findRouteRules = ${emitFindRouteRules(ctx)};\n`;
|
|
108
|
+
const body = matcherExport ? find + matcherExport.body : find;
|
|
109
|
+
const code = `${imports ? imports + "\n" : ""}${body}`;
|
|
110
|
+
return {
|
|
111
|
+
imports,
|
|
112
|
+
body,
|
|
113
|
+
code,
|
|
114
|
+
toString: () => code
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function compileFindRouteRules(config, opts = {}) {
|
|
118
|
+
return emitFindRouteRules(resolveCompileCtx(config, opts));
|
|
119
|
+
}
|
|
120
|
+
function compileHandlersImport(config, opts = {}) {
|
|
121
|
+
return emitHandlersImport(resolveCompileCtx(config, opts));
|
|
122
|
+
}
|
|
123
|
+
function resolveCompileCtx(config, opts) {
|
|
124
|
+
const rules = normalizeRouteRules(config);
|
|
125
|
+
const ns = opts.handlersImportName || "__ruleHandlers__";
|
|
126
|
+
assertHandlerBinding(ns, "handlersImportName");
|
|
127
|
+
const ctx = {
|
|
128
|
+
rules,
|
|
129
|
+
preMerge: false,
|
|
130
|
+
runtimeRules: resolveRuntimeRules(opts.runtimeRules),
|
|
131
|
+
ns,
|
|
132
|
+
baseURL: opts.baseURL
|
|
133
|
+
};
|
|
134
|
+
if (opts.preMerge) try {
|
|
135
|
+
ctx.router = createRulesRouter(rules, {}, opts.baseURL, true);
|
|
136
|
+
ctx.preMerge = true;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn(`[h3] rules: compiler: preMerge could not be applied — falling back to plain compilation.\n ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
return ctx;
|
|
141
|
+
}
|
|
142
|
+
function emitFindRouteRules(ctx) {
|
|
143
|
+
const router = ctx.router ?? createRulesRouter(ctx.rules, {}, ctx.baseURL, ctx.preMerge);
|
|
144
|
+
return compileRouterToString(router, {
|
|
145
|
+
matchAll: true,
|
|
146
|
+
serialize: (data) => Array.isArray(data) ? serializeRouteRuleEntries(data, ctx.ns, ctx.runtimeRules) : serializePreMergedRouteRules(data, ctx.ns, ctx.runtimeRules)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function emitHandlersImport(ctx) {
|
|
150
|
+
const names = usedRuleHandlerNames(ctx);
|
|
151
|
+
if (names.length === 0) return "";
|
|
152
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
153
|
+
for (const name of names) {
|
|
154
|
+
assertHandlerBinding(name, "runtime rule name");
|
|
155
|
+
const { source, export: exportName } = resolveRuntimeRule(name, ctx.runtimeRules);
|
|
156
|
+
assertHandlerBinding(exportName, "runtime rule export");
|
|
157
|
+
let specs = bySource.get(source);
|
|
158
|
+
if (!specs) bySource.set(source, specs = []);
|
|
159
|
+
specs.push(`${exportName} as ${ctx.ns}$${name}`);
|
|
160
|
+
}
|
|
161
|
+
return [...bySource.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([source, specs]) => `import { ${specs.join(", ")} } from ${JSON.stringify(source)};`).join("\n");
|
|
162
|
+
}
|
|
163
|
+
function collectRoutes(ctx) {
|
|
164
|
+
const routes = /* @__PURE__ */ new Set();
|
|
165
|
+
for (const key in ctx.rules) routes.add(parseRouteKey(key).path);
|
|
166
|
+
return [...routes];
|
|
167
|
+
}
|
|
168
|
+
function usedRuleHandlerNames(ctx) {
|
|
169
|
+
const used = /* @__PURE__ */ new Set();
|
|
170
|
+
for (const key in ctx.rules) for (const [name, options] of Object.entries(ctx.rules[key])) if (options !== void 0 && (options !== false || !ctx.preMerge) && isRuntimeRule(name, ctx.runtimeRules)) used.add(name);
|
|
171
|
+
return [...used].sort();
|
|
172
|
+
}
|
|
173
|
+
export { DEFAULT_RUNTIME_RULES, compileFindRouteRules, compileHandlersImport, compileRouteRules };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BuiltinRouteRules, CacheRuleOptions, EventHandler, H3Event, HTTPStatus, MatchResult, MatchedRouteRule, MatchedRouteRules, Middleware, NormalizedRouteRules, ProxyRuleOptions, RedirectRuleOptions, ResolvedRouteRules, RouteRuleConfig, RouteRules, RuleHandler, RuleHandlers } from "../h3.mjs";
|
|
2
|
+
import { FindRouteRules, MatcherMemoizeOptions, RouteRuleEntry, RouteRuleLayer, RouteRulesMatcher, RouteRulesMatcherOptions, createMatcherFromFind, createRouteRulesMatcher, memoizeRouteRulesMatcher, mergeMatchedRouteRules } from "../match.mjs";
|
|
3
|
+
|
|
4
|
+
interface RouteRulesOptions extends RouteRulesMatcherOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Memoize matches by method and pathname. Enabled by default with a 1024-entry
|
|
7
|
+
* cap; shared results must be treated as read-only.
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
memoize?: boolean | MatcherMemoizeOptions;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Match route rules, expose merged options on `event.context.routeRules`, and
|
|
14
|
+
* run their middleware before the route handler.
|
|
15
|
+
*
|
|
16
|
+
* Results are memoized and shared by default; treat exposed rule options as
|
|
17
|
+
* read-only or disable memoization.
|
|
18
|
+
*/
|
|
19
|
+
export declare function routeRules(config: Record<string, RouteRuleConfig>, opts?: RouteRulesOptions): Middleware;
|
|
20
|
+
/**
|
|
21
|
+
* Normalize authored route rules by expanding shortcuts, canonicalizing keys,
|
|
22
|
+
* and validating built-in options. Custom rules pass through unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeRouteRules(config: Record<string, RouteRuleConfig>): Record<string, NormalizedRouteRules>;
|
|
25
|
+
/**
|
|
26
|
+
* Default handler registry. Cache and proxy handlers are opt-in from their
|
|
27
|
+
* subpath exports; set either to `undefined` explicitly for data-only rules.
|
|
28
|
+
*/
|
|
29
|
+
export declare const ruleHandlers: RuleHandlers;
|
|
30
|
+
export declare const headers: RuleHandler<"headers">;
|
|
31
|
+
export declare const redirect: RuleHandler<"redirect">;
|
|
32
|
+
export declare const cors: RuleHandler<"cors">;
|
|
33
|
+
/** Wrap an event handler with an injected cache implementation. */
|
|
34
|
+
type DefineCachedHandler = (handler: EventHandler, opts: CacheRuleOptions) => EventHandler;
|
|
35
|
+
/** Options for {@link createCacheRuleHandler}. */
|
|
36
|
+
interface CacheRuleHandlerOptions {
|
|
37
|
+
/** Creates the cached wrapper for a matched route handler. */
|
|
38
|
+
defineCachedHandler: DefineCachedHandler;
|
|
39
|
+
/**
|
|
40
|
+
* Requests the implementation never reads or writes a cache entry for.
|
|
41
|
+
* The rule continues the middleware chain for them instead of dispatching the
|
|
42
|
+
* matched route itself, so an uncacheable request reaches the rest of the app
|
|
43
|
+
* the way a CDN passes one through to its origin. Without it, *every* request
|
|
44
|
+
* to a matched route terminates the chain, whether or not it is cached.
|
|
45
|
+
*/
|
|
46
|
+
shouldBypass?: (event: H3Event) => boolean;
|
|
47
|
+
/** Default options merged into every cache rule (rule options win). */
|
|
48
|
+
defaults?: CacheRuleOptions;
|
|
49
|
+
/**
|
|
50
|
+
* Stable cache-key scope. By default, a process-unique scope isolates apps
|
|
51
|
+
* but prevents persistent cache sharing across processes.
|
|
52
|
+
*/
|
|
53
|
+
id?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a `cache` rule handler from an injected cache wrapper.
|
|
57
|
+
*
|
|
58
|
+
* Cache keys are isolated by handler, method, and route unless `id` or an
|
|
59
|
+
* explicit cache `name` opts into sharing.
|
|
60
|
+
*
|
|
61
|
+
* Registering `routeRules()` as *route* middleware puts this rule inside the
|
|
62
|
+
* handler it dispatches; the re-entrant pass falls through (see {@link dispatching}).
|
|
63
|
+
*
|
|
64
|
+
* Register `routeRules()` after every global middleware that must run for a
|
|
65
|
+
* cached route: this handler dispatches the matched route handler itself rather
|
|
66
|
+
* than calling `next()`, so global middleware registered after `routeRules()` is
|
|
67
|
+
* skipped on *every* cacheable request to a `cache`-matched route — misses
|
|
68
|
+
* included, not only hits. Per-route middleware is unaffected (it is part of the
|
|
69
|
+
* dispatched `~composed` pair).
|
|
70
|
+
*
|
|
71
|
+
* Requests the implementation reports as uncacheable through
|
|
72
|
+
* {@link CacheRuleHandlerOptions.shouldBypass} continue down the chain instead
|
|
73
|
+
* of being dispatched from inside the rule, so which global middleware runs for
|
|
74
|
+
* a matched route can depend on the request.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createCacheRuleHandler(opts: CacheRuleHandlerOptions): RuleHandler<"cache">;
|
|
77
|
+
export { type BuiltinRouteRules, type CacheRuleHandlerOptions, type CacheRuleOptions, type DefineCachedHandler, type FindRouteRules, type HTTPStatus, type MatchResult, type MatchedRouteRule, type MatchedRouteRules, type MatcherMemoizeOptions, type NormalizedRouteRules, type ProxyRuleOptions, type RedirectRuleOptions, type ResolvedRouteRules, type RouteRuleConfig, type RouteRuleEntry, type RouteRuleLayer, type RouteRules, type RouteRulesMatcher, type RouteRulesMatcherOptions, type RouteRulesOptions, type RuleHandler, type RuleHandlers, createMatcherFromFind, createRouteRulesMatcher, memoizeRouteRulesMatcher, mergeMatchedRouteRules };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { callMiddleware } from "../middleware.mjs";
|
|
2
|
+
import { isPreflightRequest } from "../cors.mjs";
|
|
3
|
+
import { HTTP_METHODS, buildRouteRuleMiddleware, cors, createMatcherFromFind, createRouteRulesMatcher, headers, memoizeRouteRulesMatcher, mergeMatchedRouteRules, normalizeRouteRules, redirect, ruleHandlers } from "../normalize.mjs";
|
|
4
|
+
import { createCacheRuleHandler } from "../cache2.mjs";
|
|
5
|
+
function routeRules(config, opts) {
|
|
6
|
+
const memoize = opts?.memoize ?? true;
|
|
7
|
+
const matcher = createRouteRulesMatcher(normalizeRouteRules(config), opts);
|
|
8
|
+
const match = memoize ? memoizeRouteRulesMatcher(matcher, memoize === true ? void 0 : memoize) : matcher;
|
|
9
|
+
return function routeRulesMiddleware(event, next) {
|
|
10
|
+
const pathname = event.url.pathname;
|
|
11
|
+
const method = event.req.method.toUpperCase();
|
|
12
|
+
let matched = match(method, pathname);
|
|
13
|
+
if (method === "OPTIONS" && isPreflightRequest(event)) matched = liftPreflightCors(matched, match, event, pathname);
|
|
14
|
+
const { routeRules, routeRuleMiddleware } = matched;
|
|
15
|
+
const prev = event.context.routeRules;
|
|
16
|
+
event.context.routeRules = prev ? Object.assign(Object.create(null), prev, routeRules) : routeRules;
|
|
17
|
+
return routeRuleMiddleware.length > 0 ? callMiddleware(event, routeRuleMiddleware, () => next()) : next();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function liftPreflightCors(matched, match, event, pathname) {
|
|
21
|
+
const requested = event.req.headers.get("access-control-request-method");
|
|
22
|
+
if (!requested) return matched;
|
|
23
|
+
const method = requested.toUpperCase();
|
|
24
|
+
if (method === "OPTIONS" || !HTTP_METHODS.has(method)) return matched;
|
|
25
|
+
const cors = match(method, pathname).matchedRules.cors;
|
|
26
|
+
if (!cors || cors === matched.matchedRules.cors) return matched;
|
|
27
|
+
const matchedRules = Object.assign(Object.create(null), matched.matchedRules, { cors });
|
|
28
|
+
return {
|
|
29
|
+
routeRules: Object.assign(Object.create(null), matched.routeRules, { cors: cors.options }),
|
|
30
|
+
matchedRules,
|
|
31
|
+
routeRuleMiddleware: buildRouteRuleMiddleware(matchedRules)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export { cors, createCacheRuleHandler, createMatcherFromFind, createRouteRulesMatcher, headers, memoizeRouteRulesMatcher, mergeMatchedRouteRules, normalizeRouteRules, redirect, routeRules, ruleHandlers };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { proxyRequest } from "../proxy.mjs";
|
|
2
|
+
import { prepareRuleTarget } from "../_utils.mjs";
|
|
3
|
+
const proxy = {
|
|
4
|
+
order: 2,
|
|
5
|
+
handler: (m) => {
|
|
6
|
+
const options = m.options;
|
|
7
|
+
const resolveTarget = prepareRuleTarget(options);
|
|
8
|
+
if (!resolveTarget) return function proxyRouteRule() {};
|
|
9
|
+
return function proxyRouteRule(event) {
|
|
10
|
+
return proxyRequest(event, resolveTarget(event), { ...options });
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export { proxy };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EventHandler, H3Event, H3Plugin } from "./h3.mjs";
|
|
2
|
+
|
|
3
|
+
export interface TracingRequestEvent {
|
|
4
|
+
type: "middleware" | "route";
|
|
5
|
+
event: H3Event;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Options for the tracing plugin.
|
|
9
|
+
*/
|
|
10
|
+
export interface TracingPluginOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Whether to trace middleware executions.
|
|
13
|
+
*/
|
|
14
|
+
traceMiddleware?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to trace route executions.
|
|
17
|
+
*/
|
|
18
|
+
traceRoutes?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Enables tracing for H3 apps.
|
|
22
|
+
*/
|
|
23
|
+
export declare function tracingPlugin(traceOpts?: TracingPluginOptions): H3Plugin;
|
|
24
|
+
/**
|
|
25
|
+
* Wraps an event handler so its execution is traced via the `h3.request`
|
|
26
|
+
* diagnostics channel with `type: "route"`. Intended to be called once per
|
|
27
|
+
* handler at initialization time (e.g. during codegen or module load), not
|
|
28
|
+
* per request.
|
|
29
|
+
*
|
|
30
|
+
* Returns the handler unchanged when `diagnostics_channel` is unavailable
|
|
31
|
+
* or the handler is already traced.
|
|
32
|
+
*/
|
|
33
|
+
export declare function wrapHandlerWithTracing(handler: EventHandler): EventHandler;
|
package/dist/tracing.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function tracingPlugin(traceOpts) {
|
|
2
|
+
return (h3) => {
|
|
3
|
+
const { tracingChannel } = globalThis.process?.getBuiltinModule?.("diagnostics_channel") ?? {};
|
|
4
|
+
if (!tracingChannel) return;
|
|
5
|
+
const requestHandlerChannel = tracingChannel("h3.request");
|
|
6
|
+
function wrapMiddleware(middleware) {
|
|
7
|
+
if (middleware.__traced__ || traceOpts?.traceMiddleware === false) return middleware;
|
|
8
|
+
const wrappedMiddleware = (...args) => {
|
|
9
|
+
return requestHandlerChannel.tracePromise(async () => middleware(...args), {
|
|
10
|
+
event: args[0],
|
|
11
|
+
type: "middleware"
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
wrappedMiddleware.__traced__ = true;
|
|
15
|
+
return wrappedMiddleware;
|
|
16
|
+
}
|
|
17
|
+
function wrapEventHandler(handler) {
|
|
18
|
+
if (handler.__traced__ || traceOpts?.traceRoutes === false) return handler;
|
|
19
|
+
const wrappedHandler = (...args) => {
|
|
20
|
+
return requestHandlerChannel.tracePromise(async () => handler(...args), {
|
|
21
|
+
event: args[0],
|
|
22
|
+
type: "route"
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
wrappedHandler.__traced__ = true;
|
|
26
|
+
return wrappedHandler;
|
|
27
|
+
}
|
|
28
|
+
h3["~middleware"] = h3["~middleware"].map((m) => wrapMiddleware(m));
|
|
29
|
+
h3["~routes"] = h3["~routes"].map((route) => {
|
|
30
|
+
return {
|
|
31
|
+
...route,
|
|
32
|
+
handler: wrapEventHandler(route.handler),
|
|
33
|
+
middleware: route.middleware ? route.middleware.map((m) => wrapMiddleware(m)) : void 0
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
if ("on" in h3 && typeof h3.on === "function") {
|
|
37
|
+
const originalOn = h3.on;
|
|
38
|
+
h3.on = (...args) => {
|
|
39
|
+
const instance = originalOn.apply(h3, args);
|
|
40
|
+
const lastRoute = instance["~routes"][instance["~routes"].length - 1];
|
|
41
|
+
if (lastRoute) {
|
|
42
|
+
lastRoute.handler = wrapEventHandler(lastRoute.handler);
|
|
43
|
+
lastRoute.middleware = lastRoute.middleware?.map((m) => wrapMiddleware(m));
|
|
44
|
+
}
|
|
45
|
+
return instance;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if ("use" in h3 && typeof h3.use === "function") {
|
|
49
|
+
const originalUse = h3.use;
|
|
50
|
+
h3.use = (arg1, arg2, arg3) => {
|
|
51
|
+
let route;
|
|
52
|
+
let fn;
|
|
53
|
+
let opts;
|
|
54
|
+
if (typeof arg1 === "string") {
|
|
55
|
+
route = arg1;
|
|
56
|
+
fn = arg2;
|
|
57
|
+
opts = arg3;
|
|
58
|
+
return originalUse.call(h3, route, wrapMiddleware(fn), opts);
|
|
59
|
+
}
|
|
60
|
+
fn = arg1;
|
|
61
|
+
opts = arg2;
|
|
62
|
+
return originalUse.call(h3, wrapMiddleware(fn), opts);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if ("mount" in h3 && typeof h3.mount === "function") {
|
|
66
|
+
const originalMount = h3.mount;
|
|
67
|
+
h3.mount = (base, input) => {
|
|
68
|
+
if ("register" in input) input.register(tracingPlugin(traceOpts));
|
|
69
|
+
return originalMount.call(h3, base, input);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return h3;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function wrapHandlerWithTracing(handler) {
|
|
76
|
+
const { tracingChannel } = globalThis.process?.getBuiltinModule?.("diagnostics_channel") ?? {};
|
|
77
|
+
if (!tracingChannel) return handler;
|
|
78
|
+
if (handler.__traced__) return handler;
|
|
79
|
+
const channel = tracingChannel("h3.request");
|
|
80
|
+
const wrapped = (...args) => {
|
|
81
|
+
return channel.tracePromise(async () => handler(...args), {
|
|
82
|
+
event: args[0],
|
|
83
|
+
type: "route"
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
wrapped.__traced__ = true;
|
|
87
|
+
return wrapped;
|
|
88
|
+
}
|
|
89
|
+
export { tracingPlugin, wrapHandlerWithTracing };
|
package/package.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@depup/h3",
|
|
3
|
+
"version": "2.0.1-depup.0",
|
|
4
|
+
"description": "Minimal H(TTP) framework built for high performance and portability. (with updated dependencies)",
|
|
5
|
+
"homepage": "https://h3.dev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": "h3js/h3",
|
|
8
|
+
"bin": {
|
|
9
|
+
"h3": "./bin/h3.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"dist",
|
|
14
|
+
"changes.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"types": "./dist/_entries/generic.d.mts",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
".": {
|
|
23
|
+
"deno": "./dist/_entries/deno.mjs",
|
|
24
|
+
"bun": "./dist/_entries/bun.mjs",
|
|
25
|
+
"workerd": "./dist/_entries/cloudflare.mjs",
|
|
26
|
+
"browser": "./dist/_entries/service-worker.mjs",
|
|
27
|
+
"node": "./dist/_entries/node.mjs",
|
|
28
|
+
"default": "./dist/_entries/generic.mjs"
|
|
29
|
+
},
|
|
30
|
+
"./deno": "./dist/_entries/deno.mjs",
|
|
31
|
+
"./bun": "./dist/_entries/bun.mjs",
|
|
32
|
+
"./cloudflare": "./dist/_entries/cloudflare.mjs",
|
|
33
|
+
"./service-worker": "./dist/_entries/service-worker.mjs",
|
|
34
|
+
"./node": "./dist/_entries/node.mjs",
|
|
35
|
+
"./generic": "./dist/_entries/generic.mjs",
|
|
36
|
+
"./tracing": "./dist/tracing.mjs",
|
|
37
|
+
"./rules": "./dist/rules/index.mjs",
|
|
38
|
+
"./rules/cache": "./dist/rules/cache.mjs",
|
|
39
|
+
"./rules/proxy": "./dist/rules/proxy.mjs",
|
|
40
|
+
"./rules/compiler": "./dist/rules/compiler.mjs"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"bench:bun": "bun run ./test/bench/bench.ts",
|
|
44
|
+
"bench:node": "node --expose-gc --allow-natives-syntax ./test/bench/bench.ts",
|
|
45
|
+
"build": "obuild",
|
|
46
|
+
"dev": "vitest",
|
|
47
|
+
"fmt": "automd && oxlint --fix . && oxfmt .",
|
|
48
|
+
"lint": "oxlint . && oxfmt --check .",
|
|
49
|
+
"play:bun": "bun ./test/fixture/bun.ts",
|
|
50
|
+
"play:node": "pnpm test/fixture/node.ts",
|
|
51
|
+
"play:plain": "pnpm test/fixture/plain.ts",
|
|
52
|
+
"play:web": "pnpm test/fixture/web.ts",
|
|
53
|
+
"release": "pnpm build && pnpm test && changelogen --release --prerelease && pnpm publish --tag latest && git push --follow-tags",
|
|
54
|
+
"test": "pnpm lint && pnpm typecheck && vitest --run --coverage",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"rou3": "^1.0.0",
|
|
59
|
+
"srvx": "^1.0.5"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@happy-dom/global-registrator": "^20.14.5",
|
|
63
|
+
"@mitata/counters": "^0.0.8",
|
|
64
|
+
"@types/connect": "^3.4.38",
|
|
65
|
+
"@types/express": "^5.0.6",
|
|
66
|
+
"@types/node": "^26.6.4",
|
|
67
|
+
"@types/react": "^19.3.0",
|
|
68
|
+
"@types/react-dom": "^19.3.0",
|
|
69
|
+
"@typescript/native-preview": "latest",
|
|
70
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
71
|
+
"automd": "^0.4.3",
|
|
72
|
+
"changelogen": "^0.6.2",
|
|
73
|
+
"connect": "^3.7.0",
|
|
74
|
+
"cookie-es": "^3.1.1",
|
|
75
|
+
"crossws": "^0.4.12",
|
|
76
|
+
"elysia": "^1.4.30",
|
|
77
|
+
"esbuild": "^0.28.2",
|
|
78
|
+
"express": "^5.2.1",
|
|
79
|
+
"fetchdts": "^0.2.0",
|
|
80
|
+
"get-port-please": "^3.2.0",
|
|
81
|
+
"h3": "workspace:*",
|
|
82
|
+
"h3-nightly": "latest",
|
|
83
|
+
"happy-dom": "^20.14.5",
|
|
84
|
+
"hono": "^4.13.12",
|
|
85
|
+
"magic-string": "^1.4.2",
|
|
86
|
+
"mdzilla": "^0.2.1",
|
|
87
|
+
"memoirist": "^1.2.2",
|
|
88
|
+
"mitata": "^1.0.34",
|
|
89
|
+
"obuild": "^0.4.43",
|
|
90
|
+
"ocache": "^0.3.0",
|
|
91
|
+
"oxc-parser": "^0.152.0",
|
|
92
|
+
"oxfmt": ">=0.71.0",
|
|
93
|
+
"oxlint": "^1.86.0",
|
|
94
|
+
"pkg-pr-new": "^0.0.88",
|
|
95
|
+
"react": "^19.3.0",
|
|
96
|
+
"react-dom": "^19.3.0",
|
|
97
|
+
"typescript": "^7.0.2",
|
|
98
|
+
"vite": "^8.3.2",
|
|
99
|
+
"vitest": "^5.0.3",
|
|
100
|
+
"zod": "^4.6.5"
|
|
101
|
+
},
|
|
102
|
+
"peerDependencies": {
|
|
103
|
+
"crossws": "^0.4.12",
|
|
104
|
+
"ocache": ">=0.3.0"
|
|
105
|
+
},
|
|
106
|
+
"peerDependenciesMeta": {
|
|
107
|
+
"crossws": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"ocache": {
|
|
111
|
+
"optional": true
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"resolutions": {
|
|
115
|
+
"h3": "link:.",
|
|
116
|
+
"h3-nightly": "latest"
|
|
117
|
+
},
|
|
118
|
+
"devEngines": {
|
|
119
|
+
"packageManager": {
|
|
120
|
+
"name": "pnpm",
|
|
121
|
+
"version": "12.3.4",
|
|
122
|
+
"onFail": "warn"
|
|
123
|
+
},
|
|
124
|
+
"runtime": {
|
|
125
|
+
"name": "node",
|
|
126
|
+
"version": "^24"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"engines": {
|
|
130
|
+
"node": ">=20.19.0"
|
|
131
|
+
},
|
|
132
|
+
"keywords": [
|
|
133
|
+
"h3",
|
|
134
|
+
"depup",
|
|
135
|
+
"updated-dependencies",
|
|
136
|
+
"security",
|
|
137
|
+
"latest",
|
|
138
|
+
"patched"
|
|
139
|
+
],
|
|
140
|
+
"depup": {
|
|
141
|
+
"changes": {},
|
|
142
|
+
"depsUpdated": 0,
|
|
143
|
+
"originalPackage": "h3",
|
|
144
|
+
"originalVersion": "2.0.1",
|
|
145
|
+
"processedAt": "2026-10-03T17:22:51.553Z",
|
|
146
|
+
"smokeTest": "failed"
|
|
147
|
+
}
|
|
148
|
+
}
|