bxo 0.0.5-dev.33 → 0.0.5-dev.35
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.ts +33 -0
- package/index.ts +40 -17
- package/package.json +1 -1
package/example.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import BXO, { createCookie, redirect } from 'bxo';
|
|
2
|
+
|
|
3
|
+
const app = new BXO();
|
|
4
|
+
|
|
5
|
+
// 1) Explicit return redirect (302 default)
|
|
6
|
+
app.get('/old', ({ redirect }) => {
|
|
7
|
+
return redirect('/new');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// 2) Implicit redirect (no return needed)
|
|
11
|
+
app.get('/implicit', (ctx) => {
|
|
12
|
+
ctx.redirect('/new');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// 3) Custom status (303 after POST)
|
|
16
|
+
app.post('/submit', (ctx) => {
|
|
17
|
+
// ...handle form...
|
|
18
|
+
return ctx.redirect('/thanks', 303);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 4) Redirect with cookie
|
|
22
|
+
app.get('/login', (ctx) => {
|
|
23
|
+
ctx.set.cookies = [
|
|
24
|
+
createCookie('sid', 'abc123', { httpOnly: true, path: '/' })
|
|
25
|
+
];
|
|
26
|
+
ctx.redirect('/dashboard'); // no return required
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 5) Using top-level helper (outside ctx)
|
|
30
|
+
app.get('/go-external', () => redirect('https://example.com', 307));
|
|
31
|
+
|
|
32
|
+
await app.start(3000);
|
|
33
|
+
console.log('Server running at http://localhost:3000');
|
package/index.ts
CHANGED
|
@@ -67,6 +67,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
|
|
|
67
67
|
status?: number;
|
|
68
68
|
headers?: Record<string, string>;
|
|
69
69
|
cookies?: Cookie[];
|
|
70
|
+
redirect?: { location: string; status?: number };
|
|
70
71
|
};
|
|
71
72
|
status: <T extends number>(
|
|
72
73
|
code: TConfig['response'] extends StatusResponseSchema
|
|
@@ -87,6 +88,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
|
|
|
87
88
|
? InferZodType<TConfig['response']>
|
|
88
89
|
: any;
|
|
89
90
|
redirect: (location: string, status?: number) => Response;
|
|
91
|
+
clearRedirect: () => void;
|
|
90
92
|
[key: string]: any;
|
|
91
93
|
};
|
|
92
94
|
|
|
@@ -600,15 +602,14 @@ export default class BXO {
|
|
|
600
602
|
return data;
|
|
601
603
|
}) as any,
|
|
602
604
|
redirect: ((location: string, status: number = 302) => {
|
|
603
|
-
//
|
|
604
|
-
ctx.set.
|
|
605
|
-
ctx.set.headers = {
|
|
606
|
-
...(ctx.set.headers || {}),
|
|
607
|
-
Location: location
|
|
608
|
-
};
|
|
605
|
+
// Record redirect intent only; avoid mutating generic status/headers so it can be canceled later
|
|
606
|
+
ctx.set.redirect = { location, status };
|
|
609
607
|
|
|
610
|
-
// Prepare headers for immediate Response return
|
|
611
|
-
const responseHeaders: Record<string, string> = {
|
|
608
|
+
// Prepare headers for immediate Response return without persisting to ctx.set.headers
|
|
609
|
+
const responseHeaders: Record<string, string> = {
|
|
610
|
+
Location: location,
|
|
611
|
+
...(ctx.set.headers || {})
|
|
612
|
+
};
|
|
612
613
|
|
|
613
614
|
// Handle cookies if any are set on context
|
|
614
615
|
if (ctx.set.cookies && ctx.set.cookies.length > 0) {
|
|
@@ -632,7 +633,23 @@ export default class BXO {
|
|
|
632
633
|
status,
|
|
633
634
|
headers: responseHeaders
|
|
634
635
|
});
|
|
635
|
-
}) as any
|
|
636
|
+
}) as any,
|
|
637
|
+
clearRedirect: (() => {
|
|
638
|
+
// Clear explicit redirect intent
|
|
639
|
+
delete ctx.set.redirect;
|
|
640
|
+
// Remove any Location header if present
|
|
641
|
+
if (ctx.set.headers) {
|
|
642
|
+
for (const key of Object.keys(ctx.set.headers)) {
|
|
643
|
+
if (key.toLowerCase() === 'location') {
|
|
644
|
+
delete ctx.set.headers[key];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Reset status if it is a redirect
|
|
649
|
+
if (typeof ctx.set.status === 'number' && ctx.set.status >= 300 && ctx.set.status < 400) {
|
|
650
|
+
delete ctx.set.status;
|
|
651
|
+
}
|
|
652
|
+
}) as any
|
|
636
653
|
};
|
|
637
654
|
} catch (validationError) {
|
|
638
655
|
// Validation failed - return error response
|
|
@@ -691,13 +708,19 @@ export default class BXO {
|
|
|
691
708
|
|
|
692
709
|
// If the handler did not return a response, but a redirect was configured via ctx.set,
|
|
693
710
|
// automatically create a redirect Response so users can call ctx.redirect(...) or set headers without returning.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
&& ctx.set.
|
|
698
|
-
const
|
|
699
|
-
if (
|
|
700
|
-
|
|
711
|
+
const hasImplicitRedirectIntent = !!ctx.set.redirect
|
|
712
|
+
|| (typeof ctx.set.status === 'number' && ctx.set.status >= 300 && ctx.set.status < 400);
|
|
713
|
+
if ((response === undefined || response === null) && hasImplicitRedirectIntent) {
|
|
714
|
+
const locationFromHeaders = ctx.set.headers && Object.entries(ctx.set.headers).find(([k]) => k.toLowerCase() === 'location')?.[1];
|
|
715
|
+
const location = ctx.set.redirect?.location || locationFromHeaders;
|
|
716
|
+
if (location) {
|
|
717
|
+
// Build headers, ensuring Location is present
|
|
718
|
+
let responseHeaders: Record<string, string> = ctx.set.headers ? { ...ctx.set.headers } : {};
|
|
719
|
+
if (!Object.keys(responseHeaders).some(k => k.toLowerCase() === 'location')) {
|
|
720
|
+
responseHeaders['Location'] = location;
|
|
721
|
+
}
|
|
722
|
+
// Determine status precedence: redirect.status > set.status > 302
|
|
723
|
+
const status = ctx.set.redirect?.status ?? ctx.set.status ?? 302;
|
|
701
724
|
|
|
702
725
|
// Handle cookies if any are set
|
|
703
726
|
if (ctx.set.cookies && ctx.set.cookies.length > 0) {
|
|
@@ -718,7 +741,7 @@ export default class BXO {
|
|
|
718
741
|
}
|
|
719
742
|
|
|
720
743
|
return new Response(null, {
|
|
721
|
-
status
|
|
744
|
+
status,
|
|
722
745
|
headers: responseHeaders
|
|
723
746
|
});
|
|
724
747
|
}
|