@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,11 @@
1
+ import type { Properties } from "csstype";
2
+
3
+ /** Style object accepted by the `sx` prop. Supports flat CSS, `&` selectors, at-rules, and variants. */
4
+ export type Sx = Properties<string | number> & {
5
+ [selector: `&${string}`]: Sx;
6
+ [atRule: `@${string}`]: Sx;
7
+ variants?: Record<string, Record<string, Sx>>;
8
+ };
9
+
10
+ /** Accepts strings, falsy values, and nested arrays — mirrors clsx input. */
11
+ export type ClassValue = string | false | null | undefined | ClassValue[];
@@ -0,0 +1,251 @@
1
+ import { EMPTY_ARR } from "../internal/index.ts";
2
+
3
+ const HYDRATION_PATTERNS = [/hydration/i, /mismatch/i, /unable to find dom nodes/i];
4
+
5
+ export function isHydrationError(message: string): boolean {
6
+ return HYDRATION_PATTERNS.some((p) => p.test(message));
7
+ }
8
+
9
+ export function filterByPatterns(messages: string[], patterns: readonly RegExp[]): string[] {
10
+ if (patterns.length === 0) return messages;
11
+ return messages.filter((msg) => !patterns.some((p) => p.test(msg)));
12
+ }
13
+
14
+ export function assertFlareStateShape(state: unknown): void {
15
+ if (state === null || state === undefined || typeof state !== "object") {
16
+ throw new Error("FlareState is null or not an object");
17
+ }
18
+
19
+ const obj = state as Record<string, unknown>;
20
+
21
+ if (typeof obj.p !== "string") {
22
+ throw new Error("FlareState.p (pathname) must be a string");
23
+ }
24
+
25
+ if (typeof obj.r !== "object" || obj.r === null) {
26
+ throw new Error("FlareState.r (params) must be an object");
27
+ }
28
+
29
+ if (typeof obj.c !== "object" || obj.c === null) {
30
+ throw new Error("FlareState.c (context) must be an object");
31
+ }
32
+ }
33
+
34
+ /* ── FlarePage ───────────────────────────────────────────────────────── */
35
+
36
+ export interface ConsoleMessage {
37
+ text: string;
38
+ type: "error" | "log" | "warning";
39
+ }
40
+
41
+ export interface PageError {
42
+ message: string;
43
+ stack?: string;
44
+ }
45
+
46
+ /**
47
+ * Minimal page interface matching Playwright's Page.
48
+ * Avoids hard dependency on @playwright/test.
49
+ */
50
+ export interface FlarePageInterface {
51
+ evaluate: (fn: unknown) => Promise<unknown>;
52
+ getByText: (text: string) => { click: () => Promise<void> };
53
+ goto: (url: string, options?: { waitUntil?: string }) => Promise<unknown>;
54
+ locator: (selector: string) => unknown;
55
+ on: (event: string, handler: (...args: unknown[]) => void) => void;
56
+ url: () => string;
57
+ waitForFunction: (
58
+ fn: string | ((arg: unknown) => boolean),
59
+ arg?: unknown,
60
+ options?: { timeout?: number },
61
+ ) => Promise<void>;
62
+ waitForLoadState: (state?: string) => Promise<void>;
63
+ waitForSelector: (selector: string, options?: { timeout?: number }) => Promise<void>;
64
+ }
65
+
66
+ export class FlarePage {
67
+ readonly page: FlarePageInterface;
68
+ private captures: {
69
+ consoleErrors: string[];
70
+ consoleLogs: string[];
71
+ consoleWarnings: string[];
72
+ pageErrors: PageError[];
73
+ } = {
74
+ consoleErrors: [],
75
+ consoleLogs: [],
76
+ consoleWarnings: [],
77
+ pageErrors: [],
78
+ };
79
+ private capturing = false;
80
+ private lastResponse: unknown = null;
81
+
82
+ constructor(page: FlarePageInterface) {
83
+ this.page = page;
84
+ }
85
+
86
+ /* ── Lifecycle ──────────────────────────────────────────────────── */
87
+
88
+ startCapturing(): void {
89
+ if (this.capturing) return;
90
+ this.capturing = true;
91
+
92
+ this.page.on("console", (...args: unknown[]) => {
93
+ const msg = args[0] as { text: () => string; type: () => string };
94
+ const text = msg.text();
95
+ const type = msg.type();
96
+ if (type === "error") this.captures.consoleErrors.push(text);
97
+ else if (type === "warning") this.captures.consoleWarnings.push(text);
98
+ else this.captures.consoleLogs.push(text);
99
+ });
100
+
101
+ this.page.on("pageerror", (...args: unknown[]) => {
102
+ const error = args[0] as { message: string; stack?: string };
103
+ this.captures.pageErrors.push({
104
+ message: error.message,
105
+ stack: error.stack,
106
+ });
107
+ });
108
+ }
109
+
110
+ clearCaptures(): void {
111
+ this.captures.consoleErrors = [];
112
+ this.captures.consoleLogs = [];
113
+ this.captures.consoleWarnings = [];
114
+ this.captures.pageErrors = [];
115
+ }
116
+
117
+ /* ── Navigation ─────────────────────────────────────────────────── */
118
+
119
+ async goto(path = "/"): Promise<unknown> {
120
+ this.lastResponse = await this.page.goto(path, { waitUntil: "domcontentloaded" });
121
+ await this.page.waitForSelector("body", { timeout: 15_000 });
122
+ return this.lastResponse;
123
+ }
124
+
125
+ async load(path: string): Promise<unknown> {
126
+ const response = await this.goto(path);
127
+ await this.waitForHydration();
128
+ await this.page.waitForLoadState("networkidle");
129
+ return response;
130
+ }
131
+
132
+ async waitForNavigation(path: string): Promise<void> {
133
+ await this.page.waitForFunction((expected: unknown) => window.location.pathname === expected, path, {
134
+ timeout: 15_000,
135
+ });
136
+ }
137
+
138
+ async clickLink(text: string): Promise<void> {
139
+ await this.page.getByText(text).click();
140
+ }
141
+
142
+ async navigateNdjson(linkText: string): Promise<void> {
143
+ await this.setNavigationMarker();
144
+ await this.clickLink(linkText);
145
+ await this.page.waitForLoadState("networkidle");
146
+ }
147
+
148
+ /* ── Hydration ──────────────────────────────────────────────────── */
149
+
150
+ async waitForHydration(timeout = 15_000): Promise<void> {
151
+ await this.page.waitForFunction("document.documentElement.hasAttribute('data-hydrated')", undefined, {
152
+ timeout,
153
+ });
154
+ }
155
+
156
+ /* ── State ──────────────────────────────────────────────────────── */
157
+
158
+ async getFlareState(): Promise<unknown> {
159
+ return await this.page.evaluate(() => (self as unknown as { flare?: unknown }).flare ?? null);
160
+ }
161
+
162
+ async assertFlareStateValid(): Promise<void> {
163
+ const state = await this.getFlareState();
164
+ assertFlareStateShape(state);
165
+ }
166
+
167
+ /* ── CSR detection ──────────────────────────────────────────────── */
168
+
169
+ async setNavigationMarker(): Promise<void> {
170
+ await this.page.evaluate("window.__FLARE_NAV_MARKER__ = true");
171
+ }
172
+
173
+ async wasClientNavigation(): Promise<boolean> {
174
+ const result = await this.page.evaluate("Boolean(window.__FLARE_NAV_MARKER__)");
175
+ return result as boolean;
176
+ }
177
+
178
+ /* ── Response ───────────────────────────────────────────────────── */
179
+
180
+ getLastResponse(): unknown {
181
+ return this.lastResponse;
182
+ }
183
+
184
+ /* ── Captures ───────────────────────────────────────────────────── */
185
+
186
+ getConsoleLogs(): string[] {
187
+ return [...this.captures.consoleLogs];
188
+ }
189
+
190
+ getConsoleErrors(): string[] {
191
+ return [...this.captures.consoleErrors];
192
+ }
193
+
194
+ getConsoleWarnings(): string[] {
195
+ return [...this.captures.consoleWarnings];
196
+ }
197
+
198
+ getPageErrors(): PageError[] {
199
+ return [...this.captures.pageErrors];
200
+ }
201
+
202
+ getHydrationErrors(): string[] {
203
+ const all = [...this.captures.consoleErrors, ...this.captures.pageErrors.map((e) => e.message)];
204
+ return all.filter((msg) => isHydrationError(msg));
205
+ }
206
+
207
+ hasHydrationErrors(): boolean {
208
+ return this.getHydrationErrors().length > 0;
209
+ }
210
+
211
+ /* ── Assertions ─────────────────────────────────────────────────── */
212
+
213
+ assertNoConsoleErrors(ignorePatterns: readonly RegExp[] = EMPTY_ARR): void {
214
+ const errors = filterByPatterns(this.captures.consoleErrors, ignorePatterns);
215
+ if (errors.length > 0) {
216
+ throw new Error(`Test failed: ${errors.length} console error(s): ${errors.join(", ")}`);
217
+ }
218
+ }
219
+
220
+ assertNoConsoleWarnings(ignorePatterns: readonly RegExp[] = EMPTY_ARR): void {
221
+ const warnings = filterByPatterns(this.captures.consoleWarnings, ignorePatterns);
222
+ if (warnings.length > 0) {
223
+ throw new Error(`Test failed: ${warnings.length} console warning(s): ${warnings.join(", ")}`);
224
+ }
225
+ }
226
+
227
+ assertNoPageErrors(): void {
228
+ if (this.captures.pageErrors.length > 0) {
229
+ const msgs = this.captures.pageErrors.map((e) => e.message);
230
+ throw new Error(`Test failed: ${msgs.length} page error(s): ${msgs.join(", ")}`);
231
+ }
232
+ }
233
+
234
+ assertNoHydrationErrors(): void {
235
+ const hydrationErrors = this.getHydrationErrors();
236
+ if (hydrationErrors.length > 0) {
237
+ throw new Error(`Test failed: ${hydrationErrors.length} hydration error(s): ${hydrationErrors.join(", ")}`);
238
+ }
239
+ }
240
+
241
+ assertHealthy(): void {
242
+ this.assertNoHydrationErrors();
243
+ this.assertNoPageErrors();
244
+ this.assertNoConsoleErrors();
245
+ }
246
+
247
+ assertFullyHealthy(): void {
248
+ this.assertHealthy();
249
+ this.assertNoConsoleWarnings();
250
+ }
251
+ }
package/src/testing.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { ConsoleMessage, FlarePageInterface, PageError } from "./testing/index.ts";
2
+ export { assertFlareStateShape, FlarePage, filterByPatterns, isHydrationError } from "./testing/index.ts";
@@ -0,0 +1,232 @@
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 Theme = "light" | "dark" | "system";
14
+ export type ResolvedTheme = "light" | "dark";
15
+
16
+ export interface ThemeConfig {
17
+ attribute?: string;
18
+ defaultTheme?: Theme;
19
+ disableTransitionOnChange?: boolean;
20
+ storageKey?: string;
21
+ themes?: readonly Theme[];
22
+ }
23
+
24
+ const DEFAULT_CONFIG: Required<ThemeConfig> = {
25
+ attribute: "data-theme",
26
+ defaultTheme: "system",
27
+ disableTransitionOnChange: true,
28
+ storageKey: "flare.theme",
29
+ themes: ["light", "dark", "system"],
30
+ };
31
+
32
+ /* Escape for safe interpolation into JS double-quoted strings inside <script> */
33
+ export function escapeJsString(s: string): string {
34
+ return s
35
+ .replace(/\\/g, "\\\\")
36
+ .replace(/"/g, '\\"')
37
+ .replace(/<\//g, "<\\/")
38
+ .replace(/\n/g, "\\n")
39
+ .replace(/\r/g, "\\r")
40
+ .replace(/\u2028/g, "\\u2028")
41
+ .replace(/\u2029/g, "\\u2029");
42
+ }
43
+
44
+ export function getThemeScript(opts?: ThemeConfig): string {
45
+ const attr = escapeJsString(opts?.attribute ?? DEFAULT_CONFIG.attribute);
46
+ const defaultTheme = escapeJsString(opts?.defaultTheme ?? DEFAULT_CONFIG.defaultTheme);
47
+ const storageKey = escapeJsString(opts?.storageKey ?? DEFAULT_CONFIG.storageKey);
48
+ return `((k,d,a)=>{const e=document.documentElement;let t;try{t=localStorage.getItem(k)||d}catch{t=d}if(t==="system")t=matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";e.setAttribute(a,t);try{e.style.colorScheme=t}catch{}})("${storageKey}","${defaultTheme}","${attr}")`;
49
+ }
50
+
51
+ /* ── Context ──────────────────────────────────────────────────────── */
52
+
53
+ export interface ThemeContextValue {
54
+ resolvedTheme: () => ResolvedTheme;
55
+ setTheme: (theme: Theme) => void;
56
+ theme: () => Theme;
57
+ toggleTheme: () => void;
58
+ }
59
+
60
+ const ThemeCtx = createContext<ThemeContextValue>();
61
+
62
+ export function ThemeProvider(props: { children: JSX.Element; config?: ThemeConfig }): JSX.Element {
63
+ const cfg: Required<ThemeConfig> = {
64
+ attribute: props.config?.attribute ?? DEFAULT_CONFIG.attribute,
65
+ defaultTheme: props.config?.defaultTheme ?? DEFAULT_CONFIG.defaultTheme,
66
+ disableTransitionOnChange: props.config?.disableTransitionOnChange ?? DEFAULT_CONFIG.disableTransitionOnChange,
67
+ storageKey: props.config?.storageKey ?? DEFAULT_CONFIG.storageKey,
68
+ themes: props.config?.themes ?? DEFAULT_CONFIG.themes,
69
+ };
70
+
71
+ const hydrating = !!sharedConfig.context;
72
+
73
+ /* During SSR + hydration, always start from defaultTheme so the tree matches.
74
+ ThemeScript already applied localStorage to <html> before first paint.
75
+ Reading storage here on the client would hydrate with a different signal
76
+ than the server and freeze the SSR "system" text until refresh. */
77
+ let initial: Theme = cfg.defaultTheme;
78
+ if (typeof localStorage !== "undefined" && !hydrating) {
79
+ try {
80
+ const stored = localStorage.getItem(cfg.storageKey);
81
+ if (stored && cfg.themes.includes(stored as Theme)) {
82
+ initial = stored as Theme;
83
+ }
84
+ } catch {
85
+ /* noop */
86
+ }
87
+ }
88
+
89
+ const [theme, setThemeSignal] = createSignal<Theme>(initial);
90
+
91
+ /* Initialize system theme synchronously to avoid extra effect cycle */
92
+ let initialSystem: ResolvedTheme = "light";
93
+ if (typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches) {
94
+ initialSystem = "dark";
95
+ }
96
+ const [systemTheme, setSystemTheme] = createSignal<ResolvedTheme>(initialSystem);
97
+
98
+ const resolvedTheme = (): ResolvedTheme => {
99
+ const t = theme();
100
+ return t === "system" ? systemTheme() : t;
101
+ };
102
+
103
+ const applyToDocument = (resolved: ResolvedTheme): void => {
104
+ if (typeof document === "undefined") return;
105
+ const el = document.documentElement;
106
+ el.setAttribute(cfg.attribute, resolved);
107
+ try {
108
+ el.style.colorScheme = resolved;
109
+ } catch {
110
+ /* CSP style-src may block CSSOM writes; attribute + nonce'd CSS still apply */
111
+ }
112
+ };
113
+
114
+ /* Client remount / CSR: apply immediately. During hydration, ThemeScript owns
115
+ first paint — applying here would overwrite a stored theme with "system". */
116
+ if (!hydrating) {
117
+ applyToDocument(resolvedTheme());
118
+ }
119
+
120
+ /* After hydrate, sync from localStorage so useTheme() matches ThemeScript. */
121
+ onMount(() => {
122
+ if (typeof localStorage === "undefined") return;
123
+ try {
124
+ const stored = localStorage.getItem(cfg.storageKey);
125
+ if (stored && cfg.themes.includes(stored as Theme) && stored !== theme()) {
126
+ setThemeSignal(stored as Theme);
127
+ }
128
+ } catch {
129
+ /* noop */
130
+ }
131
+ applyToDocument(resolvedTheme());
132
+ });
133
+
134
+ /* Listen for OS preference changes */
135
+ onMount(() => {
136
+ if (typeof matchMedia === "undefined") return;
137
+ const mq = matchMedia("(prefers-color-scheme: dark)");
138
+ const handler = (e: MediaQueryListEvent) => {
139
+ setSystemTheme(e.matches ? "dark" : "light");
140
+ };
141
+ mq.addEventListener("change", handler);
142
+ onCleanup(() => mq.removeEventListener("change", handler));
143
+ });
144
+
145
+ /* Cross-tab sync: StorageEvent fires in OTHER tabs only */
146
+ onMount(() => {
147
+ const handler = (e: StorageEvent) => {
148
+ if (e.key !== cfg.storageKey || e.storageArea !== localStorage) return;
149
+ const v = e.newValue;
150
+ if (v && cfg.themes.includes(v as Theme)) {
151
+ setThemeSignal(v as Theme);
152
+ }
153
+ };
154
+ window.addEventListener("storage", handler);
155
+ onCleanup(() => window.removeEventListener("storage", handler));
156
+ });
157
+
158
+ /* Sync resolved theme to DOM (skip transition-disable on first run) */
159
+ let skipFirst = true;
160
+ createEffect(() => {
161
+ const resolved = resolvedTheme();
162
+ if (typeof document === "undefined") return;
163
+
164
+ if (skipFirst) {
165
+ skipFirst = false;
166
+ /* After Solid hydrates <html> it drops ThemeScript's data-theme.
167
+ Re-apply the visual value (storage or system) without waiting for onMount. */
168
+ let visual: ResolvedTheme = resolved;
169
+ if (typeof localStorage !== "undefined") {
170
+ try {
171
+ const stored = localStorage.getItem(cfg.storageKey);
172
+ if (stored === "light" || stored === "dark") visual = stored;
173
+ } catch {
174
+ /* noop */
175
+ }
176
+ }
177
+ applyToDocument(visual);
178
+ return;
179
+ }
180
+
181
+ if (cfg.disableTransitionOnChange) {
182
+ const style = document.createElement("style");
183
+ style.textContent = "* { transition: none !important }";
184
+ document.head.appendChild(style);
185
+ applyToDocument(resolved);
186
+ if (document.body) void getComputedStyle(document.body).opacity;
187
+ requestAnimationFrame(() => style.remove());
188
+ } else {
189
+ applyToDocument(resolved);
190
+ }
191
+ });
192
+
193
+ /* Persist to localStorage on changes (not initial — it was read from there) */
194
+ createEffect(
195
+ on(
196
+ theme,
197
+ (t) => {
198
+ try {
199
+ localStorage.setItem(cfg.storageKey, t);
200
+ } catch {
201
+ /* noop */
202
+ }
203
+ },
204
+ { defer: true },
205
+ ),
206
+ );
207
+
208
+ const setTheme = (t: Theme): void => {
209
+ if (!cfg.themes.includes(t)) return;
210
+ setThemeSignal(t);
211
+ };
212
+
213
+ const toggleTheme = (): void => {
214
+ const current = resolvedTheme();
215
+ setTheme(current === "light" ? "dark" : "light");
216
+ };
217
+
218
+ const value: ThemeContextValue = {
219
+ resolvedTheme,
220
+ setTheme,
221
+ theme,
222
+ toggleTheme,
223
+ };
224
+
225
+ return <ThemeCtx.Provider value={value}>{props.children}</ThemeCtx.Provider>;
226
+ }
227
+
228
+ export function useTheme(): ThemeContextValue {
229
+ const ctx = useContext(ThemeCtx);
230
+ if (!ctx) throw new Error("useTheme() called outside ThemeProvider. Wrap your app with <ThemeProvider>.");
231
+ return ctx;
232
+ }
package/src/theme.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { ThemeScript } from "./components/theme-script.tsx";
2
+ export type { ResolvedTheme, Theme, ThemeConfig, ThemeContextValue } from "./theme/index.tsx";
3
+ export { escapeJsString, getThemeScript, ThemeProvider, useTheme } from "./theme/index.tsx";
@@ -0,0 +1,4 @@
1
+ export { noopTracer } from "./noop.ts";
2
+ export { createOtelTracer, type OtelSpanLike, type OtelTracerLike } from "./otel.ts";
3
+ export { buildServerTimingHeader, createTimingTracer, type TimingEntry, type TimingTracer } from "./timing.ts";
4
+ export type { FlareSpan, FlareTracer } from "./types.ts";
@@ -0,0 +1,13 @@
1
+ import type { FlareSpan, FlareTracer } from "./types.ts";
2
+
3
+ const noopSpan: FlareSpan = {
4
+ end() {},
5
+ setAttribute() {},
6
+ setStatus() {},
7
+ };
8
+
9
+ export const noopTracer: FlareTracer = {
10
+ startSpan() {
11
+ return noopSpan;
12
+ },
13
+ };
@@ -0,0 +1,37 @@
1
+ import type { FlareSpan, FlareTracer } from "./types.ts";
2
+
3
+ /**
4
+ * Minimal subset of the OpenTelemetry Tracer API that Flare consumes.
5
+ * Users pass their real OTel tracer — this avoids a hard dependency on @opentelemetry/*.
6
+ */
7
+ export interface OtelTracerLike {
8
+ startSpan(name: string): OtelSpanLike;
9
+ }
10
+
11
+ export interface OtelSpanLike {
12
+ end(): void;
13
+ setAttribute(key: string, value: boolean | number | string): void;
14
+ setStatus(status: { code: number; message?: string }): void;
15
+ }
16
+
17
+ /** OTel StatusCode.ERROR = 2, StatusCode.OK = 1 */
18
+ const STATUS_MAP: Record<"error" | "ok", number> = { error: 2, ok: 1 };
19
+
20
+ export function createOtelTracer(provider: OtelTracerLike): FlareTracer {
21
+ return {
22
+ startSpan(name: string): FlareSpan {
23
+ const span = provider.startSpan(name);
24
+ return {
25
+ end() {
26
+ span.end();
27
+ },
28
+ setAttribute(key: string, value: boolean | number | string) {
29
+ span.setAttribute(key, value);
30
+ },
31
+ setStatus(status: "error" | "ok") {
32
+ span.setStatus({ code: STATUS_MAP[status] });
33
+ },
34
+ };
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,34 @@
1
+ import type { FlareSpan, FlareTracer } from "./types.ts";
2
+
3
+ export interface TimingEntry {
4
+ dur: number;
5
+ name: string;
6
+ }
7
+
8
+ export interface TimingTracer extends FlareTracer {
9
+ getEntries(): TimingEntry[];
10
+ }
11
+
12
+ export function createTimingTracer(): TimingTracer {
13
+ const entries: TimingEntry[] = [];
14
+
15
+ return {
16
+ getEntries() {
17
+ return entries;
18
+ },
19
+ startSpan(name: string): FlareSpan {
20
+ const start = performance.now();
21
+ return {
22
+ end() {
23
+ entries.push({ dur: performance.now() - start, name });
24
+ },
25
+ setAttribute() {},
26
+ setStatus() {},
27
+ };
28
+ },
29
+ };
30
+ }
31
+
32
+ export function buildServerTimingHeader(entries: TimingEntry[]): string {
33
+ return entries.map((e) => `${e.name};dur=${e.dur.toFixed(1)}`).join(", ");
34
+ }
@@ -0,0 +1,9 @@
1
+ export interface FlareSpan {
2
+ end(): void;
3
+ setAttribute(key: string, value: boolean | number | string): void;
4
+ setStatus(status: "error" | "ok"): void;
5
+ }
6
+
7
+ export interface FlareTracer {
8
+ startSpan(name: string): FlareSpan;
9
+ }