@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
@@ -0,0 +1,323 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.core.pipeline",
4
+ "title": "Navigation Pipeline - Multi-Phase Navigation Orchestration",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ import { clearAllRouteErrors } from '../features/errors';
9
+ import { scrollFocusManager } from '../features/ux/scroll-focus';
10
+ import { viewTransitionManager } from '../features/ux/transitions';
11
+ /**
12
+ * Navigation Pipeline
13
+ * Orchestrates multi-phase navigation flow: GUARD → HISTORY → LOADER → MOUNT
14
+ *
15
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"NavigationPipeline","stability":"stable","signature":"class NavigationPipeline"}
16
+ */
17
+ export class NavigationPipeline {
18
+ constructor() {
19
+ this.nextNavId = 1;
20
+ this.activeNavId = 0;
21
+ this.activeTransaction = null;
22
+ }
23
+ /**
24
+ * Execute a navigation transaction
25
+ * Returns true if navigation succeeded, false if aborted/failed
26
+ *
27
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"execute","stability":"stable","signature":"async execute(path: string, options: NavigationOptions, context: NavigationContext): Promise<boolean>"}
28
+ */
29
+ async execute(path, options, context) {
30
+ const { replace = false, state = null, source = 'programmatic' } = options;
31
+ const navId = this.nextNavId++;
32
+ const tx = this.createTransaction(navId, path);
33
+ this.cancelActiveNavigation();
34
+ context.onClearLoadingUI(); // Defensive: clear any stale loading/error UI before new nav
35
+ this.activeNavId = navId;
36
+ this.activeTransaction = tx;
37
+ // Pre-commit state: navigating=true, but DO NOT set match yet
38
+ context.updateState({ isNavigating: true, activeNavigation: tx });
39
+ try {
40
+ const url = new URL(path, window.location.origin);
41
+ const pathname = context.normalizePath(url.pathname);
42
+ const search = url.search;
43
+ const match = context.findMatch(pathname);
44
+ if (!match) {
45
+ // 404 decision - crucially, don't clear DOM here
46
+ console.warn('[Router] No route found:', path);
47
+ context.updateState({ isNavigating: false, activeNavigation: null });
48
+ this.activeTransaction = null;
49
+ return false;
50
+ }
51
+ if (!this.isActive(navId))
52
+ return false;
53
+ const query = context.parseQueryParams(search);
54
+ // GUARDS (no DOM mutation)
55
+ const guards = await context.onLoadGuards(match.pattern);
56
+ const guardRes = await context.onExecuteGuards(guards, {
57
+ path: pathname,
58
+ params: match.params,
59
+ query,
60
+ signal: tx.abortController.signal,
61
+ navId,
62
+ from: context.getCurrentPath(),
63
+ });
64
+ if (!this.isActive(navId))
65
+ return false;
66
+ if (!guardRes.allowed) {
67
+ // Redirect if provided
68
+ if (guardRes.redirect) {
69
+ const redirectOpts = {
70
+ source,
71
+ };
72
+ if (guardRes.redirect.replace !== undefined) {
73
+ redirectOpts.replace = guardRes.redirect.replace;
74
+ }
75
+ return this.execute(guardRes.redirect.redirect, redirectOpts, context);
76
+ }
77
+ // Announce guard denial for screen readers
78
+ scrollFocusManager.announceGuardDenied();
79
+ // Snap back only if we mutated history earlier (we won't for pop)
80
+ if (source !== 'pop') {
81
+ context.onHistorySnapBack();
82
+ }
83
+ context.onClearLoadingUI();
84
+ context.updateState({ isNavigating: false, activeNavigation: null });
85
+ this.activeTransaction = null;
86
+ return false;
87
+ }
88
+ // HISTORY write happens only for programmatic/intercept navigations
89
+ const finalHref = pathname + search;
90
+ if (source !== 'pop') {
91
+ // Save current scroll position to history state BEFORE mutating history
92
+ context.onSaveScrollToHistory();
93
+ if (replace)
94
+ context.onHistoryReplace(state, finalHref);
95
+ else
96
+ context.onHistoryPush(state, finalHref);
97
+ }
98
+ // Pre-commit reactive state (no mount yet)
99
+ context.updateState({
100
+ path: pathname,
101
+ params: match.params,
102
+ query,
103
+ isNavigating: true,
104
+ activeNavigation: tx,
105
+ // match intentionally omitted until commit
106
+ });
107
+ // Branch by navigation source for loading UI policy
108
+ const route = context.routes.find(r => r.pattern === match.pattern);
109
+ const hasLoader = !!route?.imports.loader;
110
+ if (source === 'pop') {
111
+ // POPSTATE: defensive cleanup + instant commit
112
+ context.onClearLoadingUI(); // ensure no loader from superseded navigation
113
+ if (context.config.popstateLoading === 'none') {
114
+ // Run loader silently in background; when done, apply data (no structural swap)
115
+ if (hasLoader) {
116
+ context
117
+ .onExecuteLoaders(match.pattern, {
118
+ params: match.params,
119
+ query,
120
+ signal: tx.abortController.signal,
121
+ navId,
122
+ })
123
+ .then(() => {
124
+ if (this.isActive(navId)) {
125
+ // Restore scroll after loader completes
126
+ // Note: scrollFocusManager is accessed via context in the future
127
+ // For now, we'll let the router handle this
128
+ }
129
+ })
130
+ .catch(err => {
131
+ if (!tx.abortController.signal.aborted) {
132
+ console.warn('[Router] Background loader failed:', err);
133
+ }
134
+ });
135
+ }
136
+ // Immediately commit the route change (no loading UI)
137
+ context.updateState({
138
+ path: pathname,
139
+ params: match.params,
140
+ query,
141
+ match,
142
+ isNavigating: false,
143
+ activeNavigation: null,
144
+ });
145
+ // Restore scroll immediately for pop without loader, or before loader starts
146
+ // This will be handled by the router's scroll manager
147
+ }
148
+ else {
149
+ // Handle overlay or replace policies for popstate if configured
150
+ await this.commitPhase(route, match, pathname, query, navId, tx, source, url.hash, context);
151
+ }
152
+ }
153
+ else {
154
+ // PROGRAMMATIC/INTERCEPT: Replace outlet with loading view using atomic swap
155
+ await this.commitPhase(route, match, pathname, query, navId, tx, source, url.hash, context);
156
+ }
157
+ return true;
158
+ }
159
+ catch (err) {
160
+ if (tx.abortController.signal.aborted) {
161
+ // canceled by newer nav/pop: just clean overlay and keep previous screen
162
+ context.onClearLoadingUI();
163
+ }
164
+ else {
165
+ console.error('[Router] Navigation failed:', err);
166
+ // Announce error for screen readers
167
+ scrollFocusManager.announceError('Navigation failed');
168
+ // keep previous DOM; optional: show a toast/error boundary
169
+ context.onClearLoadingUI();
170
+ }
171
+ if (this.isActive(navId)) {
172
+ context.updateState({ isNavigating: false, activeNavigation: null });
173
+ this.activeTransaction = null;
174
+ }
175
+ // Safety: if outlet somehow got emptied by user code, restore snapshot
176
+ context.onRestoreSnapshotIfEmpty();
177
+ return false;
178
+ }
179
+ }
180
+ /**
181
+ * Create a new navigation transaction
182
+ *
183
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"createTransaction","stability":"internal","signature":"private createTransaction(id: NavigationId, path: string): NavigationTransaction"}
184
+ */
185
+ createTransaction(id, path) {
186
+ return {
187
+ id,
188
+ path,
189
+ abortController: new AbortController(),
190
+ isActive: true,
191
+ startTime: Date.now(),
192
+ };
193
+ }
194
+ /**
195
+ * Cancel active navigation
196
+ * Useful for cleanup when disposing the router
197
+ *
198
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"cancelActiveNavigation","stability":"stable","signature":"public cancelActiveNavigation(): void"}
199
+ */
200
+ cancelActiveNavigation() {
201
+ if (this.activeTransaction) {
202
+ this.activeTransaction.isActive = false;
203
+ this.activeTransaction.abortController.abort();
204
+ }
205
+ }
206
+ /**
207
+ * Check if navigation is active
208
+ *
209
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"isActive","stability":"stable","signature":"isActive(navId: NavigationId): boolean"}
210
+ */
211
+ isActive(navId) {
212
+ if (navId !== this.activeNavId) {
213
+ return false;
214
+ }
215
+ if (!this.activeTransaction) {
216
+ return false;
217
+ }
218
+ if (this.activeTransaction.abortController.signal.aborted) {
219
+ return false;
220
+ }
221
+ return this.activeTransaction.isActive;
222
+ }
223
+ /**
224
+ * Get current active navigation ID
225
+ *
226
+ * @codexApi {"parent":"pithy.router.core.pipeline","name":"getActiveNavId","stability":"stable","signature":"getActiveNavId(): NavigationId"}
227
+ */
228
+ getActiveNavId() {
229
+ return this.activeNavId;
230
+ }
231
+ /**
232
+ * Initialize next nav ID (for HMR or testing)
233
+ */
234
+ initializeNavId(startId) {
235
+ this.nextNavId = startId;
236
+ }
237
+ /**
238
+ * COMMIT phase: Execute loaders, show loading UI, commit route change
239
+ * This is the core navigation commit logic extracted from router.ts
240
+ */
241
+ async commitPhase(route, match, pathname, query, navId, tx, source, hash, context) {
242
+ // Show loading UI (router context handles the logic based on policy)
243
+ await context.onShowLoadingUI(route, match, navId, source);
244
+ // Announce loading for screen readers if there's a loader
245
+ if (route?.imports.loader) {
246
+ scrollFocusManager.announceLoading(`Loading ${pathname}`);
247
+ }
248
+ // Execute loaders and capture success/failure
249
+ const loadersOk = await context.onExecuteLoaders(match.pattern, {
250
+ params: match.params,
251
+ query,
252
+ signal: tx.abortController.signal,
253
+ navId,
254
+ });
255
+ if (!this.isActive(navId)) {
256
+ // Navigation superseded; clean up and bail
257
+ context.onClearLoadingUI();
258
+ return;
259
+ }
260
+ // COMMIT: set match to trigger mount with view transitions
261
+ const transitionOptions = viewTransitionManager.getTransitionOptionsFromElement() || undefined;
262
+ const commit = async () => {
263
+ // Only clear all errors if loaders succeeded
264
+ if (loadersOk) {
265
+ clearAllRouteErrors();
266
+ }
267
+ // Update route state and wait for mount to complete
268
+ await context.updateState({
269
+ path: pathname,
270
+ params: match.params,
271
+ query,
272
+ match,
273
+ isNavigating: false, // Navigation completes after mount
274
+ activeNavigation: null,
275
+ });
276
+ // Scroll and focus management runs AFTER mount completes
277
+ // This ensures anchor elements rendered by the new route exist
278
+ // Skip for HMR (handled separately)
279
+ if (source !== 'hmr') {
280
+ // Handle scroll restoration based on navigation type
281
+ if (source === 'pop') {
282
+ scrollFocusManager.restoreFromHistory({ navId });
283
+ }
284
+ else {
285
+ // For push/replace navigation, handle normally
286
+ if (hash) {
287
+ // Await scroll to prevent unhandled rejections after test teardown
288
+ await scrollFocusManager.scrollToAnchor(hash);
289
+ }
290
+ else {
291
+ scrollFocusManager.scrollToTop();
292
+ }
293
+ }
294
+ // Defer focus management to allow DOM to settle
295
+ queueMicrotask(() => {
296
+ requestAnimationFrame(() => {
297
+ scrollFocusManager.manageFocus({ preventScroll: true });
298
+ });
299
+ });
300
+ }
301
+ };
302
+ // Skip view transitions for HMR to prevent race conditions
303
+ if (source === 'hmr') {
304
+ await commit();
305
+ }
306
+ else {
307
+ try {
308
+ await viewTransitionManager.executeTransition(commit, transitionOptions);
309
+ }
310
+ catch (err) {
311
+ console.warn('[Router] View transition failed:', err);
312
+ // Avoid double-commit if the callback already ran
313
+ const currentMatch = context.getCurrentMatch();
314
+ if (currentMatch?.pattern !== match.pattern) {
315
+ await commit();
316
+ }
317
+ }
318
+ }
319
+ // Clear loading UI - will be replaced by actual component
320
+ context.onClearLoadingUI();
321
+ }
322
+ }
323
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/router/core/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.route-tree",
4
+ "title": "Route Tree Utilities for Nested Routes",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ import type { RouteNode, RouteParams } from './types';
9
+ /**
10
+ * Flattened route entry with parent reference for building ancestor chains
11
+ * @internal Used internally by the router for nested route resolution
12
+ */
13
+ export interface FlatRouteEntry {
14
+ /** The route node */
15
+ node: RouteNode;
16
+ /** Parent route ID (null for root routes) */
17
+ parentId: string | null;
18
+ /** Full ancestor chain from root to this route */
19
+ ancestors: RouteNode[];
20
+ }
21
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"flattenRouteTree","stability":"experimental","signature":"(routes: RouteNode[], parent?: RouteNode, ancestors?: RouteNode[]) => FlatRouteEntry[]","description":"Flatten hierarchical route tree into flat list with parent references"} */
22
+ /**
23
+ * Flatten a hierarchical route tree into a flat list with parent references
24
+ * This enables efficient route matching while preserving parent-child relationships
25
+ * for nested layouts and outlet chains
26
+ * @internal Used internally by the router for building route lookup structures
27
+ */
28
+ export declare function flattenRouteTree(routes: RouteNode[], parent?: RouteNode | null, ancestors?: RouteNode[]): FlatRouteEntry[];
29
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"buildRouteMap","stability":"experimental","signature":"(flatRoutes: FlatRouteEntry[]) => Map<string, FlatRouteEntry>","description":"Build route map from flattened routes for quick lookup by ID"} */
30
+ /**
31
+ * Build route map from flattened routes for quick lookup by ID
32
+ * @internal Used internally by the router for efficient route lookup
33
+ */
34
+ export declare function buildRouteMap(flatRoutes: FlatRouteEntry[]): Map<string, FlatRouteEntry>;
35
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"getAncestorChain","stability":"experimental","signature":"(routeId: string, routeMap: Map<string, FlatRouteEntry>) => RouteNode[]","description":"Get full ancestor chain for a route, ordered root to leaf"} */
36
+ /**
37
+ * Get full ancestor chain for a route (including the route itself)
38
+ * Returns routes in order from root to leaf
39
+ * @internal Used internally for nested route resolution
40
+ */
41
+ export declare function getAncestorChain(routeId: string, routeMap: Map<string, FlatRouteEntry>): RouteNode[];
42
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"findMatchingEntry","stability":"experimental","signature":"(path: string, flatRoutes: FlatRouteEntry[]) => FlatRouteEntry | null","description":"Find matching route entry from flattened routes for a given path"} */
43
+ /**
44
+ * Find matching route entry from flattened routes for a given path
45
+ * @internal Used internally by the router for route matching
46
+ */
47
+ export declare function findMatchingEntry(path: string, flatRoutes: FlatRouteEntry[]): FlatRouteEntry | null;
48
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"extractAncestorParams","stability":"experimental","signature":"(path: string, ancestors: RouteNode[]) => RouteParams[]","description":"Extract parameters for each ancestor by matching path progressively"} */
49
+ /**
50
+ * Extract parameters for each ancestor by matching path progressively
51
+ * @internal Used internally for nested route parameter extraction
52
+ */
53
+ export declare function extractAncestorParams(path: string, ancestors: RouteNode[]): RouteParams[];
54
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"findRouteByPattern","stability":"stable","signature":"(routes: RouteNode[], pattern: string) => RouteNode | null","description":"Find a route node by exact pattern match, searching recursively"} */
55
+ /**
56
+ * Find a route node by exact pattern match in a nested route tree.
57
+ * Searches recursively through children.
58
+ *
59
+ * @returns The matching RouteNode, or null if not found
60
+ */
61
+ export declare function findRouteByPattern(routes: RouteNode[], pattern: string): RouteNode | null;
62
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"collectPatterns","stability":"stable","signature":"(routes: RouteNode[]) => string[]","description":"Collect all route patterns from a nested tree into a flat array"} */
63
+ /**
64
+ * Collect all route patterns from a nested route tree.
65
+ * Returns a flat array of pattern strings.
66
+ */
67
+ export declare function collectPatterns(routes: RouteNode[]): string[];
68
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"mergeAncestorParams","stability":"experimental","signature":"(paramsByLevel: RouteParams[]) => RouteParams[]","description":"Merge ancestor params cumulatively; child params override parent"} */
69
+ /**
70
+ * Merge parameters from all ancestors in the chain
71
+ * Child params override parent params with same name
72
+ * Returns merged params for each level (cumulative)
73
+ * @internal Used internally for nested route parameter merging
74
+ */
75
+ export declare function mergeAncestorParams(paramsByLevel: RouteParams[]): RouteParams[];
76
+ //# sourceMappingURL=route-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../../../src/router/core/route-tree.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kDAAkD;IAClD,SAAS,EAAE,SAAS,EAAE,CAAC;CACxB;AAED,qSAAqS;AACrS;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,GAAE,SAAS,GAAG,IAAW,EAC/B,SAAS,GAAE,SAAS,EAAO,GAC1B,cAAc,EAAE,CAyBlB;AAED,gQAAgQ;AAChQ;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,cAAc,EAAE,GAC3B,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAQ7B;AAED,0QAA0Q;AAC1Q;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,SAAS,EAAE,CASb;AAED,gRAAgR;AAChR;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAAE,GAC3B,cAAc,GAAG,IAAI,CAavB;AAED,yQAAyQ;AACzQ;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,EAAE,GACrB,WAAW,EAAE,CAUf;AAED,+PAA+P;AAC/P;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CASlB;AAED,mOAAmO;AACnO;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAS7D;AAED,4PAA4P;AAC5P;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,WAAW,EAAE,GAC3B,WAAW,EAAE,CAWf"}
@@ -0,0 +1,143 @@
1
+ /** @codex
2
+ {
3
+ "id": "pithy.router.route-tree",
4
+ "title": "Route Tree Utilities for Nested Routes",
5
+ "category": "runtime"
6
+ }
7
+ */
8
+ import { matchRoute, findBestMatch } from './matcher';
9
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"flattenRouteTree","stability":"experimental","signature":"(routes: RouteNode[], parent?: RouteNode, ancestors?: RouteNode[]) => FlatRouteEntry[]","description":"Flatten hierarchical route tree into flat list with parent references"} */
10
+ /**
11
+ * Flatten a hierarchical route tree into a flat list with parent references
12
+ * This enables efficient route matching while preserving parent-child relationships
13
+ * for nested layouts and outlet chains
14
+ * @internal Used internally by the router for building route lookup structures
15
+ */
16
+ export function flattenRouteTree(routes, parent = null, ancestors = []) {
17
+ const flat = [];
18
+ for (const route of routes) {
19
+ // Add current route to flat list with parent reference
20
+ const entry = {
21
+ node: route,
22
+ parentId: parent?.id || null,
23
+ ancestors: [...ancestors],
24
+ };
25
+ flat.push(entry);
26
+ // Recursively flatten children
27
+ if (route.children && route.children.length > 0) {
28
+ const childAncestors = [...ancestors, route];
29
+ const childEntries = flattenRouteTree(route.children, route, childAncestors);
30
+ flat.push(...childEntries);
31
+ }
32
+ }
33
+ return flat;
34
+ }
35
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"buildRouteMap","stability":"experimental","signature":"(flatRoutes: FlatRouteEntry[]) => Map<string, FlatRouteEntry>","description":"Build route map from flattened routes for quick lookup by ID"} */
36
+ /**
37
+ * Build route map from flattened routes for quick lookup by ID
38
+ * @internal Used internally by the router for efficient route lookup
39
+ */
40
+ export function buildRouteMap(flatRoutes) {
41
+ const map = new Map();
42
+ for (const entry of flatRoutes) {
43
+ map.set(entry.node.id, entry);
44
+ }
45
+ return map;
46
+ }
47
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"getAncestorChain","stability":"experimental","signature":"(routeId: string, routeMap: Map<string, FlatRouteEntry>) => RouteNode[]","description":"Get full ancestor chain for a route, ordered root to leaf"} */
48
+ /**
49
+ * Get full ancestor chain for a route (including the route itself)
50
+ * Returns routes in order from root to leaf
51
+ * @internal Used internally for nested route resolution
52
+ */
53
+ export function getAncestorChain(routeId, routeMap) {
54
+ const entry = routeMap.get(routeId);
55
+ if (!entry) {
56
+ return [];
57
+ }
58
+ // Return ancestors plus the route itself
59
+ return [...entry.ancestors, entry.node];
60
+ }
61
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"findMatchingEntry","stability":"experimental","signature":"(path: string, flatRoutes: FlatRouteEntry[]) => FlatRouteEntry | null","description":"Find matching route entry from flattened routes for a given path"} */
62
+ /**
63
+ * Find matching route entry from flattened routes for a given path
64
+ * @internal Used internally by the router for route matching
65
+ */
66
+ export function findMatchingEntry(path, flatRoutes) {
67
+ // Extract patterns for matching
68
+ const patterns = flatRoutes.map(entry => entry.node.pattern);
69
+ // Use matcher to find best match
70
+ const match = findBestMatch(path, patterns);
71
+ if (!match) {
72
+ return null;
73
+ }
74
+ // Find the entry with matching pattern
75
+ return flatRoutes.find(entry => entry.node.pattern === match.pattern) || null;
76
+ }
77
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"extractAncestorParams","stability":"experimental","signature":"(path: string, ancestors: RouteNode[]) => RouteParams[]","description":"Extract parameters for each ancestor by matching path progressively"} */
78
+ /**
79
+ * Extract parameters for each ancestor by matching path progressively
80
+ * @internal Used internally for nested route parameter extraction
81
+ */
82
+ export function extractAncestorParams(path, ancestors) {
83
+ const paramsByLevel = [];
84
+ // Match path against each ancestor's pattern to extract params
85
+ for (const ancestor of ancestors) {
86
+ const match = matchRoute(path, ancestor.pattern);
87
+ paramsByLevel.push(match?.params || {});
88
+ }
89
+ return paramsByLevel;
90
+ }
91
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"findRouteByPattern","stability":"stable","signature":"(routes: RouteNode[], pattern: string) => RouteNode | null","description":"Find a route node by exact pattern match, searching recursively"} */
92
+ /**
93
+ * Find a route node by exact pattern match in a nested route tree.
94
+ * Searches recursively through children.
95
+ *
96
+ * @returns The matching RouteNode, or null if not found
97
+ */
98
+ export function findRouteByPattern(routes, pattern) {
99
+ for (const node of routes) {
100
+ if (node.pattern === pattern)
101
+ return node;
102
+ if (node.children && node.children.length > 0) {
103
+ const found = findRouteByPattern(node.children, pattern);
104
+ if (found)
105
+ return found;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"collectPatterns","stability":"stable","signature":"(routes: RouteNode[]) => string[]","description":"Collect all route patterns from a nested tree into a flat array"} */
111
+ /**
112
+ * Collect all route patterns from a nested route tree.
113
+ * Returns a flat array of pattern strings.
114
+ */
115
+ export function collectPatterns(routes) {
116
+ const patterns = [];
117
+ for (const node of routes) {
118
+ if (node.pattern)
119
+ patterns.push(node.pattern);
120
+ if (node.children && node.children.length > 0) {
121
+ patterns.push(...collectPatterns(node.children));
122
+ }
123
+ }
124
+ return patterns;
125
+ }
126
+ /** @codexApi {"parent":"pithy.router.route-tree","name":"mergeAncestorParams","stability":"experimental","signature":"(paramsByLevel: RouteParams[]) => RouteParams[]","description":"Merge ancestor params cumulatively; child params override parent"} */
127
+ /**
128
+ * Merge parameters from all ancestors in the chain
129
+ * Child params override parent params with same name
130
+ * Returns merged params for each level (cumulative)
131
+ * @internal Used internally for nested route parameter merging
132
+ */
133
+ export function mergeAncestorParams(paramsByLevel) {
134
+ const merged = [];
135
+ let cumulative = {};
136
+ // Build cumulative params at each level
137
+ for (const levelParams of paramsByLevel) {
138
+ cumulative = { ...cumulative, ...levelParams };
139
+ merged.push({ ...cumulative });
140
+ }
141
+ return merged;
142
+ }
143
+ //# sourceMappingURL=route-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../../../src/router/core/route-tree.ts"],"names":[],"mappings":"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"}