one 1.2.83 → 1.2.84

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 (288) hide show
  1. package/devtools/dev.mjs +44 -0
  2. package/devtools/devtools.mjs +540 -0
  3. package/devtools/source-inspector.mjs +201 -0
  4. package/dist/cjs/cli/daemon.cjs +136 -0
  5. package/dist/cjs/cli/daemon.js +112 -0
  6. package/dist/cjs/cli/daemon.js.map +6 -0
  7. package/dist/cjs/cli/daemon.native.js +173 -0
  8. package/dist/cjs/cli/daemon.native.js.map +1 -0
  9. package/dist/cjs/createHandleRequest.cjs +15 -8
  10. package/dist/cjs/createHandleRequest.js +10 -6
  11. package/dist/cjs/createHandleRequest.js.map +1 -1
  12. package/dist/cjs/createHandleRequest.native.js +8 -3
  13. package/dist/cjs/createHandleRequest.native.js.map +1 -1
  14. package/dist/cjs/daemon/index.cjs +24 -0
  15. package/dist/cjs/daemon/index.js +21 -0
  16. package/dist/cjs/daemon/index.js.map +6 -0
  17. package/dist/cjs/daemon/index.native.js +27 -0
  18. package/dist/cjs/daemon/index.native.js.map +1 -0
  19. package/dist/cjs/daemon/ipc.cjs +235 -0
  20. package/dist/cjs/daemon/ipc.js +204 -0
  21. package/dist/cjs/daemon/ipc.js.map +6 -0
  22. package/dist/cjs/daemon/ipc.native.js +276 -0
  23. package/dist/cjs/daemon/ipc.native.js.map +1 -0
  24. package/dist/cjs/daemon/picker.cjs +223 -0
  25. package/dist/cjs/daemon/picker.js +191 -0
  26. package/dist/cjs/daemon/picker.js.map +6 -0
  27. package/dist/cjs/daemon/picker.native.js +308 -0
  28. package/dist/cjs/daemon/picker.native.js.map +1 -0
  29. package/dist/cjs/daemon/proxy.cjs +75 -0
  30. package/dist/cjs/daemon/proxy.js +70 -0
  31. package/dist/cjs/daemon/proxy.js.map +6 -0
  32. package/dist/cjs/daemon/proxy.native.js +81 -0
  33. package/dist/cjs/daemon/proxy.native.js.map +1 -0
  34. package/dist/cjs/daemon/registry.cjs +85 -0
  35. package/dist/cjs/daemon/registry.js +81 -0
  36. package/dist/cjs/daemon/registry.js.map +6 -0
  37. package/dist/cjs/daemon/registry.native.js +120 -0
  38. package/dist/cjs/daemon/registry.native.js.map +1 -0
  39. package/dist/cjs/daemon/server.cjs +178 -0
  40. package/dist/cjs/daemon/server.js +179 -0
  41. package/dist/cjs/daemon/server.js.map +6 -0
  42. package/dist/cjs/daemon/server.native.js +200 -0
  43. package/dist/cjs/daemon/server.native.js.map +1 -0
  44. package/dist/cjs/daemon/tui.cjs +223 -0
  45. package/dist/cjs/daemon/tui.js +192 -0
  46. package/dist/cjs/daemon/tui.js.map +6 -0
  47. package/dist/cjs/daemon/tui.native.js +234 -0
  48. package/dist/cjs/daemon/tui.native.js.map +1 -0
  49. package/dist/cjs/daemon/types.cjs +16 -0
  50. package/dist/cjs/daemon/types.js +14 -0
  51. package/dist/cjs/daemon/types.js.map +6 -0
  52. package/dist/cjs/daemon/types.native.js +19 -0
  53. package/dist/cjs/daemon/types.native.js.map +1 -0
  54. package/dist/cjs/daemon/utils.cjs +74 -0
  55. package/dist/cjs/daemon/utils.js +65 -0
  56. package/dist/cjs/daemon/utils.js.map +6 -0
  57. package/dist/cjs/daemon/utils.native.js +83 -0
  58. package/dist/cjs/daemon/utils.native.js.map +1 -0
  59. package/dist/cjs/fork/createMemoryHistory.cjs +22 -8
  60. package/dist/cjs/fork/createMemoryHistory.js +20 -6
  61. package/dist/cjs/fork/createMemoryHistory.js.map +1 -1
  62. package/dist/cjs/fork/createMemoryHistory.native.js +22 -8
  63. package/dist/cjs/fork/createMemoryHistory.native.js.map +1 -1
  64. package/dist/cjs/fork/useLinking.cjs +52 -13
  65. package/dist/cjs/fork/useLinking.js +35 -10
  66. package/dist/cjs/fork/useLinking.js.map +1 -1
  67. package/dist/cjs/index.cjs +6 -2
  68. package/dist/cjs/index.js +4 -2
  69. package/dist/cjs/index.js.map +2 -2
  70. package/dist/cjs/index.native.js +6 -2
  71. package/dist/cjs/index.native.js.map +1 -1
  72. package/dist/cjs/router/linkingConfig.cjs +13 -2
  73. package/dist/cjs/router/linkingConfig.js +14 -5
  74. package/dist/cjs/router/linkingConfig.js.map +1 -1
  75. package/dist/cjs/router/linkingConfig.native.js +10 -1
  76. package/dist/cjs/router/linkingConfig.native.js.map +1 -1
  77. package/dist/cjs/router/routeMask.cjs +137 -0
  78. package/dist/cjs/router/routeMask.js +127 -0
  79. package/dist/cjs/router/routeMask.js.map +6 -0
  80. package/dist/cjs/router/routeMask.native.js +160 -0
  81. package/dist/cjs/router/routeMask.native.js.map +1 -0
  82. package/dist/cjs/router/router.cjs +26 -3
  83. package/dist/cjs/router/router.js +23 -3
  84. package/dist/cjs/router/router.js.map +1 -1
  85. package/dist/cjs/router/router.native.js +26 -3
  86. package/dist/cjs/router/router.native.js.map +1 -1
  87. package/dist/cjs/server/PreloadScripts.native.js +45 -39
  88. package/dist/cjs/server/PreloadScripts.native.js.map +1 -6
  89. package/dist/cjs/vite/DevHead.cjs +2 -909
  90. package/dist/cjs/vite/DevHead.js +1 -917
  91. package/dist/cjs/vite/DevHead.js.map +1 -1
  92. package/dist/cjs/vite/DevHead.native.js +1 -882
  93. package/dist/cjs/vite/DevHead.native.js.map +1 -1
  94. package/dist/cjs/vite/one.cjs +4 -1
  95. package/dist/cjs/vite/one.js +4 -2
  96. package/dist/cjs/vite/one.js.map +1 -1
  97. package/dist/cjs/vite/one.native.js +4 -1
  98. package/dist/cjs/vite/one.native.js.map +1 -1
  99. package/dist/cjs/vite/plugins/devtoolsPlugin.cjs +55 -0
  100. package/dist/cjs/vite/plugins/devtoolsPlugin.js +49 -0
  101. package/dist/cjs/vite/plugins/devtoolsPlugin.js.map +6 -0
  102. package/dist/cjs/vite/plugins/devtoolsPlugin.native.js +58 -0
  103. package/dist/cjs/vite/plugins/devtoolsPlugin.native.js.map +1 -0
  104. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +1 -6
  105. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js +1 -6
  106. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  107. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +1 -6
  108. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  109. package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +7 -1
  110. package/dist/cjs/vite/plugins/virtualEntryPlugin.js +5 -1
  111. package/dist/cjs/vite/plugins/virtualEntryPlugin.js.map +1 -1
  112. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +8 -1
  113. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  114. package/dist/esm/cli/daemon.js +89 -0
  115. package/dist/esm/cli/daemon.js.map +6 -0
  116. package/dist/esm/cli/daemon.mjs +102 -0
  117. package/dist/esm/cli/daemon.mjs.map +1 -0
  118. package/dist/esm/cli/daemon.native.js +136 -0
  119. package/dist/esm/cli/daemon.native.js.map +1 -0
  120. package/dist/esm/createHandleRequest.js +10 -5
  121. package/dist/esm/createHandleRequest.js.map +1 -1
  122. package/dist/esm/createHandleRequest.mjs +15 -8
  123. package/dist/esm/createHandleRequest.mjs.map +1 -1
  124. package/dist/esm/createHandleRequest.native.js +8 -3
  125. package/dist/esm/createHandleRequest.native.js.map +1 -1
  126. package/dist/esm/daemon/index.js +8 -0
  127. package/dist/esm/daemon/index.js.map +6 -0
  128. package/dist/esm/daemon/index.mjs +8 -0
  129. package/dist/esm/daemon/index.mjs.map +1 -0
  130. package/dist/esm/daemon/index.native.js +8 -0
  131. package/dist/esm/daemon/index.native.js.map +1 -0
  132. package/dist/esm/daemon/ipc.js +192 -0
  133. package/dist/esm/daemon/ipc.js.map +6 -0
  134. package/dist/esm/daemon/ipc.mjs +191 -0
  135. package/dist/esm/daemon/ipc.mjs.map +1 -0
  136. package/dist/esm/daemon/ipc.native.js +229 -0
  137. package/dist/esm/daemon/ipc.native.js.map +1 -0
  138. package/dist/esm/daemon/picker.js +169 -0
  139. package/dist/esm/daemon/picker.js.map +6 -0
  140. package/dist/esm/daemon/picker.mjs +186 -0
  141. package/dist/esm/daemon/picker.mjs.map +1 -0
  142. package/dist/esm/daemon/picker.native.js +268 -0
  143. package/dist/esm/daemon/picker.native.js.map +1 -0
  144. package/dist/esm/daemon/proxy.js +47 -0
  145. package/dist/esm/daemon/proxy.js.map +6 -0
  146. package/dist/esm/daemon/proxy.mjs +40 -0
  147. package/dist/esm/daemon/proxy.mjs.map +1 -0
  148. package/dist/esm/daemon/proxy.native.js +43 -0
  149. package/dist/esm/daemon/proxy.native.js.map +1 -0
  150. package/dist/esm/daemon/registry.js +65 -0
  151. package/dist/esm/daemon/registry.js.map +6 -0
  152. package/dist/esm/daemon/registry.mjs +53 -0
  153. package/dist/esm/daemon/registry.mjs.map +1 -0
  154. package/dist/esm/daemon/registry.native.js +85 -0
  155. package/dist/esm/daemon/registry.native.js.map +1 -0
  156. package/dist/esm/daemon/server.js +167 -0
  157. package/dist/esm/daemon/server.js.map +6 -0
  158. package/dist/esm/daemon/server.mjs +143 -0
  159. package/dist/esm/daemon/server.mjs.map +1 -0
  160. package/dist/esm/daemon/server.native.js +162 -0
  161. package/dist/esm/daemon/server.native.js.map +1 -0
  162. package/dist/esm/daemon/tui.js +171 -0
  163. package/dist/esm/daemon/tui.js.map +6 -0
  164. package/dist/esm/daemon/tui.mjs +187 -0
  165. package/dist/esm/daemon/tui.mjs.map +1 -0
  166. package/dist/esm/daemon/tui.native.js +195 -0
  167. package/dist/esm/daemon/tui.native.js.map +1 -0
  168. package/dist/esm/daemon/types.js +1 -0
  169. package/dist/esm/daemon/types.js.map +6 -0
  170. package/dist/esm/daemon/types.mjs +2 -0
  171. package/dist/esm/daemon/types.mjs.map +1 -0
  172. package/dist/esm/daemon/types.native.js +2 -0
  173. package/dist/esm/daemon/types.native.js.map +1 -0
  174. package/dist/esm/daemon/utils.js +42 -0
  175. package/dist/esm/daemon/utils.js.map +6 -0
  176. package/dist/esm/daemon/utils.mjs +39 -0
  177. package/dist/esm/daemon/utils.mjs.map +1 -0
  178. package/dist/esm/daemon/utils.native.js +45 -0
  179. package/dist/esm/daemon/utils.native.js.map +1 -0
  180. package/dist/esm/fork/createMemoryHistory.js +20 -6
  181. package/dist/esm/fork/createMemoryHistory.js.map +1 -1
  182. package/dist/esm/fork/createMemoryHistory.mjs +22 -8
  183. package/dist/esm/fork/createMemoryHistory.mjs.map +1 -1
  184. package/dist/esm/fork/createMemoryHistory.native.js +22 -8
  185. package/dist/esm/fork/createMemoryHistory.native.js.map +1 -1
  186. package/dist/esm/fork/useLinking.js +35 -9
  187. package/dist/esm/fork/useLinking.js.map +1 -1
  188. package/dist/esm/fork/useLinking.mjs +52 -13
  189. package/dist/esm/fork/useLinking.mjs.map +1 -1
  190. package/dist/esm/index.js +4 -0
  191. package/dist/esm/index.js.map +1 -1
  192. package/dist/esm/index.mjs +3 -1
  193. package/dist/esm/index.mjs.map +1 -1
  194. package/dist/esm/index.native.js +3 -1
  195. package/dist/esm/index.native.js.map +1 -1
  196. package/dist/esm/router/linkingConfig.js +14 -4
  197. package/dist/esm/router/linkingConfig.js.map +1 -1
  198. package/dist/esm/router/linkingConfig.mjs +12 -1
  199. package/dist/esm/router/linkingConfig.mjs.map +1 -1
  200. package/dist/esm/router/linkingConfig.native.js +10 -1
  201. package/dist/esm/router/linkingConfig.native.js.map +1 -1
  202. package/dist/esm/router/routeMask.js +111 -0
  203. package/dist/esm/router/routeMask.js.map +6 -0
  204. package/dist/esm/router/routeMask.mjs +108 -0
  205. package/dist/esm/router/routeMask.mjs.map +1 -0
  206. package/dist/esm/router/routeMask.native.js +128 -0
  207. package/dist/esm/router/routeMask.native.js.map +1 -0
  208. package/dist/esm/router/router.js +23 -2
  209. package/dist/esm/router/router.js.map +1 -1
  210. package/dist/esm/router/router.mjs +24 -3
  211. package/dist/esm/router/router.mjs.map +1 -1
  212. package/dist/esm/router/router.native.js +24 -3
  213. package/dist/esm/router/router.native.js.map +1 -1
  214. package/dist/esm/server/PreloadScripts.native.js +29 -33
  215. package/dist/esm/server/PreloadScripts.native.js.map +1 -6
  216. package/dist/esm/vite/DevHead.js +1 -917
  217. package/dist/esm/vite/DevHead.js.map +1 -1
  218. package/dist/esm/vite/DevHead.mjs +2 -909
  219. package/dist/esm/vite/DevHead.mjs.map +1 -1
  220. package/dist/esm/vite/DevHead.native.js +0 -881
  221. package/dist/esm/vite/DevHead.native.js.map +1 -1
  222. package/dist/esm/vite/one.js +4 -1
  223. package/dist/esm/vite/one.js.map +1 -1
  224. package/dist/esm/vite/one.mjs +4 -1
  225. package/dist/esm/vite/one.mjs.map +1 -1
  226. package/dist/esm/vite/one.native.js +4 -1
  227. package/dist/esm/vite/one.native.js.map +1 -1
  228. package/dist/esm/vite/plugins/devtoolsPlugin.js +33 -0
  229. package/dist/esm/vite/plugins/devtoolsPlugin.js.map +6 -0
  230. package/dist/esm/vite/plugins/devtoolsPlugin.mjs +31 -0
  231. package/dist/esm/vite/plugins/devtoolsPlugin.mjs.map +1 -0
  232. package/dist/esm/vite/plugins/devtoolsPlugin.native.js +31 -0
  233. package/dist/esm/vite/plugins/devtoolsPlugin.native.js.map +1 -0
  234. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js +1 -6
  235. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  236. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +1 -6
  237. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  238. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +1 -6
  239. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  240. package/dist/esm/vite/plugins/virtualEntryPlugin.js +5 -1
  241. package/dist/esm/vite/plugins/virtualEntryPlugin.js.map +1 -1
  242. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +7 -1
  243. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
  244. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +8 -1
  245. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  246. package/package.json +12 -10
  247. package/src/createHandleRequest.ts +16 -3
  248. package/src/fork/createMemoryHistory.tsx +39 -4
  249. package/src/fork/useLinking.ts +83 -12
  250. package/src/index.ts +3 -0
  251. package/src/router/linkingConfig.ts +21 -4
  252. package/src/router/routeMask.ts +293 -0
  253. package/src/router/router.ts +53 -1
  254. package/src/vite/DevHead.tsx +1 -924
  255. package/src/vite/one.ts +4 -0
  256. package/src/vite/plugins/devtoolsPlugin.ts +45 -0
  257. package/src/vite/plugins/fileSystemRouterPlugin.tsx +2 -7
  258. package/src/vite/plugins/virtualEntryPlugin.ts +12 -3
  259. package/src/vite/types.ts +1 -0
  260. package/types/cli/daemon.d.ts.map +1 -0
  261. package/types/createHandleRequest.d.ts.map +1 -1
  262. package/types/daemon/index.d.ts.map +1 -0
  263. package/types/daemon/ipc.d.ts.map +1 -0
  264. package/types/daemon/picker.d.ts.map +1 -0
  265. package/types/daemon/proxy.d.ts.map +1 -0
  266. package/types/daemon/registry.d.ts.map +1 -0
  267. package/types/daemon/server.d.ts.map +1 -0
  268. package/types/daemon/tui.d.ts.map +1 -0
  269. package/types/daemon/types.d.ts.map +1 -0
  270. package/types/daemon/utils.d.ts.map +1 -0
  271. package/types/fork/createMemoryHistory.d.ts +6 -2
  272. package/types/fork/createMemoryHistory.d.ts.map +1 -1
  273. package/types/fork/useLinking.d.ts.map +1 -1
  274. package/types/index.d.ts +2 -0
  275. package/types/index.d.ts.map +1 -1
  276. package/types/router/linkingConfig.d.ts.map +1 -1
  277. package/types/router/routeMask.d.ts +130 -0
  278. package/types/router/routeMask.d.ts.map +1 -0
  279. package/types/router/router.d.ts +23 -0
  280. package/types/router/router.d.ts.map +1 -1
  281. package/types/vite/DevHead.d.ts.map +1 -1
  282. package/types/vite/one.d.ts.map +1 -1
  283. package/types/vite/plugins/devtoolsPlugin.d.ts +5 -0
  284. package/types/vite/plugins/devtoolsPlugin.d.ts.map +1 -0
  285. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
  286. package/types/vite/plugins/virtualEntryPlugin.d.ts +1 -3
  287. package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
  288. package/types/vite/types.d.ts.map +1 -1
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Configuration for a route mask.
3
+ * Route masking displays a different URL in the browser than the actual route being rendered.
4
+ * Uses history.state to store the actual route, so on page reload the router can restore the original route.
5
+ */
6
+ export interface RouteMaskOptions {
7
+ /**
8
+ * The route pattern to match (the actual route being navigated to).
9
+ * Supports dynamic segments like [id] or $id.
10
+ *
11
+ * @example '/photos/[id]/modal' or '/photos/$photoId/modal'
12
+ */
13
+ from: string
14
+
15
+ /**
16
+ * The route pattern to display in the browser URL (the masked URL).
17
+ * Supports dynamic segments that will be populated from the matched params.
18
+ *
19
+ * @example '/photos/[id]' or '/photos/$photoId'
20
+ */
21
+ to: string
22
+
23
+ /**
24
+ * How to handle parameters when masking.
25
+ * - `true` (default): Forward all matched params to the masked URL
26
+ * - `false`: Don't forward any params
27
+ * - Function: Custom param transformation
28
+ *
29
+ * @default true
30
+ */
31
+ params?: boolean | ((matchedParams: Record<string, string>) => Record<string, string>)
32
+
33
+ /**
34
+ * If true, unmask when the page is reloaded (show the masked URL's content).
35
+ * If false (default), keep the actual route on reload (show the original content).
36
+ *
37
+ * When false: Reload at /photos/5 → still shows /photos/5/modal content
38
+ * When true: Reload at /photos/5 → shows /photos/5 content
39
+ *
40
+ * @default false
41
+ */
42
+ unmaskOnReload?: boolean
43
+
44
+ /**
45
+ * If true, encode the actual route as a base64 postfix in the URL pathname instead of history.state.
46
+ *
47
+ * URL will look like: /photos/5__L3Bob3Rvcy81L21vZGFs
48
+ *
49
+ * Benefits:
50
+ * - Server can parse the postfix and render the actual route (no SSR flash)
51
+ * - URL contains the "truth" about what to render
52
+ * - Works consistently across SSR, SSG, and SPA
53
+ * - No query parameter visible
54
+ *
55
+ * Tradeoffs:
56
+ * - URL has a base64 suffix visible after `__`
57
+ *
58
+ * @default false
59
+ */
60
+ useSearchParam?: boolean
61
+ }
62
+
63
+ /**
64
+ * A compiled route mask ready for matching.
65
+ */
66
+ export interface RouteMask extends RouteMaskOptions {
67
+ /** Regex pattern for matching the 'from' route */
68
+ _fromRegex: RegExp
69
+ /** Parameter names extracted from the 'from' pattern */
70
+ _fromParams: string[]
71
+ }
72
+
73
+ /**
74
+ * Creates a route mask for automatic URL masking during navigation.
75
+ *
76
+ * Route masking displays a different URL in the browser than the actual route.
77
+ * Uses browser history.state to store the actual route, enabling:
78
+ * - Modal overlays with clean shareable URLs
79
+ * - Different URL for in-app navigation vs direct access
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * import { createRouteMask } from 'one'
84
+ *
85
+ * const photoMask = createRouteMask({
86
+ * from: '/photos/[id]/modal',
87
+ * to: '/photos/[id]',
88
+ * params: true,
89
+ * })
90
+ *
91
+ * // In vite.config.ts:
92
+ * one({
93
+ * router: { routeMasks: [photoMask] },
94
+ * })
95
+ * ```
96
+ */
97
+ export function createRouteMask(options: RouteMaskOptions): RouteMask {
98
+ const {
99
+ from,
100
+ to,
101
+ params = true,
102
+ unmaskOnReload = false,
103
+ useSearchParam = false,
104
+ } = options
105
+
106
+ // Extract parameter names and build regex from the 'from' pattern
107
+ const fromParams: string[] = []
108
+ const fromRegexStr = from
109
+ .split('/')
110
+ .map((segment) => {
111
+ // Handle catch-all segments [...rest] or $...rest
112
+ if (segment.startsWith('[...') && segment.endsWith(']')) {
113
+ const paramName = segment.slice(4, -1)
114
+ fromParams.push(paramName)
115
+ return '(.+)'
116
+ }
117
+ if (segment.startsWith('$...')) {
118
+ const paramName = segment.slice(4)
119
+ fromParams.push(paramName)
120
+ return '(.+)'
121
+ }
122
+ // Handle dynamic segments [id] or $id
123
+ if (segment.startsWith('[') && segment.endsWith(']')) {
124
+ const paramName = segment.slice(1, -1)
125
+ fromParams.push(paramName)
126
+ return '([^/]+)'
127
+ }
128
+ if (segment.startsWith('$')) {
129
+ const paramName = segment.slice(1)
130
+ fromParams.push(paramName)
131
+ return '([^/]+)'
132
+ }
133
+ // Static segment - escape regex special chars
134
+ return segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
135
+ })
136
+ .join('/')
137
+
138
+ return {
139
+ from,
140
+ to,
141
+ params,
142
+ unmaskOnReload,
143
+ useSearchParam,
144
+ _fromRegex: new RegExp(`^${fromRegexStr}$`),
145
+ _fromParams: fromParams,
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Matches a pathname against a route mask.
151
+ * Returns the matched params if successful, or null if no match.
152
+ */
153
+ export function matchRouteMask(
154
+ pathname: string,
155
+ mask: RouteMask
156
+ ): Record<string, string> | null {
157
+ const match = pathname.match(mask._fromRegex)
158
+ if (!match) return null
159
+
160
+ const params: Record<string, string> = {}
161
+ mask._fromParams.forEach((paramName, index) => {
162
+ params[paramName] = match[index + 1]
163
+ })
164
+
165
+ return params
166
+ }
167
+
168
+ /**
169
+ * Builds the masked URL from a route mask and matched params.
170
+ */
171
+ export function buildMaskedPath(
172
+ mask: RouteMask,
173
+ matchedParams: Record<string, string>
174
+ ): string {
175
+ // Determine which params to use
176
+ let params: Record<string, string>
177
+ if (mask.params === false) {
178
+ params = {}
179
+ } else if (typeof mask.params === 'function') {
180
+ params = mask.params(matchedParams)
181
+ } else {
182
+ params = matchedParams
183
+ }
184
+
185
+ // Build the masked URL by replacing dynamic segments
186
+ return mask.to
187
+ .split('/')
188
+ .map((segment) => {
189
+ // Handle [...rest] or $...rest
190
+ if (segment.startsWith('[...') && segment.endsWith(']')) {
191
+ const paramName = segment.slice(4, -1)
192
+ return params[paramName] ?? ''
193
+ }
194
+ if (segment.startsWith('$...')) {
195
+ const paramName = segment.slice(4)
196
+ return params[paramName] ?? ''
197
+ }
198
+ // Handle [id] or $id
199
+ if (segment.startsWith('[') && segment.endsWith(']')) {
200
+ const paramName = segment.slice(1, -1)
201
+ return params[paramName] ?? ''
202
+ }
203
+ if (segment.startsWith('$')) {
204
+ const paramName = segment.slice(1)
205
+ return params[paramName] ?? ''
206
+ }
207
+ return segment
208
+ })
209
+ .join('/')
210
+ }
211
+
212
+ /**
213
+ * URL-safe base64 encode for the _unmask suffix.
214
+ * Replaces +/= with URL-safe characters so the param looks like an opaque token.
215
+ */
216
+ export function encodeUnmask(path: string): string {
217
+ return btoa(path).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
218
+ }
219
+
220
+ /**
221
+ * Decode a URL-safe base64 _unmask value back to the original path.
222
+ */
223
+ export function decodeUnmask(encoded: string): string {
224
+ const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
225
+ return atob(base64)
226
+ }
227
+
228
+ /**
229
+ * The separator used in the pathname postfix for route unmasking.
230
+ * e.g. /photos/3__L3Bob3Rvcy8zL21vZGFs
231
+ */
232
+ const UNMASK_SEPARATOR = '__'
233
+
234
+ /**
235
+ * Parse the base64-encoded unmask suffix from a pathname.
236
+ * Looks for `__` separator in the last path segment.
237
+ * Returns the decoded actual path, or null if no unmask suffix found.
238
+ *
239
+ * @example
240
+ * parseUnmaskFromPath('/photos/3__L3Bob3Rvcy8zL21vZGFs') // '/photos/3/modal'
241
+ * parseUnmaskFromPath('/photos/3') // null
242
+ */
243
+ export function parseUnmaskFromPath(pathname: string): string | null {
244
+ const lastSlash = pathname.lastIndexOf('/')
245
+ const lastSegment = pathname.slice(lastSlash + 1)
246
+ const separatorIndex = lastSegment.indexOf(UNMASK_SEPARATOR)
247
+
248
+ if (separatorIndex === -1) return null
249
+
250
+ const encoded = lastSegment.slice(separatorIndex + UNMASK_SEPARATOR.length)
251
+ if (!encoded) return null
252
+
253
+ try {
254
+ return decodeUnmask(encoded)
255
+ } catch {
256
+ return null
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Finds a matching route mask for a given pathname.
262
+ * Returns the mask result to apply, or undefined if no match.
263
+ */
264
+ export function findMatchingMask(
265
+ pathname: string,
266
+ routeMasks: RouteMask[]
267
+ ):
268
+ | {
269
+ maskedPath: string
270
+ unmaskOnReload: boolean
271
+ useSearchParam: boolean
272
+ actualPath: string
273
+ }
274
+ | undefined {
275
+ for (const mask of routeMasks) {
276
+ const matchedParams = matchRouteMask(pathname, mask)
277
+ if (matchedParams) {
278
+ const maskedPath = buildMaskedPath(mask, matchedParams)
279
+ const useSearchParam = mask.useSearchParam ?? false
280
+ return {
281
+ // If useSearchParam is true, append base64-encoded actual path as pathname postfix
282
+ // e.g. /photos/3__L3Bob3Rvcy8zL21vZGFs
283
+ maskedPath: useSearchParam
284
+ ? `${maskedPath}${UNMASK_SEPARATOR}${encodeUnmask(pathname)}`
285
+ : maskedPath,
286
+ unmaskOnReload: mask.unmaskOnReload ?? false,
287
+ useSearchParam,
288
+ actualPath: pathname,
289
+ }
290
+ }
291
+ }
292
+ return undefined
293
+ }
@@ -51,11 +51,43 @@ import { preloadRouteModules } from './useViteRoutes'
51
51
  import { getNavigateAction } from './utils/getNavigateAction'
52
52
  import { setClientMatches } from '../useMatches'
53
53
  import type { RouteMatch } from '../useMatches'
54
+ import { type RouteMask, findMatchingMask } from './routeMask'
54
55
 
55
56
  // Module-scoped variables
56
57
  export let routeNode: RouteNode | null = null
57
58
  export let rootComponent: ComponentType
58
59
 
60
+ // Route masks for automatic URL masking
61
+ let routeMasks: RouteMask[] = []
62
+
63
+ /**
64
+ * Set route masks for automatic URL masking during navigation.
65
+ * Route masks transform URLs displayed in the browser without changing the actual route.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * import { setRouteMasks, createRouteMask } from 'one'
70
+ *
71
+ * setRouteMasks([
72
+ * createRouteMask({
73
+ * from: '/photos/[id]/modal',
74
+ * to: '/photos/[id]',
75
+ * unmaskOnReload: true,
76
+ * }),
77
+ * ])
78
+ * ```
79
+ */
80
+ export function setRouteMasks(masks: RouteMask[]) {
81
+ routeMasks = masks
82
+ }
83
+
84
+ /**
85
+ * Get the current route masks.
86
+ */
87
+ export function getRouteMasks(): RouteMask[] {
88
+ return routeMasks
89
+ }
90
+
59
91
  // Global registry for protected routes
60
92
  // Key: contextKey (e.g., '/protected-test'), Value: Set of protected route names
61
93
  const protectedRouteRegistry = new Map<string, Set<string>>()
@@ -1002,8 +1034,28 @@ export async function linkTo(
1002
1034
  hashes[rootState.key] = href.slice(hash)
1003
1035
  }
1004
1036
 
1037
+ // Auto-apply route mask if no explicit mask provided and routeMasks are configured
1038
+ let finalOptions = options ?? null
1039
+ if (!finalOptions?.mask && routeMasks.length > 0) {
1040
+ // Extract pathname from href (remove search params and hash)
1041
+ const pathname = extractPathnameFromHref(href)
1042
+ const maskResult = findMatchingMask(pathname, routeMasks)
1043
+ if (maskResult) {
1044
+ finalOptions = {
1045
+ ...finalOptions,
1046
+ mask: {
1047
+ href: maskResult.maskedPath,
1048
+ unmaskOnReload: maskResult.unmaskOnReload,
1049
+ },
1050
+ }
1051
+ if (process.env.ONE_DEBUG_ROUTER) {
1052
+ console.info(`[one] 🎭 Auto-masked ${pathname} → ${maskResult.maskedPath}`)
1053
+ }
1054
+ }
1055
+ }
1056
+
1005
1057
  // a bit hacky until can figure out a reliable way to tie it to the state
1006
- nextOptions = options ?? null
1058
+ nextOptions = finalOptions
1007
1059
 
1008
1060
  startTransition(() => {
1009
1061
  const action = getNavigateAction(state, rootState, event)