bxo 0.0.5-dev.32 → 0.0.5-dev.33
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/index.ts +80 -2
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -86,6 +86,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
|
|
|
86
86
|
: TConfig['response'] extends ResponseSchema
|
|
87
87
|
? InferZodType<TConfig['response']>
|
|
88
88
|
: any;
|
|
89
|
+
redirect: (location: string, status?: number) => Response;
|
|
89
90
|
[key: string]: any;
|
|
90
91
|
};
|
|
91
92
|
|
|
@@ -597,7 +598,41 @@ export default class BXO {
|
|
|
597
598
|
status: ((code: number, data?: any) => {
|
|
598
599
|
ctx.set.status = code;
|
|
599
600
|
return data;
|
|
600
|
-
}) as any
|
|
601
|
+
}) as any,
|
|
602
|
+
redirect: ((location: string, status: number = 302) => {
|
|
603
|
+
// Persist redirect intent on context so it also works without returning
|
|
604
|
+
ctx.set.status = status;
|
|
605
|
+
ctx.set.headers = {
|
|
606
|
+
...(ctx.set.headers || {}),
|
|
607
|
+
Location: location
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// Prepare headers for immediate Response return (merging any existing headers)
|
|
611
|
+
const responseHeaders: Record<string, string> = { ...ctx.set.headers };
|
|
612
|
+
|
|
613
|
+
// Handle cookies if any are set on context
|
|
614
|
+
if (ctx.set.cookies && ctx.set.cookies.length > 0) {
|
|
615
|
+
const cookieHeaders = ctx.set.cookies.map(cookie => {
|
|
616
|
+
let cookieString = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
617
|
+
if (cookie.domain) cookieString += `; Domain=${cookie.domain}`;
|
|
618
|
+
if (cookie.path) cookieString += `; Path=${cookie.path}`;
|
|
619
|
+
if (cookie.expires) cookieString += `; Expires=${cookie.expires.toUTCString()}`;
|
|
620
|
+
if (cookie.maxAge) cookieString += `; Max-Age=${cookie.maxAge}`;
|
|
621
|
+
if (cookie.secure) cookieString += `; Secure`;
|
|
622
|
+
if (cookie.httpOnly) cookieString += `; HttpOnly`;
|
|
623
|
+
if (cookie.sameSite) cookieString += `; SameSite=${cookie.sameSite}`;
|
|
624
|
+
return cookieString;
|
|
625
|
+
});
|
|
626
|
+
cookieHeaders.forEach((cookieHeader, index) => {
|
|
627
|
+
responseHeaders[index === 0 ? 'Set-Cookie' : `Set-Cookie-${index}`] = cookieHeader;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return new Response(null, {
|
|
632
|
+
status,
|
|
633
|
+
headers: responseHeaders
|
|
634
|
+
});
|
|
635
|
+
}) as any
|
|
601
636
|
};
|
|
602
637
|
} catch (validationError) {
|
|
603
638
|
// Validation failed - return error response
|
|
@@ -654,6 +689,41 @@ export default class BXO {
|
|
|
654
689
|
}
|
|
655
690
|
}
|
|
656
691
|
|
|
692
|
+
// If the handler did not return a response, but a redirect was configured via ctx.set,
|
|
693
|
+
// automatically create a redirect Response so users can call ctx.redirect(...) or set headers without returning.
|
|
694
|
+
if ((response === undefined || response === null)
|
|
695
|
+
&& typeof ctx.set.status === 'number'
|
|
696
|
+
&& ctx.set.status >= 300
|
|
697
|
+
&& ctx.set.status < 400) {
|
|
698
|
+
const hasLocationHeader = !!(ctx.set.headers && Object.keys(ctx.set.headers).some(k => k.toLowerCase() === 'location'));
|
|
699
|
+
if (hasLocationHeader) {
|
|
700
|
+
let responseHeaders = ctx.set.headers ? { ...ctx.set.headers } : {};
|
|
701
|
+
|
|
702
|
+
// Handle cookies if any are set
|
|
703
|
+
if (ctx.set.cookies && ctx.set.cookies.length > 0) {
|
|
704
|
+
const cookieHeaders = ctx.set.cookies.map(cookie => {
|
|
705
|
+
let cookieString = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
706
|
+
if (cookie.domain) cookieString += `; Domain=${cookie.domain}`;
|
|
707
|
+
if (cookie.path) cookieString += `; Path=${cookie.path}`;
|
|
708
|
+
if (cookie.expires) cookieString += `; Expires=${cookie.expires.toUTCString()}`;
|
|
709
|
+
if (cookie.maxAge) cookieString += `; Max-Age=${cookie.maxAge}`;
|
|
710
|
+
if (cookie.secure) cookieString += `; Secure`;
|
|
711
|
+
if (cookie.httpOnly) cookieString += `; HttpOnly`;
|
|
712
|
+
if (cookie.sameSite) cookieString += `; SameSite=${cookie.sameSite}`;
|
|
713
|
+
return cookieString;
|
|
714
|
+
});
|
|
715
|
+
cookieHeaders.forEach((cookieHeader, index) => {
|
|
716
|
+
responseHeaders[index === 0 ? 'Set-Cookie' : `Set-Cookie-${index}`] = cookieHeader;
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return new Response(null, {
|
|
721
|
+
status: ctx.set.status,
|
|
722
|
+
headers: responseHeaders
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
657
727
|
// Validate response against schema if provided
|
|
658
728
|
if (this.enableValidation && route.config?.response && !(response instanceof Response)) {
|
|
659
729
|
try {
|
|
@@ -1045,8 +1115,16 @@ const file = (path: string, options?: { type?: string; headers?: Record<string,
|
|
|
1045
1115
|
return bunFile;
|
|
1046
1116
|
}
|
|
1047
1117
|
|
|
1118
|
+
// Redirect helper function (like Elysia)
|
|
1119
|
+
const redirect = (location: string, status: number = 302) => {
|
|
1120
|
+
return new Response(null, {
|
|
1121
|
+
status,
|
|
1122
|
+
headers: { Location: location }
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1048
1126
|
// Export Zod for convenience
|
|
1049
|
-
export { z, error, file };
|
|
1127
|
+
export { z, error, file, redirect };
|
|
1050
1128
|
|
|
1051
1129
|
// Export types for external use
|
|
1052
1130
|
export type { RouteConfig, RouteDetail, Handler, WebSocketHandler, WSRoute, Cookie, BXOOptions };
|