one 1.16.8 → 1.16.9

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 (276) hide show
  1. package/dist/cjs/Root.cjs +7 -6
  2. package/dist/cjs/Root.native.js +7 -6
  3. package/dist/cjs/Root.native.js.map +1 -1
  4. package/dist/cjs/babel-plugins/one-router-metro.cjs +5 -0
  5. package/dist/cjs/babel-plugins/one-router-metro.native.js +6 -0
  6. package/dist/cjs/babel-plugins/one-router-metro.native.js.map +1 -1
  7. package/dist/cjs/babel-plugins/one-router-metro.test.cjs +20 -0
  8. package/dist/cjs/babel-plugins/one-router-metro.test.native.js +20 -0
  9. package/dist/cjs/babel-plugins/one-router-metro.test.native.js.map +1 -1
  10. package/dist/cjs/cli/build.cjs +16 -1
  11. package/dist/cjs/cli/build.native.js +16 -1
  12. package/dist/cjs/cli/build.native.js.map +1 -1
  13. package/dist/cjs/cli/dev.cjs +12 -2
  14. package/dist/cjs/cli/dev.native.js +12 -2
  15. package/dist/cjs/cli/dev.native.js.map +1 -1
  16. package/dist/cjs/cli/install-error-handlers.cjs +132 -0
  17. package/dist/cjs/cli/install-error-handlers.native.js +144 -0
  18. package/dist/cjs/cli/install-error-handlers.native.js.map +1 -0
  19. package/dist/cjs/createApp.cjs +3 -0
  20. package/dist/cjs/createApp.native.js +1 -0
  21. package/dist/cjs/createApp.native.js.map +1 -1
  22. package/dist/cjs/fork/extractPathFromURL.cjs +19 -0
  23. package/dist/cjs/fork/extractPathFromURL.native.js +25 -0
  24. package/dist/cjs/fork/extractPathFromURL.native.js.map +1 -1
  25. package/dist/cjs/fork/extractPathFromURL.test.cjs +37 -0
  26. package/dist/cjs/fork/extractPathFromURL.test.native.js +40 -0
  27. package/dist/cjs/fork/extractPathFromURL.test.native.js.map +1 -0
  28. package/dist/cjs/headless.cjs +1 -0
  29. package/dist/cjs/headless.native.js +1 -0
  30. package/dist/cjs/headless.native.js.map +1 -1
  31. package/dist/cjs/index.cjs +4 -0
  32. package/dist/cjs/index.native.js +4 -0
  33. package/dist/cjs/index.native.js.map +1 -1
  34. package/dist/cjs/link/getLinking.cjs +48 -0
  35. package/dist/cjs/link/getLinking.native.js +54 -0
  36. package/dist/cjs/link/getLinking.native.js.map +1 -0
  37. package/dist/cjs/link/linking.cjs +4 -0
  38. package/dist/cjs/link/linking.native.js +9 -0
  39. package/dist/cjs/link/linking.native.js.map +1 -1
  40. package/dist/cjs/metro-config/getViteMetroPluginOptions.cjs +2 -0
  41. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js +2 -0
  42. package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  43. package/dist/cjs/router/Route.cjs +1 -1
  44. package/dist/cjs/router/Route.native.js +1 -1
  45. package/dist/cjs/router/Route.native.js.map +1 -1
  46. package/dist/cjs/router/getLinkingConfig.cjs +13 -2
  47. package/dist/cjs/router/getLinkingConfig.native.js +14 -2
  48. package/dist/cjs/router/getLinkingConfig.native.js.map +1 -1
  49. package/dist/cjs/router/getLinkingConfig.test.cjs +41 -0
  50. package/dist/cjs/router/getLinkingConfig.test.native.js +44 -0
  51. package/dist/cjs/router/getLinkingConfig.test.native.js.map +1 -0
  52. package/dist/cjs/router/getRouteInfo.cjs +1 -1
  53. package/dist/cjs/router/getRouteInfo.native.js +2 -2
  54. package/dist/cjs/router/getRouteInfo.native.js.map +1 -1
  55. package/dist/cjs/router/linkingConfig.cjs +34 -9
  56. package/dist/cjs/router/linkingConfig.native.js +35 -9
  57. package/dist/cjs/router/linkingConfig.native.js.map +1 -1
  58. package/dist/cjs/router/router.cjs +6 -6
  59. package/dist/cjs/router/router.native.js +6 -6
  60. package/dist/cjs/router/router.native.js.map +1 -1
  61. package/dist/cjs/router/sitemap.cjs +65 -0
  62. package/dist/cjs/router/sitemap.native.js +75 -0
  63. package/dist/cjs/router/sitemap.native.js.map +1 -0
  64. package/dist/cjs/router/sitemap.test.cjs +34 -0
  65. package/dist/cjs/router/sitemap.test.native.js +37 -0
  66. package/dist/cjs/router/sitemap.test.native.js.map +1 -0
  67. package/dist/cjs/router/useInitializeOneRouter.cjs +5 -5
  68. package/dist/cjs/router/useInitializeOneRouter.native.js +5 -5
  69. package/dist/cjs/router/useInitializeOneRouter.native.js.map +1 -1
  70. package/dist/cjs/serve.cjs +14 -1
  71. package/dist/cjs/serve.native.js +14 -1
  72. package/dist/cjs/serve.native.js.map +1 -1
  73. package/dist/cjs/views/Navigator.cjs +1 -1
  74. package/dist/cjs/views/Navigator.native.js +1 -1
  75. package/dist/cjs/views/Navigator.native.js.map +1 -1
  76. package/dist/cjs/vite/one.cjs +2 -0
  77. package/dist/cjs/vite/one.native.js +4 -2
  78. package/dist/cjs/vite/one.native.js.map +1 -1
  79. package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +4 -0
  80. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +6 -0
  81. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  82. package/dist/cjs/vite/plugins/virtualEntryPlugin.test.cjs +13 -0
  83. package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js +13 -0
  84. package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
  85. package/dist/esm/Root.mjs +8 -7
  86. package/dist/esm/Root.mjs.map +1 -1
  87. package/dist/esm/Root.native.js +8 -7
  88. package/dist/esm/Root.native.js.map +1 -1
  89. package/dist/esm/babel-plugins/one-router-metro.mjs +5 -0
  90. package/dist/esm/babel-plugins/one-router-metro.mjs.map +1 -1
  91. package/dist/esm/babel-plugins/one-router-metro.native.js +6 -0
  92. package/dist/esm/babel-plugins/one-router-metro.native.js.map +1 -1
  93. package/dist/esm/babel-plugins/one-router-metro.test.mjs +20 -0
  94. package/dist/esm/babel-plugins/one-router-metro.test.mjs.map +1 -1
  95. package/dist/esm/babel-plugins/one-router-metro.test.native.js +20 -0
  96. package/dist/esm/babel-plugins/one-router-metro.test.native.js.map +1 -1
  97. package/dist/esm/cli/build.mjs +16 -1
  98. package/dist/esm/cli/build.mjs.map +1 -1
  99. package/dist/esm/cli/build.native.js +16 -1
  100. package/dist/esm/cli/build.native.js.map +1 -1
  101. package/dist/esm/cli/dev.mjs +12 -2
  102. package/dist/esm/cli/dev.mjs.map +1 -1
  103. package/dist/esm/cli/dev.native.js +12 -2
  104. package/dist/esm/cli/dev.native.js.map +1 -1
  105. package/dist/esm/cli/install-error-handlers.mjs +105 -0
  106. package/dist/esm/cli/install-error-handlers.mjs.map +1 -0
  107. package/dist/esm/cli/install-error-handlers.native.js +114 -0
  108. package/dist/esm/cli/install-error-handlers.native.js.map +1 -0
  109. package/dist/esm/createApp.mjs +3 -0
  110. package/dist/esm/createApp.mjs.map +1 -1
  111. package/dist/esm/createApp.native.js +1 -0
  112. package/dist/esm/createApp.native.js.map +1 -1
  113. package/dist/esm/fork/extractPathFromURL.mjs +19 -0
  114. package/dist/esm/fork/extractPathFromURL.mjs.map +1 -1
  115. package/dist/esm/fork/extractPathFromURL.native.js +25 -0
  116. package/dist/esm/fork/extractPathFromURL.native.js.map +1 -1
  117. package/dist/esm/fork/extractPathFromURL.test.mjs +38 -0
  118. package/dist/esm/fork/extractPathFromURL.test.mjs.map +1 -0
  119. package/dist/esm/fork/extractPathFromURL.test.native.js +38 -0
  120. package/dist/esm/fork/extractPathFromURL.test.native.js.map +1 -0
  121. package/dist/esm/headless.mjs +1 -0
  122. package/dist/esm/headless.mjs.map +1 -1
  123. package/dist/esm/headless.native.js +1 -0
  124. package/dist/esm/headless.native.js.map +1 -1
  125. package/dist/esm/index.js +3 -1
  126. package/dist/esm/index.js.map +1 -1
  127. package/dist/esm/index.mjs +3 -1
  128. package/dist/esm/index.mjs.map +1 -1
  129. package/dist/esm/index.native.js +3 -1
  130. package/dist/esm/index.native.js.map +1 -1
  131. package/dist/esm/link/getLinking.mjs +22 -0
  132. package/dist/esm/link/getLinking.mjs.map +1 -0
  133. package/dist/esm/link/getLinking.native.js +25 -0
  134. package/dist/esm/link/getLinking.native.js.map +1 -0
  135. package/dist/esm/link/linking.mjs +4 -1
  136. package/dist/esm/link/linking.mjs.map +1 -1
  137. package/dist/esm/link/linking.native.js +9 -1
  138. package/dist/esm/link/linking.native.js.map +1 -1
  139. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs +2 -0
  140. package/dist/esm/metro-config/getViteMetroPluginOptions.mjs.map +1 -1
  141. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js +2 -0
  142. package/dist/esm/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
  143. package/dist/esm/router/Route.mjs +2 -2
  144. package/dist/esm/router/Route.mjs.map +1 -1
  145. package/dist/esm/router/Route.native.js +2 -2
  146. package/dist/esm/router/Route.native.js.map +1 -1
  147. package/dist/esm/router/getLinkingConfig.mjs +14 -4
  148. package/dist/esm/router/getLinkingConfig.mjs.map +1 -1
  149. package/dist/esm/router/getLinkingConfig.native.js +15 -4
  150. package/dist/esm/router/getLinkingConfig.native.js.map +1 -1
  151. package/dist/esm/router/getLinkingConfig.test.mjs +42 -0
  152. package/dist/esm/router/getLinkingConfig.test.mjs.map +1 -0
  153. package/dist/esm/router/getLinkingConfig.test.native.js +42 -0
  154. package/dist/esm/router/getLinkingConfig.test.native.js.map +1 -0
  155. package/dist/esm/router/getRouteInfo.mjs +2 -2
  156. package/dist/esm/router/getRouteInfo.mjs.map +1 -1
  157. package/dist/esm/router/getRouteInfo.native.js +3 -3
  158. package/dist/esm/router/getRouteInfo.native.js.map +1 -1
  159. package/dist/esm/router/linkingConfig.mjs +35 -10
  160. package/dist/esm/router/linkingConfig.mjs.map +1 -1
  161. package/dist/esm/router/linkingConfig.native.js +36 -10
  162. package/dist/esm/router/linkingConfig.native.js.map +1 -1
  163. package/dist/esm/router/router.mjs +7 -7
  164. package/dist/esm/router/router.mjs.map +1 -1
  165. package/dist/esm/router/router.native.js +7 -7
  166. package/dist/esm/router/router.native.js.map +1 -1
  167. package/dist/esm/router/sitemap.mjs +39 -0
  168. package/dist/esm/router/sitemap.mjs.map +1 -0
  169. package/dist/esm/router/sitemap.native.js +46 -0
  170. package/dist/esm/router/sitemap.native.js.map +1 -0
  171. package/dist/esm/router/sitemap.test.mjs +35 -0
  172. package/dist/esm/router/sitemap.test.mjs.map +1 -0
  173. package/dist/esm/router/sitemap.test.native.js +35 -0
  174. package/dist/esm/router/sitemap.test.native.js.map +1 -0
  175. package/dist/esm/router/useInitializeOneRouter.mjs +5 -5
  176. package/dist/esm/router/useInitializeOneRouter.mjs.map +1 -1
  177. package/dist/esm/router/useInitializeOneRouter.native.js +5 -5
  178. package/dist/esm/router/useInitializeOneRouter.native.js.map +1 -1
  179. package/dist/esm/serve.mjs +14 -1
  180. package/dist/esm/serve.mjs.map +1 -1
  181. package/dist/esm/serve.native.js +14 -1
  182. package/dist/esm/serve.native.js.map +1 -1
  183. package/dist/esm/views/Navigator.mjs +2 -2
  184. package/dist/esm/views/Navigator.mjs.map +1 -1
  185. package/dist/esm/views/Navigator.native.js +2 -2
  186. package/dist/esm/views/Navigator.native.js.map +1 -1
  187. package/dist/esm/vite/one.mjs +2 -0
  188. package/dist/esm/vite/one.mjs.map +1 -1
  189. package/dist/esm/vite/one.native.js +4 -2
  190. package/dist/esm/vite/one.native.js.map +1 -1
  191. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +4 -0
  192. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
  193. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +6 -0
  194. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  195. package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs +13 -0
  196. package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs.map +1 -1
  197. package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js +13 -0
  198. package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
  199. package/metro-entry.js +3 -0
  200. package/package.json +9 -9
  201. package/src/Root.tsx +15 -6
  202. package/src/babel-plugins/one-router-metro.test.ts +33 -0
  203. package/src/babel-plugins/one-router-metro.ts +9 -0
  204. package/src/cli/build.ts +25 -1
  205. package/src/cli/dev.ts +18 -5
  206. package/src/cli/install-error-handlers.ts +172 -0
  207. package/src/createApp.native.tsx +1 -0
  208. package/src/createApp.tsx +5 -0
  209. package/src/fork/extractPathFromURL.test.ts +90 -0
  210. package/src/fork/extractPathFromURL.ts +28 -0
  211. package/src/headless.tsx +3 -0
  212. package/src/index.ts +2 -0
  213. package/src/link/getLinking.ts +56 -0
  214. package/src/link/linking.native.ts +7 -0
  215. package/src/link/linking.ts +4 -0
  216. package/src/metro-config/getViteMetroPluginOptions.ts +3 -0
  217. package/src/router/Route.tsx +2 -2
  218. package/src/router/getLinkingConfig.test.ts +63 -0
  219. package/src/router/getLinkingConfig.ts +27 -3
  220. package/src/router/getRouteInfo.ts +2 -2
  221. package/src/router/linkingConfig.ts +52 -8
  222. package/src/router/router.ts +12 -7
  223. package/src/router/sitemap.test.ts +50 -0
  224. package/src/router/sitemap.ts +66 -0
  225. package/src/router/useInitializeOneRouter.ts +7 -5
  226. package/src/serve.ts +22 -1
  227. package/src/views/Navigator.tsx +2 -2
  228. package/src/vite/one.ts +2 -0
  229. package/src/vite/plugins/virtualEntryPlugin.test.ts +17 -0
  230. package/src/vite/plugins/virtualEntryPlugin.ts +8 -0
  231. package/src/vite/types.ts +23 -0
  232. package/types/Root.d.ts +2 -0
  233. package/types/Root.d.ts.map +1 -1
  234. package/types/babel-plugins/one-router-metro.d.ts +1 -0
  235. package/types/babel-plugins/one-router-metro.d.ts.map +1 -1
  236. package/types/cli/build.d.ts.map +1 -1
  237. package/types/cli/dev.d.ts.map +1 -1
  238. package/types/cli/install-error-handlers.d.ts +4 -0
  239. package/types/cli/install-error-handlers.d.ts.map +1 -0
  240. package/types/createApp.d.ts +2 -0
  241. package/types/createApp.d.ts.map +1 -1
  242. package/types/createApp.native.d.ts.map +1 -1
  243. package/types/fork/extractPathFromURL.d.ts.map +1 -1
  244. package/types/fork/extractPathFromURL.test.d.ts +2 -0
  245. package/types/fork/extractPathFromURL.test.d.ts.map +1 -0
  246. package/types/headless.d.ts +2 -0
  247. package/types/headless.d.ts.map +1 -1
  248. package/types/index.d.ts +2 -0
  249. package/types/index.d.ts.map +1 -1
  250. package/types/link/getLinking.d.ts +22 -0
  251. package/types/link/getLinking.d.ts.map +1 -0
  252. package/types/link/linking.d.ts +1 -0
  253. package/types/link/linking.d.ts.map +1 -1
  254. package/types/link/linking.native.d.ts +1 -0
  255. package/types/link/linking.native.d.ts.map +1 -1
  256. package/types/metro-config/getViteMetroPluginOptions.d.ts +2 -1
  257. package/types/metro-config/getViteMetroPluginOptions.d.ts.map +1 -1
  258. package/types/router/getLinkingConfig.d.ts +3 -1
  259. package/types/router/getLinkingConfig.d.ts.map +1 -1
  260. package/types/router/getLinkingConfig.test.d.ts +2 -0
  261. package/types/router/getLinkingConfig.test.d.ts.map +1 -0
  262. package/types/router/linkingConfig.d.ts +5 -4
  263. package/types/router/linkingConfig.d.ts.map +1 -1
  264. package/types/router/router.d.ts +2 -1
  265. package/types/router/router.d.ts.map +1 -1
  266. package/types/router/sitemap.d.ts +14 -0
  267. package/types/router/sitemap.d.ts.map +1 -0
  268. package/types/router/sitemap.test.d.ts +2 -0
  269. package/types/router/sitemap.test.d.ts.map +1 -0
  270. package/types/router/useInitializeOneRouter.d.ts +2 -1
  271. package/types/router/useInitializeOneRouter.d.ts.map +1 -1
  272. package/types/serve.d.ts.map +1 -1
  273. package/types/vite/one.d.ts.map +1 -1
  274. package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
  275. package/types/vite/types.d.ts +23 -0
  276. package/types/vite/types.d.ts.map +1 -1
package/src/headless.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import './setup'
2
2
 
3
+ import type { OneLinkingConfig } from './link/getLinking'
3
4
  import { Root } from './Root'
4
5
  import { resolveClientLoader } from './clientLoaderResolver'
5
6
  import { render } from './render'
@@ -13,6 +14,7 @@ export type CreateHeadlessAppProps = {
13
14
  routerRoot: string
14
15
  path?: string
15
16
  flags?: One.Flags
17
+ linking?: OneLinkingConfig
16
18
  getSetupPromise?: () => Promise<unknown>
17
19
  }
18
20
 
@@ -34,6 +36,7 @@ export function createApp(options: CreateHeadlessAppProps) {
34
36
  flags={options.flags}
35
37
  routes={options.routes}
36
38
  routerRoot={options.routerRoot}
39
+ linking={options.linking}
37
40
  path={
38
41
  options.path || (typeof window !== 'undefined' ? window.location.href : '/')
39
42
  }
package/src/index.ts CHANGED
@@ -77,7 +77,9 @@ export {
77
77
  useSegments,
78
78
  useUnstableGlobalHref,
79
79
  } from './hooks'
80
+ export { useSitemap, type SitemapType } from './router/sitemap'
80
81
  export { href } from './href'
82
+ export { getLinking, type OneLinkingConfig } from './link/getLinking'
81
83
  // components
82
84
  export { Stack } from './layouts/Stack'
83
85
  export { Tabs } from './layouts/Tabs'
@@ -0,0 +1,56 @@
1
+ export type OneLinkingConfig = {
2
+ /**
3
+ * Custom app scheme or schemes. Each scheme is expanded to double- and
4
+ * triple-slashed URL prefixes.
5
+ */
6
+ scheme?: string | string[]
7
+ /**
8
+ * Fully qualified URL prefixes to strip before matching routes.
9
+ *
10
+ * For host-bearing custom scheme URLs, include the host:
11
+ * `myapp://app`.
12
+ */
13
+ prefixes?: string[]
14
+ filter?: (url: string) => boolean
15
+ }
16
+
17
+ export type NormalizedOneLinkingConfig = {
18
+ prefixes: string[]
19
+ filter?: (url: string) => boolean
20
+ }
21
+
22
+ export function getLinking(config: OneLinkingConfig = {}): OneLinkingConfig {
23
+ return config
24
+ }
25
+
26
+ export function normalizeLinkingConfig(
27
+ config: OneLinkingConfig | undefined,
28
+ defaultPrefixes: string[] = []
29
+ ): NormalizedOneLinkingConfig {
30
+ // merge: defaults from the native manifest combine with whatever the user
31
+ // provides via scheme/prefixes, so URLs from any registered scheme are
32
+ // recognized even when the user only mentions a subset
33
+ const merged = [
34
+ ...defaultPrefixes,
35
+ ...getSchemePrefixes(config?.scheme),
36
+ ...(config?.prefixes ?? []),
37
+ ]
38
+
39
+ return {
40
+ prefixes: dedupe(merged),
41
+ filter: config?.filter,
42
+ }
43
+ }
44
+
45
+ function getSchemePrefixes(scheme: string | string[] | undefined): string[] {
46
+ const schemes = Array.isArray(scheme) ? scheme : scheme ? [scheme] : []
47
+
48
+ return schemes.flatMap((value) => {
49
+ const normalized = value.replace(/:\/+$/, '')
50
+ return [`${normalized}://`, `${normalized}:///`]
51
+ })
52
+ }
53
+
54
+ function dedupe(values: string[]): string[] {
55
+ return [...new Set(values.filter(Boolean))]
56
+ }
@@ -62,6 +62,13 @@ export function getRootURL(): string {
62
62
  return _rootURL
63
63
  }
64
64
 
65
+ export function getDefaultLinkingPrefixes(): string[] {
66
+ return Linking.collectManifestSchemes?.().flatMap((scheme) => {
67
+ const normalized = scheme.replace(/:\/+$/, '')
68
+ return [`${normalized}://`, `${normalized}:///`]
69
+ }) ?? []
70
+ }
71
+
65
72
  export function addEventListener(listener: (url: string) => void) {
66
73
  let callback: (({ url }: { url: string }) => void) | undefined
67
74
 
@@ -12,6 +12,10 @@ export function getRootURL(): string {
12
12
  return '/'
13
13
  }
14
14
 
15
+ export function getDefaultLinkingPrefixes(): string[] {
16
+ return []
17
+ }
18
+
15
19
  export function addEventListener(listener: (url: string) => void) {
16
20
  if (typeof window === 'undefined') {
17
21
  return () => {}
@@ -20,12 +20,14 @@ export function getViteMetroPluginOptions({
20
20
  projectRoot,
21
21
  relativeRouterRoot,
22
22
  ignoredRouteFiles,
23
+ linking,
23
24
  userDefaultConfigOverrides,
24
25
  setupFile,
25
26
  }: {
26
27
  projectRoot: string
27
28
  relativeRouterRoot: string
28
29
  ignoredRouteFiles?: Array<`**/*${string}`>
30
+ linking?: unknown
29
31
  userDefaultConfigOverrides?: NonNullable<
30
32
  Parameters<typeof metroPlugin>[0]
31
33
  >['defaultConfigOverrides']
@@ -236,6 +238,7 @@ export function getViteMetroPluginOptions({
236
238
  ),
237
239
  ONE_ROUTER_ROOT_FOLDER_NAME: relativeRouterRoot,
238
240
  ONE_ROUTER_REQUIRE_CONTEXT_REGEX_STRING: routerRequireContextRegexString,
241
+ ONE_ROUTER_LINKING_CONFIG: linking,
239
242
  ONE_SETUP_FILE_NATIVE: (() => {
240
243
  if (!setupFile) return undefined
241
244
  // Extract native setup file path
@@ -4,7 +4,7 @@ import type { ErrorBoundaryProps } from '../views/Try'
4
4
  import type { LoaderProps } from '../types'
5
5
  import type { One } from '../vite/types'
6
6
  import type { ParamValidator, RouteValidationFn } from '../validateParams'
7
- import { getLinking } from './linkingConfig'
7
+ import { getResolvedLinking } from './linkingConfig'
8
8
  import { getContextKey } from './matchers'
9
9
  import { mergeDynamicParams } from './params'
10
10
  import { routeInfo } from './router'
@@ -153,7 +153,7 @@ function getParamsFromCurrentUrl(route?: {
153
153
  path?: string
154
154
  params?: Record<string, string | undefined>
155
155
  }): Record<string, any> | undefined {
156
- const linking = getLinking()
156
+ const linking = getResolvedLinking()
157
157
  if (!linking?.getStateFromPath) return undefined
158
158
  const path =
159
159
  routeInfo?.unstable_globalHref ||
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { normalizeLinkingConfig } from '../link/getLinking'
3
+ import { getRoutes } from './getRoutes'
4
+ import { getLinkingConfig } from './getLinkingConfig'
5
+ import { getMockContext } from '../testing-utils'
6
+
7
+ describe('getLinkingConfig', () => {
8
+ it('expands configured schemes into prefixes', () => {
9
+ const routes = getRoutes(getMockContext(['_layout.tsx', 'index.tsx']))!
10
+ const linking = getLinkingConfig(routes, true, {
11
+ scheme: 'threepunchconvo',
12
+ })
13
+
14
+ expect(linking.prefixes).toEqual([
15
+ 'threepunchconvo://',
16
+ 'threepunchconvo:///',
17
+ ])
18
+ })
19
+
20
+ it('merges explicit prefixes with scheme-derived prefixes', () => {
21
+ const routes = getRoutes(getMockContext(['_layout.tsx', 'index.tsx']))!
22
+ const linking = getLinkingConfig(routes, true, {
23
+ scheme: 'threepunchconvo',
24
+ prefixes: ['threepunchconvo://app'],
25
+ })
26
+
27
+ expect(linking.prefixes).toEqual([
28
+ 'threepunchconvo://',
29
+ 'threepunchconvo:///',
30
+ 'threepunchconvo://app',
31
+ ])
32
+ })
33
+ })
34
+
35
+ describe('normalizeLinkingConfig', () => {
36
+ it('merges manifest-default prefixes with user-supplied scheme/prefixes', () => {
37
+ const result = normalizeLinkingConfig(
38
+ { scheme: 'foo', prefixes: ['https://example.test/app'] },
39
+ ['bar://', 'bar:///']
40
+ )
41
+
42
+ expect(result.prefixes).toEqual([
43
+ 'bar://',
44
+ 'bar:///',
45
+ 'foo://',
46
+ 'foo:///',
47
+ 'https://example.test/app',
48
+ ])
49
+ })
50
+
51
+ it('falls back to manifest-default prefixes when nothing is configured', () => {
52
+ const result = normalizeLinkingConfig(undefined, ['bar://', 'bar:///'])
53
+ expect(result.prefixes).toEqual(['bar://', 'bar:///'])
54
+ })
55
+
56
+ it('dedupes overlapping defaults and configured prefixes', () => {
57
+ const result = normalizeLinkingConfig(
58
+ { scheme: 'foo' },
59
+ ['foo://', 'foo:///']
60
+ )
61
+ expect(result.prefixes).toEqual(['foo://', 'foo:///'])
62
+ })
63
+ })
@@ -3,10 +3,16 @@ import type { State } from '../fork/getPathFromState'
3
3
  import { getReactNavigationConfig, type Screen } from '../getReactNavigationConfig'
4
4
  import {
5
5
  addEventListener,
6
+ getDefaultLinkingPrefixes,
6
7
  getInitialURL,
7
8
  getPathFromState,
8
9
  getStateFromPath,
9
10
  } from '../link/linking'
11
+ import {
12
+ normalizeLinkingConfig,
13
+ type OneLinkingConfig,
14
+ } from '../link/getLinking'
15
+ import { evictOldest } from '../utils/evictOldest'
10
16
  import type { RouteNode } from './Route'
11
17
 
12
18
  export function getNavigationConfig(
@@ -23,10 +29,19 @@ export type OneLinkingOptions = LinkingOptions<object> & {
23
29
  getPathFromState?: typeof getPathFromState
24
30
  }
25
31
 
26
- export function getLinkingConfig(routes: RouteNode, metaOnly = true): OneLinkingOptions {
32
+ export function getLinkingConfig(
33
+ routes: RouteNode,
34
+ metaOnly = true,
35
+ linking?: OneLinkingConfig
36
+ ): OneLinkingOptions {
27
37
  const config = getNavigationConfig(routes, metaOnly)
38
+ const resolvedLinking = normalizeLinkingConfig(
39
+ linking,
40
+ getDefaultLinkingPrefixes()
41
+ )
28
42
  return {
29
- prefixes: [],
43
+ prefixes: resolvedLinking.prefixes,
44
+ filter: resolvedLinking.filter,
30
45
  // @ts-expect-error
31
46
  config,
32
47
  // A custom getInitialURL is used on native to ensure the app always starts at
@@ -53,7 +68,15 @@ export function getLinkingConfig(routes: RouteNode, metaOnly = true): OneLinking
53
68
 
54
69
  export const stateCache = new Map<string, any>()
55
70
 
56
- /** We can reduce work by memoizing the state by the pathname. This only works because the options (linking config) theoretically never change. */
71
+ const STATE_CACHE_THRESHOLD = 5000
72
+ const STATE_CACHE_EVICTION = 1000
73
+
74
+ export function clearStateCache() {
75
+ stateCache.clear()
76
+ }
77
+
78
+ /** memoize getStateFromPath by pathname. cache is cleared when the route tree
79
+ * or linking config changes (see ensureBaseLinkingConfig in linkingConfig.ts). */
57
80
  function getStateFromPathMemoized(
58
81
  path: string,
59
82
  options: Parameters<typeof getStateFromPath>[1]
@@ -63,6 +86,7 @@ function getStateFromPathMemoized(
63
86
  return cached
64
87
  }
65
88
  const result = getStateFromPath(path, options)
89
+ evictOldest(stateCache, STATE_CACHE_THRESHOLD, STATE_CACHE_EVICTION)
66
90
  stateCache.set(path, result)
67
91
  return result
68
92
  }
@@ -4,14 +4,14 @@ import { stripBaseUrl } from '../fork/getStateFromPath-mods'
4
4
  import type { OneRouter } from '../interfaces/router'
5
5
  import { getNormalizedStatePath, type UrlObject } from './getNormalizedStatePath'
6
6
  import { isIndexPath } from './isIndexPath'
7
- import { getLinking } from './linkingConfig'
7
+ import { getResolvedLinking } from './linkingConfig'
8
8
 
9
9
  export function getRouteInfo(state: OneRouter.ResultState) {
10
10
  return getRouteInfoFromState(
11
11
  (state: Parameters<typeof originalGetPathFromState>[0], asPath: boolean) => {
12
12
  return getPathDataFromState(state, {
13
13
  screens: [],
14
- ...getLinking()?.config,
14
+ ...getResolvedLinking()?.config,
15
15
  preserveDynamicRoutes: asPath,
16
16
  preserveGroups: asPath,
17
17
  })
@@ -1,7 +1,9 @@
1
1
  import { isWebClient } from '../constants'
2
2
  import type { OneRouter } from '../interfaces/router'
3
+ import type { OneLinkingConfig } from '../link/getLinking'
3
4
  import { evictOldest } from '../utils/evictOldest'
4
5
  import {
6
+ clearStateCache,
5
7
  getLinkingConfig as createLinkingConfig,
6
8
  type OneLinkingOptions,
7
9
  } from './getLinkingConfig'
@@ -12,12 +14,13 @@ let linkingConfig: OneLinkingOptions | undefined
12
14
  // cache the base linking config (route-tree dependent, not URL-dependent)
13
15
  let cachedBaseLinkingConfig: OneLinkingOptions | undefined
14
16
  let cachedRouteNodeForLinking: RouteNode | null = null
17
+ let cachedLinkingConfigKey = ''
15
18
 
16
19
  // cache getStateFromPath results by path for SSR performance
17
20
  // same path always produces the same navigation state (route tree is static in prod)
18
21
  const ssrStateCache = new Map<string, OneRouter.ResultState | undefined>()
19
22
 
20
- export function getLinking() {
23
+ export function getResolvedLinking() {
21
24
  return linkingConfig
22
25
  }
23
26
 
@@ -33,13 +36,25 @@ export function resetLinking() {
33
36
  * Ensure the base linking config is initialized for a given route tree.
34
37
  * Does not set any per-request state.
35
38
  */
36
- export function ensureBaseLinkingConfig(routeNode: RouteNode | null) {
39
+ export function ensureBaseLinkingConfig(
40
+ routeNode: RouteNode | null,
41
+ linking?: OneLinkingConfig
42
+ ) {
43
+ const linkingConfigKey = getLinkingConfigKey(linking)
37
44
  if (
38
45
  routeNode &&
39
- (routeNode !== cachedRouteNodeForLinking || !cachedBaseLinkingConfig)
46
+ (routeNode !== cachedRouteNodeForLinking ||
47
+ linkingConfigKey !== cachedLinkingConfigKey ||
48
+ !cachedBaseLinkingConfig)
40
49
  ) {
41
- cachedBaseLinkingConfig = createLinkingConfig(routeNode)
50
+ // route tree or linking config changed — drop memoized navigation states
51
+ // so getStateFromPathMemoized doesn't return entries computed against
52
+ // the previous configuration
53
+ clearStateCache()
54
+ ssrStateCache.clear()
55
+ cachedBaseLinkingConfig = createLinkingConfig(routeNode, true, linking)
42
56
  cachedRouteNodeForLinking = routeNode
57
+ cachedLinkingConfigKey = linkingConfigKey
43
58
  }
44
59
  }
45
60
 
@@ -49,11 +64,12 @@ export function ensureBaseLinkingConfig(routeNode: RouteNode | null) {
49
64
  */
50
65
  export function getSSRInitialState(
51
66
  routeNode: RouteNode | null,
52
- initialLocation: URL
67
+ initialLocation: URL,
68
+ linking?: OneLinkingConfig
53
69
  ): OneRouter.ResultState | undefined {
54
70
  if (!routeNode) return undefined
55
71
 
56
- ensureBaseLinkingConfig(routeNode)
72
+ ensureBaseLinkingConfig(routeNode, linking)
57
73
  if (!cachedBaseLinkingConfig) return undefined
58
74
 
59
75
  const path = initialLocation.pathname + (initialLocation.search || '')
@@ -70,13 +86,14 @@ export function getSSRInitialState(
70
86
 
71
87
  export function setupLinking(
72
88
  routeNode: RouteNode | null,
73
- initialLocation?: URL
89
+ initialLocation?: URL,
90
+ linking?: OneLinkingConfig
74
91
  ): OneRouter.ResultState | undefined {
75
92
  let initialState: OneRouter.ResultState | undefined
76
93
 
77
94
  if (routeNode) {
78
95
  // reuse ensureBaseLinkingConfig to avoid duplicating cache logic
79
- ensureBaseLinkingConfig(routeNode)
96
+ ensureBaseLinkingConfig(routeNode, linking)
80
97
 
81
98
  // shallow copy so per-request mutations (getInitialURL) don't affect cache
82
99
  linkingConfig = { ...cachedBaseLinkingConfig! }
@@ -101,3 +118,30 @@ export function setupLinking(
101
118
 
102
119
  return initialState
103
120
  }
121
+
122
+ // each unique filter function gets a stable id so reference-equal filters
123
+ // share a cache key while distinct filters invalidate it
124
+ const filterIds = new WeakMap<NonNullable<OneLinkingConfig['filter']>, number>()
125
+ let filterIdCounter = 0
126
+ function getFilterId(filter: OneLinkingConfig['filter']) {
127
+ if (!filter) return 0
128
+ let id = filterIds.get(filter)
129
+ if (id === undefined) {
130
+ id = ++filterIdCounter
131
+ filterIds.set(filter, id)
132
+ }
133
+ return id
134
+ }
135
+
136
+ function getLinkingConfigKey(linking: OneLinkingConfig | undefined) {
137
+ // sort prefixes/schemes so ordering doesn't cause spurious cache misses
138
+ const schemes = Array.isArray(linking?.scheme)
139
+ ? [...linking.scheme].sort()
140
+ : (linking?.scheme ?? null)
141
+ const prefixes = linking?.prefixes ? [...linking.prefixes].sort() : null
142
+ return JSON.stringify({
143
+ scheme: schemes,
144
+ prefixes,
145
+ filterId: getFilterId(linking?.filter),
146
+ })
147
+ }
@@ -20,6 +20,7 @@ import { Platform } from 'react-native'
20
20
  import { devtoolsRegistry } from '../devtools/registry'
21
21
  import type { OneRouter } from '../interfaces/router'
22
22
  import { resolveHref } from '../link/href'
23
+ import type { OneLinkingConfig } from '../link/getLinking'
23
24
  import { openExternalURL } from '../link/openExternalURL'
24
25
  import { resolve } from '../link/path'
25
26
  import { checkBlocker } from '../useBlocker'
@@ -45,7 +46,7 @@ import type { UrlObject } from './getNormalizedStatePath'
45
46
  import { getRouteInfo } from './getRouteInfo'
46
47
  import { getRoutes } from './getRoutes'
47
48
  import { setLastAction } from './lastAction'
48
- import { getLinking, resetLinking, setupLinking } from './linkingConfig'
49
+ import { getResolvedLinking, resetLinking, setupLinking } from './linkingConfig'
49
50
  import type { RouteNode } from './Route'
50
51
  import { sortRoutes } from './sortRoutes'
51
52
  import { getQualifiedRouteComponent } from './useScreens'
@@ -214,7 +215,8 @@ let cachedContext: One.RouteContext | null = null
214
215
  export function initialize(
215
216
  context: One.RouteContext,
216
217
  ref: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>,
217
- initialLocation?: URL
218
+ initialLocation?: URL,
219
+ linking?: OneLinkingConfig
218
220
  ) {
219
221
  cleanUpState()
220
222
 
@@ -281,7 +283,7 @@ export function initialize(
281
283
  }
282
284
 
283
285
  navigationRef = ref as unknown as OneRouter.NavigationRef
284
- setupLinkingAndRouteInfo(initialLocation)
286
+ setupLinkingAndRouteInfo(initialLocation, linking)
285
287
  subscribeToNavigationChanges()
286
288
  }
287
289
 
@@ -297,8 +299,11 @@ function cleanUpState() {
297
299
  storeSubscribers.clear()
298
300
  }
299
301
 
300
- function setupLinkingAndRouteInfo(initialLocation?: URL) {
301
- initialState = setupLinking(routeNode, initialLocation)
302
+ function setupLinkingAndRouteInfo(
303
+ initialLocation?: URL,
304
+ linking?: OneLinkingConfig
305
+ ) {
306
+ initialState = setupLinking(routeNode, initialLocation, linking)
302
307
 
303
308
  // capture the original pathname before React Navigation's linking can modify it
304
309
  initialPathname =
@@ -557,7 +562,7 @@ function getSnapshot() {
557
562
  linkTo,
558
563
  routeNode,
559
564
  rootComponent,
560
- linking: getLinking(),
565
+ linking: getResolvedLinking(),
561
566
  hasAttemptedToHideSplash,
562
567
  initialState,
563
568
  rootState,
@@ -1059,7 +1064,7 @@ export async function linkTo(
1059
1064
  )
1060
1065
  }
1061
1066
 
1062
- const linking = getLinking()
1067
+ const linking = getResolvedLinking()
1063
1068
 
1064
1069
  if (!linking) {
1065
1070
  throw new Error('Attempted to link to route when no routes are present')
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getRoutes } from './getRoutes'
3
+ import { getSitemap } from './sitemap'
4
+ import { getMockContext } from '../testing-utils'
5
+
6
+ describe('getSitemap', () => {
7
+ it('maps the route tree to a public sitemap shape', () => {
8
+ const routes = getRoutes(
9
+ getMockContext([
10
+ '_layout.tsx',
11
+ 'index.tsx',
12
+ '(app)/_layout.tsx',
13
+ '(app)/thread/[id].tsx',
14
+ 'docs/[...slug].tsx',
15
+ ])
16
+ )!
17
+ const sitemap = getSitemap(routes)!
18
+
19
+ expect(sitemap).toMatchObject({
20
+ contextKey: './_layout.tsx',
21
+ href: '/',
22
+ isInternal: false,
23
+ isGenerated: false,
24
+ children: expect.arrayContaining([
25
+ expect.objectContaining({
26
+ contextKey: './index.tsx',
27
+ filename: 'index',
28
+ href: '/',
29
+ }),
30
+ expect.objectContaining({
31
+ contextKey: './(app)/_layout.tsx',
32
+ filename: '(app)/_layout',
33
+ href: '/(app)',
34
+ children: expect.arrayContaining([
35
+ expect.objectContaining({
36
+ contextKey: './(app)/thread/[id].tsx',
37
+ filename: 'thread/[id]',
38
+ href: '/(app)/thread/[id]',
39
+ }),
40
+ ]),
41
+ }),
42
+ expect.objectContaining({
43
+ contextKey: './docs/[...slug].tsx',
44
+ filename: 'docs/[...slug]',
45
+ href: '/docs/[...slug]',
46
+ }),
47
+ ]),
48
+ })
49
+ })
50
+ })
@@ -0,0 +1,66 @@
1
+ import { useMemo } from 'react'
2
+ import type { OneRouter } from '../interfaces/router'
3
+ import type { RouteNode } from './Route'
4
+ import { removeSupportedExtensions } from './matchers'
5
+ import { routeNode } from './router'
6
+ import { sortRoutes } from './sortRoutes'
7
+
8
+ export type SitemapType = {
9
+ contextKey: string
10
+ filename: string
11
+ href: string | OneRouter.Href
12
+ isInitial: boolean
13
+ isInternal: boolean
14
+ isGenerated: boolean
15
+ children: SitemapType[]
16
+ }
17
+
18
+ export function useSitemap(): SitemapType | null {
19
+ return useMemo(() => getSitemap(routeNode), [routeNode])
20
+ }
21
+
22
+ export function getSitemap(root: RouteNode | null): SitemapType | null {
23
+ return root ? mapRouteToSitemap(root, []) : null
24
+ }
25
+
26
+ function mapRouteToSitemap(route: RouteNode, parents: string[]): SitemapType {
27
+ return {
28
+ contextKey: route.contextKey,
29
+ filename: getRouteFilename(route),
30
+ href: getRouteHref(route, parents),
31
+ isInitial: route.initialRouteName === route.route,
32
+ isInternal: route.internal ?? false,
33
+ isGenerated: route.generated ?? false,
34
+ children: [...route.children]
35
+ .sort(sortRoutes)
36
+ .map((child) => mapRouteToSitemap(child, getRouteSegments(route, parents))),
37
+ }
38
+ }
39
+
40
+ function getRouteSegments(route: RouteNode, parents: string[]) {
41
+ return [...parents, ...route.route.split('/')]
42
+ }
43
+
44
+ // the sitemap exposes route patterns rather than concrete sample URLs:
45
+ // dynamic segments stay as `[id]` / `[...slug]`, and `index` collapses to '/'.
46
+ // callers wanting clickable links should fill in their own params.
47
+ function getRouteHref(route: RouteNode, parents: string[]) {
48
+ const path = getRouteSegments(route, parents)
49
+ .map((segment) => (segment === 'index' ? '' : segment))
50
+ .filter(Boolean)
51
+ .join('/')
52
+
53
+ return `/${path}`
54
+ }
55
+
56
+ function getRouteFilename(route: RouteNode) {
57
+ const contextKey = removeSupportedExtensions(route.contextKey)
58
+ const segments = contextKey.split('/')
59
+
60
+ if (route.contextKey.match(/_layout\.[jt]sx?$/)) {
61
+ return segments.slice(-2).join('/')
62
+ }
63
+
64
+ const routeSegmentsCount = route.route.split('/').length
65
+ return segments.slice(-routeSegmentsCount).join('/')
66
+ }
@@ -1,6 +1,7 @@
1
1
  import { useNavigationContainerRef } from '@react-navigation/native'
2
2
  import { resetLoaderState } from '../useLoader'
3
3
  import type { One } from '../vite/types'
4
+ import type { OneLinkingConfig } from '../link/getLinking'
4
5
  import * as routerStore from './router'
5
6
  import { initialize } from './router'
6
7
  import { getSSRInitialState, ensureBaseLinkingConfig } from './linkingConfig'
@@ -15,7 +16,8 @@ let ssrRouteTreeInitialized = false
15
16
 
16
17
  export function useInitializeOneRouter(
17
18
  context: One.RouteContext,
18
- initialLocation: URL | undefined
19
+ initialLocation: URL | undefined,
20
+ linking?: OneLinkingConfig
19
21
  ) {
20
22
  const navigationRef = useNavigationContainerRef()
21
23
 
@@ -23,15 +25,15 @@ export function useInitializeOneRouter(
23
25
  if (typeof window === 'undefined') {
24
26
  if (!ssrRouteTreeInitialized) {
25
27
  // first SSR request: full initialization to set up route tree, root component, etc.
26
- initialize(context, navigationRef, initialLocation)
28
+ initialize(context, navigationRef, initialLocation, linking)
27
29
  ssrRouteTreeInitialized = true
28
30
  // also ensure linking config base is cached
29
- ensureBaseLinkingConfig(routerStore.routeNode)
31
+ ensureBaseLinkingConfig(routerStore.routeNode, linking)
30
32
  }
31
33
 
32
34
  // per-request: compute initialState from URL (cached by path)
33
35
  const initialState = initialLocation
34
- ? getSSRInitialState(routerStore.routeNode, initialLocation)
36
+ ? getSSRInitialState(routerStore.routeNode, initialLocation, linking)
35
37
  : routerStore.initialState
36
38
 
37
39
  // return per-request snapshot to prevent concurrent request trampling
@@ -48,7 +50,7 @@ export function useInitializeOneRouter(
48
50
  const contexts = '__react_navigation__elements_contexts'
49
51
  globalThis[contexts] = new Map<string, React.Context<any>>()
50
52
 
51
- initialize(context, navigationRef, initialLocation)
53
+ initialize(context, navigationRef, initialLocation, linking)
52
54
  lastInitVersion = initVersion
53
55
  }
54
56
 
package/src/serve.ts CHANGED
@@ -8,8 +8,29 @@ import { setupBuildInfo } from './server/setupBuildOptions'
8
8
  import { ensureExists } from './utils/ensureExists'
9
9
  import type { One } from './vite/types'
10
10
 
11
+ // formatErrorSafely + the prepareStackTrace guard prevent a buggy transitive
12
+ // formatter (source-map-support without recursion guard) from pinning the
13
+ // serve process forever. see cli/install-error-handlers.ts for the full
14
+ // story behind the 9-day Onejs:build zombies on the soot CI runner.
15
+ import {
16
+ formatErrorSafely,
17
+ installPrepareStackTraceGuard,
18
+ } from './cli/install-error-handlers'
19
+
20
+ installPrepareStackTraceGuard()
21
+
11
22
  process.on('uncaughtException', (err) => {
12
- console.error(`[one] Uncaught exception`, err?.stack || err)
23
+ try {
24
+ process.stderr.write(`[one serve] uncaught exception\n${formatErrorSafely(err)}\n`)
25
+ } catch {}
26
+ })
27
+
28
+ process.on('unhandledRejection', (reason) => {
29
+ try {
30
+ process.stderr.write(
31
+ `[one serve] unhandled rejection\n${formatErrorSafely(reason)}\n`,
32
+ )
33
+ } catch {}
13
34
  })
14
35
 
15
36
  export async function serve(