UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

111 lines (110 loc) 4.42 kB
import * as Solid from "solid-js"; //#region src/registryTransfer.ts /** * Match-state transfer over Solid's hydration registry — the bare pairing's * native SSR channel (no `__TSR_SSR__` script injection, no router-owned * stream protocol). Entries are content-addressed (`tsr:<matchId>`; match * ids are deterministic route-id + interpolated params, so both sides * derive the same key from the same URL), the identical mechanism * solid-query v6 ships query payloads through (`sq:<queryHash>`). * * Server half: while the render's serialization context is live, the * provider writes each settled match's transferable state. Client half: the * hydration-claiming boot — matching is synchronous, so the provider primes * match state from the registry and commits before rendering, without * running loaders or resolving route chunks up front. Route components * resolve at the read point under the boundaries the server actually * rendered (Solid `lazy` semantics), and staleness rules own any * post-hydration refetching. * * Both halves no-op under TanStack Start: `router.serverSsr` marks the * Start contract (`attachRouterServerSsrUtils` / `dehydrate` / `hydrate`), * which owns transfer there — and a Start-hydrated router reaches the * client boot with matches already committed, which skips it. */ var MATCH_KEY_PREFIX = "tsr:"; function serializeMatchTransfer(router) { if (router.serverSsr) return; const ctx = Solid.sharedConfig.context; if (!ctx || typeof ctx.serialize !== "function" || ctx.noHydrate) return; for (const match of router.stores.matches.get()) { if (match.status === "pending") continue; const entry = { status: match.status, updatedAt: match.updatedAt }; if (match.loaderData !== void 0) entry.loaderData = match.loaderData; if (match.error !== void 0) entry.error = match.error; if (match._notFound) entry.notFound = true; const beforeLoadContext = match.__beforeLoadContext; if (beforeLoadContext !== void 0) entry.beforeLoadContext = beforeLoadContext; if (match.ssr !== void 0) entry.ssr = match.ssr; ctx.serialize(MATCH_KEY_PREFIX + match.id, entry); } } /** * The hydration-claiming boot. Returns true when every synchronously * matched route found its registry entry and the matches were committed; * false falls back to the caller's existing behavior (no entries — a * non-registry server, `noHydrate`, or a pending match the server skipped). * * Reads the raw registry rather than sharedConfig's accessors: entries * arrive as inline scripts that execute at document parse, so they are * complete before any client code runs — and the boot must commit BEFORE * the hydration render (store writes inside it are owned-scope writes). */ function primeRouterFromRegistry(router) { if (router.stores.matches.get().length > 0) return false; const registry = globalThis._$HY?.r; if (!registry) return false; let hasMatchEntries = false; for (const key in registry) if (key.startsWith("tsr:")) { hasMatchEntries = true; break; } if (!hasMatchEntries) return false; const matches = router.matchRoutes(router.latestLocation); if (matches.length === 0) return false; const primed = []; for (const match of matches) { const key = MATCH_KEY_PREFIX + match.id; if (!(key in registry)) return false; const raw = registry[key]; delete registry[key]; const entry = raw != null && typeof raw === "object" && "s" in raw && raw.s === 1 ? raw.v : raw; if (!entry || typeof entry.status !== "string") return false; primed.push(applyTransferredMatch(match, entry)); } router._committed = primed; router.batch(() => { router.stores.setMatches(primed); router.stores.status.set("idle"); router.stores.resolvedLocation.set(router.stores.location.get()); }); return true; } function applyTransferredMatch(match, entry) { const next = { ...match, status: entry.status, updatedAt: entry.updatedAt, error: entry.error, invalid: false, isFetching: false, preload: false, _notFound: entry.notFound }; if ("loaderData" in entry) next.loaderData = entry.loaderData; if ("beforeLoadContext" in entry) { next.__beforeLoadContext = entry.beforeLoadContext; next.context = { ...next.context, ...entry.beforeLoadContext }; } if ("ssr" in entry) next.ssr = entry.ssr; return next; } //#endregion export { primeRouterFromRegistry, serializeMatchTransfer }; //# sourceMappingURL=registryTransfer.js.map