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.
Files changed (3) hide show
  1. package/example.ts +33 -0
  2. package/index.ts +40 -17
  3. 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
- // 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
- };
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 (merging any existing headers)
611
- const responseHeaders: Record<string, string> = { ...ctx.set.headers };
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
- 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 } : {};
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: ctx.set.status,
744
+ status,
722
745
  headers: responseHeaders
723
746
  });
724
747
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bxo",
3
3
  "module": "index.ts",
4
- "version": "0.0.5-dev.33",
4
+ "version": "0.0.5-dev.35",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {