@revealui/router 0.2.0
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/LICENSE +22 -0
- package/README.md +353 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +285 -0
- package/dist/index.js.map +1 -0
- package/dist/router-DctgwX83.d.ts +126 -0
- package/dist/server.d.ts +32 -0
- package/dist/server.js +371 -0
- package/dist/server.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components.tsx","../src/router.ts","../../core/src/observability/logger.ts"],"sourcesContent":["import type React from 'react'\nimport { createContext, useContext, useEffect, useSyncExternalStore } from 'react'\nimport type { Router } from './router'\nimport type { NavigateOptions, RouteMatch } from './types'\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null)\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null)\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router\n children: React.ReactNode\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter()\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n )\n\n if (!match) {\n return <NotFound />\n }\n\n const { route, params, data } = match\n const Component = route.component\n const Layout = route.layout\n\n const element = <Component params={params} data={data} />\n\n return (\n <MatchContext.Provider value={match}>\n {Layout ? <Layout>{element}</Layout> : element}\n </MatchContext.Provider>\n )\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: {\n to: string\n replace?: boolean\n children: React.ReactNode\n className?: string\n style?: React.CSSProperties\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void\n [key: string]: unknown\n}) {\n const router = useRouter()\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e)\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return\n }\n\n e.preventDefault()\n router.navigate(to, { replace })\n }\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n )\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = useContext(RouterContext)\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider')\n }\n\n return router\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return useContext(MatchContext)\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch()\n return (match?.params as T) || ({} as T)\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch()\n return match?.data as T | undefined\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate() {\n const router = useRouter()\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options)\n }\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n )\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter()\n\n useEffect(() => {\n router.navigate(to, { replace })\n }, [to, replace, router])\n\n return null\n}\n","import { match as pathMatch } from 'path-to-regexp'\nimport { logger } from '../../core/src/observability/logger.js'\nimport type { NavigateOptions, Route, RouteMatch, RouteParams, RouterOptions } from './types'\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = []\n private options: RouterOptions\n private listeners: Set<() => void> = new Set()\n private currentMatch: RouteMatch | null = null\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n }\n }\n\n /**\n * Register a route\n */\n register(route: Route): void {\n this.routes.push(route)\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route)\n })\n }\n\n /**\n * Match a URL to a route\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url)\n\n // Try to match each route\n for (const route of this.routes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent })\n const result = matcher(path)\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n }\n }\n }\n\n return null\n }\n\n /**\n * Resolve a route with data loading\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url)\n\n if (!matched) {\n return null\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params)\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n )\n throw error\n }\n }\n\n // Store match for SSR (if on server)\n if (typeof window === 'undefined') {\n this.currentMatch = matched\n }\n\n return matched\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return\n }\n\n const fullUrl = this.options.basePath + url\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl)\n } else {\n window.history.pushState(options.state || null, '', fullUrl)\n }\n\n this.notifyListeners()\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back()\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward()\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n // On server, return stored match (set by resolve during SSR)\n if (typeof window === 'undefined') {\n return this.currentMatch\n }\n\n // On client, match against current URL\n return this.match(window.location.pathname)\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes]\n }\n\n /**\n * Clear all routes\n */\n clear(): void {\n this.routes = []\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length)\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0]\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n\n return path\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener()\n })\n }\n\n /**\n * Initialize client-side routing\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n // Handle browser back/forward buttons\n window.addEventListener('popstate', () => {\n this.notifyListeners()\n })\n\n // Intercept link clicks\n document.addEventListener('click', (e) => {\n const target = (e.target as HTMLElement).closest('a')\n\n if (!target) return\n\n const href = target.getAttribute('href')\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault()\n this.navigate(href)\n }\n })\n }\n}\n","/**\n * Structured Logging Infrastructure\n *\n * Re-exports from @revealui/utils to maintain backward compatibility.\n * The actual implementation has been moved to @revealui/utils to break circular dependencies.\n */\n\n// Import logger for internal use\nimport { logger as utilsLogger } from '@revealui/utils/logger'\n\n// Re-export all types and functions from utils\nexport type {\n LogContext,\n LogEntry,\n LoggerConfig,\n LogLevel,\n} from '@revealui/utils/logger'\n\nexport {\n createLogger,\n Logger,\n logAudit,\n logError,\n logger,\n logQuery,\n} from '@revealui/utils/logger'\n\n// Additional helper functions that were in core but not in utils\n// These can stay here as they're core-specific\n\n/**\n * Request logger middleware\n */\nexport function createRequestLogger<TRequest = unknown, TResponse = unknown>(\n options: { includeBody?: boolean; includeHeaders?: boolean } = {},\n) {\n return async (\n request: TRequest & {\n method: string\n url: string\n headers?: { get?: (key: string) => string | null; entries?: () => Iterable<[string, string]> }\n },\n next: () => Promise<TResponse>,\n ): Promise<TResponse> => {\n // Import logger at runtime to avoid circular deps\n const { logger } = await import('@revealui/utils/logger')\n const requestId = crypto.randomUUID()\n const startTime = Date.now()\n\n const requestLogger = logger.child({\n requestId,\n method: request.method,\n url: request.url,\n userAgent: request.headers?.get?.('user-agent'),\n })\n\n requestLogger.info('Request started')\n\n if (options.includeHeaders) {\n requestLogger.debug('Request headers', {\n headers: Object.fromEntries(request.headers?.entries?.() || []),\n })\n }\n\n try {\n const response = await next()\n\n const duration = Date.now() - startTime\n const responseWithStatus = response as typeof response & { status?: number }\n\n requestLogger.info('Request completed', {\n status: responseWithStatus.status ?? 200,\n duration,\n })\n\n return response\n } catch (error) {\n const duration = Date.now() - startTime\n\n requestLogger.error(\n 'Request failed',\n error instanceof Error ? error : new Error(String(error)),\n { duration },\n )\n\n throw error\n }\n }\n}\n\n/**\n * Performance logger\n */\nexport function logPerformance(\n operation: string,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const level = duration > 1000 ? 'warn' : 'info'\n\n utilsLogger[level](`Performance: ${operation}`, {\n ...context,\n operation,\n duration,\n slow: duration > 1000,\n })\n}\n\n/**\n * API call logger\n */\nexport function logAPICall(\n method: string,\n url: string,\n status: number,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const apiContext = {\n ...context,\n method,\n url,\n status,\n duration,\n }\n\n if (status >= 400) {\n utilsLogger.error('API call', undefined, apiContext)\n } else if (status >= 300) {\n utilsLogger.warn('API call', apiContext)\n } else {\n utilsLogger.info('API call', apiContext)\n }\n}\n\n/**\n * Cache operation logger\n */\nexport function logCache(\n operation: 'hit' | 'miss' | 'set' | 'delete',\n key: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.debug(`Cache ${operation}`, {\n ...context,\n operation,\n key,\n })\n}\n\n/**\n * User action logger\n */\nexport function logUserAction(\n action: string,\n userId?: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.info('User action', {\n ...context,\n action,\n userId,\n })\n}\n\n/**\n * System event logger\n */\nexport function logSystemEvent(event: string, context?: Record<string, unknown>): void {\n utilsLogger.info('System event', {\n ...context,\n event,\n })\n}\n\n/**\n * Sanitize sensitive data from logs\n */\nexport function sanitizeLogData(data: Record<string, unknown>): Record<string, unknown> {\n const sensitiveKeys = [\n 'password',\n 'token',\n 'secret',\n 'apiKey',\n 'accessToken',\n 'refreshToken',\n 'creditCard',\n 'ssn',\n ]\n\n const sanitized: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase()\n\n if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {\n sanitized[key] = '[REDACTED]'\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n sanitized[key] = sanitizeLogData(value as Record<string, unknown>)\n } else {\n sanitized[key] = value\n }\n }\n\n return sanitized\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route configuration
|
|
5
|
+
*/
|
|
6
|
+
interface Route<TData = unknown, TProps = Record<string, unknown>> {
|
|
7
|
+
/** Route path pattern (e.g., '/', '/about', '/posts/:id') */
|
|
8
|
+
path: string;
|
|
9
|
+
/** Component to render for this route */
|
|
10
|
+
component: ComponentType<TProps>;
|
|
11
|
+
/** Optional layout component */
|
|
12
|
+
layout?: ComponentType<{
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}>;
|
|
15
|
+
/** Optional data loader function */
|
|
16
|
+
loader?: (params: RouteParams) => Promise<TData> | TData;
|
|
17
|
+
/** Optional metadata */
|
|
18
|
+
meta?: RouteMeta;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Route parameters extracted from URL
|
|
22
|
+
*/
|
|
23
|
+
interface RouteParams {
|
|
24
|
+
[key: string]: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Route metadata (for SEO, etc.)
|
|
28
|
+
*/
|
|
29
|
+
interface RouteMeta {
|
|
30
|
+
title?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Matched route result
|
|
36
|
+
*/
|
|
37
|
+
interface RouteMatch<TData = unknown> {
|
|
38
|
+
route: Route<TData>;
|
|
39
|
+
params: RouteParams;
|
|
40
|
+
data?: TData;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Router configuration options
|
|
44
|
+
*/
|
|
45
|
+
interface RouterOptions {
|
|
46
|
+
/** Base URL path */
|
|
47
|
+
basePath?: string;
|
|
48
|
+
/** 404 component */
|
|
49
|
+
notFound?: ComponentType;
|
|
50
|
+
/** Error boundary component */
|
|
51
|
+
errorBoundary?: ComponentType<{
|
|
52
|
+
error: Error;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Navigation options
|
|
57
|
+
*/
|
|
58
|
+
interface NavigateOptions<TState = unknown> {
|
|
59
|
+
/** Replace current history entry instead of pushing */
|
|
60
|
+
replace?: boolean;
|
|
61
|
+
/** State to pass with navigation */
|
|
62
|
+
state?: TState;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* RevealUI Router - Lightweight file-based routing with SSR support
|
|
67
|
+
*/
|
|
68
|
+
declare class Router {
|
|
69
|
+
private routes;
|
|
70
|
+
private options;
|
|
71
|
+
private listeners;
|
|
72
|
+
private currentMatch;
|
|
73
|
+
constructor(options?: RouterOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Register a route
|
|
76
|
+
*/
|
|
77
|
+
register(route: Route): void;
|
|
78
|
+
/**
|
|
79
|
+
* Register multiple routes
|
|
80
|
+
*/
|
|
81
|
+
registerRoutes(routes: Route[]): void;
|
|
82
|
+
/**
|
|
83
|
+
* Match a URL to a route
|
|
84
|
+
*/
|
|
85
|
+
match(url: string): RouteMatch | null;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a route with data loading
|
|
88
|
+
*/
|
|
89
|
+
resolve(url: string): Promise<RouteMatch | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Navigate to a URL (client-side only)
|
|
92
|
+
*/
|
|
93
|
+
navigate(url: string, options?: NavigateOptions): void;
|
|
94
|
+
/**
|
|
95
|
+
* Go back in history
|
|
96
|
+
*/
|
|
97
|
+
back(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Go forward in history
|
|
100
|
+
*/
|
|
101
|
+
forward(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Subscribe to route changes
|
|
104
|
+
*/
|
|
105
|
+
subscribe(listener: () => void): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Get current route match
|
|
108
|
+
*/
|
|
109
|
+
getCurrentMatch(): RouteMatch | null;
|
|
110
|
+
/**
|
|
111
|
+
* Get all registered routes
|
|
112
|
+
*/
|
|
113
|
+
getRoutes(): Route[];
|
|
114
|
+
/**
|
|
115
|
+
* Clear all routes
|
|
116
|
+
*/
|
|
117
|
+
clear(): void;
|
|
118
|
+
private normalizePath;
|
|
119
|
+
private notifyListeners;
|
|
120
|
+
/**
|
|
121
|
+
* Initialize client-side routing
|
|
122
|
+
*/
|
|
123
|
+
initClient(): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { type NavigateOptions as N, Router as R, type RouteMatch as a, type Route as b, type RouteMeta as c, type RouteParams as d, type RouterOptions as e };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as _hono_node_server from '@hono/node-server';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { b as Route, R as Router } from './router-DctgwX83.js';
|
|
4
|
+
import 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SSR Options
|
|
8
|
+
*/
|
|
9
|
+
interface SSROptions {
|
|
10
|
+
/** HTML template function */
|
|
11
|
+
template?: (html: string, data?: Record<string, unknown>) => string;
|
|
12
|
+
/** Enable streaming SSR */
|
|
13
|
+
streaming?: boolean;
|
|
14
|
+
/** Error handler */
|
|
15
|
+
onError?: (error: Error, context: Context) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a Hono handler for SSR
|
|
19
|
+
*/
|
|
20
|
+
declare function createSSRHandler(routes: Route[], options?: SSROptions): (c: Context) => Promise<Response>;
|
|
21
|
+
/**
|
|
22
|
+
* Create a simple dev server with Hono
|
|
23
|
+
*/
|
|
24
|
+
declare function createDevServer(routes: Route[], options?: SSROptions & {
|
|
25
|
+
port?: number;
|
|
26
|
+
}): Promise<_hono_node_server.ServerType>;
|
|
27
|
+
/**
|
|
28
|
+
* Hydrate the client-side app
|
|
29
|
+
*/
|
|
30
|
+
declare function hydrate(router: Router, rootElement?: HTMLElement | null): Promise<void>;
|
|
31
|
+
|
|
32
|
+
export { type SSROptions, createDevServer, createSSRHandler, hydrate };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/server.tsx
|
|
9
|
+
import { logger as logger2 } from "@revealui/core/observability/logger";
|
|
10
|
+
import { renderToPipeableStream, renderToString } from "react-dom/server";
|
|
11
|
+
|
|
12
|
+
// src/components.tsx
|
|
13
|
+
import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var RouterContext = createContext(null);
|
|
16
|
+
var MatchContext = createContext(null);
|
|
17
|
+
function RouterProvider({
|
|
18
|
+
router,
|
|
19
|
+
children
|
|
20
|
+
}) {
|
|
21
|
+
return /* @__PURE__ */ jsx(RouterContext.Provider, { value: router, children });
|
|
22
|
+
}
|
|
23
|
+
function Routes() {
|
|
24
|
+
const router = useRouter();
|
|
25
|
+
const match = useSyncExternalStore(
|
|
26
|
+
(callback) => router.subscribe(callback),
|
|
27
|
+
() => router.getCurrentMatch(),
|
|
28
|
+
() => router.getCurrentMatch()
|
|
29
|
+
// Server-side snapshot (same as client)
|
|
30
|
+
);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return /* @__PURE__ */ jsx(NotFound, {});
|
|
33
|
+
}
|
|
34
|
+
const { route, params, data } = match;
|
|
35
|
+
const Component = route.component;
|
|
36
|
+
const Layout = route.layout;
|
|
37
|
+
const element = /* @__PURE__ */ jsx(Component, { params, data });
|
|
38
|
+
return /* @__PURE__ */ jsx(MatchContext.Provider, { value: match, children: Layout ? /* @__PURE__ */ jsx(Layout, { children: element }) : element });
|
|
39
|
+
}
|
|
40
|
+
function Link({
|
|
41
|
+
to,
|
|
42
|
+
replace = false,
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
style,
|
|
46
|
+
onClick,
|
|
47
|
+
...props
|
|
48
|
+
}) {
|
|
49
|
+
const router = useRouter();
|
|
50
|
+
const handleClick = (e) => {
|
|
51
|
+
onClick?.(e);
|
|
52
|
+
if (e.defaultPrevented) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (e.button !== 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
router.navigate(to, { replace });
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ jsx("a", { href: to, onClick: handleClick, className, style, ...props, children });
|
|
65
|
+
}
|
|
66
|
+
function useRouter() {
|
|
67
|
+
const router = useContext(RouterContext);
|
|
68
|
+
if (!router) {
|
|
69
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
70
|
+
}
|
|
71
|
+
return router;
|
|
72
|
+
}
|
|
73
|
+
function NotFound() {
|
|
74
|
+
return /* @__PURE__ */ jsxs("div", { style: { padding: "2rem", textAlign: "center" }, children: [
|
|
75
|
+
/* @__PURE__ */ jsx("h1", { children: "404 - Page Not Found" }),
|
|
76
|
+
/* @__PURE__ */ jsx("p", { children: "The page you're looking for doesn't exist." }),
|
|
77
|
+
/* @__PURE__ */ jsx(Link, { to: "/", children: "Go Home" })
|
|
78
|
+
] });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/router.ts
|
|
82
|
+
import { match as pathMatch } from "path-to-regexp";
|
|
83
|
+
|
|
84
|
+
// ../core/src/observability/logger.ts
|
|
85
|
+
import { logger as utilsLogger } from "@revealui/utils/logger";
|
|
86
|
+
import {
|
|
87
|
+
createLogger,
|
|
88
|
+
Logger,
|
|
89
|
+
logAudit,
|
|
90
|
+
logError,
|
|
91
|
+
logger,
|
|
92
|
+
logQuery
|
|
93
|
+
} from "@revealui/utils/logger";
|
|
94
|
+
|
|
95
|
+
// src/router.ts
|
|
96
|
+
var Router = class {
|
|
97
|
+
routes = [];
|
|
98
|
+
options;
|
|
99
|
+
listeners = /* @__PURE__ */ new Set();
|
|
100
|
+
currentMatch = null;
|
|
101
|
+
constructor(options = {}) {
|
|
102
|
+
this.options = {
|
|
103
|
+
basePath: "",
|
|
104
|
+
...options
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Register a route
|
|
109
|
+
*/
|
|
110
|
+
register(route) {
|
|
111
|
+
this.routes.push(route);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Register multiple routes
|
|
115
|
+
*/
|
|
116
|
+
registerRoutes(routes) {
|
|
117
|
+
routes.forEach((route) => {
|
|
118
|
+
this.register(route);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Match a URL to a route
|
|
123
|
+
*/
|
|
124
|
+
match(url) {
|
|
125
|
+
const path = this.normalizePath(url);
|
|
126
|
+
for (const route of this.routes) {
|
|
127
|
+
const matcher = pathMatch(route.path, { decode: decodeURIComponent });
|
|
128
|
+
const result = matcher(path);
|
|
129
|
+
if (result) {
|
|
130
|
+
return {
|
|
131
|
+
route,
|
|
132
|
+
params: result.params || {}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolve a route with data loading
|
|
140
|
+
*/
|
|
141
|
+
async resolve(url) {
|
|
142
|
+
const matched = this.match(url);
|
|
143
|
+
if (!matched) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (matched.route.loader) {
|
|
147
|
+
try {
|
|
148
|
+
matched.data = await matched.route.loader(matched.params);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
logger.error(
|
|
151
|
+
"Route loader error",
|
|
152
|
+
error instanceof Error ? error : new Error(String(error))
|
|
153
|
+
);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (typeof window === "undefined") {
|
|
158
|
+
this.currentMatch = matched;
|
|
159
|
+
}
|
|
160
|
+
return matched;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Navigate to a URL (client-side only)
|
|
164
|
+
*/
|
|
165
|
+
navigate(url, options = {}) {
|
|
166
|
+
if (typeof window === "undefined") {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const fullUrl = this.options.basePath + url;
|
|
170
|
+
if (options.replace) {
|
|
171
|
+
window.history.replaceState(options.state || null, "", fullUrl);
|
|
172
|
+
} else {
|
|
173
|
+
window.history.pushState(options.state || null, "", fullUrl);
|
|
174
|
+
}
|
|
175
|
+
this.notifyListeners();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Go back in history
|
|
179
|
+
*/
|
|
180
|
+
back() {
|
|
181
|
+
if (typeof window !== "undefined") {
|
|
182
|
+
window.history.back();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Go forward in history
|
|
187
|
+
*/
|
|
188
|
+
forward() {
|
|
189
|
+
if (typeof window !== "undefined") {
|
|
190
|
+
window.history.forward();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Subscribe to route changes
|
|
195
|
+
*/
|
|
196
|
+
subscribe(listener) {
|
|
197
|
+
this.listeners.add(listener);
|
|
198
|
+
return () => {
|
|
199
|
+
this.listeners.delete(listener);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get current route match
|
|
204
|
+
*/
|
|
205
|
+
getCurrentMatch() {
|
|
206
|
+
if (typeof window === "undefined") {
|
|
207
|
+
return this.currentMatch;
|
|
208
|
+
}
|
|
209
|
+
return this.match(window.location.pathname);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get all registered routes
|
|
213
|
+
*/
|
|
214
|
+
getRoutes() {
|
|
215
|
+
return [...this.routes];
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Clear all routes
|
|
219
|
+
*/
|
|
220
|
+
clear() {
|
|
221
|
+
this.routes = [];
|
|
222
|
+
}
|
|
223
|
+
normalizePath(url) {
|
|
224
|
+
let path = url;
|
|
225
|
+
if (this.options.basePath && path.startsWith(this.options.basePath)) {
|
|
226
|
+
path = path.slice(this.options.basePath.length);
|
|
227
|
+
}
|
|
228
|
+
path = path.split("?")[0].split("#")[0];
|
|
229
|
+
if (!path.startsWith("/")) {
|
|
230
|
+
path = `/${path}`;
|
|
231
|
+
}
|
|
232
|
+
return path;
|
|
233
|
+
}
|
|
234
|
+
notifyListeners() {
|
|
235
|
+
this.listeners.forEach((listener) => {
|
|
236
|
+
listener();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Initialize client-side routing
|
|
241
|
+
*/
|
|
242
|
+
initClient() {
|
|
243
|
+
if (typeof window === "undefined") {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
window.addEventListener("popstate", () => {
|
|
247
|
+
this.notifyListeners();
|
|
248
|
+
});
|
|
249
|
+
document.addEventListener("click", (e) => {
|
|
250
|
+
const target = e.target.closest("a");
|
|
251
|
+
if (!target) return;
|
|
252
|
+
const href = target.getAttribute("href");
|
|
253
|
+
if (href?.startsWith("/") && !target.hasAttribute("target") && !target.hasAttribute("download") && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
this.navigate(href);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/server.tsx
|
|
262
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
263
|
+
function createSSRHandler(routes, options = {}) {
|
|
264
|
+
const router = new Router();
|
|
265
|
+
router.registerRoutes(routes);
|
|
266
|
+
const defaultTemplate = (html, data) => `
|
|
267
|
+
<!DOCTYPE html>
|
|
268
|
+
<html lang="en">
|
|
269
|
+
<head>
|
|
270
|
+
<meta charset="UTF-8">
|
|
271
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
272
|
+
<title>${data?.title || "RevealUI"}</title>
|
|
273
|
+
${data?.meta || ""}
|
|
274
|
+
</head>
|
|
275
|
+
<body>
|
|
276
|
+
<div id="root">${html}</div>
|
|
277
|
+
<script id="__REVEALUI_DATA__" type="application/json">${JSON.stringify(data || {})}</script>
|
|
278
|
+
<script type="module" src="/src/client.tsx"></script>
|
|
279
|
+
</body>
|
|
280
|
+
</html>
|
|
281
|
+
`.trim();
|
|
282
|
+
const template = options.template || defaultTemplate;
|
|
283
|
+
return async (c) => {
|
|
284
|
+
const url = c.req.url;
|
|
285
|
+
const pathname = new URL(url).pathname;
|
|
286
|
+
try {
|
|
287
|
+
logger2.debug("Attempting to match pathname", { pathname });
|
|
288
|
+
const match = await router.resolve(pathname);
|
|
289
|
+
logger2.debug("Match result", {
|
|
290
|
+
match: match ? { path: match.route.path, hasComponent: !!match.route.component } : null
|
|
291
|
+
});
|
|
292
|
+
if (!match) {
|
|
293
|
+
c.status(404);
|
|
294
|
+
return c.html(template("<div>404 - Page Not Found</div>"));
|
|
295
|
+
}
|
|
296
|
+
if (options.streaming) {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
const { pipe } = renderToPipeableStream(
|
|
299
|
+
/* @__PURE__ */ jsx2(RouterProvider, { router, children: /* @__PURE__ */ jsx2(Routes, {}) }),
|
|
300
|
+
{
|
|
301
|
+
onShellReady() {
|
|
302
|
+
c.header("Content-Type", "text/html");
|
|
303
|
+
const html2 = pipe;
|
|
304
|
+
resolve(c.body(html2));
|
|
305
|
+
},
|
|
306
|
+
onError(error) {
|
|
307
|
+
logger2.error("SSR error", error instanceof Error ? error : new Error(String(error)));
|
|
308
|
+
if (options.onError) {
|
|
309
|
+
options.onError(error, c);
|
|
310
|
+
}
|
|
311
|
+
reject(error);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const html = renderToString(
|
|
318
|
+
/* @__PURE__ */ jsx2(RouterProvider, { router, children: /* @__PURE__ */ jsx2(Routes, {}) })
|
|
319
|
+
);
|
|
320
|
+
const data = {
|
|
321
|
+
route: match.route.path,
|
|
322
|
+
params: match.params,
|
|
323
|
+
data: match.data,
|
|
324
|
+
title: match.route.meta?.title
|
|
325
|
+
};
|
|
326
|
+
return c.html(template(html, data));
|
|
327
|
+
} catch (error) {
|
|
328
|
+
logger2.error("SSR error", error instanceof Error ? error : new Error(String(error)));
|
|
329
|
+
if (options.onError) {
|
|
330
|
+
options.onError(error, c);
|
|
331
|
+
}
|
|
332
|
+
c.status(500);
|
|
333
|
+
return c.html(template("<div>500 - Server Error</div>"));
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function createDevServer(routes, options = {}) {
|
|
338
|
+
const { Hono } = await import("hono");
|
|
339
|
+
const { serve } = await import("@hono/node-server");
|
|
340
|
+
const app = new Hono();
|
|
341
|
+
const port = options.port || 3e3;
|
|
342
|
+
app.get("*", createSSRHandler(routes, options));
|
|
343
|
+
const server = serve({ fetch: app.fetch, port });
|
|
344
|
+
logger2.info("RevealUI dev server running", { url: `http://localhost:${port}` });
|
|
345
|
+
return server;
|
|
346
|
+
}
|
|
347
|
+
async function hydrate(router, rootElement = null) {
|
|
348
|
+
if (typeof window === "undefined") {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const root = rootElement || document.getElementById("root");
|
|
352
|
+
if (!root) {
|
|
353
|
+
logger2.error("Root element not found", new Error("Root element not found"));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const dataScript = document.getElementById("__REVEALUI_DATA__");
|
|
357
|
+
const _ssrData = dataScript ? JSON.parse(dataScript.textContent || "{}") : {};
|
|
358
|
+
router.initClient();
|
|
359
|
+
const { hydrateRoot } = __require("react-dom/client");
|
|
360
|
+
hydrateRoot(
|
|
361
|
+
root,
|
|
362
|
+
/* @__PURE__ */ jsx2(RouterProvider, { router, children: /* @__PURE__ */ jsx2(Routes, {}) })
|
|
363
|
+
);
|
|
364
|
+
logger2.info("RevealUI hydrated");
|
|
365
|
+
}
|
|
366
|
+
export {
|
|
367
|
+
createDevServer,
|
|
368
|
+
createSSRHandler,
|
|
369
|
+
hydrate
|
|
370
|
+
};
|
|
371
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.tsx","../src/components.tsx","../src/router.ts","../../core/src/observability/logger.ts"],"sourcesContent":["import { logger } from '@revealui/core/observability/logger'\nimport type { Context } from 'hono'\nimport { renderToPipeableStream, renderToString } from 'react-dom/server'\nimport { RouterProvider, Routes } from './components'\nimport { Router } from './router'\nimport type { Route } from './types'\n\n/**\n * SSR Options\n */\nexport interface SSROptions {\n /** HTML template function */\n template?: (html: string, data?: Record<string, unknown>) => string\n /** Enable streaming SSR */\n streaming?: boolean\n /** Error handler */\n onError?: (error: Error, context: Context) => void\n}\n\n/**\n * Create a Hono handler for SSR\n */\nexport function createSSRHandler(routes: Route[], options: SSROptions = {}) {\n const router = new Router()\n router.registerRoutes(routes)\n\n const defaultTemplate = (html: string, data?: Record<string, unknown>) =>\n `\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${data?.title || 'RevealUI'}</title>\n ${data?.meta || ''}\n </head>\n <body>\n <div id=\"root\">${html}</div>\n <script id=\"__REVEALUI_DATA__\" type=\"application/json\">${JSON.stringify(data || {})}</script>\n <script type=\"module\" src=\"/src/client.tsx\"></script>\n </body>\n</html>\n `.trim()\n\n const template = options.template || defaultTemplate\n\n return async (c: Context) => {\n const url = c.req.url\n const pathname = new URL(url).pathname\n\n try {\n // Match and resolve route\n logger.debug('Attempting to match pathname', { pathname })\n const match = await router.resolve(pathname)\n logger.debug('Match result', {\n match: match ? { path: match.route.path, hasComponent: !!match.route.component } : null,\n })\n\n if (!match) {\n c.status(404)\n return c.html(template('<div>404 - Page Not Found</div>'))\n }\n\n // Render with streaming if enabled\n if (options.streaming) {\n return new Promise<Response>((resolve, reject) => {\n const { pipe } = renderToPipeableStream(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n {\n onShellReady() {\n c.header('Content-Type', 'text/html')\n const html = pipe\n // biome-ignore lint/suspicious/noExplicitAny: pipe stream typing incompatibility with Hono\n resolve(c.body(html as any) as Response)\n },\n onError(error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)))\n if (options.onError) {\n options.onError(error as Error, c)\n }\n reject(error)\n },\n },\n )\n })\n }\n\n // Regular SSR\n const html = renderToString(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n )\n\n const data = {\n route: match.route.path,\n params: match.params,\n data: match.data,\n title: match.route.meta?.title,\n }\n\n return c.html(template(html, data))\n } catch (error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)))\n\n if (options.onError) {\n options.onError(error as Error, c)\n }\n\n c.status(500)\n return c.html(template('<div>500 - Server Error</div>'))\n }\n }\n}\n\n/**\n * Create a simple dev server with Hono\n */\nexport async function createDevServer(\n routes: Route[],\n options: SSROptions & { port?: number } = {},\n) {\n const { Hono } = await import('hono')\n const { serve } = await import('@hono/node-server')\n\n const app = new Hono()\n const port = options.port || 3000\n\n // Static files (you'll need to add your own static middleware)\n // app.use('/assets/*', serveStatic({ root: './public' }))\n\n // SSR handler for all routes\n app.get('*', createSSRHandler(routes, options))\n\n const server = serve({ fetch: app.fetch, port })\n\n logger.info('RevealUI dev server running', { url: `http://localhost:${port}` })\n\n return server\n}\n\n/**\n * Hydrate the client-side app\n */\nexport async function hydrate(router: Router, rootElement: HTMLElement | null = null) {\n if (typeof window === 'undefined') {\n return\n }\n\n const root = rootElement || document.getElementById('root')\n\n if (!root) {\n logger.error('Root element not found', new Error('Root element not found'))\n return\n }\n\n // Get SSR data\n const dataScript = document.getElementById('__REVEALUI_DATA__')\n const _ssrData = dataScript ? JSON.parse(dataScript.textContent || '{}') : {}\n\n // Initialize client-side routing\n router.initClient()\n\n // Use hydrateRoot for React 18+\n const { hydrateRoot } = require('react-dom/client')\n\n hydrateRoot(\n root,\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n )\n\n logger.info('RevealUI hydrated')\n}\n","import type React from 'react'\nimport { createContext, useContext, useEffect, useSyncExternalStore } from 'react'\nimport type { Router } from './router'\nimport type { NavigateOptions, RouteMatch } from './types'\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null)\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null)\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router\n children: React.ReactNode\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter()\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n )\n\n if (!match) {\n return <NotFound />\n }\n\n const { route, params, data } = match\n const Component = route.component\n const Layout = route.layout\n\n const element = <Component params={params} data={data} />\n\n return (\n <MatchContext.Provider value={match}>\n {Layout ? <Layout>{element}</Layout> : element}\n </MatchContext.Provider>\n )\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: {\n to: string\n replace?: boolean\n children: React.ReactNode\n className?: string\n style?: React.CSSProperties\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void\n [key: string]: unknown\n}) {\n const router = useRouter()\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e)\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return\n }\n\n e.preventDefault()\n router.navigate(to, { replace })\n }\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n )\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = useContext(RouterContext)\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider')\n }\n\n return router\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return useContext(MatchContext)\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch()\n return (match?.params as T) || ({} as T)\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch()\n return match?.data as T | undefined\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate() {\n const router = useRouter()\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options)\n }\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n )\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter()\n\n useEffect(() => {\n router.navigate(to, { replace })\n }, [to, replace, router])\n\n return null\n}\n","import { match as pathMatch } from 'path-to-regexp'\nimport { logger } from '../../core/src/observability/logger.js'\nimport type { NavigateOptions, Route, RouteMatch, RouteParams, RouterOptions } from './types'\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = []\n private options: RouterOptions\n private listeners: Set<() => void> = new Set()\n private currentMatch: RouteMatch | null = null\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n }\n }\n\n /**\n * Register a route\n */\n register(route: Route): void {\n this.routes.push(route)\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route)\n })\n }\n\n /**\n * Match a URL to a route\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url)\n\n // Try to match each route\n for (const route of this.routes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent })\n const result = matcher(path)\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n }\n }\n }\n\n return null\n }\n\n /**\n * Resolve a route with data loading\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url)\n\n if (!matched) {\n return null\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params)\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n )\n throw error\n }\n }\n\n // Store match for SSR (if on server)\n if (typeof window === 'undefined') {\n this.currentMatch = matched\n }\n\n return matched\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return\n }\n\n const fullUrl = this.options.basePath + url\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl)\n } else {\n window.history.pushState(options.state || null, '', fullUrl)\n }\n\n this.notifyListeners()\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back()\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward()\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n // On server, return stored match (set by resolve during SSR)\n if (typeof window === 'undefined') {\n return this.currentMatch\n }\n\n // On client, match against current URL\n return this.match(window.location.pathname)\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes]\n }\n\n /**\n * Clear all routes\n */\n clear(): void {\n this.routes = []\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length)\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0]\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n\n return path\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener()\n })\n }\n\n /**\n * Initialize client-side routing\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n // Handle browser back/forward buttons\n window.addEventListener('popstate', () => {\n this.notifyListeners()\n })\n\n // Intercept link clicks\n document.addEventListener('click', (e) => {\n const target = (e.target as HTMLElement).closest('a')\n\n if (!target) return\n\n const href = target.getAttribute('href')\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault()\n this.navigate(href)\n }\n })\n }\n}\n","/**\n * Structured Logging Infrastructure\n *\n * Re-exports from @revealui/utils to maintain backward compatibility.\n * The actual implementation has been moved to @revealui/utils to break circular dependencies.\n */\n\n// Import logger for internal use\nimport { logger as utilsLogger } from '@revealui/utils/logger'\n\n// Re-export all types and functions from utils\nexport type {\n LogContext,\n LogEntry,\n LoggerConfig,\n LogLevel,\n} from '@revealui/utils/logger'\n\nexport {\n createLogger,\n Logger,\n logAudit,\n logError,\n logger,\n logQuery,\n} from '@revealui/utils/logger'\n\n// Additional helper functions that were in core but not in utils\n// These can stay here as they're core-specific\n\n/**\n * Request logger middleware\n */\nexport function createRequestLogger<TRequest = unknown, TResponse = unknown>(\n options: { includeBody?: boolean; includeHeaders?: boolean } = {},\n) {\n return async (\n request: TRequest & {\n method: string\n url: string\n headers?: { get?: (key: string) => string | null; entries?: () => Iterable<[string, string]> }\n },\n next: () => Promise<TResponse>,\n ): Promise<TResponse> => {\n // Import logger at runtime to avoid circular deps\n const { logger } = await import('@revealui/utils/logger')\n const requestId = crypto.randomUUID()\n const startTime = Date.now()\n\n const requestLogger = logger.child({\n requestId,\n method: request.method,\n url: request.url,\n userAgent: request.headers?.get?.('user-agent'),\n })\n\n requestLogger.info('Request started')\n\n if (options.includeHeaders) {\n requestLogger.debug('Request headers', {\n headers: Object.fromEntries(request.headers?.entries?.() || []),\n })\n }\n\n try {\n const response = await next()\n\n const duration = Date.now() - startTime\n const responseWithStatus = response as typeof response & { status?: number }\n\n requestLogger.info('Request completed', {\n status: responseWithStatus.status ?? 200,\n duration,\n })\n\n return response\n } catch (error) {\n const duration = Date.now() - startTime\n\n requestLogger.error(\n 'Request failed',\n error instanceof Error ? error : new Error(String(error)),\n { duration },\n )\n\n throw error\n }\n }\n}\n\n/**\n * Performance logger\n */\nexport function logPerformance(\n operation: string,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const level = duration > 1000 ? 'warn' : 'info'\n\n utilsLogger[level](`Performance: ${operation}`, {\n ...context,\n operation,\n duration,\n slow: duration > 1000,\n })\n}\n\n/**\n * API call logger\n */\nexport function logAPICall(\n method: string,\n url: string,\n status: number,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const apiContext = {\n ...context,\n method,\n url,\n status,\n duration,\n }\n\n if (status >= 400) {\n utilsLogger.error('API call', undefined, apiContext)\n } else if (status >= 300) {\n utilsLogger.warn('API call', apiContext)\n } else {\n utilsLogger.info('API call', apiContext)\n }\n}\n\n/**\n * Cache operation logger\n */\nexport function logCache(\n operation: 'hit' | 'miss' | 'set' | 'delete',\n key: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.debug(`Cache ${operation}`, {\n ...context,\n operation,\n key,\n })\n}\n\n/**\n * User action logger\n */\nexport function logUserAction(\n action: string,\n userId?: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.info('User action', {\n ...context,\n action,\n userId,\n })\n}\n\n/**\n * System event logger\n */\nexport function logSystemEvent(event: string, context?: Record<string, unknown>): void {\n utilsLogger.info('System event', {\n ...context,\n event,\n })\n}\n\n/**\n * Sanitize sensitive data from logs\n */\nexport function sanitizeLogData(data: Record<string, unknown>): Record<string, unknown> {\n const sensitiveKeys = [\n 'password',\n 'token',\n 'secret',\n 'apiKey',\n 'accessToken',\n 'refreshToken',\n 'creditCard',\n 'ssn',\n ]\n\n const sanitized: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase()\n\n if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {\n sanitized[key] = '[REDACTED]'\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n sanitized[key] = sanitizeLogData(value as Record<string, unknown>)\n } else {\n sanitized[key] = value\n }\n }\n\n return sanitized\n}\n"],"mappings":"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","names":["logger","jsx","logger","html"]}
|