@tanstack/solid-router
Version:
Modern and scalable routing for Solid applications
111 lines (110 loc) • 4.42 kB
JavaScript
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