@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,710 @@
1
+ import type { FlattenedError } from "../errors/index.ts";
2
+ import {
3
+ isNotFoundError,
4
+ isRedirectResponse,
5
+ isServerFnValidationError,
6
+ isUnauthenticatedError,
7
+ isUnauthorizedError,
8
+ ServerFnValidationError,
9
+ } from "../errors/index.ts";
10
+ import type { RevalidateFn, RevalidateOptions } from "../revalidation/index.ts";
11
+ import { createRevalidateFn } from "../revalidation/index.ts";
12
+ /* package self-reference: routes through `exports.browser` -> stub on client, real on server.
13
+ Bypassing this with `../server-context/index.ts` would pull `node:async_hooks` into the
14
+ client bundle (server-fn is reachable from <Form action={fn}> in route components). */
15
+ import {
16
+ addRevalidatedTags,
17
+ getRevalidatedTags,
18
+ getRevalidationContext,
19
+ getServerContext,
20
+ } from "@lovrozagar/flare/server-context";
21
+
22
+ import type { Validator } from "../validation/index.ts";
23
+ import { runValidator } from "../validation/index.ts";
24
+
25
+ export type { Validator } from "../validation/index.ts";
26
+
27
+ export interface ServerFnConfig {
28
+ __id?: string;
29
+ method?: "get" | "post";
30
+ name: string;
31
+ }
32
+
33
+ export interface PiggybackedQuery {
34
+ data: unknown;
35
+ key: unknown[];
36
+ }
37
+
38
+ export interface HandlerContext<TAuth, TInput, TEnv = unknown> {
39
+ auth: TAuth;
40
+ env: TEnv;
41
+ input: TInput;
42
+ /**
43
+ * Populate a TanStack Query cache entry alongside the server function response.
44
+ * Data is sent in the response body and applied via `queryClient.setQueryData()`
45
+ * on the client, avoiding extra round-trips for related data.
46
+ *
47
+ * Request-scoped: each request gets its own collector. Discarded on handler error.
48
+ *
49
+ * @param key - TanStack Query key array (must match the client's `queryKey`)
50
+ * @param data - Serializable data to cache under the key
51
+ */
52
+ piggyback: (key: unknown[], data: unknown) => void;
53
+ request: Request;
54
+ revalidate: RevalidateFn;
55
+ serverContext: Record<string, unknown>;
56
+ }
57
+
58
+ export interface StreamContext<TAuth, TInput, TEnv = unknown> {
59
+ auth: TAuth;
60
+ env: TEnv;
61
+ input: TInput;
62
+ request: Request;
63
+ serverContext: Record<string, unknown>;
64
+ signal: AbortSignal;
65
+ }
66
+
67
+ export interface ServerFnRegistrationBase {
68
+ authenticate: boolean;
69
+ authorizeFn?: (ctx: { auth: unknown; input: unknown }) => boolean | Promise<boolean>;
70
+ id: string;
71
+ input?: Validator<unknown>;
72
+ method: "get" | "post";
73
+ name: string;
74
+ }
75
+
76
+ export interface ServerFnHandlerRegistration extends ServerFnRegistrationBase {
77
+ fn: (ctx: HandlerContext<unknown, unknown>) => unknown | Promise<unknown>;
78
+ stream?: false;
79
+ }
80
+
81
+ export interface ServerFnStreamRegistration extends ServerFnRegistrationBase {
82
+ fn: (ctx: StreamContext<unknown, unknown>) => AsyncGenerator<unknown>;
83
+ stream: true;
84
+ }
85
+
86
+ export type ServerFnRegistration = ServerFnHandlerRegistration | ServerFnStreamRegistration;
87
+
88
+ interface BuilderState {
89
+ __id?: string;
90
+ authenticate: boolean;
91
+ authorizeFn?: (ctx: { auth: unknown; input: unknown }) => boolean | Promise<boolean>;
92
+ input?: Validator<unknown>;
93
+ method: "get" | "post";
94
+ name: string;
95
+ }
96
+
97
+ interface ServerFnBuilderTerminal<TAuth, TInput, TOutput> {
98
+ handler(fn: (ctx: HandlerContext<TAuth, TInput>) => TOutput | Promise<TOutput>): ServerFn<TInput, TOutput>;
99
+ input<T>(validator: Validator<T>): ServerFnBuilderTerminal<TAuth, T, TOutput>;
100
+ stream<TChunk>(fn: (ctx: StreamContext<TAuth, TInput>) => AsyncGenerator<TChunk>): StreamFn<TInput, TChunk>;
101
+ }
102
+
103
+ interface ServerFnBuilderAfterAuth<TAuth, TInput, TOutput> extends ServerFnBuilderTerminal<TAuth, TInput, TOutput> {
104
+ authorize(
105
+ fn: (ctx: { auth: TAuth; input: TInput }) => boolean | Promise<boolean>,
106
+ ): ServerFnBuilderTerminal<TAuth, TInput, TOutput>;
107
+ input<T>(validator: Validator<T>): ServerFnBuilderAfterAuth<TAuth, T, TOutput>;
108
+ }
109
+
110
+ interface ServerFnBuilder<TAuth, TInput, TOutput> extends ServerFnBuilderAfterAuth<TAuth, TInput, TOutput> {
111
+ authenticate(): ServerFnBuilderAfterAuth<unknown, TInput, TOutput>;
112
+ input<T>(validator: Validator<T>): ServerFnBuilder<TAuth, T, TOutput>;
113
+ }
114
+
115
+ export type ServerFn<_TInput, TOutput> = ((_input: _TInput) => Promise<TOutput>) & {
116
+ _registration?: ServerFnRegistration;
117
+ };
118
+
119
+ export type StreamFn<_TInput, TChunk> = ((
120
+ _input: _TInput,
121
+ options?: { signal?: AbortSignal },
122
+ ) => AsyncIterable<TChunk>) & {
123
+ _registration?: ServerFnRegistration;
124
+ };
125
+
126
+ function createBuilderTerminal<TAuth, TInput, TOutput>(
127
+ state: BuilderState,
128
+ ): ServerFnBuilderTerminal<TAuth, TInput, TOutput> {
129
+ return {
130
+ handler(fn) {
131
+ const registration: ServerFnHandlerRegistration = {
132
+ authenticate: state.authenticate,
133
+ authorizeFn: state.authorizeFn,
134
+ fn: fn as (ctx: HandlerContext<unknown, unknown>) => unknown | Promise<unknown>,
135
+ id: state.__id && state.__id.length > 0 ? state.__id : state.name,
136
+ input: state.input,
137
+ method: state.method,
138
+ name: state.name,
139
+ };
140
+
141
+ const serverFn: ServerFn<TInput, TOutput> = async (_input: TInput): Promise<TOutput> => {
142
+ if (state.authenticate) {
143
+ throw new Error(
144
+ `Server fn "${state.name}" requires authentication and cannot be called directly. Use the HTTP endpoint instead.`,
145
+ );
146
+ }
147
+ /* server-side direct invocation — validate input if validator configured */
148
+ const validated = state.input ? ((await runValidator(state.input, _input)) as TInput) : _input;
149
+ const revalidateFn = createRevalidateFn(getRevalidationContext());
150
+ const wrappedRevalidate: RevalidateFn = async (options: RevalidateOptions) => {
151
+ await revalidateFn(options);
152
+ if (options.tags && options.tags.length > 0) {
153
+ addRevalidatedTags(options.tags);
154
+ }
155
+ };
156
+ return await fn({
157
+ auth: null as TAuth,
158
+ env: {},
159
+ input: validated,
160
+ piggyback: () => {},
161
+ request: new Request("http://localhost"),
162
+ revalidate: wrappedRevalidate,
163
+ serverContext: getServerContext(),
164
+ });
165
+ };
166
+ serverFn._registration = registration;
167
+ return serverFn;
168
+ },
169
+ input<T>(validator: Validator<T>) {
170
+ return createBuilderTerminal<TAuth, T, TOutput>({
171
+ ...state,
172
+ input: validator as Validator<unknown>,
173
+ });
174
+ },
175
+ stream<TChunk>(fn: (ctx: StreamContext<TAuth, TInput>) => AsyncGenerator<TChunk>): StreamFn<TInput, TChunk> {
176
+ const registration: ServerFnStreamRegistration = {
177
+ authenticate: state.authenticate,
178
+ authorizeFn: state.authorizeFn,
179
+ fn: fn as (ctx: StreamContext<unknown, unknown>) => AsyncGenerator<unknown>,
180
+ id: state.__id && state.__id.length > 0 ? state.__id : state.name,
181
+ input: state.input,
182
+ method: state.method,
183
+ name: state.name,
184
+ stream: true,
185
+ };
186
+
187
+ const streamFn: StreamFn<TInput, TChunk> = (
188
+ _input: TInput,
189
+ options?: { signal?: AbortSignal },
190
+ ): AsyncIterable<TChunk> => {
191
+ const signal = options?.signal ?? new AbortController().signal;
192
+ if (state.authenticate) {
193
+ throw new Error(
194
+ `Server fn "${state.name}" requires authentication and cannot be called directly. Use the HTTP endpoint instead.`,
195
+ );
196
+ }
197
+ /* validate once, lazily create generator on first .next() */
198
+ const validatedPromise = state.input ? runValidator(state.input, _input) : Promise.resolve(_input);
199
+ return {
200
+ [Symbol.asyncIterator]() {
201
+ let generator: AsyncGenerator<TChunk> | undefined;
202
+ return {
203
+ async next() {
204
+ if (!generator) {
205
+ const validated = (await validatedPromise) as TInput;
206
+ generator = fn({
207
+ auth: null as TAuth,
208
+ env: {} as unknown,
209
+ input: validated,
210
+ request: new Request("http://localhost"),
211
+ serverContext: getServerContext(),
212
+ signal,
213
+ });
214
+ }
215
+ return generator.next();
216
+ },
217
+ return(value?: TChunk) {
218
+ return generator?.return(value) ?? Promise.resolve({ done: true as const, value: undefined as TChunk });
219
+ },
220
+ throw(e?: unknown) {
221
+ return generator?.throw(e) ?? Promise.resolve({ done: true as const, value: undefined as TChunk });
222
+ },
223
+ };
224
+ },
225
+ };
226
+ };
227
+ streamFn._registration = registration;
228
+ return streamFn;
229
+ },
230
+ };
231
+ }
232
+
233
+ function createBuilderAfterAuth<TAuth, TInput, TOutput>(
234
+ state: BuilderState,
235
+ ): ServerFnBuilderAfterAuth<TAuth, TInput, TOutput> {
236
+ return {
237
+ ...createBuilderTerminal<TAuth, TInput, TOutput>(state),
238
+ authorize(fn) {
239
+ return createBuilderTerminal<TAuth, TInput, TOutput>({
240
+ ...state,
241
+ authorizeFn: fn as (ctx: { auth: unknown; input: unknown }) => boolean | Promise<boolean>,
242
+ });
243
+ },
244
+ input<T>(validator: Validator<T>) {
245
+ return createBuilderAfterAuth<TAuth, T, TOutput>({
246
+ ...state,
247
+ input: validator as Validator<unknown>,
248
+ });
249
+ },
250
+ };
251
+ }
252
+
253
+ function createBuilder<TAuth, TInput, TOutput>(state: BuilderState): ServerFnBuilder<TAuth, TInput, TOutput> {
254
+ return {
255
+ ...createBuilderAfterAuth<TAuth, TInput, TOutput>(state),
256
+ authenticate() {
257
+ return createBuilderAfterAuth<unknown, TInput, TOutput>({
258
+ ...state,
259
+ authenticate: true,
260
+ });
261
+ },
262
+ input<T>(validator: Validator<T>) {
263
+ return createBuilder<TAuth, T, TOutput>({
264
+ ...state,
265
+ input: validator as Validator<unknown>,
266
+ });
267
+ },
268
+ };
269
+ }
270
+
271
+ export function createServerFn(config: ServerFnConfig): ServerFnBuilder<null, void, unknown> {
272
+ return createBuilder<null, void, unknown>({
273
+ __id: config.__id,
274
+ authenticate: false,
275
+ method: config.method ?? "post",
276
+ name: config.name,
277
+ });
278
+ }
279
+
280
+ function jsonResponse(data: unknown, status: number): Response {
281
+ return new Response(JSON.stringify(data), {
282
+ headers: { "content-type": "application/json; charset=utf-8" },
283
+ status,
284
+ });
285
+ }
286
+
287
+ /* ── CSRF Origin validation ────────────────────────────────────────── */
288
+
289
+ /**
290
+ * Validate that the request Origin header matches the request URL origin.
291
+ * Browsers always send Origin on POST; some browsers omit it on GET.
292
+ * When Origin is present (any method), validate it — cross-origin GET
293
+ * server function calls are just as dangerous as POST for CSRF.
294
+ * Missing Origin is allowed since CSRF is a browser-only attack vector
295
+ * and non-browser clients (curl, etc) won't have cookies.
296
+ */
297
+ function validateOrigin(request: Request): boolean {
298
+ const origin = request.headers.get("origin");
299
+ if (!origin) return true;
300
+
301
+ const requestOrigin = new URL(request.url).origin;
302
+ return origin === requestOrigin;
303
+ }
304
+
305
+ /* ── formDataToObject ───────────────────────────────────────────────── */
306
+
307
+ export function formDataToObject(formData: FormData): Record<string, File | string | string[]> {
308
+ const obj: Record<string, File | string | string[]> = Object.create(null);
309
+ for (const [key, val] of formData) {
310
+ if (key === "__flare_fn" || key === "__proto__" || key === "constructor" || key === "prototype") continue;
311
+ if (val instanceof File) {
312
+ obj[key] = val;
313
+ continue;
314
+ }
315
+ const existing = obj[key];
316
+ if (typeof existing === "string") {
317
+ obj[key] = [existing, val];
318
+ } else if (Array.isArray(existing)) {
319
+ existing.push(val);
320
+ } else {
321
+ obj[key] = val;
322
+ }
323
+ }
324
+ return obj;
325
+ }
326
+
327
+ export async function handleServerFnRequest(
328
+ request: Request,
329
+ env: unknown,
330
+ fns: Map<string, ServerFnRegistration>,
331
+ authenticateFn?: (env: unknown, request: Request) => unknown | Promise<unknown>,
332
+ ): Promise<Response> {
333
+ /* parse URL: /_fn/{id}/{name} */
334
+ const url = new URL(request.url);
335
+ const segments = url.pathname.split("/").filter(Boolean);
336
+ /* segments: ["_fn", id, name] */
337
+ if (segments.length < 3 || segments[0] !== "_fn") {
338
+ return jsonResponse({ message: "Server function not found" }, 404);
339
+ }
340
+
341
+ const id = segments[1];
342
+ const name = segments[2];
343
+
344
+ if (!id || !name) {
345
+ return jsonResponse({ message: "Server function not found" }, 404);
346
+ }
347
+
348
+ /* CSRF: validate Origin before any processing */
349
+ if (!validateOrigin(request)) {
350
+ return jsonResponse({ message: "Origin mismatch" }, 403);
351
+ }
352
+
353
+ /* lookup */
354
+ const registration = fns.get(id);
355
+ if (!registration || registration.name !== name) {
356
+ return jsonResponse({ message: "Server function not found" }, 404);
357
+ }
358
+
359
+ /* method validation */
360
+ if (request.method.toLowerCase() !== registration.method) {
361
+ return jsonResponse({ message: "Method not allowed" }, 405);
362
+ }
363
+
364
+ try {
365
+ /* authentication */
366
+ let auth: unknown = null;
367
+ if (registration.authenticate) {
368
+ if (!authenticateFn) {
369
+ return jsonResponse({ message: "Unauthorized" }, 401);
370
+ }
371
+ auth = await authenticateFn(env, request);
372
+ if (auth === null || auth === undefined) {
373
+ return jsonResponse({ message: "Unauthorized" }, 401);
374
+ }
375
+ }
376
+
377
+ /* parse input — FormData for form submissions, JSON for programmatic calls */
378
+ let input: unknown;
379
+ if (registration.method === "post") {
380
+ const contentType = request.headers.get("content-type") ?? "";
381
+ if (contentType.includes("multipart/form-data") || contentType.includes("x-www-form-urlencoded")) {
382
+ input = formDataToObject(await request.formData());
383
+ } else {
384
+ const text = await request.text();
385
+ if (text) {
386
+ try {
387
+ input = JSON.parse(text);
388
+ } catch {
389
+ return jsonResponse({ message: "Invalid JSON" }, 400);
390
+ }
391
+ }
392
+ }
393
+ } else {
394
+ const params = url.searchParams;
395
+ const obj: Record<string, string | string[]> = Object.create(null);
396
+ let hasParams = false;
397
+ for (const [key, val] of params) {
398
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
399
+ hasParams = true;
400
+ const existing = obj[key];
401
+ if (existing !== undefined) {
402
+ obj[key] = Array.isArray(existing) ? [...existing, val] : [existing, val];
403
+ } else {
404
+ obj[key] = val;
405
+ }
406
+ }
407
+ if (hasParams) {
408
+ input = obj;
409
+ }
410
+ }
411
+
412
+ /* validate input — ServerFnValidationError propagates to outer catch for structured errors */
413
+ if (registration.input) {
414
+ try {
415
+ input = await runValidator(registration.input, input);
416
+ } catch (e) {
417
+ if (isServerFnValidationError(e)) throw e;
418
+ const message = e instanceof Error ? e.message : "Validation failed";
419
+ return jsonResponse({ message }, 400);
420
+ }
421
+ }
422
+
423
+ /* authorize */
424
+ if (registration.authorizeFn) {
425
+ const allowed = await registration.authorizeFn({ auth, input });
426
+ if (!allowed) {
427
+ return jsonResponse({ message: "Forbidden" }, 403);
428
+ }
429
+ }
430
+
431
+ /* streaming handler */
432
+ if (registration.stream) {
433
+ const abortController = new AbortController();
434
+ const iterator = registration.fn({
435
+ auth,
436
+ env,
437
+ input,
438
+ request,
439
+ serverContext: getServerContext(),
440
+ signal: abortController.signal,
441
+ });
442
+ const encoder = new TextEncoder();
443
+ let cleaned = false;
444
+
445
+ const stream = new ReadableStream({
446
+ cancel() {
447
+ abortController.abort();
448
+ if (!cleaned) {
449
+ cleaned = true;
450
+ iterator.return(undefined).catch(() => {});
451
+ }
452
+ },
453
+ async pull(controller) {
454
+ try {
455
+ const { done, value } = await iterator.next();
456
+ if (done) {
457
+ controller.enqueue(encoder.encode(`${JSON.stringify({ d: true })}\n`));
458
+ controller.close();
459
+ if (!cleaned) {
460
+ cleaned = true;
461
+ await iterator.return(undefined).catch(() => {});
462
+ }
463
+ return;
464
+ }
465
+ controller.enqueue(encoder.encode(`${JSON.stringify({ c: value ?? null })}\n`));
466
+ } catch (e) {
467
+ const message = e instanceof Error ? e.message : "Stream error";
468
+ controller.enqueue(encoder.encode(`${JSON.stringify({ e: { message } })}\n`));
469
+ controller.close();
470
+ abortController.abort();
471
+ if (!cleaned) {
472
+ cleaned = true;
473
+ await iterator.return(undefined).catch(() => {});
474
+ }
475
+ }
476
+ },
477
+ });
478
+
479
+ return new Response(stream, {
480
+ headers: { "content-type": "text/x-ndjson" },
481
+ });
482
+ }
483
+
484
+ /* execute handler with piggyback collector */
485
+ const piggybacked: PiggybackedQuery[] = [];
486
+ const piggyback = (key: unknown[], data: unknown) => {
487
+ piggybacked.push({ data, key });
488
+ };
489
+ const revalidateFn = createRevalidateFn(getRevalidationContext());
490
+ const wrappedRevalidate: RevalidateFn = async (options: RevalidateOptions) => {
491
+ await revalidateFn(options);
492
+ if (options.tags && options.tags.length > 0) {
493
+ addRevalidatedTags(options.tags);
494
+ }
495
+ };
496
+ const result = await registration.fn({
497
+ auth,
498
+ env,
499
+ input,
500
+ piggyback,
501
+ request,
502
+ revalidate: wrappedRevalidate,
503
+ serverContext: getServerContext(),
504
+ });
505
+ const response: Record<string, unknown> = { data: result ?? null };
506
+ if (piggybacked.length > 0) {
507
+ response.queries = piggybacked;
508
+ }
509
+ const revalidatedTags = getRevalidatedTags();
510
+ if (revalidatedTags.length > 0) {
511
+ response.revalidatedTags = revalidatedTags;
512
+ }
513
+ return jsonResponse(response, 200);
514
+ } catch (e) {
515
+ if (isRedirectResponse(e)) throw e;
516
+ /* Pass-through for HTTP-shaped errors (e.g. SDK ClientError carrying upstream Response).
517
+ Handler can throw a Response directly, or any error object whose `.response` is a Response,
518
+ and the upstream status + body propagate to the caller verbatim — no envelope rewrap. */
519
+ if (e instanceof Response) return e.clone();
520
+ if (
521
+ typeof e === "object" &&
522
+ e !== null &&
523
+ "response" in e &&
524
+ (e as { response: unknown }).response instanceof Response
525
+ ) {
526
+ return (e as { response: Response }).response.clone();
527
+ }
528
+ if (isServerFnValidationError(e)) {
529
+ return jsonResponse({ errors: e.errors, message: e.message }, 400);
530
+ }
531
+ if (isUnauthenticatedError(e)) {
532
+ return jsonResponse({ message: e.message }, 401);
533
+ }
534
+ if (isUnauthorizedError(e)) {
535
+ return jsonResponse({ message: e.message }, 403);
536
+ }
537
+ if (isNotFoundError(e)) {
538
+ return jsonResponse({ message: e.message }, 404);
539
+ }
540
+ /* Surface the real error in the dev terminal — without this, the catch-all 500 returns a
541
+ generic message and the actual failure (gateway 4xx/5xx, missing binding, etc.) is invisible. */
542
+ const errMsg = e instanceof Error ? (e.stack ?? e.message) : String(e);
543
+ console.error(`[flare:server-fn] unhandled error in handler:\n${errMsg}`);
544
+ return jsonResponse({ message: "Internal server error" }, 500);
545
+ }
546
+ }
547
+
548
+ /* ── serverFnQueryOptions ────────────────────────────────────────────── */
549
+
550
+ interface ServerFnQueryConfig<TInput, _TOutput> {
551
+ input?: TInput;
552
+ onRevalidate?: (tags: string[]) => void;
553
+ queryClient?: QueryClientLike;
554
+ queryKey?: unknown[];
555
+ staleTime?: number;
556
+ }
557
+
558
+ interface QueryClientLike {
559
+ invalidateQueries(options: { queryKey: unknown[] }): Promise<void>;
560
+ setQueryData(key: unknown[], data: unknown): unknown;
561
+ }
562
+
563
+ export function serverFnQueryOptions<TInput, TOutput>(
564
+ serverFn: ServerFn<TInput, TOutput>,
565
+ config?: ServerFnQueryConfig<TInput, TOutput>,
566
+ ): { queryFn: () => Promise<TOutput>; queryKey: unknown[]; staleTime?: number } {
567
+ const reg = serverFn._registration;
568
+ const name = reg?.name ?? "unknown";
569
+ const id = reg?.id ?? name;
570
+ const queryKey = config?.queryKey ?? [name, config?.input];
571
+
572
+ return {
573
+ queryFn: async () => {
574
+ /* server: direct call */
575
+ if (typeof window === "undefined") {
576
+ return serverFn(config?.input as TInput);
577
+ }
578
+
579
+ /* client: HTTP fetch */
580
+ const url = `/_fn/${id}/${name}`;
581
+ const method = reg?.method ?? "post";
582
+
583
+ const res =
584
+ method === "get"
585
+ ? await fetch(
586
+ config?.input !== undefined
587
+ ? `${url}?${new URLSearchParams(config.input as Record<string, string>)}`
588
+ : url,
589
+ )
590
+ : await fetch(url, {
591
+ body: config?.input !== undefined ? JSON.stringify(config.input) : undefined,
592
+ headers: { "content-type": "application/json" },
593
+ method: "POST",
594
+ });
595
+
596
+ if (!res.ok) {
597
+ const body: unknown = await res.json().catch(() => null);
598
+ if (typeof body === "object" && body !== null && "errors" in body) {
599
+ throw new ServerFnValidationError((body as { errors: FlattenedError }).errors);
600
+ }
601
+ const errMsg =
602
+ typeof body === "object" && body !== null && "message" in body
603
+ ? String((body as Record<string, unknown>).message)
604
+ : `Request failed (${res.status})`;
605
+ throw new Error(`Server function "${name}" failed: ${errMsg}`);
606
+ }
607
+
608
+ const json = (await res.json()) as {
609
+ data: TOutput;
610
+ queries?: Array<{ data: unknown; key: unknown[] }>;
611
+ revalidatedTags?: string[];
612
+ };
613
+
614
+ /* apply piggybacked queries to cache */
615
+ if (json.queries && config?.queryClient) {
616
+ for (const q of json.queries) {
617
+ if (Array.isArray(q.key)) {
618
+ config.queryClient.setQueryData(q.key, q.data);
619
+ }
620
+ }
621
+ }
622
+
623
+ if (json.revalidatedTags && json.revalidatedTags.length > 0 && config?.onRevalidate) {
624
+ config.onRevalidate(json.revalidatedTags);
625
+ }
626
+
627
+ return json.data;
628
+ },
629
+ queryKey,
630
+ staleTime: config?.staleTime,
631
+ };
632
+ }
633
+
634
+ /* ── serverFnMutationOptions ─────────────────────────────────────────── */
635
+
636
+ interface ServerFnMutationConfig<_TOutput> {
637
+ invalidates?: unknown[][];
638
+ onRevalidate?: (tags: string[]) => void;
639
+ queryClient?: QueryClientLike;
640
+ }
641
+
642
+ export function serverFnMutationOptions<TInput, TOutput>(
643
+ serverFn: ServerFn<TInput, TOutput>,
644
+ config?: ServerFnMutationConfig<TOutput>,
645
+ ): {
646
+ mutationFn: (input: TInput) => Promise<TOutput>;
647
+ onSuccess?: () => void;
648
+ } {
649
+ const reg = serverFn._registration;
650
+ const name = reg?.name ?? "unknown";
651
+ const id = reg?.id ?? name;
652
+
653
+ return {
654
+ mutationFn: async (input: TInput) => {
655
+ /* server: direct call */
656
+ if (typeof window === "undefined") {
657
+ return serverFn(input);
658
+ }
659
+
660
+ /* client: HTTP fetch — mutations always POST */
661
+ const url = `/_fn/${id}/${name}`;
662
+ const res = await fetch(url, {
663
+ body: input !== undefined ? JSON.stringify(input) : undefined,
664
+ headers: { "content-type": "application/json" },
665
+ method: "POST",
666
+ });
667
+
668
+ if (!res.ok) {
669
+ const body: unknown = await res.json().catch(() => null);
670
+ if (typeof body === "object" && body !== null && "errors" in body) {
671
+ throw new ServerFnValidationError((body as { errors: FlattenedError }).errors);
672
+ }
673
+ const errMsg =
674
+ typeof body === "object" && body !== null && "message" in body
675
+ ? String((body as Record<string, unknown>).message)
676
+ : `Request failed (${res.status})`;
677
+ throw new Error(`Server function "${name}" failed: ${errMsg}`);
678
+ }
679
+
680
+ const json = (await res.json()) as {
681
+ data: TOutput;
682
+ queries?: Array<{ data: unknown; key: unknown[] }>;
683
+ revalidatedTags?: string[];
684
+ };
685
+
686
+ /* apply piggybacked queries to cache */
687
+ if (json.queries && config?.queryClient) {
688
+ for (const q of json.queries) {
689
+ if (Array.isArray(q.key)) {
690
+ config.queryClient.setQueryData(q.key, q.data);
691
+ }
692
+ }
693
+ }
694
+
695
+ if (json.revalidatedTags && json.revalidatedTags.length > 0 && config?.onRevalidate) {
696
+ config.onRevalidate(json.revalidatedTags);
697
+ }
698
+
699
+ return json.data;
700
+ },
701
+ onSuccess:
702
+ config?.invalidates && config.queryClient
703
+ ? () => {
704
+ for (const key of config.invalidates ?? []) {
705
+ void config.queryClient?.invalidateQueries({ queryKey: key });
706
+ }
707
+ }
708
+ : undefined,
709
+ };
710
+ }