bxo 0.0.5-dev.73 → 0.0.5-dev.75
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/example/cookie-example.ts +151 -0
- package/example/passthrough-validation-example.ts +115 -0
- package/package.json +1 -1
- package/src/index.ts +102 -18
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import BXO, { z } from "../src";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const app = new BXO({ serve: { port: 3001 } });
|
|
5
|
+
|
|
6
|
+
// Example 1: Simple cookie setting
|
|
7
|
+
app.get("/set-simple-cookie", (ctx) => {
|
|
8
|
+
// Set a simple cookie
|
|
9
|
+
ctx.set.cookie("theme", "dark");
|
|
10
|
+
|
|
11
|
+
return ctx.json({ message: "Simple cookie set!" });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Example 2: Cookie with options
|
|
15
|
+
app.get("/set-secure-cookie", (ctx) => {
|
|
16
|
+
// Set a secure cookie with various options
|
|
17
|
+
ctx.set.cookie("sessionId", "abc123", {
|
|
18
|
+
httpOnly: true,
|
|
19
|
+
secure: true,
|
|
20
|
+
sameSite: "strict",
|
|
21
|
+
maxAge: 3600, // 1 hour
|
|
22
|
+
path: "/"
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return ctx.json({ message: "Secure cookie set!" });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Example 3: Multiple cookies
|
|
29
|
+
app.get("/set-multiple-cookies", (ctx) => {
|
|
30
|
+
// Set multiple cookies
|
|
31
|
+
ctx.set.cookie("user", "john_doe", {
|
|
32
|
+
httpOnly: true,
|
|
33
|
+
maxAge: 86400 // 1 day
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ctx.set.cookie("preferences", "dark_mode", {
|
|
37
|
+
maxAge: 604800 // 1 week
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ctx.set.cookie("lastVisit", new Date().toISOString(), {
|
|
41
|
+
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return ctx.json({ message: "Multiple cookies set!" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Example 4: Reading cookies
|
|
48
|
+
app.get("/read-cookies", (ctx) => {
|
|
49
|
+
return ctx.json({
|
|
50
|
+
message: "Current cookies:",
|
|
51
|
+
cookies: ctx.cookies,
|
|
52
|
+
theme: ctx.cookies.theme || "not set",
|
|
53
|
+
sessionId: ctx.cookies.sessionId || "not set",
|
|
54
|
+
user: ctx.cookies.user || "not set"
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Example 5: Login with cookie validation
|
|
59
|
+
app.post("/login", (ctx) => {
|
|
60
|
+
const { username, password } = ctx.body;
|
|
61
|
+
|
|
62
|
+
// Simple validation (in real app, check against database)
|
|
63
|
+
if (username === "admin" && password === "password") {
|
|
64
|
+
// Set session cookie
|
|
65
|
+
ctx.set.cookie("sessionId", `session_${Date.now()}`, {
|
|
66
|
+
httpOnly: true,
|
|
67
|
+
secure: process.env.NODE_ENV === "production",
|
|
68
|
+
sameSite: "strict",
|
|
69
|
+
maxAge: 3600 // 1 hour
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
ctx.set.cookie("username", username, {
|
|
73
|
+
maxAge: 3600
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return ctx.json({
|
|
77
|
+
message: "Login successful!",
|
|
78
|
+
username
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ctx.status(401, { error: "Invalid credentials" });
|
|
83
|
+
}, {
|
|
84
|
+
body: z.object({
|
|
85
|
+
username: z.string(),
|
|
86
|
+
password: z.string()
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Example 6: Protected route with cookie validation
|
|
91
|
+
app.get("/profile", (ctx) => {
|
|
92
|
+
const sessionId = ctx.cookies.sessionId;
|
|
93
|
+
const username = ctx.cookies.username;
|
|
94
|
+
|
|
95
|
+
if (!sessionId) {
|
|
96
|
+
return ctx.status(401, { error: "Not authenticated" });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ctx.json({
|
|
100
|
+
message: "Welcome to your profile!",
|
|
101
|
+
username,
|
|
102
|
+
sessionId: sessionId.substring(0, 10) + "..." // Don't expose full session ID
|
|
103
|
+
});
|
|
104
|
+
}, {
|
|
105
|
+
cookies: z.object({
|
|
106
|
+
sessionId: z.string().optional(),
|
|
107
|
+
username: z.string().optional()
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Example 7: Logout (clear cookies)
|
|
112
|
+
app.post("/logout", (ctx) => {
|
|
113
|
+
// Clear cookies by setting them with maxAge: 0
|
|
114
|
+
ctx.set.cookie("sessionId", "", {
|
|
115
|
+
maxAge: 0,
|
|
116
|
+
path: "/"
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
ctx.set.cookie("username", "", {
|
|
120
|
+
maxAge: 0,
|
|
121
|
+
path: "/"
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return ctx.json({ message: "Logged out successfully" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Example 8: Cookie with domain and path
|
|
128
|
+
app.get("/set-domain-cookie", (ctx) => {
|
|
129
|
+
ctx.set.cookie("globalPref", "enabled", {
|
|
130
|
+
domain: "localhost", // or your domain
|
|
131
|
+
path: "/api",
|
|
132
|
+
maxAge: 86400
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return ctx.json({ message: "Domain-specific cookie set!" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
app.start();
|
|
139
|
+
console.log(`Cookie example server running on http://localhost:${app.server?.port}`);
|
|
140
|
+
console.log("\nTry these endpoints:");
|
|
141
|
+
console.log("GET /set-simple-cookie");
|
|
142
|
+
console.log("GET /set-secure-cookie");
|
|
143
|
+
console.log("GET /set-multiple-cookies");
|
|
144
|
+
console.log("GET /read-cookies");
|
|
145
|
+
console.log("POST /login (with body: {\"username\": \"admin\", \"password\": \"password\"})");
|
|
146
|
+
console.log("GET /profile");
|
|
147
|
+
console.log("POST /logout");
|
|
148
|
+
console.log("GET /set-domain-cookie");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import BXO, { z } from "../src";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const app = new BXO({ serve: { port: 3002 } });
|
|
5
|
+
|
|
6
|
+
// Example 1: Header validation with passthrough
|
|
7
|
+
app.get("/api/headers", (ctx) => {
|
|
8
|
+
return ctx.json({
|
|
9
|
+
message: "Headers received",
|
|
10
|
+
// All headers are available, including extra ones not in schema
|
|
11
|
+
allHeaders: ctx.headers,
|
|
12
|
+
// Schema-validated headers are typed
|
|
13
|
+
contentType: ctx.headers["content-type"],
|
|
14
|
+
authorization: ctx.headers["authorization"]
|
|
15
|
+
});
|
|
16
|
+
}, {
|
|
17
|
+
headers: z.object({
|
|
18
|
+
"content-type": z.string().optional(),
|
|
19
|
+
"authorization": z.string().optional()
|
|
20
|
+
}).passthrough() // This allows extra headers to pass through
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Example 2: Cookie validation with passthrough
|
|
24
|
+
app.get("/api/cookies", (ctx) => {
|
|
25
|
+
return ctx.json({
|
|
26
|
+
message: "Cookies received",
|
|
27
|
+
// All cookies are available, including extra ones not in schema
|
|
28
|
+
allCookies: ctx.cookies,
|
|
29
|
+
// Schema-validated cookies are typed
|
|
30
|
+
sessionId: ctx.cookies.sessionId,
|
|
31
|
+
theme: ctx.cookies.theme
|
|
32
|
+
});
|
|
33
|
+
}, {
|
|
34
|
+
cookies: z.object({
|
|
35
|
+
sessionId: z.string().optional(),
|
|
36
|
+
theme: z.enum(["light", "dark"]).optional()
|
|
37
|
+
}).passthrough() // This allows extra cookies to pass through
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Example 3: Both headers and cookies with passthrough
|
|
41
|
+
app.post("/api/auth", (ctx) => {
|
|
42
|
+
return ctx.json({
|
|
43
|
+
message: "Authentication successful",
|
|
44
|
+
headers: {
|
|
45
|
+
// Only the required headers are typed
|
|
46
|
+
contentType: ctx.headers["content-type"],
|
|
47
|
+
authorization: ctx.headers["authorization"],
|
|
48
|
+
// But all headers are available
|
|
49
|
+
allHeaders: ctx.headers
|
|
50
|
+
},
|
|
51
|
+
cookies: {
|
|
52
|
+
// Only the required cookies are typed
|
|
53
|
+
sessionId: ctx.cookies.sessionId,
|
|
54
|
+
theme: ctx.cookies.theme,
|
|
55
|
+
// But all cookies are available
|
|
56
|
+
allCookies: ctx.cookies
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}, {
|
|
60
|
+
headers: z.object({
|
|
61
|
+
"content-type": z.string(),
|
|
62
|
+
"authorization": z.string()
|
|
63
|
+
}).passthrough(),
|
|
64
|
+
cookies: z.object({
|
|
65
|
+
sessionId: z.string(),
|
|
66
|
+
theme: z.enum(["light", "dark"])
|
|
67
|
+
}).passthrough(),
|
|
68
|
+
body: z.object({
|
|
69
|
+
username: z.string(),
|
|
70
|
+
password: z.string()
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Example 4: Without passthrough (strict validation)
|
|
75
|
+
app.get("/api/strict", (ctx) => {
|
|
76
|
+
return ctx.json({
|
|
77
|
+
message: "Strict validation - only schema fields available",
|
|
78
|
+
headers: ctx.headers,
|
|
79
|
+
cookies: ctx.cookies
|
|
80
|
+
});
|
|
81
|
+
}, {
|
|
82
|
+
headers: z.object({
|
|
83
|
+
"content-type": z.string().optional()
|
|
84
|
+
}), // No passthrough - will fail if extra headers are present
|
|
85
|
+
cookies: z.object({
|
|
86
|
+
sessionId: z.string().optional()
|
|
87
|
+
}) // No passthrough - will fail if extra cookies are present
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Example 5: Demonstrating the difference
|
|
91
|
+
app.get("/api/demo", (ctx) => {
|
|
92
|
+
// Set some cookies to demonstrate
|
|
93
|
+
ctx.set.cookie("sessionId", "abc123");
|
|
94
|
+
ctx.set.cookie("theme", "dark");
|
|
95
|
+
ctx.set.cookie("extraCookie", "this-will-be-available-with-passthrough");
|
|
96
|
+
|
|
97
|
+
return ctx.json({
|
|
98
|
+
message: "Check the cookies in your browser dev tools",
|
|
99
|
+
note: "extraCookie will be available in /api/cookies but not in /api/strict"
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
app.start();
|
|
104
|
+
console.log(`Passthrough validation example server running on http://localhost:${app.server?.port}`);
|
|
105
|
+
console.log("\nTry these endpoints:");
|
|
106
|
+
console.log("GET /api/headers (with extra headers)");
|
|
107
|
+
console.log("GET /api/cookies (with extra cookies)");
|
|
108
|
+
console.log("POST /api/auth (with both headers and cookies)");
|
|
109
|
+
console.log("GET /api/strict (strict validation - may fail with extra fields)");
|
|
110
|
+
console.log("GET /api/demo (sets some cookies for testing)");
|
|
111
|
+
console.log("\nExample with curl:");
|
|
112
|
+
console.log('curl -H "Content-Type: application/json" -H "Authorization: Bearer token123" -H "X-Custom-Header: value" -b "sessionId=abc123; theme=dark; extraCookie=test" http://localhost:3002/api/headers');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().catch(console.error);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
type Method =
|
|
5
4
|
| "GET"
|
|
6
5
|
| "POST"
|
|
@@ -52,6 +51,16 @@ type QueryObject = Record<string, string | string[]>;
|
|
|
52
51
|
type CookieObject = Record<string, string>;
|
|
53
52
|
type HeaderObject = Record<string, string>;
|
|
54
53
|
|
|
54
|
+
export type CookieOptions = {
|
|
55
|
+
domain?: string;
|
|
56
|
+
expires?: Date;
|
|
57
|
+
httpOnly?: boolean;
|
|
58
|
+
maxAge?: number;
|
|
59
|
+
path?: string;
|
|
60
|
+
sameSite?: "strict" | "lax" | "none";
|
|
61
|
+
secure?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
55
64
|
// Lifecycle hook types
|
|
56
65
|
export type BeforeRequestHook = (req: Request, ctx?: Partial<Context<any, any>>) => Request | Response | Promise<Request | Response | void>;
|
|
57
66
|
export type AfterRequestHook = (req: Request, res: Response, ctx?: Partial<Context<any, any>>) => Response | Promise<Response | void>;
|
|
@@ -65,7 +74,10 @@ export type Context<P extends string = string, S extends RouteSchema | undefined
|
|
|
65
74
|
headers: S extends RouteSchema ? InferOr<S["headers"], HeaderObject> : HeaderObject;
|
|
66
75
|
cookies: S extends RouteSchema ? InferOr<S["cookies"], CookieObject> : CookieObject;
|
|
67
76
|
body: S extends RouteSchema ? InferOr<S["body"], unknown> : unknown;
|
|
68
|
-
set: {
|
|
77
|
+
set: {
|
|
78
|
+
headers: Record<string, string | string[]>;
|
|
79
|
+
cookie: (name: string, value: string, options?: CookieOptions) => void;
|
|
80
|
+
};
|
|
69
81
|
json: <T>(data: T, status?: number) => Response;
|
|
70
82
|
text: (data: string, status?: number) => Response;
|
|
71
83
|
status: <T extends number>(status: T, data: InferResponse<S, T>) => Response;
|
|
@@ -108,6 +120,42 @@ function parseCookies(cookieHeader: string | null): CookieObject {
|
|
|
108
120
|
return out;
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {
|
|
124
|
+
let cookie = `${name}=${encodeURIComponent(value)}`;
|
|
125
|
+
|
|
126
|
+
if (options.domain) {
|
|
127
|
+
cookie += `; Domain=${options.domain}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.path) {
|
|
131
|
+
cookie += `; Path=${options.path}`;
|
|
132
|
+
} else {
|
|
133
|
+
cookie += `; Path=/`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.expires) {
|
|
137
|
+
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (options.maxAge !== undefined) {
|
|
141
|
+
cookie += `; Max-Age=${options.maxAge}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (options.httpOnly) {
|
|
145
|
+
cookie += `; HttpOnly`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (options.secure) {
|
|
149
|
+
cookie += `; Secure`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.sameSite) {
|
|
153
|
+
cookie += `; SameSite=${options.sameSite}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return cookie;
|
|
157
|
+
}
|
|
158
|
+
|
|
111
159
|
function parseQuery(searchParams: URLSearchParams): QueryObject;
|
|
112
160
|
function parseQuery<T extends z.ZodTypeAny>(searchParams: URLSearchParams, schema: T): z.infer<T>;
|
|
113
161
|
function parseQuery(searchParams: URLSearchParams, schema?: z.ZodTypeAny): any {
|
|
@@ -161,9 +209,18 @@ function buildMatcher(path: string): { regex: RegExp | null; names: string[] } {
|
|
|
161
209
|
return { regex, names };
|
|
162
210
|
}
|
|
163
211
|
|
|
164
|
-
function mergeHeaders(base: HeadersInit | undefined, extra: Record<string, string>): Headers {
|
|
212
|
+
function mergeHeaders(base: HeadersInit | undefined, extra: Record<string, string | string[]>): Headers {
|
|
165
213
|
const h = new Headers(base);
|
|
166
|
-
for (const [k, v] of Object.entries(extra))
|
|
214
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
215
|
+
if (Array.isArray(v)) {
|
|
216
|
+
// For arrays (like Set-Cookie), append each value as a separate header
|
|
217
|
+
for (const value of v) {
|
|
218
|
+
h.append(k, value);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
h.set(k, v);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
167
224
|
return h;
|
|
168
225
|
}
|
|
169
226
|
|
|
@@ -277,7 +334,9 @@ export default class BXO {
|
|
|
277
334
|
this.server = Bun.serve({
|
|
278
335
|
...this.serveOptions,
|
|
279
336
|
routes: nativeRoutes as any,
|
|
280
|
-
fetch: (req: Request) =>
|
|
337
|
+
fetch: (req: Request) => {
|
|
338
|
+
return new Response("Not Found", { status: 404 });
|
|
339
|
+
}
|
|
281
340
|
});
|
|
282
341
|
}
|
|
283
342
|
|
|
@@ -347,7 +406,7 @@ export default class BXO {
|
|
|
347
406
|
try {
|
|
348
407
|
queryObj = route.schema?.query ? parseQuery(url.searchParams, route.schema.query as any) : parseQuery(url.searchParams);
|
|
349
408
|
} catch (err: any) {
|
|
350
|
-
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error" };
|
|
409
|
+
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error", issues: [], message: err.message };
|
|
351
410
|
return new Response(JSON.stringify(payload), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
352
411
|
}
|
|
353
412
|
|
|
@@ -355,27 +414,27 @@ export default class BXO {
|
|
|
355
414
|
try {
|
|
356
415
|
const contentType = req.headers.get("content-type") || "";
|
|
357
416
|
if (contentType.includes("application/json")) {
|
|
358
|
-
const raw = await req.json();
|
|
417
|
+
const raw = await req.json().catch(() => undefined);
|
|
359
418
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
360
419
|
} else if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
|
|
361
|
-
const fd = await req.formData();
|
|
362
|
-
const raw = formDataToObject(fd);
|
|
420
|
+
const fd = await req.formData().catch(() => undefined);
|
|
421
|
+
const raw = formDataToObject(fd || new FormData());
|
|
363
422
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
364
423
|
} else if (contentType.includes("text/")) {
|
|
365
|
-
const raw = await req.text();
|
|
424
|
+
const raw = await req.text().catch(() => undefined);
|
|
366
425
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
367
426
|
} else if (contentType) {
|
|
368
427
|
// Unknown content-type: provide ArrayBuffer
|
|
369
|
-
const raw = await req.arrayBuffer();
|
|
428
|
+
const raw = await req.arrayBuffer().catch(() => undefined);
|
|
370
429
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
371
430
|
} else {
|
|
372
431
|
// No content-type: try JSON then text, otherwise undefined
|
|
373
432
|
try {
|
|
374
|
-
const raw = await req.json();
|
|
433
|
+
const raw = await req.json().catch(() => undefined);
|
|
375
434
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
376
435
|
} catch {
|
|
377
436
|
try {
|
|
378
|
-
const raw = await req.text();
|
|
437
|
+
const raw = await req.text().catch(() => undefined);
|
|
379
438
|
bodyObj = route.schema?.body ? (route.schema.body as any).parse(raw) : raw;
|
|
380
439
|
} catch {
|
|
381
440
|
bodyObj = undefined;
|
|
@@ -386,7 +445,7 @@ export default class BXO {
|
|
|
386
445
|
return new Response(JSON.stringify({ error: "Body is required" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
387
446
|
}
|
|
388
447
|
} catch (err: any) {
|
|
389
|
-
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error" };
|
|
448
|
+
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error", issues: [], message: err.message };
|
|
390
449
|
return new Response(JSON.stringify(payload), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
391
450
|
}
|
|
392
451
|
|
|
@@ -400,7 +459,22 @@ export default class BXO {
|
|
|
400
459
|
headers: headerObj,
|
|
401
460
|
cookies: cookieObj,
|
|
402
461
|
body: bodyObj,
|
|
403
|
-
set: {
|
|
462
|
+
set: {
|
|
463
|
+
headers: {},
|
|
464
|
+
cookie: (name: string, value: string, options: CookieOptions = {}) => {
|
|
465
|
+
const cookieString = serializeCookie(name, value, options);
|
|
466
|
+
const existingCookies = ctx.set.headers["Set-Cookie"];
|
|
467
|
+
if (existingCookies) {
|
|
468
|
+
if (Array.isArray(existingCookies)) {
|
|
469
|
+
existingCookies.push(cookieString);
|
|
470
|
+
} else {
|
|
471
|
+
ctx.set.headers["Set-Cookie"] = [existingCookies, cookieString];
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
ctx.set.headers["Set-Cookie"] = [cookieString];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
},
|
|
404
478
|
json: (data, status = 200) => {
|
|
405
479
|
// Response validation if declared
|
|
406
480
|
if (route.schema?.response?.[status]) {
|
|
@@ -449,13 +523,23 @@ export default class BXO {
|
|
|
449
523
|
if (route.schema) {
|
|
450
524
|
try {
|
|
451
525
|
if (route.schema.headers) {
|
|
452
|
-
|
|
526
|
+
const headerSchema = route.schema.headers as any;
|
|
527
|
+
if (headerSchema.passthrough) {
|
|
528
|
+
headerSchema.passthrough().parse(headerObj);
|
|
529
|
+
} else {
|
|
530
|
+
headerSchema.parse(headerObj);
|
|
531
|
+
}
|
|
453
532
|
}
|
|
454
533
|
if (route.schema.cookies) {
|
|
455
|
-
|
|
534
|
+
const cookieSchema = route.schema.cookies as any;
|
|
535
|
+
if (cookieSchema.passthrough) {
|
|
536
|
+
cookieSchema.passthrough().parse(cookieObj);
|
|
537
|
+
} else {
|
|
538
|
+
cookieSchema.parse(cookieObj);
|
|
539
|
+
}
|
|
456
540
|
}
|
|
457
541
|
} catch (err: any) {
|
|
458
|
-
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error" };
|
|
542
|
+
const payload = err?.issues ? { error: "Validation Error", issues: err.issues } : { error: "Validation Error", issues: [], message: err.message };
|
|
459
543
|
return new Response(JSON.stringify(payload), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
460
544
|
}
|
|
461
545
|
}
|