@buzola/router 0.0.3 → 0.0.4
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/dist/define/create-page.d.ts +17 -5
- package/dist/define/create-page.d.ts.map +1 -1
- package/dist/define/create-page.js +78 -38
- package/dist/define/create-page.js.map +1 -1
- package/dist/engine/loader-cache.d.ts +18 -0
- package/dist/engine/loader-cache.d.ts.map +1 -0
- package/dist/engine/loader-cache.js +48 -0
- package/dist/engine/loader-cache.js.map +1 -0
- package/dist/engine/matcher.d.ts +1 -1
- package/dist/engine/matcher.d.ts.map +1 -1
- package/dist/engine/navigation-adapter.d.ts +1 -1
- package/dist/engine/navigation-adapter.d.ts.map +1 -1
- package/dist/engine/route-tree.d.ts +1 -2
- package/dist/engine/route-tree.d.ts.map +1 -1
- package/dist/engine/route-tree.js +5 -4
- package/dist/engine/route-tree.js.map +1 -1
- package/dist/engine/router.d.ts +7 -10
- package/dist/engine/router.d.ts.map +1 -1
- package/dist/engine/router.js +23 -67
- package/dist/engine/router.js.map +1 -1
- package/dist/engine/schema.d.ts +5 -1
- package/dist/engine/schema.d.ts.map +1 -1
- package/dist/engine/schema.js +11 -1
- package/dist/engine/schema.js.map +1 -1
- package/dist/engine/types.d.ts +2 -15
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/utils.d.ts +8 -0
- package/dist/engine/utils.d.ts.map +1 -1
- package/dist/engine/utils.js +10 -3
- package/dist/engine/utils.js.map +1 -1
- package/dist/index.d.ts +14 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/react/context.d.ts +2 -2
- package/dist/react/context.d.ts.map +1 -1
- package/dist/react/hooks.d.ts +3 -3
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +11 -9
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/index.d.ts +11 -11
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +6 -6
- package/dist/react/index.js.map +1 -1
- package/dist/react/link.d.ts +5 -0
- package/dist/react/link.d.ts.map +1 -1
- package/dist/react/link.js +10 -1
- package/dist/react/link.js.map +1 -1
- package/dist/react/outlet.d.ts +3 -1
- package/dist/react/outlet.d.ts.map +1 -1
- package/dist/react/outlet.js +25 -1
- package/dist/react/outlet.js.map +1 -1
- package/dist/react/provider.d.ts +15 -2
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react/provider.js +15 -4
- package/dist/react/provider.js.map +1 -1
- package/package.json +6 -3
- package/src/__tests__/create-page.test.ts +42 -6
- package/src/__tests__/loader-cache.test.ts +115 -0
- package/src/__tests__/matcher.test.ts +3 -3
- package/src/__tests__/react.test.tsx +1020 -0
- package/src/__tests__/router.test.ts +4 -214
- package/src/__tests__/setup-dom.ts +21 -0
- package/src/define/create-page.tsx +94 -46
- package/src/engine/loader-cache.ts +55 -0
- package/src/engine/matcher.ts +1 -1
- package/src/engine/navigation-adapter.ts +1 -1
- package/src/engine/route-tree.ts +4 -6
- package/src/engine/router.ts +27 -73
- package/src/engine/schema.ts +13 -2
- package/src/engine/types.ts +2 -18
- package/src/engine/utils.ts +11 -3
- package/src/index.ts +15 -17
- package/src/react/context.ts +2 -2
- package/src/react/hooks.ts +14 -13
- package/src/react/index.ts +21 -11
- package/src/react/link.tsx +16 -0
- package/src/react/outlet.tsx +32 -1
- package/src/react/provider.tsx +33 -5
- package/dist/__tests__/create-page.test.d.ts +0 -2
- package/dist/__tests__/create-page.test.d.ts.map +0 -1
- package/dist/__tests__/create-page.test.js +0 -457
- package/dist/__tests__/create-page.test.js.map +0 -1
- package/dist/__tests__/matcher.test.d.ts +0 -2
- package/dist/__tests__/matcher.test.d.ts.map +0 -1
- package/dist/__tests__/matcher.test.js +0 -108
- package/dist/__tests__/matcher.test.js.map +0 -1
- package/dist/__tests__/router.test.d.ts +0 -2
- package/dist/__tests__/router.test.d.ts.map +0 -1
- package/dist/__tests__/router.test.js +0 -327
- package/dist/__tests__/router.test.js.map +0 -1
- package/dist/__tests__/type-safety.test.d.ts +0 -15
- package/dist/__tests__/type-safety.test.d.ts.map +0 -1
- package/dist/__tests__/type-safety.test.js +0 -163
- package/dist/__tests__/type-safety.test.js.map +0 -1
package/dist/react/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { BuzolaProvider } from './provider';
|
|
2
|
-
export type { BuzolaProviderProps } from './provider';
|
|
3
|
-
export { Outlet } from './outlet';
|
|
4
|
-
export type { OutletProps } from './outlet';
|
|
5
|
-
export { Link } from './link';
|
|
6
|
-
export type { LinkProps } from './link';
|
|
7
|
-
export { ErrorBoundary } from './error-boundary';
|
|
8
|
-
export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams } from './hooks';
|
|
9
|
-
export type { BlockerState } from './hooks';
|
|
10
|
-
export { RouteContext, RouterContext } from './context';
|
|
11
|
-
export type { RouteContextValue } from './context';
|
|
1
|
+
export { BuzolaProvider } from './provider.js';
|
|
2
|
+
export type { BuzolaProviderProps, MiddlewareComponent, MiddlewareProps } from './provider.js';
|
|
3
|
+
export { Outlet } from './outlet.js';
|
|
4
|
+
export type { OutletProps } from './outlet.js';
|
|
5
|
+
export { Link } from './link.js';
|
|
6
|
+
export type { LinkProps } from './link.js';
|
|
7
|
+
export { ErrorBoundary } from './error-boundary.js';
|
|
8
|
+
export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams, } from './hooks.js';
|
|
9
|
+
export type { BlockerState } from './hooks.js';
|
|
10
|
+
export { RouteContext, RouterContext } from './context.js';
|
|
11
|
+
export type { RouteContextValue } from './context.js';
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE9F,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EACN,UAAU,EACV,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,QAAQ,EACR,SAAS,EACT,cAAc,EACd,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { BuzolaProvider } from './provider';
|
|
2
|
-
export { Outlet } from './outlet';
|
|
3
|
-
export { Link } from './link';
|
|
4
|
-
export { ErrorBoundary } from './error-boundary';
|
|
5
|
-
export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams } from './hooks';
|
|
6
|
-
export { RouteContext, RouterContext } from './context';
|
|
1
|
+
export { BuzolaProvider } from './provider.js';
|
|
2
|
+
export { Outlet } from './outlet.js';
|
|
3
|
+
export { Link } from './link.js';
|
|
4
|
+
export { ErrorBoundary } from './error-boundary.js';
|
|
5
|
+
export { useBlocker, useInvalidate, useNavigate, useNavigationState, useParams, useRoute, useRouter, useRouterState, useSearchParams, } from './hooks.js';
|
|
6
|
+
export { RouteContext, RouterContext } from './context.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EACN,UAAU,EACV,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,QAAQ,EACR,SAAS,EACT,cAAc,EACd,eAAe,GACf,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/react/link.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
|
|
|
18
18
|
* When `false`, `data-active` is also set when the current path starts with the link target.
|
|
19
19
|
*/
|
|
20
20
|
activeExact?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Render as child element instead of `<a>`.
|
|
23
|
+
* When `true`, clones the single child element and merges `href`, `onClick`, and `data-active` props into it.
|
|
24
|
+
*/
|
|
25
|
+
asChild?: boolean;
|
|
21
26
|
};
|
|
22
27
|
export type LinkProps<P extends RegisteredPage = RegisteredPage> = [keyof EffectivePageParams<P>] extends [never] ? LinkPropsBase & {
|
|
23
28
|
to: P;
|
package/dist/react/link.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAK9F,KAAK,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,GAAG;IAClF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAA;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAK9F,KAAK,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,GAAG;IAClF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CAAC,MAAM,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9G,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAEzC,EAAE,SAAS,mBAAmB,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,GAC9F,aAAa,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAAE,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CA+DtF"}
|
package/dist/react/link.js
CHANGED
|
@@ -10,7 +10,7 @@ import { useRouter, useRouterState } from './hooks.js';
|
|
|
10
10
|
* Sets `data-active` attribute when the link target matches the current URL.
|
|
11
11
|
*/
|
|
12
12
|
export function Link(props) {
|
|
13
|
-
const { to, params, replace, state, viewTransition, resetScroll, activeExact = true, onClick, children, ...rest } = props;
|
|
13
|
+
const { to, params, replace, state, viewTransition, resetScroll, activeExact = true, asChild, onClick, children, ...rest } = props;
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const routerState = useRouterState();
|
|
16
16
|
const href = router.buildPagePath(to, params);
|
|
@@ -33,6 +33,15 @@ export function Link(props) {
|
|
|
33
33
|
router.navigateToPage(to, params, { replace, state, viewTransition, resetScroll });
|
|
34
34
|
}
|
|
35
35
|
}, [onClick, needsProgrammaticNav, router, to, params, replace, state, viewTransition, resetScroll]);
|
|
36
|
+
if (asChild) {
|
|
37
|
+
const child = React.Children.only(children);
|
|
38
|
+
return React.cloneElement(child, {
|
|
39
|
+
href,
|
|
40
|
+
onClick: handleClick,
|
|
41
|
+
'data-active': isActive ? '' : undefined,
|
|
42
|
+
...rest,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
36
45
|
return (_jsx("a", { href: href, onClick: handleClick, "data-active": isActive ? '' : undefined, ...rest, children: children }));
|
|
37
46
|
}
|
|
38
47
|
//# sourceMappingURL=link.js.map
|
package/dist/react/link.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/react/link.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAmCtD;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAA2B,KAAmB;IACjE,MAAM,EACL,EAAE,EACF,MAAM,EACN,OAAO,EACP,KAAK,EACL,cAAc,EACd,WAAW,EACX,WAAW,GAAG,IAAI,EAClB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,GAAG,IAAI,EACP,GAAG,KAAwE,CAAA;IAE5E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IAEpC,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IAC7C,MAAM,oBAAoB,GAAG,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,cAAc,IAAI,WAAW,KAAK,KAAK,CAAA;IAEtG,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG,WAAW,KAAK,UAAU,CAAA;IAC1C,MAAM,QAAQ,GAAG,UAAU,KAAK,GAAG,IAAI,WAAW,CAAC,UAAU,CAAC,UAAU,GAAG,GAAG,CAAC,CAAA;IAC/E,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,CAAA;IAEtD,MAAM,WAAW,GAAG,WAAW,CAC9B,CAAC,CAAsC,EAAE,EAAE;QAC1C,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;QACZ,IAAI,CAAC,CAAC,gBAAgB;YAAE,OAAM;QAE9B,gDAAgD;QAChD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAM;QAE9E,IAAI,oBAAoB,EAAE,CAAC;YAC1B,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,MAAM,CAAC,cAAc,CACpB,EAAE,EACF,MAAM,EACN,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAqB,CAClE,CAAA;QACF,CAAC;IACF,CAAC,EACD,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,CAAC,CAChG,CAAA;IAED,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAA4B,CAAA;QACtE,OAAO,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE;YAChC,IAAI;YACJ,OAAO,EAAE,WAAW;YACpB,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACxC,GAAG,IAAI;SACP,CAAC,CAAA;IACH,CAAC;IAED,OAAO,CACN,YAAG,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,iBAAe,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAM,IAAI,YACnF,QAAQ,GACN,CACJ,CAAA;AACF,CAAC"}
|
package/dist/react/outlet.d.ts
CHANGED
|
@@ -4,10 +4,12 @@ export interface OutletProps {
|
|
|
4
4
|
fallback?: React.ReactNode;
|
|
5
5
|
/** Fallback to show when a route component throws an error. */
|
|
6
6
|
errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);
|
|
7
|
+
/** Content to show when no route matches. Defaults to a built-in 404 page. Pass `null` to render nothing. */
|
|
8
|
+
notFound?: React.ReactNode;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Renders the component for the current depth in the route match chain.
|
|
10
12
|
* Each Outlet increments the depth and renders the next matched route's component.
|
|
11
13
|
*/
|
|
12
|
-
export declare function Outlet({ fallback, errorFallback }: OutletProps): React.ReactElement | null;
|
|
14
|
+
export declare function Outlet({ fallback, errorFallback, notFound }: OutletProps): React.ReactElement | null;
|
|
13
15
|
//# sourceMappingURL=outlet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outlet.d.ts","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiC,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"outlet.d.ts","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiC,MAAM,OAAO,CAAA;AA4BrD,MAAM,WAAW,WAAW;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC,CAAA;IACrE,6GAA6G;IAC7G,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAwDpG"}
|
package/dist/react/outlet.js
CHANGED
|
@@ -2,11 +2,29 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import React, { Suspense, use, useMemo } from 'react';
|
|
3
3
|
import { RouteContext } from './context.js';
|
|
4
4
|
import { ErrorBoundary } from './error-boundary.js';
|
|
5
|
+
const isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
|
|
6
|
+
function DefaultNotFound() {
|
|
7
|
+
return React.createElement('div', {
|
|
8
|
+
style: { fontFamily: 'system-ui, sans-serif', padding: '2rem', maxWidth: '600px', margin: '0 auto' },
|
|
9
|
+
}, isDev
|
|
10
|
+
? [
|
|
11
|
+
React.createElement('h1', { key: 'h' }, '404 — Page Not Found'),
|
|
12
|
+
React.createElement('p', { key: 'p1' }, `No route matches "${typeof window !== 'undefined' ? window.location.pathname : ''}".`),
|
|
13
|
+
React.createElement('p', {
|
|
14
|
+
key: 'p2',
|
|
15
|
+
style: { color: '#666', fontSize: '0.9rem' },
|
|
16
|
+
}, 'To handle this, add a _404.tsx catch-all route or pass a notFound prop to <Outlet />.'),
|
|
17
|
+
]
|
|
18
|
+
: [
|
|
19
|
+
React.createElement('h1', { key: 'h' }, '404'),
|
|
20
|
+
React.createElement('p', { key: 'p' }, 'Page not found.'),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
5
23
|
/**
|
|
6
24
|
* Renders the component for the current depth in the route match chain.
|
|
7
25
|
* Each Outlet increments the depth and renders the next matched route's component.
|
|
8
26
|
*/
|
|
9
|
-
export function Outlet({ fallback, errorFallback }) {
|
|
27
|
+
export function Outlet({ fallback, errorFallback, notFound }) {
|
|
10
28
|
const routeContext = use(RouteContext);
|
|
11
29
|
if (!routeContext) {
|
|
12
30
|
throw new Error('<Outlet /> must be used within a <BuzolaProvider>');
|
|
@@ -38,6 +56,12 @@ export function Outlet({ fallback, errorFallback }) {
|
|
|
38
56
|
};
|
|
39
57
|
}, [state, depth, effectiveDepth, matches, match, routeContext.params]);
|
|
40
58
|
if (!match) {
|
|
59
|
+
// At root depth with no matches — show 404
|
|
60
|
+
if (depth === 0 && matches.length === 0) {
|
|
61
|
+
if (notFound === null)
|
|
62
|
+
return null;
|
|
63
|
+
return (notFound ?? React.createElement(DefaultNotFound, null));
|
|
64
|
+
}
|
|
41
65
|
return null;
|
|
42
66
|
}
|
|
43
67
|
const Component = match.node.component;
|
package/dist/react/outlet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,YAAY,EAA0B,MAAM,cAAc,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/react/outlet.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/provider.d.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Router } from '../engine/router.js';
|
|
3
|
-
import type { RouteNode } from '../engine/types.js';
|
|
3
|
+
import type { RouteMatch, RouteNode } from '../engine/types.js';
|
|
4
|
+
/** Props passed to the middleware component. */
|
|
5
|
+
export interface MiddlewareProps {
|
|
6
|
+
/** Merged params from all matched routes. */
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
/** All route matches from root to leaf. */
|
|
9
|
+
matches: RouteMatch[];
|
|
10
|
+
/** The rendered route tree to wrap. */
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/** Middleware component that wraps the entire router output. */
|
|
14
|
+
export type MiddlewareComponent = React.ComponentType<MiddlewareProps>;
|
|
4
15
|
export type BuzolaProviderProps = {
|
|
5
16
|
children?: React.ReactNode;
|
|
17
|
+
/** Global middleware that wraps the rendered route tree. Has access to all matched params. */
|
|
18
|
+
middleware?: MiddlewareComponent;
|
|
6
19
|
} & ({
|
|
7
20
|
router: Router;
|
|
8
21
|
routes?: never;
|
|
@@ -18,5 +31,5 @@ export type BuzolaProviderProps = {
|
|
|
18
31
|
* Root provider for Buzola router.
|
|
19
32
|
* Subscribes to router state and provides context to the React tree.
|
|
20
33
|
*/
|
|
21
|
-
export declare function BuzolaProvider({ routes, children, ...props }: BuzolaProviderProps): React.ReactElement;
|
|
34
|
+
export declare function BuzolaProvider({ routes, children, middleware: Middleware, ...props }: BuzolaProviderProps): React.ReactElement;
|
|
22
35
|
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8E,MAAM,OAAO,CAAA;AAElG,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAI/D,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC/B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,2CAA2C;IAC3C,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,uCAAuC;IACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED,gEAAgE;AAChE,MAAM,MAAM,mBAAmB,GAAG,KAAK,CAAC,aAAa,CAAC,eAAe,CAAC,CAAA;AAEtE,MAAM,MAAM,mBAAmB,GAC5B;IACD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,8FAA8F;IAC9F,UAAU,CAAC,EAAE,mBAAmB,CAAA;CAChC,GACC,CACC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,GAClF;IACD,MAAM,CAAC,EAAE,KAAK,CAAA;IACd,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC,CACD,CAAA;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CA+C9H"}
|
package/dist/react/provider.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React, { useCallback,
|
|
2
|
+
import React, { useCallback, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
3
3
|
import { createBrowserNavigationAdapter } from '../engine/navigation-adapter.js';
|
|
4
4
|
import { Router } from '../engine/router.js';
|
|
5
5
|
import { RouteContext, RouterContext } from './context.js';
|
|
@@ -8,7 +8,7 @@ import { Outlet } from './outlet.js';
|
|
|
8
8
|
* Root provider for Buzola router.
|
|
9
9
|
* Subscribes to router state and provides context to the React tree.
|
|
10
10
|
*/
|
|
11
|
-
export function BuzolaProvider({ routes, children, ...props }) {
|
|
11
|
+
export function BuzolaProvider({ routes, children, middleware: Middleware, ...props }) {
|
|
12
12
|
const routerRef = useRef(props.router);
|
|
13
13
|
if (!routerRef.current) {
|
|
14
14
|
routerRef.current = new Router({
|
|
@@ -23,7 +23,7 @@ export function BuzolaProvider({ routes, children, ...props }) {
|
|
|
23
23
|
const getSnapshot = useCallback(() => router.getState(), [router]);
|
|
24
24
|
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
25
25
|
// Start navigation interception
|
|
26
|
-
|
|
26
|
+
useLayoutEffect(() => {
|
|
27
27
|
return router.start();
|
|
28
28
|
}, [router]);
|
|
29
29
|
const routeContextValue = useMemo(() => ({
|
|
@@ -32,6 +32,17 @@ export function BuzolaProvider({ routes, children, ...props }) {
|
|
|
32
32
|
matches: state.matches,
|
|
33
33
|
params: {},
|
|
34
34
|
}), [state]);
|
|
35
|
-
|
|
35
|
+
// Merge params from all matches for middleware
|
|
36
|
+
const allParams = useMemo(() => {
|
|
37
|
+
let merged = {};
|
|
38
|
+
for (const match of state.matches) {
|
|
39
|
+
merged = { ...merged, ...match.params };
|
|
40
|
+
}
|
|
41
|
+
return merged;
|
|
42
|
+
}, [state.matches]);
|
|
43
|
+
const content = children ?? _jsx(Outlet, {});
|
|
44
|
+
return (_jsx(RouterContext, { value: router, children: _jsx(RouteContext, { value: routeContextValue, children: Middleware
|
|
45
|
+
? _jsx(Middleware, { params: allParams, matches: state.matches, children: content })
|
|
46
|
+
: content }) }));
|
|
36
47
|
}
|
|
37
48
|
//# sourceMappingURL=provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAClG,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EAAE,YAAY,EAA0B,aAAa,EAAE,MAAM,cAAc,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AA+BpC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,KAAK,EAAuB;IACzG,MAAM,SAAS,GAAG,MAAM,CAAqB,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1D,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC;YAC9B,MAAM,EAAE,MAAO;YACf,OAAO,EAAE,8BAA8B,EAAE;YACzC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,YAAY,EAAE,KAAK,CAAC,YAAY;SAChC,CAAC,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAA;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IACjF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAClE,MAAM,KAAK,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,CAAC,CAAA;IAEvE,gCAAgC;IAChC,eAAe,CAAC,GAAG,EAAE;QACpB,OAAO,MAAM,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,MAAM,iBAAiB,GAAG,OAAO,CAAoB,GAAG,EAAE,CAAC,CAAC;QAC3D,KAAK;QACL,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,EAAE;KACV,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEZ,+CAA+C;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC9B,IAAI,MAAM,GAA2B,EAAE,CAAA;QACvC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAA;QACxC,CAAC;QACD,OAAO,MAAM,CAAA;IACd,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IAEnB,MAAM,OAAO,GAAG,QAAQ,IAAI,KAAC,MAAM,KAAG,CAAA;IAEtC,OAAO,CACN,KAAC,aAAa,IAAC,KAAK,EAAE,MAAM,YAC3B,KAAC,YAAY,IAAC,KAAK,EAAE,iBAAiB,YACpC,UAAU;gBACV,CAAC,CAAC,KAAC,UAAU,IAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,YAAG,OAAO,GAAc;gBAC/E,CAAC,CAAC,OAAO,GACI,GACA,CAChB,CAAA;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buzola/router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -29,9 +29,12 @@
|
|
|
29
29
|
"react": "^18.0.0 || ^19.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"
|
|
33
|
-
"@
|
|
32
|
+
"@testing-library/dom": "^10.4.1",
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
34
|
"@types/bun": "latest",
|
|
35
|
+
"@types/react": "^19.0.0",
|
|
36
|
+
"happy-dom": "^20.8.3",
|
|
37
|
+
"react": "^19.0.0",
|
|
35
38
|
"typescript": "^5.7.0"
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
2
|
import { createElement } from 'react'
|
|
3
|
-
import { createPage } from '../define/create-page'
|
|
4
|
-
import { createMemoryNavigationAdapter } from '../engine/navigation-adapter'
|
|
5
|
-
import { buildRouteTree } from '../engine/route-tree'
|
|
6
|
-
import { Router } from '../engine/router'
|
|
7
|
-
import { s } from '../engine/schema'
|
|
8
|
-
import type { RouteConfig, StandardSchema } from '../engine/types'
|
|
3
|
+
import { BuzolaRedirect, createPage, redirect } from '../define/create-page.js'
|
|
4
|
+
import { createMemoryNavigationAdapter } from '../engine/navigation-adapter.js'
|
|
5
|
+
import { buildRouteTree } from '../engine/route-tree.js'
|
|
6
|
+
import { Router } from '../engine/router.js'
|
|
7
|
+
import { s } from '../engine/schema.js'
|
|
8
|
+
import type { RouteConfig, StandardSchema } from '../engine/types.js'
|
|
9
9
|
|
|
10
10
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
@@ -586,3 +586,39 @@ describe('navigate with extra params (integration)', () => {
|
|
|
586
586
|
expect(state.location.searchParams.get('tab')).toBe('posts')
|
|
587
587
|
})
|
|
588
588
|
})
|
|
589
|
+
|
|
590
|
+
describe('redirect()', () => {
|
|
591
|
+
it('returns a BuzolaRedirect instance', () => {
|
|
592
|
+
const r = redirect('LoginPage' as any)
|
|
593
|
+
expect(r).toBeInstanceOf(BuzolaRedirect)
|
|
594
|
+
expect(r.pageId).toBe('LoginPage')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it('stores params on the redirect', () => {
|
|
598
|
+
const r = redirect('UserPage' as any, { userId: '123' } as any)
|
|
599
|
+
expect(r).toBeInstanceOf(BuzolaRedirect)
|
|
600
|
+
expect(r.pageId).toBe('UserPage')
|
|
601
|
+
expect(r.params).toEqual({ userId: '123' })
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
it('loader receives redirect in context', async () => {
|
|
605
|
+
let receivedRedirect: unknown
|
|
606
|
+
const page = createPage()
|
|
607
|
+
.loader(async ({ redirect: r }) => {
|
|
608
|
+
receivedRedirect = r
|
|
609
|
+
return {}
|
|
610
|
+
})
|
|
611
|
+
.render(() => null)
|
|
612
|
+
|
|
613
|
+
expect(typeof receivedRedirect).toBe('undefined') // not called yet — component not rendered
|
|
614
|
+
// Verify the type is correct by checking the function signature
|
|
615
|
+
expect(page.__buzolaPage).toBe(true)
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
it('redirect() result is recognized as BuzolaRedirect', () => {
|
|
619
|
+
const r = redirect('DashboardPage' as any)
|
|
620
|
+
expect(r instanceof BuzolaRedirect).toBe(true)
|
|
621
|
+
expect(r.__buzolaRedirect).toBe(true)
|
|
622
|
+
expect(r.pageId).toBe('DashboardPage')
|
|
623
|
+
})
|
|
624
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { LoaderCache } from '../engine/loader-cache.js'
|
|
3
|
+
|
|
4
|
+
describe('LoaderCache', () => {
|
|
5
|
+
it('stores and retrieves values', () => {
|
|
6
|
+
const cache = new LoaderCache(10)
|
|
7
|
+
cache.set('a', 1)
|
|
8
|
+
expect(cache.get('a')).toBe(1)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('returns undefined for missing keys', () => {
|
|
12
|
+
const cache = new LoaderCache(10)
|
|
13
|
+
expect(cache.get('missing')).toBeUndefined()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('has() returns true for existing keys', () => {
|
|
17
|
+
const cache = new LoaderCache(10)
|
|
18
|
+
cache.set('a', 1)
|
|
19
|
+
expect(cache.has('a')).toBe(true)
|
|
20
|
+
expect(cache.has('b')).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('delete() removes entries', () => {
|
|
24
|
+
const cache = new LoaderCache(10)
|
|
25
|
+
cache.set('a', 1)
|
|
26
|
+
cache.delete('a')
|
|
27
|
+
expect(cache.has('a')).toBe(false)
|
|
28
|
+
expect(cache.size).toBe(0)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('tracks size correctly', () => {
|
|
32
|
+
const cache = new LoaderCache(10)
|
|
33
|
+
expect(cache.size).toBe(0)
|
|
34
|
+
cache.set('a', 1)
|
|
35
|
+
cache.set('b', 2)
|
|
36
|
+
expect(cache.size).toBe(2)
|
|
37
|
+
cache.delete('a')
|
|
38
|
+
expect(cache.size).toBe(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('clear() removes all entries', () => {
|
|
42
|
+
const cache = new LoaderCache(10)
|
|
43
|
+
cache.set('a', 1)
|
|
44
|
+
cache.set('b', 2)
|
|
45
|
+
cache.clear()
|
|
46
|
+
expect(cache.size).toBe(0)
|
|
47
|
+
expect(cache.has('a')).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// ─── LRU eviction ────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
it('evicts oldest entry when exceeding maxSize', () => {
|
|
53
|
+
const cache = new LoaderCache(2)
|
|
54
|
+
cache.set('a', 1)
|
|
55
|
+
cache.set('b', 2)
|
|
56
|
+
cache.set('c', 3)
|
|
57
|
+
|
|
58
|
+
expect(cache.has('a')).toBe(false)
|
|
59
|
+
expect(cache.get('b')).toBe(2)
|
|
60
|
+
expect(cache.get('c')).toBe(3)
|
|
61
|
+
expect(cache.size).toBe(2)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('evicts multiple entries when needed', () => {
|
|
65
|
+
const cache = new LoaderCache(1)
|
|
66
|
+
cache.set('a', 1)
|
|
67
|
+
cache.set('b', 2)
|
|
68
|
+
|
|
69
|
+
expect(cache.has('a')).toBe(false)
|
|
70
|
+
expect(cache.get('b')).toBe(2)
|
|
71
|
+
expect(cache.size).toBe(1)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('get() refreshes LRU order', () => {
|
|
75
|
+
const cache = new LoaderCache(2)
|
|
76
|
+
cache.set('a', 1)
|
|
77
|
+
cache.set('b', 2)
|
|
78
|
+
|
|
79
|
+
// Access 'a' to move it to most recent
|
|
80
|
+
cache.get('a')
|
|
81
|
+
|
|
82
|
+
// Now 'b' is the oldest, should be evicted
|
|
83
|
+
cache.set('c', 3)
|
|
84
|
+
expect(cache.has('b')).toBe(false)
|
|
85
|
+
expect(cache.get('a')).toBe(1)
|
|
86
|
+
expect(cache.get('c')).toBe(3)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('set() on existing key updates value and refreshes LRU order', () => {
|
|
90
|
+
const cache = new LoaderCache(2)
|
|
91
|
+
cache.set('a', 1)
|
|
92
|
+
cache.set('b', 2)
|
|
93
|
+
|
|
94
|
+
// Update 'a' — should move to most recent
|
|
95
|
+
cache.set('a', 10)
|
|
96
|
+
|
|
97
|
+
// Now 'b' is the oldest
|
|
98
|
+
cache.set('c', 3)
|
|
99
|
+
expect(cache.has('b')).toBe(false)
|
|
100
|
+
expect(cache.get('a')).toBe(10)
|
|
101
|
+
expect(cache.get('c')).toBe(3)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('maxSize of 0 keeps no entries', () => {
|
|
105
|
+
const cache = new LoaderCache(0)
|
|
106
|
+
cache.set('a', 1)
|
|
107
|
+
expect(cache.size).toBe(0)
|
|
108
|
+
expect(cache.has('a')).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('exposes maxSize', () => {
|
|
112
|
+
const cache = new LoaderCache(5)
|
|
113
|
+
expect(cache.maxSize).toBe(5)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
|
-
import { matchRoutes } from '../engine/matcher'
|
|
3
|
-
import { buildRouteTree } from '../engine/route-tree'
|
|
4
|
-
import type { RouteConfig } from '../engine/types'
|
|
2
|
+
import { matchRoutes } from '../engine/matcher.js'
|
|
3
|
+
import { buildRouteTree } from '../engine/route-tree.js'
|
|
4
|
+
import type { RouteConfig } from '../engine/types.js'
|
|
5
5
|
|
|
6
6
|
function dummyComponent() {
|
|
7
7
|
return null
|