@lovrozagar/flare 0.1.0

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 (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/package.json +146 -0
  4. package/scripts/download-fonts.ts +136 -0
  5. package/scripts/populate-fonts.ts +721 -0
  6. package/src/await.ts +2 -0
  7. package/src/boundaries/index.ts +99 -0
  8. package/src/broadcast/channel.ts +65 -0
  9. package/src/broadcast/context.ts +12 -0
  10. package/src/broadcast/index.ts +75 -0
  11. package/src/broadcast/provider.tsx +7 -0
  12. package/src/build-location.ts +2 -0
  13. package/src/caches/index.ts +384 -0
  14. package/src/client/index.ts +75 -0
  15. package/src/client.tsx +2 -0
  16. package/src/codegen/index.ts +41 -0
  17. package/src/components/await.tsx +140 -0
  18. package/src/components/dev-error-overlay.tsx +154 -0
  19. package/src/components/direction-script.tsx +22 -0
  20. package/src/components/index.ts +150 -0
  21. package/src/components/locale-script.tsx +26 -0
  22. package/src/components/reset-css.tsx +14 -0
  23. package/src/components/ssr-context.tsx +37 -0
  24. package/src/components/theme-script.tsx +22 -0
  25. package/src/components/view-transition-css.tsx +21 -0
  26. package/src/config/index.ts +84 -0
  27. package/src/create-client-only-fn.ts +1 -0
  28. package/src/create-isomorphic-fn.ts +2 -0
  29. package/src/create-layout.ts +1 -0
  30. package/src/create-page.ts +1 -0
  31. package/src/create-path-segment.ts +1 -0
  32. package/src/create-query-client-getter.ts +2 -0
  33. package/src/create-root-layout.ts +1 -0
  34. package/src/create-router.ts +8 -0
  35. package/src/create-server-only-fn.ts +1 -0
  36. package/src/dedupe/index.ts +151 -0
  37. package/src/defer/index.ts +82 -0
  38. package/src/direction/index.tsx +163 -0
  39. package/src/direction.ts +3 -0
  40. package/src/duration/index.ts +18 -0
  41. package/src/env-fn/index.ts +101 -0
  42. package/src/env-fn.ts +2 -0
  43. package/src/errors/index.ts +166 -0
  44. package/src/fonts/abeezee.ts +50 -0
  45. package/src/fonts/abel.ts +26 -0
  46. package/src/fonts/abril-fatface.ts +34 -0
  47. package/src/fonts/acme.ts +26 -0
  48. package/src/fonts/advent-pro.ts +92 -0
  49. package/src/fonts/alegreya-sans.ts +228 -0
  50. package/src/fonts/alegreya.ts +228 -0
  51. package/src/fonts/alex-brush.ts +42 -0
  52. package/src/fonts/alfa-slab-one.ts +42 -0
  53. package/src/fonts/alice.ts +48 -0
  54. package/src/fonts/amatic-sc.ts +94 -0
  55. package/src/fonts/amiri.ts +114 -0
  56. package/src/fonts/antic-slab.ts +26 -0
  57. package/src/fonts/anton.ts +42 -0
  58. package/src/fonts/archivo-black.ts +34 -0
  59. package/src/fonts/archivo-narrow.ts +114 -0
  60. package/src/fonts/archivo.ts +66 -0
  61. package/src/fonts/arimo.ts +256 -0
  62. package/src/fonts/arvo.ts +50 -0
  63. package/src/fonts/asap.ts +66 -0
  64. package/src/fonts/assistant.ts +64 -0
  65. package/src/fonts/baloo-2.ts +82 -0
  66. package/src/fonts/barlow-condensed.ts +114 -0
  67. package/src/fonts/barlow-semi-condensed.ts +114 -0
  68. package/src/fonts/barlow.ts +114 -0
  69. package/src/fonts/be-vietnam-pro.ts +114 -0
  70. package/src/fonts/bebas-neue.ts +34 -0
  71. package/src/fonts/besley.ts +82 -0
  72. package/src/fonts/biorhyme.ts +50 -0
  73. package/src/fonts/bitter.ts +94 -0
  74. package/src/fonts/black-ops-one.ts +49 -0
  75. package/src/fonts/bree-serif.ts +34 -0
  76. package/src/fonts/bricolage-grotesque.ts +66 -0
  77. package/src/fonts/cabin.ts +114 -0
  78. package/src/fonts/cairo.ts +66 -0
  79. package/src/fonts/cardo.ts +193 -0
  80. package/src/fonts/catamaran.ts +41 -0
  81. package/src/fonts/caveat.ts +78 -0
  82. package/src/fonts/chakra-petch.ts +142 -0
  83. package/src/fonts/chivo.ts +66 -0
  84. package/src/fonts/cinzel.ts +50 -0
  85. package/src/fonts/comfortaa.ts +109 -0
  86. package/src/fonts/commissioner.ts +64 -0
  87. package/src/fonts/component.tsx +27 -0
  88. package/src/fonts/cormorant-garamond.ts +171 -0
  89. package/src/fonts/cormorant.ts +170 -0
  90. package/src/fonts/courgette.ts +34 -0
  91. package/src/fonts/courier-prime.ts +82 -0
  92. package/src/fonts/create-font.ts +47 -0
  93. package/src/fonts/create-registry-font.ts +40 -0
  94. package/src/fonts/crete-round.ts +50 -0
  95. package/src/fonts/crimson-text.ts +114 -0
  96. package/src/fonts/css.ts +86 -0
  97. package/src/fonts/dancing-script.ts +66 -0
  98. package/src/fonts/dm-mono.ts +50 -0
  99. package/src/fonts/dm-sans.ts +50 -0
  100. package/src/fonts/dm-serif-display.ts +50 -0
  101. package/src/fonts/domine.ts +50 -0
  102. package/src/fonts/dosis.ts +66 -0
  103. package/src/fonts/eb-garamond.ts +228 -0
  104. package/src/fonts/eczar.ts +94 -0
  105. package/src/fonts/encode-sans.ts +42 -0
  106. package/src/fonts/epilogue.ts +66 -0
  107. package/src/fonts/exo-2.ts +94 -0
  108. package/src/fonts/exo.ts +66 -0
  109. package/src/fonts/figtree.ts +82 -0
  110. package/src/fonts/fira-code.ts +121 -0
  111. package/src/fonts/fira-sans-condensed.ts +228 -0
  112. package/src/fonts/fira-sans.ts +228 -0
  113. package/src/fonts/fjalla-one.ts +49 -0
  114. package/src/fonts/font-urls.gen.json +1962 -0
  115. package/src/fonts/fraunces.ts +66 -0
  116. package/src/fonts/fredoka.ts +64 -0
  117. package/src/fonts/gelasio.ts +114 -0
  118. package/src/fonts/gloria-hallelujah.ts +34 -0
  119. package/src/fonts/gothic-a1.ts +124 -0
  120. package/src/fonts/great-vibes.ts +64 -0
  121. package/src/fonts/gudea.ts +66 -0
  122. package/src/fonts/heebo.ts +57 -0
  123. package/src/fonts/hind-siliguri.ts +66 -0
  124. package/src/fonts/hind.ts +66 -0
  125. package/src/fonts/ibm-plex-mono.ts +172 -0
  126. package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
  127. package/src/fonts/ibm-plex-sans.ts +199 -0
  128. package/src/fonts/ibm-plex-serif.ts +171 -0
  129. package/src/fonts/inconsolata.ts +66 -0
  130. package/src/fonts/index.ts +4 -0
  131. package/src/fonts/indie-flower.ts +34 -0
  132. package/src/fonts/instrument-sans.ts +82 -0
  133. package/src/fonts/instrument-serif.ts +50 -0
  134. package/src/fonts/inter-display.ts +52 -0
  135. package/src/fonts/inter-tight.ts +124 -0
  136. package/src/fonts/inter.ts +123 -0
  137. package/src/fonts/jetbrains-mono.ts +199 -0
  138. package/src/fonts/josefin-sans.ts +114 -0
  139. package/src/fonts/josefin-slab.ts +50 -0
  140. package/src/fonts/jost.ts +64 -0
  141. package/src/fonts/kalam.ts +66 -0
  142. package/src/fonts/kanit.ts +142 -0
  143. package/src/fonts/karla.ts +82 -0
  144. package/src/fonts/khand.ts +66 -0
  145. package/src/fonts/lato.ts +82 -0
  146. package/src/fonts/league-spartan.ts +42 -0
  147. package/src/fonts/lexend-deca.ts +42 -0
  148. package/src/fonts/lexend.ts +42 -0
  149. package/src/fonts/libre-baskerville.ts +82 -0
  150. package/src/fonts/libre-franklin.ts +95 -0
  151. package/src/fonts/lilita-one.ts +34 -0
  152. package/src/fonts/lobster-two.ts +50 -0
  153. package/src/fonts/lobster.ts +56 -0
  154. package/src/fonts/lora.ts +235 -0
  155. package/src/fonts/m-plus-rounded-1c.ts +138 -0
  156. package/src/fonts/manrope.ts +109 -0
  157. package/src/fonts/maven-pro.ts +66 -0
  158. package/src/fonts/merriweather-sans.ts +142 -0
  159. package/src/fonts/merriweather.ts +171 -0
  160. package/src/fonts/montserrat-alternates.ts +171 -0
  161. package/src/fonts/montserrat.ts +94 -0
  162. package/src/fonts/mukta.ts +66 -0
  163. package/src/fonts/mulish.ts +170 -0
  164. package/src/fonts/nanum-gothic.ts +34 -0
  165. package/src/fonts/nanum-myeongjo.ts +34 -0
  166. package/src/fonts/neuton.ts +66 -0
  167. package/src/fonts/newsreader.ts +114 -0
  168. package/src/fonts/noticia-text.ts +114 -0
  169. package/src/fonts/noto-sans-arabic.ts +58 -0
  170. package/src/fonts/noto-sans-hk.ts +49 -0
  171. package/src/fonts/noto-sans-jp.ts +49 -0
  172. package/src/fonts/noto-sans-kr.ts +49 -0
  173. package/src/fonts/noto-sans-sc.ts +49 -0
  174. package/src/fonts/noto-sans-tc.ts +49 -0
  175. package/src/fonts/noto-sans.ts +140 -0
  176. package/src/fonts/noto-serif-jp.ts +80 -0
  177. package/src/fonts/noto-serif.ts +140 -0
  178. package/src/fonts/nunito-sans.ts +170 -0
  179. package/src/fonts/nunito.ts +170 -0
  180. package/src/fonts/old-standard-tt.ts +133 -0
  181. package/src/fonts/onest.ts +48 -0
  182. package/src/fonts/open-sans.ts +340 -0
  183. package/src/fonts/orbitron.ts +34 -0
  184. package/src/fonts/oswald.ts +94 -0
  185. package/src/fonts/outfit.ts +34 -0
  186. package/src/fonts/overpass.ts +94 -0
  187. package/src/fonts/oxygen.ts +50 -0
  188. package/src/fonts/pacifico.ts +56 -0
  189. package/src/fonts/passion-one.ts +50 -0
  190. package/src/fonts/pathway-gothic-one.ts +34 -0
  191. package/src/fonts/permanent-marker.ts +26 -0
  192. package/src/fonts/philosopher.ts +172 -0
  193. package/src/fonts/play.ts +109 -0
  194. package/src/fonts/playfair-display.ts +142 -0
  195. package/src/fonts/plus-jakarta-sans.ts +142 -0
  196. package/src/fonts/poppins.ts +114 -0
  197. package/src/fonts/prompt.ts +142 -0
  198. package/src/fonts/pt-sans-narrow.ts +78 -0
  199. package/src/fonts/pt-sans.ts +138 -0
  200. package/src/fonts/pt-serif.ts +138 -0
  201. package/src/fonts/public-sans.ts +66 -0
  202. package/src/fonts/questrial.ts +42 -0
  203. package/src/fonts/quicksand.ts +66 -0
  204. package/src/fonts/rajdhani.ts +66 -0
  205. package/src/fonts/raleway.ts +94 -0
  206. package/src/fonts/recursive.ts +80 -0
  207. package/src/fonts/red-hat-display.ts +82 -0
  208. package/src/fonts/red-rose.ts +66 -0
  209. package/src/fonts/registry.gen.ts +173 -0
  210. package/src/fonts/righteous.ts +34 -0
  211. package/src/fonts/roboto-condensed.ts +124 -0
  212. package/src/fonts/roboto-flex.ts +64 -0
  213. package/src/fonts/roboto-mono.ts +199 -0
  214. package/src/fonts/roboto-slab.ts +72 -0
  215. package/src/fonts/roboto.ts +156 -0
  216. package/src/fonts/rokkitt.ts +66 -0
  217. package/src/fonts/rubik.ts +199 -0
  218. package/src/fonts/sacramento.ts +34 -0
  219. package/src/fonts/saira.ts +66 -0
  220. package/src/fonts/sarabun.ts +142 -0
  221. package/src/fonts/satisfy.ts +26 -0
  222. package/src/fonts/sawarabi-mincho.ts +41 -0
  223. package/src/fonts/shadows-into-light.ts +34 -0
  224. package/src/fonts/signika-negative.ts +66 -0
  225. package/src/fonts/signika.ts +66 -0
  226. package/src/fonts/slabo-27px.ts +34 -0
  227. package/src/fonts/sora.ts +50 -0
  228. package/src/fonts/source-code-pro.ts +228 -0
  229. package/src/fonts/source-sans-3.ts +228 -0
  230. package/src/fonts/source-serif-4.ts +199 -0
  231. package/src/fonts/space-grotesk.ts +66 -0
  232. package/src/fonts/space-mono.ts +114 -0
  233. package/src/fonts/spectral.ts +170 -0
  234. package/src/fonts/tajawal.ts +50 -0
  235. package/src/fonts/teko.ts +66 -0
  236. package/src/fonts/tenor-sans.ts +41 -0
  237. package/src/fonts/tinos.ts +256 -0
  238. package/src/fonts/titillium-web.ts +82 -0
  239. package/src/fonts/types.ts +43 -0
  240. package/src/fonts/ubuntu-condensed.ts +63 -0
  241. package/src/fonts/ubuntu-mono.ts +195 -0
  242. package/src/fonts/ubuntu.ts +195 -0
  243. package/src/fonts/unbounded.ts +94 -0
  244. package/src/fonts/unicode-ranges.gen.ts +28 -0
  245. package/src/fonts/urbanist.ts +50 -0
  246. package/src/fonts/varela-round.ts +49 -0
  247. package/src/fonts/vollkorn.ts +199 -0
  248. package/src/fonts/work-sans.ts +66 -0
  249. package/src/fonts/yanone-kaffeesatz.ts +128 -0
  250. package/src/fonts/yantramanav.ts +66 -0
  251. package/src/fonts/yellowtail.ts +34 -0
  252. package/src/fonts/young-serif.ts +34 -0
  253. package/src/fonts/zen-kaku-gothic-new.ts +64 -0
  254. package/src/fonts/zilla-slab.ts +82 -0
  255. package/src/form/index.tsx +205 -0
  256. package/src/generators/index.ts +1589 -0
  257. package/src/head-client/index.ts +649 -0
  258. package/src/history/index.ts +210 -0
  259. package/src/hooks.ts +61 -0
  260. package/src/hydrate/index.tsx +381 -0
  261. package/src/hydration/index.ts +187 -0
  262. package/src/i18n/index.ts +250 -0
  263. package/src/i18n/jsx.tsx +42 -0
  264. package/src/i18n/message-format.ts +174 -0
  265. package/src/image/index.tsx +473 -0
  266. package/src/image.ts +12 -0
  267. package/src/intercept-outlet/index.tsx +18 -0
  268. package/src/intercept-outlet.ts +2 -0
  269. package/src/internal/index.ts +143 -0
  270. package/src/internal/once-idle.ts +24 -0
  271. package/src/internal.ts +12 -0
  272. package/src/lazy/index.tsx +205 -0
  273. package/src/link/index.tsx +367 -0
  274. package/src/loader-pipeline/index.ts +639 -0
  275. package/src/locale/index.tsx +137 -0
  276. package/src/locale.ts +3 -0
  277. package/src/logger.ts +22 -0
  278. package/src/middleware/builtins/api-proxy.ts +64 -0
  279. package/src/middleware/builtins/cdn-proxy.ts +58 -0
  280. package/src/middleware/builtins/i18n.ts +256 -0
  281. package/src/middleware/builtins/keepalive.ts +24 -0
  282. package/src/middleware/builtins/markdown-negotiation.ts +141 -0
  283. package/src/middleware/builtins/static-assets.ts +32 -0
  284. package/src/middleware/index.ts +223 -0
  285. package/src/module-graph/index.ts +181 -0
  286. package/src/mount/index.ts +146 -0
  287. package/src/navigate.ts +1 -0
  288. package/src/navigation/index.ts +1278 -0
  289. package/src/navigation/types.ts +59 -0
  290. package/src/navigation-progress/index.tsx +247 -0
  291. package/src/navigation-progress.ts +1 -0
  292. package/src/ndjson-client/index.ts +376 -0
  293. package/src/ndjson-server/index.ts +284 -0
  294. package/src/outlet/index.tsx +633 -0
  295. package/src/outlet/types.ts +243 -0
  296. package/src/plugins/css-transform.ts +70 -0
  297. package/src/plugins/dev-cdn-cache.ts +454 -0
  298. package/src/plugins/dev-dashboard/client.tsx +2603 -0
  299. package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
  300. package/src/plugins/dev-dashboard/plugin.ts +404 -0
  301. package/src/plugins/dev-prerender.ts +168 -0
  302. package/src/plugins/dev-server.ts +327 -0
  303. package/src/plugins/generate-watch.ts +32 -0
  304. package/src/plugins/image-plugin.ts +171 -0
  305. package/src/plugins/index.ts +445 -0
  306. package/src/plugins/options.ts +37 -0
  307. package/src/plugins/parse-utils.ts +59 -0
  308. package/src/plugins/prerender-plugin.ts +144 -0
  309. package/src/plugins/purge.ts +242 -0
  310. package/src/plugins/server-fn.ts +641 -0
  311. package/src/plugins/service-worker.ts +225 -0
  312. package/src/plugins/solid-jsx.d.ts +12 -0
  313. package/src/plugins/sx-ast/atomic.ts +166 -0
  314. package/src/plugins/sx-ast/evaluator.ts +235 -0
  315. package/src/plugins/sx-ast/index.ts +372 -0
  316. package/src/plugins/sx-ast/parser.ts +22 -0
  317. package/src/plugins/sx-ast/rewrite.ts +763 -0
  318. package/src/plugins/sx.d.ts +14 -0
  319. package/src/plugins/tw-compile.ts +240 -0
  320. package/src/plugins/types.ts +41 -0
  321. package/src/plugins/virtual.d.ts +110 -0
  322. package/src/plugins/virtual.ts +184 -0
  323. package/src/preload/index.ts +77 -0
  324. package/src/prerender/index.ts +431 -0
  325. package/src/query-client/index.tsx +168 -0
  326. package/src/registry/index.ts +88 -0
  327. package/src/reset-css.ts +1 -0
  328. package/src/revalidation/index.ts +84 -0
  329. package/src/rewrite/index.ts +83 -0
  330. package/src/route-builder/create-layout.ts +655 -0
  331. package/src/route-builder/create-page.ts +806 -0
  332. package/src/route-builder/create-path-segment.ts +34 -0
  333. package/src/route-builder/create-root-layout.ts +608 -0
  334. package/src/route-builder/index.ts +49 -0
  335. package/src/route-builder/register.ts +242 -0
  336. package/src/route-builder/types.ts +294 -0
  337. package/src/router-config/index.ts +91 -0
  338. package/src/router-primitives/index.ts +25 -0
  339. package/src/router-primitives/location.ts +29 -0
  340. package/src/router-primitives/match-id.ts +111 -0
  341. package/src/router-primitives/paths.ts +109 -0
  342. package/src/router-primitives/tree.ts +243 -0
  343. package/src/router-primitives/types.ts +74 -0
  344. package/src/router.ts +31 -0
  345. package/src/search-engine/bing.ts +32 -0
  346. package/src/search-engine/google-jwt.ts +95 -0
  347. package/src/search-engine/google.ts +139 -0
  348. package/src/search-engine/index-now.ts +64 -0
  349. package/src/search-engine/index.ts +12 -0
  350. package/src/security/index.ts +230 -0
  351. package/src/server/index.ts +286 -0
  352. package/src/server-context/browser.ts +113 -0
  353. package/src/server-context/index.ts +217 -0
  354. package/src/server-fn/index.ts +710 -0
  355. package/src/server-fn-query.ts +169 -0
  356. package/src/server-handler/etag.ts +51 -0
  357. package/src/server-handler/headers.ts +5 -0
  358. package/src/server-handler/index.ts +1664 -0
  359. package/src/server-handler/types.ts +24 -0
  360. package/src/server-handler/validate-static-params.ts +130 -0
  361. package/src/server.ts +2 -0
  362. package/src/service-worker/template.ts +153 -0
  363. package/src/service-worker-hook/index.ts +70 -0
  364. package/src/sitemap/index.ts +231 -0
  365. package/src/ssr/critical-css.ts +150 -0
  366. package/src/ssr/head-prefix.ts +65 -0
  367. package/src/ssr/head.ts +259 -0
  368. package/src/ssr/index.tsx +879 -0
  369. package/src/state-parser/index.ts +266 -0
  370. package/src/store/filesystem.ts +131 -0
  371. package/src/store/index.ts +29 -0
  372. package/src/styles/index.ts +1200 -0
  373. package/src/styles/sx-types.ts +11 -0
  374. package/src/testing/index.ts +251 -0
  375. package/src/testing.ts +2 -0
  376. package/src/theme/index.tsx +232 -0
  377. package/src/theme.ts +3 -0
  378. package/src/tracing/index.ts +4 -0
  379. package/src/tracing/noop.ts +13 -0
  380. package/src/tracing/otel.ts +37 -0
  381. package/src/tracing/timing.ts +34 -0
  382. package/src/tracing/types.ts +9 -0
  383. package/src/url/index.ts +140 -0
  384. package/src/use-router.ts +11 -0
  385. package/src/use-suspense-query.ts +2 -0
  386. package/src/validation/index.ts +57 -0
  387. package/src/view-transition-css.ts +1 -0
@@ -0,0 +1,22 @@
1
+ import type { JSX } from "solid-js";
2
+ import { NoHydration } from "solid-js/web";
3
+ import type { ThemeConfig } from "../theme.ts";
4
+ import { getThemeScript } from "../theme.ts";
5
+ import { useSSRContext } from "./ssr-context.tsx";
6
+
7
+ export interface ThemeScriptProps {
8
+ config?: ThemeConfig;
9
+ }
10
+
11
+ export function ThemeScript(props: ThemeScriptProps): JSX.Element {
12
+ const ctx = useSSRContext();
13
+ const nonce = ctx?.nonce ?? "";
14
+ const config = props.config ?? ctx?.theme;
15
+ const script = getThemeScript(config);
16
+
17
+ return (
18
+ <NoHydration>
19
+ <script innerHTML={script} nonce={nonce} />
20
+ </NoHydration>
21
+ ) as unknown as JSX.Element;
22
+ }
@@ -0,0 +1,21 @@
1
+ import type { JSX } from "solid-js";
2
+ import { useSSRContext } from "./ssr-context.tsx";
3
+
4
+ /* Duplicated from index.ts to avoid circular import (index re-exports this component) */
5
+ function buildViewTransitionCss(duration: number): string {
6
+ return `@view-transition{navigation:auto}::view-transition-old(*),::view-transition-new(*){animation-duration:${duration}ms}`;
7
+ }
8
+
9
+ const DEFAULT_CSS = buildViewTransitionCss(175);
10
+
11
+ export interface ViewTransitionCSSProps {
12
+ duration?: number;
13
+ }
14
+
15
+ export function ViewTransitionCSS(props: ViewTransitionCSSProps): JSX.Element {
16
+ const ctx = useSSRContext();
17
+ const nonce = ctx?.nonce ?? "";
18
+ const css = props.duration !== undefined ? buildViewTransitionCss(props.duration) : DEFAULT_CSS;
19
+
20
+ return (<style innerHTML={css} nonce={nonce} />) as unknown as JSX.Element;
21
+ }
@@ -0,0 +1,84 @@
1
+ export interface GlobalBoundariesConfig {
2
+ error?: string;
3
+ notFound?: string;
4
+ unauthorized?: string;
5
+ }
6
+
7
+ export interface CssConfig {
8
+ scoped?: boolean;
9
+ }
10
+
11
+ export interface ServerFnConfig {
12
+ exclude?: RegExp;
13
+ include?: RegExp;
14
+ }
15
+
16
+ export interface FlareBuildConfig {
17
+ clientEntryFilePath?: string;
18
+ css?: CssConfig | false;
19
+ generated?: {
20
+ routesFilePath?: string;
21
+ typesFilePath?: string;
22
+ };
23
+ globalBoundaries?: GlobalBoundariesConfig;
24
+ ignorePrefix?: string;
25
+ serverEntryFilePath?: string;
26
+ serverFn?: ServerFnConfig | false;
27
+ viewTransitions?: boolean;
28
+ }
29
+
30
+ const MARKER = Symbol.for("flare/build-config");
31
+
32
+ export interface MarkedFlareBuildConfig extends FlareBuildConfig {
33
+ [key: symbol]: true;
34
+ }
35
+
36
+ export function createFlareBuild(config: FlareBuildConfig): MarkedFlareBuildConfig {
37
+ return { ...config, [MARKER]: true } as MarkedFlareBuildConfig;
38
+ }
39
+
40
+ export function isFlareBuildConfig(value: unknown): value is MarkedFlareBuildConfig {
41
+ return (
42
+ value !== null &&
43
+ value !== undefined &&
44
+ typeof value === "object" &&
45
+ MARKER in (value as Record<symbol, unknown>) &&
46
+ (value as Record<symbol, unknown>)[MARKER] === true
47
+ );
48
+ }
49
+
50
+ export function validateFlareBuildConfig(value: unknown): string[] {
51
+ const errors: string[] = [];
52
+
53
+ if (!isFlareBuildConfig(value)) {
54
+ errors.push("Config missing flare/build-config marker — use createFlareBuild()");
55
+ return errors;
56
+ }
57
+
58
+ const config = value as FlareBuildConfig;
59
+
60
+ if (config.ignorePrefix !== undefined && typeof config.ignorePrefix !== "string") {
61
+ errors.push(`ignorePrefix must be a string, got ${typeof config.ignorePrefix}`);
62
+ }
63
+
64
+ if (config.serverFn !== undefined && config.serverFn !== false) {
65
+ if (typeof config.serverFn !== "object" || config.serverFn === null) {
66
+ errors.push(`serverFn must be an object or false, got ${typeof config.serverFn}`);
67
+ } else {
68
+ if (config.serverFn.include !== undefined && !(config.serverFn.include instanceof RegExp)) {
69
+ errors.push(`serverFn.include must be a RegExp, got ${typeof config.serverFn.include}`);
70
+ }
71
+ if (config.serverFn.exclude !== undefined && !(config.serverFn.exclude instanceof RegExp)) {
72
+ errors.push(`serverFn.exclude must be a RegExp, got ${typeof config.serverFn.exclude}`);
73
+ }
74
+ }
75
+ }
76
+
77
+ if (config.css !== undefined && config.css !== false) {
78
+ if (typeof config.css !== "object" || config.css === null) {
79
+ errors.push(`css must be an object or false, got ${typeof config.css}`);
80
+ }
81
+ }
82
+
83
+ return errors;
84
+ }
@@ -0,0 +1 @@
1
+ export { createClientOnlyFn } from "./env-fn.ts";
@@ -0,0 +1,2 @@
1
+ export type { ClientBoundFn, IsomorphicFn, IsomorphicFnBuilder, ServerBoundFn } from "./env-fn.ts";
2
+ export { createIsomorphicFn } from "./env-fn.ts";
@@ -0,0 +1 @@
1
+ export { createLayout } from "./route-builder/index.ts";
@@ -0,0 +1 @@
1
+ export { createPage } from "./route-builder/index.ts";
@@ -0,0 +1 @@
1
+ export { createPathSegment } from "./route-builder/index.ts";
@@ -0,0 +1,2 @@
1
+ export type { QueryClientGetterOptions } from "./query-client/index.tsx";
2
+ export { createQueryClientGetter } from "./query-client/index.tsx";
@@ -0,0 +1 @@
1
+ export { createRootLayout } from "./route-builder/index.ts";
@@ -0,0 +1,8 @@
1
+ export type {
2
+ PrefetchStrategy,
3
+ RouterCacheConfig,
4
+ RouterConfig,
5
+ TrailingSlashMode,
6
+ ViewTransitionDefaults,
7
+ } from "./router-config/index.ts";
8
+ export { createRouter } from "./router-config/index.ts";
@@ -0,0 +1 @@
1
+ export { createServerOnlyFn } from "./env-fn.ts";
@@ -0,0 +1,151 @@
1
+ import { warn } from "../logger.ts";
2
+ import { getServerRequestContext } from "@lovrozagar/flare/server-context";
3
+
4
+ const DEDUPE_CACHE_KEY = "__flare_dedupe";
5
+ const FETCH_CACHE_KEY = "__flare_fetch_dedupe";
6
+
7
+ let fnCounter = 0;
8
+
9
+ function getDedupeCache(): Map<string, Promise<unknown>> {
10
+ const ctx = getServerRequestContext();
11
+ let cache = ctx.get<Map<string, Promise<unknown>>>(DEDUPE_CACHE_KEY);
12
+ if (!cache) {
13
+ cache = new Map();
14
+ ctx.set(DEDUPE_CACHE_KEY, cache);
15
+ }
16
+ return cache;
17
+ }
18
+
19
+ function getFetchCache(): Map<string, Promise<Response>> | undefined {
20
+ try {
21
+ const ctx = getServerRequestContext();
22
+ let cache = ctx.get<Map<string, Promise<Response>>>(FETCH_CACHE_KEY);
23
+ if (!cache) {
24
+ cache = new Map();
25
+ ctx.set(FETCH_CACHE_KEY, cache);
26
+ }
27
+ return cache;
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ export function dedupe<TArgs extends unknown[], TResult>(
34
+ fn: (...args: TArgs) => Promise<TResult>,
35
+ ): (...args: TArgs) => Promise<TResult> {
36
+ fnCounter = (fnCounter + 1) % Number.MAX_SAFE_INTEGER;
37
+ const fnId = fnCounter;
38
+ return (...args: TArgs): Promise<TResult> => {
39
+ const cache = getDedupeCache();
40
+ /* JSON.stringify maps both undefined and null to "null" — replacer preserves distinction */
41
+ const key = `${fnId}:${JSON.stringify(args, (_, v) => (v === undefined ? "\x00" : v))}`;
42
+ const existing = cache.get(key);
43
+ if (existing) return existing as Promise<TResult>;
44
+ const promise = fn(...args);
45
+ cache.set(key, promise);
46
+ return promise;
47
+ };
48
+ }
49
+
50
+ const EXCLUDED_HEADERS = new Set(["traceparent", "tracestate", "x-request-id", "x-correlation-id"]);
51
+
52
+ function getFetchCacheKey(input: RequestInfo | URL, init?: RequestInit): string | undefined {
53
+ let method = "GET";
54
+ let url: string;
55
+
56
+ if (input instanceof Request) {
57
+ method = input.method.toUpperCase();
58
+ url = input.url;
59
+ } else if (input instanceof URL) {
60
+ url = input.toString();
61
+ } else {
62
+ url = input;
63
+ }
64
+
65
+ if (init?.method) {
66
+ method = init.method.toUpperCase();
67
+ }
68
+
69
+ if (method !== "GET" && method !== "HEAD") {
70
+ return undefined;
71
+ }
72
+
73
+ let headersKey = "";
74
+ const headers = init?.headers ?? (input instanceof Request ? input.headers : undefined);
75
+ if (headers) {
76
+ const entries: Array<[string, string]> = [];
77
+ if (headers instanceof Headers) {
78
+ headers.forEach((v, k) => {
79
+ if (!EXCLUDED_HEADERS.has(k.toLowerCase())) {
80
+ entries.push([k.toLowerCase(), v]);
81
+ }
82
+ });
83
+ } else if (Array.isArray(headers)) {
84
+ for (const [k, v] of headers) {
85
+ if (!EXCLUDED_HEADERS.has(k.toLowerCase())) {
86
+ entries.push([k.toLowerCase(), v]);
87
+ }
88
+ }
89
+ } else {
90
+ for (const k of Object.keys(headers)) {
91
+ if (!EXCLUDED_HEADERS.has(k.toLowerCase())) {
92
+ entries.push([k.toLowerCase(), headers[k] as string]);
93
+ }
94
+ }
95
+ }
96
+ entries.sort((a, b) => a[0].localeCompare(b[0]));
97
+ headersKey = JSON.stringify(entries);
98
+ }
99
+
100
+ return `fetch:${method}:${url}:${headersKey}`;
101
+ }
102
+
103
+ let originalFetch: typeof globalThis.fetch | undefined;
104
+ let enableCount = 0;
105
+
106
+ function createDedupedFetch(baseFetch: typeof globalThis.fetch): typeof globalThis.fetch {
107
+ return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
108
+ const key = getFetchCacheKey(input, init);
109
+ if (!key) return baseFetch(input, init);
110
+
111
+ const cache = getFetchCache();
112
+ if (!cache) return baseFetch(input, init);
113
+
114
+ const existing = cache.get(key);
115
+ if (existing) return (await existing).clone();
116
+
117
+ const promise = baseFetch(input, init);
118
+ cache.set(key, promise);
119
+ /* Clean up cache on rejection so retries within same request can succeed */
120
+ promise.catch((e: unknown) => {
121
+ warn("dedupe", "fetch failed, clearing cache", e);
122
+ cache.delete(key);
123
+ });
124
+ return (await promise).clone();
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Reference-counted enable/disable to avoid race conditions
130
+ * when concurrent requests share the same isolate.
131
+ */
132
+ export function enableFetchDedupe(): void {
133
+ enableCount++;
134
+ if (enableCount === 1) {
135
+ originalFetch = globalThis.fetch;
136
+ globalThis.fetch = createDedupedFetch(originalFetch);
137
+ }
138
+ }
139
+
140
+ export function disableFetchDedupe(): void {
141
+ enableCount--;
142
+ if (enableCount <= 0) {
143
+ if (originalFetch) globalThis.fetch = originalFetch;
144
+ originalFetch = undefined;
145
+ enableCount = 0;
146
+ }
147
+ }
148
+
149
+ export function isFetchDedupeEnabled(): boolean {
150
+ return enableCount > 0;
151
+ }
@@ -0,0 +1,82 @@
1
+ import type { StaticDeferMode } from "../route-builder/types.ts";
2
+
3
+ export interface Deferred<T> {
4
+ __deferred: true;
5
+ error?: unknown;
6
+ key: string;
7
+ prerender?: StaticDeferMode;
8
+ promise: Promise<T>;
9
+ }
10
+
11
+ export interface DeferContext {
12
+ defer: DeferFn;
13
+ entries: () => DeferredEntry[];
14
+ }
15
+
16
+ export type DeferFn = <T>(fn: () => Promise<T>, options?: { key?: string; prerender?: StaticDeferMode }) => Deferred<T>;
17
+
18
+ export interface DeferredEntry {
19
+ key: string;
20
+ matchId: string;
21
+ prerender?: StaticDeferMode;
22
+ promise: Promise<unknown>;
23
+ }
24
+
25
+ export interface DeferContextOptions {
26
+ prefetch?: boolean;
27
+ }
28
+
29
+ export function createDeferContext(matchId: string, opts?: DeferContextOptions): DeferContext {
30
+ const isPrefetch = opts?.prefetch ?? false;
31
+ let counter = 0;
32
+ const cache = new Map<string, Deferred<unknown>>();
33
+ const entryList: DeferredEntry[] = [];
34
+
35
+ const defer: DeferFn = <T>(
36
+ fn: () => Promise<T>,
37
+ options?: { key?: string; prerender?: StaticDeferMode },
38
+ ): Deferred<T> => {
39
+ const key = options?.key ?? `d${counter++}`;
40
+ const existing = cache.get(key);
41
+ if (existing) return existing as Deferred<T>;
42
+
43
+ if (isPrefetch) {
44
+ const deferred: Deferred<T> = { __deferred: true, key, promise: new Promise<T>(() => {}) };
45
+ cache.set(key, deferred as Deferred<unknown>);
46
+ return deferred;
47
+ }
48
+
49
+ let promise: Promise<T>;
50
+ try {
51
+ promise = fn();
52
+ } catch (e) {
53
+ promise = Promise.reject(e);
54
+ }
55
+ const deferred: Deferred<T> = { __deferred: true, key, prerender: options?.prerender, promise };
56
+
57
+ /* Prevent unhandled rejection for both sync throw and async reject paths.
58
+ * Error surfaces when consumer awaits the promise via Promise.allSettled.
59
+ * Also capture error on the deferred object for direct access. */
60
+ promise.catch((e) => {
61
+ deferred.error = e;
62
+ });
63
+ cache.set(key, deferred as Deferred<unknown>);
64
+ entryList.push({ key, matchId, prerender: options?.prerender, promise });
65
+ return deferred;
66
+ };
67
+
68
+ return {
69
+ defer,
70
+ entries: () => entryList,
71
+ };
72
+ }
73
+
74
+ export function isDeferred(value: unknown): value is Deferred<unknown> {
75
+ return (
76
+ value !== null &&
77
+ value !== undefined &&
78
+ typeof value === "object" &&
79
+ "__deferred" in value &&
80
+ (value as Record<string, unknown>).__deferred === true
81
+ );
82
+ }
@@ -0,0 +1,163 @@
1
+ import {
2
+ createContext,
3
+ createEffect,
4
+ createSignal,
5
+ type JSX,
6
+ on,
7
+ onCleanup,
8
+ onMount,
9
+ sharedConfig,
10
+ useContext,
11
+ } from "solid-js";
12
+
13
+ export type Direction = "ltr" | "rtl";
14
+
15
+ export interface DirectionConfig {
16
+ attribute?: string;
17
+ defaultDir?: Direction;
18
+ rtlLocales?: readonly string[];
19
+ storageKey?: string;
20
+ }
21
+
22
+ const DEFAULT_CONFIG: Required<DirectionConfig> = {
23
+ attribute: "data-dir",
24
+ defaultDir: "ltr",
25
+ rtlLocales: ["ar", "he", "fa", "ur"],
26
+ storageKey: "flare.dir",
27
+ };
28
+
29
+ /* Escape for safe interpolation into JS double-quoted strings inside <script> */
30
+ function escapeJsString(s: string): string {
31
+ return s
32
+ .replace(/\\/g, "\\\\")
33
+ .replace(/"/g, '\\"')
34
+ .replace(/<\//g, "<\\/")
35
+ .replace(/\n/g, "\\n")
36
+ .replace(/\r/g, "\\r")
37
+ .replace(/\u2028/g, "\\u2028")
38
+ .replace(/\u2029/g, "\\u2029");
39
+ }
40
+
41
+ export function getDirectionScript(opts?: DirectionConfig): string {
42
+ const attr = escapeJsString(opts?.attribute ?? DEFAULT_CONFIG.attribute);
43
+ const defaultDir = escapeJsString(opts?.defaultDir ?? DEFAULT_CONFIG.defaultDir);
44
+ const storageKey = escapeJsString(opts?.storageKey ?? DEFAULT_CONFIG.storageKey);
45
+ return `((k,d,a)=>{const e=document.documentElement;let v;try{v=localStorage.getItem(k)}catch{}v=v||e.getAttribute("dir")||d;e.setAttribute(a,v);e.setAttribute("dir",v)})("${storageKey}","${defaultDir}","${attr}")`;
46
+ }
47
+
48
+ export function getDirFromLocale(locale: string | undefined, rtlLocales?: readonly string[]): Direction {
49
+ if (!locale) return "ltr";
50
+ const base = locale.split("-")[0]?.toLowerCase() ?? "";
51
+ return (rtlLocales ?? DEFAULT_CONFIG.rtlLocales).includes(base) ? "rtl" : "ltr";
52
+ }
53
+
54
+ /* ── Context ──────────────────────────────────────────────────────── */
55
+
56
+ export interface DirectionContextValue {
57
+ direction: () => Direction;
58
+ getDirFromLocale: (locale: string | undefined) => Direction;
59
+ setDirection: (dir: Direction) => void;
60
+ toggleDirection: () => void;
61
+ }
62
+
63
+ const DirectionCtx = createContext<DirectionContextValue>();
64
+
65
+ export function DirectionProvider(props: { children: JSX.Element; config?: DirectionConfig }): JSX.Element {
66
+ /* SSR: pass through children (context not needed on server, scripts handle direction) */
67
+ if (sharedConfig.context) {
68
+ return props.children;
69
+ }
70
+
71
+ const cfg: Required<DirectionConfig> = {
72
+ attribute: props.config?.attribute ?? DEFAULT_CONFIG.attribute,
73
+ defaultDir: props.config?.defaultDir ?? DEFAULT_CONFIG.defaultDir,
74
+ rtlLocales: props.config?.rtlLocales ?? DEFAULT_CONFIG.rtlLocales,
75
+ storageKey: props.config?.storageKey ?? DEFAULT_CONFIG.storageKey,
76
+ };
77
+
78
+ /* Read initial direction from localStorage, DOM dir attr, or default */
79
+ let initial: Direction = cfg.defaultDir;
80
+ if (typeof localStorage !== "undefined") {
81
+ try {
82
+ const stored = localStorage.getItem(cfg.storageKey);
83
+ if (stored === "ltr" || stored === "rtl") {
84
+ initial = stored;
85
+ }
86
+ } catch {
87
+ /* noop */
88
+ }
89
+ }
90
+ if (initial === cfg.defaultDir && typeof document !== "undefined") {
91
+ const domDir = document.documentElement.getAttribute("dir");
92
+ if (domDir === "ltr" || domDir === "rtl") {
93
+ initial = domDir;
94
+ }
95
+ }
96
+
97
+ const [direction, setDirectionSignal] = createSignal<Direction>(initial);
98
+
99
+ /* Sync direction to DOM */
100
+ createEffect(() => {
101
+ const dir = direction();
102
+ if (typeof document === "undefined") return;
103
+ const el = document.documentElement;
104
+ el.setAttribute(cfg.attribute, dir);
105
+ el.setAttribute("dir", dir);
106
+ });
107
+
108
+ /* Cross-tab sync: StorageEvent fires in OTHER tabs only */
109
+ onMount(() => {
110
+ const handler = (e: StorageEvent) => {
111
+ if (e.key !== cfg.storageKey || e.storageArea !== localStorage) return;
112
+ const v = e.newValue;
113
+ if (v === "ltr" || v === "rtl") {
114
+ setDirectionSignal(v);
115
+ }
116
+ };
117
+ window.addEventListener("storage", handler);
118
+ onCleanup(() => window.removeEventListener("storage", handler));
119
+ });
120
+
121
+ /* Persist to localStorage on changes */
122
+ createEffect(
123
+ on(
124
+ direction,
125
+ (dir) => {
126
+ try {
127
+ localStorage.setItem(cfg.storageKey, dir);
128
+ } catch {
129
+ /* noop */
130
+ }
131
+ },
132
+ { defer: true },
133
+ ),
134
+ );
135
+
136
+ const setDirection = (dir: Direction): void => {
137
+ setDirectionSignal(dir);
138
+ };
139
+
140
+ const toggleDirection = (): void => {
141
+ const current = direction();
142
+ setDirection(current === "ltr" ? "rtl" : "ltr");
143
+ };
144
+
145
+ const dirFromLocale = (locale: string | undefined): Direction => {
146
+ return getDirFromLocale(locale, cfg.rtlLocales);
147
+ };
148
+
149
+ const value: DirectionContextValue = {
150
+ direction,
151
+ getDirFromLocale: dirFromLocale,
152
+ setDirection,
153
+ toggleDirection,
154
+ };
155
+
156
+ return <DirectionCtx.Provider value={value}>{props.children}</DirectionCtx.Provider>;
157
+ }
158
+
159
+ export function useDirection(): DirectionContextValue {
160
+ const ctx = useContext(DirectionCtx);
161
+ if (!ctx) throw new Error("useDirection() called outside DirectionProvider. Wrap your app with <DirectionProvider>.");
162
+ return ctx;
163
+ }
@@ -0,0 +1,3 @@
1
+ export { DirectionScript } from "./components/direction-script.tsx";
2
+ export type { Direction, DirectionConfig, DirectionContextValue } from "./direction/index.tsx";
3
+ export { DirectionProvider, getDirectionScript, getDirFromLocale, useDirection } from "./direction/index.tsx";
@@ -0,0 +1,18 @@
1
+ export type Duration = number | `${number}${"d" | "h" | "m" | "s"}`;
2
+
3
+ const UNITS: Record<string, number> = { d: 86400, h: 3600, m: 60, s: 1 };
4
+ const DURATION_RE = /^(\d+)(s|m|h|d)$/;
5
+
6
+ export function parseSeconds(value: Duration): number {
7
+ if (typeof value === "number") return value;
8
+ const match = value.match(DURATION_RE);
9
+ if (!match || !match[1] || !match[2]) throw new Error(`Invalid duration: ${value}`);
10
+ const unit = UNITS[match[2]];
11
+ if (unit === undefined) throw new Error(`Invalid duration: ${value}`);
12
+ return Number(match[1]) * unit;
13
+ }
14
+
15
+ export function parseMilliseconds(value: Duration): number {
16
+ if (typeof value === "number") return value;
17
+ return parseSeconds(value) * 1000;
18
+ }
@@ -0,0 +1,101 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ /* ── createServerOnlyFn ─────────────────────────────────────────────── */
4
+
5
+ /**
6
+ * Marks a function as server-only. On client builds, the Vite plugin
7
+ * replaces the implementation with a throwing stub and tree-shakes
8
+ * any server-only imports the function body references.
9
+ *
10
+ * Runtime fallback (tests, no plugin): uses `import.meta.env.SSR`.
11
+ */
12
+ export function createServerOnlyFn<TArgs extends unknown[], TReturn>(
13
+ fn: (...args: TArgs) => TReturn,
14
+ ): (...args: TArgs) => TReturn {
15
+ if (import.meta.env.SSR) return fn;
16
+ return (..._args: TArgs): never => {
17
+ throw new Error("Server-only function called on client");
18
+ };
19
+ }
20
+
21
+ /* ── createClientOnlyFn ─────────────────────────────────────────────── */
22
+
23
+ /**
24
+ * Marks a function as client-only. On SSR builds, the Vite plugin
25
+ * replaces the implementation with a throwing stub and tree-shakes
26
+ * any client-only imports the function body references.
27
+ *
28
+ * Runtime fallback (tests, no plugin): uses `import.meta.env.SSR`.
29
+ */
30
+ export function createClientOnlyFn<TArgs extends unknown[], TReturn>(
31
+ fn: (...args: TArgs) => TReturn,
32
+ ): (...args: TArgs) => TReturn {
33
+ if (!import.meta.env.SSR) return fn;
34
+ return (..._args: TArgs): never => {
35
+ throw new Error("Client-only function called on server");
36
+ };
37
+ }
38
+
39
+ /* ── createIsomorphicFn ─────────────────────────────────────────────── */
40
+
41
+ /**
42
+ * Creates a function with different implementations per environment.
43
+ * The Vite plugin extracts the env-specific implementation at build time,
44
+ * eliminating the unused branch and its imports from the bundle.
45
+ *
46
+ * ```ts
47
+ * const getUser = createIsomorphicFn()
48
+ * .server(() => db.getUser(cookies()))
49
+ * .client(() => JSON.parse(localStorage.getItem("user")!))
50
+ * ```
51
+ *
52
+ * Runtime fallback (tests, no plugin): uses `import.meta.env.SSR`.
53
+ */
54
+
55
+ export type IsomorphicFn<TArgs extends unknown[] = [], TServer = undefined, TClient = undefined> = (
56
+ ...args: TArgs
57
+ ) => TClient | TServer;
58
+
59
+ export interface ServerBoundFn<TArgs extends unknown[], TServer> extends IsomorphicFn<TArgs, TServer> {
60
+ client: <TClient>(clientImpl: (...args: TArgs) => TClient) => IsomorphicFn<TArgs, TServer, TClient>;
61
+ }
62
+
63
+ export interface ClientBoundFn<TArgs extends unknown[], TClient> extends IsomorphicFn<TArgs, undefined, TClient> {
64
+ server: <TServer>(serverImpl: (...args: TArgs) => TServer) => IsomorphicFn<TArgs, TServer, TClient>;
65
+ }
66
+
67
+ export interface IsomorphicFnBuilder {
68
+ client: <TArgs extends unknown[], TClient>(clientImpl: (...args: TArgs) => TClient) => ClientBoundFn<TArgs, TClient>;
69
+ server: <TArgs extends unknown[], TServer>(serverImpl: (...args: TArgs) => TServer) => ServerBoundFn<TArgs, TServer>;
70
+ }
71
+
72
+ function buildIsomorphic<TArgs extends unknown[], TServer, TClient>(
73
+ serverImpl: ((...args: TArgs) => TServer) | null,
74
+ clientImpl: ((...args: TArgs) => TClient) | null,
75
+ ): IsomorphicFn<TArgs, TServer, TClient> {
76
+ return ((...args: TArgs) => {
77
+ if (import.meta.env.SSR) return serverImpl ? serverImpl(...args) : undefined;
78
+ return clientImpl ? clientImpl(...args) : undefined;
79
+ }) as IsomorphicFn<TArgs, TServer, TClient>;
80
+ }
81
+
82
+ export function createIsomorphicFn(): IsomorphicFnBuilder {
83
+ return {
84
+ client<TArgs extends unknown[], TClient>(clientImpl: (...args: TArgs) => TClient) {
85
+ const fn = buildIsomorphic<TArgs, undefined, TClient>(null, clientImpl);
86
+ return Object.assign(fn, {
87
+ server<TServer>(serverImpl: (...args: TArgs) => TServer) {
88
+ return buildIsomorphic<TArgs, TServer, TClient>(serverImpl, clientImpl);
89
+ },
90
+ }) as ClientBoundFn<TArgs, TClient>;
91
+ },
92
+ server<TArgs extends unknown[], TServer>(serverImpl: (...args: TArgs) => TServer) {
93
+ const fn = buildIsomorphic<TArgs, TServer, undefined>(serverImpl, null);
94
+ return Object.assign(fn, {
95
+ client<TClient>(clientImpl: (...args: TArgs) => TClient) {
96
+ return buildIsomorphic<TArgs, TServer, TClient>(serverImpl, clientImpl);
97
+ },
98
+ }) as ServerBoundFn<TArgs, TServer>;
99
+ },
100
+ };
101
+ }