@pithyjs/router 0.1.0-beta.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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +621 -0
  3. package/dist/index.d.ts +29 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +49 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/router/core/config.d.ts +22 -0
  8. package/dist/router/core/config.d.ts.map +1 -0
  9. package/dist/router/core/config.js +54 -0
  10. package/dist/router/core/config.js.map +1 -0
  11. package/dist/router/core/constants.d.ts +89 -0
  12. package/dist/router/core/constants.d.ts.map +1 -0
  13. package/dist/router/core/constants.js +95 -0
  14. package/dist/router/core/constants.js.map +1 -0
  15. package/dist/router/core/history.d.ts +54 -0
  16. package/dist/router/core/history.d.ts.map +1 -0
  17. package/dist/router/core/history.js +89 -0
  18. package/dist/router/core/history.js.map +1 -0
  19. package/dist/router/core/matcher.d.ts +43 -0
  20. package/dist/router/core/matcher.d.ts.map +1 -0
  21. package/dist/router/core/matcher.js +242 -0
  22. package/dist/router/core/matcher.js.map +1 -0
  23. package/dist/router/core/params.d.ts +88 -0
  24. package/dist/router/core/params.d.ts.map +1 -0
  25. package/dist/router/core/params.js +150 -0
  26. package/dist/router/core/params.js.map +1 -0
  27. package/dist/router/core/pipeline.d.ts +90 -0
  28. package/dist/router/core/pipeline.d.ts.map +1 -0
  29. package/dist/router/core/pipeline.js +323 -0
  30. package/dist/router/core/pipeline.js.map +1 -0
  31. package/dist/router/core/route-tree.d.ts +76 -0
  32. package/dist/router/core/route-tree.d.ts.map +1 -0
  33. package/dist/router/core/route-tree.js +143 -0
  34. package/dist/router/core/route-tree.js.map +1 -0
  35. package/dist/router/core/router.d.ts +229 -0
  36. package/dist/router/core/router.d.ts.map +1 -0
  37. package/dist/router/core/router.js +762 -0
  38. package/dist/router/core/router.js.map +1 -0
  39. package/dist/router/core/types.d.ts +387 -0
  40. package/dist/router/core/types.d.ts.map +1 -0
  41. package/dist/router/core/types.js +22 -0
  42. package/dist/router/core/types.js.map +1 -0
  43. package/dist/router/core/utils.d.ts +66 -0
  44. package/dist/router/core/utils.d.ts.map +1 -0
  45. package/dist/router/core/utils.js +152 -0
  46. package/dist/router/core/utils.js.map +1 -0
  47. package/dist/router/features/coordinator.d.ts +75 -0
  48. package/dist/router/features/coordinator.d.ts.map +1 -0
  49. package/dist/router/features/coordinator.js +203 -0
  50. package/dist/router/features/coordinator.js.map +1 -0
  51. package/dist/router/features/errors/error-codes.d.ts +107 -0
  52. package/dist/router/features/errors/error-codes.d.ts.map +1 -0
  53. package/dist/router/features/errors/error-codes.js +247 -0
  54. package/dist/router/features/errors/error-codes.js.map +1 -0
  55. package/dist/router/features/errors/error-resolution.d.ts +31 -0
  56. package/dist/router/features/errors/error-resolution.d.ts.map +1 -0
  57. package/dist/router/features/errors/error-resolution.js +47 -0
  58. package/dist/router/features/errors/error-resolution.js.map +1 -0
  59. package/dist/router/features/errors/error-types.d.ts +60 -0
  60. package/dist/router/features/errors/error-types.d.ts.map +1 -0
  61. package/dist/router/features/errors/error-types.js +57 -0
  62. package/dist/router/features/errors/error-types.js.map +1 -0
  63. package/dist/router/features/errors/global-error-fallback.d.ts +33 -0
  64. package/dist/router/features/errors/global-error-fallback.d.ts.map +1 -0
  65. package/dist/router/features/errors/global-error-fallback.js +182 -0
  66. package/dist/router/features/errors/global-error-fallback.js.map +1 -0
  67. package/dist/router/features/errors/index.d.ts +15 -0
  68. package/dist/router/features/errors/index.d.ts.map +1 -0
  69. package/dist/router/features/errors/index.js +18 -0
  70. package/dist/router/features/errors/index.js.map +1 -0
  71. package/dist/router/features/errors/router-errors.d.ts +44 -0
  72. package/dist/router/features/errors/router-errors.d.ts.map +1 -0
  73. package/dist/router/features/errors/router-errors.js +53 -0
  74. package/dist/router/features/errors/router-errors.js.map +1 -0
  75. package/dist/router/features/guards/engine.d.ts +40 -0
  76. package/dist/router/features/guards/engine.d.ts.map +1 -0
  77. package/dist/router/features/guards/engine.js +67 -0
  78. package/dist/router/features/guards/engine.js.map +1 -0
  79. package/dist/router/features/guards/index.d.ts +52 -0
  80. package/dist/router/features/guards/index.d.ts.map +1 -0
  81. package/dist/router/features/guards/index.js +95 -0
  82. package/dist/router/features/guards/index.js.map +1 -0
  83. package/dist/router/features/loaders/cache.d.ts +60 -0
  84. package/dist/router/features/loaders/cache.d.ts.map +1 -0
  85. package/dist/router/features/loaders/cache.js +80 -0
  86. package/dist/router/features/loaders/cache.js.map +1 -0
  87. package/dist/router/features/loaders/engine.d.ts +17 -0
  88. package/dist/router/features/loaders/engine.d.ts.map +1 -0
  89. package/dist/router/features/loaders/engine.js +27 -0
  90. package/dist/router/features/loaders/engine.js.map +1 -0
  91. package/dist/router/features/loaders/index.d.ts +49 -0
  92. package/dist/router/features/loaders/index.d.ts.map +1 -0
  93. package/dist/router/features/loaders/index.js +112 -0
  94. package/dist/router/features/loaders/index.js.map +1 -0
  95. package/dist/router/features/loading-ui-manager.d.ts +69 -0
  96. package/dist/router/features/loading-ui-manager.d.ts.map +1 -0
  97. package/dist/router/features/loading-ui-manager.js +209 -0
  98. package/dist/router/features/loading-ui-manager.js.map +1 -0
  99. package/dist/router/features/module-loader.d.ts +50 -0
  100. package/dist/router/features/module-loader.d.ts.map +1 -0
  101. package/dist/router/features/module-loader.js +121 -0
  102. package/dist/router/features/module-loader.js.map +1 -0
  103. package/dist/router/features/outlet/errors.d.ts +24 -0
  104. package/dist/router/features/outlet/errors.d.ts.map +1 -0
  105. package/dist/router/features/outlet/errors.js +96 -0
  106. package/dist/router/features/outlet/errors.js.map +1 -0
  107. package/dist/router/features/outlet/index.d.ts +45 -0
  108. package/dist/router/features/outlet/index.d.ts.map +1 -0
  109. package/dist/router/features/outlet/index.js +95 -0
  110. package/dist/router/features/outlet/index.js.map +1 -0
  111. package/dist/router/features/outlet/mount.d.ts +38 -0
  112. package/dist/router/features/outlet/mount.d.ts.map +1 -0
  113. package/dist/router/features/outlet/mount.js +301 -0
  114. package/dist/router/features/outlet/mount.js.map +1 -0
  115. package/dist/router/features/outlet/segment-chain.d.ts +16 -0
  116. package/dist/router/features/outlet/segment-chain.d.ts.map +1 -0
  117. package/dist/router/features/outlet/segment-chain.js +62 -0
  118. package/dist/router/features/outlet/segment-chain.js.map +1 -0
  119. package/dist/router/features/prefetch/index.d.ts +97 -0
  120. package/dist/router/features/prefetch/index.d.ts.map +1 -0
  121. package/dist/router/features/prefetch/index.js +345 -0
  122. package/dist/router/features/prefetch/index.js.map +1 -0
  123. package/dist/router/features/ux/focus.d.ts +62 -0
  124. package/dist/router/features/ux/focus.d.ts.map +1 -0
  125. package/dist/router/features/ux/focus.js +136 -0
  126. package/dist/router/features/ux/focus.js.map +1 -0
  127. package/dist/router/features/ux/live-region.d.ts +81 -0
  128. package/dist/router/features/ux/live-region.d.ts.map +1 -0
  129. package/dist/router/features/ux/live-region.js +166 -0
  130. package/dist/router/features/ux/live-region.js.map +1 -0
  131. package/dist/router/features/ux/scroll-focus.d.ts +105 -0
  132. package/dist/router/features/ux/scroll-focus.d.ts.map +1 -0
  133. package/dist/router/features/ux/scroll-focus.js +111 -0
  134. package/dist/router/features/ux/scroll-focus.js.map +1 -0
  135. package/dist/router/features/ux/scroll.d.ts +71 -0
  136. package/dist/router/features/ux/scroll.d.ts.map +1 -0
  137. package/dist/router/features/ux/scroll.js +137 -0
  138. package/dist/router/features/ux/scroll.js.map +1 -0
  139. package/dist/router/features/ux/transitions.d.ts +120 -0
  140. package/dist/router/features/ux/transitions.d.ts.map +1 -0
  141. package/dist/router/features/ux/transitions.js +235 -0
  142. package/dist/router/features/ux/transitions.js.map +1 -0
  143. package/dist/router/integration/directive-binder.d.ts +20 -0
  144. package/dist/router/integration/directive-binder.d.ts.map +1 -0
  145. package/dist/router/integration/directive-binder.js +139 -0
  146. package/dist/router/integration/directive-binder.js.map +1 -0
  147. package/dist/router/integration/directives.d.ts +42 -0
  148. package/dist/router/integration/directives.d.ts.map +1 -0
  149. package/dist/router/integration/directives.js +109 -0
  150. package/dist/router/integration/directives.js.map +1 -0
  151. package/dist/router/integration/history-adapter.d.ts +76 -0
  152. package/dist/router/integration/history-adapter.d.ts.map +1 -0
  153. package/dist/router/integration/history-adapter.js +157 -0
  154. package/dist/router/integration/history-adapter.js.map +1 -0
  155. package/dist/router/integration/hmr.d.ts +83 -0
  156. package/dist/router/integration/hmr.d.ts.map +1 -0
  157. package/dist/router/integration/hmr.js +160 -0
  158. package/dist/router/integration/hmr.js.map +1 -0
  159. package/dist/router/integration/link-interceptor.d.ts +49 -0
  160. package/dist/router/integration/link-interceptor.d.ts.map +1 -0
  161. package/dist/router/integration/link-interceptor.js +94 -0
  162. package/dist/router/integration/link-interceptor.js.map +1 -0
  163. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PithyJS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,621 @@
1
+ # @pithyjs/router
2
+
3
+ File-based client-side router for PithyJS with guards, loaders, view transitions, and fine-grained reactivity.
4
+
5
+ ## Features
6
+
7
+ - **File-Based Routing** - Automatic route generation from file structure
8
+ - **Zero-Flash Navigation** - Guards execute before DOM/history mutations
9
+ - **Data Loading** - Loaders with SWR caching and loading states
10
+ - **Two-Phase Mounting** - Atomic DOM updates with diff-based rendering
11
+ - **Error Boundaries** - Parent chain resolution for error components
12
+ - **Intelligent Prefetching** - Hover, visible, and intent-based strategies
13
+ - **View Transitions** - Smooth animations with View Transitions API
14
+ - **Accessibility First** - Focus management, scroll restoration, screen reader announcements
15
+ - **HMR Support** - Hot module replacement for development
16
+ - **TypeScript** - Full type safety with strict mode
17
+
18
+ ## Installation
19
+
20
+ <!-- @codex:auto install="@pithyjs/router" -->
21
+ ```bash
22
+ npm install @pithyjs/router
23
+ ```
24
+
25
+ ```bash
26
+ pnpm add @pithyjs/router
27
+ ```
28
+
29
+ ```bash
30
+ yarn add @pithyjs/router
31
+ ```
32
+ <!-- @codex:end -->
33
+
34
+ ## Quick Start
35
+
36
+ ### 1. Define Routes
37
+
38
+ Create route files in your `src/routes/` directory:
39
+
40
+ ```
41
+ src/routes/
42
+ ├── +page.ts # /
43
+ ├── +layout.ts # Root layout
44
+ ├── about/
45
+ │ └── +page.ts # /about
46
+ └── products/
47
+ ├── +layout.ts # Products layout
48
+ ├── +page.ts # /products
49
+ ├── +loader.ts # Products data loader
50
+ ├── [id]/
51
+ │ ├── +page.ts # /products/:id
52
+ │ ├── +loader.ts # Product details loader
53
+ │ └── +error.ts # Product error boundary
54
+ └── +config.ts # Products route config
55
+ ```
56
+
57
+ ### 2. Create Router
58
+
59
+ ```typescript
60
+ // src/main.ts
61
+ import { createRouter } from '@pithyjs/router';
62
+ import { setupLinkInterceptor } from '@pithyjs/router/integration';
63
+ import routes from './routes'; // Generated by Vite plugin
64
+
65
+ const router = createRouter(routes, {
66
+ basePath: '/',
67
+ loadingPolicy: 'replace'
68
+ });
69
+
70
+ // Enable client-side navigation for all links
71
+ setupLinkInterceptor(router);
72
+
73
+ // Mount root outlet
74
+ const app = document.getElementById('app');
75
+ if (app) {
76
+ router.mount(app);
77
+ }
78
+
79
+ // Expose router for dev tools
80
+ if (import.meta.env.DEV) {
81
+ (globalThis as any).router = router;
82
+ }
83
+ ```
84
+
85
+ ### 3. Configure Vite Plugin
86
+
87
+ ```typescript
88
+ // vite.config.ts
89
+ import { defineConfig } from 'vite';
90
+ import { pithyRouter } from '@pithyjs/vite-plugin-router';
91
+
92
+ export default defineConfig({
93
+ plugins: [
94
+ pithyRouter({
95
+ routesDir: 'src/routes',
96
+ outputFile: 'src/routes.ts'
97
+ })
98
+ ]
99
+ });
100
+ ```
101
+
102
+ ## Route File Conventions
103
+
104
+ | File | Purpose |
105
+ |------|---------|
106
+ | `+page.ts` | Page component |
107
+ | `+layout.ts` | Layout wrapper with `<div @routerOutlet>` |
108
+ | `+loader.ts` | Data loader (runs before page render) |
109
+ | `+error.ts` | Error boundary component |
110
+ | `+config.ts` | Route configuration (guards, cache options) |
111
+ | `[param]/` | Dynamic route segment |
112
+ | `(group)/` | Route group (doesn't affect URL) |
113
+
114
+ ## Core Concepts
115
+
116
+ ### Guards (Authorization)
117
+
118
+ Guards execute **before** navigation to protect routes:
119
+
120
+ ```typescript
121
+ // guards/auth.ts
122
+ import type { GuardFunction } from '@pithyjs/router';
123
+
124
+ export const authGuard: GuardFunction = async ({ to, signal }) => {
125
+ const isAuthenticated = await checkAuth(signal);
126
+
127
+ if (!isAuthenticated) {
128
+ return `/login?returnUrl=${encodeURIComponent(to.path)}`;
129
+ }
130
+
131
+ return true; // Allow navigation
132
+ };
133
+ ```
134
+
135
+ ```typescript
136
+ // products/+config.ts
137
+ import { authGuard } from '../guards/auth';
138
+
139
+ export const guards = [authGuard];
140
+ ```
141
+
142
+ ### Loaders (Data Fetching)
143
+
144
+ Loaders fetch data before rendering:
145
+
146
+ ```typescript
147
+ // products/[id]/+loader.ts
148
+ import type { LoaderFunction } from '@pithyjs/router';
149
+
150
+ export const loader: LoaderFunction = async ({ params, signal }) => {
151
+ const response = await fetch(`/api/products/${params.id}`, { signal });
152
+
153
+ if (!response.ok) {
154
+ throw new Error(`HTTP ${response.status}`);
155
+ }
156
+
157
+ return response.json();
158
+ };
159
+ ```
160
+
161
+ ```typescript
162
+ // products/[id]/+page.ts
163
+ import { html } from '@pithyjs/core';
164
+
165
+ export default function ProductPage({ id, loaderData }) {
166
+ const product = loaderData;
167
+
168
+ return html`
169
+ <div>
170
+ <h1>{{ product.name }}</h1>
171
+ <p>{{ product.description }}</p>
172
+ <p>Price: {{ product.price }}</p>
173
+ </div>
174
+ `;
175
+ }
176
+ ```
177
+
178
+ ### Layouts
179
+
180
+ Layouts wrap child pages:
181
+
182
+ ```typescript
183
+ // products/+layout.ts
184
+ import { html } from '@pithyjs/core';
185
+
186
+ export default function ProductsLayout() {
187
+ return html`
188
+ <div class="products-layout">
189
+ <aside>
190
+ <nav>
191
+ <a href="/products" @routerLink>All Products</a>
192
+ <a href="/products/featured" @routerLink>Featured</a>
193
+ </nav>
194
+ </aside>
195
+
196
+ <main>
197
+ <!-- Child pages render here -->
198
+ <div @routerOutlet></div>
199
+ </main>
200
+ </div>
201
+ `;
202
+ }
203
+ ```
204
+
205
+ ### Error Boundaries
206
+
207
+ Error components handle errors in child routes:
208
+
209
+ ```typescript
210
+ // products/+error.ts
211
+ import { html } from '@pithyjs/core';
212
+
213
+ export default function ProductError({ error, retry }: { error: Error; retry: () => void }) {
214
+ return html`<div class="error"><h1>{{ error.message }}</h1><button @click="retry()">Retry</button></div>`;
215
+ }
216
+ ```
217
+
218
+ ## Navigation
219
+
220
+ ### Declarative Navigation
221
+
222
+ ```html
223
+ <!-- Basic link -->
224
+ <a href="/products" @routerLink>Products</a>
225
+
226
+ <!-- With active state -->
227
+ <a href="/products" routerActive="active" @routerLink>Products</a>
228
+
229
+ <!-- With prefetch -->
230
+ <a href="/products" data-prefetch="hover" @routerLink>Products</a>
231
+
232
+ <!-- Button navigation -->
233
+ <button href="/dashboard" @routerLink>Go to Dashboard</button>
234
+ ```
235
+
236
+ ### Programmatic Navigation
237
+
238
+ ```typescript
239
+ import { createRouter } from '@pithyjs/router';
240
+
241
+ const router = createRouter(routes);
242
+
243
+ // Basic navigation
244
+ router.navigate('/products');
245
+
246
+ // With options
247
+ router.navigate('/products/123', {
248
+ replace: true, // Replace history entry
249
+ skipScroll: true, // Don't scroll to top
250
+ state: { from: 'search' }
251
+ });
252
+
253
+ // History navigation
254
+ router.back();
255
+ router.forward();
256
+ ```
257
+
258
+ ## Configuration
259
+
260
+ ### Router Options
261
+
262
+ ```typescript
263
+ const router = createRouter(routes, {
264
+ // Core options
265
+ basePath: '/', // Base path for all routes
266
+ loadingPolicy: 'replace', // 'replace' | 'overlay' | 'progress-only'
267
+ popstateLoading: 'none', // Silent loaders for back/forward
268
+
269
+ // Cache options (default for all routes)
270
+ cache: {
271
+ strategy: 'swr', // 'swr' | 'cache-first' | 'no-store'
272
+ ttl: 300000 // 5 minutes
273
+ },
274
+
275
+ // UX options
276
+ scroll: {
277
+ enableAutoScroll: true,
278
+ scrollBehavior: 'smooth',
279
+ anchorScrollDelay: 100
280
+ },
281
+ focus: {
282
+ enableAutoFocus: true,
283
+ focusTarget: '[data-focus-target]'
284
+ },
285
+ transitions: {
286
+ enabled: true,
287
+ defaultTransition: 'fade',
288
+ duration: 400
289
+ },
290
+
291
+ // Prefetch options
292
+ prefetch: {
293
+ hoverDelay: 200,
294
+ visibleMargin: '200px'
295
+ }
296
+ });
297
+ ```
298
+
299
+ ### Route-Level Configuration
300
+
301
+ ```typescript
302
+ // +config.ts
303
+ import { authGuard, adminGuard } from '../guards';
304
+ import { lazy } from '@pithyjs/core';
305
+
306
+ export const guards = [authGuard, adminGuard];
307
+
308
+ export const cache = {
309
+ strategy: 'swr' as const,
310
+ ttl: 60000 // 1 minute
311
+ };
312
+
313
+ // Lazy components used in this route
314
+ const Chart = lazy(() => import('./Chart.pithy'), { key: 'lazy-chart' });
315
+ const Table = lazy(() => import('./Table.pithy'), { key: 'lazy-table' });
316
+
317
+ // Prefetch these chunks when route is prefetched
318
+ export const prefetchChunks = [
319
+ () => Chart.load(),
320
+ () => Table.load()
321
+ ];
322
+ ```
323
+
324
+ ## Advanced Features
325
+
326
+ ### Prefetching
327
+
328
+ ```typescript
329
+ // Manual prefetch (also prefetches lazy chunks defined in +config.ts)
330
+ await router.prefetchRoute('/products');
331
+ ```
332
+
333
+ Directive-based (automatic):
334
+
335
+ ```html
336
+ <a href="/products" @routerLink data-prefetch="hover">Products</a>
337
+ <a href="/about" @routerLink data-prefetch="visible">About</a>
338
+ <a href="/dashboard" @routerLink data-prefetch="intent">Dashboard</a>
339
+ ```
340
+
341
+ When prefetching a route, the router also executes any `prefetchChunks` defined in the route's `+config.ts`. This enables prefetching lazy-loaded components along with route data.
342
+
343
+ ### View Transitions
344
+
345
+ ```typescript
346
+ // Enable globally
347
+ const router = createRouter(routes, {
348
+ transitions: {
349
+ enabled: true,
350
+ defaultTransition: 'fade',
351
+ duration: 300
352
+ }
353
+ });
354
+ ```
355
+
356
+ ```html
357
+ <!-- Custom transition name -->
358
+ <div data-transition-name="hero">
359
+ <img src="/hero.jpg" alt="Hero">
360
+ </div>
361
+ ```
362
+
363
+ ### Scroll Restoration
364
+
365
+ ```typescript
366
+ // Automatic scroll restoration for back/forward
367
+ router.navigate('/products'); // Scrolls to top
368
+
369
+ // Manual control
370
+ router.navigate('/products#section-2'); // Scrolls to anchor
371
+
372
+ router.navigate('/products', {
373
+ skipScroll: true // Don't auto-scroll
374
+ });
375
+ ```
376
+
377
+ ## API Reference
378
+
379
+ | API | Signature | Stability | Description |
380
+ | --- | --- | --- | --- |
381
+ | Router | `class Router` | stable | Core router class with navigation transaction state machine |
382
+ | state | `() => Signal<RouterState>` | stable | Get current router state as a reactive signal |
383
+ | path | `() => () => string` | stable | Get current path as a computed reactive signal |
384
+ | params | `() => () => RouteParams` | stable | Get current route params as a computed reactive signal |
385
+ | query | `() => () => QueryParams` | stable | Get current query params as a computed reactive signal |
386
+ | loaderData | `() => () => ResourceState \| null` | stable | Get current loader data as a computed reactive signal |
387
+ | navigate | `(path: string, options?: NavigationOptions) => Promise<boolean>` | stable | Navigate to a path with two-phase commit via the pipeline |
388
+ | back | `() => void` | stable | Go back one entry in browser history |
389
+ | forward | `() => void` | stable | Go forward one entry in browser history |
390
+ | mount | `(rootElement: HTMLElement) => void` | stable | Mount router to a DOM element and activate outlet routing |
391
+ | getActiveNavId | `() => NavigationId` | experimental | Get the active navigation ID for outlet manager integration |
392
+ | isNavActive | `(navId: NavigationId) => boolean` | experimental | Check if a navigation ID is still active |
393
+ | prefetchRoute | `(href: string, signal: AbortSignal) => Promise<void>` | experimental | Prefetch route components using shared module cache |
394
+ | updateRoutes | `(newRoutes: RouteNode[], options?: { source?: string }) => Promise<void>` | stable | Hot-update routes for HMR, preserving router state |
395
+ | prefetchLoader | `(href: string, signal: AbortSignal) => Promise<void>` | experimental | Prefetch route loader data with guard checks |
396
+ | getRouteCacheTTL | `(href: string) => Promise<number \| undefined>` | experimental | Get the configured cache TTL for a specific route |
397
+ | dispose | `() => void` | stable | Dispose router and clean up all listeners, outlets, and navigations |
398
+ | createRouter | `(routes: RouteNode[], config?: RouterConfig) => Router` | stable | Create a router instance with HMR hot-singleton reuse |
399
+
400
+ ### Matcher
401
+
402
+ | API | Signature | Stability | Description |
403
+ | --- | --- | --- | --- |
404
+ | parseRoutePattern | `(pattern: string) => RouteSegment[]` | stable | Parse a route pattern into typed segments (static, dynamic, catch-all, group) |
405
+ | calculateRouteScore | `(segments: RouteSegment[]) => number` | stable | Calculate route precedence score for conflict resolution |
406
+ | normalizePath | `(path: string, policy?: TrailingSlashPolicy) => string` | stable | Normalize path according to trailing slash policy |
407
+ | extractParams | `(pathSegments: string[], routeSegments: RouteSegment[], decodeParams?: boolean) => RouteParams` | stable | Extract parameters from a matched path using route segments |
408
+ | matchRoute | `(path: string, pattern: string, config?: RouterConfig) => RouteMatch \| null` | stable | Check if a path matches a route pattern and return match details |
409
+ | findBestMatch | `(path: string, patterns: string[], config?: RouterConfig) => RouteMatch \| null` | stable | Find the best matching route by highest precedence score |
410
+
411
+ ### Route Tree
412
+
413
+ | API | Signature | Stability | Description |
414
+ | --- | --- | --- | --- |
415
+ | flattenRouteTree | `(routes: RouteNode[], parent?: RouteNode, ancestors?: RouteNode[]) => FlatRouteEntry[]` | experimental | Flatten hierarchical route tree into flat list with parent references |
416
+ | buildRouteMap | `(flatRoutes: FlatRouteEntry[]) => Map<string, FlatRouteEntry>` | experimental | Build route map from flattened routes for quick lookup by ID |
417
+ | getAncestorChain | `(routeId: string, routeMap: Map<string, FlatRouteEntry>) => RouteNode[]` | experimental | Get full ancestor chain for a route, ordered root to leaf |
418
+ | findMatchingEntry | `(path: string, flatRoutes: FlatRouteEntry[]) => FlatRouteEntry \| null` | experimental | Find matching route entry from flattened routes for a given path |
419
+ | extractAncestorParams | `(path: string, ancestors: RouteNode[]) => RouteParams[]` | experimental | Extract parameters for each ancestor by matching path progressively |
420
+ | findRouteByPattern | `(routes: RouteNode[], pattern: string) => RouteNode \| null` | stable | Find a route node by exact pattern match, searching recursively |
421
+ | collectPatterns | `(routes: RouteNode[]) => string[]` | stable | Collect all route patterns from a nested tree into a flat array |
422
+ | mergeAncestorParams | `(paramsByLevel: RouteParams[]) => RouteParams[]` | experimental | Merge ancestor params cumulatively; child params override parent |
423
+
424
+ ### Guards
425
+
426
+ | API | Signature | Stability | Description |
427
+ | --- | --- | --- | --- |
428
+ | RedirectConfig | `interface RedirectConfig` | stable | Redirect configuration for guard results with optional history replacement |
429
+ | GuardManager | `class GuardManager` | stable | Manages route guards with zero-flash navigation and cancellation |
430
+ | executeGuards | `(guards: GuardFunction[], context: GuardContext) => Promise<GuardExecutionResult>` | stable | Execute guards in parallel with proper cancellation support |
431
+ | cancelGuards | `(navId: NavigationId) => void` | stable | Cancel all active guards for a navigation |
432
+ | AuthProvider | `interface AuthProvider` | stable | Authentication provider interface for user state and permissions |
433
+
434
+ ### Loaders
435
+
436
+ | API | Signature | Stability | Description |
437
+ | --- | --- | --- | --- |
438
+ | CacheOptions | `interface CacheOptions` | stable | Cache configuration with TTL and strategy for route loaders |
439
+ | LoaderManager | `class LoaderManager` | stable | Manages route loaders with caching and cancellation support |
440
+ | executeLoader | `<T>(routeId: string, loader: LoaderFunction<T>, context: LoaderContext, options?: CacheOptions) => Promise<T>` | stable | Execute a loader with caching and cancellation support |
441
+ | executeRouteLoader | `<T>(routeId: string, loader: LoaderFunction<T>, context: LoaderContext, options?: CacheOptions) => Promise<T>` | stable | Execute a route loader with caching and cancellation via global manager |
442
+ | getResourceCache | `() => ResourceCache` | stable | Get the global resource cache instance |
443
+
444
+ ### Prefetching
445
+
446
+ | API | Signature | Stability | Description |
447
+ | --- | --- | --- | --- |
448
+ | PrefetchStrategy | `type PrefetchStrategy` | stable | Strategy options controlling when routes are prefetched |
449
+ | PrefetchConfig | `interface PrefetchConfig` | stable | Fine-tune prefetch behavior with delays, thresholds, and caching |
450
+ | PrefetchManager | `class PrefetchManager` | stable | Intelligent resource prefetching with enhanced heuristics |
451
+ | registerLink | `(element: HTMLElement, strategy: PrefetchStrategy) => void` | stable | Register a link element for prefetching with a given strategy |
452
+ | unregisterLink | `(element: HTMLElement) => void` | stable | Unregister a link and clean up its prefetch listeners |
453
+ | prefetchRoute | `(href: string, strategy: PrefetchStrategy) => Promise<void>` | stable | Prefetch a route with priority handling and deduplication |
454
+ | cancelPrefetch | `(href: string) => void` | stable | Cancel an in-flight prefetch for a specific route |
455
+ | dispose | `() => void` | stable | Clean up all prefetch resources, requests, and timers |
456
+ | getPrefetchManager | `() => PrefetchManager` | stable | Get the lazy-initialized global prefetch manager |
457
+
458
+ ### Error Handling
459
+
460
+ | API | Signature | Stability | Description |
461
+ | --- | --- | --- | --- |
462
+ | ErrorComponentProps | `interface ErrorComponentProps` | stable | Props passed to error boundary components |
463
+ | GlobalErrorFallback | `(props: ErrorComponentProps) => Component` | stable | Default error UI when no route-specific error.ts exists |
464
+ | setRouteError | `(routeId: string, err: RouterError) => void` | stable | Set an error for a route, triggering reactive outlet update |
465
+ | clearRouteError | `(routeId: string) => void` | stable | Clear error for a specific route on retry or navigation |
466
+ | clearAllRouteErrors | `() => void` | stable | Clear all route errors on successful navigation |
467
+ | resolveErrorImport | `(node: RouteNode, getParent: ...) => (() => Promise<unknown>) \| null` | internal | Resolve error component by walking parent chain |
468
+ | hasErrorComponent | `(node: RouteNode) => boolean` | internal | Check if a route node has an error component |
469
+
470
+ ### Router Class
471
+
472
+ ```typescript
473
+ class Router {
474
+ state(): Signal<RouterState>
475
+ path(): () => string
476
+ params(): () => RouteParams
477
+ query(): () => QueryParams
478
+ loaderData(): () => ResourceState | null
479
+ navigate(path: string, options?: NavigationOptions): Promise<boolean>
480
+ back(): void
481
+ forward(): void
482
+ mount(rootElement: HTMLElement): void
483
+ prefetchRoute(href: string, signal: AbortSignal): Promise<void>
484
+ prefetchLoader(href: string, signal: AbortSignal): Promise<void>
485
+ updateRoutes(newRoutes: RouteNode[], options?: { source?: string }): Promise<void>
486
+ dispose(): void
487
+ }
488
+ ```
489
+
490
+ ### Types
491
+
492
+ ```typescript
493
+ interface RouterConfig {
494
+ basePath?: string;
495
+ loadingPolicy?: 'replace' | 'overlay' | 'progress-only';
496
+ cache?: CacheOptions;
497
+ scroll?: ScrollConfig;
498
+ focus?: FocusConfig;
499
+ transitions?: ViewTransitionConfig;
500
+ // ... more options
501
+ }
502
+
503
+ interface NavigationOptions {
504
+ replace?: boolean;
505
+ skipScroll?: boolean;
506
+ state?: any;
507
+ }
508
+
509
+ type GuardFunction = (context: GuardContext) =>
510
+ | boolean
511
+ | string
512
+ | RedirectConfig
513
+ | Promise<boolean | string | RedirectConfig>;
514
+
515
+ type LoaderFunction<T = any> = (context: LoaderContext) =>
516
+ | T
517
+ | Promise<T>;
518
+ ```
519
+
520
+ ## Documentation
521
+
522
+ - **[Implementation Overview](src/router/README.md)** - Architecture and navigation flow
523
+ - **[Core Engine](src/router/core/README.md)** - Router engine and pipeline
524
+ - **[Features](src/router/features/README.md)** - Feature modules overview
525
+ - [Error Handling](src/router/features/errors/README.md)
526
+ - [Route Guards](src/router/features/guards/README.md)
527
+ - [Data Loaders](src/router/features/loaders/README.md)
528
+ - [Outlet System](src/router/features/outlet/README.md)
529
+ - [Prefetch System](src/router/features/prefetch/README.md)
530
+ - [UX Features](src/router/features/ux/README.md)
531
+ - **[Integration Layer](src/router/integration/README.md)** - Directives, HMR, link interception
532
+ - **[Error Handling Guide](ERROR_HANDLING.md)** - Complete error handling documentation
533
+
534
+ ## Examples
535
+
536
+ ### Basic Navigation
537
+
538
+ ```typescript
539
+ import { createRouter } from '@pithyjs/router';
540
+ import { effect } from '@pithyjs/signals';
541
+
542
+ const router = createRouter(routes);
543
+
544
+ // Navigate and check result
545
+ const success = await router.navigate('/about');
546
+ console.log(success); // true
547
+
548
+ // Reactive state — updates automatically on navigation
549
+ const state = router.state();
550
+ effect(() => {
551
+ console.log('Current path:', state().path);
552
+ console.log('Params:', state().params);
553
+ console.log('Query:', state().query);
554
+ });
555
+ ```
556
+
557
+ ### Dynamic Route Parameters
558
+
559
+ ```typescript
560
+ // Given route pattern: /products/:id
561
+ await router.navigate('/products/laptop-123');
562
+
563
+ const state = router.state();
564
+ console.log(state().params); // { id: 'laptop-123' }
565
+ ```
566
+
567
+ ### Guarded Navigation
568
+
569
+ ```typescript
570
+ // Guard blocks unauthorized access and redirects
571
+ import type { GuardFunction } from '@pithyjs/router';
572
+
573
+ const authGuard: GuardFunction = async ({ to }) => {
574
+ const user = await getUser();
575
+ if (!user) return `/login?returnUrl=${to.path}`;
576
+ return true;
577
+ };
578
+ ```
579
+
580
+ See the [tests/demo-app/](tests/demo-app/) for a complete example application demonstrating:
581
+
582
+ - File-based routing
583
+ - Guards and loaders
584
+ - Error boundaries
585
+ - Nested layouts
586
+ - View transitions
587
+ - Prefetching
588
+
589
+ ## Testing
590
+
591
+ The router package has 18 test files with 281+ passing tests. The table below highlights the 15 files that include `@codex:example` markers:
592
+
593
+ | Module | Test File | Markers |
594
+ | --- | --- | --- |
595
+ | Navigation & state | `navigation.test.ts` | 5 |
596
+ | Guards | `guards.test.ts` | 3 |
597
+ | Loaders & caching | `loader.test.ts` | 2 |
598
+ | Route tree | `route-tree.test.ts` | 3 |
599
+ | Outlet mounting | `outlet.test.ts` | 2 |
600
+ | Error resolution | `error-resolution.test.ts` | 2 |
601
+ | Error types | `router-error.test.ts` | 1 |
602
+ | Configuration | `config.test.ts` | 2 |
603
+ | Prefetching | `prefetch.test.ts` | 1 |
604
+ | Scroll & focus | `ux.test.ts` | 2 |
605
+ | Live region a11y | `live-region.test.ts` | 1 |
606
+ | HMR state preservation | `hmr.test.ts` | 1 |
607
+ | Integration flow | `integration.test.ts` | 1 |
608
+ | Directive binding | `directive-binder.test.ts` | 1 |
609
+ | Router dispose | `router-dispose.test.ts` | 1 |
610
+
611
+ ```bash
612
+ cd packages/router && pnpm test
613
+ ```
614
+
615
+ ## License
616
+
617
+ MIT
618
+
619
+ ## Contributing
620
+
621
+ See the main [DEVELOPMENT.md](../../docs/DEVELOPMENT.md) for development guidelines.
@@ -0,0 +1,29 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router",
4
+ "title": "PithyJS Router Core",
5
+ "category": "feature"
6
+ }
7
+ */
8
+ export * from './router/core/types';
9
+ export * from './router/core/matcher';
10
+ export * from './router/core/route-tree';
11
+ export * from './router/core/params';
12
+ export * from './router/core/utils';
13
+ export * from './router/core/router';
14
+ export * from './router/core/config';
15
+ export * from './router/core/history';
16
+ export * from './router/integration/directives';
17
+ export { setGlobalRouter, getGlobalRouter, } from './router/integration/directive-binder';
18
+ export * from './router/features/loaders';
19
+ export * from './router/features/guards';
20
+ export * from './router/features/prefetch';
21
+ export * from './router/integration/hmr';
22
+ export * from './router/integration/link-interceptor';
23
+ export * from './router/features/ux/scroll-focus';
24
+ export * from './router/features/ux/transitions';
25
+ export * from './router/features/errors';
26
+ export { OutletManager, type RouterInstance } from './router/features/outlet';
27
+ export { buildSegmentChain } from './router/features/outlet/segment-chain';
28
+ export { BrowserHistoryAdapter, MemoryHistoryAdapter, } from './router/integration/history-adapter';
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAGF,cAAc,qBAAqB,CAAC;AAGpC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,0BAA0B,CAAC;AAGzC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,uBAAuB,CAAC;AAGtC,cAAc,iCAAiC,CAAC;AAGhD,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,uCAAuC,CAAC;AAG/C,cAAc,2BAA2B,CAAC;AAG1C,cAAc,0BAA0B,CAAC;AAGzC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,0BAA0B,CAAC;AAGzC,cAAc,uCAAuC,CAAC;AAGtD,cAAc,mCAAmC,CAAC;AAGlD,cAAc,kCAAkC,CAAC;AAGjD,cAAc,0BAA0B,CAAC;AAGzC,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAG3E,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sCAAsC,CAAC"}