@vobs/router 0.1.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/history.d.ts DELETED
@@ -1,25 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type RouterHistoryOptions, type RouterWindow } from './types.js';
6
- export interface HistoryBinding {
7
- readPath(): string;
8
- commit(path: string, action: 'none' | 'push' | 'replace', key: string | undefined): string | undefined;
9
- install(handlers: HistoryHandlers): void;
10
- destroy(): void;
11
- }
12
- interface HistoryHandlers {
13
- onLink(path: string): void;
14
- onPop(path: string, historyEntryKey: string | undefined): void;
15
- }
16
- export declare function createHistoryBinding(options: boolean | RouterHistoryOptions | undefined): HistoryBinding | undefined;
17
- export declare function defaultRouterWindow(): RouterWindow | undefined;
18
- export declare function resolveHistoryWindow(options: boolean | RouterHistoryOptions | undefined): RouterWindow | undefined;
19
- export interface ScrollRestoration {
20
- saveCurrent(): void;
21
- settle(action: 'none' | 'push' | 'replace', historyEntryKey: string | undefined): void;
22
- destroy(): void;
23
- }
24
- export declare function createScrollRestoration(window: RouterWindow | undefined): ScrollRestoration;
25
- export {};
package/dist/history.js DELETED
@@ -1,153 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import {} from './types.js';
6
- import { readHistoryEntryKey, readWindowPath } from './location.js';
7
- export function createHistoryBinding(options) {
8
- if (options === undefined || options === false)
9
- return undefined;
10
- const window = resolveHistoryWindow(options);
11
- if (window === undefined)
12
- return undefined;
13
- const interceptLinks = typeof options === 'object' ? (options.interceptLinks ?? true) : true;
14
- let cleanup;
15
- let nextHistoryEntryKey = 0;
16
- function createHistoryEntryKey() {
17
- nextHistoryEntryKey += 1;
18
- return `vobs:${nextHistoryEntryKey}`;
19
- }
20
- return {
21
- readPath() {
22
- return readWindowPath(window.location);
23
- },
24
- commit(path, action, key) {
25
- if (action === 'none')
26
- return key;
27
- const historyEntryKey = key ?? createHistoryEntryKey();
28
- const state = { __vobsHistoryEntryKey: historyEntryKey };
29
- if (action === 'replace') {
30
- window.history.replaceState?.(state, '', path);
31
- return historyEntryKey;
32
- }
33
- window.history.pushState(state, '', path);
34
- return historyEntryKey;
35
- },
36
- install(handlers) {
37
- const onPopState = (event) => {
38
- handlers.onPop(readWindowPath(window.location), readHistoryEntryKey(event.state ?? window.history.state));
39
- };
40
- window.addEventListener('popstate', onPopState);
41
- const onClick = (event) => {
42
- if (!interceptLinks || shouldIgnoreClick(event))
43
- return;
44
- const anchor = findAnchor(event.target);
45
- if (anchor === undefined || shouldIgnoreAnchor(anchor))
46
- return;
47
- let url;
48
- try {
49
- url = new URL(anchor.href, window.location.href);
50
- }
51
- catch {
52
- return;
53
- }
54
- if (url.origin !== window.location.origin)
55
- return;
56
- event.preventDefault();
57
- handlers.onLink(`${url.pathname}${url.search}${url.hash}`);
58
- };
59
- window.document?.addEventListener('click', onClick);
60
- cleanup = () => {
61
- window.removeEventListener('popstate', onPopState);
62
- window.document?.removeEventListener('click', onClick);
63
- };
64
- },
65
- destroy() {
66
- cleanup?.();
67
- cleanup = undefined;
68
- },
69
- };
70
- }
71
- export function defaultRouterWindow() {
72
- const candidate = globalThis;
73
- if (candidate.location === undefined ||
74
- candidate.history === undefined ||
75
- typeof candidate.addEventListener !== 'function' ||
76
- typeof candidate.removeEventListener !== 'function') {
77
- return undefined;
78
- }
79
- return candidate;
80
- }
81
- export function resolveHistoryWindow(options) {
82
- if (options === undefined || options === false)
83
- return undefined;
84
- return typeof options === 'object'
85
- ? (options.window ?? defaultRouterWindow())
86
- : defaultRouterWindow();
87
- }
88
- export function createScrollRestoration(window) {
89
- const positions = new Map();
90
- let currentKey;
91
- if (window?.history?.scrollRestoration !== undefined) {
92
- try {
93
- window.history.scrollRestoration = 'manual';
94
- }
95
- catch {
96
- // ignore read-only scrollRestoration (e.g. sandboxed iframes)
97
- }
98
- }
99
- const readPosition = () => ({
100
- x: window?.scrollX ?? 0,
101
- y: window?.scrollY ?? 0,
102
- });
103
- const scrollTo = (position) => {
104
- window?.scrollTo?.(position.x, position.y);
105
- };
106
- return {
107
- saveCurrent() {
108
- if (window === undefined || currentKey === undefined)
109
- return;
110
- positions.set(currentKey, readPosition());
111
- },
112
- settle(action, historyEntryKey) {
113
- if (window === undefined)
114
- return;
115
- const restored = action === 'none' && historyEntryKey !== undefined
116
- ? positions.get(historyEntryKey)
117
- : undefined;
118
- scrollTo(restored ?? { x: 0, y: 0 });
119
- currentKey = historyEntryKey;
120
- },
121
- destroy() {
122
- positions.clear();
123
- currentKey = undefined;
124
- },
125
- };
126
- }
127
- function shouldIgnoreClick(event) {
128
- const click = event;
129
- return (click.defaultPrevented ||
130
- click.button !== 0 ||
131
- click.metaKey ||
132
- click.ctrlKey ||
133
- click.shiftKey ||
134
- click.altKey);
135
- }
136
- function shouldIgnoreAnchor(anchor) {
137
- const target = anchor.getAttribute('target');
138
- return (anchor.hasAttribute('download') ||
139
- (target !== null && target !== '' && target.toLowerCase() !== '_self'));
140
- }
141
- function findAnchor(target) {
142
- if (target === null || typeof target !== 'object' || !('closest' in target))
143
- return undefined;
144
- const closest = target.closest('a[href]');
145
- if (closest === null || typeof closest !== 'object')
146
- return undefined;
147
- const anchor = closest;
148
- return typeof anchor.href === 'string' &&
149
- typeof anchor.getAttribute === 'function' &&
150
- typeof anchor.hasAttribute === 'function'
151
- ? anchor
152
- : undefined;
153
- }
package/dist/index.d.ts DELETED
@@ -1,15 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import type { RouterPort } from '@vobs/runtime-core';
6
- /**
7
- * Router plugin capability: declares that this plugin provides route navigation (RouterPort contract).
8
- * Forms the extension trio with RouterPortKey (port) and createRouter (adapter implementation).
9
- */
10
- export declare const RouterCapability: import("@vobs/runtime-core").Capability<RouterPort>;
11
- export { CurrentRouteKey, PendingRouteKey, RouteRecordsKey } from './types.js';
12
- export type { RouteLocation, RouteMeta, RouteQuery, RouteQueryValue, RouteComponent, RouteInitialState, RouteLoaderContext, RouteGuardContext, RouteErrorState, RouteLoader, RouteGuardResult, RouteGuard, RouteLoaderCacheKey, RouteLoaderCachePolicy, RouteLoaderCache, RouteRecord, PageRouteRecord, PageRouteRecordWithoutInitialState, PageRouteRecordWithInitialState, PageRouteRecordWithLoader, NestedRouteRecord, RedirectRouteRecord, RouteNavigationMeta, RouteNavigationItem, CollectRouteNavigationOptions, RouteNavigationScopeOptions, RouteNavigationScope, RouterOptions, RouterNavigationOptions, RouterNavigationContext, RouterHistoryOptions, RouterWindow, RouterDocument, RouterPopStateEvent, Router, RouterPrefetchOptions, ResolvedRouteStatus, ResolvedRoute, ResolveRouteInitialStateOptions, ResolveRouteGuardOptions, ResolveRouteOptions, } from './types.js';
13
- export { collectRouteNavigation, resolveActiveRouteNavigationId, createRouteNavigationScope, } from './navigation.js';
14
- export { resolveRoute, resolveRouteGuard, resolveRouteInitialState, createRouteLoaderCache, createRouteResolver, loadRouteComponent, } from './resolve.js';
15
- export { createRouter } from './router.js';
package/dist/index.js DELETED
@@ -1,14 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { createCapability } from '@vobs/runtime-core';
6
- /**
7
- * Router plugin capability: declares that this plugin provides route navigation (RouterPort contract).
8
- * Forms the extension trio with RouterPortKey (port) and createRouter (adapter implementation).
9
- */
10
- export const RouterCapability = createCapability('vobs.router');
11
- export { CurrentRouteKey, PendingRouteKey, RouteRecordsKey } from './types.js';
12
- export { collectRouteNavigation, resolveActiveRouteNavigationId, createRouteNavigationScope, } from './navigation.js';
13
- export { resolveRoute, resolveRouteGuard, resolveRouteInitialState, createRouteLoaderCache, createRouteResolver, loadRouteComponent, } from './resolve.js';
14
- export { createRouter } from './router.js';
@@ -1,20 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type PageInstance } from '@vobs/runtime-core';
6
- import { type RouteLocation } from './types.js';
7
- /**
8
- * keep-alive identity key: defaults to route.path; overridable via meta.keepAliveKey.
9
- * A string is a fixed key, while a function `(route) => string` can derive from query/params,
10
- * preventing /list?page=2 and /list?page=3 from sharing the same cached instance (state bleed).
11
- */
12
- export declare function readKeepAliveKey(route: RouteLocation): string;
13
- export interface KeepAliveCache {
14
- take(key: string): PageInstance | undefined;
15
- put(key: string, value: PageInstance): void;
16
- remove(key: string): void;
17
- destroyAll(): void;
18
- }
19
- export declare function createKeepAliveCache(max: number): KeepAliveCache;
20
- export declare function disposeChainInstances(instances: readonly PageInstance[], previousRoute: RouteLocation | null, cache: KeepAliveCache): Promise<void>;
@@ -1,75 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import {} from '@vobs/runtime-core';
6
- import {} from './types.js';
7
- import { destroyPageInstances } from './mount.js';
8
- /**
9
- * keep-alive identity key: defaults to route.path; overridable via meta.keepAliveKey.
10
- * A string is a fixed key, while a function `(route) => string` can derive from query/params,
11
- * preventing /list?page=2 and /list?page=3 from sharing the same cached instance (state bleed).
12
- */
13
- export function readKeepAliveKey(route) {
14
- const custom = route.meta?.keepAliveKey;
15
- if (typeof custom === 'function') {
16
- return custom(route);
17
- }
18
- if (typeof custom === 'string')
19
- return custom;
20
- return route.path;
21
- }
22
- export function createKeepAliveCache(max) {
23
- const entries = new Map();
24
- const evict = () => {
25
- while (entries.size > max) {
26
- const oldest = entries.keys().next().value;
27
- if (oldest === undefined)
28
- break;
29
- const victim = entries.get(oldest);
30
- entries.delete(oldest);
31
- victim?.destroy();
32
- }
33
- };
34
- return {
35
- take(key) {
36
- const value = entries.get(key);
37
- if (value !== undefined)
38
- entries.delete(key);
39
- return value;
40
- },
41
- put(key, value) {
42
- const previous = entries.get(key);
43
- if (previous !== undefined && previous !== value) {
44
- previous.destroy();
45
- }
46
- entries.set(key, value);
47
- evict();
48
- },
49
- remove(key) {
50
- const value = entries.get(key);
51
- if (value !== undefined) {
52
- entries.delete(key);
53
- value.destroy();
54
- }
55
- },
56
- destroyAll() {
57
- for (const value of entries.values())
58
- value.destroy();
59
- entries.clear();
60
- },
61
- };
62
- }
63
- export async function disposeChainInstances(instances, previousRoute, cache) {
64
- if (instances.length === 0)
65
- return;
66
- const leaf = instances[instances.length - 1];
67
- await leaf.leave();
68
- if (previousRoute?.meta?.keepAlive === true && leaf !== undefined) {
69
- leaf.deactivate();
70
- cache.put(readKeepAliveKey(previousRoute), leaf);
71
- destroyPageInstances(instances.slice(0, -1).reverse());
72
- return;
73
- }
74
- destroyPageInstances([...instances].reverse());
75
- }
@@ -1,20 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type RouteLocation, type RouteMeta, type RouterWindow } from './types.js';
6
- export interface ParsedRoutePath {
7
- readonly path: string;
8
- readonly href: string;
9
- readonly query?: RouteLocation['query'];
10
- readonly hash?: string;
11
- }
12
- export declare function decodePathSegment(segment: string): string;
13
- export declare function normalizePath(path: string): string;
14
- export declare function splitPath(path: string): readonly string[];
15
- export declare function parseRoutePath(path: string): ParsedRoutePath;
16
- export declare function createRouteLocationFromParsed(parsed: ParsedRoutePath, params: Readonly<Record<string, string>>, meta?: RouteMeta): RouteLocation;
17
- export declare function createRouteLocation(path: string, params?: Readonly<Record<string, string>>, meta?: RouteMeta): RouteLocation;
18
- export declare function createRouteHref(path: string): string;
19
- export declare function readWindowPath(location: RouterWindow['location']): string;
20
- export declare function readHistoryEntryKey(state: unknown): string | undefined;
package/dist/location.js DELETED
@@ -1,107 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import {} from './types.js';
6
- function splitRoutePath(path) {
7
- const trimmed = path.trim();
8
- const hashIndex = trimmed.indexOf('#');
9
- const beforeHash = hashIndex < 0 ? trimmed : trimmed.slice(0, hashIndex);
10
- const hash = hashIndex < 0 ? '' : trimmed.slice(hashIndex);
11
- const searchIndex = beforeHash.indexOf('?');
12
- return {
13
- pathname: searchIndex < 0 ? beforeHash : beforeHash.slice(0, searchIndex),
14
- search: searchIndex < 0 ? '' : beforeHash.slice(searchIndex),
15
- hash,
16
- };
17
- }
18
- function parseRouteQuery(search) {
19
- if (search.length <= 1)
20
- return undefined;
21
- const query = {};
22
- for (const [key, value] of new URLSearchParams(search.slice(1))) {
23
- const previous = query[key];
24
- if (previous === undefined) {
25
- query[key] = value;
26
- }
27
- else if (typeof previous === 'string') {
28
- query[key] = [previous, value];
29
- }
30
- else {
31
- query[key] = [...previous, value];
32
- }
33
- }
34
- return Object.keys(query).length === 0 ? undefined : query;
35
- }
36
- function parseRouteHash(hash) {
37
- if (hash.length <= 1)
38
- return undefined;
39
- const rawHash = hash.slice(1);
40
- try {
41
- return decodeURIComponent(rawHash);
42
- }
43
- catch {
44
- return rawHash;
45
- }
46
- }
47
- export function decodePathSegment(segment) {
48
- try {
49
- return decodeURIComponent(segment);
50
- }
51
- catch {
52
- return segment;
53
- }
54
- }
55
- export function normalizePath(path) {
56
- const pathname = splitRoutePath(path).pathname;
57
- if (pathname === '')
58
- return '/';
59
- const withLeadingSlash = pathname.startsWith('/') ? pathname : `/${pathname}`;
60
- if (withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/')) {
61
- return withLeadingSlash.slice(0, -1);
62
- }
63
- return withLeadingSlash;
64
- }
65
- export function splitPath(path) {
66
- const normalizedPath = normalizePath(path);
67
- if (normalizedPath === '/')
68
- return [];
69
- return normalizedPath.slice(1).split('/');
70
- }
71
- export function parseRoutePath(path) {
72
- const parts = splitRoutePath(path);
73
- const normalizedPath = normalizePath(parts.pathname);
74
- const query = parseRouteQuery(parts.search);
75
- const hash = parseRouteHash(parts.hash);
76
- return {
77
- path: normalizedPath,
78
- href: `${normalizedPath}${parts.search}${parts.hash}`,
79
- ...(query === undefined ? {} : { query }),
80
- ...(hash === undefined ? {} : { hash }),
81
- };
82
- }
83
- export function createRouteLocationFromParsed(parsed, params, meta) {
84
- return {
85
- path: parsed.path,
86
- params,
87
- ...(parsed.query === undefined ? {} : { query: parsed.query }),
88
- ...(parsed.hash === undefined ? {} : { hash: parsed.hash }),
89
- ...(meta === undefined ? {} : { meta }),
90
- };
91
- }
92
- export function createRouteLocation(path, params = {}, meta) {
93
- return createRouteLocationFromParsed(parseRoutePath(path), params, meta);
94
- }
95
- export function createRouteHref(path) {
96
- return parseRoutePath(path).href;
97
- }
98
- export function readWindowPath(location) {
99
- return `${location.pathname}${location.search ?? ''}${location.hash ?? ''}`;
100
- }
101
- export function readHistoryEntryKey(state) {
102
- if (typeof state !== 'object' || state === null || !('__vobsHistoryEntryKey' in state)) {
103
- return undefined;
104
- }
105
- const key = state.__vobsHistoryEntryKey;
106
- return typeof key === 'string' ? key : undefined;
107
- }
package/dist/match.d.ts DELETED
@@ -1,23 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type NestedRouteRecord, type RouteComponent, type RouteGuard, type RouteLoader, type RouteLoaderCachePolicy, type RouteLocation, type RouteMeta, type RouteRecord } from './types.js';
6
- export interface RouteMatch {
7
- readonly component?: RouteComponent | undefined;
8
- readonly layouts?: readonly RouteComponent[] | undefined;
9
- readonly error?: RouteComponent | undefined;
10
- readonly meta?: RouteMeta | undefined;
11
- readonly guard?: RouteGuard | undefined;
12
- readonly initialState?: unknown;
13
- readonly loader?: RouteLoader | undefined;
14
- readonly loaderCache?: RouteLoaderCachePolicy | undefined;
15
- readonly redirect?: string | undefined;
16
- readonly location: RouteLocation;
17
- readonly href: string;
18
- }
19
- export declare function isNestedRouteRecord(route: RouteRecord): route is NestedRouteRecord;
20
- export declare function joinRouteRecordPath(parentPath: string, routePath: string): string;
21
- export declare function mergeRouteMeta(parent: RouteMeta | undefined, child: RouteMeta | undefined): RouteMeta | undefined;
22
- export declare function matchRoute(routes: readonly RouteRecord[], path: string): RouteMatch | undefined;
23
- export declare function resolveRouteMatch(routes: readonly RouteRecord[], path: string, redirectLimit: number, redirects: number): RouteMatch | undefined;
package/dist/match.js DELETED
@@ -1,203 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { createRuntimeError } from '@vobs/runtime-core';
6
- import {} from './types.js';
7
- import { createRouteLocationFromParsed, decodePathSegment, normalizePath, parseRoutePath, splitPath, } from './location.js';
8
- export function isNestedRouteRecord(route) {
9
- return route.children !== undefined;
10
- }
11
- export function joinRouteRecordPath(parentPath, routePath) {
12
- if (parentPath === '' || routePath.startsWith('/'))
13
- return normalizePath(routePath);
14
- if (routePath === '')
15
- return normalizePath(parentPath);
16
- return normalizePath(`${parentPath}/${routePath}`);
17
- }
18
- export function mergeRouteMeta(parent, child) {
19
- if (parent === undefined)
20
- return child;
21
- if (child === undefined)
22
- return parent;
23
- return { ...parent, ...child };
24
- }
25
- function composeRouteGuards(guards) {
26
- if (guards.length === 0)
27
- return undefined;
28
- if (guards.length === 1)
29
- return guards[0];
30
- return async (context) => {
31
- for (const guard of guards) {
32
- const result = await guard(context);
33
- if (result === false || typeof result === 'string' || typeof result === 'object') {
34
- return result;
35
- }
36
- }
37
- return true;
38
- };
39
- }
40
- function matchRouteSegments(routeSegments, pathSegments) {
41
- const params = {};
42
- for (let index = 0; index < routeSegments.length; index += 1) {
43
- const routeSegment = routeSegments[index];
44
- if (routeSegment === undefined)
45
- return undefined;
46
- if (routeSegment.startsWith('*')) {
47
- const name = routeSegment.slice(1);
48
- if (name === '' || index !== routeSegments.length - 1)
49
- return undefined;
50
- if (pathSegments.length <= index)
51
- return undefined;
52
- params[name] = pathSegments.slice(index).map(decodePathSegment).join('/');
53
- return params;
54
- }
55
- const pathSegment = pathSegments[index];
56
- if (pathSegment === undefined)
57
- return undefined;
58
- if (routeSegment.startsWith(':')) {
59
- const name = routeSegment.slice(1);
60
- if (name === '')
61
- return undefined;
62
- params[name] = decodePathSegment(pathSegment);
63
- continue;
64
- }
65
- if (routeSegment !== pathSegment)
66
- return undefined;
67
- }
68
- return pathSegments.length === routeSegments.length ? params : undefined;
69
- }
70
- function createPageRouteMatch(route, component, parsed, params, inherited) {
71
- const guard = composeRouteGuards(inherited.guards);
72
- return {
73
- component,
74
- ...(inherited.layouts.length === 0 ? {} : { layouts: inherited.layouts }),
75
- ...(inherited.error === undefined ? {} : { error: inherited.error }),
76
- ...(inherited.meta === undefined ? {} : { meta: inherited.meta }),
77
- ...(guard === undefined ? {} : { guard }),
78
- ...(isNestedRouteRecord(route)
79
- ? {}
80
- : {
81
- initialState: route.initialState,
82
- loader: route.loader,
83
- loaderCache: route.loaderCache,
84
- }),
85
- location: createRouteLocationFromParsed(parsed, params, inherited.meta),
86
- href: parsed.href,
87
- };
88
- }
89
- function matchRouteRecords(routes, parsed, pathSegments, parentPath, inherited) {
90
- const parentDepth = splitPath(parentPath).length;
91
- const candidates = parentDepth === 0 ? readMatchCandidates(routes, pathSegments) : routes;
92
- for (const route of candidates) {
93
- const routePath = joinRouteRecordPath(parentPath, route.path);
94
- const routeSegments = splitPath(routePath);
95
- const leading = routeSegments[parentDepth];
96
- if (leading !== undefined && !leading.startsWith(':') && !leading.startsWith('*')) {
97
- if (pathSegments[parentDepth] !== leading)
98
- continue;
99
- }
100
- if (isNestedRouteRecord(route)) {
101
- const layouts = [...inherited.layouts, ...(route.layouts ?? [])];
102
- const error = route.error ?? inherited.error;
103
- const meta = mergeRouteMeta(inherited.meta, route.meta);
104
- const guards = [...inherited.guards, ...(route.guard === undefined ? [] : [route.guard])];
105
- const childMatch = matchRouteRecords(route.children, parsed, pathSegments, routePath, {
106
- layouts: route.component === undefined ? layouts : [...layouts, route.component],
107
- ...(error === undefined ? {} : { error }),
108
- ...(meta === undefined ? {} : { meta }),
109
- guards,
110
- });
111
- if (childMatch !== undefined)
112
- return childMatch;
113
- const params = matchRouteSegments(routeSegments, pathSegments);
114
- if (params === undefined || route.component === undefined)
115
- continue;
116
- return createPageRouteMatch(route, route.component, parsed, params, {
117
- layouts,
118
- ...(error === undefined ? {} : { error }),
119
- ...(meta === undefined ? {} : { meta }),
120
- guards,
121
- });
122
- }
123
- const params = matchRouteSegments(routeSegments, pathSegments);
124
- if (params === undefined)
125
- continue;
126
- if ('redirect' in route) {
127
- return {
128
- redirect: route.redirect,
129
- location: createRouteLocationFromParsed(parsed, params, inherited.meta),
130
- href: parsed.href,
131
- };
132
- }
133
- const error = route.error ?? inherited.error;
134
- const meta = mergeRouteMeta(inherited.meta, route.meta);
135
- return createPageRouteMatch(route, route.component, parsed, params, {
136
- layouts: [...inherited.layouts, ...(route.layouts ?? [])],
137
- ...(error === undefined ? {} : { error }),
138
- ...(meta === undefined ? {} : { meta }),
139
- guards: [...inherited.guards, ...(route.guard === undefined ? [] : [route.guard])],
140
- });
141
- }
142
- return undefined;
143
- }
144
- const matchIndexCache = new WeakMap();
145
- function buildRouteMatchIndex(routes) {
146
- const staticSegments = new Map();
147
- const dynamicRoutes = [];
148
- for (const route of routes) {
149
- const leading = splitPath(normalizePath(route.path))[0];
150
- if (leading !== undefined && !leading.startsWith(':') && !leading.startsWith('*')) {
151
- const bucket = staticSegments.get(leading);
152
- if (bucket === undefined) {
153
- staticSegments.set(leading, [route]);
154
- }
155
- else {
156
- bucket.push(route);
157
- }
158
- }
159
- else {
160
- dynamicRoutes.push(route);
161
- }
162
- }
163
- return { staticSegments, dynamicRoutes };
164
- }
165
- function readRouteMatchIndex(routes) {
166
- let index = matchIndexCache.get(routes);
167
- if (index === undefined) {
168
- index = buildRouteMatchIndex(routes);
169
- matchIndexCache.set(routes, index);
170
- }
171
- return index;
172
- }
173
- /**
174
- * Top-level match candidates: static-bucket hits take priority (more specific),
175
- * while dynamic-segment routes (:param/*) serve as fallback since they match any leading segment.
176
- * Reduces an O(n) scan to O(bucket) + O(dynamic routes).
177
- */
178
- function readMatchCandidates(routes, pathSegments) {
179
- const index = readRouteMatchIndex(routes);
180
- const leading = pathSegments[0];
181
- if (leading === undefined)
182
- return index.dynamicRoutes;
183
- const staticBucket = index.staticSegments.get(leading);
184
- if (staticBucket === undefined)
185
- return index.dynamicRoutes;
186
- return [...staticBucket, ...index.dynamicRoutes];
187
- }
188
- export function matchRoute(routes, path) {
189
- const parsed = parseRoutePath(path);
190
- return matchRouteRecords(routes, parsed, splitPath(parsed.path), '', {
191
- layouts: [],
192
- guards: [],
193
- });
194
- }
195
- export function resolveRouteMatch(routes, path, redirectLimit, redirects) {
196
- if (redirects > redirectLimit) {
197
- throw createRuntimeError('VOR604', `Redirect limit exceeded: ${path}`);
198
- }
199
- const match = matchRoute(routes, path);
200
- if (match?.redirect === undefined)
201
- return match;
202
- return resolveRouteMatch(routes, match.redirect, redirectLimit, redirects + 1);
203
- }
package/dist/mount.d.ts DELETED
@@ -1,18 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/router
4
- */
5
- import { type PageDefinition, type PageEnvironment, type PageInstance } from '@vobs/runtime-core';
6
- import { type RouteComponent } from './types.js';
7
- export interface MountedRouteChain {
8
- readonly layouts: readonly RouteComponent[];
9
- readonly instances: readonly PageInstance[];
10
- readonly outlets: readonly Element[];
11
- destroy(): void;
12
- }
13
- export declare function mountPageWithLayouts(page: PageDefinition, layouts: readonly RouteComponent[], container: Element, environment: PageEnvironment, cachedLeaf?: PageInstance): Promise<MountedRouteChain>;
14
- export declare function hydratePageWithLayouts(page: PageDefinition, layouts: readonly RouteComponent[], container: Element, environment: PageEnvironment): Promise<MountedRouteChain>;
15
- export declare function createMountedRouteChain(layouts: readonly RouteComponent[], instances: readonly PageInstance[], outlets: readonly Element[]): MountedRouteChain;
16
- export declare function errorLayoutsForFailure(layouts: readonly RouteComponent[], error: unknown): readonly RouteComponent[];
17
- export declare function unwrapRouteMountFailure(error: unknown): unknown;
18
- export declare function destroyPageInstances(instances: readonly PageInstance[]): void;