@mandujs/core 0.54.24 → 0.54.25
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/package.json +1 -1
- package/src/filling/filling.ts +14 -0
- package/src/filling/head-method.test.ts +47 -0
- package/src/runtime/handlers.ts +10 -0
package/package.json
CHANGED
package/src/filling/filling.ts
CHANGED
|
@@ -606,6 +606,20 @@ export class ManduFilling<TLoaderData = unknown> {
|
|
|
606
606
|
if (this.config.handlers.has("GET") && !allowed.includes("HEAD")) {
|
|
607
607
|
allowed.push("HEAD");
|
|
608
608
|
}
|
|
609
|
+
// RFC 7231 §4.3.7: auto-respond to a plain OPTIONS request (no explicit
|
|
610
|
+
// OPTIONS handler) with 204 + an `Allow` header. CORS preflights — which
|
|
611
|
+
// carry `Access-Control-Request-Method` — are left to the normal flow so
|
|
612
|
+
// CORS middleware can attach its Access-Control-* headers.
|
|
613
|
+
if (
|
|
614
|
+
method === "OPTIONS" &&
|
|
615
|
+
!normalizedRequest.headers.get("access-control-request-method")
|
|
616
|
+
) {
|
|
617
|
+
const optionsAllowed = allowed.includes("OPTIONS") ? allowed : [...allowed, "OPTIONS"];
|
|
618
|
+
return new Response(null, {
|
|
619
|
+
status: 204,
|
|
620
|
+
headers: { Allow: optionsAllowed.join(", ") },
|
|
621
|
+
});
|
|
622
|
+
}
|
|
609
623
|
// RFC 7231 §6.5.5: a 405 response MUST include an `Allow` header listing
|
|
610
624
|
// every supported method (not just the body field). ctx.json() cannot
|
|
611
625
|
// attach headers, so build the response directly here.
|
|
@@ -104,4 +104,51 @@ describe("Filling HEAD handling (#319)", () => {
|
|
|
104
104
|
// HEAD is implied by GET.
|
|
105
105
|
expect(methods).toContain("HEAD");
|
|
106
106
|
});
|
|
107
|
+
|
|
108
|
+
// RFC 7231 §4.3.7: a plain OPTIONS request (no explicit OPTIONS handler)
|
|
109
|
+
// should return 204 with an Allow header, not 405.
|
|
110
|
+
it("auto-responds to a plain OPTIONS request with 204 + Allow", async () => {
|
|
111
|
+
const filling = ManduFillingFactory.filling()
|
|
112
|
+
.get((ctx) => ctx.json({ ok: true }))
|
|
113
|
+
.post((ctx) => ctx.json({ ok: true }));
|
|
114
|
+
|
|
115
|
+
const res = await filling.handle(
|
|
116
|
+
new Request("http://localhost/api/pledges", { method: "OPTIONS" }),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(res.status).toBe(204);
|
|
120
|
+
const methods = (res.headers.get("Allow") ?? "").split(", ");
|
|
121
|
+
expect(methods).toContain("GET");
|
|
122
|
+
expect(methods).toContain("POST");
|
|
123
|
+
expect(methods).toContain("HEAD");
|
|
124
|
+
expect(methods).toContain("OPTIONS");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("defers CORS preflight OPTIONS (Access-Control-Request-Method) instead of auto-answering", async () => {
|
|
128
|
+
const filling = ManduFillingFactory.filling().get((ctx) => ctx.json({ ok: true }));
|
|
129
|
+
|
|
130
|
+
const res = await filling.handle(
|
|
131
|
+
new Request("http://localhost/api/pledges", {
|
|
132
|
+
method: "OPTIONS",
|
|
133
|
+
headers: { "Access-Control-Request-Method": "GET", Origin: "https://x" },
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// No CORS middleware registered → falls through to 405 (not the 204
|
|
138
|
+
// auto-OPTIONS path), leaving preflight handling to CORS middleware.
|
|
139
|
+
expect(res.status).toBe(405);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("an explicit OPTIONS handler still takes precedence", async () => {
|
|
143
|
+
const filling = ManduFillingFactory.filling()
|
|
144
|
+
.get((ctx) => ctx.json({ ok: true }))
|
|
145
|
+
.options((ctx) => ctx.json({ custom: true }));
|
|
146
|
+
|
|
147
|
+
const res = await filling.handle(
|
|
148
|
+
new Request("http://localhost/api/pledges", { method: "OPTIONS" }),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(res.status).toBe(200);
|
|
152
|
+
expect(await res.json()).toEqual({ custom: true });
|
|
153
|
+
});
|
|
107
154
|
});
|
package/src/runtime/handlers.ts
CHANGED
|
@@ -76,6 +76,16 @@ function createMethodDispatcher(module: RouteModule, routeId: string) {
|
|
|
76
76
|
if (typeof module.GET === "function" && !allow.includes("HEAD")) {
|
|
77
77
|
allow.push("HEAD");
|
|
78
78
|
}
|
|
79
|
+
// RFC 7231 §4.3.7: auto-respond to a plain OPTIONS request (no explicit
|
|
80
|
+
// OPTIONS export) with 204 + an `Allow` header. CORS preflights (which
|
|
81
|
+
// carry `Access-Control-Request-Method`) are left to the normal flow.
|
|
82
|
+
if (method === "OPTIONS" && !req.headers.get("access-control-request-method")) {
|
|
83
|
+
const optionsAllow = allow.includes("OPTIONS") ? allow : [...allow, "OPTIONS"];
|
|
84
|
+
return new Response(null, {
|
|
85
|
+
status: 204,
|
|
86
|
+
headers: { Allow: optionsAllow.join(", ") },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
79
89
|
return Response.json(
|
|
80
90
|
{
|
|
81
91
|
error: `Method ${method} not allowed for route ${routeId}`,
|