@bleedingdev/modern-js-renderer-solid 0.0.0-stage → 3.9.3-ultramodern.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.
Files changed (297) hide show
  1. package/dist/esm/actions.js +283 -0
  2. package/dist/esm/application-router.js +25 -0
  3. package/dist/esm/client.js +49 -0
  4. package/dist/esm/entry-application.js +60 -0
  5. package/dist/esm/entry-client.js +23 -0
  6. package/dist/esm/federation-context.js +25 -0
  7. package/dist/esm/federation-ssr.js +84 -0
  8. package/dist/esm/federation.js +83 -0
  9. package/dist/esm/i18n/I18nProvider.js +33 -0
  10. package/dist/esm/i18n/LocalizedLink.js +74 -0
  11. package/dist/esm/i18n/context.js +3 -0
  12. package/dist/esm/i18n/index.js +8 -0
  13. package/dist/esm/i18n/languageSync.js +50 -0
  14. package/dist/esm/i18n/types.js +0 -0
  15. package/dist/esm/i18n/useI18n.js +74 -0
  16. package/dist/esm/index.js +58 -0
  17. package/dist/esm/manifest.js +150 -0
  18. package/dist/esm/route-completion.js +150 -0
  19. package/dist/esm/route-data-error.js +108 -0
  20. package/dist/esm/router-binding/Asset.js +201 -0
  21. package/dist/esm/router-binding/CatchBoundary.js +94 -0
  22. package/dist/esm/router-binding/ClientOnly.js +26 -0
  23. package/dist/esm/router-binding/HeadContent.js +8 -0
  24. package/dist/esm/router-binding/LICENSE +21 -0
  25. package/dist/esm/router-binding/Match.js +319 -0
  26. package/dist/esm/router-binding/Matches.js +117 -0
  27. package/dist/esm/router-binding/PROVENANCE.json +94 -0
  28. package/dist/esm/router-binding/RouterProvider.js +51 -0
  29. package/dist/esm/router-binding/SafeFragment.js +5 -0
  30. package/dist/esm/router-binding/ScriptOnce.js +15 -0
  31. package/dist/esm/router-binding/Scripts.js +30 -0
  32. package/dist/esm/router-binding/ScrollRestoration.js +16 -0
  33. package/dist/esm/router-binding/Transitioner.js +103 -0
  34. package/dist/esm/router-binding/awaited.js +48 -0
  35. package/dist/esm/router-binding/fileRoute.js +83 -0
  36. package/dist/esm/router-binding/flightData.js +23 -0
  37. package/dist/esm/router-binding/headContentUtils.js +162 -0
  38. package/dist/esm/router-binding/history.js +0 -0
  39. package/dist/esm/router-binding/index.js +35 -0
  40. package/dist/esm/router-binding/lazyRouteComponent.js +30 -0
  41. package/dist/esm/router-binding/link.js +389 -0
  42. package/dist/esm/router-binding/matchContext.js +7 -0
  43. package/dist/esm/router-binding/nonRouteComponentContext.js +13 -0
  44. package/dist/esm/router-binding/not-found.js +35 -0
  45. package/dist/esm/router-binding/publicMatchData.js +447 -0
  46. package/dist/esm/router-binding/registryTransfer.js +210 -0
  47. package/dist/esm/router-binding/renderRouteNotFound.js +23 -0
  48. package/dist/esm/router-binding/route.js +143 -0
  49. package/dist/esm/router-binding/router.js +13 -0
  50. package/dist/esm/router-binding/routerContext.js +3 -0
  51. package/dist/esm/router-binding/routerStores.js +50 -0
  52. package/dist/esm/router-binding/scroll-restoration.js +13 -0
  53. package/dist/esm/router-binding/typePrimitives.js +0 -0
  54. package/dist/esm/router-binding/useBlocker.js +152 -0
  55. package/dist/esm/router-binding/useCanGoBack.js +7 -0
  56. package/dist/esm/router-binding/useLoaderData.js +9 -0
  57. package/dist/esm/router-binding/useLoaderDeps.js +8 -0
  58. package/dist/esm/router-binding/useLocation.js +14 -0
  59. package/dist/esm/router-binding/useMatch.js +28 -0
  60. package/dist/esm/router-binding/useNavigate.js +19 -0
  61. package/dist/esm/router-binding/useParams.js +13 -0
  62. package/dist/esm/router-binding/useRouteContext.js +8 -0
  63. package/dist/esm/router-binding/useRouter.js +10 -0
  64. package/dist/esm/router-binding/useRouterState.js +19 -0
  65. package/dist/esm/router-binding/useSearch.js +13 -0
  66. package/dist/esm/router-binding/utils.js +23 -0
  67. package/dist/esm/router.js +187 -0
  68. package/dist/esm-node/actions.js +263 -0
  69. package/dist/esm-node/application-router.js +25 -0
  70. package/dist/esm-node/client.js +49 -0
  71. package/dist/esm-node/entry-application.js +110 -0
  72. package/dist/esm-node/entry-client.js +23 -0
  73. package/dist/esm-node/entry-server.js +47 -0
  74. package/dist/esm-node/federation-context.js +25 -0
  75. package/dist/esm-node/federation-ssr.js +84 -0
  76. package/dist/esm-node/federation.js +83 -0
  77. package/dist/esm-node/i18n/I18nProvider.js +42 -0
  78. package/dist/esm-node/i18n/LocalizedLink.js +142 -0
  79. package/dist/esm-node/i18n/context.js +3 -0
  80. package/dist/esm-node/i18n/index.js +8 -0
  81. package/dist/esm-node/i18n/languageSync.js +50 -0
  82. package/dist/esm-node/i18n/types.js +1 -0
  83. package/dist/esm-node/i18n/useI18n.js +74 -0
  84. package/dist/esm-node/index.js +58 -0
  85. package/dist/esm-node/manifest.js +150 -0
  86. package/dist/esm-node/native-promise-serialization.js +113 -0
  87. package/dist/esm-node/plugin/compiler.js +209 -0
  88. package/dist/esm-node/plugin/create.js +235 -0
  89. package/dist/esm-node/plugin/index.js +73 -0
  90. package/dist/esm-node/plugin/profile.js +52 -0
  91. package/dist/esm-node/route-completion.js +160 -0
  92. package/dist/esm-node/route-data-error.js +108 -0
  93. package/dist/esm-node/router-binding/Asset.js +171 -0
  94. package/dist/esm-node/router-binding/CatchBoundary.js +136 -0
  95. package/dist/esm-node/router-binding/ClientOnly.js +46 -0
  96. package/dist/esm-node/router-binding/HeadContent.js +8 -0
  97. package/dist/esm-node/router-binding/LICENSE +21 -0
  98. package/dist/esm-node/router-binding/Match.js +660 -0
  99. package/dist/esm-node/router-binding/Matches.js +161 -0
  100. package/dist/esm-node/router-binding/PROVENANCE.json +94 -0
  101. package/dist/esm-node/router-binding/RouterProvider.js +87 -0
  102. package/dist/esm-node/router-binding/SafeFragment.js +5 -0
  103. package/dist/esm-node/router-binding/ScriptOnce.js +16 -0
  104. package/dist/esm-node/router-binding/Scripts.js +49 -0
  105. package/dist/esm-node/router-binding/ScrollRestoration.js +16 -0
  106. package/dist/esm-node/router-binding/Transitioner.js +103 -0
  107. package/dist/esm-node/router-binding/awaited.js +75 -0
  108. package/dist/esm-node/router-binding/fileRoute.js +83 -0
  109. package/dist/esm-node/router-binding/flightData.js +23 -0
  110. package/dist/esm-node/router-binding/headContentUtils.js +162 -0
  111. package/dist/esm-node/router-binding/history.js +1 -0
  112. package/dist/esm-node/router-binding/index.js +35 -0
  113. package/dist/esm-node/router-binding/lazyRouteComponent.js +30 -0
  114. package/dist/esm-node/router-binding/link.js +399 -0
  115. package/dist/esm-node/router-binding/matchContext.js +7 -0
  116. package/dist/esm-node/router-binding/nonRouteComponentContext.js +22 -0
  117. package/dist/esm-node/router-binding/not-found.js +49 -0
  118. package/dist/esm-node/router-binding/publicMatchData.js +447 -0
  119. package/dist/esm-node/router-binding/registryTransfer.js +210 -0
  120. package/dist/esm-node/router-binding/renderRouteNotFound.js +22 -0
  121. package/dist/esm-node/router-binding/route.js +162 -0
  122. package/dist/esm-node/router-binding/router.js +13 -0
  123. package/dist/esm-node/router-binding/routerContext.js +3 -0
  124. package/dist/esm-node/router-binding/routerStores.js +50 -0
  125. package/dist/esm-node/router-binding/scroll-restoration.js +12 -0
  126. package/dist/esm-node/router-binding/typePrimitives.js +1 -0
  127. package/dist/esm-node/router-binding/useBlocker.js +152 -0
  128. package/dist/esm-node/router-binding/useCanGoBack.js +7 -0
  129. package/dist/esm-node/router-binding/useLoaderData.js +9 -0
  130. package/dist/esm-node/router-binding/useLoaderDeps.js +8 -0
  131. package/dist/esm-node/router-binding/useLocation.js +14 -0
  132. package/dist/esm-node/router-binding/useMatch.js +28 -0
  133. package/dist/esm-node/router-binding/useNavigate.js +19 -0
  134. package/dist/esm-node/router-binding/useParams.js +13 -0
  135. package/dist/esm-node/router-binding/useRouteContext.js +8 -0
  136. package/dist/esm-node/router-binding/useRouter.js +10 -0
  137. package/dist/esm-node/router-binding/useRouterState.js +19 -0
  138. package/dist/esm-node/router-binding/useSearch.js +13 -0
  139. package/dist/esm-node/router-binding/utils.js +23 -0
  140. package/dist/esm-node/router.js +187 -0
  141. package/dist/esm-node/server.js +396 -0
  142. package/dist/types/actions.d.ts +45 -0
  143. package/dist/types/application-router.d.ts +5 -0
  144. package/dist/types/client.d.ts +23 -0
  145. package/dist/types/entry-application.d.ts +10 -0
  146. package/dist/types/entry-client.d.ts +5 -0
  147. package/dist/types/entry-server.d.ts +5 -0
  148. package/dist/types/federation-context.d.ts +16 -0
  149. package/dist/types/federation-ssr.d.ts +21 -0
  150. package/dist/types/federation.d.ts +30 -0
  151. package/dist/types/i18n/I18nProvider.d.ts +21 -0
  152. package/dist/types/i18n/LocalizedLink.d.ts +43 -0
  153. package/dist/types/i18n/context.d.ts +11 -0
  154. package/dist/types/i18n/index.d.ts +9 -0
  155. package/dist/types/i18n/languageSync.d.ts +31 -0
  156. package/dist/types/i18n/types.d.ts +33 -0
  157. package/dist/types/i18n/useI18n.d.ts +24 -0
  158. package/dist/types/index.d.ts +41 -0
  159. package/dist/types/manifest.d.ts +55 -0
  160. package/dist/types/native-promise-serialization.d.ts +9 -0
  161. package/dist/types/plugin/compiler.d.ts +6 -0
  162. package/dist/types/plugin/create.d.ts +21 -0
  163. package/dist/types/plugin/index.d.ts +3 -0
  164. package/dist/types/plugin/lazy-modules.d.cts +15 -0
  165. package/dist/types/plugin/preserve-import-loader.d.cts +3 -0
  166. package/dist/types/plugin/profile.d.ts +2 -0
  167. package/dist/types/plugin/solid-loader.d.cts +2 -0
  168. package/dist/types/plugin/svg-component-template.d.cts +6 -0
  169. package/dist/types/route-completion.d.ts +21 -0
  170. package/dist/types/route-data-error.d.ts +17 -0
  171. package/dist/types/router-binding/Asset.d.ts +3 -0
  172. package/dist/types/router-binding/CatchBoundary.d.ts +13 -0
  173. package/dist/types/router-binding/ClientOnly.d.ts +31 -0
  174. package/dist/types/router-binding/HeadContent.d.ts +13 -0
  175. package/dist/types/router-binding/Match.d.ts +6 -0
  176. package/dist/types/router-binding/Matches.d.ts +26 -0
  177. package/dist/types/router-binding/RouterProvider.d.ts +10 -0
  178. package/dist/types/router-binding/SafeFragment.d.ts +2 -0
  179. package/dist/types/router-binding/ScriptOnce.d.ts +6 -0
  180. package/dist/types/router-binding/Scripts.d.ts +2 -0
  181. package/dist/types/router-binding/ScrollRestoration.d.ts +14 -0
  182. package/dist/types/router-binding/Transitioner.d.ts +2 -0
  183. package/dist/types/router-binding/awaited.d.ts +11 -0
  184. package/dist/types/router-binding/fileRoute.d.ts +54 -0
  185. package/dist/types/router-binding/flightData.d.ts +38 -0
  186. package/dist/types/router-binding/headContentUtils.d.ts +15 -0
  187. package/dist/types/router-binding/history.d.ts +8 -0
  188. package/dist/types/router-binding/index.d.ts +45 -0
  189. package/dist/types/router-binding/lazyRouteComponent.d.ts +2 -0
  190. package/dist/types/router-binding/link.d.ts +43 -0
  191. package/dist/types/router-binding/matchContext.d.ts +7 -0
  192. package/dist/types/router-binding/nonRouteComponentContext.d.ts +5 -0
  193. package/dist/types/router-binding/not-found.d.ts +11 -0
  194. package/dist/types/router-binding/publicMatchData.d.ts +19 -0
  195. package/dist/types/router-binding/registryTransfer.d.ts +47 -0
  196. package/dist/types/router-binding/renderRouteNotFound.d.ts +11 -0
  197. package/dist/types/router-binding/route.d.ts +105 -0
  198. package/dist/types/router-binding/router.d.ts +70 -0
  199. package/dist/types/router-binding/routerContext.d.ts +3 -0
  200. package/dist/types/router-binding/routerStores.d.ts +2 -0
  201. package/dist/types/router-binding/scroll-restoration.d.ts +2 -0
  202. package/dist/types/router-binding/typePrimitives.d.ts +10 -0
  203. package/dist/types/router-binding/useBlocker.d.ts +70 -0
  204. package/dist/types/router-binding/useCanGoBack.d.ts +2 -0
  205. package/dist/types/router-binding/useLoaderData.d.ts +8 -0
  206. package/dist/types/router-binding/useLoaderDeps.d.ts +8 -0
  207. package/dist/types/router-binding/useLocation.d.ts +7 -0
  208. package/dist/types/router-binding/useMatch.d.ts +10 -0
  209. package/dist/types/router-binding/useNavigate.d.ts +5 -0
  210. package/dist/types/router-binding/useParams.d.ts +9 -0
  211. package/dist/types/router-binding/useRouteContext.d.ts +4 -0
  212. package/dist/types/router-binding/useRouter.d.ts +4 -0
  213. package/dist/types/router-binding/useRouterState.d.ts +8 -0
  214. package/dist/types/router-binding/useSearch.d.ts +9 -0
  215. package/dist/types/router-binding/utils.d.ts +25 -0
  216. package/dist/types/router.d.ts +31 -0
  217. package/dist/types/server.d.ts +47 -0
  218. package/package.json +123 -4
  219. package/src/actions.tsx +437 -0
  220. package/src/application-router.tsx +41 -0
  221. package/src/client.ts +106 -0
  222. package/src/entry-application.tsx +73 -0
  223. package/src/entry-client.tsx +48 -0
  224. package/src/entry-server.tsx +85 -0
  225. package/src/federation-context.ts +47 -0
  226. package/src/federation-ssr.ts +117 -0
  227. package/src/federation.tsx +193 -0
  228. package/src/i18n/I18nProvider.tsx +54 -0
  229. package/src/i18n/LocalizedLink.tsx +122 -0
  230. package/src/i18n/context.ts +13 -0
  231. package/src/i18n/index.ts +35 -0
  232. package/src/i18n/languageSync.ts +113 -0
  233. package/src/i18n/types.ts +35 -0
  234. package/src/i18n/useI18n.ts +121 -0
  235. package/src/index.ts +41 -0
  236. package/src/manifest.ts +337 -0
  237. package/src/native-promise-serialization.ts +167 -0
  238. package/src/plugin/compiler.ts +320 -0
  239. package/src/plugin/create.ts +270 -0
  240. package/src/plugin/index.ts +72 -0
  241. package/src/plugin/lazy-modules.cjs +146 -0
  242. package/src/plugin/preserve-import-loader.cjs +46 -0
  243. package/src/plugin/profile.ts +42 -0
  244. package/src/plugin/solid-loader.cjs +83 -0
  245. package/src/plugin/svg-component-template.cjs +19 -0
  246. package/src/route-completion.tsx +222 -0
  247. package/src/route-data-error.ts +175 -0
  248. package/src/router-binding/Asset.tsx +283 -0
  249. package/src/router-binding/CatchBoundary.tsx +130 -0
  250. package/src/router-binding/ClientOnly.tsx +76 -0
  251. package/src/router-binding/HeadContent.tsx +23 -0
  252. package/src/router-binding/LICENSE +21 -0
  253. package/src/router-binding/Match.tsx +397 -0
  254. package/src/router-binding/Matches.tsx +242 -0
  255. package/src/router-binding/PROVENANCE.json +94 -0
  256. package/src/router-binding/RouterProvider.tsx +129 -0
  257. package/src/router-binding/SafeFragment.tsx +5 -0
  258. package/src/router-binding/ScriptOnce.tsx +23 -0
  259. package/src/router-binding/Scripts.tsx +44 -0
  260. package/src/router-binding/ScrollRestoration.tsx +49 -0
  261. package/src/router-binding/Transitioner.tsx +172 -0
  262. package/src/router-binding/awaited.tsx +71 -0
  263. package/src/router-binding/fileRoute.ts +268 -0
  264. package/src/router-binding/flightData.ts +90 -0
  265. package/src/router-binding/headContentUtils.tsx +290 -0
  266. package/src/router-binding/history.ts +9 -0
  267. package/src/router-binding/index.tsx +318 -0
  268. package/src/router-binding/lazyRouteComponent.tsx +70 -0
  269. package/src/router-binding/link.tsx +865 -0
  270. package/src/router-binding/matchContext.tsx +15 -0
  271. package/src/router-binding/nonRouteComponentContext.tsx +23 -0
  272. package/src/router-binding/not-found.tsx +65 -0
  273. package/src/router-binding/publicMatchData.ts +674 -0
  274. package/src/router-binding/registryTransfer.ts +383 -0
  275. package/src/router-binding/renderRouteNotFound.tsx +53 -0
  276. package/src/router-binding/route.tsx +665 -0
  277. package/src/router-binding/router.ts +121 -0
  278. package/src/router-binding/routerContext.tsx +6 -0
  279. package/src/router-binding/routerStores.ts +85 -0
  280. package/src/router-binding/scroll-restoration.tsx +15 -0
  281. package/src/router-binding/typePrimitives.ts +74 -0
  282. package/src/router-binding/useBlocker.tsx +359 -0
  283. package/src/router-binding/useCanGoBack.ts +9 -0
  284. package/src/router-binding/useLoaderData.tsx +50 -0
  285. package/src/router-binding/useLoaderDeps.tsx +46 -0
  286. package/src/router-binding/useLocation.tsx +43 -0
  287. package/src/router-binding/useMatch.tsx +111 -0
  288. package/src/router-binding/useNavigate.tsx +43 -0
  289. package/src/router-binding/useParams.tsx +72 -0
  290. package/src/router-binding/useRouteContext.ts +30 -0
  291. package/src/router-binding/useRouter.tsx +17 -0
  292. package/src/router-binding/useRouterState.tsx +50 -0
  293. package/src/router-binding/useSearch.tsx +72 -0
  294. package/src/router-binding/utils.ts +59 -0
  295. package/src/router.ts +376 -0
  296. package/src/server.ts +645 -0
  297. package/README.md +0 -3
@@ -0,0 +1,222 @@
1
+ import { DataProtocolError } from '@modern-js/renderer-core/data';
2
+ import type { RequestSession } from '@modern-js/renderer-core/session';
3
+ import { Dynamic, type JSX } from '@solidjs/web';
4
+ import type { AnyRouter, CreateRouterFn } from '@tanstack/router-core';
5
+ import type { Accessor } from 'solid-js';
6
+ import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
7
+ import type { AnyRoute, RouteComponent } from './router-binding/index';
8
+ import {
9
+ createRouter,
10
+ preparePublicContextData,
11
+ useMatch,
12
+ } from './router-binding/index';
13
+ import { registerPublicRouteDataOwner } from './router-binding/registryTransfer';
14
+
15
+ interface CompletionRecord {
16
+ readonly failure: Accessor<{ reason: unknown } | undefined>;
17
+ acquire(): () => void;
18
+ }
19
+
20
+ interface PublicDataGeneration {
21
+ readonly owner: Pick<RequestSession, 'fail' | 'signal'>;
22
+ publish(value: unknown, completion?: Promise<void>): void;
23
+ }
24
+
25
+ export interface RouteCompletionScope {
26
+ readonly hydrationOwner: Pick<RequestSession, 'fail' | 'signal'>;
27
+ readonly privateRoots: readonly object[];
28
+ attach(router: AnyRouter): void;
29
+ begin(signal: AbortSignal, cancel: () => void): PublicDataGeneration;
30
+ component(component: RouteComponent): RouteComponent;
31
+ dispose(): void;
32
+ }
33
+
34
+ // These weak associations contain owned lifecycles, never ambient request data.
35
+ const routeScopes = new WeakMap<AnyRoute, RouteCompletionScope>();
36
+ const routerScopes = new WeakMap<AnyRouter, RouteCompletionScope>();
37
+
38
+ function assertManagedRouteData(route: AnyRoute | undefined): void {
39
+ if (!route) return;
40
+ if (route.options.loader || route.options.beforeLoad || route.options.context)
41
+ throw new DataProtocolError(
42
+ 'Custom native Solid loader/beforeLoad/context routes are unsupported by the immutable public-data profile; use createFileSystemRouteTree',
43
+ );
44
+ for (const child of route.children ?? []) assertManagedRouteData(child);
45
+ }
46
+
47
+ export function registerRouteCompletionScope(
48
+ route: AnyRoute,
49
+ scope: RouteCompletionScope,
50
+ ): void {
51
+ routeScopes.set(route, scope);
52
+ }
53
+
54
+ export const createApplicationRouter: CreateRouterFn = options => {
55
+ const scope = options.routeTree
56
+ ? routeScopes.get(options.routeTree)
57
+ : undefined;
58
+ if (!scope) assertManagedRouteData(options.routeTree);
59
+ if (scope && options.routeTree)
60
+ registerPublicRouteDataOwner(
61
+ options.routeTree,
62
+ scope.hydrationOwner,
63
+ scope.privateRoots,
64
+ );
65
+ const context = preparePublicContextData(
66
+ options.context,
67
+ scope?.hydrationOwner,
68
+ );
69
+ if (
70
+ context !== undefined &&
71
+ (!context || typeof context !== 'object' || Array.isArray(context))
72
+ )
73
+ throw new DataProtocolError(
74
+ 'The native Solid router context must be a public record',
75
+ );
76
+ const router = createRouter({ ...options, context });
77
+ if (scope) {
78
+ scope.attach(router);
79
+ routerScopes.set(router, scope);
80
+ }
81
+ return router;
82
+ };
83
+
84
+ export function ownApplicationRouteData(router: AnyRouter): void {
85
+ const scope = routerScopes.get(router);
86
+ if (scope) onCleanup(() => scope.dispose());
87
+ else assertManagedRouteData(router.options.routeTree);
88
+ }
89
+
90
+ /** A framing failure belongs to the exact native loader generation that read it. */
91
+ export function createRouteCompletionScope(
92
+ privateRoots: readonly object[] = [],
93
+ session?: RequestSession,
94
+ ): RouteCompletionScope {
95
+ const records = new WeakMap<object, CompletionRecord>();
96
+ const pending = new Set<() => void>();
97
+ const hydration = new AbortController();
98
+ let router: AnyRouter | undefined;
99
+
100
+ function begin(
101
+ signal: AbortSignal,
102
+ cancel: () => void,
103
+ ): PublicDataGeneration {
104
+ if (!router)
105
+ throw new Error(
106
+ 'Use createApplicationRouter to own progressive Solid route data',
107
+ );
108
+ const [failure, setFailure] = createSignal<{ reason: unknown }>();
109
+ const native = router;
110
+ let value: unknown;
111
+ let leases = 0;
112
+ let settled = false;
113
+ let failed = false;
114
+ const stop = () => {
115
+ if (!settled) {
116
+ pending.delete(stop);
117
+ if (value && typeof value === 'object')
118
+ native.clearCache({ filter: match => match.loaderData === value });
119
+ cancel();
120
+ }
121
+ };
122
+ const fail = (reason: unknown) => {
123
+ if (failed) return;
124
+ failed = true;
125
+ settled = true;
126
+ pending.delete(stop);
127
+ if (value && typeof value === 'object')
128
+ native.clearCache({ filter: match => match.loaderData === value });
129
+ setFailure({ reason });
130
+ cancel();
131
+ };
132
+ const record: CompletionRecord = {
133
+ failure,
134
+ acquire() {
135
+ leases++;
136
+ let released = false;
137
+ return () => {
138
+ if (released) return;
139
+ released = true;
140
+ if (
141
+ --leases === 0 &&
142
+ !native.state.matches.some(match => match.loaderData === value)
143
+ )
144
+ stop();
145
+ };
146
+ },
147
+ };
148
+ return {
149
+ owner: { signal, fail },
150
+ publish(publicValue, completion) {
151
+ value = publicValue;
152
+ const deferred =
153
+ value && typeof value === 'object'
154
+ ? Reflect.ownKeys(value).flatMap(key => {
155
+ const entry = Object.getOwnPropertyDescriptor(value, key);
156
+ return entry && !entry.get && entry.value instanceof Promise
157
+ ? [entry.value]
158
+ : [];
159
+ })
160
+ : [];
161
+ if (!completion && deferred.length === 0) return;
162
+ if (value && typeof value === 'object') {
163
+ records.set(value, record);
164
+ pending.add(stop);
165
+ } else if (completion) {
166
+ throw new DataProtocolError(
167
+ 'Progressive route data requires its native value object',
168
+ );
169
+ }
170
+ if (completion)
171
+ void completion.then(() => {
172
+ settled = true;
173
+ pending.delete(stop);
174
+ }, fail);
175
+ else
176
+ void Promise.allSettled(deferred).then(() => {
177
+ settled = true;
178
+ pending.delete(stop);
179
+ });
180
+ },
181
+ };
182
+ }
183
+
184
+ return {
185
+ privateRoots,
186
+ hydrationOwner: session ?? {
187
+ signal: hydration.signal,
188
+ fail: error => hydration.abort(error),
189
+ },
190
+ attach(native) {
191
+ if (router && router !== native)
192
+ throw new Error('A Solid data scope already owns its native router');
193
+ router = native;
194
+ },
195
+ begin,
196
+ component(component) {
197
+ return function RouteCompletionView(): JSX.Element {
198
+ // Select the registry record before native structural sharing can reuse
199
+ // a deeply equal previous loaderData object from another generation.
200
+ const record = useMatch({
201
+ strict: false,
202
+ select: match =>
203
+ match.loaderData && typeof match.loaderData === 'object'
204
+ ? records.get(match.loaderData)
205
+ : undefined,
206
+ });
207
+ createEffect(record, current => current?.acquire());
208
+ const ready = createMemo(() => {
209
+ const failure = record()?.failure();
210
+ if (failure) throw failure.reason;
211
+ return component;
212
+ });
213
+ return <Dynamic component={ready()} />;
214
+ };
215
+ },
216
+ dispose() {
217
+ hydration.abort();
218
+ for (const stop of pending) stop();
219
+ pending.clear();
220
+ },
221
+ };
222
+ }
@@ -0,0 +1,175 @@
1
+ import {
2
+ assertPublicData,
3
+ DataProtocolError,
4
+ } from '@modern-js/renderer-core/data';
5
+ import { RouteDataError } from '@modern-js/renderer-core/router';
6
+
7
+ export interface RouteDataErrorSnapshot {
8
+ kind: 'ultramodern-route-data-error';
9
+ version: 1;
10
+ routeId: string;
11
+ status: number;
12
+ data: unknown;
13
+ error: { name: string; message: string; stack?: string };
14
+ }
15
+
16
+ function invalid(message: string): never {
17
+ throw new DataProtocolError(message);
18
+ }
19
+
20
+ function publicFields(
21
+ value: object,
22
+ required: readonly string[],
23
+ optional: readonly string[] = [],
24
+ allowStackAccessor = false,
25
+ ): Record<string, PropertyDescriptor> {
26
+ const descriptors = Object.getOwnPropertyDescriptors(value);
27
+ for (const key of Reflect.ownKeys(descriptors)) {
28
+ if (
29
+ typeof key !== 'string' ||
30
+ (!required.includes(key) && !optional.includes(key))
31
+ ) {
32
+ invalid('Route data errors must not contain undeclared fields');
33
+ }
34
+ const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
35
+ if (descriptor.get || descriptor.set) {
36
+ // Native Node errors can own a lazy stack getter. Never evaluate it.
37
+ if (allowStackAccessor && key === 'stack') continue;
38
+ invalid('Route data errors must not contain accessors');
39
+ }
40
+ }
41
+ for (const key of required) {
42
+ if (!Object.hasOwn(descriptors, key)) {
43
+ invalid('Route data errors are missing a declared field');
44
+ }
45
+ }
46
+ return descriptors;
47
+ }
48
+
49
+ function validateFields(
50
+ routeId: unknown,
51
+ status: unknown,
52
+ name: unknown,
53
+ message: unknown,
54
+ ): { routeId: string; status: number; name: string; message: string } {
55
+ if (typeof routeId !== 'string' || routeId.length === 0) {
56
+ invalid('Route data errors require a nonempty route identifier');
57
+ }
58
+ if (
59
+ typeof status !== 'number' ||
60
+ !Number.isInteger(status) ||
61
+ status < 200 ||
62
+ status > 599
63
+ ) {
64
+ invalid('Route data errors require a valid public HTTP status');
65
+ }
66
+ if (typeof name !== 'string' || typeof message !== 'string') {
67
+ invalid('Route data errors require public name and message strings');
68
+ }
69
+ return { routeId, status, name, message };
70
+ }
71
+
72
+ /** Preserve the known public route error without serializing its Error object. */
73
+ export function projectRouteDataError(
74
+ error: unknown,
75
+ production = true,
76
+ ): RouteDataErrorSnapshot {
77
+ if (
78
+ !error ||
79
+ typeof error !== 'object' ||
80
+ Object.getPrototypeOf(error) !== RouteDataError.prototype
81
+ ) {
82
+ invalid('Only the exact RouteDataError class has a route error projection');
83
+ }
84
+ const descriptors = publicFields(
85
+ error,
86
+ ['routeId', 'status', 'data', 'name', 'message'],
87
+ ['stack'],
88
+ true,
89
+ );
90
+ const routeId: unknown = descriptors.routeId!.value;
91
+ const status: unknown = descriptors.status!.value;
92
+ const name: unknown = descriptors.name!.value;
93
+ const message: unknown = descriptors.message!.value;
94
+ const fields = validateFields(routeId, status, name, message);
95
+ const snapshot: RouteDataErrorSnapshot = {
96
+ kind: 'ultramodern-route-data-error',
97
+ version: 1,
98
+ routeId: fields.routeId,
99
+ status: fields.status,
100
+ data: descriptors.data!.value,
101
+ error: {
102
+ name: fields.name,
103
+ message: fields.message,
104
+ },
105
+ };
106
+ const stack = descriptors.stack;
107
+ if (stack && !stack.get && !stack.set) {
108
+ if (typeof stack.value !== 'string') {
109
+ invalid('Route data error stacks must be strings');
110
+ }
111
+ if (!production) snapshot.error.stack = stack.value;
112
+ }
113
+ assertPublicData(snapshot);
114
+ return snapshot;
115
+ }
116
+
117
+ function plainRecord(value: unknown): object {
118
+ if (!value || typeof value !== 'object') {
119
+ invalid('Malformed route data error snapshot');
120
+ }
121
+ const prototype = Object.getPrototypeOf(value);
122
+ if (prototype !== Object.prototype && prototype !== null) {
123
+ invalid('Route data error snapshots require plain records');
124
+ }
125
+ return value;
126
+ }
127
+
128
+ /** Reconstruct only the validated route error tag produced by this adapter. */
129
+ export function restoreRouteDataError(snapshot: unknown): RouteDataError {
130
+ const descriptors = publicFields(plainRecord(snapshot), [
131
+ 'kind',
132
+ 'version',
133
+ 'routeId',
134
+ 'status',
135
+ 'data',
136
+ 'error',
137
+ ]);
138
+ if (
139
+ descriptors.kind!.value !== 'ultramodern-route-data-error' ||
140
+ descriptors.version!.value !== 1
141
+ ) {
142
+ invalid('Unsupported route data error snapshot');
143
+ }
144
+ const error = publicFields(
145
+ plainRecord(descriptors.error!.value),
146
+ ['name', 'message'],
147
+ ['stack'],
148
+ );
149
+ const routeId: unknown = descriptors.routeId!.value;
150
+ const fields = validateFields(
151
+ routeId,
152
+ descriptors.status!.value,
153
+ error.name!.value,
154
+ error.message!.value,
155
+ );
156
+ if (error.stack && typeof error.stack.value !== 'string') {
157
+ invalid('Route data error stacks must be strings');
158
+ }
159
+ assertPublicData(snapshot);
160
+ const restored = new RouteDataError(fields.routeId, {
161
+ kind: 'error',
162
+ status: fields.status,
163
+ data: descriptors.data!.value,
164
+ error: { name: fields.name, message: fields.message },
165
+ thrown: true,
166
+ });
167
+ if (error.stack) {
168
+ Object.defineProperty(restored, 'stack', {
169
+ value: error.stack.value,
170
+ configurable: true,
171
+ writable: true,
172
+ });
173
+ }
174
+ return restored;
175
+ }
@@ -0,0 +1,283 @@
1
+ import type { JSX } from '@solidjs/web';
2
+ import type { RouterManagedTag } from '@tanstack/router-core';
3
+ import { isServer } from '@tanstack/router-core/isServer';
4
+ import { createEffect, onCleanup, onSettled } from 'solid-js';
5
+ import { useRouter } from './useRouter';
6
+
7
+ const INLINE_CSS_HYDRATION_ATTR = 'data-tsr-inline-css';
8
+
9
+ export function Asset(asset: RouterManagedTag): JSX.Element | null {
10
+ const { tag, attrs, children } = asset;
11
+
12
+ switch (tag) {
13
+ case 'title':
14
+ return <Title attrs={attrs} children={children} />;
15
+ case 'meta':
16
+ return <HeadElement tag="meta" attrs={attrs} />;
17
+ case 'link':
18
+ return <HeadElement tag="link" attrs={attrs} />;
19
+ case 'style':
20
+ if (
21
+ asset.inlineCss &&
22
+ (process.env.TSS_INLINE_CSS_ENABLED === 'true' ||
23
+ (process.env.TSS_INLINE_CSS_ENABLED === undefined && isServer))
24
+ ) {
25
+ return <InlineCssStyle attrs={attrs}>{children}</InlineCssStyle>;
26
+ }
27
+
28
+ return <HeadElement tag="style" attrs={attrs} children={children} />;
29
+ case 'script':
30
+ return <Script attrs={attrs} children={children} />;
31
+ default:
32
+ return null;
33
+ }
34
+ }
35
+
36
+ // On the client, relocate a rendered head element into document.head so head
37
+ // tags end up in the right place even when <HeadContent /> is rendered in
38
+ // <body>. The *same* node that Solid rendered/hydrated is moved — never
39
+ // recreated — so the server-rendered node stays claimed (its hydration id is
40
+ // preserved) and stylesheets/scripts are not refetched or re-executed.
41
+ //
42
+ // When <HeadContent /> is placed in <head> (the SSR/hydration case), the node
43
+ // is already in document.head, so this is a no-op and the element is left
44
+ // exactly where Solid hydrated it.
45
+ function useRelocateToHead(getEl: () => Node | undefined) {
46
+ onSettled(() => {
47
+ const el = getEl();
48
+ if (el && el.parentNode !== document.head) {
49
+ document.head.appendChild(el);
50
+ }
51
+ });
52
+
53
+ onCleanup(() => {
54
+ const el = getEl();
55
+ if (el?.parentNode) {
56
+ el.parentNode.removeChild(el);
57
+ }
58
+ });
59
+ }
60
+
61
+ function InlineCssStyle({
62
+ attrs,
63
+ children,
64
+ }: {
65
+ attrs?: Record<string, any>;
66
+ children?: RouterManagedTag['children'];
67
+ }) {
68
+ const isInlineCssPlaceholder = children === undefined;
69
+ const html = isInlineCssPlaceholder
70
+ ? typeof document === 'undefined'
71
+ ? ''
72
+ : (document.querySelector<HTMLStyleElement>(
73
+ `style[${INLINE_CSS_HYDRATION_ATTR}]`,
74
+ )?.textContent ?? '')
75
+ : (children ?? '');
76
+
77
+ return (
78
+ <HeadElement
79
+ tag="style"
80
+ attrs={{ ...attrs, [INLINE_CSS_HYDRATION_ATTR]: '' }}
81
+ children={html}
82
+ />
83
+ );
84
+ }
85
+
86
+ function HeadElement(props: {
87
+ tag: 'meta' | 'link' | 'style';
88
+ attrs?: Record<string, any>;
89
+ children?: unknown;
90
+ }): JSX.Element | null {
91
+ // Each branch must live in its own `case` block so the Solid compiler emits
92
+ // exactly ONE getNextElement() per invocation. Sequential `if ... return`
93
+ // statements compile to multiple getNextElement() calls that all execute,
94
+ // desyncing the hydration key counter (causing "expected <style>" mismatches
95
+ // and unclaimed nodes).
96
+ //
97
+ // We capture the element via the JSX return value (a real DOM node in Solid)
98
+ // rather than a `ref` attribute: a `ref` compiles to a _$ref() call that
99
+ // interferes with attribute spreading on hydration-claimed nodes (wiping the
100
+ // SSR attributes).
101
+ let element: Element;
102
+ switch (props.tag) {
103
+ case 'style': {
104
+ const attrs = {
105
+ ...props.attrs,
106
+ innerHTML:
107
+ typeof props.children === 'string' ? props.children : undefined,
108
+ };
109
+ element = (<style {...attrs} />) as unknown as Element;
110
+ break;
111
+ }
112
+ case 'meta':
113
+ element = (<meta {...props.attrs} />) as unknown as Element;
114
+ break;
115
+ default:
116
+ element = (<link {...props.attrs} />) as unknown as Element;
117
+ }
118
+
119
+ // Move the rendered/hydrated element into <head> when <HeadContent /> is
120
+ // placed in <body>. No-op when already in <head>. Called unconditionally so
121
+ // server and client register the same primitives (see Title).
122
+ useRelocateToHead(() => element);
123
+
124
+ return element as unknown as JSX.Element;
125
+ }
126
+
127
+ function Title(props: {
128
+ attrs?: Record<string, any>;
129
+ children?: unknown;
130
+ }): JSX.Element | null {
131
+ let el: HTMLTitleElement | undefined;
132
+
133
+ // IMPORTANT: call these hooks UNCONDITIONALLY (do not guard with isServer).
134
+ // Solid's hydration relies on the component body registering the same
135
+ // reactive primitives in the same order on the server and the client. An
136
+ // `if (!isServer)` guard would skip them on the server but run them on the
137
+ // client, desyncing the hydration owner tree and causing the server <title>
138
+ // to be left unclaimed (and a duplicate appended). These hooks are no-ops
139
+ // during SSR anyway.
140
+
141
+ // Move the rendered/hydrated <title> into <head> when <HeadContent /> is
142
+ // placed in <body>. No-op when already in <head>.
143
+ useRelocateToHead(() => el);
144
+
145
+ // Keep document.title in sync during client-side navigation.
146
+ createEffect(
147
+ () => props.children,
148
+ titleText => {
149
+ document.title = typeof titleText === 'string' ? titleText : '';
150
+ },
151
+ );
152
+
153
+ return (
154
+ <title ref={e => (el = e)} {...props.attrs}>
155
+ {props.children as string}
156
+ </title>
157
+ );
158
+ }
159
+
160
+ function Script(props: {
161
+ attrs?: Record<string, any>;
162
+ children?: unknown;
163
+ }): JSX.Element | null {
164
+ const router = useRouter();
165
+ const attrs = props.attrs;
166
+ const children = props.children;
167
+
168
+ const dataScript =
169
+ typeof attrs?.type === 'string' &&
170
+ attrs.type !== '' &&
171
+ attrs.type !== 'text/javascript' &&
172
+ attrs.type !== 'module';
173
+
174
+ // --- Server rendering ---
175
+ if (isServer ?? router.isServer) {
176
+ if (attrs?.src) {
177
+ return <script {...attrs} />;
178
+ }
179
+
180
+ if (typeof children === 'string') {
181
+ return <script {...attrs} innerHTML={children} />;
182
+ }
183
+
184
+ return null;
185
+ }
186
+
187
+ // --- Client rendering ---
188
+
189
+ // Data scripts (e.g. application/ld+json) are rendered in the tree;
190
+ // they don't need to execute.
191
+ if (dataScript && typeof children === 'string') {
192
+ return <script {...attrs} innerHTML={children} />;
193
+ }
194
+
195
+ // For executable scripts, use imperative DOM injection so the browser
196
+ // actually executes them during client-side navigation. The injection is
197
+ // idempotent (it checks for an already-present matching script), so the
198
+ // server-rendered script node is reused rather than duplicated.
199
+ createEffect(
200
+ () => ({ attrs, children, dataScript }) as const,
201
+ ({ attrs, children, dataScript }) => {
202
+ if (dataScript) return;
203
+
204
+ let script: HTMLScriptElement | undefined;
205
+
206
+ if (attrs?.src) {
207
+ const normSrc = (() => {
208
+ try {
209
+ const base = document.baseURI || window.location.href;
210
+ return new URL(attrs.src, base).href;
211
+ } catch {
212
+ return attrs.src;
213
+ }
214
+ })();
215
+ const existingScript = Array.from(
216
+ document.querySelectorAll('script[src]'),
217
+ ).find(el => (el as HTMLScriptElement).src === normSrc);
218
+
219
+ if (existingScript) {
220
+ return;
221
+ }
222
+
223
+ script = document.createElement('script');
224
+
225
+ for (const [key, value] of Object.entries(attrs)) {
226
+ if (value !== undefined && value !== false) {
227
+ script.setAttribute(
228
+ key,
229
+ typeof value === 'boolean' ? '' : String(value),
230
+ );
231
+ }
232
+ }
233
+
234
+ document.head.appendChild(script);
235
+ } else if (typeof children === 'string') {
236
+ const typeAttr =
237
+ typeof attrs?.type === 'string' ? attrs.type : 'text/javascript';
238
+ const nonceAttr =
239
+ typeof attrs?.nonce === 'string' ? attrs.nonce : undefined;
240
+ const existingScript = Array.from(
241
+ document.querySelectorAll('script:not([src])'),
242
+ ).find(el => {
243
+ if (!(el instanceof HTMLScriptElement)) return false;
244
+ const sType = el.getAttribute('type') ?? 'text/javascript';
245
+ const sNonce = el.getAttribute('nonce') ?? undefined;
246
+ return (
247
+ el.textContent === children &&
248
+ sType === typeAttr &&
249
+ sNonce === nonceAttr
250
+ );
251
+ });
252
+
253
+ if (existingScript) {
254
+ return;
255
+ }
256
+
257
+ script = document.createElement('script');
258
+ script.textContent = children;
259
+
260
+ if (attrs) {
261
+ for (const [key, value] of Object.entries(attrs)) {
262
+ if (value !== undefined && value !== false) {
263
+ script.setAttribute(
264
+ key,
265
+ typeof value === 'boolean' ? '' : String(value),
266
+ );
267
+ }
268
+ }
269
+ }
270
+
271
+ document.head.appendChild(script);
272
+ }
273
+
274
+ return () => {
275
+ if (script?.parentNode) {
276
+ script.parentNode.removeChild(script);
277
+ }
278
+ };
279
+ },
280
+ );
281
+
282
+ return null;
283
+ }