@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,59 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import type {
|
|
3
|
+
ClientErrorRenderProps,
|
|
4
|
+
ClientNotFoundRenderProps,
|
|
5
|
+
ClientUnauthenticatedRenderProps,
|
|
6
|
+
ClientUnauthorizedRenderProps,
|
|
7
|
+
RenderProps,
|
|
8
|
+
} from "../outlet/types.ts";
|
|
9
|
+
import type { SearchParams } from "../url/index.ts";
|
|
10
|
+
|
|
11
|
+
export interface LocationSnapshot {
|
|
12
|
+
hash: string;
|
|
13
|
+
params: Record<string, string | string[]>;
|
|
14
|
+
pathname: string;
|
|
15
|
+
search: SearchParams;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EffectsConfig {
|
|
19
|
+
loaderDeps?: (ctx: { search: SearchParams }) => unknown[];
|
|
20
|
+
shouldRefetch?: (ctx: {
|
|
21
|
+
location: { current: LocationSnapshot; next: LocationSnapshot };
|
|
22
|
+
trigger: string;
|
|
23
|
+
}) => boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LoadedRouteModule {
|
|
27
|
+
_type: "layout" | "render" | "root-layout";
|
|
28
|
+
effectsConfig?: EffectsConfig;
|
|
29
|
+
errorRender?: (props: ClientErrorRenderProps) => JSX.Element;
|
|
30
|
+
notFoundRender?: (props: ClientNotFoundRenderProps) => JSX.Element;
|
|
31
|
+
cache?: {
|
|
32
|
+
client?:
|
|
33
|
+
| {
|
|
34
|
+
cacheDeferred?: boolean;
|
|
35
|
+
gcTime?: number;
|
|
36
|
+
prefetchGcTime?: number;
|
|
37
|
+
prefetchStaleTime?: number;
|
|
38
|
+
staleTime?: number;
|
|
39
|
+
}
|
|
40
|
+
| false;
|
|
41
|
+
};
|
|
42
|
+
render: (props: RenderProps) => JSX.Element;
|
|
43
|
+
unauthenticatedRender?: (props: ClientUnauthenticatedRenderProps) => JSX.Element;
|
|
44
|
+
unauthorizedRender?: (props: ClientUnauthorizedRenderProps) => JSX.Element;
|
|
45
|
+
variablePath: string;
|
|
46
|
+
virtualPath: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LoadedRouteModules {
|
|
50
|
+
layouts: LoadedRouteModule[];
|
|
51
|
+
page: LoadedRouteModule;
|
|
52
|
+
params: Record<string, string | string[]>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type LoadRouteModulesFn = (
|
|
56
|
+
pathname: string,
|
|
57
|
+
routeTree: unknown,
|
|
58
|
+
layouts: Record<string, () => Promise<{ default: unknown }>>,
|
|
59
|
+
) => Promise<LoadedRouteModules>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createEffect, type JSX, onCleanup, useContext } from "solid-js";
|
|
2
|
+
import { isServer } from "solid-js/web";
|
|
3
|
+
import { RouterContext } from "../outlet/index.tsx";
|
|
4
|
+
|
|
5
|
+
export interface NavigationProgressProps {
|
|
6
|
+
color?: string;
|
|
7
|
+
height?: number;
|
|
8
|
+
minDisplayTime?: number;
|
|
9
|
+
showPeg?: boolean;
|
|
10
|
+
speed?: number;
|
|
11
|
+
trickleSpeed?: number;
|
|
12
|
+
zIndex?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function clamp(n: number, min: number, max: number): number {
|
|
16
|
+
if (n < min) return min;
|
|
17
|
+
if (n > max) return max;
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* nprogress-style increment curve.
|
|
23
|
+
* Slows dramatically as it approaches 1 to feel realistic.
|
|
24
|
+
*/
|
|
25
|
+
function trickleIncrement(current: number): number {
|
|
26
|
+
if (current < 0.2) return 0.1;
|
|
27
|
+
if (current < 0.5) return 0.04;
|
|
28
|
+
if (current < 0.8) return 0.02;
|
|
29
|
+
if (current < 0.99) return 0.005;
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert 0..1 progress to translate3d percentage (-100%..0%).
|
|
35
|
+
* GPU-composited — no layout/paint, just composite.
|
|
36
|
+
*/
|
|
37
|
+
function toBarPerc(n: number): number {
|
|
38
|
+
return (-1 + n) * 100;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toTranslate(n: number): string {
|
|
42
|
+
return `translate3d(${toBarPerc(n)}%,0,0)`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function NavigationProgress(props: NavigationProgressProps): JSX.Element {
|
|
46
|
+
let wrapperRef: HTMLDivElement | undefined;
|
|
47
|
+
let barRef: HTMLDivElement | undefined;
|
|
48
|
+
let pegRef: HTMLDivElement | undefined;
|
|
49
|
+
|
|
50
|
+
const speed = (): number => props.speed ?? 200;
|
|
51
|
+
const trickleSpeed = (): number => props.trickleSpeed ?? 200;
|
|
52
|
+
const showPeg = (): boolean => props.showPeg !== false;
|
|
53
|
+
|
|
54
|
+
let status: number | null = null;
|
|
55
|
+
let trickleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
56
|
+
let fadeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
57
|
+
let minDisplayTimer: ReturnType<typeof setTimeout> | undefined;
|
|
58
|
+
let startTime = 0;
|
|
59
|
+
let pendingComplete = false;
|
|
60
|
+
|
|
61
|
+
function clearTimers(): void {
|
|
62
|
+
if (trickleTimer !== undefined) {
|
|
63
|
+
clearTimeout(trickleTimer);
|
|
64
|
+
trickleTimer = undefined;
|
|
65
|
+
}
|
|
66
|
+
if (fadeTimer !== undefined) {
|
|
67
|
+
clearTimeout(fadeTimer);
|
|
68
|
+
fadeTimer = undefined;
|
|
69
|
+
}
|
|
70
|
+
if (minDisplayTimer !== undefined) {
|
|
71
|
+
clearTimeout(minDisplayTimer);
|
|
72
|
+
minDisplayTimer = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function set(value: number): void {
|
|
77
|
+
const started = status !== null;
|
|
78
|
+
const n = clamp(value, 0.08, 1);
|
|
79
|
+
status = n === 1 ? null : n;
|
|
80
|
+
|
|
81
|
+
if (!barRef || !wrapperRef) return;
|
|
82
|
+
|
|
83
|
+
/* Force reflow before transition on first render */
|
|
84
|
+
if (!started) void wrapperRef.offsetWidth;
|
|
85
|
+
|
|
86
|
+
barRef.style.transition = `all ${speed()}ms linear`;
|
|
87
|
+
barRef.style.transform = toTranslate(n);
|
|
88
|
+
barRef.setAttribute("aria-valuenow", String(Math.round(n * 100)));
|
|
89
|
+
|
|
90
|
+
if (pegRef) {
|
|
91
|
+
pegRef.style.display = showPeg() ? "block" : "none";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (n === 1) {
|
|
95
|
+
/*
|
|
96
|
+
* nprogress completion sequence:
|
|
97
|
+
* 1. Snap wrapper to opacity 1, no transition
|
|
98
|
+
* 2. Reflow
|
|
99
|
+
* 3. After speed ms: fade wrapper to opacity 0
|
|
100
|
+
* 4. After speed ms: reset bar position
|
|
101
|
+
*/
|
|
102
|
+
wrapperRef.style.transition = "none";
|
|
103
|
+
wrapperRef.style.opacity = "1";
|
|
104
|
+
void wrapperRef.offsetWidth;
|
|
105
|
+
|
|
106
|
+
fadeTimer = setTimeout(() => {
|
|
107
|
+
if (!wrapperRef || !barRef) return;
|
|
108
|
+
wrapperRef.style.transition = `all ${speed()}ms linear`;
|
|
109
|
+
wrapperRef.style.opacity = "0";
|
|
110
|
+
fadeTimer = setTimeout(() => {
|
|
111
|
+
if (!wrapperRef || !barRef) return;
|
|
112
|
+
barRef.style.transition = "none";
|
|
113
|
+
barRef.style.transform = toTranslate(0);
|
|
114
|
+
wrapperRef.style.transition = "none";
|
|
115
|
+
wrapperRef.style.opacity = "1";
|
|
116
|
+
}, speed());
|
|
117
|
+
}, speed());
|
|
118
|
+
} else {
|
|
119
|
+
wrapperRef.style.opacity = "1";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function inc(amount?: number): void {
|
|
124
|
+
const n = status;
|
|
125
|
+
if (n === null) {
|
|
126
|
+
start();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (n > 1) return;
|
|
130
|
+
|
|
131
|
+
const increment = amount ?? trickleIncrement(n);
|
|
132
|
+
set(clamp(n + increment, 0, 0.994));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function start(): void {
|
|
136
|
+
if (status === null) set(0);
|
|
137
|
+
startTime = Date.now();
|
|
138
|
+
pendingComplete = false;
|
|
139
|
+
|
|
140
|
+
function work(): void {
|
|
141
|
+
trickleTimer = setTimeout(() => {
|
|
142
|
+
if (status === null) return;
|
|
143
|
+
inc();
|
|
144
|
+
work();
|
|
145
|
+
}, trickleSpeed());
|
|
146
|
+
}
|
|
147
|
+
work();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function done(): void {
|
|
151
|
+
/* nprogress-style: burst forward with random increment before completing */
|
|
152
|
+
inc(0.3 + 0.5 * Math.random());
|
|
153
|
+
set(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function completeBar(): void {
|
|
157
|
+
clearTimers();
|
|
158
|
+
pendingComplete = false;
|
|
159
|
+
done();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleComplete(): void {
|
|
163
|
+
const minTime = props.minDisplayTime ?? 0;
|
|
164
|
+
const elapsed = Date.now() - startTime;
|
|
165
|
+
|
|
166
|
+
if (elapsed < minTime) {
|
|
167
|
+
pendingComplete = true;
|
|
168
|
+
minDisplayTimer = setTimeout(() => {
|
|
169
|
+
if (pendingComplete) {
|
|
170
|
+
completeBar();
|
|
171
|
+
}
|
|
172
|
+
}, minTime - elapsed);
|
|
173
|
+
} else {
|
|
174
|
+
completeBar();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* SSR: effects are no-ops. Client: RouterContext available via full-document hydration. */
|
|
179
|
+
const ctx = useContext(RouterContext);
|
|
180
|
+
|
|
181
|
+
if (!isServer && ctx) {
|
|
182
|
+
createEffect(() => {
|
|
183
|
+
const phase = ctx.navigationPhase();
|
|
184
|
+
|
|
185
|
+
if (phase === "loading") {
|
|
186
|
+
clearTimers();
|
|
187
|
+
start();
|
|
188
|
+
} else if (phase === "idle" && status !== null) {
|
|
189
|
+
handleComplete();
|
|
190
|
+
} else if (phase === "transitioning" && status !== null) {
|
|
191
|
+
handleComplete();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
onCleanup(clearTimers);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const barColor = (): string => props.color ?? "currentColor";
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div
|
|
202
|
+
ref={wrapperRef}
|
|
203
|
+
style={{
|
|
204
|
+
height: `${props.height ?? 2}px`,
|
|
205
|
+
left: "0",
|
|
206
|
+
"pointer-events": "none",
|
|
207
|
+
position: "fixed",
|
|
208
|
+
top: "0",
|
|
209
|
+
width: "100%",
|
|
210
|
+
"z-index": props.zIndex ?? 9999,
|
|
211
|
+
}}
|
|
212
|
+
>
|
|
213
|
+
<div
|
|
214
|
+
aria-label="Page load progress"
|
|
215
|
+
aria-valuemax="100"
|
|
216
|
+
aria-valuemin="0"
|
|
217
|
+
aria-valuenow={0}
|
|
218
|
+
ref={barRef}
|
|
219
|
+
role="progressbar"
|
|
220
|
+
style={{
|
|
221
|
+
"background-color": barColor(),
|
|
222
|
+
height: "100%",
|
|
223
|
+
left: "0",
|
|
224
|
+
position: "absolute",
|
|
225
|
+
top: "0",
|
|
226
|
+
transform: toTranslate(0),
|
|
227
|
+
transition: "all 0 linear",
|
|
228
|
+
width: "100%",
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<div
|
|
232
|
+
ref={pegRef}
|
|
233
|
+
style={{
|
|
234
|
+
"box-shadow": `0 0 10px ${barColor()}, 0 0 5px ${barColor()}`,
|
|
235
|
+
display: showPeg() ? "block" : "none",
|
|
236
|
+
height: "100%",
|
|
237
|
+
opacity: "1",
|
|
238
|
+
position: "absolute",
|
|
239
|
+
right: "0",
|
|
240
|
+
transform: "rotate(3deg) translate(0px, -4px)",
|
|
241
|
+
width: "100px",
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
) as JSX.Element;
|
|
247
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NavigationProgress, type NavigationProgressProps } from "./navigation-progress/index.tsx";
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { NotFoundError, RedirectResponse, UnauthenticatedError, UnauthorizedError } from "../errors/index.ts";
|
|
2
|
+
import { warn } from "../logger.ts";
|
|
3
|
+
import type { HeadConfig } from "../route-builder/types.ts";
|
|
4
|
+
import type { DeferredResolver } from "../state-parser/index.ts";
|
|
5
|
+
import { hydrateLoaderData } from "../state-parser/index.ts";
|
|
6
|
+
|
|
7
|
+
const MAX_LINE_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
const MAX_MESSAGES = 10_000;
|
|
9
|
+
|
|
10
|
+
export interface NDJSONFetchOptions {
|
|
11
|
+
matchIds?: string[];
|
|
12
|
+
prefetch?: boolean;
|
|
13
|
+
queryClient?: unknown;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NDJSONFetchResult {
|
|
19
|
+
matches: FetchedMatch[];
|
|
20
|
+
perRouteHeads: PerRouteHead[];
|
|
21
|
+
success: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FetchedMatch {
|
|
25
|
+
error?: Error;
|
|
26
|
+
hasDeferredMarkers?: boolean;
|
|
27
|
+
loaderData: unknown;
|
|
28
|
+
matchId: string;
|
|
29
|
+
preloaderContext?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PerRouteHead {
|
|
33
|
+
head: HeadConfig;
|
|
34
|
+
matchId: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface NDJSONMessage {
|
|
38
|
+
a?: unknown[];
|
|
39
|
+
d?: unknown;
|
|
40
|
+
e?: { message: string; name?: string };
|
|
41
|
+
k?: string;
|
|
42
|
+
l?: string;
|
|
43
|
+
m?: string;
|
|
44
|
+
p?: Record<string, unknown>;
|
|
45
|
+
/* Redirect fields (t:"x") */
|
|
46
|
+
r?: boolean;
|
|
47
|
+
s?: number;
|
|
48
|
+
t: string;
|
|
49
|
+
u?: string;
|
|
50
|
+
xl?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* Reconstruct the correct error class from NDJSON error name.
|
|
55
|
+
* Server sends `{ message, name }` — client needs `instanceof` checks
|
|
56
|
+
* for NotFoundError, UnauthenticatedError, UnauthorizedError.
|
|
57
|
+
*/
|
|
58
|
+
function reconstructError(e?: { message: string; name?: string }): Error {
|
|
59
|
+
const message = e?.message ?? "Unknown error";
|
|
60
|
+
switch (e?.name) {
|
|
61
|
+
case "NotFoundError":
|
|
62
|
+
return new NotFoundError(message);
|
|
63
|
+
case "UnauthenticatedError":
|
|
64
|
+
return new UnauthenticatedError(message);
|
|
65
|
+
case "UnauthorizedError":
|
|
66
|
+
return new UnauthorizedError(message);
|
|
67
|
+
default: {
|
|
68
|
+
const err = new Error(message);
|
|
69
|
+
if (e?.name) err.name = e.name;
|
|
70
|
+
return err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SERVER_LOG_PREFIX = "%c[server]";
|
|
76
|
+
const SERVER_LOG_STYLE = "color:#60a5fa;font-weight:bold";
|
|
77
|
+
const SOURCE_STYLE = "color:#9ca3af";
|
|
78
|
+
|
|
79
|
+
function forwardServerLog(msg: NDJSONMessage): void {
|
|
80
|
+
const args = Array.isArray(msg.a) ? msg.a : [];
|
|
81
|
+
const prefix = msg.s
|
|
82
|
+
? [SERVER_LOG_PREFIX, SERVER_LOG_STYLE, `%c ${msg.s}`, SOURCE_STYLE, ...args]
|
|
83
|
+
: [SERVER_LOG_PREFIX, SERVER_LOG_STYLE, ...args];
|
|
84
|
+
switch (msg.l) {
|
|
85
|
+
case "warn":
|
|
86
|
+
console.warn(...prefix);
|
|
87
|
+
break;
|
|
88
|
+
case "error":
|
|
89
|
+
console.error(...prefix);
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
console.log(...prefix);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function fetchNDJSON(options: NDJSONFetchOptions): Promise<NDJSONFetchResult> {
|
|
98
|
+
const headers: Record<string, string> = { "x-d": "1" };
|
|
99
|
+
|
|
100
|
+
if (options.matchIds) {
|
|
101
|
+
headers["x-m"] = options.matchIds.join(",");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (options.prefetch) {
|
|
105
|
+
headers["x-p"] = "1";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const response = await fetch(options.url, {
|
|
109
|
+
headers,
|
|
110
|
+
signal: options.signal,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const contentType = response.headers?.get("content-type") ?? "";
|
|
114
|
+
const isNdjson = contentType.includes("ndjson");
|
|
115
|
+
/* Loader errors often travel as HTTP 500 + application/x-ndjson. Dropping the
|
|
116
|
+
* body makes retry() rebuild matches from a cache that never stored the
|
|
117
|
+
* pipeline error, so the error UI cannot recover. */
|
|
118
|
+
if (!response.ok && !isNdjson) {
|
|
119
|
+
return { matches: [], perRouteHeads: [], success: false };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const matches: FetchedMatch[] = [];
|
|
123
|
+
const perRouteHeads: PerRouteHead[] = [];
|
|
124
|
+
const deferredResolvers = new Map<string, DeferredResolver>();
|
|
125
|
+
|
|
126
|
+
let loadersReadyResolve: () => void = () => {};
|
|
127
|
+
let loadersReadyResolved = false;
|
|
128
|
+
const loadersReady = new Promise<void>((resolve) => {
|
|
129
|
+
loadersReadyResolve = resolve;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function resolveLoadersReady() {
|
|
133
|
+
if (!loadersReadyResolved) {
|
|
134
|
+
loadersReadyResolved = true;
|
|
135
|
+
loadersReadyResolve();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!response.body) {
|
|
140
|
+
return { matches: [], perRouteHeads: [], success: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const reader = response.body.getReader();
|
|
144
|
+
const decoder = new TextDecoder();
|
|
145
|
+
let buffer = "";
|
|
146
|
+
|
|
147
|
+
function processLine(raw: string) {
|
|
148
|
+
const trimmed = raw.trim();
|
|
149
|
+
if (!trimmed) return;
|
|
150
|
+
|
|
151
|
+
let msg: NDJSONMessage;
|
|
152
|
+
try {
|
|
153
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
154
|
+
if (typeof parsed !== "object" || parsed === null || typeof (parsed as NDJSONMessage).t !== "string") {
|
|
155
|
+
warn("ndjson", "invalid message, skipping", trimmed);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
msg = parsed as NDJSONMessage;
|
|
159
|
+
} catch (e: unknown) {
|
|
160
|
+
warn("ndjson", "invalid message, skipping", e);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
switch (msg.t) {
|
|
165
|
+
case "g": {
|
|
166
|
+
forwardServerLog(msg);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "l": {
|
|
171
|
+
if (options.prefetch) {
|
|
172
|
+
matches.push({
|
|
173
|
+
hasDeferredMarkers: containsDeferredMarkers(msg.d) || undefined,
|
|
174
|
+
loaderData: msg.d,
|
|
175
|
+
matchId: msg.m ?? "",
|
|
176
|
+
preloaderContext: msg.p,
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
const sizeBefore = deferredResolvers.size;
|
|
180
|
+
const hydrated = hydrateLoaderData(msg.m ?? "", msg.d, deferredResolvers);
|
|
181
|
+
matches.push({
|
|
182
|
+
hasDeferredMarkers: deferredResolvers.size > sizeBefore || undefined,
|
|
183
|
+
loaderData: hydrated,
|
|
184
|
+
matchId: msg.m ?? "",
|
|
185
|
+
preloaderContext: msg.p,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case "c": {
|
|
192
|
+
const resolverKey = `${msg.m}:${msg.k}`;
|
|
193
|
+
const resolver = deferredResolvers.get(resolverKey);
|
|
194
|
+
if (resolver) {
|
|
195
|
+
resolver.resolve(msg.d);
|
|
196
|
+
deferredResolvers.delete(resolverKey);
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case "e": {
|
|
202
|
+
const err = reconstructError(msg.e);
|
|
203
|
+
if (msg.k) {
|
|
204
|
+
const resolverKey = `${msg.m}:${msg.k}`;
|
|
205
|
+
const resolver = deferredResolvers.get(resolverKey);
|
|
206
|
+
if (resolver) {
|
|
207
|
+
resolver.reject(err);
|
|
208
|
+
deferredResolvers.delete(resolverKey);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
matches.push({
|
|
212
|
+
error: err,
|
|
213
|
+
loaderData: undefined,
|
|
214
|
+
matchId: msg.m ?? "",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "h": {
|
|
221
|
+
if (typeof msg.d === "object" && msg.d !== null) {
|
|
222
|
+
perRouteHeads.push({
|
|
223
|
+
head: msg.d as HeadConfig,
|
|
224
|
+
matchId: msg.m ?? "",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case "x": {
|
|
231
|
+
reader.cancel();
|
|
232
|
+
rejectPendingResolvers();
|
|
233
|
+
if (msg.xl) {
|
|
234
|
+
throw new RedirectResponse({
|
|
235
|
+
href: msg.u ?? "/",
|
|
236
|
+
replace: msg.r,
|
|
237
|
+
status: msg.s,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
throw new RedirectResponse({
|
|
241
|
+
replace: msg.r,
|
|
242
|
+
status: msg.s,
|
|
243
|
+
to: msg.u ?? "/",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case "q": {
|
|
248
|
+
if (options.queryClient && Array.isArray(msg.d)) {
|
|
249
|
+
import("../query-client")
|
|
250
|
+
.then(({ hydrateQueryCache }) => {
|
|
251
|
+
hydrateQueryCache(
|
|
252
|
+
options.queryClient as Parameters<typeof hydrateQueryCache>[0],
|
|
253
|
+
msg.d as Parameters<typeof hydrateQueryCache>[1],
|
|
254
|
+
);
|
|
255
|
+
})
|
|
256
|
+
.catch(() => {});
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case "r": {
|
|
262
|
+
resolveLoadersReady();
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
case "d": {
|
|
267
|
+
resolveLoadersReady();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
default:
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function rejectPendingResolvers() {
|
|
277
|
+
for (const [key, resolver] of deferredResolvers) {
|
|
278
|
+
resolver.reject(new Error("Navigation cancelled"));
|
|
279
|
+
deferredResolvers.delete(key);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const readPromise = (async () => {
|
|
284
|
+
let messageCount = 0;
|
|
285
|
+
try {
|
|
286
|
+
while (true) {
|
|
287
|
+
if (options.signal?.aborted) {
|
|
288
|
+
reader.cancel();
|
|
289
|
+
rejectPendingResolvers();
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const { done, value } = await reader.read();
|
|
294
|
+
if (done) {
|
|
295
|
+
/* Flush any buffered partial UTF-8 bytes from the decoder */
|
|
296
|
+
buffer += decoder.decode();
|
|
297
|
+
if (buffer.trim()) {
|
|
298
|
+
processLine(buffer);
|
|
299
|
+
}
|
|
300
|
+
resolveLoadersReady();
|
|
301
|
+
rejectPendingResolvers();
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
buffer += decoder.decode(value, { stream: true });
|
|
306
|
+
if (buffer.length > MAX_LINE_BYTES) {
|
|
307
|
+
warn("ndjson", "line buffer exceeded maximum size, aborting stream");
|
|
308
|
+
reader.cancel();
|
|
309
|
+
rejectPendingResolvers();
|
|
310
|
+
resolveLoadersReady();
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const lines = buffer.split("\n");
|
|
314
|
+
buffer = lines.pop() ?? "";
|
|
315
|
+
|
|
316
|
+
for (const ln of lines) {
|
|
317
|
+
messageCount++;
|
|
318
|
+
if (messageCount > MAX_MESSAGES) {
|
|
319
|
+
warn("ndjson", "max message count exceeded, aborting stream");
|
|
320
|
+
reader.cancel();
|
|
321
|
+
rejectPendingResolvers();
|
|
322
|
+
resolveLoadersReady();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
processLine(ln);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (e) {
|
|
329
|
+
rejectPendingResolvers();
|
|
330
|
+
/* Redirects always propagate — caller must handle redirect even mid-stream */
|
|
331
|
+
if (e instanceof RedirectResponse) {
|
|
332
|
+
throw e;
|
|
333
|
+
}
|
|
334
|
+
if (loadersReadyResolved) {
|
|
335
|
+
warn("ndjson", "post-loaders stream error", e);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
/* Error before loaders ready — propagate so caller sees the failure */
|
|
339
|
+
throw e;
|
|
340
|
+
} finally {
|
|
341
|
+
if (typeof reader.releaseLock === "function") reader.releaseLock();
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
344
|
+
|
|
345
|
+
/*
|
|
346
|
+
* Track redirects that arrive after loadersReady resolves.
|
|
347
|
+
* Non-redirect errors are swallowed — they happen after loaders
|
|
348
|
+
* are already delivered, so the caller doesn't need them.
|
|
349
|
+
*/
|
|
350
|
+
let pendingRedirect: RedirectResponse | undefined;
|
|
351
|
+
readPromise.catch((e) => {
|
|
352
|
+
if (e instanceof RedirectResponse) pendingRedirect = e;
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
/* Wait for loadersReady, but if readPromise rejects first, propagate */
|
|
356
|
+
await Promise.race([loadersReady, readPromise]);
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* Flush microtask queue so the readPromise catch handler has a chance
|
|
360
|
+
* to set pendingRedirect if the redirect arrived in the same chunk.
|
|
361
|
+
*/
|
|
362
|
+
await new Promise<void>((r) => queueMicrotask(r));
|
|
363
|
+
if (pendingRedirect) throw pendingRedirect;
|
|
364
|
+
|
|
365
|
+
return { matches, perRouteHeads, success: true };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const MAX_DEFERRED_DEPTH = 50;
|
|
369
|
+
|
|
370
|
+
function containsDeferredMarkers(data: unknown, depth = 0): boolean {
|
|
371
|
+
if (depth > MAX_DEFERRED_DEPTH) return false;
|
|
372
|
+
if (data === null || data === undefined || typeof data !== "object") return false;
|
|
373
|
+
if ((data as Record<string, unknown>).__deferred === true) return true;
|
|
374
|
+
if (Array.isArray(data)) return data.some((v) => containsDeferredMarkers(v, depth + 1));
|
|
375
|
+
return Object.values(data as Record<string, unknown>).some((v) => containsDeferredMarkers(v, depth + 1));
|
|
376
|
+
}
|