@rangojs/router 0.0.0-experimental.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CLAUDE.md +7 -0
  2. package/README.md +19 -0
  3. package/dist/vite/index.js +1298 -0
  4. package/package.json +140 -0
  5. package/skills/caching/SKILL.md +319 -0
  6. package/skills/document-cache/SKILL.md +152 -0
  7. package/skills/hooks/SKILL.md +359 -0
  8. package/skills/intercept/SKILL.md +292 -0
  9. package/skills/layout/SKILL.md +216 -0
  10. package/skills/loader/SKILL.md +365 -0
  11. package/skills/middleware/SKILL.md +442 -0
  12. package/skills/parallel/SKILL.md +255 -0
  13. package/skills/route/SKILL.md +141 -0
  14. package/skills/router-setup/SKILL.md +403 -0
  15. package/skills/theme/SKILL.md +54 -0
  16. package/skills/typesafety/SKILL.md +352 -0
  17. package/src/__mocks__/version.ts +6 -0
  18. package/src/__tests__/component-utils.test.ts +76 -0
  19. package/src/__tests__/route-definition.test.ts +63 -0
  20. package/src/__tests__/urls.test.tsx +436 -0
  21. package/src/browser/event-controller.ts +876 -0
  22. package/src/browser/index.ts +18 -0
  23. package/src/browser/link-interceptor.ts +121 -0
  24. package/src/browser/lru-cache.ts +69 -0
  25. package/src/browser/merge-segment-loaders.ts +126 -0
  26. package/src/browser/navigation-bridge.ts +893 -0
  27. package/src/browser/navigation-client.ts +162 -0
  28. package/src/browser/navigation-store.ts +823 -0
  29. package/src/browser/partial-update.ts +559 -0
  30. package/src/browser/react/Link.tsx +248 -0
  31. package/src/browser/react/NavigationProvider.tsx +275 -0
  32. package/src/browser/react/ScrollRestoration.tsx +94 -0
  33. package/src/browser/react/context.ts +53 -0
  34. package/src/browser/react/index.ts +52 -0
  35. package/src/browser/react/location-state-shared.ts +120 -0
  36. package/src/browser/react/location-state.ts +62 -0
  37. package/src/browser/react/use-action.ts +240 -0
  38. package/src/browser/react/use-client-cache.ts +56 -0
  39. package/src/browser/react/use-handle.ts +178 -0
  40. package/src/browser/react/use-href.tsx +208 -0
  41. package/src/browser/react/use-link-status.ts +134 -0
  42. package/src/browser/react/use-navigation.ts +150 -0
  43. package/src/browser/react/use-segments.ts +188 -0
  44. package/src/browser/request-controller.ts +164 -0
  45. package/src/browser/rsc-router.tsx +353 -0
  46. package/src/browser/scroll-restoration.ts +324 -0
  47. package/src/browser/server-action-bridge.ts +747 -0
  48. package/src/browser/shallow.ts +35 -0
  49. package/src/browser/types.ts +464 -0
  50. package/src/cache/__tests__/document-cache.test.ts +522 -0
  51. package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
  52. package/src/cache/__tests__/memory-store.test.ts +484 -0
  53. package/src/cache/cache-scope.ts +565 -0
  54. package/src/cache/cf/__tests__/cf-cache-store.test.ts +428 -0
  55. package/src/cache/cf/cf-cache-store.ts +428 -0
  56. package/src/cache/cf/index.ts +19 -0
  57. package/src/cache/document-cache.ts +340 -0
  58. package/src/cache/index.ts +58 -0
  59. package/src/cache/memory-segment-store.ts +150 -0
  60. package/src/cache/memory-store.ts +253 -0
  61. package/src/cache/types.ts +387 -0
  62. package/src/client.rsc.tsx +88 -0
  63. package/src/client.tsx +621 -0
  64. package/src/component-utils.ts +76 -0
  65. package/src/components/DefaultDocument.tsx +23 -0
  66. package/src/default-error-boundary.tsx +88 -0
  67. package/src/deps/browser.ts +8 -0
  68. package/src/deps/html-stream-client.ts +2 -0
  69. package/src/deps/html-stream-server.ts +2 -0
  70. package/src/deps/rsc.ts +10 -0
  71. package/src/deps/ssr.ts +2 -0
  72. package/src/errors.ts +259 -0
  73. package/src/handle.ts +120 -0
  74. package/src/handles/MetaTags.tsx +193 -0
  75. package/src/handles/index.ts +6 -0
  76. package/src/handles/meta.ts +247 -0
  77. package/src/href-client.ts +128 -0
  78. package/src/href-context.ts +33 -0
  79. package/src/href.ts +177 -0
  80. package/src/index.rsc.ts +79 -0
  81. package/src/index.ts +87 -0
  82. package/src/loader.rsc.ts +204 -0
  83. package/src/loader.ts +47 -0
  84. package/src/network-error-thrower.tsx +21 -0
  85. package/src/outlet-context.ts +15 -0
  86. package/src/root-error-boundary.tsx +277 -0
  87. package/src/route-content-wrapper.tsx +198 -0
  88. package/src/route-definition.ts +1371 -0
  89. package/src/route-map-builder.ts +146 -0
  90. package/src/route-types.ts +198 -0
  91. package/src/route-utils.ts +89 -0
  92. package/src/router/__tests__/match-context.test.ts +104 -0
  93. package/src/router/__tests__/match-pipelines.test.ts +537 -0
  94. package/src/router/__tests__/match-result.test.ts +566 -0
  95. package/src/router/__tests__/on-error.test.ts +935 -0
  96. package/src/router/__tests__/pattern-matching.test.ts +577 -0
  97. package/src/router/error-handling.ts +287 -0
  98. package/src/router/handler-context.ts +158 -0
  99. package/src/router/loader-resolution.ts +326 -0
  100. package/src/router/manifest.ts +138 -0
  101. package/src/router/match-context.ts +264 -0
  102. package/src/router/match-middleware/background-revalidation.ts +236 -0
  103. package/src/router/match-middleware/cache-lookup.ts +261 -0
  104. package/src/router/match-middleware/cache-store.ts +266 -0
  105. package/src/router/match-middleware/index.ts +81 -0
  106. package/src/router/match-middleware/intercept-resolution.ts +268 -0
  107. package/src/router/match-middleware/segment-resolution.ts +174 -0
  108. package/src/router/match-pipelines.ts +214 -0
  109. package/src/router/match-result.ts +214 -0
  110. package/src/router/metrics.ts +62 -0
  111. package/src/router/middleware.test.ts +1355 -0
  112. package/src/router/middleware.ts +748 -0
  113. package/src/router/pattern-matching.ts +272 -0
  114. package/src/router/revalidation.ts +190 -0
  115. package/src/router/router-context.ts +299 -0
  116. package/src/router/types.ts +96 -0
  117. package/src/router.ts +3876 -0
  118. package/src/rsc/__tests__/helpers.test.ts +175 -0
  119. package/src/rsc/handler.ts +1060 -0
  120. package/src/rsc/helpers.ts +64 -0
  121. package/src/rsc/index.ts +56 -0
  122. package/src/rsc/nonce.ts +18 -0
  123. package/src/rsc/types.ts +237 -0
  124. package/src/segment-system.tsx +456 -0
  125. package/src/server/__tests__/request-context.test.ts +171 -0
  126. package/src/server/context.ts +417 -0
  127. package/src/server/handle-store.ts +230 -0
  128. package/src/server/loader-registry.ts +174 -0
  129. package/src/server/request-context.ts +554 -0
  130. package/src/server/root-layout.tsx +10 -0
  131. package/src/server/tsconfig.json +14 -0
  132. package/src/server.ts +146 -0
  133. package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
  134. package/src/ssr/index.tsx +234 -0
  135. package/src/theme/ThemeProvider.tsx +291 -0
  136. package/src/theme/ThemeScript.tsx +61 -0
  137. package/src/theme/__tests__/theme.test.ts +120 -0
  138. package/src/theme/constants.ts +55 -0
  139. package/src/theme/index.ts +58 -0
  140. package/src/theme/theme-context.ts +70 -0
  141. package/src/theme/theme-script.ts +152 -0
  142. package/src/theme/types.ts +182 -0
  143. package/src/theme/use-theme.ts +44 -0
  144. package/src/types.ts +1561 -0
  145. package/src/urls.ts +726 -0
  146. package/src/use-loader.tsx +346 -0
  147. package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
  148. package/src/vite/expose-action-id.ts +344 -0
  149. package/src/vite/expose-handle-id.ts +209 -0
  150. package/src/vite/expose-loader-id.ts +357 -0
  151. package/src/vite/expose-location-state-id.ts +177 -0
  152. package/src/vite/index.ts +787 -0
  153. package/src/vite/package-resolution.ts +125 -0
  154. package/src/vite/version.d.ts +12 -0
  155. package/src/vite/virtual-entries.ts +109 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Generates an inline script for FOUC prevention.
3
+ *
4
+ * This script runs synchronously before page paint to:
5
+ * 1. Read theme from cookie or localStorage
6
+ * 2. Detect system preference if theme is "system"
7
+ * 3. Apply theme to HTML element via class or data attribute
8
+ * 4. Optionally set color-scheme CSS property
9
+ *
10
+ * The script is minified and inlined in <head> before any other content.
11
+ */
12
+
13
+ import type { ResolvedThemeConfig } from "./types.js";
14
+
15
+ /**
16
+ * Generate the inline script for theme initialization
17
+ *
18
+ * The script is designed to:
19
+ * - Run synchronously before paint (blocking)
20
+ * - Be as small as possible to minimize blocking time
21
+ * - Work without any external dependencies
22
+ * - Handle all edge cases (no localStorage, no cookie, etc.)
23
+ */
24
+ export function generateThemeScript(config: ResolvedThemeConfig): string {
25
+ // Build the script as a string, then minify
26
+ const script = `
27
+ (function() {
28
+ var storageKey = ${JSON.stringify(config.storageKey)};
29
+ var defaultTheme = ${JSON.stringify(config.defaultTheme)};
30
+ var attribute = ${JSON.stringify(config.attribute)};
31
+ var enableSystem = ${config.enableSystem};
32
+ var enableColorScheme = ${config.enableColorScheme};
33
+ var valueMap = ${JSON.stringify(config.value)};
34
+ var themes = ${JSON.stringify(config.themes)};
35
+
36
+ // Read theme from cookie or localStorage
37
+ function getStoredTheme() {
38
+ // Try cookie first (for SSR consistency)
39
+ var cookies = document.cookie.split(';');
40
+ for (var i = 0; i < cookies.length; i++) {
41
+ var cookie = cookies[i].trim();
42
+ if (cookie.indexOf(storageKey + '=') === 0) {
43
+ return decodeURIComponent(cookie.substring(storageKey.length + 1));
44
+ }
45
+ }
46
+ // Fall back to localStorage
47
+ try {
48
+ return localStorage.getItem(storageKey);
49
+ } catch (e) {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ // Get system preference
55
+ function getSystemTheme() {
56
+ if (typeof window !== 'undefined' && window.matchMedia) {
57
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
58
+ }
59
+ return 'light';
60
+ }
61
+
62
+ // Resolve "system" to actual theme
63
+ function resolveTheme(theme) {
64
+ if (theme === 'system' && enableSystem) {
65
+ return getSystemTheme();
66
+ }
67
+ return theme;
68
+ }
69
+
70
+ // Apply theme to HTML element
71
+ function applyTheme(theme) {
72
+ var resolved = resolveTheme(theme);
73
+ var value = valueMap[resolved] || resolved;
74
+ var el = document.documentElement;
75
+
76
+ // Apply attribute
77
+ if (attribute === 'class') {
78
+ // Remove all theme classes, then add current
79
+ for (var i = 0; i < themes.length; i++) {
80
+ var v = valueMap[themes[i]] || themes[i];
81
+ el.classList.remove(v);
82
+ }
83
+ el.classList.add(value);
84
+ } else {
85
+ el.setAttribute(attribute, value);
86
+ }
87
+
88
+ // Set color-scheme for native dark mode support
89
+ if (enableColorScheme) {
90
+ el.style.colorScheme = resolved;
91
+ }
92
+ }
93
+
94
+ // Get stored theme or use default
95
+ var stored = getStoredTheme();
96
+ var theme = stored && (stored === 'system' || themes.indexOf(stored) !== -1)
97
+ ? stored
98
+ : defaultTheme;
99
+
100
+ // Apply immediately
101
+ applyTheme(theme);
102
+
103
+ // Listen for system preference changes (for "system" theme)
104
+ if (enableSystem && typeof window !== 'undefined' && window.matchMedia) {
105
+ try {
106
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() {
107
+ var current = getStoredTheme() || defaultTheme;
108
+ if (current === 'system') {
109
+ applyTheme('system');
110
+ }
111
+ });
112
+ } catch (e) {
113
+ // Older browsers may not support addEventListener on MediaQueryList
114
+ }
115
+ }
116
+ })();
117
+ `;
118
+
119
+ // Minify by removing comments, extra whitespace, and newlines
120
+ return minifyScript(script);
121
+ }
122
+
123
+ /**
124
+ * Basic script minification
125
+ * Removes comments, extra whitespace, and unnecessary newlines
126
+ */
127
+ function minifyScript(script: string): string {
128
+ return script
129
+ // Remove single-line comments
130
+ .replace(/\/\/.*$/gm, "")
131
+ // Remove multi-line comments
132
+ .replace(/\/\*[\s\S]*?\*\//g, "")
133
+ // Remove leading/trailing whitespace from lines
134
+ .split("\n")
135
+ .map((line) => line.trim())
136
+ .filter((line) => line.length > 0)
137
+ .join("")
138
+ // Collapse multiple spaces to single space
139
+ .replace(/\s+/g, " ")
140
+ // Remove spaces around operators and punctuation
141
+ .replace(/\s*([{};,=!<>()[\]+\-*/&|?:])\s*/g, "$1")
142
+ // Add back necessary spaces (e.g., "var x")
143
+ .replace(/(var|function|return|if|for|try|catch|typeof|else)\(/g, "$1 (")
144
+ .replace(/\)([a-zA-Z])/g, ") $1");
145
+ }
146
+
147
+ /**
148
+ * Generate nonce attribute string if nonce is provided
149
+ */
150
+ export function getNonceAttribute(nonce?: string): string {
151
+ return nonce ? ` nonce="${nonce}"` : "";
152
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Theme type definitions for rsc-router theme system.
3
+ *
4
+ * The theme system provides:
5
+ * - Opt-in via router config
6
+ * - FOUC prevention via inline script
7
+ * - Server-side theme access via ctx.theme/ctx.setTheme
8
+ * - Client-side theme access via useTheme hook
9
+ */
10
+
11
+ /**
12
+ * Theme value - stored in cookies/localStorage
13
+ */
14
+ export type Theme = "light" | "dark" | "system";
15
+
16
+ /**
17
+ * Resolved theme - actual visual appearance (system preference resolved)
18
+ */
19
+ export type ResolvedTheme = "light" | "dark";
20
+
21
+ /**
22
+ * Attribute used to apply theme to HTML element
23
+ * - "class": Adds theme value as CSS class (e.g., <html class="dark">)
24
+ * - "data-*": Sets data attribute (e.g., <html data-theme="dark">)
25
+ */
26
+ export type ThemeAttribute = "class" | `data-${string}`;
27
+
28
+ /**
29
+ * Theme configuration for router
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const router = createRSCRouter<Env>({
34
+ * theme: {
35
+ * defaultTheme: "system",
36
+ * themes: ["light", "dark"],
37
+ * attribute: "class",
38
+ * storageKey: "theme",
39
+ * }
40
+ * });
41
+ * ```
42
+ */
43
+ export interface ThemeConfig {
44
+ /**
45
+ * Default theme when no preference is stored
46
+ * @default "system"
47
+ */
48
+ defaultTheme?: Theme;
49
+
50
+ /**
51
+ * Available theme options
52
+ * @default ["light", "dark"]
53
+ */
54
+ themes?: string[];
55
+
56
+ /**
57
+ * Attribute to apply to HTML element
58
+ * @default "class"
59
+ */
60
+ attribute?: ThemeAttribute;
61
+
62
+ /**
63
+ * Key for cookie and localStorage
64
+ * @default "theme"
65
+ */
66
+ storageKey?: string;
67
+
68
+ /**
69
+ * Enable system preference detection
70
+ * When true, "system" theme will resolve based on prefers-color-scheme
71
+ * @default true
72
+ */
73
+ enableSystem?: boolean;
74
+
75
+ /**
76
+ * Set CSS color-scheme property on HTML element
77
+ * This enables native dark mode for form controls, scrollbars, etc.
78
+ * @default true
79
+ */
80
+ enableColorScheme?: boolean;
81
+
82
+ /**
83
+ * Custom mapping of theme names to attribute values
84
+ * Useful when theme names don't match desired attribute values
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * value: {
89
+ * light: "light-mode",
90
+ * dark: "dark-mode",
91
+ * }
92
+ * ```
93
+ */
94
+ value?: Record<string, string>;
95
+ }
96
+
97
+ /**
98
+ * Resolved theme configuration with defaults applied
99
+ */
100
+ export interface ResolvedThemeConfig {
101
+ defaultTheme: Theme;
102
+ themes: string[];
103
+ attribute: ThemeAttribute;
104
+ storageKey: string;
105
+ enableSystem: boolean;
106
+ enableColorScheme: boolean;
107
+ value: Record<string, string>;
108
+ }
109
+
110
+ /**
111
+ * Return type from useTheme hook
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * function ThemeToggle() {
116
+ * const { theme, setTheme, resolvedTheme, themes } = useTheme();
117
+ *
118
+ * return (
119
+ * <select value={theme} onChange={e => setTheme(e.target.value as Theme)}>
120
+ * {themes.map(t => <option key={t}>{t}</option>)}
121
+ * </select>
122
+ * );
123
+ * }
124
+ * ```
125
+ */
126
+ export interface UseThemeReturn {
127
+ /**
128
+ * Current theme setting ("light" | "dark" | "system")
129
+ */
130
+ theme: Theme;
131
+
132
+ /**
133
+ * Set the theme
134
+ */
135
+ setTheme: (theme: Theme) => void;
136
+
137
+ /**
138
+ * Resolved theme based on system preference
139
+ * When theme is "system", this reflects the actual appearance
140
+ */
141
+ resolvedTheme: ResolvedTheme;
142
+
143
+ /**
144
+ * Current system preference ("light" | "dark")
145
+ */
146
+ systemTheme: ResolvedTheme;
147
+
148
+ /**
149
+ * Available theme options (includes "system" if enableSystem is true)
150
+ */
151
+ themes: string[];
152
+ }
153
+
154
+ /**
155
+ * Props for ThemeProvider component
156
+ */
157
+ export interface ThemeProviderProps {
158
+ /**
159
+ * Theme configuration
160
+ */
161
+ config: ResolvedThemeConfig;
162
+
163
+ /**
164
+ * Initial theme from server (from cookie)
165
+ */
166
+ initialTheme?: Theme;
167
+
168
+ /**
169
+ * Children to render
170
+ */
171
+ children: React.ReactNode;
172
+ }
173
+
174
+ /**
175
+ * Context value for ThemeContext
176
+ */
177
+ export interface ThemeContextValue extends UseThemeReturn {
178
+ /**
179
+ * Full theme configuration
180
+ */
181
+ config: ResolvedThemeConfig;
182
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useTheme - Hook for accessing theme state in client components.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { useTheme } from "@rangojs/router/theme";
9
+ *
10
+ * function ThemeToggle() {
11
+ * const { theme, setTheme, resolvedTheme, themes } = useTheme();
12
+ *
13
+ * return (
14
+ * <select value={theme} onChange={e => setTheme(e.target.value)}>
15
+ * {themes.map(t => <option key={t}>{t}</option>)}
16
+ * </select>
17
+ * );
18
+ * }
19
+ * ```
20
+ */
21
+
22
+ import { requireThemeContext } from "./theme-context.js";
23
+ import type { UseThemeReturn } from "./types.js";
24
+
25
+ /**
26
+ * Hook to access theme state and methods.
27
+ *
28
+ * Must be used within a ThemeProvider (which is automatically included
29
+ * in NavigationProvider when theme is enabled in router config).
30
+ *
31
+ * @returns Theme state and methods
32
+ * @throws Error if used outside ThemeProvider
33
+ */
34
+ export function useTheme(): UseThemeReturn {
35
+ const ctx = requireThemeContext();
36
+
37
+ return {
38
+ theme: ctx.theme,
39
+ setTheme: ctx.setTheme,
40
+ resolvedTheme: ctx.resolvedTheme,
41
+ systemTheme: ctx.systemTheme,
42
+ themes: ctx.themes,
43
+ };
44
+ }