@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.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +146 -0
- package/scripts/download-fonts.ts +136 -0
- package/scripts/populate-fonts.ts +721 -0
- package/src/await.ts +2 -0
- package/src/boundaries/index.ts +99 -0
- package/src/broadcast/channel.ts +65 -0
- package/src/broadcast/context.ts +12 -0
- package/src/broadcast/index.ts +75 -0
- package/src/broadcast/provider.tsx +7 -0
- package/src/build-location.ts +2 -0
- package/src/caches/index.ts +384 -0
- package/src/client/index.ts +75 -0
- package/src/client.tsx +2 -0
- package/src/codegen/index.ts +41 -0
- package/src/components/await.tsx +140 -0
- package/src/components/dev-error-overlay.tsx +154 -0
- package/src/components/direction-script.tsx +22 -0
- package/src/components/index.ts +150 -0
- package/src/components/locale-script.tsx +26 -0
- package/src/components/reset-css.tsx +14 -0
- package/src/components/ssr-context.tsx +37 -0
- package/src/components/theme-script.tsx +22 -0
- package/src/components/view-transition-css.tsx +21 -0
- package/src/config/index.ts +84 -0
- package/src/create-client-only-fn.ts +1 -0
- package/src/create-isomorphic-fn.ts +2 -0
- package/src/create-layout.ts +1 -0
- package/src/create-page.ts +1 -0
- package/src/create-path-segment.ts +1 -0
- package/src/create-query-client-getter.ts +2 -0
- package/src/create-root-layout.ts +1 -0
- package/src/create-router.ts +8 -0
- package/src/create-server-only-fn.ts +1 -0
- package/src/dedupe/index.ts +151 -0
- package/src/defer/index.ts +82 -0
- package/src/direction/index.tsx +163 -0
- package/src/direction.ts +3 -0
- package/src/duration/index.ts +18 -0
- package/src/env-fn/index.ts +101 -0
- package/src/env-fn.ts +2 -0
- package/src/errors/index.ts +166 -0
- package/src/fonts/abeezee.ts +50 -0
- package/src/fonts/abel.ts +26 -0
- package/src/fonts/abril-fatface.ts +34 -0
- package/src/fonts/acme.ts +26 -0
- package/src/fonts/advent-pro.ts +92 -0
- package/src/fonts/alegreya-sans.ts +228 -0
- package/src/fonts/alegreya.ts +228 -0
- package/src/fonts/alex-brush.ts +42 -0
- package/src/fonts/alfa-slab-one.ts +42 -0
- package/src/fonts/alice.ts +48 -0
- package/src/fonts/amatic-sc.ts +94 -0
- package/src/fonts/amiri.ts +114 -0
- package/src/fonts/antic-slab.ts +26 -0
- package/src/fonts/anton.ts +42 -0
- package/src/fonts/archivo-black.ts +34 -0
- package/src/fonts/archivo-narrow.ts +114 -0
- package/src/fonts/archivo.ts +66 -0
- package/src/fonts/arimo.ts +256 -0
- package/src/fonts/arvo.ts +50 -0
- package/src/fonts/asap.ts +66 -0
- package/src/fonts/assistant.ts +64 -0
- package/src/fonts/baloo-2.ts +82 -0
- package/src/fonts/barlow-condensed.ts +114 -0
- package/src/fonts/barlow-semi-condensed.ts +114 -0
- package/src/fonts/barlow.ts +114 -0
- package/src/fonts/be-vietnam-pro.ts +114 -0
- package/src/fonts/bebas-neue.ts +34 -0
- package/src/fonts/besley.ts +82 -0
- package/src/fonts/biorhyme.ts +50 -0
- package/src/fonts/bitter.ts +94 -0
- package/src/fonts/black-ops-one.ts +49 -0
- package/src/fonts/bree-serif.ts +34 -0
- package/src/fonts/bricolage-grotesque.ts +66 -0
- package/src/fonts/cabin.ts +114 -0
- package/src/fonts/cairo.ts +66 -0
- package/src/fonts/cardo.ts +193 -0
- package/src/fonts/catamaran.ts +41 -0
- package/src/fonts/caveat.ts +78 -0
- package/src/fonts/chakra-petch.ts +142 -0
- package/src/fonts/chivo.ts +66 -0
- package/src/fonts/cinzel.ts +50 -0
- package/src/fonts/comfortaa.ts +109 -0
- package/src/fonts/commissioner.ts +64 -0
- package/src/fonts/component.tsx +27 -0
- package/src/fonts/cormorant-garamond.ts +171 -0
- package/src/fonts/cormorant.ts +170 -0
- package/src/fonts/courgette.ts +34 -0
- package/src/fonts/courier-prime.ts +82 -0
- package/src/fonts/create-font.ts +47 -0
- package/src/fonts/create-registry-font.ts +40 -0
- package/src/fonts/crete-round.ts +50 -0
- package/src/fonts/crimson-text.ts +114 -0
- package/src/fonts/css.ts +86 -0
- package/src/fonts/dancing-script.ts +66 -0
- package/src/fonts/dm-mono.ts +50 -0
- package/src/fonts/dm-sans.ts +50 -0
- package/src/fonts/dm-serif-display.ts +50 -0
- package/src/fonts/domine.ts +50 -0
- package/src/fonts/dosis.ts +66 -0
- package/src/fonts/eb-garamond.ts +228 -0
- package/src/fonts/eczar.ts +94 -0
- package/src/fonts/encode-sans.ts +42 -0
- package/src/fonts/epilogue.ts +66 -0
- package/src/fonts/exo-2.ts +94 -0
- package/src/fonts/exo.ts +66 -0
- package/src/fonts/figtree.ts +82 -0
- package/src/fonts/fira-code.ts +121 -0
- package/src/fonts/fira-sans-condensed.ts +228 -0
- package/src/fonts/fira-sans.ts +228 -0
- package/src/fonts/fjalla-one.ts +49 -0
- package/src/fonts/font-urls.gen.json +1962 -0
- package/src/fonts/fraunces.ts +66 -0
- package/src/fonts/fredoka.ts +64 -0
- package/src/fonts/gelasio.ts +114 -0
- package/src/fonts/gloria-hallelujah.ts +34 -0
- package/src/fonts/gothic-a1.ts +124 -0
- package/src/fonts/great-vibes.ts +64 -0
- package/src/fonts/gudea.ts +66 -0
- package/src/fonts/heebo.ts +57 -0
- package/src/fonts/hind-siliguri.ts +66 -0
- package/src/fonts/hind.ts +66 -0
- package/src/fonts/ibm-plex-mono.ts +172 -0
- package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
- package/src/fonts/ibm-plex-sans.ts +199 -0
- package/src/fonts/ibm-plex-serif.ts +171 -0
- package/src/fonts/inconsolata.ts +66 -0
- package/src/fonts/index.ts +4 -0
- package/src/fonts/indie-flower.ts +34 -0
- package/src/fonts/instrument-sans.ts +82 -0
- package/src/fonts/instrument-serif.ts +50 -0
- package/src/fonts/inter-display.ts +52 -0
- package/src/fonts/inter-tight.ts +124 -0
- package/src/fonts/inter.ts +123 -0
- package/src/fonts/jetbrains-mono.ts +199 -0
- package/src/fonts/josefin-sans.ts +114 -0
- package/src/fonts/josefin-slab.ts +50 -0
- package/src/fonts/jost.ts +64 -0
- package/src/fonts/kalam.ts +66 -0
- package/src/fonts/kanit.ts +142 -0
- package/src/fonts/karla.ts +82 -0
- package/src/fonts/khand.ts +66 -0
- package/src/fonts/lato.ts +82 -0
- package/src/fonts/league-spartan.ts +42 -0
- package/src/fonts/lexend-deca.ts +42 -0
- package/src/fonts/lexend.ts +42 -0
- package/src/fonts/libre-baskerville.ts +82 -0
- package/src/fonts/libre-franklin.ts +95 -0
- package/src/fonts/lilita-one.ts +34 -0
- package/src/fonts/lobster-two.ts +50 -0
- package/src/fonts/lobster.ts +56 -0
- package/src/fonts/lora.ts +235 -0
- package/src/fonts/m-plus-rounded-1c.ts +138 -0
- package/src/fonts/manrope.ts +109 -0
- package/src/fonts/maven-pro.ts +66 -0
- package/src/fonts/merriweather-sans.ts +142 -0
- package/src/fonts/merriweather.ts +171 -0
- package/src/fonts/montserrat-alternates.ts +171 -0
- package/src/fonts/montserrat.ts +94 -0
- package/src/fonts/mukta.ts +66 -0
- package/src/fonts/mulish.ts +170 -0
- package/src/fonts/nanum-gothic.ts +34 -0
- package/src/fonts/nanum-myeongjo.ts +34 -0
- package/src/fonts/neuton.ts +66 -0
- package/src/fonts/newsreader.ts +114 -0
- package/src/fonts/noticia-text.ts +114 -0
- package/src/fonts/noto-sans-arabic.ts +58 -0
- package/src/fonts/noto-sans-hk.ts +49 -0
- package/src/fonts/noto-sans-jp.ts +49 -0
- package/src/fonts/noto-sans-kr.ts +49 -0
- package/src/fonts/noto-sans-sc.ts +49 -0
- package/src/fonts/noto-sans-tc.ts +49 -0
- package/src/fonts/noto-sans.ts +140 -0
- package/src/fonts/noto-serif-jp.ts +80 -0
- package/src/fonts/noto-serif.ts +140 -0
- package/src/fonts/nunito-sans.ts +170 -0
- package/src/fonts/nunito.ts +170 -0
- package/src/fonts/old-standard-tt.ts +133 -0
- package/src/fonts/onest.ts +48 -0
- package/src/fonts/open-sans.ts +340 -0
- package/src/fonts/orbitron.ts +34 -0
- package/src/fonts/oswald.ts +94 -0
- package/src/fonts/outfit.ts +34 -0
- package/src/fonts/overpass.ts +94 -0
- package/src/fonts/oxygen.ts +50 -0
- package/src/fonts/pacifico.ts +56 -0
- package/src/fonts/passion-one.ts +50 -0
- package/src/fonts/pathway-gothic-one.ts +34 -0
- package/src/fonts/permanent-marker.ts +26 -0
- package/src/fonts/philosopher.ts +172 -0
- package/src/fonts/play.ts +109 -0
- package/src/fonts/playfair-display.ts +142 -0
- package/src/fonts/plus-jakarta-sans.ts +142 -0
- package/src/fonts/poppins.ts +114 -0
- package/src/fonts/prompt.ts +142 -0
- package/src/fonts/pt-sans-narrow.ts +78 -0
- package/src/fonts/pt-sans.ts +138 -0
- package/src/fonts/pt-serif.ts +138 -0
- package/src/fonts/public-sans.ts +66 -0
- package/src/fonts/questrial.ts +42 -0
- package/src/fonts/quicksand.ts +66 -0
- package/src/fonts/rajdhani.ts +66 -0
- package/src/fonts/raleway.ts +94 -0
- package/src/fonts/recursive.ts +80 -0
- package/src/fonts/red-hat-display.ts +82 -0
- package/src/fonts/red-rose.ts +66 -0
- package/src/fonts/registry.gen.ts +173 -0
- package/src/fonts/righteous.ts +34 -0
- package/src/fonts/roboto-condensed.ts +124 -0
- package/src/fonts/roboto-flex.ts +64 -0
- package/src/fonts/roboto-mono.ts +199 -0
- package/src/fonts/roboto-slab.ts +72 -0
- package/src/fonts/roboto.ts +156 -0
- package/src/fonts/rokkitt.ts +66 -0
- package/src/fonts/rubik.ts +199 -0
- package/src/fonts/sacramento.ts +34 -0
- package/src/fonts/saira.ts +66 -0
- package/src/fonts/sarabun.ts +142 -0
- package/src/fonts/satisfy.ts +26 -0
- package/src/fonts/sawarabi-mincho.ts +41 -0
- package/src/fonts/shadows-into-light.ts +34 -0
- package/src/fonts/signika-negative.ts +66 -0
- package/src/fonts/signika.ts +66 -0
- package/src/fonts/slabo-27px.ts +34 -0
- package/src/fonts/sora.ts +50 -0
- package/src/fonts/source-code-pro.ts +228 -0
- package/src/fonts/source-sans-3.ts +228 -0
- package/src/fonts/source-serif-4.ts +199 -0
- package/src/fonts/space-grotesk.ts +66 -0
- package/src/fonts/space-mono.ts +114 -0
- package/src/fonts/spectral.ts +170 -0
- package/src/fonts/tajawal.ts +50 -0
- package/src/fonts/teko.ts +66 -0
- package/src/fonts/tenor-sans.ts +41 -0
- package/src/fonts/tinos.ts +256 -0
- package/src/fonts/titillium-web.ts +82 -0
- package/src/fonts/types.ts +43 -0
- package/src/fonts/ubuntu-condensed.ts +63 -0
- package/src/fonts/ubuntu-mono.ts +195 -0
- package/src/fonts/ubuntu.ts +195 -0
- package/src/fonts/unbounded.ts +94 -0
- package/src/fonts/unicode-ranges.gen.ts +28 -0
- package/src/fonts/urbanist.ts +50 -0
- package/src/fonts/varela-round.ts +49 -0
- package/src/fonts/vollkorn.ts +199 -0
- package/src/fonts/work-sans.ts +66 -0
- package/src/fonts/yanone-kaffeesatz.ts +128 -0
- package/src/fonts/yantramanav.ts +66 -0
- package/src/fonts/yellowtail.ts +34 -0
- package/src/fonts/young-serif.ts +34 -0
- package/src/fonts/zen-kaku-gothic-new.ts +64 -0
- package/src/fonts/zilla-slab.ts +82 -0
- package/src/form/index.tsx +205 -0
- package/src/generators/index.ts +1589 -0
- package/src/head-client/index.ts +649 -0
- package/src/history/index.ts +210 -0
- package/src/hooks.ts +61 -0
- package/src/hydrate/index.tsx +381 -0
- package/src/hydration/index.ts +187 -0
- package/src/i18n/index.ts +250 -0
- package/src/i18n/jsx.tsx +42 -0
- package/src/i18n/message-format.ts +174 -0
- package/src/image/index.tsx +473 -0
- package/src/image.ts +12 -0
- package/src/intercept-outlet/index.tsx +18 -0
- package/src/intercept-outlet.ts +2 -0
- package/src/internal/index.ts +143 -0
- package/src/internal/once-idle.ts +24 -0
- package/src/internal.ts +12 -0
- package/src/lazy/index.tsx +205 -0
- package/src/link/index.tsx +367 -0
- package/src/loader-pipeline/index.ts +639 -0
- package/src/locale/index.tsx +137 -0
- package/src/locale.ts +3 -0
- package/src/logger.ts +22 -0
- package/src/middleware/builtins/api-proxy.ts +64 -0
- package/src/middleware/builtins/cdn-proxy.ts +58 -0
- package/src/middleware/builtins/i18n.ts +256 -0
- package/src/middleware/builtins/keepalive.ts +24 -0
- package/src/middleware/builtins/markdown-negotiation.ts +141 -0
- package/src/middleware/builtins/static-assets.ts +32 -0
- package/src/middleware/index.ts +223 -0
- package/src/module-graph/index.ts +181 -0
- package/src/mount/index.ts +146 -0
- package/src/navigate.ts +1 -0
- package/src/navigation/index.ts +1278 -0
- package/src/navigation/types.ts +59 -0
- package/src/navigation-progress/index.tsx +247 -0
- package/src/navigation-progress.ts +1 -0
- package/src/ndjson-client/index.ts +376 -0
- package/src/ndjson-server/index.ts +284 -0
- package/src/outlet/index.tsx +633 -0
- package/src/outlet/types.ts +243 -0
- package/src/plugins/css-transform.ts +70 -0
- package/src/plugins/dev-cdn-cache.ts +454 -0
- package/src/plugins/dev-dashboard/client.tsx +2603 -0
- package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
- package/src/plugins/dev-dashboard/plugin.ts +404 -0
- package/src/plugins/dev-prerender.ts +168 -0
- package/src/plugins/dev-server.ts +327 -0
- package/src/plugins/generate-watch.ts +32 -0
- package/src/plugins/image-plugin.ts +171 -0
- package/src/plugins/index.ts +445 -0
- package/src/plugins/options.ts +37 -0
- package/src/plugins/parse-utils.ts +59 -0
- package/src/plugins/prerender-plugin.ts +144 -0
- package/src/plugins/purge.ts +242 -0
- package/src/plugins/server-fn.ts +641 -0
- package/src/plugins/service-worker.ts +225 -0
- package/src/plugins/solid-jsx.d.ts +12 -0
- package/src/plugins/sx-ast/atomic.ts +166 -0
- package/src/plugins/sx-ast/evaluator.ts +235 -0
- package/src/plugins/sx-ast/index.ts +372 -0
- package/src/plugins/sx-ast/parser.ts +22 -0
- package/src/plugins/sx-ast/rewrite.ts +763 -0
- package/src/plugins/sx.d.ts +14 -0
- package/src/plugins/tw-compile.ts +240 -0
- package/src/plugins/types.ts +41 -0
- package/src/plugins/virtual.d.ts +110 -0
- package/src/plugins/virtual.ts +184 -0
- package/src/preload/index.ts +77 -0
- package/src/prerender/index.ts +431 -0
- package/src/query-client/index.tsx +168 -0
- package/src/registry/index.ts +88 -0
- package/src/reset-css.ts +1 -0
- package/src/revalidation/index.ts +84 -0
- package/src/rewrite/index.ts +83 -0
- package/src/route-builder/create-layout.ts +655 -0
- package/src/route-builder/create-page.ts +806 -0
- package/src/route-builder/create-path-segment.ts +34 -0
- package/src/route-builder/create-root-layout.ts +608 -0
- package/src/route-builder/index.ts +49 -0
- package/src/route-builder/register.ts +242 -0
- package/src/route-builder/types.ts +294 -0
- package/src/router-config/index.ts +91 -0
- package/src/router-primitives/index.ts +25 -0
- package/src/router-primitives/location.ts +29 -0
- package/src/router-primitives/match-id.ts +111 -0
- package/src/router-primitives/paths.ts +109 -0
- package/src/router-primitives/tree.ts +243 -0
- package/src/router-primitives/types.ts +74 -0
- package/src/router.ts +31 -0
- package/src/search-engine/bing.ts +32 -0
- package/src/search-engine/google-jwt.ts +95 -0
- package/src/search-engine/google.ts +139 -0
- package/src/search-engine/index-now.ts +64 -0
- package/src/search-engine/index.ts +12 -0
- package/src/security/index.ts +230 -0
- package/src/server/index.ts +286 -0
- package/src/server-context/browser.ts +113 -0
- package/src/server-context/index.ts +217 -0
- package/src/server-fn/index.ts +710 -0
- package/src/server-fn-query.ts +169 -0
- package/src/server-handler/etag.ts +51 -0
- package/src/server-handler/headers.ts +5 -0
- package/src/server-handler/index.ts +1664 -0
- package/src/server-handler/types.ts +24 -0
- package/src/server-handler/validate-static-params.ts +130 -0
- package/src/server.ts +2 -0
- package/src/service-worker/template.ts +153 -0
- package/src/service-worker-hook/index.ts +70 -0
- package/src/sitemap/index.ts +231 -0
- package/src/ssr/critical-css.ts +150 -0
- package/src/ssr/head-prefix.ts +65 -0
- package/src/ssr/head.ts +259 -0
- package/src/ssr/index.tsx +879 -0
- package/src/state-parser/index.ts +266 -0
- package/src/store/filesystem.ts +131 -0
- package/src/store/index.ts +29 -0
- package/src/styles/index.ts +1200 -0
- package/src/styles/sx-types.ts +11 -0
- package/src/testing/index.ts +251 -0
- package/src/testing.ts +2 -0
- package/src/theme/index.tsx +232 -0
- package/src/theme.ts +3 -0
- package/src/tracing/index.ts +4 -0
- package/src/tracing/noop.ts +13 -0
- package/src/tracing/otel.ts +37 -0
- package/src/tracing/timing.ts +34 -0
- package/src/tracing/types.ts +9 -0
- package/src/url/index.ts +140 -0
- package/src/use-router.ts +11 -0
- package/src/use-suspense-query.ts +2 -0
- package/src/validation/index.ts +57 -0
- package/src/view-transition-css.ts +1 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-render engine — runs at build time to generate static HTML + NDJSON
|
|
3
|
+
* artifacts for routes configured with `static: true` or ISR.
|
|
4
|
+
*
|
|
5
|
+
* For each route:
|
|
6
|
+
* 1. Create synthetic GET request
|
|
7
|
+
* 2. Call handler.fetch() → collect HTML response
|
|
8
|
+
* 3. Call handler.fetch() with x-d:1 header → collect NDJSON response
|
|
9
|
+
* 4. Replace CSP nonce with __FLARE_NONCE__ placeholder
|
|
10
|
+
* 5. Store entry with headers, html, ndjson
|
|
11
|
+
* 6. Generate manifest entry
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import type { ExtractedStaticDeferMode, RouteDefinition } from "../generators/index.ts";
|
|
17
|
+
import type { StaticDeferMode } from "../route-builder/types.ts";
|
|
18
|
+
import type { ServerHandler } from "../server-handler/index.ts";
|
|
19
|
+
import { resolvePathParams } from "../url/index.ts";
|
|
20
|
+
|
|
21
|
+
export const NONCE_PLACEHOLDER = "__FLARE_NONCE__";
|
|
22
|
+
|
|
23
|
+
/** Build-time prerender fetch — skips ISR `dynamicParams: false` store-miss 404. */
|
|
24
|
+
export const PRERENDER_HEADER = "x-flare-prerender";
|
|
25
|
+
|
|
26
|
+
export interface PrerenderRoute {
|
|
27
|
+
defer?: StaticDeferMode;
|
|
28
|
+
dynamicParams?: boolean;
|
|
29
|
+
mode: "isr" | "static";
|
|
30
|
+
pathname: string;
|
|
31
|
+
revalidate?: number;
|
|
32
|
+
tags?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PrerenderConfig {
|
|
36
|
+
concurrency?: number;
|
|
37
|
+
env?: Record<string, unknown> | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
38
|
+
exclude?: string[];
|
|
39
|
+
handler: ServerHandler;
|
|
40
|
+
origin: string;
|
|
41
|
+
routes: PrerenderRoute[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PrerenderManifestEntry {
|
|
45
|
+
defer?: StaticDeferMode;
|
|
46
|
+
dynamicParams?: boolean;
|
|
47
|
+
headers: Record<string, string>;
|
|
48
|
+
html: string;
|
|
49
|
+
mode: "isr" | "static";
|
|
50
|
+
ndjson: string;
|
|
51
|
+
pathname: string;
|
|
52
|
+
revalidate?: number;
|
|
53
|
+
tags?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PrerenderResult {
|
|
57
|
+
entries: PrerenderManifestEntry[];
|
|
58
|
+
errors: Array<{ message: string; pathname: string }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract the nonce value from an HTML string by looking for
|
|
63
|
+
* `nonce="..."` attributes or `nonce-xxx` in CSP-style content.
|
|
64
|
+
*/
|
|
65
|
+
export function extractNonce(html: string): string | undefined {
|
|
66
|
+
/* Try nonce attribute first: nonce="value" — supports hex and base64 */
|
|
67
|
+
const attrMatch = /\bnonce="([a-zA-Z0-9+/_-]+=*)"/.exec(html);
|
|
68
|
+
if (attrMatch) return attrMatch[1];
|
|
69
|
+
|
|
70
|
+
/* Fallback: nonce-value in CSP content — supports hex and base64 */
|
|
71
|
+
const cspMatch = /nonce-([a-zA-Z0-9+/_-]+=*)/.exec(html);
|
|
72
|
+
return cspMatch?.[1];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Replace all occurrences of a nonce value in a string with the placeholder.
|
|
77
|
+
*/
|
|
78
|
+
export function replaceNonce(text: string, nonce: string): string {
|
|
79
|
+
return text.replaceAll(nonce, NONCE_PLACEHOLDER);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Convert Response headers to a plain object, excluding hop-by-hop headers.
|
|
84
|
+
*/
|
|
85
|
+
function headersToRecord(headers: Headers): Record<string, string> {
|
|
86
|
+
const result: Record<string, string> = {};
|
|
87
|
+
headers.forEach((value, key) => {
|
|
88
|
+
/* Skip transfer-encoding and connection headers */
|
|
89
|
+
if (key === "transfer-encoding" || key === "connection") return;
|
|
90
|
+
result[key] = value;
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Render a single route — fetches HTML + NDJSON, replaces nonce.
|
|
97
|
+
*/
|
|
98
|
+
async function renderRoute(
|
|
99
|
+
route: PrerenderRoute,
|
|
100
|
+
origin: string,
|
|
101
|
+
handler: ServerHandler,
|
|
102
|
+
env: unknown,
|
|
103
|
+
): Promise<{ entry: PrerenderManifestEntry } | { error: { message: string; pathname: string } }> {
|
|
104
|
+
const url = `${origin}${route.pathname}`;
|
|
105
|
+
|
|
106
|
+
/* 1. Fetch HTML */
|
|
107
|
+
let htmlResponse: Response;
|
|
108
|
+
try {
|
|
109
|
+
htmlResponse = await handler.fetch(new Request(url, { headers: { [PRERENDER_HEADER]: "1" }, method: "GET" }), env);
|
|
110
|
+
} catch (e: unknown) {
|
|
111
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
112
|
+
return { error: { message: msg, pathname: route.pathname } };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (htmlResponse.status !== 200) {
|
|
116
|
+
return {
|
|
117
|
+
error: {
|
|
118
|
+
message: `HTML response status ${htmlResponse.status}`,
|
|
119
|
+
pathname: route.pathname,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let html = await htmlResponse.text();
|
|
125
|
+
const htmlHeaders = headersToRecord(htmlResponse.headers);
|
|
126
|
+
|
|
127
|
+
/* 2. Fetch NDJSON */
|
|
128
|
+
let ndjson = "";
|
|
129
|
+
try {
|
|
130
|
+
const dataResponse = await handler.fetch(
|
|
131
|
+
new Request(url, {
|
|
132
|
+
headers: { "x-d": "1", [PRERENDER_HEADER]: "1" },
|
|
133
|
+
method: "GET",
|
|
134
|
+
}),
|
|
135
|
+
env,
|
|
136
|
+
);
|
|
137
|
+
ndjson = await dataResponse.text();
|
|
138
|
+
} catch {
|
|
139
|
+
/* NDJSON fetch failure is non-fatal — store empty */
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* 3. Extract and replace nonce */
|
|
143
|
+
const nonce = extractNonce(html);
|
|
144
|
+
if (nonce) {
|
|
145
|
+
html = replaceNonce(html, nonce);
|
|
146
|
+
/* Replace nonce in headers too (CSP header) */
|
|
147
|
+
for (const [key, value] of Object.entries(htmlHeaders)) {
|
|
148
|
+
if (value.includes(nonce)) {
|
|
149
|
+
htmlHeaders[key] = replaceNonce(value, nonce);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* 4. Build manifest entry */
|
|
155
|
+
const entry: PrerenderManifestEntry = {
|
|
156
|
+
headers: htmlHeaders,
|
|
157
|
+
html,
|
|
158
|
+
mode: route.mode,
|
|
159
|
+
ndjson,
|
|
160
|
+
pathname: route.pathname,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (route.defer !== undefined) entry.defer = route.defer;
|
|
164
|
+
if (route.dynamicParams !== undefined) entry.dynamicParams = route.dynamicParams;
|
|
165
|
+
if (route.revalidate !== undefined) entry.revalidate = route.revalidate;
|
|
166
|
+
if (route.tags !== undefined) entry.tags = route.tags;
|
|
167
|
+
|
|
168
|
+
return { entry };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Pre-render static and ISR routes.
|
|
173
|
+
*
|
|
174
|
+
* Routes are rendered in parallel with configurable concurrency (default: 6).
|
|
175
|
+
* Each route produces an HTML + NDJSON pair with nonce placeholders.
|
|
176
|
+
*/
|
|
177
|
+
export async function prerender(config: PrerenderConfig): Promise<PrerenderResult> {
|
|
178
|
+
const { concurrency = 6, handler, origin } = config;
|
|
179
|
+
const excludeSet = new Set(config.exclude ?? []);
|
|
180
|
+
|
|
181
|
+
/* Filter routes */
|
|
182
|
+
const routes = config.routes.filter((r) => !excludeSet.has(r.pathname));
|
|
183
|
+
|
|
184
|
+
if (routes.length === 0) {
|
|
185
|
+
return { entries: [], errors: [] };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Resolve env */
|
|
189
|
+
let env: unknown;
|
|
190
|
+
if (config.env !== undefined) {
|
|
191
|
+
env = typeof config.env === "function" ? await config.env() : config.env;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Render with concurrency control */
|
|
195
|
+
const entries: PrerenderManifestEntry[] = [];
|
|
196
|
+
const errors: Array<{ message: string; pathname: string }> = [];
|
|
197
|
+
|
|
198
|
+
/* Simple semaphore-based concurrency control */
|
|
199
|
+
let activeCount = 0;
|
|
200
|
+
let routeIndex = 0;
|
|
201
|
+
const totalRoutes = routes.length;
|
|
202
|
+
|
|
203
|
+
await new Promise<void>((resolve) => {
|
|
204
|
+
let completedCount = 0;
|
|
205
|
+
|
|
206
|
+
function startNext(): void {
|
|
207
|
+
while (activeCount < concurrency && routeIndex < totalRoutes) {
|
|
208
|
+
const route = routes[routeIndex++];
|
|
209
|
+
if (!route) continue;
|
|
210
|
+
activeCount++;
|
|
211
|
+
|
|
212
|
+
renderRoute(route, origin, handler, env)
|
|
213
|
+
.then((result) => {
|
|
214
|
+
if ("entry" in result) {
|
|
215
|
+
entries.push(result.entry);
|
|
216
|
+
} else {
|
|
217
|
+
errors.push(result.error);
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
.catch((e: unknown) => {
|
|
221
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
222
|
+
errors.push({ message: msg, pathname: route.pathname });
|
|
223
|
+
})
|
|
224
|
+
.finally(() => {
|
|
225
|
+
activeCount--;
|
|
226
|
+
completedCount++;
|
|
227
|
+
if (completedCount === totalRoutes) {
|
|
228
|
+
resolve();
|
|
229
|
+
} else {
|
|
230
|
+
startNext();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
startNext();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return { entries, errors };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ── Route definition → PrerenderRoute bridge ──────────────────── */
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Compute URL pathname from virtualPath, stripping root prefix, group
|
|
246
|
+
* segments, and "index" → "/". Same logic as generators/computeUrlPattern.
|
|
247
|
+
*/
|
|
248
|
+
function virtualPathToPathname(virtualPath: string): string {
|
|
249
|
+
const parts = virtualPath.split("/");
|
|
250
|
+
const rootIdx = parts.findIndex((p) => p.startsWith("_") && p.endsWith("_") && p.length >= 3);
|
|
251
|
+
const urlParts: string[] = [];
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < parts.length; i++) {
|
|
254
|
+
const part = parts[i] ?? "";
|
|
255
|
+
if (rootIdx >= 0 && i <= rootIdx) continue;
|
|
256
|
+
if (part.startsWith("(") && part.endsWith(")")) continue;
|
|
257
|
+
if (part === "") continue;
|
|
258
|
+
urlParts.push(part);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (urlParts.length === 0) return "/";
|
|
262
|
+
return `/${urlParts.join("/")}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function mapDynamicParams(extracted: boolean | undefined): boolean | undefined {
|
|
266
|
+
return extracted;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function mapDefer(extracted: ExtractedStaticDeferMode | undefined): StaticDeferMode | undefined {
|
|
270
|
+
return extracted;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Convert generator RouteDefinitions to PrerenderRoutes.
|
|
275
|
+
* Filters to pages with static config, validates auth constraints.
|
|
276
|
+
* When `staticParams` is provided, dynamic routes are expanded into
|
|
277
|
+
* concrete pathnames using `resolvePathParams`.
|
|
278
|
+
*/
|
|
279
|
+
export function buildPrerenderRoutes(
|
|
280
|
+
defs: RouteDefinition[],
|
|
281
|
+
staticParams?: Map<string, Record<string, string | string[]>[]>,
|
|
282
|
+
): PrerenderRoute[] {
|
|
283
|
+
const routes: PrerenderRoute[] = [];
|
|
284
|
+
|
|
285
|
+
for (const def of defs) {
|
|
286
|
+
/* Only pages, not layouts or response routes */
|
|
287
|
+
if (def.type !== "page") continue;
|
|
288
|
+
if (def.responseRoute) continue;
|
|
289
|
+
|
|
290
|
+
const hasPrerenderConfig = def.cache.ssg || def.cache.isr;
|
|
291
|
+
if (!hasPrerenderConfig) continue;
|
|
292
|
+
|
|
293
|
+
/* isr without revalidate = on-demand only, skip build-time prerender */
|
|
294
|
+
if (def.cache.isr && def.cache.isrRevalidate === undefined) continue;
|
|
295
|
+
|
|
296
|
+
/* Auth + static = build error (authenticateOptional is allowed) */
|
|
297
|
+
if (def.authenticateMode === true) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Route "${def.virtualPath}" has both authenticate and static config. ` +
|
|
300
|
+
"Authenticated routes cannot be pre-rendered.",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const pathname = virtualPathToPathname(def.virtualPath);
|
|
305
|
+
const mode: "isr" | "static" = def.cache.isr ? "isr" : "static";
|
|
306
|
+
|
|
307
|
+
/* Dynamic route — expand with staticParams */
|
|
308
|
+
if (pathname.includes("[")) {
|
|
309
|
+
const paramSets = staticParams?.get(def.virtualPath);
|
|
310
|
+
if (!paramSets || paramSets.length === 0) continue;
|
|
311
|
+
|
|
312
|
+
for (const params of paramSets) {
|
|
313
|
+
const resolved = resolvePathParams(pathname, params);
|
|
314
|
+
const route: PrerenderRoute = { mode, pathname: resolved };
|
|
315
|
+
|
|
316
|
+
if (def.cache.isrRevalidate !== undefined) {
|
|
317
|
+
route.revalidate = def.cache.isrRevalidate;
|
|
318
|
+
}
|
|
319
|
+
const defer = mapDefer(def.cache.isrDefer ?? def.cache.ssgDefer);
|
|
320
|
+
if (defer !== undefined) route.defer = defer;
|
|
321
|
+
const dp = mapDynamicParams(def.cache.isrDynamicParams);
|
|
322
|
+
if (dp !== undefined) route.dynamicParams = dp;
|
|
323
|
+
|
|
324
|
+
routes.push(route);
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const route: PrerenderRoute = { mode, pathname };
|
|
330
|
+
|
|
331
|
+
if (def.cache.isrRevalidate !== undefined) {
|
|
332
|
+
route.revalidate = def.cache.isrRevalidate;
|
|
333
|
+
}
|
|
334
|
+
const defer = mapDefer(def.cache.isrDefer ?? def.cache.ssgDefer);
|
|
335
|
+
if (defer !== undefined) route.defer = defer;
|
|
336
|
+
const dp2 = mapDynamicParams(def.cache.isrDynamicParams);
|
|
337
|
+
if (dp2 !== undefined) route.dynamicParams = dp2;
|
|
338
|
+
|
|
339
|
+
routes.push(route);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return routes;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* ── Output generation ─────────────────────────────────────────── */
|
|
346
|
+
|
|
347
|
+
export interface PrerenderOutputFile {
|
|
348
|
+
content: string;
|
|
349
|
+
path: string;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface PrerenderManifestRecord {
|
|
353
|
+
defer?: StaticDeferMode;
|
|
354
|
+
dynamicParams?: boolean;
|
|
355
|
+
mode: "isr" | "static";
|
|
356
|
+
pathname: string;
|
|
357
|
+
revalidate?: number;
|
|
358
|
+
tags?: string[];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface PrerenderOutput {
|
|
362
|
+
files: PrerenderOutputFile[];
|
|
363
|
+
manifest: PrerenderManifestRecord[];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Convert prerender entries to file outputs + manifest.
|
|
368
|
+
* Each entry produces:
|
|
369
|
+
* - `{pathname}.html` — pre-rendered HTML with nonce placeholder
|
|
370
|
+
* - `{pathname}.ndjson` — serialized data
|
|
371
|
+
* - `{pathname}.headers.json` — response headers
|
|
372
|
+
*/
|
|
373
|
+
export function writePrerenderOutput(entries: PrerenderManifestEntry[]): PrerenderOutput {
|
|
374
|
+
const files: PrerenderOutputFile[] = [];
|
|
375
|
+
const manifest: PrerenderManifestRecord[] = [];
|
|
376
|
+
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
const base = entry.pathname === "/" ? "/index" : entry.pathname;
|
|
379
|
+
|
|
380
|
+
files.push({ content: entry.html, path: `${base}.html` });
|
|
381
|
+
files.push({ content: entry.ndjson, path: `${base}.ndjson` });
|
|
382
|
+
files.push({
|
|
383
|
+
content: JSON.stringify(entry.headers, null, 2),
|
|
384
|
+
path: `${base}.headers.json`,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const record: PrerenderManifestRecord = {
|
|
388
|
+
mode: entry.mode,
|
|
389
|
+
pathname: entry.pathname,
|
|
390
|
+
};
|
|
391
|
+
if (entry.defer !== undefined) record.defer = entry.defer;
|
|
392
|
+
if (entry.dynamicParams !== undefined) record.dynamicParams = entry.dynamicParams;
|
|
393
|
+
if (entry.revalidate !== undefined) record.revalidate = entry.revalidate;
|
|
394
|
+
if (entry.tags !== undefined) record.tags = entry.tags;
|
|
395
|
+
|
|
396
|
+
manifest.push(record);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { files, manifest };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Load build-time prerender artifacts from disk into a store.
|
|
404
|
+
* Reads manifest.json + .html/.ndjson/.headers.json files from `staticDir`.
|
|
405
|
+
* Silently no-ops if manifest doesn't exist (e.g. dev mode without prerender).
|
|
406
|
+
*/
|
|
407
|
+
export function loadPrerenderArtifacts(staticDir: string, store: import("../store").FlareStore): void {
|
|
408
|
+
const manifestPath = join(staticDir, "manifest.json");
|
|
409
|
+
if (!existsSync(manifestPath)) return;
|
|
410
|
+
|
|
411
|
+
const manifest: PrerenderManifestRecord[] = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
412
|
+
|
|
413
|
+
for (const record of manifest) {
|
|
414
|
+
const base = record.pathname === "/" ? "/index" : record.pathname;
|
|
415
|
+
const htmlPath = join(staticDir, `${base}.html`);
|
|
416
|
+
if (!existsSync(htmlPath)) continue;
|
|
417
|
+
|
|
418
|
+
const html = readFileSync(htmlPath, "utf-8");
|
|
419
|
+
const ndjsonPath = join(staticDir, `${base}.ndjson`);
|
|
420
|
+
const ndjson = existsSync(ndjsonPath) ? readFileSync(ndjsonPath, "utf-8") : "";
|
|
421
|
+
const headersPath = join(staticDir, `${base}.headers.json`);
|
|
422
|
+
const headers: Record<string, string> = existsSync(headersPath)
|
|
423
|
+
? JSON.parse(readFileSync(headersPath, "utf-8"))
|
|
424
|
+
: {};
|
|
425
|
+
|
|
426
|
+
store.set(`static:${record.pathname}`, {
|
|
427
|
+
data: { headers, html, ndjson },
|
|
428
|
+
storedAt: Date.now(),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider, type QueryKey, useQuery } from "@tanstack/solid-query";
|
|
2
|
+
import type { Accessor } from "solid-js";
|
|
3
|
+
|
|
4
|
+
export { QueryClientProvider };
|
|
5
|
+
|
|
6
|
+
/* ── useSuspenseQuery ────────────────────────────────────────────────── */
|
|
7
|
+
|
|
8
|
+
export interface UseSuspenseQueryOptions<TData = unknown, _TError = Error, TQueryKey extends QueryKey = QueryKey> {
|
|
9
|
+
queryFn: (context: { queryKey: TQueryKey; signal: AbortSignal }) => TData | Promise<TData>;
|
|
10
|
+
queryKey: Accessor<TQueryKey> | TQueryKey;
|
|
11
|
+
retry?: boolean | number;
|
|
12
|
+
staleTime?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseSuspenseQueryResult<TData = unknown, TError = Error> {
|
|
16
|
+
data: Accessor<TData | undefined>;
|
|
17
|
+
dataUpdatedAt: Accessor<number>;
|
|
18
|
+
error: Accessor<TError | null>;
|
|
19
|
+
errorUpdatedAt: Accessor<number>;
|
|
20
|
+
failureCount: Accessor<number>;
|
|
21
|
+
failureReason: Accessor<TError | null>;
|
|
22
|
+
fetchStatus: Accessor<string>;
|
|
23
|
+
isError: Accessor<boolean>;
|
|
24
|
+
isFetched: Accessor<boolean>;
|
|
25
|
+
isFetchedAfterMount: Accessor<boolean>;
|
|
26
|
+
isFetching: Accessor<boolean>;
|
|
27
|
+
isLoading: Accessor<boolean>;
|
|
28
|
+
isPaused: Accessor<boolean>;
|
|
29
|
+
isPlaceholderData: Accessor<boolean>;
|
|
30
|
+
isRefetchError: Accessor<boolean>;
|
|
31
|
+
isRefetching: Accessor<boolean>;
|
|
32
|
+
isStale: Accessor<boolean>;
|
|
33
|
+
isSuccess: Accessor<boolean>;
|
|
34
|
+
refetch: () => Promise<unknown>;
|
|
35
|
+
status: Accessor<"success">;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveQueryKey<T extends QueryKey>(v: Accessor<T> | T): T {
|
|
39
|
+
return Array.isArray(v) ? v : (v as Accessor<T>)();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Thin wrapper over TanStack's `useQuery` with suspense semantics.
|
|
44
|
+
* Delegates entirely to TanStack's SSR-aware implementation which uses
|
|
45
|
+
* `createResource` for proper Solid SSR streaming integration.
|
|
46
|
+
*/
|
|
47
|
+
export function useSuspenseQuery<TData = unknown, TError = Error, TQueryKey extends QueryKey = QueryKey>(
|
|
48
|
+
options: UseSuspenseQueryOptions<TData, TError, TQueryKey>,
|
|
49
|
+
): UseSuspenseQueryResult<TData, TError> {
|
|
50
|
+
const result = useQuery(() => ({
|
|
51
|
+
enabled: true,
|
|
52
|
+
queryFn: options.queryFn as (context: { queryKey: QueryKey; signal: AbortSignal }) => TData | Promise<TData>,
|
|
53
|
+
queryKey: resolveQueryKey(options.queryKey),
|
|
54
|
+
retry: options.retry,
|
|
55
|
+
staleTime: options.staleTime,
|
|
56
|
+
throwOnError: true,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
data: () => result.data as TData,
|
|
61
|
+
dataUpdatedAt: () => result.dataUpdatedAt,
|
|
62
|
+
error: () => result.error as TError | null,
|
|
63
|
+
errorUpdatedAt: () => result.errorUpdatedAt,
|
|
64
|
+
failureCount: () => result.failureCount,
|
|
65
|
+
failureReason: () => result.failureReason as TError | null,
|
|
66
|
+
fetchStatus: () => result.fetchStatus,
|
|
67
|
+
isError: () => result.isError,
|
|
68
|
+
isFetched: () => result.isFetched,
|
|
69
|
+
isFetchedAfterMount: () => result.isFetchedAfterMount,
|
|
70
|
+
isFetching: () => result.isFetching,
|
|
71
|
+
isLoading: () => result.isLoading,
|
|
72
|
+
isPaused: () => result.isPaused,
|
|
73
|
+
isPlaceholderData: () => result.isPlaceholderData,
|
|
74
|
+
isRefetchError: () => result.isRefetchError,
|
|
75
|
+
isRefetching: () => result.isRefetching,
|
|
76
|
+
isStale: () => result.isStale,
|
|
77
|
+
isSuccess: () => result.isSuccess,
|
|
78
|
+
refetch: () => result.refetch(),
|
|
79
|
+
status: () => "success" as const,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── createQueryClientGetter (per-request on server, singleton on client) */
|
|
84
|
+
|
|
85
|
+
export interface QueryClientGetterOptions {
|
|
86
|
+
broadcast?: boolean | string;
|
|
87
|
+
defaultOptions?: {
|
|
88
|
+
mutations?: { gcTime?: number; retry?: boolean | number };
|
|
89
|
+
queries?: { gcTime?: number; retry?: boolean | number; staleTime?: number };
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createQueryClientGetter(options?: QueryClientGetterOptions): () => QueryClient {
|
|
94
|
+
let clientInstance: QueryClient | undefined;
|
|
95
|
+
return () => {
|
|
96
|
+
if (typeof window === "undefined") {
|
|
97
|
+
return new QueryClient(options);
|
|
98
|
+
}
|
|
99
|
+
if (!clientInstance) {
|
|
100
|
+
clientInstance = new QueryClient(options);
|
|
101
|
+
if (options?.broadcast) {
|
|
102
|
+
const channelName = typeof options.broadcast === "string" ? options.broadcast : "flare:qc";
|
|
103
|
+
import("@tanstack/query-broadcast-client-experimental")
|
|
104
|
+
.then(({ broadcastQueryClient }) => {
|
|
105
|
+
broadcastQueryClient({
|
|
106
|
+
broadcastChannel: channelName,
|
|
107
|
+
queryClient: clientInstance as QueryClient,
|
|
108
|
+
});
|
|
109
|
+
})
|
|
110
|
+
.catch(() => {});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return clientInstance;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ── createTrackedQueryClient (SSR query tracking) ───────────────────── */
|
|
118
|
+
|
|
119
|
+
export interface QueryState {
|
|
120
|
+
data: unknown;
|
|
121
|
+
key: unknown[];
|
|
122
|
+
staleTime?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface TrackedQueryClient {
|
|
126
|
+
client: QueryClient;
|
|
127
|
+
drain(): QueryState[];
|
|
128
|
+
getTrackedQueries(): QueryState[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createTrackedQueryClient(client: QueryClient): TrackedQueryClient {
|
|
132
|
+
const tracked: QueryState[] = [];
|
|
133
|
+
|
|
134
|
+
const originalSetQueryData = client.setQueryData.bind(client);
|
|
135
|
+
/* TS 7: QueryClient.setQueryData identities from query-core vs solid-query do not unify. */
|
|
136
|
+
client.setQueryData = ((...args: Parameters<QueryClient["setQueryData"]>) => {
|
|
137
|
+
const result = originalSetQueryData(...args);
|
|
138
|
+
|
|
139
|
+
const defaults = client.getQueryDefaults(args[0]);
|
|
140
|
+
const rawStaleTime = defaults?.staleTime;
|
|
141
|
+
tracked.push({
|
|
142
|
+
data: result,
|
|
143
|
+
key: [...args[0]],
|
|
144
|
+
staleTime: typeof rawStaleTime === "number" ? rawStaleTime : undefined,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
}) as QueryClient["setQueryData"];
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
client,
|
|
152
|
+
drain: () => tracked.splice(0),
|
|
153
|
+
getTrackedQueries: () => tracked,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ── hydrateQueryCache (client-side query hydration) ─────────────────── */
|
|
158
|
+
|
|
159
|
+
export function hydrateQueryCache(client: QueryClient, queries: QueryState[]): void {
|
|
160
|
+
for (const entry of queries) {
|
|
161
|
+
if (!Array.isArray(entry.key)) continue;
|
|
162
|
+
const key = entry.key as QueryKey;
|
|
163
|
+
client.setQueryData(key, entry.data);
|
|
164
|
+
if (entry.staleTime !== undefined && entry.staleTime !== null) {
|
|
165
|
+
client.setQueryDefaults(key, { staleTime: entry.staleTime });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
export interface LazyComponent {
|
|
4
|
+
preload(): Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Registry {
|
|
8
|
+
get(key: string): LazyComponent | undefined;
|
|
9
|
+
keys(): string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface RegistryTrackingResult<R> {
|
|
13
|
+
dk: () => string[];
|
|
14
|
+
result: R;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const trackingStore = new AsyncLocalStorage<Set<string>>();
|
|
18
|
+
const registries: Registry[] = [];
|
|
19
|
+
|
|
20
|
+
export function createRegistry<T extends Record<string, LazyComponent>>(components: T): Registry {
|
|
21
|
+
const proxy = new Proxy(components, {
|
|
22
|
+
get(target, prop, receiver) {
|
|
23
|
+
const store = trackingStore.getStore();
|
|
24
|
+
if (store && typeof prop === "string" && prop !== "keys") {
|
|
25
|
+
store.add(prop);
|
|
26
|
+
}
|
|
27
|
+
return Reflect.get(target, prop, receiver);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
get(key: string) {
|
|
33
|
+
return proxy[key];
|
|
34
|
+
},
|
|
35
|
+
keys() {
|
|
36
|
+
return Object.keys(components);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function withRegistryTracking<R>(fn: () => R): RegistryTrackingResult<R> {
|
|
42
|
+
const tracked = new Set<string>();
|
|
43
|
+
const result = trackingStore.run(tracked, fn);
|
|
44
|
+
return {
|
|
45
|
+
dk: () => Array.from(tracked),
|
|
46
|
+
result,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function dk(): string[] {
|
|
51
|
+
const store = trackingStore.getStore();
|
|
52
|
+
return store ? Array.from(store) : [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function registerRegistry(registry: Registry): void {
|
|
56
|
+
registries.push(registry);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getRegisteredRegistries(): Registry[] {
|
|
60
|
+
return registries;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function scanStringsForPreload(data: unknown, regs: Registry[], preloads: Promise<unknown>[]): void {
|
|
64
|
+
if (data === null || data === undefined) return;
|
|
65
|
+
|
|
66
|
+
if (typeof data === "string") {
|
|
67
|
+
for (const reg of regs) {
|
|
68
|
+
const component = reg.get(data);
|
|
69
|
+
if (component) {
|
|
70
|
+
preloads.push(component.preload());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (Array.isArray(data)) {
|
|
77
|
+
for (const item of data) {
|
|
78
|
+
scanStringsForPreload(item, regs, preloads);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof data === "object") {
|
|
84
|
+
for (const value of Object.values(data)) {
|
|
85
|
+
scanStringsForPreload(value, regs, preloads);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/reset-css.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ResetCSS } from "./components/index.ts";
|