@zenithbuild/router 1.0.1

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/src/runtime.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Zenith Runtime Router (Native Bridge)
3
+ *
4
+ * SPA-style client-side router with SSR support via native resolution.
5
+ */
6
+
7
+ // @ts-ignore
8
+ import native from "../index.js"
9
+ import type {
10
+ RouteState,
11
+ NavigateOptions,
12
+ RouteRecord,
13
+ PageModule,
14
+ RuntimeRouteRecord,
15
+ RouteListener
16
+ } from "./types"
17
+
18
+ const { resolveRouteNative, generateRuntimeRouterNative } = native
19
+
20
+ let currentRoute: RouteState = {
21
+ path: "/",
22
+ params: {},
23
+ query: {}
24
+ }
25
+
26
+ const routeListeners: Set<RouteListener> = new Set()
27
+ let routeManifest: RuntimeRouteRecord[] = []
28
+ let routerOutlet: HTMLElement | null = null
29
+
30
+ export function initRouter(
31
+ manifest: RuntimeRouteRecord[],
32
+ outlet?: HTMLElement | string
33
+ ): void {
34
+ routeManifest = manifest
35
+ if (outlet) {
36
+ routerOutlet = typeof outlet === "string" ? document.querySelector(outlet) : outlet
37
+ }
38
+ window.addEventListener("popstate", handlePopState)
39
+ resolveAndRender(window.location.pathname, parseQueryString(window.location.search), false)
40
+ }
41
+
42
+ function parseQueryString(search: string): Record<string, string> {
43
+ const query: Record<string, string> = {}
44
+ if (!search || search === "?") return query
45
+ const params = new URLSearchParams(search)
46
+ params.forEach((value, key) => { query[key] = value; })
47
+ return query
48
+ }
49
+
50
+ function handlePopState(_event: PopStateEvent): void {
51
+ resolveAndRender(window.location.pathname, parseQueryString(window.location.search), false, false)
52
+ }
53
+
54
+ /**
55
+ * Resolve route - uses native implementation if available (Node.js/SSR)
56
+ * or falls back to JS implementation for the browser.
57
+ */
58
+ export function resolveRoute(
59
+ pathname: string
60
+ ): { record: RuntimeRouteRecord; params: Record<string, string> } | null {
61
+ if (typeof process !== 'undefined' && process.versions && process.versions.node) {
62
+ // SSR / Node environment
63
+ const resolved = resolveRouteNative(pathname, routeManifest as any)
64
+ if (!resolved) return null
65
+ return {
66
+ record: resolved.matched as unknown as RuntimeRouteRecord,
67
+ params: resolved.params
68
+ }
69
+ }
70
+
71
+ // Client-side fallback implementation
72
+ const normalizedPath = pathname === "" ? "/" : pathname
73
+ for (const route of routeManifest) {
74
+ const match = route.regex.exec(normalizedPath)
75
+ if (match) {
76
+ const params: Record<string, string> = {}
77
+ for (let i = 0; i < route.paramNames.length; i++) {
78
+ const paramName = route.paramNames[i]
79
+ const paramValue = match[i + 1]
80
+ if (paramName && paramValue !== undefined) {
81
+ params[paramName] = decodeURIComponent(paramValue)
82
+ }
83
+ }
84
+ return { record: route, params }
85
+ }
86
+ }
87
+ return null
88
+ }
89
+
90
+ async function resolveAndRender(
91
+ path: string,
92
+ query: Record<string, string>,
93
+ updateHistory: boolean = true,
94
+ replace: boolean = false
95
+ ): Promise<void> {
96
+ const prevRoute = { ...currentRoute }
97
+ const resolved = resolveRoute(path)
98
+
99
+ if (resolved) {
100
+ currentRoute = {
101
+ path,
102
+ params: resolved.params,
103
+ query,
104
+ matched: resolved.record as unknown as RouteRecord
105
+ }
106
+ const pageModule = resolved.record.module || (resolved.record.load ? await resolved.record.load() : null)
107
+ if (pageModule) await renderPage(pageModule)
108
+ } else {
109
+ currentRoute = { path, params: {}, query, matched: undefined }
110
+ }
111
+
112
+ if (updateHistory) {
113
+ const url = path + (Object.keys(query).length > 0 ? "?" + new URLSearchParams(query).toString() : "")
114
+ if (replace) window.history.replaceState(null, "", url)
115
+ else window.history.pushState(null, "", url)
116
+ }
117
+ notifyListeners(currentRoute, prevRoute)
118
+ ; (window as any).__zenith_route = currentRoute
119
+ }
120
+
121
+ async function renderPage(pageModule: PageModule): Promise<void> {
122
+ if (!routerOutlet) return
123
+ routerOutlet.innerHTML = pageModule.html
124
+ // ... logic for styles and scripts
125
+ }
126
+
127
+ function notifyListeners(route: RouteState, prevRoute: RouteState): void {
128
+ routeListeners.forEach(listener => { listener(route, prevRoute) })
129
+ }
130
+
131
+ export async function navigate(to: string, options: NavigateOptions = {}): Promise<void> {
132
+ let path, query: Record<string, string> = {}
133
+ if (to.includes("?")) {
134
+ const [pathname, search] = to.split("?")
135
+ path = pathname || "/"
136
+ query = parseQueryString("?" + (search || ""))
137
+ } else path = to
138
+
139
+ await resolveAndRender(path, query, true, options.replace || false)
140
+ }
141
+
142
+ export function getRoute(): RouteState { return { ...currentRoute } }
143
+ export function onRouteChange(listener: RouteListener): () => void {
144
+ routeListeners.add(listener)
145
+ return () => { routeListeners.delete(listener) }
146
+ }
147
+
148
+ export function isActive(path: string, exact: boolean = false): boolean {
149
+ return exact ? currentRoute.path === path : currentRoute.path.startsWith(path)
150
+ }
151
+
152
+ export async function prefetch(path: string): Promise<void> {
153
+ const resolved = resolveRoute(path)
154
+ if (resolved && resolved.record.load && !resolved.record.module) {
155
+ resolved.record.module = await resolved.record.load()
156
+ }
157
+ }
158
+
159
+ export function isPrefetched(_path: string): boolean { return false }
160
+
161
+ export function generateRuntimeRouterCode(): string {
162
+ return generateRuntimeRouterNative()
163
+ }
@@ -0,0 +1,14 @@
1
+ use napi_derive::napi;
2
+
3
+ #[napi]
4
+ pub fn generate_runtime_router_native() -> String {
5
+ format!(
6
+ r#"
7
+ (function() {{
8
+ 'use strict';
9
+ // Zenith Native Router Runtime
10
+ // ... (Full implementation would go here)
11
+ }})();
12
+ "#
13
+ )
14
+ }
package/src/types.rs ADDED
@@ -0,0 +1,46 @@
1
+ use napi_derive::napi;
2
+ use serde::{Deserialize, Serialize};
3
+ use std::collections::HashMap;
4
+
5
+ #[napi]
6
+ #[derive(Debug, Serialize, Deserialize)]
7
+ pub enum SegmentType {
8
+ Static,
9
+ Dynamic,
10
+ CatchAll,
11
+ OptionalCatchAll,
12
+ }
13
+
14
+ #[derive(Debug, Clone, Serialize, Deserialize)]
15
+ #[napi(object)]
16
+ pub struct ParsedSegment {
17
+ pub segment_type: SegmentType,
18
+ pub param_name: Option<String>,
19
+ pub raw: String,
20
+ }
21
+
22
+ #[derive(Debug, Clone, Serialize, Deserialize)]
23
+ #[napi(object)]
24
+ pub struct RouteRecord {
25
+ pub path: String,
26
+ pub regex: String,
27
+ pub param_names: Vec<String>,
28
+ pub score: i32,
29
+ pub file_path: String,
30
+ }
31
+
32
+ #[derive(Debug, Clone, Serialize, Deserialize)]
33
+ #[napi(object)]
34
+ pub struct RouteState {
35
+ pub path: String,
36
+ pub params: HashMap<String, String>,
37
+ pub query: HashMap<String, String>,
38
+ pub matched: Option<RouteRecord>,
39
+ }
40
+
41
+ #[derive(Debug, Clone, Serialize, Deserialize)]
42
+ #[napi(object)]
43
+ pub struct RouteManifest {
44
+ pub routes: Vec<RouteRecord>,
45
+ pub generated_at: i64,
46
+ }
package/src/types.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Zenith Router Types
3
+ *
4
+ * Re-exports types from the native Rust implementation.
5
+ */
6
+
7
+ import {
8
+ RouteRecord as NativeRouteRecord,
9
+ RouteState as NativeRouteState,
10
+ RouteManifest as NativeRouteManifest,
11
+ SegmentType as NativeSegmentType,
12
+ ParsedSegment as NativeParsedSegment
13
+ } from "../index.js"
14
+
15
+ export { NativeSegmentType as SegmentType }
16
+
17
+ export interface RouteRecord extends Omit<NativeRouteRecord, 'regex'> {
18
+ regex: RegExp
19
+ }
20
+
21
+ export interface RouteState extends Omit<NativeRouteState, 'matched'> {
22
+ matched?: RouteRecord
23
+ }
24
+
25
+ export interface RouteManifest extends Omit<NativeRouteManifest, 'routes'> {
26
+ routes: RouteRecord[]
27
+ }
28
+
29
+ export interface ParsedSegment extends NativeParsedSegment { }
30
+
31
+ export interface PageModule {
32
+ html: string
33
+ scripts: string[]
34
+ styles: string[]
35
+ meta?: PageMeta
36
+ }
37
+
38
+ export interface PageMeta {
39
+ title?: string
40
+ description?: string
41
+ [key: string]: string | undefined
42
+ }
43
+
44
+ export interface NavigateOptions {
45
+ replace?: boolean
46
+ scrollToTop?: boolean
47
+ state?: Record<string, unknown>
48
+ }
49
+
50
+ export interface RouteDefinition extends RouteRecord { }
51
+
52
+ export interface RuntimeRouteRecord extends RouteRecord {
53
+ module?: PageModule | null;
54
+ load?: () => Promise<PageModule>;
55
+ }
56
+
57
+ export type RouteListener = (route: RouteState, prevRoute: RouteState) => void;
58
+
59
+ export interface Router {
60
+ readonly route: RouteState
61
+ navigate(to: string, options?: NavigateOptions): Promise<void>
62
+ resolve(path: string): { record: RouteRecord; params: Record<string, string> } | null
63
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "lib": [
14
+ "ES2022",
15
+ "DOM",
16
+ "DOM.Iterable"
17
+ ],
18
+ "types": [
19
+ "bun-types",
20
+ "node"
21
+ ]
22
+ },
23
+ "include": [
24
+ "src/**/*"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist"
29
+ ]
30
+ }