@matthesketh/utopia-router 0.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.
@@ -0,0 +1,215 @@
1
+ import * as _matthesketh_utopia_core from '@matthesketh/utopia-core';
2
+
3
+ /**
4
+ * A compiled route entry in the route table.
5
+ *
6
+ * Routes are built at compile time from the file system manifest and stored
7
+ * as an array. At runtime, incoming URLs are matched against `pattern`.
8
+ */
9
+ interface Route {
10
+ /** URL pattern like '/users/:id' or '/blog/:slug'. */
11
+ path: string;
12
+ /** Compiled regex for matching incoming URLs against this route. */
13
+ pattern: RegExp;
14
+ /** Parameter names extracted from the path (e.g., ['id'] for '/users/:id'). */
15
+ params: string[];
16
+ /** Lazy component import — called only when the route is matched. */
17
+ component: () => Promise<any>;
18
+ /** Optional layout component that wraps the page. */
19
+ layout?: () => Promise<any>;
20
+ /** Optional error boundary component shown when loading fails. */
21
+ error?: () => Promise<any>;
22
+ }
23
+ /**
24
+ * The result of successfully matching a URL against a route.
25
+ */
26
+ interface RouteMatch {
27
+ /** The matched route entry. */
28
+ route: Route;
29
+ /** Extracted parameter values keyed by parameter name. */
30
+ params: Record<string, string>;
31
+ /** The full URL that was matched. */
32
+ url: URL;
33
+ }
34
+ /**
35
+ * Observable router state exposed as signals.
36
+ */
37
+ interface RouterState {
38
+ /** The currently matched route, or null if no route matches. */
39
+ current: RouteMatch | null;
40
+ /** Whether a navigation is currently in progress. */
41
+ navigating: boolean;
42
+ }
43
+ /**
44
+ * Callback signature for beforeNavigate hooks (navigation guards).
45
+ *
46
+ * Return `false` to cancel the navigation, or `void`/`true` to allow it.
47
+ * Returning a string redirects to that URL instead.
48
+ */
49
+ type BeforeNavigateHook = (from: RouteMatch | null, to: RouteMatch | null) => boolean | string | void | Promise<boolean | string | void>;
50
+
51
+ /**
52
+ * Converts a file-system path (relative to the routes directory) into a URL
53
+ * route pattern.
54
+ *
55
+ * @param filePath - File path like 'src/routes/blog/[slug]/+page.utopia'
56
+ * @returns URL pattern like '/blog/:slug'
57
+ *
58
+ * @example
59
+ * filePathToRoute('src/routes/+page.utopia') // '/'
60
+ * filePathToRoute('src/routes/about/+page.utopia') // '/about'
61
+ * filePathToRoute('src/routes/blog/[slug]/+page.utopia') // '/blog/:slug'
62
+ * filePathToRoute('src/routes/[...rest]/+page.utopia') // '/*rest'
63
+ * filePathToRoute('src/routes/(auth)/login/+page.utopia') // '/login'
64
+ */
65
+ declare function filePathToRoute(filePath: string): string;
66
+ /**
67
+ * Compiles a URL route pattern into a regex and extracts parameter names.
68
+ *
69
+ * @param pattern - Route pattern like '/blog/:slug' or '/*rest'
70
+ * @returns Object with `regex` and `params` array
71
+ *
72
+ * @example
73
+ * compilePattern('/blog/:slug')
74
+ * // { regex: /^\/blog\/([^/]+)\/?$/, params: ['slug'] }
75
+ *
76
+ * compilePattern('/*rest')
77
+ * // { regex: /^\/(.+)\/?$/, params: ['rest'] }
78
+ */
79
+ declare function compilePattern(pattern: string): {
80
+ regex: RegExp;
81
+ params: string[];
82
+ };
83
+ /**
84
+ * Matches a URL against an ordered list of routes. Returns the first match
85
+ * with extracted parameters, or null if no route matches.
86
+ *
87
+ * Routes are tested in order, so more specific routes should come first.
88
+ * The `buildRouteTable` function handles this ordering automatically.
89
+ *
90
+ * @param url - The URL to match
91
+ * @param routes - Ordered array of compiled routes
92
+ * @returns The matched route with params, or null
93
+ */
94
+ declare function matchRoute(url: URL, routes: Route[]): RouteMatch | null;
95
+ /**
96
+ * Builds an ordered route table from a Vite-style glob import manifest.
97
+ *
98
+ * The manifest maps file paths to lazy import functions, e.g.:
99
+ * ```ts
100
+ * {
101
+ * 'src/routes/+page.utopia': () => import('./routes/+page.utopia'),
102
+ * 'src/routes/about/+page.utopia': () => import('./routes/about/+page.utopia'),
103
+ * 'src/routes/blog/[slug]/+page.utopia': () => import('./routes/blog/[slug]/+page.utopia'),
104
+ * 'src/routes/+layout.utopia': () => import('./routes/+layout.utopia'),
105
+ * 'src/routes/+error.utopia': () => import('./routes/+error.utopia'),
106
+ * }
107
+ * ```
108
+ *
109
+ * Routes are sorted so that:
110
+ * 1. Static routes come before dynamic routes
111
+ * 2. Dynamic parameter routes come before catch-all routes
112
+ * 3. More specific routes (more static segments) come first
113
+ *
114
+ * @param manifest - Record of file paths to lazy import functions
115
+ * @returns Ordered array of compiled Route objects
116
+ */
117
+ declare function buildRouteTable(manifest: Record<string, () => Promise<any>>): Route[];
118
+
119
+ /** The currently matched route, or null if no route matches. */
120
+ declare const currentRoute: _matthesketh_utopia_core.Signal<RouteMatch | null>;
121
+ /** Whether a navigation is currently in progress (loading component, etc.). */
122
+ declare const isNavigating: _matthesketh_utopia_core.Signal<boolean>;
123
+ /**
124
+ * Initialize the router with a compiled route table.
125
+ *
126
+ * This must be called once at application startup. It:
127
+ * 1. Stores the route table
128
+ * 2. Matches the current URL and sets `currentRoute`
129
+ * 3. Sets up popstate listener for browser back/forward
130
+ * 4. Sets up click listener for <a> interception
131
+ *
132
+ * @param routeTable - Array of compiled routes from `buildRouteTable()`
133
+ */
134
+ declare function createRouter(routeTable: Route[]): void;
135
+ /**
136
+ * Navigate to a new URL.
137
+ *
138
+ * @param url - The URL to navigate to (absolute path like '/about' or full URL)
139
+ * @param options - Navigation options
140
+ * @param options.replace - If true, replace the current history entry instead of pushing
141
+ * @returns Promise that resolves when navigation is complete
142
+ */
143
+ declare function navigate(url: string, options?: {
144
+ replace?: boolean;
145
+ }): Promise<void>;
146
+ /** Navigate back one entry in the browser history. */
147
+ declare function back(): void;
148
+ /** Navigate forward one entry in the browser history. */
149
+ declare function forward(): void;
150
+ /**
151
+ * Register a navigation guard that runs before each navigation.
152
+ *
153
+ * The hook receives the current and next route matches. It can:
154
+ * - Return `void` or `true` to allow navigation
155
+ * - Return `false` to cancel navigation
156
+ * - Return a string to redirect to a different URL
157
+ *
158
+ * @param hook - The guard callback
159
+ * @returns A function that removes the hook
160
+ */
161
+ declare function beforeNavigate(hook: BeforeNavigateHook): () => void;
162
+ /**
163
+ * Tear down the router, removing all event listeners and clearing state.
164
+ * Primarily useful for testing and HMR.
165
+ */
166
+ declare function destroy(): void;
167
+
168
+ /**
169
+ * Creates a DOM node that renders the current route's component.
170
+ *
171
+ * When the route changes:
172
+ * 1. The old component is unmounted (removed from DOM)
173
+ * 2. The new component is lazily loaded
174
+ * 3. If the route has a layout, the page is wrapped in the layout
175
+ * 4. If loading fails and an error component exists, it is shown instead
176
+ *
177
+ * @returns A container DOM node that manages route component lifecycle
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const view = createRouterView();
182
+ * document.getElementById('app')!.appendChild(view);
183
+ * ```
184
+ */
185
+ declare function createRouterView(): Node;
186
+ /**
187
+ * Creates an anchor element that performs client-side navigation when clicked.
188
+ *
189
+ * The link integrates with the router's click interception, but also sets up
190
+ * the correct `href` and can apply active-state CSS classes.
191
+ *
192
+ * @param props - Link properties
193
+ * @param props.href - The URL to navigate to
194
+ * @param props.children - Child node(s) to render inside the anchor
195
+ * @param props.class - Optional CSS class name
196
+ * @param props.activeClass - Optional CSS class applied when the link's href matches the current route
197
+ * @returns An HTMLAnchorElement
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const link = createLink({
202
+ * href: '/about',
203
+ * children: document.createTextNode('About'),
204
+ * });
205
+ * document.body.appendChild(link);
206
+ * ```
207
+ */
208
+ declare function createLink(props: {
209
+ href: string;
210
+ children: Node | string;
211
+ class?: string;
212
+ activeClass?: string;
213
+ }): HTMLAnchorElement;
214
+
215
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, isNavigating, matchRoute, navigate };
@@ -0,0 +1,215 @@
1
+ import * as _matthesketh_utopia_core from '@matthesketh/utopia-core';
2
+
3
+ /**
4
+ * A compiled route entry in the route table.
5
+ *
6
+ * Routes are built at compile time from the file system manifest and stored
7
+ * as an array. At runtime, incoming URLs are matched against `pattern`.
8
+ */
9
+ interface Route {
10
+ /** URL pattern like '/users/:id' or '/blog/:slug'. */
11
+ path: string;
12
+ /** Compiled regex for matching incoming URLs against this route. */
13
+ pattern: RegExp;
14
+ /** Parameter names extracted from the path (e.g., ['id'] for '/users/:id'). */
15
+ params: string[];
16
+ /** Lazy component import — called only when the route is matched. */
17
+ component: () => Promise<any>;
18
+ /** Optional layout component that wraps the page. */
19
+ layout?: () => Promise<any>;
20
+ /** Optional error boundary component shown when loading fails. */
21
+ error?: () => Promise<any>;
22
+ }
23
+ /**
24
+ * The result of successfully matching a URL against a route.
25
+ */
26
+ interface RouteMatch {
27
+ /** The matched route entry. */
28
+ route: Route;
29
+ /** Extracted parameter values keyed by parameter name. */
30
+ params: Record<string, string>;
31
+ /** The full URL that was matched. */
32
+ url: URL;
33
+ }
34
+ /**
35
+ * Observable router state exposed as signals.
36
+ */
37
+ interface RouterState {
38
+ /** The currently matched route, or null if no route matches. */
39
+ current: RouteMatch | null;
40
+ /** Whether a navigation is currently in progress. */
41
+ navigating: boolean;
42
+ }
43
+ /**
44
+ * Callback signature for beforeNavigate hooks (navigation guards).
45
+ *
46
+ * Return `false` to cancel the navigation, or `void`/`true` to allow it.
47
+ * Returning a string redirects to that URL instead.
48
+ */
49
+ type BeforeNavigateHook = (from: RouteMatch | null, to: RouteMatch | null) => boolean | string | void | Promise<boolean | string | void>;
50
+
51
+ /**
52
+ * Converts a file-system path (relative to the routes directory) into a URL
53
+ * route pattern.
54
+ *
55
+ * @param filePath - File path like 'src/routes/blog/[slug]/+page.utopia'
56
+ * @returns URL pattern like '/blog/:slug'
57
+ *
58
+ * @example
59
+ * filePathToRoute('src/routes/+page.utopia') // '/'
60
+ * filePathToRoute('src/routes/about/+page.utopia') // '/about'
61
+ * filePathToRoute('src/routes/blog/[slug]/+page.utopia') // '/blog/:slug'
62
+ * filePathToRoute('src/routes/[...rest]/+page.utopia') // '/*rest'
63
+ * filePathToRoute('src/routes/(auth)/login/+page.utopia') // '/login'
64
+ */
65
+ declare function filePathToRoute(filePath: string): string;
66
+ /**
67
+ * Compiles a URL route pattern into a regex and extracts parameter names.
68
+ *
69
+ * @param pattern - Route pattern like '/blog/:slug' or '/*rest'
70
+ * @returns Object with `regex` and `params` array
71
+ *
72
+ * @example
73
+ * compilePattern('/blog/:slug')
74
+ * // { regex: /^\/blog\/([^/]+)\/?$/, params: ['slug'] }
75
+ *
76
+ * compilePattern('/*rest')
77
+ * // { regex: /^\/(.+)\/?$/, params: ['rest'] }
78
+ */
79
+ declare function compilePattern(pattern: string): {
80
+ regex: RegExp;
81
+ params: string[];
82
+ };
83
+ /**
84
+ * Matches a URL against an ordered list of routes. Returns the first match
85
+ * with extracted parameters, or null if no route matches.
86
+ *
87
+ * Routes are tested in order, so more specific routes should come first.
88
+ * The `buildRouteTable` function handles this ordering automatically.
89
+ *
90
+ * @param url - The URL to match
91
+ * @param routes - Ordered array of compiled routes
92
+ * @returns The matched route with params, or null
93
+ */
94
+ declare function matchRoute(url: URL, routes: Route[]): RouteMatch | null;
95
+ /**
96
+ * Builds an ordered route table from a Vite-style glob import manifest.
97
+ *
98
+ * The manifest maps file paths to lazy import functions, e.g.:
99
+ * ```ts
100
+ * {
101
+ * 'src/routes/+page.utopia': () => import('./routes/+page.utopia'),
102
+ * 'src/routes/about/+page.utopia': () => import('./routes/about/+page.utopia'),
103
+ * 'src/routes/blog/[slug]/+page.utopia': () => import('./routes/blog/[slug]/+page.utopia'),
104
+ * 'src/routes/+layout.utopia': () => import('./routes/+layout.utopia'),
105
+ * 'src/routes/+error.utopia': () => import('./routes/+error.utopia'),
106
+ * }
107
+ * ```
108
+ *
109
+ * Routes are sorted so that:
110
+ * 1. Static routes come before dynamic routes
111
+ * 2. Dynamic parameter routes come before catch-all routes
112
+ * 3. More specific routes (more static segments) come first
113
+ *
114
+ * @param manifest - Record of file paths to lazy import functions
115
+ * @returns Ordered array of compiled Route objects
116
+ */
117
+ declare function buildRouteTable(manifest: Record<string, () => Promise<any>>): Route[];
118
+
119
+ /** The currently matched route, or null if no route matches. */
120
+ declare const currentRoute: _matthesketh_utopia_core.Signal<RouteMatch | null>;
121
+ /** Whether a navigation is currently in progress (loading component, etc.). */
122
+ declare const isNavigating: _matthesketh_utopia_core.Signal<boolean>;
123
+ /**
124
+ * Initialize the router with a compiled route table.
125
+ *
126
+ * This must be called once at application startup. It:
127
+ * 1. Stores the route table
128
+ * 2. Matches the current URL and sets `currentRoute`
129
+ * 3. Sets up popstate listener for browser back/forward
130
+ * 4. Sets up click listener for <a> interception
131
+ *
132
+ * @param routeTable - Array of compiled routes from `buildRouteTable()`
133
+ */
134
+ declare function createRouter(routeTable: Route[]): void;
135
+ /**
136
+ * Navigate to a new URL.
137
+ *
138
+ * @param url - The URL to navigate to (absolute path like '/about' or full URL)
139
+ * @param options - Navigation options
140
+ * @param options.replace - If true, replace the current history entry instead of pushing
141
+ * @returns Promise that resolves when navigation is complete
142
+ */
143
+ declare function navigate(url: string, options?: {
144
+ replace?: boolean;
145
+ }): Promise<void>;
146
+ /** Navigate back one entry in the browser history. */
147
+ declare function back(): void;
148
+ /** Navigate forward one entry in the browser history. */
149
+ declare function forward(): void;
150
+ /**
151
+ * Register a navigation guard that runs before each navigation.
152
+ *
153
+ * The hook receives the current and next route matches. It can:
154
+ * - Return `void` or `true` to allow navigation
155
+ * - Return `false` to cancel navigation
156
+ * - Return a string to redirect to a different URL
157
+ *
158
+ * @param hook - The guard callback
159
+ * @returns A function that removes the hook
160
+ */
161
+ declare function beforeNavigate(hook: BeforeNavigateHook): () => void;
162
+ /**
163
+ * Tear down the router, removing all event listeners and clearing state.
164
+ * Primarily useful for testing and HMR.
165
+ */
166
+ declare function destroy(): void;
167
+
168
+ /**
169
+ * Creates a DOM node that renders the current route's component.
170
+ *
171
+ * When the route changes:
172
+ * 1. The old component is unmounted (removed from DOM)
173
+ * 2. The new component is lazily loaded
174
+ * 3. If the route has a layout, the page is wrapped in the layout
175
+ * 4. If loading fails and an error component exists, it is shown instead
176
+ *
177
+ * @returns A container DOM node that manages route component lifecycle
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const view = createRouterView();
182
+ * document.getElementById('app')!.appendChild(view);
183
+ * ```
184
+ */
185
+ declare function createRouterView(): Node;
186
+ /**
187
+ * Creates an anchor element that performs client-side navigation when clicked.
188
+ *
189
+ * The link integrates with the router's click interception, but also sets up
190
+ * the correct `href` and can apply active-state CSS classes.
191
+ *
192
+ * @param props - Link properties
193
+ * @param props.href - The URL to navigate to
194
+ * @param props.children - Child node(s) to render inside the anchor
195
+ * @param props.class - Optional CSS class name
196
+ * @param props.activeClass - Optional CSS class applied when the link's href matches the current route
197
+ * @returns An HTMLAnchorElement
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const link = createLink({
202
+ * href: '/about',
203
+ * children: document.createTextNode('About'),
204
+ * });
205
+ * document.body.appendChild(link);
206
+ * ```
207
+ */
208
+ declare function createLink(props: {
209
+ href: string;
210
+ children: Node | string;
211
+ class?: string;
212
+ activeClass?: string;
213
+ }): HTMLAnchorElement;
214
+
215
+ export { type BeforeNavigateHook, type Route, type RouteMatch, type RouterState, back, beforeNavigate, buildRouteTable, compilePattern, createLink, createRouter, createRouterView, currentRoute, destroy, filePathToRoute, forward, isNavigating, matchRoute, navigate };