@solidjs/router 2.0.0-next.13 → 2.0.0-next.15
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/README.md +1 -1
- package/dist/data/events.js +4 -0
- package/dist/data/query.js +76 -33
- package/dist/fs.d.ts +8 -4
- package/dist/fs.js +8 -3
- package/dist/index.js +72 -40
- package/dist/routing.js +1 -16
- package/dist/types.d.ts +1 -4
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -358,7 +358,7 @@ The import only fires when something needs the subtree — hovering a link into
|
|
|
358
358
|
- **Types**: TypeScript never runs the thunk — inference flows through the import's promise type, so `paths.admin.users(2)` typechecks (match filters and search schemas included) before any of the subtree's code exists client-side. The module's `default` or `routes` export is used. Only tables genuinely built at runtime (typed as plain `RouteDefinition[]`) degrade to untyped.
|
|
359
359
|
- **Navigation**: the table load folds into the navigation transition — the old screen holds until the subtree (and its matched components) are ready, exactly like a `lazy()` route component.
|
|
360
360
|
- **Preloading**: hover intent kicks the table load, and when it lands the preload continues into the inner routes' components and `preload` functions — one cascading warm-up from the earliest possible moment.
|
|
361
|
-
- **Server**: SSR resolves matched boundaries during the render (use the streaming entry
|
|
361
|
+
- **Server**: SSR resolves matched boundaries during the render (use the streaming entry point `renderToStream` — awaiting it resolves with the settled HTML — as with any async work), and the single-flight collector resolves them before its data pass.
|
|
362
362
|
|
|
363
363
|
Resolution is cached per thunk and append-only: the tree never changes shape after a subtree lands, it just gets more specific. Keep thunks deterministic — `() => import(...)` — rather than switching tables on runtime state.
|
|
364
364
|
|
package/dist/data/events.js
CHANGED
|
@@ -35,6 +35,10 @@ export function setupNativeEvents({ preload = true, explicitLinks = false, actio
|
|
|
35
35
|
if (a.hasAttribute("download") || (rel && rel.includes("external")))
|
|
36
36
|
return;
|
|
37
37
|
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
|
38
|
+
// Skip non-http(s) schemes (blob:, mailto:, tel:, data:, ...). blob: URLs
|
|
39
|
+
// inherit the page origin, so the origin check below won't reject them. #382
|
|
40
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
41
|
+
return;
|
|
38
42
|
if (url.origin !== window.location.origin ||
|
|
39
43
|
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
40
44
|
return;
|
package/dist/data/query.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { createSignal, getObserver, getOwner, onCleanup, sharedConfig } from "solid-js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// Everything server-function-shaped comes off the CORE entry: detection
|
|
3
|
+
// (isServerFunction/getServerFunctionMetadata, registered-symbol reads) and
|
|
4
|
+
// the late-bound RPC seam (getServerFunctionRPC). The server-functions
|
|
5
|
+
// entry itself — the fetch transport + the seroval codec behind it — is
|
|
6
|
+
// deliberately NOT imported here: query() is in every router app's eager
|
|
7
|
+
// graph, and a static import made every zero-server-function app ship
|
|
8
|
+
// ~9 KB gz of codec it could never invoke. The transport registers itself
|
|
9
|
+
// into the seam when a `'use server'` reference is created (compiled
|
|
10
|
+
// output, module scope), so by the time a server function can reach
|
|
11
|
+
// query() the seam is filled; plain-fetch apps read undefined forever.
|
|
12
|
+
import { getRequestEvent, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, isServer, isServerFunction, REVALIDATE_HEADER } from "@solidjs/web";
|
|
13
|
+
import { useRouter, getIntent, getInPreloadFn } from "../routing.js";
|
|
5
14
|
const LocationHeader = "Location";
|
|
6
15
|
const PRELOAD_TIMEOUT = 5000;
|
|
7
16
|
const CACHE_TIMEOUT = 180000;
|
|
17
|
+
// When this client booted. Flight-registry entries (sharedConfig.has/load)
|
|
18
|
+
// hold values the server computed while rendering THIS page, so their age is
|
|
19
|
+
// anchored here — not at whenever a late query() call happens to consume one.
|
|
20
|
+
const bootTime = Date.now();
|
|
8
21
|
let cacheMap = new Map();
|
|
9
22
|
// cleanup forward/back cache
|
|
10
23
|
if (!isServer) {
|
|
@@ -49,9 +62,16 @@ export function query(fn, name) {
|
|
|
49
62
|
// declaration for dispatch, the client half swaps in the GET transport.
|
|
50
63
|
// An explicit `GET(fn)` already carries the declaration on the metadata
|
|
51
64
|
// channel (`getServerFunctionMetadata(fn)?.method === "GET"`) and passes
|
|
52
|
-
// through; non-server functions are untouched.
|
|
65
|
+
// through; non-server functions are untouched. `GET` comes through the
|
|
66
|
+
// late-bound RPC seam: a reference in the graph guarantees the transport
|
|
67
|
+
// registered it (references are created at module scope, ahead of the
|
|
68
|
+
// module handing one to query()), so the read only misses for hand-built
|
|
69
|
+
// brands that never saw the runtime — which have no transport to declare
|
|
70
|
+
// on either.
|
|
53
71
|
if (isServerFunction(fn) && !getServerFunctionMetadata(fn)?.method) {
|
|
54
|
-
|
|
72
|
+
const rpc = getServerFunctionRPC();
|
|
73
|
+
if (rpc)
|
|
74
|
+
fn = rpc.GET(fn);
|
|
55
75
|
}
|
|
56
76
|
const cachedFn = ((...args) => {
|
|
57
77
|
const cache = getCache();
|
|
@@ -59,7 +79,6 @@ export function query(fn, name) {
|
|
|
59
79
|
const inPreloadFn = getInPreloadFn();
|
|
60
80
|
const owner = getOwner();
|
|
61
81
|
const router = owner ? useRouter() : undefined;
|
|
62
|
-
const route = owner ? useRoute() : undefined;
|
|
63
82
|
const navigate = router && router.navigatorFactory();
|
|
64
83
|
const now = Date.now();
|
|
65
84
|
const key = name + hashKey(args);
|
|
@@ -84,27 +103,17 @@ export function query(fn, name) {
|
|
|
84
103
|
tracking = true;
|
|
85
104
|
onCleanup(() => cached[4].count--);
|
|
86
105
|
}
|
|
87
|
-
// A read under a route section the in-flight navigation is leaving keeps
|
|
88
|
-
// its invalidated value instead of refetching: this render is disposed at
|
|
89
|
-
// commit and never paints, and refiring would put a second request on
|
|
90
|
-
// what single flight promises is one. The entry stays a cache miss, so
|
|
91
|
-
// the fetch happens at its next real use — a later navigation's preload
|
|
92
|
-
// or read — unless the flight payload seeds it first. Retained sections
|
|
93
|
-
// still refetch here, inside the navigation's transition, so the
|
|
94
|
-
// redirect commits with fresh data.
|
|
95
|
-
const leaving = cached && !cached[0] && route && router.leaving?.(route);
|
|
96
106
|
if (cached &&
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: leaving)) {
|
|
107
|
+
cached[0] &&
|
|
108
|
+
(isServer ||
|
|
109
|
+
intent === "native" ||
|
|
110
|
+
cached[4].count ||
|
|
111
|
+
Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
|
103
112
|
if (tracking) {
|
|
104
113
|
cached[4].count++;
|
|
105
114
|
cached[4][0](); // track
|
|
106
115
|
}
|
|
107
|
-
if (
|
|
116
|
+
if (cached[3] === "preload" && intent !== "preload") {
|
|
108
117
|
cached[0] = now;
|
|
109
118
|
}
|
|
110
119
|
let res = cached[1];
|
|
@@ -113,27 +122,54 @@ export function query(fn, name) {
|
|
|
113
122
|
"then" in cached[1]
|
|
114
123
|
? cached[1].then(handleResponse(false), handleResponse(true))
|
|
115
124
|
: handleResponse(false)(cached[1]);
|
|
116
|
-
!
|
|
125
|
+
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
117
126
|
}
|
|
118
127
|
inPreloadFn && "then" in res && res.catch(() => { });
|
|
119
128
|
return res;
|
|
120
129
|
}
|
|
121
130
|
let res;
|
|
131
|
+
let adopted = false;
|
|
132
|
+
// Flight entries stay consumable past hydration `done` on purpose — a
|
|
133
|
+
// boundary inside a deferred claim scope (a lazy route module) can
|
|
134
|
+
// legitimately make its first query() call after global hydration reads
|
|
135
|
+
// as done, and must adopt the promise the server DOM was rendered from
|
|
136
|
+
// (#2964). Adoption slots into the cache's own freshness model as a
|
|
137
|
+
// fetch that happened at boot: outside a navigation (no intent — the
|
|
138
|
+
// hydration/claim case) DOM consistency wins at any age; a back/forward
|
|
139
|
+
// restore honors the payload within the cache's retention window, the
|
|
140
|
+
// same bar the sweep applies to entries the router fetched itself; an
|
|
141
|
+
// active navigation (or its preload) accepts the payload only while a
|
|
142
|
+
// real preload of the same age would still satisfy it — anything older
|
|
143
|
+
// refetches instead of serving a minutes-old value on a fresh navigation.
|
|
122
144
|
if (!isServer && sharedConfig.has && sharedConfig.has(key)) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
const payloadAge = now - bootTime;
|
|
146
|
+
if (!intent || payloadAge < (intent === "native" ? CACHE_TIMEOUT : PRELOAD_TIMEOUT)) {
|
|
147
|
+
adopted = true;
|
|
148
|
+
res = sharedConfig.load(key); // hydrating
|
|
149
|
+
// @ts-ignore at least until we add a delete method to sharedConfig
|
|
150
|
+
delete globalThis._$HY.r[key];
|
|
151
|
+
}
|
|
152
|
+
// A payload too old for this intent stays in the registry: it is
|
|
153
|
+
// inert while the refetched entry below lives, but a later claim
|
|
154
|
+
// read (no intent) may still need it for server-DOM consistency.
|
|
126
155
|
}
|
|
127
|
-
|
|
156
|
+
if (!adopted)
|
|
128
157
|
res = fn(...args);
|
|
158
|
+
// Adopted entries are stamped at boot — when their data was actually
|
|
159
|
+
// fetched — not at consumption. The hydration render burst still dedups
|
|
160
|
+
// through the normal window/count reuse above (it happens within
|
|
161
|
+
// milliseconds of boot), while a navigation that comes along later sees
|
|
162
|
+
// the payload's true age and refetches past PRELOAD_TIMEOUT, exactly as
|
|
163
|
+
// it would for a preload performed when the server fetched the data.
|
|
164
|
+
const stamp = adopted ? bootTime : now;
|
|
129
165
|
if (cached) {
|
|
130
|
-
cached[0] =
|
|
166
|
+
cached[0] = stamp;
|
|
131
167
|
cached[1] = res;
|
|
132
168
|
cached[3] = intent;
|
|
133
169
|
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
134
170
|
}
|
|
135
171
|
else {
|
|
136
|
-
cache.set(key, (cached = [
|
|
172
|
+
cache.set(key, (cached = [stamp, res, , intent, createSignal(stamp, { ownedWrite: true })]));
|
|
137
173
|
cached[4].count = 0;
|
|
138
174
|
}
|
|
139
175
|
if (tracking) {
|
|
@@ -210,10 +246,17 @@ export function query(fn, name) {
|
|
|
210
246
|
v = enveloped;
|
|
211
247
|
else if (v.body) {
|
|
212
248
|
// responses the transport hands over whole (revalidation) carry a
|
|
213
|
-
// codec-encoded body; anything else (a raw user Response) stays
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
249
|
+
// codec-encoded body; anything else (a raw user Response) stays
|
|
250
|
+
// whole. The decoder rides the late-bound RPC seam: only a graph
|
|
251
|
+
// with server functions has codec-encoded responses to decode,
|
|
252
|
+
// and only such a graph has the codec registered — a plain-fetch
|
|
253
|
+
// query's raw Response passes through identically either way.
|
|
254
|
+
const rpc = getServerFunctionRPC();
|
|
255
|
+
if (rpc) {
|
|
256
|
+
const decoded = await rpc.decodeResponse(v);
|
|
257
|
+
if (decoded !== undefined)
|
|
258
|
+
v = decoded;
|
|
259
|
+
}
|
|
217
260
|
}
|
|
218
261
|
}
|
|
219
262
|
if (error)
|
package/dist/fs.d.ts
CHANGED
|
@@ -47,13 +47,15 @@ export interface FileRouteLazyRef<M = Record<string, unknown>> {
|
|
|
47
47
|
}
|
|
48
48
|
/** An eager module ref: its picked exports are imported statically. */
|
|
49
49
|
export interface FileRouteEagerRef<M = Record<string, unknown>> {
|
|
50
|
+
src?: string;
|
|
50
51
|
require(): M;
|
|
51
52
|
}
|
|
52
53
|
/** The shape of a nested route-manifest entry the adapter consumes. */
|
|
53
54
|
export interface FileRouteEntry {
|
|
54
55
|
path: string;
|
|
55
56
|
page?: boolean;
|
|
56
|
-
|
|
57
|
+
/** Code-split by default; an eager ref when delivered with `codeSplitting: false`. */
|
|
58
|
+
$component?: FileRouteLazyRef<any> | FileRouteEagerRef<any> | undefined;
|
|
57
59
|
$$route?: FileRouteEagerRef<any> | undefined;
|
|
58
60
|
children?: readonly FileRouteEntry[] | undefined;
|
|
59
61
|
}
|
|
@@ -85,10 +87,12 @@ export type FileRoutesFrom<T extends readonly FileRouteEntry[]> = {
|
|
|
85
87
|
};
|
|
86
88
|
/**
|
|
87
89
|
* Converts the nested page entries of a file-system route manifest into
|
|
88
|
-
* route definitions: `$component` refs become
|
|
90
|
+
* route definitions: code-split `$component` refs become `lazy` components
|
|
89
91
|
* (their `src` doubles as the `moduleUrl` core resolves assets and islands
|
|
90
|
-
* against),
|
|
91
|
-
*
|
|
92
|
+
* against), eager refs — a manifest delivered with `codeSplitting: false` —
|
|
93
|
+
* pass their component through as-is (no `lazy` wrapper; the module is
|
|
94
|
+
* already statically imported), and each entry's `route` config export is
|
|
95
|
+
* spread into its definition. Pass the result to `createRouter({ routes })`.
|
|
92
96
|
*/
|
|
93
97
|
export declare function fileRoutes<const T extends readonly FileRouteEntry[]>(entries: T): FileRoutesFrom<T>;
|
|
94
98
|
export {};
|
package/dist/fs.js
CHANGED
|
@@ -22,14 +22,19 @@ export function defineFileRoute(path, config) {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Converts the nested page entries of a file-system route manifest into
|
|
25
|
-
* route definitions: `$component` refs become
|
|
25
|
+
* route definitions: code-split `$component` refs become `lazy` components
|
|
26
26
|
* (their `src` doubles as the `moduleUrl` core resolves assets and islands
|
|
27
|
-
* against),
|
|
28
|
-
*
|
|
27
|
+
* against), eager refs — a manifest delivered with `codeSplitting: false` —
|
|
28
|
+
* pass their component through as-is (no `lazy` wrapper; the module is
|
|
29
|
+
* already statically imported), and each entry's `route` config export is
|
|
30
|
+
* spread into its definition. Pass the result to `createRouter({ routes })`.
|
|
29
31
|
*/
|
|
30
32
|
export function fileRoutes(entries) {
|
|
31
33
|
const components = new Map();
|
|
32
34
|
const componentOf = (ref) => {
|
|
35
|
+
if ("require" in ref) {
|
|
36
|
+
return ref.require().default;
|
|
37
|
+
}
|
|
33
38
|
let component = components.get(ref.src);
|
|
34
39
|
if (!component) {
|
|
35
40
|
component = lazy(ref.import, ref.src);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getOwner, runWithOwner, createMemo, createRenderEffect, onCleanup, untrack, createContext, createSignal, useContext, NotReadyError, isPending, flush, createComponent, createRoot, createEffect, sharedConfig, getObserver, $TRACK, action as action$1 } from 'solid-js';
|
|
2
|
-
import { registerElementClaim, delegateEvents, isServer, getRequestEvent, createComponent as createComponent$1, memo, isResponseEnvelope, REVALIDATE_HEADER } from '@solidjs/web';
|
|
3
|
-
import {
|
|
2
|
+
import { registerElementClaim, delegateEvents, isServer, getRequestEvent, hasFlashCookie, clearFlashCookie, createComponent as createComponent$1, memo, isServerFunction, getServerFunctionMetadata, getServerFunctionRPC, isResponseEnvelope, REVALIDATE_HEADER } from '@solidjs/web';
|
|
3
|
+
import { subscribeFlightData, decodeResponsePayload, createServerReference } from '@solidjs/web/server-functions';
|
|
4
4
|
import { decodeFlashCookie } from '@solidjs/web/server-functions/server';
|
|
5
5
|
|
|
6
6
|
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
@@ -336,6 +336,9 @@ function setupNativeEvents({
|
|
|
336
336
|
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
337
337
|
if (a.hasAttribute("download") || rel && rel.includes("external")) return;
|
|
338
338
|
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
|
339
|
+
// Skip non-http(s) schemes (blob:, mailto:, tel:, data:, ...). blob: URLs
|
|
340
|
+
// inherit the page origin, so the origin check below won't reject them. #382
|
|
341
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return;
|
|
339
342
|
if (url.origin !== window.location.origin || basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
|
|
340
343
|
return [a, url];
|
|
341
344
|
}
|
|
@@ -505,6 +508,13 @@ function createPathsProxy(renderPath = p => p, base = "") {
|
|
|
505
508
|
return node(normalizePath(base));
|
|
506
509
|
}
|
|
507
510
|
|
|
511
|
+
// Flash-cookie detection/clearing are cookie utilities on the CORE entry
|
|
512
|
+
// (they live beside the cookie codec) — routing is in every app's eager
|
|
513
|
+
// graph and must never import the server-functions package, whose client
|
|
514
|
+
// half is the fetch transport + the seroval codec. The type below is
|
|
515
|
+
// erased at build; only the server-only action path imports that module's
|
|
516
|
+
// values (behind isServer, tree-shaken from client bundles).
|
|
517
|
+
|
|
508
518
|
const MAX_REDIRECTS = 100;
|
|
509
519
|
|
|
510
520
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
@@ -1165,12 +1175,6 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
1165
1175
|
get pendingTarget() {
|
|
1166
1176
|
return lastTransitionTarget;
|
|
1167
1177
|
},
|
|
1168
|
-
leaving(route) {
|
|
1169
|
-
const t = lastTransitionTarget;
|
|
1170
|
-
if (!t || !route.retained) return false;
|
|
1171
|
-
const pathname = t.value.split(/[?#]/, 1)[0];
|
|
1172
|
-
return !route.retained(options.transformUrl ? options.transformUrl(pathname) : pathname);
|
|
1173
|
-
},
|
|
1174
1178
|
renderPath,
|
|
1175
1179
|
parsePath,
|
|
1176
1180
|
navigatorFactory,
|
|
@@ -1375,13 +1379,6 @@ function createRouteContext(router, parent, outlet, match, matches = () => [matc
|
|
|
1375
1379
|
pattern,
|
|
1376
1380
|
params,
|
|
1377
1381
|
path,
|
|
1378
|
-
retained(target) {
|
|
1379
|
-
const m = match();
|
|
1380
|
-
const p = m.route.matcher(target);
|
|
1381
|
-
// params derive from the matched path, so equal paths mean this
|
|
1382
|
-
// section instance (layouts match prefixes via partial matchers)
|
|
1383
|
-
return !!p && p.path === m.path;
|
|
1384
|
-
},
|
|
1385
1382
|
outlet: () => component ? createComponent(component, {
|
|
1386
1383
|
params,
|
|
1387
1384
|
location,
|
|
@@ -2144,6 +2141,10 @@ const useBeforeLeave = listener => {
|
|
|
2144
2141
|
const LocationHeader = "Location";
|
|
2145
2142
|
const PRELOAD_TIMEOUT = 5000;
|
|
2146
2143
|
const CACHE_TIMEOUT = 180000;
|
|
2144
|
+
// When this client booted. Flight-registry entries (sharedConfig.has/load)
|
|
2145
|
+
// hold values the server computed while rendering THIS page, so their age is
|
|
2146
|
+
// anchored here — not at whenever a late query() call happens to consume one.
|
|
2147
|
+
const bootTime = Date.now();
|
|
2147
2148
|
let cacheMap = new Map();
|
|
2148
2149
|
|
|
2149
2150
|
// cleanup forward/back cache
|
|
@@ -2187,9 +2188,15 @@ function query(fn, name) {
|
|
|
2187
2188
|
// declaration for dispatch, the client half swaps in the GET transport.
|
|
2188
2189
|
// An explicit `GET(fn)` already carries the declaration on the metadata
|
|
2189
2190
|
// channel (`getServerFunctionMetadata(fn)?.method === "GET"`) and passes
|
|
2190
|
-
// through; non-server functions are untouched.
|
|
2191
|
+
// through; non-server functions are untouched. `GET` comes through the
|
|
2192
|
+
// late-bound RPC seam: a reference in the graph guarantees the transport
|
|
2193
|
+
// registered it (references are created at module scope, ahead of the
|
|
2194
|
+
// module handing one to query()), so the read only misses for hand-built
|
|
2195
|
+
// brands that never saw the runtime — which have no transport to declare
|
|
2196
|
+
// on either.
|
|
2191
2197
|
if (isServerFunction(fn) && !getServerFunctionMetadata(fn)?.method) {
|
|
2192
|
-
|
|
2198
|
+
const rpc = getServerFunctionRPC();
|
|
2199
|
+
if (rpc) fn = rpc.GET(fn);
|
|
2193
2200
|
}
|
|
2194
2201
|
const cachedFn = (...args) => {
|
|
2195
2202
|
const cache = getCache();
|
|
@@ -2197,7 +2204,6 @@ function query(fn, name) {
|
|
|
2197
2204
|
const inPreloadFn = getInPreloadFn();
|
|
2198
2205
|
const owner = getOwner();
|
|
2199
2206
|
const router = owner ? useRouter() : undefined;
|
|
2200
|
-
const route = owner ? useRoute() : undefined;
|
|
2201
2207
|
const navigate = router && router.navigatorFactory();
|
|
2202
2208
|
const now = Date.now();
|
|
2203
2209
|
const key = name + hashKey(args);
|
|
@@ -2221,45 +2227,64 @@ function query(fn, name) {
|
|
|
2221
2227
|
tracking = true;
|
|
2222
2228
|
onCleanup(() => cached[4].count--);
|
|
2223
2229
|
}
|
|
2224
|
-
|
|
2225
|
-
// A read under a route section the in-flight navigation is leaving keeps
|
|
2226
|
-
// its invalidated value instead of refetching: this render is disposed at
|
|
2227
|
-
// commit and never paints, and refiring would put a second request on
|
|
2228
|
-
// what single flight promises is one. The entry stays a cache miss, so
|
|
2229
|
-
// the fetch happens at its next real use — a later navigation's preload
|
|
2230
|
-
// or read — unless the flight payload seeds it first. Retained sections
|
|
2231
|
-
// still refetch here, inside the navigation's transition, so the
|
|
2232
|
-
// redirect commits with fresh data.
|
|
2233
|
-
const leaving = cached && !cached[0] && route && router.leaving?.(route);
|
|
2234
|
-
if (cached && (cached[0] ? isServer || intent === "native" || cached[4].count || Date.now() - cached[0] < PRELOAD_TIMEOUT : leaving)) {
|
|
2230
|
+
if (cached && cached[0] && (isServer || intent === "native" || cached[4].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
|
2235
2231
|
if (tracking) {
|
|
2236
2232
|
cached[4].count++;
|
|
2237
2233
|
cached[4][0](); // track
|
|
2238
2234
|
}
|
|
2239
|
-
if (
|
|
2235
|
+
if (cached[3] === "preload" && intent !== "preload") {
|
|
2240
2236
|
cached[0] = now;
|
|
2241
2237
|
}
|
|
2242
2238
|
let res = cached[1];
|
|
2243
2239
|
if (intent !== "preload") {
|
|
2244
2240
|
res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
|
|
2245
|
-
!
|
|
2241
|
+
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
2246
2242
|
}
|
|
2247
2243
|
inPreloadFn && "then" in res && res.catch(() => {});
|
|
2248
2244
|
return res;
|
|
2249
2245
|
}
|
|
2250
2246
|
let res;
|
|
2247
|
+
let adopted = false;
|
|
2248
|
+
// Flight entries stay consumable past hydration `done` on purpose — a
|
|
2249
|
+
// boundary inside a deferred claim scope (a lazy route module) can
|
|
2250
|
+
// legitimately make its first query() call after global hydration reads
|
|
2251
|
+
// as done, and must adopt the promise the server DOM was rendered from
|
|
2252
|
+
// (#2964). Adoption slots into the cache's own freshness model as a
|
|
2253
|
+
// fetch that happened at boot: outside a navigation (no intent — the
|
|
2254
|
+
// hydration/claim case) DOM consistency wins at any age; a back/forward
|
|
2255
|
+
// restore honors the payload within the cache's retention window, the
|
|
2256
|
+
// same bar the sweep applies to entries the router fetched itself; an
|
|
2257
|
+
// active navigation (or its preload) accepts the payload only while a
|
|
2258
|
+
// real preload of the same age would still satisfy it — anything older
|
|
2259
|
+
// refetches instead of serving a minutes-old value on a fresh navigation.
|
|
2251
2260
|
if (!isServer && sharedConfig.has && sharedConfig.has(key)) {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2261
|
+
const payloadAge = now - bootTime;
|
|
2262
|
+
if (!intent || payloadAge < (intent === "native" ? CACHE_TIMEOUT : PRELOAD_TIMEOUT)) {
|
|
2263
|
+
adopted = true;
|
|
2264
|
+
res = sharedConfig.load(key); // hydrating
|
|
2265
|
+
// @ts-ignore at least until we add a delete method to sharedConfig
|
|
2266
|
+
delete globalThis._$HY.r[key];
|
|
2267
|
+
}
|
|
2268
|
+
// A payload too old for this intent stays in the registry: it is
|
|
2269
|
+
// inert while the refetched entry below lives, but a later claim
|
|
2270
|
+
// read (no intent) may still need it for server-DOM consistency.
|
|
2271
|
+
}
|
|
2272
|
+
if (!adopted) res = fn(...args);
|
|
2273
|
+
|
|
2274
|
+
// Adopted entries are stamped at boot — when their data was actually
|
|
2275
|
+
// fetched — not at consumption. The hydration render burst still dedups
|
|
2276
|
+
// through the normal window/count reuse above (it happens within
|
|
2277
|
+
// milliseconds of boot), while a navigation that comes along later sees
|
|
2278
|
+
// the payload's true age and refetches past PRELOAD_TIMEOUT, exactly as
|
|
2279
|
+
// it would for a preload performed when the server fetched the data.
|
|
2280
|
+
const stamp = adopted ? bootTime : now;
|
|
2256
2281
|
if (cached) {
|
|
2257
|
-
cached[0] =
|
|
2282
|
+
cached[0] = stamp;
|
|
2258
2283
|
cached[1] = res;
|
|
2259
2284
|
cached[3] = intent;
|
|
2260
2285
|
!isServer && intent === "navigate" && cached[4][1](cached[0]); // update version
|
|
2261
2286
|
} else {
|
|
2262
|
-
cache.set(key, cached = [
|
|
2287
|
+
cache.set(key, cached = [stamp, res,, intent, createSignal(stamp, {
|
|
2263
2288
|
ownedWrite: true
|
|
2264
2289
|
})]);
|
|
2265
2290
|
cached[4].count = 0;
|
|
@@ -2326,9 +2351,16 @@ function query(fn, name) {
|
|
|
2326
2351
|
}
|
|
2327
2352
|
if (hasEnveloped) v = enveloped;else if (v.body) {
|
|
2328
2353
|
// responses the transport hands over whole (revalidation) carry a
|
|
2329
|
-
// codec-encoded body; anything else (a raw user Response) stays
|
|
2330
|
-
|
|
2331
|
-
|
|
2354
|
+
// codec-encoded body; anything else (a raw user Response) stays
|
|
2355
|
+
// whole. The decoder rides the late-bound RPC seam: only a graph
|
|
2356
|
+
// with server functions has codec-encoded responses to decode,
|
|
2357
|
+
// and only such a graph has the codec registered — a plain-fetch
|
|
2358
|
+
// query's raw Response passes through identically either way.
|
|
2359
|
+
const rpc = getServerFunctionRPC();
|
|
2360
|
+
if (rpc) {
|
|
2361
|
+
const decoded = await rpc.decodeResponse(v);
|
|
2362
|
+
if (decoded !== undefined) v = decoded;
|
|
2363
|
+
}
|
|
2332
2364
|
}
|
|
2333
2365
|
}
|
|
2334
2366
|
if (error) throw v;
|
package/dist/routing.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { flush, runWithOwner } from "solid-js";
|
|
2
2
|
import { createComponent, createContext, createMemo, createSignal, isPending, NotReadyError, untrack, useContext } from "solid-js";
|
|
3
|
-
import {
|
|
3
|
+
import { clearFlashCookie, getRequestEvent, hasFlashCookie, isServer } from "@solidjs/web";
|
|
4
4
|
import { mockBase, comparablePath, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
|
|
5
|
-
import { clearFlashCookie, hasFlashCookie } from "@solidjs/web/server-functions";
|
|
6
5
|
const MAX_REDIRECTS = 100;
|
|
7
6
|
/** Consider this API opaque and internal. It is likely to change in the future. */
|
|
8
7
|
export const RouterContextObj = createContext();
|
|
@@ -621,13 +620,6 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
621
620
|
get pendingTarget() {
|
|
622
621
|
return lastTransitionTarget;
|
|
623
622
|
},
|
|
624
|
-
leaving(route) {
|
|
625
|
-
const t = lastTransitionTarget;
|
|
626
|
-
if (!t || !route.retained)
|
|
627
|
-
return false;
|
|
628
|
-
const pathname = t.value.split(/[?#]/, 1)[0];
|
|
629
|
-
return !route.retained(options.transformUrl ? options.transformUrl(pathname) : pathname);
|
|
630
|
-
},
|
|
631
623
|
renderPath,
|
|
632
624
|
parsePath,
|
|
633
625
|
navigatorFactory,
|
|
@@ -815,13 +807,6 @@ export function createRouteContext(router, parent, outlet, match, matches = () =
|
|
|
815
807
|
pattern,
|
|
816
808
|
params,
|
|
817
809
|
path,
|
|
818
|
-
retained(target) {
|
|
819
|
-
const m = match();
|
|
820
|
-
const p = m.route.matcher(target);
|
|
821
|
-
// params derive from the matched path, so equal paths mean this
|
|
822
|
-
// section instance (layouts match prefixes via partial matchers)
|
|
823
|
-
return !!p && p.path === m.path;
|
|
824
|
-
},
|
|
825
810
|
outlet: () => component
|
|
826
811
|
? createComponent(component, {
|
|
827
812
|
params,
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ declare module "@solidjs/web" {
|
|
|
6
6
|
status?: number;
|
|
7
7
|
statusText?: string;
|
|
8
8
|
headers: Headers;
|
|
9
|
+
committed?: boolean;
|
|
9
10
|
};
|
|
10
11
|
router?: {
|
|
11
12
|
matches?: OutputMatch[];
|
|
@@ -244,8 +245,6 @@ export interface RouteContext {
|
|
|
244
245
|
path: () => string;
|
|
245
246
|
outlet: () => JSX.Element;
|
|
246
247
|
resolvePath(to: string): string | undefined;
|
|
247
|
-
/** Whether this route section still matches `target` (same matched path), i.e. survives a navigation there. Absent on the base route, which always survives. */
|
|
248
|
-
retained?(target: string): boolean;
|
|
249
248
|
}
|
|
250
249
|
export interface RouterUtils {
|
|
251
250
|
renderPath(path: string): string;
|
|
@@ -264,8 +263,6 @@ export interface RouterContext {
|
|
|
264
263
|
isRouting: () => boolean;
|
|
265
264
|
/** The target of the in-flight navigation transition, if any. Not reactive. */
|
|
266
265
|
readonly pendingTarget?: LocationChange;
|
|
267
|
-
/** Whether `route` is a section the in-flight navigation is leaving. False when no navigation is pending. */
|
|
268
|
-
leaving?(route: RouteContext): boolean;
|
|
269
266
|
matches: () => RouteMatch[];
|
|
270
267
|
renderPath(path: string): string;
|
|
271
268
|
parsePath(str: string): string;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "2.0.0-next.
|
|
9
|
+
"version": "2.0.0-next.15",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@solidjs/web": "2.0.0-beta.
|
|
34
|
+
"@solidjs/web": "2.0.0-beta.33",
|
|
35
35
|
"@babel/core": "^7.26.0",
|
|
36
36
|
"@babel/preset-typescript": "^7.26.0",
|
|
37
37
|
"@changesets/cli": "^2.27.10",
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
"@rollup/plugin-terser": "0.4.4",
|
|
41
41
|
"@types/jest": "^29.5.14",
|
|
42
42
|
"@types/node": "^22.10.0",
|
|
43
|
-
"babel-preset-solid": "2.0.0-beta.
|
|
43
|
+
"babel-preset-solid": "2.0.0-beta.33",
|
|
44
44
|
"jsdom": "^25.0.1",
|
|
45
45
|
"prettier": "^3.4.1",
|
|
46
46
|
"rollup": "^4.27.4",
|
|
47
|
-
"solid-js": "2.0.0-beta.
|
|
47
|
+
"solid-js": "2.0.0-beta.33",
|
|
48
48
|
"typescript": "^5.7.2",
|
|
49
49
|
"vite": "^6.0.0",
|
|
50
50
|
"vite-plugin-solid": "3.0.0-next.10",
|
|
51
51
|
"vitest": "^2.1.6"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@solidjs/web": ">=2.0.0-beta.
|
|
55
|
-
"solid-js": ">=2.0.0-beta.
|
|
54
|
+
"@solidjs/web": ">=2.0.0-beta.33 <2.0.0-experimental.0",
|
|
55
|
+
"solid-js": ">=2.0.0-beta.33 <2.0.0-experimental.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rm -rf dist && tsc && rollup -c",
|