bxo 0.0.5-dev.21 → 0.0.5-dev.23

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/README.md +76 -1
  2. package/index.ts +79 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,7 +20,7 @@ BXO is a fast, lightweight, and fully type-safe web framework built specifically
20
20
  ### Installation
21
21
 
22
22
  ```bash
23
- bun add zod
23
+ bun add bxo
24
24
  ```
25
25
 
26
26
  ### Basic Usage
@@ -93,11 +93,86 @@ interface Context<TConfig> {
93
93
  status?: number;
94
94
  headers?: Record<string, string>;
95
95
  };
96
+ status: (code: number, data?: any) => any; // Type-safe status method
96
97
  user?: any; // Added by auth plugin
97
98
  [key: string]: any; // Extended by plugins
98
99
  }
99
100
  ```
100
101
 
102
+ ### Type-Safe Status Method
103
+
104
+ BXO provides a type-safe `ctx.status()` method similar to Elysia, which allows you to set HTTP status codes and return data in one call:
105
+
106
+ ```typescript
107
+ // Simple usage
108
+ app.get('/hello', (ctx) => {
109
+ return ctx.status(200, { message: 'Hello World' });
110
+ });
111
+
112
+ // With response validation
113
+ app.get('/user/:id', (ctx) => {
114
+ const userId = ctx.params.id;
115
+
116
+ if (userId === 'not-found') {
117
+ // TypeScript suggests 404 as valid status
118
+ return ctx.status(404, { error: 'User not found' });
119
+ }
120
+
121
+ // TypeScript suggests 200 as valid status
122
+ return ctx.status(200, { user: { id: userId, name: 'John Doe' } });
123
+ }, {
124
+ response: {
125
+ 200: z.object({
126
+ user: z.object({
127
+ id: z.string(),
128
+ name: z.string()
129
+ })
130
+ }),
131
+ 404: z.object({
132
+ error: z.string()
133
+ })
134
+ }
135
+ });
136
+
137
+ // POST with validation and status responses
138
+ app.post('/users', (ctx) => {
139
+ const { name, email } = ctx.body;
140
+
141
+ if (!name || !email) {
142
+ return ctx.status(400, { error: 'Missing required fields' });
143
+ }
144
+
145
+ return ctx.status(201, {
146
+ success: true,
147
+ user: { id: 1, name, email }
148
+ });
149
+ }, {
150
+ body: z.object({
151
+ name: z.string(),
152
+ email: z.string().email()
153
+ }),
154
+ response: {
155
+ 201: z.object({
156
+ success: z.boolean(),
157
+ user: z.object({
158
+ id: z.number(),
159
+ name: z.string(),
160
+ email: z.string()
161
+ })
162
+ }),
163
+ 400: z.object({
164
+ error: z.string()
165
+ })
166
+ }
167
+ });
168
+ ```
169
+
170
+ **Key Features:**
171
+ - **Type Safety**: Status codes are suggested based on your response configuration
172
+ - **Data Validation**: Return data is validated against the corresponding schema
173
+ - **Autocomplete**: TypeScript provides autocomplete for valid status codes
174
+ - **Return Type Inference**: Return types are properly inferred from schemas
175
+
101
176
  ### Validation Configuration
102
177
 
103
178
  Each route can specify validation schemas for different parts of the request:
package/index.ts CHANGED
@@ -3,6 +3,18 @@ import { z } from 'zod';
3
3
  // Type utilities for extracting types from Zod schemas
4
4
  type InferZodType<T> = T extends z.ZodType<infer U> ? U : never;
5
5
 
6
+ // Response configuration types
7
+ type ResponseSchema = z.ZodSchema<any>;
8
+ type StatusResponseSchema = Record<number, ResponseSchema>;
9
+ type ResponseConfig = ResponseSchema | StatusResponseSchema;
10
+
11
+ // Type utility to extract response type from response config
12
+ type InferResponseType<T> = T extends ResponseSchema
13
+ ? InferZodType<T>
14
+ : T extends StatusResponseSchema
15
+ ? { [K in keyof T]: InferZodType<T[K]> }[keyof T]
16
+ : never;
17
+
6
18
  // Cookie interface
7
19
  interface Cookie {
8
20
  name: string;
@@ -35,10 +47,13 @@ interface RouteConfig {
35
47
  body?: z.ZodSchema<any>;
36
48
  headers?: z.ZodSchema<any>;
37
49
  cookies?: z.ZodSchema<any>;
38
- response?: z.ZodSchema<any>;
50
+ response?: ResponseConfig;
39
51
  detail?: RouteDetail;
40
52
  }
41
53
 
54
+ // Helper type to extract status codes from response config
55
+ type StatusCodes<T> = T extends Record<number, any> ? keyof T : never;
56
+
42
57
  // Context type that's fully typed based on the route configuration
43
58
  export type Context<TConfig extends RouteConfig = {}> = {
44
59
  params: TConfig['params'] extends z.ZodSchema<any> ? InferZodType<TConfig['params']> : Record<string, string>;
@@ -53,11 +68,29 @@ export type Context<TConfig extends RouteConfig = {}> = {
53
68
  headers?: Record<string, string>;
54
69
  cookies?: Cookie[];
55
70
  };
71
+ status: <T extends number>(
72
+ code: TConfig['response'] extends StatusResponseSchema
73
+ ? StatusCodes<TConfig['response']> | number
74
+ : T,
75
+ data?: TConfig['response'] extends StatusResponseSchema
76
+ ? T extends keyof TConfig['response']
77
+ ? InferZodType<TConfig['response'][T]>
78
+ : any
79
+ : TConfig['response'] extends ResponseSchema
80
+ ? InferZodType<TConfig['response']>
81
+ : any
82
+ ) => TConfig['response'] extends StatusResponseSchema
83
+ ? T extends keyof TConfig['response']
84
+ ? InferZodType<TConfig['response'][T]>
85
+ : any
86
+ : TConfig['response'] extends ResponseSchema
87
+ ? InferZodType<TConfig['response']>
88
+ : any;
56
89
  [key: string]: any;
57
90
  };
58
91
 
59
- // Handler function type
60
- type Handler<TConfig extends RouteConfig = {}, EC = {}> = (ctx: Context<TConfig> & EC) => Promise<any> | any;
92
+ // Handler function type with proper response typing
93
+ type Handler<TConfig extends RouteConfig = {}, EC = {}> = (ctx: Context<TConfig> & EC) => Promise<InferResponseType<TConfig['response']>> | InferResponseType<TConfig['response']>;
61
94
 
62
95
  // Route definition
63
96
  interface Route {
@@ -395,18 +428,18 @@ export default class BXO {
395
428
  // Parse query string
396
429
  private parseQuery(searchParams: URLSearchParams): Record<string, string | undefined> {
397
430
  const query: Record<string, string | undefined> = {};
398
- for (const [key, value] of searchParams.entries()) {
431
+ searchParams.forEach((value, key) => {
399
432
  query[key] = value;
400
- }
433
+ });
401
434
  return query;
402
435
  }
403
436
 
404
437
  // Parse headers
405
438
  private parseHeaders(headers: Headers): Record<string, string> {
406
439
  const headerObj: Record<string, string> = {};
407
- for (const [key, value] of headers.entries()) {
440
+ headers.forEach((value, key) => {
408
441
  headerObj[key] = value;
409
- }
442
+ });
410
443
  return headerObj;
411
444
  }
412
445
 
@@ -433,6 +466,38 @@ export default class BXO {
433
466
  return schema.parse(data);
434
467
  }
435
468
 
469
+ // Validate response against response config (supports both simple and status-based schemas)
470
+ private validateResponse(responseConfig: ResponseConfig | undefined, data: any, status: number = 200): any {
471
+ if (!responseConfig) return data;
472
+
473
+ // If it's a simple schema (not status-based)
474
+ if ('parse' in responseConfig && typeof responseConfig.parse === 'function') {
475
+ return responseConfig.parse(data);
476
+ }
477
+
478
+ // If it's a status-based schema
479
+ if (typeof responseConfig === 'object' && !('parse' in responseConfig)) {
480
+ const statusSchema = responseConfig[status];
481
+ if (statusSchema) {
482
+ return statusSchema.parse(data);
483
+ }
484
+
485
+ // If no specific status schema found, try to find a fallback
486
+ // Common fallback statuses: 200, 201, 400, 500
487
+ const fallbackStatuses = [200, 201, 400, 500];
488
+ for (const fallbackStatus of fallbackStatuses) {
489
+ if (responseConfig[fallbackStatus]) {
490
+ return responseConfig[fallbackStatus].parse(data);
491
+ }
492
+ }
493
+
494
+ // If no schema found for the status, return data as-is
495
+ return data;
496
+ }
497
+
498
+ return data;
499
+ }
500
+
436
501
  // Main request handler
437
502
  private async handleRequest(request: Request, server?: any): Promise<Response | undefined> {
438
503
  const url = new URL(request.url);
@@ -514,7 +579,11 @@ export default class BXO {
514
579
  cookies: validatedCookies,
515
580
  path: pathname,
516
581
  request,
517
- set: {}
582
+ set: {},
583
+ status: ((code: number, data?: any) => {
584
+ ctx.set.status = code;
585
+ return data;
586
+ }) as any
518
587
  };
519
588
  } catch (validationError) {
520
589
  // Validation failed - return error response
@@ -575,7 +644,8 @@ export default class BXO {
575
644
  if (route.config?.response && !(response instanceof Response)) {
576
645
  try {
577
646
  console.log('response', response);
578
- response = this.validateData(route.config.response, response);
647
+ const status = ctx.set.status || 200;
648
+ response = this.validateResponse(route.config.response, response, status);
579
649
  } catch (validationError) {
580
650
  // Response validation failed
581
651
 
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.21",
4
+ "version": "0.0.5-dev.23",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {