bxo 0.0.5-dev.34 → 0.0.5-dev.36

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/index.ts +190 -19
  2. package/package.json +1 -1
  3. package/example.ts +0 -33
package/index.ts CHANGED
@@ -88,6 +88,7 @@ export type Context<TConfig extends RouteConfig = {}> = {
88
88
  ? InferZodType<TConfig['response']>
89
89
  : any;
90
90
  redirect: (location: string, status?: number) => Response;
91
+ clearRedirect: () => void;
91
92
  [key: string]: any;
92
93
  };
93
94
 
@@ -318,13 +319,22 @@ export default class BXO {
318
319
  const params: Record<string, string> = {};
319
320
  let isMatch = true;
320
321
 
321
- // Handle wildcard at the end (catch-all)
322
+ // Check for double wildcard (**) in the route
323
+ const hasDoubleWildcard = routeSegments.some(segment => segment === '**');
324
+
325
+ // Handle double wildcard at the end (catch-all with slashes)
326
+ const hasDoubleWildcardAtEnd = routeSegments.length > 0 && routeSegments[routeSegments.length - 1] === '**';
327
+
328
+ // Handle single wildcard at the end (catch-all)
322
329
  const hasWildcardAtEnd = routeSegments.length > 0 && routeSegments[routeSegments.length - 1] === '*';
323
330
 
324
- if (hasWildcardAtEnd) {
325
- // For catch-all wildcard, path must have at least as many segments as route (minus the wildcard)
331
+ if (hasDoubleWildcardAtEnd) {
332
+ // For double wildcard at end, path must have at least as many segments as route (minus the double wildcard)
326
333
  if (pathSegments.length < routeSegments.length - 1) continue;
327
- } else {
334
+ } else if (hasWildcardAtEnd) {
335
+ // For single wildcard at end, path must have at least as many segments as route (minus the wildcard)
336
+ if (pathSegments.length < routeSegments.length - 1) continue;
337
+ } else if (!hasDoubleWildcard) {
328
338
  // For exact matching (with possible single-segment wildcards), lengths must match
329
339
  if (routeSegments.length !== pathSegments.length) continue;
330
340
  }
@@ -338,7 +348,14 @@ export default class BXO {
338
348
  break;
339
349
  }
340
350
 
341
- // Handle catch-all wildcard at the end
351
+ // Handle double wildcard at the end (matches everything including slashes)
352
+ if (routeSegment === '**' && i === routeSegments.length - 1) {
353
+ const remainingPath = pathSegments.slice(i).join('/');
354
+ params['**'] = remainingPath;
355
+ break;
356
+ }
357
+
358
+ // Handle single wildcard at the end (catch-all)
342
359
  if (routeSegment === '*' && i === routeSegments.length - 1) {
343
360
  // Wildcard at end matches remaining path segments
344
361
  const remainingPath = pathSegments.slice(i).join('/');
@@ -346,6 +363,68 @@ export default class BXO {
346
363
  break;
347
364
  }
348
365
 
366
+ // Handle double wildcard in the middle (matches everything including slashes)
367
+ if (routeSegment === '**') {
368
+ // Find the next non-wildcard segment to match against
369
+ let nextNonWildcardIndex = i + 1;
370
+ while (nextNonWildcardIndex < routeSegments.length &&
371
+ (routeSegments[nextNonWildcardIndex] === '*' || routeSegments[nextNonWildcardIndex] === '**')) {
372
+ nextNonWildcardIndex++;
373
+ }
374
+
375
+ if (nextNonWildcardIndex >= routeSegments.length) {
376
+ // Double wildcard is at the end or followed by other wildcards
377
+ const remainingPath = pathSegments.slice(i).join('/');
378
+ params['**'] = remainingPath;
379
+ break;
380
+ }
381
+
382
+ // Find the next matching segment in the path
383
+ const nextRouteSegment = routeSegments[nextNonWildcardIndex];
384
+ if (!nextRouteSegment) {
385
+ isMatch = false;
386
+ break;
387
+ }
388
+
389
+ let foundMatch = false;
390
+ let matchedPath = '';
391
+
392
+ for (let j = i; j < pathSegments.length; j++) {
393
+ const currentPathSegment = pathSegments[j];
394
+
395
+ // Check if this path segment matches the next route segment
396
+ if (nextRouteSegment.startsWith(':')) {
397
+ // Param segment - always matches
398
+ matchedPath = pathSegments.slice(i, j).join('/');
399
+ params['**'] = matchedPath;
400
+ i = j - 1; // Adjust index for the next iteration
401
+ foundMatch = true;
402
+ break;
403
+ } else if (nextRouteSegment === '*') {
404
+ // Single wildcard - always matches
405
+ matchedPath = pathSegments.slice(i, j).join('/');
406
+ params['**'] = matchedPath;
407
+ i = j - 1; // Adjust index for the next iteration
408
+ foundMatch = true;
409
+ break;
410
+ } else if (nextRouteSegment === currentPathSegment) {
411
+ // Exact match
412
+ matchedPath = pathSegments.slice(i, j).join('/');
413
+ params['**'] = matchedPath;
414
+ i = j - 1; // Adjust index for the next iteration
415
+ foundMatch = true;
416
+ break;
417
+ }
418
+ }
419
+
420
+ if (!foundMatch) {
421
+ isMatch = false;
422
+ break;
423
+ }
424
+
425
+ continue;
426
+ }
427
+
349
428
  if (!pathSegment) {
350
429
  isMatch = false;
351
430
  break;
@@ -382,13 +461,22 @@ export default class BXO {
382
461
  const params: Record<string, string> = {};
383
462
  let isMatch = true;
384
463
 
385
- // Handle wildcard at the end (catch-all)
464
+ // Check for double wildcard (**) in the route
465
+ const hasDoubleWildcard = routeSegments.some(segment => segment === '**');
466
+
467
+ // Handle double wildcard at the end (catch-all with slashes)
468
+ const hasDoubleWildcardAtEnd = routeSegments.length > 0 && routeSegments[routeSegments.length - 1] === '**';
469
+
470
+ // Handle single wildcard at the end (catch-all)
386
471
  const hasWildcardAtEnd = routeSegments.length > 0 && routeSegments[routeSegments.length - 1] === '*';
387
472
 
388
- if (hasWildcardAtEnd) {
389
- // For catch-all wildcard, path must have at least as many segments as route (minus the wildcard)
473
+ if (hasDoubleWildcardAtEnd) {
474
+ // For double wildcard at end, path must have at least as many segments as route (minus the double wildcard)
390
475
  if (pathSegments.length < routeSegments.length - 1) continue;
391
- } else {
476
+ } else if (hasWildcardAtEnd) {
477
+ // For single wildcard at end, path must have at least as many segments as route (minus the wildcard)
478
+ if (pathSegments.length < routeSegments.length - 1) continue;
479
+ } else if (!hasDoubleWildcard) {
392
480
  // For exact matching (with possible single-segment wildcards), lengths must match
393
481
  if (routeSegments.length !== pathSegments.length) continue;
394
482
  }
@@ -402,7 +490,14 @@ export default class BXO {
402
490
  break;
403
491
  }
404
492
 
405
- // Handle catch-all wildcard at the end
493
+ // Handle double wildcard at the end (matches everything including slashes)
494
+ if (routeSegment === '**' && i === routeSegments.length - 1) {
495
+ const remainingPath = pathSegments.slice(i).join('/');
496
+ params['**'] = remainingPath;
497
+ break;
498
+ }
499
+
500
+ // Handle single wildcard at the end (catch-all)
406
501
  if (routeSegment === '*' && i === routeSegments.length - 1) {
407
502
  // Wildcard at end matches remaining path segments
408
503
  const remainingPath = pathSegments.slice(i).join('/');
@@ -410,6 +505,68 @@ export default class BXO {
410
505
  break;
411
506
  }
412
507
 
508
+ // Handle double wildcard in the middle (matches everything including slashes)
509
+ if (routeSegment === '**') {
510
+ // Find the next non-wildcard segment to match against
511
+ let nextNonWildcardIndex = i + 1;
512
+ while (nextNonWildcardIndex < routeSegments.length &&
513
+ (routeSegments[nextNonWildcardIndex] === '*' || routeSegments[nextNonWildcardIndex] === '**')) {
514
+ nextNonWildcardIndex++;
515
+ }
516
+
517
+ if (nextNonWildcardIndex >= routeSegments.length) {
518
+ // Double wildcard is at the end or followed by other wildcards
519
+ const remainingPath = pathSegments.slice(i).join('/');
520
+ params['**'] = remainingPath;
521
+ break;
522
+ }
523
+
524
+ // Find the next matching segment in the path
525
+ const nextRouteSegment = routeSegments[nextNonWildcardIndex];
526
+ if (!nextRouteSegment) {
527
+ isMatch = false;
528
+ break;
529
+ }
530
+
531
+ let foundMatch = false;
532
+ let matchedPath = '';
533
+
534
+ for (let j = i; j < pathSegments.length; j++) {
535
+ const currentPathSegment = pathSegments[j];
536
+
537
+ // Check if this path segment matches the next route segment
538
+ if (nextRouteSegment.startsWith(':')) {
539
+ // Param segment - always matches
540
+ matchedPath = pathSegments.slice(i, j).join('/');
541
+ params['**'] = matchedPath;
542
+ i = j - 1; // Adjust index for the next iteration
543
+ foundMatch = true;
544
+ break;
545
+ } else if (nextRouteSegment === '*') {
546
+ // Single wildcard - always matches
547
+ matchedPath = pathSegments.slice(i, j).join('/');
548
+ params['**'] = matchedPath;
549
+ i = j - 1; // Adjust index for the next iteration
550
+ foundMatch = true;
551
+ break;
552
+ } else if (nextRouteSegment === currentPathSegment) {
553
+ // Exact match
554
+ matchedPath = pathSegments.slice(i, j).join('/');
555
+ params['**'] = matchedPath;
556
+ i = j - 1; // Adjust index for the next iteration
557
+ foundMatch = true;
558
+ break;
559
+ }
560
+ }
561
+
562
+ if (!foundMatch) {
563
+ isMatch = false;
564
+ break;
565
+ }
566
+
567
+ continue;
568
+ }
569
+
413
570
  if (!pathSegment) {
414
571
  isMatch = false;
415
572
  break;
@@ -601,16 +758,14 @@ export default class BXO {
601
758
  return data;
602
759
  }) as any,
603
760
  redirect: ((location: string, status: number = 302) => {
604
- // Persist redirect intent on context so it also works without returning
605
- ctx.set.status = status;
606
- ctx.set.headers = {
607
- ...(ctx.set.headers || {}),
608
- Location: location
609
- };
761
+ // Record redirect intent only; avoid mutating generic status/headers so it can be canceled later
610
762
  ctx.set.redirect = { location, status };
611
763
 
612
- // Prepare headers for immediate Response return (merging any existing headers)
613
- const responseHeaders: Record<string, string> = { ...ctx.set.headers };
764
+ // Prepare headers for immediate Response return without persisting to ctx.set.headers
765
+ const responseHeaders: Record<string, string> = {
766
+ Location: location,
767
+ ...(ctx.set.headers || {})
768
+ };
614
769
 
615
770
  // Handle cookies if any are set on context
616
771
  if (ctx.set.cookies && ctx.set.cookies.length > 0) {
@@ -634,7 +789,23 @@ export default class BXO {
634
789
  status,
635
790
  headers: responseHeaders
636
791
  });
637
- }) as any
792
+ }) as any,
793
+ clearRedirect: (() => {
794
+ // Clear explicit redirect intent
795
+ delete ctx.set.redirect;
796
+ // Remove any Location header if present
797
+ if (ctx.set.headers) {
798
+ for (const key of Object.keys(ctx.set.headers)) {
799
+ if (key.toLowerCase() === 'location') {
800
+ delete ctx.set.headers[key];
801
+ }
802
+ }
803
+ }
804
+ // Reset status if it is a redirect
805
+ if (typeof ctx.set.status === 'number' && ctx.set.status >= 300 && ctx.set.status < 400) {
806
+ delete ctx.set.status;
807
+ }
808
+ }) as any
638
809
  };
639
810
  } catch (validationError) {
640
811
  // Validation failed - return error response
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.34",
4
+ "version": "0.0.5-dev.36",
5
5
  "description": "A simple and lightweight web framework for Bun",
6
6
  "type": "module",
7
7
  "devDependencies": {
package/example.ts DELETED
@@ -1,33 +0,0 @@
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');