@ivogt/rsc-router 0.0.0-experimental.1
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/README.md +19 -0
- package/package.json +131 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +891 -0
- package/src/browser/navigation-client.ts +155 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +545 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +228 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +149 -0
- package/src/browser/rsc-router.tsx +310 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +443 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +361 -0
- package/src/cache/cf/cf-cache-store.ts +274 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/index.ts +52 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +366 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +609 -0
- package/src/components/DefaultDocument.tsx +20 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +178 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href.ts +139 -0
- package/src/index.rsc.ts +69 -0
- package/src/index.ts +84 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1333 -0
- package/src/route-map-builder.ts +140 -0
- package/src/route-types.ts +148 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +60 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +116 -0
- package/src/router/match-context.ts +261 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +261 -0
- package/src/router/match-middleware/cache-store.ts +250 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +212 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +271 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3484 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +942 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +225 -0
- package/src/segment-system.tsx +405 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +340 -0
- package/src/server/handle-store.ts +230 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +470 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +126 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +215 -0
- package/src/types.ts +1473 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +608 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe route map builder
|
|
3
|
+
*
|
|
4
|
+
* Provides a fluent API for building route maps with prefixes.
|
|
5
|
+
* Can be imported in client code without pulling in server dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createRouteMap, registerRouteMap } from "rsc-router/browser";
|
|
10
|
+
*
|
|
11
|
+
* const routeMap = createRouteMap()
|
|
12
|
+
* .add(homeRoutes)
|
|
13
|
+
* .add(blogRoutes, "blog")
|
|
14
|
+
* .add(shopRoutes, "shop");
|
|
15
|
+
*
|
|
16
|
+
* registerRouteMap(routeMap.routes);
|
|
17
|
+
*
|
|
18
|
+
* declare global {
|
|
19
|
+
* namespace RSCRouter {
|
|
20
|
+
* interface RegisteredRoutes extends typeof routeMap.routes {}
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { PrefixedRoutes } from "./href.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Route map builder interface
|
|
30
|
+
*
|
|
31
|
+
* Accumulates route types through the builder chain for type-safe href.
|
|
32
|
+
*/
|
|
33
|
+
export interface RouteMapBuilder<TRoutes extends Record<string, string> = {}> {
|
|
34
|
+
/**
|
|
35
|
+
* Add routes without prefix
|
|
36
|
+
*/
|
|
37
|
+
add<T extends Record<string, string>>(routes: T): RouteMapBuilder<TRoutes & T>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Add routes with prefix
|
|
41
|
+
*/
|
|
42
|
+
add<T extends Record<string, string>, P extends string>(
|
|
43
|
+
routes: T,
|
|
44
|
+
prefix: P
|
|
45
|
+
): RouteMapBuilder<TRoutes & PrefixedRoutes<T, P>>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The accumulated route map (for typeof extraction in module augmentation)
|
|
49
|
+
*/
|
|
50
|
+
readonly routes: TRoutes;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add routes to a map with optional prefix
|
|
55
|
+
*
|
|
56
|
+
* @param routeMap - The map to add routes to
|
|
57
|
+
* @param routes - Routes to add
|
|
58
|
+
* @param prefix - Optional prefix for keys and paths
|
|
59
|
+
*/
|
|
60
|
+
function addRoutes(
|
|
61
|
+
routeMap: Record<string, string>,
|
|
62
|
+
routes: Record<string, string>,
|
|
63
|
+
prefix: string = ""
|
|
64
|
+
): void {
|
|
65
|
+
for (const [key, pattern] of Object.entries(routes)) {
|
|
66
|
+
const prefixedKey = prefix ? `${prefix}.${key}` : key;
|
|
67
|
+
const prefixedPattern =
|
|
68
|
+
prefix && pattern !== "/"
|
|
69
|
+
? `/${prefix}${pattern}`
|
|
70
|
+
: prefix && pattern === "/"
|
|
71
|
+
? `/${prefix}`
|
|
72
|
+
: pattern;
|
|
73
|
+
routeMap[prefixedKey] = prefixedPattern;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a new route map builder
|
|
79
|
+
*
|
|
80
|
+
* @returns A builder for accumulating routes with type-safe prefixes
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const routeMap = createRouteMap()
|
|
85
|
+
* .add(homeRoutes)
|
|
86
|
+
* .add(blogRoutes, "blog");
|
|
87
|
+
*
|
|
88
|
+
* // Types are accumulated through the chain
|
|
89
|
+
* type AppRoutes = typeof routeMap.routes;
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function createRouteMap(): RouteMapBuilder<{}> {
|
|
93
|
+
const routeMap: Record<string, string> = {};
|
|
94
|
+
|
|
95
|
+
const builder: RouteMapBuilder<any> = {
|
|
96
|
+
add(routes: Record<string, string>, prefix?: string) {
|
|
97
|
+
addRoutes(routeMap, routes, prefix);
|
|
98
|
+
return builder;
|
|
99
|
+
},
|
|
100
|
+
get routes() {
|
|
101
|
+
return routeMap;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return builder;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Singleton route map instance - populated when routes.ts is imported
|
|
109
|
+
let globalRouteMap: Record<string, string> = {};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Register the route map globally for href to use at runtime
|
|
113
|
+
*
|
|
114
|
+
* Call this after building your route map to make it available to href.
|
|
115
|
+
*
|
|
116
|
+
* @param map - The route map to register
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const routeMap = createRouteMap()
|
|
121
|
+
* .add(homeRoutes)
|
|
122
|
+
* .add(blogRoutes, "blog");
|
|
123
|
+
*
|
|
124
|
+
* registerRouteMap(routeMap.routes);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function registerRouteMap(map: Record<string, string>): void {
|
|
128
|
+
globalRouteMap = map;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the globally registered route map
|
|
133
|
+
*
|
|
134
|
+
* Used internally by href to resolve route names to URLs at runtime.
|
|
135
|
+
*
|
|
136
|
+
* @returns The registered route map
|
|
137
|
+
*/
|
|
138
|
+
export function getGlobalRouteMap(): Record<string, string> {
|
|
139
|
+
return globalRouteMap;
|
|
140
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for route system items
|
|
3
|
+
* These are extracted separately to avoid circular dependencies
|
|
4
|
+
* and to prevent bundling server-only code in client bundles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Branded return types for route helpers
|
|
9
|
+
*/
|
|
10
|
+
export declare const LayoutBrand: unique symbol;
|
|
11
|
+
export declare const RouteBrand: unique symbol;
|
|
12
|
+
export declare const ParallelBrand: unique symbol;
|
|
13
|
+
export declare const InterceptBrand: unique symbol;
|
|
14
|
+
export declare const MiddlewareBrand: unique symbol;
|
|
15
|
+
export declare const RevalidateBrand: unique symbol;
|
|
16
|
+
export declare const LoaderBrand: unique symbol;
|
|
17
|
+
export declare const LoadingBrand: unique symbol;
|
|
18
|
+
export declare const ErrorBoundaryBrand: unique symbol;
|
|
19
|
+
export declare const NotFoundBoundaryBrand: unique symbol;
|
|
20
|
+
export declare const WhenBrand: unique symbol;
|
|
21
|
+
export declare const CacheBrand: unique symbol;
|
|
22
|
+
|
|
23
|
+
export type LayoutItem = {
|
|
24
|
+
name: string;
|
|
25
|
+
type: "layout";
|
|
26
|
+
uses?: AllUseItems[];
|
|
27
|
+
[LayoutBrand]: void;
|
|
28
|
+
};
|
|
29
|
+
export type RouteItem = {
|
|
30
|
+
name: string;
|
|
31
|
+
type: "route";
|
|
32
|
+
uses?: AllUseItems[];
|
|
33
|
+
[RouteBrand]: void;
|
|
34
|
+
};
|
|
35
|
+
export type ParallelItem = {
|
|
36
|
+
name: string;
|
|
37
|
+
type: "parallel";
|
|
38
|
+
uses?: ParallelUseItem[];
|
|
39
|
+
[ParallelBrand]: void;
|
|
40
|
+
};
|
|
41
|
+
export type InterceptItem = {
|
|
42
|
+
name: string;
|
|
43
|
+
type: "intercept";
|
|
44
|
+
uses?: InterceptUseItem[];
|
|
45
|
+
[InterceptBrand]: void;
|
|
46
|
+
};
|
|
47
|
+
export type LoaderItem = {
|
|
48
|
+
name: string;
|
|
49
|
+
type: "loader";
|
|
50
|
+
uses?: LoaderUseItem[];
|
|
51
|
+
[LoaderBrand]: void;
|
|
52
|
+
};
|
|
53
|
+
export type MiddlewareItem = {
|
|
54
|
+
name: string;
|
|
55
|
+
type: "middleware";
|
|
56
|
+
uses?: AllUseItems[];
|
|
57
|
+
[MiddlewareBrand]: void;
|
|
58
|
+
};
|
|
59
|
+
export type RevalidateItem = {
|
|
60
|
+
name: string;
|
|
61
|
+
type: "revalidate";
|
|
62
|
+
uses?: AllUseItems[];
|
|
63
|
+
[RevalidateBrand]: void;
|
|
64
|
+
};
|
|
65
|
+
export type LoadingItem = {
|
|
66
|
+
name: string;
|
|
67
|
+
type: "loading";
|
|
68
|
+
[LoadingBrand]: void;
|
|
69
|
+
};
|
|
70
|
+
export type ErrorBoundaryItem = {
|
|
71
|
+
name: string;
|
|
72
|
+
type: "errorBoundary";
|
|
73
|
+
uses?: AllUseItems[];
|
|
74
|
+
[ErrorBoundaryBrand]: void;
|
|
75
|
+
};
|
|
76
|
+
export type NotFoundBoundaryItem = {
|
|
77
|
+
name: string;
|
|
78
|
+
type: "notFoundBoundary";
|
|
79
|
+
uses?: AllUseItems[];
|
|
80
|
+
[NotFoundBoundaryBrand]: void;
|
|
81
|
+
};
|
|
82
|
+
export type WhenItem = {
|
|
83
|
+
name: string;
|
|
84
|
+
type: "when";
|
|
85
|
+
[WhenBrand]: void;
|
|
86
|
+
};
|
|
87
|
+
export type CacheItem = {
|
|
88
|
+
name: string;
|
|
89
|
+
type: "cache";
|
|
90
|
+
uses?: AllUseItems[];
|
|
91
|
+
[CacheBrand]: void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Union types for use() callbacks
|
|
96
|
+
*/
|
|
97
|
+
export type AllUseItems =
|
|
98
|
+
| LayoutItem
|
|
99
|
+
| RouteItem
|
|
100
|
+
| MiddlewareItem
|
|
101
|
+
| RevalidateItem
|
|
102
|
+
| ParallelItem
|
|
103
|
+
| InterceptItem
|
|
104
|
+
| LoaderItem
|
|
105
|
+
| LoadingItem
|
|
106
|
+
| ErrorBoundaryItem
|
|
107
|
+
| NotFoundBoundaryItem
|
|
108
|
+
| CacheItem;
|
|
109
|
+
export type LayoutUseItem =
|
|
110
|
+
| LayoutItem
|
|
111
|
+
| RouteItem
|
|
112
|
+
| MiddlewareItem
|
|
113
|
+
| RevalidateItem
|
|
114
|
+
| ParallelItem
|
|
115
|
+
| InterceptItem
|
|
116
|
+
| LoaderItem
|
|
117
|
+
| LoadingItem
|
|
118
|
+
| ErrorBoundaryItem
|
|
119
|
+
| NotFoundBoundaryItem
|
|
120
|
+
| CacheItem;
|
|
121
|
+
export type RouteUseItem =
|
|
122
|
+
| LayoutItem
|
|
123
|
+
| ParallelItem
|
|
124
|
+
| InterceptItem
|
|
125
|
+
| MiddlewareItem
|
|
126
|
+
| RevalidateItem
|
|
127
|
+
| LoaderItem
|
|
128
|
+
| LoadingItem
|
|
129
|
+
| ErrorBoundaryItem
|
|
130
|
+
| NotFoundBoundaryItem
|
|
131
|
+
| CacheItem;
|
|
132
|
+
export type ParallelUseItem =
|
|
133
|
+
| RevalidateItem
|
|
134
|
+
| LoaderItem
|
|
135
|
+
| LoadingItem
|
|
136
|
+
| ErrorBoundaryItem
|
|
137
|
+
| NotFoundBoundaryItem;
|
|
138
|
+
export type InterceptUseItem =
|
|
139
|
+
| MiddlewareItem
|
|
140
|
+
| RevalidateItem
|
|
141
|
+
| LoaderItem
|
|
142
|
+
| LoadingItem
|
|
143
|
+
| ErrorBoundaryItem
|
|
144
|
+
| NotFoundBoundaryItem
|
|
145
|
+
| LayoutItem
|
|
146
|
+
| RouteItem
|
|
147
|
+
| WhenItem;
|
|
148
|
+
export type LoaderUseItem = RevalidateItem | CacheItem;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe route utilities
|
|
3
|
+
*
|
|
4
|
+
* These utilities can be imported in client code without pulling in server dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RouteDefinition, RouteConfig, ResolvedRouteMap, TrailingSlashMode } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if a value is a RouteConfig object
|
|
11
|
+
*/
|
|
12
|
+
function isRouteConfig(value: unknown): value is RouteConfig {
|
|
13
|
+
return (
|
|
14
|
+
typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
"path" in value &&
|
|
17
|
+
typeof (value as RouteConfig).path === "string"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Flatten nested route definitions
|
|
23
|
+
*/
|
|
24
|
+
function flattenRoutes(
|
|
25
|
+
routes: RouteDefinition,
|
|
26
|
+
prefix: string,
|
|
27
|
+
trailingSlashConfig: Record<string, TrailingSlashMode>
|
|
28
|
+
): Record<string, string> {
|
|
29
|
+
const flattened: Record<string, string> = {};
|
|
30
|
+
|
|
31
|
+
for (const [key, value] of Object.entries(routes)) {
|
|
32
|
+
const fullKey = prefix + key;
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
// Direct route pattern - include prefix
|
|
35
|
+
flattened[fullKey] = value;
|
|
36
|
+
} else if (isRouteConfig(value)) {
|
|
37
|
+
// Route config object - extract path and trailing slash config
|
|
38
|
+
flattened[fullKey] = value.path;
|
|
39
|
+
if (value.trailingSlash) {
|
|
40
|
+
trailingSlashConfig[fullKey] = value.trailingSlash;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Nested routes - flatten recursively
|
|
44
|
+
const nested = flattenRoutes(value, `${fullKey}.`, trailingSlashConfig);
|
|
45
|
+
Object.assign(flattened, nested);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return flattened;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Define routes with type safety (client-safe version)
|
|
54
|
+
*
|
|
55
|
+
* This is a client-safe version of the route function that can be imported
|
|
56
|
+
* in client code without pulling in server dependencies.
|
|
57
|
+
*
|
|
58
|
+
* @param input - Route definition object
|
|
59
|
+
* @returns Flattened route map with full type information
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const shopRoutes = route({
|
|
64
|
+
* index: "/",
|
|
65
|
+
* cart: "/cart",
|
|
66
|
+
* products: {
|
|
67
|
+
* detail: "/product/:slug",
|
|
68
|
+
* category: "/products/:category",
|
|
69
|
+
* },
|
|
70
|
+
* });
|
|
71
|
+
* // Result: { index: "/", cart: "/cart", "products.detail": "/product/:slug", "products.category": "/products/:category" }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function route<const T extends RouteDefinition>(
|
|
75
|
+
input: T
|
|
76
|
+
): ResolvedRouteMap<T> {
|
|
77
|
+
const trailingSlash: Record<string, TrailingSlashMode> = {};
|
|
78
|
+
const routes = flattenRoutes(input as RouteDefinition, "", trailingSlash);
|
|
79
|
+
|
|
80
|
+
const result = routes as ResolvedRouteMap<T> & { __trailingSlash?: Record<string, TrailingSlashMode> };
|
|
81
|
+
if (Object.keys(trailingSlash).length > 0) {
|
|
82
|
+
Object.defineProperty(result, "__trailingSlash", {
|
|
83
|
+
value: trailingSlash,
|
|
84
|
+
enumerable: false,
|
|
85
|
+
writable: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createPipelineState } from "../match-context";
|
|
3
|
+
|
|
4
|
+
describe("match-context", () => {
|
|
5
|
+
describe("createPipelineState()", () => {
|
|
6
|
+
it("should create initial state with default values", () => {
|
|
7
|
+
const state = createPipelineState();
|
|
8
|
+
|
|
9
|
+
expect(state.cacheHit).toBe(false);
|
|
10
|
+
expect(state.segments).toEqual([]);
|
|
11
|
+
expect(state.matchedIds).toEqual([]);
|
|
12
|
+
expect(state.interceptSegments).toEqual([]);
|
|
13
|
+
expect(state.slots).toEqual({});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should create state without cached data by default", () => {
|
|
17
|
+
const state = createPipelineState();
|
|
18
|
+
|
|
19
|
+
expect(state.cachedSegments).toBeUndefined();
|
|
20
|
+
expect(state.cachedMatchedIds).toBeUndefined();
|
|
21
|
+
expect(state.shouldRevalidate).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should allow mutation of state properties", () => {
|
|
25
|
+
const state = createPipelineState();
|
|
26
|
+
|
|
27
|
+
state.cacheHit = true;
|
|
28
|
+
state.segments.push({
|
|
29
|
+
id: "seg1",
|
|
30
|
+
type: "route",
|
|
31
|
+
component: null,
|
|
32
|
+
params: {},
|
|
33
|
+
});
|
|
34
|
+
state.matchedIds.push("seg1");
|
|
35
|
+
state.slots["@modal"] = { active: true, segments: [] };
|
|
36
|
+
|
|
37
|
+
expect(state.cacheHit).toBe(true);
|
|
38
|
+
expect(state.segments).toHaveLength(1);
|
|
39
|
+
expect(state.matchedIds).toContain("seg1");
|
|
40
|
+
expect(state.slots["@modal"]).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should create independent state instances", () => {
|
|
44
|
+
const state1 = createPipelineState();
|
|
45
|
+
const state2 = createPipelineState();
|
|
46
|
+
|
|
47
|
+
state1.cacheHit = true;
|
|
48
|
+
state1.segments.push({
|
|
49
|
+
id: "seg1",
|
|
50
|
+
type: "route",
|
|
51
|
+
component: null,
|
|
52
|
+
params: {},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(state2.cacheHit).toBe(false);
|
|
56
|
+
expect(state2.segments).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should support setting cache-related properties", () => {
|
|
60
|
+
const state = createPipelineState();
|
|
61
|
+
|
|
62
|
+
state.cacheHit = true;
|
|
63
|
+
state.shouldRevalidate = true;
|
|
64
|
+
state.cachedSegments = [
|
|
65
|
+
{ id: "cached1", type: "route", component: "CachedComponent", params: {} },
|
|
66
|
+
];
|
|
67
|
+
state.cachedMatchedIds = ["cached1"];
|
|
68
|
+
|
|
69
|
+
expect(state.cacheHit).toBe(true);
|
|
70
|
+
expect(state.shouldRevalidate).toBe(true);
|
|
71
|
+
expect(state.cachedSegments).toHaveLength(1);
|
|
72
|
+
expect(state.cachedMatchedIds).toContain("cached1");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should support intercept segments", () => {
|
|
76
|
+
const state = createPipelineState();
|
|
77
|
+
|
|
78
|
+
state.interceptSegments.push({
|
|
79
|
+
id: "modal-seg",
|
|
80
|
+
type: "route",
|
|
81
|
+
component: "ModalComponent",
|
|
82
|
+
params: {},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(state.interceptSegments).toHaveLength(1);
|
|
86
|
+
expect(state.interceptSegments[0].id).toBe("modal-seg");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should support slots with nested segments", () => {
|
|
90
|
+
const state = createPipelineState();
|
|
91
|
+
|
|
92
|
+
state.slots["@modal"] = {
|
|
93
|
+
active: true,
|
|
94
|
+
segments: [
|
|
95
|
+
{ id: "modal1", type: "route", component: "Modal1", params: {} },
|
|
96
|
+
{ id: "modal2", type: "route", component: "Modal2", params: {} },
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
expect(state.slots["@modal"].active).toBe(true);
|
|
101
|
+
expect(state.slots["@modal"].segments).toHaveLength(2);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|