bxo 0.0.5-dev.60 → 0.0.5-dev.61

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bxo",
3
3
  "module": "index.ts",
4
- "version": "0.0.5-dev.60",
4
+ "version": "0.0.5-dev.61",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "exports": {
@@ -3,15 +3,13 @@ import { z } from 'zod';
3
3
  // Type utilities for extracting types from Zod schemas
4
4
  export type InferZodType<T> = T extends z.ZodType<infer U> ? U : never;
5
5
 
6
- // Response configuration types
6
+ // Response configuration types - only allow Record<number, schema> for better maintainability
7
7
  export type ResponseSchema = z.ZodSchema<any>;
8
8
  export type StatusResponseSchema = Record<number, ResponseSchema>;
9
- export type ResponseConfig = ResponseSchema | StatusResponseSchema;
9
+ export type ResponseConfig = StatusResponseSchema;
10
10
 
11
11
  // Type utility to extract response type from response config
12
- export type InferResponseType<T> = T extends ResponseSchema
13
- ? InferZodType<T>
14
- : T extends StatusResponseSchema
12
+ export type InferResponseType<T> = T extends StatusResponseSchema
15
13
  ? { [K in keyof T]: InferZodType<T[K]> }[number]
16
14
  : never;
17
15
 
@@ -75,15 +73,11 @@ export type Context<TConfig extends RouteConfig = {}> = {
75
73
  ? T extends keyof TConfig['response']
76
74
  ? InferZodType<TConfig['response'][T]>
77
75
  : any
78
- : TConfig['response'] extends ResponseSchema
79
- ? InferZodType<TConfig['response']>
80
76
  : any
81
77
  ) => TConfig['response'] extends StatusResponseSchema
82
78
  ? T extends keyof TConfig['response']
83
79
  ? InferZodType<TConfig['response'][T]>
84
80
  : any
85
- : TConfig['response'] extends ResponseSchema
86
- ? InferZodType<TConfig['response']>
87
81
  : any;
88
82
  redirect: (location: string, status?: number) => Response;
89
83
  clearRedirect: () => void;
@@ -42,7 +42,7 @@ export function validateData<T>(schema: z.ZodSchema<T> | undefined, data: any):
42
42
  return schema.parse(data);
43
43
  }
44
44
 
45
- // Validate response against response config (supports both simple and status-based schemas)
45
+ // Validate response against response config (only supports Record<number, schema> format)
46
46
  export function validateResponse(
47
47
  responseConfig: ResponseConfig | undefined,
48
48
  data: any,
@@ -50,31 +50,22 @@ export function validateResponse(
50
50
  ): any {
51
51
  if (!responseConfig) return data;
52
52
 
53
- // If it's a simple schema (not status-based)
54
- if ('parse' in responseConfig && typeof responseConfig.parse === 'function') {
55
- return responseConfig.parse(data);
53
+ // Get the schema for the specific status code
54
+ const statusSchema = responseConfig[status];
55
+ if (statusSchema) {
56
+ return statusSchema.parse(data);
56
57
  }
57
58
 
58
- // If it's a status-based schema
59
- if (typeof responseConfig === 'object' && !('parse' in responseConfig)) {
60
- const statusSchema = responseConfig[status];
61
- if (statusSchema) {
62
- return statusSchema.parse(data);
59
+ // If no specific status schema found, try to find a fallback
60
+ // Common fallback statuses: 200, 201, 400, 500
61
+ const fallbackStatuses = [200, 201, 400, 500];
62
+ for (const fallbackStatus of fallbackStatuses) {
63
+ if (responseConfig[fallbackStatus]) {
64
+ return responseConfig[fallbackStatus]?.parse(data);
63
65
  }
64
-
65
- // If no specific status schema found, try to find a fallback
66
- // Common fallback statuses: 200, 201, 400, 500
67
- const fallbackStatuses = [200, 201, 400, 500];
68
- for (const fallbackStatus of fallbackStatuses) {
69
- if (responseConfig[fallbackStatus]) {
70
- return responseConfig[fallbackStatus]?.parse(data);
71
- }
72
- }
73
-
74
- // If no schema found for the status, return data as-is
75
- return data;
76
66
  }
77
67
 
68
+ // If no schema found for the status, return data as-is
78
69
  return data;
79
70
  }
80
71
 
@@ -175,10 +166,6 @@ function setNestedValue(obj: any, baseKey: string, path: string[], value: any, i
175
166
  // Numeric key - treat as array index
176
167
  const index = parseInt(lastKey, 10);
177
168
  if (Array.isArray(current)) {
178
- // Ensure array is large enough
179
- while (current.length <= index) {
180
- current.push(undefined);
181
- }
182
169
  current[index] = value;
183
170
  } else {
184
171
  // Convert to array if needed
@@ -22,7 +22,7 @@ describe('Utility Functions', () => {
22
22
  it('should parse URLSearchParams correctly', () => {
23
23
  const searchParams = new URLSearchParams('name=john&age=25&city=new%20york');
24
24
  const result = parseQuery(searchParams);
25
-
25
+
26
26
  expect(result).toEqual({
27
27
  name: 'john',
28
28
  age: '25',
@@ -33,7 +33,7 @@ describe('Utility Functions', () => {
33
33
  it('should handle empty search params', () => {
34
34
  const searchParams = new URLSearchParams('');
35
35
  const result = parseQuery(searchParams);
36
-
36
+
37
37
  expect(result).toEqual({});
38
38
  });
39
39
  });
@@ -43,9 +43,9 @@ describe('Utility Functions', () => {
43
43
  const headers = new Headers();
44
44
  headers.set('content-type', 'application/json');
45
45
  headers.set('authorization', 'Bearer token123');
46
-
46
+
47
47
  const result = parseHeaders(headers);
48
-
48
+
49
49
  expect(result).toEqual({
50
50
  'content-type': 'application/json',
51
51
  'authorization': 'Bearer token123'
@@ -57,7 +57,7 @@ describe('Utility Functions', () => {
57
57
  it('should parse cookie header correctly', () => {
58
58
  const cookieHeader = 'name=john; age=25; city=new%20york';
59
59
  const result = parseCookies(cookieHeader);
60
-
60
+
61
61
  expect(result).toEqual({
62
62
  name: 'john',
63
63
  age: '25',
@@ -82,17 +82,17 @@ describe('Utility Functions', () => {
82
82
  name: z.string(),
83
83
  age: z.number()
84
84
  });
85
-
85
+
86
86
  const data = { name: 'john', age: 25 };
87
87
  const result = validateData(schema, data);
88
-
88
+
89
89
  expect(result).toEqual(data);
90
90
  });
91
91
 
92
92
  it('should return data without validation when no schema', () => {
93
93
  const data = { name: 'john', age: 25 };
94
94
  const result = validateData(undefined, data);
95
-
95
+
96
96
  expect(result).toEqual(data);
97
97
  });
98
98
 
@@ -101,9 +101,9 @@ describe('Utility Functions', () => {
101
101
  name: z.string(),
102
102
  age: z.number()
103
103
  });
104
-
104
+
105
105
  const data = { name: 'john', age: 'invalid' };
106
-
106
+
107
107
  expect(() => validateData(schema, data)).toThrow();
108
108
  });
109
109
  });
@@ -113,10 +113,12 @@ describe('Utility Functions', () => {
113
113
  const schema = z.object({
114
114
  message: z.string()
115
115
  });
116
-
116
+
117
117
  const data = { message: 'success' };
118
- const result = validateResponse(schema, data);
119
-
118
+ const result = validateResponse({
119
+ 200: schema
120
+ }, data, 200);
121
+
120
122
  expect(result).toEqual(data);
121
123
  });
122
124
 
@@ -125,17 +127,17 @@ describe('Utility Functions', () => {
125
127
  200: z.object({ message: z.string() }),
126
128
  400: z.object({ error: z.string() })
127
129
  };
128
-
130
+
129
131
  const data = { message: 'success' };
130
132
  const result = validateResponse(schema, data, 200);
131
-
133
+
132
134
  expect(result).toEqual(data);
133
135
  });
134
136
 
135
137
  it('should return data without validation when no schema', () => {
136
138
  const data = { message: 'success' };
137
139
  const result = validateResponse(undefined, data);
138
-
140
+
139
141
  expect(result).toEqual(data);
140
142
  });
141
143
  });
@@ -152,9 +154,9 @@ describe('Utility Functions', () => {
152
154
  httpOnly: true
153
155
  }
154
156
  ];
155
-
157
+
156
158
  const result = cookiesToHeaders(cookies);
157
-
159
+
158
160
  expect(result).toHaveLength(1);
159
161
  expect(result[0]).toContain('session=abc123');
160
162
  expect(result[0]).toContain('Domain=example.com');
@@ -170,9 +172,9 @@ describe('Utility Functions', () => {
170
172
  const cookies = [
171
173
  { name: 'session', value: 'abc123' }
172
174
  ];
173
-
175
+
174
176
  const result = mergeHeadersWithCookies(headers, cookies);
175
-
177
+
176
178
  expect(result.get('content-type')).toBe('application/json');
177
179
  expect(result.get('set-cookie')).toBe('session=abc123');
178
180
  });
@@ -181,14 +183,14 @@ describe('Utility Functions', () => {
181
183
  describe('createRedirectResponse', () => {
182
184
  it('should create redirect response with default status', () => {
183
185
  const result = createRedirectResponse('/new-location');
184
-
186
+
185
187
  expect(result.status).toBe(302);
186
188
  expect(result.headers.get('location')).toBe('/new-location');
187
189
  });
188
190
 
189
191
  it('should create redirect response with custom status', () => {
190
192
  const result = createRedirectResponse('/new-location', 301);
191
-
193
+
192
194
  expect(result.status).toBe(301);
193
195
  expect(result.headers.get('location')).toBe('/new-location');
194
196
  });
@@ -196,7 +198,7 @@ describe('Utility Functions', () => {
196
198
  it('should include additional headers', () => {
197
199
  const headers = { 'x-custom': 'value' };
198
200
  const result = createRedirectResponse('/new-location', 302, headers);
199
-
201
+
200
202
  expect(result.headers.get('x-custom')).toBe('value');
201
203
  });
202
204
  });
@@ -228,7 +230,7 @@ describe('Utility Functions', () => {
228
230
  });
229
231
 
230
232
  const result = await parseRequestBody(request);
231
-
233
+
232
234
  expect(result.app).toBe('zodula');
233
235
  expect(result.model).toBe('zodula_User');
234
236
  expect(Array.isArray(result.recordIds)).toBe(true);
@@ -247,7 +249,7 @@ describe('Utility Functions', () => {
247
249
  });
248
250
 
249
251
  const result = await parseRequestBody(request);
250
-
252
+
251
253
  expect(result.name).toBe('john');
252
254
  expect(result.email).toBe('john@example.com');
253
255
  });
@@ -264,7 +266,7 @@ describe('Utility Functions', () => {
264
266
  });
265
267
 
266
268
  const result = await parseRequestBody(request);
267
-
269
+
268
270
  expect(result.name).toBe('john');
269
271
  expect(result.file).toBeInstanceOf(File);
270
272
  expect(result.file.name).toBe('test.txt');
@@ -282,7 +284,7 @@ describe('Utility Functions', () => {
282
284
  });
283
285
 
284
286
  const result = await parseRequestBody(request);
285
-
287
+
286
288
  expect(result.test).toEqual({
287
289
  test: 'test',
288
290
  new: 'new',
@@ -313,7 +315,7 @@ describe('Utility Functions', () => {
313
315
  });
314
316
 
315
317
  const result = await parseRequestBody(request);
316
-
318
+
317
319
  expect(result.x).toBe('1');
318
320
  expect(result.arr).toEqual(['1', '2', '3']);
319
321
  expect(result.arr2).toEqual(['1', ['2'], '3']);
@@ -335,7 +337,7 @@ describe('Utility Functions', () => {
335
337
  });
336
338
 
337
339
  const result = await parseRequestBody(request);
338
-
340
+
339
341
  expect(result.myObj).toEqual({ x: 1, s: 'foo' });
340
342
  expect(result.settings).toEqual({ theme: 'dark', notifications: true });
341
343
  });
@@ -354,7 +356,7 @@ describe('Utility Functions', () => {
354
356
  });
355
357
 
356
358
  const result = await parseRequestBody(request);
357
-
359
+
358
360
  expect(result.tags).toEqual(['javascript', 'typescript']);
359
361
  expect(result.scores).toEqual(['100', '95', '88']);
360
362
  });
@@ -363,7 +365,7 @@ describe('Utility Functions', () => {
363
365
  describe('File Upload Utilities', () => {
364
366
  it('should identify file uploads correctly', () => {
365
367
  const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
366
-
368
+
367
369
  expect(isFileUpload(file)).toBe(true);
368
370
  expect(isFileUpload('not a file')).toBe(false);
369
371
  expect(isFileUpload({ type: 'text' })).toBe(false);
@@ -371,7 +373,7 @@ describe('Utility Functions', () => {
371
373
 
372
374
  it('should extract file from upload', () => {
373
375
  const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
374
-
376
+
375
377
  const result = getFileFromUpload(file);
376
378
  expect(result).toBe(file);
377
379
  });
@@ -383,7 +385,7 @@ describe('Utility Functions', () => {
383
385
 
384
386
  it('should get file info', () => {
385
387
  const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
386
-
388
+
387
389
  const result = getFileInfo(file);
388
390
  expect(result).toEqual({
389
391
  name: 'test.jpg',
@@ -405,9 +407,9 @@ describe('Utility Functions', () => {
405
407
  avatar: file,
406
408
  email: 'john@example.com'
407
409
  };
408
-
410
+
409
411
  const result = getFileUploads(formData);
410
-
412
+
411
413
  expect(Object.keys(result)).toHaveLength(1);
412
414
  expect(result.avatar).toBeInstanceOf(File);
413
415
  });
@@ -419,9 +421,9 @@ describe('Utility Functions', () => {
419
421
  avatar: file,
420
422
  email: 'john@example.com'
421
423
  };
422
-
424
+
423
425
  const result = getFormFields(formData);
424
-
426
+
425
427
  expect(result).toEqual({
426
428
  name: 'john',
427
429
  email: 'john@example.com'