@reactra/router 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akhil Shastri and the Reactra contributors
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,17 @@
1
+ # @reactra/router
2
+
3
+ > Reactra router runtime — file-based routing, params/query, navigation, and middleware.
4
+
5
+ **Alpha** — published under the npm dist-tag `alpha`. APIs may change before 1.0.
6
+
7
+ ```bash
8
+ npm install @reactra/router@alpha
9
+ ```
10
+
11
+ Part of [Reactra](https://github.com/akhilshastri/reactra) — a compiler-first,
12
+ React-19-compatible framework. See the [documentation](https://reactra-docs.vercel.app) to get
13
+ started.
14
+
15
+ ## License
16
+
17
+ MIT
@@ -0,0 +1,508 @@
1
+ import type { CSSProperties, ComponentType, LazyExoticComponent, ReactNode } from "react";
2
+ import type { MiddlewareDef } from "./middleware.ts";
3
+ import type { ScrollRestorationMode } from "./scrollManager.ts";
4
+ /**
5
+ * Routing bindings the compiler registers for a page component with
6
+ * argumented `use storeX(...)`. The router looks these up by NAME on enter /
7
+ * exit (see {@link RouterRegistryImpl.registerRouteBindings}).
8
+ *
9
+ * Day 16 (`#18b`): name-keyed registration replaced the prior
10
+ * `Object.assign(component, { routeBindings })` wrap. The wrap broke React
11
+ * Fast Refresh's export-compatibility check, forcing a full reload on every
12
+ * edit to a mixed page+route-store file. With name-keyed registration, the
13
+ * page export is a plain arrow Fast Refresh accepts, and the
14
+ * `RouterRegistry.registerRouteBindings("Foo", { … })` module-level call
15
+ * overwrites the bindings entry under the same name when the module
16
+ * re-evaluates after HMR.
17
+ */
18
+ /** Route-entry coordinates handed to onEnter/onExit; keys preserved-state slots (§8.4). */
19
+ export interface RouteLifecycleCtx {
20
+ pathname: string;
21
+ params: Record<string, string>;
22
+ query: Record<string, string | string[]>;
23
+ }
24
+ export interface RouteBindings {
25
+ onEnter: (ctx: RouteLifecycleCtx) => void;
26
+ onExit: (fromCtx: RouteLifecycleCtx, toCtx?: RouteLifecycleCtx | null) => void;
27
+ /**
28
+ * Stage E (Resource v1 §8.3 / §8.5) — compiler-emitted prefetch warmer.
29
+ * `PrefetchRuntime.warm(to, params, query)` calls this with the parsed
30
+ * lifecycle ctx + a per-key AbortController signal so destination route
31
+ * stores' `warm(inputs, signal)` companions can pre-fill the resource
32
+ * cache ahead of real navigation. Bindings that own no route stores
33
+ * with resources omit this; PrefetchRuntime treats absence as a no-op.
34
+ * Best-effort: returned promise resolves silently on any failure (matches
35
+ * the warmResource contract — RES015 deliberately not minted).
36
+ */
37
+ warm?: (ctx: RouteLifecycleCtx, signal: AbortSignal) => Promise<void>;
38
+ }
39
+ /**
40
+ * A page component is a plain React function component, or — once routes are
41
+ * code-split (§5.2) — the `lazy()`-wrapped exotic component the generated
42
+ * manifest emits (`component: lazy(() => import("./pages/..."))`). Day-16 `#18b`
43
+ * dropped the `routeBindings?` field — bindings are stored in the registry by
44
+ * name, not on the component identity itself.
45
+ */
46
+ export type PageComponent = ComponentType | LazyExoticComponent<ComponentType>;
47
+ /**
48
+ * Route registration shape. The user passes these to `configureRouter` —
49
+ * one entry per page. `path` is a pattern with `:name` placeholders matching
50
+ * URL segments. `bindingsName` is set by the walker for pages with
51
+ * argumented `use storeX(...)` so the router can look up the
52
+ * compiler-registered route bindings on enter / exit.
53
+ */
54
+ /**
55
+ * Prefetch trigger for a route / `RouteLink` (Router §5.5). `none` disables it.
56
+ * `visible` uses an IntersectionObserver; `hover` fires on pointer-enter/focus;
57
+ * `mount` warms as soon as a link to the route renders.
58
+ */
59
+ export type PrefetchTrigger = "hover" | "visible" | "mount" | "none";
60
+ export interface RouteConfig {
61
+ id: string;
62
+ path: string;
63
+ component: PageComponent;
64
+ /**
65
+ * §5.2/§5.5 — warms this route's code-split chunk: the SAME dynamic import the
66
+ * `component` `lazy()` wraps, exposed as a bare thunk the walker emits so
67
+ * {@link PrefetchRuntime} can trigger the fetch ahead of navigation. Rollup
68
+ * dedups — `lazy(() => import(X))` and `() => import(X)` share one chunk.
69
+ */
70
+ preload?: () => Promise<unknown>;
71
+ /**
72
+ * §5.5 — the page's declared `prefetch on <trigger>` policy. Reserved: the
73
+ * walker does not emit it yet (the page-keyword→manifest hop is a follow-up),
74
+ * so in Phase 1 the `RouteLink` `prefetch` prop is the driver.
75
+ */
76
+ prefetch?: {
77
+ trigger: PrefetchTrigger;
78
+ };
79
+ /**
80
+ * §2.3 `_loading.tsx` — the nearest-ancestor loading component the walker
81
+ * resolved for this route (file convention). `RouteRenderer` uses it as the
82
+ * Suspense fallback in place of the global `configureRouter({ loadingFallback })`.
83
+ * Eagerly imported in the manifest so it renders synchronously when the
84
+ * route's lazy chunk suspends.
85
+ */
86
+ loading?: ComponentType;
87
+ /**
88
+ * §2.3 `_error.tsx` — the nearest-ancestor error boundary component the walker
89
+ * resolved for this route. `RouteRenderer` wraps the route in a
90
+ * {@link RouteErrorBoundary} that renders this component with `{ error, reset }`
91
+ * props on a render-time throw. Eagerly imported so it renders synchronously
92
+ * (a lazy error UI that itself suspends would surface as a confusing loading
93
+ * state while the error is in flight).
94
+ */
95
+ errorBoundary?: ComponentType<{
96
+ error: unknown;
97
+ reset: () => void;
98
+ }>;
99
+ /**
100
+ * §2.3 `_layout.tsx` — the full ancestor chain (outermost-first) wrapping the
101
+ * page. Each layout receives `children` (the next inner layout, or the
102
+ * page-with-Suspense at the bottom of the chain). Eagerly imported so layouts
103
+ * persist around a (suspending) page below — only the page area shows the
104
+ * route's loading fallback.
105
+ */
106
+ layouts?: ReadonlyArray<ComponentType<{
107
+ children?: ReactNode;
108
+ }>>;
109
+ bindingsName?: string;
110
+ }
111
+ /**
112
+ * What `useRoute()` returns. `route` is the matched config (or `null` if
113
+ * the current pathname doesn't match any route — the renderer shows nothing).
114
+ */
115
+ export interface RouteState {
116
+ pathname: string;
117
+ params: Record<string, string>;
118
+ /** Repeated keys (`?tag=x&tag=y`) arrive as arrays — backs `query foo: string[]`. */
119
+ query: Record<string, string | string[]>;
120
+ route: RouteConfig | null;
121
+ }
122
+ /**
123
+ * The transition shape handed to the optional replay route tap (Replay §5).
124
+ * Carries everything the receiver needs to fold a `Route#1` state_snapshot and
125
+ * reconstruct the navigable `path` (`pathname` + raw query string), without the
126
+ * receiver re-serializing the query.
127
+ */
128
+ export interface ReplayRouteState {
129
+ pathname: string;
130
+ /** Raw query string WITHOUT a leading `?` (the router's `currentQuery`). */
131
+ search: string;
132
+ params: Record<string, string>;
133
+ query: Record<string, string | string[]>;
134
+ }
135
+ /**
136
+ * Opt-in replay route tap (Replay §5). The router calls this — when installed —
137
+ * on every committed transition, so `@reactra/behaviours/replayable` can record a
138
+ * `Route#1` snapshot WITHOUT the router depending on the replay runtime. Mirrors
139
+ * the `__REACTRA_TEST__` neutral-global pattern; a no-op `?.` until the replay
140
+ * receiver installs it (`configureReplay`).
141
+ */
142
+ declare global {
143
+ var __REACTRA_REPLAY_ROUTE__: ((route: ReplayRouteState) => void) | undefined;
144
+ }
145
+ /**
146
+ * Match a pathname against a registered pattern. Returns the extracted
147
+ * params if it matches, `null` otherwise.
148
+ *
149
+ * Patterns use Express-style `:name` placeholders. Each segment is matched
150
+ * literally except for placeholders, which capture into `params[name]`.
151
+ * No catch-all (`*`) or optional segments in this MVP.
152
+ */
153
+ /**
154
+ * Match a pathname against a route pattern, returning the extracted params
155
+ * if it matches or `null` otherwise. Exposed for {@link useRouteMatch} and
156
+ * for app code that wants to test an arbitrary pattern against the current
157
+ * (or any) pathname.
158
+ */
159
+ export declare const matchRoute: (pattern: string, pathname: string) => Record<string, string> | null;
160
+ /**
161
+ * Build a concrete URL from a route PATTERN plus params + query — the
162
+ * runtime half of the typed-navigation surface (Router §5.3 / `#5c-typed`).
163
+ *
164
+ * The generated manifest's typed `navigate` (Router §5.1) delegates here:
165
+ * the compiler-derived `RouteId` / `RouteParams` types guarantee the
166
+ * caller passes the right param keys, and this function fills the
167
+ * `:name` placeholders with their (URL-encoded) values and appends a
168
+ * query string.
169
+ *
170
+ * buildPath("/", ) → "/"
171
+ * buildPath("/customers/:id", { id: "c 3" }) → "/customers/c%203"
172
+ * buildPath("/customers/:id", { id: "c3" }, { tab: "notes" })
173
+ * → "/customers/c3?tab=notes"
174
+ *
175
+ * Throws (RO002-shaped) when a `:name` segment has no matching param —
176
+ * surfaced as a runtime guard behind the compile-time type check, so a
177
+ * JS caller bypassing the types still gets a clear error rather than a
178
+ * literal `/customers/:id` URL.
179
+ */
180
+ export declare const buildPath: (pattern: string, params?: Record<string, string>, query?: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>>) => string;
181
+ /**
182
+ * How a `query` field should be coerced from its raw URL string, per the
183
+ * page's declared type (Router §3.1 coercion table). A `readonly string[]`
184
+ * is a string-literal-union (enum) whose members are the allowed raw values
185
+ * (e.g. `["active", "archived"]`). `"string"` is passthrough.
186
+ */
187
+ export type QueryCoercion = "string" | "number" | "boolean" | "string[]" | readonly string[];
188
+ /**
189
+ * Coerce one raw query-string value to its declared type at read time
190
+ * (Router §3.1 / §5.3). The compiler emits one of these per `query foo: T`
191
+ * read, threading the declared kind + the `= default` expression as
192
+ * `fallback`. Coercion is lenient — a malformed value (non-numeric for
193
+ * `number`, off-enum for an enum) yields the `fallback` rather than throwing,
194
+ * since the URL is an external, user-editable source (the spec's RO024/RO025
195
+ * are navigation-time checks; reads degrade gracefully). An absent value
196
+ * (`undefined`) always yields `fallback`.
197
+ *
198
+ * coerceQuery("42", "number") → 42
199
+ * coerceQuery("abc", "number", 1) → 1
200
+ * coerceQuery("true", "boolean") → true
201
+ * coerceQuery("x", ["a","b"], "a") → "a" (off-enum → fallback)
202
+ */
203
+ export declare const coerceQuery: (raw: string | readonly string[] | undefined, kind: QueryCoercion, fallback?: unknown) => unknown;
204
+ declare class RouterRegistryImpl {
205
+ private routes;
206
+ private listeners;
207
+ private currentPathname;
208
+ private currentQuery;
209
+ private currentSnapshot;
210
+ private previousRoute;
211
+ private routeBindings;
212
+ private currentEntered;
213
+ private middlewareChains;
214
+ /** Register a route. Idempotent on `id` — re-registering replaces. */
215
+ register: (route: RouteConfig) => void;
216
+ /**
217
+ * Day 27 / `#5c-followup`: replace the entire routes array (typically
218
+ * with the freshly-evaluated `newMod.ROUTES` from a HMR-accept
219
+ * callback on the generated route manifest). Re-runs `setLocation`
220
+ * for the current pathname so a removed-mid-session route flips to
221
+ * the null-route state, a renamed route picks up its new shape, and
222
+ * an added route is matchable on the very next render — all without
223
+ * a page reload.
224
+ *
225
+ * The route's `onExit` (if any) fires for the previously-matched
226
+ * route inside `setLocation`, mirroring a normal navigation. The
227
+ * `previousRoute` snapshot is then updated to whatever matches now
228
+ * under the new set; the next genuine `navigate()` will exit it
229
+ * cleanly.
230
+ */
231
+ replaceRoutes: (newRoutes: readonly RouteConfig[]) => void;
232
+ /**
233
+ * Register route lifecycle bindings under a stable name. Compiler-
234
+ * emitted in the same module that exports the page component (see
235
+ * Pass 9 codegen). Module-level call — runs once on first load AND
236
+ * again on every HMR re-evaluation, overwriting the prior entry.
237
+ * Safe to call before / after `register(route)`; lookup happens at
238
+ * route-enter time which is after both have been called.
239
+ */
240
+ registerRouteBindings: (name: string, bindings: RouteBindings) => void;
241
+ /** Read access for tests + setLocation lifecycle. */
242
+ lookupRouteBindings: (name: string) => RouteBindings | undefined;
243
+ /**
244
+ * Find a registered route by its path pattern (e.g. `/customers/:id`) — the
245
+ * value a `RouteLink`'s `to` carries. Used by {@link PrefetchRuntime} to reach
246
+ * a route's `preload` thunk. Returns the first match or `undefined`.
247
+ */
248
+ getRouteByPath: (path: string) => RouteConfig | undefined;
249
+ /**
250
+ * Drive the registry to a new pathname + query. Fires the previous
251
+ * route's `onExit` (if any), updates the snapshot, fires the new
252
+ * route's `onEnter` (if any), then notifies React subscribers.
253
+ */
254
+ setLocation: (pathname: string, query: string) => void;
255
+ /** Subscribe to route changes. useSyncExternalStore feeds React this. */
256
+ subscribe: (onChange: () => void) => (() => void);
257
+ /**
258
+ * Install the middleware `ctx.service` resolver (Middleware spec v2 §3).
259
+ * Called by `@reactra/service`'s `bindScopedServicesToRouter` through the
260
+ * structural `RouterLike` contract — never by app code. Internal tier
261
+ * (Runtime v1 §1); delegates to the middleware module's slot.
262
+ */
263
+ __setMiddlewareServiceResolver: (resolver: (name: string) => unknown) => void;
264
+ /** Current route snapshot — stable reference until the next transition. */
265
+ getSnapshot: () => RouteState;
266
+ /** Read-only access for tests + the browser-sync helper. */
267
+ getCurrentPathname: () => string;
268
+ getCurrentQuery: () => string;
269
+ /**
270
+ * Register the per-route middleware chains emitted by the walker (Wave 3,
271
+ * Stage 2). Accepts either a `Map` or a plain record (the
272
+ * `router-middleware.generated.ts` shape). Overwrites the prior set — the
273
+ * generated file's HMR re-registration goes through here.
274
+ */
275
+ setMiddlewareChains: (chains: ReadonlyMap<string, ReadonlyArray<MiddlewareDef>> | Readonly<Record<string, ReadonlyArray<MiddlewareDef>>>) => void;
276
+ /**
277
+ * Read the registered chain for a route path, or `undefined` if none.
278
+ * Returning `undefined` (rather than `[]`) lets the caller short-circuit
279
+ * the async wrapper for the empty-chain fast path.
280
+ */
281
+ getMiddlewareChain: (routePath: string) => ReadonlyArray<MiddlewareDef> | undefined;
282
+ /** All registered routes — used by `navigate` to match before running the chain. */
283
+ getRoutes: () => readonly RouteConfig[];
284
+ }
285
+ /**
286
+ * The process-wide router singleton. The compiler emits imports of this
287
+ * (via `useRoute`) and the codegen-emitted `routeBindings` reference it
288
+ * indirectly through the page component's lifecycle hooks.
289
+ */
290
+ export declare const RouterRegistry: RouterRegistryImpl;
291
+ declare class PrefetchRuntimeImpl {
292
+ private inflight;
293
+ private warmControllers;
294
+ /**
295
+ * Warm the destination route ahead of navigation. Fires the code-split
296
+ * chunk preload (if any) AND the compiler-emitted route-bindings warmer
297
+ * (Stage E) which pre-fills the resource cache via `warmResource`. No-op
298
+ * if the route is already warming this exact `(to, params, query)`. Both
299
+ * branches share the dedup key; both fire under a per-key AbortController
300
+ * that `cancel(...)` aborts on real navigation.
301
+ */
302
+ warm: (to: string, params?: Record<string, unknown>, query?: Record<string, unknown>) => void;
303
+ /**
304
+ * Clear the dedup slot for a specific destination (params+query given) or
305
+ * for every in-flight warmup of a route (params+query omitted — used on real
306
+ * navigation). Aborts the per-key controller so any in-flight data warmer
307
+ * stops fetching (chunk imports stay running — harmless and browser-cached).
308
+ */
309
+ cancel: (to: string, params?: Record<string, unknown>, query?: Record<string, unknown>) => void;
310
+ }
311
+ /** Process-wide prefetch singleton (Router §8.5). */
312
+ export declare const PrefetchRuntime: PrefetchRuntimeImpl;
313
+ export declare const __getRouterMode: () => "hash" | "history";
314
+ /**
315
+ * Bootstrap call — invoked from `src/main.tsx` per Runtime v0 §5. Registers
316
+ * every route the app cares about, then attaches to the browser's location
317
+ * (hash or History API depending on `mode`) if `window` is available. Order
318
+ * rules: `configureStores` must precede `configureRouter` (route stores
319
+ * depend on the registry).
320
+ *
321
+ * `mode`:
322
+ * - `"hash"` (default): URL fragment drives routing
323
+ * (`#/customers/1`). No server config needed; works on static hosts.
324
+ * - `"history"`: real paths (`/customers/1`) drive routing via the
325
+ * History API. The dev server / production host MUST serve the SPA
326
+ * fallback (index.html) for any non-asset path. Vite's default
327
+ * `appType: "spa"` does this automatically.
328
+ */
329
+ export declare const configureRouter: (config: {
330
+ routes: RouteConfig[];
331
+ mode?: "hash" | "history";
332
+ /** Suspense fallback while a code-split route chunk loads (§5.2). Default null. */
333
+ loadingFallback?: ReactNode;
334
+ /**
335
+ * Wave 3, Stage 3 — per-route middleware chains. Typically the
336
+ * `middlewareChains` export of the walker-generated
337
+ * `router-middleware.generated.ts`. Omitting it leaves no chains
338
+ * registered (the same effect as the empty-map case from a fresh-clone
339
+ * project with no `_middleware.ts` files).
340
+ */
341
+ middlewareChains?: ReadonlyMap<string, ReadonlyArray<MiddlewareDef>> | Readonly<Record<string, ReadonlyArray<MiddlewareDef>>>;
342
+ /**
343
+ * Wave 3, §2b — app-wide scroll restoration policy. Spec §3.5 + §13.1.
344
+ * `defaultMode` applies to every route until the per-page meta classifier
345
+ * lands (then `meta { scrollRestoration: "manual" }` overrides per page).
346
+ * - `auto` (recommended): scroll-to-top on push, restore saved on pop.
347
+ * - `top`: always scroll-to-top.
348
+ * - `manual`: the app controls scroll explicitly.
349
+ * `scrollKey` is the sessionStorage namespace (default `"reactra:scroll"`).
350
+ * Omitting the whole field leaves scroll management unmanaged (browser
351
+ * default — historical Phase-1 behaviour).
352
+ */
353
+ scrollRestoration?: {
354
+ defaultMode?: ScrollRestorationMode;
355
+ scrollKey?: string;
356
+ };
357
+ }) => void;
358
+ /**
359
+ * Navigate to a new path. Three flow shapes per Wave 3, Stage 3:
360
+ *
361
+ * - **No matched route OR empty middleware chain (fast path).** Commit
362
+ * synchronously — preserves the existing behaviour for every route
363
+ * that doesn't have a `_middleware.ts` ancestor.
364
+ *
365
+ * - **Non-empty middleware chain.** Fire-and-forget: build the
366
+ * {@link MiddlewareContext}, run `runBeforeEnterChain` async, then:
367
+ * - `redirect` command → recursive `navigate(cmd.path, cmd.replace)`.
368
+ * - `abort` command → return without commit (current route stays).
369
+ * - allow → commit, then run the PREVIOUS route's
370
+ * `afterLeave` chain in reverse.
371
+ * The function returns `void` synchronously; tests should await an
372
+ * `await Promise.resolve()` tick to see the post-chain state.
373
+ *
374
+ * Transport per mode:
375
+ * - `hash`: `window.location.hash = to` (fires `hashchange` →
376
+ * `RouterRegistry.setLocation`).
377
+ * - `history`: `history.pushState(...)` then drives `setLocation`
378
+ * directly (pushState does NOT fire `popstate`).
379
+ *
380
+ * In pure Node (tests), `setLocation` is called regardless so the registry
381
+ * is observable without a DOM.
382
+ *
383
+ * **Limitation — popstate / hashchange (browser back/forward).** Those
384
+ * paths skip the middleware chain in this stage: by the time the event
385
+ * fires, the URL has already changed in the browser, and fighting that
386
+ * with a back-push is fragile. A follow-up will harden this by replacing
387
+ * the offending history entry on abort and chaining to the redirect
388
+ * target. The same applies to the initial-mount `sync()`.
389
+ */
390
+ export declare const navigate: (to: string, replace?: boolean) => void;
391
+ /**
392
+ * React hook returning the active route state. Subscribes via
393
+ * `useSyncExternalStore` so a component re-renders when the route changes.
394
+ */
395
+ export declare const useRoute: () => RouteState;
396
+ /**
397
+ * Merge `updates` into a current query object, returning a fresh plain object.
398
+ * Values are coerced to strings (the query bag is string-valued); a key set to
399
+ * `undefined` is REMOVED. Pure — the testable core of {@link useQueryUpdater}.
400
+ */
401
+ export declare const mergeQuery: (current: Record<string, string | string[]>, updates: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean> | undefined>) => Record<string, string | string[]>;
402
+ /**
403
+ * React hook: does the current pathname match `pattern`? Returns the extracted
404
+ * params (`{}` for a param-less match) or `null` if it doesn't match. Useful
405
+ * for highlighting nav, conditional UI, or reading params for a pattern other
406
+ * than the active route. Subscribes to route changes via {@link useRoute}.
407
+ */
408
+ export declare const useRouteMatch: (pattern: string) => Record<string, string> | null;
409
+ /**
410
+ * React hook returning a query-updater bound to the CURRENT route: call it with
411
+ * a partial `{ key: value }` to merge into the existing query and navigate to
412
+ * the same pathname with the new query string (a key set to `undefined` is
413
+ * removed). The URL stays the source of truth (Router §3.4 — query fields are
414
+ * read-only state sourced from the URL; to change one you navigate).
415
+ *
416
+ * const setQuery = useQueryUpdater()
417
+ * setQuery({ page: 2 }) // /customers → /customers?page=2
418
+ * setQuery({ page: undefined }, { replace: true }) // drop `page`, replace entry
419
+ */
420
+ export declare const useQueryUpdater: () => ((updates: Record<string, string | number | boolean | undefined>, options?: {
421
+ replace?: boolean;
422
+ }) => void);
423
+ /**
424
+ * Render the currently active route's component. Returns null when no route
425
+ * matches — Phase 1 demos can wrap with their own 404 layer. The route is
426
+ * composed (innermost → outermost) as:
427
+ * page → Suspense(loading) → layouts(innermost→outermost) → ErrorBoundary
428
+ * Layouts sit OUTSIDE the Suspense so they persist while the page area shows
429
+ * the loading fallback; the error boundary is outermost so it catches throws
430
+ * from any layout, the Suspense fallback, or the page (Router §2.3).
431
+ */
432
+ export declare const RouteRenderer: () => ReactNode;
433
+ /**
434
+ * Shape of the bits of a click event `RouteLink` needs to decide whether to
435
+ * intercept. Kept structural (not React's `MouseEvent`) so the decision is a
436
+ * pure, DOM-free, unit-testable function.
437
+ */
438
+ export interface ClickLike {
439
+ button: number;
440
+ metaKey: boolean;
441
+ ctrlKey: boolean;
442
+ shiftKey: boolean;
443
+ altKey: boolean;
444
+ defaultPrevented: boolean;
445
+ }
446
+ /**
447
+ * Should a `RouteLink` click be intercepted for SPA navigation, or left to
448
+ * the browser? Standard SPA-link rules (Router §3.3): only a plain primary
449
+ * (left) click with no modifier keys, not already prevented, and not aimed
450
+ * at another browsing context (`target` other than `_self`). A modifier or
451
+ * middle click — "open in new tab" — falls through to native navigation.
452
+ */
453
+ export declare const shouldInterceptClick: (e: ClickLike, target?: string) => boolean;
454
+ /**
455
+ * Is `linkPath` the active route given the current pathname? Pure so it's
456
+ * unit-testable without the router. Exact match (or the root `/`, which would
457
+ * otherwise prefix-match everything) compares equality; otherwise a link is
458
+ * active for its own path and any descendant (`/customers` is active on
459
+ * `/customers/c1`).
460
+ */
461
+ export declare const isRouteActive: (currentPath: string, linkPath: string, exactMatch?: boolean) => boolean;
462
+ /**
463
+ * Props for the runtime {@link RouteLink}. `to` is a route pattern (e.g.
464
+ * `/customers/:id`); `params` fills its `:name` segments and `query` becomes
465
+ * the query string — both via {@link buildPath}. The generated manifest
466
+ * re-exports a typed wrapper whose `to`/`params`/`query` are checked against
467
+ * `RouteId`/`RouteParams`/`RouteQuery` (Router §3.3, `#5c-typed`).
468
+ */
469
+ export interface RouteLinkProps {
470
+ to: string;
471
+ params?: Record<string, string>;
472
+ query?: Record<string, string | number | boolean>;
473
+ replace?: boolean;
474
+ /** Class applied (in addition to `className`) when this link's route is active. */
475
+ activeClass?: string;
476
+ /** Active only on an exact pathname match (default: also active for descendants). */
477
+ exactMatch?: boolean;
478
+ className?: string;
479
+ style?: CSSProperties;
480
+ target?: string;
481
+ children?: ReactNode;
482
+ /**
483
+ * §5.5 — warm the destination route's code-split chunk ahead of click:
484
+ * `"hover"` (pointer-enter/focus), `"visible"` (IntersectionObserver),
485
+ * `"mount"` (on render), or `"none"`/omitted (no prefetch). Phase 1 warms the
486
+ * chunk only; data warming lands with Resource v1.
487
+ */
488
+ prefetch?: PrefetchTrigger;
489
+ }
490
+ /**
491
+ * A navigation anchor. Renders a real `<a href>` (so it is a normal,
492
+ * right-clickable, middle-clickable link with a visible URL) and intercepts
493
+ * plain left-clicks to drive SPA navigation via {@link navigate} instead of a
494
+ * full document load. Replaces the hand-rolled
495
+ * `<a onClick={e => { e.preventDefault(); navigate(...) }}>` pattern.
496
+ *
497
+ * `prefetch` (Router §5.5) warms the destination chunk ahead of click — on
498
+ * hover/focus, on viewport entry (IntersectionObserver), or on mount. Phase 1
499
+ * warms the code-split chunk only; resource/data warming lands with Resource v1.
500
+ */
501
+ export declare const RouteLink: (props: RouteLinkProps) => ReactNode;
502
+ export { Portal } from "./portal.ts";
503
+ export type { PortalProps } from "./portal.ts";
504
+ export { buildContextCommands, compose, defineMiddleware, isAbortCommand, isRedirectCommand, runAfterLeaveChain, runBeforeEnterChain, } from "./middleware.ts";
505
+ export type { MiddlewareContext, MiddlewareDef, MiddlewareFn, MiddlewareRouteCoord, NavigationCommand, RouteMetaResolved, } from "./middleware.ts";
506
+ export { createScrollManager, generateHistoryKey } from "./scrollManager.ts";
507
+ export type { NavigationKind, ScrollManager, ScrollManagerConfig, ScrollPosition, ScrollRestorationMode, } from "./scrollManager.ts";
508
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,mBAAmB,EAEnB,SAAS,EACV,MAAM,OAAO,CAAA;AAQd,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAQvE,OAAO,KAAK,EAAiB,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAE9E;;;;;;;;;;;;;GAaG;AACH,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAOzC,MAAM,EAAE,CACN,OAAO,EAAE,iBAAiB,EAC1B,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,KAC7B,IAAI,CAAA;IACT;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtE;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,mBAAmB,CAAC,aAAa,CAAC,CAAA;AAE9E;;;;;;GAMG;AACH;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAEpE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,aAAa,CAAA;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,CAAA;KAAE,CAAA;IACvC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAA;IACpE;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC,CAAA;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;IACxC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;CACzC;AAED;;;;;;GAMG;AACH,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,wBAAwB,EAAE,CAAC,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CAC9E;AAED;;;;;;;GAOG;AACH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,SAAS,MAAM,EACf,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAiC3B,CAAA;AAwCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,KAC3F,MA4BF,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,MAAM,EAAE,CAAA;AAE5F;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GACtB,KAAK,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,EAC3C,MAAM,aAAa,EACnB,WAAW,OAAO,KACjB,OAwBF,CAAA;AAED,cAAM,kBAAkB;IACtB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAKtB;IACD,OAAO,CAAC,aAAa,CAA2B;IAQhD,OAAO,CAAC,aAAa,CAAmC;IAQxD,OAAO,CAAC,cAAc,CAAQ;IAK9B,OAAO,CAAC,gBAAgB,CAAuD;IAE/E,sEAAsE;IACtE,QAAQ,GAAI,OAAO,WAAW,KAAG,IAAI,CAIpC;IAED;;;;;;;;;;;;;;OAcG;IACH,aAAa,GAAI,WAAW,SAAS,WAAW,EAAE,KAAG,IAAI,CAGxD;IAED;;;;;;;OAOG;IACH,qBAAqB,GAAI,MAAM,MAAM,EAAE,UAAU,aAAa,KAAG,IAAI,CAiBpE;IAED,qDAAqD;IACrD,mBAAmB,GAAI,MAAM,MAAM,KAAG,aAAa,GAAG,SAAS,CACjC;IAE9B;;;;OAIG;IACH,cAAc,GAAI,MAAM,MAAM,KAAG,WAAW,GAAG,SAAS,CACd;IAE1C;;;;OAIG;IACH,WAAW,GAAI,UAAU,MAAM,EAAE,OAAO,MAAM,KAAG,IAAI,CAoEpD;IAED,yEAAyE;IACzE,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAG/C;IAED;;;;;OAKG;IACH,8BAA8B,GAAI,UAAU,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAAG,IAAI,CAE3E;IAED,2EAA2E;IAC3E,WAAW,QAAO,UAAU,CAAwB;IAEpD,4DAA4D;IAC5D,kBAAkB,QAAO,MAAM,CAAwB;IACvD,eAAe,QAAO,MAAM,CAAqB;IAEjD;;;;;OAKG;IACH,mBAAmB,GACjB,QACI,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,GACjD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,KACzD,IAAI,CAON;IAED;;;;OAIG;IACH,kBAAkB,GAChB,WAAW,MAAM,KAChB,aAAa,CAAC,aAAa,CAAC,GAAG,SAAS,CACL;IAEtC,oFAAoF;IACpF,SAAS,QAAO,SAAS,WAAW,EAAE,CAAe;CACtD;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,oBAA2B,CAAA;AAuBtD,cAAM,mBAAmB;IACvB,OAAO,CAAC,QAAQ,CAAoB;IAGpC,OAAO,CAAC,eAAe,CAAqC;IAE5D;;;;;;;OAOG;IACH,IAAI,GACF,IAAI,MAAM,EACV,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,QAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,KAClC,IAAI,CAoCN;IAED;;;;;OAKG;IACH,MAAM,GACJ,IAAI,MAAM,EACV,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,IAAI,CAeN;CACF;AAED,qDAAqD;AACrD,eAAO,MAAM,eAAe,qBAA4B,CAAA;AAOxD,eAAO,MAAM,eAAe,QAAO,MAAM,GAAG,SAAuB,CAAA;AAkBnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ;IACtC,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,mFAAmF;IACnF,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B;;;;;;OAMG;IACH,gBAAgB,CAAC,EACb,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,GACjD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;IAC1D;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE;QAClB,WAAW,CAAC,EAAE,qBAAqB,CAAA;QACnC,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF,KAAG,IA+EH,CAAA;AAiGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,QAAQ,GAAI,IAAI,MAAM,EAAE,iBAAe,KAAG,IA4DtD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAO,UAO3B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAC1C,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC,KACxG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAGxE,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,QAAO,CAAC,CAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,EAC9D,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KAC5B,IAAI,CAQR,CAAA;AAmCD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAAO,SA8BhC,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,GAAG,SAAS,EAAE,SAAS,MAAM,KAAG,OAMpE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,aAAa,MAAM,EACnB,UAAU,MAAM,EAChB,oBAAkB,KACjB,OAKF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qFAAqF;IACrF,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,cAAc,KAAG,SAsEjD,CAAA;AAGD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAG9C,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,YAAY,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,qBAAqB,GACtB,MAAM,oBAAoB,CAAA"}