@vobs/router 0.3.0 → 1.0.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/dist/router.js DELETED
@@ -1,406 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { batch, signal } from '@vobs/reactivity';
6
- import { createInjectionScope, createRuntimeError, isVobsError, toErrorEvent, } from '@vobs/runtime-core';
7
- import { CurrentRouteKey, PendingRouteKey, RouteRecordsKey, } from './types.js';
8
- import { createRouteLoaderCache, defaultRedirectLimit, loadRouteComponent, readLoaderCacheKey, resolveRoute, resolveRouteGuard, resolveRouteInitialState, } from './resolve.js';
9
- import { createMountedRouteChain, errorLayoutsForFailure, hydratePageWithLayouts, mountPageWithLayouts, unwrapRouteMountFailure, } from './mount.js';
10
- import { createKeepAliveCache, disposeChainInstances, readKeepAliveKey } from './keep-alive.js';
11
- import { createHistoryBinding, createScrollRestoration, defaultRouterWindow, resolveHistoryWindow, } from './history.js';
12
- import { createRouteHref, createRouteLocation } from './location.js';
13
- import { matchRoute } from './match.js';
14
- export function createRouter(options) {
15
- const routes = [...options.routes];
16
- const current = signal(null);
17
- const pending = signal(null);
18
- let container;
19
- let environment;
20
- let currentPage;
21
- let navigationToken = 0;
22
- let mounted = false;
23
- let historyBinding;
24
- let loaderAbortController;
25
- let routerInjectionScope;
26
- const loaderCache = createRouteLoaderCache();
27
- // keep-alive cache is bounded by default (10) so long-running SPAs cannot accumulate page instances; overridable via keepAliveMax.
28
- const keepAliveCache = createKeepAliveCache(options.keepAliveMax ?? 10);
29
- let scrollRestoration;
30
- async function mount(nextContainer, nextEnvironment) {
31
- if (mounted) {
32
- throw createRuntimeError('VOR602', 'Router is already mounted');
33
- }
34
- container = nextContainer;
35
- routerInjectionScope = createInjectionScope(nextEnvironment.injections ?? null);
36
- routerInjectionScope.provide(CurrentRouteKey, current);
37
- routerInjectionScope.provide(PendingRouteKey, pending);
38
- routerInjectionScope.provide(RouteRecordsKey, routes);
39
- environment = {
40
- ...nextEnvironment,
41
- injections: routerInjectionScope,
42
- };
43
- mounted = true;
44
- historyBinding = createHistoryBinding(options.history);
45
- scrollRestoration =
46
- options.scrollRestoration === true
47
- ? createScrollRestoration(resolveHistoryWindow(options.history) ?? defaultRouterWindow(), options.scrollRestorationMaxEntries)
48
- : undefined;
49
- try {
50
- await commitNavigation(options.initialPath ?? historyBinding?.readPath() ?? '/', historyBinding === undefined ? 'none' : 'replace', 0, options.hydrate === true ? 'hydrate' : 'mount', undefined);
51
- historyBinding?.install({
52
- onLink(path) {
53
- void commitNavigation(path, 'push', 0, 'mount', undefined).catch(nextEnvironment.onError);
54
- },
55
- onPop(path, historyEntryKey) {
56
- void commitNavigation(path, 'none', 0, 'mount', historyEntryKey).catch(nextEnvironment.onError);
57
- },
58
- });
59
- }
60
- catch (error) {
61
- mounted = false;
62
- container = undefined;
63
- environment = undefined;
64
- routerInjectionScope?.dispose();
65
- routerInjectionScope = undefined;
66
- historyBinding?.destroy();
67
- historyBinding = undefined;
68
- scrollRestoration?.destroy();
69
- scrollRestoration = undefined;
70
- current.value = null;
71
- pending.value = null;
72
- throw error;
73
- }
74
- }
75
- async function navigate(path) {
76
- await commitNavigation(path, 'push', 0, 'mount', undefined);
77
- }
78
- async function prefetch(path, prefetchOptions = {}) {
79
- await prefetchRoute(path, prefetchOptions, 0);
80
- }
81
- async function prefetchRoute(path, prefetchOptions, redirects) {
82
- if (redirects > defaultRedirectLimit) {
83
- throw createRuntimeError('VOR604', `Redirect limit exceeded: ${path}`);
84
- }
85
- const signal = prefetchOptions.signal;
86
- if (signal?.aborted)
87
- return;
88
- const route = resolveRoute({ routes, path, notFound: options.notFound });
89
- if (prefetchOptions.data === true && route.guard !== undefined) {
90
- try {
91
- // Same guard semantics as navigation (resolveRouteGuard): guard false → VOR606.
92
- const guardRedirect = await Promise.resolve(resolveRouteGuard(route, {
93
- ...(signal === undefined ? {} : { signal }),
94
- ...(environment?.injections === undefined
95
- ? {}
96
- : { injections: environment.injections }),
97
- }));
98
- if (signal?.aborted)
99
- return;
100
- if (guardRedirect !== undefined) {
101
- await prefetchRoute(guardRedirect, prefetchOptions, redirects + 1);
102
- return;
103
- }
104
- }
105
- catch (error) {
106
- // Prefetch is optional: a guard rejection does not throw into the error boundary; stop prefetching and report cancelled (observable).
107
- if (isVobsError(error) && error.code === 'VOR606') {
108
- reportNavigation('cancelled', path);
109
- return;
110
- }
111
- throw error;
112
- }
113
- }
114
- for (const component of [
115
- route.component,
116
- ...(route.layouts ?? []),
117
- route.error ?? options.error,
118
- ]) {
119
- if (signal?.aborted)
120
- return;
121
- if (component === undefined)
122
- continue;
123
- await loadRouteComponent(component);
124
- }
125
- if (prefetchOptions.data !== true || signal?.aborted)
126
- return;
127
- await Promise.resolve(resolveRouteInitialState(route, {
128
- ...(signal === undefined ? {} : { signal }),
129
- loaderCache,
130
- ...(environment?.injections === undefined ? {} : { injections: environment.injections }),
131
- }));
132
- }
133
- function reportNavigation(status, path, error) {
134
- const observability = environment?.observability;
135
- if (observability === undefined)
136
- return;
137
- const event = {
138
- kind: 'navigation',
139
- timestamp: Date.now(),
140
- navigation: {
141
- status,
142
- path,
143
- ...(error === undefined ? {} : { error: toErrorEvent(error) }),
144
- },
145
- };
146
- observability.report(event);
147
- }
148
- async function commitNavigation(path, historyAction, redirects, renderMode, historyEntryKey) {
149
- if (!mounted || container === undefined || environment === undefined) {
150
- throw createRuntimeError('VOR602', 'Router is not mounted');
151
- }
152
- if (redirects > defaultRedirectLimit) {
153
- pending.value = null;
154
- throw createRuntimeError('VOR604', `Redirect limit exceeded: ${path}`);
155
- }
156
- reportNavigation('start', path);
157
- const token = ++navigationToken;
158
- loaderAbortController?.abort();
159
- const abortController = new AbortController();
160
- loaderAbortController = abortController;
161
- const match = matchRoute(routes, path);
162
- const historyPath = match?.href ?? createRouteHref(path);
163
- if (match?.redirect !== undefined) {
164
- await commitNavigation(match.redirect, historyAction, redirects + 1, renderMode, historyEntryKey);
165
- return;
166
- }
167
- const component = match?.component ?? options.notFound;
168
- if (component === undefined) {
169
- pending.value = null;
170
- throw createRuntimeError('VOR601', `Route not found: ${path}`);
171
- }
172
- const layouts = match?.layouts ?? [];
173
- const errorComponent = match?.error ?? options.error;
174
- const location = match?.location ?? createRouteLocation(path);
175
- pending.value = location;
176
- try {
177
- if (match?.guard !== undefined) {
178
- const guardRedirect = await Promise.resolve(resolveRouteGuard({
179
- status: match === undefined ? 'notFound' : 'matched',
180
- route: location,
181
- component,
182
- ...(layouts.length === 0 ? {} : { layouts }),
183
- ...(errorComponent === undefined ? {} : { error: errorComponent }),
184
- ...(match.meta === undefined ? {} : { meta: match.meta }),
185
- guard: match.guard,
186
- ...(match.initialState === undefined ? {} : { initialState: match.initialState }),
187
- ...(match.loader === undefined ? {} : { loader: match.loader }),
188
- ...(match.loaderCache === undefined ? {} : { loaderCache: match.loaderCache }),
189
- }, {
190
- signal: abortController.signal,
191
- ...(environment.injections === undefined
192
- ? {}
193
- : { injections: environment.injections }),
194
- }));
195
- if (token !== navigationToken || !mounted) {
196
- reportNavigation('cancelled', location.path);
197
- return;
198
- }
199
- if (guardRedirect !== undefined) {
200
- await commitNavigation(guardRedirect, historyAction, redirects + 1, renderMode, historyEntryKey);
201
- return;
202
- }
203
- }
204
- const initialState = await Promise.resolve(resolveRouteInitialState({
205
- status: match === undefined ? 'notFound' : 'matched',
206
- route: location,
207
- component,
208
- ...(layouts.length === 0 ? {} : { layouts }),
209
- ...(errorComponent === undefined ? {} : { error: errorComponent }),
210
- ...(match?.meta === undefined ? {} : { meta: match.meta }),
211
- ...(match?.guard === undefined ? {} : { guard: match.guard }),
212
- ...(match?.initialState === undefined ? {} : { initialState: match.initialState }),
213
- ...(match?.loader === undefined ? {} : { loader: match.loader }),
214
- ...(match?.loaderCache === undefined ? {} : { loaderCache: match.loaderCache }),
215
- }, {
216
- signal: abortController.signal,
217
- loaderCache,
218
- ...(environment.injections === undefined ? {} : { injections: environment.injections }),
219
- }));
220
- if (token !== navigationToken || !mounted) {
221
- reportNavigation('cancelled', location.path);
222
- return;
223
- }
224
- await mountPage(component, layouts, location, initialState, token, historyAction, renderMode, historyPath, historyEntryKey);
225
- reportNavigation('success', location.path);
226
- }
227
- catch (error) {
228
- if (token !== navigationToken || !mounted)
229
- return;
230
- reportNavigation('error', location.path, error);
231
- await mountErrorPage(error, errorComponent, layouts, location, token, historyAction, historyPath, historyEntryKey);
232
- }
233
- }
234
- async function mountPage(component, layouts, location, initialState, token, historyAction, renderMode, historyPath, historyEntryKey) {
235
- const page = await loadRouteComponent(component);
236
- if (token !== navigationToken || !mounted) {
237
- reportNavigation('cancelled', location.path);
238
- return;
239
- }
240
- scrollRestoration?.saveCurrent();
241
- const previous = currentPage;
242
- const previousRoute = current.value;
243
- const reusedLayouts = renderMode === 'mount' ? commonLayoutPrefix(previous, layouts) : 0;
244
- const keepAlive = location.meta?.keepAlive === true;
245
- const cachedLeaf = renderMode === 'mount' && keepAlive
246
- ? keepAliveCache.take(readKeepAliveKey(location))
247
- : undefined;
248
- if (reusedLayouts === 0) {
249
- await disposeChainInstances(previous?.instances ?? [], previousRoute, keepAliveCache);
250
- currentPage = undefined;
251
- current.value = null;
252
- }
253
- try {
254
- const pageEnvironment = {
255
- ...environment,
256
- route: location,
257
- initialState,
258
- };
259
- const nextPage = renderMode === 'hydrate'
260
- ? await hydratePageWithLayouts(page, layouts, container, pageEnvironment)
261
- : await mountPageWithLayouts(page, layouts.slice(reusedLayouts), reusedLayouts === 0 ? container : previous.outlets[reusedLayouts - 1], pageEnvironment, cachedLeaf);
262
- if (token !== navigationToken || !mounted) {
263
- nextPage.destroy();
264
- return;
265
- }
266
- currentPage =
267
- reusedLayouts === 0
268
- ? nextPage
269
- : createMountedRouteChain(layouts, [...previous.instances.slice(0, reusedLayouts), ...nextPage.instances], [...previous.outlets.slice(0, reusedLayouts), ...nextPage.outlets]);
270
- // Commit: current + pending update together in one batch, so observers
271
- // (menu highlighting / page-level pending state) receive a single notification.
272
- batch(() => {
273
- current.value = location;
274
- pending.value = null;
275
- });
276
- const committedHistoryEntryKey = historyBinding?.commit(historyPath, historyAction, historyEntryKey) ?? historyEntryKey;
277
- options.navigation?.onNavigate?.({
278
- route: location,
279
- historyAction,
280
- preservedLayouts: reusedLayouts,
281
- ...(previousRoute === null ? {} : { previousRoute }),
282
- ...(committedHistoryEntryKey === undefined
283
- ? {}
284
- : { historyEntryKey: committedHistoryEntryKey }),
285
- });
286
- scrollRestoration?.settle(historyAction, committedHistoryEntryKey);
287
- if (reusedLayouts > 0) {
288
- await disposeChainInstances(previous.instances.slice(reusedLayouts), previousRoute, keepAliveCache);
289
- }
290
- }
291
- catch (error) {
292
- if (token === navigationToken) {
293
- current.value = null;
294
- pending.value = null;
295
- }
296
- throw error;
297
- }
298
- }
299
- async function mountErrorPage(error, errorComponent, layouts, location, token, historyAction, historyPath, historyEntryKey) {
300
- if (errorComponent === undefined) {
301
- if (token === navigationToken) {
302
- currentPage?.destroy();
303
- currentPage = undefined;
304
- current.value = null;
305
- pending.value = null;
306
- }
307
- throw error;
308
- }
309
- if (token !== navigationToken || !mounted)
310
- return;
311
- await mountPage(errorComponent, errorLayoutsForFailure(layouts, error), location, {
312
- error: unwrapRouteMountFailure(error),
313
- route: location,
314
- }, token, historyAction, 'mount', historyPath, historyEntryKey);
315
- }
316
- function invalidateLoaderCache(path) {
317
- if (path === undefined) {
318
- loaderCache.clear();
319
- return;
320
- }
321
- const match = matchRoute(routes, path);
322
- if (match?.loaderCache === undefined)
323
- return;
324
- const cacheKey = readLoaderCacheKey(match.loaderCache, match.location, { loaderCache });
325
- if (cacheKey !== undefined)
326
- loaderCache.delete(cacheKey);
327
- }
328
- function invalidateKeepAlive(path) {
329
- if (path === undefined) {
330
- keepAliveCache.destroyAll();
331
- return;
332
- }
333
- keepAliveCache.remove(path);
334
- }
335
- function destroy() {
336
- navigationToken += 1;
337
- loaderAbortController?.abort();
338
- loaderAbortController = undefined;
339
- mounted = false;
340
- container = undefined;
341
- environment = undefined;
342
- const errors = [];
343
- try {
344
- historyBinding?.destroy();
345
- }
346
- catch (error) {
347
- errors.push(error);
348
- }
349
- historyBinding = undefined;
350
- const page = currentPage;
351
- currentPage = undefined;
352
- current.value = null;
353
- pending.value = null;
354
- try {
355
- page?.destroy();
356
- }
357
- catch (error) {
358
- errors.push(error);
359
- }
360
- try {
361
- keepAliveCache.destroyAll();
362
- }
363
- catch (error) {
364
- errors.push(error);
365
- }
366
- try {
367
- scrollRestoration?.destroy();
368
- }
369
- catch (error) {
370
- errors.push(error);
371
- }
372
- scrollRestoration = undefined;
373
- try {
374
- routerInjectionScope?.dispose();
375
- }
376
- catch (error) {
377
- errors.push(error);
378
- }
379
- routerInjectionScope = undefined;
380
- if (errors.length > 0) {
381
- throw new AggregateError(errors, 'Router destroy failed');
382
- }
383
- }
384
- return {
385
- current,
386
- pending,
387
- mount,
388
- navigate,
389
- prefetch,
390
- invalidateLoaderCache,
391
- invalidateKeepAlive,
392
- destroy,
393
- };
394
- }
395
- function commonLayoutPrefix(current, layouts) {
396
- if (current === undefined)
397
- return 0;
398
- let index = 0;
399
- while (index < layouts.length &&
400
- index < current.layouts.length &&
401
- index < current.outlets.length &&
402
- current.layouts[index] === layouts[index]) {
403
- index += 1;
404
- }
405
- return index;
406
- }
package/dist/types.d.ts DELETED
@@ -1,272 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type ReadonlySignal } from '@vobs/reactivity';
6
- import { type InjectionKey, type InjectionScope, type PAGE_INITIAL_STATE, type ComponentRegistry, type PageDefinition, type PageEnvironment, type RouteLocation, type RouteMeta, type RouteQuery, type RouteQueryValue } from '@vobs/runtime-core';
7
- export type { RouteLocation, RouteMeta, RouteQuery, RouteQueryValue };
8
- export declare const CurrentRouteKey: InjectionKey<ReadonlySignal<RouteLocation | null>>;
9
- export declare const PendingRouteKey: InjectionKey<ReadonlySignal<RouteLocation | null>>;
10
- export declare const RouteRecordsKey: InjectionKey<readonly RouteRecord<RouteComponent>[]>;
11
- export type RoutablePageDefinition = Omit<PageDefinition<object, ComponentRegistry, never>, typeof PAGE_INITIAL_STATE> & {
12
- readonly [PAGE_INITIAL_STATE]?: unknown;
13
- };
14
- export type RouteComponent = RoutablePageDefinition | (() => Promise<{
15
- readonly default: RoutablePageDefinition;
16
- }>);
17
- type PageInitialState<Page> = Page extends {
18
- readonly [PAGE_INITIAL_STATE]?: infer InitialState;
19
- } ? InitialState : unknown;
20
- export type RouteInitialState<Component extends RouteComponent> = Component extends () => Promise<{
21
- readonly default: infer Page;
22
- }> ? PageInitialState<Page> : PageInitialState<Component>;
23
- export interface RouteLoaderContext {
24
- readonly route: RouteLocation;
25
- readonly signal: AbortSignal;
26
- readonly request?: unknown;
27
- readonly injections: InjectionScope;
28
- maybeInject<T>(key: InjectionKey<T>): T | undefined;
29
- inject<T>(key: InjectionKey<T>): T;
30
- inject<T>(key: InjectionKey<T>, fallback: T | (() => T)): T;
31
- }
32
- export interface RouteGuardContext {
33
- readonly route: RouteLocation;
34
- readonly signal: AbortSignal;
35
- readonly request?: unknown;
36
- readonly injections: InjectionScope;
37
- maybeInject<T>(key: InjectionKey<T>): T | undefined;
38
- inject<T>(key: InjectionKey<T>): T;
39
- inject<T>(key: InjectionKey<T>, fallback: T | (() => T)): T;
40
- }
41
- export interface RouteErrorState {
42
- readonly error: unknown;
43
- readonly route: RouteLocation;
44
- }
45
- export type RouteLoader<Component extends RouteComponent = RouteComponent> = (context: RouteLoaderContext) => RouteInitialState<Component> | Promise<RouteInitialState<Component>>;
46
- export type RouteGuardResult = void | true | false | string | {
47
- readonly redirect: string;
48
- };
49
- export type RouteGuard = (context: RouteGuardContext) => RouteGuardResult | Promise<RouteGuardResult>;
50
- export type RouteLoaderCacheKey = string | ((route: RouteLocation) => string);
51
- export type RouteLoaderCachePolicy = boolean | {
52
- readonly key?: RouteLoaderCacheKey;
53
- };
54
- export interface RouteLoaderCache {
55
- has(key: string): boolean;
56
- get(key: string): unknown;
57
- set(key: string, value: unknown): void;
58
- delete(key: string): boolean;
59
- clear(): void;
60
- }
61
- export type RouteRecord<Component extends RouteComponent = RouteComponent> = PageRouteRecord<Component> | NestedRouteRecord<Component> | RedirectRouteRecord;
62
- type RouteRecordForValue<Route> = Route extends RouteRecord ? Route extends {
63
- readonly children: infer Children extends readonly RouteRecord[];
64
- } ? Omit<Route, 'children'> & {
65
- readonly children: {
66
- readonly [Key in keyof Children]: RouteRecordForValue<Children[Key]>;
67
- };
68
- } : Route extends {
69
- readonly component: infer Component extends RouteComponent;
70
- } ? Route & PageRouteRecord<Component> : Route : never;
71
- export type ValidatedRouteRecords<Routes extends readonly RouteRecord[]> = {
72
- readonly [Key in keyof Routes]: RouteRecordForValue<Routes[Key]>;
73
- };
74
- export type PageRouteRecord<Component extends RouteComponent = RouteComponent> = PageRouteRecordWithoutInitialState<Component> | PageRouteRecordWithInitialState<Component> | PageRouteRecordWithLoader<Component>;
75
- export interface PageRouteRecordWithoutInitialState<Component extends RouteComponent = RouteComponent> {
76
- readonly path: string;
77
- readonly component: Component;
78
- readonly layouts?: readonly RouteComponent[];
79
- readonly error?: RouteComponent;
80
- readonly meta?: RouteMeta;
81
- readonly guard?: RouteGuard;
82
- readonly initialState?: never;
83
- readonly loader?: never;
84
- readonly loaderCache?: never;
85
- readonly children?: never;
86
- readonly redirect?: never;
87
- }
88
- export interface PageRouteRecordWithInitialState<Component extends RouteComponent = RouteComponent> {
89
- readonly path: string;
90
- readonly component: Component;
91
- readonly layouts?: readonly RouteComponent[];
92
- readonly error?: RouteComponent;
93
- readonly meta?: RouteMeta;
94
- readonly guard?: RouteGuard;
95
- readonly initialState: RouteInitialState<Component>;
96
- readonly loader?: never;
97
- readonly loaderCache?: never;
98
- readonly children?: never;
99
- readonly redirect?: never;
100
- }
101
- export interface PageRouteRecordWithLoader<Component extends RouteComponent = RouteComponent> {
102
- readonly path: string;
103
- readonly component: Component;
104
- readonly layouts?: readonly RouteComponent[];
105
- readonly error?: RouteComponent;
106
- readonly meta?: RouteMeta;
107
- readonly guard?: RouteGuard;
108
- readonly loader: RouteLoader<Component>;
109
- readonly loaderCache?: RouteLoaderCachePolicy;
110
- readonly initialState?: never;
111
- readonly children?: never;
112
- readonly redirect?: never;
113
- }
114
- export interface NestedRouteRecord<Component extends RouteComponent = RouteComponent> {
115
- readonly path: string;
116
- readonly component?: Component;
117
- readonly children: readonly RouteRecord<Component>[];
118
- readonly layouts?: readonly RouteComponent[];
119
- readonly error?: RouteComponent;
120
- readonly meta?: RouteMeta;
121
- readonly guard?: RouteGuard;
122
- readonly initialState?: never;
123
- readonly loader?: never;
124
- readonly loaderCache?: never;
125
- readonly redirect?: never;
126
- }
127
- export interface RedirectRouteRecord {
128
- readonly path: string;
129
- readonly redirect: string;
130
- readonly component?: never;
131
- readonly initialState?: never;
132
- readonly loader?: never;
133
- readonly loaderCache?: never;
134
- readonly meta?: never;
135
- readonly guard?: never;
136
- readonly children?: never;
137
- }
138
- export interface RouteNavigationMeta {
139
- readonly id: string;
140
- readonly label?: string;
141
- readonly icon?: string;
142
- readonly area?: string;
143
- readonly order?: number;
144
- readonly state?: string;
145
- }
146
- export interface RouteNavigationItem {
147
- readonly id: string;
148
- readonly label: string;
149
- readonly href: string;
150
- readonly area: string;
151
- readonly order: number;
152
- readonly icon?: string;
153
- readonly state: string;
154
- readonly meta: RouteMeta;
155
- }
156
- export interface CollectRouteNavigationOptions {
157
- readonly area?: string;
158
- readonly defaultArea?: string;
159
- }
160
- export interface RouteNavigationScopeOptions extends CollectRouteNavigationOptions {
161
- readonly activeFallback?: string;
162
- readonly titleFallback?: string;
163
- readonly descriptionFallback?: string;
164
- readonly statusFallback?: string;
165
- }
166
- export interface RouteNavigationScope {
167
- readonly route: ReadonlySignal<RouteLocation>;
168
- readonly items: ReadonlySignal<readonly RouteNavigationItem[]>;
169
- readonly activeId: ReadonlySignal<string | undefined>;
170
- readonly title: ReadonlySignal<string>;
171
- readonly description: ReadonlySignal<string>;
172
- readonly status: ReadonlySignal<string>;
173
- }
174
- export interface RouterOptions<Routes extends readonly RouteRecord[] = readonly RouteRecord[]> {
175
- readonly routes: Routes;
176
- readonly notFound?: RouteComponent | undefined;
177
- readonly error?: RouteComponent | undefined;
178
- readonly initialPath?: string;
179
- readonly history?: boolean | RouterHistoryOptions;
180
- readonly hydrate?: boolean;
181
- readonly navigation?: RouterNavigationOptions;
182
- readonly scrollRestoration?: boolean;
183
- /** Maximum number of saved history-entry scroll positions. Defaults to 100. */
184
- readonly scrollRestorationMaxEntries?: number;
185
- readonly keepAliveMax?: number;
186
- }
187
- export interface RouterNavigationOptions {
188
- onNavigate?(context: RouterNavigationContext): void;
189
- }
190
- export interface RouterNavigationContext {
191
- readonly route: RouteLocation;
192
- readonly historyAction: 'none' | 'push' | 'replace';
193
- readonly preservedLayouts: number;
194
- readonly previousRoute?: RouteLocation;
195
- readonly historyEntryKey?: string;
196
- }
197
- export interface RouterHistoryOptions {
198
- readonly window?: RouterWindow;
199
- readonly interceptLinks?: boolean;
200
- }
201
- export interface RouterWindow {
202
- readonly location: {
203
- readonly href: string;
204
- readonly origin: string;
205
- readonly pathname: string;
206
- readonly search?: string;
207
- readonly hash?: string;
208
- };
209
- readonly history: {
210
- readonly state?: unknown;
211
- scrollRestoration?: 'auto' | 'manual';
212
- pushState(data: unknown, title: string, url?: string | URL | null): void;
213
- replaceState?(data: unknown, title: string, url?: string | URL | null): void;
214
- };
215
- readonly document?: RouterDocument;
216
- readonly scrollX?: number;
217
- readonly scrollY?: number;
218
- scrollTo?(x: number, y: number): void;
219
- addEventListener(type: 'popstate', listener: (event: RouterPopStateEvent) => void): void;
220
- removeEventListener(type: 'popstate', listener: (event: RouterPopStateEvent) => void): void;
221
- }
222
- export interface RouterDocument {
223
- addEventListener(type: 'click', listener: (event: Event) => void): void;
224
- removeEventListener(type: 'click', listener: (event: Event) => void): void;
225
- }
226
- export interface RouterPopStateEvent {
227
- readonly state?: unknown;
228
- }
229
- export interface Router {
230
- readonly current: ReadonlySignal<RouteLocation | null>;
231
- readonly pending: ReadonlySignal<RouteLocation | null>;
232
- mount(container: Element, environment: PageEnvironment): Promise<void>;
233
- navigate(path: string): Promise<void>;
234
- prefetch(path: string, options?: RouterPrefetchOptions): Promise<void>;
235
- invalidateLoaderCache(path?: string): void;
236
- invalidateKeepAlive(path?: string): void;
237
- destroy(): void;
238
- }
239
- export interface RouterPrefetchOptions {
240
- readonly data?: boolean;
241
- readonly signal?: AbortSignal;
242
- }
243
- export type ResolvedRouteStatus = 'matched' | 'notFound';
244
- export interface ResolvedRoute {
245
- readonly status: ResolvedRouteStatus;
246
- readonly route: RouteLocation;
247
- readonly component: RouteComponent;
248
- readonly layouts?: readonly RouteComponent[];
249
- readonly error?: RouteComponent;
250
- readonly meta?: RouteMeta;
251
- readonly guard?: RouteGuard;
252
- readonly initialState?: unknown;
253
- readonly loader?: RouteLoader | undefined;
254
- readonly loaderCache?: RouteLoaderCachePolicy | undefined;
255
- }
256
- export interface ResolveRouteInitialStateOptions {
257
- readonly signal?: AbortSignal;
258
- readonly request?: unknown;
259
- readonly loaderCache?: RouteLoaderCache;
260
- readonly injections?: InjectionScope;
261
- }
262
- export interface ResolveRouteGuardOptions {
263
- readonly signal?: AbortSignal;
264
- readonly request?: unknown;
265
- readonly injections?: InjectionScope;
266
- }
267
- export interface ResolveRouteOptions {
268
- readonly routes: readonly RouteRecord[];
269
- readonly path: string;
270
- readonly notFound?: RouteComponent | undefined;
271
- readonly redirectLimit?: number;
272
- }
package/dist/types.js DELETED
@@ -1,9 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import {} from '@vobs/reactivity';
6
- import { createInjectionKey, } from '@vobs/runtime-core';
7
- export const CurrentRouteKey = createInjectionKey('vobs.router.currentRoute');
8
- export const PendingRouteKey = createInjectionKey('vobs.router.pendingRoute');
9
- export const RouteRecordsKey = createInjectionKey('vobs.router.routeRecords');