@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,141 @@
1
+ /**
2
+ * Markdown content negotiation — opt-in builtin. Converts the HTML response to
3
+ * Markdown when the request carries `Accept: text/markdown`. Subpath export,
4
+ * tree-shaken when unused.
5
+ *
6
+ * Pattern cribbed from `@markdown-for-agents/web` + Vercel's content-negotiation
7
+ * guide:
8
+ * - always advertise `Vary: Accept` so caches keep HTML + Markdown variants
9
+ * separate under the same URL
10
+ * - convert 2xx `text/html` responses only; everything else passes through
11
+ * - set `Content-Type: text/markdown; charset=utf-8`, drop stale
12
+ * `Content-Length`, attach an optional `x-markdown-tokens` rough estimate
13
+ *
14
+ * Default converter is lazily imported `node-html-markdown` (pure JS, no DOM,
15
+ * CF Worker safe). Pass a custom `convert` to swap engines and keep the peer
16
+ * dep out of your bundle entirely.
17
+ */
18
+
19
+ import type { FlareMiddleware } from "..";
20
+
21
+ type Convert = (html: string) => string | Promise<string>;
22
+
23
+ export interface MarkdownNegotiationOptions {
24
+ /** Custom HTML → Markdown converter. Omit to use lazy `node-html-markdown`. */
25
+ convert?: Convert;
26
+ /** Opt-in token header (GPT/Claude-style ~4 chars/token estimate). Default `true`. */
27
+ emitTokenHeader?: boolean;
28
+ /** Upper bound on HTML body size in bytes — larger responses pass through. Default 2 MiB. */
29
+ maxBytes?: number;
30
+ /** Per-request gate. Return `false` to skip conversion (e.g. interactive routes). */
31
+ when?: (input: { request: Request; url: URL }) => boolean;
32
+ }
33
+
34
+ const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
35
+
36
+ function acceptsMarkdown(request: Request): boolean {
37
+ const accept = request.headers.get("accept");
38
+ if (!accept) return false;
39
+ /* Substring match — RFC 7231 q-value parse is overkill here.
40
+ `text/html, text/markdown;q=0.9` matches; `text/html` does not. */
41
+ return accept.includes("text/markdown");
42
+ }
43
+
44
+ async function defaultConvert(html: string): Promise<string> {
45
+ /* Dynamic import so the peer dep is only loaded by consumers who import
46
+ this middleware. Bundlers (Vite / esbuild) pre-bundle it as a separate
47
+ chunk — required for CF Workers which have no runtime module loader. */
48
+ const { NodeHtmlMarkdown } = await import("node-html-markdown");
49
+ return NodeHtmlMarkdown.translate(html);
50
+ }
51
+
52
+ function estimateTokens(text: string): number {
53
+ return Math.ceil(text.length / 4);
54
+ }
55
+
56
+ export function markdownNegotiation<TEnv = unknown>(options: MarkdownNegotiationOptions = {}): FlareMiddleware<TEnv> {
57
+ const convert = options.convert ?? defaultConvert;
58
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
59
+ const emitTokenHeader = options.emitTokenHeader ?? true;
60
+
61
+ return (ctx) => {
62
+ /* Always advertise Accept-based variance so shared caches don't serve
63
+ the wrong representation. Added even on requests that won't convert
64
+ — the HTML response still has an MD counterpart under the same URL. */
65
+ ctx.onResponse((response) => {
66
+ response.headers.append("vary", "Accept");
67
+ return response;
68
+ });
69
+
70
+ if (!acceptsMarkdown(ctx.request)) return ctx.next();
71
+ if (options.when && !options.when({ request: ctx.request, url: ctx.url })) {
72
+ return ctx.next();
73
+ }
74
+
75
+ ctx.onResponse(async (response) => {
76
+ if (!response.ok) return response;
77
+
78
+ const contentType = response.headers.get("content-type") ?? "";
79
+ if (!contentType.startsWith("text/html")) return response;
80
+
81
+ /* Status codes that forbid a body (Response constructor throws if we
82
+ try to give them one). Pass through unconverted. */
83
+ if (response.status === 204 || response.status === 205 || response.status === 304) {
84
+ return response;
85
+ }
86
+
87
+ /* Pre-flight size check using Content-Length when present — avoids
88
+ buffering multi-MB responses just to reject them. */
89
+ const declaredLength = Number(response.headers.get("content-length") ?? 0);
90
+ if (declaredLength > 0 && declaredLength > maxBytes) return response;
91
+
92
+ let html: string;
93
+ try {
94
+ html = await response.text();
95
+ } catch {
96
+ /* Body read failure — upstream closed stream or similar. Nothing
97
+ we can do; return original response (stream may already be dead,
98
+ but caller must handle that case regardless). */
99
+ return response;
100
+ }
101
+
102
+ if (html.length > maxBytes) {
103
+ /* Rebuild from captured body so the stream isn't consumed downstream. */
104
+ return new Response(html, {
105
+ headers: response.headers,
106
+ status: response.status,
107
+ statusText: response.statusText,
108
+ });
109
+ }
110
+
111
+ let markdown: string;
112
+ try {
113
+ markdown = await convert(html);
114
+ } catch {
115
+ /* Conversion failure must not break the page — return the HTML we
116
+ already buffered so the client gets a working response. */
117
+ return new Response(html, {
118
+ headers: response.headers,
119
+ status: response.status,
120
+ statusText: response.statusText,
121
+ });
122
+ }
123
+
124
+ const headers = new Headers(response.headers);
125
+ headers.set("content-type", "text/markdown; charset=utf-8");
126
+ /* Length changes; drop the stale header so downstream recomputes. */
127
+ headers.delete("content-length");
128
+ if (emitTokenHeader) {
129
+ headers.set("x-markdown-tokens", String(estimateTokens(markdown)));
130
+ }
131
+
132
+ return new Response(markdown, {
133
+ headers,
134
+ status: response.status,
135
+ statusText: response.statusText,
136
+ });
137
+ });
138
+
139
+ return ctx.next();
140
+ };
141
+ }
@@ -0,0 +1,32 @@
1
+ import type { FlareMiddleware } from "..";
2
+
3
+ interface StaticAssetsConfig {
4
+ paths: string[];
5
+ }
6
+
7
+ export function staticAssets(config: StaticAssetsConfig): FlareMiddleware {
8
+ const exactPaths = new Set<string>();
9
+ const prefixPaths: string[] = [];
10
+
11
+ for (const path of config.paths) {
12
+ if (path.endsWith("/")) {
13
+ prefixPaths.push(path);
14
+ } else {
15
+ exactPaths.add(path);
16
+ }
17
+ }
18
+
19
+ return async (ctx) => {
20
+ const pathname = ctx.url.pathname;
21
+
22
+ const matched = exactPaths.has(pathname) || prefixPaths.some((prefix) => pathname.startsWith(prefix));
23
+
24
+ if (!matched) return ctx.next();
25
+
26
+ const env = ctx.env as Record<string, { fetch: (req: Request) => Promise<Response> }>;
27
+ if (!env.ASSETS) return ctx.next();
28
+
29
+ const response = await env.ASSETS.fetch(ctx.request);
30
+ return ctx.bypass(response);
31
+ };
32
+ }
@@ -0,0 +1,223 @@
1
+ import type { FlareTracer } from "../tracing/types.ts";
2
+
3
+ export type ResponseHandler = (response: Response) => Response | Promise<Response>;
4
+
5
+ export type MiddlewareResult =
6
+ | { response: Response; type: "bypass" }
7
+ | { response: Response; type: "respond" }
8
+ | { type: "next" };
9
+
10
+ export type MiddlewareRunResult =
11
+ | { response: Response; type: "bypass" }
12
+ | { response: Response; type: "respond" }
13
+ | { type: "next" };
14
+
15
+ export type RequestType = "internal" | "mount" | "page" | "server-fn";
16
+
17
+ export interface MiddlewareContext<TEnv = unknown> {
18
+ bypass: (response: Response) => MiddlewareResult;
19
+ env: TEnv;
20
+ error: (...args: unknown[]) => void;
21
+ locale?: import("../locale").LocaleConfig;
22
+ log: (...args: unknown[]) => void;
23
+ next: () => Promise<MiddlewareResult>;
24
+ nonce: string;
25
+ onResponse: (handler: ResponseHandler) => void;
26
+ request: Request;
27
+ requestType: RequestType;
28
+ respond: (response: Response) => MiddlewareResult;
29
+ /** Present on live requests. Used by i18n to keep `/en/...` when the unprefixed path is not a route. */
30
+ routeTree?: import("../router-primitives/types.ts").TreeNode;
31
+ serverContext: Record<string, unknown>;
32
+ url: URL;
33
+ warn: (...args: unknown[]) => void;
34
+ }
35
+
36
+ export type FlareMiddleware<TEnv = unknown> = (ctx: MiddlewareContext<TEnv>) => Promise<MiddlewareResult>;
37
+
38
+ export interface MiddlewareRunOutput {
39
+ responseHandlers: ResponseHandler[];
40
+ result: MiddlewareRunResult;
41
+ }
42
+
43
+ const NEXT_RESULT: MiddlewareResult = Object.freeze({ type: "next" as const });
44
+
45
+ function bypassResult(response: Response): MiddlewareResult {
46
+ return Object.freeze({ response, type: "bypass" as const });
47
+ }
48
+
49
+ function respondResult(response: Response): MiddlewareResult {
50
+ return Object.freeze({ response, type: "respond" as const });
51
+ }
52
+
53
+ export async function runMiddlewares<TEnv = unknown>(
54
+ middlewares: FlareMiddleware<TEnv>[],
55
+ ctx: Omit<MiddlewareContext<TEnv>, "bypass" | "next" | "respond"> &
56
+ Partial<Pick<MiddlewareContext<TEnv>, "bypass" | "next" | "respond">>,
57
+ tracer?: FlareTracer,
58
+ ): Promise<MiddlewareRunOutput> {
59
+ const responseHandlers: ResponseHandler[] = [];
60
+
61
+ /* override onResponse to collect handlers, preserving ctx identity */
62
+ ctx.onResponse = (handler) => {
63
+ responseHandlers.push(handler);
64
+ };
65
+
66
+ ctx.bypass = bypassResult;
67
+ ctx.respond = respondResult;
68
+
69
+ /* After assigning flow-control methods, ctx satisfies the full interface */
70
+ const fullCtx = ctx as MiddlewareContext<TEnv>;
71
+
72
+ if (middlewares.length === 0) {
73
+ return { responseHandlers, result: { type: "next" } };
74
+ }
75
+
76
+ function buildChain(index: number): () => Promise<MiddlewareResult> {
77
+ if (index >= middlewares.length) {
78
+ return () => Promise.resolve(NEXT_RESULT);
79
+ }
80
+ let result: Promise<MiddlewareResult> | undefined;
81
+ return () => {
82
+ if (result) return result;
83
+ const mw = middlewares[index];
84
+ if (!mw) return Promise.resolve(NEXT_RESULT);
85
+ fullCtx.next = buildChain(index + 1);
86
+ if (tracer) {
87
+ const span = tracer.startSpan(`flare.middleware.${index}`);
88
+ span.setAttribute("index", index);
89
+ result = mw(fullCtx).then(
90
+ (r) => {
91
+ span.end();
92
+ return r;
93
+ },
94
+ (e: unknown) => {
95
+ span.setStatus("error");
96
+ span.end();
97
+ throw e;
98
+ },
99
+ );
100
+ } else {
101
+ result = mw(fullCtx);
102
+ }
103
+ return result;
104
+ };
105
+ }
106
+
107
+ const result = await buildChain(0)();
108
+
109
+ return { responseHandlers, result };
110
+ }
111
+
112
+ /* ── Path matching ────────────────────────────────────────────────────── */
113
+
114
+ export function compilePattern(pattern: string): (path: string) => boolean {
115
+ if (pattern.endsWith("/*")) {
116
+ const prefix = pattern.slice(0, -2);
117
+ return (p) => p === prefix || p.startsWith(`${prefix}/`);
118
+ }
119
+ return (p) => p === pattern;
120
+ }
121
+
122
+ /* ── Virtual path matching ─────────────────────────────────────────── */
123
+
124
+ const MATCHER_BRAND = Symbol.for("flare/matcher");
125
+
126
+ export interface PathMatcher {
127
+ (path: string): boolean;
128
+ [MATCHER_BRAND]: true;
129
+ }
130
+
131
+ export function isPathMatcher(value: unknown): value is PathMatcher {
132
+ return typeof value === "function" && MATCHER_BRAND in (value as object);
133
+ }
134
+
135
+ type SegmentMatcher =
136
+ | { type: "catch"; min: number }
137
+ | { type: "literal"; value: string }
138
+ | { type: "optional" }
139
+ | { type: "param" };
140
+
141
+ function parseSegments(pattern: string): SegmentMatcher[] {
142
+ const raw = pattern.split("/").filter(Boolean);
143
+ return raw.map((seg): SegmentMatcher => {
144
+ if (seg.startsWith("[[...") && seg.endsWith("]]")) return { min: 0, type: "catch" };
145
+ if (seg.startsWith("[...") && seg.endsWith("]")) return { min: 1, type: "catch" };
146
+ if (seg.startsWith("[[") && seg.endsWith("]]")) return { type: "optional" };
147
+ if (seg.startsWith("[") && seg.endsWith("]")) return { type: "param" };
148
+ return { type: "literal", value: seg };
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Compile a virtual path pattern into a branded URL matcher.
154
+ * Supports `[param]`, `[[optional]]`, `[...catch]`, `[[...optional-catch]]`.
155
+ */
156
+ export function virtualPath(pattern: string): PathMatcher {
157
+ const matchers = parseSegments(pattern);
158
+
159
+ const fn = ((path: string): boolean => {
160
+ const segments = path.split("/").filter(Boolean);
161
+
162
+ function walk(mi: number, si: number): boolean {
163
+ if (mi === matchers.length) return si === segments.length;
164
+
165
+ const m = matchers[mi];
166
+ if (!m) return false;
167
+
168
+ if (m.type === "literal") {
169
+ return si < segments.length && segments[si] === m.value && walk(mi + 1, si + 1);
170
+ }
171
+ if (m.type === "param") {
172
+ return si < segments.length && walk(mi + 1, si + 1);
173
+ }
174
+ if (m.type === "optional") {
175
+ return walk(mi + 1, si) || (si < segments.length && walk(mi + 1, si + 1));
176
+ }
177
+ if (m.type === "catch") {
178
+ const remaining = segments.length - si;
179
+ for (let take = remaining; take >= m.min; take--) {
180
+ if (walk(mi + 1, si + take)) return true;
181
+ }
182
+ return false;
183
+ }
184
+ return false;
185
+ }
186
+
187
+ return walk(0, 0);
188
+ }) as PathMatcher;
189
+ fn[MATCHER_BRAND] = true;
190
+ return fn;
191
+ }
192
+
193
+ /* ── Request-type filter helpers ──────────────────────────────────────── */
194
+
195
+ export function onPage<TEnv = unknown>(mw: FlareMiddleware<TEnv>): FlareMiddleware<TEnv> {
196
+ return (ctx) => (ctx.requestType === "page" ? mw(ctx) : ctx.next());
197
+ }
198
+
199
+ export function onServerFn<TEnv = unknown>(mw: FlareMiddleware<TEnv>): FlareMiddleware<TEnv> {
200
+ return (ctx) => (ctx.requestType === "server-fn" ? mw(ctx) : ctx.next());
201
+ }
202
+
203
+ /* ── Middleware entry (used by server builder) ────────────────────────── */
204
+
205
+ export interface MiddlewareEntry<TEnv = unknown> {
206
+ matchers?: Array<(path: string) => boolean>;
207
+ middlewares: FlareMiddleware<TEnv>[];
208
+ }
209
+
210
+ export function resolveMiddlewareEntries<TEnv = unknown>(
211
+ entries: MiddlewareEntry<TEnv>[],
212
+ pathname: string,
213
+ ): FlareMiddleware<TEnv>[] {
214
+ const result: FlareMiddleware<TEnv>[] = [];
215
+ for (const entry of entries) {
216
+ if (!entry.matchers || entry.matchers.some((m) => m(pathname))) {
217
+ for (const mw of entry.middlewares) {
218
+ result.push(mw);
219
+ }
220
+ }
221
+ }
222
+ return result;
223
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Vite manifest walker for module preloading.
3
+ *
4
+ * Walks the Vite client build manifest to collect the full transitive
5
+ * dependency graph (JS + CSS) for a given entry point or set of route
6
+ * modules. Used to emit `<link rel="modulepreload">` tags in SSR HTML,
7
+ * eliminating the sequential module discovery waterfall.
8
+ */
9
+
10
+ export interface ModulePreloads {
11
+ css: string[];
12
+ js: string[];
13
+ }
14
+
15
+ export interface ViteManifestEntry {
16
+ css?: string[];
17
+ dynamicImports?: string[];
18
+ file: string;
19
+ imports?: string[];
20
+ isDynamicEntry?: boolean;
21
+ isEntry?: boolean;
22
+ name?: string;
23
+ src?: string;
24
+ }
25
+
26
+ export type ViteManifest = Record<string, ViteManifestEntry>;
27
+
28
+ /**
29
+ * Walk Vite manifest from an entry key, collecting all transitive
30
+ * static imports (JS files) and CSS files.
31
+ *
32
+ * Only follows `imports` (static), not `dynamicImports` (lazy routes).
33
+ * Returns deduplicated, ordered arrays of asset paths.
34
+ */
35
+ function ensureLeadingSlash(path: string): string {
36
+ return path.startsWith("/") ? path : `/${path}`;
37
+ }
38
+
39
+ export function resolveModulePreloads(manifest: ViteManifest, entryKey: string): ModulePreloads {
40
+ const js: string[] = [];
41
+ const css: string[] = [];
42
+ const visited = new Set<string>();
43
+ const cssVisited = new Set<string>();
44
+
45
+ function walk(key: string) {
46
+ if (visited.has(key)) return;
47
+ visited.add(key);
48
+
49
+ const entry = manifest[key];
50
+ if (!entry) return;
51
+
52
+ js.push(ensureLeadingSlash(entry.file));
53
+
54
+ if (entry.css) {
55
+ for (const href of entry.css) {
56
+ const path = ensureLeadingSlash(href);
57
+ if (!cssVisited.has(path)) {
58
+ cssVisited.add(path);
59
+ css.push(path);
60
+ }
61
+ }
62
+ }
63
+
64
+ if (entry.imports) {
65
+ for (const dep of entry.imports) {
66
+ walk(dep);
67
+ }
68
+ }
69
+ }
70
+
71
+ walk(entryKey);
72
+
73
+ /* Hydrate is a dynamic import from createClient — not a route chunk.
74
+ Preload that one hop so Lighthouse does not chain client.js → hydrate.js.
75
+ Never follow src/routes/* dynamicImports (those stay lazy). */
76
+ const root = manifest[entryKey];
77
+ if (root?.dynamicImports) {
78
+ for (const dep of root.dynamicImports) {
79
+ if (/[/\\]hydrate[/\\]index\.[cm]?[tj]sx?$/.test(dep)) {
80
+ walk(dep);
81
+ }
82
+ }
83
+ }
84
+
85
+ return { css, js };
86
+ }
87
+
88
+ /**
89
+ * Resolve route-specific chunk preloads from manifest.
90
+ *
91
+ * Given a set of matched route module IDs (source paths like
92
+ * `src/routes/index.tsx`), collects all their chunks + transitive
93
+ * static imports, excluding any chunks already covered by the entry.
94
+ *
95
+ * Returns only the NEW chunks the route needs beyond the entry graph.
96
+ */
97
+ export function resolveRoutePreloads(
98
+ manifest: ViteManifest,
99
+ routeModuleIds: string[],
100
+ entryPreloads?: ModulePreloads,
101
+ ): ModulePreloads {
102
+ const entryJs = new Set(entryPreloads?.js);
103
+ const entryCss = new Set(entryPreloads?.css);
104
+
105
+ const js: string[] = [];
106
+ const css: string[] = [];
107
+ const visited = new Set<string>();
108
+ const cssVisited = new Set(entryCss);
109
+
110
+ function walk(key: string) {
111
+ if (visited.has(key)) return;
112
+ visited.add(key);
113
+
114
+ const entry = manifest[key];
115
+ if (!entry) return;
116
+
117
+ const file = ensureLeadingSlash(entry.file);
118
+ if (!entryJs.has(file)) {
119
+ js.push(file);
120
+ }
121
+
122
+ if (entry.css) {
123
+ for (const href of entry.css) {
124
+ const cssFile = ensureLeadingSlash(href);
125
+ if (!cssVisited.has(cssFile)) {
126
+ cssVisited.add(cssFile);
127
+ css.push(cssFile);
128
+ }
129
+ }
130
+ }
131
+
132
+ if (entry.imports) {
133
+ for (const dep of entry.imports) {
134
+ walk(dep);
135
+ }
136
+ }
137
+ }
138
+
139
+ for (const moduleId of routeModuleIds) {
140
+ walk(moduleId);
141
+ }
142
+
143
+ return { css, js };
144
+ }
145
+
146
+ /**
147
+ * Find the entry key in a Vite manifest (the chunk with isEntry: true).
148
+ */
149
+ export function findEntryKey(manifest: ViteManifest): string | undefined {
150
+ for (const [key, entry] of Object.entries(manifest)) {
151
+ if (entry.isEntry) return key;
152
+ }
153
+ return undefined;
154
+ }
155
+
156
+ /**
157
+ * Merge two ModulePreloads, deduplicating.
158
+ */
159
+ export function mergePreloads(a: ModulePreloads, b: ModulePreloads): ModulePreloads {
160
+ const jsSet = new Set(a.js);
161
+ const cssSet = new Set(a.css);
162
+
163
+ const js = [...a.js];
164
+ const css = [...a.css];
165
+
166
+ for (const href of b.js) {
167
+ if (!jsSet.has(href)) {
168
+ js.push(href);
169
+ jsSet.add(href);
170
+ }
171
+ }
172
+
173
+ for (const href of b.css) {
174
+ if (!cssSet.has(href)) {
175
+ css.push(href);
176
+ cssSet.add(href);
177
+ }
178
+ }
179
+
180
+ return { css, js };
181
+ }