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.
- package/index.ts +190 -19
- package/package.json +1 -1
- 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
|
-
//
|
|
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 (
|
|
325
|
-
// For
|
|
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
|
|
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
|
-
//
|
|
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 (
|
|
389
|
-
// For
|
|
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
|
|
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
|
-
//
|
|
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
|
|
613
|
-
const responseHeaders: Record<string, string> = {
|
|
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
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');
|